wakz-chat-widget 2.2.0 → 4.0.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.
Files changed (3) hide show
  1. package/index.js +834 -456
  2. package/package.json +1 -38
  3. package/README.md +0 -71
package/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * WAKZ Chat Widget v2.2.0
2
+ * WAKZ Chat Widget v4.0.0
3
3
  * ─────────────────────────────────────────────────────────────────
4
4
  * A production-grade, self-contained chat widget using Shadow DOM.
5
- * Competes with Intercom & Crisp in quality.
5
+ * Liquid Glass design pill header, floating input, dark user bubbles.
6
6
  *
7
7
  * Embed: <script src="/wakz-widget.js" data-api-key="xxx" data-server="https://..." async></script>
8
8
  *
@@ -127,17 +127,24 @@
127
127
  return fetch(url, merged).finally(function () { clearTimeout(timer); });
128
128
  }
129
129
 
130
+ /** XSS-safe: escape HTML entities */
131
+ function _escapeHTML(str) {
132
+ var div = document.createElement('div');
133
+ div.appendChild(document.createTextNode(str));
134
+ return div.innerHTML;
135
+ }
136
+
130
137
  /* ════════════════════════════════════════════════════════════════
131
138
  SVG ICONS (inline for zero-dependency)
132
139
  ════════════════════════════════════════════════════════════════ */
133
140
 
134
141
  var _ICONS = {
135
142
  chatBubble:
136
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>',
143
+ '<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
137
144
  send:
138
- '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>',
145
+ '<svg width="18" height="18" viewBox="0 0 24 24" fill="none"><path d="M19 12H5M5 12L11 6M5 12L11 18" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
139
146
  close:
140
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
147
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/><line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>',
141
148
  error:
142
149
  '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>'
143
150
  };
@@ -149,7 +156,7 @@
149
156
  var _I18N = {
150
157
  ar: {
151
158
  placeholder: 'اكتب رسالتك...',
152
- online: 'متصل',
159
+ online: 'متصل الآن',
153
160
  offline: 'غير متصل',
154
161
  errorMsg: 'حدث خطأ في الاتصال. يرجى المحاولة مرة أخرى.',
155
162
  retry: 'إعادة المحاولة',
@@ -157,7 +164,8 @@
157
164
  openChat: 'فتح المحادثة',
158
165
  closeChat: 'إغلاق المحادثة',
159
166
  sendMessage: 'إرسال',
160
- humanBadge: '👤 فريق الدعم'
167
+ humanBadge: 'فريق الدعم',
168
+ defaultSubtitle: 'المساعد الذكي'
161
169
  },
162
170
  en: {
163
171
  placeholder: 'Type your message...',
@@ -169,7 +177,8 @@
169
177
  openChat: 'Open chat',
170
178
  closeChat: 'Close chat',
171
179
  sendMessage: 'Send',
172
- humanBadge: '👤 Support Team'
180
+ humanBadge: 'Support Team',
181
+ defaultSubtitle: 'Smart Assistant'
173
182
  },
174
183
  fr: {
175
184
  placeholder: 'Tapez votre message...',
@@ -181,7 +190,8 @@
181
190
  openChat: 'Ouvrir le chat',
182
191
  closeChat: 'Fermer le chat',
183
192
  sendMessage: 'Envoyer',
184
- humanBadge: '👤 Équipe de support'
193
+ humanBadge: 'Équipe de support',
194
+ defaultSubtitle: 'Assistant intelligent'
185
195
  }
186
196
  };
187
197
 
@@ -195,10 +205,11 @@
195
205
 
196
206
  var _DEFAULTS = {
197
207
  botName: 'WAKZ',
208
+ botSubtitle: '',
198
209
  welcomeMessage: '',
199
- primaryColor: '#171717',
200
- chatBg: '#f8f9fa',
201
- btnColor: '#171717',
210
+ primaryColor: '#111111',
211
+ chatBg: '#ffffff',
212
+ btnColor: '#111111',
202
213
  widgetBg: '#ffffff',
203
214
  position: 'bottom-right',
204
215
  language: 'en',
@@ -222,7 +233,7 @@
222
233
  self.apiKey = attrs.apiKey;
223
234
  self.server = attrs.server;
224
235
  self.visitorId = _getVisitorId();
225
- self._deviceInfo = _getDeviceInfo(); // Device model, platform, type — collected once
236
+ self._deviceInfo = _getDeviceInfo();
226
237
 
227
238
  /* ── Runtime state ── */
228
239
  self.config = Object.assign({}, _DEFAULTS);
@@ -235,22 +246,28 @@
235
246
  self._configError = false;
236
247
  self._pollTimer = null;
237
248
  self._lastPollTime = null;
238
- self._POLL_INTERVAL = 4000; /* Poll every 4 seconds */
239
- self._knownMessageIds = {}; /* Deduplication set for message IDs */
240
- self._pendingReply = false; /* True while waiting for API response — pauses polling */
249
+ self._POLL_INTERVAL = 4000;
250
+ self._knownMessageIds = {};
251
+ self._pendingReply = false;
241
252
 
242
253
  /* ── DOM refs (populated after mount) ── */
243
254
  self._host = null;
244
255
  self._shadow = null;
245
256
  self._root = null;
257
+ self._overlay = null;
246
258
  self._chatWindow = null;
247
- self._messagesContainer = null;
259
+ self._messagesInner = null;
260
+ self._inputWrap = null;
248
261
  self._inputEl = null;
249
262
  self._sendBtn = null;
250
263
  self._toggleBtn = null;
251
264
  self._statusDot = null;
265
+ self._headerTitleEl = null;
266
+ self._headerSubtitleEl = null;
267
+ self._headerStatusPill = null;
252
268
  self._headerStatusDot = null;
253
269
  self._headerStatusText = null;
270
+ self._closeBtn = null;
254
271
 
255
272
  /* ── Bootstrap ── */
256
273
  self._injectCSS();
@@ -261,7 +278,7 @@
261
278
  }
262
279
 
263
280
  /* ════════════════════════════════════════════════════════════════
264
- CSS — Complete styling injected into Shadow DOM
281
+ CSS — Complete Liquid Glass design (v4.0.0)
265
282
  ════════════════════════════════════════════════════════════════ */
266
283
 
267
284
  WAKZWidget.prototype._injectCSS = function () {
@@ -271,21 +288,12 @@
271
288
  self._styleEl.textContent = [
272
289
  /* ── CSS Custom Properties (theming) ── */
273
290
  ':host {',
274
- ' --wakz-primary: #171717;',
275
- ' --wakz-btn: #171717;',
276
- ' --wakz-chat-bg: #f8f9fa;',
291
+ ' --wakz-primary: #111111;',
292
+ ' --wakz-btn: #111111;',
293
+ ' --wakz-chat-bg: #ffffff;',
277
294
  ' --wakz-widget-bg: #ffffff;',
278
- ' --wakz-radius-window: 16px;',
279
- ' --wakz-radius-bubble: 16px;',
280
- ' --wakz-radius-fab: 28px;',
281
- ' --wakz-shadow-sm: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06);',
282
- ' --wakz-shadow-md: 0 4px 12px rgba(0,0,0,.1), 0 2px 4px rgba(0,0,0,.06);',
283
- ' --wakz-shadow-lg: 0 12px 40px rgba(0,0,0,.15), 0 4px 12px rgba(0,0,0,.1);',
284
- ' --wakz-shadow-xl: 0 20px 60px rgba(0,0,0,.2), 0 8px 20px rgba(0,0,0,.12);',
285
- ' --wakz-transition: 200ms ease;',
286
- ' --wakz-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;',
287
295
  ' all: initial;',
288
- ' font-family: var(--wakz-font);',
296
+ ' font-family: "Cairo", "Tajawal", "IBM Plex Sans Arabic", system-ui, sans-serif;',
289
297
  '}',
290
298
 
291
299
  /* ── Reset ── */
@@ -296,312 +304,486 @@
296
304
  '}',
297
305
 
298
306
  /* ══════════════════════════════════════════════════
299
- FLOATING ACTION BUTTON (FAB)
307
+ ANIMATIONS
308
+ ══════════════════════════════════════════════════ */
309
+ '@keyframes wakz-fade-slide-up {',
310
+ ' from { opacity: 0; transform: translateY(6px); }',
311
+ ' to { opacity: 1; transform: translateY(0); }',
312
+ '}',
313
+
314
+ '@keyframes wakz-typing-bounce {',
315
+ ' 0%, 60%, 100% { transform: translateY(0); opacity: 0.35; }',
316
+ ' 30% { transform: translateY(-4px); opacity: 1; }',
317
+ '}',
318
+
319
+ '@keyframes wakz-pulse-green {',
320
+ ' 0%, 100% { box-shadow: 0 0 0 2px rgba(34,197,94,0.2); }',
321
+ ' 50% { box-shadow: 0 0 0 4px rgba(34,197,94,0.08); }',
322
+ '}',
323
+
324
+ '@keyframes wakz-fab-enter {',
325
+ ' 0% { opacity: 0; transform: scale(0.3); }',
326
+ ' 50% { opacity: 1; transform: scale(1.12); }',
327
+ ' 70% { transform: scale(0.92); }',
328
+ ' 100% { transform: scale(1); }',
329
+ '}',
330
+
331
+ /* ══════════════════════════════════════════════════
332
+ FLOATING ACTION BUTTON (FAB) — 56px black circle
300
333
  ══════════════════════════════════════════════════ */
301
334
  '.wakz-fab {',
302
335
  ' position: fixed;',
303
336
  ' z-index: 2147483647;',
304
337
  ' width: 56px;',
305
338
  ' height: 56px;',
306
- ' border-radius: var(--wakz-radius-fab);',
339
+ ' border-radius: 50%;',
307
340
  ' border: none;',
308
341
  ' cursor: pointer;',
309
342
  ' display: flex;',
310
343
  ' align-items: center;',
311
344
  ' justify-content: center;',
312
- ' box-shadow: var(--wakz-shadow-lg);',
313
- ' transition: transform var(--wakz-transition), box-shadow var(--wakz-transition);',
345
+ ' color: #ffffff;',
346
+ ' background: var(--wakz-btn);',
347
+ ' box-shadow: 0 4px 20px rgba(0,0,0,0.2), 0 1px 6px rgba(0,0,0,0.12);',
348
+ ' transition: transform 0.3s cubic-bezier(0.22,1,0.36,1), box-shadow 0.3s cubic-bezier(0.22,1,0.36,1);',
314
349
  ' outline: none;',
315
350
  ' -webkit-tap-highlight-color: transparent;',
316
- ' background: var(--wakz-btn);',
317
351
  '}',
318
352
  '.wakz-fab:hover {',
319
- ' transform: scale(1.08);',
320
- ' box-shadow: var(--wakz-shadow-xl);',
321
- '}',
322
- '.wakz-fab:active {',
323
- ' transform: scale(0.96);',
324
- '}',
325
- '.wakz-fab svg {',
326
- ' width: 26px;',
327
- ' height: 26px;',
328
- ' color: #ffffff;',
329
- ' pointer-events: none;',
353
+ ' transform: scale(1.1);',
354
+ ' box-shadow: 0 6px 28px rgba(0,0,0,0.25), 0 2px 8px rgba(0,0,0,0.15);',
330
355
  '}',
356
+ '.wakz-fab:active { transform: scale(0.93); }',
357
+ '.wakz-fab svg { width: 26px; height: 26px; color: #fff; pointer-events: none; }',
331
358
 
332
359
  /* ── FAB Positioning ── */
333
360
  '.wakz-fab-pos-br { bottom: 24px; right: 24px; }',
334
361
  '.wakz-fab-pos-bl { bottom: 24px; left: 24px; }',
335
362
 
336
- /* ── FAB Entrance Bounce ── */
337
- '@keyframes wakz-fab-enter {',
338
- ' 0% { opacity: 0; transform: scale(0.3); }',
339
- ' 50% { opacity: 1; transform: scale(1.12); }',
340
- ' 70% { transform: scale(0.92); }',
341
- ' 100% { transform: scale(1); }',
363
+ /* ── Hide FAB when chat is open ── */
364
+ '.wakz-window.wakz-visible ~ .wakz-fab,',
365
+ '.wakz-fab.wakz-fab-hidden {',
366
+ ' display: none !important;',
342
367
  '}',
368
+
369
+ /* ── FAB Entrance Bounce ── */
343
370
  '.wakz-fab-enter {',
344
- ' animation: wakz-fab-enter 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;',
371
+ ' animation: wakz-fab-enter 0.6s cubic-bezier(0.34,1.56,0.64,1) forwards;',
345
372
  '}',
346
373
 
347
374
  /* ══════════════════════════════════════════════════
348
- STATUS DOT (on FAB)
375
+ STATUS DOT (on FAB) — 10px, top-right
349
376
  ══════════════════════════════════════════════════ */
350
377
  '.wakz-fab-dot {',
351
378
  ' position: absolute;',
352
- ' top: 0px;',
353
- ' right: 0px;',
354
- ' width: 14px;',
355
- ' height: 14px;',
379
+ ' top: -1px;',
380
+ ' right: -1px;',
381
+ ' width: 10px;',
382
+ ' height: 10px;',
356
383
  ' border-radius: 50%;',
357
- ' border: 2.5px solid #ffffff;',
384
+ ' border: 2px solid #ffffff;',
358
385
  ' z-index: 2;',
359
386
  ' transition: background 0.3s ease;',
360
387
  '}',
361
- '.wakz-fab-pos-bl .wakz-fab-dot {',
362
- ' right: auto;',
363
- ' left: 0px;',
364
- '}',
365
-
366
- /* ── Online (green pulse) ── */
388
+ '.wakz-fab-pos-bl .wakz-fab-dot { right: auto; left: -1px; }',
367
389
  '.wakz-fab-dot.online {',
368
390
  ' background: #22c55e;',
391
+ ' animation: wakz-pulse-green 2.5s ease-in-out infinite;',
369
392
  '}',
370
- '@keyframes wakz-dot-pulse {',
371
- ' 0%, 100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }',
372
- ' 50% { box-shadow: 0 0 0 6px rgba(34,197,94,0); }',
373
- '}',
374
- '.wakz-fab-dot.online {',
375
- ' animation: wakz-dot-pulse 2s ease-in-out infinite;',
376
- '}',
377
-
378
- /* ── Offline (red, no pulse) ── */
379
393
  '.wakz-fab-dot.offline {',
380
394
  ' background: #ef4444;',
381
395
  ' animation: none;',
382
396
  '}',
383
397
 
384
- /* ── Error (red, no pulse) ── */
385
- '.wakz-fab-dot.error {',
386
- ' background: #ef4444;',
387
- ' animation: none;',
398
+ /* ══════════════════════════════════════════════════
399
+ OVERLAY / BACKDROP — blur behind modal
400
+ ══════════════════════════════════════════════════ */
401
+ '.wakz-overlay {',
402
+ ' position: fixed;',
403
+ ' inset: 0;',
404
+ ' z-index: 2147483645;',
405
+ ' background: rgba(0,0,0,0.25);',
406
+ ' backdrop-filter: blur(4px);',
407
+ ' -webkit-backdrop-filter: blur(4px);',
408
+ ' opacity: 0;',
409
+ ' pointer-events: none;',
410
+ ' transition: opacity 0.3s cubic-bezier(0.4,0,0.2,1);',
411
+ '}',
412
+ '.wakz-overlay.wakz-visible {',
413
+ ' opacity: 1;',
414
+ ' pointer-events: auto;',
388
415
  '}',
389
416
 
390
417
  /* ══════════════════════════════════════════════════
391
- CHAT WINDOW
418
+ CHAT WINDOW — Centered, 420x560, 24px radius
392
419
  ══════════════════════════════════════════════════ */
393
420
  '.wakz-window {',
394
421
  ' position: fixed;',
395
422
  ' z-index: 2147483646;',
396
- ' width: 380px;',
397
- ' min-height: 500px;',
398
- ' max-height: 70vh;',
399
- ' border-radius: var(--wakz-radius-window);',
423
+ ' top: 50%;',
424
+ ' left: 50%;',
425
+ ' transform: translate(-50%, -50%) scale(0.95);',
426
+ ' width: 420px;',
427
+ ' height: 560px;',
428
+ ' max-width: calc(100vw - 24px);',
429
+ ' max-height: calc(100vh - 24px);',
430
+ ' border-radius: 24px;',
400
431
  ' overflow: hidden;',
401
432
  ' display: flex;',
402
433
  ' flex-direction: column;',
403
434
  ' background: var(--wakz-widget-bg);',
404
- ' box-shadow: var(--wakz-shadow-xl);',
435
+ ' box-shadow:',
436
+ ' 0 0 0 0.5px rgba(0,0,0,0.06),',
437
+ ' 0 8px 40px rgba(0,0,0,0.08),',
438
+ ' 0 2px 12px rgba(0,0,0,0.04);',
405
439
  ' opacity: 0;',
406
- ' transform: translateY(20px) scale(0.95);',
407
440
  ' pointer-events: none;',
408
- ' transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),',
409
- ' transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
441
+ ' transition: opacity 0.3s cubic-bezier(0.4,0,0.2,1),',
442
+ ' transform 0.3s cubic-bezier(0.4,0,0.2,1);',
410
443
  '}',
411
444
  '.wakz-window.wakz-visible {',
412
445
  ' opacity: 1;',
413
- ' transform: translateY(0) scale(1);',
446
+ ' transform: translate(-50%, -50%) scale(1);',
414
447
  ' pointer-events: auto;',
415
448
  '}',
416
449
 
417
- /* ── Window Positioning ── */
418
- '.wakz-win-pos-br { bottom: 92px; right: 24px; }',
419
- '.wakz-win-pos-bl { bottom: 92px; left: 24px; }',
420
-
421
450
  /* ══════════════════════════════════════════════════
422
- HEADER
451
+ FLOATING HEADER WRAP — absolute over messages
423
452
  ══════════════════════════════════════════════════ */
424
- '.wakz-hdr {',
425
- ' flex-shrink: 0;',
426
- ' padding: 14px 18px;',
453
+ '.wakz-header-wrap {',
454
+ ' position: absolute;',
455
+ ' top: 12px;',
456
+ ' right: 12px;',
457
+ ' left: 12px;',
458
+ ' z-index: 100;',
459
+ ' pointer-events: none;',
460
+ '}',
461
+
462
+ /* ── HEADER — Liquid Glass Pill ── */
463
+ '.wakz-header {',
464
+ ' pointer-events: all;',
427
465
  ' display: flex;',
428
466
  ' align-items: center;',
429
467
  ' justify-content: space-between;',
430
- ' color: #ffffff;',
431
- ' cursor: default;',
432
- ' user-select: none;',
433
- ' background: var(--wakz-primary);',
468
+ ' position: relative;',
469
+ ' overflow: hidden;',
470
+ ' backdrop-filter: blur(40px) saturate(180%) brightness(102%);',
471
+ ' -webkit-backdrop-filter: blur(40px) saturate(180%) brightness(102%);',
472
+ ' background: rgba(255,255,255,0.82);',
473
+ ' border: 0.5px solid rgba(255,255,255,0.7);',
474
+ ' border-radius: 9999px;',
475
+ ' box-shadow:',
476
+ ' inset 0 1px 0 0 rgba(255,255,255,0.9),',
477
+ ' inset 0 -1px 0 0 rgba(0,0,0,0.04),',
478
+ ' 0 0 0 0.5px rgba(0,0,0,0.06),',
479
+ ' 0 8px 32px rgba(0,0,0,0.08),',
480
+ ' 0 2px 8px rgba(0,0,0,0.04);',
481
+ ' padding: 8px 8px 8px 12px;',
482
+ ' gap: 12px;',
434
483
  '}',
435
- '.wakz-hdr-left {',
436
- ' display: flex;',
437
- ' align-items: center;',
438
- ' gap: 11px;',
439
- ' min-width: 0;',
484
+
485
+ /* ── Specular Highlight (real DOM element, not ::before) ── */
486
+ '.wakz-specular {',
487
+ ' position: absolute;',
488
+ ' top: 0;',
489
+ ' left: 15%;',
490
+ ' right: 15%;',
491
+ ' height: 1px;',
492
+ ' background: linear-gradient(',
493
+ ' 90deg,',
494
+ ' transparent,',
495
+ ' rgba(255,255,255,0.6) 20%,',
496
+ ' rgba(255,255,255,0.95) 50%,',
497
+ ' rgba(255,255,255,0.6) 80%,',
498
+ ' transparent',
499
+ ' );',
500
+ ' border-radius: inherit;',
501
+ ' pointer-events: none;',
502
+ ' z-index: 1;',
440
503
  '}',
441
504
 
442
- /* ── Bot Avatar ── */
443
- '.wakz-avatar {',
444
- ' width: 38px;',
445
- ' height: 38px;',
446
- ' border-radius: 50%;',
447
- ' background: rgba(255,255,255,0.18);',
505
+ /* ── Brand (right side in RTL) ── */
506
+ '.wakz-hdr-brand {',
448
507
  ' display: flex;',
449
508
  ' align-items: center;',
450
- ' justify-content: center;',
451
- ' font-weight: 700;',
452
- ' font-size: 16px;',
453
509
  ' flex-shrink: 0;',
454
- ' letter-spacing: 0.5px;',
510
+ ' padding: 0 6px;',
455
511
  '}',
456
-
457
- /* ── Header Info ── */
458
512
  '.wakz-hdr-info {',
459
513
  ' display: flex;',
460
514
  ' flex-direction: column;',
461
- ' min-width: 0;',
515
+ ' gap: 1px;',
462
516
  '}',
463
- '.wakz-hdr-name {',
517
+ '.wakz-hdr-title {',
464
518
  ' font-size: 15px;',
465
- ' font-weight: 600;',
466
- ' line-height: 1.3;',
467
- ' white-space: nowrap;',
468
- ' overflow: hidden;',
469
- ' text-overflow: ellipsis;',
519
+ ' font-weight: 900;',
520
+ ' color: #0a0a0a;',
521
+ ' letter-spacing: -0.2px;',
522
+ ' line-height: 1.2;',
523
+ ' font-family: "Cairo", sans-serif;',
470
524
  '}',
471
- '.wakz-hdr-status {',
472
- ' font-size: 12px;',
473
- ' opacity: 0.9;',
525
+ '.wakz-hdr-subtitle {',
526
+ ' font-size: 11px;',
527
+ ' font-weight: 500;',
528
+ ' color: #999;',
529
+ ' line-height: 1.2;',
530
+ ' font-family: "Tajawal", "Cairo", sans-serif;',
531
+ '}',
532
+
533
+ /* ── Actions (left side in RTL) ── */
534
+ '.wakz-hdr-actions {',
474
535
  ' display: flex;',
475
536
  ' align-items: center;',
537
+ ' gap: 8px;',
538
+ ' flex-shrink: 0;',
539
+ '}',
540
+
541
+ /* ── Status Pill ── */
542
+ '.wakz-status-pill {',
543
+ ' display: inline-flex;',
544
+ ' align-items: center;',
476
545
  ' gap: 5px;',
477
- ' margin-top: 2px;',
546
+ ' padding: 4px 10px;',
547
+ ' border-radius: 50px;',
548
+ ' font-size: 11px;',
549
+ ' font-weight: 600;',
550
+ ' font-family: "Cairo", sans-serif;',
551
+ ' white-space: nowrap;',
552
+ ' transition: all 0.3s ease;',
553
+ '}',
554
+ '.wakz-status-pill--online {',
555
+ ' background: rgba(34,197,94,0.08);',
556
+ ' color: #16a34a;',
557
+ ' border: 1px solid rgba(34,197,94,0.15);',
478
558
  '}',
479
- '.wakz-hdr-status-dot {',
480
- ' width: 7px;',
481
- ' height: 7px;',
559
+ '.wakz-status-pill--offline {',
560
+ ' background: rgba(156,163,175,0.1);',
561
+ ' color: #6b7280;',
562
+ ' border: 1px solid rgba(156,163,175,0.15);',
563
+ '}',
564
+
565
+ /* ── Status Dot inside pill (6px) ── */
566
+ '.wakz-status-dot {',
567
+ ' width: 6px;',
568
+ ' height: 6px;',
482
569
  ' border-radius: 50%;',
483
- ' display: inline-block;',
484
570
  ' flex-shrink: 0;',
485
- ' transition: background 0.3s ease;',
486
571
  '}',
487
- '.wakz-hdr-status-dot.online { background: #4ade80; }',
488
- '.wakz-hdr-status-dot.offline { background: #f87171; }',
572
+ '.wakz-status-pill--online .wakz-status-dot {',
573
+ ' background: #22c55e;',
574
+ ' animation: wakz-pulse-green 2.5s ease-in-out infinite;',
575
+ '}',
576
+ '.wakz-status-pill--offline .wakz-status-dot {',
577
+ ' background: #9ca3af;',
578
+ '}',
489
579
 
490
- /* ── Close Button ── */
580
+ /* ── Close Button (glass circle) ── */
491
581
  '.wakz-close {',
492
- ' width: 32px;',
493
- ' height: 32px;',
582
+ ' width: 30px;',
583
+ ' height: 30px;',
584
+ ' min-width: 30px;',
585
+ ' min-height: 30px;',
494
586
  ' border-radius: 50%;',
495
587
  ' border: none;',
496
- ' background: rgba(255,255,255,0.12);',
497
- ' color: #ffffff;',
498
588
  ' cursor: pointer;',
499
589
  ' display: flex;',
500
590
  ' align-items: center;',
501
591
  ' justify-content: center;',
502
- ' transition: background var(--wakz-transition);',
592
+ ' color: #888;',
593
+ ' position: relative;',
594
+ ' overflow: hidden;',
595
+ ' backdrop-filter: blur(20px) saturate(180%);',
596
+ ' -webkit-backdrop-filter: blur(20px) saturate(180%);',
597
+ ' background: rgba(255,255,255,0.6);',
598
+ ' border: 0.5px solid rgba(255,255,255,0.6);',
599
+ ' box-shadow:',
600
+ ' inset 0 1px 0 0 rgba(255,255,255,0.8),',
601
+ ' 0 1px 4px rgba(0,0,0,0.04);',
602
+ ' transition: all 0.2s cubic-bezier(0.22,1,0.36,1);',
503
603
  ' outline: none;',
504
- ' flex-shrink: 0;',
505
604
  '}',
506
605
  '.wakz-close:hover {',
507
- ' background: rgba(255,255,255,0.28);',
508
- '}',
509
- '.wakz-close svg {',
510
- ' width: 18px;',
511
- ' height: 18px;',
606
+ ' background: rgba(255,80,80,0.08);',
607
+ ' color: #ef4444;',
608
+ ' transform: scale(1.1);',
609
+ ' box-shadow:',
610
+ ' inset 0 1px 0 0 rgba(255,255,255,0.8),',
611
+ ' 0 2px 8px rgba(239,68,68,0.1);',
512
612
  '}',
613
+ '.wakz-close:active { transform: scale(0.92); }',
614
+ '.wakz-close svg { width: 14px; height: 14px; }',
513
615
 
514
616
  /* ══════════════════════════════════════════════════
515
- MESSAGES AREA
617
+ MESSAGES AREA — scrollable
516
618
  ══════════════════════════════════════════════════ */
517
619
  '.wakz-msgs {',
518
620
  ' flex: 1;',
519
621
  ' overflow-y: auto;',
520
622
  ' overflow-x: hidden;',
521
- ' padding: 18px 16px 10px;',
522
- ' display: flex;',
523
- ' flex-direction: column;',
524
- ' gap: 6px;',
623
+ ' padding: 0;',
624
+ ' padding-top: 76px;',
625
+ ' padding-bottom: 100px;',
626
+ ' padding-right: 14px;',
627
+ ' padding-left: 14px;',
525
628
  ' scroll-behavior: smooth;',
526
- ' background: var(--wakz-chat-bg);',
629
+ ' position: relative;',
527
630
  '}',
528
- '.wakz-msgs::-webkit-scrollbar { width: 5px; }',
631
+ '.wakz-msgs::-webkit-scrollbar { width: 3px; }',
529
632
  '.wakz-msgs::-webkit-scrollbar-track { background: transparent; }',
530
633
  '.wakz-msgs::-webkit-scrollbar-thumb {',
531
- ' background: rgba(0,0,0,0.12);',
532
- ' border-radius: 10px;',
634
+ ' background: rgba(0,0,0,0.08);',
635
+ ' border-radius: 99px;',
636
+ '}',
637
+
638
+ '.wakz-msgs-inner {',
639
+ ' display: flex;',
640
+ ' flex-direction: column;',
641
+ ' gap: 6px;',
533
642
  '}',
534
643
 
535
644
  /* ══════════════════════════════════════════════════
536
- MESSAGE BUBBLES
645
+ MESSAGE ROWS
537
646
  ══════════════════════════════════════════════════ */
538
647
  '.wakz-msg-row {',
539
648
  ' display: flex;',
540
- ' max-width: 82%;',
541
- ' animation: wakz-msg-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
649
+ ' align-items: flex-end;',
650
+ ' width: 100%;',
651
+ ' animation: wakz-fade-slide-up 0.3s cubic-bezier(0.22,1,0.36,1) both;',
542
652
  '}',
543
- '.wakz-msg-row.bot { align-self: flex-start; }',
544
- '.wakz-msg-row.user { align-self: flex-end; }',
545
653
 
546
- '@keyframes wakz-msg-in {',
547
- ' 0% { opacity: 0; transform: translateY(8px); }',
548
- ' 100% { opacity: 1; transform: translateY(0); }',
654
+ /* ── USER MESSAGE — dark bubble ── */
655
+ '.wakz-msg-row--user { justify-content: flex-end; }',
656
+ '.wakz-user-card {',
657
+ ' max-width: 78%;',
658
+ ' padding: 10px 14px 8px;',
659
+ ' background: #111111;',
660
+ ' color: #ffffff;',
661
+ ' border-radius: 18px 18px 5px 18px;',
662
+ ' box-shadow: 0 2px 10px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.03);',
549
663
  '}',
550
-
551
- '.wakz-bubble {',
552
- ' padding: 10px 14px;',
553
- ' font-size: 14px;',
554
- ' line-height: 1.55;',
555
- ' word-wrap: break-word;',
556
- ' overflow-wrap: break-word;',
664
+ '.wakz-user-text {',
665
+ ' font-size: 13.5px;',
666
+ ' font-weight: 400;',
667
+ ' line-height: 1.7;',
557
668
  ' white-space: pre-wrap;',
558
- ' border-radius: var(--wakz-radius-bubble);',
559
- ' position: relative;',
669
+ ' word-break: break-word;',
670
+ ' text-align: right;',
671
+ ' font-family: "Tajawal", "Cairo", sans-serif;',
560
672
  '}',
561
- '.wakz-bubble.bot {',
562
- ' background: #ffffff;',
563
- ' color: #1a1a1a;',
564
- ' border-bottom-left-radius: 4px;',
565
- ' box-shadow: var(--wakz-shadow-sm);',
673
+ '.wakz-user-time {',
674
+ ' font-size: 9.5px;',
675
+ ' color: rgba(255,255,255,0.4);',
676
+ ' display: block;',
677
+ ' margin-top: 4px;',
678
+ ' text-align: left;',
679
+ ' font-family: "Tajawal", sans-serif;',
566
680
  '}',
567
- '.wakz-bubble.user {',
568
- ' color: #ffffff;',
569
- ' border-bottom-right-radius: 4px;',
570
- ' background: var(--wakz-primary);',
681
+
682
+ /* ── BOT MESSAGE — no bubble, on background ── */
683
+ '.wakz-msg-row--bot { justify-content: flex-start; }',
684
+ '.wakz-bot-content {',
685
+ ' max-width: 78%;',
686
+ ' display: flex;',
687
+ ' flex-direction: column;',
688
+ ' align-items: flex-end;',
689
+ ' gap: 1px;',
571
690
  '}',
572
- '.wakz-bubble.error-bubble {',
573
- ' background: #fef2f2;',
574
- ' color: #dc2626;',
575
- ' border: 1px solid #fecaca;',
576
- ' border-bottom-left-radius: 4px;',
577
- ' box-shadow: none;',
691
+ '.wakz-bot-label {',
692
+ ' font-size: 10px;',
693
+ ' font-weight: 700;',
694
+ ' color: #aaa;',
695
+ ' letter-spacing: 0.1px;',
696
+ ' padding: 0 4px;',
697
+ ' font-family: "Cairo", sans-serif;',
578
698
  '}',
579
-
580
- /* ── Message Timestamp ── */
581
- '.wakz-ts {',
699
+ '.wakz-bot-text {',
700
+ ' font-size: 13.5px;',
701
+ ' font-weight: 400;',
702
+ ' line-height: 1.75;',
703
+ ' color: #333333;',
704
+ ' white-space: pre-wrap;',
705
+ ' word-break: break-word;',
706
+ ' text-align: right;',
707
+ ' font-family: "Tajawal", "Cairo", sans-serif;',
708
+ ' padding: 0 4px;',
709
+ '}',
710
+ '.wakz-bot-time {',
711
+ ' font-size: 9.5px;',
712
+ ' color: #cccccc;',
582
713
  ' display: block;',
583
- ' font-size: 11px;',
584
- ' opacity: 0.45;',
585
- ' margin-top: 4px;',
714
+ ' margin-top: 1px;',
715
+ ' text-align: left;',
716
+ ' padding-left: 4px;',
717
+ ' font-family: "Tajawal", sans-serif;',
718
+ '}',
719
+
720
+ /* ── SUPPORT MESSAGE — green accent ── */
721
+ '.wakz-msg-row--support { justify-content: flex-start; }',
722
+ '.wakz-support-content {',
723
+ ' max-width: 78%;',
724
+ ' display: flex;',
725
+ ' flex-direction: column;',
726
+ ' align-items: flex-end;',
727
+ ' gap: 1px;',
728
+ ' border-right: 2.5px solid #22c55e;',
729
+ ' padding-right: 10px;',
730
+ '}',
731
+ '.wakz-support-label {',
732
+ ' font-size: 10px;',
733
+ ' font-weight: 700;',
734
+ ' color: #16a34a;',
735
+ ' letter-spacing: 0.1px;',
736
+ ' padding: 0 4px;',
737
+ ' font-family: "Cairo", sans-serif;',
738
+ ' display: flex;',
739
+ ' align-items: center;',
740
+ ' gap: 4px;',
741
+ '}',
742
+ '.wakz-support-label-dot {',
743
+ ' width: 4px;',
744
+ ' height: 4px;',
745
+ ' border-radius: 50%;',
746
+ ' background: #22c55e;',
747
+ ' flex-shrink: 0;',
586
748
  '}',
587
- '.wakz-bubble.user .wakz-ts {',
749
+ '.wakz-support-text {',
750
+ ' font-size: 13.5px;',
751
+ ' font-weight: 400;',
752
+ ' line-height: 1.75;',
753
+ ' color: #333333;',
754
+ ' white-space: pre-wrap;',
755
+ ' word-break: break-word;',
588
756
  ' text-align: right;',
757
+ ' font-family: "Tajawal", "Cairo", sans-serif;',
758
+ ' padding: 0 4px;',
759
+ '}',
760
+ '.wakz-support-time {',
761
+ ' font-size: 9.5px;',
762
+ ' color: #cccccc;',
763
+ ' display: block;',
764
+ ' margin-top: 1px;',
765
+ ' text-align: left;',
766
+ ' padding-left: 4px;',
767
+ ' font-family: "Tajawal", sans-serif;',
589
768
  '}',
590
769
 
591
- /* ── Human Agent Badge ── */
592
- '.wakz-human-badge {',
593
- ' display: inline-block;',
594
- ' font-size: 10px;',
595
- ' background: rgba(22, 163, 74, 0.1);',
596
- ' color: #16A34A;',
597
- ' border-radius: 8px;',
598
- ' padding: 1px 7px;',
599
- ' margin-top: 5px;',
600
- ' font-weight: 600;',
601
- ' letter-spacing: 0.2px;',
770
+ /* ── Error Messages ── */
771
+ '.wakz-error-bubble {',
772
+ ' max-width: 78%;',
773
+ ' padding: 10px 14px;',
774
+ ' font-size: 13.5px;',
775
+ ' line-height: 1.6;',
776
+ ' white-space: pre-wrap;',
777
+ ' word-break: break-word;',
778
+ ' border-radius: 14px;',
779
+ ' border-bottom-left-radius: 4px;',
780
+ ' background: #FEF2F2;',
781
+ ' color: #DC2626;',
782
+ ' border: 1px solid #FECACA;',
783
+ ' font-family: "Tajawal", "Cairo", sans-serif;',
602
784
  '}',
603
785
 
604
- /* ── Retry Button (inside error bubble) ── */
786
+ /* ── Retry Button ── */
605
787
  '.wakz-retry {',
606
788
  ' display: inline-flex;',
607
789
  ' align-items: center;',
@@ -615,183 +797,226 @@
615
797
  ' background: #ffffff;',
616
798
  ' color: #dc2626;',
617
799
  ' cursor: pointer;',
618
- ' font-family: var(--wakz-font);',
619
- ' transition: background var(--wakz-transition), border-color var(--wakz-transition);',
800
+ ' font-family: "Cairo", "Tajawal", sans-serif;',
801
+ ' transition: background 0.2s ease, border-color 0.2s ease;',
620
802
  ' outline: none;',
621
803
  '}',
622
- '.wakz-retry:hover {',
623
- ' background: #fef2f2;',
624
- ' border-color: #f87171;',
625
- '}',
804
+ '.wakz-retry:hover { background: #fef2f2; border-color: #f87171; }',
626
805
 
627
806
  /* ══════════════════════════════════════════════════
628
807
  TYPING INDICATOR
629
808
  ══════════════════════════════════════════════════ */
630
- '.wakz-typing {',
809
+ '.wakz-typing-row {',
810
+ ' display: flex;',
811
+ ' align-items: flex-end;',
812
+ ' justify-content: flex-start;',
813
+ ' width: 100%;',
814
+ ' animation: wakz-fade-slide-up 0.25s ease both;',
815
+ '}',
816
+ '.wakz-typing-content {',
817
+ ' display: flex;',
818
+ ' flex-direction: column;',
819
+ ' align-items: flex-end;',
820
+ ' gap: 3px;',
821
+ '}',
822
+ '.wakz-typing-label {',
823
+ ' font-size: 10px;',
824
+ ' font-weight: 700;',
825
+ ' color: #aaa;',
826
+ ' padding: 0 4px;',
827
+ ' font-family: "Cairo", sans-serif;',
828
+ '}',
829
+ '.wakz-typing-dots {',
631
830
  ' display: flex;',
632
831
  ' align-items: center;',
633
832
  ' gap: 4px;',
634
- ' padding: 14px 18px;',
635
- ' background: #ffffff;',
636
- ' border-radius: var(--wakz-radius-bubble);',
637
- ' border-bottom-left-radius: 4px;',
638
- ' box-shadow: var(--wakz-shadow-sm);',
833
+ ' padding: 8px 12px;',
639
834
  '}',
640
- '.wakz-typing-dot {',
641
- ' width: 7px;',
642
- ' height: 7px;',
835
+ '.wakz-typing-dots span {',
836
+ ' width: 6px;',
837
+ ' height: 6px;',
643
838
  ' border-radius: 50%;',
644
- ' background: #9ca3af;',
645
- ' animation: wakz-bounce-dot 1.4s ease-in-out infinite;',
646
- '}',
647
- '.wakz-typing-dot:nth-child(2) { animation-delay: 0.16s; }',
648
- '.wakz-typing-dot:nth-child(3) { animation-delay: 0.32s; }',
649
- '@keyframes wakz-bounce-dot {',
650
- ' 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }',
651
- ' 30% { transform: translateY(-7px); opacity: 1; }',
839
+ ' background: #bbb;',
840
+ ' animation: wakz-typing-bounce 1.4s ease-in-out infinite;',
652
841
  '}',
842
+ '.wakz-typing-dots span:nth-child(1) { animation-delay: 0s; }',
843
+ '.wakz-typing-dots span:nth-child(2) { animation-delay: 0.18s; }',
844
+ '.wakz-typing-dots span:nth-child(3) { animation-delay: 0.36s; }',
653
845
 
654
846
  /* ══════════════════════════════════════════════════
655
- INPUT AREA
847
+ FLOATING INPUT WRAP — absolute bottom
656
848
  ══════════════════════════════════════════════════ */
657
849
  '.wakz-input-wrap {',
658
- ' flex-shrink: 0;',
659
- ' padding: 12px 14px;',
660
- ' border-top: 1px solid #e5e7eb;',
661
- ' background: var(--wakz-widget-bg);',
850
+ ' position: absolute;',
851
+ ' bottom: 12px;',
852
+ ' right: 12px;',
853
+ ' left: 12px;',
854
+ ' z-index: 100;',
855
+ ' pointer-events: none;',
856
+ '}',
857
+ '.wakz-input-row {',
858
+ ' pointer-events: all;',
859
+ ' width: 100%;',
662
860
  ' display: flex;',
663
- ' align-items: flex-end;',
861
+ ' align-items: center;',
664
862
  ' gap: 8px;',
665
863
  '}',
864
+
865
+ /* ── Input Glass Pill ── */
866
+ '.wakz-input-glass {',
867
+ ' flex: 1;',
868
+ ' display: flex;',
869
+ ' align-items: center;',
870
+ ' position: relative;',
871
+ ' overflow: hidden;',
872
+ ' backdrop-filter: blur(40px) saturate(180%) brightness(102%);',
873
+ ' -webkit-backdrop-filter: blur(40px) saturate(180%) brightness(102%);',
874
+ ' background: rgba(255,255,255,0.85);',
875
+ ' border: 0.5px solid rgba(255,255,255,0.7);',
876
+ ' border-radius: 9999px;',
877
+ ' box-shadow:',
878
+ ' inset 0 1px 0 0 rgba(255,255,255,0.9),',
879
+ ' inset 0 -1px 0 0 rgba(0,0,0,0.04),',
880
+ ' 0 0 0 0.5px rgba(0,0,0,0.06),',
881
+ ' 0 8px 32px rgba(0,0,0,0.08),',
882
+ ' 0 2px 8px rgba(0,0,0,0.04);',
883
+ ' padding: 8px 16px;',
884
+ '}',
885
+
886
+ /* ── Input specular highlight ── */
887
+ '.wakz-input-specular {',
888
+ ' position: absolute;',
889
+ ' top: 0;',
890
+ ' left: 10%;',
891
+ ' right: 10%;',
892
+ ' height: 1px;',
893
+ ' background: linear-gradient(',
894
+ ' 90deg,',
895
+ ' transparent,',
896
+ ' rgba(255,255,255,0.5) 20%,',
897
+ ' rgba(255,255,255,0.8) 50%,',
898
+ ' rgba(255,255,255,0.5) 80%,',
899
+ ' transparent',
900
+ ' );',
901
+ ' border-radius: inherit;',
902
+ ' pointer-events: none;',
903
+ ' z-index: 1;',
904
+ '}',
905
+
906
+ /* ── Textarea ── */
666
907
  '.wakz-input {',
667
908
  ' flex: 1;',
668
- ' border: 1.5px solid #e5e7eb;',
669
- ' border-radius: 22px;',
670
- ' padding: 10px 16px;',
671
- ' font-size: 14px;',
672
- ' line-height: 1.4;',
673
- ' outline: none;',
674
- ' font-family: var(--wakz-font);',
675
- ' transition: border-color var(--wakz-transition);',
676
909
  ' resize: none;',
677
- ' max-height: 100px;',
910
+ ' border: none;',
911
+ ' outline: none;',
912
+ ' background: transparent;',
913
+ ' font-family: "Tajawal", "Cairo", sans-serif;',
914
+ ' font-size: 13.5px;',
915
+ ' font-weight: 400;',
916
+ ' color: #111;',
917
+ ' line-height: 1.5;',
918
+ ' padding: 1px 4px;',
919
+ ' max-height: 48px;',
678
920
  ' overflow-y: auto;',
679
- ' background: #f9fafb;',
680
- ' color: #111827;',
681
- '}',
682
- '.wakz-input:focus {',
683
- ' border-color: var(--wakz-primary);',
684
- ' background: #ffffff;',
921
+ ' align-self: center;',
685
922
  '}',
686
923
  '.wakz-input::placeholder {',
687
- ' color: #9ca3af;',
924
+ ' color: #bbb;',
925
+ ' font-weight: 400;',
926
+ ' font-family: "Cairo", "Tajawal", sans-serif;',
688
927
  '}',
928
+ '.wakz-input:focus { outline: none; }',
929
+ '.wakz-input::-webkit-scrollbar { width: 3px; }',
930
+ '.wakz-input::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.1); border-radius: 99px; }',
689
931
 
690
- /* ── Send Button ── */
932
+ /* ── Send Button — 40px circle, separate ── */
691
933
  '.wakz-send {',
934
+ ' flex-shrink: 0;',
692
935
  ' width: 40px;',
693
936
  ' height: 40px;',
937
+ ' min-width: 40px;',
938
+ ' min-height: 40px;',
694
939
  ' border-radius: 50%;',
695
940
  ' border: none;',
696
- ' color: #ffffff;',
697
941
  ' cursor: pointer;',
698
942
  ' display: flex;',
699
943
  ' align-items: center;',
700
944
  ' justify-content: center;',
701
- ' flex-shrink: 0;',
702
- ' transition: opacity var(--wakz-transition), transform 0.1s ease, box-shadow var(--wakz-transition);',
945
+ ' position: relative;',
946
+ ' overflow: hidden;',
947
+ ' transition: all 0.25s cubic-bezier(0.22,1,0.36,1);',
703
948
  ' outline: none;',
704
- ' background: var(--wakz-primary);',
705
- ' box-shadow: var(--wakz-shadow-sm);',
706
- '}',
707
- '.wakz-send:hover:not(:disabled) {',
708
- ' box-shadow: var(--wakz-shadow-md);',
709
- '}',
710
- '.wakz-send:disabled {',
711
- ' opacity: 0.35;',
712
- ' cursor: not-allowed;',
713
- '}',
714
- '.wakz-send:not(:disabled):active {',
715
- ' transform: scale(0.88);',
949
+ ' backdrop-filter: blur(40px) saturate(180%) brightness(102%);',
950
+ ' -webkit-backdrop-filter: blur(40px) saturate(180%) brightness(102%);',
951
+ ' background: rgba(255,255,255,0.82);',
952
+ ' border: 0.5px solid rgba(255,255,255,0.7);',
953
+ ' box-shadow:',
954
+ ' inset 0 1px 0 0 rgba(255,255,255,0.9),',
955
+ ' inset 0 -1px 0 0 rgba(0,0,0,0.04),',
956
+ ' 0 0 0 0.5px rgba(0,0,0,0.06),',
957
+ ' 0 4px 16px rgba(0,0,0,0.06);',
958
+ ' color: #bbb;',
716
959
  '}',
960
+ '.wakz-send:disabled { cursor: default; }',
717
961
  '.wakz-send svg {',
718
962
  ' width: 18px;',
719
963
  ' height: 18px;',
720
964
  ' pointer-events: none;',
721
965
  '}',
722
-
723
- /* ══════════════════════════════════════════════════
724
- WELCOME MESSAGE
725
- ══════════════════════════════════════════════════ */
726
- '.wakz-welcome-wrap {',
727
- ' display: flex;',
728
- ' gap: 8px;',
729
- ' max-width: 88%;',
730
- ' align-self: flex-start;',
731
- ' animation: wakz-msg-in 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
732
- '}',
733
- '.wakz-welcome-avatar {',
734
- ' width: 28px;',
735
- ' height: 28px;',
736
- ' border-radius: 50%;',
737
- ' background: var(--wakz-primary);',
738
- ' display: flex;',
739
- ' align-items: center;',
740
- ' justify-content: center;',
966
+ /* ── Send Active State ── */
967
+ '.wakz-send--active {',
968
+ ' background: #111111;',
969
+ ' border-color: #111111;',
741
970
  ' color: #ffffff;',
742
- ' font-weight: 700;',
743
- ' font-size: 12px;',
744
- ' flex-shrink: 0;',
745
- ' align-self: flex-end;',
746
- ' margin-bottom: 2px;',
971
+ ' filter: none;',
972
+ ' backdrop-filter: none;',
973
+ ' -webkit-backdrop-filter: none;',
974
+ ' box-shadow: 0 4px 20px rgba(0,0,0,0.22), 0 1px 6px rgba(0,0,0,0.12);',
975
+ '}',
976
+ '.wakz-send--active:hover:not(:disabled) {',
977
+ ' background: #222;',
978
+ ' transform: scale(1.08);',
979
+ ' box-shadow: 0 6px 24px rgba(0,0,0,0.28), 0 2px 8px rgba(0,0,0,0.14);',
747
980
  '}',
981
+ '.wakz-send--active:active:not(:disabled) { transform: scale(0.93); }',
748
982
 
749
983
  /* ══════════════════════════════════════════════════
750
984
  RTL SUPPORT
751
985
  ══════════════════════════════════════════════════ */
752
986
  '.wakz-rtl { direction: rtl; }',
753
- '.wakz-rtl .wakz-bubble.bot {',
754
- ' border-bottom-left-radius: var(--wakz-radius-bubble);',
755
- ' border-bottom-right-radius: 4px;',
987
+ '.wakz-rtl .wakz-user-card {',
988
+ ' border-radius: 18px 18px 18px 5px;',
756
989
  '}',
757
- '.wakz-rtl .wakz-bubble.user {',
758
- ' border-bottom-right-radius: var(--wakz-radius-bubble);',
759
- ' border-bottom-left-radius: 4px;',
760
- '}',
761
- '.wakz-rtl .wakz-typing {',
762
- ' border-bottom-left-radius: var(--wakz-radius-bubble);',
990
+ '.wakz-rtl .wakz-error-bubble {',
991
+ ' border-bottom-left-radius: 14px;',
763
992
  ' border-bottom-right-radius: 4px;',
764
993
  '}',
765
- '.wakz-rtl .wakz-bubble.user .wakz-ts {',
766
- ' text-align: left;',
767
- '}',
768
994
 
769
995
  /* ══════════════════════════════════════════════════
770
- MOBILE RESPONSIVE
996
+ RESPONSIVE
771
997
  ══════════════════════════════════════════════════ */
772
- '@media (max-width: 480px) {',
998
+ '@media (max-width: 440px) {',
773
999
  ' .wakz-window {',
774
- ' width: 100vw !important;',
775
- ' height: 100vh !important;',
776
- ' min-height: 100vh !important;',
777
- ' max-height: 100vh !important;',
778
- ' bottom: 0 !important;',
779
- ' top: 0 !important;',
780
- ' right: 0 !important;',
781
- ' left: 0 !important;',
1000
+ ' width: 100% !important;',
1001
+ ' height: 100% !important;',
1002
+ ' max-width: 100% !important;',
1003
+ ' max-height: 100% !important;',
782
1004
  ' border-radius: 0 !important;',
1005
+ ' box-shadow: none !important;',
783
1006
  ' }',
784
- ' .wakz-window.wakz-visible {',
785
- ' transform: translateY(0) scale(1);',
786
- ' }',
787
- ' .wakz-fab-pos-br { bottom: 16px; right: 16px; }',
788
- ' .wakz-fab-pos-bl { bottom: 16px; left: 16px; }',
1007
+ '}',
1008
+ '@media (max-width: 380px) {',
1009
+ ' .wakz-hdr-title { font-size: 13px; }',
1010
+ ' .wakz-status-pill span:last-child { display: none; }',
1011
+ ' .wakz-status-pill { padding: 4px 8px; }',
1012
+ ' .wakz-send { width: 36px; height: 36px; min-width: 36px; min-height: 36px; }',
1013
+ ' .wakz-close { width: 26px; height: 26px; min-width: 26px; min-height: 26px; }',
789
1014
  '}'
790
1015
  ].join('\n');
791
1016
  };
792
1017
 
793
1018
  /* ════════════════════════════════════════════════════════════════
794
- DOM CREATION
1019
+ DOM CREATION — v4.0.0 Liquid Glass structure
795
1020
  ════════════════════════════════════════════════════════════════ */
796
1021
 
797
1022
  WAKZWidget.prototype._createDOM = function () {
@@ -805,58 +1030,98 @@
805
1030
  document.body.appendChild(self._host);
806
1031
  self._shadow = self._host.attachShadow({ mode: 'closed' });
807
1032
 
1033
+ /* ── Google Fonts link (injected into shadow root) ── */
1034
+ var fontsLink = _el('link', {
1035
+ rel: 'stylesheet',
1036
+ href: 'https://fonts.googleapis.com/css2?family=Cairo:wght@400;600;700;900&family=Tajawal:wght@300;400;500;700&display=swap'
1037
+ });
1038
+ self._shadow.appendChild(fontsLink);
1039
+
808
1040
  /* ── Root wrapper ── */
809
1041
  self._root = _el('div', { className: 'wakz' + (isRtl ? ' wakz-rtl' : '') });
810
1042
  self._shadow.appendChild(self._styleEl);
811
1043
  self._shadow.appendChild(self._root);
812
1044
 
813
- /* ══════════════ FLOATING ACTION BUTTON ══════════════ */
1045
+ /* ══════════════ FLOATING ACTION BUTTON (FAB) ══════════════ */
814
1046
  self._toggleBtn = _el('button', {
815
1047
  className: 'wakz-fab wakz-fab-pos-' + posClass,
816
1048
  'aria-label': str.openChat
817
1049
  });
818
1050
  self._toggleBtn.innerHTML = _ICONS.chatBubble;
819
1051
 
820
- /* Status dot on FAB */
1052
+ /* Status dot on FAB (10px) */
821
1053
  self._statusDot = _el('span', { className: 'wakz-fab-dot ' + (self.config.online ? 'online' : 'offline') });
822
1054
  self._statusDot.style.display = self.config.showStatus ? '' : 'none';
823
1055
  self._toggleBtn.appendChild(self._statusDot);
824
1056
  self._root.appendChild(self._toggleBtn);
825
1057
 
1058
+ /* ══════════════ OVERLAY / BACKDROP ══════════════ */
1059
+ self._overlay = _el('div', { className: 'wakz-overlay' });
1060
+ self._root.appendChild(self._overlay);
1061
+
826
1062
  /* ══════════════ CHAT WINDOW ══════════════ */
827
- self._chatWindow = _el('div', { className: 'wakz-window wakz-win-pos-' + posClass });
1063
+ self._chatWindow = _el('div', { className: 'wakz-window' });
828
1064
 
829
- /* ── Header ── */
830
- var headerEl = _el('div', { className: 'wakz-hdr' });
1065
+ /* ── Messages Container (with floating header + input inside) ── */
1066
+ self._messagesContainer = _el('div', { className: 'wakz-msgs' });
831
1067
 
832
- var headerLeft = _el('div', { className: 'wakz-hdr-left' });
833
- var avatarLetter = (self.config.botName || 'W')[0].toUpperCase();
834
- headerLeft.appendChild(_el('div', { className: 'wakz-avatar' }, [avatarLetter]));
1068
+ /* ══════════════ FLOATING HEADER (pill, absolute over messages) ══════════════ */
1069
+ var headerWrap = _el('div', { className: 'wakz-header-wrap' });
1070
+ var header = _el('div', { className: 'wakz-header' });
835
1071
 
836
- var headerInfo = _el('div', { className: 'wakz-hdr-info' });
837
- headerInfo.appendChild(_el('span', { className: 'wakz-hdr-name' }, [self.config.botName]));
1072
+ /* Specular highlight (real DOM element, not ::before) */
1073
+ header.appendChild(_el('div', { className: 'wakz-specular' }));
838
1074
 
839
- var statusLine = _el('span', { className: 'wakz-hdr-status' });
840
- self._headerStatusDot = _el('span', { className: 'wakz-hdr-status-dot ' + (self.config.online ? 'online' : 'offline') });
841
- if (self.config.showStatus) statusLine.appendChild(self._headerStatusDot);
842
- self._headerStatusText = document.createTextNode(self.config.online ? str.online : str.offline);
843
- statusLine.appendChild(self._headerStatusText);
844
- headerInfo.appendChild(statusLine);
1075
+ /* Brand side (right in RTL) */
1076
+ var brand = _el('div', { className: 'wakz-hdr-brand' });
1077
+ var info = _el('div', { className: 'wakz-hdr-info' });
1078
+ self._headerTitleEl = _el('span', { className: 'wakz-hdr-title' }, [self.config.botName]);
1079
+ info.appendChild(self._headerTitleEl);
845
1080
 
846
- headerLeft.appendChild(headerInfo);
847
- headerEl.appendChild(headerLeft);
1081
+ var subtitleText = self.config.botSubtitle || str.defaultSubtitle || '';
1082
+ self._headerSubtitleEl = _el('span', { className: 'wakz-hdr-subtitle' }, [subtitleText]);
1083
+ info.appendChild(self._headerSubtitleEl);
848
1084
 
849
- var closeBtn = _el('button', { className: 'wakz-close', 'aria-label': str.closeChat });
850
- closeBtn.innerHTML = _ICONS.close;
851
- headerEl.appendChild(closeBtn);
852
- self._chatWindow.appendChild(headerEl);
1085
+ brand.appendChild(info);
1086
+ header.appendChild(brand);
853
1087
 
854
- /* ── Messages Container ── */
855
- self._messagesContainer = _el('div', { className: 'wakz-msgs' });
856
- self._chatWindow.appendChild(self._messagesContainer);
1088
+ /* Actions side (left in RTL) */
1089
+ var actions = _el('div', { className: 'wakz-hdr-actions' });
1090
+
1091
+ /* Status pill */
1092
+ self._headerStatusPill = _el('div', {
1093
+ className: 'wakz-status-pill ' + (self.config.online ? 'wakz-status-pill--online' : 'wakz-status-pill--offline')
1094
+ });
1095
+ if (self.config.showStatus) {
1096
+ self._headerStatusDot = _el('span', { className: 'wakz-status-dot' });
1097
+ self._headerStatusPill.appendChild(self._headerStatusDot);
1098
+ self._headerStatusText = document.createTextNode(self.config.online ? str.online : str.offline);
1099
+ self._headerStatusPill.appendChild(self._headerStatusText);
1100
+ }
1101
+ actions.appendChild(self._headerStatusPill);
1102
+
1103
+ /* Close button (glass circle) */
1104
+ self._closeBtn = _el('button', { className: 'wakz-close', 'aria-label': str.closeChat });
1105
+ self._closeBtn.innerHTML = _ICONS.close;
1106
+ actions.appendChild(self._closeBtn);
1107
+
1108
+ header.appendChild(actions);
1109
+ headerWrap.appendChild(header);
1110
+ self._messagesContainer.appendChild(headerWrap);
1111
+
1112
+ /* ── Messages inner (scrollable content) ── */
1113
+ self._messagesInner = _el('div', { className: 'wakz-msgs-inner' });
1114
+ self._messagesContainer.appendChild(self._messagesInner);
1115
+
1116
+ /* ══════════════ FLOATING INPUT (pill, absolute bottom) ══════════════ */
1117
+ self._inputWrap = _el('div', { className: 'wakz-input-wrap' });
1118
+ var inputRow = _el('div', { className: 'wakz-input-row' });
857
1119
 
858
- /* ── Input Area ── */
859
- var inputWrap = _el('div', { className: 'wakz-input-wrap' });
1120
+ /* Input glass pill */
1121
+ var inputGlass = _el('div', { className: 'wakz-input-glass' });
1122
+
1123
+ /* Specular highlight for input glass */
1124
+ inputGlass.appendChild(_el('div', { className: 'wakz-input-specular' }));
860
1125
 
861
1126
  self._inputEl = _el('textarea', {
862
1127
  className: 'wakz-input',
@@ -864,16 +1129,21 @@
864
1129
  rows: 1,
865
1130
  dir: isRtl ? 'rtl' : 'ltr'
866
1131
  });
867
- inputWrap.appendChild(self._inputEl);
1132
+ inputGlass.appendChild(self._inputEl);
1133
+ inputRow.appendChild(inputGlass);
868
1134
 
1135
+ /* Send button (40px circle, separate from input) */
869
1136
  self._sendBtn = _el('button', {
870
1137
  className: 'wakz-send',
871
1138
  'aria-label': str.sendMessage
872
1139
  });
873
1140
  self._sendBtn.innerHTML = _ICONS.send;
874
- inputWrap.appendChild(self._sendBtn);
875
- self._chatWindow.appendChild(inputWrap);
1141
+ inputRow.appendChild(self._sendBtn);
1142
+
1143
+ self._inputWrap.appendChild(inputRow);
1144
+ self._messagesContainer.appendChild(self._inputWrap);
876
1145
 
1146
+ self._chatWindow.appendChild(self._messagesContainer);
877
1147
  self._root.appendChild(self._chatWindow);
878
1148
  };
879
1149
 
@@ -889,8 +1159,13 @@
889
1159
  self.toggleChat(!self.isOpen);
890
1160
  });
891
1161
 
1162
+ /* Overlay click to close */
1163
+ self._overlay.addEventListener('click', function () {
1164
+ self.toggleChat(false);
1165
+ });
1166
+
892
1167
  /* Close button click */
893
- self._chatWindow.querySelector('.wakz-close').addEventListener('click', function () {
1168
+ self._closeBtn.addEventListener('click', function () {
894
1169
  self.toggleChat(false);
895
1170
  });
896
1171
 
@@ -907,10 +1182,16 @@
907
1182
  }
908
1183
  });
909
1184
 
910
- /* Auto-resize textarea */
1185
+ /* Auto-resize textarea (max 48px) + toggle send active state */
911
1186
  self._inputEl.addEventListener('input', function () {
912
1187
  self._inputEl.style.height = 'auto';
913
- self._inputEl.style.height = Math.min(self._inputEl.scrollHeight, 100) + 'px';
1188
+ self._inputEl.style.height = Math.min(self._inputEl.scrollHeight, 48) + 'px';
1189
+ var hasText = (self._inputEl.value || '').trim().length > 0;
1190
+ if (hasText) {
1191
+ self._sendBtn.classList.add('wakz-send--active');
1192
+ } else {
1193
+ self._sendBtn.classList.remove('wakz-send--active');
1194
+ }
914
1195
  });
915
1196
 
916
1197
  /* Escape to close */
@@ -925,7 +1206,6 @@
925
1206
 
926
1207
  WAKZWidget.prototype._playFabEntrance = function () {
927
1208
  var self = this;
928
- /* Start invisible, then animate after a brief delay */
929
1209
  self._toggleBtn.style.opacity = '0';
930
1210
  setTimeout(function () {
931
1211
  self._toggleBtn.style.opacity = '';
@@ -942,6 +1222,8 @@
942
1222
  self.isOpen = open;
943
1223
  if (open) {
944
1224
  self._chatWindow.classList.add('wakz-visible');
1225
+ self._overlay.classList.add('wakz-visible');
1226
+ self._toggleBtn.classList.add('wakz-fab-hidden');
945
1227
  /* Fetch history on first open (after config is loaded) */
946
1228
  if (self._configLoaded && !self._hasFetchedHistory) {
947
1229
  self._fetchHistory();
@@ -955,6 +1237,8 @@
955
1237
  setTimeout(function () { self._inputEl.focus(); }, 320);
956
1238
  } else {
957
1239
  self._chatWindow.classList.remove('wakz-visible');
1240
+ self._overlay.classList.remove('wakz-visible');
1241
+ self._toggleBtn.classList.remove('wakz-fab-hidden');
958
1242
  /* Stop polling when chat is closed */
959
1243
  self._stopPolling();
960
1244
  }
@@ -988,7 +1272,6 @@
988
1272
  }
989
1273
  })
990
1274
  .catch(function (err) {
991
- /* AbortError means timeout */
992
1275
  self._handleConfigError();
993
1276
  });
994
1277
  };
@@ -1004,9 +1287,13 @@
1004
1287
  self._statusDot.className = 'wakz-fab-dot offline';
1005
1288
  self._statusDot.style.display = '';
1006
1289
  }
1007
- /* Update header status */
1290
+ /* Update header status pill to offline */
1291
+ if (self._headerStatusPill) {
1292
+ self._headerStatusPill.className = 'wakz-status-pill wakz-status-pill--offline';
1293
+ self._headerStatusPill.style.display = '';
1294
+ }
1008
1295
  if (self._headerStatusDot) {
1009
- self._headerStatusDot.className = 'wakz-hdr-status-dot offline';
1296
+ self._headerStatusDot.className = 'wakz-status-dot';
1010
1297
  }
1011
1298
  if (self._headerStatusText) {
1012
1299
  self._headerStatusText.textContent = _strings(self.config.language).offline;
@@ -1033,33 +1320,37 @@
1033
1320
  else self._root.classList.remove('wakz-rtl');
1034
1321
 
1035
1322
  /* ── FAB ── */
1036
- self._toggleBtn.className = 'wakz-fab wakz-fab-pos-' + posClass + ' wakz-fab-enter';
1323
+ self._toggleBtn.className = 'wakz-fab wakz-fab-pos-' + posClass + ' wakz-fab-enter' + (self.isOpen ? ' wakz-fab-hidden' : '');
1037
1324
  self._toggleBtn.setAttribute('aria-label', str.openChat);
1038
1325
 
1039
1326
  /* ── FAB Status Dot ── */
1040
1327
  self._statusDot.className = 'wakz-fab-dot ' + (cfg.online ? 'online' : 'offline');
1041
1328
  self._statusDot.style.display = cfg.showStatus ? '' : 'none';
1042
1329
 
1043
- /* ── Window Position ── */
1044
- self._chatWindow.className = 'wakz-window wakz-win-pos-' + posClass + (self.isOpen ? ' wakz-visible' : '');
1330
+ /* ── Window ── */
1331
+ self._chatWindow.className = 'wakz-window' + (self.isOpen ? ' wakz-visible' : '');
1045
1332
 
1046
- /* ── Header ── */
1047
- var headerEl = self._chatWindow.querySelector('.wakz-hdr');
1048
- if (headerEl) headerEl.style.background = cfg.primaryColor;
1333
+ /* ── Overlay ── */
1334
+ self._overlay.className = 'wakz-overlay' + (self.isOpen ? ' wakz-visible' : '');
1049
1335
 
1050
- /* ── Bot Name & Avatar ── */
1051
- var nameEl = self._chatWindow.querySelector('.wakz-hdr-name');
1052
- if (nameEl) nameEl.textContent = cfg.botName;
1053
- var avatarEl = self._chatWindow.querySelector('.wakz-avatar');
1054
- if (avatarEl) avatarEl.textContent = (cfg.botName || 'W')[0].toUpperCase();
1055
-
1056
- /* ── Header Status ── */
1057
- if (self._headerStatusDot) {
1058
- self._headerStatusDot.className = 'wakz-hdr-status-dot ' + (cfg.online ? 'online' : 'offline');
1059
- self._headerStatusDot.style.display = cfg.showStatus ? '' : 'none';
1336
+ /* ── Header Title & Subtitle (DYNAMIC from config) ── */
1337
+ if (self._headerTitleEl) self._headerTitleEl.textContent = cfg.botName;
1338
+ if (self._headerSubtitleEl) {
1339
+ var sub = cfg.botSubtitle || str.defaultSubtitle || '';
1340
+ self._headerSubtitleEl.textContent = sub;
1060
1341
  }
1061
- if (self._headerStatusText) {
1062
- self._headerStatusText.textContent = cfg.online ? str.online : str.offline;
1342
+
1343
+ /* ── Header Status Pill ── */
1344
+ if (self._headerStatusPill) {
1345
+ self._headerStatusPill.className = 'wakz-status-pill ' + (cfg.online ? 'wakz-status-pill--online' : 'wakz-status-pill--offline');
1346
+ self._headerStatusPill.style.display = cfg.showStatus ? '' : 'none';
1347
+ /* Rebuild pill content if needed */
1348
+ if (self._headerStatusDot) {
1349
+ self._headerStatusDot.className = 'wakz-status-dot';
1350
+ }
1351
+ if (self._headerStatusText) {
1352
+ self._headerStatusText.textContent = cfg.online ? str.online : str.offline;
1353
+ }
1063
1354
  }
1064
1355
 
1065
1356
  /* ── Input ── */
@@ -1067,8 +1358,7 @@
1067
1358
  self._inputEl.dir = isRtl ? 'rtl' : 'ltr';
1068
1359
 
1069
1360
  /* ── Close button label ── */
1070
- var closeBtn = self._chatWindow.querySelector('.wakz-close');
1071
- if (closeBtn) closeBtn.setAttribute('aria-label', str.closeChat);
1361
+ if (self._closeBtn) self._closeBtn.setAttribute('aria-label', str.closeChat);
1072
1362
 
1073
1363
  /* ── Send button label ── */
1074
1364
  self._sendBtn.setAttribute('aria-label', str.sendMessage);
@@ -1098,7 +1388,6 @@
1098
1388
  .then(function (res) { return res.json(); })
1099
1389
  .then(function (data) {
1100
1390
  if (data && data.success && data.messages && data.messages.length > 0) {
1101
- /* Clear any existing messages (e.g., welcome) and render history */
1102
1391
  self._clearMessages();
1103
1392
  self._knownMessageIds = {};
1104
1393
  var msgs = data.messages;
@@ -1109,7 +1398,6 @@
1109
1398
  var role = m.role === 'user' ? 'user' : 'bot';
1110
1399
  self._appendMessage(role, m.content, false, m.createdAt, m.role === 'human_agent');
1111
1400
  }
1112
- /* Set last poll time to the most recent message */
1113
1401
  var lastMsg = msgs[msgs.length - 1];
1114
1402
  if (lastMsg && lastMsg.createdAt) {
1115
1403
  self._lastPollTime = lastMsg.createdAt;
@@ -1127,7 +1415,7 @@
1127
1415
 
1128
1416
  WAKZWidget.prototype._startPolling = function () {
1129
1417
  var self = this;
1130
- self._stopPolling(); /* Clear any existing timer */
1418
+ self._stopPolling();
1131
1419
  if (!self._lastPollTime) {
1132
1420
  self._lastPollTime = new Date().toISOString();
1133
1421
  }
@@ -1147,7 +1435,7 @@
1147
1435
  WAKZWidget.prototype._pollForNewMessages = function () {
1148
1436
  var self = this;
1149
1437
  if (!self.server || !self.apiKey || !self._lastPollTime) return;
1150
- /* Skip polling while waiting for our own reply — prevents race condition duplicates */
1438
+ /* Skip polling while waiting for our own reply */
1151
1439
  if (self._pendingReply) return;
1152
1440
 
1153
1441
  var url = self.server + '/api/v1/embed/chat?api_key=' +
@@ -1171,7 +1459,6 @@
1171
1459
  var isHumanAgent = m.role === 'human_agent';
1172
1460
  self._appendMessage(role, m.content, false, m.createdAt, isHumanAgent);
1173
1461
  }
1174
- /* Update last poll time */
1175
1462
  var lastMsg = msgs[msgs.length - 1];
1176
1463
  if (lastMsg && lastMsg.createdAt) {
1177
1464
  self._lastPollTime = lastMsg.createdAt;
@@ -1189,51 +1476,51 @@
1189
1476
 
1190
1477
  WAKZWidget.prototype._clearMessages = function () {
1191
1478
  var self = this;
1192
- self._messagesContainer.innerHTML = '';
1479
+ /* Remove all children from messages-inner */
1480
+ if (self._messagesInner) {
1481
+ while (self._messagesInner.firstChild) {
1482
+ self._messagesInner.removeChild(self._messagesInner.firstChild);
1483
+ }
1484
+ }
1193
1485
  self.messages = [];
1194
1486
  self._knownMessageIds = {};
1195
1487
  };
1196
1488
 
1197
1489
  /* ════════════════════════════════════════════════════════════════
1198
- APPEND WELCOME MESSAGE (with bot avatar)
1490
+ APPEND WELCOME MESSAGE no bubble, matching bot messages
1199
1491
  ════════════════════════════════════════════════════════════════ */
1200
1492
 
1201
1493
  WAKZWidget.prototype._appendWelcomeMessage = function (text) {
1202
1494
  var self = this;
1203
1495
  if (!text) return;
1204
1496
 
1205
- var wrap = _el('div', { className: 'wakz-welcome-wrap' });
1497
+ var row = _el('div', { className: 'wakz-msg-row wakz-msg-row--bot' });
1498
+
1499
+ var content = _el('div', { className: 'wakz-bot-content' });
1206
1500
 
1207
- /* Bot avatar */
1208
- var avatar = _el('div', { className: 'wakz-welcome-avatar' },
1209
- [(self.config.botName || 'W')[0].toUpperCase()]);
1210
- wrap.appendChild(avatar);
1501
+ /* Bot label */
1502
+ var label = _el('span', { className: 'wakz-bot-label' }, [self.config.botName]);
1503
+ content.appendChild(label);
1211
1504
 
1212
- /* Bubble */
1213
- var bubble = _el('div', { className: 'wakz-bubble bot' }, [text]);
1214
- wrap.appendChild(bubble);
1505
+ /* Bot text */
1506
+ var textEl = _el('p', { className: 'wakz-bot-text' });
1507
+ textEl.textContent = text;
1508
+ content.appendChild(textEl);
1215
1509
 
1216
- self._messagesContainer.appendChild(wrap);
1510
+ row.appendChild(content);
1511
+ self._messagesInner.appendChild(row);
1217
1512
  self.messages.push({ sender: 'bot', text: text });
1218
1513
  self._scrollToBottom();
1219
1514
  };
1220
1515
 
1221
1516
  /* ════════════════════════════════════════════════════════════════
1222
- APPEND MESSAGE TO CHAT
1517
+ APPEND MESSAGE TO CHAT — v4 rendering
1223
1518
  ════════════════════════════════════════════════════════════════ */
1224
1519
 
1225
1520
  WAKZWidget.prototype._appendMessage = function (sender, text, isError, timestamp, isHumanAgent) {
1226
1521
  var self = this;
1227
1522
 
1228
- var row = _el('div', { className: 'wakz-msg-row ' + sender });
1229
-
1230
- var bubbleClass = 'wakz-bubble ' + sender;
1231
- if (isError) bubbleClass += ' error-bubble';
1232
- var bubble = _el('div', { className: bubbleClass });
1233
-
1234
- bubble.appendChild(document.createTextNode(text));
1235
-
1236
- /* Timestamp */
1523
+ /* ── Format timestamp ── */
1237
1524
  var tsText = '';
1238
1525
  if (timestamp) {
1239
1526
  try {
@@ -1249,69 +1536,161 @@
1249
1536
  tsText = now.getHours().toString().padStart(2, '0') + ':' +
1250
1537
  now.getMinutes().toString().padStart(2, '0');
1251
1538
  }
1252
- bubble.appendChild(_el('span', { className: 'wakz-ts' }, [tsText]));
1253
1539
 
1254
- /* Human agent badge — visual distinction for admin replies */
1255
- if (isHumanAgent) {
1256
- var iStr = _strings(self.config.language);
1257
- var badge = _el('span', { className: 'wakz-human-badge' }, [iStr.humanBadge || '👤 فريق الدعم']);
1258
- bubble.appendChild(badge);
1259
- }
1260
-
1261
- /* Retry button for errors */
1262
- if (isError) {
1263
- var str = _strings(self.config.language);
1264
- var retryBtn = _el('button', { className: 'wakz-retry' }, [
1265
- _ICONS.error + ' ' + str.retry
1266
- ]);
1267
- (function (rowCapture) {
1268
- retryBtn.addEventListener('click', function () {
1269
- /* Remove error row */
1270
- if (rowCapture.parentNode) rowCapture.parentNode.removeChild(rowCapture);
1271
- /* Find and remove from messages array */
1272
- for (var i = self.messages.length - 1; i >= 0; i--) {
1273
- if (self.messages[i].text === text && self.messages[i].isError) {
1274
- self.messages.splice(i, 1);
1275
- break;
1540
+ if (sender === 'user') {
1541
+ /* ── USER MESSAGE: dark bubble (#111) ── */
1542
+ if (isError) {
1543
+ /* Error: red error bubble */
1544
+ var errRow = _el('div', { className: 'wakz-msg-row wakz-msg-row--user' });
1545
+ var errBubble = _el('div', { className: 'wakz-error-bubble' });
1546
+ errBubble.appendChild(document.createTextNode(text));
1547
+
1548
+ var errStr = _strings(self.config.language);
1549
+ var errRetryBtn = _el('button', { className: 'wakz-retry' }, [
1550
+ _ICONS.error + ' ' + errStr.retry
1551
+ ]);
1552
+ (function (rowCapture, errText) {
1553
+ errRetryBtn.addEventListener('click', function () {
1554
+ if (rowCapture.parentNode) rowCapture.parentNode.removeChild(rowCapture);
1555
+ for (var i = self.messages.length - 1; i >= 0; i--) {
1556
+ if (self.messages[i].text === errText && self.messages[i].isError) {
1557
+ self.messages.splice(i, 1);
1558
+ break;
1559
+ }
1276
1560
  }
1277
- }
1278
- /* Resend last user message */
1279
- var lastUserMsg = null;
1280
- for (var j = self.messages.length - 1; j >= 0; j--) {
1281
- if (self.messages[j].sender === 'user') {
1282
- lastUserMsg = self.messages[j].text;
1283
- break;
1561
+ var lastUserMsg = null;
1562
+ for (var j = self.messages.length - 1; j >= 0; j--) {
1563
+ if (self.messages[j].sender === 'user') {
1564
+ lastUserMsg = self.messages[j].text;
1565
+ break;
1566
+ }
1284
1567
  }
1285
- }
1286
- if (lastUserMsg) self._sendToAPI(lastUserMsg);
1287
- });
1288
- })(row);
1289
- bubble.appendChild(retryBtn);
1290
- }
1568
+ if (lastUserMsg) self._sendToAPI(lastUserMsg);
1569
+ });
1570
+ })(errRow, text);
1571
+ errBubble.appendChild(errRetryBtn);
1572
+ errRow.appendChild(errBubble);
1573
+ self._messagesInner.appendChild(errRow);
1574
+ self.messages.push({ sender: sender, text: text, isError: !!isError });
1575
+ } else {
1576
+ /* Normal user message: dark bubble */
1577
+ var userRow = _el('div', { className: 'wakz-msg-row wakz-msg-row--user' });
1578
+ var userCard = _el('div', { className: 'wakz-user-card' });
1579
+
1580
+ var userText = _el('p', { className: 'wakz-user-text' });
1581
+ userText.textContent = text; /* XSS safe: textContent */
1582
+ userCard.appendChild(userText);
1583
+
1584
+ userCard.appendChild(_el('span', { className: 'wakz-user-time' }, [tsText]));
1585
+ userRow.appendChild(userCard);
1586
+ self._messagesInner.appendChild(userRow);
1587
+ self.messages.push({ sender: sender, text: text, isError: false });
1588
+ }
1589
+ } else if (isHumanAgent) {
1590
+ /* ── SUPPORT MESSAGE: green accent ── */
1591
+ var supRow = _el('div', { className: 'wakz-msg-row wakz-msg-row--support' });
1592
+ var supContent = _el('div', { className: 'wakz-support-content' });
1593
+
1594
+ /* Support label with dot */
1595
+ var supLabel = _el('span', { className: 'wakz-support-label' });
1596
+ supLabel.appendChild(_el('span', { className: 'wakz-support-label-dot' }));
1597
+ var supStr = _strings(self.config.language);
1598
+ supLabel.appendChild(document.createTextNode(supStr.humanBadge || 'Support Team'));
1599
+ supContent.appendChild(supLabel);
1600
+
1601
+ /* Support text */
1602
+ var supText = _el('p', { className: 'wakz-support-text' });
1603
+ supText.textContent = text; /* XSS safe: textContent */
1604
+ supContent.appendChild(supText);
1605
+
1606
+ supContent.appendChild(_el('span', { className: 'wakz-support-time' }, [tsText]));
1607
+ supRow.appendChild(supContent);
1608
+ self._messagesInner.appendChild(supRow);
1609
+ self.messages.push({ sender: 'support', text: text, isError: false });
1610
+ } else {
1611
+ /* ── BOT MESSAGE: no bubble, on background ── */
1612
+ if (isError) {
1613
+ /* Error: red error bubble */
1614
+ var botErrRow = _el('div', { className: 'wakz-msg-row wakz-msg-row--bot' });
1615
+ var botErrBubble = _el('div', { className: 'wakz-error-bubble' });
1616
+ botErrBubble.appendChild(document.createTextNode(text));
1617
+
1618
+ var botErrStr = _strings(self.config.language);
1619
+ var botErrRetry = _el('button', { className: 'wakz-retry' }, [
1620
+ _ICONS.error + ' ' + botErrStr.retry
1621
+ ]);
1622
+ (function (rowCapture, errText) {
1623
+ botErrRetry.addEventListener('click', function () {
1624
+ if (rowCapture.parentNode) rowCapture.parentNode.removeChild(rowCapture);
1625
+ for (var i = self.messages.length - 1; i >= 0; i--) {
1626
+ if (self.messages[i].text === errText && self.messages[i].isError) {
1627
+ self.messages.splice(i, 1);
1628
+ break;
1629
+ }
1630
+ }
1631
+ var lastUserMsg = null;
1632
+ for (var j = self.messages.length - 1; j >= 0; j--) {
1633
+ if (self.messages[j].sender === 'user') {
1634
+ lastUserMsg = self.messages[j].text;
1635
+ break;
1636
+ }
1637
+ }
1638
+ if (lastUserMsg) self._sendToAPI(lastUserMsg);
1639
+ });
1640
+ })(botErrRow, text);
1641
+ botErrBubble.appendChild(botErrRetry);
1642
+ botErrRow.appendChild(botErrBubble);
1643
+ self._messagesInner.appendChild(botErrRow);
1644
+ self.messages.push({ sender: sender, text: text, isError: true });
1645
+ } else {
1646
+ /* Normal bot message: no bubble */
1647
+ var botRow = _el('div', { className: 'wakz-msg-row wakz-msg-row--bot' });
1648
+ var botContent = _el('div', { className: 'wakz-bot-content' });
1649
+
1650
+ /* Bot label */
1651
+ botContent.appendChild(_el('span', { className: 'wakz-bot-label' }, [self.config.botName]));
1291
1652
 
1292
- row.appendChild(bubble);
1293
- self._messagesContainer.appendChild(row);
1653
+ /* Bot text */
1654
+ var botTextEl = _el('p', { className: 'wakz-bot-text' });
1655
+ botTextEl.textContent = text; /* XSS safe: textContent */
1656
+ botContent.appendChild(botTextEl);
1657
+
1658
+ /* Timestamp */
1659
+ botContent.appendChild(_el('span', { className: 'wakz-bot-time' }, [tsText]));
1660
+
1661
+ botRow.appendChild(botContent);
1662
+ self._messagesInner.appendChild(botRow);
1663
+ self.messages.push({ sender: sender, text: text, isError: false });
1664
+ }
1665
+ }
1294
1666
 
1295
- self.messages.push({ sender: sender, text: text, isError: !!isError });
1296
1667
  self._scrollToBottom();
1297
1668
  };
1298
1669
 
1299
1670
  /* ════════════════════════════════════════════════════════════════
1300
- TYPING INDICATOR
1671
+ TYPING INDICATOR — dots with label, no bubble
1301
1672
  ════════════════════════════════════════════════════════════════ */
1302
1673
 
1303
1674
  WAKZWidget.prototype._showTyping = function () {
1304
1675
  var self = this;
1305
1676
  if (self._typingEl) return;
1306
1677
 
1307
- self._typingEl = _el('div', { className: 'wakz-msg-row bot' });
1308
- self._typingEl.innerHTML =
1309
- '<div class="wakz-typing">' +
1310
- '<span class="wakz-typing-dot"></span>' +
1311
- '<span class="wakz-typing-dot"></span>' +
1312
- '<span class="wakz-typing-dot"></span>' +
1313
- '</div>';
1314
- self._messagesContainer.appendChild(self._typingEl);
1678
+ self._typingEl = _el('div', { className: 'wakz-typing-row' });
1679
+
1680
+ var typingContent = _el('div', { className: 'wakz-typing-content' });
1681
+
1682
+ /* Typing label */
1683
+ typingContent.appendChild(_el('span', { className: 'wakz-typing-label' }, [self.config.botName]));
1684
+
1685
+ /* Bouncing dots */
1686
+ var typingDots = _el('div', { className: 'wakz-typing-dots' });
1687
+ typingDots.appendChild(_el('span'));
1688
+ typingDots.appendChild(_el('span'));
1689
+ typingDots.appendChild(_el('span'));
1690
+ typingContent.appendChild(typingDots);
1691
+
1692
+ self._typingEl.appendChild(typingContent);
1693
+ self._messagesInner.appendChild(self._typingEl);
1315
1694
  self._scrollToBottom();
1316
1695
  };
1317
1696
 
@@ -1351,6 +1730,7 @@
1351
1730
  /* Clear input */
1352
1731
  self._inputEl.value = '';
1353
1732
  self._inputEl.style.height = 'auto';
1733
+ self._sendBtn.classList.remove('wakz-send--active');
1354
1734
 
1355
1735
  /* Send to API */
1356
1736
  self._sendToAPI(text);
@@ -1372,7 +1752,7 @@
1372
1752
  self._sendBtn.disabled = true;
1373
1753
  self._showTyping();
1374
1754
 
1375
- /* Freeze poll time BEFORE sending — prevents polling from fetching our own messages */
1755
+ /* Freeze poll time BEFORE sending */
1376
1756
  self._lastPollTime = new Date().toISOString();
1377
1757
  self._pendingReply = true;
1378
1758
 
@@ -1408,7 +1788,6 @@
1408
1788
  } else {
1409
1789
  self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
1410
1790
  }
1411
- /* Advance poll time past any messages created during this request */
1412
1791
  self._lastPollTime = (data && data.timestamp)
1413
1792
  ? new Date(new Date(data.timestamp).getTime() + 1000).toISOString()
1414
1793
  : new Date(Date.now() + 1000).toISOString();
@@ -1417,7 +1796,6 @@
1417
1796
  self._hideTyping();
1418
1797
  self._pendingReply = false;
1419
1798
  self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
1420
- /* Advance poll time even on error */
1421
1799
  self._lastPollTime = new Date(Date.now() + 1000).toISOString();
1422
1800
  })
1423
1801
  .finally(function () {
@@ -1432,7 +1810,7 @@
1432
1810
  ════════════════════════════════════════════════════════════════ */
1433
1811
 
1434
1812
  function boot() {
1435
- try { new WAKZWidget(); } catch (e) { console.error('[WAKZ Widget] Init error:', e); }
1813
+ try { new WAKZWidget(); } catch (e) { console.error('[WAKZ Widget v4.0.0] Init error:', e); }
1436
1814
  }
1437
1815
 
1438
1816
  if (document.readyState === 'loading') {