@samirify/messenger 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.
@@ -0,0 +1,1272 @@
1
+ import { AnimatePresence, motion } from 'framer-motion';
2
+ import { createContext, useState, useRef, useEffect, useContext, useMemo } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
5
+
6
+ // src/web/ChatDock.tsx
7
+ var MessengerContext = createContext(null);
8
+ function useMessengerConfig() {
9
+ const ctx = useContext(MessengerContext);
10
+ if (!ctx) {
11
+ throw new Error(
12
+ "@gymlium/messenger: useMessengerConfig must be used within MessengerProvider"
13
+ );
14
+ }
15
+ return ctx;
16
+ }
17
+ function useOptionalMessengerConfig() {
18
+ return useContext(MessengerContext);
19
+ }
20
+ var MessengerUiContext = createContext(null);
21
+ function useMessengerUi() {
22
+ const ctx = useContext(MessengerUiContext);
23
+ if (!ctx) {
24
+ throw new Error(
25
+ "@gymlium/messenger: useMessengerUi must be used within MessengerUiProvider"
26
+ );
27
+ }
28
+ return ctx;
29
+ }
30
+
31
+ // src/api.ts
32
+ async function listThreads(http) {
33
+ const res = await http.getJson(
34
+ "/api/messages/threads"
35
+ );
36
+ return { ...res, data: res.data?.threads ?? [] };
37
+ }
38
+ async function openThread(http, clientUserId) {
39
+ return http.postJson("/api/messages/threads", {
40
+ ...clientUserId ? { client_user_id: clientUserId } : {}
41
+ });
42
+ }
43
+ async function getThread(http, threadId, limit = 50, beforeId) {
44
+ const params = new URLSearchParams({ limit: String(limit) });
45
+ return http.getJson(`/api/messages/threads/${threadId}?${params.toString()}`);
46
+ }
47
+ async function sendMessage(http, threadId, body, image) {
48
+ const path = `/api/messages/threads/${threadId}/messages`;
49
+ if (image?.uri || image?.file) {
50
+ const form = new FormData();
51
+ if (body.trim()) form.append("body", body.trim());
52
+ const filename = image.name ?? image.uri?.split("/").pop() ?? "photo.jpg";
53
+ const match = /\.(\w+)$/.exec(filename);
54
+ const type = image.type ?? (match ? `image/${match[1]}` : "image/jpeg");
55
+ if (image.file) {
56
+ form.append("image", image.file, filename);
57
+ } else if (image.uri.startsWith("blob:") || image.uri.startsWith("data:")) {
58
+ const blob = await fetch(image.uri).then((r) => r.blob());
59
+ form.append("image", blob, filename);
60
+ } else {
61
+ form.append("image", {
62
+ uri: image.uri,
63
+ name: filename,
64
+ type
65
+ });
66
+ }
67
+ return http.postForm(path, form);
68
+ }
69
+ return http.postJson(path, { body });
70
+ }
71
+ function sumUnread(threads) {
72
+ return threads.reduce((sum, th) => sum + Number(th.unread_count ?? 0), 0);
73
+ }
74
+
75
+ // src/queryKeys.ts
76
+ var messengerKeys = {
77
+ all: ["messenger"],
78
+ threads: () => [...messengerKeys.all, "threads"],
79
+ thread: (id) => [...messengerKeys.all, "thread", id]
80
+ };
81
+
82
+ // src/hooks/useUnreadTotal.ts
83
+ function useUnreadTotal(options) {
84
+ const cfg = useOptionalMessengerConfig();
85
+ const featureOn = cfg?.enabled === true;
86
+ const http = cfg?.http;
87
+ const enabled = (options?.enabled ?? true) && featureOn && !!http;
88
+ const query = useQuery({
89
+ queryKey: messengerKeys.threads(),
90
+ queryFn: async () => {
91
+ const res = await listThreads(http);
92
+ if (!res.success) {
93
+ throw new Error(res.message ?? "Failed to load threads");
94
+ }
95
+ return res.data;
96
+ },
97
+ enabled
98
+ });
99
+ const total = useMemo(
100
+ () => query.data ? sumUnread(query.data) : 0,
101
+ [query.data]
102
+ );
103
+ return {
104
+ total,
105
+ isLoading: query.isLoading,
106
+ isFetching: query.isFetching,
107
+ refetch: query.refetch,
108
+ enabled: featureOn
109
+ };
110
+ }
111
+
112
+ // src/i18nKeys.ts
113
+ var MessengerI18n = {
114
+ off: {
115
+ key: "mobile.messenger.off",
116
+ fallback: "Messaging is not enabled for this account."
117
+ },
118
+ chat: { key: "mobile.messenger.chat", fallback: "Chat" },
119
+ /** Dock / list title — prefer over inbox for web UI copy. */
120
+ messages: { key: "mobile.messenger.messages", fallback: "Chat" },
121
+ messagesHint: {
122
+ key: "mobile.messenger.messagesHint",
123
+ fallback: "Message your coach anytime"
124
+ },
125
+ messageCoach: {
126
+ key: "mobile.messenger.messageCoach",
127
+ fallback: "Message your coach"
128
+ },
129
+ empty: {
130
+ key: "mobile.messenger.empty",
131
+ fallback: "No conversations yet"
132
+ },
133
+ yourCoach: {
134
+ key: "mobile.messenger.yourCoach",
135
+ fallback: "Your coach"
136
+ },
137
+ photo: { key: "mobile.messenger.photo", fallback: "Photo" },
138
+ noMessages: {
139
+ key: "mobile.messenger.noMessages",
140
+ fallback: "No messages yet"
141
+ },
142
+ startConversation: {
143
+ key: "mobile.messenger.startConversation",
144
+ fallback: "Say hello \u2014 send the first message."
145
+ },
146
+ typeMessage: {
147
+ key: "mobile.messenger.typeMessage",
148
+ fallback: "Type a message\u2026"
149
+ },
150
+ client: { key: "mobile.trainer.client", fallback: "Client" },
151
+ back: { key: "common.back", fallback: "Back" }
152
+ };
153
+ function useThreads(options) {
154
+ const { http, enabled: featureOn } = useMessengerConfig();
155
+ const enabled = featureOn;
156
+ return useQuery({
157
+ queryKey: messengerKeys.threads(),
158
+ queryFn: async () => {
159
+ const res = await listThreads(http);
160
+ if (!res.success) {
161
+ throw new Error(res.message ?? "Failed to load threads");
162
+ }
163
+ return res.data;
164
+ },
165
+ enabled
166
+ });
167
+ }
168
+ function useOpenThread() {
169
+ const { http, navigation } = useMessengerConfig();
170
+ const qc = useQueryClient();
171
+ return useMutation({
172
+ mutationFn: async (clientUserId) => {
173
+ const res = await openThread(http, clientUserId);
174
+ if (!res.success || !res.data?.thread?.id) {
175
+ throw new Error(res.message ?? "Could not open thread");
176
+ }
177
+ return res.data.thread;
178
+ },
179
+ onSuccess: (thread) => {
180
+ void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
181
+ navigation.openThread(thread.id);
182
+ }
183
+ });
184
+ }
185
+ function initials(name) {
186
+ const parts = name.trim().split(/\s+/).filter(Boolean);
187
+ if (parts.length === 0) return "?";
188
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
189
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
190
+ }
191
+ function ThreadRow({
192
+ thread,
193
+ onSelect
194
+ }) {
195
+ const { user, t, isRtl, theme } = useMessengerConfig();
196
+ const unread = Number(thread.unread_count ?? 0);
197
+ const title = user?.role === "admin" ? thread.client_name ?? t(MessengerI18n.client.key, MessengerI18n.client.fallback) : t(MessengerI18n.yourCoach.key, MessengerI18n.yourCoach.fallback);
198
+ const preview = thread.last_body || (thread.last_image_url ? t(MessengerI18n.photo.key, MessengerI18n.photo.fallback) : t(MessengerI18n.noMessages.key, MessengerI18n.noMessages.fallback));
199
+ return /* @__PURE__ */ jsxs(
200
+ "button",
201
+ {
202
+ type: "button",
203
+ onClick: () => onSelect(thread.id),
204
+ style: {
205
+ display: "flex",
206
+ flexDirection: isRtl ? "row-reverse" : "row",
207
+ alignItems: "center",
208
+ gap: 12,
209
+ width: "calc(100% - 16px)",
210
+ textAlign: isRtl ? "right" : "left",
211
+ padding: "12px 14px",
212
+ margin: "0 8px",
213
+ border: "none",
214
+ borderRadius: 16,
215
+ background: unread > 0 ? `color-mix(in srgb, ${theme.accentColor} 8%, transparent)` : "transparent",
216
+ cursor: "pointer",
217
+ color: theme.colors.text,
218
+ transition: "background 160ms ease"
219
+ },
220
+ onMouseEnter: (e) => {
221
+ e.currentTarget.style.background = `color-mix(in srgb, ${theme.accentColor} 12%, ${theme.colors.surfaceSecondary})`;
222
+ },
223
+ onMouseLeave: (e) => {
224
+ e.currentTarget.style.background = unread > 0 ? `color-mix(in srgb, ${theme.accentColor} 8%, transparent)` : "transparent";
225
+ },
226
+ children: [
227
+ /* @__PURE__ */ jsx(
228
+ "div",
229
+ {
230
+ "aria-hidden": true,
231
+ style: {
232
+ width: 44,
233
+ height: 44,
234
+ borderRadius: 14,
235
+ flexShrink: 0,
236
+ display: "grid",
237
+ placeItems: "center",
238
+ fontFamily: theme.fonts.displaySemiBold,
239
+ fontWeight: 700,
240
+ fontSize: 13,
241
+ letterSpacing: "0.02em",
242
+ color: theme.accentColor,
243
+ background: `
244
+ linear-gradient(145deg,
245
+ color-mix(in srgb, ${theme.accentColor} 22%, ${theme.colors.surface}),
246
+ color-mix(in srgb, ${theme.accentColor} 8%, ${theme.colors.surfaceSecondary})
247
+ )
248
+ `,
249
+ border: `1px solid color-mix(in srgb, ${theme.accentColor} 22%, transparent)`
250
+ },
251
+ children: initials(title)
252
+ }
253
+ ),
254
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
255
+ /* @__PURE__ */ jsxs(
256
+ "div",
257
+ {
258
+ style: {
259
+ display: "flex",
260
+ flexDirection: isRtl ? "row-reverse" : "row",
261
+ alignItems: "baseline",
262
+ gap: 8
263
+ },
264
+ children: [
265
+ /* @__PURE__ */ jsx(
266
+ "div",
267
+ {
268
+ style: {
269
+ flex: 1,
270
+ fontFamily: theme.fonts.displaySemiBold,
271
+ fontWeight: unread > 0 ? 700 : 600,
272
+ fontSize: 14.5,
273
+ overflow: "hidden",
274
+ textOverflow: "ellipsis",
275
+ whiteSpace: "nowrap"
276
+ },
277
+ children: title
278
+ }
279
+ ),
280
+ unread > 0 ? /* @__PURE__ */ jsx(
281
+ "span",
282
+ {
283
+ style: {
284
+ minWidth: 20,
285
+ height: 20,
286
+ borderRadius: 10,
287
+ padding: "0 6px",
288
+ background: theme.accentColor,
289
+ color: "var(--color-text-inverse, #fff)",
290
+ fontSize: 11,
291
+ fontFamily: theme.fonts.bodySemiBold,
292
+ fontWeight: 700,
293
+ display: "inline-flex",
294
+ alignItems: "center",
295
+ justifyContent: "center",
296
+ flexShrink: 0
297
+ },
298
+ children: unread
299
+ }
300
+ ) : null
301
+ ]
302
+ }
303
+ ),
304
+ /* @__PURE__ */ jsx(
305
+ "div",
306
+ {
307
+ style: {
308
+ fontFamily: theme.fonts.body,
309
+ fontSize: 12.5,
310
+ color: unread > 0 ? theme.colors.textSecondary : theme.colors.textMuted,
311
+ marginTop: 3,
312
+ overflow: "hidden",
313
+ textOverflow: "ellipsis",
314
+ whiteSpace: "nowrap",
315
+ fontWeight: unread > 0 ? 500 : 400
316
+ },
317
+ children: preview
318
+ }
319
+ )
320
+ ] })
321
+ ]
322
+ }
323
+ );
324
+ }
325
+ function ThreadList({
326
+ onSelectThread
327
+ }) {
328
+ const { user, t, theme } = useMessengerConfig();
329
+ const threadsQuery = useThreads();
330
+ const openThread2 = useOpenThread();
331
+ const threads = threadsQuery.data ?? [];
332
+ return /* @__PURE__ */ jsxs(
333
+ "div",
334
+ {
335
+ style: {
336
+ display: "flex",
337
+ flexDirection: "column",
338
+ height: "100%",
339
+ paddingTop: 6,
340
+ paddingBottom: 10
341
+ },
342
+ children: [
343
+ user?.role === "client" && threads.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: "8px 16px 4px" }, children: /* @__PURE__ */ jsx(
344
+ "button",
345
+ {
346
+ type: "button",
347
+ onClick: () => openThread2.mutate(void 0),
348
+ disabled: openThread2.isPending,
349
+ style: {
350
+ width: "100%",
351
+ padding: "13px 16px",
352
+ borderRadius: 14,
353
+ border: "none",
354
+ background: `
355
+ linear-gradient(135deg,
356
+ color-mix(in srgb, ${theme.accentColor} 92%, #fff),
357
+ ${theme.accentColor}
358
+ )
359
+ `,
360
+ color: "var(--color-text-inverse, #fff)",
361
+ fontFamily: theme.fonts.bodySemiBold,
362
+ fontWeight: 600,
363
+ fontSize: 14,
364
+ cursor: openThread2.isPending ? "default" : "pointer",
365
+ boxShadow: `0 10px 24px color-mix(in srgb, ${theme.accentColor} 32%, transparent)`
366
+ },
367
+ children: openThread2.isPending ? "\u2026" : t(
368
+ MessengerI18n.messageCoach.key,
369
+ MessengerI18n.messageCoach.fallback
370
+ )
371
+ }
372
+ ) }),
373
+ threadsQuery.isLoading && threads.length === 0 ? /* @__PURE__ */ jsx(
374
+ "div",
375
+ {
376
+ style: {
377
+ padding: 48,
378
+ textAlign: "center",
379
+ color: theme.colors.textMuted,
380
+ fontFamily: theme.fonts.body
381
+ },
382
+ children: "\u2026"
383
+ }
384
+ ) : threads.length === 0 ? /* @__PURE__ */ jsxs(
385
+ "div",
386
+ {
387
+ style: {
388
+ flex: 1,
389
+ display: "flex",
390
+ flexDirection: "column",
391
+ alignItems: "center",
392
+ justifyContent: "center",
393
+ padding: 32,
394
+ textAlign: "center",
395
+ gap: 10
396
+ },
397
+ children: [
398
+ /* @__PURE__ */ jsx(
399
+ "div",
400
+ {
401
+ "aria-hidden": true,
402
+ style: {
403
+ width: 56,
404
+ height: 56,
405
+ borderRadius: 18,
406
+ display: "grid",
407
+ placeItems: "center",
408
+ background: `color-mix(in srgb, ${theme.accentColor} 12%, ${theme.colors.surfaceSecondary})`,
409
+ color: theme.accentColor,
410
+ fontSize: 22,
411
+ marginBottom: 4
412
+ },
413
+ children: "\u2726"
414
+ }
415
+ ),
416
+ /* @__PURE__ */ jsx(
417
+ "div",
418
+ {
419
+ style: {
420
+ fontFamily: theme.fonts.displaySemiBold,
421
+ fontWeight: 600,
422
+ fontSize: 15,
423
+ color: theme.colors.text
424
+ },
425
+ children: t(MessengerI18n.empty.key, MessengerI18n.empty.fallback)
426
+ }
427
+ ),
428
+ /* @__PURE__ */ jsx(
429
+ "div",
430
+ {
431
+ style: {
432
+ fontFamily: theme.fonts.body,
433
+ fontSize: 13,
434
+ color: theme.colors.textMuted,
435
+ maxWidth: 220,
436
+ lineHeight: 1.45
437
+ },
438
+ children: t(
439
+ MessengerI18n.startConversation.key,
440
+ MessengerI18n.startConversation.fallback
441
+ )
442
+ }
443
+ )
444
+ ]
445
+ }
446
+ ) : /* @__PURE__ */ jsx(
447
+ "div",
448
+ {
449
+ style: {
450
+ overflowY: "auto",
451
+ flex: 1,
452
+ display: "flex",
453
+ flexDirection: "column",
454
+ gap: 2,
455
+ paddingBottom: 8
456
+ },
457
+ children: threads.map((th) => /* @__PURE__ */ jsx(ThreadRow, { thread: th, onSelect: onSelectThread }, th.id))
458
+ }
459
+ )
460
+ ]
461
+ }
462
+ );
463
+ }
464
+ var POLL_MS = 18e3;
465
+ function useThread(threadId, options) {
466
+ const { http, enabled: featureOn } = useMessengerConfig();
467
+ const enabled = featureOn && Number.isFinite(threadId) && threadId > 0;
468
+ const poll = true;
469
+ return useQuery({
470
+ queryKey: messengerKeys.thread(threadId),
471
+ queryFn: async () => {
472
+ const res = await getThread(http, threadId);
473
+ if (!res.success) {
474
+ throw new Error(res.message ?? "Failed to load thread");
475
+ }
476
+ return res.data;
477
+ },
478
+ enabled,
479
+ refetchInterval: enabled && poll ? POLL_MS : false
480
+ });
481
+ }
482
+ function useSendMessage(threadId) {
483
+ const { http } = useMessengerConfig();
484
+ const qc = useQueryClient();
485
+ return useMutation({
486
+ mutationFn: async (input) => {
487
+ const res = await sendMessage(http, threadId, input.body, input.image);
488
+ if (!res.success || !res.data?.message) {
489
+ throw new Error(res.message ?? "Send failed");
490
+ }
491
+ return res.data.message;
492
+ },
493
+ onSuccess: (message) => {
494
+ qc.setQueryData(
495
+ messengerKeys.thread(threadId),
496
+ (prev) => {
497
+ if (!prev) return prev;
498
+ if (prev.messages.some((m) => m.id === message.id)) return prev;
499
+ return { ...prev, messages: [...prev.messages, message] };
500
+ }
501
+ );
502
+ void qc.invalidateQueries({ queryKey: messengerKeys.threads() });
503
+ }
504
+ });
505
+ }
506
+ function ChatBubbleIcon({ size = 26 }) {
507
+ return /* @__PURE__ */ jsxs(
508
+ "svg",
509
+ {
510
+ width: size,
511
+ height: size,
512
+ viewBox: "0 0 24 24",
513
+ fill: "none",
514
+ "aria-hidden": true,
515
+ style: { display: "block" },
516
+ children: [
517
+ /* @__PURE__ */ jsx(
518
+ "path",
519
+ {
520
+ d: "M5 7.2A3.2 3.2 0 0 1 8.2 4h7.6A3.2 3.2 0 0 1 19 7.2v5.6A3.2 3.2 0 0 1 15.8 16H12.1l-3.55 2.75a.75.75 0 0 1-1.2-.6V16H8.2A3.2 3.2 0 0 1 5 12.8V7.2Z",
521
+ fill: "currentColor"
522
+ }
523
+ ),
524
+ /* @__PURE__ */ jsx("circle", { cx: "9.1", cy: "10", r: "1.05", fill: "rgba(0,0,0,0.28)" }),
525
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "10", r: "1.05", fill: "rgba(0,0,0,0.28)" }),
526
+ /* @__PURE__ */ jsx("circle", { cx: "14.9", cy: "10", r: "1.05", fill: "rgba(0,0,0,0.28)" })
527
+ ]
528
+ }
529
+ );
530
+ }
531
+ function CloseIcon({ size = 18 }) {
532
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, style: { display: "block" }, children: /* @__PURE__ */ jsx(
533
+ "path",
534
+ {
535
+ d: "M6.4 6.4a1 1 0 0 1 1.4 0L12 10.6l4.2-4.2a1 1 0 1 1 1.4 1.4L13.4 12l4.2 4.2a1 1 0 0 1-1.4 1.4L12 13.4l-4.2 4.2a1 1 0 0 1-1.4-1.4L10.6 12 6.4 7.8a1 1 0 0 1 0-1.4Z",
536
+ fill: "currentColor"
537
+ }
538
+ ) });
539
+ }
540
+ function BackIcon({ size = 18, flip }) {
541
+ return /* @__PURE__ */ jsx(
542
+ "svg",
543
+ {
544
+ width: size,
545
+ height: size,
546
+ viewBox: "0 0 24 24",
547
+ fill: "none",
548
+ "aria-hidden": true,
549
+ style: { display: "block", transform: flip ? "scaleX(-1)" : void 0 },
550
+ children: /* @__PURE__ */ jsx(
551
+ "path",
552
+ {
553
+ d: "M14.7 5.3a1 1 0 0 1 0 1.4L9.4 12l5.3 5.3a1 1 0 0 1-1.4 1.4l-6-6a1 1 0 0 1 0-1.4l6-6a1 1 0 0 1 1.4 0Z",
554
+ fill: "currentColor"
555
+ }
556
+ )
557
+ }
558
+ );
559
+ }
560
+ function ImageIcon({ size = 18 }) {
561
+ return /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, style: { display: "block" }, children: [
562
+ /* @__PURE__ */ jsx(
563
+ "path",
564
+ {
565
+ d: "M6.5 4h11A2.5 2.5 0 0 1 20 6.5v11A2.5 2.5 0 0 1 17.5 20h-11A2.5 2.5 0 0 1 4 17.5v-11A2.5 2.5 0 0 1 6.5 4Z",
566
+ stroke: "currentColor",
567
+ strokeWidth: "1.7"
568
+ }
569
+ ),
570
+ /* @__PURE__ */ jsx("circle", { cx: "9.2", cy: "9", r: "1.4", fill: "currentColor" }),
571
+ /* @__PURE__ */ jsx(
572
+ "path",
573
+ {
574
+ d: "M5.5 16.2 9.8 12l2.4 2.3 2.6-3.2 4.2 5.1",
575
+ stroke: "currentColor",
576
+ strokeWidth: "1.7",
577
+ strokeLinecap: "round",
578
+ strokeLinejoin: "round"
579
+ }
580
+ )
581
+ ] });
582
+ }
583
+ function SendIcon({ size = 16 }) {
584
+ return /* @__PURE__ */ jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, style: { display: "block" }, children: /* @__PURE__ */ jsx(
585
+ "path",
586
+ {
587
+ d: "M5.2 11.2 19.1 4.4a.8.8 0 0 1 1.1.9l-3.4 14.2a.8.8 0 0 1-1.3.45l-4.2-3.5-2.5 2.4a.7.7 0 0 1-1.2-.45v-4.05l-3.1-1.6a.8.8 0 0 1 .5-1.55Z",
588
+ fill: "currentColor"
589
+ }
590
+ ) });
591
+ }
592
+ function WebComposer({
593
+ sending,
594
+ onSend
595
+ }) {
596
+ const { t, isRtl, theme, pickImage } = useMessengerConfig();
597
+ const [body, setBody] = useState("");
598
+ const [focused, setFocused] = useState(false);
599
+ const fileRef = useRef(null);
600
+ const canSend = Boolean(body.trim()) && !sending;
601
+ async function handleSend() {
602
+ if (!body.trim() || sending) return;
603
+ const text = body;
604
+ setBody("");
605
+ await onSend(text, null);
606
+ }
607
+ async function handlePick() {
608
+ const fromHost = await pickImage();
609
+ if (fromHost) {
610
+ await onSend(body, fromHost);
611
+ setBody("");
612
+ return;
613
+ }
614
+ fileRef.current?.click();
615
+ }
616
+ async function onFileChange(e) {
617
+ const file = e.target.files?.[0];
618
+ e.target.value = "";
619
+ if (!file) return;
620
+ const uri = URL.createObjectURL(file);
621
+ await onSend(body, {
622
+ uri,
623
+ name: file.name,
624
+ type: file.type,
625
+ file
626
+ });
627
+ setBody("");
628
+ }
629
+ return /* @__PURE__ */ jsx(
630
+ "div",
631
+ {
632
+ style: {
633
+ padding: "10px 12px 14px",
634
+ borderTop: `1px solid color-mix(in srgb, ${theme.colors.border} 75%, transparent)`,
635
+ background: `linear-gradient(0deg, ${theme.colors.surface}, color-mix(in srgb, ${theme.colors.surface} 88%, transparent))`
636
+ },
637
+ children: /* @__PURE__ */ jsxs(
638
+ "div",
639
+ {
640
+ style: {
641
+ display: "flex",
642
+ flexDirection: isRtl ? "row-reverse" : "row",
643
+ alignItems: "flex-end",
644
+ gap: 8,
645
+ padding: "6px 6px 6px 8px",
646
+ borderRadius: 18,
647
+ border: `1px solid ${focused ? `color-mix(in srgb, ${theme.accentColor} 45%, ${theme.colors.border})` : `color-mix(in srgb, ${theme.colors.border} 85%, transparent)`}`,
648
+ background: theme.colors.background,
649
+ boxShadow: focused ? `0 0 0 3px color-mix(in srgb, ${theme.accentColor} 16%, transparent)` : "none",
650
+ transition: "border-color 160ms ease, box-shadow 160ms ease"
651
+ },
652
+ children: [
653
+ /* @__PURE__ */ jsx(
654
+ "input",
655
+ {
656
+ ref: fileRef,
657
+ type: "file",
658
+ accept: "image/*",
659
+ hidden: true,
660
+ onChange: onFileChange
661
+ }
662
+ ),
663
+ /* @__PURE__ */ jsx(
664
+ "button",
665
+ {
666
+ type: "button",
667
+ onClick: () => void handlePick(),
668
+ disabled: sending,
669
+ "aria-label": "Attach image",
670
+ style: {
671
+ width: 36,
672
+ height: 36,
673
+ borderRadius: 12,
674
+ border: "none",
675
+ background: "transparent",
676
+ color: theme.colors.textMuted,
677
+ cursor: sending ? "default" : "pointer",
678
+ display: "grid",
679
+ placeItems: "center",
680
+ flexShrink: 0
681
+ },
682
+ children: /* @__PURE__ */ jsx(ImageIcon, { size: 18 })
683
+ }
684
+ ),
685
+ /* @__PURE__ */ jsx(
686
+ "textarea",
687
+ {
688
+ value: body,
689
+ onChange: (e) => setBody(e.target.value),
690
+ onFocus: () => setFocused(true),
691
+ onBlur: () => setFocused(false),
692
+ onKeyDown: (e) => {
693
+ if (e.key === "Enter" && !e.shiftKey) {
694
+ e.preventDefault();
695
+ void handleSend();
696
+ }
697
+ },
698
+ placeholder: t(
699
+ MessengerI18n.typeMessage.key,
700
+ MessengerI18n.typeMessage.fallback
701
+ ),
702
+ rows: 1,
703
+ style: {
704
+ flex: 1,
705
+ resize: "none",
706
+ maxHeight: 96,
707
+ minHeight: 36,
708
+ border: "none",
709
+ padding: "8px 4px",
710
+ fontFamily: theme.fonts.body,
711
+ fontSize: 14,
712
+ color: theme.colors.text,
713
+ background: "transparent",
714
+ textAlign: isRtl ? "right" : "left",
715
+ outline: "none",
716
+ lineHeight: 1.4
717
+ }
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsx(
721
+ "button",
722
+ {
723
+ type: "button",
724
+ onClick: () => void handleSend(),
725
+ disabled: !canSend,
726
+ "aria-label": "Send",
727
+ style: {
728
+ width: 36,
729
+ height: 36,
730
+ borderRadius: 12,
731
+ border: "none",
732
+ background: canSend ? `linear-gradient(145deg, color-mix(in srgb, ${theme.accentColor} 90%, #fff), ${theme.accentColor})` : `color-mix(in srgb, ${theme.colors.surfaceSecondary} 90%, transparent)`,
733
+ color: canSend ? "var(--color-text-inverse, #fff)" : theme.colors.textMuted,
734
+ cursor: canSend ? "pointer" : "default",
735
+ display: "grid",
736
+ placeItems: "center",
737
+ flexShrink: 0,
738
+ boxShadow: canSend ? `0 6px 16px color-mix(in srgb, ${theme.accentColor} 35%, transparent)` : "none",
739
+ transition: "background 160ms ease, box-shadow 160ms ease"
740
+ },
741
+ children: sending ? /* @__PURE__ */ jsx("span", { style: { fontSize: 14, lineHeight: 1 }, children: "\u2026" }) : /* @__PURE__ */ jsx(SendIcon, { size: 15 })
742
+ }
743
+ )
744
+ ]
745
+ }
746
+ )
747
+ }
748
+ );
749
+ }
750
+ function initials2(name) {
751
+ const parts = name.trim().split(/\s+/).filter(Boolean);
752
+ if (parts.length === 0) return "?";
753
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
754
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
755
+ }
756
+ function Bubble({ message }) {
757
+ const { user, isRtl, theme } = useMessengerConfig();
758
+ const mine = message.sender_user_id === user?.id;
759
+ const alignEnd = mine !== isRtl;
760
+ return /* @__PURE__ */ jsxs(
761
+ "div",
762
+ {
763
+ style: {
764
+ alignSelf: alignEnd ? "flex-end" : "flex-start",
765
+ maxWidth: "78%",
766
+ marginBottom: 8,
767
+ padding: "10px 13px",
768
+ borderRadius: mine ? alignEnd ? "18px 18px 6px 18px" : "18px 18px 18px 6px" : alignEnd ? "18px 18px 6px 18px" : "18px 18px 18px 6px",
769
+ background: mine ? `linear-gradient(145deg, color-mix(in srgb, ${theme.accentColor} 88%, #fff), ${theme.accentColor})` : `color-mix(in srgb, ${theme.colors.surfaceSecondary} 92%, ${theme.colors.surface})`,
770
+ color: mine ? "var(--color-text-inverse, #fff)" : theme.colors.text,
771
+ fontFamily: theme.fonts.body,
772
+ fontSize: 14,
773
+ lineHeight: 1.45,
774
+ boxShadow: mine ? `0 8px 20px color-mix(in srgb, ${theme.accentColor} 28%, transparent)` : `0 1px 0 color-mix(in srgb, ${theme.colors.border} 80%, transparent)`,
775
+ border: mine ? "none" : `1px solid color-mix(in srgb, ${theme.colors.border} 70%, transparent)`
776
+ },
777
+ children: [
778
+ message.image_url ? (
779
+ // eslint-disable-next-line @next/next/no-img-element
780
+ /* @__PURE__ */ jsx(
781
+ "img",
782
+ {
783
+ src: message.image_url,
784
+ alt: "",
785
+ style: {
786
+ width: "100%",
787
+ maxWidth: 220,
788
+ borderRadius: 12,
789
+ display: "block",
790
+ marginBottom: message.body ? 8 : 0
791
+ }
792
+ }
793
+ )
794
+ ) : null,
795
+ message.body ? /* @__PURE__ */ jsx("div", { style: { whiteSpace: "pre-wrap" }, children: message.body }) : null
796
+ ]
797
+ }
798
+ );
799
+ }
800
+ function Conversation({
801
+ threadId,
802
+ onBack,
803
+ onClose
804
+ }) {
805
+ const { user, t, isRtl, theme } = useMessengerConfig();
806
+ const listRef = useRef(null);
807
+ const threadQuery = useThread(threadId);
808
+ const threadsQuery = useThreads();
809
+ const sendMutation = useSendMessage(threadId);
810
+ const messages = threadQuery.data?.messages ?? [];
811
+ const threadMeta = threadsQuery.data?.find((th) => th.id === threadId);
812
+ const title = user?.role === "admin" ? threadMeta?.client_name ?? t(MessengerI18n.client.key, MessengerI18n.client.fallback) : t(MessengerI18n.yourCoach.key, MessengerI18n.yourCoach.fallback);
813
+ useEffect(() => {
814
+ const el = listRef.current;
815
+ if (el) el.scrollTop = el.scrollHeight;
816
+ }, [messages.length, threadId]);
817
+ async function handleSend(body, image) {
818
+ if (!body.trim() && !image || sendMutation.isPending) return;
819
+ await sendMutation.mutateAsync({ body, image });
820
+ }
821
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
822
+ /* @__PURE__ */ jsxs(
823
+ "header",
824
+ {
825
+ style: {
826
+ display: "flex",
827
+ flexDirection: isRtl ? "row-reverse" : "row",
828
+ alignItems: "center",
829
+ gap: 10,
830
+ padding: "12px 14px",
831
+ borderBottom: `1px solid color-mix(in srgb, ${theme.colors.border} 80%, transparent)`,
832
+ background: `linear-gradient(180deg, color-mix(in srgb, ${theme.colors.surface} 94%, transparent), color-mix(in srgb, ${theme.colors.surface} 72%, transparent))`,
833
+ backdropFilter: "blur(12px)"
834
+ },
835
+ children: [
836
+ onBack ? /* @__PURE__ */ jsx(
837
+ "button",
838
+ {
839
+ type: "button",
840
+ onClick: onBack,
841
+ "aria-label": t(MessengerI18n.back.key, MessengerI18n.back.fallback),
842
+ style: {
843
+ border: `1px solid color-mix(in srgb, ${theme.colors.border} 90%, transparent)`,
844
+ background: `color-mix(in srgb, ${theme.colors.surfaceSecondary} 75%, transparent)`,
845
+ color: theme.colors.text,
846
+ cursor: "pointer",
847
+ width: 36,
848
+ height: 36,
849
+ borderRadius: 12,
850
+ display: "grid",
851
+ placeItems: "center",
852
+ flexShrink: 0
853
+ },
854
+ children: /* @__PURE__ */ jsx(BackIcon, { flip: isRtl })
855
+ }
856
+ ) : null,
857
+ /* @__PURE__ */ jsx(
858
+ "div",
859
+ {
860
+ "aria-hidden": true,
861
+ style: {
862
+ width: 36,
863
+ height: 36,
864
+ borderRadius: 12,
865
+ flexShrink: 0,
866
+ display: "grid",
867
+ placeItems: "center",
868
+ fontFamily: theme.fonts.displaySemiBold,
869
+ fontWeight: 700,
870
+ fontSize: 12,
871
+ color: theme.accentColor,
872
+ background: `color-mix(in srgb, ${theme.accentColor} 14%, ${theme.colors.surfaceSecondary})`,
873
+ border: `1px solid color-mix(in srgb, ${theme.accentColor} 20%, transparent)`
874
+ },
875
+ children: initials2(title)
876
+ }
877
+ ),
878
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0, textAlign: isRtl ? "right" : "left" }, children: [
879
+ /* @__PURE__ */ jsx(
880
+ "div",
881
+ {
882
+ style: {
883
+ fontFamily: theme.fonts.displayBold,
884
+ fontWeight: 700,
885
+ fontSize: 15,
886
+ color: theme.colors.text,
887
+ overflow: "hidden",
888
+ textOverflow: "ellipsis",
889
+ whiteSpace: "nowrap"
890
+ },
891
+ children: title
892
+ }
893
+ ),
894
+ /* @__PURE__ */ jsx(
895
+ "div",
896
+ {
897
+ style: {
898
+ fontFamily: theme.fonts.body,
899
+ fontSize: 11.5,
900
+ color: theme.colors.textMuted,
901
+ marginTop: 1
902
+ },
903
+ children: t(MessengerI18n.chat.key, MessengerI18n.chat.fallback)
904
+ }
905
+ )
906
+ ] }),
907
+ onClose ? /* @__PURE__ */ jsx(
908
+ "button",
909
+ {
910
+ type: "button",
911
+ onClick: onClose,
912
+ "aria-label": "Close",
913
+ style: {
914
+ border: `1px solid color-mix(in srgb, ${theme.colors.border} 90%, transparent)`,
915
+ background: `color-mix(in srgb, ${theme.colors.surfaceSecondary} 75%, transparent)`,
916
+ color: theme.colors.textSecondary,
917
+ width: 36,
918
+ height: 36,
919
+ borderRadius: 12,
920
+ cursor: "pointer",
921
+ display: "grid",
922
+ placeItems: "center",
923
+ flexShrink: 0
924
+ },
925
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 16 })
926
+ }
927
+ ) : null
928
+ ]
929
+ }
930
+ ),
931
+ /* @__PURE__ */ jsx(
932
+ "div",
933
+ {
934
+ ref: listRef,
935
+ style: {
936
+ flex: 1,
937
+ overflowY: "auto",
938
+ padding: "16px 14px 12px",
939
+ display: "flex",
940
+ flexDirection: "column",
941
+ background: `
942
+ radial-gradient(80% 50% at 50% 0%, color-mix(in srgb, ${theme.accentColor} 6%, transparent), transparent 70%),
943
+ ${theme.colors.background}
944
+ `
945
+ },
946
+ children: threadQuery.isLoading && messages.length === 0 ? /* @__PURE__ */ jsx("div", { style: { color: theme.colors.textMuted, textAlign: "center", marginTop: 40 }, children: "\u2026" }) : messages.length === 0 ? /* @__PURE__ */ jsx(
947
+ "div",
948
+ {
949
+ style: {
950
+ color: theme.colors.textMuted,
951
+ textAlign: "center",
952
+ marginTop: 48,
953
+ fontFamily: theme.fonts.body,
954
+ fontSize: 13.5,
955
+ lineHeight: 1.5,
956
+ padding: "0 24px"
957
+ },
958
+ children: t(
959
+ MessengerI18n.startConversation.key,
960
+ MessengerI18n.startConversation.fallback
961
+ )
962
+ }
963
+ ) : messages.map((m) => /* @__PURE__ */ jsx(Bubble, { message: m }, m.id))
964
+ }
965
+ ),
966
+ /* @__PURE__ */ jsx(WebComposer, { sending: sendMutation.isPending, onSend: handleSend })
967
+ ] });
968
+ }
969
+ function ChatPanel({
970
+ threadId,
971
+ onSelectThread,
972
+ onBackToList,
973
+ onClose,
974
+ variant = "dock"
975
+ }) {
976
+ const { enabled, t, isRtl, theme, onFeatureOff } = useMessengerConfig();
977
+ if (!enabled) {
978
+ if (onFeatureOff) return /* @__PURE__ */ jsx(Fragment, { children: onFeatureOff() });
979
+ return /* @__PURE__ */ jsx(
980
+ "div",
981
+ {
982
+ style: {
983
+ padding: 32,
984
+ textAlign: "center",
985
+ color: theme.colors.textMuted,
986
+ fontFamily: theme.fonts.body
987
+ },
988
+ children: t(MessengerI18n.off.key, MessengerI18n.off.fallback)
989
+ }
990
+ );
991
+ }
992
+ const shell = variant === "page" ? {
993
+ display: "flex",
994
+ flexDirection: "column",
995
+ height: "min(70vh, 640px)",
996
+ maxWidth: 720,
997
+ margin: "0 auto",
998
+ borderRadius: 24,
999
+ overflow: "hidden",
1000
+ border: `1px solid ${theme.colors.border}`,
1001
+ background: theme.colors.surface,
1002
+ boxShadow: `0 24px 64px color-mix(in srgb, ${theme.colors.text} 14%, transparent)`
1003
+ } : {
1004
+ display: "flex",
1005
+ flexDirection: "column",
1006
+ height: "100%",
1007
+ background: `
1008
+ radial-gradient(120% 80% at 100% 0%, color-mix(in srgb, ${theme.accentColor} 14%, transparent), transparent 55%),
1009
+ radial-gradient(90% 60% at 0% 100%, color-mix(in srgb, ${theme.accentColor} 8%, transparent), transparent 50%),
1010
+ ${theme.colors.surface}
1011
+ `
1012
+ };
1013
+ const inThread = threadId != null;
1014
+ return /* @__PURE__ */ jsxs("div", { style: shell, children: [
1015
+ !inThread ? /* @__PURE__ */ jsxs(
1016
+ "header",
1017
+ {
1018
+ style: {
1019
+ display: "flex",
1020
+ flexDirection: isRtl ? "row-reverse" : "row",
1021
+ alignItems: "center",
1022
+ gap: 12,
1023
+ padding: "16px 16px 14px",
1024
+ borderBottom: `1px solid color-mix(in srgb, ${theme.colors.border} 80%, transparent)`,
1025
+ background: `linear-gradient(180deg, color-mix(in srgb, ${theme.colors.surface} 92%, transparent), color-mix(in srgb, ${theme.colors.surface} 70%, transparent))`,
1026
+ backdropFilter: "blur(12px)"
1027
+ },
1028
+ children: [
1029
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0, textAlign: isRtl ? "right" : "left" }, children: [
1030
+ /* @__PURE__ */ jsx(
1031
+ "div",
1032
+ {
1033
+ style: {
1034
+ fontFamily: theme.fonts.displayBold,
1035
+ fontWeight: 700,
1036
+ fontSize: 20,
1037
+ letterSpacing: "-0.02em",
1038
+ color: theme.colors.text,
1039
+ lineHeight: 1.15
1040
+ },
1041
+ children: t(MessengerI18n.messages.key, MessengerI18n.messages.fallback)
1042
+ }
1043
+ ),
1044
+ /* @__PURE__ */ jsx(
1045
+ "div",
1046
+ {
1047
+ style: {
1048
+ marginTop: 3,
1049
+ fontFamily: theme.fonts.body,
1050
+ fontSize: 12.5,
1051
+ color: theme.colors.textMuted
1052
+ },
1053
+ children: t(MessengerI18n.messagesHint.key, MessengerI18n.messagesHint.fallback)
1054
+ }
1055
+ )
1056
+ ] }),
1057
+ onClose ? /* @__PURE__ */ jsx(
1058
+ "button",
1059
+ {
1060
+ type: "button",
1061
+ onClick: onClose,
1062
+ "aria-label": "Close",
1063
+ style: {
1064
+ border: `1px solid color-mix(in srgb, ${theme.colors.border} 90%, transparent)`,
1065
+ background: `color-mix(in srgb, ${theme.colors.surfaceSecondary} 80%, transparent)`,
1066
+ color: theme.colors.textSecondary,
1067
+ width: 36,
1068
+ height: 36,
1069
+ borderRadius: 12,
1070
+ cursor: "pointer",
1071
+ display: "grid",
1072
+ placeItems: "center",
1073
+ flexShrink: 0
1074
+ },
1075
+ children: /* @__PURE__ */ jsx(CloseIcon, { size: 16 })
1076
+ }
1077
+ ) : null
1078
+ ]
1079
+ }
1080
+ ) : null,
1081
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, position: "relative" }, children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: inThread ? /* @__PURE__ */ jsx(
1082
+ motion.div,
1083
+ {
1084
+ initial: { opacity: 0, x: isRtl ? -16 : 16 },
1085
+ animate: { opacity: 1, x: 0 },
1086
+ exit: { opacity: 0, x: isRtl ? 12 : -12 },
1087
+ transition: { duration: 0.22, ease: [0.22, 1, 0.36, 1] },
1088
+ style: { height: "100%" },
1089
+ children: /* @__PURE__ */ jsx(
1090
+ Conversation,
1091
+ {
1092
+ threadId,
1093
+ onBack: onBackToList,
1094
+ onClose
1095
+ }
1096
+ )
1097
+ },
1098
+ `thread-${threadId}`
1099
+ ) : /* @__PURE__ */ jsx(
1100
+ motion.div,
1101
+ {
1102
+ initial: { opacity: 0, x: isRtl ? 16 : -16 },
1103
+ animate: { opacity: 1, x: 0 },
1104
+ exit: { opacity: 0, x: isRtl ? -12 : 12 },
1105
+ transition: { duration: 0.22, ease: [0.22, 1, 0.36, 1] },
1106
+ style: { height: "100%" },
1107
+ children: /* @__PURE__ */ jsx(ThreadList, { onSelectThread })
1108
+ },
1109
+ "list"
1110
+ ) }) })
1111
+ ] });
1112
+ }
1113
+ function ChatDock() {
1114
+ const { enabled, isRtl, theme, t } = useMessengerConfig();
1115
+ const ui = useMessengerUi();
1116
+ const unread = useUnreadTotal({ enabled });
1117
+ if (!enabled) return null;
1118
+ const side = isRtl ? "left" : "right";
1119
+ const open = ui.open;
1120
+ const label = open ? "Close chat" : t(MessengerI18n.messages.key, MessengerI18n.messages.fallback);
1121
+ return /* @__PURE__ */ jsxs(
1122
+ "div",
1123
+ {
1124
+ style: {
1125
+ position: "fixed",
1126
+ bottom: "max(20px, env(safe-area-inset-bottom))",
1127
+ [side]: "max(20px, env(safe-area-inset-right, env(safe-area-inset-left)))",
1128
+ zIndex: 1e4,
1129
+ display: "flex",
1130
+ flexDirection: "column",
1131
+ alignItems: isRtl ? "flex-start" : "flex-end",
1132
+ gap: 14,
1133
+ pointerEvents: "none",
1134
+ // Lets SVG dots / text on accent pick up inverse ink
1135
+ ["--gymlium-on-accent"]: "var(--color-text-inverse, #fff)"
1136
+ },
1137
+ children: [
1138
+ /* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsx(
1139
+ motion.div,
1140
+ {
1141
+ initial: { opacity: 0, y: 28, scale: 0.94, filter: "blur(6px)" },
1142
+ animate: { opacity: 1, y: 0, scale: 1, filter: "blur(0px)" },
1143
+ exit: { opacity: 0, y: 18, scale: 0.96, filter: "blur(4px)" },
1144
+ transition: { duration: 0.32, ease: [0.22, 1, 0.36, 1] },
1145
+ style: {
1146
+ width: "min(380px, calc(100vw - 28px))",
1147
+ height: "min(440px, calc(100vh - 160px))",
1148
+ maxHeight: 440,
1149
+ borderRadius: 24,
1150
+ overflow: "hidden",
1151
+ border: `1px solid color-mix(in srgb, ${theme.colors.border} 85%, transparent)`,
1152
+ background: theme.colors.surface,
1153
+ boxShadow: `
1154
+ 0 0 0 1px color-mix(in srgb, ${theme.accentColor} 12%, transparent),
1155
+ 0 28px 80px color-mix(in srgb, ${theme.colors.text} 22%, transparent),
1156
+ 0 8px 24px color-mix(in srgb, ${theme.accentColor} 18%, transparent)
1157
+ `,
1158
+ pointerEvents: "auto",
1159
+ backdropFilter: "blur(20px)"
1160
+ },
1161
+ children: /* @__PURE__ */ jsx(
1162
+ ChatPanel,
1163
+ {
1164
+ threadId: ui.threadId,
1165
+ onSelectThread: ui.openThread,
1166
+ onBackToList: ui.openInbox,
1167
+ onClose: ui.close,
1168
+ variant: "dock"
1169
+ }
1170
+ )
1171
+ },
1172
+ "panel"
1173
+ ) : null }),
1174
+ /* @__PURE__ */ jsxs(
1175
+ motion.button,
1176
+ {
1177
+ type: "button",
1178
+ onClick: () => open ? ui.close() : ui.openInbox(),
1179
+ "aria-label": label,
1180
+ "aria-expanded": open,
1181
+ whileHover: { scale: 1.05 },
1182
+ whileTap: { scale: 0.94 },
1183
+ transition: { type: "spring", stiffness: 420, damping: 24 },
1184
+ style: {
1185
+ pointerEvents: "auto",
1186
+ width: 58,
1187
+ height: 58,
1188
+ borderRadius: 20,
1189
+ border: "none",
1190
+ padding: 0,
1191
+ background: `
1192
+ radial-gradient(120% 120% at 30% 20%, color-mix(in srgb, #fff 28%, transparent), transparent 55%),
1193
+ linear-gradient(145deg, color-mix(in srgb, ${theme.accentColor} 88%, #fff) 0%, ${theme.accentColor} 55%, color-mix(in srgb, ${theme.accentColor} 78%, #000) 100%)
1194
+ `,
1195
+ color: "var(--color-text-inverse, #fff)",
1196
+ cursor: "pointer",
1197
+ boxShadow: `
1198
+ 0 14px 36px color-mix(in srgb, ${theme.accentColor} 42%, transparent),
1199
+ 0 2px 0 color-mix(in srgb, #fff 22%, transparent) inset
1200
+ `,
1201
+ position: "relative",
1202
+ display: "grid",
1203
+ placeItems: "center",
1204
+ overflow: "visible"
1205
+ },
1206
+ children: [
1207
+ !open && unread.total > 0 ? /* @__PURE__ */ jsx(
1208
+ motion.span,
1209
+ {
1210
+ "aria-hidden": true,
1211
+ initial: { opacity: 0.55, scale: 1 },
1212
+ animate: { opacity: [0.45, 0.15, 0.45], scale: [1, 1.28, 1] },
1213
+ transition: { duration: 2.4, repeat: Infinity, ease: "easeInOut" },
1214
+ style: {
1215
+ position: "absolute",
1216
+ inset: -6,
1217
+ borderRadius: 24,
1218
+ background: `color-mix(in srgb, ${theme.accentColor} 35%, transparent)`,
1219
+ zIndex: -1
1220
+ }
1221
+ }
1222
+ ) : null,
1223
+ /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: /* @__PURE__ */ jsx(
1224
+ motion.span,
1225
+ {
1226
+ initial: { opacity: 0, rotate: open ? -40 : 40, scale: 0.7 },
1227
+ animate: { opacity: 1, rotate: 0, scale: 1 },
1228
+ exit: { opacity: 0, rotate: open ? 40 : -40, scale: 0.7 },
1229
+ transition: { duration: 0.18 },
1230
+ style: { display: "grid", placeItems: "center", lineHeight: 0 },
1231
+ children: open ? /* @__PURE__ */ jsx(CloseIcon, { size: 20 }) : /* @__PURE__ */ jsx(ChatBubbleIcon, { size: 26 })
1232
+ },
1233
+ open ? "close" : "chat"
1234
+ ) }),
1235
+ !open && unread.total > 0 ? /* @__PURE__ */ jsx(
1236
+ motion.span,
1237
+ {
1238
+ initial: { scale: 0 },
1239
+ animate: { scale: 1 },
1240
+ style: {
1241
+ position: "absolute",
1242
+ top: -3,
1243
+ [isRtl ? "left" : "right"]: -3,
1244
+ minWidth: 22,
1245
+ height: 22,
1246
+ borderRadius: 11,
1247
+ padding: "0 6px",
1248
+ background: theme.colors.surface,
1249
+ color: theme.colors.text,
1250
+ fontSize: 11,
1251
+ fontWeight: 700,
1252
+ fontFamily: theme.fonts.bodySemiBold,
1253
+ display: "inline-flex",
1254
+ alignItems: "center",
1255
+ justifyContent: "center",
1256
+ border: `2px solid ${theme.accentColor}`,
1257
+ boxShadow: `0 4px 12px color-mix(in srgb, ${theme.colors.text} 25%, transparent)`
1258
+ },
1259
+ children: unread.total > 99 ? "99+" : unread.total
1260
+ }
1261
+ ) : null
1262
+ ]
1263
+ }
1264
+ )
1265
+ ]
1266
+ }
1267
+ );
1268
+ }
1269
+
1270
+ export { ChatDock, ChatPanel, Conversation, ThreadList, WebComposer };
1271
+ //# sourceMappingURL=index.js.map
1272
+ //# sourceMappingURL=index.js.map