1(function($) { 2 'use strict'; 3 4 class ChatWidgetPro { 5 constructor() { 6 this.config = window.cwp_config || {}; 7 this.currentQrIndex = 0; 8 this.messengerKeys = Object.keys(this.config.messengers); 9 this.isModalOpen = false; 10 this.isMobileSheetOpen = false; 11 this.widgetInterval = null; 12 this.touchStartY = 0; 13 this.touchCurrentY = 0; 14 this.currentMessengerIndex = 0; 15 this.widgetVisible = false; 16 this.firstChangeDone = false; 17 this.scanInterval = null; 18 this.isScanActive = false; 19 20 this.init(); 21 } 22 23 init() { 24 this.setupWidget(); 25 this.setupModal(); 26 this.setupMobileSheet(); 27 this.setupQrNavigation(); 28 this.setupScanAnimation(); 29 this.applyDesignSettings(); 30 31 this.$widget.hide(); 32 33 setTimeout(() => { 34 this.showWidget(); 35 }, this.config.design.delay); 36 } 37 38 setupWidget() { 39 this.$widget = $('#cwp-widget'); 40 this.$widgetContainer = this.$widget.find('.cwp-widget-container'); 41 this.$widgetBubble = this.$widget.find('.cwp-widget-bubble'); 42 this.$widgetIcon = this.$widget.find('.cwp-widget-icon'); 43 44 this.$widget.addClass(this.config.design.position); 45 46 const marginRight = this.config.design.margin_right || 20; 47 const marginLeft = this.config.design.margin_left || 20; 48 const marginBottom = this.config.design.margin_bottom || 20; 49 50 if (this.config.design.position === 'bottom-right') { 51 this.$widget.css({ 52 'right': marginRight + 'px', 53 'left': 'auto', 54 'bottom': marginBottom + 'px' 55 }); 56 } else { 57 this.$widget.css({ 58 'left': marginLeft + 'px', 59 'right': 'auto', 60 'bottom': marginBottom + 'px' 61 }); 62 } 63 64 this.setInitialIcon(); 65 66 this.$widget.on('click', () => { 67 if (this.config.is_mobile) { 68 this.openMobileSheet(); 69 } else { 70 this.openModal(); 71 } 72 }); 73 74 if (!this.config.is_mobile) { 75 this.$widget.on('mouseenter', () => { 76 this.$widget.addClass('pulse'); 77 }).on('mouseleave', () => { 78 this.$widget.removeClass('pulse'); 79 }); 80 } 81 } 82 83 setInitialIcon() { 84 if (this.messengerKeys.length > 0) { 85 const firstMessengerKey = this.messengerKeys[this.currentMessengerIndex]; 86 const messenger = this.config.messengers[firstMessengerKey]; 87 88 this.$widgetIcon.html(this.getMessengerSvg(firstMessengerKey, true)); 89 90 if (firstMessengerKey === 'max') { 91 this.createMaxGradientBubble(); 92 } else { 93 this.updateBubbleColor(messenger.color); 94 } 95 } 96 } 97 98 setupModal() { 99 this.$modal = $('#cwp-modal'); 100 this.$modalOverlay = this.$modal.find('.cwp-modal-overlay'); 101 this.$modalClose = this.$modal.find('.cwp-modal-close'); 102 this.$modalContent = this.$modal.find('.cwp-modal-content'); 103 104 this.$modalClose.on('click', () => this.closeModal()); 105 this.$modalOverlay.on('click', () => this.closeModal()); 106 107 this.$modalContent.on('click', (e) => e.stopPropagation()); 108 109 $(document).on('keydown', (e) => { 110 if (e.keyCode === 27 && this.isModalOpen) { 111 this.closeModal(); 112 } 113 }); 114 } 115 116 setupMobileSheet() { 117 this.$mobileSheet = $('#cwp-mobile-sheet'); 118 this.$mobileOverlay = this.$mobileSheet.find('.cwp-mobile-overlay'); 119 this.$mobileContent = this.$mobileSheet.find('.cwp-mobile-content'); 120 this.$mobileDragHandle = this.$mobileSheet.find('.cwp-mobile-drag-handle'); 121 122 this.$mobileOverlay.on('click', () => this.closeMobileSheet()); 123 124 this.setupSwipeGestures(); 125 126 this.$mobileContent.on('click', (e) => e.stopPropagation()); 127 } 128 129 setupSwipeGestures() { 130 const content = this.$mobileContent[0]; 131 132 content.addEventListener('touchstart', (e) => { 133 this.touchStartY = e.touches[0].clientY; 134 }, { passive: true });
135 136 content.addEventListener('touchmove', (e) => { 137 if (this.touchStartY === 0) return; 138 139 this.touchCurrentY = e.touches[0].clientY; 140 const diff = this.touchCurrentY - this.touchStartY; 141 142 if (diff > 0) { 143 e.preventDefault(); 144 this.$mobileContent.css('transform', `translateY(${diff}px)`); 145 } 146 }, { passive: false }); 147 148 content.addEventListener('touchend', () => { 149 const diff = this.touchCurrentY - this.touchStartY; 150 const threshold = 100; 151 152 if (diff > threshold) { 153 this.closeMobileSheet(); 154 } else { 155 this.$mobileContent.css('transform', 'translateY(0)'); 156 } 157 158 this.touchStartY = 0; 159 this.touchCurrentY = 0; 160 }); 161 } 162 163 setupQrNavigation() { 164 this.$qrImage = this.$modal.find('.cwp-qr-image'); 165 this.$qrTitle = this.$modal.find('.cwp-qr-title'); 166 this.$qrPrev = this.$modal.find('.cwp-qr-prev'); 167 this.$qrNext = this.$modal.find('.cwp-qr-next'); 168 169 this.$qrPrev.on('click', () => this.prevQr()); 170 this.$qrNext.on('click', () => this.nextQr()); 171 172 this.$qrPrev.css({ 173 'opacity': '1', 174 'visibility': 'visible' 175 }); 176 this.$qrNext.css({ 177 'opacity': '1', 178 'visibility': 'visible' 179 }); 180 181 this.updateQrDisplay(); 182 } 183 184 setupScanAnimation() { 185 this.$scanAnimation = this.$modal.find('.cwp-qr-scan-animation'); 186 this.$scanLine = this.$modal.find('.cwp-scan-line'); 187 188 this.startScanAnimation(); 189 } 190 191 startScanAnimation() { 192 if (this.scanInterval) { 193 clearInterval(this.scanInterval); 194 } 195 196 this.scanInterval = setInterval(() => { 197 if (this.isModalOpen && this.$scanAnimation.is(':visible')) { 198 this.showScanAnimation(); 199 } 200 }, 7000); 201 } 202 203 showScanAnimation() { 204 if (this.isScanActive) return; 205 206 this.isScanActive = true; 207 208 this.$scanAnimation.addClass('active'); 209 210 this.$scanLine.css('animation', 'none'); 211 setTimeout(() => { 212 this.$scanLine.css('animation', 'cwp-scan-line 2s ease-in-out'); 213 }, 10); 214 215 setTimeout(() => { 216 this.$scanAnimation.removeClass('active'); 217 this.isScanActive = false; 218 }, 2000); 219 } 220 221 stopScanAnimation() { 222 if (this.scanInterval) { 223 clearInterval(this.scanInterval); 224 this.scanInterval = null; 225 } 226 this.$scanAnimation.removeClass('active'); 227 this.isScanActive = false; 228 } 229 230 applyDesignSettings() { 231 if (this.config.design.modal_bg_color) { 232 this.$modalContent.css('background-color', this.config.design.modal_bg_color); 233 this.$mobileContent.css('background-color', this.config.design.modal_bg_color); 234 } 235 236 if (this.config.texts.title_font_size) { 237 this.$modal.find('.cwp-modal-title').css('font-size', this.config.texts.title_font_size + 'px'); 238 } 239 if (this.config.texts.title_color) { 240 this.$modal.find('.cwp-modal-title').css('color', this.config.texts.title_color); 241 } 242 if (this.config.texts.subtitle_font_size) { 243 this.$modal.find('.cwp-modal-subtitle').css('font-size', this.config.texts.subtitle_font_size + 'px'); 244 } 245 if (this.config.texts.subtitle_color) { 246 this.$modal.find('.cwp-modal-subtitle').css('color', this.config.texts.subtitle_color); 247 } 248 249 if (this.config.texts.title_font_size) { 250 this.$mobileSheet.find('.cwp-mobile-title').css('font-size', this.config.texts.title_font_size + 'px'); 251 } 252 if (this.config.texts.title_color) { 253 this.$mobileSheet.find('.cwp-mobile-title').css('color', this.config.texts.title_color); 254 } 255 if (this.config.texts.subtitle_font_size) { 256 this.$mobileSheet.find('.cwp-mobile-subtitle').css('font-size', this.config.texts.subtitle_font_size + 'px'); 257 } 258 if (this.config.texts.subtitle_color) { 259 this.$mobileSheet.find('.cwp-mobile-subtitle').css('color', this.config.texts.subtitle_color); 260 } 261 262 if (this.config.texts.qr_hint_font_size) { 263 this.$modal.find('.cwp-qr-hint').css('font-size', this.config.texts.qr_hint_font_size + 'px'); 264 } 265 if (this.config.texts.qr_hint_color) { 266 this.$modal.find('.cwp-qr-hint').css('color', this.config.texts.qr_hint_color); 267 } 268 269 if (this.config.texts.footer_text_font_size) { 270 this.$modal.find('.cwp-modal-footer').css('font-size', this.config.texts.footer_text_font_size + 'px'); 271 this.$mobileSheet.find('.cwp-mobile-footer').css('font-size', this.config.texts.footer_text_font_size + 'px'); 272 } 273 if (this.config.texts.footer_text_color) { 274 this.$modal.find('.cwp-modal-footer').css('color', this.config.texts.footer_text_color); 275 this.$mobileSheet.find('.cwp-mobile-footer').css('color', this.config.texts.footer_text_color); 276 } 277 } 278 279 startWidgetAnimation() { 280 if (this.messengerKeys.length <= 1) { 281 this.$widgetIcon.addClass('float'); 282 return; 283 } 284 285 setTimeout(() => { 286 this.animateIconChange(); 287 }, 2000); 288 289 this.widgetInterval = setInterval(() => { 290 this.animateIconChange(); 291 }, 3000); 292 } 293 294 animateIconChange() { 295 if (this.messengerKeys.length <= 1) return; 296 297 if (!this.firstChangeDone) { 298 this.currentMessengerIndex = (this.currentMessengerIndex + 1) % this.messengerKeys.length; 299 this.firstChangeDone = true; 300 } 301 302 const currentMessengerKey = this.messengerKeys[this.currentMessengerIndex]; 303 const messenger = this.config.messengers[currentMessengerKey]; 304 305 this.$widgetIcon.addClass('animate-out'); 306 this.rotateBubble(); 307 308 setTimeout(() => { 309 this.$widgetIcon.html(this.getMessengerSvg(currentMessengerKey, true)); 310 311 if (currentMessengerKey === 'max') { 312 this.createMaxGradientBubble(); 313 } else { 314 this.updateBubbleColor(messenger.color); 315 } 316 317 this.$widgetIcon.removeClass('animate-out').addClass('animate-in'); 318 319 this.currentMessengerIndex = (this.currentMessengerIndex + 1) % this.messengerKeys.length; 320 321 setTimeout(() => { 322 this.$widgetIcon.removeClass('animate-in'); 323 }, 600); 324 325 }, 400); 326 } 327 328 rotateBubble() { 329 this.$widgetBubble.css('transform', 'rotate(-10deg)'); 330 331 setTimeout(() => { 332 this.$widgetBubble.css('transform', 'rotate(0deg)'); 333 }, 200); 334 } 335 336 createMaxGradientBubble() { 337 const newBubbleSvg = ` 338 <svg class="cwp-widget-bubble" viewBox="0 0 16 16" width="60" height="60"> 339 <defs> 340 <linearGradient id="max-gradient" x1="0%" y1="0%" x2="100%" y2="100%"> 341 <stop offset="0%" style="stop-color:#4bc2fd;stop-opacity:1" /> 342 <stop offset="100%" style="stop-color:#8E44AD;stop-opacity:1" /> 343 </linearGradient> 344 </defs> 345 <path d="M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9.06 9.06 0 0 0 8 15z" fill="url(#max-gradient)"/> 346 </svg> 347 `; 348 this.$widgetBubble.replaceWith(newBubbleSvg); 349 this.$widgetBubble = this.$widget.find('.cwp-widget-bubble'); 350 } 351 352 updateBubbleColor(color) { 353 const newBubbleSvg = ` 354 <svg class="cwp-widget-bubble" viewBox="0 0 16 16" width="60" height="60" style="fill: ${color}"> 355 <path d="M8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6-.097 1.016-.417 2.13-.771 2.966-.079.186.074.394.273.362 2.256-.37 3.597-.938 4.18-1.234A9.06 9.06 0 0 0 8 15z"/> 356 </svg> 357 `; 358 this.$widgetBubble.replaceWith(newBubbleSvg); 359 this.$widgetBubble = this.$widget.find('.cwp-widget-bubble'); 360 } 361 362 stopWidgetAnimation() { 363 if (this.widgetInterval) { 364 clearInterval(this.widgetInterval); 365 this.widgetInterval = null; 366 } 367 this.$widgetIcon.removeClass('animate-in animate-out float'); 368 this.firstChangeDone = false; 369 } 370 371 showWidget() { 372 if (this.widgetVisible) return; 373 374 this.widgetVisible = true; 375 this.$widget.addClass('animate').show(); 376 377 setTimeout(() => { 378 this.$widget.addClass('pulse'); 379 this.startWidgetAnimation(); 380 }, 1000); 381 } 382 383 hideWidget() { 384 this.$widget.removeClass('pulse animate'); 385 this.$widget.hide(); 386 this.stopWidgetAnimation(); 387 this.widgetVisible = false; 388 } 389 390 showWidgetAgain() { 391 if (this.widgetVisible) return; 392 393 this.widgetVisible = true; 394 this.$widget.addClass('animate').show(); 395 setTimeout(() => { 396 this.$widget.addClass('pulse'); 397 this.startWidgetAnimation(); 398 }, 1000); 399 } 400 401 openModal() { 402 this.isModalOpen = true; 403 this.$modal.show(); 404 this.$modalContent.hide().fadeIn(300); 405 406 this.updateQrDisplay(); 407 408 this.startScanAnimation(); 409 } 410 411 closeModal() { 412 this.isModalOpen = false; 413 this.stopScanAnimation(); 414 this.$modalContent.fadeOut(300, () => { 415 this.$modal.hide(); 416 this.showWidgetAgain(); 417 }); 418 } 419 420 openMobileSheet() { 421 this.isMobileSheetOpen = true; 422 this.$mobileSheet.show(); 423 424 setTimeout(() => { 425 this.$mobileSheet.addClass('open'); 426 }, 10); 427 428 $('body').css('overflow', 'hidden'); 429 } 430 431 closeMobileSheet() { 432 this.isMobileSheetOpen = false; 433 this.$mobileSheet.removeClass('open'); 434 435 setTimeout(() => { 436 this.$mobileSheet.hide(); 437 this.showWidgetAgain(); 438 }, 300); 439 440 $('body').css('overflow', ''); 441 } 442 443 updateQrDisplay() { 444 if (this.messengerKeys.length === 0) return; 445 446 const currentMessengerKey = this.messengerKeys[this.currentQrIndex]; 447 const messenger = this.config.messengers[currentMessengerKey]; 448 449 if (messenger && messenger.qr_url) { 450 this.$qrImage.attr('src', messenger.qr_url) 451 .attr('alt', `QR код Ð´Ð»Ñ ${messenger.name}`); 452 453 this.$qrTitle.html(`
454 <span class="cwp-qr-title-icon"> 455 ${this.getMessengerSvg(currentMessengerKey, false)} 456 </span> 457 ${messenger.name} 458 `); 459 } 460 461 const showNavigation = this.messengerKeys.length > 1 && !this.config.is_mobile; 462 463 if (showNavigation) { 464 this.$qrPrev.css({ 465 'opacity': '1', 466 'visibility': 'visible', 467 'display': 'flex' 468 }); 469 this.$qrNext.css({ 470 'opacity': '1', 471 'visibility': 'visible', 472 'display': 'flex' 473 }); 474 } else { 475 this.$qrPrev.hide(); 476 this.$qrNext.hide(); 477 } 478 } 479 480 nextQr() { 481 this.currentQrIndex = (this.currentQrIndex + 1) % this.messengerKeys.length; 482 this.updateQrDisplay(); 483 } 484 485 prevQr() { 486 this.currentQrIndex = this.currentQrIndex === 0 ? 487 this.messengerKeys.length - 1 : 488 this.currentQrIndex - 1; 489 this.updateQrDisplay(); 490 } 491 492 getMessengerSvg(messengerKey, forWidget = true) { 493 let fillColor = forWidget ? 'white' : 'currentColor'; 494 495 const svgMap = { 496 'whatsapp': `<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill="${fillColor}" d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893-.001-3.189-1.262-6.209-3.553-8.503"/></svg>`, 497 'telegram': `<svg viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg"><path fill="${fillColor}" d="M41.4193 7.30899C41.4193 7.30899 45.3046 5.79399 44.9808 9.47328C44.8729 10.9883 43.9016 16.2908 43.1461 22.0262L40.5559 39.0159C40.5559 39.0159 40.3401 41.5048 38.3974 41.9377C36.4547 42.3705 33.5408 40.4227 33.0011 39.9898C32.5694 39.6652 24.9068 34.7955 22.2086 32.4148C21.4531 31.7655 20.5897 30.4669 22.3165 28.9519L33.6487 18.1305C34.9438 16.8319 36.2389 13.8019 30.8426 17.4812L15.7331 27.7616C15.7331 27.7616 14.0063 28.8437 10.7686 27.8698L3.75342 25.7055C3.75342 25.7055 1.16321 24.0823 5.58815 22.459C16.3807 17.3729 29.6555 12.1786 41.4193 7.30899Z"/></svg>`, 498 'max': `<svg viewBox="0 0 42 42" xmlns="http://www.w3.org/2000/svg"><path fill="${fillColor}" fill-rule="evenodd" d="M21.47 41.88c-4.11 0-6.02-.6-9.34-3-2.1 2.7-8.75 4.81-9.04 1.2 0-2.71-.6-5-1.28-7.5C1 29.5.08 26.07.08 21.1.08 9.23 9.82.3 21.36.3c11.55 0 20.6 9.37 20.6 20.91a20.6 20.6 0 0 1-20.49 20.67Zm.17-31.32c-5.62-.29-10 3.6-10.97 9.7-.8 5.05.62 11.2 1.83 11.52.58.14 2.04-1.04 2.95-1.95a10.4 10.4 0 0 0 5.08 1.81 10.7 10.7 0 0 0 11.
49819-9.97 10.7 10.7 0 0 0-10.08-11.1Z" clip-rule="evenodd"></path></svg>` 499 }; 500 501 return svgMap[messengerKey] || ''; 502 } 503 } 504 505 $(document).ready(() => { 506 new ChatWidgetPro(); 507 }); 508 509})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.