wakz-chat-widget 2.2.0 → 3.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/README.md +1 -71
  2. package/index.js +442 -287
  3. package/package.json +5 -30
package/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  /**
2
- * WAKZ Chat Widget v2.2.0
2
+ * WAKZ Chat Widget v3.0.0
3
3
  * ─────────────────────────────────────────────────────────────────
4
4
  * A production-grade, self-contained chat widget using Shadow DOM.
5
- * Competes with Intercom & Crisp in quality.
5
+ * Complete UI/UX revamp glass morphism, centered modal, modern design.
6
6
  *
7
7
  * Embed: <script src="/wakz-widget.js" data-api-key="xxx" data-server="https://..." async></script>
8
8
  *
@@ -222,7 +222,7 @@
222
222
  self.apiKey = attrs.apiKey;
223
223
  self.server = attrs.server;
224
224
  self.visitorId = _getVisitorId();
225
- self._deviceInfo = _getDeviceInfo(); // Device model, platform, type — collected once
225
+ self._deviceInfo = _getDeviceInfo();
226
226
 
227
227
  /* ── Runtime state ── */
228
228
  self.config = Object.assign({}, _DEFAULTS);
@@ -235,22 +235,26 @@
235
235
  self._configError = false;
236
236
  self._pollTimer = null;
237
237
  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 */
238
+ self._POLL_INTERVAL = 4000;
239
+ self._knownMessageIds = {};
240
+ self._pendingReply = false;
241
241
 
242
242
  /* ── DOM refs (populated after mount) ── */
243
243
  self._host = null;
244
244
  self._shadow = null;
245
245
  self._root = null;
246
+ self._overlay = null;
246
247
  self._chatWindow = null;
247
248
  self._messagesContainer = null;
249
+ self._inputArea = null;
248
250
  self._inputEl = null;
249
251
  self._sendBtn = null;
250
252
  self._toggleBtn = null;
251
253
  self._statusDot = null;
252
254
  self._headerStatusDot = null;
253
255
  self._headerStatusText = null;
256
+ self._headerAvatarEl = null;
257
+ self._headerNameEl = null;
254
258
 
255
259
  /* ── Bootstrap ── */
256
260
  self._injectCSS();
@@ -261,7 +265,7 @@
261
265
  }
262
266
 
263
267
  /* ════════════════════════════════════════════════════════════════
264
- CSS — Complete styling injected into Shadow DOM
268
+ CSS — Complete styling injected into Shadow DOM (v3.0.0 revamp)
265
269
  ════════════════════════════════════════════════════════════════ */
266
270
 
267
271
  WAKZWidget.prototype._injectCSS = function () {
@@ -275,15 +279,12 @@
275
279
  ' --wakz-btn: #171717;',
276
280
  ' --wakz-chat-bg: #f8f9fa;',
277
281
  ' --wakz-widget-bg: #ffffff;',
278
- ' --wakz-radius-window: 16px;',
279
- ' --wakz-radius-bubble: 16px;',
280
- ' --wakz-radius-fab: 28px;',
282
+ ' --wakz-icon-radius: 50%;',
281
283
  ' --wakz-shadow-sm: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06);',
282
284
  ' --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-shadow-lg: 0 25px 60px rgba(0,0,0,.15), 0 10px 20px rgba(0,0,0,.1);',
285
286
  ' --wakz-transition: 200ms ease;',
286
- ' --wakz-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;',
287
+ ' --wakz-font: \'Inter\', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;',
287
288
  ' all: initial;',
288
289
  ' font-family: var(--wakz-font);',
289
290
  '}',
@@ -296,35 +297,35 @@
296
297
  '}',
297
298
 
298
299
  /* ══════════════════════════════════════════════════
299
- FLOATING ACTION BUTTON (FAB)
300
+ FLOATING ACTION BUTTON (FAB) — 48px, independent color
300
301
  ══════════════════════════════════════════════════ */
301
302
  '.wakz-fab {',
302
303
  ' position: fixed;',
303
304
  ' z-index: 2147483647;',
304
- ' width: 56px;',
305
- ' height: 56px;',
306
- ' border-radius: var(--wakz-radius-fab);',
305
+ ' width: 48px;',
306
+ ' height: 48px;',
307
+ ' border-radius: var(--wakz-icon-radius);',
307
308
  ' border: none;',
308
309
  ' cursor: pointer;',
309
310
  ' display: flex;',
310
311
  ' align-items: center;',
311
312
  ' justify-content: center;',
312
- ' box-shadow: var(--wakz-shadow-lg);',
313
+ ' box-shadow: 0 4px 14px rgba(0,0,0,.15), 0 2px 6px rgba(0,0,0,.1);',
313
314
  ' transition: transform var(--wakz-transition), box-shadow var(--wakz-transition);',
314
315
  ' outline: none;',
315
316
  ' -webkit-tap-highlight-color: transparent;',
316
317
  ' background: var(--wakz-btn);',
317
318
  '}',
318
319
  '.wakz-fab:hover {',
319
- ' transform: scale(1.08);',
320
- ' box-shadow: var(--wakz-shadow-xl);',
320
+ ' transform: scale(1.06);',
321
+ ' box-shadow: 0 8px 24px rgba(0,0,0,.2), 0 4px 10px rgba(0,0,0,.12);',
321
322
  '}',
322
323
  '.wakz-fab:active {',
323
- ' transform: scale(0.96);',
324
+ ' transform: scale(0.94);',
324
325
  '}',
325
326
  '.wakz-fab svg {',
326
- ' width: 26px;',
327
- ' height: 26px;',
327
+ ' width: 22px;',
328
+ ' height: 22px;',
328
329
  ' color: #ffffff;',
329
330
  ' pointer-events: none;',
330
331
  '}',
@@ -333,6 +334,12 @@
333
334
  '.wakz-fab-pos-br { bottom: 24px; right: 24px; }',
334
335
  '.wakz-fab-pos-bl { bottom: 24px; left: 24px; }',
335
336
 
337
+ /* ── Hide FAB when chat is open ── */
338
+ '.wakz-window.wakz-visible ~ .wakz-fab,',
339
+ '.wakz-fab.wakz-fab-hidden {',
340
+ ' display: none !important;',
341
+ '}',
342
+
336
343
  /* ── FAB Entrance Bounce ── */
337
344
  '@keyframes wakz-fab-enter {',
338
345
  ' 0% { opacity: 0; transform: scale(0.3); }',
@@ -345,22 +352,22 @@
345
352
  '}',
346
353
 
347
354
  /* ══════════════════════════════════════════════════
348
- STATUS DOT (on FAB)
355
+ STATUS DOT (on FAB) — 10px, top-right
349
356
  ══════════════════════════════════════════════════ */
350
357
  '.wakz-fab-dot {',
351
358
  ' position: absolute;',
352
- ' top: 0px;',
353
- ' right: 0px;',
354
- ' width: 14px;',
355
- ' height: 14px;',
359
+ ' top: -1px;',
360
+ ' right: -1px;',
361
+ ' width: 10px;',
362
+ ' height: 10px;',
356
363
  ' border-radius: 50%;',
357
- ' border: 2.5px solid #ffffff;',
364
+ ' border: 2px solid #ffffff;',
358
365
  ' z-index: 2;',
359
366
  ' transition: background 0.3s ease;',
360
367
  '}',
361
368
  '.wakz-fab-pos-bl .wakz-fab-dot {',
362
369
  ' right: auto;',
363
- ' left: 0px;',
370
+ ' left: -1px;',
364
371
  '}',
365
372
 
366
373
  /* ── Online (green pulse) ── */
@@ -369,7 +376,7 @@
369
376
  '}',
370
377
  '@keyframes wakz-dot-pulse {',
371
378
  ' 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); }',
379
+ ' 50% { box-shadow: 0 0 0 5px rgba(34,197,94,0); }',
373
380
  '}',
374
381
  '.wakz-fab-dot.online {',
375
382
  ' animation: wakz-dot-pulse 2s ease-in-out infinite;',
@@ -381,77 +388,100 @@
381
388
  ' animation: none;',
382
389
  '}',
383
390
 
384
- /* ── Error (red, no pulse) ── */
385
- '.wakz-fab-dot.error {',
386
- ' background: #ef4444;',
387
- ' animation: none;',
391
+ /* ══════════════════════════════════════════════════
392
+ OVERLAY / BACKDROP — glass blur behind modal
393
+ ══════════════════════════════════════════════════ */
394
+ '.wakz-overlay {',
395
+ ' position: fixed;',
396
+ ' inset: 0;',
397
+ ' z-index: 2147483645;',
398
+ ' background: rgba(0,0,0,0.25);',
399
+ ' backdrop-filter: blur(4px);',
400
+ ' -webkit-backdrop-filter: blur(4px);',
401
+ ' opacity: 0;',
402
+ ' pointer-events: none;',
403
+ ' transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
404
+ '}',
405
+ '.wakz-overlay.wakz-visible {',
406
+ ' opacity: 1;',
407
+ ' pointer-events: auto;',
388
408
  '}',
389
409
 
390
410
  /* ══════════════════════════════════════════════════
391
- CHAT WINDOW
411
+ CHAT WINDOW — Centered Modal
392
412
  ══════════════════════════════════════════════════ */
393
413
  '.wakz-window {',
394
414
  ' position: fixed;',
395
415
  ' z-index: 2147483646;',
396
- ' width: 380px;',
397
- ' min-height: 500px;',
398
- ' max-height: 70vh;',
399
- ' border-radius: var(--wakz-radius-window);',
416
+ ' top: 50%;',
417
+ ' left: 50%;',
418
+ ' transform: translate(-50%, -50%) scale(0.95);',
419
+ ' width: 390px;',
420
+ ' height: min(580px, 80vh);',
421
+ ' border-radius: 16px;',
400
422
  ' overflow: hidden;',
401
423
  ' display: flex;',
402
424
  ' flex-direction: column;',
403
425
  ' background: var(--wakz-widget-bg);',
404
- ' box-shadow: var(--wakz-shadow-xl);',
426
+ ' box-shadow: 0 25px 60px rgba(0,0,0,0.15), 0 10px 20px rgba(0,0,0,0.1);',
405
427
  ' opacity: 0;',
406
- ' transform: translateY(20px) scale(0.95);',
407
428
  ' pointer-events: none;',
408
429
  ' transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),',
409
430
  ' transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
410
431
  '}',
411
432
  '.wakz-window.wakz-visible {',
412
433
  ' opacity: 1;',
413
- ' transform: translateY(0) scale(1);',
434
+ ' transform: translate(-50%, -50%) scale(1);',
414
435
  ' pointer-events: auto;',
415
436
  '}',
416
437
 
417
- /* ── Window Positioning ── */
418
- '.wakz-win-pos-br { bottom: 92px; right: 24px; }',
419
- '.wakz-win-pos-bl { bottom: 92px; left: 24px; }',
420
-
421
438
  /* ══════════════════════════════════════════════════
422
- HEADER
439
+ HEADER — Glass effect matching main site
423
440
  ══════════════════════════════════════════════════ */
424
441
  '.wakz-hdr {',
425
442
  ' flex-shrink: 0;',
426
- ' padding: 14px 18px;',
443
+ ' height: 52px;',
444
+ ' padding: 0 16px;',
427
445
  ' display: flex;',
428
446
  ' align-items: center;',
429
447
  ' justify-content: space-between;',
430
- ' color: #ffffff;',
448
+ ' color: #171717;',
431
449
  ' cursor: default;',
432
450
  ' user-select: none;',
433
- ' background: var(--wakz-primary);',
451
+ ' background: rgba(255,255,255,0.85);',
452
+ ' backdrop-filter: blur(24px);',
453
+ ' -webkit-backdrop-filter: blur(24px);',
454
+ ' border-bottom: 1px solid rgba(229,229,229,0.6);',
455
+ ' box-shadow: 0 4px 12px rgba(0,0,0,0.05);',
456
+ ' border-radius: 16px 16px 0 0;',
434
457
  '}',
435
458
  '.wakz-hdr-left {',
436
459
  ' display: flex;',
437
460
  ' align-items: center;',
438
- ' gap: 11px;',
461
+ ' gap: 10px;',
439
462
  ' min-width: 0;',
463
+ ' flex: 1;',
464
+ '}',
465
+ '.wakz-hdr-right {',
466
+ ' display: flex;',
467
+ ' align-items: center;',
468
+ ' gap: 8px;',
469
+ ' flex-shrink: 0;',
440
470
  '}',
441
471
 
442
- /* ── Bot Avatar ── */
472
+ /* ── Bot Avatar (32px circle) ── */
443
473
  '.wakz-avatar {',
444
- ' width: 38px;',
445
- ' height: 38px;',
474
+ ' width: 32px;',
475
+ ' height: 32px;',
446
476
  ' border-radius: 50%;',
447
- ' background: rgba(255,255,255,0.18);',
477
+ ' background: rgba(23,23,23,0.1);',
448
478
  ' display: flex;',
449
479
  ' align-items: center;',
450
480
  ' justify-content: center;',
451
481
  ' font-weight: 700;',
452
- ' font-size: 16px;',
482
+ ' font-size: 14px;',
453
483
  ' flex-shrink: 0;',
454
- ' letter-spacing: 0.5px;',
484
+ ' color: var(--wakz-primary);',
455
485
  '}',
456
486
 
457
487
  /* ── Header Info ── */
@@ -464,139 +494,186 @@
464
494
  ' font-size: 15px;',
465
495
  ' font-weight: 600;',
466
496
  ' line-height: 1.3;',
497
+ ' color: #171717;',
467
498
  ' white-space: nowrap;',
468
499
  ' overflow: hidden;',
469
500
  ' text-overflow: ellipsis;',
470
501
  '}',
471
502
  '.wakz-hdr-status {',
472
- ' font-size: 12px;',
473
- ' opacity: 0.9;',
503
+ ' font-size: 11px;',
504
+ ' color: #A3A3A3;',
474
505
  ' display: flex;',
475
506
  ' align-items: center;',
476
- ' gap: 5px;',
477
- ' margin-top: 2px;',
507
+ ' gap: 4px;',
508
+ ' margin-top: 1px;',
478
509
  '}',
510
+
511
+ /* ── Header Status Dot (6px) ── */
479
512
  '.wakz-hdr-status-dot {',
480
- ' width: 7px;',
481
- ' height: 7px;',
513
+ ' width: 6px;',
514
+ ' height: 6px;',
482
515
  ' border-radius: 50%;',
483
516
  ' display: inline-block;',
484
517
  ' flex-shrink: 0;',
485
518
  ' transition: background 0.3s ease;',
486
519
  '}',
487
- '.wakz-hdr-status-dot.online { background: #4ade80; }',
488
- '.wakz-hdr-status-dot.offline { background: #f87171; }',
520
+ '.wakz-hdr-status-dot.online { background: #22c55e; }',
521
+ '.wakz-hdr-status-dot.offline { background: #ef4444; }',
489
522
 
490
- /* ── Close Button ── */
523
+ /* ── Close Button (28px circle, dark) ── */
491
524
  '.wakz-close {',
492
- ' width: 32px;',
493
- ' height: 32px;',
525
+ ' width: 28px;',
526
+ ' height: 28px;',
494
527
  ' border-radius: 50%;',
495
528
  ' border: none;',
496
- ' background: rgba(255,255,255,0.12);',
497
- ' color: #ffffff;',
529
+ ' background: rgba(23,23,23,0.05);',
530
+ ' color: #525252;',
498
531
  ' cursor: pointer;',
499
532
  ' display: flex;',
500
533
  ' align-items: center;',
501
534
  ' justify-content: center;',
502
- ' transition: background var(--wakz-transition);',
535
+ ' transition: background var(--wakz-transition), color var(--wakz-transition);',
503
536
  ' outline: none;',
504
537
  ' flex-shrink: 0;',
505
538
  '}',
506
539
  '.wakz-close:hover {',
507
- ' background: rgba(255,255,255,0.28);',
540
+ ' background: rgba(23,23,23,0.1);',
541
+ ' color: #171717;',
508
542
  '}',
509
543
  '.wakz-close svg {',
510
- ' width: 18px;',
511
- ' height: 18px;',
544
+ ' width: 16px;',
545
+ ' height: 16px;',
512
546
  '}',
513
547
 
514
548
  /* ══════════════════════════════════════════════════
515
- MESSAGES AREA
549
+ MESSAGES AREA — scrollable, padding-bottom for input
516
550
  ══════════════════════════════════════════════════ */
517
551
  '.wakz-msgs {',
518
552
  ' flex: 1;',
519
553
  ' overflow-y: auto;',
520
554
  ' overflow-x: hidden;',
521
- ' padding: 18px 16px 10px;',
555
+ ' padding: 16px 14px 88px;',
522
556
  ' display: flex;',
523
557
  ' flex-direction: column;',
524
- ' gap: 6px;',
558
+ ' gap: 4px;',
525
559
  ' scroll-behavior: smooth;',
526
560
  ' background: var(--wakz-chat-bg);',
561
+ ' position: relative;',
527
562
  '}',
528
- '.wakz-msgs::-webkit-scrollbar { width: 5px; }',
563
+ '.wakz-msgs::-webkit-scrollbar { width: 4px; }',
529
564
  '.wakz-msgs::-webkit-scrollbar-track { background: transparent; }',
530
565
  '.wakz-msgs::-webkit-scrollbar-thumb {',
531
- ' background: rgba(0,0,0,0.12);',
566
+ ' background: rgba(0,0,0,0.1);',
532
567
  ' border-radius: 10px;',
533
568
  '}',
534
569
 
535
570
  /* ══════════════════════════════════════════════════
536
- MESSAGE BUBBLES
571
+ MESSAGE ROWS — new v3 styles
537
572
  ══════════════════════════════════════════════════ */
538
573
  '.wakz-msg-row {',
539
574
  ' display: flex;',
540
- ' max-width: 82%;',
541
575
  ' animation: wakz-msg-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
542
576
  '}',
543
- '.wakz-msg-row.bot { align-self: flex-start; }',
544
- '.wakz-msg-row.user { align-self: flex-end; }',
577
+ '.wakz-msg-row.bot {',
578
+ ' align-self: flex-start;',
579
+ ' align-items: flex-start;',
580
+ ' gap: 8px;',
581
+ ' max-width: 80%;',
582
+ '}',
583
+ '.wakz-msg-row.user {',
584
+ ' align-self: flex-end;',
585
+ ' max-width: 75%;',
586
+ '}',
545
587
 
546
588
  '@keyframes wakz-msg-in {',
547
589
  ' 0% { opacity: 0; transform: translateY(8px); }',
548
590
  ' 100% { opacity: 1; transform: translateY(0); }',
549
591
  '}',
550
592
 
551
- '.wakz-bubble {',
593
+ /* ── User Bubbles ── */
594
+ '.wakz-bubble.user {',
552
595
  ' padding: 10px 14px;',
553
596
  ' font-size: 14px;',
554
597
  ' line-height: 1.55;',
555
598
  ' word-wrap: break-word;',
556
599
  ' overflow-wrap: break-word;',
557
600
  ' white-space: pre-wrap;',
558
- ' border-radius: var(--wakz-radius-bubble);',
559
- ' position: relative;',
560
- '}',
561
- '.wakz-bubble.bot {',
562
- ' background: #ffffff;',
563
- ' color: #1a1a1a;',
564
- ' border-bottom-left-radius: 4px;',
565
- ' box-shadow: var(--wakz-shadow-sm);',
566
- '}',
567
- '.wakz-bubble.user {',
568
- ' color: #ffffff;',
601
+ ' border-radius: 14px;',
569
602
  ' border-bottom-right-radius: 4px;',
570
603
  ' background: var(--wakz-primary);',
604
+ ' color: #ffffff;',
605
+ ' box-shadow: 0 2px 8px rgba(0,0,0,0.08);',
571
606
  '}',
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;',
607
+
608
+ /* ── Bot Messages — NO bubble, text directly on background ── */
609
+ '.wakz-bot-content {',
610
+ ' display: flex;',
611
+ ' flex-direction: column;',
612
+ ' gap: 2px;',
613
+ ' min-width: 0;',
614
+ '}',
615
+ '.wakz-bot-text {',
616
+ ' font-size: 14px;',
617
+ ' line-height: 1.6;',
618
+ ' color: #404040;',
619
+ ' word-wrap: break-word;',
620
+ ' overflow-wrap: break-word;',
621
+ ' white-space: pre-wrap;',
622
+ '}',
623
+ '.wakz-bot-ts {',
624
+ ' font-size: 10px;',
625
+ ' color: #A3A3A3;',
626
+ '}',
627
+
628
+ /* ── Bot Avatar (22px, inline with message) ── */
629
+ '.wakz-bot-avatar {',
630
+ ' width: 22px;',
631
+ ' height: 22px;',
632
+ ' border-radius: 50%;',
633
+ ' background: rgba(23,23,23,0.08);',
634
+ ' display: flex;',
635
+ ' align-items: center;',
636
+ ' justify-content: center;',
637
+ ' font-weight: 700;',
638
+ ' font-size: 10px;',
639
+ ' flex-shrink: 0;',
640
+ ' color: var(--wakz-primary);',
641
+ ' margin-top: 2px;',
578
642
  '}',
579
643
 
580
- /* ── Message Timestamp ── */
644
+ /* ── User Timestamp ── */
581
645
  '.wakz-ts {',
582
646
  ' display: block;',
583
- ' font-size: 11px;',
584
- ' opacity: 0.45;',
585
- ' margin-top: 4px;',
586
- '}',
587
- '.wakz-bubble.user .wakz-ts {',
647
+ ' font-size: 10px;',
648
+ ' opacity: 0.7;',
649
+ ' margin-top: 3px;',
588
650
  ' text-align: right;',
589
651
  '}',
590
652
 
653
+ /* ── Error Messages ── */
654
+ '.wakz-bubble.error-bubble {',
655
+ ' padding: 10px 14px;',
656
+ ' font-size: 14px;',
657
+ ' line-height: 1.55;',
658
+ ' word-wrap: break-word;',
659
+ ' overflow-wrap: break-word;',
660
+ ' white-space: pre-wrap;',
661
+ ' border-radius: 14px;',
662
+ ' border-bottom-left-radius: 4px;',
663
+ ' background: #FEF2F2;',
664
+ ' color: #DC2626;',
665
+ ' border: 1px solid #FECACA;',
666
+ '}',
667
+
591
668
  /* ── Human Agent Badge ── */
592
669
  '.wakz-human-badge {',
593
670
  ' display: inline-block;',
594
671
  ' font-size: 10px;',
595
- ' background: rgba(22, 163, 74, 0.1);',
672
+ ' background: rgba(22, 163, 74, 0.08);',
596
673
  ' color: #16A34A;',
597
- ' border-radius: 8px;',
598
- ' padding: 1px 7px;',
599
- ' margin-top: 5px;',
674
+ ' border-radius: 6px;',
675
+ ' padding: 2px 8px;',
676
+ ' margin-top: 4px;',
600
677
  ' font-weight: 600;',
601
678
  ' letter-spacing: 0.2px;',
602
679
  '}',
@@ -625,23 +702,26 @@
625
702
  '}',
626
703
 
627
704
  /* ══════════════════════════════════════════════════
628
- TYPING INDICATOR
705
+ TYPING INDICATOR — no bubble, just dots on background
629
706
  ══════════════════════════════════════════════════ */
707
+ '.wakz-typing-row {',
708
+ ' display: flex;',
709
+ ' align-items: flex-start;',
710
+ ' gap: 8px;',
711
+ ' align-self: flex-start;',
712
+ ' animation: wakz-msg-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
713
+ '}',
630
714
  '.wakz-typing {',
631
715
  ' display: flex;',
632
716
  ' align-items: center;',
633
717
  ' 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);',
718
+ ' padding: 6px 0;',
639
719
  '}',
640
720
  '.wakz-typing-dot {',
641
- ' width: 7px;',
642
- ' height: 7px;',
721
+ ' width: 6px;',
722
+ ' height: 6px;',
643
723
  ' border-radius: 50%;',
644
- ' background: #9ca3af;',
724
+ ' background: #A3A3A3;',
645
725
  ' animation: wakz-bounce-dot 1.4s ease-in-out infinite;',
646
726
  '}',
647
727
  '.wakz-typing-dot:nth-child(2) { animation-delay: 0.16s; }',
@@ -652,45 +732,53 @@
652
732
  '}',
653
733
 
654
734
  /* ══════════════════════════════════════════════════
655
- INPUT AREA
735
+ INPUT AREA — Floating, glass effect, inside messages
656
736
  ══════════════════════════════════════════════════ */
657
- '.wakz-input-wrap {',
658
- ' flex-shrink: 0;',
659
- ' padding: 12px 14px;',
660
- ' border-top: 1px solid #e5e7eb;',
661
- ' background: var(--wakz-widget-bg);',
737
+ '.wakz-input-area {',
738
+ ' position: absolute;',
739
+ ' bottom: 12px;',
740
+ ' left: 12px;',
741
+ ' right: 12px;',
662
742
  ' display: flex;',
663
743
  ' align-items: flex-end;',
664
744
  ' gap: 8px;',
745
+ ' padding: 8px 10px 8px 14px;',
746
+ ' border-radius: 12px;',
747
+ ' background: rgba(255,255,255,0.85);',
748
+ ' backdrop-filter: blur(24px);',
749
+ ' -webkit-backdrop-filter: blur(24px);',
750
+ ' border: 1px solid rgba(229,229,229,0.6);',
751
+ ' box-shadow: 0 4px 12px rgba(0,0,0,0.05);',
752
+ ' z-index: 10;',
665
753
  '}',
666
754
  '.wakz-input {',
667
755
  ' flex: 1;',
668
- ' border: 1.5px solid #e5e7eb;',
669
- ' border-radius: 22px;',
670
- ' padding: 10px 16px;',
756
+ ' border: none;',
757
+ ' border-radius: 0;',
758
+ ' padding: 6px 4px;',
671
759
  ' font-size: 14px;',
672
760
  ' line-height: 1.4;',
673
761
  ' outline: none;',
674
762
  ' font-family: var(--wakz-font);',
675
- ' transition: border-color var(--wakz-transition);',
676
763
  ' resize: none;',
677
- ' max-height: 100px;',
764
+ ' max-height: 80px;',
765
+ ' min-height: 22px;',
678
766
  ' overflow-y: auto;',
679
- ' background: #f9fafb;',
680
- ' color: #111827;',
767
+ ' background: transparent;',
768
+ ' color: #171717;',
681
769
  '}',
682
770
  '.wakz-input:focus {',
683
- ' border-color: var(--wakz-primary);',
684
- ' background: #ffffff;',
771
+ ' outline: none;',
685
772
  '}',
686
773
  '.wakz-input::placeholder {',
687
- ' color: #9ca3af;',
774
+ ' color: #A3A3A3;',
688
775
  '}',
689
776
 
690
- /* ── Send Button ── */
777
+ /* ── Send Button (36px floating circle, separate from input) ── */
691
778
  '.wakz-send {',
692
- ' width: 40px;',
693
- ' height: 40px;',
779
+ ' width: 36px;',
780
+ ' height: 36px;',
781
+ ' min-width: 36px;',
694
782
  ' border-radius: 50%;',
695
783
  ' border: none;',
696
784
  ' color: #ffffff;',
@@ -699,99 +787,88 @@
699
787
  ' align-items: center;',
700
788
  ' justify-content: center;',
701
789
  ' flex-shrink: 0;',
702
- ' transition: opacity var(--wakz-transition), transform 0.1s ease, box-shadow var(--wakz-transition);',
790
+ ' transition: transform var(--wakz-transition), opacity var(--wakz-transition), box-shadow var(--wakz-transition);',
703
791
  ' outline: none;',
704
792
  ' background: var(--wakz-primary);',
705
- ' box-shadow: var(--wakz-shadow-sm);',
793
+ ' box-shadow: 0 2px 8px rgba(0,0,0,0.12);',
706
794
  '}',
707
795
  '.wakz-send:hover:not(:disabled) {',
708
- ' box-shadow: var(--wakz-shadow-md);',
796
+ ' transform: scale(1.05);',
797
+ ' box-shadow: 0 4px 14px rgba(0,0,0,0.18);',
709
798
  '}',
710
799
  '.wakz-send:disabled {',
711
- ' opacity: 0.35;',
800
+ ' opacity: 0.3;',
712
801
  ' cursor: not-allowed;',
713
802
  '}',
714
803
  '.wakz-send:not(:disabled):active {',
715
- ' transform: scale(0.88);',
804
+ ' transform: scale(0.92);',
716
805
  '}',
717
806
  '.wakz-send svg {',
718
- ' width: 18px;',
719
- ' height: 18px;',
807
+ ' width: 16px;',
808
+ ' height: 16px;',
720
809
  ' pointer-events: none;',
721
810
  '}',
722
811
 
723
812
  /* ══════════════════════════════════════════════════
724
- WELCOME MESSAGE
813
+ WELCOME MESSAGE — same as bot (no bubble)
725
814
  ══════════════════════════════════════════════════ */
726
815
  '.wakz-welcome-wrap {',
727
816
  ' display: flex;',
817
+ ' align-items: flex-start;',
728
818
  ' gap: 8px;',
729
- ' max-width: 88%;',
819
+ ' max-width: 80%;',
730
820
  ' align-self: flex-start;',
731
821
  ' animation: wakz-msg-in 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
732
822
  '}',
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;',
741
- ' color: #ffffff;',
742
- ' font-weight: 700;',
743
- ' font-size: 12px;',
744
- ' flex-shrink: 0;',
745
- ' align-self: flex-end;',
746
- ' margin-bottom: 2px;',
747
- '}',
748
823
 
749
824
  /* ══════════════════════════════════════════════════
750
825
  RTL SUPPORT
751
826
  ══════════════════════════════════════════════════ */
752
827
  '.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;',
756
- '}',
757
828
  '.wakz-rtl .wakz-bubble.user {',
758
- ' border-bottom-right-radius: var(--wakz-radius-bubble);',
829
+ ' border-bottom-right-radius: 14px;',
759
830
  ' border-bottom-left-radius: 4px;',
760
831
  '}',
761
- '.wakz-rtl .wakz-typing {',
762
- ' border-bottom-left-radius: var(--wakz-radius-bubble);',
832
+ '.wakz-rtl .wakz-bubble.error-bubble {',
833
+ ' border-bottom-left-radius: 14px;',
763
834
  ' border-bottom-right-radius: 4px;',
764
835
  '}',
765
- '.wakz-rtl .wakz-bubble.user .wakz-ts {',
836
+ '.wakz-rtl .wakz-ts {',
766
837
  ' text-align: left;',
767
838
  '}',
768
839
 
769
840
  /* ══════════════════════════════════════════════════
770
- MOBILE RESPONSIVE
841
+ MOBILE RESPONSIVE — centered modal, NOT fullscreen
771
842
  ══════════════════════════════════════════════════ */
772
843
  '@media (max-width: 480px) {',
773
844
  ' .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;',
782
- ' border-radius: 0 !important;',
845
+ ' width: calc(100vw - 32px) !important;',
846
+ ' height: min(500px, 75vh) !important;',
783
847
  ' }',
784
848
  ' .wakz-window.wakz-visible {',
785
- ' transform: translateY(0) scale(1);',
849
+ ' transform: translate(-50%, -50%) scale(1);',
850
+ ' }',
851
+ ' .wakz-fab {',
852
+ ' width: 44px;',
853
+ ' height: 44px;',
786
854
  ' }',
787
855
  ' .wakz-fab-pos-br { bottom: 16px; right: 16px; }',
788
856
  ' .wakz-fab-pos-bl { bottom: 16px; left: 16px; }',
857
+ ' .wakz-input-area {',
858
+ ' left: 10px;',
859
+ ' right: 10px;',
860
+ ' bottom: 10px;',
861
+ ' padding: 6px 8px 6px 12px;',
862
+ ' }',
863
+ ' .wakz-msgs {',
864
+ ' padding: 14px 12px 84px;',
865
+ ' }',
789
866
  '}'
790
867
  ].join('\n');
791
868
  };
792
869
 
793
870
  /* ════════════════════════════════════════════════════════════════
794
- DOM CREATION
871
+ DOM CREATION — v3.0.0 new structure
795
872
  ════════════════════════════════════════════════════════════════ */
796
873
 
797
874
  WAKZWidget.prototype._createDOM = function () {
@@ -810,31 +887,38 @@
810
887
  self._shadow.appendChild(self._styleEl);
811
888
  self._shadow.appendChild(self._root);
812
889
 
813
- /* ══════════════ FLOATING ACTION BUTTON ══════════════ */
890
+ /* ══════════════ FLOATING ACTION BUTTON (FAB) ══════════════ */
814
891
  self._toggleBtn = _el('button', {
815
892
  className: 'wakz-fab wakz-fab-pos-' + posClass,
816
893
  'aria-label': str.openChat
817
894
  });
818
895
  self._toggleBtn.innerHTML = _ICONS.chatBubble;
819
896
 
820
- /* Status dot on FAB */
897
+ /* Status dot on FAB (10px) */
821
898
  self._statusDot = _el('span', { className: 'wakz-fab-dot ' + (self.config.online ? 'online' : 'offline') });
822
899
  self._statusDot.style.display = self.config.showStatus ? '' : 'none';
823
900
  self._toggleBtn.appendChild(self._statusDot);
824
901
  self._root.appendChild(self._toggleBtn);
825
902
 
826
- /* ══════════════ CHAT WINDOW ══════════════ */
827
- self._chatWindow = _el('div', { className: 'wakz-window wakz-win-pos-' + posClass });
903
+ /* ══════════════ OVERLAY / BACKDROP ══════════════ */
904
+ self._overlay = _el('div', { className: 'wakz-overlay' });
905
+ self._root.appendChild(self._overlay);
906
+
907
+ /* ══════════════ CHAT WINDOW (Centered Modal) ══════════════ */
908
+ self._chatWindow = _el('div', { className: 'wakz-window' });
828
909
 
829
- /* ── Header ── */
910
+ /* ── Header (glass effect, dark text) ── */
830
911
  var headerEl = _el('div', { className: 'wakz-hdr' });
831
912
 
913
+ /* Left side: avatar + name + status */
832
914
  var headerLeft = _el('div', { className: 'wakz-hdr-left' });
833
915
  var avatarLetter = (self.config.botName || 'W')[0].toUpperCase();
834
- headerLeft.appendChild(_el('div', { className: 'wakz-avatar' }, [avatarLetter]));
916
+ self._headerAvatarEl = _el('div', { className: 'wakz-avatar' }, [avatarLetter]);
917
+ headerLeft.appendChild(self._headerAvatarEl);
835
918
 
836
919
  var headerInfo = _el('div', { className: 'wakz-hdr-info' });
837
- headerInfo.appendChild(_el('span', { className: 'wakz-hdr-name' }, [self.config.botName]));
920
+ self._headerNameEl = _el('span', { className: 'wakz-hdr-name' }, [self.config.botName]);
921
+ headerInfo.appendChild(self._headerNameEl);
838
922
 
839
923
  var statusLine = _el('span', { className: 'wakz-hdr-status' });
840
924
  self._headerStatusDot = _el('span', { className: 'wakz-hdr-status-dot ' + (self.config.online ? 'online' : 'offline') });
@@ -846,17 +930,20 @@
846
930
  headerLeft.appendChild(headerInfo);
847
931
  headerEl.appendChild(headerLeft);
848
932
 
933
+ /* Right side: status dot + text + close button */
934
+ var headerRight = _el('div', { className: 'wakz-hdr-right' });
849
935
  var closeBtn = _el('button', { className: 'wakz-close', 'aria-label': str.closeChat });
850
936
  closeBtn.innerHTML = _ICONS.close;
851
- headerEl.appendChild(closeBtn);
937
+ headerRight.appendChild(closeBtn);
938
+ headerEl.appendChild(headerRight);
939
+
852
940
  self._chatWindow.appendChild(headerEl);
853
941
 
854
- /* ── Messages Container ── */
942
+ /* ── Messages Container (with floating input area inside) ── */
855
943
  self._messagesContainer = _el('div', { className: 'wakz-msgs' });
856
- self._chatWindow.appendChild(self._messagesContainer);
857
944
 
858
- /* ── Input Area ── */
859
- var inputWrap = _el('div', { className: 'wakz-input-wrap' });
945
+ /* ── Floating Input Area (inside messages container) ── */
946
+ self._inputArea = _el('div', { className: 'wakz-input-area' });
860
947
 
861
948
  self._inputEl = _el('textarea', {
862
949
  className: 'wakz-input',
@@ -864,15 +951,17 @@
864
951
  rows: 1,
865
952
  dir: isRtl ? 'rtl' : 'ltr'
866
953
  });
867
- inputWrap.appendChild(self._inputEl);
954
+ self._inputArea.appendChild(self._inputEl);
868
955
 
869
956
  self._sendBtn = _el('button', {
870
957
  className: 'wakz-send',
871
958
  'aria-label': str.sendMessage
872
959
  });
873
960
  self._sendBtn.innerHTML = _ICONS.send;
874
- inputWrap.appendChild(self._sendBtn);
875
- self._chatWindow.appendChild(inputWrap);
961
+ self._inputArea.appendChild(self._sendBtn);
962
+
963
+ self._messagesContainer.appendChild(self._inputArea);
964
+ self._chatWindow.appendChild(self._messagesContainer);
876
965
 
877
966
  self._root.appendChild(self._chatWindow);
878
967
  };
@@ -889,6 +978,11 @@
889
978
  self.toggleChat(!self.isOpen);
890
979
  });
891
980
 
981
+ /* Overlay click to close */
982
+ self._overlay.addEventListener('click', function () {
983
+ self.toggleChat(false);
984
+ });
985
+
892
986
  /* Close button click */
893
987
  self._chatWindow.querySelector('.wakz-close').addEventListener('click', function () {
894
988
  self.toggleChat(false);
@@ -907,10 +1001,10 @@
907
1001
  }
908
1002
  });
909
1003
 
910
- /* Auto-resize textarea */
1004
+ /* Auto-resize textarea (max 80px) */
911
1005
  self._inputEl.addEventListener('input', function () {
912
1006
  self._inputEl.style.height = 'auto';
913
- self._inputEl.style.height = Math.min(self._inputEl.scrollHeight, 100) + 'px';
1007
+ self._inputEl.style.height = Math.min(self._inputEl.scrollHeight, 80) + 'px';
914
1008
  });
915
1009
 
916
1010
  /* Escape to close */
@@ -925,7 +1019,6 @@
925
1019
 
926
1020
  WAKZWidget.prototype._playFabEntrance = function () {
927
1021
  var self = this;
928
- /* Start invisible, then animate after a brief delay */
929
1022
  self._toggleBtn.style.opacity = '0';
930
1023
  setTimeout(function () {
931
1024
  self._toggleBtn.style.opacity = '';
@@ -942,6 +1035,8 @@
942
1035
  self.isOpen = open;
943
1036
  if (open) {
944
1037
  self._chatWindow.classList.add('wakz-visible');
1038
+ self._overlay.classList.add('wakz-visible');
1039
+ self._toggleBtn.classList.add('wakz-fab-hidden');
945
1040
  /* Fetch history on first open (after config is loaded) */
946
1041
  if (self._configLoaded && !self._hasFetchedHistory) {
947
1042
  self._fetchHistory();
@@ -955,6 +1050,8 @@
955
1050
  setTimeout(function () { self._inputEl.focus(); }, 320);
956
1051
  } else {
957
1052
  self._chatWindow.classList.remove('wakz-visible');
1053
+ self._overlay.classList.remove('wakz-visible');
1054
+ self._toggleBtn.classList.remove('wakz-fab-hidden');
958
1055
  /* Stop polling when chat is closed */
959
1056
  self._stopPolling();
960
1057
  }
@@ -988,7 +1085,6 @@
988
1085
  }
989
1086
  })
990
1087
  .catch(function (err) {
991
- /* AbortError means timeout */
992
1088
  self._handleConfigError();
993
1089
  });
994
1090
  };
@@ -1027,31 +1123,29 @@
1027
1123
  hostStyle.setProperty('--wakz-btn', cfg.btnColor);
1028
1124
  hostStyle.setProperty('--wakz-chat-bg', cfg.chatBg);
1029
1125
  hostStyle.setProperty('--wakz-widget-bg', cfg.widgetBg);
1126
+ hostStyle.setProperty('--wakz-icon-radius', cfg.iconRadius || '50%');
1030
1127
 
1031
1128
  /* ── RTL ── */
1032
1129
  if (isRtl) self._root.classList.add('wakz-rtl');
1033
1130
  else self._root.classList.remove('wakz-rtl');
1034
1131
 
1035
1132
  /* ── FAB ── */
1036
- self._toggleBtn.className = 'wakz-fab wakz-fab-pos-' + posClass + ' wakz-fab-enter';
1133
+ self._toggleBtn.className = 'wakz-fab wakz-fab-pos-' + posClass + ' wakz-fab-enter' + (self.isOpen ? ' wakz-fab-hidden' : '');
1037
1134
  self._toggleBtn.setAttribute('aria-label', str.openChat);
1038
1135
 
1039
1136
  /* ── FAB Status Dot ── */
1040
1137
  self._statusDot.className = 'wakz-fab-dot ' + (cfg.online ? 'online' : 'offline');
1041
1138
  self._statusDot.style.display = cfg.showStatus ? '' : 'none';
1042
1139
 
1043
- /* ── Window Position ── */
1044
- self._chatWindow.className = 'wakz-window wakz-win-pos-' + posClass + (self.isOpen ? ' wakz-visible' : '');
1140
+ /* ── Window ── */
1141
+ self._chatWindow.className = 'wakz-window' + (self.isOpen ? ' wakz-visible' : '');
1045
1142
 
1046
- /* ── Header ── */
1047
- var headerEl = self._chatWindow.querySelector('.wakz-hdr');
1048
- if (headerEl) headerEl.style.background = cfg.primaryColor;
1143
+ /* ── Overlay ── */
1144
+ self._overlay.className = 'wakz-overlay' + (self.isOpen ? ' wakz-visible' : '');
1049
1145
 
1050
1146
  /* ── 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();
1147
+ if (self._headerNameEl) self._headerNameEl.textContent = cfg.botName;
1148
+ if (self._headerAvatarEl) self._headerAvatarEl.textContent = (cfg.botName || 'W')[0].toUpperCase();
1055
1149
 
1056
1150
  /* ── Header Status ── */
1057
1151
  if (self._headerStatusDot) {
@@ -1098,7 +1192,6 @@
1098
1192
  .then(function (res) { return res.json(); })
1099
1193
  .then(function (data) {
1100
1194
  if (data && data.success && data.messages && data.messages.length > 0) {
1101
- /* Clear any existing messages (e.g., welcome) and render history */
1102
1195
  self._clearMessages();
1103
1196
  self._knownMessageIds = {};
1104
1197
  var msgs = data.messages;
@@ -1109,7 +1202,6 @@
1109
1202
  var role = m.role === 'user' ? 'user' : 'bot';
1110
1203
  self._appendMessage(role, m.content, false, m.createdAt, m.role === 'human_agent');
1111
1204
  }
1112
- /* Set last poll time to the most recent message */
1113
1205
  var lastMsg = msgs[msgs.length - 1];
1114
1206
  if (lastMsg && lastMsg.createdAt) {
1115
1207
  self._lastPollTime = lastMsg.createdAt;
@@ -1127,7 +1219,7 @@
1127
1219
 
1128
1220
  WAKZWidget.prototype._startPolling = function () {
1129
1221
  var self = this;
1130
- self._stopPolling(); /* Clear any existing timer */
1222
+ self._stopPolling();
1131
1223
  if (!self._lastPollTime) {
1132
1224
  self._lastPollTime = new Date().toISOString();
1133
1225
  }
@@ -1147,7 +1239,7 @@
1147
1239
  WAKZWidget.prototype._pollForNewMessages = function () {
1148
1240
  var self = this;
1149
1241
  if (!self.server || !self.apiKey || !self._lastPollTime) return;
1150
- /* Skip polling while waiting for our own reply — prevents race condition duplicates */
1242
+ /* Skip polling while waiting for our own reply */
1151
1243
  if (self._pendingReply) return;
1152
1244
 
1153
1245
  var url = self.server + '/api/v1/embed/chat?api_key=' +
@@ -1171,7 +1263,6 @@
1171
1263
  var isHumanAgent = m.role === 'human_agent';
1172
1264
  self._appendMessage(role, m.content, false, m.createdAt, isHumanAgent);
1173
1265
  }
1174
- /* Update last poll time */
1175
1266
  var lastMsg = msgs[msgs.length - 1];
1176
1267
  if (lastMsg && lastMsg.createdAt) {
1177
1268
  self._lastPollTime = lastMsg.createdAt;
@@ -1189,13 +1280,19 @@
1189
1280
 
1190
1281
  WAKZWidget.prototype._clearMessages = function () {
1191
1282
  var self = this;
1192
- self._messagesContainer.innerHTML = '';
1283
+ /* Remove all children except the input area */
1284
+ var children = Array.prototype.slice.call(self._messagesContainer.children);
1285
+ for (var i = 0; i < children.length; i++) {
1286
+ if (children[i] !== self._inputArea) {
1287
+ self._messagesContainer.removeChild(children[i]);
1288
+ }
1289
+ }
1193
1290
  self.messages = [];
1194
1291
  self._knownMessageIds = {};
1195
1292
  };
1196
1293
 
1197
1294
  /* ════════════════════════════════════════════════════════════════
1198
- APPEND WELCOME MESSAGE (with bot avatar)
1295
+ APPEND WELCOME MESSAGE no bubble style, matching bot messages
1199
1296
  ════════════════════════════════════════════════════════════════ */
1200
1297
 
1201
1298
  WAKZWidget.prototype._appendWelcomeMessage = function (text) {
@@ -1204,36 +1301,30 @@
1204
1301
 
1205
1302
  var wrap = _el('div', { className: 'wakz-welcome-wrap' });
1206
1303
 
1207
- /* Bot avatar */
1208
- var avatar = _el('div', { className: 'wakz-welcome-avatar' },
1304
+ /* Bot avatar (22px) */
1305
+ var avatar = _el('div', { className: 'wakz-bot-avatar' },
1209
1306
  [(self.config.botName || 'W')[0].toUpperCase()]);
1210
1307
  wrap.appendChild(avatar);
1211
1308
 
1212
- /* Bubble */
1213
- var bubble = _el('div', { className: 'wakz-bubble bot' }, [text]);
1214
- wrap.appendChild(bubble);
1309
+ /* Bot text content — no bubble */
1310
+ var content = _el('div', { className: 'wakz-bot-content' });
1311
+ content.appendChild(_el('div', { className: 'wakz-bot-text' }, [text]));
1312
+ wrap.appendChild(content);
1215
1313
 
1216
- self._messagesContainer.appendChild(wrap);
1314
+ /* Insert before input area */
1315
+ self._messagesContainer.insertBefore(wrap, self._inputArea);
1217
1316
  self.messages.push({ sender: 'bot', text: text });
1218
1317
  self._scrollToBottom();
1219
1318
  };
1220
1319
 
1221
1320
  /* ════════════════════════════════════════════════════════════════
1222
- APPEND MESSAGE TO CHAT
1321
+ APPEND MESSAGE TO CHAT — v3 rendering
1223
1322
  ════════════════════════════════════════════════════════════════ */
1224
1323
 
1225
1324
  WAKZWidget.prototype._appendMessage = function (sender, text, isError, timestamp, isHumanAgent) {
1226
1325
  var self = this;
1227
1326
 
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 */
1327
+ /* ── Format timestamp ── */
1237
1328
  var tsText = '';
1238
1329
  if (timestamp) {
1239
1330
  try {
@@ -1249,69 +1340,135 @@
1249
1340
  tsText = now.getHours().toString().padStart(2, '0') + ':' +
1250
1341
  now.getMinutes().toString().padStart(2, '0');
1251
1342
  }
1252
- bubble.appendChild(_el('span', { className: 'wakz-ts' }, [tsText]));
1253
1343
 
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
- }
1344
+ if (sender === 'user') {
1345
+ /* ── USER MESSAGE: colored bubble with tail ── */
1346
+ var row = _el('div', { className: 'wakz-msg-row user' });
1347
+
1348
+ var bubbleClass = 'wakz-bubble user';
1349
+ if (isError) bubbleClass += ' error-bubble';
1350
+ var bubble = _el('div', { className: bubbleClass });
1351
+ bubble.appendChild(document.createTextNode(text));
1352
+ bubble.appendChild(_el('span', { className: 'wakz-ts' }, [tsText]));
1353
+
1354
+ /* Retry button for error user messages */
1355
+ if (isError) {
1356
+ var str = _strings(self.config.language);
1357
+ var retryBtn = _el('button', { className: 'wakz-retry' }, [
1358
+ _ICONS.error + ' ' + str.retry
1359
+ ]);
1360
+ (function (rowCapture) {
1361
+ retryBtn.addEventListener('click', function () {
1362
+ if (rowCapture.parentNode) rowCapture.parentNode.removeChild(rowCapture);
1363
+ for (var i = self.messages.length - 1; i >= 0; i--) {
1364
+ if (self.messages[i].text === text && self.messages[i].isError) {
1365
+ self.messages.splice(i, 1);
1366
+ break;
1367
+ }
1368
+ }
1369
+ var lastUserMsg = null;
1370
+ for (var j = self.messages.length - 1; j >= 0; j--) {
1371
+ if (self.messages[j].sender === 'user') {
1372
+ lastUserMsg = self.messages[j].text;
1373
+ break;
1374
+ }
1375
+ }
1376
+ if (lastUserMsg) self._sendToAPI(lastUserMsg);
1377
+ });
1378
+ })(row);
1379
+ bubble.appendChild(retryBtn);
1380
+ }
1260
1381
 
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;
1382
+ row.appendChild(bubble);
1383
+ self._messagesContainer.insertBefore(row, self._inputArea);
1384
+ self.messages.push({ sender: sender, text: text, isError: !!isError });
1385
+ } else {
1386
+ /* ── BOT / HUMAN AGENT MESSAGE: no bubble, text on background ── */
1387
+ var botRow = _el('div', { className: 'wakz-msg-row bot' });
1388
+
1389
+ /* Small bot avatar (22px) */
1390
+ var botAvatar = _el('div', { className: 'wakz-bot-avatar' },
1391
+ [(self.config.botName || 'W')[0].toUpperCase()]);
1392
+ botRow.appendChild(botAvatar);
1393
+
1394
+ /* Content wrapper */
1395
+ var botContent = _el('div', { className: 'wakz-bot-content' });
1396
+
1397
+ if (isError) {
1398
+ /* Error: subtle red area */
1399
+ var errBubble = _el('div', { className: 'wakz-bubble error-bubble' });
1400
+ errBubble.appendChild(document.createTextNode(text));
1401
+
1402
+ var errStr = _strings(self.config.language);
1403
+ var errRetryBtn = _el('button', { className: 'wakz-retry' }, [
1404
+ _ICONS.error + ' ' + errStr.retry
1405
+ ]);
1406
+ (function (rowCapture, errText) {
1407
+ errRetryBtn.addEventListener('click', function () {
1408
+ if (rowCapture.parentNode) rowCapture.parentNode.removeChild(rowCapture);
1409
+ for (var i = self.messages.length - 1; i >= 0; i--) {
1410
+ if (self.messages[i].text === errText && self.messages[i].isError) {
1411
+ self.messages.splice(i, 1);
1412
+ break;
1413
+ }
1276
1414
  }
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;
1415
+ var lastUserMsg = null;
1416
+ for (var j = self.messages.length - 1; j >= 0; j--) {
1417
+ if (self.messages[j].sender === 'user') {
1418
+ lastUserMsg = self.messages[j].text;
1419
+ break;
1420
+ }
1284
1421
  }
1285
- }
1286
- if (lastUserMsg) self._sendToAPI(lastUserMsg);
1287
- });
1288
- })(row);
1289
- bubble.appendChild(retryBtn);
1290
- }
1422
+ if (lastUserMsg) self._sendToAPI(lastUserMsg);
1423
+ });
1424
+ })(botRow, text);
1425
+ errBubble.appendChild(errRetryBtn);
1426
+ botContent.appendChild(errBubble);
1427
+ } else {
1428
+ /* Normal bot/human message: no bubble */
1429
+ botContent.appendChild(_el('div', { className: 'wakz-bot-text' }, [text]));
1291
1430
 
1292
- row.appendChild(bubble);
1293
- self._messagesContainer.appendChild(row);
1431
+ /* Timestamp */
1432
+ botContent.appendChild(_el('span', { className: 'wakz-bot-ts' }, [tsText]));
1433
+
1434
+ /* Human agent badge */
1435
+ if (isHumanAgent) {
1436
+ var iStr = _strings(self.config.language);
1437
+ var badge = _el('span', { className: 'wakz-human-badge' }, [iStr.humanBadge || '👤 فريق الدعم']);
1438
+ botContent.appendChild(badge);
1439
+ }
1440
+ }
1441
+
1442
+ botRow.appendChild(botContent);
1443
+ self._messagesContainer.insertBefore(botRow, self._inputArea);
1444
+ self.messages.push({ sender: sender, text: text, isError: !!isError });
1445
+ }
1294
1446
 
1295
- self.messages.push({ sender: sender, text: text, isError: !!isError });
1296
1447
  self._scrollToBottom();
1297
1448
  };
1298
1449
 
1299
1450
  /* ════════════════════════════════════════════════════════════════
1300
- TYPING INDICATOR
1451
+ TYPING INDICATOR — no bubble, dots on background with avatar
1301
1452
  ════════════════════════════════════════════════════════════════ */
1302
1453
 
1303
1454
  WAKZWidget.prototype._showTyping = function () {
1304
1455
  var self = this;
1305
1456
  if (self._typingEl) return;
1306
1457
 
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);
1458
+ self._typingEl = _el('div', { className: 'wakz-typing-row' });
1459
+
1460
+ /* Small avatar for consistency */
1461
+ self._typingEl.appendChild(_el('div', { className: 'wakz-bot-avatar' },
1462
+ [(self.config.botName || 'W')[0].toUpperCase()]));
1463
+
1464
+ /* Bouncing dots — no bubble wrapper */
1465
+ var typingDots = _el('div', { className: 'wakz-typing' });
1466
+ typingDots.appendChild(_el('span', { className: 'wakz-typing-dot' }));
1467
+ typingDots.appendChild(_el('span', { className: 'wakz-typing-dot' }));
1468
+ typingDots.appendChild(_el('span', { className: 'wakz-typing-dot' }));
1469
+ self._typingEl.appendChild(typingDots);
1470
+
1471
+ self._messagesContainer.insertBefore(self._typingEl, self._inputArea);
1315
1472
  self._scrollToBottom();
1316
1473
  };
1317
1474
 
@@ -1372,7 +1529,7 @@
1372
1529
  self._sendBtn.disabled = true;
1373
1530
  self._showTyping();
1374
1531
 
1375
- /* Freeze poll time BEFORE sending — prevents polling from fetching our own messages */
1532
+ /* Freeze poll time BEFORE sending */
1376
1533
  self._lastPollTime = new Date().toISOString();
1377
1534
  self._pendingReply = true;
1378
1535
 
@@ -1408,7 +1565,6 @@
1408
1565
  } else {
1409
1566
  self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
1410
1567
  }
1411
- /* Advance poll time past any messages created during this request */
1412
1568
  self._lastPollTime = (data && data.timestamp)
1413
1569
  ? new Date(new Date(data.timestamp).getTime() + 1000).toISOString()
1414
1570
  : new Date(Date.now() + 1000).toISOString();
@@ -1417,7 +1573,6 @@
1417
1573
  self._hideTyping();
1418
1574
  self._pendingReply = false;
1419
1575
  self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
1420
- /* Advance poll time even on error */
1421
1576
  self._lastPollTime = new Date(Date.now() + 1000).toISOString();
1422
1577
  })
1423
1578
  .finally(function () {
@@ -1432,7 +1587,7 @@
1432
1587
  ════════════════════════════════════════════════════════════════ */
1433
1588
 
1434
1589
  function boot() {
1435
- try { new WAKZWidget(); } catch (e) { console.error('[WAKZ Widget] Init error:', e); }
1590
+ try { new WAKZWidget(); } catch (e) { console.error('[WAKZ Widget v3.0.0] Init error:', e); }
1436
1591
  }
1437
1592
 
1438
1593
  if (document.readyState === 'loading') {