@z-api/call 1.0.0-staging.0

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.
@@ -0,0 +1,915 @@
1
+ var ZAPICall=(function(x){"use strict";class O{constructor(){this.listeners=new Map}on(t,e){return this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e),this}off(t,e){var a;return(a=this.listeners.get(t))==null||a.delete(e),this}emit(t,...e){const a=this.listeners.get(t);if(a)for(const i of a)try{i(...e)}catch(s){console.error(`[ZAPICall] Event listener error (${String(t)}):`,s)}}removeAllListeners(){this.listeners.clear()}}const $=1e3,F=3e4,U=25e3;class j{constructor(t,e,a){this.ws=null,this.reconnectMs=$,this.reconnectTimer=null,this.pingTimer=null,this.destroyed=!1,this.onJson=()=>{},this.onBinary=()=>{},this.onConnectionChange=()=>{},this.baseUrl=t,this.instanceId=e,this.getToken=a}buildUrl(t){const e=new URL(this.baseUrl);return e.protocol=e.protocol==="https:"?"wss:":"ws:",e.pathname="/ws/call",e.searchParams.set("instance",this.instanceId),e.searchParams.set("token",t),e.toString()}async connect(){if(this.destroyed)return;this.cleanup();let t;try{t=await this.getToken()}catch(i){console.warn("[ZAPICall] Failed to get token:",i),this.scheduleReconnect();return}if(this.destroyed)return;const e=this.buildUrl(t);console.log("[ZAPICall] Connecting to",e);const a=new WebSocket(e);a.binaryType="arraybuffer",this.ws=a,a.onopen=()=>{console.log("[ZAPICall] WebSocket connected"),this.reconnectMs=$,this.onConnectionChange(!0),this.startPing()},a.onmessage=i=>{if(typeof i.data=="string")try{const s=JSON.parse(i.data);console.log("[ZAPICall] <<",s.type,s),this.onJson(s)}catch{}else i.data instanceof ArrayBuffer&&this.onBinary(i.data)},a.onclose=i=>{if(console.log("[ZAPICall] WebSocket closed:",i.code,i.reason),this.stopPing(),this.onConnectionChange(!1),i.code===4001){console.warn("[ZAPICall] Invalid credentials (4001), not reconnecting");return}this.scheduleReconnect()},a.onerror=i=>{console.warn("[ZAPICall] WebSocket error:",i)}}send(t){var e;((e=this.ws)==null?void 0:e.readyState)===WebSocket.OPEN&&this.ws.send(t)}sendJson(t){this.send(JSON.stringify(t))}isConnected(){var t;return((t=this.ws)==null?void 0:t.readyState)===WebSocket.OPEN}destroy(){this.destroyed=!0,this.cleanup(),this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null)}cleanup(){this.stopPing(),this.ws&&(this.ws.onopen=null,this.ws.onmessage=null,this.ws.onclose=null,this.ws.onerror=null,(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING)&&this.ws.close(),this.ws=null)}scheduleReconnect(){this.destroyed||(this.reconnectTimer=setTimeout(()=>{this.connect()},this.reconnectMs),this.reconnectMs=Math.min(this.reconnectMs*2,F))}startPing(){this.pingTimer=setInterval(()=>{this.sendJson({type:"ping"})},U)}stopPing(){this.pingTimer&&(clearInterval(this.pingTimer),this.pingTimer=null)}}function q(c){const t=new Float32Array(c.length);for(let e=0;e<c.length;e++)t[e]=c[e]/32768;return t}function _(c){const t=new Int16Array(c.length);for(let e=0;e<c.length;e++){let a=c[e];a>1?a=1:a<-1&&(a=-1),t[e]=Math.round(a*32767)}return t}function W(c){if(c.length===0)return 0;let t=0;for(let e=0;e<c.length;e++)t+=c[e]*c[e];return Math.sqrt(t/c.length)}const N=.02,K=.15;class z{constructor(t=48e3){this.playbackCtx=null,this.micCtx=null,this.micStream=null,this.micProcessor=null,this.micSource=null,this.nextPlayTime=0,this.destroyed=!1,this.onMicData=()=>{},this.onAudioLevel=()=>{},this.playLogCount=0,this.sampleRate=t}playPcm(t){if(this.destroyed||t.byteLength===0)return;if(this.playbackCtx||(this.playbackCtx=new AudioContext({sampleRate:this.sampleRate}),this.nextPlayTime=this.playbackCtx.currentTime),this.playbackCtx.state==="suspended"&&this.playbackCtx.resume(),this.playLogCount++,this.playLogCount<=5||this.playLogCount%250===0){const o=new Int16Array(t);let l=0;for(let p=0;p<Math.min(o.length,100);p++){const u=Math.abs(o[p]);u>l&&(l=u)}console.log(`[AudioEngine] playPcm #${this.playLogCount} bytes=${t.byteLength} ctxState=${this.playbackCtx.state} sampleRate=${this.sampleRate} maxSample=${l} nextPlayT=${this.nextPlayTime.toFixed(3)} ctxTime=${this.playbackCtx.currentTime.toFixed(3)}`)}const e=new Int16Array(t),a=q(e),i=W(a);this.onAudioLevel(i);const s=this.playbackCtx.createBuffer(1,a.length,this.sampleRate);s.getChannelData(0).set(a);const n=this.playbackCtx.createBufferSource();n.buffer=s,n.connect(this.playbackCtx.destination);const r=this.playbackCtx.currentTime;this.nextPlayTime<r?this.nextPlayTime=r+N:this.nextPlayTime-r>K&&(this.nextPlayTime=r+N),n.start(this.nextPlayTime),this.nextPlayTime+=s.duration}async startMic(){this.destroyed||this.micStream||(this.micStream=await navigator.mediaDevices.getUserMedia({audio:{sampleRate:this.sampleRate,channelCount:1,echoCancellation:!0,noiseSuppression:!0,autoGainControl:!0}}),this.micCtx=new AudioContext({sampleRate:this.sampleRate}),this.micSource=this.micCtx.createMediaStreamSource(this.micStream),this.micProcessor=this.micCtx.createScriptProcessor(4096,1,1),this.micProcessor.onaudioprocess=t=>{if(this.destroyed)return;const e=t.inputBuffer.getChannelData(0),a=_(e);this.onMicData(a.buffer)},this.micSource.connect(this.micProcessor),this.micProcessor.connect(this.micCtx.destination))}stopMic(){if(this.micProcessor&&(this.micProcessor.disconnect(),this.micProcessor=null),this.micSource&&(this.micSource.disconnect(),this.micSource=null),this.micStream){for(const t of this.micStream.getTracks())t.stop();this.micStream=null}this.micCtx&&(this.micCtx.close().catch(()=>{}),this.micCtx=null)}resume(){this.playbackCtx||(this.playbackCtx=new AudioContext({sampleRate:this.sampleRate}),this.nextPlayTime=this.playbackCtx.currentTime),this.playbackCtx.state==="suspended"&&this.playbackCtx.resume()}destroy(){this.destroyed=!0,this.stopMic(),this.playbackCtx&&(this.playbackCtx.close().catch(()=>{}),this.playbackCtx=null)}}const Z=100,G="avc1.42001E",J=5;class Y{constructor(){this.canvas=null,this.ctx=null,this.localVideo=null,this.localStream=null,this.captureCanvas=null,this.captureCtx=null,this.tempCanvas=null,this.tempCtx=null,this.captureInterval=null,this.h264Decoder=null,this.h264ErrorCount=0,this.orientationMap=new Map,this.onCameraFrame=null,this.onEncodedCameraFrame=null,this.h264Encoder=null,this.encoderKeyFrameInterval=3e3,this.lastKeyFrameTs=0,this.h264NeedKeyFrame=!1,this.h264GotKeyFrame=!1,this.h264FrameIndex=0}setRemoteCanvas(t){this.canvas=t,this.ctx=t.getContext("2d")}renderFrame(t){if(!this.canvas||!this.ctx)return;const{width:e,height:a,data:i,orientation:s,format:n,isKeyFrame:r,timestamp:o}=t;if(!(e<=0||a<=0)){if(n===Z){this.decodeH264Frame(i,e,a,s,r,o);return}this.renderNV12Frame(i,e,a,s)}}decodeH264Frame(t,e,a,i,s,n){if(t.byteLength===0)return;if(!this.h264GotKeyFrame){if(!s)return;this.h264GotKeyFrame=!0}if(!this.h264Decoder||this.h264Decoder.state==="closed"){if(this.h264ErrorCount>=J||(this.h264Decoder=this.createH264Decoder(),!this.h264Decoder)||(this.h264GotKeyFrame=!1,!s))return;this.h264GotKeyFrame=!0}this.h264FrameIndex++;const r=this.h264FrameIndex*66666;this.orientationMap.set(r,i);try{this.h264Decoder.decode(new EncodedVideoChunk({type:s?"key":"delta",timestamp:r,data:t}))}catch{this.h264ErrorCount++,this.h264GotKeyFrame=!1}}createH264Decoder(){if(typeof globalThis.VideoDecoder!="function")return null;try{const t=new VideoDecoder({output:e=>{try{if(!this.canvas||!this.ctx){e.close();return}const a=this.orientationMap.get(e.timestamp)??1;this.orientationMap.delete(e.timestamp);const i=e.codedWidth,s=e.codedHeight,n=a===2||a===4,r=n?s:i,o=n?i:s;(this.canvas.width!==r||this.canvas.height!==o)&&(this.canvas.width=r,this.canvas.height=o),this.ctx.save(),a===2?(this.ctx.translate(r,0),this.ctx.rotate(Math.PI/2)):a===3?(this.ctx.translate(r,o),this.ctx.rotate(Math.PI)):a===4&&(this.ctx.translate(0,o),this.ctx.rotate(-Math.PI/2)),this.ctx.drawImage(e,0,0),this.ctx.restore()}finally{e.close()}},error:()=>{this.h264ErrorCount++,this.orientationMap.clear()}});return t.configure({codec:G,optimizeForLatency:!0}),this.h264ErrorCount=0,t}catch{return null}}renderNV12Frame(t,e,a,i){if(!this.canvas||!this.ctx)return;const s=i===2||i===4,n=s?a:e,r=s?e:a;if((this.canvas.width!==n||this.canvas.height!==r)&&(this.canvas.width=n,this.canvas.height=r),typeof globalThis.VideoFrame=="function")try{const u=new globalThis.VideoFrame(new Uint8Array(t),{format:"NV12",codedWidth:e,codedHeight:a,timestamp:0});this.ctx.save(),i===2?(this.ctx.translate(n,0),this.ctx.rotate(Math.PI/2)):i===3?(this.ctx.translate(n,r),this.ctx.rotate(Math.PI)):i===4&&(this.ctx.translate(0,r),this.ctx.rotate(-Math.PI/2)),this.ctx.drawImage(u,0,0),this.ctx.restore(),u.close();return}catch{}const o=new Uint8Array(t),l=this.nv12ToRgba(o,e,a),p=new ImageData(new Uint8ClampedArray(l.buffer),e,a);i>=2&&i<=4?(this.tempCanvas||(this.tempCanvas=document.createElement("canvas"),this.tempCtx=this.tempCanvas.getContext("2d")),this.tempCanvas.width=e,this.tempCanvas.height=a,this.tempCtx.putImageData(p,0,0),this.ctx.save(),i===2?(this.ctx.translate(n,0),this.ctx.rotate(Math.PI/2)):i===3?(this.ctx.translate(n,r),this.ctx.rotate(Math.PI)):i===4&&(this.ctx.translate(0,r),this.ctx.rotate(-Math.PI/2)),this.ctx.drawImage(this.tempCanvas,0,0),this.ctx.restore()):this.ctx.putImageData(p,0,0)}async startCamera(t,e=640,a=480,i=15){this.localVideo=t;const s=await navigator.mediaDevices.getUserMedia({video:{width:{ideal:e},height:{ideal:a},frameRate:{ideal:i}},audio:!1});this.localStream=s,t.srcObject=s,t.muted=!0,await t.play(),this.canUseH264Encoder()?this.startH264Capture(s,e,a,i):(this.captureCanvas=document.createElement("canvas"),this.captureCanvas.width=e,this.captureCanvas.height=a,this.captureCtx=this.captureCanvas.getContext("2d",{willReadFrequently:!0}),this.captureInterval=setInterval(()=>this.captureFrameRaw(),Math.floor(1e3/i)))}stopCamera(){if(this.captureInterval&&(clearInterval(this.captureInterval),this.captureInterval=null),this.h264Encoder&&this.h264Encoder.state!=="closed")try{this.h264Encoder.close()}catch{}this.h264Encoder=null,this.localStream&&(this.localStream.getTracks().forEach(t=>t.stop()),this.localStream=null),this.localVideo&&(this.localVideo.srcObject=null,this.localVideo=null),this.captureCanvas=null,this.captureCtx=null}canUseH264Encoder(){return typeof globalThis.VideoEncoder=="function"&&typeof globalThis.MediaStreamTrackProcessor=="function"}startH264Capture(t,e,a,i){const s=t.getVideoTracks()[0];if(!s)return;let n=e,r=a,o=0;const l=new Map;this.h264Encoder=new VideoEncoder({output:(d,m)=>{var h,v;if(!this.onEncodedCameraFrame)return;const f=new ArrayBuffer(d.byteLength);d.copyTo(f);const g=d.type==="key",C=((h=m==null?void 0:m.decoderConfig)==null?void 0:h.codedWidth)??n,E=((v=m==null?void 0:m.decoderConfig)==null?void 0:v.codedHeight)??r;let y=0;const w=l.get(d.timestamp);w!=null&&(l.delete(d.timestamp),y=Date.now()-w);const M=d.timestamp/1e3;this.onEncodedCameraFrame(f,C,E,M,g,y)},error:d=>{console.error("[VideoEngine] encoder error:",d)}}),this.h264Encoder.configure({codec:"avc1.42001f",width:n,height:r,bitrate:5e5,framerate:i,latencyMode:"realtime",bitrateMode:"constant",avc:{format:"annexb"}});const u=new globalThis.MediaStreamTrackProcessor({track:s}).readable.getReader();(async()=>{for(;this.h264Encoder&&this.h264Encoder.state!=="closed";)try{const{value:d,done:m}=await u.read();if(m||!d)break;if(this.h264Encoder.state!=="configured"){d.close();continue}const f=Date.now(),g=f-this.lastKeyFrameTs,C=this.h264NeedKeyFrame||g>=this.encoderKeyFrameInterval||o===0;l.set(d.timestamp,f),this.h264Encoder.encode(d,{keyFrame:C}),d.close(),o++,C&&(this.lastKeyFrameTs=f,this.h264NeedKeyFrame=!1)}catch{break}})()}captureFrameRaw(){if(!this.captureCtx||!this.captureCanvas||!this.localVideo||!this.onCameraFrame||this.localVideo.readyState<2)return;const t=this.captureCanvas.width,e=this.captureCanvas.height;this.captureCtx.drawImage(this.localVideo,0,0,t,e);const a=this.captureCtx.getImageData(0,0,t,e);this.onCameraFrame(a.data.buffer,t,e)}nv12ToRgba(t,e,a){const i=new Uint8ClampedArray(e*a*4),s=e*a;for(let n=0;n<a;n++)for(let r=0;r<e;r++){const o=n*e+r,l=s+(n>>1)*e+(r&-2),p=t[o],u=t[l]-128,b=t[l+1]-128,d=o*4;i[d]=L(p+1.402*b),i[d+1]=L(p-.344*u-.714*b),i[d+2]=L(p+1.772*u),i[d+3]=255}return i}clearCanvas(){this.canvas&&this.ctx&&this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height)}destroy(){if(this.stopCamera(),this.h264Decoder&&this.h264Decoder.state!=="closed")try{this.h264Decoder.close()}catch{}this.h264Decoder=null,this.orientationMap.clear(),this.canvas=null,this.ctx=null}}function L(c){return c<0?0:c>255?255:c|0}class X{constructor(){this.calls=new Map}addCall(t){this.calls.set(t.callId,{...t})}updateState(t,e){const a=this.calls.get(t);a&&(a.state=e,e==="ended"&&this.calls.delete(t))}updateIsVideo(t,e){const a=this.calls.get(t);a&&(a.isVideo=e)}removeCall(t){this.calls.delete(t)}getCall(t){return this.calls.get(t)}getActiveCall(){for(const t of this.calls.values())if(t.state==="accepted"||t.state==="active")return t}getRingingCalls(){const t=[];for(const e of this.calls.values())(e.state==="ringing"||e.state==="preaccepted")&&t.push(e);return t}getAllCalls(){return Array.from(this.calls.values())}hasActiveCalls(){for(const t of this.calls.values())if(t.state!=="ended")return!0;return!1}clear(){this.calls.clear()}}function Q(c){const t=c.mode==="light",e=c.primaryColor||"#00a884",a=c.dangerColor||"#f44336",i=c.backgroundColor||(t?"#f0f2f5":"#202c33"),s=c.surfaceColor||(t?"#ffffff":"#111b21"),n=c.textColor||(t?"#111b21":"#e9edef"),r=c.textSecondaryColor||(t?"#667781":"#8696a0"),o=c.borderRadius||"16px",l=c.fontFamily||"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",p=t?"rgba(0,0,0,0.06)":"rgba(255,255,255,0.08)",u=t?"rgba(0,0,0,0.1)":"rgba(255,255,255,0.1)",b=t?"rgba(0,0,0,0.14)":"rgba(255,255,255,0.15)",d=t?"rgba(0,0,0,0.12)":"rgba(0,0,0,0.3)",m=t?"rgba(0,0,0,0.16)":"rgba(0,0,0,0.4)",f=t?"rgba(0,0,0,0.08)":"rgba(255,255,255,0.06)",g=t?"rgba(0,0,0,0.12)":"rgba(255,255,255,0.1)";return`
2
+ :host {
3
+ all: initial;
4
+ font-family: ${l};
5
+ color: ${n};
6
+ --primary: ${e};
7
+ --danger: ${a};
8
+ --bg: ${i};
9
+ --surface: ${s};
10
+ --text: ${n};
11
+ --text-sec: ${r};
12
+ --radius: ${o};
13
+ }
14
+
15
+ * { margin: 0; padding: 0; box-sizing: border-box; }
16
+
17
+ .zapi-call-root {
18
+ position: fixed;
19
+ z-index: 2147483647;
20
+ font-family: ${l};
21
+ }
22
+ .zapi-call-root.bottom-right { bottom: 20px; right: 20px; }
23
+ .zapi-call-root.bottom-left { bottom: 20px; left: 20px; }
24
+ .zapi-call-root.top-right { top: 20px; right: 20px; }
25
+ .zapi-call-root.top-left { top: 20px; left: 20px; }
26
+
27
+ /* Bubble */
28
+ .bubble {
29
+ width: 56px;
30
+ height: 56px;
31
+ border-radius: 50%;
32
+ background: var(--primary);
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: center;
36
+ cursor: pointer;
37
+ box-shadow: 0 4px 12px ${d};
38
+ transition: transform 0.2s, box-shadow 0.2s;
39
+ position: relative;
40
+ user-select: none;
41
+ }
42
+ .bubble:hover { transform: scale(1.08); box-shadow: 0 6px 20px ${m}; }
43
+ .bubble:active { transform: scale(0.95); }
44
+ .bubble svg { width: 26px; height: 26px; color: #fff; }
45
+
46
+ .bubble .badge {
47
+ position: absolute;
48
+ top: -2px;
49
+ right: -2px;
50
+ width: 18px;
51
+ height: 18px;
52
+ border-radius: 50%;
53
+ background: var(--danger);
54
+ color: #fff;
55
+ font-size: 11px;
56
+ font-weight: 700;
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ display: none;
61
+ }
62
+ .bubble .badge.visible { display: flex; }
63
+
64
+ .bubble .conn-dot {
65
+ position: absolute;
66
+ bottom: 2px;
67
+ right: 2px;
68
+ width: 10px;
69
+ height: 10px;
70
+ border-radius: 50%;
71
+ background: #f44336;
72
+ border: 2px solid var(--primary);
73
+ }
74
+ .bubble .conn-dot.connected { background: #4caf50; }
75
+
76
+ .bubble.ringing {
77
+ animation: bubble-pulse 1.5s ease-in-out infinite;
78
+ }
79
+ @keyframes bubble-pulse {
80
+ 0%, 100% { box-shadow: 0 4px 12px ${d}; }
81
+ 50% { box-shadow: 0 4px 12px ${d}, 0 0 0 12px rgba(0,168,132,0.2); }
82
+ }
83
+
84
+ /* Panel */
85
+ .panel {
86
+ position: absolute;
87
+ bottom: 66px;
88
+ right: 0;
89
+ width: 360px;
90
+ max-height: 560px;
91
+ background: var(--surface);
92
+ border-radius: var(--radius);
93
+ box-shadow: 0 8px 32px ${m};
94
+ overflow: hidden;
95
+ transform: translateY(10px);
96
+ opacity: 0;
97
+ pointer-events: none;
98
+ transition: transform 0.25s ease, opacity 0.25s ease;
99
+ display: flex;
100
+ flex-direction: column;
101
+ }
102
+ .panel.open {
103
+ transform: translateY(0);
104
+ opacity: 1;
105
+ pointer-events: auto;
106
+ }
107
+ .zapi-call-root.bottom-left .panel { right: auto; left: 0; }
108
+ .zapi-call-root.top-right .panel { bottom: auto; top: 66px; }
109
+ .zapi-call-root.top-left .panel { bottom: auto; top: 66px; right: auto; left: 0; }
110
+
111
+ /* Panel header with tabs */
112
+ .panel-header {
113
+ background: var(--bg);
114
+ border-bottom: 1px solid ${f};
115
+ flex-shrink: 0;
116
+ }
117
+ .panel-header-top {
118
+ padding: 10px 16px;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: space-between;
122
+ }
123
+ .panel-header-top h3 {
124
+ font-size: 15px;
125
+ font-weight: 600;
126
+ color: var(--text);
127
+ }
128
+ .panel-header .close-btn {
129
+ width: 28px;
130
+ height: 28px;
131
+ border: none;
132
+ background: transparent;
133
+ color: var(--text-sec);
134
+ cursor: pointer;
135
+ border-radius: 50%;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ transition: background 0.2s;
140
+ }
141
+ .panel-header .close-btn:hover { background: ${p}; }
142
+ .panel-header .close-btn svg { width: 18px; height: 18px; }
143
+
144
+ /* Tabs */
145
+ .tabs {
146
+ display: flex;
147
+ padding: 0 8px;
148
+ }
149
+ .tab {
150
+ flex: 1;
151
+ padding: 10px 8px;
152
+ border: none;
153
+ background: transparent;
154
+ color: var(--text-sec);
155
+ font-size: 13px;
156
+ font-weight: 600;
157
+ cursor: pointer;
158
+ position: relative;
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ gap: 6px;
163
+ transition: color 0.2s;
164
+ font-family: inherit;
165
+ }
166
+ .tab svg { width: 16px; height: 16px; }
167
+ .tab:hover { color: var(--text); }
168
+ .tab.active { color: var(--primary); }
169
+ .tab.active::after {
170
+ content: '';
171
+ position: absolute;
172
+ bottom: 0;
173
+ left: 16px;
174
+ right: 16px;
175
+ height: 2px;
176
+ background: var(--primary);
177
+ border-radius: 2px 2px 0 0;
178
+ }
179
+
180
+ /* Panel body / views */
181
+ .panel-body {
182
+ flex: 1;
183
+ overflow-y: auto;
184
+ overflow-x: hidden;
185
+ min-height: 0;
186
+ }
187
+ .view {
188
+ display: none;
189
+ animation: view-in 0.2s ease;
190
+ }
191
+ .view.active { display: block; }
192
+ @keyframes view-in {
193
+ from { opacity: 0; transform: translateY(6px); }
194
+ to { opacity: 1; transform: translateY(0); }
195
+ }
196
+
197
+ /* ── Dialer view ── */
198
+ .dialer {
199
+ padding: 8px 16px 14px;
200
+ display: flex;
201
+ flex-direction: column;
202
+ align-items: center;
203
+ }
204
+ .dialer-display {
205
+ width: 100%;
206
+ height: 56px;
207
+ display: flex;
208
+ align-items: center;
209
+ justify-content: center;
210
+ position: relative;
211
+ margin-bottom: 8px;
212
+ }
213
+ .dialer-input {
214
+ width: 100%;
215
+ background: transparent;
216
+ border: none;
217
+ padding: 0 40px;
218
+ color: var(--text);
219
+ font-size: 28px;
220
+ font-weight: 300;
221
+ font-family: inherit;
222
+ text-align: center;
223
+ letter-spacing: 1.5px;
224
+ outline: none;
225
+ font-variant-numeric: tabular-nums;
226
+ caret-color: var(--primary);
227
+ }
228
+ .dialer-input::placeholder { color: var(--text-sec); font-size: 16px; font-weight: 400; letter-spacing: 0; }
229
+ .dialer-input.error::placeholder { color: #e74c3c; }
230
+ .backspace-btn {
231
+ position: absolute;
232
+ right: 0;
233
+ width: 36px;
234
+ height: 36px;
235
+ border: none;
236
+ background: transparent;
237
+ color: var(--text-sec);
238
+ cursor: pointer;
239
+ border-radius: 50%;
240
+ display: flex;
241
+ align-items: center;
242
+ justify-content: center;
243
+ transition: background 0.2s, color 0.2s;
244
+ }
245
+ .backspace-btn:hover { background: ${p}; color: var(--text); }
246
+ .backspace-btn svg { width: 20px; height: 20px; }
247
+
248
+ /* Numpad */
249
+ .numpad {
250
+ display: grid;
251
+ grid-template-columns: repeat(3, 1fr);
252
+ gap: 6px;
253
+ width: 100%;
254
+ max-width: 240px;
255
+ margin-bottom: 12px;
256
+ }
257
+ .numpad-key {
258
+ width: 60px;
259
+ height: 60px;
260
+ border-radius: 50%;
261
+ border: none;
262
+ background: var(--bg);
263
+ color: var(--text);
264
+ font-size: 22px;
265
+ font-weight: 400;
266
+ cursor: pointer;
267
+ display: flex;
268
+ flex-direction: column;
269
+ align-items: center;
270
+ justify-content: center;
271
+ transition: background 0.15s, transform 0.1s;
272
+ user-select: none;
273
+ font-family: inherit;
274
+ justify-self: center;
275
+ line-height: 1;
276
+ }
277
+ .numpad-key:hover { background: ${u}; }
278
+ .numpad-key:active { transform: scale(0.92); background: ${b}; }
279
+ .numpad-key .sub {
280
+ font-size: 8px;
281
+ color: var(--text-sec);
282
+ letter-spacing: 1.5px;
283
+ margin-top: 1px;
284
+ font-weight: 600;
285
+ }
286
+
287
+ /* Call buttons */
288
+ .call-btns {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 16px;
292
+ }
293
+ .call-btn {
294
+ width: 56px;
295
+ height: 56px;
296
+ border-radius: 50%;
297
+ border: none;
298
+ background: var(--primary);
299
+ color: #fff;
300
+ cursor: pointer;
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ box-shadow: 0 4px 16px rgba(0,168,132,0.3);
305
+ transition: transform 0.15s, box-shadow 0.15s;
306
+ }
307
+ .call-btn:hover { transform: scale(1.08); box-shadow: 0 6px 24px rgba(0,168,132,0.4); }
308
+ .call-btn:active { transform: scale(0.95); }
309
+ .call-btn svg { width: 26px; height: 26px; }
310
+ .call-btn:disabled {
311
+ opacity: 0.5;
312
+ cursor: not-allowed;
313
+ transform: none;
314
+ box-shadow: 0 4px 16px rgba(0,168,132,0.15);
315
+ }
316
+ .video-call-btn {
317
+ background: #2196f3;
318
+ box-shadow: 0 4px 16px rgba(33,150,243,0.3);
319
+ }
320
+ .video-call-btn:hover { box-shadow: 0 6px 24px rgba(33,150,243,0.4); }
321
+
322
+ /* ── Calls view ── */
323
+ .calls-view {
324
+ padding: 8px;
325
+ }
326
+
327
+ .empty-state {
328
+ padding: 40px 16px;
329
+ text-align: center;
330
+ color: var(--text-sec);
331
+ font-size: 13px;
332
+ }
333
+ .empty-state .icon {
334
+ font-size: 36px;
335
+ margin-bottom: 8px;
336
+ }
337
+
338
+ /* Call Card */
339
+ .call-card {
340
+ background: var(--bg);
341
+ border-radius: 12px;
342
+ padding: 14px;
343
+ margin-bottom: 8px;
344
+ animation: card-in 0.3s ease;
345
+ }
346
+ .call-card.ended {
347
+ opacity: 0.5;
348
+ animation: card-out 0.5s ease forwards;
349
+ }
350
+ @keyframes card-in {
351
+ from { transform: translateY(-8px); opacity: 0; }
352
+ to { transform: translateY(0); opacity: 1; }
353
+ }
354
+ @keyframes card-out {
355
+ to { transform: translateY(8px); opacity: 0; }
356
+ }
357
+
358
+ .call-card-top {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 10px;
362
+ margin-bottom: 10px;
363
+ }
364
+
365
+ .avatar {
366
+ width: 40px;
367
+ height: 40px;
368
+ border-radius: 50%;
369
+ background: var(--surface);
370
+ display: flex;
371
+ align-items: center;
372
+ justify-content: center;
373
+ flex-shrink: 0;
374
+ }
375
+ .avatar svg { width: 22px; height: 22px; color: var(--text-sec); }
376
+ .avatar .avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
377
+
378
+ .call-info { flex: 1; min-width: 0; }
379
+ .call-phone {
380
+ font-size: 15px;
381
+ font-weight: 600;
382
+ color: var(--text);
383
+ white-space: nowrap;
384
+ overflow: hidden;
385
+ text-overflow: ellipsis;
386
+ }
387
+ .call-meta {
388
+ font-size: 12px;
389
+ color: var(--text-sec);
390
+ display: flex;
391
+ align-items: center;
392
+ gap: 6px;
393
+ margin-top: 2px;
394
+ }
395
+ .call-meta svg { width: 13px; height: 13px; }
396
+
397
+ .state-badge {
398
+ padding: 3px 8px;
399
+ border-radius: 6px;
400
+ font-size: 11px;
401
+ font-weight: 700;
402
+ text-transform: uppercase;
403
+ letter-spacing: 0.5px;
404
+ flex-shrink: 0;
405
+ }
406
+ .state-badge.ringing {
407
+ background: #f9a825;
408
+ color: #000;
409
+ animation: badge-pulse 1.5s ease-in-out infinite;
410
+ }
411
+ .state-badge.preaccepted { background: #ff9800; color: #000; }
412
+ .state-badge.accepted { background: #2196f3; color: #fff; }
413
+ .state-badge.active { background: var(--primary); color: #fff; }
414
+ .state-badge.ended { background: var(--danger); color: #fff; }
415
+ @keyframes badge-pulse {
416
+ 0%, 100% { opacity: 1; }
417
+ 50% { opacity: 0.5; }
418
+ }
419
+
420
+ .timer {
421
+ font-size: 22px;
422
+ font-weight: 300;
423
+ text-align: center;
424
+ margin: 8px 0;
425
+ font-variant-numeric: tabular-nums;
426
+ color: var(--text);
427
+ }
428
+
429
+ .call-actions {
430
+ display: flex;
431
+ gap: 8px;
432
+ margin-top: 8px;
433
+ }
434
+
435
+ .btn {
436
+ flex: 1;
437
+ padding: 10px 16px;
438
+ border: none;
439
+ border-radius: 24px;
440
+ font-size: 14px;
441
+ font-weight: 600;
442
+ cursor: pointer;
443
+ transition: transform 0.15s, opacity 0.15s;
444
+ display: flex;
445
+ align-items: center;
446
+ justify-content: center;
447
+ gap: 6px;
448
+ font-family: inherit;
449
+ }
450
+ .btn:hover { transform: scale(1.03); }
451
+ .btn:active { transform: scale(0.97); }
452
+ .btn svg { width: 18px; height: 18px; }
453
+ .btn-accept { background: var(--primary); color: #fff; }
454
+ .btn-reject { background: var(--danger); color: #fff; }
455
+ .btn-mute {
456
+ width: 44px;
457
+ height: 44px;
458
+ flex: none;
459
+ border-radius: 50%;
460
+ background: var(--bg);
461
+ border: 1px solid ${g};
462
+ color: var(--text);
463
+ padding: 0;
464
+ display: flex;
465
+ align-items: center;
466
+ justify-content: center;
467
+ cursor: pointer;
468
+ transition: background 0.2s;
469
+ }
470
+ .btn-mute:hover { background: ${p}; }
471
+ .btn-mute.muted { background: var(--danger); color: #fff; border-color: transparent; }
472
+ .btn-mute svg { width: 20px; height: 20px; }
473
+
474
+ /* ── In-Call view ── */
475
+ .incall-view {
476
+ display: flex;
477
+ flex-direction: column;
478
+ align-items: center;
479
+ text-align: center;
480
+ }
481
+ .incall-view .audio-call-ui {
482
+ padding: 24px 16px;
483
+ }
484
+ .incall-avatar {
485
+ width: 80px;
486
+ height: 80px;
487
+ border-radius: 50%;
488
+ background: var(--bg);
489
+ display: flex;
490
+ align-items: center;
491
+ justify-content: center;
492
+ margin-bottom: 16px;
493
+ }
494
+ .incall-avatar svg { width: 40px; height: 40px; color: var(--text-sec); }
495
+ .incall-avatar .avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }
496
+ .incall-phone {
497
+ font-size: 20px;
498
+ font-weight: 600;
499
+ color: var(--text);
500
+ margin-bottom: 4px;
501
+ }
502
+ .incall-state {
503
+ font-size: 14px;
504
+ color: var(--text-sec);
505
+ margin-bottom: 4px;
506
+ }
507
+ .incall-timer {
508
+ font-size: 28px;
509
+ font-weight: 300;
510
+ color: var(--text);
511
+ font-variant-numeric: tabular-nums;
512
+ margin-bottom: 20px;
513
+ }
514
+
515
+ /* Audio level bar */
516
+ .audio-level-wrap {
517
+ width: 100%;
518
+ max-width: 200px;
519
+ height: 6px;
520
+ background: var(--bg);
521
+ border-radius: 3px;
522
+ overflow: hidden;
523
+ margin-bottom: 28px;
524
+ }
525
+ .audio-level-bar {
526
+ height: 100%;
527
+ width: 0%;
528
+ background: var(--primary);
529
+ border-radius: 3px;
530
+ transition: width 0.1s ease;
531
+ }
532
+
533
+ /* In-call controls */
534
+ .incall-controls {
535
+ display: flex;
536
+ align-items: center;
537
+ gap: 32px;
538
+ }
539
+ .incall-btn {
540
+ width: 56px;
541
+ height: 56px;
542
+ border-radius: 50%;
543
+ border: none;
544
+ display: flex;
545
+ align-items: center;
546
+ justify-content: center;
547
+ cursor: pointer;
548
+ transition: transform 0.15s;
549
+ }
550
+ .incall-btn:hover { transform: scale(1.08); }
551
+ .incall-btn:active { transform: scale(0.95); }
552
+ .incall-btn svg { width: 24px; height: 24px; }
553
+ .incall-btn-mute {
554
+ background: var(--bg);
555
+ color: var(--text);
556
+ border: 1px solid ${g};
557
+ }
558
+ .incall-btn-mute.muted {
559
+ background: var(--danger);
560
+ color: #fff;
561
+ border-color: transparent;
562
+ }
563
+ .incall-btn-camera {
564
+ background: var(--bg);
565
+ color: var(--text);
566
+ border: 1px solid ${g};
567
+ }
568
+ .incall-btn-camera:hover { background: ${p}; }
569
+ .incall-btn-camera.muted {
570
+ color: var(--text-sec);
571
+ }
572
+ .incall-btn-end {
573
+ background: var(--danger);
574
+ color: #fff;
575
+ width: 64px;
576
+ height: 64px;
577
+ }
578
+ .incall-btn-end svg { width: 28px; height: 28px; }
579
+
580
+ /* ── Video Call ── */
581
+ .audio-call-ui {
582
+ display: flex;
583
+ flex-direction: column;
584
+ align-items: center;
585
+ }
586
+ .video-container {
587
+ position: relative;
588
+ width: 100%;
589
+ height: 420px;
590
+ overflow: hidden;
591
+ background: #000;
592
+ display: flex;
593
+ align-items: center;
594
+ justify-content: center;
595
+ border-radius: 0 0 12px 12px;
596
+ }
597
+ .remote-video {
598
+ max-width: 100%;
599
+ max-height: 100%;
600
+ display: block;
601
+ object-fit: contain;
602
+ }
603
+ .video-overlay-top {
604
+ position: absolute;
605
+ top: 0; left: 0; right: 0;
606
+ padding: 16px;
607
+ background: linear-gradient(to bottom, rgba(0,0,0,0.6), transparent);
608
+ z-index: 2;
609
+ }
610
+ .video-caller-info {
611
+ display: flex;
612
+ align-items: center;
613
+ justify-content: space-between;
614
+ }
615
+ .video-phone {
616
+ font-size: 15px;
617
+ font-weight: 600;
618
+ color: #fff;
619
+ text-shadow: 0 1px 3px rgba(0,0,0,0.5);
620
+ }
621
+ .video-timer {
622
+ font-size: 14px;
623
+ font-weight: 500;
624
+ color: rgba(255,255,255,0.85);
625
+ font-variant-numeric: tabular-nums;
626
+ text-shadow: 0 1px 3px rgba(0,0,0,0.5);
627
+ }
628
+ .video-overlay-bottom {
629
+ position: absolute;
630
+ bottom: 0; left: 0; right: 0;
631
+ padding: 20px;
632
+ background: linear-gradient(to top, rgba(0,0,0,0.6), transparent);
633
+ display: flex;
634
+ align-items: center;
635
+ justify-content: center;
636
+ gap: 32px;
637
+ z-index: 2;
638
+ }
639
+ .video-overlay-bottom .incall-btn {
640
+ backdrop-filter: blur(8px);
641
+ }
642
+ .video-btn-mute {
643
+ background: rgba(255,255,255,0.2) !important;
644
+ color: #fff !important;
645
+ border: none !important;
646
+ }
647
+ .video-btn-mute.muted {
648
+ background: var(--danger) !important;
649
+ }
650
+ .video-btn-camera {
651
+ background: rgba(255,255,255,0.2) !important;
652
+ color: #fff !important;
653
+ border: none !important;
654
+ }
655
+ .video-btn-camera.muted {
656
+ background: rgba(255,255,255,0.1) !important;
657
+ color: rgba(255,255,255,0.5) !important;
658
+ }
659
+ .video-btn-end {
660
+ background: var(--danger) !important;
661
+ color: #fff !important;
662
+ }
663
+ .video-btn-end svg { width: 28px; height: 28px; }
664
+
665
+ /* ── Contacts view ── */
666
+ .contacts-wrapper {
667
+ display: flex;
668
+ flex-direction: column;
669
+ height: 400px;
670
+ }
671
+ .contacts-search-row {
672
+ display: flex;
673
+ align-items: center;
674
+ gap: 8px;
675
+ padding: 10px 12px;
676
+ border-bottom: 1px solid ${f};
677
+ flex-shrink: 0;
678
+ }
679
+ .contacts-search-input-wrap {
680
+ flex: 1;
681
+ display: flex;
682
+ align-items: center;
683
+ gap: 8px;
684
+ background: var(--bg);
685
+ border-radius: 20px;
686
+ padding: 0 12px;
687
+ height: 36px;
688
+ }
689
+ .contacts-search-input-wrap svg {
690
+ width: 16px;
691
+ height: 16px;
692
+ color: var(--text-sec);
693
+ flex-shrink: 0;
694
+ }
695
+ .contacts-search-input {
696
+ flex: 1;
697
+ border: none;
698
+ background: transparent;
699
+ color: var(--text);
700
+ font-size: 13px;
701
+ font-family: inherit;
702
+ outline: none;
703
+ }
704
+ .contacts-search-input::placeholder { color: var(--text-sec); }
705
+ .contacts-refresh-btn {
706
+ width: 32px;
707
+ height: 32px;
708
+ border: none;
709
+ background: transparent;
710
+ color: var(--text-sec);
711
+ cursor: pointer;
712
+ border-radius: 50%;
713
+ display: flex;
714
+ align-items: center;
715
+ justify-content: center;
716
+ transition: background 0.2s, color 0.2s;
717
+ flex-shrink: 0;
718
+ }
719
+ .contacts-refresh-btn:hover { background: ${p}; color: var(--text); }
720
+ .contacts-refresh-btn svg { width: 18px; height: 18px; }
721
+
722
+ .contacts-list {
723
+ flex: 1;
724
+ overflow-y: auto;
725
+ padding: 4px 8px;
726
+ }
727
+ .contacts-loading {
728
+ display: flex;
729
+ flex-direction: column;
730
+ align-items: center;
731
+ justify-content: center;
732
+ padding: 40px 16px;
733
+ color: var(--text-sec);
734
+ font-size: 13px;
735
+ gap: 12px;
736
+ }
737
+ .spinner {
738
+ width: 24px;
739
+ height: 24px;
740
+ border: 3px solid ${p};
741
+ border-top-color: var(--primary);
742
+ border-radius: 50%;
743
+ animation: spin 0.8s linear infinite;
744
+ }
745
+ @keyframes spin { to { transform: rotate(360deg); } }
746
+
747
+ .contact-item {
748
+ display: flex;
749
+ align-items: center;
750
+ gap: 10px;
751
+ padding: 8px 8px;
752
+ border-radius: 10px;
753
+ cursor: default;
754
+ transition: background 0.15s;
755
+ }
756
+ .contact-item:hover { background: ${p}; }
757
+ .contact-avatar {
758
+ width: 36px;
759
+ height: 36px;
760
+ border-radius: 50%;
761
+ background: var(--bg);
762
+ display: flex;
763
+ align-items: center;
764
+ justify-content: center;
765
+ flex-shrink: 0;
766
+ overflow: hidden;
767
+ font-size: 14px;
768
+ font-weight: 600;
769
+ color: var(--text-sec);
770
+ }
771
+ .contact-avatar img {
772
+ width: 100%;
773
+ height: 100%;
774
+ object-fit: cover;
775
+ }
776
+ .contact-info {
777
+ flex: 1;
778
+ min-width: 0;
779
+ }
780
+ .contact-name {
781
+ font-size: 14px;
782
+ font-weight: 500;
783
+ color: var(--text);
784
+ white-space: nowrap;
785
+ overflow: hidden;
786
+ text-overflow: ellipsis;
787
+ }
788
+ .contact-phone {
789
+ font-size: 12px;
790
+ color: var(--text-sec);
791
+ white-space: nowrap;
792
+ overflow: hidden;
793
+ text-overflow: ellipsis;
794
+ }
795
+ .contact-call-btn {
796
+ width: 32px;
797
+ height: 32px;
798
+ border: none;
799
+ background: var(--primary);
800
+ color: #fff;
801
+ border-radius: 50%;
802
+ display: flex;
803
+ align-items: center;
804
+ justify-content: center;
805
+ cursor: pointer;
806
+ flex-shrink: 0;
807
+ transition: transform 0.15s;
808
+ }
809
+ .contact-call-btn:hover { transform: scale(1.1); }
810
+ .contact-call-btn:active { transform: scale(0.95); }
811
+ .contact-call-btn svg { width: 16px; height: 16px; }
812
+
813
+ /* Hide header tabs during video call */
814
+ .incall-view .video-container[style*="flex"] ~ .panel-header { display: none; }
815
+ `}const k='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6.62 10.79a15.05 15.05 0 006.59 6.59l2.2-2.2a1 1 0 011.01-.24c1.12.37 2.33.57 3.57.57a1 1 0 011 1V20a1 1 0 01-1 1A17 17 0 013 4a1 1 0 011-1h3.5a1 1 0 011 1c0 1.25.2 2.45.57 3.57a1 1 0 01-.25 1.02l-2.2 2.2z"/></svg>',I='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 9c-1.6 0-3.15.25-4.6.72v3.1c0 .39-.23.74-.56.9-.98.49-1.87 1.12-2.66 1.85-.18.18-.43.28-.7.28-.28 0-.53-.11-.71-.29L.29 13.08a.956.956 0 010-1.36C3.34 8.75 7.46 7 12 7s8.66 1.75 11.71 4.72c.18.18.29.44.29.71 0 .28-.11.53-.29.71l-2.48 2.48c-.18.18-.43.29-.71.29-.27 0-.52-.11-.7-.28a11.27 11.27 0 00-2.67-1.85.99.99 0 01-.56-.9v-3.1C15.15 9.25 13.6 9 12 9z"/></svg>',tt='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20.01 15.38c-1.23 0-2.42-.2-3.53-.56a.977.977 0 00-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z"/></svg>',S='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5z"/><path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/></svg>',R='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 11h-1.7c0 .74-.16 1.43-.43 2.05l1.23 1.23c.56-.98.9-2.09.9-3.28zm-4.02.17c0-.06.02-.11.02-.17V5c0-1.66-1.34-3-3-3S9 3.34 9 5v.18l5.98 5.99zM4.27 3L3 4.27l6.01 6.01V11c0 1.66 1.33 3 2.99 3 .22 0 .44-.03.65-.08l1.66 1.66c-.71.33-1.5.52-2.31.52-2.76 0-5.3-2.1-5.3-5.1H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c.91-.13 1.77-.45 2.54-.9L19.73 21 21 19.73 4.27 3z"/></svg>',D='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>',T='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17 10.5V7c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4z"/></svg>',A='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21 6.5l-4 4V7c0-.55-.45-1-1-1H9.82L21 17.18V6.5zM3.27 2L2 3.27 4.73 6H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.21 0 .39-.08.54-.18L19.73 21 21 19.73 3.27 2z"/></svg>',et='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>',at='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22 3H7c-.69 0-1.23.35-1.59.88L0 12l5.41 8.11c.36.53.9.89 1.59.89h15c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-3 12.59L17.59 17 14 13.41 10.41 17 9 15.59 12.59 12 9 8.41 10.41 7 14 10.59 17.59 7 19 8.41 15.41 12 19 15.59z"/></svg>',it='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 19c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zM6 1c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm12-8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm-6 8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-6 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>',st='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M20 15.5c-1.25 0-2.45-.2-3.57-.57a1.02 1.02 0 00-1.02.24l-2.2 2.2a15.045 15.045 0 01-6.59-6.59l2.2-2.21a.96.96 0 00.25-1A11.36 11.36 0 018.5 4c0-.55-.45-1-1-1H4c-.55 0-1 .45-1 1 0 9.39 7.61 17 17 17 .55 0 1-.45 1-1v-3.5c0-.55-.45-1-1-1zM19 12h2c0-4.97-4.03-9-9-9v2c3.87 0 7 3.13 7 7zm-4 0h2c0-2.76-2.24-5-5-5v2c1.66 0 3 1.34 3 3z"/></svg>',nt='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5s-3 1.34-3 3 1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>',rt='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 001.48-5.34c-.47-2.78-2.79-5-5.59-5.34a6.505 6.505 0 00-7.27 7.27c.34 2.8 2.56 5.12 5.34 5.59a6.5 6.5 0 005.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>',ot='<svg viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>';function B(c){const t=Math.floor(c/1e3),e=Math.floor(t/60),a=t%60;return`${String(e).padStart(2,"0")}:${String(a).padStart(2,"0")}`}function V(c){if(!c||c==="unknown")return c;const t=c.replace(/\D/g,"");return t.length<=4?c:t.length===13&&t.startsWith("55")?`+${t.slice(0,2)} (${t.slice(2,4)}) ${t.slice(4,9)}-${t.slice(9)}`:t.length===12&&t.startsWith("55")?`+${t.slice(0,2)} (${t.slice(2,4)}) ${t.slice(4,8)}-${t.slice(8)}`:`+${t}`}const ct=[{digit:"1",sub:""},{digit:"2",sub:"ABC"},{digit:"3",sub:"DEF"},{digit:"4",sub:"GHI"},{digit:"5",sub:"JKL"},{digit:"6",sub:"MNO"},{digit:"7",sub:"PQRS"},{digit:"8",sub:"TUV"},{digit:"9",sub:"WXYZ"},{digit:"*",sub:""},{digit:"0",sub:"+"},{digit:"#",sub:""}];class lt{constructor(t){this.open=!1,this.muted=!1,this.timers=new Map,this.timerStarts=new Map,this.currentView="dialer",this.contactsLoaded=!1,this.contactsCache=[],this.pictureCache=new Map,this.pictureRequested=new Set,this.activeCallId=null,this.cameraOn=!1,this.incallTimer=null,this.config=t,this.host=document.createElement("div"),this.host.id="zapi-call-widget",this.shadow=this.host.attachShadow({mode:"closed"});const e=document.createElement("style");e.textContent=Q(t.theme),this.shadow.appendChild(e),this.buildDOM(),document.body.appendChild(this.host)}buildDOM(){this.root=document.createElement("div"),this.root.className=`zapi-call-root ${this.config.position}`,this.panel=document.createElement("div"),this.panel.className="panel";const t=document.createElement("div");t.className="panel-header",t.innerHTML=`
816
+ <div class="panel-header-top">
817
+ <h3>Telefone</h3>
818
+ <button class="close-btn">${et}</button>
819
+ </div>
820
+ <div class="tabs">
821
+ <button class="tab active" data-tab="dialer">${it} Discador</button>
822
+ <button class="tab" data-tab="calls">${st} Chamadas</button>
823
+ <button class="tab" data-tab="contacts">${nt} Contatos</button>
824
+ </div>
825
+ `,t.querySelector(".close-btn").addEventListener("click",()=>this.togglePanel(!1)),this.tabDialer=t.querySelector('[data-tab="dialer"]'),this.tabCalls=t.querySelector('[data-tab="calls"]'),this.tabContacts=t.querySelector('[data-tab="contacts"]'),this.tabDialer.addEventListener("click",()=>this.switchView("dialer")),this.tabCalls.addEventListener("click",()=>this.switchView("calls")),this.tabContacts.addEventListener("click",()=>this.switchView("contacts")),this.panel.appendChild(t);const e=document.createElement("div");e.className="panel-body",this.dialerView=document.createElement("div"),this.dialerView.className="view active",this.buildDialerView(),e.appendChild(this.dialerView),this.callsView=document.createElement("div"),this.callsView.className="view",this.callsView.innerHTML=`
826
+ <div class="calls-view">
827
+ <div class="empty-state">
828
+ <div class="icon">&#128222;</div>
829
+ <p>Nenhuma chamada ativa</p>
830
+ </div>
831
+ </div>
832
+ `,e.appendChild(this.callsView),this.contactsView=document.createElement("div"),this.contactsView.className="view",this.buildContactsView(),e.appendChild(this.contactsView),this.incallView=document.createElement("div"),this.incallView.className="view",this.buildInCallView(),e.appendChild(this.incallView),this.panel.appendChild(e),this.root.appendChild(this.panel),this.bubble=document.createElement("div"),this.bubble.className="bubble",this.bubble.innerHTML=`
833
+ ${k}
834
+ <span class="badge">0</span>
835
+ <span class="conn-dot"></span>
836
+ `,this.badge=this.bubble.querySelector(".badge"),this.connDot=this.bubble.querySelector(".conn-dot"),this.bubble.addEventListener("click",()=>{this.togglePanel(),this.config.onResume()}),this.root.appendChild(this.bubble),this.shadow.appendChild(this.root)}buildDialerView(){const t=document.createElement("div");t.className="dialer";const e=document.createElement("div");e.className="dialer-display",this.dialerInput=document.createElement("input"),this.dialerInput.className="dialer-input",this.dialerInput.type="tel",this.dialerInput.placeholder="Digite o numero",this.dialerInput.addEventListener("keydown",r=>{r.key==="Enter"&&this.handleMakeCall()});const a=document.createElement("button");a.className="backspace-btn",a.innerHTML=at,a.addEventListener("click",()=>{this.dialerInput.value=this.dialerInput.value.slice(0,-1),this.dialerInput.focus()}),e.appendChild(this.dialerInput),e.appendChild(a),t.appendChild(e);const i=document.createElement("div");i.className="numpad";for(const r of ct){const o=document.createElement("button");o.className="numpad-key",o.innerHTML=`<span>${r.digit}</span>${r.sub?`<span class="sub">${r.sub}</span>`:""}`,o.addEventListener("click",()=>{this.dialerInput.value+=r.digit,this.dialerInput.focus()}),i.appendChild(o)}t.appendChild(i);const s=document.createElement("div");s.className="call-btns";const n=document.createElement("button");if(n.className="call-btn",n.innerHTML=k,n.addEventListener("click",()=>this.handleMakeCall(!1)),s.appendChild(n),this.config.showVideoDialButton){const r=document.createElement("button");r.className="call-btn video-call-btn",r.innerHTML=T,r.addEventListener("click",()=>this.handleMakeCall(!0)),s.appendChild(r)}t.appendChild(s),this.dialerView.appendChild(t)}buildContactsView(){const t=document.createElement("div");t.className="contacts-wrapper";const e=document.createElement("div");e.className="contacts-search-row",e.innerHTML=`
837
+ <div class="contacts-search-input-wrap">
838
+ ${rt}
839
+ <input class="contacts-search-input" type="text" placeholder="Buscar contato..." />
840
+ </div>
841
+ <button class="contacts-refresh-btn">${ot}</button>
842
+ `;const a=e.querySelector(".contacts-search-input");a.addEventListener("input",()=>this.filterContacts(a.value)),e.querySelector(".contacts-refresh-btn").addEventListener("click",()=>{this.contactsLoaded=!1,this.loadContacts()}),t.appendChild(e);const s=document.createElement("div");s.className="contacts-list",s.innerHTML='<div class="empty-state"><p>Clique na aba para carregar</p></div>',t.appendChild(s),this.contactsView.appendChild(t)}loadContacts(){const t=this.contactsView.querySelector(".contacts-list");t.innerHTML='<div class="contacts-loading"><div class="spinner"></div><p>Carregando contatos...</p></div>',this.config.onRequestContacts()}filterContacts(t){const e=t.toLowerCase().trim(),a=this.contactsView.querySelector(".contacts-list"),i=e?this.contactsCache.filter(s=>s.name.toLowerCase().includes(e)||s.phone.includes(e)):this.contactsCache;this.renderContactsList(a,i)}renderContactsList(t,e){if(e.length===0){t.innerHTML='<div class="empty-state"><p>Nenhum contato encontrado</p></div>';return}t.innerHTML="";for(const a of e){const i=document.createElement("div");i.className="contact-item";const s=(a.short||a.name||"?").charAt(0).toUpperCase();i.innerHTML=`
843
+ <div class="contact-avatar">${a.imgUrl?`<img src="${a.imgUrl}" />`:`<span>${s}</span>`}</div>
844
+ <div class="contact-info">
845
+ <div class="contact-name">${a.name||a.phone}</div>
846
+ <div class="contact-phone">${a.phone}</div>
847
+ </div>
848
+ <button class="contact-call-btn">${k}</button>
849
+ `,i.querySelector(".contact-call-btn").addEventListener("click",()=>{const n=a.phone.replace(/[^0-9+]/g,"");this.config.onMakeCall(n,!1)}),t.appendChild(i)}}updateContacts(t){this.contactsCache=t,this.contactsLoaded=!0;const e=this.contactsView.querySelector(".contacts-list");e&&this.renderContactsList(e,t)}buildInCallView(){const t=document.createElement("div");t.className="incall-view",t.innerHTML=`
850
+ <div class="video-container" style="display:none;">
851
+ <canvas class="remote-video"></canvas>
852
+ <div class="video-overlay-top">
853
+ <div class="video-caller-info">
854
+ <div class="video-phone"></div>
855
+ <div class="video-timer">00:00</div>
856
+ </div>
857
+ </div>
858
+ <div class="video-overlay-bottom">
859
+ <button class="incall-btn video-btn-mute">${S}</button>
860
+ <button class="incall-btn video-btn-camera">${A}</button>
861
+ <button class="incall-btn video-btn-end">${I}</button>
862
+ </div>
863
+ </div>
864
+ <div class="audio-call-ui">
865
+ <div class="incall-avatar">${D}</div>
866
+ <div class="incall-phone"></div>
867
+ <div class="incall-state"></div>
868
+ <div class="incall-timer">00:00</div>
869
+ <div class="audio-level-wrap">
870
+ <div class="audio-level-bar"></div>
871
+ </div>
872
+ <div class="incall-controls">
873
+ <button class="incall-btn incall-btn-mute">${S}</button>
874
+ <button class="incall-btn incall-btn-camera">${A}</button>
875
+ <button class="incall-btn incall-btn-end">${I}</button>
876
+ </div>
877
+ </div>
878
+ `,this.videoContainer=t.querySelector(".video-container"),this.remoteVideoCanvas=t.querySelector(".remote-video"),this.incallPhoneEl=t.querySelector(".incall-phone"),this.incallStateEl=t.querySelector(".incall-state"),this.incallTimerEl=t.querySelector(".incall-timer"),this.audioLevelBar=t.querySelector(".audio-level-bar"),this.videoPhoneEl=t.querySelector(".video-phone"),this.videoTimerEl=t.querySelector(".video-timer"),this.incallMuteBtn=t.querySelector(".incall-btn-mute"),this.incallMuteBtn.addEventListener("click",()=>{this.muted=!this.muted,this.config.onMute(this.muted),this.updateMuteButtons()}),t.querySelector(".video-btn-mute").addEventListener("click",()=>{this.muted=!this.muted,this.config.onMute(this.muted),this.updateMuteButtons()}),t.querySelectorAll(".video-btn-camera, .incall-btn-camera").forEach(n=>{n.addEventListener("click",()=>{this.cameraOn=!this.cameraOn,this.config.onCamera(this.cameraOn),this.updateCameraButton()})}),t.querySelector(".incall-btn-end").addEventListener("click",()=>{this.activeCallId&&this.config.onReject(this.activeCallId)}),t.querySelector(".video-btn-end").addEventListener("click",()=>{this.activeCallId&&this.config.onReject(this.activeCallId)}),this.incallView.appendChild(t)}handleMakeCall(t=!1){const e=this.dialerInput.value.replace(/\s/g,"");e&&(this.config.onMakeCall(e,t),console.log("[ZAPICall] makeCall from widget:",e,t?"(video)":"(audio)"))}switchView(t){this.currentView=t,this.dialerView.classList.toggle("active",t==="dialer"),this.callsView.classList.toggle("active",t==="calls"),this.contactsView.classList.toggle("active",t==="contacts"),this.incallView.classList.toggle("active",t==="incall"),this.tabDialer.classList.toggle("active",t==="dialer"),this.tabCalls.classList.toggle("active",t==="calls"),this.tabContacts.classList.toggle("active",t==="contacts"),t==="contacts"&&!this.contactsLoaded&&this.loadContacts()}togglePanel(t){this.open=t!==void 0?t:!this.open,this.panel.classList.toggle("open",this.open)}updateCalls(t,e){this.connDot.classList.toggle("connected",e);const a=t.filter(n=>n.state!=="ended"),i=t.filter(n=>n.state==="ringing"||n.state==="preaccepted"||n.state==="calling"),s=t.find(n=>n.state==="active")||t.find(n=>n.state==="accepted")||t.find(n=>n.state==="calling");if(a.length>0?(this.badge.textContent=String(a.length),this.badge.classList.add("visible")):this.badge.classList.remove("visible"),this.bubble.classList.toggle("ringing",i.length>0),i.length>0&&!this.open&&this.togglePanel(!0),s){this.activeCallId=s.callId,this.incallPhoneEl.textContent=s.notify||V(s.from),this.incallStateEl.textContent=this.stateLabel(s.state),this.videoPhoneEl&&(this.videoPhoneEl.textContent=s.notify||V(s.from));const n=this.incallView.querySelector(".incall-avatar");n&&n.dataset.avatarPhone!==s.from&&(n.dataset.avatarPhone=s.from,n.innerHTML=this.renderAvatar(s.from)),s.state==="accepted"||s.state==="active"?(this.timerStarts.has(s.callId)||this.timerStarts.set(s.callId,Date.now()),this.startInCallTimer(s.callId)):this.stopInCallTimer(),this.currentView!=="incall"&&this.switchView("incall")}else this.currentView==="incall"&&(this.activeCallId=null,this.stopInCallTimer(),i.length>0?this.switchView("calls"):this.switchView("dialer"));i.length>0&&!s&&this.currentView==="dialer"&&this.switchView("calls"),this.renderCallsList(t)}renderCallsList(t){const e=this.callsView.querySelector(".calls-view");if(t.length===0){e.innerHTML=`
879
+ <div class="empty-state">
880
+ <div class="icon">&#128222;</div>
881
+ <p>Nenhuma chamada ativa</p>
882
+ </div>
883
+ `,this.clearAllTimers();return}e.innerHTML="";for(const a of t)e.appendChild(this.buildCallCard(a))}buildCallCard(t){const e=document.createElement("div");e.className=`call-card${t.state==="ended"?" ended":""}`,e.dataset.callId=t.callId;const a=t.state==="ringing"||t.state==="preaccepted",i=t.state==="calling",s=t.state==="accepted"||t.state==="active";e.innerHTML=`
884
+ <div class="call-card-top">
885
+ <div class="avatar" data-avatar-phone="${t.from}">${this.renderAvatar(t.from)}</div>
886
+ <div class="call-info">
887
+ ${t.notify?`<div class="call-name">${t.notify}</div>`:""}
888
+ <div class="call-phone">${V(t.from)}</div>
889
+ <div class="call-meta">
890
+ ${t.isVideo?T:k}
891
+ <span>${t.isVideo?"Video":"Audio"}${t.isGroup?" (Grupo)":""}</span>
892
+ </div>
893
+ </div>
894
+ <span class="state-badge ${t.state}">${this.stateLabel(t.state)}</span>
895
+ </div>
896
+ ${s?`<div class="timer" data-timer="${t.callId}">00:00</div>`:""}
897
+ ${a?`
898
+ <div class="call-actions">
899
+ <button class="btn btn-accept" data-accept="${t.callId}">
900
+ ${tt} Aceitar
901
+ </button>
902
+ <button class="btn btn-reject" data-reject="${t.callId}">
903
+ ${I} Rejeitar
904
+ </button>
905
+ </div>
906
+ `:""}
907
+ ${i||s?`
908
+ <div class="call-actions">
909
+ <button class="btn btn-reject" data-reject="${t.callId}" style="flex:1">
910
+ ${I} Encerrar
911
+ </button>
912
+ </div>
913
+ `:""}
914
+ `;const n=e.querySelector(`[data-accept="${t.callId}"]`);return n==null||n.addEventListener("click",()=>{this.config.onAccept(t.callId),this.config.onResume()}),e.querySelectorAll(`[data-reject="${t.callId}"]`).forEach(o=>o.addEventListener("click",()=>this.config.onReject(t.callId))),s&&(this.timerStarts.has(t.callId)||this.timerStarts.set(t.callId,Date.now()),this.startTimer(t.callId,e)),t.state==="ended"&&this.stopTimer(t.callId),e}stateLabel(t){switch(t){case"ringing":return"Chamando";case"calling":return"Ligando...";case"preaccepted":return"Conectando";case"accepted":return"Aceita";case"active":return"Ativa";case"ended":return"Encerrada";default:return t}}startTimer(t,e){this.stopTimer(t);const a=this.timerStarts.get(t)||Date.now(),i=e.querySelector(`[data-timer="${t}"]`);if(!i)return;const s=()=>{i.textContent=B(Date.now()-a)};s(),this.timers.set(t,setInterval(s,1e3))}stopTimer(t){const e=this.timers.get(t);e&&(clearInterval(e),this.timers.delete(t))}startInCallTimer(t){this.stopInCallTimer();const e=this.timerStarts.get(t)||Date.now(),a=()=>{const i=B(Date.now()-e);this.incallTimerEl.textContent=i,this.videoTimerEl&&(this.videoTimerEl.textContent=i)};a(),this.incallTimer=setInterval(a,1e3)}stopInCallTimer(){this.incallTimer&&(clearInterval(this.incallTimer),this.incallTimer=null),this.incallTimerEl.textContent="00:00"}showVideo(t){if(!this.videoContainer)return;this.videoContainer.style.display=t?"flex":"none";const e=this.incallView.querySelector(".audio-call-ui");e&&(e.style.display=t?"none":""),t&&(this.videoPhoneEl.textContent=this.incallPhoneEl.textContent,this.videoTimerEl.textContent=this.incallTimerEl.textContent)}getRemoteCanvas(){return this.remoteVideoCanvas}resetCamera(){this.cameraOn=!1,this.updateCameraButton()}updateMuteButtons(){this.incallMuteBtn.classList.toggle("muted",this.muted),this.incallMuteBtn.innerHTML=this.muted?R:S;const t=this.incallView.querySelector(".video-btn-mute");t&&(t.classList.toggle("muted",this.muted),t.innerHTML=this.muted?R:S)}updateCameraButton(){this.incallView.querySelectorAll(".video-btn-camera, .incall-btn-camera").forEach(e=>{e.classList.toggle("muted",!this.cameraOn),e.innerHTML=this.cameraOn?T:A})}showMakeCallError(t){const e=t.includes("PHONE_NOT_ON_WHATSAPP")?"Número não encontrado":"Erro ao realizar chamada",a=this.dialerInput.placeholder;this.dialerInput.value="",this.dialerInput.placeholder=e,this.dialerInput.classList.add("error"),setTimeout(()=>{this.dialerInput.placeholder=a,this.dialerInput.classList.remove("error")},3e3)}updateAudioLevel(t){const e=Math.min(100,Math.max(0,t*100));this.audioLevelBar.style.width=`${e}%`}renderAvatar(t){const e=this.pictureCache.get(t);return e?`<img src="${e}" class="avatar-img" />`:(!this.pictureRequested.has(t)&&t&&(this.pictureRequested.add(t),this.config.onRequestProfilePicture(t)),D)}updateProfilePicture(t,e){if(this.pictureCache.set(t,e),!e)return;const a=`<img src="${e}" class="avatar-img" />`;this.shadow.querySelectorAll(`[data-avatar-phone="${t}"]`).forEach(i=>{i.innerHTML=a})}clearAllTimers(){for(const t of this.timers.values())clearInterval(t);this.timers.clear(),this.timerStarts.clear(),this.stopInCallTimer()}destroy(){this.clearAllTimers(),this.host.remove()}}class P extends O{constructor(t){super(),this.widget=null,this.audioConfig={sampleRate:16e3,channels:1},this.connected=!1,this.micStarted=!1,this.cameraStarted=!1,this.destroyed=!1,this.sdkId="",this.contacts=[],this.opts=t,this.state=new X,this.audio=new z(this.audioConfig.sampleRate),this.video=new Y,this.ws=new j("https://staging-call.z-api.io",t.instanceId,t.getToken),this.ws.onJson=e=>this.handleJsonMessage(e),this.ws.onBinary=e=>this.handleBinaryMessage(e),this.ws.onConnectionChange=e=>this.handleConnectionChange(e),this.audio.onMicData=e=>{const a=new ArrayBuffer(1+e.byteLength);new Uint8Array(a)[0]=1,new Uint8Array(a,1).set(new Uint8Array(e)),this.ws.send(a)},this.audio.onAudioLevel=e=>{var a;this.emit("audio:level",e),(a=this.widget)==null||a.updateAudioLevel(e)},this.on("video:frame",e=>this.video.renderFrame(e)),this.video.onEncodedCameraFrame=(e,a,i,s,n,r)=>{const o=new ArrayBuffer(22+e.byteLength),l=new DataView(o);l.setUint8(0,3),l.setUint16(1,a,!0),l.setUint16(3,i,!0),l.setFloat64(5,s),l.setUint8(13,n?1:0),l.setFloat64(14,r),new Uint8Array(o,22).set(new Uint8Array(e)),this.ws.send(o)},this.video.onCameraFrame=(e,a,i)=>{const s=this.rgbaToNv12(new Uint8Array(e),a,i),n=new ArrayBuffer(5+s.byteLength),r=new DataView(n);r.setUint8(0,2),r.setUint16(1,a,!0),r.setUint16(3,i,!0),new Uint8Array(n,5).set(s),this.ws.send(n)},t.autoWidget!==!1&&(this.widget=new lt({position:t.position||"bottom-right",theme:t.theme||{},showVideoDialButton:t.showVideoDialButton===!0,onAccept:e=>this.accept(e),onReject:e=>this.reject(e),onMute:e=>this.mute(e),onCamera:e=>this.setCamera(e),onResume:()=>this.audio.resume(),onMakeCall:(e,a)=>this.makeCall(e,a),onRequestContacts:()=>this.requestContacts(),onRequestProfilePicture:e=>this.requestProfilePicture(e)}),this.video.setRemoteCanvas(this.widget.getRemoteCanvas())),this.ws.connect()}accept(t){console.log("[ZAPICall] >> call:accept",t),this.ws.sendJson({type:"call:accept",callId:t}),this.startMicIfNeeded()}reject(t){console.log("[ZAPICall] >> call:reject",t),this.ws.sendJson({type:"call:reject",callId:t})}mute(t){this.ws.sendJson({type:"call:mute",muted:t})}async setCamera(t){var e;this.ws.sendJson({type:"call:camera",enabled:t}),t?(await this.startCameraIfNeeded(),(e=this.widget)==null||e.showVideo(!0)):(this.video.stopCamera(),this.cameraStarted=!1)}isCameraActive(){return this.cameraStarted}makeCall(t,e=!1){var a,i;console.log("[ZAPICall] >> call:make",t,e?"(video)":"(audio)"),this.audio.resume(),this.ws.sendJson({type:"call:make",number:t,isVideo:e}),this.emit("call:make",t),(i=(a=this.opts).onMakeCall)==null||i.call(a,t)}getCalls(){return this.state.getAllCalls()}getActiveCall(){return this.state.getActiveCall()}isConnected(){return this.connected}getSdkId(){return this.sdkId}requestContacts(){this.ws.sendJson({type:"contacts:list"})}requestProfilePicture(t){this.ws.sendJson({type:"profile:picture",phone:t})}getContacts(){return this.contacts}destroy(){var t;this.destroyed||(this.destroyed=!0,this.ws.destroy(),this.audio.destroy(),this.video.destroy(),(t=this.widget)==null||t.destroy(),this.state.clear(),this.removeAllListeners())}handleJsonMessage(t){var e,a,i,s,n,r,o,l,p,u,b,d,m,f,g,C,E,y,w,M;switch(t.type){case"connected":this.sdkId=t.sdkId||"",t.audioConfig&&(t.audioConfig.sampleRate!==this.audioConfig.sampleRate&&(this.audio.destroy(),this.audio=new z(t.audioConfig.sampleRate),this.audio.onMicData=h=>{const v=new ArrayBuffer(1+h.byteLength);new Uint8Array(v)[0]=1,new Uint8Array(v,1).set(new Uint8Array(h)),this.ws.send(v)},this.audio.onAudioLevel=h=>{var v;this.emit("audio:level",h),(v=this.widget)==null||v.updateAudioLevel(h)}),this.audioConfig=t.audioConfig);break;case"call:incoming":{const h={callId:t.callId,from:t.from,notify:t.notify,isVideo:t.isVideo,isGroup:t.isGroup,state:t.state||"ringing",timestamp:t.timestamp};this.state.addCall(h),this.emit("call:incoming",h),(a=(e=this.opts).onIncomingCall)==null||a.call(e,h),(i=this.widget)==null||i.updateCalls(this.state.getAllCalls(),this.connected);break}case"call:outgoing":{const h={callId:t.callId,from:t.to,isVideo:t.isVideo,isGroup:!1,state:"calling",timestamp:Date.now()};this.state.addCall(h),this.emit("call:outgoing",h),(s=this.widget)==null||s.updateCalls(this.state.getAllCalls(),this.connected),this.startMicIfNeeded();break}case"call:state":{if(!this.state.getCall(t.callId))break;if(this.state.updateState(t.callId,t.state),this.emit("call:state",t.callId,t.state),t.state==="accepted"||t.state==="active"){this.startMicIfNeeded();const h=this.state.getCall(t.callId);h!=null&&h.isVideo?((n=this.widget)==null||n.showVideo(!0),this.startCameraIfNeeded()):(this.video.stopCamera(),this.cameraStarted=!1,(r=this.widget)==null||r.showVideo(!1))}(o=this.widget)==null||o.updateCalls(this.state.getAllCalls(),this.connected);break}case"call:terminated":{this.state.updateState(t.callId,"ended"),this.emit("call:terminated",t.callId,t.reason),(p=(l=this.opts).onCallTerminated)==null||p.call(l,t.callId),this.video.stopCamera(),this.video.clearCanvas(),this.cameraStarted=!1,(u=this.widget)==null||u.showVideo(!1),(b=this.widget)==null||b.resetCamera(),setTimeout(()=>{var h;this.state.removeCall(t.callId),(h=this.widget)==null||h.updateCalls(this.state.getAllCalls(),this.connected),this.state.hasActiveCalls()||(this.audio.stopMic(),this.micStarted=!1)},3e3),(d=this.widget)==null||d.updateCalls(this.state.getAllCalls(),this.connected);break}case"call:video-state":{this.state.updateIsVideo(t.callId,t.isVideo),this.emit("call:video-state",t.callId,t.isVideo);const h=this.state.getCall(t.callId);h&&(h.state==="accepted"||h.state==="active")&&(t.isVideo?(m=this.widget)==null||m.showVideo(!0):(this.video.stopCamera(),this.cameraStarted=!1,(f=this.widget)==null||f.showVideo(!1))),(g=this.widget)==null||g.updateCalls(this.state.getAllCalls(),this.connected);break}case"call:claimed":{this.state.removeCall(t.callId),this.emit("call:claimed",t.callId),(C=this.widget)==null||C.updateCalls(this.state.getAllCalls(),this.connected);break}case"call:dismissed":{this.state.removeCall(t.callId),this.emit("call:dismissed",t.callId),(E=this.widget)==null||E.updateCalls(this.state.getAllCalls(),this.connected);break}case"contacts:list":{this.contacts=t.contacts||[],this.emit("contacts:list",this.contacts),(y=this.widget)==null||y.updateContacts(this.contacts);break}case"profile:picture":{this.emit("profile:picture",t.phone,t.url),(w=this.widget)==null||w.updateProfilePicture(t.phone,t.url);break}case"pong":break;case"error":t.command==="call:make"&&(this.emit("call:make:error","",t.message||"UNKNOWN_ERROR"),(M=this.widget)==null||M.showMakeCallError(t.message||"UNKNOWN_ERROR")),this.emit("error",new Error(`${t.command}: ${t.message}`));break}}handleBinaryMessage(t){if(t.byteLength<2)return;const e=new DataView(t),a=e.getUint8(0);if(a===1&&t.byteLength>=16){const i=e.getUint16(1,!0),s=e.getUint16(3,!0),n=e.getUint8(5),r=e.getUint8(6),o=e.getUint8(7)!==0,l=e.getFloat64(8,!1),p=t.slice(16),u={width:i,height:s,format:n,orientation:r,isKeyFrame:o,timestamp:l,data:p};this.emit("video:frame",u);return}if(a===0&&t.byteLength%2===1){this.audio.playPcm(t.slice(1));return}this.audio.playPcm(t)}handleConnectionChange(t){var e,a,i;this.connected=t,t?this.emit("connected"):this.emit("disconnected"),(a=(e=this.opts).onConnectionChange)==null||a.call(e,t),(i=this.widget)==null||i.updateCalls(this.state.getAllCalls(),t)}async startCameraIfNeeded(){if(!(this.cameraStarted||this.destroyed)){this.cameraStarted=!0;try{const t=document.createElement("video");t.style.display="none",document.body.appendChild(t),await this.video.startCamera(t,320,240,30)}catch(t){console.warn("[ZAPICall] Camera start failed:",t),this.cameraStarted=!1}}}rgbaToNv12(t,e,a){const i=e*a,s=new Uint8Array(i+(i>>1));for(let r=0;r<a;r++)for(let o=0;o<e;o++){const l=(r*e+o)*4;s[r*e+o]=(66*t[l]+129*t[l+1]+25*t[l+2]+128>>8)+16}let n=i;for(let r=0;r<a;r+=2)for(let o=0;o<e;o+=2){const l=(r*e+o)*4;s[n++]=(-38*t[l]-74*t[l+1]+112*t[l+2]+128>>8)+128,s[n++]=(112*t[l]-94*t[l+1]-18*t[l+2]+128>>8)+128}return s}async startMicIfNeeded(){if(!(this.micStarted||this.destroyed)){this.micStarted=!0;try{await this.audio.startMic()}catch(t){this.emit("error",t instanceof Error?t:new Error(String(t)))}}}}const dt={NV12:0,I420:1,RGB24:2,RGBA:3,H264:100},ht={Unknown:0,Normal:1,Rotate90:2,Rotate180:3,Rotate270:4};function H(c){return new P(c)}const pt={init:H,Client:P};return x.VideoFormat=dt,x.VideoOrientation=ht,x.ZAPICall=pt,x.ZAPICallClient=P,x.init=H,Object.defineProperty(x,Symbol.toStringTag,{value:"Module"}),x})({});
915
+ //# sourceMappingURL=z-api-call.global.js.map