PageSourceSearch

https://msaood.com/assets/js/live-chat.js

js msaood.com collected 2026-10-02 01:36:28 UTC 27,766 bytes, 663 lines download raw bytes

1(function() {
2  const chatTemplate = `
3    <style>
4      #liveChatBubble {
5        position: fixed;
6        bottom: 25px;
7        left: 25px;
8        z-index: 9998;
9        background: #ff4444;
10        color: #fff;
11        width: 54px;
12        height: 54px;
13        border-radius: 50%;
14        display: flex;
15        align-items: center;
16        justify-content: center;
17        font-size: 22px;
18        cursor: pointer;
19        border: 2px solid #11120f;
20        box-shadow: 3px 3px 0px #11120f;
21        user-select: none;
22        transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.2s ease, background-color 0.2s ease, bottom 0.2s ease;
23      }
24      #liveChatBubble:hover {
25        transform: translateY(-4px) scale(1.08);
26        box-shadow: 5px 5px 0px #11120f;
27        background: #ff2a2a;
28      }
29      #liveChatBubble:active {
30        transform: translate(1px, 1px) scale(0.94);
31        box-shadow: 1px 1px 0px #11120f;
32      }
33      #liveChatBubble.is-open {
34        background: #11120f;
35      }
36      #liveChatBubble #chatBubbleIcon {
37        display: inline-block;
38        transition: transform 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
39      }
40      #liveChatBubble.is-open #chatBubbleIcon {
41        transform: rotate(90deg) scale(1.1);
42      }
43
44      #liveChatWidget {
45        display: none;
46        position: fixed;
47        bottom: 90px;
48        left: 25px;
49        width: min(340px, calc(100vw - 30px));
50        height: 480px;
51        background: #f8f6ef;
52        border: 2px solid #11120f;
53        box-shadow: 6px 6px 0px #11120f;
54        z-index: 9999;
55        flex-direction: column;
56        border-radius: 4px;
57        overflow: hidden;
58        font-family: 'Cairo', 'NotoColorEmoji', sans-serif;
59        box-sizing: border-box;
60        transform-origin: bottom left;
61      }
62      #liveChatWidget.chat-anim-in {
63        display: flex !important;
64        animation: chatWidgetPopIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
65      }
66      #liveChatWidget.chat-anim-out {
67        display: flex !important;
68        animation: chatWidgetPopOut 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards;
69      }
70
71      @media (max-width: 800px) {
72        #liveChatBubble {
73          bottom: 95px !important;
74          left: 18px !important;
75          width: 48px !important;
76          height: 48px !important;
77          font-size: 20px !important;
78        }
79        #liveChatWidget {
80          bottom: 155px !important;
81          left: 15px !important;
82          right: 15px !important;
83          width: calc(100vw - 30px) !important;
84          height: min(440px, calc(100dvh - 175px)) !important;
85          transform-origin: bottom center;
86        }
87      }
88
89      .chat-btn-action {
90        transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.18s ease, background-color 0.18s ease;
91      }
92      .chat-btn-action:hover {
93        transform: translateY(-2px);
94        box-shadow: 3px 3px 0px #11120f !important;
95      }
96      .chat-btn-action:active {
97        transform: translate(1px, 1px);
98        box-shadow: 0px 0px 0px #11120f !important;
99      }
100
101      .rating-btn {
102        background: #fff;
103        border: 1px solid #11120f;
104        font-size: 20px;
105        padding: 4px 8px;
106        cursor: pointer;
107        border-radius: 4px;
108        box-shadow: 1px 1px 0px #11120f;
109        transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1), background-color 0.18s ease;
110      }
111      .rating-btn:hover, .rating-btn.active {
112        transform: translateY(-3px) scale(1.22);
113        background: #fee2e2;
114        border-color: #ff4444;
115        box-shadow: 2px 2px 0px #11120f;
116      }
117
118      .typing-wave-dot {
119        display: inline-block;
120        animation: typingWave 1.2s infinite ease-in-out;
121      }
122      @keyframes typingWave {
123        0%, 60%, 100% { transform: translateY(0); }
124        30% { transform: translateY(-4px); }
125      }
126
127      .chat-msg-bubble {
128        animation: bubblePopIn 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
129        transition: transform 0.15s ease;
130      }
131      .chat-msg-bubble:hover { transform: translateY(-1px); }
132
133      .chat-input-focus {
134        transition: border-color 0.2s ease, box-shadow 0.2s ease;
135      }
136      .chat-input-focus:focus {
137        border-color: #ff4444 !important;
138        box-shadow: inset 1px 1px 2px rgba(0,0,0,0.08), 0 0 0 2px #fee2e2 !important;
139      }
140
141      @keyframes chatWidgetPopIn {
142        0% { opacity: 0; transform: scale(0.85) translateY(24px); }
143        100% { opacity: 1; transform: scale(1) translateY(0); }
144      }
145      @keyframes chatWidgetPopOut {
146        0% { opacity: 1; transform: scale(1) translateY(0); }
147        100% { opacity: 0; transform: scale(0.88) translateY(20px); }
148      }
149      @keyframes bubblePopIn {
150        0% { opacity: 0; transform: scale(0.82) translateY(12px); }
151        100% { opacity: 1; transform: scale(1) translateY(0); }
152      }
153      @keyframes leadBoxFade {
154        from { opacity: 0; transform: translateY(8px); }
155        to { opacity: 1; transform: translateY(0); }
156      }
157    </style>
158
159    <div id="liveChatBubble" onclick="toggleLiveChat()">
160      <span id="chatBubbleIcon">💬</span>
161    </div>
162
163    <div id="liveChatWidget">
164      <div style="background: #11120f; color: #fff; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #11120f; flex-shrink: 0;">
165        <div>
166          <b style="font-size: 13px; display: block; line-height: 1.2;">مسعود عبدالله</b>
167          <small id="chatStatusText" style="color: #4ade80; font-size: 10px; font-weight: 600;">● متصل وجاهز للرد</small>
168        </div>
169        <span onclick="toggleLiveChat()" style="cursor: pointer; font-size: 18px; font-weight: 700; color: #fff; padding: 0 4px; transition: transform 0.18s ease;" onmouseenter="this.style.transform='rotate(90deg)'" onmouseleave="this.style.transform='none'">✕</span>
170      </div>
171
172      <div id="leadFormBox" style="display: none; padding: 18px; background: #f8f6ef; flex-direction: column; gap: 10px; justify-content: center; flex: 1; animation: leadBoxFade 0.25s ease;">
173        <p id="leadGreetingTitle" style="font-size: 13px; font-weight: 700; margin: 0 0 4px; color: #11120f;">مرحباً بك! 👋</p>
174        <p id="leadGreetingDesc" style="font-size: 11.5px; color: #6c6b64; margin: 0 0 8px; line-height: 1.6;">يسعدني التواصل معك، أدخل بياناتك للبدء ومتابعة استفسارك:</p>
175        <input type="text" id="leadName" class="chat-input-focus" placeholder="اسمك الكريم" style="padding: 10px; border: 1px solid #11120f; font-family: inherit; font-size: 12px; outline: none; background: #fff; color: #11120f; border-radius: 3px;">
176        <input type="email" id="leadEmail" class="chat-input-focus" placeholder="البريد الإلكتروني (اختياري)" style="padding: 10px; border: 1px solid #11120f; font-family: inherit; font-size: 12px; outline: none; background: #fff; color: #11120f; border-radius: 3px;">
177        <button onclick="startChatWithLead()" class="chat-btn-action" style="background: #ff4444; color: #fff; border: 1px solid #11120f; padding: 10px; font-size: 12px; font-weight: 700; cursor: pointer; font-family: inherit; box-shadow: 2px 2px 0px #11120f; border-radius: 3px;">بدء المحادثة 🚀</button>
178        <button onclick="skipLeadAndStart()" style="background: transparent; color: #6c6b64; border: none; font-size: 11px; cursor: pointer; text-decoration: underline; font-family: inherit; margin-top: 4px; transition: color 0.15s;" onmouseenter="this.style.color='#11120f'" onmouseleave="this.style.color='#6c6b64'">تخطي والبدء كزائر</button>
179      </div>
180
181      <div id="liveChatLogs" style="flex: 1; min-height: 0; padding: 14px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; background: #eeeae1; scroll-behavior: smooth;"></div>
182
183      <div id="typingIndicator" style="display: none; padding: 5px 14px; font-size: 11px; font-weight: 600; color: #15803d; background: #dcfce7; border-top: 1px solid #86efac;">
184        <span class="typing-wave-dot">✍️</span> مسعود يكتب ردّاً الآن...
185      </div>
186
187      <form id="liveChatForm" onsubmit="sendLiveClientMsg(event)" style="display: flex; border-top: 2px solid #11120f; background: #fff; margin: 0; flex-shrink: 0;">
188        <input type="text" id="liveChatInput" placeholder="اكتب رسالتك هنا..." required autocomplete="off" oninput="handleVisitorTyping()" style="flex: 1; border: none; padding: 12px 14px; font-family: inherit; font-size: 13px; outline: none; background: transparent; color: #11120f;">
189        <button type="submit" id="liveChatSendBtn" style="background: #ff4444; color: #fff; border: none; border-right: 2px solid #11120f; padding: 0 18px; font-weight: 700; font-size: 13px; cursor: pointer; font-family: inherit; transition: background 0.15s ease;" onmouseenter="this.style.background='#ff2a2a'" onmouseleave="this.style.background='#ff4444'">إرسال</button>
190      </form>
191
192      <div id="chatEndedBar" style="display: none; padding: 12px 14px; background: #f8f6ef; border-top: 2px solid #11120f; text-align: center; flex-direction: column; gap: 6px; flex-shrink: 0; max-height: 220px; overflow-y: auto;">
193        <div id="ratingSection" style="display: flex; flex-direction: column; gap: 5px;">
194          <span style="font-size: 11.5px; font-weight: 700; color: #11120f;">كيف كانت تجربتك معنا؟ ⭐</span>
195          <div style="display: flex; justify-content: center; gap: 8px;">
196            <button type="button" class="rating-btn" onclick="submitRating(5, this)" title="ممتاز">😍</button>
197            <button type="button" class="rating-btn" onclick="submitRating(4, this)" title="جيد">😊</button>
198            <button type="button" class="rating-btn" onclick="submitRating(3, this)" title="عادي">😐</button>
199            <button type="button" class="rating-btn" onclick="submitRating(1, this)" title="سيء">😞</button>
200          </div>
201          <div id="feedbackBox" style="display: none; margin-top: 4px;">
202            <input type="text" id="visitorFeedbackText" class="chat-input-focus" placeholder="ملاحظة إضافية (اختياري)..." style="width: 100%; padding: 6px 8px; border: 1px solid #11120f; font-family: inherit; font-size: 11px; outline: none; background: #fff; box-sizing: border-box; border-radius: 3px;">
203            <button onclick="saveFeedbackText()" class="chat-btn-action" style="width: 100%; margin-top: 4px; background: #11120f; color: #fff; border: none; padding: 5px; font-size: 10.5px; font-weight: 700; cursor: pointer; font-family: inherit; border-radius: 3px;">إرسال الملاحظة ✓</button>
204          </div>
205          <span id="ratingStatusText" style="font-size: 10.5px; color: #2e7d32; font-weight: 700; display: none;">شكراً لتقييمك! ❤️</span>
206        </div>
207
208        <div style="display: flex; gap: 6px; margin-top: 6px;">
209          <button onclick="sendTranscriptViaEmail()" class="chat-btn-action" style="flex: 1; background: #fff; color: #11120f; border: 1px solid #11120f; padding: 6px; font-size: 10.5px; font-weight: 700; cursor: pointer; font-family: inherit; box-shadow: 1px 1px 0px #11120f; border-radius: 3px;">إرسال نسخة لإيميلي ✉️</button>
210          <button onclick="startNewChatFlow()" class="chat-btn-action" style="flex: 1; background: #ff4444; color: #fff; border: 1px solid #11120f; padding: 6px; font-size: 10.5px; font-weight: 700; cursor: pointer; font-family: inherit; box-shadow: 1px 1px 0px #11120f; border-radius: 3px;">محادثة جديدة 🔄</button>
211        </div>
212      </div>
213    </div>
214  `;
215
216  function mount() {
217    if (document.getElementById('liveChatContainer')) return;
218    const el = document.createElement('div');
219    el.id = 'liveChatContainer';
220    el.innerHTML = chatTemplate;
221    document.body.appendChild(el);
222  }
223
224  function ensureDependencies() {
225    if (!window.PocketBase) {
226      const s = document.createElement('script');
227      s.src = 'https://cdnjs.cloudflare.com/ajax/libs/pocketbase/0.21.3/pocketbase.umd.js';
228      s.onload = () =>
228 { initClientApp(); };
229      document.head.appendChild(s);
230    } else {
231      initClientApp();
232    }
233  }
234
235  if (document.readyState === 'loading') {
236    document.addEventListener('DOMContentLoaded', () => { mount(); ensureDependencies(); });
237  } else {
238    mount();
239    ensureDependencies();
240  }
241})();
242
243let pbClient;
244let activeChatId = localStorage.getItem('pb_chat_session_id');
245let renderedIds = new Set();
246let clientLastDate = null;
247let isHostOnline = true;
248let currentChatCachedData = null;
249let visitorTypingTimeout = null;
250
251function initClientApp() {
252  if (typeof PocketBase === 'undefined') return;
253  pbClient = new PocketBase('https://api.msaood.com');
254  pbClient.autoCancellation(false);
255  checkAdminOnlineStatus();
256  if (activeChatId) {
257    subscribeToCurrentChat();
258  }
259}
260
261function formatMessageText(rawText) {
262  const div = document.createElement('div');
263  div.innerText = rawText || '';
264  let safeText = div.innerHTML;
265
266  const urlRegex = /(https?:\/\/[^\s]+)/g;
267  safeText = safeText.replace(urlRegex, (url) => `<a href="${url}" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline; font-weight: 700;">${url} ↗</a>`);
268
269  const emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g;
270  safeText = safeText.replace(emailRegex, (email) => `<a href="mailto:${email}" style="color: inherit; text-decoration: underline; font-weight: 700;">${email}</a>`);
271
272  return safeText;
273}
274
275function formatDateLabel(dateStr) {
276  const d = new Date(dateStr);
277  const today = new Date();
278  const yesterday = new Date();
279  yesterday.setDate(today.getDate() - 1);
280
281  if (d.toDateString() === today.toDateString()) return 'اليوم';
282  if (d.toDateString() === yesterday.toDateString()) return 'أمس';
283  return d.toLocaleDateString('ar-SA', { day: 'numeric', month: 'short' });
284}
285
286window.toggleLiveChat = function() {
287  const widget = document.getElementById('liveChatWidget');
288  const bubble = document.getElementById('liveChatBubble');
289  const icon = document.getElementById('chatBubbleIcon');
290  if (!widget) return;
291
292  const isOpen = widget.classList.contains('chat-anim-in');
293
294  if (isOpen) {
295    widget.classList.remove('chat-anim-in');
296    widget.classList.add('chat-anim-out');
297    if (bubble) bubble.classList.remove('is-open');
298    if (icon) icon.innerText = '💬';
299    setTimeout(() => {
300      widget.classList.remove('chat-anim-out');
301      widget.style.display = 'none';
302    }, 200);
303  } else {
304    widget.classList.remove('chat-anim-out');
305    widget.classList.add('chat-anim-in');
306    if (bubble) bubble.classList.add('is-open');
307    if (icon) icon.innerText = '✕';
308    checkAdminOnlineStatus();
309    checkAndInitSession();
310  }
311};
312
313async function checkAdminOnlineStatus() {
314  if (!pbClient) return;
315  try {
316    const list = await pbClient.collection('admin_settings').getList(1, 1, { requestKey: null });
317    if (list.items.length > 0) {
318      isHostOnline = list.items[0].is_online;
319    }
320  } catch (_) {}
321
322  const status = document.getElementById('chatStatusText');
323  const desc = document.getElementById('leadGreetingDesc');
324  if (isHostOnline) {
325    if (status) { status.innerText = '● متصل وجاهز للرد'; status.style.color = '#4ade80'; }
326    if (desc) desc.innerText = 'يسعدني التواصل معك، تفضل بإدخال بياناتك لبدء المحادثة:';
327  } else {
328    if (status) { status.innerText = '○ غير متصل حالياً'; status.style.color = '#cbd5e1'; }
329    if (desc) desc.innerText = 'أنا غير متصل حالياً. اترك رسالتك وإيميلك وسأعاود الرد عليك فور عودتي! 📬';
330  }
331}
332
333async function checkAndInitSession() {
334  if (!pbClient) initClientApp();
335  if (activeChatId && pbClient) {
336    try {
337      const chat = await pbClient.collection('chats').getOne(activeChatId, { requestKey: null });
338      currentChatCachedData = chat;
339      if (chat.status === 'closed' || chat.status === 'banned') {
340        showChatEndedState();
341        await loadPastMessages();
342        return;
343      }
344      showChatInterface();
345      await loadPastMessages();
346      subscribeToCurrentChat();
347      return;
348    } catch (_) {
349      // إذا كانت الجلسة محذوفة أو غير صالحة، مسحها فوراً لتجنب تعليق الواجهة
350      localStorage.removeItem('pb_chat_session_id');
351      activeChatId = null;
352      currentChatCachedData = null;
353    }
354  }
355  showLeadForm();
356}
357
358function showLeadForm() {
359  const formBox = document.getElementById('leadFormBox');
360  if (formBox) formBox.style.display = 'flex';
361  const logs = document.getElementById('liveChatLogs');
362  if (logs) logs.style.display = 'none';
363  const form = document.getElementById('liveChatForm');
364  if (form) form.style.display = 'none';
365  const ended = document.getElementById('chatEndedBar');
366  if (ended) ended.style.display = 'none';
367  const typing = document.getElementById('typingIndicator');
368  if (typing) typing.style.display = 'none';
369}
370
371function showChatInterface() {
372  const formBox = document.getElementById('leadFormBox');
373  if (formBox) formBox.style.display = 'none';
374  const logs = document.getElementById('liveChatLogs');
375  if (logs) logs.style.display = 'flex';
376  const form = document.getElementById('liveChatForm');
377  if (form) form.style.display = 'flex';
378  const ended = document.getElementById('chatEndedBar');
379  if (ended) ended.style.display = 'none';
380  setTimeout(() => {
381    const input = document.getElementById('liveChatInput');
382    if (input) input.focus();
383  }, 100);
384}
385
386function showChatEndedState() {
387  const formBox = document.getElementById('leadFormBox');
388  if (formBox) formBox.style.display = 'none';
389  const logs = document.getElementById('liveChatLogs');
390  if (logs) logs.style.display = 'flex';
391  const form = document.getElementById('liveChatForm');
392  if (form) form.style.display = 'none';
393  const ended = document.getElementById('chatEndedBar');
394  if (ended) ended.style.display = 'flex';
395  const typing = document.getElementById('typingIndicator');
396  if (typing) typing.style.display = 'none';
397  const status = document.getElementById('chatStatusText');
398  if (status) {
399    status.innerText = '● المحادثة مغلقة';
400    status.style.color = '#6c6b64';
401  }
402}
403
404function handleVisitorTyping() {
405  if (!activeChatId || !pbClient) return;
406  pbClient.collection('chats').update(activeChatId, { is_visitor_typing: true }, { requestKey: null }).catch(() => {});
407
408  if (visitorTypingTimeout) clearTimeout(visitorTypingTimeout);
409  visitorTypingTimeout = setTimeout(() => {
410    if (activeChatId && pbClient) {
411      pbClient.collection('chats').update(activeChatId, { is_visitor_typing: false }, { requestKey: null }).catch(() => {});
412    }
413  }, 2500);
414}
415
416window.submitRating = async function(val, btn) {
417  if (!activeChatId || !pbClient) return;
418  document.querySelectorAll('.rating-btn').forEach(b => b.classList.remove('active'));
419  btn.classList.add('active');
420
421  try {
422    await pbClient.collection('chats').update(activeChatId, { rating: val }, { requestKey: null });
423    document.getElementById('feedbackBox').style.display = 'block';
424    const status = document.getElementById('ratingStatusText');
425    status.style.display = 'block';
426    status.innerText = 'تم تسجيل التقييم، شكراً لك! ⭐';
427  } catch (e) {
428    console.error('فشل حفظ التقييم:', e);
429  }
430};
431
432window.saveFeedbackText = async function() {
433  if (!activeChatId || !pbClient) return;
434  const txt = document.getElementById('visitorFeedbackText').value.trim();
435  if (!txt) return;
436
437  try {
438    await pbClient.collection('chats').update(activeChatId, { feedback: txt }, { requestKey: null });
439    const status = document.getElementById('ratingStatusText');
440    status.innerText = 'تم استلام ملاحظتك بنجاح! ❤️';
441    document.getElementById('feedbackBox').style.display = 'none';
442  } catch (e) {
443    console.error('فشل حفظ الملاحظة:', e);
444  }
445};
446
447window.sendTranscriptViaEmail = async function() {
448  if (!activeChatId || !pbClient) return;
449  try {
450    let email = currentChatCachedData ? currentChatCachedData.visitor_email : '';
451    if (!email) {
452      email = prompt('أدخل بريدك الإلكتروني لتجهيز نسخة المحادثة:');
453      if (!email) return;
454    }
455
456    const res = await pbClient.collection('chat_messages').getList(1, 50, {
457      filter: `chat = "${activeChatId}"`,
458      sort: 'created',
459      requestKey: null
460    });
461
462    let bodyText = `سجل محادثتك مع مسعود عبدالله:\n\n`;
463    res.items.forEach(m => {
464      const senderName = m.sender === 'admin' ? 'مسعود' : 'أنت';
465      const time = new Date(m.created).toLocaleTimeString('ar-SA', { hour: '2-digit', minute: '2-digit' });
466      bodyText += `[${time}] ${senderName}: ${m.text}\n`;
467    });
468
469    bodyText += `\n---\nرابط الموقع: https://msaood.com`;
470
471    const mailtoUri = `mailto:${encodeURIComponent(email)}?subject=${encodeURIComponent('نسخة محادثة الدعم المباشر — مسعود عبدالله')}&body=${encodeURIComponent(bodyText)}`;
472    window.location.href = mailtoUri;
473  } catch (e) {
474    alert('تعذر استخراج السجل.');
475  }
476};
477
478window.startNewChatFlow = function() {
479  if (activeChatId && pbClient) {
480    pbClient.collection('chats').unsubscribe(activeChatId).catch(() => {});
481  }
482  if (pbClient) pbClient.collection('chat_messages').unsubscribe('*').catch(() => {});
483  localStorage.removeItem('pb_chat_session_id');
484  activeChatId = null;
485  currentChatCachedData = null;
486  resetLogsUI();
487  checkAdminOnlineStatus();
488  document.getElementById('leadName').value = '';
489  document.getElementById('leadEmail').value = '';
490  document.getElementById('feedbackBox').style.display = 'none';
491  document.getElementById('ratingStatusText').style.display = 'none';
492  showLeadForm();
493};
494
495window.startChatWithLead = async function() {
496  const name = document.getElementById('leadName').value.trim();
497  const email = document.getElementById('leadEmail').value.trim();
498  await createNewChatSession(name, email);
499  showChatInterface();
500};
501
502window.skipLeadAndStart = async function() {
503  await createNewChatSession();
504  showChatInterface();
505};
506
507function resetLogsUI() {
508  renderedIds.clear();
509  clientLastDate = null;
510  const logs = document.getElementById('liveChatLogs');
511  if (logs) logs.innerHTML = '';
512}
513
514async function createNewChatSession(name = '', email = '') {
515  if (!pbClient) initClientApp();
516  try {
517    const chat = await pbClient.collection('chats').create({
518      session_id: 'guest_' + Math.random().toString(36).substring(2, 10),
519      visitor_name: name,
520      visitor_email: email,
521      page_url: window.location.pathname,
522      status: 'active',
523      is_typing: false,
524      is_visitor_typing: false
525    }, { requestKey: null });
526
527    activeChatId = chat.id;
528    currentChatCachedData = chat;
529    localStorage.setItem('pb_chat_session_id', activeChatId);
530    resetLogsUI();
531
532    const visitorDisplayName = name ? `يا ${name}` : 'بك';
533    const autoWelcomeText = isHostOnline
534      ? `أهلاً ${visitorDisplayName}! معك مسعود عبدالله. تفضل باستفسارك وجاري الرد عليك فوراً. 👋`
535      : `أهلاً ${visitorDisplayName}! أنا غير متصل حالياً على لوحة المحادثات. تم استلام رسالتك وسأتواصل معك عبر الإيميل في أقرب وقت بإذن الله. 📬`;
536
537    await pbClient.collection('chat_messages').create({
538      chat: activeChatId,
539      sender: 'admin',
540      text: autoWelcomeText
541    }, { requestKey: null });
542
543    await loadPastMessages();
544    subscribeToCurrentChat();
545  } catch (e) {
546    console.error('فشل إنشاء الجلسة:', e);
547  }
548}
549
550async function loadPastMessages() {
551  if (!activeChatId || !pbClient) return;
552  try {
553    const res = await pbClient.collection('chat_messages').getList(1, 50, {
554      filter: `chat = "${activeChatId}"`,
555      sort: 'created',
556      requestKey: null
557    });
558    resetLogsUI();
559    res.items.forEach(msg => {
560      renderedIds.add(msg.id);
561      renderMessageBubble(msg.text, msg.sender, msg.created);
562    });
563  } catch (_) {}
564}
565
566function subscribeToCurrentChat() {
567  if (!activeChatId || !pbClient) return;
568
569  pbClient.collection('chat_messages').subscribe('*', function (e) {
570    if (e.action === 'create' && e.record.chat === activeChatId) {
571      if (!renderedIds.has(e.record.id)) {
572        renderedIds.add(e.record.id);
573        renderMessageBubble(e.record.text, e.record.sender, e.record.created);
574      }
575    }
576  });
577
578  pbClient.collection('chats').subscribe(activeChatId, function (e) {
579    if (e.action === 'update') {
580      currentChatCachedData = e.record;
581      const typingEl = document.getElementById('typingIndicator');
582      if (typingEl) {
583        if (e.record.is_typing && e.record.status !== 'closed') {
584          typingEl.style.display = 'block';
585        } else {
586          typingEl.style.display = 'none';
587        }
588      }
589
590      if (e.record.status === 'closed') {
591        showChatEndedState();
592      }
593    }
594  });
595}
596
597function renderMessageBubble(text, sender, createdAt = null) {
598  const logs = document.getElementById('liveChatLogs');
599  if (!logs) return;
600  const isVisitor = sender === 'visitor';
601  const msgDate = createdAt ? new Date(createdAt) : new Date();
602  const dateKey = msgDate.toDateString();
603
604  if (clientLastDate !== dateKey) {
605    clientLastDate = dateKey;
606    const dateDiv = document.createElement('div');
607    dateDiv.className = 'chat-msg-bubble';
608    dateDiv.style.cssText = 'align-self: center; background: #e2ddd3; border: 1px solid #11120f; padding: 2px 8px; font-size: 10px; font-weight: 700; color: #6c6b64; margin: 4px 0; border-radius: 3px;';
609    dateDiv.innerText = formatDateLabel(msgDate);
610    logs.appendChild(dateDiv);
611  }
612
613  const bubble = document.createElement('div');
614  bubble.className = 'chat-msg-bubble';
615  bubble.style.cssText = `
616    align-self: ${isVisitor ? 'flex-end' : 'flex-start'};
617    background: ${isVisitor ? '#ff4444' : '#ffffff'};
618    color: ${isVisitor ? '#ffffff' : '#11120f'};
619    border: 1px solid #11120f;
620    padding: 8px 12px 6px;
621    font-size: 12px;
622    max-width: 85%;
623    box-shadow: 2px 2px 0px #11120f;
624    line-height: 1.6;
625    word-break: break-word;
626    display: flex;
627    flex-direction: column;
628    border-radius: 4px;
629    transform-origin: ${isVisitor ? 'bottom left' : 'bottom right'};
630  `;
631  
632  const timeFormatted = msgDate.toLocaleTimeString('ar-SA', { hour: '2-digit', minute: '2-digit' });
633  bubble.innerHTML = `
634    <div>${formatMessageText(text)}</div>
635    <span style="font-size: 9px; align-self: flex-end; margin-top: 3px; opacity: 0.8; font-family: monospace;">${timeFormatted}</span>
636  `;
637
638  logs.appendChild(bubble);
639  logs.scrollTop = logs.scrollHeight;
640}
641
642window.sendLiveClientMsg = async function(e) {
643  e.preventDefault();
644  const input = document.getElementById('liveChatInput');
645  const text = input.value.trim();
646  if (!text) return;
647
648  if (!activeChatId) await createNewChatSession();
649  if (!activeChatId || !pbClient) return;
650
651  input.value = '';
652  pbClient.collection('chats').update(activeChatId, { is_visitor_typing: false }, { requestKey: null }).catch(() => {});
653
654  try {
655    await pbClient.collection('chat_messages').create({
656      chat: activeChatId,
657      sender: 'visitor',
658      text: text
659    }, { requestKey: null });
660  } catch (err) {
661    console.error('فشل إرسال الرسالة:', err);
662  }
663};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.