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">×</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.