@chaterafrikang/call-ui 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1743 @@
1
+ // src/CallUI.jsx
2
+ import { useEffect as useEffect5 } from "react";
3
+
4
+ // src/callStyles.js
5
+ var CALL_STYLE_ID = "cafcall-styles";
6
+ var callStyles = `
7
+ .cafcall{
8
+ --caf-primary:#2563EB; --caf-primary-d:#1D4ED8;
9
+ --caf-danger:#EF4444; --caf-ok:#16A34A; --caf-warn:#F59E0B;
10
+ --caf-ink:#0B2545; --caf-muted:#6B7280; --caf-line:#E5E7EB;
11
+ --caf-bg:#F4F6FB; --caf-card:#FFFFFF; --caf-stage:#0B1220;
12
+ --caf-radius:16px;
13
+ position:fixed; inset:0; z-index:2147483000;
14
+ display:flex; flex-direction:column;
15
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
16
+ color:var(--caf-ink); background:var(--caf-bg);
17
+ }
18
+ .cafcall *{ box-sizing:border-box; }
19
+ /* Icons have a viewBox but no intrinsic size \u2014 constrain them everywhere or they fill their box. */
20
+ .cafcall svg{ width:18px; height:18px; flex:0 0 auto; display:inline-block; vertical-align:middle; }
21
+ .cafcall-pill svg{ width:14px; height:14px; }
22
+ .cafcall-btn svg{ width:18px; height:18px; }
23
+ .cafcall-meter svg{ width:16px; height:16px; color:var(--caf-muted); }
24
+ .cafcall-seg .cafcall-btn svg{ width:20px; height:20px; }
25
+ .cafcall-icon-lg svg{ width:40px; height:40px; }
26
+ .cafcall-peerchip svg{ width:14px; height:14px; }
27
+ .cafcall-embed{ position:relative; inset:auto; height:100%; width:100%; border-radius:var(--caf-radius); overflow:hidden; }
28
+
29
+ /* Header */
30
+ .cafcall-head{ display:flex; align-items:center; justify-content:space-between; gap:12px;
31
+ padding:12px 16px; background:var(--caf-card); border-bottom:1px solid var(--caf-line); flex:0 0 auto; }
32
+ .cafcall-brand{ display:flex; align-items:center; gap:10px; min-width:0; }
33
+ .cafcall-logo{ width:34px; height:34px; border-radius:9px; background:var(--caf-primary); color:#fff;
34
+ display:flex; align-items:center; justify-content:center; font-weight:800; font-size:13px; flex:0 0 auto; }
35
+ .cafcall-title{ font-weight:700; font-size:14px; line-height:1.2; }
36
+ .cafcall-sub{ font-size:12px; color:var(--caf-muted); }
37
+ .cafcall-head-right{ display:flex; align-items:center; gap:8px; }
38
+
39
+ /* Pills / badges */
40
+ .cafcall-pill{ display:inline-flex; align-items:center; gap:6px; padding:4px 10px; border-radius:999px;
41
+ font-size:12px; font-weight:600; background:#EEF2F7; color:var(--caf-muted); border:1px solid transparent; }
42
+ .cafcall-pill.ok{ background:#DCFCE7; color:#166534; }
43
+ .cafcall-pill.warn{ background:#FEF3C7; color:#92400E; }
44
+ .cafcall-pill.enc{ background:#F1F5F9; color:#475569; }
45
+ .cafcall-dot{ width:7px; height:7px; border-radius:50%; background:currentColor; }
46
+
47
+ /* Body layout: stage + optional side panel */
48
+ .cafcall-body{ flex:1 1 auto; display:flex; min-height:0; }
49
+ .cafcall-main{ flex:1 1 auto; position:relative; min-width:0; display:flex; flex-direction:column; }
50
+ .cafcall-side{ width:360px; flex:0 0 360px; background:var(--caf-bg); border-left:1px solid var(--caf-line);
51
+ padding:20px; overflow-y:auto; }
52
+ /* Mobile: stack the side panel BELOW the main content (don't hide it \u2014 it holds Join / settings). */
53
+ @media(max-width:820px){
54
+ .cafcall-body{ flex-direction:column; overflow-y:auto; }
55
+ .cafcall-main{ flex:0 0 auto; }
56
+ .cafcall-main.cafcall-center{ padding:20px 16px; }
57
+ .cafcall-side{ width:100%; flex:0 0 auto; border-left:0; border-top:1px solid var(--caf-line); }
58
+ }
59
+
60
+ /* Stage (active video) */
61
+ .cafcall-stage{ flex:1 1 auto; position:relative; background:var(--caf-stage); overflow:hidden; }
62
+ .cafcall-stage video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; background:#0B1220; }
63
+ .cafcall-selftile{ position:absolute; right:16px; bottom:16px; width:210px; max-width:34vw; aspect-ratio:16/10;
64
+ border-radius:12px; overflow:hidden; background:#111827; box-shadow:0 8px 24px rgba(0,0,0,.35);
65
+ border:2px solid rgba(255,255,255,.12); }
66
+ .cafcall-selftile video{ transform:scaleX(-1); }
67
+ .cafcall-selftile .cafcall-name{ position:absolute; left:8px; bottom:6px; color:#fff; font-size:12px;
68
+ text-shadow:0 1px 2px rgba(0,0,0,.6); display:inline-flex; align-items:center; gap:5px; }
69
+ .cafcall-selftile .cafcall-name svg{ width:13px; height:13px; }
70
+ .cafcall-stage-top{ position:absolute; top:14px; left:14px; right:14px; display:grid;
71
+ grid-template-columns:1fr auto 1fr; align-items:start; gap:10px; pointer-events:none; }
72
+ .cafcall-stage-top .cafcall-pill,.cafcall-stage-top .cafcall-iconbtn{ pointer-events:auto; }
73
+ .cafcall-stack{ display:flex; flex-direction:column; gap:10px; justify-self:start; }
74
+ .cafcall-titlechip{ display:inline-flex; align-items:center; gap:10px; color:#fff; }
75
+ .cafcall-titlechip .cafcall-logo{ width:36px; height:36px; }
76
+ .cafcall-titlechip b{ font-size:14px; line-height:1.2; display:block; }
77
+ .cafcall-titlechip small{ font-size:12px; opacity:.85; }
78
+ .cafcall-topmid{ justify-self:center; }
79
+ .cafcall-topright{ justify-self:end; display:flex; flex-direction:column; align-items:flex-end; gap:10px; }
80
+ .cafcall-topright .row{ display:flex; align-items:center; gap:8px; }
81
+ .cafcall-iconbtn{ width:34px; height:34px; border-radius:50%; border:0; cursor:pointer; color:#fff;
82
+ background:rgba(17,24,39,.55); backdrop-filter:blur(6px); display:flex; align-items:center; justify-content:center; }
83
+ .cafcall-iconbtn:hover{ background:rgba(17,24,39,.8); }
84
+ .cafcall-peerchip{ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; border-radius:999px;
85
+ background:rgba(17,24,39,.6); color:#fff; font-size:12px; backdrop-filter:blur(6px); width:fit-content; }
86
+ .cafcall-peerchip img{ width:22px; height:22px; border-radius:50%; object-fit:cover; }
87
+ @media(max-width:640px){ .cafcall-stage-top{ grid-template-columns:1fr auto; } .cafcall-topmid{ display:none; } .cafcall-titlechip small{ display:none; } }
88
+
89
+ /* Voice stage */
90
+ .cafcall-voice{ flex:1 1 auto; display:flex; flex-direction:column; align-items:center; justify-content:center;
91
+ gap:26px; padding:24px; text-align:center; }
92
+ .cafcall-voice-row{ display:flex; align-items:center; justify-content:center; gap:64px; flex-wrap:wrap; }
93
+ .cafcall-part{ display:flex; flex-direction:column; align-items:center; gap:10px; }
94
+ .cafcall-avatar{ border-radius:50%; object-fit:cover; background:#DCE8FF; color:var(--caf-primary);
95
+ display:flex; align-items:center; justify-content:center; font-weight:700; }
96
+ .cafcall-ring{ padding:5px; border-radius:50%; border:2px solid transparent; }
97
+ .cafcall-ring.speaking{ border-color:var(--caf-primary); box-shadow:0 0 0 6px rgba(37,99,235,.12); }
98
+ .cafcall-pname{ font-weight:700; font-size:16px; }
99
+ .cafcall-prole{ font-size:13px; color:var(--caf-muted); }
100
+ .cafcall-wave{ display:flex; align-items:flex-end; gap:3px; height:22px; }
101
+ .cafcall-wave i{ width:3px; background:var(--caf-primary); border-radius:2px; animation:cafwave 1s infinite ease-in-out; }
102
+ @keyframes cafwave{ 0%,100%{ height:5px } 50%{ height:20px } }
103
+
104
+ /* Pulse (calling / incoming) */
105
+ .cafcall-pulse{ position:relative; display:flex; align-items:center; justify-content:center; }
106
+ .cafcall-pulse::before,.cafcall-pulse::after{ content:""; position:absolute; border-radius:50%;
107
+ border:2px solid var(--caf-primary); opacity:0; animation:cafpulse 2.4s infinite ease-out; }
108
+ .cafcall-pulse::after{ animation-delay:1.2s; }
109
+ @keyframes cafpulse{ 0%{ width:100%; height:100%; opacity:.5 } 100%{ width:180%; height:180%; opacity:0 } }
110
+ .cafcall-center{ flex:1 1 auto; display:flex; flex-direction:column; align-items:center; justify-content:center;
111
+ gap:18px; padding:24px; text-align:center; }
112
+ .cafcall-h{ font-size:24px; font-weight:800; }
113
+ .cafcall-p{ color:var(--caf-muted); max-width:420px; }
114
+ .cafcall-dots{ display:inline-flex; gap:6px; }
115
+ .cafcall-dots i{ width:7px; height:7px; border-radius:50%; background:var(--caf-primary); opacity:.35;
116
+ animation:cafdot 1.4s infinite; }
117
+ .cafcall-dots i:nth-child(2){ animation-delay:.2s } .cafcall-dots i:nth-child(3){ animation-delay:.4s }
118
+ @keyframes cafdot{ 0%,100%{ opacity:.25; transform:translateY(0) } 50%{ opacity:1; transform:translateY(-3px) } }
119
+
120
+ /* Control bar \u2014 three zones (left \xB7 center \xB7 right) so the main controls stay centred */
121
+ .cafcall-controls{ flex:0 0 auto; display:grid; grid-template-columns:1fr auto 1fr; align-items:center;
122
+ gap:14px; padding:16px 22px; background:var(--caf-card); border-top:1px solid var(--caf-line); }
123
+ .cafcall-controls-l{ justify-self:start; display:flex; gap:14px; }
124
+ .cafcall-controls-c{ justify-self:center; display:flex; gap:14px; }
125
+ .cafcall-controls-r{ justify-self:end; display:flex; gap:14px; }
126
+ /* Dark variant for the video stage */
127
+ .cafcall-controls.dark{ background:#111418; border-top-color:rgba(255,255,255,.08); }
128
+ .cafcall-controls.dark .cafcall-ctl{ color:#9aa4b2; }
129
+ .cafcall-controls.dark .cafcall-ctl .ic{ background:#252A31; border-color:transparent; color:#E5E7EB; }
130
+ .cafcall-controls.dark .cafcall-ctl:hover .ic{ background:#323842; }
131
+ .cafcall-controls.dark .cafcall-ctl.danger .ic{ background:var(--caf-danger); color:#fff; }
132
+ .cafcall-controls.dark .cafcall-ctl.on .ic{ background:var(--caf-danger); color:#fff; }
133
+ .cafcall-controls.dark .cafcall-ctl.active .ic{ background:#323842; color:#fff; }
134
+ /* Mobile: match the Figma \u2014 ONE light row: Mute \xB7 Camera \xB7 End(red,centre) \xB7 Speaker \xB7 More.
135
+ People/Chat/Share/Settings fold into the More bottom-sheet. */
136
+ @media(max-width:640px){
137
+ .cafcall-controls{ display:flex; flex-wrap:nowrap; justify-content:space-around; align-items:flex-start; gap:2px; padding:14px 6px 22px; }
138
+ .cafcall-controls.dark{ background:var(--caf-bg); border-top:0; }
139
+ .cafcall-controls.dark .cafcall-ctl{ color:var(--caf-muted); }
140
+ .cafcall-controls.dark .cafcall-ctl .ic{ background:#E7EBF2; color:var(--caf-ink); border:0; }
141
+ .cafcall-controls.dark .cafcall-ctl-end .ic{ background:var(--caf-danger); color:#fff; }
142
+ .cafcall-controls-l,.cafcall-controls-c,.cafcall-controls-r{ display:contents; }
143
+ .cafcall-ctl-people,.cafcall-ctl-chat,.cafcall-ctl-share,.cafcall-ctl-settings{ display:none; }
144
+ .cafcall-ctl .ic{ width:54px; height:54px; }
145
+ .cafcall-ctl-mute{ order:1 } .cafcall-ctl-camera{ order:2 } .cafcall-ctl-end{ order:3 } .cafcall-ctl-speaker{ order:4 } .cafcall-ctl-more{ order:5 }
146
+ }
147
+
148
+ /* Mobile refinements for the calmer screens */
149
+ @media(max-width:640px){
150
+ .cafcall-voice-row{ gap:30px; }
151
+ .cafcall-voice .cafcall-ring .cafcall-avatar{ width:96px!important; height:96px!important; }
152
+ .cafcall-stats{ grid-template-columns:repeat(2,1fr)!important; }
153
+ .cafcall-h{ font-size:21px; }
154
+ .cafcall-pulse .cafcall-avatar{ width:104px!important; height:104px!important; }
155
+ .cafcall-infostrip{ gap:14px; padding:12px 14px; flex-wrap:wrap; justify-content:center; }
156
+ }
157
+
158
+ /* Floating quick-controls over the video stage (mobile only, per Figma) */
159
+ .cafcall-floatctl{ display:none; }
160
+ @media(max-width:640px){
161
+ .cafcall-floatctl{ display:flex; position:absolute; left:0; right:0; bottom:14px; z-index:4;
162
+ align-items:center; justify-content:center; gap:20px; pointer-events:none; }
163
+ .cafcall-floatctl > *{ pointer-events:auto; }
164
+ }
165
+ .cafcall-floatbtn{ width:50px; height:50px; border-radius:50%; border:0; cursor:pointer; color:#fff;
166
+ background:rgba(17,24,39,.55); backdrop-filter:blur(8px); display:flex; align-items:center; justify-content:center; }
167
+ .cafcall-floatbtn svg{ width:22px; height:22px; }
168
+ .cafcall-floatbtn.end{ background:var(--caf-danger); width:58px; height:58px; }
169
+ .cafcall-selftile{ z-index:5; }
170
+
171
+ /* Mobile "More" bottom sheet */
172
+ .cafcall-sheet-scrim{ position:fixed; inset:0; z-index:2147483001; background:rgba(0,0,0,.35); display:flex; align-items:flex-end; }
173
+ .cafcall-sheet{ width:100%; background:var(--caf-card); border-radius:18px 18px 0 0; padding:8px 16px 26px;
174
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; }
175
+ .cafcall-sheet-grab{ width:40px; height:4px; border-radius:2px; background:#D1D5DB; margin:8px auto 16px; }
176
+ .cafcall-sheet-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:12px; }
177
+ .cafcall-sheet-item{ display:flex; flex-direction:column; align-items:center; gap:8px; background:none; border:0;
178
+ cursor:pointer; font:inherit; font-size:12px; color:var(--caf-ink); }
179
+ .cafcall-sheet-item .ic{ width:58px; height:58px; border-radius:16px; background:#F1F3F7; display:flex;
180
+ align-items:center; justify-content:center; color:var(--caf-ink); }
181
+ .cafcall-sheet-item .ic.on{ background:#DCE8FF; color:var(--caf-primary); }
182
+ .cafcall-sheet-item svg{ width:22px; height:22px; }
183
+ .cafcall-ctl{ display:flex; flex-direction:column; align-items:center; gap:5px; background:none; border:0;
184
+ cursor:pointer; color:var(--caf-muted); font-size:11px; font-family:inherit; }
185
+ .cafcall-ctl .ic{ width:52px; height:52px; border-radius:50%; background:#F1F3F7; border:1px solid var(--caf-line);
186
+ display:flex; align-items:center; justify-content:center; color:var(--caf-ink); transition:.15s; }
187
+ .cafcall-ctl:hover .ic{ background:#E7EBF2; }
188
+ .cafcall-ctl.active .ic{ background:#DCE8FF; color:var(--caf-primary); border-color:transparent; }
189
+ .cafcall-ctl.danger .ic{ background:var(--caf-danger); color:#fff; border-color:transparent; }
190
+ .cafcall-ctl.on .ic{ background:#FEE2E2; color:var(--caf-danger); border-color:transparent; }
191
+ .cafcall-ctl:disabled{ opacity:.45; cursor:not-allowed; }
192
+ .cafcall-ctl svg{ width:22px; height:22px; }
193
+
194
+ /* Cards / side panel */
195
+ .cafcall-card{ background:var(--caf-card); border:1px solid var(--caf-line); border-radius:14px; padding:14px; }
196
+ .cafcall-card + .cafcall-card{ margin-top:12px; }
197
+ .cafcall-note{ border-radius:12px; padding:12px 14px; font-size:13px; line-height:1.45; }
198
+ .cafcall-note.info{ background:#EFF6FF; color:#1E40AF; } .cafcall-note.ok{ background:#DCFCE7; color:#166534; }
199
+ .cafcall-note.warn{ background:#FEF3C7; color:#92400E; } .cafcall-note.err{ background:#FEF2F2; color:#B91C1C; }
200
+ .cafcall-label{ font-size:11px; letter-spacing:.04em; text-transform:uppercase; color:var(--caf-muted); margin:14px 0 8px; }
201
+ .cafcall-person{ display:flex; align-items:center; gap:10px; }
202
+ .cafcall-person .cafcall-avatar{ width:40px; height:40px; font-size:14px; }
203
+ .cafcall-person .nm{ font-weight:600; font-size:14px; } .cafcall-person .rl{ font-size:12px; color:var(--caf-muted); }
204
+
205
+ /* Buttons */
206
+ .cafcall-btn{ display:inline-flex; align-items:center; justify-content:center; gap:8px; width:100%;
207
+ padding:12px 16px; border-radius:12px; font-weight:600; font-size:14px; cursor:pointer; border:1px solid transparent;
208
+ font-family:inherit; }
209
+ .cafcall-btn.pri{ background:var(--caf-primary); color:#fff; } .cafcall-btn.pri:hover{ background:var(--caf-primary-d); }
210
+ .cafcall-btn.ghost{ background:var(--caf-card); color:var(--caf-ink); border-color:var(--caf-line); }
211
+ .cafcall-btn.dng{ background:var(--caf-danger); color:#fff; }
212
+ .cafcall-btn.dngghost{ background:#FEF2F2; color:#B91C1C; border-color:#FECACA; }
213
+ .cafcall-btn + .cafcall-btn{ margin-top:10px; }
214
+ .cafcall-seg{ display:flex; gap:8px; }
215
+ .cafcall-seg .cafcall-btn{ margin:0; }
216
+ .cafcall-seg .cafcall-btn.sel{ background:#DCE8FF; color:var(--caf-primary); border-color:transparent; }
217
+
218
+ /* Preview (lobby) */
219
+ .cafcall-preview{ width:min(560px,100%); }
220
+ .cafcall-preview .frame{ position:relative; aspect-ratio:16/10; border-radius:14px; overflow:hidden; background:#111827; }
221
+ .cafcall-preview video{ width:100%; height:100%; object-fit:cover; transform:scaleX(-1); }
222
+ .cafcall-meter{ margin-top:10px; display:flex; align-items:center; gap:10px; background:var(--caf-card);
223
+ border:1px solid var(--caf-line); border-radius:10px; padding:10px 12px; font-size:13px; }
224
+ .cafcall-meter .bar{ flex:1; height:6px; border-radius:3px; background:#E5E7EB; overflow:hidden; }
225
+ .cafcall-meter .bar>i{ display:block; height:100%; background:var(--caf-primary); width:0; transition:width .1s; }
226
+
227
+ /* Device pickers */
228
+ .cafcall-picker{ display:flex; align-items:center; gap:10px; background:var(--caf-card); border:1px solid var(--caf-line);
229
+ border-radius:10px; padding:8px 10px; margin-bottom:8px; }
230
+ .cafcall-picker .ic{ color:var(--caf-muted); display:flex; }
231
+ .cafcall-picker select{ flex:1; border:0; background:none; font:inherit; font-size:13px; color:var(--caf-ink); outline:none; cursor:pointer; }
232
+
233
+ /* Toggle switch */
234
+ .cafcall-toggle{ width:40px; height:24px; border-radius:999px; border:0; background:#CBD5E1; cursor:pointer; padding:0; position:relative; flex:0 0 auto; transition:.15s; }
235
+ .cafcall-toggle.on{ background:var(--caf-primary); }
236
+ .cafcall-toggle i{ position:absolute; top:3px; left:3px; width:18px; height:18px; border-radius:50%; background:#fff; transition:.15s; }
237
+ .cafcall-toggle.on i{ left:19px; }
238
+
239
+ /* Preference / answer rows */
240
+ .cafcall-prefrow{ display:flex; align-items:center; justify-content:space-between; gap:12px; padding:9px 0; border-bottom:1px solid var(--caf-line); }
241
+ .cafcall-prefrow:last-of-type{ border-bottom:0; }
242
+ .cafcall-prefrow .t{ font-size:13px; font-weight:600; } .cafcall-prefrow .s{ font-size:12px; color:var(--caf-muted); }
243
+ .cafcall-answerrow{ display:flex; align-items:center; gap:10px; background:var(--caf-card); border:1px solid var(--caf-line);
244
+ border-radius:12px; padding:10px 12px; margin-bottom:8px; width:100%; }
245
+ .cafcall-answerrow .ic{ width:34px; height:34px; border-radius:9px; background:#EFF3FC; color:var(--caf-primary);
246
+ display:flex; align-items:center; justify-content:center; flex:0 0 auto; }
247
+ .cafcall-answerrow .t{ font-size:13px; font-weight:600; } .cafcall-answerrow .s{ font-size:12px; color:var(--caf-muted); }
248
+ .cafcall-answerrow.as-btn{ cursor:pointer; font-family:inherit; text-align:left; }
249
+ .cafcall-answerrow.as-btn:hover{ background:#F9FAFB; }
250
+
251
+ /* Incoming call info strip */
252
+ .cafcall-infostrip{ display:flex; gap:22px; background:var(--caf-card); border:1px solid var(--caf-line);
253
+ border-radius:12px; padding:12px 20px; }
254
+ .cafcall-infostrip .k{ font-size:11px; color:var(--caf-muted); text-transform:uppercase; letter-spacing:.03em; }
255
+ .cafcall-infostrip .v{ font-weight:700; font-size:14px; margin-top:2px; }
256
+
257
+ /* Ended stats */
258
+ .cafcall-stats{ display:grid; grid-template-columns:repeat(4,1fr); gap:10px; margin-top:16px; }
259
+ .cafcall-stat{ background:var(--caf-card); border:1px solid var(--caf-line); border-radius:12px; padding:12px; text-align:center; }
260
+ .cafcall-stat .v{ font-weight:800; font-size:18px; } .cafcall-stat .k{ font-size:12px; color:var(--caf-muted); }
261
+ .cafcall-stars{ display:flex; gap:6px; }
262
+ .cafcall-star{ background:none; border:0; cursor:pointer; font-size:26px; line-height:1; color:#D1D5DB; }
263
+ .cafcall-star.on{ color:#F59E0B; }
264
+
265
+ .cafcall-icon-lg{ width:84px; height:84px; border-radius:50%; display:flex; align-items:center; justify-content:center; }
266
+ .cafcall-spin{ animation:cafspin 1s linear infinite; } @keyframes cafspin{ to{ transform:rotate(360deg) } }
267
+ .cafcall-hide-sm{ } @media(max-width:820px){ .cafcall-hide-sm{ display:none!important; } }
268
+
269
+ /* In-call side drawer (People / Chat) */
270
+ .cafcall-drawer{ position:absolute; top:0; right:0; bottom:0; width:340px; max-width:86%; z-index:6;
271
+ background:var(--caf-card); border-left:1px solid var(--caf-line); display:flex; flex-direction:column;
272
+ box-shadow:-8px 0 24px rgba(0,0,0,.12); }
273
+ .cafcall-drawer-head{ display:flex; align-items:center; justify-content:space-between; padding:14px 16px;
274
+ border-bottom:1px solid var(--caf-line); font-size:15px; }
275
+ .cafcall-drawer-head .cafcall-iconbtn{ width:30px; height:30px; font-size:14px; }
276
+ .cafcall-drawer-body{ flex:1 1 auto; overflow-y:auto; padding:10px 16px; }
277
+ .cafcall-chat{ display:flex; flex-direction:column; gap:8px; }
278
+ .cafcall-msg{ align-self:flex-start; max-width:82%; padding:8px 11px; border-radius:12px; font-size:13px;
279
+ background:#F1F3F7; color:var(--caf-ink); border-bottom-left-radius:4px; }
280
+ .cafcall-msg.mine{ align-self:flex-end; background:var(--caf-primary); color:#fff; border-bottom-left-radius:12px; border-bottom-right-radius:4px; }
281
+ .cafcall-chat-compose{ display:flex; gap:8px; padding:12px 14px; border-top:1px solid var(--caf-line); }
282
+ .cafcall-chat-compose input{ flex:1; border:1px solid var(--caf-line); border-radius:10px; padding:9px 11px; font:inherit; font-size:13px; outline:none; }
283
+ .cafcall-chat-compose input:focus{ border-color:var(--caf-primary); }
284
+
285
+ /* Settings panel (wider drawer with tabs + footer) */
286
+ .cafcall-settings{ width:400px; max-width:92%; }
287
+ .cafcall-set-badge{ width:34px; height:34px; border-radius:9px; background:#EFF3FC; color:var(--caf-primary);
288
+ display:flex; align-items:center; justify-content:center; }
289
+ .cafcall-tabs{ display:flex; gap:6px; padding:0 12px; border-bottom:1px solid var(--caf-line); overflow-x:auto; }
290
+ .cafcall-tab{ flex:0 0 auto; background:none; border:0; border-bottom:2px solid transparent; cursor:pointer;
291
+ padding:11px 8px; font:inherit; font-size:13px; font-weight:600; color:var(--caf-muted); }
292
+ .cafcall-tab.on{ color:var(--caf-primary); border-bottom-color:var(--caf-primary); }
293
+ .cafcall-sliderrow{ display:flex; align-items:center; gap:10px; background:var(--caf-card); border:1px solid var(--caf-line);
294
+ border-radius:10px; padding:10px 12px; margin-bottom:8px; font-size:13px; }
295
+ .cafcall-sliderrow svg{ width:18px; height:18px; color:var(--caf-muted); flex:0 0 auto; }
296
+ .cafcall-sliderrow span{ flex:0 0 auto; }
297
+ .cafcall-sliderrow input[type=range]{ flex:1; accent-color:var(--caf-primary); }
298
+ .cafcall-settings-foot{ display:flex; align-items:center; gap:10px; padding:12px 16px; border-top:1px solid var(--caf-line); }
299
+ .cafcall-settings-foot .cafcall-pill{ margin-right:auto; }
300
+
301
+ /* Screen-share banner */
302
+ .cafcall-sharebar{ position:absolute; top:64px; left:50%; transform:translateX(-50%); z-index:3;
303
+ display:inline-flex; align-items:center; gap:8px; background:rgba(37,99,235,.92); color:#fff;
304
+ padding:7px 14px; border-radius:999px; font-size:13px; font-weight:600; box-shadow:0 6px 18px rgba(0,0,0,.3); }
305
+ .cafcall-sharebar svg{ width:16px; height:16px; }
306
+
307
+ /* Minimized floating bar */
308
+ .cafcall-mini{ position:fixed; right:20px; bottom:20px; z-index:2147483000; display:flex; align-items:center; gap:12px;
309
+ background:#111418; color:#fff; border-radius:14px; padding:10px 14px; box-shadow:0 12px 32px rgba(0,0,0,.35);
310
+ font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif; }
311
+ .cafcall-mini *{ box-sizing:border-box; }
312
+ .cafcall-mini svg{ width:18px; height:18px; }
313
+ .cafcall-mini-expand{ width:34px; height:34px; border-radius:9px; border:0; background:#252A31; color:#fff; cursor:pointer;
314
+ display:flex; align-items:center; justify-content:center; }
315
+ .cafcall-mini-info .t{ font-size:13px; font-weight:600; }
316
+ .cafcall-mini-info .s{ font-size:12px; color:#9aa4b2; display:flex; align-items:center; gap:5px; }
317
+ .cafcall-mini-ctls{ display:flex; gap:8px; }
318
+ .cafcall-mini-ctls .cafcall-ctl .ic{ width:38px; height:38px; background:#252A31; color:#E5E7EB; border:0; }
319
+ .cafcall-mini-ctls .cafcall-ctl.danger .ic{ background:var(--caf-danger); color:#fff; }
320
+ .cafcall-mini-ctls .cafcall-ctl.on .ic{ background:var(--caf-danger); color:#fff; }
321
+ .cafcall-mini-ctls .cafcall-ctl span:last-child{ display:none; }
322
+ `;
323
+ function injectCallStyles(doc = document) {
324
+ let el = doc.getElementById(CALL_STYLE_ID);
325
+ if (!el) {
326
+ el = doc.createElement("style");
327
+ el.id = CALL_STYLE_ID;
328
+ doc.head.appendChild(el);
329
+ }
330
+ if (el.textContent !== callStyles) el.textContent = callStyles;
331
+ }
332
+
333
+ // src/useCall.js
334
+ import { useEffect, useRef, useState, useCallback } from "react";
335
+ function useCall(convo, { startInLobby = false } = {}) {
336
+ const [phase, setPhase] = useState("idle");
337
+ const [callType, setCallType] = useState("video");
338
+ const [peerId, setPeerId] = useState(null);
339
+ const [muted, setMuted] = useState(false);
340
+ const [cameraOn, setCameraOn] = useState(true);
341
+ const [peerMuted, setPeerMuted] = useState(false);
342
+ const [peerCameraOff, setPeerCameraOff] = useState(false);
343
+ const [error, setError] = useState("");
344
+ const [endedInfo, setEndedInfo] = useState(null);
345
+ const [elapsed, setElapsed] = useState(0);
346
+ const [minimized, setMinimized] = useState(false);
347
+ const [sharing, setSharing] = useState(false);
348
+ const [panel, setPanel] = useState(null);
349
+ const [moreOpen, setMoreOpen] = useState(false);
350
+ const [messages, setMessages] = useState([]);
351
+ const [speakerId, setSpeakerId] = useState("");
352
+ const [mirror, setMirror] = useState(true);
353
+ const [volume, setVolume] = useState(1);
354
+ const localStream = useRef(null);
355
+ const remoteStream = useRef(null);
356
+ const startedAt = useRef(null);
357
+ const reconnectTimer = useRef(null);
358
+ const phaseRef = useRef(phase);
359
+ phaseRef.current = phase;
360
+ useEffect(() => {
361
+ if (phase !== "active" && phase !== "reconnecting") return;
362
+ if (startedAt.current == null) startedAt.current = Date.now();
363
+ const id = setInterval(() => setElapsed(Math.floor((Date.now() - startedAt.current) / 1e3)), 1e3);
364
+ return () => clearInterval(id);
365
+ }, [phase]);
366
+ const grabStreams = useCallback((payload) => {
367
+ localStream.current = payload?.localStream ?? convo?.getLocalStream?.() ?? localStream.current;
368
+ let rs = payload?.remoteStream;
369
+ if (!rs && convo?.getRemoteStream && payload?.remoteUserId) {
370
+ rs = convo.getRemoteStream(payload.remoteUserId) ?? convo.getRemoteStream(String(payload.remoteUserId));
371
+ }
372
+ if (!rs && convo?.getAllRemoteStreams) {
373
+ const m = convo.getAllRemoteStreams();
374
+ rs = m && (m.values ? [...m.values()][0] : Object.values(m)[0]);
375
+ }
376
+ remoteStream.current = rs ?? remoteStream.current;
377
+ }, [convo]);
378
+ useEffect(() => {
379
+ if (!convo) return;
380
+ const offs = [];
381
+ const on = (ev, fn) => {
382
+ convo.on(ev, fn);
383
+ offs.push(() => convo.off?.(ev, fn));
384
+ };
385
+ on("call_invite", (e) => {
386
+ setCallType(e?.callType || "video");
387
+ setPeerId(e?.callerId ?? null);
388
+ setError("");
389
+ setPhase("incoming");
390
+ });
391
+ on("call_connected", (e) => {
392
+ grabStreams(e);
393
+ if (e?.remoteUserId) setPeerId(e.remoteUserId);
394
+ if (reconnectTimer.current) {
395
+ clearTimeout(reconnectTimer.current);
396
+ reconnectTimer.current = null;
397
+ }
398
+ setPhase("active");
399
+ });
400
+ on("call_hangup", () => finish("remote_hangup"));
401
+ on("call_timeout", () => finish("no_answer"));
402
+ on("call_reject", () => finish("declined"));
403
+ on("call_error", (err) => {
404
+ const reason = err?.reason || "";
405
+ if (reason === "permission_denied") {
406
+ setError(err?.message || "Camera / microphone blocked");
407
+ setPhase("permission_denied");
408
+ return;
409
+ }
410
+ if (reason === "ice_failed") {
411
+ if (phaseRef.current === "active") {
412
+ setPhase("reconnecting");
413
+ reconnectTimer.current = setTimeout(() => {
414
+ if (phaseRef.current === "reconnecting") finish("connection_lost");
415
+ }, 2e4);
416
+ }
417
+ return;
418
+ }
419
+ setError(err?.message || "Call error");
420
+ });
421
+ on("local_mute_changed", ({ muted: muted2 }) => setMuted(!!muted2));
422
+ on("local_video_changed", ({ enabled }) => setCameraOn(!!enabled));
423
+ on("peer_mute", () => setPeerMuted(true));
424
+ on("peer_unmute", () => setPeerMuted(false));
425
+ on("peer_video_off", () => setPeerCameraOff(true));
426
+ on("peer_video_on", () => setPeerCameraOff(false));
427
+ on("screen_share_changed", ({ active }) => setSharing(!!active));
428
+ on("peer_left", () => finish("remote_hangup"));
429
+ return () => {
430
+ offs.forEach((f) => {
431
+ try {
432
+ f();
433
+ } catch {
434
+ }
435
+ });
436
+ };
437
+ }, [convo, grabStreams]);
438
+ useEffect(() => {
439
+ if (!convo) return;
440
+ let stop = false;
441
+ const norm = (m) => ({
442
+ id: m.id ?? m.message_id,
443
+ text: m.content ?? m.text ?? "",
444
+ sender: String(m.sender_id ?? m.senderId ?? ""),
445
+ at: m.created_at ?? m.sent_at ?? Date.now()
446
+ });
447
+ const onMsg = (m) => setMessages((prev) => {
448
+ const n = norm(m);
449
+ if (!n.id || prev.some((x) => x.id === n.id)) return prev;
450
+ return [...prev, n];
451
+ });
452
+ convo.on("message", onMsg);
453
+ if (convo.fetchMessages) {
454
+ convo.fetchMessages({ limit: 50 }).then((r) => {
455
+ if (!stop) setMessages((r?.messages ?? r?.data?.messages ?? []).map(norm));
456
+ }).catch(() => {
457
+ });
458
+ }
459
+ return () => {
460
+ stop = true;
461
+ convo.off?.("message", onMsg);
462
+ };
463
+ }, [convo]);
464
+ const finish = useCallback((reason) => {
465
+ if (phaseRef.current === "ended" || phaseRef.current === "idle") return;
466
+ const seconds = startedAt.current ? Math.floor((Date.now() - startedAt.current) / 1e3) : 0;
467
+ const done = (stats) => {
468
+ setEndedInfo({ reason, stats: stats || null, seconds });
469
+ setPhase("ended");
470
+ };
471
+ if (convo?.getCallStats) convo.getCallStats().then(done).catch(() => done(null));
472
+ else done(null);
473
+ }, [convo]);
474
+ const actions = {
475
+ openLobby: (type = "video") => {
476
+ setCallType(type);
477
+ setError("");
478
+ setPhase("lobby");
479
+ },
480
+ start: (type) => {
481
+ const t = type || callType;
482
+ setCallType(t);
483
+ setError("");
484
+ startedAt.current = null;
485
+ setElapsed(0);
486
+ try {
487
+ convo.startCall(t);
488
+ setPhase("outgoing");
489
+ } catch (e) {
490
+ setError(e?.message || "Failed to start call");
491
+ }
492
+ },
493
+ accept: () => {
494
+ try {
495
+ convo.acceptCall();
496
+ } catch (e) {
497
+ setError(e?.message || "Failed to accept");
498
+ }
499
+ },
500
+ acceptVoice: () => {
501
+ setCallType("voice");
502
+ try {
503
+ convo.acceptCall();
504
+ convo.setVideoEnabled?.(false);
505
+ } catch (e) {
506
+ setError(e?.message);
507
+ }
508
+ },
509
+ decline: () => {
510
+ try {
511
+ convo.rejectCall();
512
+ } catch {
513
+ }
514
+ finish("declined_local");
515
+ },
516
+ hangup: () => {
517
+ try {
518
+ convo.endCall();
519
+ } catch {
520
+ }
521
+ finish("local_hangup");
522
+ },
523
+ // Optimistically flip local state so the button reflects it immediately, then tell the
524
+ // SDK. (The local_*_changed event confirms the same value; we no longer depend on it.)
525
+ toggleMute: () => {
526
+ const next = !muted;
527
+ setMuted(next);
528
+ try {
529
+ convo.setMuted(next);
530
+ } catch {
531
+ }
532
+ },
533
+ toggleCamera: () => {
534
+ const next = !cameraOn;
535
+ setCameraOn(next);
536
+ try {
537
+ convo.setVideoEnabled(next);
538
+ } catch {
539
+ }
540
+ },
541
+ switchToVoice: () => {
542
+ setCallType("voice");
543
+ try {
544
+ convo.setVideoEnabled(false);
545
+ } catch {
546
+ }
547
+ },
548
+ // Device / preference selection (lobby + incoming), forwarded to the SDK.
549
+ setCallOptions: (opts) => {
550
+ try {
551
+ convo?.setCallOptions?.(opts);
552
+ } catch {
553
+ }
554
+ },
555
+ listDevices: () => convo?.listDevices ? convo.listDevices() : Promise.resolve([]),
556
+ setSpeaker: (id) => setSpeakerId(id),
557
+ setMirror: (on) => setMirror(!!on),
558
+ setVolume: (v) => setVolume(Math.max(0, Math.min(1, v))),
559
+ // Live media adjustments from the Settings panel (forwarded to the SDK).
560
+ setVideoQuality: (w, h, fps) => {
561
+ try {
562
+ convo?.applyVideoConstraints?.({ width: { ideal: w }, height: { ideal: h }, ...fps ? { frameRate: { ideal: fps } } : {} });
563
+ } catch {
564
+ }
565
+ },
566
+ setAudioProcessing: (opts) => {
567
+ try {
568
+ convo?.applyAudioConstraints?.(opts);
569
+ } catch {
570
+ }
571
+ },
572
+ switchDevice: (kind, id) => {
573
+ try {
574
+ return convo?.switchDevice?.(kind, id);
575
+ } catch {
576
+ }
577
+ },
578
+ setLowBandwidth: (on) => {
579
+ try {
580
+ return convo?.setLowBandwidth?.(on);
581
+ } catch {
582
+ }
583
+ },
584
+ minimize: () => setMinimized(true),
585
+ maximize: () => setMinimized(false),
586
+ openPanel: (name) => {
587
+ setMoreOpen(false);
588
+ setPanel((p) => p === name ? null : name);
589
+ },
590
+ closePanel: () => setPanel(null),
591
+ toggleMore: () => setMoreOpen((o) => !o),
592
+ closeMore: () => setMoreOpen(false),
593
+ sendMessage: (text) => {
594
+ const t = (text || "").trim();
595
+ if (!t) return;
596
+ try {
597
+ convo.sendMessage(t);
598
+ } catch {
599
+ }
600
+ },
601
+ toggleShare: async () => {
602
+ try {
603
+ if (sharing) {
604
+ await convo.stopScreenShare?.();
605
+ setSharing(false);
606
+ } else {
607
+ const ok = await convo.startScreenShare?.();
608
+ setSharing(!!ok);
609
+ }
610
+ } catch {
611
+ }
612
+ },
613
+ reset: () => {
614
+ setPhase("idle");
615
+ setMinimized(false);
616
+ setEndedInfo(null);
617
+ setElapsed(0);
618
+ startedAt.current = null;
619
+ localStream.current = null;
620
+ remoteStream.current = null;
621
+ }
622
+ };
623
+ useEffect(() => {
624
+ if (startInLobby && phase === "idle") setPhase("lobby");
625
+ }, [startInLobby, phase]);
626
+ return {
627
+ phase,
628
+ callType,
629
+ peerId,
630
+ muted,
631
+ cameraOn,
632
+ peerMuted,
633
+ peerCameraOff,
634
+ error,
635
+ endedInfo,
636
+ elapsed,
637
+ minimized,
638
+ sharing,
639
+ panel,
640
+ moreOpen,
641
+ messages,
642
+ speakerId,
643
+ mirror,
644
+ volume,
645
+ localStream,
646
+ remoteStream,
647
+ actions
648
+ };
649
+ }
650
+ function fmtTime(s) {
651
+ s = Math.max(0, s | 0);
652
+ const m = Math.floor(s / 60), r = s % 60;
653
+ return `${String(m).padStart(2, "0")}:${String(r).padStart(2, "0")}`;
654
+ }
655
+
656
+ // src/screens.jsx
657
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
658
+
659
+ // src/parts.jsx
660
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
661
+ import { jsx, jsxs } from "react/jsx-runtime";
662
+ var I = (p) => /* @__PURE__ */ jsx(
663
+ "svg",
664
+ {
665
+ viewBox: "0 0 24 24",
666
+ fill: "none",
667
+ stroke: "currentColor",
668
+ strokeWidth: "2",
669
+ strokeLinecap: "round",
670
+ strokeLinejoin: "round",
671
+ ...p
672
+ }
673
+ );
674
+ var Icon = {
675
+ mic: () => /* @__PURE__ */ jsxs(I, { children: [
676
+ /* @__PURE__ */ jsx("path", { d: "M12 2a3 3 0 0 0-3 3v6a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3z" }),
677
+ /* @__PURE__ */ jsx("path", { d: "M19 10a7 7 0 0 1-14 0" }),
678
+ /* @__PURE__ */ jsx("path", { d: "M12 19v3" })
679
+ ] }),
680
+ micOff: () => /* @__PURE__ */ jsxs(I, { children: [
681
+ /* @__PURE__ */ jsx("path", { d: "M1 1l22 22" }),
682
+ /* @__PURE__ */ jsx("path", { d: "M9 9v2a3 3 0 0 0 5.1 2.1" }),
683
+ /* @__PURE__ */ jsx("path", { d: "M15 9.3V5a3 3 0 0 0-5.9-.7" }),
684
+ /* @__PURE__ */ jsx("path", { d: "M19 10a7 7 0 0 1-1.2 3.9M5 10a7 7 0 0 0 10 6.3" }),
685
+ /* @__PURE__ */ jsx("path", { d: "M12 19v3" })
686
+ ] }),
687
+ cam: () => /* @__PURE__ */ jsxs(I, { children: [
688
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "6", width: "14", height: "12", rx: "2" }),
689
+ /* @__PURE__ */ jsx("path", { d: "M22 8l-6 4 6 4V8z" })
690
+ ] }),
691
+ camOff: () => /* @__PURE__ */ jsxs(I, { children: [
692
+ /* @__PURE__ */ jsx("path", { d: "M1 1l22 22" }),
693
+ /* @__PURE__ */ jsx("path", { d: "M16 16H4a2 2 0 0 1-2-2V8" }),
694
+ /* @__PURE__ */ jsx("path", { d: "M9.5 6H14a2 2 0 0 1 2 2v4.5" }),
695
+ /* @__PURE__ */ jsx("path", { d: "M22 8l-6 4" })
696
+ ] }),
697
+ end: () => /* @__PURE__ */ jsx(I, { children: /* @__PURE__ */ jsx("path", { d: "M22 16.9v2a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2 3.2 2 2 0 0 1 4 1h2a2 2 0 0 1 2 1.7c.1.9.4 1.8.7 2.6a2 2 0 0 1-.5 2.1L7.1 8.6a16 16 0 0 0 6 6l1.2-1.1a2 2 0 0 1 2.1-.5c.8.3 1.7.6 2.6.7A2 2 0 0 1 22 16.9z", transform: "rotate(135 12 12)" }) }),
698
+ speaker: () => /* @__PURE__ */ jsxs(I, { children: [
699
+ /* @__PURE__ */ jsx("path", { d: "M11 5L6 9H2v6h4l5 4V5z" }),
700
+ /* @__PURE__ */ jsx("path", { d: "M15.5 8.5a5 5 0 0 1 0 7M19 5a9 9 0 0 1 0 14" })
701
+ ] }),
702
+ share: () => /* @__PURE__ */ jsxs(I, { children: [
703
+ /* @__PURE__ */ jsx("path", { d: "M4 16V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10" }),
704
+ /* @__PURE__ */ jsx("path", { d: "M2 20h20" }),
705
+ /* @__PURE__ */ jsx("path", { d: "M12 12V7m0 0l-2 2m2-2l2 2" })
706
+ ] }),
707
+ people: () => /* @__PURE__ */ jsxs(I, { children: [
708
+ /* @__PURE__ */ jsx("path", { d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
709
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "7", r: "4" }),
710
+ /* @__PURE__ */ jsx("path", { d: "M23 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8" })
711
+ ] }),
712
+ chat: () => /* @__PURE__ */ jsx(I, { children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }),
713
+ more: () => /* @__PURE__ */ jsxs(I, { children: [
714
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
715
+ /* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "1" }),
716
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1" })
717
+ ] }),
718
+ phone: () => /* @__PURE__ */ jsx(I, { children: /* @__PURE__ */ jsx("path", { d: "M22 16.9v2a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2 3.2 2 2 0 0 1 4 1h2a2 2 0 0 1 2 1.7c.1.9.4 1.8.7 2.6a2 2 0 0 1-.5 2.1L7.1 8.6a16 16 0 0 0 6 6l1.2-1.1a2 2 0 0 1 2.1-.5c.8.3 1.7.6 2.6.7A2 2 0 0 1 22 16.9z" }) }),
719
+ lock: () => /* @__PURE__ */ jsxs(I, { children: [
720
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2" }),
721
+ /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
722
+ ] }),
723
+ check: () => /* @__PURE__ */ jsx(I, { children: /* @__PURE__ */ jsx("path", { d: "M20 6L9 17l-5-5" }) }),
724
+ wifi: () => /* @__PURE__ */ jsxs(I, { children: [
725
+ /* @__PURE__ */ jsx("path", { d: "M5 12.5a10 10 0 0 1 14 0M8.5 16a5 5 0 0 1 7 0" }),
726
+ /* @__PURE__ */ jsx("path", { d: "M12 20h.01" })
727
+ ] }),
728
+ shrink: () => /* @__PURE__ */ jsx(I, { children: /* @__PURE__ */ jsx("path", { d: "M8 3v3a2 2 0 0 1-2 2H3M21 8h-3a2 2 0 0 1-2-2V3M3 16h3a2 2 0 0 1 2 2v3M16 21v-3a2 2 0 0 1 2-2h3" }) }),
729
+ info: () => /* @__PURE__ */ jsxs(I, { children: [
730
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
731
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4M12 8h.01" })
732
+ ] }),
733
+ spinner: () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", className: "cafcall-spin", children: [
734
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "3" }),
735
+ /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 0-9-9", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round" })
736
+ ] }),
737
+ settings: () => /* @__PURE__ */ jsxs(I, { children: [
738
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
739
+ /* @__PURE__ */ jsx("path", { d: "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" })
740
+ ] })
741
+ };
742
+ function Avatar({ name = "?", src, size = 96, className = "", style = {} }) {
743
+ const init = String(name).split(/\s+/).map((w) => w[0]).slice(0, 2).join("").toUpperCase();
744
+ return src ? /* @__PURE__ */ jsx("img", { className: `cafcall-avatar ${className}`, src, alt: name, style: { width: size, height: size, ...style } }) : /* @__PURE__ */ jsx("span", { className: `cafcall-avatar ${className}`, style: { width: size, height: size, fontSize: size * 0.36, ...style }, children: init });
745
+ }
746
+ function Stream({ stream, muted, mirror, sinkId, volume, className, style }) {
747
+ const ref = useRef2(null);
748
+ const ms = stream?.current ?? stream ?? null;
749
+ useEffect2(() => {
750
+ const el = ref.current;
751
+ if (!el) return;
752
+ let cancelled = false;
753
+ const attach = () => {
754
+ if (cancelled || !el) return;
755
+ if (el.srcObject !== ms) el.srcObject = ms || null;
756
+ if (typeof volume === "number") el.volume = volume;
757
+ if (ms) el.play?.().catch(() => {
758
+ });
759
+ if (sinkId && typeof el.setSinkId === "function") el.setSinkId(sinkId).catch(() => {
760
+ });
761
+ };
762
+ attach();
763
+ el.onloadedmetadata = attach;
764
+ const onTrack = () => attach();
765
+ ms?.addEventListener?.("addtrack", onTrack);
766
+ const timers = [120, 400, 900, 1800].map((t) => setTimeout(attach, t));
767
+ return () => {
768
+ cancelled = true;
769
+ ms?.removeEventListener?.("addtrack", onTrack);
770
+ timers.forEach(clearTimeout);
771
+ };
772
+ }, [ms, sinkId, volume]);
773
+ return /* @__PURE__ */ jsx(
774
+ "video",
775
+ {
776
+ ref,
777
+ playsInline: true,
778
+ autoPlay: true,
779
+ muted,
780
+ className,
781
+ style: { ...mirror ? { transform: "scaleX(-1)" } : {}, ...style }
782
+ }
783
+ );
784
+ }
785
+ function Ctl({ icon, name, label, onClick, tone = "", active, on, disabled }) {
786
+ const G = Icon[icon] || Icon.more;
787
+ return /* @__PURE__ */ jsxs(
788
+ "button",
789
+ {
790
+ type: "button",
791
+ className: `cafcall-ctl ${tone} ${active ? "active" : ""} ${on ? "on" : ""} ${name ? `cafcall-ctl-${name}` : ""}`,
792
+ onClick,
793
+ disabled,
794
+ title: label,
795
+ children: [
796
+ /* @__PURE__ */ jsx("span", { className: "ic", children: /* @__PURE__ */ jsx(G, {}) }),
797
+ /* @__PURE__ */ jsx("span", { children: label })
798
+ ]
799
+ }
800
+ );
801
+ }
802
+ function EncBadge() {
803
+ return /* @__PURE__ */ jsxs("span", { className: "cafcall-pill enc", children: [
804
+ /* @__PURE__ */ jsx(Icon.lock, {}),
805
+ " E2E Encrypted"
806
+ ] });
807
+ }
808
+ function Waveform({ bars = 9 }) {
809
+ return /* @__PURE__ */ jsx("span", { className: "cafcall-wave", children: Array.from({ length: bars }).map((_, i) => /* @__PURE__ */ jsx("i", { style: { animationDelay: `${i % 5 * 0.12}s`, height: 5 + i % 4 * 4 } }, i)) });
810
+ }
811
+ function qualityFromStats(stats) {
812
+ if (!stats) return { label: "Good", tone: "ok" };
813
+ const rtt = stats.rtt_ms ?? 0, loss = stats.packet_loss_pct ?? stats.packetsLostPct ?? 0;
814
+ if (rtt > 300 || loss > 5) return { label: "Poor", tone: "warn" };
815
+ if (rtt > 150 || loss > 2) return { label: "Fair", tone: "warn" };
816
+ return { label: "Good", tone: "ok" };
817
+ }
818
+
819
+ // src/settingsPanel.jsx
820
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
821
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
822
+ var QUALITIES = [
823
+ { label: "1080p HD", w: 1920, h: 1080 },
824
+ { label: "720p HD", w: 1280, h: 720 },
825
+ { label: "360p", w: 640, h: 360 },
826
+ { label: "180p", w: 320, h: 180 }
827
+ ];
828
+ var TABS = ["devices", "network", "accessibility", "privacy"];
829
+ var DEFAULT_PREF = {
830
+ mirror: true,
831
+ blur: false,
832
+ noiseSuppression: true,
833
+ echoCancellation: true,
834
+ adaptiveBitrate: true,
835
+ lowBandwidth: false,
836
+ hardwareAccel: true
837
+ };
838
+ var Toggle = ({ on, onClick }) => /* @__PURE__ */ jsx2("button", { type: "button", className: `cafcall-toggle ${on ? "on" : ""}`, onClick, "aria-pressed": on, children: /* @__PURE__ */ jsx2("i", {}) });
839
+ function SettingsPanel({ c, cfg }) {
840
+ const [tab, setTab] = useState2("devices");
841
+ const [devices, setDevices] = useState2({ cam: [], mic: [], spk: [] });
842
+ const [sel, setSel] = useState2({ cam: "", mic: "", spk: c.speakerId || "" });
843
+ const [quality, setQuality] = useState2("720p HD");
844
+ const [pref, setPref] = useState2({ ...DEFAULT_PREF, mirror: c.mirror });
845
+ const previewRef = useRef3(null);
846
+ useEffect3(() => {
847
+ c.actions.listDevices().then((list) => {
848
+ const by = (k) => list.filter((d) => d.kind === k).map((d) => ({ id: d.deviceId, label: d.label || k }));
849
+ setDevices({ cam: by("videoinput"), mic: by("audioinput"), spk: by("audiooutput") });
850
+ });
851
+ }, []);
852
+ useEffect3(() => {
853
+ const el = previewRef.current;
854
+ const ms = c.localStream?.current;
855
+ if (el && ms) {
856
+ el.srcObject = ms;
857
+ el.play?.().catch(() => {
858
+ });
859
+ }
860
+ });
861
+ if (c.panel !== "settings") return null;
862
+ const setQ = (label) => {
863
+ setQuality(label);
864
+ const q = QUALITIES.find((x) => x.label === label);
865
+ if (q) c.actions.setVideoQuality(q.w, q.h);
866
+ };
867
+ const togglePref = (k) => setPref((p) => {
868
+ const v = !p[k];
869
+ if (k === "mirror") c.actions.setMirror(v);
870
+ if (k === "noiseSuppression" || k === "echoCancellation") c.actions.setAudioProcessing({ [k]: v });
871
+ if (k === "lowBandwidth") {
872
+ c.actions.setLowBandwidth(v);
873
+ setQuality(v ? "360p" : "720p HD");
874
+ }
875
+ return { ...p, [k]: v };
876
+ });
877
+ const Picker2 = ({ icon, list, value, onChange }) => {
878
+ const Ico = Icon[icon] || Icon.mic;
879
+ return /* @__PURE__ */ jsxs2("div", { className: "cafcall-picker", children: [
880
+ /* @__PURE__ */ jsx2("span", { className: "ic", children: /* @__PURE__ */ jsx2(Ico, {}) }),
881
+ /* @__PURE__ */ jsxs2("select", { value, onChange: (e) => onChange(e.target.value), children: [
882
+ /* @__PURE__ */ jsx2("option", { value: "", children: list.length ? "Default" : "Grant access to list devices" }),
883
+ list.map((d) => /* @__PURE__ */ jsx2("option", { value: d.id, children: d.label }, d.id))
884
+ ] })
885
+ ] });
886
+ };
887
+ const PrefRow2 = ({ icon, title, sub, k }) => {
888
+ const Ico = Icon[icon] || Icon.info;
889
+ return /* @__PURE__ */ jsxs2("div", { className: "cafcall-answerrow", style: { marginBottom: 8 }, children: [
890
+ /* @__PURE__ */ jsx2("span", { className: "ic", children: /* @__PURE__ */ jsx2(Ico, {}) }),
891
+ /* @__PURE__ */ jsxs2("div", { style: { flex: 1 }, children: [
892
+ /* @__PURE__ */ jsx2("div", { className: "t", children: title }),
893
+ /* @__PURE__ */ jsx2("div", { className: "s", children: sub })
894
+ ] }),
895
+ /* @__PURE__ */ jsx2(Toggle, { on: pref[k], onClick: () => togglePref(k) })
896
+ ] });
897
+ };
898
+ return /* @__PURE__ */ jsxs2("div", { className: "cafcall-drawer cafcall-settings", children: [
899
+ /* @__PURE__ */ jsxs2("div", { className: "cafcall-drawer-head", children: [
900
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
901
+ /* @__PURE__ */ jsx2("span", { className: "cafcall-set-badge", children: /* @__PURE__ */ jsx2(Icon.settings, {}) }),
902
+ /* @__PURE__ */ jsxs2("div", { children: [
903
+ /* @__PURE__ */ jsx2("b", { children: "Settings" }),
904
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-prole", children: cfg.sessionTitle })
905
+ ] })
906
+ ] }),
907
+ /* @__PURE__ */ jsx2("button", { className: "cafcall-iconbtn", style: { background: "#EEF2F7", color: "var(--caf-ink)" }, onClick: c.actions.closePanel, children: "\u2715" })
908
+ ] }),
909
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-tabs", children: TABS.map((t) => /* @__PURE__ */ jsx2("button", { className: `cafcall-tab ${tab === t ? "on" : ""}`, onClick: () => setTab(t), children: t[0].toUpperCase() + t.slice(1) }, t)) }),
910
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-drawer-body", children: tab === "devices" ? /* @__PURE__ */ jsxs2(Fragment, { children: [
911
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-label", children: "Camera" }),
912
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-preview", style: { width: "100%" }, children: /* @__PURE__ */ jsx2("div", { className: "frame", children: /* @__PURE__ */ jsx2("video", { ref: previewRef, playsInline: true, autoPlay: true, muted: true, style: { transform: pref.mirror ? "scaleX(-1)" : "none" } }) }) }),
913
+ /* @__PURE__ */ jsx2(Picker2, { icon: "cam", list: devices.cam, value: sel.cam, onChange: (id) => {
914
+ setSel((s) => ({ ...s, cam: id }));
915
+ c.actions.switchDevice("video", id);
916
+ } }),
917
+ /* @__PURE__ */ jsxs2("div", { className: "cafcall-picker", children: [
918
+ /* @__PURE__ */ jsx2("span", { className: "ic", children: /* @__PURE__ */ jsx2(Icon.settings, {}) }),
919
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1, fontSize: 13, fontWeight: 600 }, children: "Video Quality" }),
920
+ /* @__PURE__ */ jsx2("select", { value: quality, onChange: (e) => setQ(e.target.value), children: QUALITIES.map((q) => /* @__PURE__ */ jsx2("option", { children: q.label }, q.label)) })
921
+ ] }),
922
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "cam", title: "Mirror My Video", sub: "Only affects your own preview", k: "mirror" }),
923
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "info", title: "Background Blur", sub: "Blur background for privacy \u2014 coming soon", k: "blur" }),
924
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-label", children: "Microphone" }),
925
+ /* @__PURE__ */ jsx2(Picker2, { icon: "mic", list: devices.mic, value: sel.mic, onChange: (id) => {
926
+ setSel((s) => ({ ...s, mic: id }));
927
+ c.actions.switchDevice("audio", id);
928
+ } }),
929
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "mic", title: "Noise Suppression", sub: "Filter background noise for clearer audio", k: "noiseSuppression" }),
930
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "info", title: "Echo Cancellation", sub: "Reduce feedback and echo", k: "echoCancellation" }),
931
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-label", children: "Speaker & Output" }),
932
+ /* @__PURE__ */ jsx2(Picker2, { icon: "speaker", list: devices.spk, value: sel.spk, onChange: (id) => {
933
+ setSel((s) => ({ ...s, spk: id }));
934
+ c.actions.setSpeaker(id);
935
+ } }),
936
+ /* @__PURE__ */ jsxs2("div", { className: "cafcall-sliderrow", children: [
937
+ /* @__PURE__ */ jsx2(Icon.speaker, {}),
938
+ /* @__PURE__ */ jsx2("span", { children: "Speaker Volume" }),
939
+ /* @__PURE__ */ jsx2("input", { type: "range", min: "0", max: "1", step: "0.05", value: c.volume, onChange: (e) => c.actions.setVolume(Number(e.target.value)) })
940
+ ] }),
941
+ /* @__PURE__ */ jsx2("div", { className: "cafcall-label", children: "Bandwidth & Performance" }),
942
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "wifi", title: "Adaptive Bitrate", sub: "Auto-adjusts quality to network (automatic)", k: "adaptiveBitrate" }),
943
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "wifi", title: "Low Bandwidth Mode", sub: "Reduce video quality to save data", k: "lowBandwidth" }),
944
+ /* @__PURE__ */ jsx2(PrefRow2, { icon: "settings", title: "Hardware Acceleration", sub: "Use GPU for video \u2014 browser-managed", k: "hardwareAccel" })
945
+ ] }) : /* @__PURE__ */ jsxs2("p", { className: "cafcall-p", style: { fontSize: 13, padding: "18px 0" }, children: [
946
+ tab[0].toUpperCase() + tab.slice(1),
947
+ " settings \u2014 coming soon."
948
+ ] }) }),
949
+ /* @__PURE__ */ jsxs2("div", { className: "cafcall-settings-foot", children: [
950
+ /* @__PURE__ */ jsxs2("span", { className: "cafcall-pill enc", children: [
951
+ /* @__PURE__ */ jsx2(Icon.lock, {}),
952
+ " E2E Encrypted"
953
+ ] }),
954
+ /* @__PURE__ */ jsx2(
955
+ "button",
956
+ {
957
+ className: "cafcall-btn ghost",
958
+ style: { width: "auto", margin: 0, padding: "9px 14px" },
959
+ onClick: () => {
960
+ setPref({ ...DEFAULT_PREF, mirror: c.mirror });
961
+ },
962
+ children: "Reset"
963
+ }
964
+ ),
965
+ /* @__PURE__ */ jsx2(
966
+ "button",
967
+ {
968
+ className: "cafcall-btn pri",
969
+ style: { width: "auto", margin: 0, padding: "9px 16px" },
970
+ onClick: c.actions.closePanel,
971
+ children: "Apply"
972
+ }
973
+ )
974
+ ] })
975
+ ] });
976
+ }
977
+
978
+ // src/screens.jsx
979
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
980
+ var Header = ({ cfg, right }) => /* @__PURE__ */ jsxs3("div", { className: "cafcall-head", children: [
981
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-brand", children: [
982
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-logo", children: cfg.logoText }),
983
+ /* @__PURE__ */ jsxs3("div", { style: { minWidth: 0 }, children: [
984
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-title", children: cfg.sessionTitle }),
985
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-sub", children: cfg.sessionSubtitle })
986
+ ] })
987
+ ] }),
988
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-head-right", children: right })
989
+ ] });
990
+ var Toggle2 = ({ on, onClick }) => /* @__PURE__ */ jsx3("button", { type: "button", className: `cafcall-toggle ${on ? "on" : ""}`, onClick, "aria-pressed": on, children: /* @__PURE__ */ jsx3("i", {}) });
991
+ function Picker({ icon, list, value, onChange }) {
992
+ const Ico = Icon[icon] || Icon.mic;
993
+ return /* @__PURE__ */ jsxs3("div", { className: "cafcall-picker", children: [
994
+ /* @__PURE__ */ jsx3("span", { className: "ic", children: /* @__PURE__ */ jsx3(Ico, {}) }),
995
+ /* @__PURE__ */ jsxs3("select", { value, onChange: (e) => onChange(e.target.value), children: [
996
+ /* @__PURE__ */ jsx3("option", { value: "", children: list.length ? "Default" : "No device / grant access" }),
997
+ list.map((d) => /* @__PURE__ */ jsx3("option", { value: d.id, children: d.label }, d.id))
998
+ ] })
999
+ ] });
1000
+ }
1001
+ function PrefRow({ title, sub, on, onToggle }) {
1002
+ return /* @__PURE__ */ jsxs3("div", { className: "cafcall-prefrow", children: [
1003
+ /* @__PURE__ */ jsxs3("div", { children: [
1004
+ /* @__PURE__ */ jsx3("div", { className: "t", children: title }),
1005
+ /* @__PURE__ */ jsx3("div", { className: "s", children: sub })
1006
+ ] }),
1007
+ /* @__PURE__ */ jsx3(Toggle2, { on, onClick: onToggle })
1008
+ ] });
1009
+ }
1010
+ function MicMeter({ stream, ready, muted }) {
1011
+ const [level, setLevel] = useState3(0);
1012
+ useEffect4(() => {
1013
+ if (!stream) return;
1014
+ let ac, raf, last = 0;
1015
+ try {
1016
+ ac = new (window.AudioContext || window.webkitAudioContext)();
1017
+ const src = ac.createMediaStreamSource(stream);
1018
+ const an = ac.createAnalyser();
1019
+ an.fftSize = 256;
1020
+ src.connect(an);
1021
+ const buf = new Uint8Array(an.frequencyBinCount);
1022
+ const tick = (t) => {
1023
+ raf = requestAnimationFrame(tick);
1024
+ if (t - last < 90) return;
1025
+ last = t;
1026
+ an.getByteFrequencyData(buf);
1027
+ setLevel(Math.min(100, buf.reduce((a, b) => a + b, 0) / buf.length * 1.6));
1028
+ };
1029
+ raf = requestAnimationFrame(tick);
1030
+ } catch {
1031
+ }
1032
+ return () => {
1033
+ cancelAnimationFrame(raf);
1034
+ ac?.close?.();
1035
+ };
1036
+ }, [stream]);
1037
+ return /* @__PURE__ */ jsxs3("div", { className: "cafcall-meter", children: [
1038
+ /* @__PURE__ */ jsx3(Icon.mic, {}),
1039
+ /* @__PURE__ */ jsx3("span", { children: "Microphone level" }),
1040
+ /* @__PURE__ */ jsx3("span", { className: "bar", children: /* @__PURE__ */ jsx3("i", { style: { width: `${muted ? 0 : level}%` } }) }),
1041
+ /* @__PURE__ */ jsx3("span", { style: { color: ready ? "var(--caf-ok)" : "var(--caf-muted)", fontWeight: 600 }, children: ready ? "Detected" : "\u2014" })
1042
+ ] });
1043
+ }
1044
+ function Lobby({ c, cfg }) {
1045
+ const [type, setType] = useState3(c.callType || "video");
1046
+ const [ready, setReady] = useState3(false);
1047
+ const [stream, setStream] = useState3(null);
1048
+ const previewRef = useRef4(null);
1049
+ const mediaRef = useRef4(null);
1050
+ const [devices, setDevices] = useState3({ cam: [], mic: [], spk: [] });
1051
+ const [sel, setSel] = useState3({ cam: "", mic: "", spk: "" });
1052
+ const [pref, setPref] = useState3({ startMuted: false, startVideoOff: false, noiseSuppression: true, lowBandwidth: false });
1053
+ const togglePref = (k) => setPref((p) => ({ ...p, [k]: !p[k] }));
1054
+ useEffect4(() => {
1055
+ let stop = false;
1056
+ c.actions.listDevices().then((list) => {
1057
+ if (stop) return;
1058
+ const by = (k) => list.filter((d) => d.kind === k).map((d) => ({ id: d.deviceId, label: d.label || k }));
1059
+ setDevices({ cam: by("videoinput"), mic: by("audioinput"), spk: by("audiooutput") });
1060
+ });
1061
+ return () => {
1062
+ stop = true;
1063
+ };
1064
+ }, [ready]);
1065
+ useEffect4(() => {
1066
+ let stopped = false;
1067
+ (async () => {
1068
+ try {
1069
+ const s = await navigator.mediaDevices.getUserMedia({
1070
+ video: type === "video" ? sel.cam ? { deviceId: { exact: sel.cam } } : true : false,
1071
+ audio: sel.mic ? { deviceId: { exact: sel.mic } } : true
1072
+ });
1073
+ if (stopped) {
1074
+ s.getTracks().forEach((t) => t.stop());
1075
+ return;
1076
+ }
1077
+ mediaRef.current = s;
1078
+ setReady(true);
1079
+ setStream(s);
1080
+ if (previewRef.current) {
1081
+ previewRef.current.srcObject = s;
1082
+ previewRef.current.play?.().catch(() => {
1083
+ });
1084
+ }
1085
+ } catch {
1086
+ setReady(false);
1087
+ }
1088
+ })();
1089
+ return () => {
1090
+ stopped = true;
1091
+ mediaRef.current?.getTracks().forEach((t) => t.stop());
1092
+ setStream(null);
1093
+ };
1094
+ }, [type, sel.cam, sel.mic]);
1095
+ const join = () => {
1096
+ mediaRef.current?.getTracks().forEach((t) => t.stop());
1097
+ c.actions.setCallOptions({
1098
+ videoDeviceId: sel.cam || void 0,
1099
+ audioDeviceId: sel.mic || void 0,
1100
+ noiseSuppression: pref.noiseSuppression,
1101
+ lowBandwidth: pref.lowBandwidth,
1102
+ startMuted: pref.startMuted,
1103
+ startVideoOff: pref.startVideoOff
1104
+ });
1105
+ if (sel.spk) c.actions.setSpeaker(sel.spk);
1106
+ c.actions.start(type);
1107
+ };
1108
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1109
+ /* @__PURE__ */ jsx3(Header, { cfg, right: /* @__PURE__ */ jsx3(EncBadge, {}) }),
1110
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-body", children: [
1111
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-center", children: [
1112
+ /* @__PURE__ */ jsxs3("div", { children: [
1113
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-h", children: "Ready to join?" }),
1114
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-p", children: "Preview your camera and microphone before entering the call. Adjust settings on the right." })
1115
+ ] }),
1116
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-preview", children: [
1117
+ /* @__PURE__ */ jsx3("div", { className: "frame", children: type === "video" ? /* @__PURE__ */ jsx3("video", { ref: previewRef, playsInline: true, autoPlay: true, muted: true, style: { opacity: pref.startVideoOff ? 0.25 : 1 } }) : /* @__PURE__ */ jsx3("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.self.name, src: cfg.self.avatar, size: 110 }) }) }),
1118
+ /* @__PURE__ */ jsx3(MicMeter, { stream, ready, muted: pref.startMuted })
1119
+ ] })
1120
+ ] }),
1121
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-side", children: [
1122
+ /* @__PURE__ */ jsx3("div", { style: { fontWeight: 700, fontSize: 15 }, children: "Device Setup" }),
1123
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-p", style: { fontSize: 13, margin: "4px 0 14px" }, children: "Select your camera, microphone and speaker before joining." }),
1124
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-card cafcall-person", children: [
1125
+ /* @__PURE__ */ jsx3(Avatar, { name: cfg.self.name, src: cfg.self.avatar, size: 40 }),
1126
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1127
+ /* @__PURE__ */ jsx3("div", { className: "nm", children: cfg.self.name }),
1128
+ /* @__PURE__ */ jsxs3("div", { className: "rl", children: [
1129
+ cfg.self.role,
1130
+ " \xB7 You"
1131
+ ] })
1132
+ ] }),
1133
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill ok", children: [
1134
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-dot" }),
1135
+ " ",
1136
+ ready ? "Ready" : "\u2026"
1137
+ ] })
1138
+ ] }),
1139
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Call type" }),
1140
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-seg", children: [
1141
+ /* @__PURE__ */ jsxs3("button", { className: `cafcall-btn ${type === "video" ? "sel" : "ghost"}`, onClick: () => setType("video"), children: [
1142
+ /* @__PURE__ */ jsx3(Icon.cam, {}),
1143
+ " Video Call"
1144
+ ] }),
1145
+ /* @__PURE__ */ jsxs3("button", { className: `cafcall-btn ${type === "voice" ? "sel" : "ghost"}`, onClick: () => setType("voice"), children: [
1146
+ /* @__PURE__ */ jsx3(Icon.mic, {}),
1147
+ " Voice Only"
1148
+ ] })
1149
+ ] }),
1150
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Devices" }),
1151
+ type === "video" && /* @__PURE__ */ jsx3(Picker, { icon: "cam", list: devices.cam, value: sel.cam, onChange: (v) => setSel((s) => ({ ...s, cam: v })) }),
1152
+ /* @__PURE__ */ jsx3(Picker, { icon: "mic", list: devices.mic, value: sel.mic, onChange: (v) => setSel((s) => ({ ...s, mic: v })) }),
1153
+ /* @__PURE__ */ jsx3(Picker, { icon: "speaker", list: devices.spk, value: sel.spk, onChange: (v) => setSel((s) => ({ ...s, spk: v })) }),
1154
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Preferences" }),
1155
+ /* @__PURE__ */ jsx3(PrefRow, { title: "Start muted", sub: "Join with microphone off", on: pref.startMuted, onToggle: () => togglePref("startMuted") }),
1156
+ type === "video" && /* @__PURE__ */ jsx3(PrefRow, { title: "Start with camera off", sub: "Join with video disabled", on: pref.startVideoOff, onToggle: () => togglePref("startVideoOff") }),
1157
+ /* @__PURE__ */ jsx3(PrefRow, { title: "Noise suppression", sub: "Filter background noise for clearer audio", on: pref.noiseSuppression, onToggle: () => togglePref("noiseSuppression") }),
1158
+ /* @__PURE__ */ jsx3(PrefRow, { title: "Low-bandwidth mode", sub: "Reduce video quality to save data", on: pref.lowBandwidth, onToggle: () => togglePref("lowBandwidth") }),
1159
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Calling" }),
1160
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-card cafcall-person", children: [
1161
+ /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 40 }),
1162
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1163
+ /* @__PURE__ */ jsx3("div", { className: "nm", children: cfg.peer.name }),
1164
+ /* @__PURE__ */ jsx3("div", { className: "rl", children: cfg.peer.role })
1165
+ ] }),
1166
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-pill", children: "Waiting" })
1167
+ ] }),
1168
+ /* @__PURE__ */ jsxs3("div", { style: { marginTop: 18 }, children: [
1169
+ /* @__PURE__ */ jsxs3("button", { className: "cafcall-btn pri", onClick: join, children: [
1170
+ /* @__PURE__ */ jsx3(Icon.phone, {}),
1171
+ " Join Call"
1172
+ ] }),
1173
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-btn ghost", onClick: c.actions.reset, children: "Cancel" })
1174
+ ] })
1175
+ ] })
1176
+ ] })
1177
+ ] });
1178
+ }
1179
+ function Calling({ c, cfg }) {
1180
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1181
+ /* @__PURE__ */ jsx3(Header, { cfg, right: /* @__PURE__ */ jsx3(EncBadge, {}) }),
1182
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-body", children: /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-center", children: [
1183
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-pulse", children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 128 }) }),
1184
+ /* @__PURE__ */ jsxs3("div", { children: [
1185
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-h", children: cfg.peer.name }),
1186
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-p", children: cfg.peer.role })
1187
+ ] }),
1188
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 10, color: "var(--caf-muted)" }, children: [
1189
+ "Calling ",
1190
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-dots", children: [
1191
+ /* @__PURE__ */ jsx3("i", {}),
1192
+ /* @__PURE__ */ jsx3("i", {}),
1193
+ /* @__PURE__ */ jsx3("i", {})
1194
+ ] })
1195
+ ] }),
1196
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-pill", children: c.callType === "video" ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1197
+ /* @__PURE__ */ jsx3(Icon.cam, {}),
1198
+ " Video call"
1199
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1200
+ /* @__PURE__ */ jsx3(Icon.mic, {}),
1201
+ " Voice call"
1202
+ ] }) })
1203
+ ] }) }),
1204
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-controls", children: /* @__PURE__ */ jsx3(Ctl, { icon: "end", label: "Cancel", tone: "danger", onClick: c.actions.hangup }) })
1205
+ ] });
1206
+ }
1207
+ function Incoming({ c, cfg }) {
1208
+ const video = c.callType === "video";
1209
+ const [ans, setAns] = useState3({ cam: video, mic: true });
1210
+ const [connecting, setConnecting] = useState3(false);
1211
+ const accept = () => {
1212
+ if (connecting) return;
1213
+ setConnecting(true);
1214
+ c.actions.setCallOptions({ startVideoOff: video && !ans.cam, startMuted: !ans.mic });
1215
+ if (video && !ans.cam) c.actions.acceptVoice();
1216
+ else c.actions.accept();
1217
+ };
1218
+ const acceptVoiceOnly = () => {
1219
+ if (connecting) return;
1220
+ setConnecting(true);
1221
+ c.actions.acceptVoice();
1222
+ };
1223
+ useEffect4(() => {
1224
+ if (!connecting) return;
1225
+ const t = setTimeout(() => setConnecting(false), 2e4);
1226
+ return () => clearTimeout(t);
1227
+ }, [connecting]);
1228
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1229
+ /* @__PURE__ */ jsx3(Header, { cfg, right: /* @__PURE__ */ jsx3(EncBadge, {}) }),
1230
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-body", children: [
1231
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-center", children: [
1232
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-pulse", children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 128 }) }),
1233
+ /* @__PURE__ */ jsxs3("div", { children: [
1234
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-h", children: cfg.peer.name }),
1235
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-p", children: cfg.peer.role })
1236
+ ] }),
1237
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-pill", children: video ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1238
+ /* @__PURE__ */ jsx3(Icon.cam, {}),
1239
+ " Incoming Video Call"
1240
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1241
+ /* @__PURE__ */ jsx3(Icon.mic, {}),
1242
+ " Incoming Voice Call"
1243
+ ] }) }),
1244
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 10, color: "var(--caf-muted)" }, children: [
1245
+ "Ringing ",
1246
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-dots", children: [
1247
+ /* @__PURE__ */ jsx3("i", {}),
1248
+ /* @__PURE__ */ jsx3("i", {}),
1249
+ /* @__PURE__ */ jsx3("i", {})
1250
+ ] })
1251
+ ] }),
1252
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-infostrip", children: [
1253
+ /* @__PURE__ */ jsxs3("div", { children: [
1254
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Call Type" }),
1255
+ /* @__PURE__ */ jsx3("div", { className: "v", children: video ? "Video" : "Voice" })
1256
+ ] }),
1257
+ /* @__PURE__ */ jsxs3("div", { children: [
1258
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Network" }),
1259
+ /* @__PURE__ */ jsx3("div", { className: "v", style: { color: "var(--caf-ok)" }, children: "Good" })
1260
+ ] }),
1261
+ /* @__PURE__ */ jsxs3("div", { children: [
1262
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Encryption" }),
1263
+ /* @__PURE__ */ jsx3("div", { className: "v", children: "E2E" })
1264
+ ] })
1265
+ ] })
1266
+ ] }),
1267
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-side", children: [
1268
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-note ok", children: [
1269
+ /* @__PURE__ */ jsxs3("b", { style: { color: "var(--caf-primary)" }, children: [
1270
+ "Incoming ",
1271
+ c.callType,
1272
+ " call"
1273
+ ] }),
1274
+ /* @__PURE__ */ jsx3("br", {}),
1275
+ cfg.peer.name,
1276
+ " is calling you",
1277
+ cfg.sessionTitle ? ` for a ${cfg.sessionTitle}.` : "."
1278
+ ] }),
1279
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Caller" }),
1280
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-card cafcall-person", children: [
1281
+ /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 40 }),
1282
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1283
+ /* @__PURE__ */ jsx3("div", { className: "nm", children: cfg.peer.name }),
1284
+ /* @__PURE__ */ jsx3("div", { className: "rl", children: cfg.peer.role })
1285
+ ] }),
1286
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill ok", children: [
1287
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-dot" }),
1288
+ " Calling"
1289
+ ] })
1290
+ ] }),
1291
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Answer with" }),
1292
+ video && /* @__PURE__ */ jsxs3("div", { className: "cafcall-answerrow", children: [
1293
+ /* @__PURE__ */ jsx3("span", { className: "ic", children: /* @__PURE__ */ jsx3(Icon.cam, {}) }),
1294
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1295
+ /* @__PURE__ */ jsx3("div", { className: "t", children: "Camera" }),
1296
+ /* @__PURE__ */ jsx3("div", { className: "s", children: "Join with video" })
1297
+ ] }),
1298
+ /* @__PURE__ */ jsx3(Toggle2, { on: ans.cam, onClick: () => setAns((a) => ({ ...a, cam: !a.cam })) })
1299
+ ] }),
1300
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-answerrow", children: [
1301
+ /* @__PURE__ */ jsx3("span", { className: "ic", children: /* @__PURE__ */ jsx3(Icon.mic, {}) }),
1302
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1303
+ /* @__PURE__ */ jsx3("div", { className: "t", children: "Microphone" }),
1304
+ /* @__PURE__ */ jsx3("div", { className: "s", children: "Join with audio" })
1305
+ ] }),
1306
+ /* @__PURE__ */ jsx3(Toggle2, { on: ans.mic, onClick: () => setAns((a) => ({ ...a, mic: !a.mic })) })
1307
+ ] }),
1308
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-answerrow", children: [
1309
+ /* @__PURE__ */ jsx3("span", { className: "ic", children: /* @__PURE__ */ jsx3(Icon.wifi, {}) }),
1310
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1311
+ /* @__PURE__ */ jsx3("div", { className: "t", children: "Network Quality" }),
1312
+ /* @__PURE__ */ jsx3("div", { className: "s", children: "Good connection" })
1313
+ ] }),
1314
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-pill ok", children: "Good" })
1315
+ ] }),
1316
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Can\u2019t answer?" }),
1317
+ /* @__PURE__ */ jsxs3("button", { className: "cafcall-answerrow as-btn", onClick: c.actions.decline, children: [
1318
+ /* @__PURE__ */ jsx3("span", { className: "ic", children: /* @__PURE__ */ jsx3(Icon.chat, {}) }),
1319
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1, textAlign: "left" }, children: [
1320
+ /* @__PURE__ */ jsx3("div", { className: "t", children: "Send a Message" }),
1321
+ /* @__PURE__ */ jsxs3("div", { className: "s", children: [
1322
+ "Let ",
1323
+ cfg.peer.name,
1324
+ " know you\u2019re unavailable"
1325
+ ] })
1326
+ ] })
1327
+ ] }),
1328
+ /* @__PURE__ */ jsxs3("div", { style: { marginTop: 18, display: "flex", gap: 10 }, children: [
1329
+ /* @__PURE__ */ jsxs3("button", { className: "cafcall-btn dngghost", onClick: c.actions.decline, disabled: connecting, children: [
1330
+ /* @__PURE__ */ jsx3(Icon.end, {}),
1331
+ " Decline"
1332
+ ] }),
1333
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-btn pri", onClick: accept, disabled: connecting, children: connecting ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1334
+ /* @__PURE__ */ jsx3(Icon.spinner, {}),
1335
+ " Connecting\u2026"
1336
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1337
+ /* @__PURE__ */ jsx3(Icon.phone, {}),
1338
+ " Accept Call"
1339
+ ] }) })
1340
+ ] }),
1341
+ video && /* @__PURE__ */ jsx3("button", { className: "cafcall-btn ghost", style: { marginTop: 10 }, onClick: acceptVoiceOnly, disabled: connecting, children: connecting ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
1342
+ /* @__PURE__ */ jsx3(Icon.spinner, {}),
1343
+ " Connecting\u2026"
1344
+ ] }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1345
+ /* @__PURE__ */ jsx3(Icon.mic, {}),
1346
+ " Answer as Voice Only"
1347
+ ] }) })
1348
+ ] })
1349
+ ] })
1350
+ ] });
1351
+ }
1352
+ function MoreSheet({ c, videoCall }) {
1353
+ if (!c.moreOpen) return null;
1354
+ const item = (icon, label, onClick, on) => /* @__PURE__ */ jsxs3("button", { className: "cafcall-sheet-item", onClick: () => {
1355
+ onClick();
1356
+ c.actions.closeMore();
1357
+ }, children: [
1358
+ /* @__PURE__ */ jsx3("span", { className: `ic ${on ? "on" : ""}`, children: (() => {
1359
+ const G = Icon[icon] || Icon.more;
1360
+ return /* @__PURE__ */ jsx3(G, {});
1361
+ })() }),
1362
+ label
1363
+ ] });
1364
+ return /* @__PURE__ */ jsx3("div", { className: "cafcall-sheet-scrim", onClick: c.actions.closeMore, children: /* @__PURE__ */ jsxs3("div", { className: "cafcall-sheet", onClick: (e) => e.stopPropagation(), children: [
1365
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-sheet-grab" }),
1366
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-sheet-grid", children: [
1367
+ item("people", "People", () => c.actions.openPanel("people")),
1368
+ item("chat", "Chat", () => c.actions.openPanel("chat")),
1369
+ videoCall && item("share", c.sharing ? "Stop Share" : "Share", c.actions.toggleShare, c.sharing),
1370
+ item("settings", "Settings", () => c.actions.openPanel("settings"))
1371
+ ] })
1372
+ ] }) });
1373
+ }
1374
+ function SidePanel({ c, cfg }) {
1375
+ const [draft, setDraft] = useState3("");
1376
+ const endRef = useRef4(null);
1377
+ useEffect4(() => {
1378
+ endRef.current?.scrollIntoView({ behavior: "smooth" });
1379
+ }, [c.messages, c.panel]);
1380
+ if (!c.panel) return null;
1381
+ const send = () => {
1382
+ c.actions.sendMessage(draft);
1383
+ setDraft("");
1384
+ };
1385
+ return /* @__PURE__ */ jsxs3("div", { className: "cafcall-drawer", children: [
1386
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-drawer-head", children: [
1387
+ /* @__PURE__ */ jsx3("b", { children: c.panel === "people" ? "People" : "Chat" }),
1388
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-iconbtn", style: { background: "#EEF2F7", color: "var(--caf-ink)" }, onClick: c.actions.closePanel, children: "\u2715" })
1389
+ ] }),
1390
+ c.panel === "people" ? /* @__PURE__ */ jsx3("div", { className: "cafcall-drawer-body", children: [
1391
+ { ...cfg.self, you: true, muted: c.muted, off: !c.cameraOn },
1392
+ { ...cfg.peer, you: false, muted: c.peerMuted, off: c.peerCameraOff }
1393
+ ].map((p, i) => /* @__PURE__ */ jsxs3("div", { className: "cafcall-person", style: { padding: "10px 4px" }, children: [
1394
+ /* @__PURE__ */ jsx3(Avatar, { name: p.name, src: p.avatar, size: 40 }),
1395
+ /* @__PURE__ */ jsxs3("div", { style: { flex: 1 }, children: [
1396
+ /* @__PURE__ */ jsxs3("div", { className: "nm", children: [
1397
+ p.name,
1398
+ p.you ? " (You)" : ""
1399
+ ] }),
1400
+ /* @__PURE__ */ jsx3("div", { className: "rl", children: p.role })
1401
+ ] }),
1402
+ /* @__PURE__ */ jsx3("span", { className: "ic", style: { color: p.muted ? "var(--caf-danger)" : "var(--caf-muted)" }, children: p.muted ? /* @__PURE__ */ jsx3(Icon.micOff, {}) : /* @__PURE__ */ jsx3(Icon.mic, {}) }),
1403
+ c.callType !== "voice" && /* @__PURE__ */ jsx3("span", { className: "ic", style: { color: p.off ? "var(--caf-danger)" : "var(--caf-muted)" }, children: p.off ? /* @__PURE__ */ jsx3(Icon.camOff, {}) : /* @__PURE__ */ jsx3(Icon.cam, {}) })
1404
+ ] }, i)) }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1405
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-drawer-body cafcall-chat", children: [
1406
+ c.messages.length === 0 && /* @__PURE__ */ jsx3("div", { className: "cafcall-p", style: { fontSize: 13 }, children: "No messages yet. Say hello \u{1F44B}" }),
1407
+ c.messages.map((m) => {
1408
+ const mine = String(m.sender) !== String(c.peerId);
1409
+ return /* @__PURE__ */ jsx3("div", { className: `cafcall-msg ${mine ? "mine" : ""}`, children: m.text }, m.id);
1410
+ }),
1411
+ /* @__PURE__ */ jsx3("div", { ref: endRef })
1412
+ ] }),
1413
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-chat-compose", children: [
1414
+ /* @__PURE__ */ jsx3(
1415
+ "input",
1416
+ {
1417
+ value: draft,
1418
+ onChange: (e) => setDraft(e.target.value),
1419
+ onKeyDown: (e) => {
1420
+ if (e.key === "Enter") send();
1421
+ },
1422
+ placeholder: "Type a message\u2026"
1423
+ }
1424
+ ),
1425
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-btn pri", style: { width: "auto", padding: "10px 14px" }, onClick: send, children: "Send" })
1426
+ ] })
1427
+ ] })
1428
+ ] });
1429
+ }
1430
+ var ControlBar = ({ c, videoCall, dark }) => /* @__PURE__ */ jsxs3("div", { className: `cafcall-controls ${dark ? "dark" : ""}`, children: [
1431
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-controls-l", children: [
1432
+ /* @__PURE__ */ jsx3(Ctl, { icon: "people", name: "people", label: "People", active: c.panel === "people", onClick: () => c.actions.openPanel("people") }),
1433
+ /* @__PURE__ */ jsx3(Ctl, { icon: "chat", name: "chat", label: "Chat", active: c.panel === "chat", onClick: () => c.actions.openPanel("chat") })
1434
+ ] }),
1435
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-controls-c", children: [
1436
+ /* @__PURE__ */ jsx3(Ctl, { icon: c.muted ? "micOff" : "mic", name: "mute", label: c.muted ? "Unmute" : "Mute", on: c.muted, onClick: c.actions.toggleMute }),
1437
+ videoCall && /* @__PURE__ */ jsx3(Ctl, { icon: c.cameraOn ? "cam" : "camOff", name: "camera", label: c.cameraOn ? "Camera" : "Camera Off", on: !c.cameraOn, onClick: c.actions.toggleCamera }),
1438
+ /* @__PURE__ */ jsx3(Ctl, { icon: "end", name: "end", label: "End Call", tone: "danger", onClick: c.actions.hangup }),
1439
+ /* @__PURE__ */ jsx3(Ctl, { icon: "speaker", name: "speaker", label: "Speaker", active: true, onClick: () => {
1440
+ } }),
1441
+ videoCall && /* @__PURE__ */ jsx3(Ctl, { icon: "share", name: "share", label: c.sharing ? "Stop Share" : "Share", active: c.sharing, onClick: c.actions.toggleShare })
1442
+ ] }),
1443
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-controls-r", children: [
1444
+ /* @__PURE__ */ jsx3(Ctl, { icon: "more", name: "more", label: "More", active: c.moreOpen, onClick: c.actions.toggleMore }),
1445
+ /* @__PURE__ */ jsx3(Ctl, { icon: "settings", name: "settings", label: "Settings", active: c.panel === "settings", onClick: () => c.actions.openPanel("settings") })
1446
+ ] })
1447
+ ] });
1448
+ function ActiveVideo({ c, cfg }) {
1449
+ const q = qualityFromStats(null);
1450
+ const speaking = !c.peerMuted;
1451
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1452
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-body", style: { flexDirection: "column" }, children: /* @__PURE__ */ jsxs3("div", { className: "cafcall-stage", children: [
1453
+ /* @__PURE__ */ jsx3(Stream, { stream: c.remoteStream, sinkId: c.speakerId, volume: c.volume }),
1454
+ c.sharing && /* @__PURE__ */ jsxs3("div", { className: "cafcall-sharebar", children: [
1455
+ /* @__PURE__ */ jsx3(Icon.share, {}),
1456
+ " You\u2019re sharing your screen"
1457
+ ] }),
1458
+ c.peerCameraOff && /* @__PURE__ */ jsx3("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", color: "#fff" }, children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 110 }) }),
1459
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stage-top", children: [
1460
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stack", children: [
1461
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-titlechip", children: [
1462
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-logo", children: cfg.logoText }),
1463
+ /* @__PURE__ */ jsxs3("span", { children: [
1464
+ /* @__PURE__ */ jsx3("b", { children: cfg.sessionTitle }),
1465
+ /* @__PURE__ */ jsx3("small", { children: cfg.sessionSubtitle })
1466
+ ] })
1467
+ ] }),
1468
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-peerchip", children: [
1469
+ cfg.peer.avatar && /* @__PURE__ */ jsx3("img", { src: cfg.peer.avatar, alt: "" }),
1470
+ /* @__PURE__ */ jsx3("b", { children: cfg.peer.name }),
1471
+ /* @__PURE__ */ jsxs3("span", { style: { opacity: 0.85 }, children: [
1472
+ "\xB7 ",
1473
+ cfg.peer.role,
1474
+ c.peerMuted ? " \xB7 muted" : speaking ? " \xB7 Speaking" : ""
1475
+ ] })
1476
+ ] })
1477
+ ] }),
1478
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-topmid", children: /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill ok", children: [
1479
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-dot" }),
1480
+ " Connected\xA0\xA0",
1481
+ fmtTime(c.elapsed)
1482
+ ] }) }),
1483
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-topright", children: [
1484
+ /* @__PURE__ */ jsxs3("div", { className: "row", children: [
1485
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill enc", children: [
1486
+ /* @__PURE__ */ jsx3(Icon.lock, {}),
1487
+ " E2E Encrypted"
1488
+ ] }),
1489
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-iconbtn", title: "Call info", children: /* @__PURE__ */ jsx3(Icon.info, {}) }),
1490
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-iconbtn", title: "Minimize", onClick: c.actions.minimize, children: /* @__PURE__ */ jsx3(Icon.shrink, {}) })
1491
+ ] }),
1492
+ /* @__PURE__ */ jsxs3("span", { className: `cafcall-pill ${q.tone}`, children: [
1493
+ /* @__PURE__ */ jsx3(Icon.wifi, {}),
1494
+ " ",
1495
+ q.label
1496
+ ] })
1497
+ ] })
1498
+ ] }),
1499
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-selftile", children: [
1500
+ c.cameraOn ? /* @__PURE__ */ jsx3(Stream, { stream: c.localStream, muted: true, mirror: c.mirror }) : /* @__PURE__ */ jsx3("div", { style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.self.name, src: cfg.self.avatar, size: 44 }) }),
1501
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-name", children: [
1502
+ cfg.self.name,
1503
+ " (You) ",
1504
+ c.muted ? /* @__PURE__ */ jsx3(Icon.micOff, {}) : /* @__PURE__ */ jsx3(Icon.mic, {})
1505
+ ] })
1506
+ ] }),
1507
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-floatctl", children: [
1508
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-floatbtn", title: "Flip camera", onClick: c.actions.toggleCamera, children: /* @__PURE__ */ jsx3(Icon.cam, {}) }),
1509
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-floatbtn end", title: "End call", onClick: c.actions.hangup, children: /* @__PURE__ */ jsx3(Icon.end, {}) }),
1510
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-floatbtn", title: c.muted ? "Unmute" : "Mute", onClick: c.actions.toggleMute, children: c.muted ? /* @__PURE__ */ jsx3(Icon.micOff, {}) : /* @__PURE__ */ jsx3(Icon.mic, {}) })
1511
+ ] }),
1512
+ /* @__PURE__ */ jsx3(SidePanel, { c, cfg }),
1513
+ /* @__PURE__ */ jsx3(SettingsPanel, { c, cfg })
1514
+ ] }) }),
1515
+ /* @__PURE__ */ jsx3(ControlBar, { c, videoCall: true, dark: true }),
1516
+ /* @__PURE__ */ jsx3(MoreSheet, { c, videoCall: true })
1517
+ ] });
1518
+ }
1519
+ function ActiveVoice({ c, cfg }) {
1520
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1521
+ /* @__PURE__ */ jsx3(Header, { cfg, right: /* @__PURE__ */ jsxs3(Fragment2, { children: [
1522
+ /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill ok", children: [
1523
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-dot" }),
1524
+ " Voice Only \xB7 ",
1525
+ fmtTime(c.elapsed)
1526
+ ] }),
1527
+ /* @__PURE__ */ jsx3(EncBadge, {})
1528
+ ] }) }),
1529
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-body", children: /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-voice", children: [
1530
+ /* @__PURE__ */ jsx3(SidePanel, { c, cfg }),
1531
+ /* @__PURE__ */ jsx3(SettingsPanel, { c, cfg }),
1532
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-note warn", children: "Video disabled \u2014 voice call active." }),
1533
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-voice-row", children: [
1534
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-part", children: [
1535
+ /* @__PURE__ */ jsx3("div", { className: `cafcall-ring ${!c.peerMuted ? "speaking" : ""}`, children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 120 }) }),
1536
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-pname", children: cfg.peer.name }),
1537
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-prole", children: cfg.peer.role }),
1538
+ !c.peerMuted ? /* @__PURE__ */ jsx3(Waveform, {}) : /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill warn", children: [
1539
+ /* @__PURE__ */ jsx3(Icon.micOff, {}),
1540
+ " Muted"
1541
+ ] })
1542
+ ] }),
1543
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-part", children: [
1544
+ /* @__PURE__ */ jsx3("div", { className: `cafcall-ring ${!c.muted ? "speaking" : ""}`, children: /* @__PURE__ */ jsx3(Avatar, { name: cfg.self.name, src: cfg.self.avatar, size: 120 }) }),
1545
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-pname", children: cfg.self.name }),
1546
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-prole", children: [
1547
+ cfg.self.role,
1548
+ " \xB7 You"
1549
+ ] }),
1550
+ c.muted ? /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill warn", children: [
1551
+ /* @__PURE__ */ jsx3(Icon.micOff, {}),
1552
+ " Microphone Muted"
1553
+ ] }) : /* @__PURE__ */ jsx3(Waveform, {})
1554
+ ] })
1555
+ ] })
1556
+ ] }) }),
1557
+ /* @__PURE__ */ jsx3(ControlBar, { c, videoCall: false }),
1558
+ /* @__PURE__ */ jsx3(MoreSheet, { c, videoCall: false })
1559
+ ] });
1560
+ }
1561
+ function Reconnecting({ c, cfg }) {
1562
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1563
+ /* @__PURE__ */ jsx3(Header, { cfg, right: /* @__PURE__ */ jsxs3("span", { className: "cafcall-pill warn", children: [
1564
+ /* @__PURE__ */ jsx3("span", { className: "cafcall-dot" }),
1565
+ " Reconnecting"
1566
+ ] }) }),
1567
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-body", children: [
1568
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-center", style: { background: "var(--caf-stage)", color: "#fff" }, children: [
1569
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-icon-lg cafcall-spin", style: { background: "rgba(245,158,11,.15)", color: "var(--caf-warn)" }, children: /* @__PURE__ */ jsx3(Icon.wifi, {}) }),
1570
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-h", style: { color: "#fff" }, children: "Reconnecting\u2026" }),
1571
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-p", style: { color: "#cbd5e1" }, children: "Your connection was interrupted. Restoring the call automatically." })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-side", children: [
1574
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-note warn", children: [
1575
+ /* @__PURE__ */ jsx3("b", { children: "Connection lost." }),
1576
+ /* @__PURE__ */ jsx3("br", {}),
1577
+ "Network instability detected \u2014 attempting to restore your call."
1578
+ ] }),
1579
+ /* @__PURE__ */ jsxs3("div", { style: { marginTop: 18 }, children: [
1580
+ /* @__PURE__ */ jsxs3("button", { className: "cafcall-btn ghost", onClick: c.actions.switchToVoice, children: [
1581
+ /* @__PURE__ */ jsx3(Icon.mic, {}),
1582
+ " Switch to Voice Only"
1583
+ ] }),
1584
+ /* @__PURE__ */ jsxs3("button", { className: "cafcall-btn dngghost", onClick: c.actions.hangup, children: [
1585
+ /* @__PURE__ */ jsx3(Icon.end, {}),
1586
+ " End Call"
1587
+ ] })
1588
+ ] })
1589
+ ] })
1590
+ ] })
1591
+ ] });
1592
+ }
1593
+ function PermissionDenied({ c, cfg }) {
1594
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1595
+ /* @__PURE__ */ jsx3(Header, { cfg }),
1596
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-body", children: /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-center", children: [
1597
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-icon-lg", style: { background: "#FEF2F2", color: "var(--caf-danger)" }, children: /* @__PURE__ */ jsx3(Icon.camOff, {}) }),
1598
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-h", children: "Camera & microphone blocked" }),
1599
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-p", children: c.error || "ChaterAfrika needs access to your camera and microphone to start the call. Enable them in your browser\u2019s site settings, then try again." }),
1600
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: 10 }, children: [
1601
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-btn pri", onClick: () => c.actions.start(c.callType), style: { width: 160 }, children: "Try Again" }),
1602
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-btn ghost", onClick: c.actions.reset, style: { width: 120 }, children: "Cancel" })
1603
+ ] })
1604
+ ] }) })
1605
+ ] });
1606
+ }
1607
+ var REASON = {
1608
+ local_hangup: "You ended the call.",
1609
+ remote_hangup: "The other participant ended the call.",
1610
+ declined: "Call declined.",
1611
+ declined_local: "You declined the call.",
1612
+ no_answer: "No answer.",
1613
+ connection_lost: "The call ended due to a lost connection."
1614
+ };
1615
+ function Ended({ c, cfg }) {
1616
+ const [rating, setRating] = useState3(0);
1617
+ const [sent, setSent] = useState3(false);
1618
+ const info = c.endedInfo || {};
1619
+ const stats = info.stats || {};
1620
+ const rate = (n) => {
1621
+ setRating(n);
1622
+ setSent(true);
1623
+ cfg.onRate?.(n);
1624
+ };
1625
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1626
+ /* @__PURE__ */ jsx3(Header, { cfg, right: /* @__PURE__ */ jsx3(EncBadge, {}) }),
1627
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-body", children: [
1628
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-main cafcall-center", children: [
1629
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-icon-lg", style: { background: "#DCFCE7", color: "var(--caf-ok)" }, children: /* @__PURE__ */ jsx3(Icon.check, {}) }),
1630
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-h", children: "Call Ended" }),
1631
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-p", children: [
1632
+ REASON[info.reason] || "The call has concluded.",
1633
+ " A summary is below."
1634
+ ] }),
1635
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center", gap: 26 }, children: [
1636
+ /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center" }, children: [
1637
+ /* @__PURE__ */ jsx3(Avatar, { name: cfg.self.name, src: cfg.self.avatar, size: 56 }),
1638
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-prole", style: { marginTop: 6 }, children: cfg.self.name })
1639
+ ] }),
1640
+ /* @__PURE__ */ jsx3(Icon.check, {}),
1641
+ /* @__PURE__ */ jsxs3("div", { style: { textAlign: "center" }, children: [
1642
+ /* @__PURE__ */ jsx3(Avatar, { name: cfg.peer.name, src: cfg.peer.avatar, size: 56 }),
1643
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-prole", style: { marginTop: 6 }, children: cfg.peer.name })
1644
+ ] })
1645
+ ] }),
1646
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stats", style: { width: "min(520px,100%)" }, children: [
1647
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stat", children: [
1648
+ /* @__PURE__ */ jsx3("div", { className: "v", children: fmtTime(info.seconds || 0) }),
1649
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Duration" })
1650
+ ] }),
1651
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stat", children: [
1652
+ /* @__PURE__ */ jsx3("div", { className: "v", children: c.callType === "video" ? "HD" : "Voice" }),
1653
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Type" })
1654
+ ] }),
1655
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stat", children: [
1656
+ /* @__PURE__ */ jsx3("div", { className: "v", children: qualityFromStats(stats).label }),
1657
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Network" })
1658
+ ] }),
1659
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-stat", children: [
1660
+ /* @__PURE__ */ jsx3("div", { className: "v", children: stats.rtt_ms != null ? `${Math.round(stats.rtt_ms)}ms` : "\u2014" }),
1661
+ /* @__PURE__ */ jsx3("div", { className: "k", children: "Latency" })
1662
+ ] })
1663
+ ] })
1664
+ ] }),
1665
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-side", children: [
1666
+ /* @__PURE__ */ jsxs3("div", { className: "cafcall-note ok", children: [
1667
+ /* @__PURE__ */ jsx3("b", { style: { color: "var(--caf-primary)" }, children: "Call complete" }),
1668
+ /* @__PURE__ */ jsx3("br", {}),
1669
+ "Both participants were connected for the session."
1670
+ ] }),
1671
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-label", children: "Rate this call" }),
1672
+ /* @__PURE__ */ jsx3("div", { className: "cafcall-stars", children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsx3("button", { className: `cafcall-star ${n <= rating ? "on" : ""}`, onClick: () => rate(n), children: "\u2605" }, n)) }),
1673
+ sent && /* @__PURE__ */ jsx3("div", { className: "cafcall-prole", style: { marginTop: 8 }, children: "Thanks for your feedback!" }),
1674
+ /* @__PURE__ */ jsxs3("div", { style: { marginTop: 18 }, children: [
1675
+ /* @__PURE__ */ jsxs3("button", { className: "cafcall-btn pri", onClick: () => c.actions.openLobby(c.callType), children: [
1676
+ /* @__PURE__ */ jsx3(Icon.phone, {}),
1677
+ " Start New Call"
1678
+ ] }),
1679
+ /* @__PURE__ */ jsx3("button", { className: "cafcall-btn ghost", onClick: c.actions.reset, children: "Close" })
1680
+ ] })
1681
+ ] })
1682
+ ] })
1683
+ ] });
1684
+ }
1685
+
1686
+ // src/CallUI.jsx
1687
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1688
+ function MiniBar({ c, cfg }) {
1689
+ return /* @__PURE__ */ jsxs4("div", { className: "cafcall-mini", style: { "--caf-primary": cfg.primaryColor }, children: [
1690
+ /* @__PURE__ */ jsx4("button", { className: "cafcall-mini-expand", onClick: c.actions.maximize, title: "Expand call", children: /* @__PURE__ */ jsx4(Icon.shrink, {}) }),
1691
+ /* @__PURE__ */ jsxs4("div", { className: "cafcall-mini-info", children: [
1692
+ /* @__PURE__ */ jsx4("div", { className: "t", children: cfg.peer.name }),
1693
+ /* @__PURE__ */ jsxs4("div", { className: "s", children: [
1694
+ /* @__PURE__ */ jsx4("span", { className: "cafcall-dot", style: { color: "var(--caf-ok)" } }),
1695
+ " ",
1696
+ fmtTime(c.elapsed)
1697
+ ] })
1698
+ ] }),
1699
+ /* @__PURE__ */ jsxs4("div", { className: "cafcall-mini-ctls", children: [
1700
+ /* @__PURE__ */ jsx4(Ctl, { icon: c.muted ? "micOff" : "mic", label: "", on: c.muted, onClick: c.actions.toggleMute }),
1701
+ c.callType !== "voice" && /* @__PURE__ */ jsx4(Ctl, { icon: c.cameraOn ? "cam" : "camOff", label: "", on: !c.cameraOn, onClick: c.actions.toggleCamera }),
1702
+ /* @__PURE__ */ jsx4(Ctl, { icon: "end", label: "", tone: "danger", onClick: c.actions.hangup })
1703
+ ] })
1704
+ ] });
1705
+ }
1706
+ function CallUI({ controller, config = {}, embed = false }) {
1707
+ const c = controller;
1708
+ const cfg = {
1709
+ brandName: "ChaterAfrika",
1710
+ logoText: "CA",
1711
+ sessionTitle: "Call",
1712
+ sessionSubtitle: "Secured via ChaterAfrika",
1713
+ primaryColor: "#2563EB",
1714
+ self: { name: "You", role: "Participant", avatar: null },
1715
+ peer: { name: "Guest", role: "Participant", avatar: null },
1716
+ onRate: config.onRate,
1717
+ ...config
1718
+ };
1719
+ if (c) c.actions.openChat = config.onChat;
1720
+ useEffect5(() => {
1721
+ injectCallStyles();
1722
+ }, []);
1723
+ if (!c || c.phase === "idle") return null;
1724
+ if (c.minimized && (c.phase === "active" || c.phase === "reconnecting")) {
1725
+ return /* @__PURE__ */ jsx4(MiniBar, { c, cfg });
1726
+ }
1727
+ const SCREENS = {
1728
+ lobby: Lobby,
1729
+ outgoing: Calling,
1730
+ incoming: Incoming,
1731
+ active: c.callType === "voice" ? ActiveVoice : ActiveVideo,
1732
+ reconnecting: Reconnecting,
1733
+ permission_denied: PermissionDenied,
1734
+ ended: Ended
1735
+ };
1736
+ const Screen = SCREENS[c.phase] || ActiveVideo;
1737
+ return /* @__PURE__ */ jsx4("div", { className: `cafcall ${embed ? "cafcall-embed" : ""}`, style: { "--caf-primary": cfg.primaryColor }, children: /* @__PURE__ */ jsx4(Screen, { c, cfg }) });
1738
+ }
1739
+ export {
1740
+ CallUI,
1741
+ CallUI as default,
1742
+ useCall
1743
+ };