1(function($){ 2 if ('scrollRestoration' in history) { 3 history.scrollRestoration = 'manual'; 4 } 5 6 function updateVHVariable() { 7 let vh = window.innerHeight * 0.01; 8 document.documentElement.style.setProperty('--vh', `${vh}px`); 9 } 10 var timeout = null; 11 $(document).ready(function() { 12 $('aside').trigger('scroll'); 13 $('.related.video, .related.off-programme-detail').mouseenter(function() { 14 $('.hover').removeClass('hover'); 15 $(this).addClass('hover'); 16 clearTimeout(timeout); 17 }) 18 $('.related.video, .related.off-programme-detail').mouseleave(function() { 19 const $this = $(this); 20 timeout = setTimeout(function() { 21 $this.removeClass('hover'); 22 }, 500); 23 24 }) 25 26 var homeHoverMq = window.matchMedia('(min-width: 769px)'); 27 $(document).on('mouseenter mousemove', '.home-item.has-hover-image', function(e) { 28 if (!homeHoverMq.matches) { 29 return; 30 } 31 $(this).find('.home-item-hover').css({ 32 top: e.clientY + 'px', 33 left: e.clientX + 'px' 34 }); 35 }); 36 37 /* $('aside').on('scroll', function(e) { 38 $('.related .video-container, .related .preview-off-programme').each(function() { 39 var overlayHeight = $(this).outerHeight(); 40 var windowHeight = $(window).height(); 41 var asideHeight = $('aside').height(); 42 43 // Calculate position, ensuring overlay doesn't go below viewport 44 let topPosition = $(this).parent().offset().top; 45 let scrollPosition = $('aside').scrollTop(); 46 47 let asideDiff = asideHeight - windowHeight; 48 topPosition = topPosition + scrollPosition + asideDiff; 49 50 console.log('topPosition'+topPosition+'asideDiff'+asideDiff); 51 console.log('scrollPos ' + scrollPosition); 52 if (topPosition + overlayHeight + 10 > windowHeight) { 53 topPosition = windowHeight - overlayHeight - 10; 54 } 55 if(topPosition < 10) { 56 topPosition = 10; 57 } 58 59 // Position the overlay 60 $(this).css({ 61 'top': topPosition + 'px' 62 }); 63 }) 64 })*/ 65 66 $('aside').on('scroll', function(e) { 67 $('.related .video-container, .related .preview-off-programme').each(function() {
68 var $overlay = $(this); 69 var $parent = $overlay.parent(); 70 var overlayHeight = $overlay.outerHeight(); 71 var windowHeight = $(window).height(); 72 73 // Get the parent's position relative to the aside 74 var parentOffset = $parent.offset(); 75 var asideOffset = $('aside').offset(); 76 var asideScrollTop = $('aside').scrollTop(); 77 78 // Calculate the parent's position relative to the aside 79 var parentRelativeTop = parentOffset.top - asideOffset.top + asideScrollTop; 80 81 // Position the overlay at the same height as its parent 82 var topPosition = parentRelativeTop; 83 84 // Ensure overlay doesn't go below viewport 85 if (topPosition + overlayHeight > windowHeight) { 86 topPosition = windowHeight - overlayHeight - 10; 87 } 88 89 // Ensure overlay doesn't go above viewport 90 if (topPosition < 10) { 91 topPosition = 10; 92 } 93 94 // Position the overlay 95 $overlay.css({ 96 'top': topPosition + 'px' 97 }); 98 }); 99 }); 100 101 102 103 $('.artists-overlay').hide(); 104 $('[data-artists]').mouseenter(function(e) { 105 $('.artists-overlay').removeClass('solo'); 106 if($(this).attr('data-solo')) { 107 $('.artists-overlay').addClass('solo'); 108 $('.artists-overlay h2').html($(this).data('solo')); 109 $('.artists-overlay h3').text('Solo artist shown at the fair'); 110 } 111 else { 112 $('.artists-overlay h2').html($(this).data('artists')); 113 $('.artists-overlay h3').text('Artists shown at the fair'); 114 } 115 116 // Get overlay height and window height 117 const overlayHeight = $('.artists-overlay').outerHeight(); 118 const windowHeight = $(window).height(); 119 120 // Calculate position, ensuring overlay doesn't go below viewport 121 let topPosition = e.clientY; 122 if (topPosition + overlayHeight > windowHeight) { 123 topPosition = windowHeight - overlayHeight; 124 } 125 126 // Position the overlay 127 $('.artists-overlay').css({ 128 'top': topPosition + 'px' 129 }).show(); 130 }); 131 $('[data-artists]').mouseleave(function() { 132 $('.artists-overlay').hide(); 133 }); 134 $('.slider').flickity({ 135 cellAlign: 'center', 136 arrowShape: 'M43.45,93.45l7.47-7.37-30.75-30.75H99.8v-10.67H20.17L50.92,14.02l-7.47-7.47L0,50l43.45,43.45Z', 137 wrapAround: true, 138 imagesLoaded: true, 139 pageDots: false, 140 }); 141 if ($('body').hasClass('home')) { 142 window.scrollTo(0, 0); 143 setTimeout(() => { 144 window.scrollTo(0, 0); 145 }, 50); 146 } 147 $('.aside-toggle').click(function() { 148 $('body').toggleClass('show-aside'); 149 }) 150 const titleElement = document.querySelector('header div.title h2'); 151 152 if (titleElement && titleElement.scrollWidth > titleElement.clientWidth) { 153 // Wait a bit after page load before starting animation 154 setTimeout(() => { 155 // Custom scroll animation function with adjustable speed 156 const scrollToElement = (element, to, duration) => { 157 const start = element.scrollLeft; 158 const change = to - start; 159 const increment = 20; // Lower increment = smoother animation 160 let currentTime = 0; 161 162 const animateScroll = () => { 163 currentTime += increment; 164 const val = easeInOutQuad(currentTime, start, change, duration); 165 element.scrollLeft = val; 166 if (currentTime < duration) { 167 requestAnimationFrame(animateScroll); 168 } else { 169 // After reaching the end, scroll back to start 170 setTimeout(() => { 171 scrollToElement(element, 0, 1200); // Scroll back with 3 second duration 172 }, 2000); // Pause at the end for 2 seconds 173 } 174 }; 175 176 // Easing function for smoother start/end 177 const easeInOutQuad = (t, b, c, d) => { 178 t /= d/2; 179 if (t < 1) return c/2*t*t + b; 180 t--; 181 return -c/2 * (t*(t-2) - 1) + b; 182 }; 183 184 requestAnimationFrame(animateScroll); 185 }; 186 187 // Start scrolling to the end with 4 second duration (slower scroll) 188 scrollToElement(titleElement, titleElement.scrollWidth, 2000); 189 }, 1500); 190 } 191 updateVHVariable(); 192 193 // Update on resize and orientation change 194 window.addEventListener('resize', updateVHVariable); 195 window.addEventListener('orientationchange', updateVHVariable); 196 $('section.foldouttxt h2').click(function() { 197 $(this).parent().toggleClass('open'); 198 $(this).parent().find('.foldouttxt-content').slideToggle(); 199 }); 200 $('.fold-out-item h3').click(function() { 201 $(this).parent().toggleClass('open'); 202 $(this).parent().find('.fold-out-content').slideToggle(); 203 }); 204 $('.back-to-top').click(function() { 205 $('html, body').animate({ 206 scrollTop: 0 207 }, 1000); 208 return false;
209 }); 210 $('.menu-toggle').click(function() { 211 $('body').toggleClass('show-menu'); 212 return false; 213 }); 214 $('.filter-group h3').click(function() { 215 if($(this).parent().hasClass('display-filter')) { 216 $(this).parent().removeClass('display-filter'); 217 } 218 else { 219 $('.filter-group').removeClass('display-filter'); 220 $(this).parent().addClass('display-filter'); 221 } 222 }); 223 $('.section-preview .section-toggle').click(function() { 224 $(this).parent().toggleClass('show-content'); 225 }); 226 $('nav>div>p>span:first-child').click(function() { 227 $('.menu-open').removeClass('menu-open'); 228 $(this).parent().addClass('menu-open'); 229 }); 230 231 // Function to create infinite scrolling carousel 232 function setupTicker(sectionSelector, tickerSpeed = 0.8) { 233 if ($(sectionSelector).length) { 234 let $section = $(sectionSelector); 235 let $ticker = $section.find('.ticker'); 236 237 // For partners section, make sure all images have loaded before setting up the carousel 238 if (sectionSelector === '.partners') { 239 let $images = $ticker.find('img'); 240 let imagesLoaded = 0; 241 let totalImages = $images.length; 242 243 // Only set up the ticker after all images have loaded 244 if (totalImages > 0) { 245 $images.each(function() { 246 if (this.complete) { 247 imagesLoaded++; 248 if (imagesLoaded === totalImages) { 249 initializeTicker(); 250 } 251 } else { 252 $(this).on('load', function() { 253 imagesLoaded++; 254 if (imagesLoaded === totalImages) { 255 initializeTicker(); 256 } 257 }); 258 259 // Handle image loading errors 260 $(this).on('error', function() { 261 imagesLoaded++; 262 if (imagesLoaded === totalImages) { 263 initializeTicker(); 264 } 265 }); 266 } 267 }); 268 } else { 269 initializeTicker(); 270 } 271 } else { 272 // For non-image based tickers, initialize immediately 273 initializeTicker(); 274 } 275 276 function initializeTicker() { 277 // First, make sure each item has a consistent width 278 // This is especially important for partners with images 279 let initialItems = $ticker.children().toArray(); 280 let viewportWidth = $section.width(); 281 let totalWidth = 0; 282 283 // For partners, ensure consistent figure widths 284 if (sectionSelector === '.partners') { 285 // Set a fixed width for each partner figure based on its content 286 $(initialItems).each(function() { 287 // If we want each figure to have the same width 288 // $(this).css('width', '150px'); // Adjust this value as needed 289 290 // Or if we want to preserve proportions but ensure consistency 291 let imgWidth = $(this).find('img').width(); 292 let imgHeight = $(this).find('img').height(); 293 $(this).css({ 294 'width': imgWidth + 20 + 'px', // Width plus padding 295 'height': '85px', // Fixed height for consistency 296 'flex-shrink': 0 // Prevent shrinking 297 }); 298 }); 299 } 300 301 // Calculate initial width
302 initialItems.forEach(item => { 303 totalWidth += $(item).outerWidth(true); 304 }); 305 306 // Clone items until we have enough content 307 let iterations = Math.max(3, Math.ceil((viewportWidth * 3) / totalWidth)); 308 $ticker.empty(); // Clear ticker before adding cloned items 309 310 // First add the original items 311 for (let item of initialItems) { 312 $ticker.append(item); 313 } 314 315 // Then add the clones 316 for (let i = 0; i < iterations; i++) { 317 let clonedItems = $(initialItems).clone(true); 318 $ticker.append(clonedItems); 319 } 320 321 // Recalculate total width with all cloned items 322 let itemWidth = 0; 323 let clonePoint = 0; 324 $ticker.children().each(function(index) { 325 let width = $(this).outerWidth(true); 326 itemWidth += width; 327 328 // Mark the point where we should reset (end of first set) 329 if (index === initialItems.length - 1) { 330 clonePoint = itemWidth; 331 } 332 }); 333 334 // Setup animation 335 let animationFrame; 336 let scrollPosition = 0; 337 let scrollSpeed = tickerSpeed; // Can be customized per ticker 338 let pixelAccumulator = 0; 339 340 // Create the scrolling animation 341 function scrollTicker() { 342 pixelAccumulator += scrollSpeed; 343 344 // Only move when we've accumulated at least 1 pixel 345 if (pixelAccumulator >= 1) { 346 // Calculate how many pixels to move this frame 347 let pixelsToMove = Math.floor(pixelAccumulator); 348 scrollPosition += pixelsToMove; 349 pixelAccumulator -= pixelsToMove; 350 351 // When we reach the clone point, reset to beginning 352 if (scrollPosition >= clonePoint) { 353 // Jump back to start immediately without visual disruption 354 scrollPosition = 0; 355 $ticker.css({ 356 'transform': 'translateX(0)', 357 'transition': 'none' // Ensure no transition for reset 358 }); 359 } else { 360 // Smooth transition during normal scrolling 361 $ticker.css({ 362 'transform': 'translateX(-' + scrollPosition + 'px)', 363 'transition': 'transform 0.1s linear' // Short transition for smoothness 364 }); 365 } 366 } 367 368 animationFrame = requestAnimationFrame(scrollTicker); 369 } 370 371 // Force a reflow before starting animation to ensure transitions work properly 372 $ticker[0].offsetHeight; 373 374 // Start the animation 375 animationFrame = requestAnimationFrame(scrollTicker); 376 377 // Pause animation when ticker isn't visible 378 $(window).on('scroll', function() { 379 let sectionTop = $section.offset().top; 380 let windowBottom = $(window).scrollTop() + $(window).height(); 381 let windowTop = $(window).scrollTop(); 382 383 if (sectionTop >
383 windowBottom || (sectionTop + $section.height()) < windowTop) { 384 // Section not visible, cancel animation 385 if (animationFrame) { 386 cancelAnimationFrame(animationFrame); 387 animationFrame = null; 388 } 389 } else if (!animationFrame) { 390 // Section visible again, restart animation 391 animationFrame = requestAnimationFrame(scrollTicker); 392 } 393 }); 394 } 395 } 396 } 397 398 // Setup both tickers with different speeds 399 setupTicker('.home-menu', 1.2); // Home menu ticker with slower speed 400 setupTicker('.partners', 0.8); // Partners ticker with faster speed 401 402 // Gallery AJAX filtering 403 if ($('#gallery-filter-form').length) { 404 // Track if AJAX request is in progress 405 let ajaxRunning = false; 406 407 // Handle filter form submission 408 $('#gallery-filter-form').on('submit', function(e) { 409 e.preventDefault(); 410 411 if (ajaxRunning) return false; 412 413 ajaxRunning = true; 414 $('.gallery-links').addClass('loading'); 415 416 // Get all checked values 417 const sections = []; 418 $('input[name="sections[]"]:checked').each(function() { 419 sections.push($(this).val()); 420 }); 421 422 const countries = []; 423 $('input[name="countries[]"]:checked').each(function() { 424 countries.push($(this).val()); 425 }); 426 427 // Update URL with current filters (for bookmarking/sharing) 428 let newUrl = window.location.pathname; 429 let params = []; 430 431 if (sections.length) { 432 sections.forEach(section => { 433 params.push(`sections[]=${encodeURIComponent(section)}`); 434 }); 435 } 436 437 if (countries.length) { 438 countries.forEach(country => { 439 params.push(`countries[]=${encodeURIComponent(country)}`); 440 }); 441 } 442 443 if (params.length) { 444 newUrl += '?' + params.join('&'); 445 } 446 447 window.history.pushState({}, '', newUrl); 448 449 // Make AJAX request 450 $.ajax({ 451 url: ajax_object.ajax_url, 452 type: 'POST', 453 data: { 454 action: 'gallery_filter', 455 sections: sections, 456 countries: countries, 457 nonce: ajax_object.nonce 458 }, 459 success: function(response) { 460 if (response.success) { 461 $('.gallery-links').html(response.data.html); 462 } else { 463 console.error('AJAX error:', response); 464 $('.gallery-links').html('<p class="error">Error loading galleries. Please try again.</p>'); 465 } 466 }, 467 error: function(xhr, status, error) { 468 console.error('AJAX error:', error); 469 $('.gallery-links').html('<p class="error">Error loading galleries. Please try again.</p>'); 470 }, 471 complete: function() { 472 ajaxRunning = false; 473 $('.gallery-links').removeClass('loading'); 474 } 475 }); 476 }); 477 478 // Auto-submit when checkboxes are clicked 479 $('.filter-checkbox').on('change', function() { 480 updateFilterActiveStatus(); // Make sure status is updated before form submission 481 $('#gallery-filter-form').submit(); 482 }); 483 } 484 485 // Setup Vimeo background videos
486 function setupVimeoBackgroundVideos() { 487 // Check if we have video preview sections with data-video-url 488 if ($('.video-preview[data-video-url]').length) { 489 // Load Vimeo Player API if not already loaded 490 if (!window.Vimeo) { 491 let tag = document.createElement('script'); 492 tag.src = "https://player.vimeo.com/api/player.js"; 493 let firstScriptTag = document.getElementsByTagName('script')[0]; 494 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 495 496 // Wait for Vimeo API to load 497 let checkVimeoAPI = setInterval(function() { 498 if (window.Vimeo) { 499 clearInterval(checkVimeoAPI); 500 initVimeoPlayers(); 501 } 502 }, 100); 503 } else { 504 // Vimeo API already loaded 505 initVimeoPlayers(); 506 } 507 } 508 } 509 510 function initVimeoPlayers() { 511 $('.video-preview[data-video-url]').each(function(index) { 512 const $container = $(this); 513 const videoUrl = $container.data('video-url'); 514 const $playerContainer = $container.find('.video-player'); 515 516 if (videoUrl && $playerContainer.length) { 517 // Initialize Vimeo player with specific options for background video 518 const player = new Vimeo.Player($playerContainer[0], { 519 url: videoUrl, 520 background: true, // This creates a background video with no controls 521 muted: true, 522 loop: false, 523 autoplay: false, // Make sure autoplay is explicitly set to false 524 title: false, 525 byline: false, 526 portrait: false, 527 dnt: true, // Do not track to avoid analytics cookies 528 controls: false // Explicitly disable controls 529 }); 530 531 // Make sure the player is paused initially 532 player.ready().then(function() { 533 player.pause(); 534 $container.removeClass('loading'); 535 }).catch(function(error) { 536 console.error('Player ready error:', error); 537 }); 538 539 // Store player instance in container for reference 540 $container.data('player', player); 541 542 // Set up hover events on parent link 543 const $videoLink = $container.closest('.video-item-link'); 544 545 $videoLink.on('mouseenter', function() { 546 const player = $container.data('player'); 547 $container.data('hovering', true); 548 if (player) { 549 player.getPaused().then(isPaused => { 550 player.getCurrentTime().then(currentTime => { 551 if (Math.abs(currentTime - 20) > 0.1) { 552 return player.setCurrentTime(20); 553 } 554 return Promise.resolve(); 555 }).then(() => { 556 return player.play(); 557 }).then(() => { 558 if ($container.data('hovering')) { 559 $container.find('.video-thumbnail').addClass('hidden'); 560 } 561 562 const loopInterval = setInterval(() => { 563 player.getCurrentTime().then(time => { 564 if (time >= 50) { 565 player.setCurrentTime(20); 566 } 567 }).catch(error => { 568 clearInterval(loopInterval); 569 console.error('Error in loop interval:', error); 570 }); 571 }, 250); 572 573 $container.data('loopInterval', loopInterval); 574 }).catch(error => { 575 console.error('Video play sequence error:', error); 576 }); 577 }).catch(error => { 578 console.error('Get paused state error:', error); 579 }); 580 } 581 }); 582 583 $videoLink.on('mouseleave', function() { 584 const player = $container.data('player'); 585 const loopInterval = $container.data('loopInterval'); 586 $container.data('hovering', false); 587 588 if (player) { 589 player.pause().catch(error => console.error('Video pause error:', error)); 590 $container.find('.video-thumbnail').removeClass('hidden'); 591 } 592 593 if (loopInterval) { 594 clearInterval(loopInterval); 595 $container.data('loopInterval', null); 596 } 597 }); 598 } 599 }); 600 } 601 602 // Initialize the Vimeo background videos
603 setupVimeoBackgroundVideos(); 604 605 // Function to update filter active status 606 function updateFilterActiveStatus() { 607 $('.filter-group').each(function() { 608 const $filterGroup = $(this); 609 const $header = $filterGroup.find('h3'); 610 const hasActiveFilters = $filterGroup.find('input[type="checkbox"]:checked').length > 0; 611 612 if (hasActiveFilters) { 613 $header.addClass('filter-active'); 614 } else { 615 $header.removeClass('filter-active'); 616 } 617 }); 618 } 619 620 // Run on page load to set initial state 621 updateFilterActiveStatus(); 622 623 // Update when checkboxes change 624 $('.filter-checkbox').on('change', function() { 625 updateFilterActiveStatus(); 626 }); 627 }) 628})(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.