1/** 2 * Tally Chat Widget â Options-only UI (floating pills, no chat window) 3 * 4 * Usage: 5 * <link rel="stylesheet" href="chat-widget.css"> 6 * <script src="chat-widget.js" data-auto-init></script> 7 * 8 * Or manual: 9 * TallyChatWidget.init({ 10 * delay: 4000, 11 * onVideoChat: function () { }, 12 * onExpertChat: function () { }, 13 * onAiChat: function () { } 14 * }); 15 */ 16(function () { 17 'use strict'; 18 19 var STORAGE_KEY = 'tally_chat_prompt_dismissed'; 20 21 var TALLY_LOGO = 'https://tallysolutions.store/cdn/shop/files/tally-logo-black.svg?v=1780031137&width=120'; 22 var WHATSAPP_ICON = 'https://resources.tallysolutions.com/wp-content/themes/tally/assets/images/icons/whats-app-icon.svg'; 23 24 var defaultConfig = { 25 delay: 120000, 26 // Empty = show on all pages (no restrictions) 27 showOnPages: [], 28 headerTitle: 'Chat with us', 29 headerSubtitle: 'Online', 30 greeting: 'Need help choosing the right plan? Pick how you\'d like to get assistance.', 31 subtitle: '', 32 logoUrl: TALLY_LOGO, 33 poweredBy: 'Orbitel', 34 options: [ 35 { 36 id: 'video', 37 label: 'Video Chat', 38 description: 'Connect with a sales expert for buying guidance.', 39 action: 'video-chat', 40 className: 'tally-chat-prompt__option--video' 41 }, 42 { 43 id: 'whatsapp', 44 label: 'WhatsApp Support', 45 description: 'Get help with technical and product support.', 46 action: 'whatsapp', 47 className: 'tally-chat-prompt__option--expert' 48 }, 49 { 50 id: 'ai', 51 label: '24Ã7 Chat', 52 description: 'Instant answers to product and pricing questions.', 53 action: 'ai-chat', 54 className: 'tally-chat-prompt__option--ai' 55 } 56 ], 57 // Per-option click callbacks (pass these in init) 58 onVideoChat: null, 59 onExpertChat: null, 60 onAiChat: null, 61 // Called for any option after the specific handler above 62 onSelect: null, 63 onDismiss: null, 64 // Default: options-only â 3 floating pills, no chat window 65 variant: 'options-only' 66 }; 67 68 function createSVGElement(paths, viewBox) { 69 var ns = 'http://www.w3.org/2000/svg'; 70 var svg = document.createElementNS(ns, 'svg'); 71 svg.setAttribute('viewBox', viewBox || '0 0 24 24'); 72 svg.setAttribute('fill', 'none'); 73 svg.setAttribute('stroke', 'currentColor'); 74 svg.setAttribute('stroke-width', '2'); 75 svg.setAttribute('stroke-linecap', 'round'); 76 svg.setAttribute('stroke-linejoin', 'round'); 77 svg.setAttribute('aria-hidden', 'true'); 78 paths.forEach(function (d) { 79 var path = document.createElementNS(ns, 'path'); 80 path.setAttribute('d', d); 81 svg.appendChild(path); 82 }); 83 return svg; 84 } 85 86 function createImageIcon(src, alt) { 87 var img = document.createElement('img'); 88 img.src = src; 89 img.alt = alt || ''; 90 img.setAttribute('aria-hidden', 'true'); 91 img.setAttribute('style', 'width: 2rem;'); 92 return img; 93 } 94 95 var icons = { 96 tally: createSVGElement([ 97 'M12 2L2 7l10 5 10-5-10-5z', 98 'M2 17l10 5 10-5', 99 'M2 12l10 5 10-5' 100 ]), 101 close: createSVGElement(['M18 6L6 18', 'M6 6l12 12']), 102 video: createSVGElement([ 103 'M23 7l-7 5 7 5V7z', 104 'M14 5H3a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2z' 105 ]), 106 whatsapp: createImageIcon(WHATSAPP_ICON, 'WhatsApp'), 107 ai: createSVGElement([ 108 'M12 2a4 4 0 0 1 4 4c0 1.5-.8 2.8-2 3.5V12h4a2 2 0 0 1 2 2v1a2 2 0 0 1-2 2h-1v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2H9a2 2 0 0 1-2-2v-1a2 2 0 0 1 2-2h4V9.5A4 4 0 0 1 12 2z' 109 ]), 110 chat: createSVGElement([ 111 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z' 112 ]), 113 arrow: createSVGElement(['M9 18l6-6-6-6']) 114 }; 115 116 function shouldShowOnPage(config) { 117 var pages = config.showOnPages || [];
118 // No list (or empty) â show on every page 119 if (!pages.length) return true; 120 var path = window.location.pathname.toLowerCase(); 121 return pages.some(function (segment) { 122 return path.indexOf(String(segment).toLowerCase()) !== -1; 123 }); 124 } 125 126 function wasDismissed() { 127 try { 128 return sessionStorage.getItem(STORAGE_KEY) === '1'; 129 } catch (e) { 130 return false; 131 } 132 } 133 134 function markDismissed() { 135 try { 136 sessionStorage.setItem(STORAGE_KEY, '1'); 137 } catch (e) { /* ignore */ } 138 } 139 140 function handleOptionClick(config, opt) { 141 142 var namedHandlers = { 143 video: config.onVideoChat, 144 expert: config.onExpertChat, 145 ai: config.onAiChat, 146 'video-chat': config.onVideoChat, 147 'whatsapp': config.onExpertChat, 148 'ai-chat': config.onAiChat 149 }; 150 var named = namedHandlers[opt.id] || namedHandlers[opt.action]; 151 if (typeof named === 'function') { 152 named(opt); 153 } 154 155 if (typeof config.onSelect === 'function') { 156 config.onSelect(opt); 157 } else if ( 158 typeof opt.onClick !== 'function' && 159 typeof named !== 'function' 160 ) { 161 if(opt.action == "video-chat"){ 162 Popin('open'); 163 } 164 if(opt.action == "ai-chat"){ 165 Popin('close'); 166 openOrbitelChat(); 167 } 168 if(opt.action == "whatsapp"){ 169 Popin('close'); 170 window.open("https://api.whatsapp.com/send/?phone=919019910043&text=Hi&type=phone_number&app_absent=0", "_blank"); 171 } 172 } 173 } 174 175 /** 176 * iOS-friendly tap binding. 177 * Uses pointer events when available; otherwise touchend + click 178 * with a short lock to avoid double-firing. Cancels if the finger moves 179 * (scroll) more than TAP_MOVE_THRESHOLD. 180 */ 181 var TAP_MOVE_THRESHOLD = 10; 182 183 function bindTap(el, handler) { 184 if (!el || typeof handler !== 'function') return; 185 186 var locked = false; 187 var touchStartX = 0; 188 var touchStartY = 0; 189 var moved = false; 190 191 function run(e) { 192 if (locked) return; 193 locked = true; 194 if (e && e.cancelable) e.preventDefault(); 195 try { 196 handler(e); 197 } finally { 198 setTimeout(function () { 199 locked = false; 200 }, 400); 201 } 202 } 203 204 if (window.PointerEvent) { 205 el.addEventListener('pointerup', function (e) { 206 if (e.pointerType === 'mouse' && e.button !== 0) return; 207 if (moved) { 208 moved = false; 209 return; 210 } 211 run(e); 212 }); 213 el.addEventListener('pointerdown', function (e) { 214 moved = false; 215 touchStartX = e.clientX; 216 touchStartY = e.clientY; 217 }); 218 el.addEventListener('pointermove', function (e) { 219 if ( 220 Math.abs(e.clientX - touchStartX) > TAP_MOVE_THRESHOLD || 221 Math.abs(e.clientY - touchStartY) > TAP_MOVE_THRESHOLD 222 ) { 223 moved = true; 224 } 225 }); 226 // Fallback click for older Safari / keyboard activate 227 el.addEventListener('click', function (e) { 228 if (!locked) run(e); 229 }); 230 return; 231 } 232 233 el.addEventListener('touchstart', function (e) { 234 moved = false; 235 var t = e.touches && e.touches[0]; 236 if (t) { 237 touchStartX = t.clientX; 238 touchStartY = t.clientY; 239 } 240 }, { passive: true }); 241 242 el.addEventListener('touchmove', function (e) { 243 var t = e.touches && e.touches[0]; 244 if (!t) return; 245 if ( 246 Math.abs(t.clientX - touchStartX) > TAP_MOVE_THRESHOLD || 247 Math.abs(t.clientY - touchStartY) > TAP_MOVE_THRESHOLD 248 ) { 249 moved = true; 250 } 251 }, { passive: true }); 252 253 el.addEventListener('touchend', function (e) { 254 if (moved) { 255 moved = false; 256 return; 257 } 258 run(e); 259 }, { passive: false }); 260 261 el.addEventListener('click', function (e) { 262 if (!locked) run(e); 263 }); 264 } 265 266 function buildWidget(config) { 267 var widget = document.createElement('div'); 268 widget.className = 'tally-chat-widget'; 269 if (config.variant) { 270 widget.classList.add('tally-chat-widget--' + config.variant); 271 } 272 widget.setAttribute('role', 'complementary'); 273 widget.setAttribute('aria-label', 'Tally support chat'); 274 275 /* Prompt card */ 276 var prompt = document.createElement('div'); 277 prompt.className = 'tally-chat-prompt'; 278 prompt.setAttribute('role', 'dialog'); 279 prompt.setAttribute('aria-labelledby', 'tally-chat-prompt-title'); 280 prompt.setAttribute('aria-describedby', 'tally-chat-prompt-greeting'); 281 282 var header = document.createElement('div'); 283 header.className = 'tally-chat-prompt__header'; 284 285 var avatar = document.createElement('div'); 286 avatar.className = 'tally-chat-prompt__avatar'; 287 var logoImg = document.createElement('img'); 288 logoImg.src = config.logoUrl; 289 logoImg.alt = 'Tally'; 290 avatar.appendChild(logoImg); 291 292 var titleWrap = document.createElement('div'); 293 titleWrap.className = 'tally-chat-prompt__title-wrap'; 294 295 var title = document.createElement('h3'); 296 title.className = 'tally-chat-prompt__title'; 297 title.id = 'tally-chat-prompt-title'; 298 title.textContent = config.headerTitle; 299 300 var subtitle = document.createElement('p'); 301 subtitle.className = 'tally-chat-prompt__subtitle'; 302 subtitle.id = 'tally-chat-prompt-subtitle'; 303 subtitle.textContent = config.headerSubtitle; 304 305 titleWrap.appendChild(title); 306 titleWrap.appendChild(subtitle); 307 308 var closeBtn = document.createElement('button'); 309 closeBtn.className = 'tally-chat-prompt__close'; 310 closeBtn.setAttribute('aria-label', 'Dismiss'); 311 closeBtn.appendChild(icons.close.cloneNode(true)); 312 313 header.appendChild(avatar); 314 header.appendChild(titleWrap); 315 header.appendChild(closeBtn); 316 317 var body = document.createElement('div'); 318 body.className = 'tally-chat-prompt__body'; 319 320 var message = document.createElement('div'); 321 message.className = 'tally-chat-prompt__message'; 322 323 var msgAvatar = document.createElement('div'); 324 msgAvatar.className = 'tally-chat-prompt__msg-avatar'; 325 var msgLogo = document.createElement('img'); 326 msgLogo.src = config.logoUrl; 327 msgLogo.alt = ''; 328 msgAvatar.appendChild(msgLogo); 329 330 var bubble = document.createElement('p'); 331 bubble.className = 'tally-chat-prompt__bubble'; 332 bubble.id = 'tally-chat-prompt-greeting'; 333 bubble.textContent = config.greeting; 334 335 message.appendChild(msgAvatar); 336 message.appendChild(bubble); 337 body.appendChild(message); 338 339 var optionsList = document.createElement('div'); 340 optionsList.className = 'tally-chat-prompt__options'; 341 optionsList.setAttribute('role', 'list'); 342 343 var optionIcons = { video: icons.video, whatsapp: icons.whatsapp, ai: icons.ai }; 344 345 config.options.forEach(function (opt) { 346 var btn = document.createElement('button'); 347 btn.className = 'tally-chat-prompt__option ' + (opt.className || ''); 348 btn.setAttribute('role', 'listitem'); 349 btn.setAttribute('data-action', opt.action); 350 btn.setAttribute('type', 'button'); 351 352 var iconWrap = document.createElement('span'); 353 iconWrap.className = 'tally-chat-prompt__option-icon'; 354 var iconKey = opt.id; 355 if (optionIcons[iconKey]) { 356 iconWrap.appendChild(optionIcons[iconKey].cloneNode(true)); 357 } 358 359 var textWrap = document.createElement('span'); 360 textWrap.className = 'tally-chat-prompt__option-text'; 361 362 var label = document.createElement('span'); 363 label.className = 'tally-chat-prompt__option-label'; 364 label.textContent = opt.label; 365 366 var desc = document.createElement('span'); 367 desc.className = 'tally-chat-prompt__option-desc'; 368 desc.textContent = opt.description; 369 370 textWrap.appendChild(label); 371 textWrap.appendChild(desc); 372 373 var arrow = document.createElement('span'); 374 arrow.className = 'tally-chat-prompt__option-arrow'; 375 arrow.appendChild(icons.arrow.cloneNode(true)); 376 377 btn.appendChild(iconWrap); 378 btn.appendChild(textWrap); 379 btn.appendChild(arrow); 380 381 // Enable iOS clickability on button children 382 btn.style.cursor = 'pointer'; 383 btn.style.webkitTapHighlightColor = 'transparent'; 384 btn.style.touchAction = 'manipulation'; 385 386 bindTap(btn, function () { 387 hidePrompt(prompt, true, widget); 388 handleOptionClick(config, opt); 389 }); 390 391 optionsList.appendChild(btn); 392 });
393 394 var footer = document.createElement('div'); 395 footer.className = 'tally-chat-prompt__footer'; 396 var footerText = document.createElement('p'); 397 footerText.className = 'tally-chat-prompt__footer-text'; 398 footerText.innerHTML = 'Powered by <strong>' + config.poweredBy + '</strong>'; 399 footer.appendChild(footerText); 400 401 body.appendChild(optionsList); 402 prompt.appendChild(header); 403 prompt.appendChild(body); 404 prompt.appendChild(footer); 405 406 /* Launcher */ 407 var launcher = document.createElement('button'); 408 launcher.className = 'tally-chat-launcher has-pulse'; 409 launcher.setAttribute('aria-label', 'Open Tally support chat'); 410 launcher.setAttribute('type', 'button'); 411 412 var chatIcon = icons.chat.cloneNode(true); 413 chatIcon.classList.add('tally-chat-launcher__icon-chat'); 414 var closeIcon = icons.close.cloneNode(true); 415 closeIcon.classList.add('tally-chat-launcher__icon-close'); 416 417 launcher.appendChild(chatIcon); 418 launcher.appendChild(closeIcon); 419 420 widget.appendChild(prompt); 421 widget.appendChild(launcher); 422 423 /* Events â iOS-compatible taps */ 424 closeBtn.style.cursor = 'pointer'; 425 closeBtn.style.webkitTapHighlightColor = 'transparent'; 426 closeBtn.style.touchAction = 'manipulation'; 427 bindTap(closeBtn, function () { 428 hidePrompt(prompt, true, widget); 429 if (typeof config.onDismiss === 'function') config.onDismiss(); 430 }); 431 432 launcher.style.cursor = 'pointer'; 433 launcher.style.webkitTapHighlightColor = 'transparent'; 434 launcher.style.touchAction = 'manipulation'; 435 bindTap(launcher, function () { 436 var isVisible = prompt.classList.contains('is-visible'); 437 if (isVisible) { 438 hidePrompt(prompt, false, widget); 439 } else { 440 Popin('close'); 441 showPrompt(prompt, launcher, widget); 442 } 443 }); 444 445 document.addEventListener('keydown', function (e) { 446 if (e.key === 'Escape' && prompt.classList.contains('is-visible')) { 447 hidePrompt(prompt, true, widget); 448 } 449 }); 450 451 return { widget: widget, prompt: prompt, launcher: launcher }; 452 } 453 454 function showPrompt(prompt, launcher, widget) { 455 prompt.classList.remove('is-minimized'); 456 prompt.classList.add('is-visible'); 457 launcher.classList.remove('has-pulse'); 458 launcher.setAttribute('aria-expanded', 'true'); 459 if (widget) widget.classList.add('is-open'); 460 } 461 462 function hidePrompt(prompt, dismiss, widget) { 463 prompt.classList.remove('is-visible'); 464 prompt.classList.add('is-minimized'); 465 if (widget) widget.classList.remove('is-open'); 466 if (dismiss) markDismissed(); 467 } 468 469 function init(userConfig) { 470 userConfig = userConfig || {}; 471 var config = Object.assign({}, defaultConfig, userConfig); 472 var forceShow = !!userConfig.forceShow; 473 474 if (!shouldShowOnPage(config) && !forceShow) { 475 return null; 476 } 477 478 var existing = document.querySelector('.tally-chat-widget'); 479 if (existing) existing.remove(); 480 481 var built = buildWidget(config); 482 document.body.appendChild(built.widget); 483 484 if (!wasDismissed() || forceShow) { 485 setTimeout(function () { 486 showPrompt(built.prompt, built.launcher, built.widget); 487 }, config.delay); 488 } 489 490 return built; 491 } 492 493 window.TallyChatWidget = { 494 init: init, 495 defaultConfig: defaultConfig, 496 hidePrompt: function () { 497 var prompt = document.querySelector('.tally-chat-prompt'); 498 var widget = document.querySelector('.tally-chat-widget'); 499 if (prompt) hidePrompt(prompt, true, widget); 500 } 501 }; 502 503 if (document.currentScript && document.currentScript.hasAttribute('data-auto-init')) { 504 if (document.readyState === 'loading') { 505 document.addEventListener('DOMContentLoaded', function () { init(); }); 506 } else { 507 init(); 508 } 509 } 510})();
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.