1/** 2 * Common JS for Header, Support Section, and Footer. 3 * Runs after DOM ready. If sections are injected dynamically (header/support), 4 * listeners are attached when 'header-loaded' or 'support-section-loaded' fires. 5 * All DOM access guarded with null checks. 6 */ 7(function () { 8 'use strict'; 9 10 /** 11 * Wrap "&" in <span class="amp"> for Poppins (.amp in style.css). 12 * Skip svg / script / style / .amp (avoid double-wrap). 13 */ 14 function wrapAmpersandsInRoot(root) { 15 if (!root) return; 16 var walker = document.createTreeWalker( 17 root, 18 NodeFilter.SHOW_TEXT, 19 { 20 acceptNode: function (node) { 21 var p = node.parentNode; 22 if (!p) return NodeFilter.FILTER_REJECT; 23 var tag = p.nodeName.toLowerCase(); 24 if (tag === 'script' || tag === 'style' || tag === 'noscript') return NodeFilter.FILTER_REJECT; 25 if (p.closest && (p.closest('svg') || p.closest('.amp'))) return NodeFilter.FILTER_REJECT; 26 return NodeFilter.FILTER_ACCEPT; 27 } 28 }, 29 false 30 ); 31 var nodes = []; 32 while (walker.nextNode()) { 33 if (/&/.test(walker.currentNode.nodeValue)) nodes.push(walker.currentNode); 34 } 35 for (var i = 0; i < nodes.length; i++) { 36 var n = nodes[i]; 37 var parts = n.nodeValue.split(/(&)/); 38 if (parts.length < 2) continue; 39 var frag = document.createDocumentFragment(); 40 for (var k = 0; k < parts.length; k++) { 41 if (parts[k] === '&') { 42 var span = document.createElement('span'); 43 span.className = 'amp'; 44 span.textContent = '&'; 45 frag.appendChild(span); 46 } else if (parts[k]) { 47 frag.appendChild(document.createTextNode(parts[k])); 48 } 49 } 50 n.parentNode.replaceChild(frag, n); 51 } 52 } 53 54 var ampObserverStarted; 55 var ampWrapRafScheduled = false; 56 57 /** 58 * ä¸ä¸å¹åå è£ï¼æ¸å° typewriter éåè¼¸åºæãå è¦è忬¾ & åè® Poppinsãå éç 59 * ï¼åä»£é· debounceï¼ä»¥åè¦ç 350ms 鿢å å ï¼èç¼å¥½æå¯è¦ºï¼ 60 */ 61 function scheduleAmpWrapNextFrame() { 62 if (ampWrapRafScheduled) return; 63 ampWrapRafScheduled = true; 64 requestAnimationFrame(function () { 65 ampWrapRafScheduled = false; 66 if (document.body) wrapAmpersandsInRoot(document.body); 67 }); 68 } 69 70 function initAmpersandWrapping() { 71 if (!document.body) return; 72 73 wrapAmpersandsInRoot(document.body); 74 75 document.addEventListener( 76 'header-loaded', 77 function () { 78 var bar = document.getElementById('announce-bar'); 79 var header = document.getElementById('site-header'); 80 if (bar) wrapAmpersandsInRoot(bar); 81 if (header) wrapAmpersandsInRoot(header); 82 scheduleAmpWrapNextFrame(); 83 }, 84 false 85 ); 86 87 if (ampObserverStarted) return; 88 ampObserverStarted = true; 89 90 var obs = new MutationObserver(function (mutations) { 91 for (var i = 0; i < mutations.length; i++) { 92 var n = mutations[i].target; 93 var el = n.nodeType === 3 ? n.parentElement : n; 94 if (!el || typeof el.closest !== 'function') { 95 scheduleAmpWrapNextFrame(); 96 return; 97 } 98 if (!el.closest('.js-skip-amp-mutation')) { 99 scheduleAmpWrapNextFrame(); 100 return; 101 } 102 } 103 }); 104 obs.observe(document.body, { subtree: true, childList: true, characterData: true }); 105 } 106 107 /* æ¬æªéå¸¸åº </body> åè¼å ¥ï¼ä¸å·è¡å°±åæ¥å 䏿¬¡ï¼æ©é DOMContentLoaded å¾å è·å inline typewriter */ 108 if (document.body) { 109 wrapAmpersandsInRoot(document.body); 110 } 111 112 window.xlcWrapAmpersandsIn = wrapAmpersandsInRoot; 113 window.xlcWrapAmpersandsAll = function () { 114 wrapAmpersandsInRoot(document.body); 115 }; 116 117 function runWhenReady() { 118 if (document.readyState === 'loading') { 119 document.addEventListener('DOMContentLoaded', runWhenReady); 120 return; 121 } 122 initAmpersandWrapping(); 123 initScrollToTop(); 124 initAnnounceBar(); 125 initNavbar(); 126 initSupportTypewriter(); 127 initRevealOnScroll();
128 // Defer so this runs after WHMCS binds .copy-to-clipboard to the original copy fn 129 setTimeout(enhanceClipboardCopy, 0); 130 } 131 132 /* ââ Scroll to Topï¼ç´ 40% æ²å徿·¡å ¥ï¼æéå¯è½é¨ footer åæ è¼å ¥ï¼ ââ */ 133 function initScrollToTop() { 134 var btn = document.getElementById('scroll-to-top'); 135 if (!btn) { 136 document.addEventListener('footer-loaded', initScrollToTop, { once: true }); 137 return; 138 } 139 var threshold = 0.4; 140 function update() { 141 var maxScroll = document.documentElement.scrollHeight - window.innerHeight; 142 var show = maxScroll > 100 && window.scrollY >= maxScroll * threshold; 143 btn.classList.toggle('scroll-to-top--visible', show); 144 } 145 window.addEventListener('scroll', function () { update(); }, { passive: true }); 146 window.addEventListener('resize', function () { update(); }); 147 btn.addEventListener('click', function () { 148 window.scrollTo({ top: 0, behavior: 'smooth' }); 149 }); 150 update(); 151 } 152 153 /* ââ Announcement bar dismiss (runs when header is ready) ââ */ 154 function initAnnounceBar() { 155 var bar = document.getElementById('announce-bar'); 156 var closeBtn = document.getElementById('announce-close'); 157 if (!bar || !closeBtn) { 158 document.addEventListener('header-loaded', initAnnounceBar, { once: true }); 159 return; 160 } 161 if (sessionStorage.getItem('xlc-announce-dismissed') === '1') { 162 bar.style.display = 'none'; 163 return; 164 } 165 closeBtn.addEventListener('click', function () { 166 bar.style.overflow = 'hidden'; 167 bar.style.maxHeight = bar.offsetHeight + 'px'; 168 bar.offsetHeight; 169 bar.style.transition = 'max-height 0.35s ease, opacity 0.3s ease, padding 0.3s ease'; 170 bar.style.maxHeight = '0'; 171 bar.style.opacity = '0'; 172 bar.style.paddingTop = '0'; 173 bar.style.paddingBottom = '0'; 174 setTimeout(function () { 175 if (bar) bar.style.display = 'none'; 176 }, 360); 177 sessionStorage.setItem('xlc-announce-dismissed', '1'); 178 }); 179 } 180 181 /* ââ Navbar: scroll shadow + mobile drawer + accordion ââ */ 182 function initNavbar() { 183 var header = document.getElementById('site-header'); 184 var hamburger = header ? header.querySelector('.navbar__hamburger') : null; 185 var overlay = document.getElementById('nav-overlay'); 186 if (!header) { 187 document.addEventListener('header-loaded', initNavbar, { once: true }); 188 return; 189 } 190 191 function closeNav() { 192 if (!header) return; 193 header.classList.remove('nav-open'); 194 if (hamburger) hamburger.setAttribute('aria-expanded', 'false'); 195 document.body.style.overflow = ''; 196 if (overlay) overlay.classList.remove('nav-overlay--visible'); 197 var openItems = header.querySelectorAll('.navbar__item--open'); 198 if (openItems.length) { 199 openItems.forEach(function (el) { 200 el.classList.remove('navbar__item--open'); 201 }); 202 } 203 } 204 205 var onScroll = function () { 206 if (header) header.classList.toggle('scrolled', window.scrollY > 8); 207 }; 208 window.addEventListener('scroll', onScroll, { passive: true }); 209 onScroll(); 210 211 if (hamburger && header) { 212 hamburger.addEventListener('click', function () { 213 var isOpen = header.classList.toggle('nav-open'); 214 hamburger.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); 215 document.body.style.overflow = isOpen ? 'hidden' : ''; 216 if (overlay) overlay.classList.toggle('nav-overlay--visible', isOpen); 217 if (!isOpen) { 218 var openItems = header.querySelectorAll('.navbar__item--open'); 219 if (openItems.length) openItems.forEach(function (el) { el.classList.remove('navbar__item--open'); }); 220 } 221 }); 222 } 223 224 var dropdownLinks = header.querySelectorAll('.navbar__item--has-dropdown .navbar__link'); 225 if (dropdownLinks.length) { 226 dropdownLinks.forEach(function (link) { 227 link.addEventListener('click', function (e) { 228 e.preventDefault(); 229 if (window.innerWidth > 1200) return; 230 var item = this.closest('.navbar__item'); 231 if (!item) return; 232 var isOpen = item.classList.contains('navbar__item--open'); 233 var openItems = header.querySelectorAll('.navbar__item--open');
234 if (openItems.length) openItems.forEach(function (el) { el.classList.remove('navbar__item--open'); }); 235 if (!isOpen) item.classList.add('navbar__item--open'); 236 }); 237 }); 238 } 239 240 var navCloseLinks = header.querySelectorAll('.navbar__dropdown-link, .navbar__mobile-cta .btn'); 241 if (navCloseLinks.length) { 242 navCloseLinks.forEach(function (el) { 243 el.addEventListener('click', closeNav); 244 }); 245 } 246 247 if (overlay) overlay.addEventListener('click', closeNav); 248 249 document.addEventListener('click', function (e) { 250 if (header && header.classList.contains('nav-open') && 251 !header.contains(e.target) && e.target !== overlay) { 252 closeNav(); 253 } 254 }); 255 256 document.addEventListener('keydown', function (e) { 257 if (e.key === 'Escape' && header && header.classList.contains('nav-open')) { 258 closeNav(); 259 } 260 }); 261 } 262 263 /* ââ Support email typewriter (runs when support-section is ready) ââ */ 264 function initSupportTypewriter() { 265 var textEl = document.getElementById('support-email-typewriter'); 266 var cursorEl = document.querySelector('.support__email-cursor'); 267 if (!textEl || !cursorEl) { 268 document.addEventListener('support-section-loaded', initSupportTypewriter, { once: true }); 269 return; 270 } 271 var fullText = textEl.getAttribute('data-text') || ''; 272 var speed = 80; 273 274 function run() { 275 if (!textEl || !cursorEl) return; 276 textEl.textContent = ''; 277 cursorEl.classList.add('support__email-cursor--on'); 278 var i = 0; 279 (function tick() { 280 if (i < fullText.length && textEl) { 281 textEl.textContent += fullText.charAt(i); 282 i++; 283 setTimeout(tick, speed); 284 } 285 })(); 286 } 287 288 var section = document.querySelector('.support-section'); 289 if (section && 'IntersectionObserver' in window) { 290 var obs = new IntersectionObserver(function (entries) { 291 if (entries[0] && entries[0].isIntersecting) { 292 run(); 293 obs.disconnect(); 294 } 295 }, { threshold: 0.2 }); 296 obs.observe(section); 297 } else { 298 setTimeout(run, 1200); 299 } 300 } 301 302 /* ââ Reusable Scroll Reveal; re-run on support-section-loaded for injected .reveal-on-scroll ââ */ 303 function initRevealOnScroll() { 304 var observer = new IntersectionObserver( 305 function (entries) { 306 for (var i = 0; i < entries.length; i++) { 307 if (entries[i].isIntersecting) { 308 var el = entries[i].target; 309 if (!el) continue; 310 var delay = el.dataset.revealDelay; 311 if (delay) { 312 setTimeout(function (target) { 313 if (target) target.classList.add('reveal-on-scroll--in-view'); 314 }, parseInt(delay, 10), el); 315 } else { 316 el.classList.add('reveal-on-scroll--in-view'); 317 } 318 observer.unobserve(el); 319 } 320 } 321 }, 322 { rootMargin: '0px 0px -60px 0px', threshold: 0.1 } 323 ); 324 325 function observeRevealElements() { 326 var els = document.querySelectorAll('.reveal-on-scroll'); 327 for (var j = 0; j < els.length; j++) { 328 if (els[j].dataset.revealObserved === '1') continue; 329 els[j].dataset.revealObserved = '1'; 330 observer.observe(els[j]); 331 } 332 } 333 334 observeRevealElements(); 335 document.addEventListener('support-section-loaded', observeRevealElements); 336 } 337 338 /** 339 * WHMCS clipboard.copy only supports data-clipboard-target (input value). 340 * Product details copy chips use data-clipboard-text â extend copy to handle both, 341 * then rebind so already-attached click handlers pick up the new function. 342 */ 343 function enhanceClipboardCopy() { 344 if (!window.jQuery || !window.WHMCS || !WHMCS.ui || !WHMCS.ui.clipboard) return; 345 var $ = window.jQuery; 346 var oldCopy = WHMCS.ui.clipboard.copy; 347 var newCopy = function (e) { 348 e.preventDefault(); 349 var trigger = $(e.currentTarget); 350 var contentElement = trigger.data('clipboard-target'); 351 var clipboardText = trigger.attr('data-clipboard-text'); 352 var tempElement; 353 var container;
354 try { 355 if (clipboardText) { 356 container = $('body'); 357 tempElement = $('<textarea>') 358 .css({ position: 'fixed', opacity: '0', width: '1px', height: '1px' }) 359 .val(clipboardText); 360 } else { 361 container = $(contentElement).parent(); 362 tempElement = $('<textarea>') 363 .css({ position: 'fixed', opacity: '0', width: '1px', height: '1px' }) 364 .val($(contentElement).val()); 365 } 366 container.append(tempElement); 367 tempElement.focus().select(); 368 document.execCommand('copy'); 369 } finally { 370 if (tempElement) tempElement.remove(); 371 } 372 trigger.tooltip({ trigger: 'click', placement: 'bottom' }); 373 WHMCS.ui.toolTip.setTip(trigger, 'Copied!'); 374 WHMCS.ui.toolTip.hideTip(trigger); 375 }; 376 WHMCS.ui.clipboard.copy = newCopy; 377 $('.copy-to-clipboard').each(function () { 378 $(this).off('click', oldCopy).on('click', newCopy); 379 }); 380 } 381 382 runWhenReady(); 383})();
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.