PageSourceSearch

https://cornishstays.co.uk/wp-content/themes/divi-child/script.js

js cornishstays.co.uk collected 2026-10-04 08:16:28 UTC 23,499 bytes, 781 lines download raw bytes

1/* ==========================================================================
2   Open Divi Show / Hide modules from URL hash links
3   ========================================================================== */
4function initDiviHashLinks() {
5    jQuery(function ($) {
6        setTimeout(function () {
7            var buttonId = location.hash.substring(1);
8
9            if (buttonId !== '') {
10                $('#' + buttonId + '.et_pb_db_show_hide_button .et_pb_button').trigger('click');
11            }
12        }, 1000);
13
14        $('a[href*="#"]').each(function () {
15            var $link = $(this);
16            var linkUrl = $link.attr('href');
17            var linkHash = linkUrl.substring(linkUrl.indexOf('#') + 1);
18
19            if (linkHash.length > 0) {
20                var $button = $('.et_pb_db_show_hide_button#' + linkHash);
21
22                if ($button.length) {
23                    $link.off('click');
24
25                    $link.on('click', function (e) {
26                        e.preventDefault();
27
28                        if (typeof et_pb_smooth_scroll === 'function') {
29                            et_pb_smooth_scroll($button, false, 800);
30                        }
31
32                        $button
33                            .not('.dshb-active-button')
34                            .find('.et_pb_button')
35                            .trigger('click');
36                    });
37                }
38            }
39        });
40    });
41}
42
43/* ==========================================================================
44   Add helper text above the mobile property menu hamburger
45   ========================================================================== */
46function initMobilePropertyMenuNote() {
47    const mobileNav =
48        document.querySelector('.property-menu .et_mobile_nav_menu') ||
49        document.querySelector('.et_mobile_nav_menu');
50
51    if (!mobileNav) return;
52    if (mobileNav.querySelector('.mobile-nav-note')) return;
53
54    const note = document.createElement('div');
55    note.textContent = 'Explore the full property details';
56    note.classList.add('mobile-nav-note');
57
58    mobileNav.prepend(note);
59
60    note.addEventListener('click', function () {
61        const toggleButton = mobileNav.querySelector('.mobile_nav .mobile_menu_bar');
62
63        if (toggleButton) {
64            toggleButton.dispatchEvent(
65                new MouseEvent('click', {
66                    bubbles: true,
67                    cancelable: true,
68                    view: window
69                })
70            );
71        }
72    });
73}
74
75/* ==========================================================================
76   Cornish Stays Desktop Mega Navigation
77   ========================================================================== */
78
79document.addEventListener("DOMContentLoaded", function () {
80
81    const nav = document.querySelector(".cs-main-nav");
82    const navItems = document.querySelectorAll(".cs-has-mega");
83
84    navItems.forEach(function (item) {
85
86        const topButton = item.querySelector(".cs-top-level");
87
88        if (!topButton) {
89            return;
90        }
91
92        topButton.addEventListener("click", function (event) {
93
94            event.preventDefault();
95            event.stopPropagation();
96
97            const isOpen = item.classList.contains("cs-open");
98
99            navItems.forEach(function (otherItem) {
100                otherItem.classList.remove("cs-open");
101            });
102
103            if (!isOpen) {
104                item.classList.add("cs-open");
105            }
106
107        });
108
109    });
110
111    document.addEventListener("click", function (event) {
112
113        if (nav && !nav.contains(event.target)) {
114
115            navItems.forEach(function (item) {
116                item.classList.remove("cs-open");
117            });
118
119        }
120
121    });
122
123    document.addEventListener("keydown", function (event) {
124
125        if (event.key === "Escape") {
126
127            navItems.forEach(function (item) {
128                item.classList.remove("cs-open");
129            });
130
131        }
132
133    });
134
135});
136
137
138/* ==========================================================================
139   Cornish Stays Destination Tabs
140   ========================================================================== */
141
142document.addEventListener("DOMContentLoaded", function () {
143
144    const destinationMenus = document.querySelectorAll(".cs-destinations-mega");
145
146    destinationMenus.forEach(function (menu) {
147
148        const tabs = menu.querySelectorAll(".cs-destination-tab");
149        const panels = menu.querySelectorAll(".cs-destination-panel");
150
151        tabs.forEach(function (tab) {
152
153            tab.addEventListener("click", function (event) {
154
155                event.preventDefault();
156                event.stopPropagation();
157
158                const target = tab.getAttribute("data-target");
159
160                tabs.forEach(function (item) {
161                    item.classList.remove("cs-active-tab");
162                });
163
164                panels.forEach(function (panel) {
165                    panel.classList.remove("cs-active-panel");
166                });
167
168                tab.classList.add("cs-active-tab");
169
170                const activePanel = menu.querySelector(
171                    '[data-panel="' + target + '"]'
172                );
173
174                if (activePanel) {
175                    activePanel.classList.add("cs-active-panel");
176                }
177
178            });
179
180        });
181
182    });
183
184});
185
186
187/* ==========================================================================
188   Cornish Stays Mobile Slide Navigation
189   ========================================================================== */
190
191document.addEventListener("DOMContentLoaded", function () {
192
193    const mainNav = document.querySelector(".cs-main-nav");
194    const mobileToggle = document.querySelector(".cs-mobile-toggle");
195    const mobileNav = document.querySelector(".cs-mobile-nav");
196
197    const parentButtons = document.querySelectorAll(".cs-mobile-parent");
198    const backButtons = document.querySelectorAll(".cs-mobile-back");
199
200    const mobilePanels = document.querySelectorAll(".cs-mobile-panel");
201
202    let panelHistory = [];
203
204    if (!mainNav || !mobileToggle || !mobileNav) {
205        return;
206    }
207
208    function showPanel(panelName) {
209
210        const targetPanel = mobileNav.querySelector(
211            '[data-mobile-panel="' + panelName + '"]'
212        );
213
214        if (!targetPanel) {
215            return;
216        }
217
218        mobilePanels.forEach(function (panel) {
219            panel.classList.remove("cs-active-panel");
220        });
221
222        targetPanel.classList.add("cs-active-panel");
223
224        mobileNav.classList.add("cs-sub-open");
225    }
226
227    function showMainPanel() {
228
229        mobileNav.classList.remove("cs-sub-open");
230
231        mobilePanels.forEach(function (panel) {
232            panel.classList.remove("cs-active-panel");
233        });
234
235        const mainPanel = mobileNav.querySelector(
236            ".cs-mobile-panel-main"
237        );
238
239        if (mainPanel) {
240            mainPanel.classList.add("cs-active-panel");
241        }
242
243    }
244
245    mobileToggle.addEventListener("click", function () {
246
247    const isOpen = mainNav.classList.contains("cs-mobile-open");
248
249    mainNav.classList.toggle("cs-mobile-open");
250    document.body.classList.toggle("cs-mobile-menu-open");
251
252    panelHistory = [];
253
254    showMainPanel();
255
256    if (!isOpen) {
257        mobileNav.classList.remove("cs-sub-open");
258    }
259
260});
261
262    parentButtons.forEach(function (button) {
263
264        button.addEventListener("click", function () {
265
266            const target = button.getAttribute(
267                "data-mobile-target"
268            );
269
270            const currentPanel = button.closest(
271                ".cs-mobile-panel"
272            );
273
274            if (currentPanel) {
275
276                const currentPanelName =
277                    currentPanel.getAttribute(
278                        "data-mobile-panel"
279                    ) || "main";
280
281                panelHistory.push(currentPanelName);
282
283            }
284
285            showPanel(target);
286
287        });
288
289    });
290
291    backButtons.forEach(function (button) {
292
293        button.addEventListener("click", function () {
294
295            const previousPanelName = panelHistory.pop();
296
297            if (
298                !previousPanelName ||
299                previousPanelName === "main"
300            ) {
301
302                showMainPanel();
303                return;
304
305            }
306
307            showPanel(previousPanelName);
308
309        });
310
311    });
312
313});
314
315/* ==========================================================================
316   Read more / read less toggle blocks
317   ========================================================================== */
318function initReadMoreBlocks() {
319    initReadMore({
320        targetId: 'toggle-overview',
321        lineLimit: 12,
322        moreText: 'Show full overview',
323        lessText: 'Hide full overview',
324        ellipsis: ''
325    });
326
327    initReadMore({
328        targetId: 'toggle-needtoknow',
329        lineLimit: 5,
330        moreText: 'Show all',
331        lessText: 'Show less',
332        ellipsis: ''
333    });
334
335    initReadMore({
336        targetId: 'toggle-localarea',
337        lineLimit: 6,
338        moreText: 'Show full local area',
339        lessText: 'Hide full local area',
340        ellipsis: ''
341    });
342
343    function initReadMore(config) {
344        const container = document.getElementById(config.targetId);
345        if (!container) return;
346        if (container.classList.contains('read-more-initialised')) return;
347
348        container.classList.add('read-more-initialised');
349
350        const originalHTML = container.innerHTML.trim();
351
352        container.innerHTML = `
353            <div class="read-more-content clamped" data-ellipsis="${config.ellipsis || ''}">
354                ${originalHTML}
355            </div>
356            <button type="button" class="read-toggle-btn">${config.moreText}</button>
357        `;
358
359        const content = container.querySelector('.read-more-content');
360        const button = container.querySelector('.read-toggle-btn');
361
362        if (!content || !button) return;
363
364        content.style.webkitLineClamp = config.lineLimit;
365
366        button.addEventListener('click', function () {
367            const isExpanded = content.classList.contains('expanded');
368
369            if (isExpanded) {
370                content.classList.remove('expanded');
371                content.classList.add('clamped');
372                content.style.webkitLineClamp = config.lineLimit;
373                button.textContent = config.moreText;
374            } else {
375                content.classList.remove('clamped');
376                content.classList.add('expanded');
377                content.style.webkitLineClamp = 'unset';
378                button.textContent = config.lessText;
379            }
380        });
381    }
382}
383
384/* ==========================================================================
385   Property video embed with poster image and click-to-play behaviour
386   ========================================================================== */
387function initPropertyVideoPlayers() {
388    const videoPlayers = document.querySelectorAll('.orwd-video-player');
389    if (!videoPlayers.length) return;
390
391    function getYouTubeId(url) {
392        try {
393            const parsed = new URL(url);
394            const host = parsed.hostname.replace('www.', '');
395            if (host === 'youtube.com' || host === 'm.youtube.com') return parsed.searchParams.get('v') || '';
396            if (host === 'youtu.be') return parsed.pathname.replace(/^\//, '').split('/')[0];
397            return '';
398        } catch (e) { return ''; }
399    }
400
401    function openVideoLightbox(player) {
402        const videoId = getYouTubeId(player.getAttribute('data-video-url'));
403        if (!videoId) return;
404        const embedUrl = 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(videoId) + '?autoplay=1&rel=0&modestbranding=1&playsinline=1';
405        const lightbox = document.createElement('div');
406        lightbox.className = 'orwd-video-lightbox';
407        lightbox.setAttribute('role', 'dialog');
408        lightbox.setAttribute('aria-modal', 'true');
409        lightbox.setAttribute('aria-label', 'Property video tour');
410        lightbox.innerHTML = `<div class="orwd-video-lightbox-inner"><button type="button" class="orwd-video-lightbox-close" aria-label="Close video">&times;</button><div class="orwd-video-lightbox-video"><iframe src="${embedUrl}" title="Property video tour" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe></div></div>`;
411        document.body.appendChild(lightbox);
412        document.body.classList.add('orwd-lightbox-open');
413        const close = () => { document.body.classList.remove('orwd-lightbox-open'); lightbox.remove(); document.removeEventListener('keydown', onKey); };
414        const onKey = e => { if (e.key === 'Escape') close(); };
415        lightbox.querySelector('.orwd-video-lightbox-close').addEventListener('click', close);
416        lightbox.addEventListener('click', e => { if (e.target === lightbox) close(); });
417        document.addEventListener('keydown', onKey);
418        lightbox.querySelector('.orwd-video-lightbox-close').focus();
419    }
420
421    videoPlayers.forEach(function (player) {
422        if (player.dataset.videoInitialised === 'true') return;
423        player.dataset.videoInitialised = 'true';
424        if (!getYouTubeId(player.getAttribute('data-video-url'))) return;
425        player.addEventListener('click', () => openVideoLightbox(player));
426        player.addEventListener('keydown', e => {
427            if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openVideoLightbox(player); }
428        });
429    });
430}
431
432/* ==========================================================================
433   Floorplan lightbox
434   ========================================================================== */
435function initFloorplanLightbox() {
436    const body = document.body;
437    if (!body || body.dataset.floorplanInitialised === 'true') return;
438
439    body.dataset.floorplanInitialised = 'true';
440
441    let activeLightbox = null;
442    let activeTrigger = null;
443
444    const lightboxes = document.querySelectorAll('.orwd-floorplan-lightbox');
445
446    lightboxes.forEach(function (lightbox) {
447        body.appendChild(lightbox);
448    });
449
450    function openFloorplan(lightbox, trigger) {
451        if (!lightbox) return;
452
453        lightbox.hidden = false;
454        lightbox.setAttribute('aria-hidden', 'false');
455        body.classList.add('orwd-lightbox-open');
456
457        activeLightbox = lightbox;
458        activeTrigger = trigger || null;
459    }
460
461    function closeFloorplan(lightbox) {
462        if (!lightbox) return;
463
464        lightbox.hidden = true;
465        lightbox.setAttribute('aria-hidden', 'true');
466        body.classList.remove('orwd-lightbox-open');
467
468        if (activeTrigger) {
469            activeTrigger.focus();
470        }
471
472        if (activeLightbox === lightbox) {
473            activeLightbox = null;
474        }
475
476        activeTrigger = null;
477    }
478
479    document.addEventListener('click', function (e) {
480        const trigger = e.target.closest('.orwd-floorplan-trigger');
481
482        if (trigger) {
483            e.preventDefault();
484
485            const targetId = trigger.getAttribute('data-floorplan-lightbox-target');
486            if (!targetId) return;
487
488            const lightbox = document.getElementById(targetId);
489            if (!lightbox) return;
490
491            openFloorplan(lightbox, trigger);
492            return;
493        }
494
495        const closeTrigger = e.target.closest('[data-floorplan-close]');
496        if (closeTrigger) {
497            const lightbox = closeTrigger.closest('.orwd-floorplan-lightbox');
498            closeFloorplan(lightbox);
499        }
500    });
501
502    document.addEventListener('keydown', function (e) {
503        if (e.key === 'Escape' && activeLightbox) {
504            closeFloorplan(activeLightbox);
505        }
506    });
507}
508
509/* ==========================================================================
510   Crop property listing excerpts to 40 words
511   --------------------------------------------------------------------------
512   Trims listing excerpt text to 40 words and adds an ellipsis.
513   Supports multiple #ps-excerpt elements on listing/search pages.
514   ========================================================================== */
515function initPropertyExcerptCrop() {
516    const excerpts = document.querySelectorAll('.ps-excerpt');
517
518    if (!excerpts.length) return;
519
520    excerpts.forEach(function (excerpt) {
521        if (excerpt.dataset.excerptCropInitialised === 'true') return;
522
523        const fullText = excerpt.textContent.trim();
524        const words = fullText.split(/\s+/);
525
526        if (words.length > 30) {
527            excerpt.textContent = words.slice(0, 30).join(' ') + '...';
528        }
529
530        excerpt.dataset.excerptCropInitialised = 'true';
531    });
532}
533
534/* ==========================================================================
535   Hide property video section if no ACF video is set
536   ========================================================================== */
537function initHideEmptyPropertyVideoSection() {
538    const section = document.getElementById('property-video');
539    const videoNavItem = document.querySelector('.cs-video-nav-item');
540
541    if (!section) {
542        if (videoNavItem) {
543            videoNavItem.style.display = 'none';
544        }
545        return;
546    }
547
548    const videoPlayer = section.querySelector('.orwd-video-player');
549
550    if (!videoPlayer) {
551        section.style.display = 'none';
552
553        if (videoNavItem) {
554            videoNavItem.style.display = 'none';
555        }
556
557        return;
558    }
559
560    const videoUrl = videoPlayer.getAttribute('data-video-url');
561
562    if (!videoUrl || videoUrl.trim() === '') {
563        section.style.display = 'none';
564
565        if (videoNavItem) {
566            videoNavItem.style.display = 'none';
567        }
568    }
569}
570
571/* ==========================================================================
572   Safely initialise property scripts
573   ========================================================================== */
574document.addEventListener('DOMContentLoaded', function () {
575    const initialisers = [
576        initMobilePropertyMenuNote,
577        initReadMoreBlocks,
578		initHideEmptyPropertyVideoSection,
579        initPropertyVideoPlayers,
580        initFloorplanLightbox,
581        initDiviHashLinks,
582        initPropertyExcerptCrop
583    ];
584
585    initialisers.forEach(function (initFunction) {
586        if (typeof initFunction !== 'function') return;
587
588        try {
589            initFunction();
590        } catch (error) {
591            console.error('Property script error:', error);
592        }
593    });
594});
595
596/* ==========================================================================
597   Cornish Stays Property Navigation - Active Underline
598   ========================================================================== */
599document.addEventListener("DOMContentLoaded", function () {
600
601  setTimeout(function () {
602
603    const navWrap = document.querySelector(".cs-property-nav-wrap");
604    const navLinks = navWrap ? navWrap.querySelectorAll('a[href^="#"]') : [];
605
606    if (!navWrap || !navLinks.length) return;
607
608    function clearActive() {
609      navLinks.forEach(function (link) {
610        link.classList.remove("active");
611      });
612    }
613
614    function setActive(link) {
615      clearActive();
616
617      if (link) {
618        link.classList.add("active");
619      }
620    }
621
622    function updateActiveNav() {
623      let closestLink = null;
624      let closestDistance = Infinity;
625      const offset = navWrap.offsetHeight + 80;
626
627      navLinks.forEach(function (link) {
628        const targetId = link.getAttribute("href");
629        const section = document.querySelector(targetId);
630
631        if (!section) return;
632
633        const style = window.getComputedStyle(section);
634
635        if (
636          style.display === "none" ||
637          style.visibility === "hidden" ||
638          section.offsetHeight === 0
639        ) {
640          return;
641        }
642
643        const distance = Math.abs(section.getBoundingClientRect().top - offset);
644
645        if (distance < closestDistance) {
646          closestDistance = distance;
647          closestLink = link;
648        }
649      });
650
651      setActive(closestLink);
652    }
653
654    navLinks.forEach(function (link) {
655      link.addEventListener("click", function (e) {
656        const targetId = link.getAttribute("href");
657        const section = document.querySelector(targetId);
658
659        if (!section) return;
660
661        e.preventDefault();
662        setActive(link);
663
664        const targetPosition =
665          section.getBoundingClientRect().top +
666          window.pageYOffset -
667          navWrap.offsetHeight -
668          20;
669
670        window.scrollTo({
671          top: targetPosition,
672          behavior: "smooth"
673        });
674
675        setTimeout(updateActiveNav, 500);
676      });
677    });
678
679    window.addEventListener("scroll", updateActiveNav);
680    window.addEventListener("resize", updateActiveNav);
681
682    updateActiveNav();
683
684  }, 2000);
685
686});
687
688
689/* ==========================================================================
690   Contact Form
691   ========================================================================== */
692document.addEventListener("DOMContentLoaded", function () {
693  const form = document.getElementById("cs-contact-form");
694
695  if (!form) return;
696
697  form.addEventListener("submit", function (e) {
698    e.preventDefault();
699
700    const messageBox = document.getElementById("cs-form-message");
701    const button = form.querySelector("button");
702
703    messageBox.textContent = "";
704    messageBox.className = "";
705
706    button.disabled = true;
707    button.textContent = "Sending...";
708
709    const formData = new FormData(form);
710
711    fetch("/wp-admin/admin-ajax.php", {
712      method: "POST",
713      body: formData
714    })
715      .then(response => response.json())
716      .then(data => {
717        if (data.success) {
718          messageBox.textContent = data.data;
719          messageBox.className = "success";
720          form.reset();
721        } else {
722          messageBox.textContent = data.data;
723          messageBox.className = "error";
724        }
725      })
726      .catch(() => {
727        messageBox.textContent = "Sorry, something went wrong. Please try again.";
728        messageBox.className = "error";
729      })
730      .finally(() => {
731        button.disabled = false;
732        button.textContent = "Send Enquiry";
733      });
734  });
735});
736
737/* ==========================================================================
738   Home page slider
739   ========================================================================== */
740
741document.addEventListener('click', function (e) {
742
743    const button = e.target.closest('.cs-slider-filter-btn');
744
745    if (!button) return;
746
747    const wrapper = button.closest('.cs-filtered-property-slider');
748
749    if (!wrapper) return;
750
751    const target = button.getAttribute('data-filter');
752
753    const buttons = wrapper.querySelectorAll('.cs-slider-filter-btn');
754    const panels = wrapper.querySelectorAll('.cs-property-filter-panel');
755
756    buttons.forEach(function (btn) {
757        btn.classList.remove('active');
758    });
759
760    panels.forEach(function (panel) {
761        panel.classList.remove('active');
762    });
763
764    button.classList.add('active');
765
766    const activePanel = wrapper.querySelector('#' + target);
767
768    if (activePanel) {
769        activePanel.classList.add('active');
770
771        const swiperEl = activePanel.querySelector('.cs-property-slider');
772
773        if (swiperEl && swiperEl.swiper) {
774            setTimeout(function () {
775                swiperEl.swiper.update();
776                swiperEl.swiper.slideToLoop(0);
777            }, 100);
778        }
779    }
780
781});

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.