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