1/* ================================================================ 2 G3 Carousel (Used in sticky nav) 3 ================================================================ */ 4//Carousel 5function g3_carousel(ele) { 6 const $car_container = ele; 7 const $car = $car_container.querySelector('.nav-carousel'); 8 const $carousel_items = $car_container.querySelectorAll('.nav-carousel__slide'); 9 const $carousel_items_rev = Array.prototype.slice.call($car_container.querySelectorAll('.nav-carousel__slide')).reverse(); 10 const $arrow_r = $car.querySelector('.nav-carousel__arrow--r'); 11 const $arrow_l = $car.querySelector('.nav-carousel__arrow--l'); 12 const $carousel = $car_container.querySelector('.nav-carousel__slider'); 13 const $toc_container = $car_container.querySelector('.nav-carousel__toc'); 14 let toc_exists = true; 15 let position = 0; 16 let cur_slide = 1; 17 const slide_qty = $carousel_items.length; 18 const speed = 0.5; 19 const auto_speed = 3; 20 const initial_on = 1; // use to offset first slide on load 21 if ($toc_container == null) { 22 toc_exists = false; 23 } 24 25 function set_items() { 26 $carousel_items.forEach(function ($this, index) { 27 gsap.set($this, { 28 left: `${index * 100}%`, 29 }); 30 }); 31 gsap.to($car, { 32 alpha: 1, 33 duration: 0.5, 34 }); 35 } 36 37 function toc_init() { 38 let toc_markup = ''; 39 for (let index = 0; index < slide_qty; index++) { 40 if (index == 0) { 41 toc_markup += '<div class="nav-carousel__toc-item g3-active-toc"></div>'; 42 } else { 43 toc_markup += '<div class="nav-carousel__toc-item"></div>'; 44 } 45 } 46 $toc_container.innerHTML = toc_markup; 47 $toc_container.querySelectorAll('.nav-carousel__toc-item').forEach(function ($this, index) { 48 $this.addEventListener('click', function () { 49 const goto = parseInt(getNodeIndex($this) + 1); 50 if (goto != cur_slide) { 51 auto_play_disable(); 52 if (goto > cur_slide) { 53 const diff = parseInt(goto - cur_slide); 54 position += diff; 55 cur_slide = parseInt(goto); 56 gsap.to($carousel, { 57 duration: speed, 58 x: `${-(position * 100)}%`, 59 ease: Power1.easeOut, 60 onUpdate: item_repos, 61 onUpdateParams: ['r'], 62 }); 63 toc(); 64 } else { 65 const diff = parseInt(cur_slide - goto); 66 position -= diff; 67 cur_slide = parseInt(goto); 68 gsap.to($carousel, { 69 duration: speed, 70 x: `${-(position * 100)}%`, 71 ease: Power1.easeOut, 72 onUpdate: item_repos, 73 onUpdateParams: ['l'], 74 }); 75 if (toc_exists) { 76 toc(); 77 } 78 } 79 } 80 }); 81 }); 82 } 83 84 //set_items(); 85 86 if (toc_exists) { 87 toc_init(); 88 } 89 90 $arrow_r.addEventListener('click', function () { 91 auto_play_disable(); 92 step_right(); 93 }); 94 95 function save_position() { 96 sessionStorage.setItem('g3_carousel_position', position); 97 sessionStorage.setItem('g3_carousel_slide', cur_slide); 98 } 99 100 function step_right() { 101 position++; 102 set_slide_num(1); 103 gsap.to($carousel, { 104 duration: speed, 105 x: `${-(position * 100)}%`, 106 ease: Power1.easeOut, 107 onUpdate: item_repos, 108 }); 109 save_position(); 110 } 111 112 $arrow_l.addEventListener('click', function () { 113 auto_play_disable(); 114 step_left(); 115 }); 116 117 function step_left() { 118 position--; 119 set_slide_num(-1); 120 gsap.to($carousel, { 121 duration: speed, 122 x: `${-(position * 100)}%`, 123 ease: Power1.easeOut, 124 onUpdate: item_repos, 125 }); 126 save_position(); 127 } 128 129 function item_repos() { 130 $carousel_items.forEach(function ($item, index) { 131 // Integrated offset calculation directly using getBoundingClientRect 132 const itemRect = $item.getBoundingClientRect(); 133 const containerRect = $item.parentNode.parentNode.getBoundingClientRect(); 134 const posX = itemRect.left - containerRect.left; 135 const itemWidth = $carousel_items[0].offsetWidth; 136 137 if (posX < -itemWidth) { 138 gsap.set($item, { 139 x: `+=${slide_qty * 100}%`, 140 }); 141 } else if (posX > itemWidth * (slide_qty - 1)) { 142 gsap.set($item, { 143 x: `-=${slide_qty * 100}%`, 144 }); 145 } 146 }); 147 } 148 149 // Draggable.create($carousel, { 150 // type: 'x', 151 // edgeResistance: 1, 152 // dragResistance: 0, 153 // zIndexBoost: false, 154 // inertia: false, 155 // dragClickables: true, 156 // lockAxis: true, 157 // minimumMovement: 2, 158 // onDragEnd: function () { 159 // const a = this.lockedAxis; 160 // if (a == 'y') { 161 // const d = this.getDirection('start'); 162 // if (d == 'left') { 163 // step_right(); 164 // } else if (d == 'right') { 165 // step_left(); 166 // } 167 // } 168 // }, 169 // onDragStart: function () { 170 // //auto_play_disable(); 171 // }, 172 // onDrag: function () { 173 // //item_repos(); 174 // }, 175 // }); 176 177 function ss_auto_play() { 178 if ($car.classList.contains('g3-auto-lpay')) { 179 TweenMax.delayedCall(auto_speed, function () { 180 if ($car.classList.contains('g3-auto-play')) {
181 auto_speed = 3; 182 step_right(); 183 ss_auto_play(); 184 } 185 }); 186 } 187 } 188 189 function auto_play_disable() { 190 if ($car.classList.contains('g3-auto-play')) { 191 $car.classList.remove('g3-auto-play'); 192 } 193 } 194 195 function set_slide_num(n) { 196 cur_slide = parseInt(cur_slide + n); 197 if (cur_slide > slide_qty) { 198 cur_slide = 1; 199 } 200 if (cur_slide < 1) { 201 cur_slide = slide_qty; 202 } 203 if (toc_exists) { 204 toc(); 205 } 206 } 207 208 function toc() { 209 $car_container.querySelector('.g3-active-toc').classList.remove('g3-active-toc'); 210 $car_container.querySelector(`.nav-carousel__toc-item:nth-child(${cur_slide})`).classList.add('g3-active-toc'); 211 } 212 213 function init() { 214 const saved_position = sessionStorage.getItem('g3_carousel_position'); 215 const saved_slide = sessionStorage.getItem('g3_carousel_slide'); 216 if (saved_position !== null && saved_slide !== null) { 217 position = parseInt(saved_position); 218 cur_slide = parseInt(saved_slide); 219 } else { 220 position = parseInt(initial_on - 1); 221 cur_slide = parseInt(initial_on); 222 } 223 gsap.set($carousel, { 224 x: `${-(position * 100)}%`, 225 }); 226 item_repos(); 227 } 228
229 init(); 230 231 if ($car.classList.contains('g3-auto-play')) { 232 ss_auto_play(); 233 } 234} 235// Usage: g3_carousel(document.querySelector('#g3GiveBack__Carousel')); 236 237/* ================================================================ 238 Header Sticky execution 239 ================================================================ */ 240document.addEventListener('DOMContentLoaded', function () { 241 // Handle navigation highlighting based on body class 242 const bodyClasses = document.body.classList; 243 const stickyNavItems = document.querySelectorAll('.header__nav-item'); 244 245 stickyNavItems.forEach((item) => { 246 const itemClass = item.dataset.navTrigger; 247 if (itemClass && bodyClasses.contains(itemClass)) { 248 item.classList.add('active'); 249 } 250 }); 251 252 // Header tray functionality 253 const $headerTrays = document.querySelectorAll('.header__tray'); 254 const $headerTrayTriggers = document.querySelectorAll('.header__nav-tray-trigger'); 255 256 // Only initialize if the elements exist 257 if ($headerTrays.length > 0 && $headerTrayTriggers.length > 0) { 258 // Create a map to track mouse state for each tray 259 const trayStates = new Map(); 260 261 // Initialize each tray with its corresponding trigger(s) 262 $headerTrays.forEach(function (tray) { 263 // Get the tray ID or data attribute to identify which tray this is 264 const trayId = tray.dataset.trayId; 265 266 // Find triggers that should open this tray 267 const relatedTriggers = Array.from($headerTrayTriggers).filter((trigger) => trigger.dataset.navTrigger === trayId); 268 269 // Initialize state for this tray 270 trayStates.set(trayId, { isMouseOver: false }); 271 272 function openTray() { 273 let trayHeight = tray.offsetHeight; 274 gsap.to(tray, { 275 y: trayHeight, 276 duration: 0.5, 277 ease: 'power2.out', 278 overwrite: true, 279 }); 280 } 281 282 function closeTray() { 283 const state = trayStates.get(trayId); 284 if (!state.isMouseOver) { 285 gsap.to(tray, { 286 y: -5, 287 duration: 0.5, 288 ease: 'power2.out', 289 }); 290 } 291 } 292 293 // Add event listeners to related triggers 294 relatedTriggers.forEach(function (trigger) { 295 trigger.addEventListener('mouseenter', function () { 296 // Close any other open trays first (optional) 297 closeAllTraysExcept(trayId); 298 299 // Update state and open this tray 300 trayStates.get(trayId).isMouseOver = true; 301 openTray(); 302 }); 303 304 trigger.addEventListener('mouseleave', function () { 305 trayStates.get(trayId).isMouseOver = false; 306 // Small delay to check if mouse moved to tray 307 gsap.delayedCall(0.15, closeTray); 308 }); 309 }); 310 311 // Add event listeners to the tray itself 312 tray.addEventListener('mouseenter', function () { 313 trayStates.get(trayId).isMouseOver = true; 314 openTray(); 315 }); 316 317 tray.addEventListener('mouseleave', function () { 318 trayStates.get(trayId).isMouseOver = false; 319 closeTray(); 320 }); 321 }); 322 323 // Helper function to close all trays except the active one 324 function closeAllTraysExcept(activeTrayId) { 325 trayStates.forEach((state, trayId) => { 326 if (trayId !== activeTrayId) { 327 state.isMouseOver = false; 328 const tray = document.querySelector(`.header__tray[data-tray-id="${trayId}"]`); 329 gsap.to(tray, { 330 y: -5, 331 duration: 0.3, 332 ease: 'power2.out', 333 }); 334 } 335 }); 336 } 337 } 338 339 function getSameOriginPath(url) { 340 try { 341 const linkUrl = new URL(url, window.location.origin); 342 if (linkUrl.origin !== window.location.origin) { 343 return null; 344 } 345 return linkUrl.pathname.replace(/\/$/, ''); 346 } catch (e) { 347 return null; 348 } 349 } 350 351 // Mobile Navigation 352 const hamburgerMenu = document.querySelector('.header--sticky__hamburger-menu'); 353 const mobileNav = document.querySelector('.header--sticky__mobile-nav'); 354 const mobileNavItems = document.querySelectorAll('.header--sticky__mobile-nav-item'); 355 356 if (hamburgerMenu && mobileNav) { 357 hamburgerMenu.addEventListener('click', function () { 358 this.classList.toggle('active'); 359 mobileNav.classList.toggle('active'); 360 }); 361 362 // Handle submenu toggles 363 mobileNavItems.forEach((item) => { 364 const submenu = item.querySelector('.header--sticky__mobile-submenu'); 365 if (submenu) { 366 item.querySelector('.header--sticky__mobile-nav-link').addEventListener('click', function (e) { 367 e.preventDefault(); 368 submenu.classList.toggle('active'); 369 }); 370 } 371 }); 372 373 // Open the current page's main item and highlight the active links 374 const mobileCurrentPath = window.location.pathname.replace(/\/$/, '');
375 const mobileForcedSubNav = (document.body.dataset.g3SubNav || '').replace(/\/$/, ''); 376 const mobilePaths = new Set(); 377 if (mobileCurrentPath) { 378 mobilePaths.add(mobileCurrentPath); 379 } 380 if (mobileForcedSubNav) { 381 mobilePaths.add(mobileForcedSubNav); 382 } 383 384 mobileNavItems.forEach((item) => { 385 const submenu = item.querySelector('.header--sticky__mobile-submenu'); 386 const mainLink = item.querySelector('.header--sticky__mobile-nav-link'); 387 let isCurrent = false; 388 389 // Match against submenu links (mark the matching sub item orange) 390 if (submenu) { 391 submenu.querySelectorAll('a').forEach((subLink) => { 392 const subPath = getSameOriginPath(subLink.href); 393 if (subPath && mobilePaths.has(subPath)) { 394 subLink.closest('.header--sticky__mobile-submenu-item').classList.add('is-current'); 395 isCurrent = true; 396 } 397 }); 398 } 399 400 // Match against the main link (covers items without a submenu, e.g. Contact) 401 if (mainLink && !mainLink.classList.contains('js-mobile-search-trigger')) { 402 const href = mainLink.getAttribute('href'); 403 if (href && href !== '#') { 404 const mainPath = getSameOriginPath(mainLink.href); 405 if (mainPath && mobilePaths.has(mainPath)) { 406 isCurrent = true; 407 } 408 } 409 } 410 411 if (isCurrent) { 412 item.classList.add('is-current'); 413 if (submenu) { 414 submenu.classList.add('active'); 415 } 416 } 417 }); 418 } 419 420 //Logo audio 421 const $playBtn = document.getElementById('header_logo_audio-play-btn'); 422 const audioPath = '/mp3/'; 423 const audioPathOgg = '/ogg/'; 424 425 // Create array of all audio files with both formats 426 const audioFiles = Array.from({ length: 17 }, (_, i) => ({ 427 mp3: audioPath + `m${i + 1}.mp3`, 428 ogg: audioPathOgg + `m${i + 1}.ogg`, 429 })); 430 431 let currentIndex = 0; 432 const audioElement = document.createElement('audio'); 433 434 function playNextAudio() { 435 // Reset the audio element 436 audioElement.pause(); 437 audioElement.currentTime = 0; 438 audioElement.removeAttribute('src'); 439 audioElement.load(); 440 441 // Set both source formats 442 audioElement.innerHTML = ` 443 <source src="${audioFiles[currentIndex].mp3}" type="audio/mpeg"> 444 <source src="${audioFiles[currentIndex].ogg}" type="audio/ogg"> 445 `; 446 447 // Load and play the audio 448 audioElement.load(); 449 const playPromise = audioElement.play(); 450 451 if (playPromise !== undefined) { 452 playPromise 453 .then(() => { 454 // Move to next file after current one finishes 455 audioElement.addEventListener( 456 'ended', 457 () => { 458 currentIndex = (currentIndex + 1) % audioFiles.length; 459 }, 460 { once: true }, 461 ); 462 }) 463 .catch((error) => { 464 console.error('Audio playback failed:', error); 465 }); 466 } 467 } 468 469 $playBtn.addEventListener('click', playNextAudio); 470 471 // Check for section classes and highlight appropriate nav item 472 const body = document.body; 473 const navItems = document.querySelectorAll('.header__nav-item'); 474 475 // Remove any existing active classes 476 navItems.forEach((item) => { 477 item.classList.remove('header__nav-item--active'); 478 }); 479 480 // Get all body classes 481 const currentBodyClasses = Array.from(body.classList); 482 483 // Find nav item with matching data-nav-trigger 484 navItems.forEach((item) => { 485 const trigger = item.getAttribute('data-nav-trigger'); 486 if (trigger && currentBodyClasses.includes(trigger)) { 487 item.classList.add('header__nav-item--active'); 488 } 489 }); 490 491 // Highlight active tray nav items based on current URL and/or data-g3-sub-nav 492 const currentPath = window.location.pathname.replace(/\/$/, ''); 493 const forcedSubNav = (document.body.dataset.g3SubNav || '').replace(/\/$/, ''); 494 const subNavPaths = new Set(); 495 if (currentPath) { 496 subNavPaths.add(currentPath); 497 } 498 if (forcedSubNav) { 499 subNavPaths.add(forcedSubNav); 500 } 501 502 document.querySelectorAll('[data-js="sub-nav-item"]').forEach(function (item) { 503 const link = item.tagName === 'A' ? item : item.querySelector('a'); 504 if (link) { 505 const linkPath = getSameOriginPath(link.href); 506 if (linkPath && subNavPaths.has(linkPath)) { 507 const title = item.querySelector('.header__tray-title'); 508 if (title) { 509 title.classList.add('header__tray-title--active'); 510 } 511 } 512 } 513 }); 514
515 // Initialize sticky nav carousel 516 g3_carousel(document.querySelector('#sticky-nav-books-carousel')); 517});
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.