PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.76
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.76
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Live_Chat / assets / frontend.js

frontend.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.76, at includes/extensions/Live_Chat/assets/frontend.js

896 lines 29.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Live Chat Frontend JavaScript
3 *
4 * Handles chat widget functionality, messaging, and polling.
5 *
6 * @package King_Addons
7 */
8
9 (function() {
10 'use strict';
11
12 /**
13 * Live Chat Widget Module
14 */
15 const KingLiveChat = {
16 /**
17 * Current state
18 */
19 state: {
20 isOpen: false,
21 isChatting: false,
22 conversationId: null,
23 visitorId: null,
24 visitorName: '',
25 visitorEmail: '',
26 messages: [],
27 lastMessageId: 0,
28 pollInterval: null,
29 isLoading: false,
30 hasError: false,
31 mode: 'live_chat' // live_chat or contact_form
32 },
33
34 /**
35 * Configuration
36 */
37 config: {},
38
39 /**
40 * Strings for i18n
41 */
42 strings: {},
43
44 /**
45 * DOM elements cache
46 */
47 elements: {},
48
49 /**
50 * Initialize the widget
51 */
52 init: function() {
53 // Get config from global
54 if (typeof kingLiveChat === 'undefined') {
55 console.error('KingLiveChat: Config not found');
56 return;
57 }
58
59 this.config = kingLiveChat;
60 this.strings = kingLiveChat.strings || {};
61 this.state.visitorId = kingLiveChat.visitorId;
62 this.state.mode = kingLiveChat.widgetMode || 'live_chat';
63
64 this.cacheElements();
65
66 if (!this.elements.container) {
67 console.error('KingLiveChat: Widget container not found');
68 return;
69 }
70
71 this.bindEvents();
72 this.initVisitorId();
73
74 // Only restore session for live chat mode
75 if (this.state.mode === 'live_chat') {
76 this.restoreSession();
77 }
78 },
79
80 /**
81 * Cache DOM elements
82 */
83 cacheElements: function() {
84 this.elements = {
85 container: document.getElementById('ka-live-chat'),
86 button: document.querySelector('.ka-live-chat__button'),
87 panel: document.querySelector('.ka-live-chat__panel'),
88 closeBtn: document.querySelector('.ka-live-chat__close'),
89 prechat: document.querySelector('.ka-live-chat__prechat'),
90 nameInput: document.getElementById('ka-chat-name'),
91 emailInput: document.getElementById('ka-chat-email'),
92 startBtn: document.querySelector('.ka-live-chat__start'),
93 messagesContainer: document.querySelector('.ka-live-chat__messages'),
94 messagesList: document.querySelector('.ka-live-chat__messages-list'),
95 inputArea: document.querySelector('.ka-live-chat__input'),
96 textarea: document.querySelector('.ka-live-chat__input textarea'),
97 sendBtn: document.querySelector('.ka-live-chat__send'),
98 badge: document.querySelector('.ka-live-chat__badge'),
99 honeypot: document.querySelector('.ka-live-chat__hp input'),
100 // Contact Form elements
101 contactForm: document.querySelector('.ka-live-chat__contact-form'),
102 subjectInput: document.getElementById('ka-chat-subject'),
103 messageTextarea: document.getElementById('ka-chat-message'),
104 submitBtn: document.querySelector('.ka-live-chat__submit'),
105 successScreen: document.querySelector('.ka-live-chat__success'),
106 newMessageBtn: document.querySelector('.ka-live-chat__new-message')
107 };
108 },
109
110 /**
111 * Bind event handlers
112 */
113 bindEvents: function() {
114 const self = this;
115
116 // Toggle chat
117 this.elements.button.addEventListener('click', function() {
118 self.toggle();
119 });
120
121 // Close button
122 if (this.elements.closeBtn) {
123 this.elements.closeBtn.addEventListener('click', function() {
124 self.close();
125 });
126 }
127
128 // Start chat (Live Chat mode)
129 if (this.elements.startBtn) {
130 this.elements.startBtn.addEventListener('click', function() {
131 self.startChat();
132 });
133 }
134
135 // Send message (Live Chat mode)
136 if (this.elements.sendBtn) {
137 this.elements.sendBtn.addEventListener('click', function() {
138 self.sendMessage();
139 });
140 }
141
142 // Enter to send (Live Chat mode)
143 if (this.elements.textarea) {
144 this.elements.textarea.addEventListener('keydown', function(e) {
145 if (e.key === 'Enter' && !e.shiftKey) {
146 e.preventDefault();
147 self.sendMessage();
148 }
149 });
150
151 // Auto-resize textarea
152 this.elements.textarea.addEventListener('input', function() {
153 this.style.height = 'auto';
154 this.style.height = Math.min(this.scrollHeight, 100) + 'px';
155 });
156 }
157
158 // Pre-chat form validation (Live Chat mode)
159 if (this.elements.nameInput) {
160 this.elements.nameInput.addEventListener('input', function() {
161 if (self.state.mode === 'live_chat') {
162 self.validatePrechat();
163 } else {
164 self.validateContactForm();
165 }
166 });
167 }
168
169 if (this.elements.emailInput) {
170 this.elements.emailInput.addEventListener('input', function() {
171 if (self.state.mode === 'live_chat') {
172 self.validatePrechat();
173 } else {
174 self.validateContactForm();
175 }
176 });
177 }
178
179 // Contact Form mode events
180 if (this.elements.submitBtn) {
181 this.elements.submitBtn.addEventListener('click', function() {
182 self.submitContactForm();
183 });
184 }
185
186 if (this.elements.messageTextarea) {
187 this.elements.messageTextarea.addEventListener('input', function() {
188 self.validateContactForm();
189 });
190 }
191
192 if (this.elements.newMessageBtn) {
193 this.elements.newMessageBtn.addEventListener('click', function() {
194 self.resetContactForm();
195 });
196 }
197
198 // Close on escape
199 document.addEventListener('keydown', function(e) {
200 if (e.key === 'Escape' && self.state.isOpen) {
201 self.close();
202 }
203 });
204 },
205
206 /**
207 * Initialize or get visitor ID from cookie
208 */
209 initVisitorId: function() {
210 const cookieName = 'king_support_vid';
211 let visitorId = this.getCookie(cookieName);
212
213 if (!visitorId) {
214 visitorId = this.state.visitorId || this.generateUUID();
215 this.setCookie(cookieName, visitorId, 365);
216 }
217
218 this.state.visitorId = visitorId;
219 },
220
221 /**
222 * Restore session from previous visit
223 */
224 restoreSession: function() {
225 const self = this;
226 const sessionData = localStorage.getItem('ka_chat_session');
227
228 // Avoid background REST calls on every page load.
229 // Only attempt to restore if a session already exists.
230 if (!sessionData) {
231 return;
232 }
233
234 if (sessionData) {
235 try {
236 const data = JSON.parse(sessionData);
237 this.state.visitorName = data.name || '';
238 this.state.visitorEmail = data.email || '';
239
240 if (data.conversationId) {
241 this.state.conversationId = data.conversationId;
242 }
243 } catch (e) {
244 localStorage.removeItem('ka_chat_session');
245 }
246 }
247
248 // Try to init conversation
249 this.initConversation().then(function(data) {
250 if (data.conversation_id) {
251 self.state.conversationId = data.conversation_id;
252 self.state.isChatting = true;
253 self.state.messages = data.messages || [];
254 self.state.lastMessageId = data.messages.length ? data.messages[data.messages.length - 1].id : 0;
255
256 self.elements.container.classList.add('ka-live-chat--chatting');
257 self.renderMessages();
258 self.updateBadge(data.unread || 0);
259 }
260 });
261 },
262
263 /**
264 * Toggle chat panel
265 */
266 toggle: function() {
267 if (this.state.isOpen) {
268 this.close();
269 } else {
270 this.open();
271 }
272 },
273
274 /**
275 * Open chat panel
276 */
277 open: function() {
278 this.state.isOpen = true;
279 this.elements.container.classList.add('ka-live-chat--open');
280
281 // Mark messages as read
282 if (this.state.conversationId) {
283 this.markAsRead();
284 }
285
286 // Start polling
287 this.startPolling();
288
289 // Focus appropriate element
290 if (this.state.isChatting) {
291 this.elements.textarea.focus();
292 this.scrollToBottom();
293 } else if (this.elements.nameInput) {
294 this.elements.nameInput.focus();
295 }
296 },
297
298 /**
299 * Close chat panel
300 */
301 close: function() {
302 this.state.isOpen = false;
303 this.elements.container.classList.remove('ka-live-chat--open');
304 this.stopPolling();
305 },
306
307 /**
308 * Validate pre-chat form
309 */
310 validatePrechat: function() {
311 const requireName = this.config.options.requireName;
312 const requireEmail = this.config.options.requireEmail;
313
314 let isValid = true;
315
316 if (requireName && this.elements.nameInput) {
317 isValid = isValid && this.elements.nameInput.value.trim().length > 0;
318 }
319
320 if (requireEmail && this.elements.emailInput) {
321 const email = this.elements.emailInput.value.trim();
322 isValid = isValid && this.isValidEmail(email);
323 }
324
325 this.elements.startBtn.disabled = !isValid;
326 },
327
328 /**
329 * Start chat (after pre-chat form)
330 */
331 startChat: function() {
332 const self = this;
333
334 // Get values
335 if (this.elements.nameInput) {
336 this.state.visitorName = this.elements.nameInput.value.trim();
337 }
338 if (this.elements.emailInput) {
339 this.state.visitorEmail = this.elements.emailInput.value.trim();
340 }
341
342 // Save to session
343 this.saveSession();
344
345 // Show chat UI
346 this.state.isChatting = true;
347 this.elements.container.classList.add('ka-live-chat--chatting');
348
349 // Add welcome message
350 if (this.strings.welcomeMessage) {
351 this.addMessage({
352 type: 'welcome',
353 text: this.strings.welcomeMessage,
354 time: new Date().toISOString()
355 });
356 }
357
358 // Focus textarea
359 this.elements.textarea.focus();
360 },
361
362 /**
363 * Send message
364 */
365 sendMessage: function() {
366 const self = this;
367 const text = this.elements.textarea.value.trim();
368
369 if (!text || this.state.isLoading) {
370 return;
371 }
372
373 // Check honeypot
374 if (this.elements.honeypot && this.elements.honeypot.value) {
375 console.warn('KingLiveChat: Spam detected');
376 return;
377 }
378
379 this.state.isLoading = true;
380 this.elements.sendBtn.disabled = true;
381
382 // Optimistically add message
383 const tempMsg = {
384 id: 'temp-' + Date.now(),
385 type: 'visitor',
386 text: text,
387 time: new Date().toISOString(),
388 pending: true
389 };
390 this.addMessage(tempMsg);
391 this.elements.textarea.value = '';
392 this.elements.textarea.style.height = 'auto';
393
394 // Send to server
395 this.apiRequest('message/send', {
396 visitor_id: this.state.visitorId,
397 conversation_id: this.state.conversationId,
398 message: text,
399 name: this.state.visitorName,
400 email: this.state.visitorEmail,
401 page_url: window.location.href,
402 referrer: document.referrer,
403 website: this.elements.honeypot ? this.elements.honeypot.value : ''
404 }).then(function(data) {
405 if (data.success) {
406 // Update conversation ID if new
407 if (!self.state.conversationId) {
408 self.state.conversationId = data.conversation_id;
409 self.saveSession();
410 }
411
412 // Update temp message
413 const tempEl = self.elements.messagesList.querySelector('[data-id="' + tempMsg.id + '"]');
414 if (tempEl) {
415 tempEl.dataset.id = data.message_id;
416 tempEl.classList.remove('ka-live-chat__message--pending');
417 }
418
419 self.state.lastMessageId = data.message_id;
420 } else if (data.error === 'rate_limit') {
421 self.showError(self.strings.errorRateLimit);
422 // Remove temp message
423 self.removeMessage(tempMsg.id);
424 } else {
425 self.showError(self.strings.errorNetwork);
426 self.removeMessage(tempMsg.id);
427 }
428 }).catch(function() {
429 self.showError(self.strings.errorNetwork);
430 self.removeMessage(tempMsg.id);
431 }).finally(function() {
432 self.state.isLoading = false;
433 self.elements.sendBtn.disabled = false;
434 self.elements.textarea.focus();
435 });
436 },
437
438 /**
439 * Add message to UI
440 *
441 * @param {Object} msg Message object
442 */
443 addMessage: function(msg) {
444 this.state.messages.push(msg);
445
446 const msgEl = document.createElement('div');
447 msgEl.className = 'ka-live-chat__message ka-live-chat__message--' + msg.type;
448 if (msg.pending) {
449 msgEl.classList.add('ka-live-chat__message--pending');
450 }
451 msgEl.dataset.id = msg.id;
452
453 const textEl = document.createElement('div');
454 textEl.className = 'ka-live-chat__message-text';
455 textEl.textContent = msg.text;
456 msgEl.appendChild(textEl);
457
458 const timeEl = document.createElement('div');
459 timeEl.className = 'ka-live-chat__message-time';
460 timeEl.textContent = this.formatTime(msg.time);
461 msgEl.appendChild(timeEl);
462
463 this.elements.messagesList.appendChild(msgEl);
464 this.scrollToBottom();
465 },
466
467 /**
468 * Remove message from UI
469 *
470 * @param {string|number} id Message ID
471 */
472 removeMessage: function(id) {
473 const el = this.elements.messagesList.querySelector('[data-id="' + id + '"]');
474 if (el) {
475 el.remove();
476 }
477 this.state.messages = this.state.messages.filter(function(m) {
478 return m.id !== id;
479 });
480 },
481
482 /**
483 * Render all messages
484 */
485 renderMessages: function() {
486 const self = this;
487 this.elements.messagesList.innerHTML = '';
488
489 // Add welcome message first if chatting
490 if (this.state.isChatting && this.strings.welcomeMessage && !this.state.messages.length) {
491 this.addMessage({
492 type: 'welcome',
493 text: this.strings.welcomeMessage,
494 time: new Date().toISOString()
495 });
496 }
497
498 this.state.messages.forEach(function(msg) {
499 self.addMessage(msg);
500 });
501 },
502
503 /**
504 * Scroll messages to bottom
505 */
506 scrollToBottom: function() {
507 const container = this.elements.messagesContainer;
508 if (container) {
509 container.scrollTop = container.scrollHeight;
510 }
511 },
512
513 /**
514 * Start polling for new messages
515 */
516 startPolling: function() {
517 if (this.state.pollInterval) {
518 return;
519 }
520
521 const self = this;
522 const interval = this.config.pollInterval || 4000;
523
524 this.state.pollInterval = setInterval(function() {
525 if (self.state.isOpen && self.state.conversationId) {
526 self.pollMessages();
527 }
528 }, interval);
529 },
530
531 /**
532 * Stop polling
533 */
534 stopPolling: function() {
535 if (this.state.pollInterval) {
536 clearInterval(this.state.pollInterval);
537 this.state.pollInterval = null;
538 }
539 },
540
541 /**
542 * Poll for new messages
543 */
544 pollMessages: function() {
545 const self = this;
546
547 this.apiRequest('messages/poll', {
548 visitor_id: this.state.visitorId,
549 conversation_id: this.state.conversationId,
550 after_id: this.state.lastMessageId
551 }, 'GET').then(function(data) {
552 if (data.messages && data.messages.length) {
553 data.messages.forEach(function(msg) {
554 // Don't duplicate
555 const exists = self.state.messages.some(function(m) {
556 return m.id === msg.id;
557 });
558
559 if (!exists) {
560 self.addMessage(msg);
561 self.state.lastMessageId = msg.id;
562
563 // Update badge if closed
564 if (!self.state.isOpen && msg.type === 'admin') {
565 self.updateBadge((parseInt(self.elements.badge.textContent, 10) || 0) + 1);
566 }
567 }
568 });
569 }
570 });
571 },
572
573 /**
574 * Mark messages as read
575 */
576 markAsRead: function() {
577 this.updateBadge(0);
578
579 this.apiRequest('messages/read', {
580 visitor_id: this.state.visitorId,
581 conversation_id: this.state.conversationId
582 });
583 },
584
585 /**
586 * Update unread badge
587 *
588 * @param {number} count Unread count
589 */
590 updateBadge: function(count) {
591 if (this.elements.badge) {
592 this.elements.badge.textContent = count;
593 this.elements.badge.style.display = count > 0 ? 'flex' : 'none';
594 }
595 },
596
597 /**
598 * Initialize conversation via API
599 *
600 * @returns {Promise}
601 */
602 initConversation: function() {
603 return this.apiRequest('conversation/init', {
604 visitor_id: this.state.visitorId,
605 name: this.state.visitorName,
606 email: this.state.visitorEmail,
607 page_url: window.location.href,
608 referrer: document.referrer
609 });
610 },
611
612 /**
613 * Make API request
614 *
615 * @param {string} endpoint API endpoint
616 * @param {Object} data Request data
617 * @param {string} method HTTP method
618 * @returns {Promise}
619 */
620 apiRequest: function(endpoint, data, method) {
621 const self = this;
622 method = method || 'POST';
623
624 let url = this.config.restUrl + '/' + endpoint;
625
626 const options = {
627 method: method,
628 headers: {
629 'Content-Type': 'application/json',
630 'X-WP-Nonce': this.config.nonce
631 }
632 };
633
634 if (method === 'GET' && data) {
635 const params = new URLSearchParams();
636 Object.keys(data).forEach(function(key) {
637 if (data[key] !== undefined && data[key] !== null) {
638 params.append(key, data[key]);
639 }
640 });
641 url += '?' + params.toString();
642 } else if (data) {
643 options.body = JSON.stringify(data);
644 }
645
646 return fetch(url, options)
647 .then(function(response) {
648 return response.json();
649 })
650 .catch(function(error) {
651 console.error('KingLiveChat API Error:', error);
652 throw error;
653 });
654 },
655
656 /**
657 * Show error message
658 *
659 * @param {string} message Error message
660 */
661 showError: function(message) {
662 // Simple alert for now, could be improved
663 console.error('KingLiveChat:', message);
664 },
665
666 /**
667 * Save session to localStorage
668 */
669 saveSession: function() {
670 const sessionData = {
671 name: this.state.visitorName,
672 email: this.state.visitorEmail,
673 conversationId: this.state.conversationId
674 };
675 localStorage.setItem('ka_chat_session', JSON.stringify(sessionData));
676 },
677
678 /**
679 * Format time for display
680 *
681 * @param {string} dateStr ISO date string
682 * @returns {string}
683 */
684 formatTime: function(dateStr) {
685 if (!dateStr) return '';
686
687 const date = new Date(dateStr);
688 const now = new Date();
689 const diff = Math.floor((now - date) / 1000);
690
691 if (diff < 60) {
692 return this.strings.justNow || 'Just now';
693 }
694
695 if (diff < 3600) {
696 return Math.floor(diff / 60) + 'm ago';
697 }
698
699 if (diff < 86400 && date.getDate() === now.getDate()) {
700 return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
701 }
702
703 return date.toLocaleDateString([], { month: 'short', day: 'numeric' }) +
704 ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
705 },
706
707 /**
708 * Validate email format
709 *
710 * @param {string} email Email address
711 * @returns {boolean}
712 */
713 isValidEmail: function(email) {
714 return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
715 },
716
717 /**
718 * Generate UUID v4
719 *
720 * @returns {string}
721 */
722 generateUUID: function() {
723 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
724 const r = Math.random() * 16 | 0;
725 const v = c === 'x' ? r : (r & 0x3 | 0x8);
726 return v.toString(16);
727 });
728 },
729
730 /**
731 * Get cookie value
732 *
733 * @param {string} name Cookie name
734 * @returns {string|null}
735 */
736 getCookie: function(name) {
737 const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
738 return match ? match[2] : null;
739 },
740
741 /**
742 * Set cookie
743 *
744 * @param {string} name Cookie name
745 * @param {string} value Cookie value
746 * @param {number} days Days until expiry
747 */
748 setCookie: function(name, value, days) {
749 const date = new Date();
750 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
751 document.cookie = name + '=' + value + ';expires=' + date.toUTCString() + ';path=/;SameSite=Lax';
752 },
753
754 /**
755 * Validate contact form
756 */
757 validateContactForm: function() {
758 const requireName = this.config.options.requireName;
759 const requireEmail = this.config.options.requireEmail;
760
761 let isValid = true;
762
763 if (requireName && this.elements.nameInput) {
764 isValid = isValid && this.elements.nameInput.value.trim().length > 0;
765 }
766
767 if (requireEmail && this.elements.emailInput) {
768 const email = this.elements.emailInput.value.trim();
769 isValid = isValid && this.isValidEmail(email);
770 }
771
772 if (this.elements.messageTextarea) {
773 isValid = isValid && this.elements.messageTextarea.value.trim().length > 0;
774 }
775
776 if (this.elements.submitBtn) {
777 this.elements.submitBtn.disabled = !isValid;
778 }
779 },
780
781 /**
782 * Submit contact form
783 */
784 submitContactForm: function() {
785 const self = this;
786
787 // Check honeypot
788 if (this.elements.honeypot && this.elements.honeypot.value) {
789 return;
790 }
791
792 // Get values
793 const name = this.elements.nameInput ? this.elements.nameInput.value.trim() : '';
794 const email = this.elements.emailInput ? this.elements.emailInput.value.trim() : '';
795 const subject = this.elements.subjectInput ? this.elements.subjectInput.value.trim() : '';
796 const message = this.elements.messageTextarea ? this.elements.messageTextarea.value.trim() : '';
797
798 if (!message) {
799 return;
800 }
801
802 // Disable submit
803 if (this.elements.submitBtn) {
804 this.elements.submitBtn.disabled = true;
805 this.elements.submitBtn.textContent = this.strings.sending || 'Sending...';
806 }
807
808 // Send via REST API
809 this.apiCall('/support/contact', 'POST', {
810 visitor_id: this.state.visitorId,
811 name: name,
812 email: email,
813 subject: subject,
814 message: message,
815 page_url: window.location.href,
816 referrer: document.referrer
817 }).then(function(data) {
818 if (data.success) {
819 self.showSuccessScreen();
820 } else {
821 self.showFormError(data.message || self.strings.errorNetwork);
822 }
823 }).catch(function(error) {
824 self.showFormError(self.strings.errorNetwork);
825 }).finally(function() {
826 if (self.elements.submitBtn) {
827 self.elements.submitBtn.disabled = false;
828 self.elements.submitBtn.textContent = self.strings.submitButton || 'Send Message';
829 }
830 });
831 },
832
833 /**
834 * Show success screen
835 */
836 showSuccessScreen: function() {
837 if (this.elements.contactForm) {
838 this.elements.contactForm.style.display = 'none';
839 }
840 if (this.elements.successScreen) {
841 this.elements.successScreen.style.display = 'flex';
842 }
843 },
844
845 /**
846 * Reset contact form
847 */
848 resetContactForm: function() {
849 if (this.elements.nameInput) {
850 this.elements.nameInput.value = '';
851 }
852 if (this.elements.emailInput) {
853 this.elements.emailInput.value = '';
854 }
855 if (this.elements.subjectInput) {
856 this.elements.subjectInput.value = '';
857 }
858 if (this.elements.messageTextarea) {
859 this.elements.messageTextarea.value = '';
860 }
861
862 if (this.elements.successScreen) {
863 this.elements.successScreen.style.display = 'none';
864 }
865 if (this.elements.contactForm) {
866 this.elements.contactForm.style.display = 'flex';
867 }
868
869 this.validateContactForm();
870 },
871
872 /**
873 * Show form error
874 *
875 * @param {string} message Error message
876 */
877 showFormError: function(message) {
878 // For now just alert, could be improved
879 alert(message);
880 }
881 };
882
883 // Initialize on DOM ready
884 if (document.readyState === 'loading') {
885 document.addEventListener('DOMContentLoaded', function() {
886 KingLiveChat.init();
887 });
888 } else {
889 KingLiveChat.init();
890 }
891
892 // Expose globally for debugging
893 window.KingLiveChat = KingLiveChat;
894
895 })();
896