wakz-chat-widget 2.0.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.
- package/README.md +1 -44
- package/index.js +495 -448
- package/package.json +6 -24
package/index.js
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* WAKZ Chat Widget v3.0.0
|
|
3
3
|
* ─────────────────────────────────────────────────────────────────
|
|
4
4
|
* A production-grade, self-contained chat widget using Shadow DOM.
|
|
5
|
-
*
|
|
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
|
-
* New attrs: data-icon-color="#171717" data-icon-shape="circle"
|
|
9
8
|
*
|
|
10
9
|
* ZERO external dependencies — pure vanilla JavaScript.
|
|
11
10
|
*/
|
|
@@ -51,9 +50,7 @@
|
|
|
51
50
|
if (!target && scripts.length > 0) target = scripts[scripts.length - 1];
|
|
52
51
|
return {
|
|
53
52
|
apiKey: (target && target.getAttribute('data-api-key')) || '',
|
|
54
|
-
server: (target && target.getAttribute('data-server')) || ''
|
|
55
|
-
iconColor: (target && target.getAttribute('data-icon-color')) || '',
|
|
56
|
-
iconShape: (target && target.getAttribute('data-icon-shape')) || ''
|
|
53
|
+
server: (target && target.getAttribute('data-server')) || ''
|
|
57
54
|
};
|
|
58
55
|
}
|
|
59
56
|
|
|
@@ -82,6 +79,46 @@
|
|
|
82
79
|
return el;
|
|
83
80
|
}
|
|
84
81
|
|
|
82
|
+
/** Detect device model, platform, and type using userAgentData API (Chrome 90+) with UA fallback */
|
|
83
|
+
function _getDeviceInfo() {
|
|
84
|
+
var info = {
|
|
85
|
+
device_model: '',
|
|
86
|
+
device_platform: '',
|
|
87
|
+
device_type: 'desktop'
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Try userAgentData API (Chrome 90+, Edge, Samsung Browser — NOT Safari/Firefox)
|
|
91
|
+
if (navigator.userAgentData) {
|
|
92
|
+
info.device_model = navigator.userAgentData.model || '';
|
|
93
|
+
info.device_platform = navigator.userAgentData.platform || '';
|
|
94
|
+
if (navigator.userAgentData.mobile === true) {
|
|
95
|
+
info.device_type = 'mobile';
|
|
96
|
+
} else if (navigator.userAgentData.mobile === false && info.device_platform === 'Android') {
|
|
97
|
+
info.device_type = 'tablet';
|
|
98
|
+
} else {
|
|
99
|
+
info.device_type = 'desktop';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Fallback: parse from user agent string
|
|
104
|
+
var ua = navigator.userAgent || '';
|
|
105
|
+
if (!info.device_platform) {
|
|
106
|
+
if (/iPhone|iPad|iPod/i.test(ua)) info.device_platform = 'iOS';
|
|
107
|
+
else if (/Android/i.test(ua)) info.device_platform = 'Android';
|
|
108
|
+
else if (/Windows/i.test(ua)) info.device_platform = 'Windows';
|
|
109
|
+
else if (/Mac OS X/i.test(ua)) info.device_platform = 'macOS';
|
|
110
|
+
else if (/CrOS/i.test(ua)) info.device_platform = 'Chrome OS';
|
|
111
|
+
else if (/Linux/i.test(ua)) info.device_platform = 'Linux';
|
|
112
|
+
}
|
|
113
|
+
if (info.device_type === 'desktop') {
|
|
114
|
+
if (/iPad/i.test(ua)) info.device_type = 'tablet';
|
|
115
|
+
else if (/Mobile|iPhone|iPod/i.test(ua)) info.device_type = 'mobile';
|
|
116
|
+
else if (/Android(?!.*Mobile)/i.test(ua)) info.device_type = 'tablet';
|
|
117
|
+
else if (/Android/i.test(ua)) info.device_type = 'mobile';
|
|
118
|
+
}
|
|
119
|
+
return info;
|
|
120
|
+
}
|
|
121
|
+
|
|
85
122
|
/** Fetch with an AbortController timeout (default 30s) */
|
|
86
123
|
function _fetchWithTimeout(url, options, timeoutMs) {
|
|
87
124
|
var controller = new AbortController();
|
|
@@ -95,21 +132,12 @@
|
|
|
95
132
|
════════════════════════════════════════════════════════════════ */
|
|
96
133
|
|
|
97
134
|
var _ICONS = {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
'<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">' +
|
|
101
|
-
'<rect x="3" y="3" width="18" height="18" rx="5" ry="5" stroke="currentColor" stroke-width="1.8" fill="none"/>' +
|
|
102
|
-
'<line x1="8" y1="9" x2="16" y2="9" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>' +
|
|
103
|
-
'<line x1="8" y1="12.5" x2="14" y2="12.5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>' +
|
|
104
|
-
'<line x1="8" y1="16" x2="12" y2="16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>' +
|
|
105
|
-
'</svg>',
|
|
106
|
-
/* Send arrow */
|
|
135
|
+
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>',
|
|
107
137
|
send:
|
|
108
138
|
'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>',
|
|
109
|
-
/* Close X */
|
|
110
139
|
close:
|
|
111
|
-
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" 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>',
|
|
112
|
-
/* Error circle */
|
|
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>',
|
|
113
141
|
error:
|
|
114
142
|
'<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>'
|
|
115
143
|
};
|
|
@@ -169,12 +197,11 @@
|
|
|
169
197
|
botName: 'WAKZ',
|
|
170
198
|
welcomeMessage: '',
|
|
171
199
|
primaryColor: '#171717',
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
chatBg: '#FAFAFA',
|
|
200
|
+
chatBg: '#f8f9fa',
|
|
201
|
+
btnColor: '#171717',
|
|
175
202
|
widgetBg: '#ffffff',
|
|
176
203
|
position: 'bottom-right',
|
|
177
|
-
language: '
|
|
204
|
+
language: 'en',
|
|
178
205
|
showStatus: true,
|
|
179
206
|
online: true
|
|
180
207
|
};
|
|
@@ -195,8 +222,7 @@
|
|
|
195
222
|
self.apiKey = attrs.apiKey;
|
|
196
223
|
self.server = attrs.server;
|
|
197
224
|
self.visitorId = _getVisitorId();
|
|
198
|
-
self.
|
|
199
|
-
self._scriptIconShape = attrs.iconShape;
|
|
225
|
+
self._deviceInfo = _getDeviceInfo();
|
|
200
226
|
|
|
201
227
|
/* ── Runtime state ── */
|
|
202
228
|
self.config = Object.assign({}, _DEFAULTS);
|
|
@@ -209,22 +235,26 @@
|
|
|
209
235
|
self._configError = false;
|
|
210
236
|
self._pollTimer = null;
|
|
211
237
|
self._lastPollTime = null;
|
|
212
|
-
self._POLL_INTERVAL = 4000;
|
|
213
|
-
self._knownMessageIds = {};
|
|
238
|
+
self._POLL_INTERVAL = 4000;
|
|
239
|
+
self._knownMessageIds = {};
|
|
240
|
+
self._pendingReply = false;
|
|
214
241
|
|
|
215
242
|
/* ── DOM refs (populated after mount) ── */
|
|
216
243
|
self._host = null;
|
|
217
244
|
self._shadow = null;
|
|
218
245
|
self._root = null;
|
|
219
|
-
self.
|
|
246
|
+
self._overlay = null;
|
|
220
247
|
self._chatWindow = null;
|
|
221
248
|
self._messagesContainer = null;
|
|
249
|
+
self._inputArea = null;
|
|
222
250
|
self._inputEl = null;
|
|
223
251
|
self._sendBtn = null;
|
|
224
252
|
self._toggleBtn = null;
|
|
225
253
|
self._statusDot = null;
|
|
226
254
|
self._headerStatusDot = null;
|
|
227
255
|
self._headerStatusText = null;
|
|
256
|
+
self._headerAvatarEl = null;
|
|
257
|
+
self._headerNameEl = null;
|
|
228
258
|
|
|
229
259
|
/* ── Bootstrap ── */
|
|
230
260
|
self._injectCSS();
|
|
@@ -235,7 +265,7 @@
|
|
|
235
265
|
}
|
|
236
266
|
|
|
237
267
|
/* ════════════════════════════════════════════════════════════════
|
|
238
|
-
CSS — Complete
|
|
268
|
+
CSS — Complete styling injected into Shadow DOM (v3.0.0 revamp)
|
|
239
269
|
════════════════════════════════════════════════════════════════ */
|
|
240
270
|
|
|
241
271
|
WAKZWidget.prototype._injectCSS = function () {
|
|
@@ -246,14 +276,15 @@
|
|
|
246
276
|
/* ── CSS Custom Properties (theming) ── */
|
|
247
277
|
':host {',
|
|
248
278
|
' --wakz-primary: #171717;',
|
|
249
|
-
' --wakz-
|
|
250
|
-
' --wakz-chat-bg: #
|
|
279
|
+
' --wakz-btn: #171717;',
|
|
280
|
+
' --wakz-chat-bg: #f8f9fa;',
|
|
251
281
|
' --wakz-widget-bg: #ffffff;',
|
|
252
|
-
' --wakz-radius:
|
|
253
|
-
' --wakz-
|
|
254
|
-
' --wakz-
|
|
255
|
-
' --wakz-shadow: 0 25px 60px rgba(0,0,0
|
|
256
|
-
' --wakz-
|
|
282
|
+
' --wakz-icon-radius: 50%;',
|
|
283
|
+
' --wakz-shadow-sm: 0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.06);',
|
|
284
|
+
' --wakz-shadow-md: 0 4px 12px rgba(0,0,0,.1), 0 2px 4px rgba(0,0,0,.06);',
|
|
285
|
+
' --wakz-shadow-lg: 0 25px 60px rgba(0,0,0,.15), 0 10px 20px rgba(0,0,0,.1);',
|
|
286
|
+
' --wakz-transition: 200ms ease;',
|
|
287
|
+
' --wakz-font: \'Inter\', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;',
|
|
257
288
|
' all: initial;',
|
|
258
289
|
' font-family: var(--wakz-font);',
|
|
259
290
|
'}',
|
|
@@ -266,55 +297,49 @@
|
|
|
266
297
|
'}',
|
|
267
298
|
|
|
268
299
|
/* ══════════════════════════════════════════════════
|
|
269
|
-
FLOATING ACTION BUTTON (FAB)
|
|
300
|
+
FLOATING ACTION BUTTON (FAB) — 48px, independent color
|
|
270
301
|
══════════════════════════════════════════════════ */
|
|
271
302
|
'.wakz-fab {',
|
|
272
303
|
' position: fixed;',
|
|
273
304
|
' z-index: 2147483647;',
|
|
274
|
-
' width:
|
|
275
|
-
' height:
|
|
276
|
-
' border-radius:
|
|
305
|
+
' width: 48px;',
|
|
306
|
+
' height: 48px;',
|
|
307
|
+
' border-radius: var(--wakz-icon-radius);',
|
|
277
308
|
' border: none;',
|
|
278
309
|
' cursor: pointer;',
|
|
279
310
|
' display: flex;',
|
|
280
311
|
' align-items: center;',
|
|
281
312
|
' justify-content: center;',
|
|
282
|
-
' box-shadow: 0
|
|
283
|
-
' transition:
|
|
313
|
+
' box-shadow: 0 4px 14px rgba(0,0,0,.15), 0 2px 6px rgba(0,0,0,.1);',
|
|
314
|
+
' transition: transform var(--wakz-transition), box-shadow var(--wakz-transition);',
|
|
284
315
|
' outline: none;',
|
|
285
316
|
' -webkit-tap-highlight-color: transparent;',
|
|
286
|
-
' background: var(--wakz-
|
|
317
|
+
' background: var(--wakz-btn);',
|
|
287
318
|
'}',
|
|
288
319
|
'.wakz-fab:hover {',
|
|
289
|
-
' transform: scale(1.
|
|
290
|
-
' box-shadow: 0
|
|
320
|
+
' transform: scale(1.06);',
|
|
321
|
+
' box-shadow: 0 8px 24px rgba(0,0,0,.2), 0 4px 10px rgba(0,0,0,.12);',
|
|
291
322
|
'}',
|
|
292
323
|
'.wakz-fab:active {',
|
|
293
|
-
' transform: scale(0.
|
|
324
|
+
' transform: scale(0.94);',
|
|
294
325
|
'}',
|
|
295
326
|
'.wakz-fab svg {',
|
|
296
|
-
' width:
|
|
297
|
-
' height:
|
|
327
|
+
' width: 22px;',
|
|
328
|
+
' height: 22px;',
|
|
298
329
|
' color: #ffffff;',
|
|
299
330
|
' pointer-events: none;',
|
|
300
331
|
'}',
|
|
301
332
|
|
|
302
|
-
/* ── FAB Shape: rounded square ── */
|
|
303
|
-
'.wakz-fab-shape-rounded {',
|
|
304
|
-
' border-radius: 16px;',
|
|
305
|
-
'}',
|
|
306
|
-
|
|
307
|
-
/* ── FAB hidden when chat is open ── */
|
|
308
|
-
'.wakz-fab-hidden {',
|
|
309
|
-
' opacity: 0;',
|
|
310
|
-
' pointer-events: none;',
|
|
311
|
-
' transform: scale(0.8);',
|
|
312
|
-
'}',
|
|
313
|
-
|
|
314
333
|
/* ── FAB Positioning ── */
|
|
315
334
|
'.wakz-fab-pos-br { bottom: 24px; right: 24px; }',
|
|
316
335
|
'.wakz-fab-pos-bl { bottom: 24px; left: 24px; }',
|
|
317
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
|
+
|
|
318
343
|
/* ── FAB Entrance Bounce ── */
|
|
319
344
|
'@keyframes wakz-fab-enter {',
|
|
320
345
|
' 0% { opacity: 0; transform: scale(0.3); }',
|
|
@@ -327,14 +352,14 @@
|
|
|
327
352
|
'}',
|
|
328
353
|
|
|
329
354
|
/* ══════════════════════════════════════════════════
|
|
330
|
-
STATUS DOT (on FAB)
|
|
355
|
+
STATUS DOT (on FAB) — 10px, top-right
|
|
331
356
|
══════════════════════════════════════════════════ */
|
|
332
357
|
'.wakz-fab-dot {',
|
|
333
358
|
' position: absolute;',
|
|
334
359
|
' top: -1px;',
|
|
335
360
|
' right: -1px;',
|
|
336
|
-
' width:
|
|
337
|
-
' height:
|
|
361
|
+
' width: 10px;',
|
|
362
|
+
' height: 10px;',
|
|
338
363
|
' border-radius: 50%;',
|
|
339
364
|
' border: 2px solid #ffffff;',
|
|
340
365
|
' z-index: 2;',
|
|
@@ -351,7 +376,7 @@
|
|
|
351
376
|
'}',
|
|
352
377
|
'@keyframes wakz-dot-pulse {',
|
|
353
378
|
' 0%, 100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }',
|
|
354
|
-
' 50% { box-shadow: 0 0 0
|
|
379
|
+
' 50% { box-shadow: 0 0 0 5px rgba(34,197,94,0); }',
|
|
355
380
|
'}',
|
|
356
381
|
'.wakz-fab-dot.online {',
|
|
357
382
|
' animation: wakz-dot-pulse 2s ease-in-out infinite;',
|
|
@@ -364,21 +389,18 @@
|
|
|
364
389
|
'}',
|
|
365
390
|
|
|
366
391
|
/* ══════════════════════════════════════════════════
|
|
367
|
-
OVERLAY BACKDROP
|
|
392
|
+
OVERLAY / BACKDROP — glass blur behind modal
|
|
368
393
|
══════════════════════════════════════════════════ */
|
|
369
394
|
'.wakz-overlay {',
|
|
370
395
|
' position: fixed;',
|
|
371
|
-
'
|
|
372
|
-
' left: 0;',
|
|
373
|
-
' right: 0;',
|
|
374
|
-
' bottom: 0;',
|
|
396
|
+
' inset: 0;',
|
|
375
397
|
' z-index: 2147483645;',
|
|
376
|
-
' background: rgba(0,0,0,0.
|
|
377
|
-
' -webkit-backdrop-filter: blur(4px);',
|
|
398
|
+
' background: rgba(0,0,0,0.25);',
|
|
378
399
|
' backdrop-filter: blur(4px);',
|
|
400
|
+
' -webkit-backdrop-filter: blur(4px);',
|
|
379
401
|
' opacity: 0;',
|
|
380
402
|
' pointer-events: none;',
|
|
381
|
-
' transition: opacity 0.3s cubic-bezier(0.
|
|
403
|
+
' transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
|
|
382
404
|
'}',
|
|
383
405
|
'.wakz-overlay.wakz-visible {',
|
|
384
406
|
' opacity: 1;',
|
|
@@ -394,20 +416,18 @@
|
|
|
394
416
|
' top: 50%;',
|
|
395
417
|
' left: 50%;',
|
|
396
418
|
' transform: translate(-50%, -50%) scale(0.95);',
|
|
397
|
-
' width:
|
|
398
|
-
'
|
|
399
|
-
' border-radius:
|
|
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
|
-
'
|
|
405
|
-
' box-shadow: 0 25px 60px rgba(0,0,0,0.15), 0 8px 20px rgba(0,0,0,0.1);',
|
|
406
|
-
' -webkit-backdrop-filter: blur(20px);',
|
|
407
|
-
' backdrop-filter: blur(20px);',
|
|
426
|
+
' box-shadow: 0 25px 60px rgba(0,0,0,0.15), 0 10px 20px rgba(0,0,0,0.1);',
|
|
408
427
|
' opacity: 0;',
|
|
409
428
|
' pointer-events: none;',
|
|
410
|
-
' transition: opacity 0.3s cubic-bezier(0.
|
|
429
|
+
' transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),',
|
|
430
|
+
' transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);',
|
|
411
431
|
'}',
|
|
412
432
|
'.wakz-window.wakz-visible {',
|
|
413
433
|
' opacity: 1;',
|
|
@@ -416,11 +436,12 @@
|
|
|
416
436
|
'}',
|
|
417
437
|
|
|
418
438
|
/* ══════════════════════════════════════════════════
|
|
419
|
-
HEADER —
|
|
439
|
+
HEADER — Glass effect matching main site
|
|
420
440
|
══════════════════════════════════════════════════ */
|
|
421
441
|
'.wakz-hdr {',
|
|
422
442
|
' flex-shrink: 0;',
|
|
423
|
-
'
|
|
443
|
+
' height: 52px;',
|
|
444
|
+
' padding: 0 16px;',
|
|
424
445
|
' display: flex;',
|
|
425
446
|
' align-items: center;',
|
|
426
447
|
' justify-content: space-between;',
|
|
@@ -428,32 +449,39 @@
|
|
|
428
449
|
' cursor: default;',
|
|
429
450
|
' user-select: none;',
|
|
430
451
|
' background: rgba(255,255,255,0.85);',
|
|
431
|
-
'
|
|
432
|
-
' backdrop-filter: blur(
|
|
433
|
-
' border-bottom: 1px solid rgba(
|
|
434
|
-
'
|
|
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;',
|
|
435
457
|
'}',
|
|
436
|
-
'.wakz-hdr-
|
|
458
|
+
'.wakz-hdr-left {',
|
|
437
459
|
' display: flex;',
|
|
438
460
|
' align-items: center;',
|
|
439
|
-
' gap:
|
|
461
|
+
' gap: 10px;',
|
|
440
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;',
|
|
441
470
|
'}',
|
|
442
471
|
|
|
443
|
-
/* ── Bot Avatar (
|
|
472
|
+
/* ── Bot Avatar (32px circle) ── */
|
|
444
473
|
'.wakz-avatar {',
|
|
445
|
-
' width:
|
|
446
|
-
' height:
|
|
474
|
+
' width: 32px;',
|
|
475
|
+
' height: 32px;',
|
|
447
476
|
' border-radius: 50%;',
|
|
448
|
-
' background:
|
|
477
|
+
' background: rgba(23,23,23,0.1);',
|
|
449
478
|
' display: flex;',
|
|
450
479
|
' align-items: center;',
|
|
451
480
|
' justify-content: center;',
|
|
452
481
|
' font-weight: 700;',
|
|
453
|
-
' font-size:
|
|
482
|
+
' font-size: 14px;',
|
|
454
483
|
' flex-shrink: 0;',
|
|
455
|
-
' color:
|
|
456
|
-
' letter-spacing: 0.5px;',
|
|
484
|
+
' color: var(--wakz-primary);',
|
|
457
485
|
'}',
|
|
458
486
|
|
|
459
487
|
/* ── Header Info ── */
|
|
@@ -463,7 +491,7 @@
|
|
|
463
491
|
' min-width: 0;',
|
|
464
492
|
'}',
|
|
465
493
|
'.wakz-hdr-name {',
|
|
466
|
-
' font-size:
|
|
494
|
+
' font-size: 15px;',
|
|
467
495
|
' font-weight: 600;',
|
|
468
496
|
' line-height: 1.3;',
|
|
469
497
|
' color: #171717;',
|
|
@@ -471,23 +499,19 @@
|
|
|
471
499
|
' overflow: hidden;',
|
|
472
500
|
' text-overflow: ellipsis;',
|
|
473
501
|
'}',
|
|
474
|
-
|
|
475
|
-
/* ── Header Left: status + close ── */
|
|
476
|
-
'.wakz-hdr-left {',
|
|
477
|
-
' display: flex;',
|
|
478
|
-
' align-items: center;',
|
|
479
|
-
' gap: 10px;',
|
|
480
|
-
'}',
|
|
481
502
|
'.wakz-hdr-status {',
|
|
482
|
-
' font-size:
|
|
483
|
-
' color: #
|
|
503
|
+
' font-size: 11px;',
|
|
504
|
+
' color: #A3A3A3;',
|
|
484
505
|
' display: flex;',
|
|
485
506
|
' align-items: center;',
|
|
486
|
-
' gap:
|
|
507
|
+
' gap: 4px;',
|
|
508
|
+
' margin-top: 1px;',
|
|
487
509
|
'}',
|
|
510
|
+
|
|
511
|
+
/* ── Header Status Dot (6px) ── */
|
|
488
512
|
'.wakz-hdr-status-dot {',
|
|
489
|
-
' width:
|
|
490
|
-
' height:
|
|
513
|
+
' width: 6px;',
|
|
514
|
+
' height: 6px;',
|
|
491
515
|
' border-radius: 50%;',
|
|
492
516
|
' display: inline-block;',
|
|
493
517
|
' flex-shrink: 0;',
|
|
@@ -496,161 +520,165 @@
|
|
|
496
520
|
'.wakz-hdr-status-dot.online { background: #22c55e; }',
|
|
497
521
|
'.wakz-hdr-status-dot.offline { background: #ef4444; }',
|
|
498
522
|
|
|
499
|
-
/* ── Close Button ── */
|
|
523
|
+
/* ── Close Button (28px circle, dark) ── */
|
|
500
524
|
'.wakz-close {',
|
|
501
|
-
' width:
|
|
502
|
-
' height:
|
|
525
|
+
' width: 28px;',
|
|
526
|
+
' height: 28px;',
|
|
503
527
|
' border-radius: 50%;',
|
|
504
528
|
' border: none;',
|
|
505
|
-
' background:
|
|
506
|
-
' color: #
|
|
529
|
+
' background: rgba(23,23,23,0.05);',
|
|
530
|
+
' color: #525252;',
|
|
507
531
|
' cursor: pointer;',
|
|
508
532
|
' display: flex;',
|
|
509
533
|
' align-items: center;',
|
|
510
534
|
' justify-content: center;',
|
|
511
|
-
' transition: background
|
|
535
|
+
' transition: background var(--wakz-transition), color var(--wakz-transition);',
|
|
512
536
|
' outline: none;',
|
|
513
537
|
' flex-shrink: 0;',
|
|
514
538
|
'}',
|
|
515
539
|
'.wakz-close:hover {',
|
|
516
|
-
' background: rgba(
|
|
540
|
+
' background: rgba(23,23,23,0.1);',
|
|
541
|
+
' color: #171717;',
|
|
517
542
|
'}',
|
|
518
543
|
'.wakz-close svg {',
|
|
519
|
-
' width:
|
|
520
|
-
' height:
|
|
544
|
+
' width: 16px;',
|
|
545
|
+
' height: 16px;',
|
|
521
546
|
'}',
|
|
522
547
|
|
|
523
548
|
/* ══════════════════════════════════════════════════
|
|
524
|
-
MESSAGES AREA
|
|
549
|
+
MESSAGES AREA — scrollable, padding-bottom for input
|
|
525
550
|
══════════════════════════════════════════════════ */
|
|
526
551
|
'.wakz-msgs {',
|
|
527
552
|
' flex: 1;',
|
|
528
553
|
' overflow-y: auto;',
|
|
529
554
|
' overflow-x: hidden;',
|
|
530
|
-
' padding: 16px;',
|
|
555
|
+
' padding: 16px 14px 88px;',
|
|
531
556
|
' display: flex;',
|
|
532
557
|
' flex-direction: column;',
|
|
533
|
-
' gap:
|
|
558
|
+
' gap: 4px;',
|
|
534
559
|
' scroll-behavior: smooth;',
|
|
535
560
|
' background: var(--wakz-chat-bg);',
|
|
561
|
+
' position: relative;',
|
|
536
562
|
'}',
|
|
537
|
-
'.wakz-msgs::-webkit-scrollbar { width:
|
|
563
|
+
'.wakz-msgs::-webkit-scrollbar { width: 4px; }',
|
|
538
564
|
'.wakz-msgs::-webkit-scrollbar-track { background: transparent; }',
|
|
539
565
|
'.wakz-msgs::-webkit-scrollbar-thumb {',
|
|
540
|
-
' background: rgba(0,0,0,0.
|
|
566
|
+
' background: rgba(0,0,0,0.1);',
|
|
541
567
|
' border-radius: 10px;',
|
|
542
568
|
'}',
|
|
543
569
|
|
|
544
570
|
/* ══════════════════════════════════════════════════
|
|
545
|
-
MESSAGE
|
|
571
|
+
MESSAGE ROWS — new v3 styles
|
|
546
572
|
══════════════════════════════════════════════════ */
|
|
547
|
-
'@keyframes wakz-msg-in {',
|
|
548
|
-
' 0% { opacity: 0; transform: translateY(8px); }',
|
|
549
|
-
' 100% { opacity: 1; transform: translateY(0); }',
|
|
550
|
-
'}',
|
|
551
|
-
|
|
552
|
-
/* ── User Message (bubble, right-aligned LTR / left-aligned RTL) ── */
|
|
553
573
|
'.wakz-msg-row {',
|
|
554
|
-
'
|
|
574
|
+
' display: flex;',
|
|
575
|
+
' animation: wakz-msg-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
|
|
576
|
+
'}',
|
|
577
|
+
'.wakz-msg-row.bot {',
|
|
578
|
+
' align-self: flex-start;',
|
|
579
|
+
' align-items: flex-start;',
|
|
580
|
+
' gap: 8px;',
|
|
581
|
+
' max-width: 80%;',
|
|
555
582
|
'}',
|
|
556
583
|
'.wakz-msg-row.user {',
|
|
557
584
|
' align-self: flex-end;',
|
|
558
585
|
' max-width: 75%;',
|
|
559
586
|
'}',
|
|
560
|
-
|
|
561
|
-
'
|
|
562
|
-
'
|
|
587
|
+
|
|
588
|
+
'@keyframes wakz-msg-in {',
|
|
589
|
+
' 0% { opacity: 0; transform: translateY(8px); }',
|
|
590
|
+
' 100% { opacity: 1; transform: translateY(0); }',
|
|
563
591
|
'}',
|
|
564
592
|
|
|
565
|
-
|
|
566
|
-
'
|
|
593
|
+
/* ── User Bubbles ── */
|
|
594
|
+
'.wakz-bubble.user {',
|
|
595
|
+
' padding: 10px 14px;',
|
|
567
596
|
' font-size: 14px;',
|
|
568
597
|
' line-height: 1.55;',
|
|
569
598
|
' word-wrap: break-word;',
|
|
570
599
|
' overflow-wrap: break-word;',
|
|
571
600
|
' white-space: pre-wrap;',
|
|
572
|
-
' border-radius:
|
|
601
|
+
' border-radius: 14px;',
|
|
602
|
+
' border-bottom-right-radius: 4px;',
|
|
573
603
|
' background: var(--wakz-primary);',
|
|
574
604
|
' color: #ffffff;',
|
|
605
|
+
' box-shadow: 0 2px 8px rgba(0,0,0,0.08);',
|
|
575
606
|
'}',
|
|
576
607
|
|
|
577
|
-
/* ──
|
|
578
|
-
'.wakz-
|
|
579
|
-
' display: block;',
|
|
580
|
-
' font-size: 10px;',
|
|
581
|
-
' opacity: 0.6;',
|
|
582
|
-
' margin-top: 4px;',
|
|
583
|
-
'}',
|
|
584
|
-
'.wakz-bubble-user .wakz-ts {',
|
|
585
|
-
' text-align: right;',
|
|
586
|
-
' color: rgba(255,255,255,0.7);',
|
|
587
|
-
'}',
|
|
588
|
-
|
|
589
|
-
/* ── Bot Message (no bubble, flat on bg with left accent border) ── */
|
|
590
|
-
'.wakz-msg-bot-wrap {',
|
|
608
|
+
/* ── Bot Messages — NO bubble, text directly on background ── */
|
|
609
|
+
'.wakz-bot-content {',
|
|
591
610
|
' display: flex;',
|
|
592
611
|
' flex-direction: column;',
|
|
593
612
|
' gap: 2px;',
|
|
613
|
+
' min-width: 0;',
|
|
594
614
|
'}',
|
|
595
|
-
'.wakz-
|
|
596
|
-
' width: 20px;',
|
|
597
|
-
' height: 20px;',
|
|
598
|
-
' border-radius: 50%;',
|
|
599
|
-
' background: var(--wakz-primary);',
|
|
600
|
-
' display: flex;',
|
|
601
|
-
' align-items: center;',
|
|
602
|
-
' justify-content: center;',
|
|
603
|
-
' color: #ffffff;',
|
|
604
|
-
' font-weight: 700;',
|
|
605
|
-
' font-size: 9px;',
|
|
606
|
-
' flex-shrink: 0;',
|
|
607
|
-
' margin-bottom: 2px;',
|
|
608
|
-
'}',
|
|
609
|
-
'.wakz-msg-bot-content {',
|
|
610
|
-
' border-left: 3px solid rgba(23,23,23,0.2);',
|
|
611
|
-
' padding: 10px 0 10px 14px;',
|
|
615
|
+
'.wakz-bot-text {',
|
|
612
616
|
' font-size: 14px;',
|
|
613
617
|
' line-height: 1.6;',
|
|
614
|
-
' color: #
|
|
618
|
+
' color: #404040;',
|
|
615
619
|
' word-wrap: break-word;',
|
|
616
620
|
' overflow-wrap: break-word;',
|
|
617
621
|
' white-space: pre-wrap;',
|
|
618
622
|
'}',
|
|
619
|
-
'.wakz-
|
|
620
|
-
'
|
|
623
|
+
'.wakz-bot-ts {',
|
|
624
|
+
' font-size: 10px;',
|
|
625
|
+
' color: #A3A3A3;',
|
|
621
626
|
'}',
|
|
622
627
|
|
|
623
|
-
/* ──
|
|
624
|
-
'.wakz-
|
|
625
|
-
'
|
|
626
|
-
'
|
|
627
|
-
'
|
|
628
|
-
'
|
|
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;',
|
|
629
635
|
' align-items: center;',
|
|
630
|
-
'
|
|
631
|
-
'
|
|
632
|
-
'
|
|
633
|
-
'
|
|
634
|
-
'
|
|
635
|
-
' margin-top:
|
|
636
|
-
'
|
|
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;',
|
|
642
|
+
'}',
|
|
643
|
+
|
|
644
|
+
/* ── User Timestamp ── */
|
|
645
|
+
'.wakz-ts {',
|
|
646
|
+
' display: block;',
|
|
647
|
+
' font-size: 10px;',
|
|
648
|
+
' opacity: 0.7;',
|
|
649
|
+
' margin-top: 3px;',
|
|
650
|
+
' text-align: right;',
|
|
637
651
|
'}',
|
|
638
652
|
|
|
639
|
-
/* ── Error
|
|
640
|
-
'.wakz-bubble-
|
|
653
|
+
/* ── Error Messages ── */
|
|
654
|
+
'.wakz-bubble.error-bubble {',
|
|
641
655
|
' padding: 10px 14px;',
|
|
642
656
|
' font-size: 14px;',
|
|
643
657
|
' line-height: 1.55;',
|
|
644
658
|
' word-wrap: break-word;',
|
|
645
659
|
' overflow-wrap: break-word;',
|
|
646
660
|
' white-space: pre-wrap;',
|
|
661
|
+
' border-radius: 14px;',
|
|
662
|
+
' border-bottom-left-radius: 4px;',
|
|
647
663
|
' background: #FEF2F2;',
|
|
648
664
|
' color: #DC2626;',
|
|
649
665
|
' border: 1px solid #FECACA;',
|
|
650
|
-
' border-radius: 18px 18px 6px 18px;',
|
|
651
666
|
'}',
|
|
652
667
|
|
|
653
|
-
/* ──
|
|
668
|
+
/* ── Human Agent Badge ── */
|
|
669
|
+
'.wakz-human-badge {',
|
|
670
|
+
' display: inline-block;',
|
|
671
|
+
' font-size: 10px;',
|
|
672
|
+
' background: rgba(22, 163, 74, 0.08);',
|
|
673
|
+
' color: #16A34A;',
|
|
674
|
+
' border-radius: 6px;',
|
|
675
|
+
' padding: 2px 8px;',
|
|
676
|
+
' margin-top: 4px;',
|
|
677
|
+
' font-weight: 600;',
|
|
678
|
+
' letter-spacing: 0.2px;',
|
|
679
|
+
'}',
|
|
680
|
+
|
|
681
|
+
/* ── Retry Button (inside error bubble) ── */
|
|
654
682
|
'.wakz-retry {',
|
|
655
683
|
' display: inline-flex;',
|
|
656
684
|
' align-items: center;',
|
|
@@ -665,7 +693,7 @@
|
|
|
665
693
|
' color: #dc2626;',
|
|
666
694
|
' cursor: pointer;',
|
|
667
695
|
' font-family: var(--wakz-font);',
|
|
668
|
-
' transition: background
|
|
696
|
+
' transition: background var(--wakz-transition), border-color var(--wakz-transition);',
|
|
669
697
|
' outline: none;',
|
|
670
698
|
'}',
|
|
671
699
|
'.wakz-retry:hover {',
|
|
@@ -674,32 +702,26 @@
|
|
|
674
702
|
'}',
|
|
675
703
|
|
|
676
704
|
/* ══════════════════════════════════════════════════
|
|
677
|
-
|
|
705
|
+
TYPING INDICATOR — no bubble, just dots on background
|
|
678
706
|
══════════════════════════════════════════════════ */
|
|
679
|
-
'.wakz-
|
|
707
|
+
'.wakz-typing-row {',
|
|
680
708
|
' display: flex;',
|
|
681
|
-
'
|
|
682
|
-
' gap:
|
|
683
|
-
' max-width: 85%;',
|
|
709
|
+
' align-items: flex-start;',
|
|
710
|
+
' gap: 8px;',
|
|
684
711
|
' align-self: flex-start;',
|
|
685
|
-
' animation: wakz-msg-in 0.
|
|
712
|
+
' animation: wakz-msg-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
|
|
686
713
|
'}',
|
|
687
|
-
|
|
688
|
-
/* ══════════════════════════════════════════════════
|
|
689
|
-
TYPING INDICATOR (no bubble — on background)
|
|
690
|
-
══════════════════════════════════════════════════ */
|
|
691
714
|
'.wakz-typing {',
|
|
692
715
|
' display: flex;',
|
|
693
716
|
' align-items: center;',
|
|
694
717
|
' gap: 4px;',
|
|
695
|
-
' padding:
|
|
696
|
-
' border-left: 3px solid rgba(23,23,23,0.2);',
|
|
718
|
+
' padding: 6px 0;',
|
|
697
719
|
'}',
|
|
698
720
|
'.wakz-typing-dot {',
|
|
699
|
-
' width:
|
|
700
|
-
' height:
|
|
721
|
+
' width: 6px;',
|
|
722
|
+
' height: 6px;',
|
|
701
723
|
' border-radius: 50%;',
|
|
702
|
-
' background: #
|
|
724
|
+
' background: #A3A3A3;',
|
|
703
725
|
' animation: wakz-bounce-dot 1.4s ease-in-out infinite;',
|
|
704
726
|
'}',
|
|
705
727
|
'.wakz-typing-dot:nth-child(2) { animation-delay: 0.16s; }',
|
|
@@ -710,47 +732,53 @@
|
|
|
710
732
|
'}',
|
|
711
733
|
|
|
712
734
|
/* ══════════════════════════════════════════════════
|
|
713
|
-
INPUT AREA —
|
|
735
|
+
INPUT AREA — Floating, glass effect, inside messages
|
|
714
736
|
══════════════════════════════════════════════════ */
|
|
715
|
-
'.wakz-input-
|
|
716
|
-
'
|
|
717
|
-
'
|
|
718
|
-
'
|
|
719
|
-
'
|
|
720
|
-
' -webkit-backdrop-filter: blur(12px);',
|
|
721
|
-
' backdrop-filter: blur(12px);',
|
|
737
|
+
'.wakz-input-area {',
|
|
738
|
+
' position: absolute;',
|
|
739
|
+
' bottom: 12px;',
|
|
740
|
+
' left: 12px;',
|
|
741
|
+
' right: 12px;',
|
|
722
742
|
' display: flex;',
|
|
723
743
|
' align-items: flex-end;',
|
|
724
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;',
|
|
725
753
|
'}',
|
|
726
754
|
'.wakz-input {',
|
|
727
755
|
' flex: 1;',
|
|
728
|
-
' border:
|
|
729
|
-
' border-radius:
|
|
730
|
-
' padding:
|
|
756
|
+
' border: none;',
|
|
757
|
+
' border-radius: 0;',
|
|
758
|
+
' padding: 6px 4px;',
|
|
731
759
|
' font-size: 14px;',
|
|
732
760
|
' line-height: 1.4;',
|
|
733
761
|
' outline: none;',
|
|
734
762
|
' font-family: var(--wakz-font);',
|
|
735
|
-
' transition: border-color 0.2s ease, background 0.2s ease;',
|
|
736
763
|
' resize: none;',
|
|
737
|
-
' max-height:
|
|
764
|
+
' max-height: 80px;',
|
|
765
|
+
' min-height: 22px;',
|
|
738
766
|
' overflow-y: auto;',
|
|
739
|
-
' background:
|
|
740
|
-
' color: #
|
|
767
|
+
' background: transparent;',
|
|
768
|
+
' color: #171717;',
|
|
741
769
|
'}',
|
|
742
770
|
'.wakz-input:focus {',
|
|
743
|
-
'
|
|
744
|
-
' background: #ffffff;',
|
|
771
|
+
' outline: none;',
|
|
745
772
|
'}',
|
|
746
773
|
'.wakz-input::placeholder {',
|
|
747
|
-
' color: #
|
|
774
|
+
' color: #A3A3A3;',
|
|
748
775
|
'}',
|
|
749
776
|
|
|
750
|
-
/* ── Send Button (floating circle, separate from
|
|
777
|
+
/* ── Send Button (36px floating circle, separate from input) ── */
|
|
751
778
|
'.wakz-send {',
|
|
752
|
-
' width:
|
|
753
|
-
' height:
|
|
779
|
+
' width: 36px;',
|
|
780
|
+
' height: 36px;',
|
|
781
|
+
' min-width: 36px;',
|
|
754
782
|
' border-radius: 50%;',
|
|
755
783
|
' border: none;',
|
|
756
784
|
' color: #ffffff;',
|
|
@@ -759,87 +787,88 @@
|
|
|
759
787
|
' align-items: center;',
|
|
760
788
|
' justify-content: center;',
|
|
761
789
|
' flex-shrink: 0;',
|
|
762
|
-
' transition:
|
|
790
|
+
' transition: transform var(--wakz-transition), opacity var(--wakz-transition), box-shadow var(--wakz-transition);',
|
|
763
791
|
' outline: none;',
|
|
764
792
|
' background: var(--wakz-primary);',
|
|
765
793
|
' box-shadow: 0 2px 8px rgba(0,0,0,0.12);',
|
|
766
794
|
'}',
|
|
767
795
|
'.wakz-send:hover:not(:disabled) {',
|
|
768
796
|
' transform: scale(1.05);',
|
|
769
|
-
' box-shadow: 0 4px
|
|
797
|
+
' box-shadow: 0 4px 14px rgba(0,0,0,0.18);',
|
|
770
798
|
'}',
|
|
771
799
|
'.wakz-send:disabled {',
|
|
772
|
-
' opacity: 0.
|
|
800
|
+
' opacity: 0.3;',
|
|
773
801
|
' cursor: not-allowed;',
|
|
774
802
|
'}',
|
|
775
803
|
'.wakz-send:not(:disabled):active {',
|
|
776
804
|
' transform: scale(0.92);',
|
|
777
805
|
'}',
|
|
778
806
|
'.wakz-send svg {',
|
|
779
|
-
' width:
|
|
780
|
-
' height:
|
|
807
|
+
' width: 16px;',
|
|
808
|
+
' height: 16px;',
|
|
781
809
|
' pointer-events: none;',
|
|
782
810
|
'}',
|
|
783
811
|
|
|
812
|
+
/* ══════════════════════════════════════════════════
|
|
813
|
+
WELCOME MESSAGE — same as bot (no bubble)
|
|
814
|
+
══════════════════════════════════════════════════ */
|
|
815
|
+
'.wakz-welcome-wrap {',
|
|
816
|
+
' display: flex;',
|
|
817
|
+
' align-items: flex-start;',
|
|
818
|
+
' gap: 8px;',
|
|
819
|
+
' max-width: 80%;',
|
|
820
|
+
' align-self: flex-start;',
|
|
821
|
+
' animation: wakz-msg-in 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;',
|
|
822
|
+
'}',
|
|
823
|
+
|
|
784
824
|
/* ══════════════════════════════════════════════════
|
|
785
825
|
RTL SUPPORT
|
|
786
826
|
══════════════════════════════════════════════════ */
|
|
787
827
|
'.wakz-rtl { direction: rtl; }',
|
|
788
|
-
'.wakz-rtl .wakz-bubble
|
|
789
|
-
' border-radius:
|
|
790
|
-
'
|
|
791
|
-
'.wakz-rtl .wakz-bubble-user .wakz-ts {',
|
|
792
|
-
' text-align: left;',
|
|
828
|
+
'.wakz-rtl .wakz-bubble.user {',
|
|
829
|
+
' border-bottom-right-radius: 14px;',
|
|
830
|
+
' border-bottom-left-radius: 4px;',
|
|
793
831
|
'}',
|
|
794
|
-
'.wakz-rtl .wakz-
|
|
795
|
-
' border-left:
|
|
796
|
-
' border-right:
|
|
797
|
-
' padding: 10px 14px 10px 0;',
|
|
832
|
+
'.wakz-rtl .wakz-bubble.error-bubble {',
|
|
833
|
+
' border-bottom-left-radius: 14px;',
|
|
834
|
+
' border-bottom-right-radius: 4px;',
|
|
798
835
|
'}',
|
|
799
|
-
'.wakz-rtl .wakz-
|
|
800
|
-
'
|
|
801
|
-
' border-right-color: #16A34A;',
|
|
802
|
-
'}',
|
|
803
|
-
'.wakz-rtl .wakz-typing {',
|
|
804
|
-
' border-left: none;',
|
|
805
|
-
' border-right: 3px solid rgba(23,23,23,0.2);',
|
|
806
|
-
' padding: 10px 14px 10px 0;',
|
|
807
|
-
'}',
|
|
808
|
-
'.wakz-rtl .wakz-bubble-error {',
|
|
809
|
-
' border-radius: 18px 18px 18px 6px;',
|
|
836
|
+
'.wakz-rtl .wakz-ts {',
|
|
837
|
+
' text-align: left;',
|
|
810
838
|
'}',
|
|
811
839
|
|
|
812
840
|
/* ══════════════════════════════════════════════════
|
|
813
|
-
MOBILE RESPONSIVE
|
|
841
|
+
MOBILE RESPONSIVE — centered modal, NOT fullscreen
|
|
814
842
|
══════════════════════════════════════════════════ */
|
|
815
843
|
'@media (max-width: 480px) {',
|
|
816
|
-
' .wakz-overlay {',
|
|
817
|
-
' display: none;',
|
|
818
|
-
' }',
|
|
819
844
|
' .wakz-window {',
|
|
820
|
-
'
|
|
821
|
-
'
|
|
822
|
-
' right: 0 !important;',
|
|
823
|
-
' bottom: 0 !important;',
|
|
824
|
-
' width: 100vw !important;',
|
|
825
|
-
' height: 100vh !important;',
|
|
826
|
-
' max-height: 100vh !important;',
|
|
827
|
-
' border-radius: 20px 20px 0 0 !important;',
|
|
828
|
-
' transform: translateY(100%) !important;',
|
|
829
|
-
' border: none !important;',
|
|
830
|
-
' border-top: 1px solid #E5E5E5 !important;',
|
|
845
|
+
' width: calc(100vw - 32px) !important;',
|
|
846
|
+
' height: min(500px, 75vh) !important;',
|
|
831
847
|
' }',
|
|
832
848
|
' .wakz-window.wakz-visible {',
|
|
833
|
-
' transform:
|
|
849
|
+
' transform: translate(-50%, -50%) scale(1);',
|
|
850
|
+
' }',
|
|
851
|
+
' .wakz-fab {',
|
|
852
|
+
' width: 44px;',
|
|
853
|
+
' height: 44px;',
|
|
834
854
|
' }',
|
|
835
855
|
' .wakz-fab-pos-br { bottom: 16px; right: 16px; }',
|
|
836
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
|
+
' }',
|
|
837
866
|
'}'
|
|
838
867
|
].join('\n');
|
|
839
868
|
};
|
|
840
869
|
|
|
841
870
|
/* ════════════════════════════════════════════════════════════════
|
|
842
|
-
DOM CREATION
|
|
871
|
+
DOM CREATION — v3.0.0 new structure
|
|
843
872
|
════════════════════════════════════════════════════════════════ */
|
|
844
873
|
|
|
845
874
|
WAKZWidget.prototype._createDOM = function () {
|
|
@@ -847,8 +876,6 @@
|
|
|
847
876
|
var isRtl = self.config.language === 'ar';
|
|
848
877
|
var posClass = self.config.position === 'bottom-left' ? 'bl' : 'br';
|
|
849
878
|
var str = _strings(self.config.language);
|
|
850
|
-
var iconColor = self._scriptIconColor || self.config.iconColor;
|
|
851
|
-
var iconShape = self._scriptIconShape || self.config.iconShape;
|
|
852
879
|
|
|
853
880
|
/* ── Host + Shadow DOM ── */
|
|
854
881
|
self._host = _el('div');
|
|
@@ -860,67 +887,63 @@
|
|
|
860
887
|
self._shadow.appendChild(self._styleEl);
|
|
861
888
|
self._shadow.appendChild(self._root);
|
|
862
889
|
|
|
863
|
-
/* ══════════════ FLOATING ACTION BUTTON ══════════════ */
|
|
864
|
-
var fabClasses = 'wakz-fab wakz-fab-pos-' + posClass;
|
|
865
|
-
if (iconShape === 'rounded') fabClasses += ' wakz-fab-shape-rounded';
|
|
866
|
-
|
|
890
|
+
/* ══════════════ FLOATING ACTION BUTTON (FAB) ══════════════ */
|
|
867
891
|
self._toggleBtn = _el('button', {
|
|
868
|
-
className:
|
|
869
|
-
'aria-label': str.openChat
|
|
870
|
-
style: { '--wakz-icon-color': iconColor }
|
|
892
|
+
className: 'wakz-fab wakz-fab-pos-' + posClass,
|
|
893
|
+
'aria-label': str.openChat
|
|
871
894
|
});
|
|
872
|
-
self._toggleBtn.
|
|
873
|
-
self._toggleBtn.innerHTML = _ICONS.chatIcon;
|
|
895
|
+
self._toggleBtn.innerHTML = _ICONS.chatBubble;
|
|
874
896
|
|
|
875
|
-
/* Status dot on FAB */
|
|
897
|
+
/* Status dot on FAB (10px) */
|
|
876
898
|
self._statusDot = _el('span', { className: 'wakz-fab-dot ' + (self.config.online ? 'online' : 'offline') });
|
|
877
899
|
self._statusDot.style.display = self.config.showStatus ? '' : 'none';
|
|
878
900
|
self._toggleBtn.appendChild(self._statusDot);
|
|
879
901
|
self._root.appendChild(self._toggleBtn);
|
|
880
902
|
|
|
881
|
-
/* ══════════════ OVERLAY BACKDROP ══════════════ */
|
|
882
|
-
self.
|
|
883
|
-
self._root.appendChild(self.
|
|
903
|
+
/* ══════════════ OVERLAY / BACKDROP ══════════════ */
|
|
904
|
+
self._overlay = _el('div', { className: 'wakz-overlay' });
|
|
905
|
+
self._root.appendChild(self._overlay);
|
|
884
906
|
|
|
885
907
|
/* ══════════════ CHAT WINDOW (Centered Modal) ══════════════ */
|
|
886
908
|
self._chatWindow = _el('div', { className: 'wakz-window' });
|
|
887
909
|
|
|
888
|
-
/* ── Header ── */
|
|
910
|
+
/* ── Header (glass effect, dark text) ── */
|
|
889
911
|
var headerEl = _el('div', { className: 'wakz-hdr' });
|
|
890
912
|
|
|
891
|
-
/*
|
|
892
|
-
var
|
|
913
|
+
/* Left side: avatar + name + status */
|
|
914
|
+
var headerLeft = _el('div', { className: 'wakz-hdr-left' });
|
|
893
915
|
var avatarLetter = (self.config.botName || 'W')[0].toUpperCase();
|
|
894
|
-
|
|
916
|
+
self._headerAvatarEl = _el('div', { className: 'wakz-avatar' }, [avatarLetter]);
|
|
917
|
+
headerLeft.appendChild(self._headerAvatarEl);
|
|
895
918
|
|
|
896
919
|
var headerInfo = _el('div', { className: 'wakz-hdr-info' });
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
headerEl.appendChild(headerRight);
|
|
900
|
-
|
|
901
|
-
/* Left side: status + close (in RTL, left side = end) */
|
|
902
|
-
var headerLeft = _el('div', { className: 'wakz-hdr-left' });
|
|
920
|
+
self._headerNameEl = _el('span', { className: 'wakz-hdr-name' }, [self.config.botName]);
|
|
921
|
+
headerInfo.appendChild(self._headerNameEl);
|
|
903
922
|
|
|
904
923
|
var statusLine = _el('span', { className: 'wakz-hdr-status' });
|
|
905
924
|
self._headerStatusDot = _el('span', { className: 'wakz-hdr-status-dot ' + (self.config.online ? 'online' : 'offline') });
|
|
906
925
|
if (self.config.showStatus) statusLine.appendChild(self._headerStatusDot);
|
|
907
926
|
self._headerStatusText = document.createTextNode(self.config.online ? str.online : str.offline);
|
|
908
927
|
statusLine.appendChild(self._headerStatusText);
|
|
909
|
-
|
|
928
|
+
headerInfo.appendChild(statusLine);
|
|
910
929
|
|
|
930
|
+
headerLeft.appendChild(headerInfo);
|
|
931
|
+
headerEl.appendChild(headerLeft);
|
|
932
|
+
|
|
933
|
+
/* Right side: status dot + text + close button */
|
|
934
|
+
var headerRight = _el('div', { className: 'wakz-hdr-right' });
|
|
911
935
|
var closeBtn = _el('button', { className: 'wakz-close', 'aria-label': str.closeChat });
|
|
912
936
|
closeBtn.innerHTML = _ICONS.close;
|
|
913
|
-
|
|
914
|
-
headerEl.appendChild(
|
|
937
|
+
headerRight.appendChild(closeBtn);
|
|
938
|
+
headerEl.appendChild(headerRight);
|
|
915
939
|
|
|
916
940
|
self._chatWindow.appendChild(headerEl);
|
|
917
941
|
|
|
918
|
-
/* ── Messages Container ── */
|
|
942
|
+
/* ── Messages Container (with floating input area inside) ── */
|
|
919
943
|
self._messagesContainer = _el('div', { className: 'wakz-msgs' });
|
|
920
|
-
self._chatWindow.appendChild(self._messagesContainer);
|
|
921
944
|
|
|
922
|
-
/* ── Input Area ── */
|
|
923
|
-
|
|
945
|
+
/* ── Floating Input Area (inside messages container) ── */
|
|
946
|
+
self._inputArea = _el('div', { className: 'wakz-input-area' });
|
|
924
947
|
|
|
925
948
|
self._inputEl = _el('textarea', {
|
|
926
949
|
className: 'wakz-input',
|
|
@@ -928,15 +951,17 @@
|
|
|
928
951
|
rows: 1,
|
|
929
952
|
dir: isRtl ? 'rtl' : 'ltr'
|
|
930
953
|
});
|
|
931
|
-
|
|
954
|
+
self._inputArea.appendChild(self._inputEl);
|
|
932
955
|
|
|
933
956
|
self._sendBtn = _el('button', {
|
|
934
957
|
className: 'wakz-send',
|
|
935
958
|
'aria-label': str.sendMessage
|
|
936
959
|
});
|
|
937
960
|
self._sendBtn.innerHTML = _ICONS.send;
|
|
938
|
-
|
|
939
|
-
|
|
961
|
+
self._inputArea.appendChild(self._sendBtn);
|
|
962
|
+
|
|
963
|
+
self._messagesContainer.appendChild(self._inputArea);
|
|
964
|
+
self._chatWindow.appendChild(self._messagesContainer);
|
|
940
965
|
|
|
941
966
|
self._root.appendChild(self._chatWindow);
|
|
942
967
|
};
|
|
@@ -953,13 +978,13 @@
|
|
|
953
978
|
self.toggleChat(!self.isOpen);
|
|
954
979
|
});
|
|
955
980
|
|
|
956
|
-
/*
|
|
957
|
-
self.
|
|
981
|
+
/* Overlay click to close */
|
|
982
|
+
self._overlay.addEventListener('click', function () {
|
|
958
983
|
self.toggleChat(false);
|
|
959
984
|
});
|
|
960
985
|
|
|
961
|
-
/*
|
|
962
|
-
self.
|
|
986
|
+
/* Close button click */
|
|
987
|
+
self._chatWindow.querySelector('.wakz-close').addEventListener('click', function () {
|
|
963
988
|
self.toggleChat(false);
|
|
964
989
|
});
|
|
965
990
|
|
|
@@ -976,10 +1001,10 @@
|
|
|
976
1001
|
}
|
|
977
1002
|
});
|
|
978
1003
|
|
|
979
|
-
/* Auto-resize textarea */
|
|
1004
|
+
/* Auto-resize textarea (max 80px) */
|
|
980
1005
|
self._inputEl.addEventListener('input', function () {
|
|
981
1006
|
self._inputEl.style.height = 'auto';
|
|
982
|
-
self._inputEl.style.height = Math.min(self._inputEl.scrollHeight,
|
|
1007
|
+
self._inputEl.style.height = Math.min(self._inputEl.scrollHeight, 80) + 'px';
|
|
983
1008
|
});
|
|
984
1009
|
|
|
985
1010
|
/* Escape to close */
|
|
@@ -994,7 +1019,6 @@
|
|
|
994
1019
|
|
|
995
1020
|
WAKZWidget.prototype._playFabEntrance = function () {
|
|
996
1021
|
var self = this;
|
|
997
|
-
/* Start invisible, then animate after a brief delay */
|
|
998
1022
|
self._toggleBtn.style.opacity = '0';
|
|
999
1023
|
setTimeout(function () {
|
|
1000
1024
|
self._toggleBtn.style.opacity = '';
|
|
@@ -1011,8 +1035,7 @@
|
|
|
1011
1035
|
self.isOpen = open;
|
|
1012
1036
|
if (open) {
|
|
1013
1037
|
self._chatWindow.classList.add('wakz-visible');
|
|
1014
|
-
self.
|
|
1015
|
-
/* Hide FAB when chat is open */
|
|
1038
|
+
self._overlay.classList.add('wakz-visible');
|
|
1016
1039
|
self._toggleBtn.classList.add('wakz-fab-hidden');
|
|
1017
1040
|
/* Fetch history on first open (after config is loaded) */
|
|
1018
1041
|
if (self._configLoaded && !self._hasFetchedHistory) {
|
|
@@ -1027,8 +1050,7 @@
|
|
|
1027
1050
|
setTimeout(function () { self._inputEl.focus(); }, 320);
|
|
1028
1051
|
} else {
|
|
1029
1052
|
self._chatWindow.classList.remove('wakz-visible');
|
|
1030
|
-
self.
|
|
1031
|
-
/* Show FAB when chat is closed */
|
|
1053
|
+
self._overlay.classList.remove('wakz-visible');
|
|
1032
1054
|
self._toggleBtn.classList.remove('wakz-fab-hidden');
|
|
1033
1055
|
/* Stop polling when chat is closed */
|
|
1034
1056
|
self._stopPolling();
|
|
@@ -1056,9 +1078,6 @@
|
|
|
1056
1078
|
.then(function (data) {
|
|
1057
1079
|
if (data && data.success && data.config) {
|
|
1058
1080
|
self.config = Object.assign({}, _DEFAULTS, data.config);
|
|
1059
|
-
/* Apply script-level overrides */
|
|
1060
|
-
if (self._scriptIconColor) self.config.iconColor = self._scriptIconColor;
|
|
1061
|
-
if (self._scriptIconShape) self.config.iconShape = self._scriptIconShape;
|
|
1062
1081
|
self._configLoaded = true;
|
|
1063
1082
|
self._applyConfig();
|
|
1064
1083
|
} else {
|
|
@@ -1066,7 +1085,6 @@
|
|
|
1066
1085
|
}
|
|
1067
1086
|
})
|
|
1068
1087
|
.catch(function (err) {
|
|
1069
|
-
/* AbortError means timeout */
|
|
1070
1088
|
self._handleConfigError();
|
|
1071
1089
|
});
|
|
1072
1090
|
};
|
|
@@ -1098,26 +1116,21 @@
|
|
|
1098
1116
|
var str = _strings(cfg.language);
|
|
1099
1117
|
var isRtl = cfg.language === 'ar';
|
|
1100
1118
|
var posClass = cfg.position === 'bottom-left' ? 'bl' : 'br';
|
|
1101
|
-
var iconColor = cfg.iconColor || '#171717';
|
|
1102
|
-
var iconShape = cfg.iconShape || 'circle';
|
|
1103
1119
|
|
|
1104
1120
|
/* ── Update CSS custom properties ── */
|
|
1105
1121
|
var hostStyle = self._shadow.host.style;
|
|
1106
1122
|
hostStyle.setProperty('--wakz-primary', cfg.primaryColor);
|
|
1107
|
-
hostStyle.setProperty('--wakz-
|
|
1123
|
+
hostStyle.setProperty('--wakz-btn', cfg.btnColor);
|
|
1108
1124
|
hostStyle.setProperty('--wakz-chat-bg', cfg.chatBg);
|
|
1109
1125
|
hostStyle.setProperty('--wakz-widget-bg', cfg.widgetBg);
|
|
1126
|
+
hostStyle.setProperty('--wakz-icon-radius', cfg.iconRadius || '50%');
|
|
1110
1127
|
|
|
1111
1128
|
/* ── RTL ── */
|
|
1112
1129
|
if (isRtl) self._root.classList.add('wakz-rtl');
|
|
1113
1130
|
else self._root.classList.remove('wakz-rtl');
|
|
1114
1131
|
|
|
1115
1132
|
/* ── FAB ── */
|
|
1116
|
-
|
|
1117
|
-
if (iconShape === 'rounded') fabClasses += ' wakz-fab-shape-rounded';
|
|
1118
|
-
if (self.isOpen) fabClasses += ' wakz-fab-hidden';
|
|
1119
|
-
self._toggleBtn.className = fabClasses;
|
|
1120
|
-
self._toggleBtn.style.background = iconColor;
|
|
1133
|
+
self._toggleBtn.className = 'wakz-fab wakz-fab-pos-' + posClass + ' wakz-fab-enter' + (self.isOpen ? ' wakz-fab-hidden' : '');
|
|
1121
1134
|
self._toggleBtn.setAttribute('aria-label', str.openChat);
|
|
1122
1135
|
|
|
1123
1136
|
/* ── FAB Status Dot ── */
|
|
@@ -1127,11 +1140,12 @@
|
|
|
1127
1140
|
/* ── Window ── */
|
|
1128
1141
|
self._chatWindow.className = 'wakz-window' + (self.isOpen ? ' wakz-visible' : '');
|
|
1129
1142
|
|
|
1143
|
+
/* ── Overlay ── */
|
|
1144
|
+
self._overlay.className = 'wakz-overlay' + (self.isOpen ? ' wakz-visible' : '');
|
|
1145
|
+
|
|
1130
1146
|
/* ── Bot Name & Avatar ── */
|
|
1131
|
-
|
|
1132
|
-
if (
|
|
1133
|
-
var avatarEl = self._chatWindow.querySelector('.wakz-avatar');
|
|
1134
|
-
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();
|
|
1135
1149
|
|
|
1136
1150
|
/* ── Header Status ── */
|
|
1137
1151
|
if (self._headerStatusDot) {
|
|
@@ -1178,7 +1192,6 @@
|
|
|
1178
1192
|
.then(function (res) { return res.json(); })
|
|
1179
1193
|
.then(function (data) {
|
|
1180
1194
|
if (data && data.success && data.messages && data.messages.length > 0) {
|
|
1181
|
-
/* Clear any existing messages (e.g., welcome) and render history */
|
|
1182
1195
|
self._clearMessages();
|
|
1183
1196
|
self._knownMessageIds = {};
|
|
1184
1197
|
var msgs = data.messages;
|
|
@@ -1189,7 +1202,6 @@
|
|
|
1189
1202
|
var role = m.role === 'user' ? 'user' : 'bot';
|
|
1190
1203
|
self._appendMessage(role, m.content, false, m.createdAt, m.role === 'human_agent');
|
|
1191
1204
|
}
|
|
1192
|
-
/* Set last poll time to the most recent message */
|
|
1193
1205
|
var lastMsg = msgs[msgs.length - 1];
|
|
1194
1206
|
if (lastMsg && lastMsg.createdAt) {
|
|
1195
1207
|
self._lastPollTime = lastMsg.createdAt;
|
|
@@ -1207,7 +1219,7 @@
|
|
|
1207
1219
|
|
|
1208
1220
|
WAKZWidget.prototype._startPolling = function () {
|
|
1209
1221
|
var self = this;
|
|
1210
|
-
self._stopPolling();
|
|
1222
|
+
self._stopPolling();
|
|
1211
1223
|
if (!self._lastPollTime) {
|
|
1212
1224
|
self._lastPollTime = new Date().toISOString();
|
|
1213
1225
|
}
|
|
@@ -1227,6 +1239,8 @@
|
|
|
1227
1239
|
WAKZWidget.prototype._pollForNewMessages = function () {
|
|
1228
1240
|
var self = this;
|
|
1229
1241
|
if (!self.server || !self.apiKey || !self._lastPollTime) return;
|
|
1242
|
+
/* Skip polling while waiting for our own reply */
|
|
1243
|
+
if (self._pendingReply) return;
|
|
1230
1244
|
|
|
1231
1245
|
var url = self.server + '/api/v1/embed/chat?api_key=' +
|
|
1232
1246
|
encodeURIComponent(self.apiKey) +
|
|
@@ -1249,7 +1263,6 @@
|
|
|
1249
1263
|
var isHumanAgent = m.role === 'human_agent';
|
|
1250
1264
|
self._appendMessage(role, m.content, false, m.createdAt, isHumanAgent);
|
|
1251
1265
|
}
|
|
1252
|
-
/* Update last poll time */
|
|
1253
1266
|
var lastMsg = msgs[msgs.length - 1];
|
|
1254
1267
|
if (lastMsg && lastMsg.createdAt) {
|
|
1255
1268
|
self._lastPollTime = lastMsg.createdAt;
|
|
@@ -1267,13 +1280,19 @@
|
|
|
1267
1280
|
|
|
1268
1281
|
WAKZWidget.prototype._clearMessages = function () {
|
|
1269
1282
|
var self = this;
|
|
1270
|
-
|
|
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
|
+
}
|
|
1271
1290
|
self.messages = [];
|
|
1272
1291
|
self._knownMessageIds = {};
|
|
1273
1292
|
};
|
|
1274
1293
|
|
|
1275
1294
|
/* ════════════════════════════════════════════════════════════════
|
|
1276
|
-
APPEND WELCOME MESSAGE
|
|
1295
|
+
APPEND WELCOME MESSAGE — no bubble style, matching bot messages
|
|
1277
1296
|
════════════════════════════════════════════════════════════════ */
|
|
1278
1297
|
|
|
1279
1298
|
WAKZWidget.prototype._appendWelcomeMessage = function (text) {
|
|
@@ -1282,155 +1301,174 @@
|
|
|
1282
1301
|
|
|
1283
1302
|
var wrap = _el('div', { className: 'wakz-welcome-wrap' });
|
|
1284
1303
|
|
|
1285
|
-
/* Bot avatar */
|
|
1286
|
-
var avatar = _el('div', { className: 'wakz-
|
|
1304
|
+
/* Bot avatar (22px) */
|
|
1305
|
+
var avatar = _el('div', { className: 'wakz-bot-avatar' },
|
|
1287
1306
|
[(self.config.botName || 'W')[0].toUpperCase()]);
|
|
1288
1307
|
wrap.appendChild(avatar);
|
|
1289
1308
|
|
|
1290
|
-
/*
|
|
1291
|
-
var content = _el('div', { className: 'wakz-
|
|
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]));
|
|
1292
1312
|
wrap.appendChild(content);
|
|
1293
1313
|
|
|
1294
|
-
|
|
1314
|
+
/* Insert before input area */
|
|
1315
|
+
self._messagesContainer.insertBefore(wrap, self._inputArea);
|
|
1295
1316
|
self.messages.push({ sender: 'bot', text: text });
|
|
1296
1317
|
self._scrollToBottom();
|
|
1297
1318
|
};
|
|
1298
1319
|
|
|
1299
1320
|
/* ════════════════════════════════════════════════════════════════
|
|
1300
|
-
APPEND MESSAGE TO CHAT
|
|
1321
|
+
APPEND MESSAGE TO CHAT — v3 rendering
|
|
1301
1322
|
════════════════════════════════════════════════════════════════ */
|
|
1302
1323
|
|
|
1303
1324
|
WAKZWidget.prototype._appendMessage = function (sender, text, isError, timestamp, isHumanAgent) {
|
|
1304
1325
|
var self = this;
|
|
1305
1326
|
|
|
1327
|
+
/* ── Format timestamp ── */
|
|
1328
|
+
var tsText = '';
|
|
1329
|
+
if (timestamp) {
|
|
1330
|
+
try {
|
|
1331
|
+
var d = new Date(timestamp);
|
|
1332
|
+
if (!isNaN(d.getTime())) {
|
|
1333
|
+
tsText = d.getHours().toString().padStart(2, '0') + ':' +
|
|
1334
|
+
d.getMinutes().toString().padStart(2, '0');
|
|
1335
|
+
}
|
|
1336
|
+
} catch (e) { /* ignore */ }
|
|
1337
|
+
}
|
|
1338
|
+
if (!tsText) {
|
|
1339
|
+
var now = new Date();
|
|
1340
|
+
tsText = now.getHours().toString().padStart(2, '0') + ':' +
|
|
1341
|
+
now.getMinutes().toString().padStart(2, '0');
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1306
1344
|
if (sender === 'user') {
|
|
1307
|
-
/* ──
|
|
1345
|
+
/* ── USER MESSAGE: colored bubble with tail ── */
|
|
1308
1346
|
var row = _el('div', { className: 'wakz-msg-row user' });
|
|
1309
|
-
var bubble = _el('div', { className: 'wakz-bubble-user' });
|
|
1310
|
-
bubble.appendChild(document.createTextNode(text));
|
|
1311
1347
|
|
|
1312
|
-
|
|
1313
|
-
|
|
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));
|
|
1314
1352
|
bubble.appendChild(_el('span', { className: 'wakz-ts' }, [tsText]));
|
|
1315
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
|
+
}
|
|
1381
|
+
|
|
1316
1382
|
row.appendChild(bubble);
|
|
1317
|
-
self._messagesContainer.
|
|
1383
|
+
self._messagesContainer.insertBefore(row, self._inputArea);
|
|
1318
1384
|
self.messages.push({ sender: sender, text: text, isError: !!isError });
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
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
|
+
}
|
|
1343
1414
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
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
|
+
}
|
|
1351
1421
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
});
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
self.messages.push({ sender: sender, text: text, isError: true });
|
|
1361
|
-
self._scrollToBottom();
|
|
1362
|
-
return;
|
|
1363
|
-
}
|
|
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]));
|
|
1364
1430
|
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
var wrap = _el('div', { className: 'wakz-msg-bot-wrap' });
|
|
1431
|
+
/* Timestamp */
|
|
1432
|
+
botContent.appendChild(_el('span', { className: 'wakz-bot-ts' }, [tsText]));
|
|
1368
1433
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
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
|
+
}
|
|
1373
1441
|
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
var content = _el('div', { className: contentClass });
|
|
1378
|
-
content.appendChild(document.createTextNode(text));
|
|
1379
|
-
|
|
1380
|
-
/* Timestamp */
|
|
1381
|
-
var botTs = self._formatTime(timestamp);
|
|
1382
|
-
content.appendChild(_el('span', { className: 'wakz-ts' }, [botTs]));
|
|
1383
|
-
|
|
1384
|
-
/* Human agent badge */
|
|
1385
|
-
if (isHumanAgent) {
|
|
1386
|
-
var iStr = _strings(self.config.language);
|
|
1387
|
-
var badge = _el('span', { className: 'wakz-human-badge' }, [iStr.humanBadge || '👤 فريق الدعم']);
|
|
1388
|
-
content.appendChild(badge);
|
|
1442
|
+
botRow.appendChild(botContent);
|
|
1443
|
+
self._messagesContainer.insertBefore(botRow, self._inputArea);
|
|
1444
|
+
self.messages.push({ sender: sender, text: text, isError: !!isError });
|
|
1389
1445
|
}
|
|
1390
1446
|
|
|
1391
|
-
wrap.appendChild(content);
|
|
1392
|
-
botRow.appendChild(wrap);
|
|
1393
|
-
self._messagesContainer.appendChild(botRow);
|
|
1394
|
-
|
|
1395
|
-
self.messages.push({ sender: sender, text: text, isError: false, isHumanAgent: !!isHumanAgent });
|
|
1396
1447
|
self._scrollToBottom();
|
|
1397
1448
|
};
|
|
1398
1449
|
|
|
1399
1450
|
/* ════════════════════════════════════════════════════════════════
|
|
1400
|
-
|
|
1401
|
-
════════════════════════════════════════════════════════════════ */
|
|
1402
|
-
|
|
1403
|
-
WAKZWidget.prototype._formatTime = function (timestamp) {
|
|
1404
|
-
if (timestamp) {
|
|
1405
|
-
try {
|
|
1406
|
-
var d = new Date(timestamp);
|
|
1407
|
-
if (!isNaN(d.getTime())) {
|
|
1408
|
-
return d.getHours().toString().padStart(2, '0') + ':' +
|
|
1409
|
-
d.getMinutes().toString().padStart(2, '0');
|
|
1410
|
-
}
|
|
1411
|
-
} catch (e) { /* ignore */ }
|
|
1412
|
-
}
|
|
1413
|
-
var now = new Date();
|
|
1414
|
-
return now.getHours().toString().padStart(2, '0') + ':' +
|
|
1415
|
-
now.getMinutes().toString().padStart(2, '0');
|
|
1416
|
-
};
|
|
1417
|
-
|
|
1418
|
-
/* ════════════════════════════════════════════════════════════════
|
|
1419
|
-
TYPING INDICATOR (no bubble — on background)
|
|
1451
|
+
TYPING INDICATOR — no bubble, dots on background with avatar
|
|
1420
1452
|
════════════════════════════════════════════════════════════════ */
|
|
1421
1453
|
|
|
1422
1454
|
WAKZWidget.prototype._showTyping = function () {
|
|
1423
1455
|
var self = this;
|
|
1424
1456
|
if (self._typingEl) return;
|
|
1425
1457
|
|
|
1426
|
-
self._typingEl = _el('div', { className: 'wakz-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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);
|
|
1434
1472
|
self._scrollToBottom();
|
|
1435
1473
|
};
|
|
1436
1474
|
|
|
@@ -1491,6 +1529,10 @@
|
|
|
1491
1529
|
self._sendBtn.disabled = true;
|
|
1492
1530
|
self._showTyping();
|
|
1493
1531
|
|
|
1532
|
+
/* Freeze poll time BEFORE sending */
|
|
1533
|
+
self._lastPollTime = new Date().toISOString();
|
|
1534
|
+
self._pendingReply = true;
|
|
1535
|
+
|
|
1494
1536
|
var payload = {
|
|
1495
1537
|
api_key: self.apiKey,
|
|
1496
1538
|
visitor_id: self.visitorId,
|
|
@@ -1499,7 +1541,10 @@
|
|
|
1499
1541
|
domain: window.location.hostname || '',
|
|
1500
1542
|
page_url: window.location.href || '',
|
|
1501
1543
|
user_agent: navigator.userAgent || '',
|
|
1502
|
-
language: navigator.language || ''
|
|
1544
|
+
language: navigator.language || '',
|
|
1545
|
+
device_model: self._deviceInfo.device_model || '',
|
|
1546
|
+
device_platform: self._deviceInfo.device_platform || '',
|
|
1547
|
+
device_type: self._deviceInfo.device_type || 'desktop'
|
|
1503
1548
|
}
|
|
1504
1549
|
};
|
|
1505
1550
|
|
|
@@ -1514,19 +1559,21 @@
|
|
|
1514
1559
|
.then(function (res) { return res.json(); })
|
|
1515
1560
|
.then(function (data) {
|
|
1516
1561
|
self._hideTyping();
|
|
1562
|
+
self._pendingReply = false;
|
|
1517
1563
|
if (data && data.success && data.reply) {
|
|
1518
1564
|
self._appendMessage('bot', data.reply);
|
|
1519
1565
|
} else {
|
|
1520
1566
|
self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
|
|
1521
1567
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1568
|
+
self._lastPollTime = (data && data.timestamp)
|
|
1569
|
+
? new Date(new Date(data.timestamp).getTime() + 1000).toISOString()
|
|
1570
|
+
: new Date(Date.now() + 1000).toISOString();
|
|
1524
1571
|
})
|
|
1525
1572
|
.catch(function () {
|
|
1526
1573
|
self._hideTyping();
|
|
1574
|
+
self._pendingReply = false;
|
|
1527
1575
|
self._appendMessage('bot', _strings(self.config.language).errorMsg, true);
|
|
1528
|
-
|
|
1529
|
-
self._lastPollTime = new Date().toISOString();
|
|
1576
|
+
self._lastPollTime = new Date(Date.now() + 1000).toISOString();
|
|
1530
1577
|
})
|
|
1531
1578
|
.finally(function () {
|
|
1532
1579
|
self.isLoading = false;
|
|
@@ -1540,7 +1587,7 @@
|
|
|
1540
1587
|
════════════════════════════════════════════════════════════════ */
|
|
1541
1588
|
|
|
1542
1589
|
function boot() {
|
|
1543
|
-
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); }
|
|
1544
1591
|
}
|
|
1545
1592
|
|
|
1546
1593
|
if (document.readyState === 'loading') {
|