PageSourceSearch

https://wildernessengland.com/wp-content/themes/wildernessuniversal/js/script.min.js?v=2201

js wildernessengland.com collected 2026-09-26 10:05:49 UTC 70,172 bytes, 1,500 lines download raw bytes

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.