1var last_recap_id; 2 3// $(window).scroll(function(){ 4// var scrollPos = $(document).scrollTop(); 5// if(scrollPos > 0) 6// $('body').addClass('scrolling'); 7// else 8// $('body').removeClass('scrolling'); 9// }); 10 11$(window).on("load resize scroll", function() { 12 var scrollPos = $(document).scrollTop(); 13 if(scrollPos > 0) 14 $('body').addClass('scrolling'); 15 else 16 $('body').removeClass('scrolling'); 17}); 18 19 20document.querySelectorAll('li').forEach(function(li) { 21 var child = li.querySelector('span[style*="color"]'); 22 if (child) { 23 li.style.color = window.getComputedStyle(child).color; 24 } 25}); 26 27$(function() { 28 29 $('a[href^="#"]').on('click', function(e) { 30 e.preventDefault(); 31 var target = $(this.hash); 32 if (target.length) { 33 $('html, body').animate({ 34 scrollTop: target.offset().top - 80 // Adjust 80 to your header's height 35 }, 800); // Animation duration in ms 36 } 37 }); 38 39 // $('section .accordion .question .title').on('click', function(e) { 40 // e.preventDefault(); 41 // $(this).parent().toggleClass('open'); 42 // }); 43 44 $('.language a').on('click', function(e) { 45 var language_domain = $(this).attr('data-url'); 46 var url = new URL(window.location.href); 47 var new_url = url.protocol+'//'+language_domain+url.pathname; 48 window.location.href = new_url; 49 }); 50 51 let $marquee = $('.marquee3k'); 52 if($marquee.length > 0) 53 { 54 Marquee3k.init({ 55 selector: 'marquee3k', 56 }); 57 } 58 59 // $('section.block_type_faqs .question .title').on('click', function(e) { 60 // e.preventDefault(); 61 // $(this).parent().toggleClass('open'); 62 // }); 63 function cleanMenuLink($link) { 64 $link = $link.clone(); 65 $link.removeAttr('class'); 66 return $link; 67 } 68 69 function buildMmenuList($container) { 70 var $list = $('<ul/>'); 71 72 $container.children().each(function() { 73 var $item = $(this); 74 75 if ($item.is('li')) { 76 var $link = $item.children('a').first(); 77 var $dropdown = $item.children('.navbar-dropdown, ul').first(); 78 79 if ($item.hasClass('has-dropdown') || $dropdown.length) { 80 var $li = $('<li/>'); 81 if ($link.length) { 82 $li.append(cleanMenuLink($link)); 83 } 84 85 if ($dropdown.length) { 86 var $subList = buildMmenuList($dropdown); 87 if ($subList.children().length) { 88 $li.append($subList); 89 } 90 } 91 92 $list.append($li); 93 return; 94 } 95 96 if ($link.length) { 97 var $simple = $('<li/>'); 98 $simple.append(cleanMenuLink($link)); 99 $list.append($simple); 100 return; 101 } 102 } 103 104 if ($item.is('a')) { 105 var $li = $('<li/>'); 106 $li.append(cleanMenuLink($item)); 107 $list.append($li); 108 return; 109 } 110 111 // Fallback for any unexpected structure 112 if ($item.children().length) { 113 var $fallback = $('<li/>').append($item.clone().removeAttr('class')); 114 $list.append($fallback); 115 } 116 }); 117 118 return $list; 119 } 120 121 var $menu = $('#mmenu').clone(); 122 var $sourceList = $menu.children('ul').first(); 123 if ($sourceList.length) { 124 var $processedList = buildMmenuList($sourceList); 125 $sourceList.replaceWith($processedList); 126 } 127 128 $menu.find('[id]').removeAttr('id'); 129 $menu.attr('id', 'menu'); 130 // The #mmenu source carries hiding utility classes (is-hidden = display:none 131 // !important); strip them from the clone or the off-canvas menu stays hidden 132 // even when opened (mm-opened). 133 $menu.removeClass('is-hidden navbar-nav'); 134 $menu.prependTo('body'); 135 $menu.mmenu({ 136 slidingSubmenus: false, 137 navbar: { 138 add: false 139 }, 140 onClick : { 141 close : true, 142 preventDefault : false, 143 } 144 }, {
145 // configuration 146 offCanvas: { 147 pageSelector: ".outerwrapper" 148 }, 149 clone: false 150 }); 151 $menu.find('.mm-title').attr('href', 'javascript:;'); 152 // $('[href^="#popup_"]').on('click', function(e) { 153 // e.preventDefault(); 154 // var popupId = $(this).attr('href'); 155 // $('.modal[data-popup="'+popupId+'"]').addClass('is-active'); 156 // $('html').addClass('is-clipped'); 157 // }); 158 159 $('.mag-gallery').each(function(u, v) { 160 $(this).magnificPopup({ 161 delegate: 'a', 162 type: 'image', 163 mainClass: 'mfp-img-mobile', 164 gallery: { 165 enabled: true, 166 navigateByImgClick: true, 167 preload: [0,1] // Will preload 0 - before current, and 1 after the current image 168 }, 169 image: { 170 tError: '<a href="%url%">The image #%curr%</a> could not be loaded.', 171 titleSrc: function(item) { 172 return item.el.attr('title'); 173 } 174 }, 175 callbacks: { 176 open: function() { 177 $('body').hasClass('popupOpen'); 178 }, 179 close: function() { 180 $('body').removeClass('popupOpen'); 181 } 182 } 183 }); 184 }); 185 186 $('.js-modal-close').on('click', function(e) { 187 e.preventDefault(); 188 $('html').removeClass('is-clipped'); 189 var $modal = $(this).closest('.modal'); 190 if ($modal.length) { 191 $modal.removeClass('is-active'); 192 } else { 193 $(this).parents('.modal.is-active').first().removeClass('is-active'); 194 } 195 196 if ($('.modal.is-active').length) { 197 $('html').addClass('is-clipped'); 198 } 199 }); 200 201 if ($('.modal.is-active').length) { 202 $('html').addClass('is-clipped'); 203 } 204 205 $(document).on('click', '.js-team-modal, .js-board-member-modal', function(e) { 206 e.preventDefault(); 207 var target = $(this).data('target'); 208 if (!target) { 209 return; 210 } 211 var $modal = $('#' + target); 212 if ($modal.length === 0) { 213 return; 214 } 215 $modal.addClass('is-active'); 216 $('html').addClass('is-clipped'); 217 }); 218 219 AOS.init({ 220 offset: -100, 221 once: true, 222 duration: 1000, 223 easing: 'ease-in-out', 224 delay: 100, 225 }); 226 227 initTwentyTwenty(); 228 229 function setFormSubmitting($form, isSubmitting) { 230 var $buttons = $form.find('button[type="submit"], input[type="submit"], .field_type_button button'); 231 if ($buttons.length === 0) { 232 return; 233 } 234 235 $buttons.each(function() { 236 var $button = $(this); 237 238 if (!$button.data('original-state')) { 239 $button.data('original-state', { 240 html: $button.is('button') ? $button.html() : null, 241 value: $button.is('input') ? $button.val() : null 242 }); 243 } 244 245 if (isSubmitting) { 246 $button.prop('disabled', true).addClass('is-loading').attr('aria-busy', 'true'); 247 if ($button.is('button')) { 248 $button.html('Sending...'); 249 } else { 250 $button.val('Sending...'); 251 } 252 } else { 253 var originalState = $button.data('original-state') || {}; 254 $button.prop('disabled', false).removeClass('is-loading').removeAttr('aria-busy'); 255 if ($button.is('button') && typeof originalState.html === 'string') { 256 $button.html(originalState.html); 257 } else if ($button.is('input') && typeof originalState.value === 'string') { 258 $button.val(originalState.value); 259 } 260 } 261 }); 262 } 263 264 $('.recaptcha_form').each(function() { 265 var $form = $(this); 266 var widgetID = $form.data('widgetid'); 267 268 $form.data('turnstileState', { 269 widgetId: widgetID, 270 pendingSubmit: false 271 }); 272 273 $form.on('submit', function(event) { 274 var state = $form.data('turnstileState') || {}; 275 if (state.submitting) { 276 return true; 277 } 278 279 event.preventDefault(); 280 setFormSubmitting($form, true); 281 state.submitting = true; 282 $form.data('turnstileState', state); 283 284 if (typeof turnstile === 'undefined' || !state.widgetId) { 285 state.pendingSubmit = true; 286 state.submitting = false;
287 $form.data('turnstileState', state); 288 return false; 289 } 290 291 var token = turnstile.getResponse(state.widgetId); 292 if (!token) { 293 state.pendingSubmit = true; 294 state.submitting = false; 295 $form.data('turnstileState', state); 296 return false; 297 } 298 299 $form.find('input[name="cf-turnstile-response"]').val(token); 300 state.pendingSubmit = false; 301 $form.data('turnstileState', state); 302 window.setTimeout(function() { 303 $form.get(0).submit(); 304 }, 60); 305 return false; 306 }); 307 308 $form.on('turnstile:error', function() { 309 var state = $form.data('turnstileState') || {}; 310 state.pendingSubmit = false; 311 state.submitting = false; 312 $form.data('turnstileState', state); 313 setFormSubmitting($form, false); 314 }); 315 }); 316 317 verifyCallback = function(response, formId) { 318 var targetId = formId || last_recap_id; 319 if (!targetId) { 320 return; 321 } 322 323 var $targetForm = $('form[data-recap-id="' + targetId + '"]'); 324 if ($targetForm.length === 0) { 325 return; 326 } 327 328 var state = $targetForm.data('turnstileState') || {}; 329 $targetForm.find('input[name="cf-turnstile-response"]').val(response || ''); 330 331 if (response && state.pendingSubmit) { 332 state.pendingSubmit = false; 333 state.submitting = true; 334 $targetForm.data('turnstileState', state); 335 setFormSubmitting($targetForm, true); 336 window.setTimeout(function() { 337 $targetForm.get(0).submit(); 338 }, 60); 339 } 340 341 if (!response) { 342 state.submitting = false; 343 $targetForm.data('turnstileState', state); 344 setFormSubmitting($targetForm, false); 345 } 346 }; 347 348 $('.notification .delete').on('click', function(e) { 349 e.preventDefault(); 350 $(this).parent().slideUp(function() { 351 $(this).remove() 352 }); 353 }); 354 355}); 356 357function addMessage(msg, type = 'success', selector = '.msg') 358{ 359 $(selector).prepend('<div class="notification is-'+type+'"><button class="delete"></button><div class="container">'+msg+'</div></div>'); 360 if(selector == '.msg') 361 $("html, body").animate({ scrollTop: 0 }); 362} 363 364function clearMessages() 365{ 366 $('.notification').remove(); 367} 368 369function escapeTags(str) 370{ 371 return String( str ) 372 .replace( /&/g, '&' ) 373 .replace( /"/g, '"' ) 374 .replace( /'/g, ''' ) 375 .replace( /</g, '<' ) 376 .replace( />/g, '>' ); 377} 378 379function init_uploader($uploader) 380{ 381 var data = $uploader.data(); 382 var btn = $uploader.find('.file-input')[0], 383 label = $uploader.find('.file-text')[0], 384 progressBar = $uploader.find('progress')[0], 385 hiddenInput = $uploader.find('input[type="hidden"]')[0]; 386 387 if(data.preview) 388 var preview = $(data.preview)[0]; 389 390 var uploader = new ss.SimpleUpload({ 391 button: btn, 392 url: ADMIN_AJAX_URL+'/upload.php', 393 name: 'file', 394 data: data, 395 multipart: true, 396 queue: false, 397 debug: true, 398 hoverClass: 'hover', 399 focusClass: 'focus', 400 responseType: 'json', 401 startXHR: function() { 402 // progressOuter.style.display = 'block'; // make progress bar visible 403 this.setProgressBar( progressBar ); 404 }, 405 onSubmit: function() { 406 clearMessages(); // empty the message box 407 $('.is-buttons button, .field_type_button button').prop('disabled', true); 408 label.innerHTML = 'Uploading...'; // change button text to "Uploading..." 409 }, 410 onComplete: function( filename, response ) { 411 console.log('response', response); 412 label.innerHTML = 'Choose a file...'; 413 $('.is-buttons button, .field_type_button button').prop('disabled', false); 414 progressBar.value = 0; // hide progress bar when upload is completed 415 if ( !response ) { 416 addMessage('Unable to upload file', 'error'); 417 return; 418 } 419 420 if ( response.success === true ) { 421 label.innerHTML = 'Uploaded'; 422 console.log('success', response); 423 // addMessage('<strong>' + escapeTags( filename ) + '</strong>' + ' successfully uploaded.'); 424 hiddenInput.value = response.file; 425 if(response.image) 426 { 427 if(preview) 428 { 429 // if($(preview).hasClass('preview-img')) 430 // { 431 // // $(preview).slideDown(); 432 // // $(preview).find('img').attr('src', response.image); 433 // $(preview).css('background-image', 'url('+response.image+')'); 434 // } else 435 $(preview).css('background-image', 'url('+response.image+')').addClass('has-preview'); 436 } 437 } else { 438 var $existing_filename = $uploader.find('.file-name'); 439 if($existing_filename.length > 0) 440 $existing_filename.text(response.name); 441 else 442 $uploader.find('label.file-label').append('<span class="file-name">
442'+response.name+'</span>') 443 } 444 } else { 445 console.log('error', response); 446 label.innerHTML = 'Upload Failed'; 447 if ( response.msg ) { 448 addMessage(escapeTags( response.msg ), 'error'); 449 450 } else { 451 addMessage('An error occurred and the upload failed.', 'error'); 452 } 453 } 454 }, 455 onError: function(filename, type, status, statusText, response, uploadBtn, size) { 456 console.log('filename', filename); 457 console.log('type', type); 458 console.log('status', status); 459 console.log('statusText', statusText); 460 console.log('response', response); 461 console.log('uploadBtn', uploadBtn); 462 console.log('size', size); 463 progressBar.value = 0; 464 addMessage('Unable to upload file', 'error'); 465 $('.is-buttons button, .field_type_button button').prop('disabled', false); 466 } 467 }); 468} 469 470function numberFormat(number, dec, dsep, tsep) { 471 if (isNaN(number)) return ""; 472 number = number.toFixed(dec || 0); 473 var pindex = number.indexOf('.'), fnums, decimals, parts = []; 474 if (pindex > -1) { 475 fnums = number.substring(0, pindex).split(''); 476 decimals = (dsep || '.') + number.substr(pindex+1); 477 } 478 else { 479 fnums = number.split(''); 480 decimals = ''; 481 } 482 do { 483 parts.unshift(fnums.splice(-3, 3).join('')); 484 } while (fnums.length); 485 return parts.join(tsep || ',') + decimals; 486} 487 488function initTwentyTwenty() { 489 if (typeof $.fn.twentytwenty !== 'function') { 490 return; 491 } 492 493 $('.twentytwenty-container').each(function() { 494 var $container = $(this); 495 if ($container.data('twentytwentyInit')) { 496 return; 497 } 498 499 var offset = parseFloat($container.data('offset')); 500 var orientation = $container.data('orientation') === 'vertical' ? 'vertical' : 'horizontal'; 501 502 $container.twentytwenty({ 503 default_offset_pct: isNaN(offset) ? 0.5 : offset, 504 orientation: orientation, 505 before_label: $container.data('before-label') || 'Before', 506 after_label: $container.data('after-label') || 'After', 507 no_overlay: !!$container.data('no-overlay'), 508 move_with_handle_only: false, 509 click_to_move: true 510 }); 511 512 $container.data('twentytwentyInit', true); 513 }); 514} 515 516$(window).on('load', initTwentyTwenty); 517 518 519document.addEventListener('DOMContentLoaded', () => { 520 521 /** 522 * A class to handle fetching and caching Vimeo thumbnails on the client-side. 523 */ 524 class VimeoThumbnailManager { 525 526 constructor() { 527 this.localStorageKey = 'vimeoThumbnails'; 528 this.cachedThumbnails = this.getThumbnailsFromStorage(); 529 } 530 531 /** 532 * Finds all blocks with a background video and processes them. 533 */ 534 processBlocks() { 535 const blocks = document.querySelectorAll('.block-item'); 536 blocks.forEach(block => { 537 const computedStyle = getComputedStyle(block); 538 const backgroundVideo = computedStyle.getPropertyValue('--block-backgroundVideo').trim(); 539 const backgroundImage = computedStyle.getPropertyValue('--block-backgroundImage').trim(); 540 541 // Process only if there's a video but no server-rendered image 542 const needsThumbnail = backgroundVideo && backgroundVideo !== 'none' && (!backgroundImage || backgroundImage === 'none'); 543 block.classList.toggle('has-video-thumbnail', !!needsThumbnail); 544 if (needsThumbnail) { 545 this.handleBlock(block, backgroundVideo); 546 } 547 }); 548 } 549 550 /** 551 * Handles a single block, either loading from cache or fetching the thumbnail. 552 * @param {HTMLElement} block - The block element. 553 * @param {string} backgroundVideoCss - The CSS value for the background video. 554 */ 555 async handleBlock(block, backgroundVideoCss) { 556 // Extract the video ID from the HLS URL (e.g., url(".../12345.m3u8?...") -> "12345") 557 const videoId = this.extractVideoIdFromUrl(backgroundVideoCss); 558 if (!videoId) { 559 return; 560 } 561 562 // If thumbnail is in our cache, apply it immediately. 563 if (this.cachedThumbnails[videoId]) { 564 this.applyThumbnail(block, this.cachedThumbnails[videoId]); 565 return; 566 } 567 568 // If not in cache, fetch from Vimeo API. 569 try { 570 const thumbnailUrl = await this.fetchThumbnailFromVimeo(videoId); 571 if (thumbnailUrl) { 572 this.applyThumbnail(block, thumbnailUrl);
573 this.saveThumbnailToStorage(videoId, thumbnailUrl); 574 } 575 } catch (error) { 576 console.error(`Failed to fetch Vimeo thumbnail for video ID ${videoId}:`, error); 577 } 578 } 579 580 /** 581 * Fetches thumbnail URL from Vimeo's oEmbed API. 582 * @param {string} videoId - The ID of the Vimeo video. 583 * @returns {Promise<string|null>} The thumbnail URL or null. 584 */ 585 async fetchThumbnailFromVimeo(videoId) { 586 const vimeoUrl = `https://vimeo.com/${videoId}`; 587 const oembedUrl = `https://vimeo.com/api/oembed.json?url=${encodeURIComponent(vimeoUrl)}`; 588 589 const response = await fetch(oembedUrl); 590 if (!response.ok) { 591 throw new Error(`Vimeo API responded with status: ${response.status}`); 592 } 593 const data = await response.json(); 594 595 // We can even get a higher quality thumbnail by modifying the URL 596 if (data.thumbnail_url) { 597 return data.thumbnail_url.replace(/_(\d+)x(\d+)/, '_1920x1080'); 598 } 599 600 return null; 601 } 602 603 /** 604 * Applies the thumbnail URL as a CSS variable to the block. 605 * @param {HTMLElement} block - The block element. 606 * @param {string} thumbnailUrl - The URL of the thumbnail image. 607 */ 608 applyThumbnail(block, thumbnailUrl) { 609 block.style.setProperty('--block-backgroundImage', `url('${thumbnailUrl}')`); 610 } 611 612 /** 613 * Extracts a Vimeo Video ID from various URL formats. 614 * @param {string} urlString - The full CSS url() value. 615 * @returns {string|null} 616 */ 617 extractVideoIdFromUrl(urlString) { 618 // Matches Vimeo video IDs from HLS URLs or standard URLs 619 const vimeoRegex = /vimeo\.com\/(?:video\/|external\/)?(\d+)/; 620 const match = urlString.match(vimeoRegex); 621 return match ? match[1] : null; 622 } 623 624 /** 625 * Retrieves the cache from localStorage. 626 * @returns {object} 627 */ 628 getThumbnailsFromStorage() { 629 try { 630 const stored = localStorage.getItem(this.localStorageKey); 631 return stored ? JSON.parse(stored) : {}; 632 } catch (e) { 633 console.error("Could not parse Vimeo thumbnail cache from localStorage.", e); 634 return {}; 635 } 636 } 637 638 /** 639 * Saves a thumbnail URL to the local cache and localStorage. 640 * @param {string} videoId - The Vimeo video ID. 641 * @param {string} thumbnailUrl - The thumbnail URL. 642 */ 643 saveThumbnailToStorage(videoId, thumbnailUrl) { 644 this.cachedThumbnails[videoId] = thumbnailUrl; 645 try { 646 localStorage.setItem(this.localStorageKey, JSON.stringify(this.cachedThumbnails)); 647 } catch (e) { 648 console.error("Could not save Vimeo thumbnail cache to localStorage.", e); 649 } 650 } 651 } 652 653 // Initialize and run the manager 654 const manager = new VimeoThumbnailManager(); 655 manager.processBlocks(); 656 657}); 658 659 660// FAQ Accordion functionality 661document.addEventListener('DOMContentLoaded', function() { 662 const accordionHeaders = document.querySelectorAll('.accordion-header'); 663 664 accordionHeaders.forEach(header => { 665 header.addEventListener('click', function() { 666 const accordionItem = this.closest('.accordion-item'); 667 const content = accordionItem.querySelector('.accordion-content'); 668 const icon = this.querySelector('.accordion-icon'); 669 const isExpanded = this.getAttribute('aria-expanded') === 'true'; 670 671 // Toggle the accordion 672 if (isExpanded) { 673 // Collapse 674 content.style.display = 'none'; 675 this.setAttribute('aria-expanded', 'false'); 676 icon.style.transform = 'rotate(0deg)'; 677 } else { 678 // Expand 679 content.style.display = 'block'; 680 this.setAttribute('aria-expanded', 'true'); 681 icon.style.transform = 'rotate(180deg)'; 682 } 683 }); 684 }); 685});
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.