1/* ========================================================================== 2 WILDERNESS SCOTLAND - MAIN JAVASCRIPT 3 Optimized: Unified Scroll Loop, IntersectionObserver, and DRY Architecture 4 ========================================================================== */ 5 6// ============================================================== 7// 1. GLOBAL VARIABLES & UTILITIES 8// ============================================================== 9var activeScrollPosition = 0; 10var scrollState = ''; 11var initialScrollPos = 0; 12var scrollPlaces = new Array(); 13var scrollOffset = 150; 14var watermarks = []; // Cached array for the parallax effect 15 16function debounce(fn, delay) { 17 let t; 18 return function() { 19 clearTimeout(t); 20 t = setTimeout(fn, delay); 21 }; 22} 23 24function getURLParameter(name) { 25 const urlParams = new URLSearchParams(window.location.search); 26 return urlParams.get(name); 27} 28 29function setCookie(cname, cvalue, exhours) { 30 var d = new Date(); 31 d.setTime(d.getTime() + (exhours * 60 * 60 * 1000)); 32 var expires = "expires=" + d.toUTCString(); 33 document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/;Secure"; 34} 35 36function getCookie(cname) { 37 var name = cname + "="; 38 var ca = document.cookie.split(';'); 39 for (var i = 0; i < ca.length; i++) { 40 var c = ca[i]; 41 while (c.charAt(0) == ' ') c = c.substring(1); 42 if (c.indexOf(name) == 0) return c.substring(name.length, c.length); 43 } 44 return ""; 45} 46 47function hexToHsl(hex) { 48 hex = hex.replace('#', ''); 49 let r = parseInt(hex.substring(0, 2), 16) / 255; 50 let g = parseInt(hex.substring(2, 4), 16) / 255;
51 let b = parseInt(hex.substring(4, 6), 16) / 255; 52 let max = Math.max(r, g, b), min = Math.min(r, g, b); 53 let h, s, l = (max + min) / 2; 54 if (max === min) h = s = 0; 55 else { 56 let d = max - min; 57 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 58 switch (max) { 59 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 60 case g: h = (b - r) / d + 2; break; 61 case b: h = (r - g) / d + 4; break; 62 } 63 h /= 6; 64 } 65 return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) }; 66} 67 68function getWatermarkStyle(hex) { 69 const { l } = hexToHsl(hex); 70 if (l < 25) return { opacity: 0.08, blendMode: 'screen' }; 71 if (l > 75) return { opacity: 0.05, blendMode: 'multiply' }; 72 return { opacity: 0.06, blendMode: 'overlay' }; 73} 74 75 76// ============================================================== 77// 2. MASTER SCROLL LOOP (Replaces multiple individual scroll listeners) 78// ============================================================== 79var isScrolling = false; 80window.addEventListener('scroll', function() { 81 if (!isScrolling) { 82 window.requestAnimationFrame(function() { 83 handleMasterScroll(); 84 isScrolling = false; 85 }); 86 isScrolling = true; 87 } 88}, { passive: true }); 89 90function handleMasterScroll() { 91 var scrollPos = jQuery(window).scrollTop(); 92 var threshold = 0; 93 94 // A. Header & Nav Status 95 if (scrollPos > threshold && scrollPos > activeScrollPosition) { 96 if (scrollState != 'down') { if (typeof collapseNav === 'function') collapseN
96av(); } 97 scrollState = 'down'; 98 } 99 if (scrollPos < activeScrollPosition) { 100 if (scrollState != 'up') { if (typeof expandNav === 'function') expandNav(); } 101 scrollState = 'up'; 102 } 103 104 if (scrollPos <= threshold) jQuery('header').removeClass('scrolling'); 105 else jQuery('header').addClass('scrolling'); 106 107 // B. Pinned Links Collapse Check 108 if (Math.abs(scrollPos - initialScrollPos) > 300) { 109 if(typeof collapsePinnedLinks === 'function') collapsePinnedLinks(); 110 initialScrollPos = scrollPos; 111 } 112 113 // C. Highlight Active Links & Offsets 114 if(typeof highlightActiveLink === 'function') highlightActiveLink(scrollPos); 115 if(typeof chkTabsOffsetScroll === 'function') chkTabsOffsetScroll(scrollPos); 116 117 // D. UI Elements Fading 118 if (scrollPos > 400) { 119 jQuery(".news-close svg").addClass('scrolled'); 120 jQuery(".data-capture").addClass('show'); 121 } else { 122 jQuery(".news-close svg").removeClass('scrolled'); 123 jQuery(".data-capture").removeClass('show'); 124 } 125 126 // E. Watermark Parallax (Hoisted Selector, Batched Read/Write) 127 if (watermarks && watermarks.length > 0) { 128 const positions = Array.from(watermarks).map(section => section.getBoundingClientRect().top); 129 watermarks.forEach((section, index) => { 130 section.style.setProperty('--mark-y', `${positions[index] * 0.15}px`); 131 }); 132 } 133 134 activeScrollPosition = scrollPos; 135} 136 137 138// ============================================================== 139// 3. INITIALIZATION ON DOM CONTENT LOADED 140// ============================================================== 141document.addEventListener("DOMContentLoaded", function() { 142 143 // Cache watermarks for the scroll loop 144 watermarks = document.querySelectorAll('.has-logo-mark'); 145 146 // --- A. Lazy Loading (Intersection Observer) --- 147 if ("IntersectionObserver" in window) { 148 const lazyImageObserver = new IntersectionObserver(function(entries, observer) { 149 entries.forEach(function(entry) { 150 if (entry.isIntersecting) { 151 let lazyElement = entry.target; 152 153 if (lazyElement.hasAttribute("data-lz-bg-image")) { 154 let bgUrl = lazyElement.getAttribute("data-lz-bg-image"); 155 let imgPreload = new Image(); 156 imgPreload.onload = function() { 157 lazyElement.style.backgroundImage = "url('" + bgUrl + "')"; 158 lazyElement.removeAttribute("data-lz-bg-image"); 159 let currentOpacity = window.getComputedStyle(lazyElement).opacity; 160 if (typeof lazyElement.animate === "function" && currentOpacity !== "0") { 161 lazyElement.animate([{ opacity: 0 }, { opacity: currentOpacity }], { duration: 500, easing: 'ease-in-out' }); 162 } 163 }; 164 imgPreload.src = bgUrl; 165 } 166 167 if (lazyElement.hasAttribute("data-lz-image")) { 168 let imgUrl = lazyElement.getAttribute("data-lz-image"); 169 lazyElement.onload = function() { 170 let currentOpacity = window.getComputedStyle(lazyElement).opacity; 171 if (typeof lazyElement.animate === "function" && currentOpacity !== "0") { 172 lazyElement.animate([{ opacity: 0 }, { opacity: currentOpacity }], { duration: 500, easing: 'ease-in-out' }); 173 } 174 }; 175 lazyElement.src = imgUrl; 176 lazyElement.removeAttribute("data-lz-image"); 177 } 178 179 observer.unobserve(lazyElement); 180 } 181 }); 182 }, { rootMargin: "0px 0px 300px 0px" }); 183 184 document.querySelectorAll("[data-lz-bg-image], [data-lz-image]").forEach(function(lazyElement) { 185 lazyImageObserver.observe(lazyElement); 186 }); 187 188 const dynamicContentObserver = new MutationObserver(function(mutations) { 189 mutations.forEach(function(mutation) {
190 mutation.addedNodes.forEach(function(node) { 191 if (node.nodeType === 1) { 192 if (node.hasAttribute("data-lz-bg-image") || node.hasAttribute("data-lz-image")) { 193 lazyImageObserver.observe(node); 194 } 195 node.querySelectorAll("[data-lz-bg-image], [data-lz-image]").forEach(function(nestedElement) { 196 lazyImageObserver.observe(nestedElement); 197 }); 198 } 199 }); 200 }); 201 }); 202 dynamicContentObserver.observe(document.body, { childList: true, subtree: true }); 203 204 } else { 205 // Fallback for old browsers 206 document.querySelectorAll("[data-lz-bg-image], [data-lz-image]").forEach(function(lazyElement) { 207 if (lazyElement.hasAttribute("data-lz-bg-image")) { 208 lazyElement.style.backgroundImage = "url('" + lazyElement.getAttribute("data-lz-bg-image") + "')"; 209 lazyElement.removeAttribute("data-lz-bg-image"); 210 } 211 if (lazyElement.hasAttribute("data-lz-image")) { 212 lazyElement.src = lazyElement.getAttribute("data-lz-image"); 213 lazyElement.removeAttribute("data-lz-image"); 214 } 215 }); 216 } 217 218 // --- B. URL Tracking & Pre-population --- 219 const firstName = getURLParameter("firstname"); 220 const lastName = getURLParameter("lastname"); 221 const email = getURLParameter("email"); 222 if (firstName && document.getElementById("first_name")) document.getElementById("first_name").value = firstName; 223 if (lastName && document.getElementById("last_name")) document.getElementById("last_name").value = lastName; 224 if (email && document.getElementById("email")) document.getElementById("email").value = email; 225 226 var trackingMap = { 227 'gclid': '00N4G000006h8QU', 'fbclid': '00NN100000ELHqb', 'msclkid': '00NN100000ELHsD', 228 'rdt_cid': '00NN100000ELHtp', 'epik': '00NN100000ELHvR', 'utm_campaign': 'cm_utm_campaign' 229 }; 230 231 function setTrackingStorage(name, value, days) { 232 var d = new Date(); d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000)); 233 document.cookie = name + "=" + encodeURIComponent(value) + ";expires=" + d.toUTCString() + ";path=/;SameSite=Lax"; 234 try { localStorage.setItem(name, value); } catch (e) {} 235 } 236 237 function getTrackingStorage(name) { 238 var v = ''; 239 try { v = localStorage.getItem(name) || ''; } catch (e) {} 240 if (!v) { 241 var m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)')); 242 v = m ? decodeURIComponent(m[1]) : ''; 243 } 244 return v; 245 } 246 247 Object.keys(trackingMap).forEach(function(paramKey) { 248 var sfFieldName = trackingMap[paramKey]; 249 var urlVal = getURLParameter(paramKey); 250 var existingVal = getTrackingStorage(paramKey); 251 252 if (urlVal && /^[A-Za-z0-9._\-\s%]+$/.test(urlVal)) { 253 if (!existingVal) { setTrackingStorage(paramKey, urlVal, 90); existingVal = urlVal; } 254 } 255
256 var finalVal = existingVal || urlVal; 257 if (finalVal) { 258 document.querySelectorAll('[name="' + sfFieldName + '"]').forEach(function(field) { 259 if (!field.value) field.value = finalVal; 260 }); 261 } 262 }); 263 264 // --- C. Web-to-Lead Spam Validation --- 265 let forms = document.querySelectorAll("form.web-to-lead"); 266 let formTimers = new Map(); 267 let firstInteraction = new Map(); 268 let honeypot = document.querySelector("input[id='00NN1000007znfi']"); 269 let fakeSelect = document.querySelector("select[name='contact_method']"); 270 let questionField = document.querySelector("textarea[id='00ND0000005xIaz']"); 271 let isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0; 272 273 if (honeypot && honeypot.value.trim() !== "") document.body.setAttribute("data-bot-detected", "true"); 274 275 let interactionTypes = new Set(); 276 let interactionCount = 0; 277 let lastInteraction = ""; 278 279 function trackInteraction(event) { 280 let eventType = event.type; 281 if (lastInteraction === eventType) return; 282 lastInteraction = eventType; 283 interactionTypes.add(eventType); 284 interactionCount++; 285 if (interactionCount >= 1) { 286 document.body.setAttribute("data-human", "true"); 287 removeInteractionListeners(); 288 } 289 } 290 291 function removeInteractionListeners() { 292 document.removeEventListener("mousemove", trackInteraction); 293 document.removeEventListener("scroll", trackInteraction); 294 document.removeEventListener("resize", trackInteraction); 295 document.removeEventListener("click", trackInteraction); 296 document.removeEventListener("keydown", trackInteraction); 297 document.removeEventListener("touchstart", trackInteraction); 298 } 299 300 document.addEventListener("mousemove", trackInteraction); 301 document.addEventListener("scroll", trackInteraction); 302 document.addEventListener("resize", trackInteraction); 303 document.addEventListener("click", trackInteraction); 304 document.addEventListener("keydown", trackInteraction); 305 if (isTouchDevice) document.addEventListener("touchstart", trackInteraction); 306 307 if (fakeSelect) { 308 fakeSelect.style.visibility = "hidden"; 309 fakeSelect.style.position = "absolute"; 310 fakeSelect.value = ""; 311 } 312 313 function validateQuestion(text) { 314 let score = 50; 315 text = text.trim().toLowerCase(); 316 let words = text.split(/\s+/); 317 if (text.length < 10 || words.length < 3) score -= 50; 318 if (/https?:\/\/|www\./i.test(text)) score -= 50; 319 if (/^(.)\1{4,}$/.test(text) || /(asdf|qwerty|12345|zxczxc|test|lorem ipsum)/i.test(text)) score -= 50; 320 if (/(.)\1{5,}/.test(text)) score -= 50; 321 322 const spamPhrases = [ "please help me", "i need help", "urgent help needed", "call me now", "hello", "Please contact me", "Support Engineer", "test message", "thank you", "help", "very nice website", "please send some information to my email", "Business Consultant", "please email me the cost details", "web content manager", "business consultant", "Please send some information to my email", "increase your website ranking", "cheap web traffic", "crypto investment opportunity", "best way to earn money", "get rich quick", "I came across your advertisement. Please contact me.", "Your advertisement caught my eye. Please contact me", "Time and tide wait for no man", "Please email me the cost details" ]; 323 let containsSpamPhrase = spamPhrases.some(spam => text.includes(spam)); 324 if (containsSpamPhrase) { 325 let meaningfulWords = words.filter(word => !spamPhrases.includes(word)).length; 326 if (meaningfulWords < 5) score -= 50; 327 } 328 329 const spamKeywords = [ "seo", "marketing", "advertising", "business opportunity", "partnership", "consultant", "ad", "advertisement", "investor", "crypto", "bitcoin", "forex", "dropshipping", "lead generation", "bulk email", "traffic boost", "backlinks", "domain for sale", "paid promotion", "urgent" ]; 330 let spamKeywordMatches = spamKeywords.filter(keyword => text.includes(keyword)).length; 331 if (spamKeywordMatches > 0) score -= spamKeywordMatches * 30; 332 333 const relevantKeywords = [ "travel", "adventure", "trip", "tour", "holiday", "vacation", "itinerary", "experience", "excursion", "journey", "destination", "exploration", "getaway", "cycling", "biking", "bike", "road cycling", "gravel bike", "mountain biking", "e-bike", "electric bike", "bikepacking", "bicycle", "cycle touring", "velo", "walking", "hiking", "trekking", "hillwalking", "rambling", "mountaineering", "long-distance walk", "footpath", "hiking trails", "wilderness walk", "wildlife", "nature", "scenic", "landscape", "forest", "national park", "outdoors", "wilderness", "flora", "fauna", "eco-tourism", "birdwatching", "kayaking", "canoeing", "paddleboarding", "rafting", "climbing", "abseiling", "zip-lining", "wild
333swimming", "sailing", "fishing", "camping", "surfing", "skiing", "snowboarding", "canyoning", "horseback riding", "stargazing", "Scotland", "England", "Ireland", "UK", "Scottish Highlands", "Highlands", "Loch Ness", "Cairngorms", "Isle of Skye", "Orkney", "Hebrides", "Lake District", "Peak District", "Cornwall", "Yorkshire Dales", "Dartmoor", "Snowdonia", "Brecon Beacons", "Glencoe", "Ben Nevis", "accommodation", "lodging", "hotel", "guesthouse", "cottage", "castle", "inn", "bed and breakfast", "resort", "campsite", "hostel", "eco-lodge", "booking", "reservations", "weather", "best time to visit", "climate", "rain", "snow", "seasons", "summer", "winter", "autumn", "spring", "daylight hours", "packing list", "kit list", "gear", "equipment", "hiking boots", "backpack", "tent", "rain jacket", "cycling gear", "travel essentials", "clothing", "waterproofs", "Scottish cuisine", "local food", "whisky", "distillery", "pubs", "afternoon tea", "restaurants", "traditional dishes", "Scottish breakfast", "English breakfast", "craft beer", "seafood", "car rental", "car hire", "self-drive", "train travel", "ferry", "flights", "public transport", "routes", "driving in Scotland", "road trip", "navigation", "Hogmanay", "Fringe Festival", "Edinburgh Festival", "Highland Games", "Celtic music", "Whisky Festival", "Burns Night", "literary festival", "music festival", "eco-friendly travel", "carbon footprint", "leave no trace", "conservation", "wild camping", "green tourism", "responsible tourism", "sustainable travel" ]; 334 let keywordMatches = relevantKeywords.filter(keyword => text.includes(keyword)).length; 335 if (keywordMatches === 1) score += 10; 336 if (keywordMatches >= 2) score += 20; 337 if (keywordMatches >= 3) score += 40; 338 339 return Math.max(0, score); 340 } 341 342 forms.forEach(form => { 343 formTimers.set(form, Date.now()); 344 345 form.addEventListener("input", function() { 346 if (!firstInteraction.has(form)) firstInteraction.set(form, Date.now()); 347 }); 348 349 form.addEventListener("submit", function(event) { 350 let currentTime = Date.now(); 351 let timeSinceLoad = (currentTime - formTimers.get(form)) / 1000; 352 let timeSinceFirstInput = firstInteraction.has(form) ? (currentTime - firstInteraction.get(form)) / 1000 : 0; 353 let spamDetected = false; 354 let userScore = 60; 355 356 if (timeSinceLoad < 1) spamDetected = true; 357 if (timeSinceLoad < 0.5) userScore -= 20; 358 if (timeSinceLoad >= 0.5 && timeSinceLoad < 1) userScore -= 10; 359 if (timeSinceLoad >= 1 && timeSinceLoad < 6) userScore += 10; 360 if (timeSinceLoad >= 3) userScore += 20; 361 362 if (timeSinceFirstInput === 0) spamDetected = true; 363 if (timeSinceFirstInput < 0.5) userScore -= 20; 364 if (timeSinceFirstInput >= 0.5 && timeSinceFirstInput < 1) userScore -= 10; 365 if (timeSinceFirstInput >= 1.5 && timeSinceFirstInput < 3) userScore += 10; 366 if (timeSinceFirstInput >= 3) userScore += 20; 367 368 if (honeypot && honeypot.value.trim() !== "" && honeypot.value !== "Verified Human") spamDetected = true; 369 if (fakeSelect && fakeSelect.value !== "") spamDetected = true; 370 if (timeSinceFirstInput > 0 && timeSinceFirstInput < 1.5) spamDetected = true; 371 else if (timeSinceFirstInput >= 1.5 && timeSinceFirstInput < 3) userScore -= 10; 372 373 let interactionCountCheck = document.body.getAttribute("data-human") === "true" ? interactionTypes.size : 0; 374 userScore += interactionCountCheck; 375 if (interactionCountCheck < 1) spamDetected = true; 376 377 if (questionField) { 378 let questionText = questionField.value.trim(); 379 let questionScore = validateQuestion(questionText); 380 if (questionScore < 50) spamDetected = true; 381 } 382 383 let firstNameField = form.querySelector("input[name='first_name'], input[id='first_name']"); 384 let lastNameField = form.querySelector("input[name='last_name'], input[id='last_name']"); 385 if (firstNameField && lastNameField && firstNameField.value.trim().toLowerCase() === lastNameField.value.trim().toLowerCase()) { 386 spamDetected = true; 387 } 388 389 if (spamDetected) { 390 event.stopImmediatePropagation(); 391 event.preventDefault(); 392 return false; 393 } 394 395 userScore = Math.max(0, Math.min(userScore, 100)); 396 if (userScore < 30) { 397 event.stopImmediatePropagation(); 398 event.preventDefault(); 399 return false; 400 } 401 402 if(honeypot) honeypot.value = "Verified Human"; 403 }); 404 });
405 406 // --- D. Mouse Tracking for Custom Button Properties (Throttled & Pointer Gated) --- 407 if (window.matchMedia('(pointer: fine)').matches) { 408 document.querySelectorAll('.btn, a span.btn, .phone-txt, .submit').forEach(button => { 409 let isHovering = false; 410 button.addEventListener('mousemove', (e) => { 411 if (!isHovering) { 412 window.requestAnimationFrame(() => { 413 const rect = button.getBoundingClientRect(); 414 button.style.setProperty('--mouse-x', `${e.clientX - rect.left}px`); 415 button.style.setProperty('--mouse-y', `${e.clientY - rect.top}px`); 416 isHovering = false; 417 }); 418 isHovering = true; 419 } 420 }); 421 }); 422 } 423 424 // --- E. WP Captions Width Fix --- 425 document.querySelectorAll('div.wp-caption').forEach(function(wpCaption) { 426 wpCaption.style.width = 'unset'; 427 }); 428}); 429 430 431// ============================================================== 432// 4. JQUERY DOM READY / INITIALIZATIONS 433// ============================================================== 434jQuery(document).ready(function($) { 435 436 // --- Pinned Links Logic --- 437 $('#pinned-links ul').hide(); 438 initialScrollPos = $(window).scrollTop(); 439 440 function adjustIconsAndWidth() { 441 if (window.innerWidth <= 740) { 442 $(".toggle-icon-mob").css("display", "block"); 443 $(".toggle-icon").css("display", "none"); 444 $("#pinned-links").css("width", "35px"); 445 } else { 446 $(".toggle-icon-mob").css("display", "none"); 447 $(".toggle-icon").css("display", "block"); 448 $("#pinned-links").css("width", "70px"); 449 } 450 } 451 452 $(window).on("resize", debounce(() => { 453 adjustIconsAndWidth(); 454 if(typeof collapsePinnedLinks === "function") collapsePinnedLinks(); 455 if(typeof chkPlacesLocations === "function") chkPlacesLocations(); 456 }, 100)); 457 458 $('#pinned-links-header').click(function() { 459 $('#pinned-links ul').toggle(); 460 $("#pinned-links").toggleClass("expanded"); 461 if ($("#pinned-links").hasClass("expanded")) { 462 var expandedWidth = $('.pinned-links ul li').length >= 10 ? "380px" : "220px"; 463 $("#pinned-links").css({"width": expandedWidth, "height": "auto"}); 464 $(".pinned-links ul li").css("opacity", "1"); 465 $(".close-links").css("display", "block"); 466 $(".toggle-icon, .toggle-icon-mob").css("display", "none"); 467 468 if ($('.pinned-links ul li').length >= 10) { 469 var columnBreakIndex = Math.ceil($('.pinned-links ul li').length / 2 + 1); 470 $('.pinned-links ul li:nth-child(' + columnBreakIndex + ')').css("border-top", "none"); 471 } 472 } else { 473 collapsePinnedLinks(); 474 } 475 var icon = $('#toggle-icon').text(); 476 $('#toggle-icon').text(icon == '+' ? '-' : '+'); 477 }); 478 479 // --- Mobile Nav & Sub-menus --- 480 $('.nav-mob-btn').click(function(event) { 481 event.preventDefault(); 482 $('#header-top-wrapper').toggleClass('open'); 483 $('body').toggleClass('no-scroll'); 484 }); 485 486 $(document).keyup(function(e) { 487 if (e.keyCode == 27 && typeof mainPopupClose === 'function') { 488 mainPopupClose(); 489 } 490 }); 491 492 if ($('body').width() <= 1100 && $('.mobile-menu-housekeeping .menu-item-has-children').length > 0) { 493 var strtmp = ''; 494 $('.mobile-menu-housekeeping .menu-item-has-children').each(function() { 495 strtmp += '<li class="page_item page_item_has_children">' + $(this).html().replace('sub-menu', 'children') + '</li>'; 496 $(this).remove(); 497 }); 498 $('.header-bottom .menu-main > ul').append(strtmp); 499 } 500 501 // --- ACF Highlights (Consolidated DRY) --- 502 $('.highlight-1, .highlight-2, .highlight-3').on('click', function() { 503 let $this = $(this); 504 let activeClass = $this.attr('class').match(/highlight-[1-3]/)[0]; 505 let bgClass = activeClass.replace('highlight', 'bg'); 506 507 $('.highlight-1, .highlight-2, .highlight-3') 508 .removeClass('highlight-active hide-read-more') 509 .addClass('hide-highlight') 510 .css('text-align', 'center'); 511 512 $this.removeClass('hide-highlight') 513 .addClass('highlight-active hide-read-more') 514 .css('text-align', 'left'); 515 516 $('.bg-1, .bg-2, .bg-3').css('opacity', 0); 517 $('.' + bgClass).css('opacity', 1); 518 });
519 520 // --- Multi-Country Popups (Consolidated DRY) --- 521 for (let i = 0; i <= 11; i++) { 522 $('.multicountry-btn:eq('+i+'), .multicountry-btn-itinerary:eq('+i+'), .multicountry-title:eq('+i+'), .multicountry-image:eq('+i+')').click(function() { 523 $('.multicountry:eq('+i+')').fadeIn(); 524 }); 525 $('.multicountry-table-title:eq('+i+'), .multicountry-table-btn:eq('+i+')').click(function() { 526 $('.multicountry-2:eq('+i+')').fadeIn(); 527 }); 528 } 529 530 $('.multicountry .closebtn, .multicountry').click(() => $('.multicountry').fadeOut()); 531 $('.multicountry-2 .closebtn, .multicountry-2').click(() => $('.multicountry-2').fadeOut()); 532 $('.multicountry-nav .closebtn, .multicountry-nav').click(() => $('.multicountry-nav').fadeOut()); 533 534 $(document).bind('keydown', function(e) { 535 if (e.which == 27) { 536 $('.multicountry, .multicountry-nav, .multicountry-2').fadeOut(); 537 } 538 }); 539 540 // --- Newsletters & Previous Pages --- 541 $(".back-one").click(function() { window.history.go(-1); return false; }); 542 $(".news-close").click(function() { 543 $(".newsletter-form.sticky").css("display", "none"); 544 $(".news-open svg").css("display", "inline-block"); 545 $(".news-close svg").css("display", "none"); 546 }); 547 $(".news-open").click(function() { 548 $(".newsletter-form.sticky").css("display", "inline-block"); 549 $(".news-open svg").css("display", "none"); 550 $(".news-close svg").css("display", "inline-block"); 551 }); 552 553 // --- Trip Page Tab Navigations --- 554 $('.pages a').click(function(evnt) { 555 evnt.preventDefault(); 556 if(typeof chkPlacesLocations === 'function') chkPlacesLocations(); 557 var tmpHash = $(this).attr('href'); 558 if (tmpHash == '#itinerary') { 559 if(typeof DownloadItinerary === 'function') DownloadItinerary('ajaxformsubmit'); 560 } else { 561 if (scrollPlaces[tmpHash]) { 562 var scrollPos = $(window).scrollTop(); 563 var scrollTo = (scrollPlaces[tmpHash] < scrollPos) ? 300 : 260; 564 $("html, body").animate({ scrollTop: (scrollPlaces[tmpHash] - scrollTo) + "px" }); 565 } 566 } 567 }); 568 569 // --- External Instances --- 570 new StrutInternalLinks(); 571 new StrutMobileNav(); 572 chkUserState(); 573}); 574 575 576// ============================================================== 577// 5. WINDOW LOAD HANDLERS 578// ============================================================== 579window.addEventListener('load', function() { 580 581 // Check itinerary popup from URL Hash 582 // Runs first so an error in any of the calls below can never prevent it firing 583 if (window.location.hash == '#download_itinerary' && typeof DownloadItinerary === 'function') { 584 DownloadItinerary('ajaxformsubmit'); 585 } 586 587 // Core Layout Functions 588 try { if(typeof populateTripTabs === 'function') populateTripTabs(); } catch(e) { console.error(e); } 589 try { if(typeof chkPlacesLocations === 'function') chkPlacesLocations(); } catch(e) { console.error(e); } 590 try { if(typeof smoothlabelsDo === 'function') smoothlabelsDo(); } catch(e) { console.error(e); } 591 try { if(typeof initializeReCaptcha === 'function') initializeReCaptcha(); } catch(e) { console.error(e); } 592 try { if(typeof initInPagePopup === 'function') initInPagePopup(); } catch(e) { console.error(e); } 593 if (typeof initItineryDownload === "function") initItineryDownload(); 594 595 // Baguette Box Lightbox Init 596 if (typeof baguetteBox !== 'undefined') { 597 if (jQuery('#tripgalleryimages').length > 0) baguetteBox.run('#tripgalleryimages'); 598 else baguetteBox.run('section.content'); 599 baguetteBox.run('section.gallery-content'); 600 } 601 602 // Scroll Down Link formatting 603 if (jQuery('.scrolldownlink').length > 0) { 604 var tmpText = jQuery('.scrolldownlink').html(); 605 jQuery('.scrolldownlink').html('<a href="JavaScript:scrollDowntoContent();" class="btn"><span>' + tmpText + '</span></a>'); 606 } 607
608 // Accordions Reset (Debounced Resizing) 609 if (jQuery('.accordioncontent').length > 0) { 610 jQuery('.accordioncontent dd.info').css('height', 'auto').each(function() { 611 jQuery(this).attr('data-height', jQuery(this).height()).css('height', '0px'); 612 }); 613 jQuery(window).resize(debounce(function() { 614 jQuery('.accordioncontent dd.info').each(function() { 615 jQuery(this).removeClass('open').css('height', 'auto').attr('data-height', jQuery(this).height()).css('height', '0px'); 616 }); 617 }, 150)); 618 } 619 if (jQuery('.trip-accordioncontent').length > 0) { 620 jQuery('.trip-accordioncontent dd.info').css('height', 'auto'); 621 jQuery(window).resize(debounce(function() { 622 jQuery('.trip-accordioncontent dd.info').each(function() { jQuery(this).css('height', 'auto'); }); 623 }, 150)); 624 } 625 626 // Tour Dates Slider 627 if (document.querySelectorAll('.tour-dates .year > li').length > 1) { 628 jQuery('.tour-dates .year > li:eq(1)').css({'left': '150%', 'top': '-80px'}); 629 var aNext = document.createElement('a'); aNext.className = 'btn-next'; 630 aNext.onclick = function() { 631 jQuery('.tour-dates .btn-next').hide(); jQuery('.tour-dates .btn-prev').show(); 632 jQuery('.tour-dates .year > li:eq(1)').css('left', '0px'); jQuery('.tour-dates .year > li:eq(0)').css('left', '-150%'); 633 }; 634 var aPrev = document.createElement('a'); aPrev.className = 'btn-prev'; 635 aPrev.onclick = function() { 636 jQuery('.tour-dates .btn-prev').hide(); jQuery('.tour-dates .btn-next').show(); 637 jQuery('.tour-dates .year > li:eq(1)').css('left', '150%'); jQuery('.tour-dates .year > li:eq(0)').css('left', '0px'); 638 }; 639 document.querySelector('.tour-dates .slider').prepend(aNext); 640 document.querySelector('.tour-dates .slider').prepend(aPrev); 641 jQuery('.tour-dates .btn-prev').hide(); 642 } 643 644 // Kinsta No-Cache fix 645 document.querySelectorAll('a[href*="nc=1"]').forEach(function(elm) { 646 elm.setAttribute("href", elm.getAttribute('href').replace("nc=1", "nc=" + Date.now())); 647 }); 648 649// Vimeo setup (Fixed Timing & Fullscreen Order) 650jQuery(document).ready(function($) { 651 var iframe = document.querySelector('iframe#video'); 652 653 // Check if the iframe exists and the Vimeo API is loaded 654 if(iframe && typeof Vimeo !== 'undefined') { 655 var player = new Vimeo.Player(iframe); 656 657 // Button functionality 658 $('#start, .video-thumbnail').click(function (e) { 659 e.preventDefault(); 660 console.log("Showreel button was successfully clicked!"); 661 662 // 1. Make the iframe visible FIRST 663 $("#video").css("display", "block"); 664 665 // 2. Attempt to play the video 666 player.play().then(function() { 667 // 3. Request fullscreen after playback starts 668 player.requestFullscreen().catch(function(error) { 669 console.error("Fullscreen request failed:", error); 670 }); 671 }).catch(function(error) { 672 console.error("Vimeo Play failed:", error); 673 }); 674 }); 675 676 player.on('exitFullscreen', function () { 677 player.unload(); 678 $("#video").css("display", "none"); 679 }); 680 681 $(document).bind('fullscreenchange webkitfullscreenchange mozfullscreenchange msfullscreenchange', function (e) { 682 var fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullscreenElement || document.msFullscreenElement; 683 if (!fullscreenElement) { 684 player.unload(); 685 $("#video").css("display", "none"); 686 } 687 }); 688 } else { 689 console.warn("Vimeo Player API is missing or iframe wasn't found."); 690 } 691}); 692 693}); 694 695 696// ============================================================== 697// 6. VANILLA JS - TRIP PAGE GALLERIES 698// ============================================================== 699document.addEventListener('DOMContentLoaded', function() {
700 let currentImageIndex = 0; 701 const images = []; 702 let touchStartX = 0; 703 704// Grab all gallery items, but explicitly filter out the #start video thumbnail 705const allTargetImages = document.querySelectorAll('.gallery-item, .proposal-gallery-image'); 706const galleryImages = Array.from(allTargetImages).filter(image => image.id !== 'start'); 707 708 // Updated extraction logic to support lazy data-url attributes 709 galleryImages.forEach(image => { 710 let imageUrl = image.getAttribute('data-url'); 711 if (!imageUrl && image.style.backgroundImage) { 712 imageUrl = image.style.backgroundImage.replace(/url\(['"]?(.*?)['"]?\)/, '$1'); 713 } 714 if (!imageUrl && image.src) { 715 imageUrl = image.src; 716 } 717 images.push(imageUrl); 718 }); 719 720 function changeImage(offset) { 721 currentImageIndex += offset; 722 if (currentImageIndex < 0) currentImageIndex = images.length - 1; 723 else if (currentImageIndex >= images.length) currentImageIndex = 0; 724 updateLightbox(); 725 } 726 727 function handleSwipeStart(event) { touchStartX = event.touches[0].clientX; } 728 function handleSwipeMove(event) { 729 if (touchStartX > 0) { 730 const swipeDistance = event.touches[0].clientX - touchStartX; 731 if (swipeDistance > 50) { changeImage(-1); touchStartX = -1; } 732 else if (swipeDistance < -50) { changeImage(1); touchStartX = -1; } 733 } 734 } 735 function handleSwipeEnd() { touchStartX = 0; } 736 737 const lightboxContent = document.getElementById('gallery-lightbox-content'); 738 if (lightboxContent) { 739 lightboxContent.addEventListener('touchstart', handleSwipeStart); 740 lightboxContent.addEventListener('touchmove', handleSwipeMove); 741 lightboxContent.addEventListener('touchend', handleSwipeEnd); 742 } 743 744 function closeLightbox() { 745 document.getElementById('gallery-lightbox').style.display = 'none'; 746 document.removeEventListener('keydown', handleKeyPress); 747 } 748 749 function updateLightbox() { 750 const lightboxContent = document.getElementById('gallery-lightbox-content'); 751 const captionElement = document.getElementById('gallery-caption'); 752 lightboxContent.innerHTML = ''; 753 754 const currentGalleryItem = galleryImages[currentImageIndex]; 755 const imgElement = document.createElement('img'); 756 const altText = currentGalleryItem.getAttribute('data-alt') || currentGalleryItem.alt || 'Gallery Image'; 757 const captionText = currentGalleryItem.getAttribute('data-caption') || currentGalleryItem.dataset.caption || ''; 758 759 imgElement.src = images[currentImageIndex] || currentGalleryItem.src; 760 imgElement.alt = altText; 761 lightboxContent.appendChild(imgElement); 762 763 captionElement.innerHTML = ` 764 <p style="font-size:24px; line-height:28px; margin-bottom:15px; color:#fff; font-family: 'astoria', Arial, 'Helvetica Neue', Helvetica, sans-serif;">${captionText}</p> 765 ${currentImageIndex + 1} of ${images.length || galleryImages.length} 766 `; 767 } 768 769 function handleKeyPress(event) { 770 if (event.key === 'Escape') closeLightbox(); 771 else if (event.key === 'ArrowLeft') changeImage(-1); 772 else if (event.key === 'ArrowRight') changeImage(1); 773 } 774 775 function openLightbox(index) { 776 currentImageIndex = index; 777 updateLightbox(); 778 document.getElementById('gallery-lightbox').style.display = 'flex'; 779 document.addEventListener('keydown', handleKeyPress); 780 } 781 782 const closeBtn = document.querySelector('.gallery-close'); 783 const prevBtn = document.querySelector('.gallery-prev'); 784 const nextBtn = document.querySelector('.gallery-next'); 785 786 if (closeBtn) closeBtn.addEventListener('click', closeLightbox); 787 if (prevBtn) prevBtn.addEventListener('click', () => changeImage(-1)); 788 if (nextBtn) nextBtn.addEventListener('click', () => changeImage(1)); 789 790 galleryImages.forEach((image, index) => { 791 image.addEventListener('click', () => openLightbox(index)); 792 });
793 794 const galleryLightbox = document.getElementById('gallery-lightbox'); 795 if (galleryLightbox) { 796 galleryLightbox.addEventListener('click', function(event) { 797 if (event.target === this) closeLightbox(); 798 }); 799 } 800}); 801 802 803// ============================================================== 804// 7. JQUERY - ACTIVITY CAROUSEL 805// ============================================================== 806jQuery(function() { 807 jQuery(".carousel-slider").each(function() { 808 const $slider = jQuery(this); 809 const $carousel = $slider.find(".carousel"); 810 const $allItems = $carousel.children(".activity"); 811 const $realItems = $allItems.filter(":not(.cloned)"); 812 813 const $controls = $slider.next(".activity-carousel-controls"); 814 const $arrows = $controls.find(".arrow"); 815 const $status = $controls.find(".carousel-status"); 816 817 let currentIndex = 0, itemW, $wrap, wrapW, centerOff; 818 let isCheckingVisibility = false; 819 const trackTrans = "transform .5s ease"; 820 const itemTrans = $allItems.css("transition"); 821 822 function recalc() { 823 itemW = $realItems.first().outerWidth(true); 824 $wrap = $carousel.closest(".carousel-wrapper"); 825 wrapW = $wrap.width(); 826 centerOff = (wrapW - itemW) / 2; 827 } 828 829 function setPos(idx, animate = true) { 830 const shift = (idx + 1) * itemW - centerOff; 831 $carousel.css({ 832 transition: animate ? trackTrans : "none", 833 transform: `translateX(-${shift}px)` 834 }); 835 } 836 837 function updateDisplay() { 838 const totalAll = $allItems.length; 839 let actIdx = ((currentIndex + 1) % totalAll + totalAll) % totalAll; 840 $allItems.removeClass("active prev next"); 841 $allItems.eq(actIdx).addClass("active"); 842 $allItems.eq((actIdx - 1 + totalAll) % totalAll).addClass("prev"); 843 $allItems.eq((actIdx + 1) % totalAll).addClass("next"); 844 845 const N = $realItems.length; 846 let realIdx = ((currentIndex % N) + N) % N; 847 $status.text(`${realIdx + 1} of ${N}`); 848 } 849 850 function slideTo(idx) { 851 currentIndex = idx; 852 setPos(idx, true); 853 updateDisplay(); 854 855 $carousel.one("transitionend", () => { 856 let wrapTo = null; 857 const N = $realItems.length; 858 if (currentIndex < 0) wrapTo = N - 1; 859 else if (currentIndex >= N) wrapTo = 0; 860 861 if (wrapTo !== null) { 862 $carousel.css("transition", "none"); 863 $allItems.css("transition", "none"); 864 currentIndex = wrapTo; 865 setPos(currentIndex, false); 866 updateDisplay(); 867 void $carousel[0].offsetHeight; 868 $carousel.css("transition", trackTrans); 869 $allItems.css("transition", itemTrans); 870 } 871 }); 872 } 873 874 function initialPosition() { 875 if ($slider.is(':visible')) { 876 recalc(); setPos(currentIndex, false); updateDisplay(); 877 isCheckingVisibility = false; 878 } else if (!isCheckingVisibility) { 879 isCheckingVisibility = true; 880 checkVisibilityLoop(); 881 } 882 } 883 884 function checkVisibilityLoop() { 885 if (!isCheckingVisibility) return; 886 if ($slider.is(':visible')) initialPosition(); 887 else requestAnimationFrame(checkVisibilityLoop); 888 } 889 890 $arrows.on("click", function() { 891 slideTo(currentIndex + (jQuery(this).hasClass("prev") ? -1 : 1)); 892 }); 893 894 let dragging = false, startX = 0, baseX = 0; 895 const getX = () => new DOMMatrixReadOnly(window.getComputedStyle($carousel[0]).transform).m41; 896 897 $carousel.on("mousedown touchstart", e => { 898 dragging = true; 899 startX = e.pageX || e.originalEvent.touches[0].pageX; 900 baseX = getX(); 901 $carousel.addClass("dragging"); 902 }); 903 904 $carousel.on("mousemove touchmove", e => { 905 if (!dragging) return; 906 const x = e.pageX || e.originalEvent.touches[0].pageX; 907 $carousel.css("transform", `translateX(${baseX + (x - startX)}px)`); 908 }); 909 910 $carousel.on("mouseup touchend mouseleave", e => { 911 if (!dragging) return; 912 dragging = false; 913 $carousel.removeClass("dragging"); 914 const x = e.pageX || (e.changedTouches ? e.changedTouches[0].pageX : e.pageX); 915 const delta = x - startX; 916 const thr = itemW / 4; 917 if (delta > thr) slideTo(currentIndex - 1); 918 else if (delta < -thr) slideTo(currentIndex + 1); 919 else slideTo(currentIndex); 920 }); 921 922 if ('ontouchstart' in window || navigator.maxTouchPoints > 0) { 923 $carousel.on("touchend", ".activity", function(e) { 924 if (dragging) return; 925 if (e.changedTouches && Math.abs(e.changedTouches[0].pageX - startX) < 10) { 926 jQuery(this).toggleClass("show-text"); 927 } 928 }); 929 } 930 931 jQuery(window).on('load', initialPosition); 932 initialPosition(); 933 jQuery(window).on("resize", debounce(() => { 934 recalc(); setPos(currentIndex, false); 935 }, 100)); 936 });
937}); 938 939 940// ============================================================== 941// 8. GLOBAL FUNCTIONS & CLASSES (Unmodified specific logic) 942// ============================================================== 943 944function collapsePinnedLinks() { 945 jQuery('#pinned-links ul').hide(); 946 jQuery("#pinned-links").removeClass("expanded").css("height", "60px"); 947 jQuery(".close-links").css("display", "none"); 948 jQuery(".pinned-links ul li").css("opacity", "0"); 949 if (window.innerWidth <= 740) { 950 jQuery(".toggle-icon-mob").css("display", "block"); 951 jQuery(".toggle-icon").css("display", "none"); 952 jQuery("#pinned-links").css("width", "35px"); 953 } else { 954 jQuery(".toggle-icon-mob").css("display", "none"); 955 jQuery(".toggle-icon").css("display", "block"); 956 jQuery("#pinned-links").css("width", "70px"); 957 } 958} 959 960function collapseNav() { 961 jQuery('header').addClass('collapsedheader'); 962 jQuery('.trip-nav-price').removeClass('collapsedheader'); 963 jQuery('#earlybirdscontainer').addClass('collapse'); 964 jQuery('.sticky-filter').removeClass('sticky-filter-scroll'); 965} 966 967function expandNav() { 968 jQuery('header').removeClass('collapsedheader'); 969 jQuery('.trip-nav-price').addClass('collapsedheader'); 970 jQuery('#earlybirdscontainer').removeClass('collapse'); 971 jQuery('.sticky-filter').removeClass('sticky-filter-scroll'); 972} 973 974function highlightActiveLink(scrollPos = jQuery(window).scrollTop()) { 975 jQuery('.pinned-links ul li').each(function() { 976 var currLink = jQuery(this).find('a'); 977 var refElement = jQuery(currLink.attr("href")); 978 if (refElement.length) { 979 if (refElement.position().top - scrollOffset <= scrollPos && refElement.position().top + refElement.height() + scrollOffset > scrollPos) { 980 jQuery('.pinned-links ul li').removeClass("active-link"); 981 jQuery(this).addClass("active-link"); 982 } else { 983 jQuery(this).removeClass("active-link"); 984 } 985 } 986 }); 987} 988 989function chkTabsOffsetScroll(scrollPos = jQuery(window).scrollTop()) { 990 var minDiffName = '', minDiff = 50000; 991 jQuery('.pages a').each(function(itm) { 992 var targetEl = jQuery(jQuery(this).attr('href')); 993 if (targetEl.length) { 994 var tmpDiff = Math.abs(targetEl.offset().top - scrollPos); 995 if (tmpDiff < minDiff) { minDiff = tmpDiff; minDiffName = itm; } 996 } 997 }); 998 jQuery('.pages a').removeClass('active'); 999 jQuery('.pages a:eq(' + minDiffName + ')').addClass('active'); 1000} 1001 1002function chkPlacesLocations() { 1003 scrollPlaces = new Array(); 1004 jQuery('.pages li a').each(function() { 1005 var targetEl = jQuery(jQuery(this).attr('href')); 1006 if (targetEl.length) scrollPlaces[jQuery(this).attr('href')] = targetEl.offset().top + 150; 1007 }); 1008} 1009 1010function searchSubmit() { 1011 var URL = jQuery('#activity_url').val(); 1012 var trip_dates = jQuery('#trip_dates').val(); 1013 if (URL === '-1') { 1014 alert('Please select what you want to do?'); 1015 } else { 1016 if (/[rgbRGB,]/.test(jQuery('#difficulty_level_val').val())) { 1017 URL += '?di=' + jQuery('#difficulty_level_val').val(); 1018 } 1019 window.location.href = URL + (trip_dates !== '-1' ? '#' + trip_dates : '#activitysearch'); 1020 } 1021} 1022 1023function activity_changed(url) { 1024 if (jQuery('#activity_url').val() === '-1') { 1025 jQuery('#overlay-holiday-search').removeClass('selected'); 1026 jQuery('#trip_dates, #difficulty_level_val').attr('disabled', 'disabled'); 1027 jQuery('#trip_dates').html('<option value="-1">Dates</option>'); 1028 } else { 1029 jQuery('#trip_dates').html('<option value="-1">Loading...</option>'); 1030 var activity = jQuery('#activity_url option:selected').text(); 1031 if (url) { 1032 jQuery.get(url + '/?activity=' + activity, function(data) { 1033 jQuery('#trip_dates').html(data); 1034 jQuery('#trip_dates, #difficulty_level_val').removeAttr('disabled'); 1035 }); 1036 } else console.error('URL parameter is undefined.'); 1037 } 1038} 1039 1040function searchSubmit2() { 1041 var URL = jQuery('#activity_url2').val(); 1042 var trip_dates = jQuery('#trip_dates').val(); 1043 if (URL === '-1') alert('Please select what you want to do?'); 1044 else window.location = URL + (trip_dates !== '-1' ? '#' + trip_dates : '#activitysearch2'); 1045} 1046 1047function activity_changed2(url) { 1048 if (jQuery('#activity_url2').val() === '-1') { 1049 jQuery('#overlay-holiday-search2').removeClass('selected'); 1050 jQuery('#trip_dates2').attr('disabled', 'disabled').html('<option value="-1">When do you want to go?</option>'); 1051 } else { 1052 jQuery('#overlay-holiday-search2').addClass('selected'); 1053 jQuery('#trip_dates2').html('<option value="-1">Loading...</option>'); 1054 var activity = jQuery('#activity_url2 option:selected').text(); 1055 jQuery.get(url + '/?activity=' + activity, function(data) { 1056 jQuery('#trip_dates2').html(data).removeAttr('disabled'); 1057 }); 1058 } 1059} 1060 1061function toggleToViewPrices() { jQuery('.pages a[href="#pricesdates"]').click(); } 1062function toggleToViewReviews() { jQuery('.pages a[href="#reviews"]').click(); } 1063 1064function populateTripTabs() { 1065 jQuery('.triptabbox').each(function() { 1066 jQuery('.pages').append('<li><a href="#' + jQuery(this).attr('id') + '">' + jQuery(this).attr('data-name') + '</a></li>'); 1067 });
1068} 1069 1070function scrollDowntoContent() { 1071 if (jQuery('section.content').length > 0) { 1072 var tmpOffset = jQuery('section.content:eq(0)').offset(); 1073 jQuery("html, body").animate({ scrollTop: tmpOffset.top - 80 + "px" }); 1074 } else { 1075 jQuery("html, body").animate({ scrollTop: "700px" }); 1076 } 1077} 1078 1079function chkUserState() { 1080 // Only fire the AJAX check if the user has the custom client portal cookie. 1081 // This stops anonymous users from generating an expensive request on every page load. 1082 if (document.cookie.indexOf('client_logged_in=true') > -1) { 1083 jQuery.get('/?userState', function(data) { 1084 jQuery('#sign-in').remove(); 1085 jQuery('#header-top').prepend(data); 1086 }); 1087 } 1088} 1089 1090function smoothlabelsDo() { 1091 if (jQuery('.smooth-label').length > 0) { 1092 jQuery('.smooth-label input, .smooth-label textarea').each(smoothLabelCheckInput); 1093 jQuery('.smooth-label input, .smooth-label textarea').focus(function() { 1094 jQuery(this).parent('.smooth-label').addClass('filled'); 1095 }); 1096 jQuery('.smooth-label input, .smooth-label textarea').change(smoothLabelCheckInput); 1097 jQuery('.smooth-label input, .smooth-label textarea').blur(smoothLabelCheckInput); 1098 } 1099} 1100 1101function smoothLabelCheckInput() { 1102 var $input = jQuery(this); 1103 if ($input && !$input.hasClass('nofill')) { 1104 if ($input.val() == '') $input.parent('.smooth-label').removeClass('filled'); 1105 else $input.parent('.smooth-label').addClass('filled'); 1106 } 1107} 1108 1109function toggleIncluded(itm) { 1110 var aItem = jQuery('a[href="JavaScript:toggleIncluded(\'' + itm + '\');"]'); 1111 var accordionItem = jQuery('#' + itm); 1112 1113 if (accordionItem.hasClass('open')) { 1114 aItem.removeClass('open'); 1115 accordionItem.stop().animate({ height: '0px' }, function() { 1116 accordionItem.removeClass('open'); 1117 }); 1118 aItem.children('span.expand').html('Read More'); 1119 } else { 1120 aItem.addClass('open'); 1121 accordionItem.css('height', 'auto'); 1122 var dynamicHeight = accordionItem.height(); 1123 accordionItem.css('height', '0px'); 1124 accordionItem.stop().animate({ height: dynamicHeight + 'px' }, function() { 1125 accordionItem.addClass('open'); 1126 accordionItem.css('height', 'auto'); 1127 }); 1128 aItem.children('span.expand').html('Read Less'); 1129 } 1130} 1131 1132function toggleIncludedAll() { 1133 if (jQuery('.expand-all').hasClass('open')) { 1134 jQuery('.expand-all').removeClass('open').html('Expand All'); 1135 jQuery('.accordioncontent a.read-more').each(function() { 1136 if (jQuery(this).hasClass('open')) toggleIncluded(jQuery(this).attr('href').replace("JavaScript:toggleIncluded(\'", "").replace("\');", "")); 1137 }); 1138 } else { 1139 jQuery('.expand-all').addClass('open').html('Collapse All'); 1140 jQuery('.accordioncontent a.read-more').each(function() { 1141 if (!jQuery(this).hasClass('open')) toggleIncluded(jQuery(this).attr('href').replace("JavaScript:toggleIncluded(\'", "").replace("\');", "")); 1142 }); 1143 } 1144} 1145 1146function tripToggleIncluded(itm) { 1147 var aItem = jQuery('a[href="JavaScript:tripToggleIncluded(\'' + itm + '\');"]'); 1148 var accordionItem = jQuery('#' + itm); 1149 1150 if (accordionItem.hasClass('open')) { 1151 aItem.removeClass('open'); 1152 accordionItem.stop().animate({ height: '0px', minHeight: '0px' }, function() { 1153 accordionItem.removeClass('open'); 1154 }); 1155 aItem.children('span.expand').html('Read More'); 1156 } else { 1157 aItem.addClass('open'); 1158 accordionItem.css({ height: 'auto', minHeight: '0px' }); 1159 var dynamicHeight = accordionItem.height(); 1160 accordionItem.css('height', '0px'); 1161 accordionItem.stop().animate({ height: dynamicHeight + 'px', minHeight: dynamicHeight + 'px' }, function() { 1162 accordionItem.addClass('open'); 1163 accordionItem.css('height', 'auto'); 1164 }); 1165 aItem.children('span.expand').html('Read Less'); 1166 } 1167} 1168 1169function tripToggleIncludedAll() { 1170 if (jQuery('.trip-expand-all').hasClass('open')) { 1171 jQuery('.trip-expand-all').removeClass('open').html('Expand All'); 1172 jQuery('.trip-accordioncontent a.read-more').each(function() { 1173 if (jQuery(this).hasClass('open')) tripToggleIncluded(jQuery(this).attr('href').replace("JavaScript:tripToggleIncluded(\'", "").replace("\');", "")); 1174 }); 1175 } else { 1176 jQuery('.trip-expand-all').addClass('open').html('Collapse All'); 1177 jQuery('.trip-accordioncontent a.read-more').each(function() { 1178 if (!jQuery(this).hasClass('open')) tripToggleIncluded(jQuery(this).attr('href').replace("JavaScript:tripToggleIncluded(\'", "").replace("\');", "")); 1179 }); 1180 } 1181} 1182 1183function showTopics() { 1184 jQuery('body').toggleClass('topics-open'); 1185 jQuery('.overlay').click(function() { showTopics(); }); 1186} 1187 1188function toggleScheduledDates() { 1189 jQuery('#tab-01, #tab-02').removeClass('active'); 1190 jQuery('#tab-01').addClass('active'); 1191 jQuery('#trip-panel-01').show(); 1192 jQuery('#trip-panel-02').hide(); 1193} 1194 1195function toggleMakeItPrivate() { 1196 jQuery('#tab-01, #tab-02').removeClass('active'); 1197 jQuery('#tab-02').addClass('active'); 1198 jQuery('#trip-panel-01').hide(); 1199 jQuery('#trip-panel-02').show(); 1200 if (typeof grecaptcha !== 'undefined') grecaptcha.render(document.querySelector('.gcaptcha-private-placeholder'), { 'sitekey': '6LcCVmsUAAAAAJmU2EbobNH9lZ9TXYFDqjrfDgKf' }); 1201} 1202 1203function showAllTripsInBox() { 1204 jQuery('.hiddentripbox:lt(8)').removeClass('hiddentripbox'); 1205 if (jQuery('.hiddentripbox').length == 0) jQuery('.tripboxmore').remove(); 1206} 1207 1208function showMapToolTip(evt, title) { 1209 jQuery('#maptooltip').stop().remove(); 1210 jQuery('body').append('<div id="maptooltip" style="display: none;">' + title + '</div>'); 1211 var width = Math.round(jQuery('#maptooltip').width() / 2); 1212 var left = 0, top = 0; 1213 if (evt) { 1214 var mouseEvent = evt.ya || evt.xa; 1215 if (mouseEvent) { left = mouseEvent.pageX - width; top = mouseEvent.pageY + 20; } 1216 } 1217 jQuery('#maptooltip').css({ 'left': left + 'px', 'top': top + 'px' }).fadeIn(); 1218} 1219 1220function removeMapToolTip(evt) { 1221 jQuery('#maptooltip').stop().fadeOut(function() { jQuery('#maptooltip').remove(); }); 1222} 1223 1224function DownloadItinerary(id) { 1225 if (jQuery('#' + id + ' .download').length > 0) { 1226 var $downloadLink = jQuery('#' + id + ' .download'); 1227 var tmpPDF = $downloadLink.attr('href'); 1228 if (/\.pdf$/.test(tmpPDF.toLowerCase())) { 1229 // A plain `window.location` navigation ignores the `download` attribute entirely 1230 // (browser just opens the PDF in-tab), so trigger a real download via a synthetic 1231 // click on an anchor instead - this respects `download` for both forcing an actual 1232 // download and using its human-readable filename instead of the cache's hash-named URL. 1233 var tempLink = document.createElement('a'); 1234 tempLink.href = tmpPDF; 1235 tempLink.download = $downloadLink.attr('download') || ''; 1236 document.body.appendChild(tempLink); 1237 tempLink.click(); 1238 document.body.removeChild(tempLink); 1239 1240 // Confirm the download and offer an in-browser view, since a triggered 1241 // download gives no other visible feedback that anything happened. 1242 if (jQuery('#itinerary-thanks-popup').length > 0) { 1243 jQuery('#itinerary-thanks-trip-name').text($downloadLink.attr('data-trip-name') || ''); 1244 jQuery('#itinerary-thanks-view-pdf').attr('href', tmpPDF); 1245 jQuery('#itinerary-thanks-popup').fadeIn().attr('data-ws-pop-close', 1); 1246 jQuery('body').on('keydown', closeItineraryFormOnEsc); 1247 } 1248 } 1249 else { jQuery('#' + id).fadeIn().attr('data-ws-pop-close', 1); jQuery('body').on('keydown', closeItineraryFormOnEsc); } 1250 } else { 1251 jQuery('#' + id).fadeIn().attr('data-ws-pop-close', 1); 1252 jQuery('body').on('keydown', closeItineraryFormOnEsc); 1253 } 1254} 1255 1256function closeItineraryFormOnEsc(e) { 1257 if (e.keyCode === 27 && jQuery('[data-ws-pop-close]').length > 0) { 1258 var id = jQuery('[data-ws-pop-close]').attr('id'); 1259 jQuery('#' + id).removeAttr('data-ws-pop-close').fadeOut(); 1260 jQuery('body').off('keydown', closeItineraryFormOnEsc); 1261 } 1262} 1263 1264function closeItineraryForm(id) { 1265 if (event.target.id == id || event.target.className == 'closebtn') jQuery('#' + id).fadeOut(); 1266} 1267 1268function downloadFormSubmit(formId, dossier, sfId) { 1269 var activityInput = document.querySelector('input[name="activity"]'); 1270 if (activityInput) document.cookie = 'user_activity=' + activityInput.value + '; expires=' + new Date(Date.now() + 24 * 60 * 60 * 1000).toUTCString() + '; path=/'; 1271 return true; 1272} 1273 1274function ScotBorderNav() { 1275 jQuery('.multicountry-nav').fadeIn(); 1276 jQuery(".multicountry-nav .redirect").attr("href", "https://www.wildernessscotland.com/adventure-holidays/road-cycl
1276ing/tour-of-the-scottish-borders/"); 1277 jQuery(".multicountry-nav h4").html("Tour of the Scottish Borders"); 1278 jQuery(".multicountry-nav").html(jQuery(".multicountry-nav").html().replace("Ireland", "Scotland")); 1279} 1280 1281function popupComfortTip() { 1282 jQuery('#mainPopup').remove(); 1283 var strTmp = '<div id="mainPopup"><div id="mainPopup_wrapper"><a href="JavaScript:mainPopupClose();">X</a><div class="insidepopupwrapper"><div class="insidepopupwrapper_pad">'; 1284 strTmp += '<h3 style="text-align: left; margin-bottom: 20px;">Comfort</h3>'; 1285 strTmp += '<p style="text-align: left; margin-bottom: 20px;"><b>Wild Camping</b> - Camping under the stars in spectacular locations near the waterway/shore. Some camps are wild with no facilities while some have very basic facilities such as a toilet and cold running water.</p>'; 1286 strTmp += '<p style="text-align: left; margin-bottom: 20px;"><b>Basic</b> - These simple rustic accommodations in breath-taking locations have a warm and cosy feel with shared bathroom facilities, warm showers and comfortable beds.</p>'; 1287 strTmp += '<p style="text-align: left; margin-bottom: 20px;"><b>Classic</b> - Staying in 3 or 4 star hotels, guesthouses and country inns, you can enjoy warm hospitality along with a hot shower, great food and a comfortable bed at the end of a day.</p>'; 1288 strTmp += '<p style="text-align: left; margin-bottom: 20px;"><b>Deluxe</b> - Staying in 4 or 5 star hotels you can enjoy superior comfort and the finest service.</p>'; 1289 strTmp += '<p style="text-align: left; margin-bottom: 20px;"><b>Luxury</b> - Selected castles in locations throughout the country, each have their own character and you will be exceptionally well looked after in each of them.</p>'; 1290 strTmp += '</div></div></div></div>'; 1291 jQuery('body').prepend(strTmp); 1292 window.setTimeout(function() { jQuery('#mainPopup').addClass('open'); }, 100); 1293 jQuery('body').bind('click', mainPopupClose); 1294} 1295 1296function mainPopupClose() { 1297 jQuery('body').unbind('click', mainPopupClose); 1298 jQuery('#mainPopup').removeClass('open'); 1299 window.setTimeout(function() { jQuery('#mainPopup').remove(); }, 300); 1300} 1301 1302function updatechkboxslider2() { 1303 if (jQuery('#terms:checked').length == 0) { 1304 jQuery('.checkboxslider .yes').removeClass('active').addClass('inactive'); 1305 jQuery('.checkboxslider .no').removeClass('inactive').addClass('active'); 1306 } else { 1307 jQuery('.checkboxslider .no').removeClass('active').addClass('inactive'); 1308 jQuery('.checkboxslider .yes').removeClass('inactive').addClass('active'); 1309 } 1310} 1311 1312function updatechkboxslider() { 1313 if (jQuery('#Conservation:checked').length == 0) { 1314 jQuery('.checkboxslider .yes').removeClass('active').addClass('inactive'); 1315 jQuery('.checkboxslider .no').removeClass('inactive').addClass('active'); 1316 } else { 1317 jQuery('.checkboxslider .no').removeClass('active').addClass('inactive'); 1318 jQuery('.checkboxslider .yes').removeClass('inactive').addClass('active'); 1319 } 1320 updateSummary(); 1321} 1322 1323function popupToolTipMobile() { 1324 jQuery('#mainPopup').remove(); 1325 var strTmp = '<div id="mainPopup"><div id="mainPopup_wrapper"><a href="JavaScript:mainPopupClose();">X</a><div class="insidepopupwrapper"><div class="insidepopupwrapper_pad"><p style="text-align: left; margin-bottom: 20px;">Please provide a contact mobile number that will enable us to contact you should you not arrive at the start point on time</p></div></div></div></div>'; 1326 jQuery('body').prepend(strTmp); 1327 window.setTimeout(function() { jQuery('#mainPopup').addClass('open'); }, 100); 1328} 1329 1330function popupToolTipRoom() { 1331 var strTmp = '<div id="mainPopup"><div id="mainPopup_wrapper"><a href="JavaScript:mainPopupClose();">X</a><div class="insidepopupwrapper"><div class="insidepopupwrapper_pad"><p style="text-align: left; margin-bottom: 20px;">Accommodation is usually in twin (2 single beds) or double (1 shared bed) rooms unless stated otherwise and we request that clients who book individually share a twin room.<br /><br />If however, you would like a single room this may be possible subject to availability and payment of a supplement (see Trip Dossier for details). If you select single, we will determine availability and advise you shortly after you have made booking.</p></div></div></div></div>'; 1332 jQuery('body').prepend(strTmp); 1333 window.setTimeout(function() { jQuery('#mainPopup').addClass('open'); }, 100); 1334} 1335 1336function popupToolTipExp() { 1337 var strTmp = '<div id="mainPopup"><div id="mainPopup_wrapper"><a href="JavaScript:mainPopupClose();">X</a><div class="insidepopupwrapper"><div class="insidepopupwrapper_pad"><p style="text-align: left; margin-bottom: 20px;">Please provide some information on your relevant experience/fitness for this trip</p></div></div></div></div>'; 1338 jQuery('body').prepend(strTmp); 1339 window.setTimeout(function() { jQuery('#mainPopup').addClass('open'); }, 100); 1340} 1341 1342function popupToolTipConservation() { 1343 var strTmp = '<div id="mainPopup"><div id="mainPopup_wrapper"><a href="JavaScript:mainPopupClose();">X</a><div class="insidepopupwrapper"><div class="insidepopupwrapper_pad"><p style="text-align: left; margin-bottom: 20px;">Your community and conservation contribution will support the environments and communities at the heart of our adventurous experiences in wild places. Should you prefer not to pay this contribution, please untick the box, and the amount will be deducted from your payment. Full details of our Wilderness Conservation and Community Fund and the organisations we support can be found on our sustainable tourism pages</a>.</p></div></div></div></div>'; 1344 jQuery('body').prepend(strTmp); 1345 window.setTimeout(function() { jQuery('#mainPopup').addClass('open'); }, 100); 1346} 1347 1348function initializeReCaptcha() { 1349 if (typeof grecaptcha === "undefined") { 1350 console.warn("Google reCAPTCHA not loaded yet. Retrying..."); 1351 setTimeout(initializeReCaptcha, 500); 1352 return; 1353 } 1354 document.querySelectorAll(".g-recaptcha").forEach((recaptchaElement) => { 1355 let form = recaptchaElement.closest("form"); 1356 if (!form.dataset.recaptchaInitialized) { 1357 grecaptcha.render(recaptchaElement, { 'sitekey': '6LcCVmsUAAAAAJmU2EbobNH9lZ9TXYFDqjrfDgKf' }); 1358 form.dataset.recaptchaInitialized = "true"; 1359 } 1360 });
1361} 1362 1363function initInPagePopup() { 1364 const id = '#inpage-popup'; 1365 const popupWrap = document.querySelector(id); 1366 let isRunning = false; 1367 let yscrollTrigPos = 0; 1368 1369 if (!popupWrap) return false; 1370 const trigpoint = popupWrap.getAttribute('data-trig-point'); 1371 1372 function showPopup() { 1373 jQuery(id).fadeIn(); 1374 jQuery('body').on('keydown', closeItineraryFormOnEsc); 1375 jQuery(id).find('a.closebtn').on('click', function(e) { 1376 e.preventDefault(); 1377 jQuery(id).removeAttr('data-ws-pop-close').fadeOut(); 1378 removeEventListners(); 1379 }); 1380 } 1381 1382 function closeItineraryFormOnEsc(e) { 1383 if (e.keyCode === 27) { 1384 jQuery(id).removeAttr('data-ws-pop-close').fadeOut(); 1385 removeEventListners(); 1386 } 1387 } 1388 1389 function removeEventListners() { 1390 jQuery('body').off('keydown', closeItineraryFormOnEsc); 1391 window.removeEventListener('scroll', scrollTriggerListen); 1392 } 1393 1394 function scrollTriggerListen() { 1395 if (!isRunning) { 1396 isRunning = true 1397 window.requestAnimationFrame(function() { 1398 if (window.pageYOffset >= yscrollTrigPos) showPopup(); 1399 isRunning = false; 1400 }) 1401 } 1402 } 1403 1404 if (trigpoint === 'onload') showPopup(); 1405 else if (trigpoint === 'halfscroll') { 1406 yscrollTrigPos = document.documentElement.scrollHeight / 2; 1407 window.addEventListener('scroll', scrollTriggerListen); 1408 } 1409} 1410 1411// ============================================================== 1412// 9. PROTOTYPES (Strut Internal Links & Mobile Nav) 1413// ============================================================== 1414function StrutInternalLinks() { 1415 "use strict"; 1416 var _self = this; 1417 var internalLinks = document.querySelectorAll('.featured-content a[href*="#"], .content a[href*="#"]'); 1418 if (internalLinks) { 1419 for (var i = 0; i < internalLinks.length; i++) { 1420 var curLink = internalLinks[i]; 1421 if (curLink.getAttribute('href') && _self.isSamePageHashLink(curLink.getAttribute('href'))) { 1422 curLink.addEventListener('click', function(event) { 1423 var href = event.currentTarget.getAttribute('href'); 1424 var targetId = href.substr(href.lastIndexOf('#')); 1425 if (jQuery(targetId).length > 0) { 1426 event.preventDefault(); 1427 _self.scrollToTarget(jQuery(targetId).get(0)); 1428 } else if (document.querySelector('a[name="' + targetId.substr(1) + '"]')) { 1429 event.preventDefault(); 1430 _self.scrollToTarget(document.querySelector('a[name="' + targetId.substr(1) + '"]')); 1431 } 1432 }, false); 1433 } 1434 } 1435 } 1436} 1437 1438StrutInternalLinks.prototype.isSamePageHashLink = function(href) { 1439 "use strict"; 1440 if (!href) return false; 1441 var isSamePageHashLink = false; 1442 if (/^#/.test(href)) { 1443 isSamePageHashLink = true; 1444 } else { 1445 var curURL = window.location.href.replace(window.location.hash, ''); 1446 var targetURL = href.replace(/#.*/, ''); 1447 if (curURL === targetURL) isSamePageHashLink = true; 1448 } 1449 return isSamePageHashLink; 1450}; 1451 1452StrutInternalLinks.prototype.scrollToTarget = function(target, duration) { 1453 "use strict"; 1454 var offset = jQuery(target).offset().top; 1455 jQuery("html, body").animate({ scrollTop: offset, duration: 500 }, { 1456 start: function() { if (window.location.hash != target.id) window.location.hash = target.id; } 1457 }); 1458}; 1459 1460function StrutMobileNav() { 1461 this.setupForwardLinks(); 1462 this.setupBackLinks(); 1463 this.setupEventHandlers(); 1464} 1465 1466StrutMobileNav.prototype.setupForwardLinks = function() { 1467 "use strict"; 1468 jQuery('li.page_item_has_children').each(function(i, v) { jQuery(v).prepend('<span class="arrow-menu-next"></span>'); }); 1469}; 1470 1471StrutMobileNav.prototype.setupBackLinks = function() { 1472 "use strict"; 1473 jQuery('.menu-main ul.children').each(function(i, v) { 1474 var sectionTitle = jQuery(v).prev('a').text(); 1475 var sectionLink = jQuery(v).prev('a').attr('href'); 1476 jQuery(v).prepend('<li class="back-link"><span class="arrow-menu-prev"></span><a href="' + sectionLink + '" class="back-link">' + sectionTitle + '</a></li>'); 1477 });
1478}; 1479 1480StrutMobileNav.prototype.setupEventHandlers = function() { 1481 "use strict"; 1482 jQuery('body').on('click', 'nav.menu-main span.arrow-menu-next', function(event) { 1483 event.stopPropagation(); 1484 jQuery(event.target).parent('li').addClass('next-menu'); 1485 }); 1486 jQuery('body').on('click', 'nav.menu-main span.arrow-menu-prev', function(e) { 1487 e.preventDefault(); 1488 jQuery(this).parent().parent('ul.children').parent('li').removeClass('next-menu'); 1489 }); 1490}; 1491 1492// Inject CSS for Vimeo Spinner 1493var vimeoStyle = document.createElement('style'); 1494vimeoStyle.innerHTML = ` 1495 @keyframes spin { 1496 0% { transform: rotate(0deg); } 1497 100% { transform: rotate(360deg); } 1498 } 1499`; 1500document.head.appendChild(vimeoStyle);
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.