1<!DOCTYPE html> 2<html class="no-js" lang=en> 3<head> 4 <meta charset="utf-8"> 5 <meta http-equiv="x-ua-compatible" content="ie=edge"> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <meta name="twitter:card" content="summary_large_image"> 8 <meta property="og:type" content="website"> 9 <title>Contact Us | Get Expert Advice | Carrie Haymond Group</title> 10 <meta property="og:title" content="Contact Us | Get Expert Advice | Carrie Haymond Group"> 11 <meta name="twitter:title" content="Contact Us | Get Expert Advice | Carrie Haymond Group"> 12 <meta name="description" content="Looking for expert advice in real estate? Contact the Carrie Haymond Group today for personalized solutions to all your property needs. Satisfaction guaranteed!"> 13 <meta property="og:description" content="Looking for expert advice in real estate? Contact the Carrie Haymond Group today for personalized solutions to all your property needs. Satisfaction guaranteed!"> 14 <meta name="twitter:description" content="Looking for expert advice in real estate? Contact the Carrie Haymond Group today for personalized solutions to all your property needs. Satisfaction guaranteed!"> 15 <meta property="og:image" content="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c"> 16 <meta name="twitter:image" content="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c"> 17 <meta property="og:url" content="https://carriehaymondgroup.com/contact"> 18 <link rel="canonical" href="https://carriehaymondgroup.com/contact"> 19
19<script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
19 20 <link rel="preconnect" href="https://fonts.googleapis.com"> 21 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 22 <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Montserrat:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i"><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open%20Sans:300,400,500,600,700,800,300i,400i,500i,600i,700i,800i"> 23 <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> 24 <noscript><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css"></noscript> 25 <link rel="icon" type="image/x-icon" href="/favicon.ico"> 26 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> 27 <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> 28 <link rel="icon" type="image/png" sizes="48x48" href="/favicon-48x48.png"> 29 <style id="global-css">:root{--global-primary-font-family:Montserrat,sans-serif;--global-primary-font-family-short:Montserrat;--global-secondary-font-family:'Open Sans',sans-serif;--global-secondary-font-family-short:Open Sans;--global-body-padding:0px;--global-background-color:#fff;--global-body-font-size:16px;--global-h1-font-size:64px;--global-h2-font-size:52px;--global-h3-font-size:42px;--global-h4-font-size:24px;--global-h5-font-size:12px;--global-h6-font-size:32px;--global-section-padding:96px;}@media (max-width: 768px){:root{--global-section-padding:64px;}}body{padding:0px;background-color:#fff;font-family:'Open Sans',sans-serif;font-size:16px;}@media (min-width: 768px){body{padding:0px 0px 0px 0px;}}h1,h2,h3,h4,h5,h6,button{font-family:Montserrat,sans-serif;}h1{font-size:64px;}h2{font-size:52px;}h3{font-size:42px;}h4{font-size:24px;}h5{font-size:12px;}h6{font-size:32px;}</style> 30 <link rel="stylesheet" type="text/css" href="https://styles.luxurypresence.com/visionary/index.css" /> 31 <style id="website-css">:root{--lp-color-primary-accent:#36454F;--primary:#36454F;--secondary:#708090;--accent:#C5A565;--blocking:#000;--text-input:#f3f3f3;--black:#000;--white:#FFF;--light-gray:#CCC;--dark-gray:#999;--lp-color-error:#F00;}:root{--global-h4-font-size:32px;--global-h5-font-size:24px;--global-h6-font-size:20px;}.lp-h1,.lp-h2,.lp-h3{font-variation-settings:'opsz' 44,'GRAD' 0,'slnt' 0,'XTRA' 510,'XOPQ' 96,'YOPQ' 40,'YTLC' 540,'YTUC' 712,'YTAS' 750,'YTDE' -220,'YTFI' 738;}.watermark{font-variation-settings:'GRAD' 0,'slnt' 0,'XTRA' 511,'XOPQ' 96,'YOPQ' 50,'YTLC' 537,'YTUC' 712,'YTAS' 750,'YTDE' -221,'YTFI' 738,'opsz' 44;}@media only screen and (max-width:768px){.contact-form-modal__close:after{height:25px;width:20px;--iconColor:#000;}}@media only screen and (min-width:769px){.contact-form-modal__close:after{height:30px;width:40px;}}@media only screen and (min-width:769px){body.page-sellers .hoverable-section{padding-bottom:0;padding-top:0;}}@media only screen and (max-width:768px){body.page-sellers .hoverable-section{padding-bottom:80px;padding-top:80px;}}@media only screen and (min-width:769px){body.page-buyers .hoverable-section{padding-bottom:0;padding-top:0;}}@media only screen and (max-width:768px){body.page-buyers .hoverable-section{padding-bottom:80px;padding-top:80px;}}.header-container{height:80px!important;}@media only screen and (max-width: 768px){.header-container{height:60px!important;}}.header-logo{height:100%;max-width:350px!important;}@media only screen and (max-width: 768px){.header-logo{height:100%;max-width:280px!important;}}.header:is(.scroll){background:var(--primary)!important;color:var(--white)!important;}.header:is(.scroll) .header-logo__img--light{display:block!important;}.header:is(.scroll) .header-logo__img--dark{display:none!important;}.header:is(.scroll) .header-navigation__link:before,.header:is(.scroll) .header-navigation__link:after{background-color:var(--white);}.header:is(.scroll) .header-hamburger-btn{border-color:var(--white);background:transparent;}.header:is(.scroll) .header-hamburger-btn:empty:before,.header:is(.scroll) .header-hamburger-btn:empty:after{background:var(--white);}.header:is(.scroll) .header-hamburger-btn:hover:not(:disabled){border-color:var(--white);background:var(--white);}.header:is(.scroll) .header-hamburger-btn:hover:not(:disabled):empty:before,.header:is(.scroll) .header-hamburger-btn:hover:not(:disabled):empty:after{background:var(--primary);}.header:is(.scroll) .header-navigation__sub-nav{padding:0px;background:var(--black);min-width:150px;}.header:is(.scroll) .header-navigation__sub-nav-link{padding:12px 16px;color:var(--white);}.header:is(.scroll) .header-navigation__sub-nav-link:hover{background-color:var(--white);color:var(--primary);}.header:not(.scroll){background:transparent!important;}.header:not(.scroll) .header-logo__img--light{display:block!important;}.header:not(.scroll) .header-logo__img--dark{display:none!important;}.header:not(.scroll) .header-hamburger-btn{border-color:var(--white);background:transparent;}.header:not(.scroll) .header-hamburger-btn:empty:before,.header:not(.scroll) .header-hamburger-btn:empty:after{background:var(--white);}.header:not(.scroll) .header-hamburger-btn:hover:not(:disabled){border-color:var(--white);background:var(--white);}.header:not(.scroll) .header-hamburger-btn:hover:not(:disabled):empty:before,.header:not(.scroll) .header-hamburger-btn:hover:not(:disabled):empty:after{background:var(--accent);}.header:not(.scroll) .header-navigation__link:before,.header:not(.scroll) .header-navigation__link:after{background-color:var(--white);}.header:not(.scroll) .header-navigation__sub-nav{padding:0px;background:var(--white);min-width:150px;}.header:not(.scroll) .header-navigation__sub-nav-link{padding:12px 16px;color:var(--black);}.header:not(.scroll) .header-navigation__sub-nav-link:hover{background-color:var(--primary);color:var(--white);}.contact-form-modal-consultation-link{color:#fff!important;position:relative!important;font-size:14px;}.contact-form-modal-consultation-link:before,.contact-form-modal-consultation-link:after{-webkit-transition:0.4s;transition:0.4s;-webkit-text-decoration:none!important;text-decoration:none!important;line-height:1;content:"";height:1px;position:absolute!important;bottom:-2px;left:0;}.contact-form-modal-consultation-link:before{width:100%!important;background-color:rgba(250,250,250,0.8)!important;}.contact-form-modal-consultation-link:after{width:0%!important;background-color:var(--primary)!important;}.contact-form-modal-consultation-link:hover:after{width:100%!important;}@media (min-width: 769px){.sidemenu{width:auto;}}.sidemenu-wrapper{min-height:-webkit-fit-content;min-height:-moz-fit-content;min-height:fit-content;max-height:100%;}@media (min-width: 1025px){.sidemenu-nav-holder:has(> .sidemenu-navigation:nth-child(1):last-child){-ms-grid-columns:auto;grid-template-columns:auto;}.sidemenu-nav-holder:has(> .sidemenu-navigation:nth-child(2):last-child){-ms-grid-columns:auto auto;grid-template-columns:auto auto;}.sidemenu-nav-holder:has(> .sidemenu-navigation:nth-child(3):last-child){-ms-grid-columns:auto auto auto;grid-template-columns:auto auto auto;}.sidemenu-nav-holder:has(> .sidemenu-navigation:nth-child(4):last-child){-ms-grid-columns:auto auto auto auto;grid-template-columns:auto auto auto auto;}}@media (max-width: 768px){.sidemenu-wrapper{width:100%;}}@media (min-width: 769px){.sidemenu-footer:before{width:100%;}}@media (max-width: 768px){.sidemenu-socials-block:before{width:100%;}}@media (max-width: 768px){#global-sidemenu{padding-top:128px;padding-bottom:48px;}}@media (min-width: 1025px){.sidemenu-footer{padding-top:70px;}}@media (max-width: 768px){.sidemenu-footer{padding-top:70px;padding-bottom:0px;}}.sidemenu__col-description{white-space:pre-line;}@media (max-width: 768px){.sidemenu-close{top:16px;right:16px;}}.sidemenu-close{border-color:var(--primary);background:var(--primary);}
31.sidemenu-close:after{background:var(--white);}.sidemenu-close:hover:not(:disabled){border-color:var(--primary);background:transparent;}.sidemenu-close:hover:not(:disabled):after{background:var(--primary);}.sidemenu-navigation__link:hover{color:var(--primary);}.sidemenu__col-phone,.sidemenu__col-email,.sidemenu-navigation-sub-nav__link{-webkit-transition:0.25s;transition:0.25s;max-width:-webkit-fit-content;max-width:-moz-fit-content;max-width:fit-content;text-decoration-color:var(--light-gray)!important;}.sidemenu__col-phone:hover,.sidemenu__col-phone:focus,.sidemenu__col-email:hover,.sidemenu__col-email:focus,.sidemenu-navigation-sub-nav__link:hover,.sidemenu-navigation-sub-nav__link:focus{text-decoration-color:var(--primary)!important;}.sidemenu__col-phone:active,.sidemenu__col-email:active,.sidemenu-navigation-sub-nav__link:active{--lp-color-primary-accent-dark:var(--primary);}.sidemenu-footer:before,.sidemenu-socials-block:before{background:var(--primary);opacity:1;}@media only screen and (max-width: 768px){.sidemenu-navigation{gap:16px;}.sidemenu-footer{padding-top:0;}}#global-sidemenu .sidemenu-navigation__link{display:block;white-space:normal;overflow:visible;}.sidemenu__col-content:first-of-type{width:64%;}.contact-form-modal__bg-image{content:url('https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c');}.contact-form-modal-details__title{max-width:100%;}
31.contact-form-modal-agent{opacity:1;}.contact-form-modal-agent__link{-webkit-transition:0.25s;transition:0.25s;--lp-color-secondary-gray-600:var(--light-gray);}.contact-form-modal-agent__link:hover,.contact-form-modal-agent__link:focus{color:var(--white);--lp-color-primary-accent:var(--primary);}.contact-form-modal-agent__link:active{--lp-color-primary-accent-dark:var(--primary);}.custom-form__form-row .lp-input,.modal-content .custom-select-toggle.is-placeholder{color:#000!important;}.modal-content .custom-select .custom-select-item{color:#000;background:#fff;}.modal-content .custom-select .custom-select-item:hover{color:#fff;background:var(--primary)!important;}.contact-form-modal__close-container{top:0px;right:0px;}.email-sign-up-modal .email-sign-up-modal__form .email-sign-up-modal__close{opacity:1!important;}.contact-form-modal__close{top:16px!important;right:48px!important;padding:0px;}.contact-form-modal__close:after{background:var(--white);}.contact-form-modal-agent__contact{max-width:100%;}@media only screen and (max-width: 768px){.contact-form-modal-form__title{text-align:center;}}.contact-form-modal-form__group .lp-input{background-color:var(--text-input);}.mobile-contact-wrapper .lp-container{padding:0px!important;}.mobile-contact-wrapper .contact-form{padding:0px;}.mobile-contact-wrapper .contact-form__container{padding:48px;}.mobile-contact-wrapper{z-index:101!important;}.mobile-contact-wrapper .close-btn{top:16px!important;right:16px!important;}.mobile-contact-wrapper .contact-form__container{border-radius:8px;}.mobile-contact-wrapper .pop-up{max-width:700px!important;}.mobile-contact-wrapper .pop-up{box-shadow:0px 8px 16px rgba(0, 0, 0, 0.15);}.mobile-contact-wrapper .contact-form__container{border-color:rgba(0, 0, 0, 0);}@media only screen and (max-width: 768px){div.mobile-contact-wrapper .pop-up .close-btn{padding:0!important;margin-top:0!important;}.mobile-contact-wrapper .contact-form__container{padding:20px;}}.floating-contact-button-pop-up{background:#fff!important;}.custom-intro__content-box{background-color:transparent;}.custom-intro__pre-title{color:var(--white);}.custom-intro__title{color:var(--white);}.custom-intro__description{color:var(--white);}.custom-intro__scroll-btn,.neighborhood-intro__scroll-btn,.blog-post-intro__scroll-btn,.agent-intro__scroll-btn{border-color:var(--white);background:var(--white);}.custom-intro__scroll-btn:after,.neighborhood-intro__scroll-btn:after,.blog-post-intro__scroll-btn:after,.agent-intro__scroll-btn:after{background:var(--primary);}.custom-intro__scroll-btn:hover,.neighborhood-intro__scroll-btn:hover,.blog-post-intro__scroll-btn:hover,.agent-intro__scroll-btn:hover{border-color:var(--white);background:transparent;}.custom-intro__scroll-btn:hover:after,.neighborhood-intro__scroll-btn:hover:after,.blog-post-intro__scroll-btn:hover:after,.agent-intro__scroll-btn:hover:after{background:var(--white);}.lp-title-group{max-width:1400px;}.lp-lightbox__close{border-color:var(--white)!important;background:var(--white)!important;color:var(--primary)!important;-webkit-transition:all 0.3s ease;transition:all 0.3s ease;}.lp-lightbox__close:hover{color:var(--white)!important;background:transparent!important;}.lp-lightbox__close:hover:after,.lp-lightbox__close:hover:before{background-color:var(--white);}.lp-lightbox.show .lp-lightbox-arrow{border-color:var(--white);background:var(--white)!important;}.lp-lightbox.show .lp-lightbox-arrow:before{border-color:var(--primary)!important;}.lp-lightbox.show .lp-lightbox-arrow:hover{background-color:transparent!important;}.lp-lightbox.show .lp-lightbox-arrow:hover:before{border-color:var(--white)!important;}li[class^="paginationjs"]{--lp-color-primary-2:#fff;}footer#global-footer a:not(.lp-socials__link){color:var(--dark);-webkit-transition:0.4s;transition:0.4s;--lp-color-secondary-gray-600:var(--light-gray);}footer#global-footer a:not(.lp-socials__link):hover,footer#global-footer a:not(.lp-socials__link):focus{color:var(--dark);--lp-color-primary-accent:var(--primary);}.footer-navigation__item+.footer-navigation__item{border-left:1px solid #fff;}.footer-navigation__item{text-transform:uppercase;}.footer-info{border-right:1px solid #fff;}.footer-contact-row{border-bottom:1px solid #fff;}@media (max-width: 768px){.footer-bottom-row{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;gap:16px;}}@media (max-width: 768px){.footer-copyright{width:100%;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:justify;
31-webkit-justify-content:space-between;justify-content:space-between;gap:16px;}.footer-copyright__powered,.footer-copyright__privacy{-webkit-flex-basis:calc((100% / 2) - 16px + (16px / 2));-ms-flex-preferred-size:calc((100% / 2) - 16px + (16px / 2));flex-basis:calc((100% / 2) - 16px + (16px / 2));}}@media (max-width: 768px){.footer-copyright__powered,.footer-copyright__privacy{-webkit-flex-basis:calc((100% / 2) - 16px + (16px / 2));-ms-flex-preferred-size:calc((100% / 2) - 16px + (16px / 2));flex-basis:calc((100% / 2) - 16px + (16px / 2));}}.footer-logos-row .footer-logo{max-height:100px;}@media (max-width: 768px){.footer-logos-row .footer-logo{max-width:180px;}}.footer__col-content{max-width:300px;}@media only screen and (max-width: 768px){.footer__section-content{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;gap:30px;}.footer-navigation{gap:10px;}}.footer__col-description{max-width:unset!important;}.footer-navigation__link:hover{cursor:pointer;}@media (max-width:768px){.lp-socials{-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;}}@media (max-width:768px){.footer-navigation__item+.footer-navigation__item,.footer-info,.footer-copyright__privacy{border:none!important;}}.lp-btn{white-space:inherit!important;}@media (max-width: 400px){h2{font-size:28px!important;}h1{font-size:32px!important;}}</style> 32 <style id="wow-hide-elements">.wow{visibility:hidden!important}</style> 33 <style id="page-css"> 34 #section-2d2580ef-5c09-4535-96fc-598bf76a02df{color:#c0c0c0;--fontColor:#c0c0c0;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:75%;--fontColor_A:1;--fontColor_darkenDir:-1;--bgColor:#ffffff;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;background-color:#ffffff;--gradient-color:var(--overlay-color, rgba(0, 0, 0, 0.00));background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c');background-repeat:no-repeat;-webkit-background-position:center;background-position:center;-webkit-background-size:cover;background-size:cover;}@media (min-width: 960px) and (orientation: landscape){#section-2d2580ef-5c09-4535-96fc-598bf76a02df{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c');}}@media (min-width: 1280px) and (orientation: landscape){#section-2d2580ef-5c09-4535-96fc-598bf76a02df{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c');}}@media (min-width: 1920px) and (orientation: landscape){#section-2d2580ef-5c09-4535-96fc-598bf76a02df{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c');}}@media (min-width: 2560px) and (orientation: landscape){#section-2d2580ef-5c09-4535-96fc-598bf76a02df{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c');}}#section-38f572f9-08ff-41a8-be87-094800542ec9{color:#1A1A1A;--fontColor:#1A1A1A;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:10%;--fontColor_A:1;--fontColor_darkenDir:1;--bgColor:#ffffff;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;background-color:#ffffff;}#section-38f572f9-08ff-41a8-be87-094800542ec9 .contact-form__container{background:var(--primary);color:var(--text-input);}#section-8a4a6b14-8bd9-4c11-bcb3-41a529fb04d0{color:#000;--fontColor:#000;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:0%;--fontColor_A:1;--fontColor_darkenDir:1;--bgColor:#fff;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;--section-padding-top:calc(0 / 4 * var(--global-section-padding));background-color:#fff;}#section-8a4a6b14-8bd9-4c11-bcb3-41a529fb04d0 h3{text-align:center;}#section-8a4a6b14-8bd9-4c11-bcb3-41a529fb04d0>div:last-child{padding-top:calc(0 / 4 * var(--global-section-padding))!important;}
34#section-bdc20bac-df92-415d-b33f-0bbba2991620{color:#1a1a1a;--fontColor:#1a1a1a;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:10%;--fontColor_A:1;--fontColor_darkenDir:1;--bgColor:#ffffff;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;background-color:#ffffff;}body>div.sidemenu{background-color:rgba(255, 255, 255, 1);color:rgba(54, 69, 79, 1);--fontColor:rgba(54, 69, 79, 1);--fontColor_H:204;--fontColor_S:19%;--fontColor_L:26%;--fontColor_A:1;--fontColor_darkenDir:1;--bgColor:rgba(255, 255, 255, 1);--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;}footer{background-color:rgba(255, 255, 255, 1);color:rgba(26, 26, 26, 1);--fontColor:rgba(26, 26, 26, 1);--fontColor_H:0;--fontColor_S:0%;--fontColor_L:10%;--fontColor_A:1;--fontColor_darkenDir:1;--bgColor:rgba(255, 255, 255, 1);--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;} 35 </style> 36 <style id="custom-style-css">.social_icons-style-1 a{border-color:#36454F!important;background-color:#36454F!important;color:rgba(255, 255, 255, 1)!important;}.social_icons-style-1 a:hover{border-color:#C5A565!important;background-color:#C5A565!important;color:#FFFFFF!important;}.social_icons-style-1 a i{color:inherit!important;}
36.social_icons-style-1 a svg,.social_icons-style-1 a svg path{fill:currentColor!important;}.button-style-1{color:rgba(255, 255, 255, 1)!important;background-color:#36454F!important;border-color:#36454F!important;}.button-style-1:hover{color:#36454F!important;background-color:rgba(0, 0, 0, 0)!important;border-color:#36454F!important;}.hyperlink-style-1{color:rgba(26, 26, 26, 1)!important;}.hyperlink-style-1:hover{color:#C5A565!important;}.pagination-style-1 li a{color:rgba(255, 255, 255, 1)!important;background-color:#36454F!important;border-color:#36454F!important;}.pagination-style-1 li a:hover{color:#36454F!important;background-color:rgba(0, 0, 0, 0)!important;border-color:#36454F!important;}.pagination-style-1 li.active a{color:#36454F!important;background-color:rgba(0, 0, 0, 0)!important;border-color:#36454F!important;}.button-style-2{color:#36454F!important;background-color:#FFFFFF!important;border-color:#FFFFFF!important;}.button-style-2:hover{color:rgba(255, 255, 255, 1)!important;background-color:rgba(0, 0, 0, 0)!important;border-color:#FFFFFF!important;}.social_icons-style-2 a{border-color:rgba(255, 255, 255, 1)!important;background-color:#FFFFFF!important;color:#36454F!important;}.social_icons-style-2 a:hover{border-color:#FFFFFF!important;background-color:rgba(255, 255, 255, 1)!important;color:#C5A565!important;}.social_icons-style-2 a i{color:inherit!important;}
36.social_icons-style-2 a svg,.social_icons-style-2 a svg path{fill:currentColor!important;}.pagination-style-2 li a{color:#36454F!important;background-color:rgba(0, 0, 0, 0)!important;border-color:rgba(0, 0, 0, 0)!important;}.pagination-style-2 li a:hover{color:#FFFFFF!important;background-color:#36454F!important;border-color:#36454F!important;}.pagination-style-2 li.active a{color:#FFFFFF!important;background-color:#36454F!important;border-color:#36454F!important;}.hyperlink-style-2{color:rgba(255, 255, 255, 1)!important;}.hyperlink-style-2:hover{color:#C5A565!important;}.button-style-3{color:#36454F!important;background-color:#FFFFFF!important;border-color:#36454F!important;}.button-style-3:hover{color:#FFFFFF!important;background-color:#36454F!important;border-color:#36454F!important;}.hyperlink-style-3{color:#36454F!important;}.hyperlink-style-3:hover{color:#C5A565!important;}</style> 37 38 39
39<script> 40 (function utmParse() { 41 const url = new URL(window.location.href); 42 if (url.search) { 43 const utm = url.search.split(/\?|&/g).reduce((acc, current) => { 44 const matched = current.match(/utm_(content|campaign|medium|source|term|id)|gclid/g); 45 if (matched) { 46 acc[matched[0]] = current.replace(`${matched[0]}=`, ''); 47 } 48 return acc; 49 }, {}); 50 if (Object.keys(utm).length) { 51 window.localStorage.setItem('utm_vars', JSON.stringify(utm)); 52 } else { 53 // Remove old utm vars 54 window.localStorage.removeItem('utm_vars'); 55 } 56 } 57 // Set referrer in local storage 58 // This is meant to be stored once to avoid self-referrals and to have the first referrer only. 59 if (!window.localStorage.getItem('referrer')) { 60 // Capture the Referrer URL 61 const referrer = document.referrer; 62 if (referrer) { 63 // Only store the referrer if it comes from outside the current domain (to avoid self-referrals) 64 const currentDomain = window.location.hostname; 65 const referrerDomain = new URL(referrer).hostname; 66 67 if (referrerDomain !== currentDomain) { 68 // Store it in localStorage 69 localStorage.setItem('referrer', referrer); 70 } 71 } 72 } 73 })(); 74 </script>
74 75 <!-- Bing Webmaster Tools Verification -->
76<script> 77// Check if on the homepage before the body loads 78if (window.location.pathname === '/' || window.location.pathname === '/index.html') { 79document.write('<meta name="msvalidate.01" content="18166674FE9A03570D2BCFB935D429DC" />'); 80} 81</script>
81 82 <!-- GOOGLE TRANSLATE STYLES are moved to external CSS --> 83</head> 84<body> 85 <div id="google_translate_element" style="display: none"></div> 86 87 <nav id="global-navbar" data-el-id="3242b8f0-253e-46f7-b814-162ccf958ec2"> 88 89<header class="header js-header" style="--textColor: rgba(255, 255, 255, 1); --backgroundColor: rgba(26, 26, 26, 1);"> 90 <div class="header-container "> 91 92 <a href="/" class="header-logo"> 93 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1920/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 1920w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=2560/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 2560w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=3456/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 3456w" sizes="350px" alt="Company logo" class="header-logo__img header-logo__img--dark"> 94 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1920/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93 1920w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=2560/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93 2560w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=3456/https://media-production.lp-cdn.com/media/b3c9faf9-995c-4ed7-83e3-cc759c40dc93 3456w" sizes="350px" alt="Company logo" class="header-logo__img header-logo__img--light"> 95 </a> 96 97 <ul class="header-navigation"> 98 <li class="header-navigation__item "> 99 100 <a class="header-navigation__link " href="/neighborhoods" style="--delay: 0s">Neighborhoods</a> 101 </li> 102 <li class="header-navigation__item "> 103 104 <a class="header-navigation__link " href="/team" style="--delay: 0.1s">About</a> 105 </li> 106 <li class="header-navigation__item "> 107 108 <a class="header-navigation__link " href="/home-search/listings" style="--delay: 0.2s">Home Search</a> 109 </li> 110 <li class="header-navigation__item "> 111 112 <a class="header-navigation__link " href="/home-valuation" style="--delay: 0.30000000000000004s">Home Valuation</a> 113 </li> 114 <li class="header-navigation__item "> 115 116 <a class="header-navigation__link " href="/properties" style="--delay: 0.4s">Portfolio</a> 117 </li> 118 <li class="header-navigation__item "> 119 120 <a class="header-navigation__link " data-type="CONTACT_US" tabindex="0" style="--delay: 0.5s">Contact Us</a> 121 </li> 122 </ul> 123 124 125 <div class="header-buttons-wrapper"> 126 127 128 129 <button type="button" class="header-hamburger-btn lp-btn hamburger-component hamburger js-sidemenu-opener" 130 title="Toggle Sidemenu"></button> 131 </div> 132 </div> 133 134</header> 135 136 </nav> 137 <div id="global-sidemenu" class="sidemenu" data-el-id="033c3235-a8bd-45bf-a4a8-96977e33190d"> 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154<button class="sidemenu-close toggle close lp-btn lp-icon link-icon--plus"></button> 155 156 157<div class="sidemenu-wrapper sidemenu-wrapper--small" style="--navItemNumberColor: rgba(197, 165, 101, 1);"> 158 <div class="sidemenu-nav-holder "> 159 <ul class="sidemenu-navigation sidemenu-navigation--index"> 160 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 161 <a 162 href="/" 163 class="sidemenu-navigation__link">Home</a> 164 165 </li> 166 167 <li class="sidemenu-navigation__item js-idemenu-navigation-item sidemenu-navigation__item--sub-nav">
168 <span class="sidemenu-navigation__link sidemenu-navigation__link--sub-nav sidemenu-navigation__link--sub-nav-active js-sub-nav-opener lp-icon"> 169 About 170 </span> 171 172 <ul class="sidemenu-navigation-sub-nav lp-a"> 173 <li class="sidemenu-navigation-sub-nav__item"><a href="/team" class="sidemenu-navigation-sub-nav__link">Meet the Team</a></li> 174 <li class="sidemenu-navigation-sub-nav__item"><a href="/testimonials" class="sidemenu-navigation-sub-nav__link">Testimonials</a></li> 175 </ul> 176 </li> 177 178 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 179 <a 180 href="/home-search" 181 class="sidemenu-navigation__link">Home Search</a> 182 183 </li> 184 185 </ul> 186 187 <ul class="sidemenu-navigation sidemenu-navigation--index"> 188 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 189 <a 190 href="/home-valuation" 191 class="sidemenu-navigation__link">Home Valuation</a> 192 193 </li> 194 195 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 196 <a 197 href="/properties" 198 class="sidemenu-navigation__link">Portfolio</a> 199 200 </li> 201 202 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 203 <a 204 href="/neighborhoods" 205 class="sidemenu-navigation__link">Neighborhoods</a> 206 207 </li> 208 209 </ul> 210 211 <ul class="sidemenu-navigation sidemenu-navigation--index"> 212 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 213 <a 214 href="/blog" 215 class="sidemenu-navigation__link">Blog</a> 216 217 </li> 218 219 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 220 <a 221 href="/vlog" 222 class="sidemenu-navigation__link">Vlog</a> 223 224 </li> 225 226 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 227 <a 228 target="_blank" href="https://www.inspiredlifescapes.com/" 229 class="sidemenu-navigation__link">Inspired Lifescapes</a> 230 231 </li> 232 233 </ul> 234 235 <ul class="sidemenu-navigation sidemenu-navigation--index"> 236 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 237 <a 238 href="/contact" 239 class="sidemenu-navigation__link">Contact Us</a> 240 241 </li> 242 243 <li class="sidemenu-navigation__item js-idemenu-navigation-item "> 244 <a 245 href="/home-search/auth/sign_in" 246 class="sidemenu-navigation__link">My Search Portal</a> 247 248 </li> 249 250 </ul> 251 </div> 252 253 <div class="sidemenu-footer"> 254 <div class="sidemenu__info-group sidemenu__col"> 255 <div class="sidemenu__section-content"> 256 <div class="sidemenu__col-content"> 257 <p class="sidemenu__col-title">Contact</p><div class="sidemenu__col-description lp-a"> 258 <p class="sidemenu__col-agent-name">Carrie Haymond Group</p> 259 <a href="tel:(206) 512-0850" class="sidemenu__col-phone">(206) 512-0850</a> 260 <a href="/cdn-cgi/l/email-protection#781b190a0a111d1019011517161c381b1a1a191116561b1715" class="sidemenu__col-email"><span class="__cf_email__" data-cfemail="77141605051e121f160e1a18191337141515161e195914181a">[email protected]</span></a> 261 </div> 262 </div> 263 <div class="sidemenu__col-content"> 264 <p class="sidemenu__col-title">Office</p> <div class="sidemenu__col-description lp-a sidemenu__col-description--pre-line"><p>150 Bellevue Way SE<br>Bellevue WA 98004 </p></div> 265 266 </div> 267 </div> 268 269 </div> 270 271 <div class="sidemenu-socials-block"> 272 <p class="sidemenu__col-title">Follow Us</p> 273 <ul class="lp-socials social_icons-style-1"> 274 275 <li> 276 <a 277 href="https://www.facebook.com/CarrieSeattleInspiredLifescapes" 278 class="lp-socials__link" 279 data-wow-delay="0s" 280 target="_blank" 281 aria-label="FACEBOOK" 282 >
283 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 284 </a> 285 </li> 286 <li> 287 <a 288 href="https://www.instagram.com/carriethebroker/" 289 class="lp-socials__link" 290 data-wow-delay="0.1s" 291 target="_blank" 292 aria-label="INSTAGRAM" 293 > 294 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 295 </a> 296 </li> 297 <li> 298 <a 299 href="https://www.linkedin.com/in/carriehaymond/" 300 class="lp-socials__link" 301 data-wow-delay="0.2s" 302 target="_blank" 303 aria-label="LINKEDIN" 304 > 305 <span class="lp-i lp-i--linkedin" aria-hidden="true"></span> 306 </a> 307 </li> 308 <li> 309 <a 310 href="https://www.youtube.com/@carriehaymond8746" 311 class="lp-socials__link" 312 data-wow-delay="0.3s" 313 target="_blank" 314 aria-label="YOUTUBE" 315 > 316 <span class="lp-i lp-i--youtube" aria-hidden="true"></span> 317 </a> 318 </li> 319 <li> 320 <a 321 href="https://x.com/carrieseattle" 322 class="lp-socials__link" 323 data-wow-delay="0.4s" 324 target="_blank" 325 aria-label="TWITTER" 326 > 327 <span class="lp-i lp-i--twitter" aria-hidden="true"></span> 328 </a> 329 </li> 330 <li> 331 <a 332 href="https://www.pinterest.com/carrieseattle1/" 333 class="lp-socials__link" 334 data-wow-delay="0.5s" 335 target="_blank" 336 aria-label="PINTEREST" 337 > 338 <span class="lp-i lp-i--pinterest" aria-hidden="true"></span> 339 </a> 340 </li> 341 <li> 342 <a 343 href="https://www.zillow.com/profile/CarrieHaymond" 344 class="lp-socials__link" 345 data-wow-delay="0.6s" 346 target="_blank" 347 aria-label="ZILLOW" 348 > 349 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 350 </a> 351 </li> 352 </ul> </div> 353 </div> 354</div> 355 </div> 356 <div id="global-mobile-contact" class="mobile-contact-wrapper" data-el-id="4c08210d-8fa1-4e4b-ad13-3209994c2ceb"> 357 <div class="floating-contact-button"> 358 <div class="floating-contact-button__btn-holder js-btn-holder" 359 style="--backgroundColor: rgba(238, 237, 233, 1); --textColor: rgba(26, 26, 26, 1); --pillTextColor: rgba(255, 255, 255, 1); --pillBackgroundColor: rgba(54, 69, 79, 1);"> 360 <button type="button" class="floating-contact-button__btn-connect js-btn-connect lp-icon lp-ico-arrow-top">Let's Connect</button> 361 </div> 362 363 <div class="floating-contact-button-pop-up js-pop-up" style="--backgroundColor: rgba(238, 237, 233, 1); --textColor: rgba(26, 26, 26, 1);"> 364 <form class="floating-contact-button-pop-up__form js-form" data-type="contact-form"> 365 <h3 class="floating-contact-button-pop-up__title">Leave a Message</h3> 366 367 <div class="floating-contact-button-pop-up__input-group lp-input-group"> 368 <input id="__FullName" type="text" name="name"
369 class="lp-input js-input-name lp-input--light" 370 required placeholder="Full Name"> 371 </div> 372 373 <div class="floating-contact-button-pop-up__input-group lp-input-group"> 374 <input id="__Email" type="email" name="email" 375 class="lp-input js-email-input " 376 required placeholder="Email"> 377 </div> 378 379 <div class="floating-contact-button-pop-up__input-group lp-input-group"> 380 <input class="lp-input " type="tel" placeholder="Phone" id="__Phone" 381 name="phone" required> 382 </div> 383 384 <select name="interest" id="-field-interest" 385 class="floating-contact-button-pop-up__input-group lp-input js-select " required> 386 <option value="" selected disabled data-label="Interest">Interested in...</option> 387 <option value="Selling & Buying">Selling & Buying</option> 388 <option value="Selling">Selling</option> 389 <option value="Buying">Buying</option> 390 <option value="Renting">Renting</option> 391 <option value="Other">Other</option> 392 <label for="-field-interest" class="lp-label sr-only">Interest</label> 393 </select> 394 395 <div class="floating-contact-button-pop-up__input-group lp-input-group"> 396 <textarea name="message" rows="4" id="__Message" class="lp-input " 397 placeholder="Message" required></textarea> 398 </div> 399 400 <div class="lp-disclaimer"> <label style='display: none;' for='-termsAccepted'>Opt In/Disclaimer Consent:</label> 401 <label class='lp-tcr-content'> 402 <span class='lp-tcr-checkbox'> 403 <input 404 type='checkbox' 405 id='-termsAccepted' 406 name='termsAccepted' 407 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 408 oninput="this.setCustomValidity('')" 409 required 410 /></span> 411 <span class='lp-tcr-message'> 412 413 I agree to be contacted by 414 Carrie Haymond 415 via call, email, and text for real estate services. To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 416 You can also click the unsubscribe link in the emails. Message and data rates may apply. Message frequency may vary. 417 <a href='/terms-and-conditions' target='_blank'>Privacy Policy</a>. 418 </span> 419 </label> 420 421</div> 422 423 <button type="submit" class="floating-contact-button-pop-up__btn-submit lp-btn lp-btn--mob-full-width button-style-1">Submit</button> 424 425 <div class="lp-h-pot"> 426 <input id="__newsSignMiddleName" name="middleName" value="" autocomplete="off" 427 tabindex="-1"> 428 </div> 429 <input style="display: none;" type="text" name="source" value="CONTACT_INQUIRY" /> 430 <input type="hidden" name="button_text" value="Let's Connect"> 431 </form> 432 433 <div class="floating-contact-button-pop-up__success js-success"> 434 <h3 class="floating-contact-button-pop-up__title">Thank you for your message. We will be in touch with you shortly.</h3> 435 436 <a 437 href="/properties" 438 439 class="lp-btn floating-contact-button-pop-up__success-btn button-style-1" 440 441 442 443 444 445 > 446 Explore Our Properties 447 </a> 448 </div> 449 <button type="button" class="floating-contact-button-pop-up__close js-close-btn lp-icon"></button> 450 </div> 451</div> 452 453 </div> 454 <section class="image-section is-font-color-light is-background-color-light" id="section-2d2580ef-5c09-4535-96fc-598bf76a02df" data-el-id="d1637c2a-e69e-42d9-8d39-7fe5c68c7ee9"> 455 <div class="custom-intro js-scroll-section custom-intro__horizon-center" 456 style=" 457 --titleColor: rgba(177, 132, 99, 1); 458 --boxBackColor: rgba(26, 26, 26, 1); 459 --backOverlayColor: rgba(255, 255, 255, 1); 460 " 461 > 462 <div class="custom-intro__container"> 463 <div class="custom-intro__content-wrapper"> 464 <div class="custom-intro__content-box"> 465 <h1 class="custom-intro__title ">Contact Us</h1> 466 <div class="custom-intro__scroll-btn jsScrollBtn"></div> 467 </div> 468 </div> 469 </div> 470</div> 471 472</section> 473<section id="section-38f572f9-08ff-41a8-be87-094800542ec9" class="solid-section is-font-color-dark is-background-color-light" data-el-id="ebc5d50e-a4a6-419c-8f09-726c01970aad">
474 <div class="contact-form lp-vertical-paddings"> 475 <div class="contact-form__container"> 476 <div class="contact-form__title-group lp-title-group"> 477 <h2 class="contact-form__title">Get in Touch</h2> 478 479 </div> 480 <form action="" class="contact-form__form js-form" data-type="contact-form"> 481 <div class="lp-h-pot"> 482 <label for="section-38f572f9-08ff-41a8-be87-094800542ec9-if-middleName">Middle Name</label> 483 <input id="section-38f572f9-08ff-41a8-be87-094800542ec9-if-middleName" name="middleName" value="" autocomplete="off" tabindex="-1"> 484 </div> 485 <div class="contact-form__group"> 486 <div class="contact-form__group-container lp-input-group"> 487 <input required id="section-38f572f9-08ff-41a8-be87-094800542ec9-name" name="name" type="text" class="contact-form__input lp-input js-input-name" 488 placeholder="Enter Your Full Name"> 489 <label for="section-38f572f9-08ff-41a8-be87-094800542ec9-name" class="contact-form__label lp-label">Name</label> 490 </div> 491 <div class="contact-form__group-container lp-input-group"> 492 <input required id="section-38f572f9-08ff-41a8-be87-094800542ec9-phone" name="phone" type="tel" class="contact-form__input lp-input" 493 placeholder="Your Phone"> 494 <label for="section-38f572f9-08ff-41a8-be87-094800542ec9-phone" class="contact-form__label lp-label">Phone</label> 495 </div> 496 <div class="contact-form__group-container lp-input-group"> 497 <div class="contact-form__input-container"> 498 <input required id="section-38f572f9-08ff-41a8-be87-094800542ec9-email" name="email" type="email" class="contact-form__input lp-input js-email-input" 499 placeholder="@"> 500 <div class="contact-form__error error">Enter a valid email</div> 501 </div> 502 503 <label for="section-38f572f9-08ff-41a8-be87-094800542ec9-email" class="contact-form__label lp-label">Email</label> 504 </div> 505 </div> 506 507 <div class="contact-form__input-group lp-input-group"> 508 <textarea id="section-38f572f9-08ff-41a8-be87-094800542ec9-message" name="message" class="contact-form__input lp-input" 509 placeholder="Type Your Message"></textarea> 510 <label for="section-38f572f9-08ff-41a8-be87-094800542ec9-message" class="contact-form__label lp-label">Your Message</label> 511 </div> 512 <div class="contact-form__disclaimer lp-disclaimer"> <label style='display: none;' for='section-38f572f9-08ff-41a8-be87-094800542ec9-termsAccepted'>Opt In/Disclaimer Consent:</label> 513 <label class='lp-tcr-content'> 514 <span class='lp-tcr-checkbox'> 515 <input 516 type='checkbox' 517 id='section-38f572f9-08ff-41a8-be87-094800542ec9-termsAccepted' 518 name='termsAccepted' 519 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 520 oninput="this.setCustomValidity('')" 521 required 522 /></span> 523 <span class='lp-tcr-message'> 524 525 I agree to be contacted by 526 Carrie Haymond 527 via call, email, and text for real estate services. To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 528 You can also click the unsubscribe link in the emails. Message and data rates may apply. Message frequency may vary. 529 <a href='/terms-and-conditions' target='_blank'>Privacy Policy</a>. 530 </span> 531 </label> 532 533</div> 534 <button type="submit" class="contact-form-submit lp-btn lp-icon button-style-2"> 535 <div class="contact-form-submit__default"> 536 <span class="contact-form-submit__content lp-btn__content">Send Message</span> 537 </div> 538 <div class="contact-form-submit__loading"> 539 <span class="contact-form-submit__content lp-btn__content">Sending</span> 540 </div> 541 <div class="contact-form-submit__success"> 542 <div class="contact-form-submit__content lp-btn__content">Message Sent!</div> 543 </div> 544 </button> 545 </form> 546 547 </div> 548</div> 549 550</section> 551<section id="section-8a4a6b14-8bd9-4c11-bcb3-41a529fb04d0" class="solid-section is-font-color-dark is-background-color-light" data-el-id="9e19adb8-cf89-4fae-8080-d1a77c6a3b57"> 552 <div class="gallery-style-menu lp-vertical-paddings"> 553 554 <div class="gallery-style-menu-grid gallery-style-menu-grid--3"> 555 <a class="gallery-style-menu-grid__item lp_animation-img-zoom" 556 href="/home-search/listings" > 557 558 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/04f9d44a-92ee-4fea-b9b2-9662f681b5f9" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/04f9d44a-92ee-4fea-b9b2-9662f681b5f9 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/04f9d44a-92ee-4fea-b9b2-9662f681b5f9 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/04f9d44a-92ee-4fea-b9b2-9662f681b5f9 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1920/https://media-production.lp-cdn.com/media/04f9d44a-92ee-4fea-b9b2-9662f681b5f9 1920w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/04f9d44a-92ee-4fea-b9b2-9662f681b5f9 2000w" alt="Home Search" sizes="(max-width: 768px) 100vw, 50vw" class="gallery-style-menu-grid__image portrait" loading="lazy"> 559 <h3 class="gallery-style-menu-grid__title">Home Search</h3> 560 </a> 561 <a class="gallery-style-menu-grid__item lp_animation-img-zoom" 562 href="/home-valuation" > 563 564 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/2e9e1444-9f8a-4b42-82a2-311831954c7f" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/2e9e1444-9f8a-4b42-82a2-311831954c7f 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/2e9e1444-9f8a-4b42-82a2-311831954c7f 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/2e9e1444-9f8a-4b42-82a2-311831954c7f 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1920/https://media-production.lp-cdn.com/media/2e9e1444-9f8a-4b42-82a2-311831954c7f 1920w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/2e9e1444-9f8a-4b42-82a2-311831954c7f 2000w" alt="Home Valuation" sizes="(max-width: 768px) 100vw, 50vw" class="gallery-style-menu-grid__image portrait" loading="lazy"> 565 <h3 class="gallery-style-menu-grid__title">Home Valuation</h3> 566 </a> 567 <a class="gallery-style-menu-grid__item lp_animation-img-zoom" 568 href="/team" > 569 570 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/4cf8e5d7-33c6-4cf5-abb3-b07272e1095a" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/4cf8e5d7-33c6-4cf5-abb3-b07272e1095a 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/4cf8e5d7-33c6-4cf5-abb3-b07272e1095a 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/4cf8e5d7-33c6-4cf5-abb3-b07272e1095a 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1920/https://media-production.lp-cdn.com/media/4cf8e5d7-33c6-4cf5-abb3-b07272e1095a 1920w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/4cf8e5d7-33c6-4cf5-abb3-b07272e1095a 2000w" alt="Meet The Team" sizes="(max-width: 768px) 100vw, 50vw" class="gallery-style-menu-grid__image portrait" loading="lazy"> 571 <h3 class="gallery-style-menu-grid__title">Meet The Team</h3> 572 </a> 573 </div> 574</div> 575</section> 576<section id="section-bdc20bac-df92-415d-b33f-0bbba2991620" class="solid-section is-font-color-dark is-background-color-light" data-el-id="85dc9e17-a4c7-449c-8890-869fdc11b485"> 577 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/css/splide-core.min.css"/>
vendor: 109 bytes, lines 577-578
577 578<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
578<script src="https://cdn.jsdelivr.net/npm/@splidejs/[email protected]/dist/js/splide.min.js"></script>
vendor: 1 bytes, line 578
578
579<script type="text/x-handlebars-template" class="jsIGTemplate"> 580 {{#each items}} 581 <a href="{{permalink}}" target="_blank" class="instagram-feed__item lp_animation-img-zoom splide__slide insta-item insta-type-{{mediaType}}"> 582 <img class="instagram-feed__image" src="{{#ifEquals mediaType 'VIDEO'}}{{thumbnailUrl}}{{else}}{{mediaUrl}}{{/ifEquals}}" alt="Instagram post by @{{username}}"> 583 </a> 584 {{/each}} 585</script>
585 586 587<div class="instagram-feed lp-vertical-paddings"> 588 <div class="instagram-feed__container" style="--titleColor: #B18463; --boxColor: rgba(238, 237, 233, 1);"> 589 <div class="instagram-feed__title-group lp-title-group"> 590 <div class="instagram-feed__icon lp-icon"></div> 591 <p class="instagram-feed__pretitle">Follow Us On Instagram</p> 592 <h2 class="instagram-feed__title instagram-feed__title--with-pretitle"><a href="#" target="_blank" class="instagram-feed__link jsIGLink jsIGName"></a></h2> 593 </div> 594 595 <div class="instagram-feed__collection splide js-slider"> 596 <div class="splide__track"> 597 <ul class="splide__list jsIGContainer"> 598 </ul> 599 </div> 600 </div> 601 </div> 602</div> 603 604 605 606 607</section> 608 609 <div id="modals" class="modals"></div> 610 <footer id="global-footer" class="is-font-color-dark is-background-color-light" data-el-id="b8c9ffd0-3b1f-4577-96fc-828efdac86e7"> 611 612 613<div class="footer"> 614 <div class="footer-logos-row"> 615 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1920/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 1920w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=2560/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 2560w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=3456/https://media-production.lp-cdn.com/media/563f4413-7569-4756-ab7a-523457967f6a 3456w" class="footer-logo" sizes="350px" alt=""> 616 617 </div> 618 <div class="footer-contact-row"> 619 <div class="footer-info "> 620 <div class="footer__col"> 621 <h5 class="footer__section-title">Carrie Haymond Group</h5> 622 <div class="footer__section-content"> 623 <div class="footer__col-content"> 624 <p class="footer__col-title">Contact</p> <div class="footer__col-description lp-a lp-pre-line"><div><a href="tel:2065120850">(206) 512-0850</a><br><a href="/cdn-cgi/l/email-protection#ec8f8d9e9e8589848d9581838288ac8f8e8e8d8582c28f8381"><span class="__cf_email__" data-cfemail="6c0f0d1e1e0509040d15010302082c0f0e0e0d0502420f0301">[email protected]</span></a></div></div> 625 </div> 626 627 <div class="footer__col-content"> 628 <p class="footer__col-title">Office</p> <div class="footer__col-description lp-a lp-pre-line"><p>150 Bellevue Way SE <br>Bellevue WA 98004 </p></div> 629 </div> 630 </div> 631 </div> 632 633 634 635 </div> 636 637 <ul class="lp-socials social_icons-style-1 footer-socials"> 638 639 <li> 640 <a 641 href="https://www.facebook.com/CarrieSeattleInspiredLifescapes" 642 class="lp-socials__link socials__link" 643 data-wow-delay="0s" 644 target="_blank" 645 aria-label="FACEBOOK" 646 >
647 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 648 </a> 649 </li> 650 <li> 651 <a 652 href="https://www.instagram.com/carriethebroker/" 653 class="lp-socials__link socials__link" 654 data-wow-delay="0.1s" 655 target="_blank" 656 aria-label="INSTAGRAM" 657 > 658 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 659 </a> 660 </li> 661 <li> 662 <a 663 href="https://www.linkedin.com/in/carriehaymond/" 664 class="lp-socials__link socials__link" 665 data-wow-delay="0.2s" 666 target="_blank" 667 aria-label="LINKEDIN" 668 > 669 <span class="lp-i lp-i--linkedin" aria-hidden="true"></span> 670 </a> 671 </li> 672 <li> 673 <a 674 href="https://www.youtube.com/@carriehaymond8746" 675 class="lp-socials__link socials__link" 676 data-wow-delay="0.3s" 677 target="_blank" 678 aria-label="YOUTUBE" 679 > 680 <span class="lp-i lp-i--youtube" aria-hidden="true"></span> 681 </a> 682 </li> 683 <li> 684 <a 685 href="https://x.com/carrieseattle" 686 class="lp-socials__link socials__link" 687 data-wow-delay="0.4s" 688 target="_blank" 689 aria-label="TWITTER" 690 > 691 <span class="lp-i lp-i--twitter" aria-hidden="true"></span> 692 </a> 693 </li> 694 <li> 695 <a 696 href="https://www.pinterest.com/carrieseattle1/" 697 class="lp-socials__link socials__link" 698 data-wow-delay="0.5s" 699 target="_blank" 700 aria-label="PINTEREST" 701 > 702 <span class="lp-i lp-i--pinterest" aria-hidden="true"></span> 703 </a> 704 </li> 705 <li> 706 <a 707 href="https://www.zillow.com/profile/CarrieHaymond" 708 class="lp-socials__link socials__link" 709 data-wow-delay="0.6s" 710 target="_blank" 711 aria-label="ZILLOW" 712 > 713 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 714 </a> 715 </li> 716 </ul> </div> 717 718 719 <div class="footer-navigation-row"> 720 <p class="footer-navigation-title">navigation</p> 721 722 <ul class="footer-navigation lp-a"> 723 <li class="footer-navigation__item"> 724 <a href="/properties" class="footer-navigation__link ">Portfolio</a> 725 726 </li> 727 <li class="footer-navigation__item"> 728 <a href="/neighborhoods" class="footer-navigation__link ">Neighborhoods</a> 729 730 </li> 731 <li class="footer-navigation__item"> 732 <a href="/home-valuation" class="footer-navigation__link ">Home Valuation</a> 733 734 </li> 735 <li class="footer-navigation__item"> 736 <a href="/home-search/listings" class="footer-navigation__link ">Search Homes</a> 737 738 </li> 739 <li class="footer-navigation__item"> 740 741 <a data-type="CONTACT_US" type="button" role="button" class="footer-navigation__link " >Contact Us</a> 742 </li> 743 </ul> 744 </div> 745 746 <div class="footer-navigation-row"> 747 <p class="footer-navigation-title">useful items</p> 748 749 <ul class="footer-navigation lp-a"> 750 <li class="footer-navigation__item"> 751 <a href="/testimonials" class="footer-navigation__link ">Testimonials</a> 752 753 </li> 754 <li class="footer-navigation__item"> 755 <a href="/dmca-notice" class="footer-navigation__link ">DMCA Notice</a> 756 757 </li> 758 </ul> 759 </div> 760 761 762 <div class="footer-disclaimer-row"> 763 <div class="footer-disclaimer"><p>
763The property information herein is derived from various sources that may include, but not be limited to, county records and the Multiple Listing Service, and it may include approximations. Although the information is believed to be accurate, it is not warranted and you should not rely upon it without personal verification. Affiliated real estate agents are independent contractor sales associates, not employees. ©<span class="auto-year-update">#</span> Coldwell Banker. All Rights Reserved. Coldwell Banker and the Coldwell Banker logo are trademarks of Coldwell Banker Real Estate LLC. The Coldwell Banker® System is comprised of company owned offices which are owned by a subsidiary of Anywhere Advisors LLC and franchised offices which are independently owned and operated. The Coldwell Banker System fully supports the principles of the Fair Housing Act and the Equal Opportunity Act. <img src="https://res.cloudinary.com/luxuryp/images/f_auto,q_auto/g5qzbyky8ifp5w0ex0ik/realtor-eho-logo-07232021-update-dark" alt="" width="25px"></p></div> 764 765 766 </div> 767 768 769 <div class="footer-bottom-row"> 770 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/jnymuan6tigs629jigsz" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/jnymuan6tigs629jigsz 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/jnymuan6tigs629jigsz 390w" class="footer-logo" sizes="350px" alt=""> 771 772 <div class="footer-copyright lp-a"> 773 <div class="footer-copyright__powered"> 774 Powered by <a href="https://www.luxurypresence.com/" class="" target="_blank">Luxury Presence</a> 775 </div> 776 777 <div class="footer-copyright__privacy"> 778 <span>Copyright © <span class="auto-year-update">2026</span></span> 779 780 781 <a href="/terms-and-conditions" class="footer-copyright__privacy-policy">Privacy Policy</a> 782 </div> 783 </div> 784 </div> 785 786</div> 787 788 </footer> 789 790 <div id="modal-global-contact-us" class="modal " 791 data-el-id="" 792 style="background-color: ;" 793 > 794 <div class="modal-content"> 795 796 797 <div class="contact-form-modal lp-vertical-paddings" style="--leftPanelBgColor: rgba(54, 69, 79, 1); --textColor:rgba(255, 255, 255, 1); --formBgColor: rgba(255, 255, 255, 1); --formTextColor: rgba(26, 26, 26, 1)"> 798 <div class="contact-form-modal__bg"> 799 <img src="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c" srcset="https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c 320w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c 960w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/86a15bdb-75b5-40cb-88f6-2a894b67e21c 1200w" sizes="(min-width: 1981px) 12.2222vw, (min-width: 1025px) 424px, 100vw" class="contact-form-modal__bg-image" alt=""> 800 </div> 801 <div class="contact-form-modal__close-container"> 802 <button class="contact-form-modal__close toggle close lp-btn lp-icon link-icon--plus"></button> 803 </div> 804 <div class="contact-form-modal__container"> 805 <div class="contact-form-modal-details only-desktop"> 806 <h2 class="contact-form-modal-details__title only-desktop js-contact-title">Get in Touch</h2> 807 <div class="contact-form-modal-agent"> 808 <div class="contact-form-modal-agent__contact"> 809 <p class="contact-form-modal-agent__name">Carrie Haymond Group</p> 810 <p class="contact-form-modal-agent__phone lp-a"> 811 <a class="contact-form-modal-agent__link" href="tel:(206) 512-0850">(206) 512-0850</a> 812 </p> 813 <p class="contact-form-modal-agent__email lp-a"> 814 <a class="contact-form-modal-agent__link" href="/cdn-cgi/l/email-protection#bddedccfcfd4d8d5dcc4d0d2d3d9fddedfdfdcd4d393ded2d0"><span class="__cf_email__" data-cfemail="2e4d4f5c5c474b464f574341404a6e4d4c4c4f4740004d4143">[email protected]</span></a> 815 </p> 816 </div> 817 <div class="contact-form-modal-agent__contact"> 818 <p class="contact-form-modal-agent__addres lp-pre-line">150 Bellevue Way SE 819 Bellevue WA 98004</p> 820 </div> 821 </div> 822 823 824 825 826 <div class="contact-form-modal-details__socials"> 827 <ul class="lp-socials social_icons-style-2 contact-form-modal-details__socials-l
827ist lp-socials"> 828 829 <li> 830 <a 831 href="https://www.facebook.com/CarrieSeattleInspiredLifescapes" 832 class="lp-socials__link contact-form-modal-details__socials-link" 833 data-wow-delay="0s" 834 target="_blank" 835 aria-label="FACEBOOK" 836 > 837 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 838 </a> 839 </li> 840 <li> 841 <a 842 href="https://www.instagram.com/carriethebroker/" 843 class="lp-socials__link contact-form-modal-details__socials-link" 844 data-wow-delay="0.1s" 845 target="_blank" 846 aria-label="INSTAGRAM" 847 > 848 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 849 </a> 850 </li> 851 <li> 852 <a 853 href="https://www.linkedin.com/in/carriehaymond/" 854 class="lp-socials__link contact-form-modal-details__socials-link" 855 data-wow-delay="0.2s" 856 target="_blank" 857 aria-label="LINKEDIN" 858 > 859 <span class="lp-i lp-i--linkedin" aria-hidden="true"></span> 860 </a> 861 </li> 862 <li> 863 <a 864 href="https://www.youtube.com/@carriehaymond8746" 865 class="lp-socials__link contact-form-modal-details__socials-link" 866 data-wow-delay="0.3s" 867 target="_blank" 868 aria-label="YOUTUBE" 869 > 870 <span class="lp-i lp-i--youtube" aria-hidden="true"></span> 871 </a> 872 </li> 873 <li> 874 <a 875 href="https://x.com/carrieseattle" 876 class="lp-socials__link contact-form-modal-details__socials-link" 877 data-wow-delay="0.4s" 878 target="_blank" 879 aria-label="TWITTER" 880 > 881 <span class="lp-i lp-i--twitter" aria-hidden="true"></span> 882 </a> 883 </li> 884 <li> 885 <a 886 href="https://www.pinterest.com/carrieseattle1/" 887 class="lp-socials__link contact-form-modal-details__socials-link" 888 data-wow-delay="0.5s" 889 target="_blank" 890 aria-label="PINTEREST" 891 > 892 <span class="lp-i lp-i--pinterest" aria-hidden="true"></span> 893 </a> 894 </li> 895 <li> 896 <a 897 href="https://www.zillow.com/profile/CarrieHaymond" 898 class="lp-socials__link contact-form-modal-details__socials-l
898ink" 899 data-wow-delay="0.6s" 900 target="_blank" 901 aria-label="ZILLOW" 902 > 903 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 904 </a> 905 </li> 906 </ul> </div> 907 </div> 908 <div class="contact-form-modal-form"> 909 <h2 class="contact-form-modal-form__title only-mobile js-contact-title">Get in Touch</h2> 910 <form action="" class="contact-form-modal-form__form js-form" data-type="contact-form"> 911 <div class="lp-h-pot"> 912 <label for="-if-middleName">Middle Name</label> 913 <input id="-if-middleName" name="middleName" value="" autocomplete="off" tabindex="-1"> 914 </div> 915 <div class="contact-form-modal-form__group"> 916 <div class="contact-form-modal-form__group-container lp-input-group"> 917 <input required id="-name" name="name" type="text" class="lp-input js-input-name" placeholder="Enter your full name"> 918 <label for="-name" class="lp-label contact-form-modal-form__label">Name</label> 919 </div> 920 <div class="contact-form-modal-form__group-container lp-input-group"> 921 <input required id="-phone" name="phone" type="tel" class="lp-input" placeholder="Your phone"> 922 <label for="-phone" class="lp-label contact-form-modal-form__label">Phone</label> 923 </div> 924 </div> 925 <div class="contact-form-modal-form__group lp-input-group"> 926 <div class="contact-form-modal-form__input-container"> 927 <input required id="-email" name="email" type="email" class="lp-input js-email-input" placeholder="@"> 928 <div class="contact-form-modal-form__error error">Enter a valid email</div> 929 </div> 930 931 <label for="-email" class="contact-form-modal-form__label lp-label">Email</label> 932 </div> 933 <div class="contact-form-modal-form__group lp-input-group"> 934 <textarea id="-message" name="message" class="contact-form-modal-form__textarea lp-input" placeholder="Type your message"></textarea> 935 <label for="-message" class="contact-form-modal-form__label lp-label">Your Message</label> 936 </div> 937 938 <div class="contact-form-modal-form__disclaimer lp-disclaimer"> <label style='display: none;' for='-termsAccepted'>Opt In/Disclaimer Consent:</label> 939 <label class='lp-tcr-content'> 940 <span class='lp-tcr-checkbox'> 941 <input 942 type='checkbox' 943 id='-termsAccepted' 944 name='termsAccepted' 945 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 946 oninput="this.setCustomValidity('')" 947 required 948 /></span> 949 <span class='lp-tcr-message'> 950 951 I agree to be contacted by 952 Carrie Haymond Group 953 via call, email, and text for real estate services. To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 954 You can also click the unsubscribe link in the emails. Message and data rates may apply. Message frequency may vary. 955 <a href='/terms-and-conditions' target='_blank'>Privacy Policy</a>. 956 </span> 957 </label> 958 959 </div> 960 961 <button type="submit" class="contact-form-modal-submit lp-btn lp-icon button-style-1"> 962 <div class="contact-form-modal-submit__default">
963 <span class="contact-form-modal-submit__content lp-btn__content">Send Message</span> 964 </div> 965 <div class="contact-form-modal-submit__loading"> 966 <span class="contact-form-modal-submit__content lp-btn__content">Sending</span> 967 </div> 968 <div class="contact-form-modal-submit__success"> 969 <span class="contact-form-modal-submit__content lp-btn__content">Message Sent!</span> 970 </div> 971 </button> 972 </form> 973 </div> 974 975 <div class="contact-form-modal-details only-mobile"> 976 <h2 class="contact-form-modal-details__title only-desktop js-contact-title">Get in Touch</h2> 977 <div class="contact-form-modal-agent"> 978 <div class="contact-form-modal-agent__contact"> 979 <p class="contact-form-modal-agent__name">Carrie Haymond Group</p> 980 <p class="contact-form-modal-agent__phone lp-a"> 981 <a class="contact-form-modal-agent__link" href="tel:(206) 512-0850">(206) 512-0850</a> 982 </p> 983 <p class="contact-form-modal-agent__email lp-a"> 984 <a class="contact-form-modal-agent__link" href="/cdn-cgi/l/email-protection#5c3f3d2e2e3539343d25313332381c3f3e3e3d3532723f3331"><span class="__cf_email__" data-cfemail="5a393b2828333f323b233735343e1a3938383b333474393537">[email protected]</span></a> 985 </p> 986 </div> 987 <div class="contact-form-modal-agent__contact"> 988 <p class="contact-form-modal-agent__addres lp-pre-line">150 Bellevue Way SE 989 Bellevue WA 98004</p> 990 </div> 991 </div> 992 993 994 995 996 <div class="contact-form-modal-details__socials"> 997 <ul class="lp-socials social_icons-style-2 contact-form-modal-details__socials-list lp-socials"> 998 999 <li> 1000 <a 1001 href="https://www.facebook.com/CarrieSeattleInspiredLifescapes" 1002 class="lp-socials__link contact-form-modal-details__socials-link" 1003 data-wow-delay="0s" 1004 target="_blank" 1005 aria-label="FACEBOOK" 1006 > 1007 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 1008 </a> 1009 </li> 1010 <li> 1011 <a 1012 href="https://www.instagram.com/carriethebroker/" 1013 class="lp-socials__link contact-form-modal-details__socials-link" 1014 data-wow-delay="0.1s" 1015 target="_blank" 1016 aria-label="INSTAGRAM" 1017 > 1018 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 1019 </a> 1020 </li> 1021 <li> 1022 <a 1023 href="https://www.linkedin.com/in/carriehaymond/" 1024 class="lp-socials__link contact-form-modal-details__socials-l
1024ink" 1025 data-wow-delay="0.2s" 1026 target="_blank" 1027 aria-label="LINKEDIN" 1028 > 1029 <span class="lp-i lp-i--linkedin" aria-hidden="true"></span> 1030 </a> 1031 </li> 1032 <li> 1033 <a 1034 href="https://www.youtube.com/@carriehaymond8746" 1035 class="lp-socials__link contact-form-modal-details__socials-link" 1036 data-wow-delay="0.3s" 1037 target="_blank" 1038 aria-label="YOUTUBE" 1039 > 1040 <span class="lp-i lp-i--youtube" aria-hidden="true"></span> 1041 </a> 1042 </li> 1043 <li> 1044 <a 1045 href="https://x.com/carrieseattle" 1046 class="lp-socials__link contact-form-modal-details__socials-link" 1047 data-wow-delay="0.4s" 1048 target="_blank" 1049 aria-label="TWITTER" 1050 > 1051 <span class="lp-i lp-i--twitter" aria-hidden="true"></span> 1052 </a> 1053 </li> 1054 <li> 1055 <a 1056 href="https://www.pinterest.com/carrieseattle1/" 1057 class="lp-socials__link contact-form-modal-details__socials-link" 1058 data-wow-delay="0.5s" 1059 target="_blank" 1060 aria-label="PINTEREST" 1061 > 1062 <span class="lp-i lp-i--pinterest" aria-hidden="true"></span> 1063 </a> 1064 </li> 1065 <li> 1066 <a 1067 href="https://www.zillow.com/profile/CarrieHaymond" 1068 class="lp-socials__link contact-form-modal-details__socials-link" 1069 data-wow-delay="0.6s" 1070 target="_blank" 1071 aria-label="ZILLOW" 1072 > 1073 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 1074 </a> 1075 </li> 1076 </ul> </div> 1077 </div> 1078 </div> 1079 </div> 1080 1081 </div> 1082 <button class="close lp-close" type="button" aria-label="close modal"> 1083 <span class="lp-close-icon" aria-hidden="true"></span> 1084 </button> 1085 </div> 1086 <div id="qualifying-questions-modal" tabindex="-1" role="dialog" aria-modal="true" aria-labelledby="qualifying-questions-modal-title" aria-hidden="true" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1050; display: none; align-items: center; justify-content: center;"> 1087 <style> 1088 #qualifying-questions-modal .custom-select-toggle { 1089 border: 1px solid #848484; 1090 border-radius: 8px; 1091 padding: 12px 16px; 1092 } 1093 1094 #qualifying-questions-modal .custom-select-toggle.is-placeholder { 1095 color: #848484; 1096 } 1097 1098 #qualifying-questions-modal #qualifying-questions-modal-phoneNumber::placeholder { 1099 color: #848484; 1100 opacity: 1; 1101 } 1102 1103 #qualifying-questions-modal .disclaimer-more { 1104 display: none; 1105 } 1106 1107 #qualifying-questions-modal #qualifying-questions-modal-disclaimer-text:not(.collapsed) .disclaimer-more { 1108 display: inline; 1109 } 1110 1111 #qualifying-questions-modal #qualifying-questions-modal-disclaimer-text:not(.collapsed) .disclaimer-ellipsis { 1112 display: none; 1113 } 1114 </style>
vendor: 119 bytes, lines 1114-1115
1114 1115 <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
1115<script> 1116 function toggleDisclaimerQualifyingQuestionsModal() { 1117 const disclaimerText = document.getElementById('qualifying-questions-modal-disclaimer-text'); 1118 const toggleButton = document.getElementById('qualifying-questions-modal-toggle-disclaimer'); 1119 1120 if (disclaimerText.classList.contains('collapsed')) { 1121 disclaimerText.classList.remove('collapsed'); 1122 toggleButton.textContent = 'Read less'; 1123 toggleButton.setAttribute('aria-expanded', 'true'); 1124 } else { 1125 disclaimerText.classList.add('collapsed'); 1126 toggleButton.textContent = 'Read more'; 1127 toggleButton.setAttribute('aria-expanded', 'false'); 1128 } 1129 } 1130 </script>
1130 1131 <div style="max-width: 400px; width: 95%; margin: 0 auto;"> 1132 <div style="background: #fff; border: none; border-radius: 16px; color: black; box-shadow: none; position: relative;"> 1133 <button type="button" data-dismiss="modal" aria-label="Close" style="position: absolute; right: 20px; top: 10px; font-size: 28px; font-weight: 300; color: black; opacity: 1; background: none; border: none; padding: 0; width: auto; height: auto; cursor: pointer; z-index: 10;"> 1134 <span aria-hidden="true">×</span> 1135 </button> 1136 <div style="padding: 40px 30px 32px;"> 1137 <div style="text-align: left; margin-bottom: 32px;"> 1138 <h2 id="qualifying-questions-modal-title" style="margin: 0; color: #333; font-size: 18px; line-height: 1.4; text-transform: uppercase; letter-spacing: 0.5px; font-weight: normal;"> 1139 Thanks, please provide more information to help serve you 1140 </h2> 1141 </div> 1142 <form id="qualifying-questions-modal-form" data-type="contact-form"> 1143 <input type="hidden" name="source" value="PROFILE_UPDATE" /> 1144 <input type="hidden" name="email" value="" id="qualifying-questions-modal-email" /> 1145 <input type="hidden" name="triggerSource" id="qualifying-questions-modal-triggerSource" value="" /> 1146 <label for="qualifying-questions-modal-email" style="display:none;"> 1147 Email 1148 </label> 1149 <label for="qualifying-questions-modal-triggerSource" style="display:none;"> 1150 Trigger Source 1151 </label> 1152 1153 <div style="margin-bottom: 20px;"> 1154 <label for="qualifying-questions-modal-phoneNumber" style="display:none;"> 1155 Phone Number 1156 </label> 1157 <input 1158 type="tel" 1159 id="qualifying-questions-modal-phoneNumber" 1160 name="phoneNumber" 1161 style="padding: 12px 16px; font-size: 16px; border: 1px solid #848484; border-radius: 8px; background: transparent; color: black; width: 100%; box-sizing: border-box; outline: none;" 1162 pattern="^\+?1?[-.\s]?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}$" 1163 required 1164 placeholder="Phone" 1165 aria-label="Phone Number" 1166 inputmode="tel" 1167 autocomplete="tel" 1168 onfocus="this.style.borderColor='black';" 1169 onblur="this.style.borderColor='#848484';" 1170 /> 1171 </div> 1172 1173 <div style="margin-bottom: 20px;"> 1174 <label for="qualifying-questions-modal-interest" style="display:none;"> 1175 Interest 1176 </label> 1177 <select 1178 id="qualifying-questions-modal-interest" 1179 name="interest" 1180 required 1181 data-label="Interest" 1182 aria-label="Interest" 1183 style="padding: 12px 16px; font-size: 16px; border: 1px solid #848484; border-radius: 8px; background: #fff; color: #848484; width: 100%; box-sizing: border-box; outline: none; appearance: none; -webkit-appearance: none; -moz-appearance: none; background-image: url('data:image/svg+xml;charset=utf8,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22%3E%3Cpath fill=%22%23666%22 d=%22M4.427 7.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 7H4.604a.25.25 0 00-.177.427z%22/%3E%3C/svg%3E'); background-repeat: no-repeat; background-position: right 16px center; background-size: 16px;" 1184 onfocus="this.style.borderColor='black'; this.style.color='black';" 1185 onblur="this.style.borderColor='#848484'; if(this.value === '') this.style.color='#848484'; else this.style.color='black';" 1186 > 1187 <option value="" style="background: #fff; color: #848484;" disabled selected>Interested in...</option> 1188 <option value="Selling & Buying" style="background: #fff; color: black;">Selling & Buying</option> 1189 <option value="Selling" style="background: #fff; color: black;">Selling</option> 1190 <option value="Buying" style="background: #fff; color: black;">Buying</option> 1191 <option value="Renting" style="background: #fff; color: black;">Renting</option> 1192 <option value="Other" style="background: #fff; color: black;">Other</option> 1193 </select> 1194 </div> 1195 1196 <div style="margin-bottom: 32px; font-size: 12px; line-height: 1.2;"> 1197 <label style='display: none;' for='qualifying-questions-modal-qualifying-questions-termsAccepted'>Opt In/Disclaimer Consent:</label> 1198 <label class='lp-tcr-content'>
1199 <span class='lp-tcr-checkbox'> 1200 <input 1201 type='checkbox' 1202 id='qualifying-questions-modal-qualifying-questions-termsAccepted' 1203 name='termsAccepted' 1204 aria-describedby='qualifying-questions-modal-disclaimer-text' 1205 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 1206 oninput="this.setCustomValidity('')" 1207 required 1208 /></span> 1209 <span class='lp-tcr-message'> 1210 <div id="qualifying-questions-modal-disclaimer-text" class="collapsed" style="color: #848484;"> 1211 1212 I agree to be contacted by 1213 <strong>Carrie Haymond</strong> 1214 via call, email, and text <span class="disclaimer-more">for real estate services. 1215 To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 1216 You can also click the unsubscribe link in the emails. 1217 Message and data rates may apply. Message frequency may vary.</span> 1218 <span class="disclaimer-ellipsis">...</span> 1219 <a href='/terms-and-conditions' target='_blank' rel='noopener noreferrer' style='text-decoration: underline;'>Privacy Policy</a>. 1220 </div> 1221 <button 1222 id="qualifying-questions-modal-toggle-disclaimer" 1223 type="button" 1224 style="background: none; border: none; color: #848484; font-size: inherit; font-weight: bold; padding: 0; margin-top: 4px; cursor: pointer;" 1225 aria-expanded="false" 1226 onclick="toggleDisclaimerQualifyingQuestionsModal()" 1227 > 1228 Read more 1229 </button> 1230 </span> 1231 </label> 1232 </div> 1233 1234 <div> 1235 <button 1236 type="submit" 1237 style="padding: 12px 24px; font-size: 16px; font-weight: normal; border-radius: 8px; border: 1px solid black; background: black; color: white; width: 100%; cursor: pointer; transition: all 0.2s ease; text-transform: uppercase; letter-spacing: 0.5px;" 1238 onmouseover="this.style.background='transparent'; this.style.color='black';" 1239 onmouseout="this.style.background='black'; this.style.color='white';" 1240 > 1241 Submit 1242 </button> 1243 </div> 1244 </form> 1245 </div> 1246 </div> 1247 </div> 1248 </div> 1249 1250
1250<script> 1251 (function() { 1252 var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy")); 1253 1254 if ("IntersectionObserver" in window && lazyVideos.length > 0) { 1255 var lazyVideoObserver = new IntersectionObserver(function(entries, observer) { 1256 entries.forEach(function(video) { 1257 if (video.isIntersecting) { 1258 for (var source in video.target.children) { 1259 var videoSource = video.target.children[source]; 1260 if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") { 1261 videoSource.src = videoSource.dataset.src; 1262 } 1263 } 1264 1265 video.target.load(); 1266 video.target.classList.remove("lazy"); 1267 lazyVideoObserver.unobserve(video.target); 1268 } 1269 }); 1270 }); 1271 1272 lazyVideos.forEach(function(lazyVideo) { 1273 lazyVideoObserver.observe(lazyVideo); 1274 }); 1275 } 1276 })(); 1277 </script>
1277 1278 1279 <!-- Global site tag (gtag.js) - Google Analytics --> 1280
1280<script defer src="https://www.googletagmanager.com/gtag/js"></script>
1280 1281 1282 <!-- Google Tag Manager --> 1283
1283<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 1284 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 1285 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.defer=true;j.async=false;j.src= 1286 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 1287 })(window,document,'script','dataLayer','GTM-T9SFQGHZ');</script>
1287 1288 <!-- End Google Tag Manager --> 1289 1290 1291 <!-- LP Tracker (divolte.js) - LP Divolte --> 1292
1292<script>window.divolteLp=window.divolteLp||function(){(divolteLp.q=divolteLp.q||[]).push(arguments)}</script>
1292 1293 <!-- LP Tracker (eventBus) - LP EventBus --> 1294
1294<script>window.eventBusLp=window.eventBusLp||function(){(eventBusLp.q=eventBusLp.q||[]).push(arguments)}</script>
1294 1295 1296
1296<script> 1297 window.dataLayer = window.dataLayer || []; 1298 document.sendGoogleTagManagerEvent = function() { 1299 /* Honor GPC-enabled browsers and prevent GTM event if GPC is enabled. More details: https://global-privacy-control.glitch.me/ */ 1300 if (window.navigator.globalPrivacyControl) { 1301 return; 1302 } 1303 1304 dataLayer.push(arguments); 1305 } 1306 1307 document.sendGoogleTagManagerEvent('js', new Date()); 1308 document.sendGoogleTagManagerEvent('config', 'G-N4XPEJXVWV', { page: location.pathname, location: location.href }); 1309 document.sendGoogleAnalyticsEvent = function(googleEvent) { 1310 document.sendGoogleTagManagerEvent('event', googleEvent.eventAction || googleEvent.hitType, { 1311 event_category: googleEvent.eventCategory, 1312 event_label: googleEvent.eventLabel, 1313 value: googleEvent.eventValue, 1314 page: googleEvent.page, 1315 location: googleEvent.location 1316 }) 1317 }; 1318 1319 document.addEventListener("DOMContentLoaded", function() { 1320 (function() { 1321 //If the page has an element with ID of auto-year-update the element will be populated with the current year. 1322 var date = new Date(); 1323 var elements = document.getElementsByClassName('auto-year-update'); 1324 var i; 1325 for (i = 0; i < elements.length; i++) { 1326 elements[i].innerText = date.getFullYear(); 1327 } 1328 })(); 1329 if (window.divolteLp) { 1330 window.divolteLp('sendLpPageView', {}); 1331 } 1332 if (window.eventBusLp) { 1333 window.eventBusLp('sendLpPageView', {}); 1334 } 1335 }); 1336 </script>
1336 1337 1338 1339
1339<script> 1340 // Function to dynamically load a script 1341 function loadScript(url, callback) { 1342 var script = document.createElement('script'); 1343 script.type = 'text/javascript'; 1344 script.src = url; 1345 script.defer = true; 1346 script.async = true; 1347 script.onload = callback; 1348 document.head.appendChild(script); 1349 } 1350 1351 // Function to dynamically load CSS file 1352 function loadCSS(url) { 1353 var link = document.createElement("link"); 1354 link.rel = "stylesheet"; 1355 link.href = url; 1356 document.head.appendChild(link); 1357 } 1358 1359 // Global methods / variables that can be accessed by element js 1360 window.luxuryPresence = { 1361 libs: { 1362 loadJs: loadScript, 1363 loadCss: loadCSS, 1364 jsLibsMap: { 1365 paroller: { 1366 jsSrc: 'https://d1e1jt2fj4r8r.cloudfront.net/javascript/jquery.paroller.min.js', 1367 isLoaded: false, 1368 }, 1369 ionRangeSlider: { 1370 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js', 1371 cssSrc: 'https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css', 1372 isLoaded: false, 1373 requires: ['jquery'], 1374 }, 1375 splide: { 1376 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/splidejs/4.1.4/js/splide.min.js', 1377 cssSrc: 'https://cdnjs.cloudflare.com/ajax/libs/splidejs/4.1.4/css/splide-core.min.css', 1378 isLoaded: false, 1379 }, 1380 wow: { 1381 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js', 1382 isLoaded: false, 1383 }, 1384 jquery: { 1385 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js', 1386 isLoaded: false, 1387 }, 1388 slick: { 1389 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js', 1390 isLoaded: false, 1391 requires: ['jquery'], 1392 }, 1393 libphonenumber: { 1394 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/libphonenumber-js/1.11.8/libphonenumber-js.min.js', 1395 isLoaded: false, 1396 }, 1397 }, 1398 }, 1399 }; 1400 </script>
1400 1401
1401<script defer src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer" onload="window.luxuryPresence.libs.jsLibsMap.jquery.isLoaded = true"></script>
1401 1402
1402<script defer src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js"></script>
1402 1403
1403<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/handlebars-intl.min.js"></script>
1403 1404
1404<script defer src="https://cdnjs.cloudflare.com/ajax/libs/superagent/4.1.0/superagent.min.js"></script>
1404 1405
1405<script defer src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js" onload="window.luxuryPresence.libs.jsLibsMap.slick.isLoaded = true"></script>
1405 1406
1406<script defer src="https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js" onload="window.luxuryPresence.libs.jsLibsMap.wow.isLoaded = true"></script>
1406 1407
1407<script defer src="https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js" onload="window.luxuryPresence.libs.jsLibsMap.ionRangeSlider.isLoaded = true"></script>
1407 1408 1409 <!-- GOOGLE TRANSLATE SCRIPT --> 1410
1410<script> 1411 function googleTranslateElementInit() { 1412 // list of languages can be overridden by setting window.customLanguages in the head JS. example `window.customLanguages = 'en,de,fr,ro,sk,pl'` 1413 // Language codes can be taken from here https://cloud.google.com/translate/docs/languages 1414 const DEFAULT_LANGUAGES = 'en,es,zh-CN,zh-TW,tl,vi,ar,ko,ru,de,fr,hi,pt'; 1415 document.querySelectorAll('[data-type="TRANSLATE"]').forEach(function(translate){ 1416 translate.innerText = ''; 1417 new google.translate.TranslateElement({ 1418 pageLanguage: 'en', 1419 layout: google.translate.TranslateElement.InlineLayout.SIMPLE, 1420 includedLanguages: window.customLanguages || DEFAULT_LANGUAGES, 1421 }, translate); 1422 }); 1423 } 1424 </script>
1424 1425
1425<script defer src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
1425 1426
1426<script defer src="https://accounts.google.com/gsi/intermediate"></script>
1426 1427 1428 <!-- GOOGLE TRANSLATE SCRIPT END--> 1429
1429<script> 1430 1431 // UI helpers 1432 (function () { 1433 1434 function getSideMenu () { 1435 return document.querySelector('body > div.sidemenu'); 1436 } 1437 1438 function sideMenuHide (instant) { 1439 var sideMenu = getSideMenu(); 1440 if (!sideMenu || !sideMenu.classList.contains('visible')) { 1441 return; 1442 } 1443 sideMenu.classList.remove('visible'); 1444 sideMenu.dispatchEvent(new CustomEvent('lpui-hide', { detail: { instant: !!instant } })); 1445 window.dispatchEvent(new CustomEvent('lpui-sidemenu-hide', { detail: {instant: !!instant} })); 1446 } 1447 1448 function sideMenuShow () { 1449 var sideMenu = getSideMenu(); 1450 if (!sideMenu || sideMenu.classList.contains('visible')) { 1451 return; 1452 } 1453 sideMenu.classList.add('visible'); 1454 sideMenu.dispatchEvent(new Event('lpui-show')); 1455 window.dispatchEvent(new Event('lpui-sidemenu-show')); 1456 } 1457 1458 function sideMenuToggle () { 1459 var sideMenu = getSideMenu(); 1460 if (!sideMenu) { 1461 return; 1462 } 1463 var isSideMenuVisible = sideMenu.classList.contains('visible'); 1464 if (isSideMenuVisible) { 1465 sideMenuHide(); 1466 } else { 1467 sideMenuShow(); 1468 } 1469 } 1470 1471 var scrollLockRequests = {}; 1472 window.__debug_scrollLockRequests = scrollLockRequests; 1473 var hideScrollStyles = ''+ 1474 'body {'+ 1475 'overflow: hidden;' + 1476 '}'; 1477 var hideScrollId = 'hide-body-scroll-style'; 1478 1479 function scrollLock () { 1480 var style = document.createElement('style'); 1481 style.id = hideScrollId; 1482 style.textContent = hideScrollStyles; 1483 document.head.appendChild(style); 1484 window.dispatchEvent(new Event('lpui-scroll-locked')); 1485 } 1486 1487 function scrollUnlock () { 1488 document.getElementById(hideScrollId)?.remove(); 1489 window.dispatchEvent(new Event('lpui-scroll-unlocked')); 1490 } 1491 1492 function requestScrollLock (lockId) { 1493 var wasLocked = Object.keys(scrollLockRequests).length > 0; 1494 scrollLockRequests[lockId || '__anonymous_lock'] = true; 1495 if (!wasLocked) { 1496 scrollLock(); 1497 } 1498 } 1499 1500 function releaseScrollLock (lockId) { 1501 if (lockId) { 1502 delete scrollLockRequests[lockId]; 1503 if (!Object.keys(scrollLockRequests).length) { 1504 scrollUnlock(); 1505 } 1506 } else { 1507 scrollLockRequests = {}; 1508 window.__debug_scrollLockRequests = scrollLockRequests; 1509 scrollUnlock(); 1510 } 1511 } 1512 1513 function showModal (modalId, options) { 1514 var modal = document.getElementById(modalId); 1515 if (!modal) { 1516 return; 1517 } 1518 1519 if (!modal.classList.contains('visible')) { 1520 modal.classList.add('visible'); 1521 if (options && options.scrollLock) { 1522 window.lpUI.requestScrollLock(modalId); 1523 } 1524 modal.dispatchEvent(new Event('lpui-show')); 1525 window.dispatchEvent(new CustomEvent('lpui-modal-show', { detail: { id: modalId } })); 1526 } 1527 1528 const visibleModals = document.querySelectorAll('.modal.visible'); 1529 1530 if (visibleModals.length > 1) { 1531 1532 const maxZIndex = Array.from(visibleModals).reduce((maxZIndex, modal) => { 1533 const zIndex = window.getComputedStyle(modal).zIndex; 1534 const parsedZIndex = zIndex !== 'auto' ? parseInt(zIndex) : 0; 1535 return Math.max(maxZIndex, parsedZIndex); 1536 }, 0); 1537 1538 modal.style.zIndex = maxZIndex + 1; 1539 } 1540 1541 } 1542 1543 function _hideModal (modalEl) { 1544 if (!modalEl) { 1545 return; 1546 } 1547 if (!modalEl.classList.contains('visible')) { 1548 return; 1549 } 1550 modalEl.classList.remove('visible'); 1551 modalEl.style.zIndex = '';
1552 modalEl.dispatchEvent(new Event('lpui-hide')); 1553 var modalId = modalEl.getAttribute('id'); 1554 if (modalId) { 1555 window.lpUI.releaseScrollLock(modalId); 1556 window.dispatchEvent(new CustomEvent('lpui-modal-hide', { detail: { id: modalId } })); 1557 } 1558 } 1559 1560 function hideModal (modalId) { 1561 _hideModal(document.getElementById(modalId)); 1562 } 1563 1564 function hideClosestModal (el) { 1565 const modal = el?.closest('.modal'); 1566 if (!modal) { 1567 return; 1568 } 1569 if (modal.classList) { 1570 _hideModal(modal); 1571 return; 1572 } 1573 if (modal.length && modal[0].classList) { 1574 _hideModal(modal[0]); 1575 } 1576 } 1577 1578 window.lpUI = { 1579 sideMenuHide: sideMenuHide, 1580 sideMenuShow: sideMenuShow, 1581 sideMenuToggle: sideMenuToggle, 1582 requestScrollLock: requestScrollLock, 1583 releaseScrollLock: releaseScrollLock, 1584 showModal: showModal, 1585 hideModal: hideModal, 1586 hideClosestModal: hideClosestModal 1587 } 1588 })(); 1589 1590 var stringifyPrimitive = function(v) { 1591 switch (typeof v) { 1592 case 'string': 1593 return v; 1594 1595 case 'boolean': 1596 return v ? 'true' : 'false'; 1597 1598 case 'number': 1599 return isFinite(v) ? v : ''; 1600 1601 default: 1602 return ''; 1603 } 1604 }; 1605 1606 function objectToQuerystring(obj, sep, eq, name) { 1607 // https://github.com/Gozala/querystring/blob/master/encode.js 1608 sep = sep || '&'; 1609 eq = eq || '='; 1610 if (obj === null) { 1611 obj = undefined; 1612 } 1613 1614 if (typeof obj === 'object') { 1615 return Object.keys(obj).map(function(k) { 1616 var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 1617 if (Array.isArray(obj[k])) { 1618 return obj[k].map(function(v) { 1619 return ks + encodeURIComponent(stringifyPrimitive(v)); 1620 }).join(sep); 1621 } else { 1622 return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 1623 } 1624 }).filter(Boolean).join(sep); 1625 1626 } 1627 1628 if (!name) return ''; 1629 return encodeURIComponent(stringifyPrimitive(name)) + eq + 1630 encodeURIComponent(stringifyPrimitive(obj)); 1631 } 1632 1633 function getPath(url, options) { 1634 var parser = document.createElement('a'); 1635 parser.href = url || ''; 1636 var path = parser.pathname || ''; 1637 if (path[0] !== '/') { 1638 // IE does not return path starting with a slash 1639 path = '/' + path; 1640 } 1641 1642 if (options && options.queryString) { 1643 path += parser.search; 1644 } 1645 1646 return path; 1647 } 1648 1649 function getMLSAutocomplete(keyword) { 1650 var searchURL = window.location.protocol + '//' + window.location.hostname + '/home-search/auto_complete'; 1651 var query = { 1652 kind: 'listings', 1653 query: keyword 1654 }; 1655 1656 return superagent 1657 .get(searchURL) 1658 .query(query) 1659 .then(function(res) { 1660 var data = res.body || []; 1661 /* 1662 Of the format: 1663 [{ 1664 "_index": "listing_auto_completes_production_20190723232720775", 1665 "_type": "listing_auto_complete", 1666 "_id": "235875624", 1667 "_score": 0, 1668 "status_modified_at": 1553250708, 1669 "city": "Jupiter", 1670 "price": 1335743, 1671 "display_address": "10088 Calabrese Trail Unit: 9 Jupiter FL 33478", 1672 "sold_price": null, 1673 "id": 235875624, 1674 "state": "FL", 1675 "photo_urls": [ 1676 "http://cdn.photos.sparkplatform.com/fl/20190322153144208228000000-o.jpg" 1677 ], 1678 "status": "Pending" 1679 }, ...] 1680 */ 1681 1682 return data; 1683 }); 1684 } 1685 1686 function getMLSAutocompleteWithFilters(keyword, filters = {}) { 1687 var searchURL = window.location.protocol + '//' + window.location.hostname + '/home-search/auto_complete'; 1688 1689 return superagent 1690 .get(searchURL) 1691 .query({ kind: 'listings', query: keyword, ...filters }) 1692 .then(function(res) { 1693 /* 1694 Of the format: 1695 [{ 1696 "_index": "listing_auto_completes_production_20190723232720775", 1697 "_type": "listing_auto_complete", 1698 "_id": "235875624", 1699 "_score": 0, 1700 "status_modified_at": 1553250708, 1701 "city": "Jupiter", 1702 "price": 1335743, 1703 "display_address": "10088 Calabrese Trail Unit: 9 Jupiter FL 33478", 1704 "sold_price": null, 1705 "id": 235875624, 1706 "state": "FL", 1707 "photo_urls": [ 1708 "http://cdn.photos.sparkplatform.com/fl/20190322153144208228000000-o.jpg" 1709 ], 1710 "status": "Pending" 1711 }, ...] 1712 */ 1713 return res.body || []; 1714 }); 1715 } 1716 1717 function makeMlsLink(data) { 1718 // Code Dupe from utils/googlePlace 1719 // mapping of google data type to mls data type 1720 var mapping = { 1721 neighborhood: 'neighborhood', 1722 locality: 'place', 1723 administrative_area_level_2: 'county', 1724 administrative_area_level_1: 'state', 1725 postal_code: 'postalcode', 1726 }; 1727 1728 function _filterTypes(types) { 1729 return types.filter(function(type) { return type !== 'political' }); 1730 } 1731 1732 if (!data || !Object.keys(data).length) { 1733 return '/home-search/listings'; 1734 } 1735 1736 var newData = {};
1737 _filterTypes(data.types).forEach(function(type) { 1738 if (mapping[type]) { 1739 newData.search_type = mapping[type]; 1740 } 1741 }); 1742 newData.omnibox = data.formatted_address; 1743 data.address_components.forEach(function(component) { 1744 _filterTypes(component.types).forEach(function(type) { 1745 if (mapping[type]) { 1746 newData[mapping[type]] = component.long_name; 1747 } 1748 }); 1749 }); 1750 var bounds = data.geometry.bounds; 1751 var convertedData = Object.assign(bounds, newData); 1752 1753 return '/home-search/listings?' + objectToQuerystring(convertedData); 1754 } 1755 1756 function debounce(func, wait) { 1757 let timeout; 1758 return function(...args) { 1759 const context = this; 1760 clearTimeout(timeout); 1761 timeout = setTimeout(() => func.apply(context, args), wait) 1762 } 1763 } 1764 1765 // Global methods / variables that can be accessed by element js 1766 window.luxuryPresence = { 1767 ...window.luxuryPresence, 1768 defaults: { 1769 companyId: 'bba607a7-df71-48c9-84ae-e87d443d6b5b', 1770 apiGatewayUrl: '/api-gw', 1771 routerUrl: '/api-nv', 1772 gql: {"agents":"\n query Agents (\n $agentId: ID\n $agentIds: [ID!]\n $companyId: String\n $network: Boolean\n $networkId: String\n $officeId: [ID!]\n $excludeOfficeId: [ID!]\n $offset: Int\n $limit: Int\n $sort: String\n $sortDir: SortDirectionEnum\n $search: String\n $tags: [String]\n $tagsMode: TagsModeEnum\n $teamId: [ID!]\n $developmentId: [ID!]\n $prioritizeIds: Boolean\n ) {\n agents (\n agentId: $agentId\n agentIds: $agentIds\n companyId: $companyId\n network: $network\n networkId: $networkId\n officeId: $officeId\n excludeOfficeId: $excludeOfficeId\n offset: $offset\n limit: $limit\n sort: $sort\n sortDir: $sortDir\n search: $search\n tags: $tags\n tagsMode: $tagsMode\n teamId: $teamId\n developmentId: $developmentId\n prioritizeIds: $prioritizeIds\n ) {\n id\n firstName\n lastName\n avatar {\n smallUrl\n mediumUrl\n largeUrl\n height\n width\n }\n position\n bioLong\n bioShort\n seoTitle\n seoDescription\n slug\n phoneNumber\n phoneNumber2\n license\n email\n leadAgent\n greaterArea\n tags\n }\n agentsCount(\n agentIds: $agentIds\n companyId: $companyId\n network: $network\n networkId: $networkId\n officeId: $officeId\n excludeOfficeId: $excludeOfficeId\n search: $search\n tags: $tags\n tagsMode: $tagsMode\n teamId: $teamId\n developmentId: $developmentId\n prioritizeIds: $prioritizeIds\n ) {\n count\n }\n }\n","properties":"\n query Properties(\n $agentIds: [ID!]\n $teamIds: [ID!]\n $neighborhoodIds: [ID!]\n $officeIds: [ID!]\n $propertyId: ID\n $companyId: String\n $networkId: String\n $network: Boolean\n $statusId: String\n $propertyIds: [ID!]\n $prioritizeIds: Boolean\n $statusIds: [String!]\n $excludeStatusId: [String!]\n $neighborhoodId: String\n $addressState: [String!]\n $addressCity: [String!]\n $relatedNeighborhoodPropertyId: String\n $developmentId: String\n $featuredListing: Boolean\n $leaseProperty: Boolean\n $search: String\n $searchTermMode: SearchTermModeEnum\n $globalProperty: Boolean\n $archived: Boolean\n $salesPriceGTE: Float\n $salesPriceLTE: Float\n $leasePriceGTE: Float\n $leasePriceLTE: Float\n $livingSpaceSizeGTE: Float\n $livingSpaceSizeLTE: Float\n $bathCountGTE: Float\n $bathCountLTE: Float\n $bedroomCountGTE: Float\n $bedroomCountLTE: Float\n $architectureStyle: String\n $lifestyle: String\n $propertyTypeId: String\n $propertyTypeIds: [String!]\n $spaceTypeIds: [ID!]\n $tag: String\n $backfillMLSResults: Boolean\n $displayMLSListings: String\n $hostname: String\n $websiteId: ID\n $backfillProviders: [String!]\n $backfillMLSListingIds: [String!]\n $backfillMLSAgentIds: [String!]\n $backfillMLSOfficeIds: [String!]\n $backfillBoundary: JSON\n $openHouse: Boolean\n $withGeo: Boolean\n $advancedFilters: JSON\n $seasonalPriceId: ID\n $offset: Int\n $limit: Int\n $sort: String\n $sortDir: SortDirectionEnum\n ) {\n properties(\n \n agentIds: $agentIds\n teamIds: $teamIds\n propertyId: $propertyId\n propertyIds: $propertyIds\n prioritizeIds: $prioritizeIds\n companyId: $companyId\n network: $network\n networkId: $networkId\n statusId: $statusId\n statusIds: $statusIds\n excludeStatusId: $excludeStatusId\n neighborhoodId: $neighborhoodId\n neighborhoodIds: $neighborhoodIds\n officeIds: $officeIds\n addressState: $addressState\n addressCity: $addressCity\n developmentId: $developmentId\n featuredListing: $featuredListing\n leaseProperty: $leaseProperty\n search: $search\n searchTermMode: $searchTermMode\n salesPriceGTE: $salesPriceGTE\n salesPriceLTE: $salesPriceLTE\n leasePriceGTE: $leasePriceGTE\n leasePriceLTE: $leasePriceLTE\n livingSpaceSizeGTE: $livingSpaceSizeGTE\n livingSpaceSizeLTE: $livingSpaceSizeLTE\n bathCountGTE: $bathCountGTE\n bathCountLTE: $bathCountLTE\n bedroomCountGTE: $bedroomCountGTE\n bedroomCountLTE: $bedroomCountLTE\n architectureStyle: $architectureStyle\n lifestyle: $lifestyle\n propertyTypeId: $propertyTypeId\n propertyTypeIds: $propertyTypeIds\n spaceTypeIds: $spaceTypeIds\n tag: $tag\n archived: $archived\n globalProperty: $globalProperty\n withGeo: $withGeo\n openHouse: $openHouse\n displayMLSListings: $displayMLSListings\n advancedFilters: $advancedFilters\n seasonalPriceId: $seasonalPriceId\n\n relatedNeighborhoodPropertyId: $relatedNeighborhoodPropertyId\n backfillMLSResults: $backfillMLSResults\n hostname: $hostname\n websiteId: $websiteId\n backfillProviders: $backfillProviders\n backfillMLSListingIds: $backfillMLSListingIds\n backfillMLSAgentIds: $backfillMLSAgentIds\n backfillMLSOfficeIds: $backfillMLSOfficeIds\n backfillBoundary: $backfillBoundary\n offset: $offset\n limit: $limit\n sort: $sort,\n sortDir: $sortDir\n ){\n id\n name\n status\n originalStatus {\n id\n name\n }\n salesPrice\n reducedPrice\n isPasswordProtected\n bedroomCount\n bathCount\n fullBathCount\n halfBathCount\n threeQuarterBathCount\n fullAddress\n addressLine1\n addressLine2\n addressCity\n addressState\n addressCountry\n postalCode\n description\n syncedAt\n officeName\n attributionContact\n neighborhood {\n id\n }\n media {\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n height\n width\n }\n seoTitle\n seoDescription\n slug\n fromMLS\n mlsId\n mlsLogo\n mlsAttribution\n openHouse\n openHouseHours\n priceUponRequest\n privateAddress\n leaseProperty\n leasePrice\n currency\n leaseTermFrequencyInterval\n leaseTermFrequencyCount\n leasePeriod\n livingSpaceSize\n livingSpaceUnits\n lotAreaSize\n lotAreaUnits\n tags\n latitude\n longitude\n soldDate\n timeZone\n buyerAgencyCompensation\n buyerAgencyCompensationType\n landIdUrl\
1772n }\n propertiesCount(\n \n agentIds: $agentIds\n teamIds: $teamIds\n propertyId: $propertyId\n propertyIds: $propertyIds\n prioritizeIds: $prioritizeIds\n companyId: $companyId\n network: $network\n networkId: $networkId\n statusId: $statusId\n statusIds: $statusIds\n excludeStatusId: $excludeStatusId\n neighborhoodId: $neighborhoodId\n neighborhoodIds: $neighborhoodIds\n officeIds: $officeIds\n addressState: $addressState\n addressCity: $addressCity\n developmentId: $developmentId\n featuredListing: $featuredListing\n leaseProperty: $leaseProperty\n search: $search\n searchTermMode: $searchTermMode\n salesPriceGTE: $salesPriceGTE\n salesPriceLTE: $salesPriceLTE\n leasePriceGTE: $leasePriceGTE\n leasePriceLTE: $leasePriceLTE\n livingSpaceSizeGTE: $livingSpaceSizeGTE\n livingSpaceSizeLTE: $livingSpaceSizeLTE\n bathCountGTE: $bathCountGTE\n bathCountLTE: $bathCountLTE\n bedroomCountGTE: $bedroomCountGTE\n bedroomCountLTE: $bedroomCountLTE\n architectureStyle: $architectureStyle\n lifestyle: $lifestyle\n propertyTypeId: $propertyTypeId\n propertyTypeIds: $propertyTypeIds\n spaceTypeIds: $spaceTypeIds\n tag: $tag\n archived: $archived\n globalProperty: $globalProperty\n withGeo: $withGeo\n openHouse: $openHouse\n displayMLSListings: $displayMLSListings\n advancedFilters: $advancedFilters\n seasonalPriceId: $seasonalPriceId\n\n ) {\n count\n }\n }\n","pressReleases":"\n query PressReleases (\n $pressReleaseId: ID\n $companyId: String\n $offset: Int\n $limit: Int\n $sort: String\n $sortDir: SortDirectionEnum\n $search: String\n $categories: [String!]\n $propertyId: [String!]\n $developmentId: [String!]\n $agentId: [String!]\n $teamId: [String!]\n ) {\n pressReleases (\n pressReleaseId: $pressReleaseId\n companyId: $companyId\n offset: $offset\n limit: $limit\n sort: $sort\n sortDir: $sortDir\n search: $search\n categories: $categories\n propertyId: $propertyId\n developmentId: $developmentId\n agentId: $agentId\n teamId: $teamId\n ) {\n id\n title\n description\n author\n externalUrl\n media {\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n height\n width\n }\n publishedAt\n categories\n }\n pressReleasesCount(\n companyId: $companyId\n search: $search\n categories: $categories\n propertyId: $propertyId\n developmentId: $developmentId\n agentId: $agentId\n teamId: $teamId\n ) {\n count\n }\n }\n","posts":"\n query Posts (\n $postId: ID\n $companyId: String\n $offset: Int\n $limit: Int\n $order: String\n $sortDir: SortDirectionEnum\n $search: String\n $featured: Boolean\n $categoryId: String\n $categoryIds: [String!]\n $neighborhoodId: [String!]\n ) {\n posts (\n postId: $postId\n companyId: $companyId\n offset: $offset\n limit: $limit\n order: $order\n sortDir: $sortDir\n search: $search\n featured: $featured\n postStatusId: \"5f528253-abb7-484e-95c3-330269ac1102\"\n categoryId: $categoryId\n categoryIds: $categoryIds\n neighborhoodId: $neighborhoodId\n ) {\n id\n title\n subtitle\n description\n status\n featured\n slug\n categories {\n id\n name\n }\n createdAt\n publishedAt\n scheduledAt\n createdBy {\n firstName\n lastName\n }\n customAuthor\n media {\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n height\n width\n }\n seoTitle\n seoDescription\n slug\n }\n postsCount(\n companyId: $companyId\n search: $search\n featured: $featured\n postStatusId: \"5f528253-
1772abb7-484e-95c3-330269ac1102\"\n categoryId: $categoryId\n categoryIds: $categoryIds\n neighborhoodId: $neighborhoodId\n ) {\n count\n }\n }\n","testimonials":"\n query Testimonials(\n $testimonialId: ID\n $agentId: ID\n $teamId: ID\n $companyId: String\n $offset: Int\n $limit: Int\n $sort: String\n $sortDir: SortDirectionEnum\n $search: String\n ) {\n testimonials(\n testimonialId: $testimonialId\n agentId: $agentId\n teamId: $teamId\n companyId: $companyId\n offset: $offset\n limit: $limit\n sort: $sort\n sortDir: $sortDir\n search: $search\n ){\n id\n body\n author\n position\n media {\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n height\n width\n }\n seoTitle\n seoDescription\n agents {\n slug\n email\n firstName\n lastName\n avatar {\n smallUrl\n height\n width\n }\n }\n }\n testimonialsCount(\n companyId: $companyId\n agentId: $agentId\n teamId: $teamId\n search: $search\n ) {\n count\n }\n }\n","neighborhoods":"\n query Neighborhoods (\n $neighborhoodId: ID\n $companyId: String\n $search: String\n $tag: String\n $offset: Int\n $limit: Int\n $sort: String\n $sortDir: SortDirectionEnum\n ) {\n neighborhoods (\n neighborhoodId: $neighborhoodId\n companyId: $companyId\n search: $search\n tag: $tag\n offset: $offset\n limit: $limit\n sort: $sort\n sortDir: $sortDir\n ) {\n id\n name\n description\n descriptionShort\n slug\n tags\n googlePlaceData\n mlsLink\n createdAt\n media {\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n height\n width\n }\n seoTitle\n seoDescription\n }\n neighborhoodsCount(\n companyId: $companyId\n search: $search\n tag: $tag\n ) {\n count\n }\n }\n","developments":"\n query Developments (\n $developmentId: ID\n $companyId: String\n $offset: Int\n $limit: Int\n $sort: String\n $sortDir: SortDirectionEnum\n $search: String\n $tag: [String!]\n $agentId: [ID!]\n $neighborhoodId: ID\n $neighborhoodIds: [ID!]\n ) {\n developments (\n developmentId: $developmentId\n companyId: $companyId\n offset: $offset\n limit: $limit\n sort: $sort\n sortDir: $sortDir\n search: $search\n tag: $tag\n agentId: $agentId\n neighborhoodId: $neighborhoodId\n neighborhoodIds: $neighborhoodIds\n ) {\n id\n name\n description\n descriptionShort\n slug\n createdAt\n media {\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n height\n width\n }\n googlePlaceData\n seoTitle\n seoDescription\n tags\n }\n developmentsCount(\n companyId: $companyId\n search: $search\n tag: $tag\n agentId: $agentId\n neighborhoodId: $neighborhoodId\n neighborhoodIds: $neighborhoodIds\n ) {\n count\n }\n }\n","pois":"\nquery PointsOfInterest(\n $regionId: String!,\n $radius: Int,\n $offset: Int,\n $limit: Int,\n $categories: [PoiCategoryEnum!],\n $sortBy: PoiSortByEnum\n) {\n pois(\n regionId: $regi
1772onId,\n radius: $radius,\n offset: $offset,\n limit: $limit,\n categories: $categories,\n sortBy: $sortBy\n ) {\n name\n imageUrl\n url\n reviewCount\n rating\n price\n displayPhone\n distance\n category\n location {\n address1\n address2\n address3\n city\n zipCode\n country\n state\n }\n }\n }\n","offices":"\n query Offices (\n $officeId: ID\n $officeIds: [ID!]\n $companyId: String\n $slug: String\n $sort: String\n $sortDir: SortDirectionEnum\n $search: String\n $tags: [String]\n $offset: Int\n $limit: Int\n ) {\n offices (\n officeId: $officeId\n officeIds: $officeIds\n companyId: $companyId\n slug: $slug\n sort: $sort\n sortDir: $sortDir\n search: $search\n tags: $tags\n offset: $offset\n limit: $limit\n ) {\n id\n companyId\n name\n slug\n officeMlsId\n shortDescription\n fullDescription\n addressLine1\n addressLine2\n addressCity\n addressState\n addressPostalCode\n addressCountry\n phone\n email\n officeHours\n coordinates\n googlePlaceData \n seoTitle\n seoDescription\n fullAddress\n media {\n id\n originalUrl\n thumbnailUrl\n smallUrl\n mediumUrl\n largeUrl\n xLargeUrl\n xxLargeUrl\n }\n }\n officesCount(\n companyId: $companyId\n officeId: $officeId\n officeIds: $officeIds\n slug: $slug\n search: $search\n tags: $tags\n ) {\n count\n }\n }\n","teams":"query TeamsAndCount(\n $offset: Int\n $limit: Int\n $search: String\n $sort: String\n $sortDir: SortDirectionEnum\n $companyId: ID\n) {\n teams(\n offset: $offset\n limit: $limit\n sort: $sort\n sortDir: $sortDir\n search: $search\n companyId: $companyId\n ) {\n \n id\n companyId\n name\n email\n shortBio\n fullBio\n addressLine1\n addressLine2\n addressCity\n addressState\n addressCountry\n phoneNumber1\n phoneNumber2\n postalCode\n avatar {\n id\n thumbnailUrl\n smallUrl\n mediumUrl\n largeUrl\n }\n urlWebsite\n socialLinks\n socialLinksArray\n tags\n seoTitle\n seoDesc\n slug\n displayOrder\n\n }\n teamsCount(\n search: $search\n companyId: $companyId\n ) {\n count\n }\n}"}, 1773 propertyPlaceholderImage: {"id":"3b8e34db-939e-46d6-8b67-bfcd12573ef7","alt":null,"bytes":37711,"width":1920,"format":"png","height":1200,"pngUrl":"","shared":true,"curated":false,"duration":null,"largeUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","smallUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=960/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","authorUrl":null,"createdAt":"2025-08-21T16:15:58.294Z","mediumUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=1280/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","sourceUrl":null,"updatedAt":"2025-08-21T16:15:58.294Z","xLargeUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","__typename":"Media","altTagText":null,"authorName":null,"sourceName":"r2","xxLargeUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","description":null,"displayName":"property_lockup_logo","originalUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","resourceType":"image","thumbnailUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85,fit=scale-down,width=320/https://media-production.lp-cdn.com/media/c62dfffb-efd1-4496-ab7f-a54c335c8054","originalFileName":"Property_lockup_logo","cloudinaryPublicId":"c62dfffb-efd1-4496-ab7f-a54c335c8054"} 1774 }, 1775 objectToQuerystring: objectToQuerystring, 1776 getPath: getPath, 1777 getMLSAutocomplete: getMLSAutocomplete, 1778 getMLSAutocompleteWithFilters: getMLSAutocompleteWithFilters, 1779 makeMlsLink: makeMlsLink, 1780 debounce: debounce, 1781 }; 1782 1783 var wowOffset = 100; 1784 </script>
1784 1785
1785<script> 1786 (() => { 1787 1788function $parcel$interopDefault(a) { 1789 return a && a.__esModule ? a.default : a; 1790} 1791// Entry point for lp-sdk bundle 1792// This file exists to ensure Parcel v2 outputs the correct filename 1793/* global window */ var $53de3c160faf5a81$exports = {}; 1794/* 1795WARNING!!! This file is used both on the server and in client's web-browser 1796please don't require/import any server-side related config/functions here 1797If we need something, let's pass it as a parameter of the factory 1798*/ var $0ce6edea77f7b81a$exports = {}; 1799$0ce6edea77f7b81a$exports = { 1800 getVideoFirstFrame (videoUrl) { 1801 // The default Cloudinary asset delivery URL has the following structure: https://res.cloudinary.com/<cloud_name>/<asset_type>/<delivery_type>/<transformations>/<version>/<public_id>.<extension> 1802 // https://cloudinary.com/documentation/video_manipulation_and_delivery#transformation_url_structure 1803 // The problem is that <transformations> and <version> are optional, and <transformations> can be chained separated by '/' 1804 // One more issue to be handled https://cloudinary.com/documentation/advanced_url_delivery_options#seo_friendly_media_asset_urls 1805 const CLOUDINARY_URL = 'https://res.cloudinary.com/'; 1806 const invalid = !videoUrl || !videoUrl.startsWith(CLOUDINARY_URL); 1807 if (invalid) return null; 1808 const urlComponents = videoUrl.replace(CLOUDINARY_URL, '').replace('f_auto:video', 'f_auto').split('/'); 1809 // checking if <asset_type> is not video or videos 1810 if (urlComponents[1].toLowerCase().indexOf('video') === -1) return null; 1811 // Filename is last urlComponent <public_id>.<extension> 1812 let filename = urlComponents[urlComponents.length - 1]; 1813 if (filename.lastIndexOf('.') > -1) filename = `${filename.slice(0, filename.lastIndexOf('.'))}.jpg`; 1814 else filename += '.jpg'; 1815 urlComponents[urlComponents.length - 1] = filename; 1816 // We need to add so_0,eo_0 transformation to the url to target the first frame. 1817 // we want to find the position at which we want to insert it as a chained transformation 1818 const firstFrameTransformation = 'so_0,eo_0'; 1819 let firstFrameTransformationPosition = 3; 1820 // checking cases for SEO-friendly URLs 1821 if (urlComponents[1].toLowerCase() === 'videos' && urlComponents[2] !== '') firstFrameTransformationPosition = 2; 1822 // checking the last transformation position in the url 1823 // https://cloudinary.com/documentation/transformation_reference 1824 // https://regex101.com/r/lE3W6u/1 1825 const transformationRegex = /^(?:[a-z]{1,3}|\$\w+)_\S+$/; 1826 while(firstFrameTransformationPosition < urlComponents.length - 1 && transformationRegex.test(urlComponents[firstFrameTransformationPosition]))firstFrameTransformationPosition++; 1827 urlComponents.splice(firstFrameTransformationPosition, 0, firstFrameTransformation); 1828 return CLOUDINARY_URL + urlComponents.join('/'); 1829 } 1830}; 1831 1832 1833/** 1834 * Create helpers with given Handlebars instance 1835 * 1836 * @param {Object} HandlebarsInstance 1837 * @param {Function} isEqual - we use different implementation if isEqual on server and on client 1838 * @return {Object} 1839 */ function $53de3c160faf5a81$var$handlebarsHelpersFactory(HandlebarsInstance, { isEqual: isEqual }) { 1840 // notice!: code below assumes list is ordered lowest to highest width 1841 const MEDIA_SIZES = [ 1842 { 1843 field: 'thumbnailUrl', 1844 w: 320 1845 }, 1846 { 1847 field: 'smallUrl', 1848 w: 960 1849 }, 1850 { 1851 field: 'mediumUrl', 1852 w: 1280 1853 }, 1854 { 1855 field: 'largeUrl', 1856 w: 1920 1857 }, 1858 { 1859 field: 'xLargeUrl', 1860 w: 2560 1861 }, 1862 { 1863 field: 'xxLargeUrl', 1864 w: 3456 1865 } 1866 ]; 1867 const MEDIA_SIZE_FOR_SRC = 1280; 1868 const lpFormatNumber = function(number, unit, precision) { 1869 const localization = 'en-US'; 1870 if (!number) return ''; 1871 if (typeof number === 'string') { 1872 const trimmed = number.trim(); 1873 if (/[^+\-0-9.]/.test(trimmed)) return number; // returning text 'as is' assuming it already has all formatting 1874 // eslint-disable-next-line 1875 number = parseFloat(trimmed); 1876 if (!number) return ''; 1877 } 1878 const currencyPrecision = typeof precision === 'number' ? precision : 0; 1879 const currencyFractionDigits = Number.isInteger(number) ? 0 : currencyPrecision; 1880 const currencyFractionOptions = { 1881 minimumFractionDigits: currencyFractionDigits, 1882 maximumFractionDigits: currencyFractionDigits 1883 };
1884 if (unit === 'USD') return `$${number.toLocaleString(localization, currencyFractionOptions)}`; 1885 if (unit === 'CAD') return `$${number.toLocaleString(localization, currencyFractionOptions)} CAD`; 1886 if (unit === 'EUR') return `\u{20AC}${number.toLocaleString(localization, currencyFractionOptions)}`; 1887 if (unit === 'GBP') return `\xa3${number.toLocaleString(localization, currencyFractionOptions)}`; 1888 if (unit === 'PERCENT') return `${number}%`; 1889 if (typeof precision === 'number' && precision > -1) return number.toLocaleString(localization, { 1890 maximumFractionDigits: precision 1891 }); 1892 return number.toLocaleString(localization); 1893 }; 1894 const formatFrequency = function(freqUnit) { 1895 switch(freqUnit){ 1896 case 'DAY': 1897 return 'daily'; 1898 case 'MONTH': 1899 return 'monthly'; 1900 case 'WEEK': 1901 return 'weekly'; 1902 case 'YEAR': 1903 return 'yearly'; 1904 case 'QUARTER': 1905 return 'quarterly'; 1906 case 'HALF_YEAR': 1907 return 'semi-annually'; 1908 case 'NIGHTLY': 1909 return 'nightly'; 1910 case 'SEASONAL': 1911 return 'seasonally'; 1912 default: 1913 return ''; 1914 } 1915 }; 1916 const formatFrequencyShort = function(freqUnit) { 1917 switch(freqUnit){ 1918 case 'DAY': 1919 return 'day'; 1920 case 'WEEK': 1921 return 'wk'; 1922 case 'MONTH': 1923 return 'mo'; 1924 case 'YEAR': 1925 return 'yr'; 1926 case 'QUARTER': 1927 return '3 mos'; 1928 case 'HALF_YEAR': 1929 return '6 mos'; 1930 case 'NIGHTLY': 1931 return 'night'; 1932 case 'SEASONAL': 1933 return 'season'; 1934 default: 1935 return ''; 1936 } 1937 }; 1938 const formatFrequencyStd = function(freqUnit) { 1939 switch(freqUnit){ 1940 case 'DAY': 1941 return 'day'; 1942 case 'WEEK': 1943 return 'week'; 1944 case 'MONTH': 1945 return 'month'; 1946 case 'YEAR': 1947 return 'year'; 1948 case 'QUARTER': 1949 return 'quarter'; 1950 case 'HALF_YEAR': 1951 return '6 months'; 1952 case 'NIGHTLY': 1953 return 'night'; 1954 case 'SEASONAL': 1955 return 'season'; 1956 default: 1957 return ''; 1958 } 1959 }; 1960 const formatFrequencyExt = function(unit, options) { 1961 let val; 1962 let separator; 1963 const opts = options || {}; 1964 switch(opts.mode){ 1965 case 'short': 1966 val = formatFrequencyShort(unit); 1967 separator = opts.frequencySeparator || '/'; 1968 break; 1969 case 'standard': 1970 case 'std': 1971 val = formatFrequencyStd(unit); 1972 separator = opts.frequencySeparator || '/'; 1973 break; 1974 case 'long': 1975 default: 1976 val = formatFrequency(unit); 1977 separator = opts.frequencySeparator || ' '; 1978 } 1979 if (!val) return ''; 1980 if (!opts.frequencyTag && !opts.frequencyClass) return separator + val; 1981 return `${separator}<${opts.frequencyTag || 'span'}${opts.frequencyClass ? ` class="${opts.frequencyClass}"` : ''}>${val}</${opts.frequencyTag || 'span'}>`; 1982 }; 1983 return { 1984 isEmpty (arg1, options) { 1985 const result = !arg1 || (typeof arg1 === 'string' ? !arg1.trim() : false) || !Object.keys(arg1).length; 1986 if (options.fn) return result ? options.fn(this) : options.inverse(this); 1987 return result; 1988 }, 1989 ifEquals (arg1, arg2, options) { 1990 return isEqual(arg1, arg2) ? options.fn(this) : options.inverse(this); 1991 }, 1992 'raw-helper': function(options) { 1993 return options.fn(); 1994 }, 1995 striptags (txt) { 1996 if (!txt) return ''; 1997 // Remove HTML Comments (escaping something to not make real html comment by expression) 1998 // eslint-disable-next-line no-useless-escape,no-param-reassign 1999 txt = txt.replace(/<\!--(.|[\r\n])*?-\->/g, ''); 2000 // Remove XML fragments and <style> and <svg> tags with content 2001 // eslint-disable-next-line 2002 txt = txt.replace(/<(xml|style|svg)("[^"]*"|'[^']*'|[^>])*>(.|[\r\n])*?<\/\1>/gi, ''); 2003 // Replace with space 2004 // eslint-disable-next-line no-param-reassign 2005 txt = txt.replace(/ /g, ' '); 2006 // Remove HTML Tags 2007 const regexp = /<\/?("[^"]*"|'[^']*'|[^>])*(>|$)/g; 2008 return txt.replace(regexp, ''); 2009 },
2010 striptagsLineBreaks (txt) { 2011 if (!txt) return ''; 2012 // Remove HTML Comments (escaping something to not make real html comment by expression) 2013 // eslint-disable-next-line no-useless-escape,no-param-reassign 2014 txt = txt.replace(/<\!--(.|[\r\n])*?-\->/g, ''); 2015 // Remove XML fragments and <style> and <svg> tags with content 2016 // eslint-disable-next-line no-param-reassign 2017 txt = txt.replace(/<(xml|style|svg)("[^"]*"|'[^']*'|[^>])*>(.|[\r\n])*?<\/\1>/gi, ''); 2018 // Replace <br> and </p> tags with line break char, skipping last </p> which is not followed by <br> or </p> 2019 // eslint-disable-next-line no-param-reassign 2020 txt = txt.replace(/(<br\s*\/?>)|(<\/p\s*>)(?=.*(<\/p\s*>|<br\s*\/?>))/g, ' '); 2021 // Replace with space 2022 // eslint-disable-next-line no-param-reassign 2023 txt = txt.replace(/ /g, ' '); 2024 // Remove HTML Tags 2025 const regexp = /<\/?("[^"]*"|'[^']*'|[^>])*(>|$)/g; 2026 return txt.replace(regexp, ''); 2027 }, 2028 truncate (text, limit, suffix) { 2029 if (!text) return ''; 2030 if (text.length > limit) return typeof suffix === 'string' ? text.slice(0, limit - suffix.length) + suffix : text.slice(0, limit); 2031 return text; 2032 }, 2033 // eslint-disable-next-line 2034 setSubArray (items = [], start, end, subArrayName, options) { 2035 // eslint-disable-next-line 2036 options.data.root[subArrayName] = items.slice(start, end); 2037 }, 2038 formatAreaUnit (areaUnit) { 2039 switch(areaUnit){ 2040 case 'ACRE': 2041 return 'Acres'; 2042 case 'SQUARE_METER': 2043 return 'Sq.M.'; 2044 case 'SQUARE_YARD': 2045 return 'Sq.Yd.'; 2046 default: 2047 return 'Sq.Ft.'; 2048 } 2049 }, 2050 formatFrequency: formatFrequency, 2051 formatFrequencyShort: formatFrequencyShort, 2052 lpFormatFrequency (freqUnit, options) { 2053 return formatFrequencyExt(freqUnit, options && options.hash); 2054 }, 2055 subArray (index, start, end, options) { 2056 return index >= start && index < end ? options.fn(this) : options.inverse(this); 2057 }, 2058 formatPropertyStatus (status, showSoldDate, date) { 2059 let formattedStatus = ''; 2060 switch(status){ 2061 case 'SOLD': 2062 formattedStatus = 'Sold'; 2063 break; 2064 case 'FOR_SALE': 2065 formattedStatus = 'For Sale'; 2066 break; 2067 case 'COMING_SOON': 2068 formattedStatus = 'Coming Soon'; 2069 break; 2070 case 'INACTIVE': 2071 formattedStatus = 'Inactive'; 2072 break; 2073 case 'POCKET_LISTING': 2074 formattedStatus = 'Pocket Listing'; 2075 break; 2076 case 'PENDING': 2077 formattedStatus = 'Pending'; 2078 break; 2079 case 'UNDER_CONTRACT': 2080 formattedStatus = 'Under Contract'; 2081 break; 2082 case 'ACTIVE_UNDER_CONTRACT': 2083 formattedStatus = 'Active Under Contract'; 2084 break; 2085 default: 2086 formattedStatus = status; 2087 } 2088 // `typeof date === 'string'` matters: when called as 2089 // `{{formatPropertyStatus status}}` Handlebars passes its options object 2090 // as `showSoldDate`/`date`, and this guard keeps the legacy output. 2091 if ((formattedStatus === 'Sold' || formattedStatus === 'Leased') && showSoldDate && typeof date === 'string' && date) formattedStatus += ` on ${date}`; 2092 return formattedStatus; 2093 }, 2094 formatGrade (gradeValue) { 2095 switch(gradeValue){ 2096 case 'UNDERGRADUATE_COURSES': 2097 return 'Undergrad'; 2098 case 'PRE_KINDERGARTEN': 2099 return 'PreK'; 2100 case 'KINDERGARTEN': 2101 return 'K'; 2102 case 'FIRST_GRADE': 2103 return '1st'; 2104 case 'SECOND_GRADE': 2105 return '2nd'; 2106 case 'THIRD_GRADE': 2107 return '3rd'; 2108 case 'FOURTH_GRADE': 2109 return '4th'; 2110 case 'FIFTH_GRADE': 2111 return '5th'; 2112 case 'SIXTH_GRADE': 2113 return '6th'; 2114 case 'SEVENTH_GRADE': 2115 return '7th'; 2116 case 'EIGHTH_GRADE': 2117 return '8th'; 2118 case 'NINTH_GRADE': 2119 return '9th'; 2120 case 'TENTH_GRADE': 2121 return '10th'; 2122 case 'ELEVENTH_GRADE': 2123 return '11th'; 2124 case 'TWELFTH_GRADE': 2125 return '12th'; 2126 default: 2127 return ''; 2128 } 2129 }, 2130 encodeURI (uri) { 2131 return encodeURI(uri); 2132 }, 2133 encodeURIComponent (txt) { 2134 return encodeURIComponent(txt); 2135 }, 2136 hbFormatDate (date, type) { 2137 if (!date) return ''; 2138 const newDate = date === 'current' ? new Date() : new Date(date); 2139 if (Number.isNaN(newDate.getTime())) return ''; 2140 const months = [ 2141 'January', 2142 'February', 2143 'March', 2144 'April', 2145 'May', 2146 'June', 2147 'July', 2148 'August', 2149 'September', 2150 'October',
2151 'November', 2152 'December' 2153 ]; 2154 if (type === 'YYYY' || type === 'year') return `${newDate.getFullYear()}`; 2155 if (type === 'MM/DD/YY') { 2156 let month = newDate.getMonth() + 1; 2157 if (month < 10) month = `0${month.toString()}`; 2158 return `${month}/${newDate.getDate()}/${newDate.getFullYear().toString().substr(-2)}`; 2159 } 2160 return `${months[newDate.getMonth()]} ${newDate.getDate()}, ${newDate.getFullYear()}`; 2161 }, 2162 openHousesFormat (startDate, endDate, timeZone) { 2163 if (!startDate || !endDate) return ''; 2164 /** 2165 * return formatted time. 2166 * @param {date} date 2167 * @return {time} time. 2168 */ const getTime = (date)=>{ 2169 let hours = date.getHours(); 2170 if (hours > 12) hours -= 12; 2171 const prefix = '0'; 2172 let time = `${hours}:${date.getMinutes() > 9 ? date.getMinutes() : prefix + date.getMinutes()}`; 2173 if (date.getHours() > 11) time += 'PM'; 2174 else time += 'AM'; 2175 return time; 2176 }; 2177 let tz = ''; 2178 let startFDate = startDate; 2179 let endFDate = endDate; 2180 if (timeZone) { 2181 startFDate = new Date(startDate).toLocaleString('en-US', { 2182 timeZone: timeZone 2183 }); 2184 endFDate = new Date(endDate).toLocaleString('en-US', { 2185 timeZone: timeZone 2186 }); 2187 [, , , tz] = new Date(endDate).toLocaleString('en-US', { 2188 timeZone: timeZone, 2189 timeZoneName: 'short' 2190 }).split(' '); 2191 } 2192 const date = new Date(startFDate); 2193 const openHouseDate = date.toLocaleDateString('en-US'); 2194 const time1 = getTime(date); 2195 const date2 = new Date(endFDate); 2196 const time2 = getTime(date2); 2197 return `${openHouseDate} ${time1} - ${time2} ${tz}`; 2198 }, 2199 math (lvalue, operator, rvalue) { 2200 // eslint-disable-next-line 2201 lvalue = parseFloat(lvalue); 2202 // eslint-disable-next-line 2203 rvalue = parseFloat(rvalue); 2204 return ({ 2205 '+': lvalue + rvalue, 2206 '-': lvalue - rvalue, 2207 '*': lvalue * rvalue, 2208 '/': lvalue / rvalue, 2209 '%': lvalue % rvalue 2210 })[operator]; 2211 }, 2212 findByKey (values, key, value, options) { 2213 const result = (values || []).find((entry)=>entry[key] === value); 2214 return options.fn(result); 2215 }, 2216 capitalize (string, split, options) { 2217 let capitalized; 2218 if (typeof string === 'string' && string.length) capitalized = string.toLowerCase().split(split).map((w)=>w.charAt(0).toUpperCase() + w.slice(1)).join(' ').toString(); 2219 return capitalized; 2220 }, 2221 and (...arg) { 2222 return Array.prototype.slice.call(arg).every(Boolean); 2223 }, 2224 or (...arg) { 2225 return Array.prototype.slice.call(arg, 0, -1).some(Boolean); 2226 }, 2227 not (arg) { 2228 return !arg; 2229 }, 2230 orderByIds (items, orderedIds, options) { 2231 let ret = ''; 2232 let data = null; 2233 if (options.data) data = HandlebarsInstance.createFrame(options.data); 2234 // When passed orderedIds, filter out empty items including empty strings and 2235 // only show items (and in that order) in a loop 2236 // If no orderedIds provided, show all items 2237 const filteredIds = (orderedIds || []).filter(Boolean); 2238 if (filteredIds && filteredIds.length) { 2239 let skippedItems = 0; 2240 ret = filteredIds.reduce((res, id, idx)=>{ 2241 const item = (items || []).find((item)=>item.id === id); 2242 // ID doesn't exist so ditch it 2243 if (!item) { 2244 skippedItems += 1; 2245 return res; 2246 } 2247 if (data) data.index = idx - skippedItems; 2248 return res + options.fn(item, { 2249 data: data 2250 }); 2251 }, ''); 2252 } else for(let i = 0, j = (items || []).length; i < j; i++){ 2253 if (data) data.index = i; 2254 ret += options.fn(items[i], { 2255 data: data 2256 }); 2257 } 2258 return ret; 2259 }, 2260 ifIn (elem, list, options) { 2261 if (list && list.indexOf(elem) > -1) return options.fn(this); 2262 return options.inverse(this); 2263 }, 2264 partial (name, options) { 2265 // Register a partial and output the content in a script for clientside rendering 2266 HandlebarsInstance.registerPartial(name, HandlebarsInstance.compile(options.fn())); 2267 // eslint-disable-next-line 2268 const script = `
2268<script type="text/x-handlebars-template" class="${name} pagination-script">${options.fn()}</${'script'}>`; 2269 return script; 2270 }, 2271 numCompare (l, r, operator, options) { 2272 const operators = { 2273 '==': function(l, r) { 2274 // eslint-disable-next-line 2275 return l == r; 2276 }, 2277 '===': function(l, r) { 2278 return l === r; 2279 }, 2280 '!=': function(l, r) { 2281 // eslint-disable-next-line 2282 return l != r; 2283 }, 2284 '<': function(l, r) { 2285 return l < r; 2286 }, 2287 '>': function(l, r) { 2288 return l > r; 2289 }, 2290 '<=': function(l, r) { 2291 return l <= r; 2292 }, 2293 '>=': function(l, r) { 2294 return l >= r; 2295 } 2296 }; 2297 const result = operators[operator](l, r); 2298 if (result) return options.fn ? options.fn(this) : true; 2299 return options.fn ? options.inverse(this) : false; 2300 }, 2301 lpFormatNumber: lpFormatNumber, 2302 arrayToHtmlList (arr, options) { 2303 // eslint-disable-next-line 2304 return arr.reduce((acc, current)=>{ 2305 try { 2306 if (typeof current === 'object' || current === null) return acc; 2307 // eslint-disable-next-line 2308 return acc += `<li>${current}</li>`; 2309 } catch (e) { 2310 /* empty */ } 2311 }, ''); 2312 }, 2313 join (array, separator, options) { 2314 if (!array || !array.join) return ''; 2315 return array.join(typeof separator === 'string' ? separator : ', '); 2316 }, 2317 dedupe (items, key, options) { 2318 let data = null; 2319 let skippedItems = 0; 2320 const list = new Set(); 2321 if (options.data) data = HandlebarsInstance.createFrame(options.data); 2322 return (items || []).reduce((res, item, idx)=>{ 2323 const val = key ? item[key] : item; 2324 // Item is unique if we're passed a key that is not defined 2325 const isUnique = !list.has(val) || key && val === undefined; 2326 if (isUnique) list.add(val); 2327 else { 2328 skippedItems += 1; 2329 return res; 2330 } 2331 if (data) data.index = idx - skippedItems; 2332 return res + options.fn(item, { 2333 data: data 2334 }); 2335 }, ''); 2336 }, 2337 JSONstringify (obj, options) { 2338 return JSON.stringify(obj); 2339 }, 2340 // eslint-disable-next-line sonarjs/cognitive-complexity 2341 formatPropertyPrice (...args) { 2342 const options = args[args.length - 1]; 2343 const opts = options.hash; 2344 const property = args.length > 1 ? args[0] : opts.property; 2345 const precision = opts.precision || 0; 2346 if (args.length > 2) // eslint-disable-next-line 2347 opts.mode = args[1]; 2348 if (!opts.mode) opts.mode = 'short'; 2349 let separator = args.length > 3 ? args[2] : opts.separator; 2350 if (typeof separator !== 'string') separator = ' | '; 2351 if (property.isPasswordProtected) return 'Exclusive Listing'; 2352 let priceUponRequestText = args.length > 4 ? args[3] : opts.priceUponRequestText; 2353 if (typeof priceUponRequestText !== 'string') priceUponRequestText = 'Price Upon Request'; 2354 if (property.priceUponRequest) return priceUponRequestText; 2355 let price = property.salesPrice; 2356 let reduced = null; 2357 // eslint-disable-next-line sonarjs/no-collapsible-if 2358 if (property.status !== 'SOLD' && property.status !== 'Leased') { 2359 if (property.reducedPrice) { 2360 if (!opts.reducedClass) price = property.reducedPrice; 2361 else reduced = property.reducedPrice; 2362 } 2363 } 2364 let result = ''; 2365 if (reduced) result += `<${opts.reducedTag || 'span'} class="${opts.reducedClass}">${lpFormatNumber(reduced, property.currency, precision)}</${opts.reducedTag || 'span'}>`; 2366 if (price) { 2367 price = lpFormatNumber(price, property.currency, precision); 2368 if (opts.priceTag || opts.priceClass) result += `<${opts.priceTag || 'span'}${opts.priceClass ? ` class="${opts.priceClass}"` : ''}>${price}</${opts.priceTag || 'span'}>`; 2369 else result += price; 2370 } 2371 const leasePrice = property.leasePrice && lpFormatNumber(property.leasePrice, property.currency, precision); 2372 if (leasePrice) { 2373 if (result) result += separator; 2374 const leaseText = `${leasePrice}${formatFrequencyExt(property.leasePeriod, opts)}`; 2375 if (opts.leaseTag || opts.leaseClass) result += `<${opts.leaseTag || 'span'}${opts.leaseClass ? ` class="${opts.leaseClass}"` : ''}>${leaseText}</${opts.leaseTag || 'span'}>`; 2376 else result += leaseText; 2377 } 2378 return new HandlebarsInstance.SafeString(result); 2379 }, 2380 // eslint-disable-next-line sonarjs/cognitive-complexity 2381 mediaImage (mediaSource, options = {}) { 2382 if (!mediaSource) return; 2383 const { lazy: lazy, ...attributes } = options.hash; 2384 let src = ''; 2385 let srcset = ''; 2386 if (typeof mediaSource === 'string') src = mediaSource; 2387 else { 2388 const srcMap = {}; 2389 const { height: height, width: width, altTagText: altTagText } = mediaSource; 2390 MEDIA_SIZES.forEach((src)=>{ 2391 if (mediaSource[src.field] && !srcMap[mediaSource[src.field]]) srcMap[mediaSource[src.field]] = src.w; 2392 }); 2393 const srcs = Object.keys(srcMap); 2394 if (!srcs.length) return; 2395 if (altTagText) attributes.alt = altTagText; 2396 if (height && width) { 2397 if (height >= width) { 2398 if (attributes.class) attributes.class += ' portrait'; 2399 else attributes.class = 'portrait'; 2400 } 2401 if ('setDims' in options.hash) { 2402 attributes.width = width; 2403 attributes.height = height; 2404 } 2405 } 2406 if (srcs.length > 1 && width && width < srcMap[srcs[srcs.length - 1]]) { 2407 const maxSrc = srcs.findIndex((src)=>
2407srcMap[src] >= width); 2408 srcs.splice(maxSrc + 1); 2409 srcMap[srcs[srcs.length - 1]] = width; 2410 } 2411 if (srcs.length > 1) { 2412 srcset = srcs.map((s)=>`${s} ${srcMap[s]}w`).join(', '); 2413 srcs.sort((a, b)=>Math.abs(srcMap[a] - MEDIA_SIZE_FOR_SRC) - Math.abs(srcMap[b] - MEDIA_SIZE_FOR_SRC)); 2414 } 2415 // eslint-disable-next-line 2416 src = srcs[0]; 2417 } 2418 delete attributes.setDims; 2419 if (!srcset) delete attributes.sizes; 2420 if (!('alt' in attributes)) attributes.alt = ''; 2421 if (!('loading' in attributes) && (options?.data?.root?.sectionIndex > 1 || lazy)) attributes.loading = 'lazy'; 2422 const attrs = Object.keys(attributes).map((k)=>`${HandlebarsInstance.escapeExpression(k)}="${HandlebarsInstance.escapeExpression(attributes[k])}"`).join(' '); 2423 return new HandlebarsInstance.SafeString(// eslint-disable-next-line sonarjs/no-nested-template-literals 2424 `<img src="${src}"${srcset ? ` srcset="${srcset}"` : ''}${attrs ? ` ${attrs}` : ''}>`); 2425 }, 2426 bgImage (mediaSource) { 2427 if (!mediaSource) return ''; 2428 if (typeof mediaSource === 'string') return mediaSource; 2429 return mediaSource.largeUrl || mediaSource.url || ''; 2430 }, 2431 concat (...rest) { 2432 let outStr = ''; 2433 // rest params are Array instances while arguments object is not 2434 for(const arg in rest)if (rest[arg] !== null && rest[arg] !== undefined && typeof rest[arg] !== 'object') outStr += rest[arg]; 2435 return outStr; 2436 }, 2437 setVideoExtension (uri, format) { 2438 if (!uri || !format) return ''; 2439 const decodedUri = new URL(uri); 2440 if (decodedUri.host !== 'res.cloudinary.com') { 2441 // Not a cloudinary uri, do not rewrite 2442 if (decodedUri.pathname.endsWith(`.${format}`)) return uri; 2443 return ''; 2444 } 2445 // Rewrite the cloudinary uri 2446 const lastPeriodIdx = decodedUri.pathname.lastIndexOf('.'); 2447 let pathWithoutFormat = decodedUri.pathname; 2448 if (lastPeriodIdx !== -1) pathWithoutFormat = decodedUri.pathname.substr(0, lastPeriodIdx); 2449 decodedUri.pathname = `${pathWithoutFormat}.${format}`; 2450 return decodedUri.toString(); 2451 }, 2452 addVideoSources (uri) { 2453 if (!uri) return ''; 2454 const VIDEO_VARIANTS = [ 2455 { 2456 format: 'webm', 2457 codec: 'vp9', 2458 htmlCodecs: ';codecs=vp9' 2459 }, 2460 { 2461 format: 'mp4', 2462 codec: 'h265', 2463 htmlCodecs: ';codecs=hvc1' 2464 }, 2465 { 2466 format: 'mp4', 2467 codec: 'h264', 2468 htmlCodecs: '' 2469 } 2470 ]; 2471 const VIDEO_EXT_REGEXP = /\.(?:3gp|mpeg|mp4|m4v|m4p|ogv|ogg|mov|webm)$/; 2472 if (!uri.includes('res.cloudinary.com')) // keep existing or no extension for non Cloudinary urls 2473 return `<source data-src="${uri}">`; 2474 // Cap delivery at 1080p (1920px wide) while preserving aspect ratio. Width-only: 2475 // c_limit never upscales, so portrait sources are left at their native size. 2476 // No q_auto: quality is left to Cloudinary's default rather than forced down. 2477 // Note the regex below consumes any q_auto in the source URL, so it is dropped 2478 // rather than carried through. 2479 const CONSTRAINED_DELIVERY = 'w_1920,c_limit'; 2480 const uriNoExt = uri.replace(VIDEO_EXT_REGEXP, ''); 2481 const sources = VIDEO_VARIANTS.map((type)=>{ 2482 const cloudinaryFormatUrl = `${uriNoExt.replace(/f_auto(?::video)?(?:,q_auto(?::\w+)?)?/, `f_${type.format},vc_${type.codec},${CONSTRAINED_DELIVERY}`)}.${type.format}`; 2483 return `<source data-src="${cloudinaryFormatUrl}" type="video/${type.format}${type.htmlCodecs}">`; 2484 }); 2485 return sources.join('\n'); 2486 }, 2487 cloudinaryVideoFirstFrame: $0ce6edea77f7b81a$exports.getVideoFirstFrame, 2488 initials (text, options) { 2489 if (!text) return ''; 2490 if (options?.hash?.endChar) // eslint-disable-next-line 2491 text = text.split(options.hash.endChar)[0]; 2492 // eslint-disable-next-line 2493 const maxChars = +options?.hash?.maxChars || 2; // we do not allow this to be 0 2494 let result = text.match(/\b\w/g)?.slice(0, maxChars); 2495 if (!result) return ''; 2496 if (options?.hash?.addText) result = result.map((item)=>item + options.hash.addText); 2497 return result.join(options?.hash?.joinWith || '').toUpperCase(); 2498 }, 2499 normalizePhone (phone) { 2500 if (!phone) return ''; 2501 const str = String(phone).trim(); 2502 return (str.startsWith('+') ? '+' : '') + str.replace(/\D/g, ''); 2503 } 2504 }; 2505} 2506$53de3c160faf5a81$exports = $53de3c160faf5a81$var$handlebarsHelpersFactory; 2507 2508 2509/* global window, document */ // eslint-disable-next-line import/prefer-default-export 2510class $dc851cf3939b6ef5$export$ff0712c26d5458f7 { 2511 /** 2512 * Initialize Modal Controller 2513 * @param {Object} options 2514 */ constructor(options){ 2515 this.isBuilder = false;
2516 this.companyId = null; 2517 this.websiteId = null; 2518 this.pageId = null; 2519 this.shareId = null; 2520 this.pageMeta = null; 2521 this.websiteApiGatewayUrl = null; 2522 this.buyerSellerServiceUrl = null; 2523 this.leadIngesterUrl = null; 2524 this.divolteServiceUrl = null; 2525 this.eventBus = null; 2526 this.templateId = null; 2527 this.myhomeDashboardEnabled = false; 2528 this.mlsEnabled = false; 2529 this.hstValuationRedirectEnabled = false; 2530 this.googleMapsAPIKey = null; 2531 } 2532 /** 2533 * Controllers must implement a "destroy" method to be called 2534 * after page navigation to remove event handlers / cleanup 2535 */ destroy() {} 2536 /** 2537 * Set page and supporting data 2538 * @param {string} pageId 2539 * @param {Object} options 2540 */ setPage(pageId, options = {}) { 2541 this.pageId = pageId; 2542 this.shareId = options.shareId || null; 2543 this.pageMeta = { 2544 sourceResource: options.sourceResource, 2545 pageElementId: options.pageElementId, 2546 pageQueryVariables: options.pageQueryVariables || {} 2547 }; 2548 } 2549 /** 2550 * Sets if we are in pagebuilder context or not 2551 * @param {boolean} isBuilder 2552 */ setIsBuilder(isBuilder) { 2553 this.isBuilder = isBuilder; 2554 } 2555 /** 2556 * Set Company and supporting data 2557 * @param {string} companyId 2558 * @param {Object} options 2559 */ setCompany(companyId, options = {}) { 2560 this.companyId = companyId; 2561 } 2562 /** 2563 * Set website and supporting data 2564 * @param {string} websiteId 2565 * @param {Object} options 2566 */ setWebsite(websiteId, options = {}) { 2567 this.websiteId = websiteId; 2568 if (options.myhomeDashboardEnabled) this.myhomeDashboardEnabled = options.myhomeDashboardEnabled; 2569 if (options.mlsEnabled) this.mlsEnabled = options.mlsEnabled; 2570 if (options.hstValuationRedirectEnabled) this.hstValuationRedirectEnabled = options.hstValuationRedirectEnabled; 2571 } 2572 /** 2573 * Set websiteApiGatewayUrl 2574 * @param {string} websiteApiGatewayUrl 2575 * @param {Object} options 2576 */ setWebsiteApiGatewayUrl(websiteApiGatewayUrl, options = {}) { 2577 this.websiteApiGatewayUrl = websiteApiGatewayUrl; 2578 } 2579 /** 2580 * Set buyerSellerServiceUrl 2581 * @param {string} buyerSellerServiceUrl 2582 * @param {Object} options 2583 */ setBuyerSellerServiceUrl(buyerSellerServiceUrl, options = {}) { 2584 this.buyerSellerServiceUrl = buyerSellerServiceUrl; 2585 } 2586 /** 2587 * Set divolteServiceUrl and supporting data 2588 * @param {string} divolteServiceUrl 2589 * @param {Object} options 2590 */ setDivolteServiceUrl(divolteServiceUrl, options = {}) { 2591 this.divolteServiceUrl = divolteServiceUrl; 2592 } 2593 /** 2594 * Set eventBusUrl 2595 * @param {string} eventBusUrl 2596 * @param {Object} options 2597 */ setEventBusUrl(eventBusUrl, options = {}) { 2598 this.eventBusUrl = eventBusUrl; 2599 } 2600 /** 2601 * Set templateId and supporting data 2602 * @param {string} templateId 2603 * @param {Object} options 2604 */ setTemplateId(templateId, options = {}) { 2605 this.templateId = templateId; 2606 } 2607 /** 2608 * Set leadIngesterUrl 2609 * @param {string} leadIngesterUrl 2610 * @param {Object} options 2611 */ setLeadIngesterUrl(leadIngesterUrl, options = {}) { 2612 this.leadIngesterUrl = leadIngesterUrl; 2613 } 2614 /** 2615 * Set googleMapsAPIKey 2616 * @param {string} googleMapsAPIKey 2617 * @param {Object} options 2618 */ setGoogleMapsAPIKey(googleMapsAPIKey, options = {}) { 2619 this.googleMapsAPIKey = googleMapsAPIKey; 2620 } 2621 /** 2622 * Set eventBus 2623 * @param {Object} options 2624 */ setEventBus({ enabled: enabled, apiKey: apiKey, url: url }) { 2625 this.eventBus = { 2626 enabled: enabled, 2627 apiKey: apiKey, 2628 url: url 2629 }; 2630 } 2631} 2632 2633 2634/* eslint-disable import/prefer-default-export */ // eslint-disable-next-line import/prefer-default-export 2635class $7c391e2f7931d60d$export$3b5bd9381a52554c { 2636 /** 2637 * Initialize Base Controller at build time 2638 * @param { ConfigController } config 2639 * @param {Window} window 2640 * @param {Document} document 2641 */ constructor(config, window, document){ 2642 // TODO: Listen for events from configController to update configuration 2643 this.config = config; 2644 this.window = window; 2645 this.document = document; 2646 if (!this.destroy) // With turbolinks, we want to ensure that on each page load we have the latest version of the sdk 2647 // Controllers are deconstructed and replaced (on the window object) per request 2648 // Controllers must use the destory method to remove event handlers to prevent undefined behavior 2649 throw new Error('Controllers must implement a deconstructor'); 2650 } 2651} 2652 2653 2654// eslint-disable-next-line import/prefer-default-export 2655const $a3e97e89bbc2a366$export$f037eeffb5561205 = 2000; 2656 2657 2658class $0ead03fdb95fa9ce$export$d507f25e63650365 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 2659 /** 2660 * Initialize Modal Controller at build time 2661 * @param { ConfigController } config 2662 * @param {Window} window 2663 * @param {Document} document 2664 */ constructor(config, window, document){ 2665 super(config, window, document); 2666 this.setLoginState = this.setLoginState.bind(this); 2667 this.pageId = null; 2668 this.modals = []; 2669 this.exitModalTriggered = {}; 2670 this.exitModalEventListeners = []; 2671 } 2672 /** 2673 * Initialize Modal Controller at run time 2674 * Attach handlers and events to DOM 2675 * @param {Object} options 2676 */ initialize(options) { 2677 try { 2678 const { config: config, document: document } = this; 2679 this.pageId = config.pageId; 2680 this.modals = document.querySelectorAll('body > #modals > .modal');
2681 options.modals.forEach((m)=>{ 2682 this._attachModalHandler(m); 2683 }); 2684 } catch (e) { 2685 // eslint-disable-next-line no-console 2686 console.log(e); 2687 } 2688 } 2689 /** 2690 * Controllers must implement a "destroy" method to be called 2691 * after page navigation to remove event handlers / cleanup 2692 */ destroy() { 2693 const { document: document } = this; 2694 this.exitModalEventListeners.forEach((listener)=>{ 2695 document.removeEventListener('mouseout', listener); 2696 }); 2697 this.modals = []; 2698 this.exitModalTriggered = {}; 2699 this.exitModalEventListeners = []; 2700 } 2701 /** 2702 * Set modal to local storage with expiration 2703 * @param {string} modalId 2704 */ setModalWithExpiry(modalId) { 2705 const { window: window } = this; 2706 const TTL = 86400000; // 24 hours 2707 const now = new Date(); 2708 const expiry = now.getTime() + TTL; 2709 const key = `modal-controller-${modalId}`; 2710 try { 2711 window.localStorage.setItem(key, JSON.stringify(expiry)); 2712 } catch (err) { 2713 // catch error when setting item to storage on iOS private browser 2714 } 2715 } 2716 /** 2717 * Save success login modal to local storage 2718 */ setLoginState() { 2719 const { window: window } = this; 2720 try { 2721 window.localStorage.setItem('isSignedUp', 'true'); 2722 } catch (err) { 2723 // catch error when setting item to storage on iOS private browser 2724 } 2725 } 2726 /** 2727 * Get modal and check expiration 2728 * @param {string} modalId 2729 * @return {boolean} 2730 */ getModalWithExpiry(modalId) { 2731 const { window: window } = this; 2732 const storage = window.localStorage; 2733 const expirationTime = storage.getItem(`modal-controller-${modalId}`); 2734 if (!expirationTime) return false; 2735 const expiry = JSON.parse(expirationTime); 2736 const now = new Date(); 2737 if (now.getTime() > expiry) { 2738 storage.removeItem(`modal-controller-${modalId}`); 2739 return false; 2740 } 2741 return true; 2742 } 2743 /** 2744 * Make a modal visible 2745 * @param {string} modalId 2746 * @param {Object} options 2747 */ show(modalId, options = {}) { 2748 // TODO: migrate logic from page.handlebars 2749 const { window: window } = this; 2750 const modalViewed = this.getModalWithExpiry(modalId); 2751 // Only show a modal if it has not been seen 2752 // Does not affect "show modal on button click" 2753 if (modalId && !modalViewed || options.forced === true) { 2754 window.lpUI.showModal(`modal-${modalId}`, options); 2755 this.setModalWithExpiry(modalId); 2756 } 2757 } 2758 /** 2759 * Attach events based on Modal type and page settings 2760 * @param {Modal} modal 2761 */ _attachModalHandler(modal) { 2762 const { pageId: pageId } = this; 2763 const { trigger: trigger, pageSpecific: pageSpecific } = modal; 2764 if (pageSpecific) { 2765 const pageIds = modal.pages.map((p)=>p.pageId); 2766 if (!pageIds.includes(pageId)) // Modal does not belong to this page 2767 return; 2768 } 2769 if (trigger === 'EXIT') this._exitModalHandler(modal); 2770 else if (trigger === 'TIMER') this._timerModalHandler(modal); 2771 else if (trigger === 'FORCED') this._forcedModalHandler(modal); 2772 } 2773 /** 2774 * Attach events for exit Modals 2775 * @param {Modal} modal 2776 */ _exitModalHandler(modal) { 2777 // eslint-disable-next-line 2778 const that = this; 2779 const { document: document, exitModalTriggered: exitModalTriggered } = this; 2780 const { modalId: modalId } = modal; 2781 /** 2782 * Handle the mouseout dom event 2783 * @param {Event} event 2784 */ function triggerExitModal(event) { 2785 if (exitModalTriggered[modalId]) return; 2786 if (!event.toElement && !event.relatedTarget) { 2787 that.show(modalId); 2788 exitModalTriggered[modalId] = true; 2789 } 2790 } 2791 document.addEventListener('mouseout', triggerExitModal); 2792 this.exitModalEventListeners.push(triggerExitModal); 2793 } 2794 /** 2795 * Attach events for timer Modals
2796 * @param {Modal} modal 2797 */ _timerModalHandler(modal) { 2798 const { modalId: modalId, timerDelay: timerDelay } = modal; 2799 setTimeout(()=>{ 2800 this.show(modalId); 2801 }, timerDelay); 2802 } 2803 /** 2804 * Attach events for Forced Modals 2805 * @param {Modal} modal 2806 */ _forcedModalHandler(modal) { 2807 const { modalId: modalId } = modal; 2808 const { window: window, document: document } = this; 2809 const isLoggedIn = window.localStorage.getItem('isSignedUp') === 'true'; 2810 if (!isLoggedIn) { 2811 // watch for event triggering success login 2812 const forcedModal = document.querySelector(`#modal-${modalId}`); 2813 const contactForm = forcedModal?.querySelector('form'); 2814 const MODAL_CLOSE_DELAY = contactForm?.dataset.delay || (0, $a3e97e89bbc2a366$export$f037eeffb5561205); 2815 // form submit autoclose is handled by contactForms controller 2816 contactForm.addEventListener('formsubmitted', ()=>{ 2817 this.setLoginState(); 2818 }); 2819 window.addEventListener('oauthSuccess', ()=>{ 2820 this.setLoginState(); 2821 contactForm.classList.add('success'); 2822 setTimeout(()=>{ 2823 window.lpUI.hideModal(`modal-${modalId}`); 2824 contactForm.reset(); 2825 contactForm.classList.remove('success'); 2826 }, MODAL_CLOSE_DELAY); 2827 }); 2828 this.show(modalId, { 2829 forced: true 2830 }); 2831 } 2832 } 2833} 2834 2835 2836/* eslint-disable import/prefer-default-export */ /* global window */ /* eslint-disable import/prefer-default-export */ /** 2837 * Handle navigation push 2838 * @param {string} href 2839 * @param {boolean} _blank 2840 */ const $fc0a058ae42af628$export$95e8d77caafa3e06 = (href, _blank = false)=>{ 2841 if (!href) return; 2842 if (_blank) { 2843 window.open(href); 2844 return; 2845 } 2846 window.location.assign(href); 2847}; 2848const $fc0a058ae42af628$export$267a1435fe18b8d4 = (obj, path, defValue)=>{ 2849 if (!path) return undefined; 2850 const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); 2851 const result = pathArray.reduce((prevObj, key)=>prevObj && prevObj[key], obj); 2852 return result === undefined ? defValue : result; 2853}; 2854 2855 2856 2857class $d48f9f944810f613$export$ca3218b7861b9cb0 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 2858 /** 2859 * Initialize Button Controller at build time 2860 * @param { ConfigController } config 2861 * @param {Window} window 2862 * @param {Document} document 2863 */ constructor(config, window, document){ 2864 super(config, window, document); 2865 this._boundClickHandler = this._buttonClickHandler.bind(this); 2866 } 2867 /** 2868 * Initialize Button Controller at run time 2869 * Attach handlers and events to DOM 2870 * @param {Object} options 2871 */ initialize(options = {}) { 2872 try { 2873 const { document: document } = this; 2874 document.body.removeEventListener('click', this._boundClickHandler); 2875 document.body.addEventListener('click', this._boundClickHandler); 2876 } catch (e) { 2877 // eslint-disable-next-line no-console 2878 console.error(e); 2879 } 2880 } 2881 /** 2882 * Controllers must implement a "destroy" method to be called 2883 * after page navigation to remove event handlers / cleanup 2884 */ destroy() { 2885 const { document: document } = this; 2886 document.body.removeEventListener('click', this._boundClickHandler); 2887 } 2888 /** 2889 * Buttons click handler 2890 * @param {event} e 2891 */ _buttonClickHandler(e) { 2892 this._handleButtonClick(e).catch((e)=>console.error(e)); 2893 } 2894 /** 2895 * Handle button click 2896 * We need special function to have easy way to detach listener 2897 * @param {Event} e 2898 */ async _handleButtonClick(e) { 2899 const { window: window, document: document } = this; 2900 const button = e.target.closest('button, a'); 2901 if (!button) return; 2902 const type = button.dataset.type || ''; 2903 const href = button.href || button.dataset.href || ''; 2904 const target = button.target || ''; 2905 const { submit: submit } = button.dataset; 2906 if (submit) { 2907 try { 2908 await this._handleSubmit(button); 2909 } catch (err) { 2910 // eslint-disable-next-line no-console 2911 console.error(err); 2912 } 2913 (0, $fc0a058ae42af628$export$95e8d77caafa3e06)(href, target === '_blank'); 2914 } 2915 this._trackConversion(button); 2916 if (type === 'VIDEO') { 2917 this._handleVideoButton(button); 2918 return; 2919 } 2920 if (type === 'CONTACT_US') { 2921 this._handleContactUsButton(button); 2922 return; 2923 } 2924 if (type === 'SUBSCRIBE') { 2925 // TODO: Extend for an overwritten subscribe 2926 window.lpUI.showModal('modal-global-subscribe'); 2927 return; 2928 } 2929 if (type === 'MY_ACCOUNT') { 2930 // TODO: Extend for an overwritten subscribe 2931 window.lpUI.showModal('modal-global-my-account'); 2932 return; 2933 } 2934 if (type === 'SHARE_POPUP') { 2935 // TODO: Extend for an overwritten subscribe 2936 const sharePopup = button.querySelector('.share-popup'); 2937 if (sharePopup) sharePopup.style.display = 'flex'; 2938 e.stopPropagation(); // this may not work in current 'delegate' mode 2939 return; 2940 } 2941 // Custom modals 2942 if (type.substring(0, 6) === 'MODAL_') { 2943 const modalId = type.substring(6); 2944 window.lpUI.showModal(`modal-${modalId}`); 2945 return; 2946 } 2947 // MAILTO / TEL Event 2948 if (href?.startsWith('mailto:')) { 2949 const email = href.substring(7); 2950 // TODO: Replace with SDK method 2951 document.sendGoogleAnalyticsEvent({ 2952 hitType: 'event', 2953 eventCategory: 'button', 2954 eventAction: 'click', 2955 eventLabel: 'Email', 2956 eventValue: email 2957 }); 2958 } else if (href?.startsWith('tel:')) { 2959 const phoneNumber = href.substring(4); 2960 // TODO: Replace with SDK method 2961 document.sendGoogleAnalyticsEvent({ 2962 hitType: 'event', 2963 eventCategory: 'button', 2964 eventAction: 'click', 2965 eventLabel: 'Phone', 2966 eventValue: phoneNumber 2967 }); 2968 } 2969 } 2970 /** 2971 * Handle submit 2972 * @param {HTMLButtonElement|HTMLAnchorElement} button 2973 */ async _handleSubmit(button) { 2974 const form = button.closest('form'); 2975 const { window: window } = this; 2976 if (form) return await window.luxuryPresence.contactForms.submitForm(form); 2977 return null; 2978 } 2979 _getVimeoSrc(link) { 2980 if (!link || !link.includes('vimeo.com') || link.length > 2000) return null; 2981 const matchResult = link.match(/(?:(?:www\.)?vimeo\.com\/(?:showcase\/\d+(?:\/video\/|\?video[=/])|channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|)|player\.vimeo\.com\/video\/)(\d+)(?:(?:\?.*&*h=|\/)+([\d,a-f]+))?/); 2982 if (!matchResult) return null; 2983 const [, vimeoId, vimeoHashParameter] = matchResult; 2984 let src = `https://player.vimeo.com/video/${vimeoId}?`; 2985 if (vimeoHashParameter) src += `h=${vimeoHashParameter}&`; 2986 src += 'autoplay=1'; 2987 return src; 2988 } 2989 /** 2990 * Handle type video click 2991 * @param {HTMLElement} button 2992 */ // eslint-disable-next-line sonarjs/cognitive-complexity 2993 _handleVideoButton(button) { 2994 const { document: document } = this; 2995 const section = button.closest('section'); 2996 // Embed youtube/vimeo video url 2997 let { src: src } = button.dataset; 2998 let youtubeId = ''; 2999 if (src && (src.includes('youtube.com') || src.includes('youtu.be')) && !src.includes('youtube.com/embed')) { 3000 // test link: https://regexr.com/3anm9 3001 const youtubeRegex = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v=)
3001)|youtu.be\/)([a-zA-Z0-9_-]{6,11})/; 3002 const youtubeResult = src.match(youtubeRegex); 3003 // eslint-disable-next-line 3004 if (youtubeResult) youtubeId = youtubeResult[1]; 3005 } 3006 const vimeoSrc = this._getVimeoSrc(src); 3007 if (youtubeId) src = `https://youtube.com/embed/${youtubeId}?autoplay=1&controls=0`; 3008 else if (vimeoSrc) src = vimeoSrc; 3009 if (!src || !section) return; 3010 const isVideoFile = src.endsWith('.mp4') || src.endsWith('.webm') || src.includes('cloudinary.com'); 3011 const modalHtml = ` 3012 <div id="modal-${section.id}" class="modal visible"> 3013 <div class="modal-content"> 3014 <div class="lightbox-video"> 3015 <div class="video-player"> 3016 <div class="embed-container"> 3017 ${isVideoFile ? `<video controls autoplay src="${src}"></video>` : `<iframe frameborder="0" allowfullscreen="1" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" title="YouTube video player" width="100%" height="100%" src="${src}"></iframe>`} 3018 </div> 3019 </div> 3020 </div> 3021 </div> 3022 <button class="close lp-close" type="button" aria-label="close modal"><span class="lp-close-icon" aria-hidden="true"></span></button> 3023 </div> 3024 `; 3025 document.body.insertAdjacentHTML('beforeend', modalHtml); 3026 const modal = document.getElementById(`modal-${section.id}`); 3027 const closeModal = modal.querySelector('.close'); 3028 closeModal.addEventListener('click', ()=>{ 3029 modal.remove(); 3030 }); 3031 } 3032 /** 3033 * Handle type contact us click 3034 * @param {HTMLElement} button 3035 */ _handleContactUsButton(button) { 3036 const { window: window, document: document } = this; 3037 // TODO: Extend for an overwritten contact us 3038 document.sendGoogleAnalyticsEvent({ 3039 hitType: 'event', 3040 eventCategory: 'button', 3041 eventAction: 'click', 3042 eventLabel: 'contact' 3043 }); 3044 window.lpUI.showModal('modal-global-contact-us', { 3045 scrollLock: true 3046 }); 3047 } 3048 /** 3049 * Track conversion 3050 * @param {HTMLElement} button 3051 */ _trackConversion(button) { 3052 const { window: window, document: document } = this; 3053 // Conversion Tracking Options 3054 const { gtagConversionId: gtagConversionId, gtagConversionLabel: gtagConversionLabel, fbEventName: fbEventName } = button.dataset; 3055 if (gtagConversionId && gtagConversionLabel) { 3056 if (window.dataLayer && window.dataLayer.length) { 3057 const configured = window.dataLayer.some((layer)=>layer.length && layer[0] === 'config' && layer[1] === gtagConversionId); 3058 if (!configured) document.sendGoogleTagManagerEvent('config', gtagConversionId); 3059 } 3060 const sentTo = `${gtagConversionId}/${gtagConversionLabel}`; 3061 document.sendGoogleTagManagerEvent('event', 'conversion', { 3062 send_to: sentTo 3063 }); 3064 } 3065 if (window.fbq && fbEventName) window.fbq('track', fbEventName, {}); 3066 } 3067} 3068 3069 3070/* eslint-disable import/prefer-default-export */ 3071class $68a2d4aa500c7a05$export$61ec8f2b6291ec31 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 3072 /** 3073 * Initialize Validation Controller at build time 3074 * @param { ConfigController } config 3075 * @param {Window} window 3076 * @param {Document} document 3077 */ constructor(config, window, document){ 3078 super(config, window, document); 3079 this.validatedForms = []; 3080 } 3081 /** 3082 * Initialize Contact Form Controller at run time 3083 * Attach handlers and events to DOM 3084 * @param {Object} options 3085 */ initialize(options = {}) { 3086 try { 3087 const { window: window, document: document } = this; 3088 const { selector: selector } = options; 3089 let validatedForms = []; 3090 if (selector) validatedForms = document.querySelectorAll(`${selector} form[data-type="contact-form"]`); 3091 else validatedForms = document.querySelectorAll('form[data-type="contact-form"]'); 3092 const isPhoneValidationLibRequired = !!document.querySelectorAll('input[type="tel"]').length; 3093 // we are loading the phone validation lib from the globally defined list of libs 3094 if (isPhoneValidationLibRequired && !window.libphonenumber && window?.luxuryPresence?.libs) { 3095 const { loadJs: loadJs, jsLibsMap: jsLibsMap } = window.luxuryPresence.libs; 3096 const { libphonenumber: libphonenumber } = jsLibsMap; 3097 loadJs(libphonenumber.jsSrc, this.attachValidation.bind(this, validatedForms)); 3098 }
3098 else this.attachValidation(validatedForms); 3099 this.validatedForms = this.validatedForms.concat(validatedForms); 3100 } catch (e) { 3101 // eslint-disable-next-line no-console 3102 console.log(e); 3103 } 3104 } 3105 /** 3106 * Controllers must implement a "destroy" method to be called 3107 * after page navigation to remove event handlers / cleanup 3108 */ destroy() { 3109 this.validatedForms = []; 3110 } 3111 /** 3112 * Validate all forms in the document 3113 * @param {NodeListOf<HTMLFormElement>} forms 3114 */ attachValidation(forms) { 3115 forms.forEach((form)=>{ 3116 const inputs = form.querySelectorAll('input, select, textarea'); 3117 inputs.forEach((input)=>{ 3118 this.applyFieldValidation(input); 3119 }); 3120 }); 3121 } 3122 /** 3123 * Field Validation Handler from global context 3124 * @param {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} inputField 3125 */ applyFieldValidation(inputField) { 3126 const fieldType = inputField.getAttribute('type'); 3127 if (fieldType === 'tel') this._applyPhoneValidation(inputField); 3128 } 3129 /** 3130 * Phone Field Validation Handler 3131 * @param {HTMLInputElement} inputField 3132 */ _applyPhoneValidation(inputField) { 3133 const { window: window } = this; 3134 if (window.libphonenumber) { 3135 // https://codepen.io/coinscope/pen/BaXBvBp?editors=1010 3136 const phoneUtil = window.libphonenumber.isValidPhoneNumber; 3137 inputField.addEventListener('keyup', function() { 3138 if (this.hasAttribute('required') || this.value.replace(/\s/g, '').length) try { 3139 const phoneNumber = this.value; 3140 const region = 'US'; 3141 const isValidNumber = phoneUtil(phoneNumber, region); 3142 if (!isValidNumber) this.setCustomValidity('Please enter a valid phone number.'); 3143 else this.setCustomValidity(''); 3144 } catch (e) { 3145 this.setCustomValidity('Please enter a valid phone number.'); 3146 } 3147 else this.setCustomValidity(''); 3148 }); 3149 } 3150 } 3151} 3152 3153 3154 3155 3156/** 3157 * Fires the `generate_lead` Google Ads conversion on any marketing-site lead 3158 * event â lead-form submits (all routed through ContactFormController) and 3159 * social sign-in (oauth.js) â at most once per session. 3160 * 3161 * Reuses the same `generate_lead` event the Home Search Tool fires; the page 3162 * template already configures the LP Google tags, so no new Google Ads setup is 3163 * needed. Cross-tab/visitor dedupe is left to the conversion action's 3164 * Count="One" â localStorage is intentionally avoided as it would suppress a 3165 * returning visitor's later lead. 3166 */ const $3ec2bf3965f87ecc$export$78b64890ea40f4e4 = 'generate_lead'; 3167const $3ec2bf3965f87ecc$export$568da12be2433b06 = 'lp_lead_form_conversion_fired'; 3168function $3ec2bf3965f87ecc$var$alreadyFired(window) { 3169 try { 3170 return window.sessionStorage.getItem($3ec2bf3965f87ecc$export$568da12be2433b06) === 'true'; 3171 } catch { 3172 // sessionStorage can throw in private browsing; fail open so we still fire. 3173 return false; 3174 } 3175} 3176function $3ec2bf3965f87ecc$var$markFired(window) { 3177 try { 3178 window.sessionStorage.setItem($3ec2bf3965f87ecc$export$568da12be2433b06, 'true'); 3179 } catch { 3180 // Ignore â see alreadyFired(). 3181 } 3182} 3183function $3ec2bf3965f87ecc$export$e70565180c0e6950(window, document) { 3184 if (!window || !document) return; 3185 // GA bootstrap may not have run yet (or no pixel on this page); don't throw, 3186 // and don't burn the once-per-session flag if we can't actually fire. 3187 if (typeof document.sendGoogleTagManagerEvent !== 'function') return; 3188 if ($3ec2bf3965f87ecc$var$alreadyFired(window)) return; 3189 try { 3190 // Mark fired only AFTER a successful send, so a thrown GTM call doesn't 3191 // permanently suppress the conversion for the rest of the session â and 3192 // never let an analytics failure break the caller's success flow. 3193 document.sendGoogleTagManagerEvent('event', $3ec2bf3965f87ecc$export$78b64890ea40f4e4); 3194 $3ec2bf3965f87ecc$var$markFired(window); 3195 } catch { 3196 // Swallow â analytics must not break form submit / sign-in. 3197 } 3198} 3199 3200 3201 3202const $2aefad83322125c9$var$homeValuationRoute = '/api/v1/home-valuation'; 3203class $2aefad83322125c9$export$2e2bcd8739ae039 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 3204 destroy() { 3205 // 3206 } 3207 /** 3208 * Clean address string by removing call time frame suffix and extra whitespace 3209 * 3210 * Handles 17 templates that append ", call time frame: [value]" to the address: 3211 * - TheTrailblazerSir_2, TheMasterpiece (2x), TheInspirationSir_1, TheIconSir_3
3212 * - Influencer, GlobalCollective, Evolution, CorcoranAgent_1 3213 * - CompassSE (2x), CompassPrestige (2x), CompassNavigator 3214 * - ColdwellBanker (2x), Classic 3215 * 3216 * @param {string} address - Raw address string that may contain time frame info 3217 * @return {string} - Cleaned address string 3218 */ _cleanAddressString(address) { 3219 if (!address || typeof address !== 'string') return ''; 3220 let cleaned = address.trim(); 3221 // Fix 1: Handle "User entered address:\n..." pattern from TheProducer templates 3222 // Extract only the user entered address, ignore geocoded part 3223 if (cleaned.includes('User entered address:')) { 3224 const match = cleaned.match(/User entered address:\s*\n?\s*([^\n]+)/); 3225 if (match && match[1]) cleaned = match[1].trim(); 3226 } 3227 // Fix 2: Remove ", call time frame: ..." suffix if present (affects 17 templates) 3228 const timeFrameIndex = cleaned.indexOf(', call time frame:'); 3229 if (timeFrameIndex !== -1) cleaned = cleaned.substring(0, timeFrameIndex).trim(); 3230 return cleaned; 3231 } 3232 /** 3233 * Transform Google Maps address components to structured format 3234 * @param {Array} addressComponents - Google Maps address components 3235 * @return {Object} - Structured address object 3236 */ _transformAddressComponents(addressComponents) { 3237 const get = (t)=>(addressComponents.find((c)=>c.componentType === t) || {}).componentName?.text; 3238 const structuredAddress = {}; 3239 const streetNumber = get('street_number'); 3240 const streetName = get('route'); 3241 const state = get('administrative_area_level_1'); 3242 const zipcode = get('postal_code'); 3243 const addressLine2 = get('subpremise'); 3244 // explicit precedence for city 3245 const city = get('locality') || get('sublocality_level_1') || get('neighborhood'); 3246 if (streetNumber) structuredAddress.streetNumber = streetNumber; 3247 if (streetName) structuredAddress.streetName = streetName; 3248 if (city) structuredAddress.city = city; 3249 if (state) structuredAddress.state = state; 3250 if (zipcode) structuredAddress.zipcode = zipcode; 3251 if (addressLine2) structuredAddress.addressLine2 = addressLine2; 3252 return structuredAddress; 3253 } 3254 /** 3255 * Extract address and coordinates from Google Maps validation data 3256 * @param {Object} validatedAddressData - Google Maps validation result 3257 * @return {Object} - Object containing structuredAddress and coordinates 3258 */ _extractAddressAndCoordinates(validatedAddressData) { 3259 let structuredAddress = null; 3260 let coordinates = null; 3261 if (validatedAddressData?.result) { 3262 const { result: result } = validatedAddressData; 3263 // Extract structured address components 3264 if (result.address?.addressComponents) structuredAddress = this._transformAddressComponents(result.address.addressComponents); 3265 // Extract coordinates 3266 if (result.geocode?.location) coordinates = { 3267 latitude: result.geocode.location.latitude, 3268 longitude: result.geocode.location.longitude 3269 }; 3270 } 3271 return { 3272 structuredAddress: structuredAddress, 3273 coordinates: coordinates 3274 }; 3275 } 3276 /** 3277 * Filter out empty string values from an object, keeping only non-empty values 3278 * @param {Object} obj - Object to filter 3279 * @return {Object} - Filtered object without empty strings 3280 */ _filterEmptyStrings(obj) { 3281 const filtered = {}; 3282 for (const [key, value] of Object.entries(obj))if (value !== '' && value !== null && value !== undefined) filtered[key] = value; 3283 return filtered; 3284 } 3285 /** 3286 * Extract raw address from form data with priority fallback chain. 3287 * Order is critical to handle 38 home valuation templates with different field naming: 3288 * 3289 * IMPORTANT: contactForms.js (line 189) stores name="message" in comments_attributes[0].text 3290 * because 'message' is in DEFAULT_LEAD_FIELDS. Fields NOT in DEFAULT_LEAD_FIELDS go to content. 3291 * 3292 * Priority (most specific â most generic): 3293 * 1. address - Pattern 1: 9 templates (explicit address, stored in content) 3294 * 2. property-address - Pattern 3: 4 templates (dash variant with JS, stored in content) 3295 * 3. property_address - Pattern 4: 3 templates (underscore, stored in content - handles VanguardProperties!) 3296 * 4. Property_Address - Pattern 5: 1 template (capital P - TheProducer/HomeValuationRansomHouse, stored in content)
3297 * 5. comments_attributes[0].text - Pattern 2: ALL 17 templates with name="message" store HERE! 3298 * (e.g., Evolution/HomeValuation, TheProducer/HomeValuation, etc.) 3299 * 6. message - Pattern 6: 4 templates with JS-populated message (rare, checked last) 3300 * 7-8. homeValuationAddress, propertyAddress - Legacy/unknown formats 3301 * 3302 * Critical: Each field is cleaned BEFORE checking, so dropdown values are skipped and we try next field 3303 * 3304 * @param {Object} data - Form data 3305 * @return {string} - Raw address string (may contain trailing text like ", call time frame: ...") 3306 */ _getRawAddress(data) { 3307 const content = data.activity.content || {}; 3308 const comments = data.activity.comments_attributes?.[0]?.text; 3309 const possibleFields = [ 3310 content.address, 3311 content['property-address'], 3312 content.property_address, 3313 content.Property_Address, 3314 comments, 3315 content.message, 3316 content.homeValuationAddress, 3317 content.propertyAddress 3318 ]; 3319 return possibleFields.map((field)=>field && this._cleanAddressString(field)).find(Boolean) || ''; 3320 } 3321 /** 3322 * Logs lead-related events for monitoring 3323 * @param {string} eventType - Type of event 3324 * @param {Object} data - Form data 3325 * @param {Error} error - Optional error object 3326 */ _logLeadEvent(eventType, data, error = null) { 3327 const eventPayload = { 3328 event: eventType, 3329 companyId: this.config.companyId, 3330 websiteId: this.config.websiteId, 3331 timestamp: new Date().toISOString(), 3332 hasEmail: !!data.activity?.author_email, 3333 hasAddress: !!this._getRawAddress(data) 3334 }; 3335 if (error) { 3336 // Use userMessage (sanitized) instead of raw message to prevent PII exposure 3337 // userMessage is set by _getUserFriendlyErrorMessage() which provides safe messages 3338 eventPayload.errorMessage = error.userMessage || (error.status ? `Request failed with status ${error.status}` : 'An error occurred'); 3339 eventPayload.errorName = error.name; 3340 eventPayload.errorStatus = error.status; 3341 } 3342 // Log to console with structured format for log aggregation 3343 // eslint-disable-next-line no-console 3344 console.error(`[HomeValuation] ${eventType}`, JSON.stringify(eventPayload)); 3345 // If PostHog is available, send event for analytics 3346 // Wrap in try/catch to prevent SDK failures from blocking submissions 3347 if (this.window.posthog) try { 3348 this.window.posthog.capture(eventType, eventPayload); 3349 } catch (posthogError) { 3350 // eslint-disable-next-line no-console 3351 console.error('[HomeValuation] PostHog capture failed:', posthogError); 3352 } 3353 } 3354 /** 3355 * Maps error types to user-friendly messages 3356 * @param {Error} error - The error object 3357 * @returns {string} - User-friendly message 3358 */ _getUserFriendlyErrorMessage(error) { 3359 if (error.name === 'AbortError' || error.message?.includes('timeout')) return 'Connection timed out. Please try again.'; 3360 if (error.name === 'TypeError' && error.message?.includes('fetch')) return 'Unable to connect. Please check your internet connection and try again.'; 3361 if (error.status === 503 || error.status === 502 || error.status === 504) return 'Service temporarily unavailable. Please try again in a moment.'; 3362 if (error.status === 400) return 'Please check your information and try again.'; 3363 return 'Something went wrong. Please try again or contact us directly.'; 3364 } 3365 /** 3366 * Build formatted address string from structured address components 3367 * @param {Object} structuredAddress - Structured address object 3368 * @return {string} - Formatted address string 3369 */ _formatStructuredAddress(structuredAddress) { 3370 if (!structuredAddress || Object.keys(structuredAddress).length === 0) return ''; 3371 const parts = []; 3372 // Line 1: streetNumber streetName addressLine2 3373 const line1 = [ 3374 structuredAddress.streetNumber, 3375 structuredAddress.streetName, 3376 structuredAddress.addressLine2
3377 ].filter(Boolean).join(' '); 3378 parts.push(line1); 3379 // Line 2: city, state zipcode 3380 const cityState = [ 3381 structuredAddress.city, 3382 structuredAddress.state 3383 ].filter(Boolean).join(', '); 3384 const line2 = [ 3385 cityState, 3386 structuredAddress.zipcode 3387 ].filter(Boolean).join(' '); 3388 parts.push(line2); 3389 return parts.filter(Boolean).join(', '); 3390 } 3391 /** 3392 * Build home valuation data object 3393 * @param {Object} data - Form data 3394 * @param {string} homeValuationEstimate - Home valuation estimate 3395 * @param {Object} structuredAddress - Structured address object 3396 * @param {Object} coordinates - Coordinates object 3397 * @return {Object} - Home valuation data object 3398 */ _buildHomeValuationData(data, homeValuationEstimate, structuredAddress, coordinates) { 3399 // Build homeValuationAddress from structured address (validated by Google) if available 3400 // Format: streetNumber streetName, city, state zipcode, addressLine2 3401 const homeValuationAddress = this._formatStructuredAddress(structuredAddress) || this._getRawAddress(data); 3402 // Validate that we have an address before proceeding 3403 if (!homeValuationAddress) throw new Error('Unable to extract home valuation address from form data'); 3404 // Required fields that must always be included 3405 const requiredFields = { 3406 companyId: data.companyId, 3407 homeValuationAddress: homeValuationAddress, 3408 homeValuationEstimate: homeValuationEstimate ? new Intl.NumberFormat('en-US', { 3409 style: 'currency', 3410 currency: 'USD', 3411 maximumFractionDigits: 0, 3412 minimumFractionDigits: 0 3413 }).format(homeValuationEstimate) : null, 3414 websiteId: data.page.websiteId, 3415 sourceUrl: data.activity.source_url 3416 }; 3417 // Optional fields that will be filtered if empty 3418 const optionalFields = { 3419 leadEmailAddress: data.activity.author_email, 3420 leadFullName: data.activity.author_name, 3421 leadPhoneNumber: data.activity.author_phone, 3422 parcelId: data.activity.content?.parcelId, 3423 resourceId: data.page.pageMeta?.resourceId, 3424 pageId: data.page.pageId,
3425 sourceResource: data.page.pageMeta?.sourceResource, 3426 pageElementId: data.page.pageMeta?.pageElementId, 3427 formId: data.page.pageMeta?.formId, 3428 utmSource: data.activity.content?.utm_source, 3429 utmMedium: data.activity.content?.utm_medium, 3430 utmCampaign: data.activity.content?.utm_campaign, 3431 utmTerm: data.activity.content?.utm_term, 3432 utmContent: data.activity.content?.utm_content, 3433 utmId: data.activity.content?.utm_id, 3434 timeToSell: data.activity.content?.timeToSell, 3435 lookingToBuy: data.activity.content?.lookingToBuy, 3436 referrer: data.referrer, 3437 content: data.activity.content || {}, 3438 fields: data.fields || {} 3439 }; 3440 // Filter out empty strings from optional fields 3441 const filteredOptionalFields = this._filterEmptyStrings(optionalFields); 3442 return { 3443 ...requiredFields, 3444 ...filteredOptionalFields, 3445 // Add structured address and coordinates if they exist 3446 ...structuredAddress && { 3447 address: structuredAddress 3448 }, 3449 ...coordinates && { 3450 coordinates: coordinates 3451 } 3452 }; 3453 } 3454 /** 3455 * Validate address with Google Maps 3456 * @param {string} rawAddress - Raw address string 3457 * @returns {Promise<Object|null>} - Validated address data or null 3458 * @throws {Error} - If validation fails, error is thrown with userMessage 3459 */ async _validateAddressSafely(rawAddress) { 3460 if (!rawAddress) { 3461 // eslint-disable-next-line no-console 3462 console.error('Home valuation: No address found in form data'); 3463 return null; 3464 } 3465 try { 3466 return await this._validateAddressWithGoogleMaps(rawAddress); 3467 } catch (error) { 3468 // eslint-disable-next-line no-console 3469 console.error('Error validating address with Google Maps:', error); 3470 // Attach user-friendly message and throw so user sees the error 3471 error.userMessage = this._getUserFriendlyErrorMessage(error); 3472 throw error; 3473 } 3474 } 3475 /** 3476 * Get home valuation estimate 3477 * @param {Object} validatedAddressData - Validated address data 3478 * @returns {Promise<number|null>} - Home valuation estimate or null 3479 * @throws {Error} - If estimate fetch fails, error is thrown with userMessage 3480 */ async _getHomeValuationEstimateSafely(validatedAddressData) { 3481 try { 3482 return await this._getHomeValuationEstimate(validatedAddressData); 3483 } catch (error) { 3484 // eslint-disable-next-line no-console 3485 console.error('Error getting home valuation estimate:', error); 3486 // Attach user-friendly message and throw so user sees the error 3487 error.userMessage = this._getUserFriendlyErrorMessage(error); 3488 throw error; 3489 } 3490 } 3491 /** 3492 * Parse response text into payload object 3493 * @param {string} text - Response text 3494 * @returns {Object} - Parsed payload 3495 */ _parseResponsePayload(text) { 3496 try { 3497 return text ? JSON.parse(text) : {}; 3498 } catch { 3499 return { 3500 raw: text 3501 }; 3502 } 3503 } 3504 /** 3505 * Create error from failed response 3506 * @param {Object} payload - Response payload 3507 * @param {number} status - HTTP status code 3508 * @returns {Error} - Error object with status and payload 3509 */ _createResponseError(payload, status) { 3510 const errorMessage = payload && typeof payload === 'object' && Object.keys(payload).length ? payload.message || JSON.stringify(payload) : 'Request failed'; 3511 const error = new Error(errorMessage); 3512 // Preserve payload properties if it's an object 3513 if (payload && typeof payload === 'object' && Object.keys(payload).length) Object.assign(error, payload); 3514 // Preserve numeric HTTP status - must be set after Object.assign 3515 // to ensure it takes precedence over any status in payload 3516 error.status = status; 3517 return error; 3518 } 3519 /** 3520 * Submit lead to API endpoint 3521 * @param {string} leadRoute - API endpoint URL 3522 * @param {Object} homeValuationData - Data to submit 3523 * @returns {Promise<Object>} - Submission result 3524 */ async _submitLead(leadRoute, homeValuationData) { 3525 const controller = new AbortController(); 3526 const timeoutId = setTimeout(()=>controller.abort(), 10000); 3527 try { 3528 const response = await fetch(leadRoute, { 3529 method: 'POST', 3530 headers: { 3531 'Content-Type': 'application/json' 3532 }, 3533 body: JSON.stringify(homeValuationData), 3534 signal: controller.signal 3535 }); 3536 clearTimeout(timeoutId); 3537 const text = await response.text(); 3538 const payload = this._parseResponsePayload(text); 3539 if (!response.ok) throw this._createResponseError(payload, response.status); 3540 if (payload && payload.status === 'success' && payload.redirectUrl) { 3541 this.window.location.href = payload.redirectUrl; 3542 return { 3543 redirected: true, 3544 url: payload.redirectUrl 3545 }; 3546 } 3547 return payload; 3548 } catch (error) { 3549 clearTimeout(timeoutId); 3550 throw error; 3551 } 3552 } 3553 /** 3554 * Submit home valuation request with Google Maps validation 3555 * @param {Object} data 3556 * @return {Promise<Object>} 3557 */ async _submitHomeValuationRequest(data) { 3558 const { config: config } = this; 3559 // Use websiteApiGatewayUrl from config 3560 if (!config.websiteApiGatewayUrl) throw new Error('websiteApiGatewayUrl not configured for home valuation'); 3561 // Log submission started 3562 this._logLeadEvent('home_valuation_submission_started', data); 3563 try { 3564 // Validate address with Google Maps 3565 const rawAddress = this._getRawAddress(data); 3566 let validatedAddressData = null; 3567 try {
3568 validatedAddressData = await this._validateAddressSafely(rawAddress); 3569 } catch (error) { 3570 // Error already has userMessage from _validateAddressSafely 3571 this._logLeadEvent('home_valuation_address_validation_failed', data, error); 3572 throw error; 3573 } 3574 // Get home valuation estimate. 3575 // The estimate is best-effort: many valid properties (e.g. commercial 3576 // parcels or addresses without AVM coverage) have no estimate and the 3577 // estimate endpoint returns a non-OK status for them. A missing estimate 3578 // must never block lead capture, so we log the failure and continue with 3579 // a null estimate rather than aborting the whole submission. 3580 let homeValuationEstimate = null; 3581 try { 3582 homeValuationEstimate = await this._getHomeValuationEstimateSafely(validatedAddressData); 3583 } catch (error) { 3584 // Error already has userMessage from _getHomeValuationEstimateSafely 3585 this._logLeadEvent('home_valuation_estimate_failed', data, error); 3586 homeValuationEstimate = null; 3587 } 3588 // Extract structured address and coordinates from Google Maps validation 3589 const { structuredAddress: structuredAddress, coordinates: coordinates } = this._extractAddressAndCoordinates(validatedAddressData); 3590 const leadRoute = `${config.websiteApiGatewayUrl}${$2aefad83322125c9$var$homeValuationRoute}`; 3591 const homeValuationData = this._buildHomeValuationData(data, homeValuationEstimate, structuredAddress, coordinates); 3592 const result = await this._submitLead(leadRoute, homeValuationData); 3593 // Log success 3594 this._logLeadEvent('home_valuation_submission_success', data); 3595 return result; 3596 } catch (error) { 3597 // Ensure error has user-friendly message (may already be set by helper methods) 3598 if (!error.userMessage) error.userMessage = this._getUserFriendlyErrorMessage(error); 3599 this._logLeadEvent('home_valuation_submission_failed', data, error); 3600 throw error; 3601 } 3602 } 3603 /** 3604 * Validate address using Google Maps Address Validation API 3605 * @param {string} addressStr - The address string to validate 3606 * @returns {Promise<Object|null>} - Validated address data or null if validation fails 3607 */ async _validateAddressWithGoogleMaps(addressStr) { 3608 const { config: config, window: window } = this; 3609 // Ensure Google Maps API key is loaded 3610 let apiKey = config.googleMapsAPIKey; 3611 if (!apiKey && window.luxuryPresence?.mapApiLoader) apiKey = await window.luxuryPresence.mapApiLoader.getMapApiKey(); 3612 if (!apiKey) throw new Error('Google Maps API key not configured'); 3613 const controller = new AbortController(); 3614 const timeoutId = setTimeout(()=>controller.abort(), 10000); 3615 try { 3616 const response = await fetch(`https://addressvalidation.googleapis.com/v1:validateAddress?key=${apiKey}`, { 3617 method: 'POST', 3618 headers: { 3619 'Content-Type': 'application/json' 3620 }, 3621 body: JSON.stringify({ 3622 address: { 3623 addressLines: [ 3624 addressStr 3625 ] 3626 }, 3627 enableUspsCass: false 3628 }), 3629 signal: controller.signal 3630 }); 3631 clearTimeout(timeoutId); 3632 if (!response.ok) { 3633 const error = new Error(`Address validation failed: ${response.status}`); 3634 error.status = response.status; 3635 throw error; 3636 } 3637 return response.json(); 3638 } catch (error) { 3639 clearTimeout(timeoutId); 3640 throw error; 3641 } 3642 } 3643 /** 3644 * Check if structured address has all mandatory fields 3645 * @param {Object} structuredAddress - Structured address object 3646 * @return {boolean} 3647 */ _hasMandatoryAddressFields(structuredAddress) { 3648 return !!(structuredAddress.streetNumber && structuredAddress.streetName && structuredAddress.city && structuredAddress.state && structuredAddress.zipcode); 3649 } 3650 /** 3651 * Log missing mandatory address fields 3652 * @param {Object} structuredAddress - Structured address object 3653 */ _logMissingAddressFields(structuredAddress) { 3654 const missingFields = [];
3655 if (!structuredAddress.streetNumber) missingFields.push('streetNumber'); 3656 if (!structuredAddress.streetName) missingFields.push('streetName'); 3657 if (!structuredAddress.city) missingFields.push('city'); 3658 if (!structuredAddress.state) missingFields.push('state'); 3659 if (!structuredAddress.zipcode) missingFields.push('zipcode'); 3660 // eslint-disable-next-line no-console 3661 console.log('Home valuation estimate skipped - missing required fields:', missingFields.join(', '), 'Available fields:', Object.keys(structuredAddress).join(', ')); 3662 } 3663 /** 3664 * Build query string from structured address 3665 * @param {Object} structuredAddress - Structured address object 3666 * @return {string} 3667 */ _buildAddressQueryString(structuredAddress) { 3668 return Object.entries(structuredAddress).map(([key, value])=>`${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&'); 3669 } 3670 /** 3671 * GET home valuation estimate 3672 * @param {string} address 3673 * @param {Object} validatedAddressData - Google Maps validated address data 3674 * @return {Promise<number|null>} 3675 */ async _getHomeValuationEstimate(validatedAddressData = null) { 3676 const { config: config } = this; 3677 if (!config.websiteApiGatewayUrl) throw new Error('websiteApiGatewayUrl not configured'); 3678 const base = `${config.websiteApiGatewayUrl}${$2aefad83322125c9$var$homeValuationRoute}`; 3679 // Check if we have validated address data 3680 if (!validatedAddressData?.result?.address?.addressComponents) return null; 3681 const structuredAddress = this._transformAddressComponents(validatedAddressData.result.address.addressComponents); 3682 // Check if all mandatory fields exist 3683 if (!this._hasMandatoryAddressFields(structuredAddress)) { 3684 this._logMissingAddressFields(structuredAddress); 3685 return null; 3686 } 3687 // Build URL with query string 3688 const qs = this._buildAddressQueryString(structuredAddress); 3689 const homeValURL = `${base}?${qs}`; 3690 const controller = new AbortController(); 3691 const timeoutId = setTimeout(()=>controller.abort(), 10000); 3692 try { 3693 const response = await fetch(homeValURL, { 3694 headers: { 3695 Accept: 'application/json' 3696 }, 3697 signal: controller.signal 3698 }); 3699 clearTimeout(timeoutId); 3700 if (!response.ok) { 3701 const error = new Error(`Home valuation estimate failed: ${response.status}`); 3702 error.status = response.status; 3703 throw error; 3704 } 3705 const data = await response.json(); 3706 return data?.valueDollars ?? null; 3707 } catch (error) { 3708 clearTimeout(timeoutId); 3709 // Re-throw to allow retry logic to handle it 3710 throw error; 3711 } 3712 } 3713} 3714 3715 3716const $e10bd00497be8876$export$97de66002300ed70 = 'middleName'; 3717// Default lead fields 3718const $e10bd00497be8876$var$DEFAULT_LEAD_FIELDS = [ 3719 'name', 3720 'firstName', 3721 'lastName', 3722 $e10bd00497be8876$export$97de66002300ed70, 3723 'email', 3724 'phone', 3725 'phoneNumber', 3726 'source', 3727 'message', 3728 'mlsId', 3729 'sourceUrl' 3730]; 3731class $e10bd00497be8876$export$3c5380623cf51249 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 3732 /** 3733 * Initialize Contact Form Controller at build time 3734 * @param { ConfigController } config 3735 * @param {Window} window 3736 * @param {Document} document 3737 */ constructor(config, window, document){ 3738 super(config, window, document); 3739 this.forms = []; 3740 this.homeValuationService = new (0, $2aefad83322125c9$export$2e2bcd8739ae039)(config, window, document); 3741 } 3742 /** 3743 * Initialize Contact Form Controller at run time 3744 * Attach handlers and events to DOM 3745 * @param {Object} options 3746 */ initialize(options = {}) { 3747 try { 3748 const { document: document } = this; 3749 const { selector: selector } = options; 3750 let forms = []; 3751 if (selector) forms = document.querySelectorAll(`${selector} form[data-type="contact-form"]`); 3752 else forms = document.querySelectorAll('form[data-type="contact-form"]');
3753 forms.forEach((form)=>{ 3754 this._attachSubmitHandler(form); 3755 // this._attachValidation(form); 3756 }); 3757 this.forms = this.forms.concat(forms); 3758 } catch (e) { 3759 // eslint-disable-next-line no-console 3760 console.log(e); 3761 } 3762 } 3763 /** 3764 * Controllers must implement a "destroy" method to be called 3765 * after page navigation to remove event handlers / cleanup 3766 */ destroy() { 3767 this.forms = []; 3768 } 3769 /** 3770 * Handle submit from global context 3771 * @param {HTMLFormElement} form 3772 */ submitForm(form) { 3773 this._handleSubmit(null, form).catch((e)=>console.error(e)); 3774 } 3775 /** 3776 * Attach submit event for form 3777 * @param {HTMLFormElement} contactForm 3778 */ _attachValidation(contactForm) { 3779 const { window: window } = this; 3780 const inputs = contactForm.querySelectorAll('input, select, textarea'); 3781 inputs.forEach((input)=>{ 3782 window.luxuryPresence.validation.applyFieldValidation(input); 3783 }); 3784 } 3785 /** 3786 * Attach submit event for form 3787 * @param {HTMLFormElement} contactForm 3788 */ _attachSubmitHandler(contactForm) { 3789 contactForm.addEventListener('submit', (e)=>this._handleSubmit(e, contactForm)); 3790 } 3791 /** 3792 * Extract and process form values 3793 * @param {HTMLFormElement} contactForm 3794 * @return {Object} - Processed form values 3795 */ _extractFormValues(contactForm) { 3796 const formData = new FormData(contactForm); 3797 const values = {}; 3798 const nameParts = []; 3799 const nameKeyRegex = /^name(?:-\d+)?$/; 3800 formData.forEach((value, key)=>{ 3801 if (typeof value === 'string') { 3802 const curr = value.trim(); 3803 if (nameKeyRegex.test(key) && curr) nameParts.push(curr); 3804 const prev = values[key]; 3805 values[key] = prev ? `${prev} ${curr}` : curr; 3806 } else values[key] = value; 3807 }); 3808 if (nameParts.length) values.name = nameParts.join(' ').trim(); 3809 else if (!values.name && values.firstName) { 3810 values.name = values.firstName; 3811 if (values.lastName) values.name += ` ${values.lastName}`; 3812 } 3813 return values; 3814 } 3815 /** 3816 * Build submission data object 3817 * @param {Object} values - Form values 3818 * @param {Object} fields - Form fields 3819 * @param {HTMLFormElement} contactForm - Form element 3820 * @return {Object} - Submission data 3821 */ _buildSubmissionData(values, fields, contactForm) { 3822 const { window: window, config: config } = this; 3823 const content = Object.keys(values).filter((key)=>!$e10bd00497be8876$var$DEFAULT_LEAD_FIELDS.includes(key)).reduce((result, key)=>{ 3824 // eslint-disable-next-line 3825 result[key] = values[key]; 3826 return result; 3827 }, {}); 3828 const { pageId: pageId, companyId: companyId, websiteId: websiteId, pageMeta: pageMeta } = config; 3829 const utm = window.localStorage.getItem('utm_vars') || '{}'; 3830 const referrer = window.localStorage.getItem('referrer') || ''; 3831 const formId = this._getFormId(contactForm); 3832 return { 3833 activity: { 3834 author_email: values.email, 3835 author_name: values.name, 3836 author_phone: values.phone || values.phoneNumber, 3837 activity_source: values.source || 'CONTACT_INQUIRY', 3838 comments_attributes: [ 3839 { 3840 text: values.message || undefined 3841 } 3842 ], 3843 source_url: window.location.href, 3844 content: { 3845 ...content, 3846 ...JSON.parse(utm) 3847 } 3848 }, 3849 page: { 3850 pageId: pageId, 3851 companyId: companyId, 3852 websiteId: websiteId, 3853 pageMeta: { 3854 ...pageMeta, 3855 formId: formId 3856 } 3857 }, 3858 companyId: companyId, 3859 referrer: referrer, 3860 fields: fields 3861 }; 3862 } 3863 /** 3864 * Handle successful form submission 3865 * @param {HTMLFormElement} contactForm - Form element 3866 */ _handleSubmissionSuccess(contactForm) { 3867 const { window: window } = this; 3868 const closeDelay = contactForm.dataset.delay || (0, $a3e97e89bbc2a366$export$f037eeffb5561205); 3869 contactForm.classList.remove('loading'); 3870 contactForm.classList.remove('error'); 3871 contactForm.classList.add('success'); 3872 // TODO: Review producer and classic to deprecate "hide" 3873 contactForm.querySelectorAll('.success').forEach((el)=>el.classList.remove('hide')); 3874 contactForm.dispatchEvent(new Event('formsubmitted')); 3875 // Any successful lead submit credits the Google Ads lead conversion 3876 // (reuses the existing `generate_lead` action), once per session. 3877 (0, $3ec2bf3965f87ecc$export$e70565180c0e6950)(this.window, this.document); 3878 setTimeout(()=>{ 3879 window.lpUI.hideClosestModal(contactForm); 3880 contactForm.reset(); 3881 window.lpUI.sideMenuHide(); 3882 contactForm.querySelectorAll('.success').forEach((el)=>el.classList.add('hide')); 3883 contactForm.classList.remove('success'); 3884 }, closeDelay); 3885 } 3886 /** 3887 * Submit Contact Form Handler 3888 * @param {Event} e 3889 * @param {HTMLFormElement} contactForm 3890 * @return {Promise<Object>} 3891 */ async _handleSubmit(e, contactForm) { 3892 if (e) e.preventDefault(); 3893 if (contactForm.classList.contains('loading')) // Form already submitting 3894 return; 3895 // Hide any previous error message and clear error state 3896 this._hideSubmissionError(contactForm); 3897 contactForm.classList.remove('error'); 3898 contactForm.classList.add('loading'); 3899 try { 3900 // Extract form values first (before async operations) 3901 const values = this._extractFormValues(contactForm); 3902 // TODO: HoneyPot should still send request 3903 if (values.middleName) { 3904 contactForm.classList.remove('loading'); 3905 return; 3906 } 3907 const fields = this._getFields(contactForm); 3908 const data = this._buildSubmissionData(values, fields, contactForm); 3909 await this._submitLeadRequest(data); 3910 this._sendGAEvent(data); 3911 this._handleSubmissionSuccess(contactForm); 3912 } catch (err) { 3913 // eslint-disable-next-line no-console 3914 console.error('Form submission failed:', err); 3915 contactForm.dispatchEvent(new Event('formsubmiterror')); 3916 contactForm.classList.remove('loading'); 3917 contactForm.classList.add('error'); 3918 // Show user-friendly error message if available (from home valuation service)
3919 if (err.userMessage) this._showSubmissionError(contactForm, err.userMessage); 3920 else this._showSubmissionError(contactForm, 'Something went wrong. Please try again.'); 3921 } 3922 } 3923 /** 3924 * Alias anonymous user with canonical ID using event bus 3925 * Links the user's anonymous browsing session to their canonical identity 3926 * @param {Object} data - Form submission data 3927 * @private 3928 */ _aliasUserWithEventBus(data) { 3929 try { 3930 // Check if event bus library and required methods are available 3931 if (!this.window.eventBusProvider?.alias || !this.window.eventBusProvider?.isIdentified || !this.window.eventBusProvider?.getDistinctId || !this.window.LuxuryPresenceEventBus?.generateUUIDv5FromCompanyEmail) return; // Event bus or required methods not available 3932 // Skip if user is already identified 3933 if (this.window.eventBusProvider.isIdentified()) { 3934 console.log('[EventBus] User already identified, skipping alias'); 3935 return; 3936 } 3937 const email = data.activity?.author_email; 3938 const { companyId: companyId } = this.config; 3939 if (!email || !companyId) { 3940 console.warn('[EventBus] Missing required data for user alias'); 3941 return; 3942 } 3943 // Get the anonymous distinct ID from PostHog 3944 const anonId = this.window.eventBusProvider.getDistinctId(); 3945 if (!anonId) { 3946 console.warn('[EventBus] No anonymous ID available for alias'); 3947 return; 3948 } 3949 // Generate deterministic canonical ID from company + email 3950 const canonicalId = this.window.LuxuryPresenceEventBus.generateUUIDv5FromCompanyEmail(companyId, email); 3951 // Create alias linking anonymous ID to canonical ID 3952 this.window.eventBusProvider.alias({ 3953 anonId: anonId, 3954 canonicalId: canonicalId 3955 }); 3956 console.log('[EventBus] User aliased with canonical ID'); 3957 } catch (error) { 3958 // Never block form submission due to analytics errors 3959 console.error('[EventBus] User alias failed:', error); 3960 } 3961 } 3962 /** 3963 * Send Lead Request 3964 * @param {Object} data 3965 * @return {Promise<Object>} 3966 */ async _submitLeadRequest(data) { 3967 const { config: config } = this; 3968 // Alias user with event bus before submitting the lead 3969 this._aliasUserWithEventBus(data); 3970 if (config.myhomeDashboardEnabled && (data.activity?.activity_source === 'HOME_VALUATION' || data.activity?.activity_source === 'HOME_VALUE')) return this.homeValuationService._submitHomeValuationRequest(data); 3971 const leadRoute = `${config.leadIngesterUrl}/api/v1/leads`; 3972 return fetch(leadRoute, { 3973 method: 'POST', 3974 headers: { 3975 'Content-Type': 'application/json' 3976 }, 3977 body: JSON.stringify(data) 3978 }).then((response)=>{ 3979 if (!response.ok) return response.json().then((error)=>{ 3980 throw error; 3981 }); 3982 return response.json(); 3983 }); 3984 } 3985 /** 3986 * Send GA event 3987 * @param {Object} data 3988 */ _sendGAEvent(data) { 3989 const { document: document } = this; 3990 let event = { 3991 hitType: 'event', 3992 eventCategory: 'button', 3993 eventAction: 'click' 3994 }; 3995 switch(data.activity.activity_source){ 3996 case 'NEWSLETTER_SIGNUP': 3997 event.eventLabel = 'Newsletter'; 3998 document.sendGoogleTagManagerEvent('event', 'newsletter_signup'); 3999 break; 4000 case 'HOME_SEARCH': 4001 event.eventLabel = 'HomeSearch'; 4002 document.sendGoogleTagManagerEvent('event', 'home_search'); 4003 break; 4004 case 'HOME_VALUE': 4005 case 'HOME_VALUATION': 4006 event.eventLabel = 'HomeValue'; 4007 document.sendGoogleTagManagerEvent('event', 'home_valuation'); 4008 break; 4009 case 'EBOOK': 4010 event.eventLabel = 'Ebook'; 4011 document.sendGoogleTagManagerEvent('event', 'ebook_download'); 4012 break; 4013 case 'CONTACT_INQUIRY': 4014 default: 4015 // for contact forms, Marketing has requested that they see a page-view of 'thank-you' as indication of success 4016 // Note that this will break if we have actual page with url of /thank-you 4017 event = { 4018 hitType: 'pageview', 4019 page: '/thank-you' 4020 }; 4021 document.sendGoogleTagManagerEvent('event', 'contact_inquiry'); 4022 break; 4023 } 4024 document.sendGoogleAnalyticsEvent(event); 4025 } 4026 /** 4027 * Gather fields / schema for form, used for display in CRM 4028 * @param {HTMLFormElement} form 4029 * @return {Object} 4030 */ _getFields(form) { 4031 const inputs = form.querySelectorAll('input, select, textarea'); 4032 const fields = {};
4033 inputs.forEach((input)=>{ 4034 const { id: id, name: name, type: inputType } = input; 4035 if (name === $e10bd00497be8876$export$97de66002300ed70) // Honeypot continue 4036 return true; 4037 let label = ''; 4038 if (id) { 4039 const labelNode = form.querySelector(`label[for='${id}']`); 4040 if (labelNode) label = labelNode.textContent.trim(); 4041 else if (input.placeholder) label = input.placeholder.trim(); 4042 } 4043 fields[name] = { 4044 label: label, 4045 type: input.tagName, 4046 inputType: input.tagName === 'INPUT' ? inputType : 'text' 4047 }; 4048 }); 4049 return fields; 4050 } 4051 /** 4052 * Figure out relevant section or div ID for form 4053 * @param {HTMLFormElement} form 4054 * @return {string} 4055 */ _getFormId(form) { 4056 const SELECTORS = "section[id^='section-'], footer[id], .modal[id^='modal-']"; 4057 const parent = form.closest(SELECTORS); 4058 return parent?.id ? this._cleanFormId(parent.id) : ''; 4059 } 4060 /** 4061 * Remove prefix from formID 4062 * @param {string} formId 4063 * @return {string} 4064 */ _cleanFormId(formId) { 4065 const PREFIXES = [ 4066 'section-', 4067 'modal-' 4068 ]; 4069 for (const prefix of PREFIXES){ 4070 if (formId.startsWith(prefix)) return formId.substring(prefix.length); 4071 } 4072 return formId; 4073 } 4074 /** 4075 * Show submission error message on form 4076 * @param {HTMLFormElement} form 4077 * @param {string} message 4078 */ _showSubmissionError(form, message) { 4079 const errorEl = form.querySelector('.submission-error-message'); 4080 if (errorEl) { 4081 errorEl.textContent = message || 'Something went wrong. Please try again.'; 4082 errorEl.style.display = 'block'; 4083 } 4084 } 4085 /** 4086 * Hide submission error message on form 4087 * @param {HTMLFormElement} form 4088 */ _hideSubmissionError(form) { 4089 const errorEl = form.querySelector('.submission-error-message'); 4090 if (errorEl) errorEl.style.display = 'none'; 4091 } 4092} 4093 4094 4095 4096const $ea6901fdf47e0ec1$var$CALLBACK = '__googleMapsCallback'; 4097const $ea6901fdf47e0ec1$var$GOOGLE_MAPS_URL = 'https://maps.googleapis.com/maps/api/js'; 4098const $ea6901fdf47e0ec1$export$76299006759be400 = 1024; 4099const $ea6901fdf47e0ec1$var$KEY_FETCH_ATTEMPTS = 3; 4100const $ea6901fdf47e0ec1$var$KEY_RETRY_BASE_DELAY_MS = 300; 4101const $ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG = 'lp_map_key_failed'; 4102class $ea6901fdf47e0ec1$export$3c19195edca3fa5c extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4103 /** 4104 * Initialize Button Controller at build time 4105 * @param { ConfigController } config 4106 * @param {Window} window 4107 * @param {Document} document 4108 */ constructor(config, window, document){ 4109 super(config, window, document); 4110 this.retries = 3; 4111 this.callbacks = []; 4112 this.id = `__googleMapsScriptId${Date.now()}`; 4113 this._apiKey = ''; 4114 this.reset(); 4115 } 4116 /** 4117 * Set the Google Maps api key 4118 * @param {string} apiKey 4119 */ set apiKey(apiKey) { 4120 this._apiKey = apiKey; 4121 this.config.setGoogleMapsAPIKey(apiKey); 4122 } 4123 /** 4124 * Get the Google Maps api key 4125 * @return {string} 4126 */ get apiKey() { 4127 return this._apiKey; 4128 } 4129 /** 4130 * Reset the loader state. 4131 */ reset() { 4132 this.deleteScript(); 4133 this.done = false; 4134 this.loading = false; 4135 this.failed = false; 4136 this.errors = []; 4137 this.onerrorEvent = null; 4138 } 4139 /** 4140 * Destroy 4141 */ destroy() { 4142 this.reset(); 4143 } 4144 /** 4145 * get map api Key 4146 * @return {Promise<string>} 4147 */ async getMapApiKey() { 4148 if (this.apiKey) return this.apiKey; 4149 const hadPreviousFailure = this._hadPreviousFailure(); 4150 let recoveryReported = false; 4151 for(let attempt = 1; attempt <= $ea6901fdf47e0ec1$var$KEY_FETCH_ATTEMPTS; attempt += 1){ 4152 const reportRecovery = hadPreviousFailure && !recoveryReported; 4153 try { 4154 // eslint-disable-next-line no-await-in-loop 4155 const response = await fetch(this._getMapKeyUrl(attempt, reportRecovery));
4156 // The request reached the server, so a recovery marker on it was 4157 // delivered and must not be repeated on later attempts or pages. A 4158 // rejected fetch never arrived, so the marker moves to the next 4159 // attempt. 4160 recoveryReported = recoveryReported || reportRecovery; 4161 this._setFailureFlag(false); 4162 if (!response.ok) throw new Error(`Failed to fetch API key: ${response.status}`); 4163 // eslint-disable-next-line no-await-in-loop 4164 const code = await response.text(); 4165 if (!code) return ''; 4166 this.apiKey = code; 4167 return this.apiKey; 4168 } catch (e) { 4169 // eslint-disable-next-line no-console 4170 console.error(e); 4171 if (attempt < $ea6901fdf47e0ec1$var$KEY_FETCH_ATTEMPTS) // eslint-disable-next-line no-await-in-loop 4172 await this._delay($ea6901fdf47e0ec1$var$KEY_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1)); 4173 } 4174 } 4175 this._setFailureFlag(true); 4176 return ''; 4177 } 4178 /** 4179 * Build the map key request url. Retries carry their attempt number, and a 4180 * single request per session carries the recovery marker, so both are 4181 * countable in existing request logs without new infrastructure. 4182 * @param {number} attempt 1-based attempt number 4183 * @param {boolean} reportRecovery tag this request as following a session 4184 * in which every attempt failed 4185 * @return {string} 4186 */ _getMapKeyUrl(attempt, reportRecovery) { 4187 const params = []; 4188 if (attempt > 1) params.push(`attempt=${attempt}`); 4189 if (reportRecovery) params.push('recovered_after_failure=1'); 4190 const query = params.length ? `?${params.join('&')}` : ''; 4191 return `${this._getBaseUrl()}/api/v1/map${query}`; 4192 } 4193 /** 4194 * Whether an earlier attempt in this session exhausted all retries. 4195 * @return {boolean} 4196 */ _hadPreviousFailure() { 4197 try { 4198 return this.window.sessionStorage.getItem($ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG) === '1'; 4199 } catch (e) { 4200 // Storage can throw when disabled (private browsing); absence of the 4201 // flag is the safe default. 4202 return false; 4203 } 4204 } 4205 /** 4206 * Record or clear the session-level key fetch failure flag. 4207 * @param {boolean} failed 4208 */ _setFailureFlag(failed) { 4209 try { 4210 if (failed) this.window.sessionStorage.setItem($ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG, '1'); 4211 else this.window.sessionStorage.removeItem($ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG); 4212 } catch (e) { 4213 // Storage unavailable - reporting is best effort, never fatal. 4214 } 4215 } 4216 /** 4217 * @param {number} ms 4218 * @return {Promise} 4219 */ _delay(ms) { 4220 return new Promise((resolve)=>{ 4221 this.window.setTimeout(resolve, ms); 4222 }); 4223 } 4224 /** 4225 * CreateUrl returns the Google Maps JavaScript API script url ]. 4226 * @return {string} 4227 */ async createUrl() { 4228 const apiKey = await this.getMapApiKey(); 4229 // Google Maps loads successfully without a key and then fails at call time 4230 // with NoApiKeys, which produces a broken map and no load error. Refuse 4231 // instead, so the failure surfaces to the caller. 4232 if (!apiKey) throw new Error('Refusing to load Google Maps without an API key'); 4233 let url = $ea6901fdf47e0ec1$var$GOOGLE_MAPS_URL; 4234 url += `?callback=${$ea6901fdf47e0ec1$var$CALLBACK}`; 4235 url += `&key=${apiKey}`; 4236 url += `&libraries=places`; 4237 return url; 4238 } 4239 /** 4240 * Set the script on document. 4241 */ async setScript() { 4242 if (this.document.getElementById(this.id)) { 4243 this.callback(); 4244 return; 4245 } 4246 let url; 4247 try { 4248 url = await this.createUrl(); 4249 } catch (e) { 4250 // eslint-disable-next-line no-console 4251 console.error(e); 4252 this.failed = true; 4253 this.loading = false; 4254 this.onerrorEvent = e; 4255 this.callback(); 4256 return; 4257 } 4258 const script = this.document.createElement('script'); 4259 script.id = this.id; 4260 script.type = 'text/javascript'; 4261 script.src = url; 4262 script.onerror = this.loadErrorCallback.bind(this); 4263 script.defer = true; 4264 script.async = true; 4265 this.document.head.appendChild(script); 4266 } 4267 /** 4268 * Function to be called if script tag fails 4269 * @param {Error} e 4270 */ loadErrorCallback(e) { 4271 this.errors.push(e); 4272 if (this.errors.length <= this.retries) {
4273 const delay = this.errors.length * 2 ** this.errors.length; 4274 // eslint-disable-next-line no-console 4275 console.log(`Failed to load Google Maps script, retrying in ${delay} ms.`); 4276 setTimeout(()=>{ 4277 this.deleteScript(); 4278 this.setScript().catch((e)=>console.error(e)); 4279 }, delay); 4280 } else { 4281 this.failed = true; 4282 this.onerrorEvent = e; 4283 this.callback(); 4284 } 4285 } 4286 /** 4287 * Delete the script tag 4288 */ deleteScript() { 4289 const script = this.document.getElementById(this.id); 4290 if (script) script.remove(); 4291 } 4292 /** 4293 * Load the Google Maps JavaScript API script and return a Promise. 4294 * @return {Promise} 4295 */ load() { 4296 return this.loadPromise(); 4297 } 4298 /** 4299 * Load the Google Maps JavaScript API script and return a Promise. 4300 * @return {Promise} 4301 */ loadPromise() { 4302 return new Promise((resolve, reject)=>{ 4303 this.loadCallback((err)=>{ 4304 if (!err) resolve(); 4305 else reject(err); 4306 }); 4307 }); 4308 } 4309 /** 4310 * Load the Google Maps JavaScript API script with a callback. 4311 * @param {Function<Event>} fn 4312 */ loadCallback(fn) { 4313 this.callbacks.push(fn); 4314 this.execute(); 4315 } 4316 /** 4317 * Sets the callback function that will be sent as part of the script URL. 4318 */ setCallback() { 4319 this.window.__googleMapsCallback = this.callback.bind(this); 4320 } 4321 /** 4322 * Reset if failed 4323 */ resetIfRetryingFailed() { 4324 if (this.failed) this.reset(); 4325 } 4326 /** 4327 * This is the function that we set the callback parameter to. 4328 */ callback() { 4329 this.done = true; 4330 this.loading = false; 4331 this.callbacks.forEach((cb)=>{ 4332 cb(this.onerrorEvent); 4333 }); 4334 this.callbacks = []; 4335 } 4336 /** 4337 * Set the script a if needed; set the callback 4338 */ execute() { 4339 if (this.window.google && this.window.google.maps && this.window.google.maps.version) // Google Maps already loaded 4340 this.callback(); 4341 this.resetIfRetryingFailed(); 4342 if (this.done) this.callback(); 4343 else if (this.loading) ; 4344 else { 4345 this.loading = true; 4346 this.setCallback(); 4347 this.setScript().catch((e)=>console.error(e)); 4348 } 4349 } 4350 /** 4351 * get base url based on current config 4352 * @return {string} 4353 */ _getBaseUrl() { 4354 // Special handling for page builder since no wag available 4355 const { isBuilder: isBuilder, websiteApiGatewayUrl: websiteApiGatewayUrl } = this.config; 4356 return isBuilder ? websiteApiGatewayUrl : ''; 4357 } 4358 /** 4359 * Resize static map requests to the max 1024px while preserving aspect ratio 4360 * @param {number} width 4361 * @param {number} height 4362 * @return {{width, height}} 4363 */ resizeStaticMapDimensions(width, height) { 4364 if (width <= $ea6901fdf47e0ec1$export$76299006759be400 && height <= $ea6901fdf47e0ec1$export$76299006759be400) return { 4365 width: width, 4366 height: height 4367 }; 4368 // Size param ignored if dimensions exceed STATIC_MAP_MAX_DIM 4369 const ratio = Math.min($ea6901fdf47e0ec1$export$76299006759be400 / width, $ea6901fdf47e0ec1$export$76299006759be400 / height); 4370 return { 4371 width: parseInt(width * ratio), 4372 height: parseInt(height * ratio) 4373 }; 4374 } 4375 /** 4376 * Convert theme to static api param object 4377 * @param {Object} theme 4378 * @return {Object} 4379 */ convertThemeToStaticApiUrl(theme) { 4380 // https://developers.google.com/maps/documentation/maps-static/style-reference 4381 // Google Maps Static API uses a special syntax for it's urls based on the interactive map styles 4382 return theme.map((t)=>{ 4383 const styles = t.stylers.reduce((acc, s)=>{ 4384 const visibility = s.visibility ? `visibility:${s.visibility}` : ''; 4385 const color = s.color ? `color:${s.color.replace('#', '0x')}` : ''; 4386 const lightness = s.lightness ? `lightness:${s.lightness}` : ''; 4387 return `${acc}|${visibility}${color}${lightness}`; 4388 }, ''); 4389 const element = t.elementType ? `|element:${t.elementType}` : ''; 4390 return `feature:${t.featureType}${element}${styles}`; 4391 }); 4392 } 4393 /** 4394 * generate digital signature for url 4395 * @param {string} queryString to sign 4396 * @return {Promise<string>} digitally signed url 4397 */ async getMapApiSignedUrl(queryString) { 4398 try { 4399 const response = await fetch(`${this._getBaseUrl()}/api/v1/map/signature?${queryString}`); 4400 if (!response.ok) throw new Error('Failed to fetch signed URL'); 4401 const { url: url } = await response.json(); 4402 return url; 4403 } catch (e) { 4404 // eslint-disable-next-line no-console 4405 console.error(e); 4406 return ''; 4407 } 4408 } 4409 /** 4410 * Generate static map url 4411 * @param {number} lat 4412 * @param {number} lng 4413 * @param {number} zoom 4414 * @param {Object} theme 4415 * @param {number} srcWidth 4416 * @param {number} srcHeight 4417 * @return {string} 4418 */ generateStaticMapUrl(lat, lng, zoom, theme, srcWidth, srcHeight) {
4419 const baseUrl = 'https://maps.googleapis.com/maps/api/staticmap'; 4420 const { width: width, height: height } = this.resizeStaticMapDimensions(srcWidth, srcHeight); 4421 const query = { 4422 center: `${lat},${lng}`, 4423 zoom: zoom, 4424 scale: 2, 4425 size: `${width}x${height}`, 4426 key: this.apiKey 4427 }; 4428 if (theme) query.style = this.convertThemeToStaticApiUrl(theme); 4429 const params = new URLSearchParams(); 4430 Object.entries(query).forEach(([key, value])=>{ 4431 if (Array.isArray(value)) value.forEach((v)=>params.append(key, v.toString())); 4432 else params.append(key, value.toString()); 4433 }); 4434 return `${baseUrl}?${params.toString()}`; 4435 } 4436 /** 4437 * Generate static map url with digital signature 4438 * @param {Object} { 4439 * srcWidth {number}, required 4440 * srcHeight {number}, required 4441 * theme {array}, 4442 * center: `${lat},${lng}`, required 4443 * zoom: number, required 4444 * markers: `${lat},${lng}`, 4445 * scale: number, 4446 * otherQueryParams... (these must match what is compatible with google maps static api query params) 4447 * } 4448 * @return {Promise<string>} digitally signed url 4449 */ async generateDigitallySignedStaticMapUrl({ srcWidth: srcWidth, srcHeight: srcHeight, theme: theme, ...query }) { 4450 const { width: width, height: height } = this.resizeStaticMapDimensions(srcWidth, srcHeight); 4451 const queryParams = { 4452 ...query, 4453 size: `${width}x${height}` 4454 }; 4455 if (theme) queryParams.style = this.convertThemeToStaticApiUrl(theme); 4456 const params = new URLSearchParams(); 4457 Object.entries(queryParams).forEach(([key, value])=>{ 4458 if (Array.isArray(value)) value.forEach((v)=>params.append(key, v.toString())); 4459 else params.append(key, value.toString()); 4460 }); 4461 const queryString = params.toString(); 4462 try { 4463 // fetch digitally signed url 4464 return await this.getMapApiSignedUrl(queryString); 4465 } catch (e) { 4466 console.error(e); 4467 return ''; 4468 } 4469 } 4470} 4471 4472 4473/* eslint-disable import/prefer-default-export */ 4474 4475class $0a0ad49f13c0b8fd$export$ba9afeb75f20f616 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4476 // constructor is not needed as it is inherited from BaseController 4477 /** 4478 * Controllers must implement a "destroy" method to be called 4479 * after page navigation to remove event handlers / cleanup 4480 */ destroy() {} 4481 /** 4482 * Places (re-places) a value into the query string under specified key 4483 * or removes it if new value is null or undefined 4484 * @param {string} key 4485 * @param {string|null|undefined|object|array} value 4486 * @param {string} url - defaults to window.location.href 4487 * @return {string} 4488 */ // eslint-disable-next-line sonarjs/cognitive-complexity 4489 updateQueryString(key, value, url) { 4490 // https://stackoverflow.com/questions/5999118/how-can-i-add-or-update-a-query-string-parameter 4491 if (!url) // eslint-disable-next-line 4492 url = this.window.location.href; 4493 let re = new RegExp(`([?&])${key}=.*?(&|#|$)`, 'i'); 4494 let replacement = ''; 4495 if (typeof value !== 'undefined' && value !== null) { 4496 if (Array.isArray(value)) { 4497 if (value.length) replacement = this.serializeArray(key, value); 4498 } else if (typeof value === 'object') { 4499 re = new RegExp(`([?&])${key}\\[[^=]*\\]=.*?(&|#|$)`, 'i'); 4500 replacement = this.serializeObj(value, '', key); 4501 } else replacement = `${key}=${encodeURIComponent(value)}`; 4502 } 4503 const hash = url.split('#'); 4504 let result = hash[0]; 4505 if (re.test(url)) { 4506 let clean = result; 4507 replacement = replacement ? `$1${replacement}$2` : '$1'; 4508 while(re.test(clean)){ 4509 result = clean.replace(re, replacement); 4510 clean = clean.replace(re, '$1'); 4511 } 4512 result = result.replace(/(&|\?)$/, ''); 4513 } else if (replacement) { 4514 const separator = result.indexOf('?') !== -1 ? '&' : '?'; 4515 result += `${separator}${replacement}`; 4516 } 4517 if (typeof hash[1] !== 'undefined' && hash[1] !== null) result += `#${hash[1]}`; 4518 return result; 4519 } 4520 /** 4521 * Returns variable from query string specified by parameter name 4522 * @param {string} name 4523 * @param {string} url defaults to window.location.href 4524 * @return {string|null} 4525 */ getParameterByName(name, url) { 4526 if (!url) // eslint-disable-next-line 4527 url = this.window.location.href; 4528 // eslint-disable-next-line 4529 name = name.replace(/[[\]]/g, '\\$&'); 4530 const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); 4531 let results = Array.from(RegExp.prototype[Symbol.matchAll].call(regex, url)).flat(); 4532 // check for object like syntax where the last value can be an array eg. advancedFilters[query][statusIds][in][0] or advancedFilters[query][city] 4533 if (!results.length) { 4534 const objRegex = new RegExp(`[?&]${name}(\\[.*?\\])=(.*?)(?=&|#|$)`, 'g'); 4535 results = Array.from(RegExp.prototype[Symbol.matchAll].call(objRegex, url)).flat(); 4536 return this._parseParamToObject(results, name === 'advancedFilters'); 4537 } 4538 // For multiple param values, eg arrays 4539 /* Results from regex match all come in this form: 4540 [ '&advancedFilters[query][statusIds][in]=uuid-segundo-123', '[query][statusIds][in]', 'uuid-segundo-123' ] 4541 So using every third value will be the actual value and the second value will be the path 4542 */ if (results.length > 3) return results.reduce((acc, value, index)=>{ 4543 if ((index + 1) % 3 === 0) acc.push(decodeURIComponent(value.replace(/\+/g, ' '))); 4544 return acc; 4545 }, []); 4546 if (!results[2]) return ''; 4547 return decodeURIComponent(results[2].replace(/\+/g, ' ')); 4548 } 4549 /** 4550 * Generates section specific key to store page number in the URL 4551 * @param {string} sectionDomId 4552 * @return {string} 4553 */ makePageKey(sectionDomId) { 4554 return sectionDomId.replace('section-', '').replace(/-/g, ''); 4555 } 4556 /** 4557 * Generates specific key to store parameter in the URL 4558 * uses prevsiouly calculated baseKey for section specific parameters 4559 * @param {Object} formParam 4560 * @param {string} baseKey pre-calculated base for section specific parameters 4561 * @return {string} 4562 */ makeParamKey(formParam, baseKey) { 4563 return formParam.global ? formParam.urlKey : `${baseKey}-${formParam.urlKey}`; 4564 } 4565 /** 4566 * Gets a set of parameters from window query string 4567 * always returns value of "page" parameter if present 4568 * @param {Object[]} formConfig 4569 * @param {string} sectionDomId 4570 * @return {Object} 4571 */ deserializeParams(formConfig, sectionDomId) { 4572 const params = {}; 4573 const urlPageKey = this.makePageKey(sectionDomId); 4574 const page = this.getParameterByName(urlPageKey); 4575 if (page) params.page = parseInt(page); 4576 if (Array.isArray(formConfig)) formConfig.forEach((formParam)=>{
4577 const key = this.makeParamKey(formParam, urlPageKey); 4578 let urlValue = this.getParameterByName(key); 4579 if (formParam.type === 'int') { 4580 urlValue = parseInt(urlValue); 4581 // eslint-disable-next-line 4582 if (!isFinite(urlValue)) urlValue = null; 4583 } else if (formParam.type === 'bool' || formParam.type === 'boolean') urlValue = // eslint-disable-next-line 4584 urlValue === 'true' ? true : urlValue === 'false' ? false : null; 4585 else if (formParam.type === 'json') try { 4586 urlValue = JSON.parse(urlValue); 4587 } catch (e) { 4588 // no need to do anything 4589 } 4590 params[formParam.formKey || formParam.param] = urlValue; 4591 }); 4592 return params; 4593 } 4594 /** 4595 * Serializes set of parameters defined by formConfig into the URL 4596 * adding or replacing or removing required parameters 4597 * @param {Object[]} formConfig 4598 * @param {string} sectionDomId 4599 * @param {Object} params 4600 * @return {string} 4601 */ serializeParams(formConfig, sectionDomId, params) { 4602 let result = this.window.location.href; 4603 const urlPageKey = this.makePageKey(sectionDomId); 4604 result = this.updateQueryString(urlPageKey, params.page, result); 4605 if (Array.isArray(formConfig)) formConfig.forEach((formParam)=>{ 4606 const key = this.makeParamKey(formParam, urlPageKey); 4607 let value = params[formParam.formKey || formParam.param]; 4608 if (formParam.type === 'json' && value !== null && typeof value !== 'undefined') value = JSON.stringify(value); 4609 result = this.updateQueryString(key, value, result); 4610 }); 4611 return result; 4612 } 4613 /** 4614 * Formats small number for slider values 4615 * @param {number} value 4616 * @param {string} currency 4617 * @return {string} 4618 */ formatSliderAtom(value, currency = '') { 4619 const formattedValue = Number.isInteger(value) ? value : value.toFixed(2); 4620 return currency + formattedValue; 4621 } 4622 /** 4623 * Formats a number for slider values 4624 * @param {number} value 4625 * @param {string} currency 4626 * @param {string} delimiter 4627 * @return {string} 4628 */ formatSliderValue(value, currency = '', delimiter = '') { 4629 if (value < 1000) return this.formatSliderAtom(value, currency); 4630 if (value < 1000000) return `${this.formatSliderAtom(value / 1000, currency)}${delimiter}K`; 4631 return `${this.formatSliderAtom(value / 1000000, currency)}${delimiter}M`; 4632 } 4633 /** 4634 * Format price slider range 4635 * @param {number} value 4636 * @param {Object} range 4637 * @param {string} currency 4638 * @param {Object} options 4639 * @return {string} 4640 */ formatSliderPriceRange(value, range, currency, options) { 4641 if (range.min === value && value !== 0) return `${options.lowPrefix}${this.formatSliderValue(value, currency, ' ')}`; 4642 if (range.max === value) return `${this.formatSliderValue(value, currency, ' ')}${options.highSuffix}`; 4643 return `${this.formatSliderValue(value, currency, ' ')}`; 4644 } 4645 /** 4646 * Format area slider range 4647 * @param {number} value 4648 * @param {Object} range 4649 * @param {string} suffix 4650 * @param {Object} options 4651 * @return {string} 4652 */ formatSliderAreaRange(value, range, suffix, options) { 4653 if (range.min === value && value !== 0) return `${options.lowPrefix}${this.formatSliderValue(value)} ${suffix}`; 4654 if (range.max === value) return `${this.formatSliderValue(value)}${options.highSuffix} ${suffix}`; 4655 return `${this.formatSliderValue(value)} ${suffix}`; 4656 } 4657 /** 4658 * Utility function to get properties search parameters from a set of jQuery objects 4659 * @param {Object} options 4660 * @param {jQuery|HTMLElement} [options.queryElement] 4661 * @param {jQuery|HTMLElement} [options.priceSliderElement] 4662 * @param {jQuery|HTMLElement} [options.areaSliderElement] 4663 * @param {jQuery|HTMLElement} [options.neighborhoodSelect] 4664 * @return {Object} 4665 */ // eslint-disable-next-line sonarjs/cognitive-complexity 4666 getSearchFormValues(options) { 4667 const { queryElement: queryElement, priceSliderElement: priceSliderElement, areaSli
4667derElement: areaSliderElement, neighborhoodSelect: neighborhoodSelect } = options; 4668 const result = {}; 4669 if (queryElement) result.search = queryElement.value || null; 4670 if (priceSliderElement) { 4671 const priceValues = priceSliderElement.value.split(';'); 4672 const priceMinLimit = parseInt(priceSliderElement.dataset.min); 4673 const priceMaxLimit = parseInt(priceSliderElement.dataset.max); 4674 const priceMin = parseInt(priceValues[0]); 4675 const priceMax = parseInt(priceValues[1]); 4676 result.priceMin = priceMin !== priceMinLimit ? priceMin : null; 4677 result.priceMax = priceMax !== priceMaxLimit ? priceMax : null; 4678 } 4679 if (areaSliderElement) { 4680 const areaValues = areaSliderElement.value.split(';'); 4681 const areaMinLimit = parseInt(areaSliderElement.dataset.min); 4682 const areaMaxLimit = parseInt(areaSliderElement.dataset.max); 4683 const areaMin = parseInt(areaValues[0]); 4684 const areaMax = parseInt(areaValues[1]); 4685 result.areaMin = areaMin !== areaMinLimit ? areaMin : null; 4686 result.areaMax = areaMax !== areaMaxLimit ? areaMax : null; 4687 } 4688 if (neighborhoodSelect) result.neighborhoodId = neighborhoodSelect.value || null; 4689 return result; 4690 } 4691 /** 4692 * Add supports for object typeof values to be encoded on URL 4693 * Ref: https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object 4694 * @param {*} obj 4695 * @param {string} prefix 4696 * @param {string} baseParam used for grouping in case of nested objects 4697 * @returns 4698 */ // eslint-disable-next-line sonarjs/cognitive-complexity 4699 serializeObj(obj, prefix = '', baseParam = '') { 4700 const result = []; 4701 for(const prop in obj)if (obj.hasOwnProperty(prop)) { 4702 const value = obj[prop]; 4703 let k; 4704 if (baseParam) { 4705 const newPrefix = prefix ? `[${prefix}][${prop}]` : `[${prop}]`; 4706 k = `${baseParam}${newPrefix}`; 4707 } else k = prefix ? `${prefix}[${prop}]` : prop; 4708 if (value !== null && Array.isArray(value)) { 4709 if (value.length) result.push(this.serializeArray(k, value)); 4710 } else if (value !== null && typeof value === 'object') result.push(this.serializeObj(value, k)); 4711 else result.push(`${k}=${encodeURIComponent(value)}`); 4712 } 4713 return result.join('&'); 4714 } 4715 /** 4716 * Adds support for array values to be encoded on URL 4717 * @param {string} key 4718 * @param {Array} value 4719 * @param {string} baseParam 4720 */ serializeArray(key, value, baseParam = '') { 4721 const result = []; 4722 value.forEach((val)=>{ 4723 const paramName = baseParam ? `${baseParam}${key}` : key; 4724 result.push(`${paramName}=${encodeURIComponent(val)}`); 4725 }); 4726 return result.join('&'); 4727 } 4728 /** 4729 * Private method to process string search values. 4730 * @param {Array} results comes from regex matchAll. 4731 * @param {Boolean} isAdvancedFilters flag to determine if the search is for advanced filters. 4732 * @returns 4733 */ _parseParamToObject(results, isAdvancedFilters = false) { 4734 if (!results.length) return null; 4735 const obj = {}; 4736 for(let i = 0; i < results.length; i++)/* Results from regex match all come in this form: 4737 [ '&advancedFilters[query][statusIds][in]=uuid-segundo-123', '[query][statusIds][in]', 'uuid-segundo-123' ] 4738 So using every third value will be the actual value and the second value will be the path 4739 */ if ((i + 1) % 3 === 0) { 4740 const value = decodeURIComponent(results[i].replace(/\+/g, ' ')); 4741 const path = results[i - 1]; 4742 Object.assign(obj, this._setObjectPath(path, value, obj, isAdvancedFilters)); 4743 } 4744 return obj; 4745 } 4746 /** * 4747 * Private method to set object value given a path 4748 * @param {String} path 4749 * @param {*} value 4750 * @param {Object} baseObj 4751 * @param {Boolean} isAdvancedFilters flag to determine if the search is for advanced filters. 4752 */ _setObjectPath(path, value, baseObj, isAdvancedFilters = false) { 4753 // eslint-disable-next-line
4754 const keys = path.split(/[\[\]]/).filter(Boolean); 4755 const afArrayKeys = [ 4756 'in', 4757 'nin', 4758 'btw', 4759 'ciin', 4760 'cinin' 4761 ]; 4762 const obj = {}; 4763 let ref = obj; 4764 const valueExists = (0, $fc0a058ae42af628$export$267a1435fe18b8d4)(baseObj, keys.join('.')); 4765 keys.forEach((key, index)=>{ 4766 if (index === keys.length - 1) { 4767 if (valueExists) ref[key] = Array.isArray(valueExists) ? [ 4768 ...valueExists, 4769 value 4770 ] : [ 4771 valueExists, 4772 value 4773 ]; 4774 else ref[key] = isAdvancedFilters && afArrayKeys.includes(key) ? [ 4775 value 4776 ] : value; 4777 } else { 4778 ref[key] = baseObj[key] ? baseObj[key] : {}; 4779 ref = ref[key]; 4780 } 4781 }); 4782 return obj; 4783 } 4784} 4785 4786 4787/* eslint-disable import/prefer-default-export */ 4788class $2b1790aa32f109d1$export$43e435c053a109f6 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4789 /** 4790 * Initialize Expand Slider Controller at build time 4791 * @param { ConfigController } config 4792 * @param {Window} window 4793 * @param {Document} document 4794 */ constructor(config, window, document){ 4795 super(config, window, document); 4796 this._createLightbox = this._createLightbox.bind(this); 4797 this.expandSliders = []; 4798 } 4799 /** 4800 * Initialize Expand Slider Controller at run time 4801 * Attach handlers and events to DOM 4802 * @param {Object} options 4803 */ initialize(options = {}) { 4804 try { 4805 const { document: document } = this; 4806 const expandSliders = document.querySelectorAll('.has-mobile-expand'); 4807 expandSliders.forEach((block)=>{ 4808 this._addExpandButton(block); 4809 }); 4810 this.expandSliders = this.expandSliders.concat(expandSliders); 4811 } catch (e) { 4812 // eslint-disable-next-line no-console 4813 console.log(e); 4814 } 4815 } 4816 /** 4817 * Controllers must implement a "destroy" method to be called 4818 * after page navigation to remove event handlers / cleanup 4819 */ destroy() { 4820 const { document: document } = this; 4821 document.querySelectorAll('.lp-expand-btn, .lp-expand-btn-wrapper').forEach((element)=>element.remove()); 4822 this.expandSliders = []; 4823 } 4824 /** 4825 * Create expand button for sliders 4826 * @param {HTMLElement} slider 4827 */ _addExpandButton(slider) { 4828 const { document: document } = this; 4829 const section = slider.closest('section[id]'); 4830 const appendTargetSelector = slider.dataset.append; 4831 let expandBtn = section.querySelector('.lp-expand-btn'); 4832 if (!expandBtn) { 4833 expandBtn = document.createElement('button'); 4834 expandBtn.className = 'lp-expand-btn'; 4835 expandBtn.setAttribute('aria-label', 'Open a lightbox'); 4836 expandBtn.innerHTML = `<span class="lp-i lp-i--expand-alt" aria-hidden="true"></span>`; 4837 if (appendTargetSelector) { 4838 const appendTarget = section.querySelector(appendTargetSelector); 4839 if (appendTarget) appendTarget.appendChild(expandBtn); 4840 } else { 4841 const wrapper = document.createElement('div'); 4842 wrapper.className = 'lp-expand-btn-wrapper'; 4843 wrapper.style.position = 'relative'; 4844 wrapper.appendChild(expandBtn); 4845 slider.parentNode.insertBefore(wrapper, slider.nextSibling); 4846 } 4847 } 4848 expandBtn.addEventListener('click', ()=>{ 4849 this._createLightbox({ 4850 slider: slider, 4851 section: section 4852 }); 4853 }); 4854 } 4855 /** 4856 * Creates a lightbox modal for displaying slider images in a larger view. 4857 * 4858 * @param {Object} params - The parameters for the function. 4859 * @param {HTMLElement} params.slider - The slider element containing images. 4860 * @param {HTMLElement} params.section - The section element related to the slider. 4861 * @param {number} [params.initialIndex] - Optional index of the image to show initially. 4862 */ _createLightbox({ slider: slider, section: section, initialIndex: initialIndex }) { 4863 const { window: window, document: document } = this; 4864 const modalId = `lightbox-for-${section?.id}`; 4865 const isSlickAvailable = window.$ && window.$.fn.slick; 4866 const isSectionSlickInitialized = isSlickAvailable && slider.slick; 4867 const isSplideAvailable = typeof window.Splide === 'function'; 4868 const isSectionSplideInitialized = slider.classList.contains('is-initialized') && slider.classList.contains('splide'); 4869 if (typeof window.lpUI.requestScrollLock === 'function') window.lpUI.requestScrollLock(modalId); 4870 else document.body.style.overflow = 'hidden'; 4871 function createSlickLightbox() { 4872 const slickOverlayOptions = { 4873 arrows: true, 4874 dots: false, 4875 infinite: true, 4876 draggable: true, 4877 prevArrow: '<button type="button" class="lp-lightbox-arrow--prev lp-lightbox-arrow" aria-label="Previous"></button>', 4878 nextArrow: '<button type="button" class="lp-lightbox-arrow--next lp-lightbox-arrow" aria-label="Next"></button>' 4879 }; 4880 const modal = document.createElement('div'); 4881 modal.id = modalId; 4882 modal.className = 'lp-lightbox'; 4883 modal.setAttribute('tabindex', '-1'); 4884 modal.setAttribute('role', 'dialog'); 4885 modal.setAttribute('aria-modal', 'true'); 4886 modal.innerHTML = ` 4887 <button class="lp-lightbox__close" aria-label="close modal"></button> 4888 <div class="lp-lightbox__content"> 4889 <div class="lp-lightbox__carousel"></div> 4890 </div> 4891 `; 4892 document.body.appendChild(modal); 4893 const overlaySlider = modal.querySelector('.lp-lightbox__carousel'); 4894 modal.classList.add('show'); 4895 let sourceImages; 4896 if (isSectionSlickInitialized) { 4897 sourceImages = slider.slick.$slides.find('img'); 4898 slickOverlayOptions.initialSlide = slider.slick?.currentSlide || 0; 4899 sourceImages.each(function() { 4900 window.$('.lp-lightbox__carousel').append(window.$(this).clone(false).removeAttr('style').wrap('<div class="lp-lightbox__slide"></div>').parent()); 4901 }); 4902 } else { 4903 sourceImages = slider.querySelectorAll('img');
4904 sourceImages.forEach((img)=>{ 4905 const imgClone = img.cloneNode(true); 4906 imgClone.removeAttribute('style'); 4907 const slide = document.createElement('div'); 4908 slide.className = 'lp-lightbox__slide'; 4909 slide.appendChild(imgClone); 4910 overlaySlider.appendChild(slide); 4911 }); 4912 } 4913 window.$(overlaySlider).slick(slickOverlayOptions); 4914 // Focus modal after it's created 4915 setTimeout(()=>{ 4916 modal.focus(); 4917 }, 100); 4918 // Keyboard navigation handler 4919 const handleKeydown = (e)=>{ 4920 if (!modal.classList.contains('show')) return; 4921 const prevArrow = modal.querySelector('.lp-lightbox-arrow--prev'); 4922 const nextArrow = modal.querySelector('.lp-lightbox-arrow--next'); 4923 const closeBtn = modal.querySelector('.lp-lightbox__close'); 4924 if (e.key === 'ArrowLeft' && prevArrow) { 4925 e.preventDefault(); 4926 prevArrow.click(); 4927 } else if (e.key === 'ArrowRight' && nextArrow) { 4928 e.preventDefault(); 4929 nextArrow.click(); 4930 } else if (e.key === 'Escape' && closeBtn) { 4931 e.preventDefault(); 4932 closeBtn.click(); 4933 } 4934 }; 4935 document.addEventListener('keydown', handleKeydown); 4936 modal.querySelector('.lp-lightbox__close').addEventListener('click', ()=>{ 4937 document.removeEventListener('keydown', handleKeydown); 4938 modal.classList.remove('show'); 4939 const currentSlide = overlaySlider?.slick ? window.$(overlaySlider).slick('slickCurrentSlide') : 0; 4940 if (overlaySlider?.slick) window.$(overlaySlider).slick('unslick'); 4941 if (isSectionSlickInitialized) window.$(slider).slick('slickGoTo', currentSlide); 4942 if (typeof window.lpUI.releaseScrollLock === 'function') window.lpUI.releaseScrollLock(modalId); 4943 else document.body.style.overflow = ''; 4944 modal.remove(); 4945 }); 4946 } 4947 function createSplideLightbox() { 4948 const splideOptions = { 4949 rewind: true, 4950 arrows: true, 4951 arrowPath: '', 4952 type: 'loop', 4953 clones: 1, 4954 pagination: false, 4955 perPage: 1, 4956 perMove: 1 4957 }; 4958 const modal = document.createElement('div'); 4959 modal.id = modalId; 4960 modal.className = 'lp-lightbox'; 4961 modal.setAttribute('tabindex', '-1'); 4962 modal.setAttribute('role', 'dialog'); 4963 modal.setAttribute('aria-modal', 'true'); 4964 modal.innerHTML = ` 4965 <button class="lp-lightbox__close" aria-label="close modal"></button> 4966 <div class="lp-lightbox__content"> 4967 <div class="lp-lightbox__carousel splide" role="group" aria-label="Image Gallery"> 4968 <div class="splide__arrows"> 4969 <button class="splide__arrow splide__arrow--prev lp-lightbox-arrow lp-lightbox-arrow--prev"></button> 4970 <button class="splide__arrow splide__arrow--next lp-lightbox-arrow lp-lightbox-arrow--next"></button> 4971 </div> 4972 <div class="splide__track"> 4973 <ul class="splide__list"></ul> 4974 </div> 4975 </div> 4976 </div> 4977 `; 4978 if (typeof window.lpUI.requestScrollLock === 'function') window.lpUI.requestScrollLock(modalId); 4979 else document.body.style.overflow = 'hidden'; 4980 document.body.appendChild(modal); 4981 const overlaySlider = modal.querySelector('.lp-lightbox__carousel'); 4982 const overlaySliderList = overlaySlider.querySelector('.splide__list'); 4983 modal.classList.add('show'); 4984 let sourceImages; 4985 if (isSectionSplideInitialized) sourceImages = slider.querySelectorAll('.splide__slide:not(.splide__slide--clone) img'); 4986 else sourceImages = slider.querySelectorAll('img');
4987 sourceImages.forEach((img)=>{ 4988 const imgClone = img.cloneNode(true); 4989 imgClone.removeAttribute('style'); 4990 const slide = document.createElement('li'); 4991 slide.className = 'lp-lightbox__slide splide__slide'; 4992 slide.appendChild(imgClone); 4993 overlaySliderList.appendChild(slide); 4994 }); 4995 const overlaySplide = new window.Splide(overlaySlider, splideOptions); 4996 overlaySplide.mount(); 4997 // Navigate to initial index if provided 4998 if (typeof initialIndex !== 'undefined' && initialIndex >= 0) overlaySplide.go(initialIndex); 4999 // Focus modal after it's created and mounted 5000 setTimeout(()=>{ 5001 modal.focus(); 5002 }, 100); 5003 // Keyboard navigation handler 5004 const handleKeydown = (e)=>{ 5005 if (!modal.classList.contains('show')) return; 5006 const prevArrow = modal.querySelector('.splide__arrow--prev'); 5007 const nextArrow = modal.querySelector('.splide__arrow--next'); 5008 const closeBtn = modal.querySelector('.lp-lightbox__close'); 5009 if (e.key === 'ArrowLeft' && prevArrow) { 5010 e.preventDefault(); 5011 prevArrow.click(); 5012 } else if (e.key === 'ArrowRight' && nextArrow) { 5013 e.preventDefault(); 5014 nextArrow.click(); 5015 } else if (e.key === 'Escape' && closeBtn) { 5016 e.preventDefault(); 5017 closeBtn.click(); 5018 } 5019 }; 5020 document.addEventListener('keydown', handleKeydown); 5021 modal.querySelector('.lp-lightbox__close').addEventListener('click', ()=>{ 5022 document.removeEventListener('keydown', handleKeydown); 5023 modal.classList.remove('show'); 5024 if (overlaySplide) overlaySplide.destroy(); 5025 if (typeof window.lpUI.releaseScrollLock === 'function') window.lpUI.releaseScrollLock(modalId); 5026 else document.body.style.overflow = ''; 5027 modal.remove(); 5028 }); 5029 } 5030 if (isSlickAvailable && !isSectionSplideInitialized) createSlickLightbox(); 5031 else if (isSplideAvailable) createSplideLightbox(); 5032 else { 5033 window.luxuryPresence.libs.loadCss(window.luxuryPresence.libs.jsLibsMap.splide.cssSrc); 5034 window.luxuryPresence.libs.loadJs(window.luxuryPresence.libs.jsLibsMap.splide.jsSrc, ()=>{ 5035 createSplideLightbox(); 5036 window.luxuryPresence.libs.jsLibsMap.splide.isLoaded = true; 5037 }); 5038 } 5039 } 5040} 5041 5042 5043/* eslint-disable import/prefer-default-export */ 5044class $4f184d9bd3037f15$export$7a6215890afba974 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5045 // constructor is not needed as it is inherited from BaseController 5046 /** 5047 * Destroy 5048 */ destroy() { 5049 if (!this.window.divolte) this.window.divolteLp = function() { 5050 (this.window.divolteLp.q = this.window.divolteLp.q || []).push(// eslint-disable-next-line prefer-rest-params 5051 arguments); 5052 }; 5053 } 5054 /** 5055 * Initialize divolte controller 5056 * @param {Object} options 5057 */ initialize(options = {}) { 5058 const { divolteServiceUrl: divolteServiceUrl } = this.config; 5059 if (!divolteServiceUrl || this.window.divolte) return; 5060 this.loadScript(); 5061 } 5062 /** 5063 * loads divolte 5064 */ loadScript() { 5065 const { divolteServiceUrl: divolteServiceUrl } = this.config; 5066 const script = this.document.createElement('script'); 5067 script.id = 'divolte-script'; 5068 script.type = 'text/javascript'; 5069 script.defer = true; 5070 script.async = true; 5071 script.src = `${divolteServiceUrl}/divolte.js`; 5072 this.document.head.appendChild(script); 5073 script.addEventListener('load', this.executeQueue.bind(this)); 5074 } 5075 /** 5076 * executeQueue 5077 */ executeQueue() { 5078 (this.window.divolteLp.q || []).forEach(([methodName, params])=>{ 5079 this[methodName](params); 5080 }); 5081 this.window.divolteLp = this.divolteLp.bind(this); 5082 } 5083 /** 5084 * divolteLp 5085 * @param {string} name 5086 * @param {Object} params 5087 */ divolteLp(name, params) { 5088 try { 5089 this[name](params); 5090 } catch (error) { 5091 // eslint-disable-next-line no-console 5092 console.log(error); 5093 } 5094 } 5095 /** 5096 * sendLpEvent 5097 * @param {string} name 5098 * @param {Object} params 5099 */ sendLpEvent(name, params) { 5100 // Events must be defined in divolte schema! To ensure correct usage, event names are validated here. 5101 if (!name.includes('pageView')) { 5102 // Event not defined 5103 // eslint-disable-next-line no-console 5104 console.log(`${name} IS NOT SUPPORTED`); 5105 return; 5106 } 5107 const { companyId: companyId, templateId: templateId, websiteId: websiteId, pageId: pageId, shareId: shareId, pageMeta: pageMeta } = this.config; 5108 const baseParams = { 5109 companyId: companyId, 5110 templateId: templateId, 5111 websiteId: websiteId, 5112 pageId: pageId, 5113 shareId: shareId, 5114 pageMeta: pageMeta 5115 }; 5116 this.window.divolte.signal(name, Object.assign(baseParams, params)); 5117 } 5118 /** 5119 * sendLpPageView 5120 */ sendLpPageView() { 5121 this.sendLpEvent('pageView', {}); 5122 } 5123} 5124 5125 5126/* eslint-disable import/prefer-default-export */ 5127const $102039871668a1df$var$EVENT_BUS_SCRIPT = 'event-bus-js.js'; 5128const $102039871668a1df$var$EVENT_BUS_VERSION = 'v2.8.0'; 5129const $102039871668a1df$var$EVENT_BUS_DEFAULT_SOURCE = 'frontend://website-render-engine'; 5130class $102039871668a1df$export$e22e4b1be0bb6c61 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5131 // eslint-disable-next-line @typescript-eslint/no-useless-constructor 5132 constructor(config, window, document){ 5133 super(config, window, document); 5134 } 5135 /** 5136 * Initialize EventBus controller 5137 * @param {Object} options 5138 */ initialize(options = {}) { 5139 const enabled = options.enabled !== undefined ? options.enabled : this.config.eventBus?.enabled; 5140 if (enabled && this.config.eventBus?.apiKey && this.config.eventBus?.url) this.loadScript(); 5141 } 5142 /** 5143 * loads eventBus script 5144 */ loadScript() { 5145 const { eventBusUrl: eventBusUrl } = this.config; 5146 const script = this.document.createElement('script'); 5147 script.id = 'posthog-script'; 5148 script.type = 'text/javascript'; 5149 script.defer = true; 5150 script.async = true; 5151 script.src = `${eventBusUrl}/${$102039871668a1df$var$EVENT_BUS_VERSION}/${$102039871668a1df$var$EVENT_BUS_SCRIPT}`; 5152 this.document.head.appendChild(script); 5153 script.addEventListener('load', this.executeQueue.bind(this)); 5154 } 5155 /** 5156 * executeQueue - Process queued eventBusLp calls 5157 */ executeQueue() { 5158 // Initialize the event bus provider using the global EventBusJS 5159 if (this.window.LuxuryPresenceEventBus) this.window.eventBusProvider = this.window.LuxuryPresenceEventBus.createEventBusProvider({ 5160 apiKey: this.config.eventBus.apiKey, 5161 host: this.config.eventBus.url, 5162 defaultSource: $102039871668a1df$var$EVENT_BUS_DEFAULT_SOURCE 5163 });
5164 (this.window.eventBusLp.q || []).forEach(([methodName, params])=>{ 5165 this[methodName](params); 5166 }); 5167 // Replace queue function with real function 5168 this.window.eventBusLp = this.eventBusLp.bind(this); 5169 } 5170 /** 5171 * Destroy 5172 */ destroy() { 5173 if (!this.window.eventBusProvider) { 5174 // Reset to queue function if no provider exists 5175 this.window.eventBusLp = function() { 5176 (this.window.eventBusLp.q = this.window.eventBusLp.q || []).push(// eslint-disable-next-line prefer-rest-params 5177 arguments); 5178 }; 5179 return; 5180 } 5181 this.window.eventBusProvider.shutdown(); 5182 } 5183 /** 5184 * sendLpEvent 5185 * @param {string} eventType 5186 * @param {Object} data 5187 */ sendLpEvent(eventType, data) { 5188 // Events must be defined in event-bus schema! To ensure correct usage, event types are validated here. 5189 if (eventType !== 'user.activity.page.viewed') { 5190 // Event type not defined 5191 // eslint-disable-next-line no-console 5192 console.log(`${eventType} IS NOT SUPPORTED`); 5193 return; 5194 } 5195 if (!this.window.eventBusProvider) return; 5196 const { companyId: companyId } = this.config; 5197 const eventData = { 5198 company: companyId, 5199 data: {} 5200 }; 5201 try { 5202 this.window.eventBusProvider.capture(eventType, eventData); 5203 } catch (error) { 5204 // eslint-disable-next-line no-console 5205 console.log('EventBus capture error:', error); 5206 } 5207 } 5208 /** 5209 * eventBusLp 5210 * @param {string} name 5211 * @param {Object} params 5212 */ eventBusLp(name, params) { 5213 try { 5214 this[name](params); 5215 } catch (error) { 5216 // eslint-disable-next-line no-console 5217 console.log(error); 5218 } 5219 } 5220 /** 5221 * sendLpPageView 5222 */ sendLpPageView() { 5223 this.sendLpEvent('user.activity.page.viewed', {}); 5224 } 5225} 5226 5227 5228/* eslint-disable import/prefer-default-export */ 5229 5230const $0d25a3fa2e0f4f92$var$EVENT_NAME = 'oauthSuccess'; 5231class $0d25a3fa2e0f4f92$export$b718a981691eae5c extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5232 /** 5233 * Initialize Button Controller at build time 5234 * @param { ConfigController } config 5235 * @param {Window} window 5236 * @param {Document} document 5237 */ constructor(config, window, document){ 5238 super(config, window, document); 5239 this.successListeners = []; 5240 this._listenToMessages = this._listenToMessages.bind(this); 5241 this._listenToOneTapMessage = this._listenToOneTapMessage.bind(this); 5242 } 5243 /** 5244 * Initialize OAuth Controller at run time 5245 * Swap out special buttons for the iframe 5246 * @param {Object} options 5247 */ initialize(options = {}) { 5248 try { 5249 // handle google one tap 5250 if (options.googleSignOnEnabled && !options.iframe) this._renderGoogleOneTap(); 5251 // handle google sign in buttons 5252 this.document.querySelectorAll('div[data-signin-type="google-sign-in"]').forEach((el)=>this._convertGoogleSignIn(el)); 5253 // handle facebook sign in buttons 5254 this.document.querySelectorAll('div[data-signin-type="facebook-sign-in"]').forEach((el)=>this._convertFacebookSignIn(el)); 5255 } catch (e) { 5256 // eslint-disable-next-line no-console 5257 console.log(e); 5258 } 5259 } 5260 /** 5261 * Controllers must implement a "destroy" method to be called 5262 * after page navigation to remove event handlers / cleanup 5263 */ destroy() { 5264 this.successListeners.forEach((callback)=>{ 5265 this.window.removeEventListener('message', this._listenToMessages); 5266 this.window.removeEventListener('message', this._listenToOneTapMessage); 5267 this.window.removeEventListener($0d25a3fa2e0f4f92$var$EVENT_NAME, this.window[callback]); 5268 }); 5269 } 5270 /** 5271 * Convert google one tap 5272 */ _renderGoogleOneTap() { 5273 if (!this._isGoogleOneTapOnCooldown()) { 5274 const dummyEl = this.document.createElement('div'); 5275 // eslint-disable-next-line no-undef 5276 google.accounts.id.initializeIntermediate({ 5277 src: this._buildUrlAndCopyAttributes(dummyEl, dummyEl, 'googleOneTap') 5278 }); 5279 this.window.localStorage.removeItem('googleOneTapExpiration'); 5280 this.window.addEventListener('message', this._listenToOneTapMessage); 5281 } 5282 } 5283 /** 5284 * Listening for messages from BSS one tap iframes 5285 * @param {MessageEvent} e 5286 */ _listenToOneTapMessage(e) { 5287 const messageTypes = { 5288 SHOW_INTERMEDIATE_IFRAME: 'show_intermediate_iframe', 5289 HIDE_INTERMEDIATE_IFRAME: 'hide_intermediate_iframe' 5290 };
5291 const iframeId = 'onetap_google_intermediate_iframe'; 5292 const { buyerSellerServiceUrl: buyerSellerServiceUrl } = this.config; 5293 const { data: data, origin: origin } = e; 5294 const { type: type, height: height, event: event, source: source, token: token } = data; 5295 if (type === messageTypes.SHOW_INTERMEDIATE_IFRAME || type === messageTypes.HIDE_INTERMEDIATE_IFRAME) { 5296 const bssIframe = this.document.getElementById(iframeId); 5297 if (bssIframe) { 5298 bssIframe.style.height = height; 5299 bssIframe.style.display = type === messageTypes.SHOW_INTERMEDIATE_IFRAME ? 'unset' : 'none'; 5300 } 5301 } 5302 // these events are generated by our code in the bss iframe 5303 if (origin === buyerSellerServiceUrl && source === 'GOOGLE_SIGN_ON') { 5304 if (event === 'success') { 5305 this._setExpirationLsItem(86400000); // 24 hours 5306 this._handleQualifyingQuestionsModal(token); 5307 } else if (event === 'cancel') this._setExpirationLsItem(7200000); // 2 hours 5308 } 5309 } 5310 /** 5311 * Set expiration local storage item for google one tap 5312 * @param {number} ms 5313 */ _setExpirationLsItem(ms) { 5314 const expiration = new Date().getTime() + ms; 5315 this.window.localStorage.setItem('googleOneTapExpiration', expiration); 5316 } 5317 /** 5318 * Check if google one tap is expired 5319 * @return {boolean} 5320 */ _isGoogleOneTapOnCooldown() { 5321 const expirationTime = this.window.localStorage.getItem('googleOneTapExpiration'); 5322 return Number(expirationTime) > new Date().getTime(); 5323 } 5324 /** 5325 * Convert google sign in buttons 5326 * @param {HTMLElement} el 5327 */ _convertGoogleSignIn(el) { 5328 this._replaceWithIframe('googleSignIn', el); 5329 } 5330 /** 5331 * Convert google sign in buttons 5332 * @param {HTMLElement} el 5333 */ _convertFacebookSignIn(el) { 5334 this._replaceWithIframe('facebookSignIn', el); 5335 } 5336 /** 5337 * Convert google sign in buttons 5338 * @param {'googleSignIn' | 'facebookSignIn'} provider 5339 * @param {HTMLElement} el 5340 */ _replaceWithIframe(provider, el) { 5341 this._attachSuccessCallback(el); 5342 const iframe = this.document.createElement('iframe'); 5343 iframe.src = this._buildUrlAndCopyAttributes(el, iframe, provider); 5344 iframe.dataset.isSocialOauthButton = true; 5345 el.replaceWith(iframe); 5346 } 5347 /** 5348 * Listening for messages from BSS iframes 5349 * @param {Event} e 5350 */ _listenToMessages(e) { 5351 const { buyerSellerServiceUrl: buyerSellerServiceUrl } = this.config; 5352 // eslint-disable-next-line sonarjs/no-collapsible-if 5353 if (e.origin === buyerSellerServiceUrl) { 5354 if (e.data.event === 'success') { 5355 const event = new Event($0d25a3fa2e0f4f92$var$EVENT_NAME); 5356 this.window.dispatchEvent(event); 5357 // Registration / social sign-in is a lead event â credit the conversion. 5358 (0, $3ec2bf3965f87ecc$export$e70565180c0e6950)(this.window, this.document); 5359 this.successListeners.forEach((callback)=>{ 5360 if (this.window[callback]) this.window[callback](event); 5361 else // eslint-disable-next-line no-console 5362 console.error(`Callback function ${callback} not found on window object`); 5363 }); 5364 this.document.documentElement.classList.add('is-login-success'); 5365 } 5366 } 5367 } 5368 /** 5369 * Attach callback to iframe 5370 * @param {HTMLElement} el 5371 */ _attachSuccessCallback(el) { 5372 this.window.addEventListener('message', this._listenToMessages); 5373 const callbackFunction = el.dataset.onSuccessFunctionName; 5374 if (callbackFunction) this.successListeners.push(callbackFunction); 5375 } 5376 /** 5377 * Build Iframe URL 5378 * @param {HTMLElement} source 5379 * @param {HTMLElement} target 5380 * @param {string} provider 5381 * @return {string} 5382 */ _buildUrlAndCopyAttributes(source, target, provider) { 5383 const { buyerSellerServiceUrl: buyerSellerServiceUrl, companyId: companyId, websiteId: websiteId, pageId: pageId, pageMeta: pageMeta } = this.config; 5384 const { location: location } = this.window; 5385 const sourceUrl = location.origin + location.pathname; 5386 const destination = new URL(`${buyerSellerServiceUrl}/buttons/${provider}`); 5387 const utm = this.window.localStorage.getItem('utm_vars'); 5388 const referrer = this.window.localStorage.getItem('referrer'); 5389 destination.searchParams.append('companyId', companyId); 5390 destination.searchParams.append('websiteId', websiteId); 5391 destination.searchParams.append('pageId', pageId); 5392 destination.searchParams.append('sourceUrl', sourceUrl); 5393 destination.searchParams.append('pageMeta', JSON.stringify(pageMeta)); 5394 if (utm) destination.searchParams.append('utm', utm); 5395 if (referrer) destination.searchParams.append('referrer', referrer); 5396 // Google and Facebook Attributes used to style the social button 5397 const RESERVED_ATTRIBUTES = [ 5398 'data-size', 5399 'data-width', 5400 'data-text', 5401 'data-button-type', 5402 'data-shape', 5403 'data-layout', 5404 'data-max-rows', 5405 'data-use-continue-as', 5406 'data-type', 5407 'data-theme', 5408 'data-logo_alignment', 5409 'data-locale' 5410 ];
5411 Array.from(source.attributes).forEach((attribute)=>{ 5412 if (RESERVED_ATTRIBUTES.includes(attribute.nodeName)) destination.searchParams.append(attribute.nodeName, attribute.nodeValue); 5413 else target.setAttribute(attribute.nodeName === 'id' ? 'data-id' : attribute.nodeName, attribute.nodeValue); 5414 }); 5415 return destination.toString(); 5416 } 5417 /** 5418 * Decode a JWT token to extract payload information 5419 * @param {string} token - The JWT token to decode 5420 * @return {Object|null} The decoded payload object or null if decoding fails 5421 */ _decodeToken(token) { 5422 try { 5423 const parts = token.split('.'); 5424 const payload = parts[1]; 5425 const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); 5426 const padding = 4 - base64.length % 4; 5427 const padded = padding === 4 ? base64 : base64 + '='.repeat(padding); 5428 return JSON.parse(atob(padded)); 5429 } catch (error) { 5430 return null; 5431 } 5432 } 5433 /** 5434 * Handle qualifying questions modal showing for Google Sign On 5435 * Decodes the JWT token to extract user email and shows the modal if available 5436 * @param {string} token - The JWT token containing user information 5437 */ _handleQualifyingQuestionsModal(token) { 5438 if (token && this.window.luxuryPresence && this.window.luxuryPresence.qualifyingQuestionsModal) { 5439 // Decoding only for lead attribution, fine to skip signature verification 5440 const decoded = this._decodeToken(token); 5441 if (decoded && decoded.email) this.window.luxuryPresence.qualifyingQuestionsModal.showModal(decoded.email, 'Google Sign On'); 5442 } 5443 } 5444} 5445 5446 5447/* eslint-disable import/prefer-default-export */ 5448class $dcf72769f009ff26$export$b900d6fe921fd49c extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5449 /** 5450 * Initialize Qualifying Questions Modal Controller at build time 5451 * @param { ConfigController } config 5452 * @param {Window} window 5453 * @param {Document} document 5454 */ constructor(config, window, document){ 5455 super(config, window, document); 5456 this.showModal = this.showModal.bind(this); 5457 this.hasModalBeenSubmitted = this.hasModalBeenSubmitted.bind(this); 5458 this.modalId = 'qualifying-questions-modal'; 5459 this.cookieKey = 'qualifyingQuestionsModalSubmitted'; 5460 this.qualifyingQuestionsModalEnabled = true; 5461 // Store references for cleanup 5462 this.eventListeners = []; 5463 this.timeouts = []; 5464 } 5465 initialize(options = {}) { 5466 this.qualifyingQuestionsModalEnabled = options.qualifyingQuestionsModalEnabled ?? this.qualifyingQuestionsModalEnabled; 5467 if (!this.qualifyingQuestionsModalEnabled) return; 5468 this.attachModalCloseListeners(); 5469 } 5470 destroy() { 5471 // Remove all stored event listeners 5472 this.eventListeners.forEach(({ element: element, event: event, handler: handler })=>{ 5473 if (element && element.removeEventListener) element.removeEventListener(event, handler); 5474 }); 5475 this.timeouts.forEach((timeoutId)=>{ 5476 clearTimeout(timeoutId); 5477 }); 5478 this.restoreBodyScroll(); 5479 this.eventListeners = []; 5480 this.timeouts = []; 5481 } 5482 /** 5483 * Check if modal has been submitted using cookie-based deduplication 5484 * @return {boolean} 5485 */ hasModalBeenSubmitted() { 5486 const cookieValue = this.getCookie(this.cookieKey); 5487 return cookieValue === 'true'; 5488 } 5489 /** 5490 * Set cookie to prevent modal from showing again 5491 */ setModalSubmittedCookie() { 5492 this.setCookie(this.cookieKey, 'true', 365); // 1 year 5493 } 5494 showModal(email, triggerSource) { 5495 if (!this.qualifyingQuestionsModalEnabled || this.hasModalBeenSubmitted()) return; 5496 this.updateEmailValue(email); 5497 this.updateTriggerSourceValue(triggerSource); 5498 this.showModalElement(); 5499 } 5500 /** 5501 * Get modal element with error handling 5502 * @return {Element|null} 5503 */ getModalElement() { 5504 const modal = this.document.getElementById(this.modalId); 5505 if (!modal) console.error('Modal element not found:', this.modalId); 5506 return modal; 5507 } 5508 updateEmailValue(email) { 5509 const modal = this.getModalElement(); 5510 if (!modal) return; 5511 const emailInput = modal.querySelector('input[name="email"]'); 5512 if (emailInput && typeof email === 'string') emailInput.value = email; 5513 } 5514 updateTriggerSourceValue(triggerSource) { 5515 const modal = this.getModalElement(); 5516 if (!modal) return;
5517 const triggerSourceInput = modal.querySelector(`#${this.modalId}-triggerSource`); 5518 if (triggerSourceInput && typeof triggerSource === 'string') triggerSourceInput.value = triggerSource; 5519 } 5520 showModalElement() { 5521 const modal = this.getModalElement(); 5522 if (!modal) return; 5523 modal.style.display = 'flex'; 5524 this.blockBodyScroll(); 5525 } 5526 /** 5527 * Helper method to get cookie value 5528 * @param {string} name 5529 * @return {string|null} 5530 */ getCookie(name) { 5531 const value = `; ${this.document.cookie}`; 5532 const parts = value.split(`; ${name}=`); 5533 if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift()); 5534 return null; 5535 } 5536 /** 5537 * Helper method to set cookie 5538 * @param {string} name 5539 * @param {string} value 5540 * @param {number} days 5541 */ setCookie(name, value, days) { 5542 let expires = ''; 5543 if (days) { 5544 const date = new Date(); 5545 date.setTime(date.getTime() + days * 86400000); 5546 expires = `; expires=${date.toUTCString()}`; 5547 } 5548 let attrs = '; path=/; SameSite=Lax'; 5549 if (this.window && this.window.location && this.window.location.protocol === 'https:') attrs += '; Secure'; 5550 this.document.cookie = `${name}=${encodeURIComponent(value || '')}${expires}${attrs}`; 5551 } 5552 blockBodyScroll() { 5553 this._prevOverflow = this.document.body.style.overflow; 5554 this.document.body.style.overflow = 'hidden'; 5555 } 5556 restoreBodyScroll() { 5557 this.document.body.style.overflow = this._prevOverflow || ''; 5558 this._prevOverflow = null; 5559 } 5560 attachModalCloseListeners() { 5561 const modal = this.getModalElement(); 5562 if (!modal) return; 5563 // Listen for form submission success (when modal closes automatically) 5564 const form = this.document.getElementById(`${this.modalId}-form`); 5565 if (form) { 5566 const formSubmittedHandler = ()=>{ 5567 // Set cookie to prevent showing again and hide modal 5568 this.setModalSubmittedCookie(); 5569 const timeoutId = setTimeout(()=>{ 5570 modal.style.display = 'none'; 5571 this.restoreBodyScroll(); 5572 }, 100); 5573 this.timeouts.push(timeoutId); 5574 }; 5575 form.addEventListener('formsubmitted', formSubmittedHandler); 5576 this.eventListeners.push({ 5577 element: form, 5578 event: 'formsubmitted', 5579 handler: formSubmittedHandler 5580 }); 5581 } 5582 // Listen for close button clicks 5583 const closeButton = modal.querySelector('[data-dismiss="modal"]'); 5584 if (closeButton) { 5585 const closeButtonHandler = ()=>{ 5586 modal.style.display = 'none'; 5587 this.restoreBodyScroll(); 5588 }; 5589 closeButton.addEventListener('click', closeButtonHandler); 5590 this.eventListeners.push({ 5591 element: closeButton, 5592 event: 'click', 5593 handler: closeButtonHandler 5594 }); 5595 } 5596 // Listen for backdrop clicks 5597 const backdropClickHandler = (e)=>{ 5598 // Check if click is on the modal backdrop (not on the modal content) 5599 if (e.target === modal || e.target.id === this.modalId) { 5600 modal.style.display = 'none'; 5601 this.restoreBodyScroll(); 5602 } 5603 }; 5604 modal.addEventListener('click', backdropClickHandler); 5605 this.eventListeners.push({ 5606 element: modal, 5607 event: 'click', 5608 handler: backdropClickHandler 5609 }); 5610 } 5611} 5612 5613 5614/* eslint-disable import/prefer-default-export */ 5615class $50eefeef8d1f2372$export$8a2bf06fbae4dde extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5616 constructor(configController, window, document){ 5617 super(configController, window, document); 5618 this.isValidating = false; 5619 this.lastValidationResult = null; 5620 this.lastValidationTime = null; 5621 this.validationCacheDuration = 300000; // 5 minutes 5622 } 5623 /** 5624 * Destroy 5625 */ destroy() { 5626 this.isValidating = false;
5627 this.lastValidationResult = null; 5628 this.lastValidationTime = null; 5629 } 5630 /** 5631 * Validate user session using GraphQL validSession query 5632 * @param {Object} options - Options for session validation 5633 * @param {boolean} options.useCache - Whether to use cached validation result 5634 * @returns {Promise<boolean>} - true if session is valid, false otherwise 5635 */ async validateSession(options = {}) { 5636 const { useCache: useCache = true } = options; 5637 if (useCache && this.lastValidationResult !== null && this.lastValidationTime) { 5638 const timeSinceLastValidation = Date.now() - this.lastValidationTime; 5639 if (timeSinceLastValidation < this.validationCacheDuration) return this.lastValidationResult; 5640 } 5641 if (this.isValidating) return this.lastValidationResult !== null ? this.lastValidationResult : false; 5642 this.isValidating = true; 5643 const query = ` 5644 query { 5645 validSession { 5646 success 5647 } 5648 } 5649 `; 5650 try { 5651 // query from same domain as the website 5652 const response = await this.window.fetch(`/graphql`, { 5653 method: 'POST', 5654 headers: { 5655 'Content-Type': 'application/json' 5656 }, 5657 credentials: 'include', 5658 body: JSON.stringify({ 5659 query: query 5660 }) 5661 }); 5662 if (!response.ok) { 5663 // eslint-disable-next-line no-console 5664 console.error('SessionController: HTTP error during session validation', response.status); 5665 this.lastValidationResult = false;
5666 this.lastValidationTime = Date.now(); 5667 return false; 5668 } 5669 const data = await response.json(); 5670 if (data.errors) { 5671 this.lastValidationResult = false; 5672 this.lastValidationTime = Date.now(); 5673 return false; 5674 } 5675 const isValid = data.data?.validSession?.success === true; 5676 this.lastValidationResult = isValid; 5677 this.lastValidationTime = Date.now(); 5678 return isValid; 5679 } catch (error) { 5680 // eslint-disable-next-line no-console 5681 console.error('SessionController: Error validating session', error); 5682 this.lastValidationResult = false; 5683 this.lastValidationTime = Date.now(); 5684 return false; 5685 } finally{ 5686 this.isValidating = false; 5687 } 5688 } 5689 /** 5690 * Clear validation cache 5691 */ clearCache() { 5692 this.lastValidationResult = null; 5693 this.lastValidationTime = null; 5694 } 5695} 5696 5697 5698/* eslint-disable import/prefer-default-export */ 5699function $396d9f0ca7465335$export$48d5542b9e3ba39(document) { 5700 const match = document.cookie.match(/cookieyes-consent=([^;]+)/); 5701 if (!match) return false; 5702 try { 5703 const cookieValue = decodeURIComponent(match[1]); 5704 if (cookieValue.includes(',')) { 5705 const segments = cookieValue.split(',').map((c)=>c.trim()); 5706 if (segments.some((s)=>s.includes(':'))) return segments.some((s)=>s === 'analytics:yes'); 5707 return segments.includes('analytics'); 5708 } 5709 return cookieValue.includes('analytics'); 5710 } catch (error) { 5711 return false; 5712 } 5713} 5714/** 5715 * Consent providers. Each entry isolates the only things that differ between 5716 * integrations: the change-event name, how to read the *initial* analytics 5717 * consent, and how to interpret a change event's detail. Everything else 5718 * (loading/removing trackers, lifecycle) is shared by the controller. 5719 */ const $396d9f0ca7465335$var$PROVIDERS = { 5720 // Third-party CookieYes banner. 5721 cookieyes: { 5722 eventName: 'cookieyes_consent_update', 5723 hasAnalyticsConsent: (document)=>$396d9f0ca7465335$export$48d5542b9e3ba39(document), 5724 isAnalyticsAccepted: (detail)=>Array.isArray(detail?.accepted) && detail.accepted.includes('analytics') 5725 }, 5726 // LP in-house consent widget (lp-consent-widget). It exposes resolved state on 5727 // window.__lpConsent and emits `lp_consent_update` with detail.choices. 5728 'lp-consent': { 5729 eventName: 'lp_consent_update', 5730 hasAnalyticsConsent: (_document, window)=>!!window.__lpConsent?.state?.c?.analytics, 5731 isAnalyticsAccepted: (detail)=>!!detail?.choices?.analytics 5732 } 5733}; 5734class $396d9f0ca7465335$export$bb018224876724b9 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5735 constructor(config, window, document){ 5736 super(config, window, document); 5737 this.consentListener = null; 5738 this.eventName = null; 5739 } 5740 /** 5741 * Destroy and cleanup event listeners 5742 */ destroy() { 5743 if (this.consentListener && this.eventName) { 5744 this.document.removeEventListener(this.eventName, this.consentListener); 5745 this.consentListener = null; 5746 } 5747 } 5748 /** 5749 * Load PostHog and Divolte scripts (idempotent â guarded by element id). 5750 */ loadScript() { 5751 if (!this.document.getElementById('posthog-script')) this.window.luxuryPresence.eventBus.initialize({ 5752 enabled: this.config.eventBus?.enabled 5753 }); 5754 if (!this.document.getElementById('divolte-script')) this.window.luxuryPresence.divolte.initialize({}); 5755 } 5756 /** 5757 * Remove PostHog and Divolte scripts (used on consent withdrawal). 5758 */ removeScripts() { 5759 this.window.luxuryPresence?.eventBus?.destroy?.(); 5760 this.window.luxuryPresence?.divolte?.destroy?.(); 5761 this.document.getElementById('posthog-script')?.remove(); 5762 this.document.getElementById('divolte-script')?.remove(); 5763 } 5764 /** 5765 * Initialize the consent controller for a given provider. 5766 * @param {{ provider: 'cookieyes' | 'lp-consent' }} options 5767 */ async initialize(options = {}) { 5768 const provider = $396d9f0ca7465335$var$PROVIDERS[options.provider]; 5769 if (!provider) return; 5770 this.eventName = provider.eventName; 5771 const isAuthenticated = await this.window.luxuryPresence.session.validateSession(); 5772 if (provider.hasAnalyticsConsent(this.document, this.window) || isAuthenticated) this.loadScript(); 5773 if (!this.consentListener) { 5774 this.consentListener = (eventData)=>{ 5775 if (provider.isAnalyticsAccepted(eventData.detail)) this.loadScript(); 5776 else this.removeScripts(); 5777 }; 5778 this.document.addEventListener(provider.eventName, this.consentListener); 5779 } 5780 } 5781} 5782 5783 5784/* global window */ /* eslint-disable import/prefer-default-export */ /** 5785 * Checks if the current site should redirect homeowner valuation submissions to HST. 5786 * Returns true and performs the redirect if all three gate conditions are met. 5787 * Returns false if the redirect did not fire (caller should continue with existing flow). 5788 * 5789 * @param {string} address - The human-readable address string from the form 5790 * @returns {boolean} 5791 */ function $0ae243a777665fd3$var$checkHstRedirect(address) { 5792 const { config: config } = window.luxuryPresence; 5793 if (config.hstValuationRedirectEnabled && config.mlsEnabled && config.myhomeDashboardEnabled) { 5794 window.location.href = `/home-search/home-valuation?address=${encodeURIComponent(address)}`; 5795 return true; 5796 } 5797 return false;
5798} 5799const $0ae243a777665fd3$export$4225ff17959c7313 = { 5800 checkHstRedirect: $0ae243a777665fd3$var$checkHstRedirect, 5801 destroy () {} 5802}; 5803 5804 5805const $fa0280e505379701$export$1fd2b467a3644d37 = new (0, $dc851cf3939b6ef5$export$ff0712c26d5458f7)({}); 5806const $fa0280e505379701$export$e12694e911a05f40 = new (0, $0ead03fdb95fa9ce$export$d507f25e63650365)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5807const $fa0280e505379701$export$3de614e59a842917 = new (0, $d48f9f944810f613$export$ca3218b7861b9cb0)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5808const $fa0280e505379701$export$fb9b298fdbf31455 = new (0, $68a2d4aa500c7a05$export$61ec8f2b6291ec31)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5809const $fa0280e505379701$export$178cc3119b453c2a = new (0, $e10bd00497be8876$export$3c5380623cf51249)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5810const $fa0280e505379701$export$9376af1a01686f2 = new (0, $ea6901fdf47e0ec1$export$3c19195edca3fa5c)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5811const $fa0280e505379701$export$94d36f806562393e = new (0, $0a0ad49f13c0b8fd$export$ba9afeb75f20f616)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5812const $fa0280e505379701$export$89e66d1faf94651e = new (0, $2b1790aa32f109d1$export$43e435c053a109f6)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5813const $fa0280e505379701$export$7743b07270cd6d6c = new (0, $4f184d9bd3037f15$export$7a6215890afba974)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5814const $fa0280e505379701$export$40a7039551318c37 = new (0, $102039871668a1df$export$e22e4b1be0bb6c61)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5815const $fa0280e505379701$export$8a0bbf7e7652fcfc = new (0, $0d25a3fa2e0f4f92$export$b718a981691eae5c)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5816const $fa0280e505379701$export$b313b3f6544c97a3 = new (0, $dcf72769f009ff26$export$b900d6fe921fd49c)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5817const $fa0280e505379701$export$b526bc4ac03268ef = new (0, $50eefeef8d1f2372$export$8a2bf06fbae4dde)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5818const $fa0280e505379701$export$c0affdb0a7a2ef6 = new (0, $396d9f0ca7465335$export$bb018224876724b9)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5819 5820 5821/** 5822 * Attach controller to window for usage 5823 * @param {string} key 5824 * @param {any} controller 5825 */ function $89a8a84b77163415$var$attachController(key, controller) { 5826 if (window.luxuryPresence[key]) try { 5827 // New version of sdk, destroy previous 5828 window.luxuryPresence[key].destroy(); 5829 } catch (e) { 5830 // eslint-disable-next-line no-console 5831 console.log(e); 5832 } 5833 window.luxuryPresence[key] = controller; 5834} 5835$89a8a84b77163415$var$attachController('config', (0, $fa0280e505379701$export$1fd2b467a3644d37)); 5836$89a8a84b77163415$var$attachController('modals', (0, $fa0280e505379701$export$e12694e911a05f40)); 5837$89a8a84b77163415$var$attachController('buttons', (0, $fa0280e505379701$export$3de614e59a842917)); 5838$89a8a84b77163415$var$attachController('validation', (0, $fa0280e505379701$export$fb9b298fdbf31455)); 5839$89a8a84b77163415$var$attachController('contactForms', (0, $fa0280e505379701$export$178cc3119b453c2a)); 5840$89a8a84b77163415$var$attachController('mapApiLoader', (0, $fa0280e505379701$export$9376af1a01686f2)); 5841$89a8a84b77163415$var$attachController('pagination', (0, $fa0280e505379701$export$94d36f806562393e)); 5842$89a8a84b77163415$var$attachController('expandSliders', (0, $fa0280e505379701$export$89e66d1faf94651e)); 5843$89a8a84b77163415$var$attachController('divolte', (0, $fa0280e505379701$export$7743b07270cd6d6c)); 5844$89a8a84b77163415$var$attachController('eventBus', (0, $fa0280e505379701$export$40a7039551318c37)); 5845$89a8a84b77163415$var$attachController('oauth', (0, $fa0280e505379701$export$8a0bbf7e7652fcfc)); 5846$89a8a84b77163415$var$attachController('qualifyingQuestionsModal', (0, $fa0280e505379701$export$b313b3f6544c97a3)); 5847$89a8a84b77163415$var$attachController('session', (0, $fa0280e505379701$export$b526bc4ac03268ef)); 5848$89a8a84b77163415$var$attachController('consent', (0, $fa0280e505379701$export$c0affdb0a7a2ef6)); 5849$89a8a84b77163415$var$attachController('redirects', (0, $0ae243a777665fd3$export$4225ff17959c7313)); 5850// methods for existing elements (can be removed later after elements updated) 5851window.luxuryPresence.formatSliderPriceRange = (0, $fa0280e505379701$export$94d36f806562393e).formatSliderPriceRange.bind((0, $fa0280e505379701$export$94d36f806562393e)); 5852window.luxuryPresence.formatSliderAreaRange = (0, $fa0280e505379701$export$94d36f806562393e).formatSliderAreaRange.bind((0, $fa0280e505379701$export$94d36f806562393e)); 5853// method below doesn't use 'this', but just to be safe in case of further method updates 5854window.luxuryPresence.getSearchFormValues = (0, $fa0280e505379701$export$94d36f806562393e).getSearchFormValues.bind((0, $fa0280e505379701$export$94d36f806562393e)); 5855window.luxuryPresence.handlebarsHelpersFactory = function(handlebarsInstance) { 5856 return (0, (/*@__PURE__*/$parcel$interopDefault($53de3c160faf5a81$exports
5856)))(handlebarsInstance, { 5857 isEqual (a, b) { 5858 // eslint-disable-next-line 5859 return a == b; 5860 } 5861 }); 5862}; 5863 5864 5865 5866})(); 5867//# sourceMappingURL=lp-sdk.js.map 5868 5869 </script>
5869 5870 5871
5871<script> 5872 var lpGlobalInitialized = false; 5873 5874 function lpGlobalInit () { 5875 if (lpGlobalInitialized) { 5876 return; 5877 } 5878 lpGlobalInitialized = true; 5879 // Initialize wow animation plugin only according to turbolinks 5880 var wow; 5881 if (window.WOW) { 5882 wow = new WOW({ 5883 animateClass: 'animated', 5884 offset: wowOffset, 5885 live: false, 5886 callback: function(box) {} 5887 }); 5888 } 5889 // Initialize handlebar helpers only once 5890 function handlebarsInit() { 5891 if (!window.Handlebars) { 5892 return; 5893 } 5894 HandlebarsIntl.registerWith(Handlebars); 5895 Handlebars.registerHelper(window.luxuryPresence.handlebarsHelpersFactory(Handlebars)); 5896 } 5897 5898 handlebarsInit(); 5899 5900 var modalContentUrl = "/modals.html"; 5901 const modalsContainer = document.getElementById('modals'); 5902 if (modalsContainer) { 5903 fetch(modalContentUrl).then(function(res) { 5904 if (!res.ok) { 5905 throw new Error(`HTTP error! Status: ${res.status}`); 5906 } 5907 return res.text(); 5908 }).then(function(resultText) { 5909 modalsContainer.innerHTML = resultText; 5910 5911 // now we need to execute all script elements 5912 Array.from(modalsContainer.getElementsByTagName('script')).forEach(function (s) { 5913 const script = document.createElement("script"); 5914 script.text = s.innerHTML; 5915 Array.from(s.attributes).forEach(function (a) { 5916 script.setAttribute(a.name, a.value); 5917 }) 5918 s.parentNode.replaceChild(script, s); 5919 }); 5920 5921 try { 5922 window.luxuryPresence.modals.initialize(window.luxuryPresence._modalIncludes); 5923 window.luxuryPresence.contactForms.initialize({ selector: '#modals' }); 5924 window.luxuryPresence.validation.initialize({ selector: '#modals' }); 5925 window.luxuryPresence.oauth.initialize({ 5926 googleSignOnEnabled: true, 5927 iframe: false }); 5928 } catch (e) { 5929 console.log(e); 5930 } 5931 document.querySelectorAll('.modals > .modal .close').forEach(closeModalBtn => { 5932 closeModalBtn.addEventListener('click', function() { 5933 window.lpUI.hideClosestModal(closeModalBtn); 5934 }); 5935 }); 5936 }).catch((e) => console.log(e)); 5937 } 5938 /* Initialize Luxury Presence SDK */ 5939 try { 5940 window.luxuryPresence.config.setCompany("bba607a7-df71-48c9-84ae-e87d443d6b5b", {}); 5941 window.luxuryPresence.config.setWebsite("ae45f4e2-58fe-4d52-921e-febac4ac146e", { 5942 myhomeDashboardEnabled: true, 5943 mlsEnabled: true, 5944 hstValuationRedirectEnabled: true 5945 }); 5946 const url = new URL(window.location.href); 5947 const shareId = url.searchParams.get('shareId'); 5948 window.luxuryPresence.config.setPage("e3f92ef5-826b-4f96-a83c-eba75b69dd7b", { 5949 sourceResource: "", 5950 pageElementId: "", 5951 pageQueryVariables: {}, 5952 shareId: "", 5953 }); 5954 window.luxuryPresence.config.setWebsiteApiGatewayUrl("https://wgw.luxurypresence.com", {}); 5955 window.luxuryPresence.config.setBuyerSellerServiceUrl("https://bss.luxurypresence.com", {}); 5956 window.luxuryPresence.config.setDivolteServiceUrl("https://t.luxurypresence.com", {}); 5957 window.luxuryPresence.config.setEventBusUrl("https://cdn.luxurypresence.com/scripts/event-bus-js", {}); 5958 window.luxuryPresence.config.setEventBus({ 5959 enabled: true, 5960 apiKey: "phc_PMYT31xlmXBaTCwpIRx1v6RrpLVZIuP47N3mOT6KLnr", 5961 url: "https://p.luxurypresence.com", 5962 }); 5963 window.luxuryPresence.config.setTemplateId("1d3967df-14f2-449d-87b7-bd68bf93906f", {}); 5964 window.luxuryPresence.config.setLeadIngesterUrl("https://leads.luxurypresence.com", {}); 5965 window.luxuryPresence.buttons.initialize({}); 5966 window.luxuryPresence.eventBus.initialize({}); 5967 window.luxuryPresence.contactForms.initialize({}); 5968 window.luxuryPresence.validation.initialize({}); 5969 window.luxuryPresence.expandSliders.initialize({}); 5970 window.luxuryPresence.oauth.initialize({ googleSignOnEnabled: true, iframe: false }); 5971 window.luxuryPresence.qualifyingQuestionsModal.initialize({ qualifyingQuestionsModalEnabled: true }); 5972 window.luxuryPresence.divolte.initialize({}); 5973 } catch (e) { 5974 console.log(e); 5975 } 5976 5977 /* Inject Section Specific Javascript */ 5978 try { 5979(function init(sectionDomId) { 5980 const section = document.getElementById(sectionDomId); 5981 const contactTitle = section.querySelector('.js-contact-title'); 5982 const emailInput = section.querySelector('.js-email-input'); 5983 const emailRegex = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~\-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+/; 5984 emailInput.addEventListener('input', function () { 5985 if (emailRegex.test(this.value)) { 5986 this.setCustomValidity(''); 5987 } else { 5988 this.setCustomValidity('Please enter a valid email address.'); 5989 } 5990 this.reportValidity(); 5991 }); 5992 const inputName = section.querySelector('.js-input-name'); 5993 inputName.addEventListener('keyup', function () { 5994 if (this.value.replaceAll(/\d/g, "").trim().split(" ").length < 2 || this.value.replaceAll(/\d/g, "").trim() === "") { 5995 this.setCustomValidity("Please input your first name and last name."); 5996 } else { 5997 this.setCustomValidity(""); 5998 } 5999 }); 6000 function setHttp(link) { 6001 if (link.search(/^http[s]?\:\/\/|\//) == -1) { 6002 link = 'https://' + link; 6003 } 6004 return link; 6005 } 6006 const socialLinks = section.querySelectorAll('.lp-socials__link, .contact-form-modal-details__socials-l
6006ink'); 6007 socialLinks.forEach(linkElement => { 6008 const href = linkElement.getAttribute('href'); 6009 if (href) { 6010 linkElement.setAttribute('href', setHttp(href)); 6011 } 6012 }); 6013 if (contactTitle.length < 1) return; 6014 let words = contactTitle.textContent.split(" "); 6015 if (words.length > 1) { 6016 contactTitle.innerHTML = `<span class="contact__title-top">${words[0]} </span><span class="contact__title-bottom">${words.splice(1).join(" ")}</span>`; 6017 } 6018 const addRedirectToForm = () => { 6019 const redirectLink = document.getElementById(`${sectionDomId}`).querySelector('.js-redirect-link'); 6020 const form = document.getElementById(`${sectionDomId}`).querySelector('.js-form'); 6021 function redirectPage(link) { 6022 if (link) { 6023 const rawHref = link.getAttribute('href'); 6024 link.setAttribute('href', setHttp(rawHref)); 6025 link.click(); 6026 } 6027 } 6028 function setHttp(link) { 6029 if (link.search(/^http[s]?\:\/\/|\//) == -1) { 6030 link = 'http://' + link; 6031 } 6032 return link; 6033 } 6034 form.addEventListener("reset", function () { 6035 redirectPage(redirectLink); 6036 }); 6037 }; 6038 addRedirectToForm(); 6039})('modal-global-contact-us'); 6040} catch(e) { 6041console.log(e); 6042} 6043 6044 try { 6045(function init(sectionDomId) { 6046 (function () { 6047 const observer = new IntersectionObserver(entries => { 6048 entries.forEach(entry => { 6049 if (entry.isIntersecting) entry.target.classList.add('is-in-viewport'); 6050 }); 6051 }, { 6052 threshold: 0.5 6053 }); 6054 const initialElements = document.querySelectorAll('[data-animate="true"]'); 6055 initialElements.forEach(element => observer.observe(element)); 6056 const container = document.querySelector('.data-container'); 6057 if (container) { 6058 const mutationObserver = new MutationObserver(mutations => { 6059 mutations.forEach(mutation => { 6060 if (mutation.type === 'childList') { 6061 mutation.addedNodes.forEach(node => { 6062 if (node.nodeType === 1 && node.getAttribute('data-animate') === 'true') observer.observe(node); 6063 }); 6064 } 6065 }); 6066 }); 6067 mutationObserver.observe(container, { 6068 childList: true, 6069 subtree: true 6070 }); 6071 } 6072 })(); 6073 const sectionSelector = document.getElementById(sectionDomId); 6074 const searchToggleSelector = sectionSelector.querySelector('.js-search-toggle-btn'); 6075 const searchWrapperSelector = sectionSelector.querySelector('.js-header-search'); 6076 const header = sectionSelector.querySelector('.js-header'); 6077 let lastScrollTop = 0; 6078 const subNavOpeners = sectionSelector.querySelectorAll('.js-subnav-opener'); 6079 subNavOpeners.forEach(opener => { 6080 opener.addEventListener('click', function (e) { 6081 e.preventDefault(); 6082 e.stopPropagation(); 6083 const parent = opener.parentElement; 6084 subNavOpeners.forEach(otherOpener => { 6085 if (otherOpener !== opener) { 6086 otherOpener.parentElement.classList.remove('header-navigation__item--sub-nav--opened'); 6087 } 6088 }); 6089 parent.classList.toggle('header-navigation__item--sub-nav--opened'); 6090 }); 6091 }); 6092 document.addEventListener('click', function (e) { 6093 if (!e.target.closest('.js-subnav-opener') && !e.target.closest('.header-navigation__item--sub-nav--opened')) { 6094 subNavOpeners.forEach(opener => { 6095 opener.parentElement.classList.remove('header-navigation__item--sub-nav--opened'); 6096 }); 6097 } 6098 }); 6099 function initHeaderColors() { 6100 try { 6101 var colorStr = window.getComputedStyle(header).getPropertyValue("--backgroundColor"); 6102 var bgColor = makeColor(colorStr); 6103 var isDark = (bgColor.rgba.r * 299 + bgColor.rgba.g * 587 + bgColor.rgba.b * 114) / 1000 < 128; 6104 sectionSelector.classList.add('is-background-color-' + (isDark ? 'dark' : 'light')); 6105 } catch (e) {} 6106 } 6107 function makeColor(str) { 6108 var color = { 6109 rgba: { 6110 r: 0, 6111 g: 0, 6112 b: 0, 6113 a: 0 6114 } 6115 }; 6116 if (str && str.match) { 6117 var m = str.match(/rgba?\s*\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\.0-9]+))?\)/); 6118 if (m) { 6119 color.rgba.r = parseInt(m[1]); 6120 color.rgba.g = parseInt(m[2]); 6121 color.rgba.b = parseInt(m[3]); 6122 color.rgba.a = parseFloat(m[4]); 6123 if (isNaN(color.rgba.a)) { 6124 color.rgba.a = 1; 6125 } 6126 } 6127 } 6128 return color; 6129 } 6130 initHeaderColors(); 6131 var navEl = document.querySelector('nav > header') || document.querySelector('nav'); 6132 var navbarHeight = navEl ? navEl.offsetHeight : 0; 6133 if (navEl) { 6134 if (window.ResizeObserver) { 6135 new ResizeObserver(function (entries) { 6136 if (!entries.length) return; 6137 navbarHeight = entries[0].target.offsetHeight; 6138 }).observe(navEl); 6139 } else { 6140 window.addEventListener('resize', function () { 6141 navbarHeight = navEl.offsetHeight; 6142 }); 6143 } 6144 } 6145 function isLocomotiveActive() { 6146 return document.body.hasAttribute('data-scroll-container'); 6147 } 6148 function updateHeaderVisibility(currentScrollTop) {
6149 if (!searchWrapperSelector || searchWrapperSelector.style.display !== 'block') { 6150 if (currentScrollTop === 0) header.classList.remove('header--hide');else if (currentScrollTop > lastScrollTop) header.classList.add('header--hide');else header.classList.remove('header--hide'); 6151 } 6152 lastScrollTop = currentScrollTop; 6153 } 6154 function customHeaderHideShow(currentScrollTop) { 6155 if (!navEl) return; 6156 if (currentScrollTop > 0) { 6157 navEl.classList.add('scroll'); 6158 } else { 6159 navEl.classList.remove('scroll'); 6160 } 6161 if (currentScrollTop > lastScrollTop && currentScrollTop > navbarHeight) { 6162 document.documentElement.style.setProperty('--global-header-height', '0px'); 6163 } else { 6164 document.documentElement.style.setProperty('--global-header-height', navbarHeight + 'px'); 6165 } 6166 } 6167 window.addEventListener('scroll', function () { 6168 if (isLocomotiveActive()) return; 6169 var currentScrollTop = window.scrollY || document.documentElement.scrollTop; 6170 updateHeaderVisibility(currentScrollTop); 6171 }); 6172 var isTicking = false; 6173 var latestLocoScrollY = 0; 6174 window.addEventListener('locomotive-scroll', function (e) { 6175 latestLocoScrollY = e.detail.scrollY; 6176 if (!isTicking) { 6177 window.requestAnimationFrame(function () { 6178 customHeaderHideShow(latestLocoScrollY); 6179 updateHeaderVisibility(latestLocoScrollY); 6180 isTicking = false; 6181 }); 6182 isTicking = true; 6183 } 6184 }); 6185 if (searchToggleSelector !== null) { 6186 searchToggleSelector.addEventListener('click', function () { 6187 if (searchWrapperSelector.style.display === 'block') { 6188 searchWrapperSelector.style.display = 'none'; 6189 } else { 6190 searchWrapperSelector.style.display = 'block'; 6191 header.classList.remove('header--hide'); 6192 } 6193 }); 6194 document.addEventListener('click', function (evt) { 6195 if (!searchToggleSelector.contains(evt.target) && !searchWrapperSelector.contains(evt.target)) searchWrapperSelector.style.display = 'none'; 6196 }); 6197 } 6198 window.luxuryPresence.mapApiLoader.load().then(() => {
6199 if (!searchWrapperSelector) return; 6200 const templateHTML = sectionSelector.querySelector('.js-card-template')?.innerHTML; 6201 const itemTemplate = templateHTML && Handlebars.compile(templateHTML); 6202 const searchMls = searchWrapperSelector.dataset.searchMls === 'true'; 6203 const searchProperties = searchWrapperSelector.dataset.searchProperties === 'true'; 6204 const selectedCountries = searchWrapperSelector.dataset.locations; 6205 const searchInputSelector = sectionSelector.querySelector('.js-header-search-input'); 6206 const searchIconSelector = sectionSelector.querySelector('.js-header-btn-search'); 6207 const closeIconSelector = sectionSelector.querySelector('.js-header-btn-close'); 6208 const searchResultsSelector = sectionSelector.querySelector('.js-header-search-results-holder'); 6209 const resultsContainerSelector = sectionSelector.querySelector('.js-header-results-container'); 6210 const loaderSelector = sectionSelector.querySelector('.js-header-spinner'); 6211 const predefinedSelector = sectionSelector.querySelector('.js-header-search-results-predefined'); 6212 const nothingFoundSelector = sectionSelector.querySelector('.js-header-spinner-nothing-found'); 6213 const service = new google.maps.places.AutocompleteService(); 6214 const geocoder = new google.maps.Geocoder(); 6215 var keyword; 6216 var leasePropertyState = false; 6217 var leasePropertySearched; 6218 var isLoading = false; 6219 var newSearchTimer; 6220 var placesResult = {}; 6221 function updateSearchDisplay() { 6222 var hasResults = !!resultsContainerSelector.innerHTML; 6223 var hasPredefined = predefinedSelector.querySelectorAll('a:not(.not-processed)').length > 0; 6224 nothingFoundSelector.style.display = 'none'; 6225 if (hasResults) { 6226 predefinedSelector.style.display = 'none'; 6227 loaderSelector.style.display = 'none'; 6228 resultsContainerSelector.style.display = 'block'; 6229 } else if (isLoading) { 6230 resultsContainerSelector.style.display = 'none'; 6231 predefinedSelector.style.display = 'none'; 6232 loaderSelector.style.display = 'block'; 6233 } else if (hasPredefined) { 6234 resultsContainerSelector.style.display = 'none'; 6235 loaderSelector.style.display = 'none'; 6236 predefinedSelector.style.display = 'block'; 6237 } else { 6238 resultsContainerSelector.style.display = 'none'; 6239 loaderSelector.style.display = 'none'; 6240 predefinedSelector.style.display = 'none'; 6241 if (keyword) nothingFoundSelector.style.display = 'block'; 6242 } 6243 } 6244 function moveSelection(up) { 6245 var count = resultsContainerSelector.innerHTML ? resultsContainerSelector : predefinedSelector; 6246 var links = count.querySelectorAll('.results-link'); 6247 if (!links.length) return; 6248 var idx = -1; 6249 links.forEach((link, i) => { 6250 if (idx === -1 && link.classList.contains('header-search-results__link--selected')) idx = i; 6251 }); 6252 var newIdx; 6253 if (up) { 6254 newIdx = idx - 1; 6255 if (newIdx < 0) newIdx = links.length - 1; 6256 } else { 6257 newIdx = idx + 1; 6258 if (newIdx > links.length - 1) newIdx = 0; 6259 } 6260 if (newIdx !== idx) { 6261 searchResultsSelector.querySelector('.header-search-results__link--selected')?.classList.remove('header-search-results__link--selected'); 6262 links[newIdx].classList.add('header-search-results__link--selected'); 6263 } 6264 } 6265 function startSearch() { 6266 const term = searchInputSelector.value; 6267 if (term === keyword && leasePropertySearched === leasePropertyState) return; 6268 leasePropertySearched = leasePropertyState; 6269 keyword = term; 6270 if (term) doSearch(term, leasePropertySearched);else setResults(null); 6271 } 6272 function mlsSearch(term) { 6273 var query = { 6274 omnibox: term, 6275 limit: 5 6276 }; 6277 return superagent.get(window.location.protocol + '//' + window.location.host + '/home-search/listings.json').query(query).then(function (res) { 6278 if (!Array.isArray(res.body)) return []; 6279 return res.body.map(function (item) { 6280 return { 6281 id: item.id, 6282 display_address: [item.address_1, item.city, [item.state, item.postal_code].filter(function (i) { 6283 return i; 6284 }).join(' ')].filter(function (i) { 6285 return i; 6286 }).join(', ') 6287 }; 6288 }); 6289 }, function (e) { 6290 console.log(e); 6291 return []; 6292 }); 6293 } 6294 function resolveGeoBounds(placeId, description) { 6295 return new Promise(function (resolve, reject) { 6296 geocoder.geocode({ 6297 'placeId': placeId 6298 }, function (results, status) { 6299 if (status != 'OK') return resolve(null); 6300 var result = results[0]; 6301 var bounds = new google.maps.LatLngBounds();
6302 bounds.extend(result.geometry.viewport.getSouthWest()); 6303 bounds.extend(result.geometry.viewport.getNorthEast()); 6304 result.geometry.bounds = bounds.toJSON(); 6305 var placeUrl = window.luxuryPresence.makeMlsLink(result); 6306 return resolve({ 6307 placeUrl: placeUrl, 6308 description: description 6309 }); 6310 }); 6311 }); 6312 } 6313 function addRestrictions(request) { 6314 switch (selectedCountries) { 6315 case 'Worldwide': 6316 return request; 6317 case 'US & Canada': 6318 request.componentRestrictions = { 6319 country: ['us', 'ca'] 6320 }; 6321 return request; 6322 default: 6323 request.componentRestrictions = { 6324 country: 'us' 6325 }; 6326 return request; 6327 } 6328 } 6329 function resolveAutocomplete(term, deeper) { 6330 var request = addRestrictions({ 6331 input: term, 6332 types: deeper ? ['geocode'] : ['(regions)'], 6333 limit: 1 6334 }); 6335 return new Promise(function (resolve, reject) { 6336 service.getPlacePredictions(request, function (results, status) { 6337 if (status !== google.maps.places.PlacesServiceStatus.OK) { 6338 resolve(null); 6339 return; 6340 } 6341 if (!Array.isArray(results)) { 6342 resolve(null); 6343 return; 6344 } 6345 if (deeper) { 6346 var allowedTypes = { 6347 'neighborhood': true, 6348 'administrative_area_level_3': true, 6349 'administrative_area_level_4': true, 6350 'administrative_area_level_5': true, 6351 'sublocality_level_1': true, 6352 'sublocality_level_2': true, 6353 'sublocality_level_3': true 6354 }; 6355 results = results.filter(function (res) { 6356 return res.types.find(t => allowedTypes[t]); 6357 }); 6358 } 6359 if (!results.length) { 6360 resolve(null); 6361 return; 6362 } 6363 resolve(results); 6364 }); 6365 }); 6366 } 6367 function placesSearch(term, ignoreInput) { 6368 if (placesResult.term !== term) { 6369 placesResult = { 6370 term: term, 6371 result: resolveAutocomplete(term).then(function (results) { 6372 if (!ignoreInput && term !== keyword) return null; 6373 if (ignoreInput && !results) return resolveAutocomplete(term, true); 6374 return results; 6375 }).then(function (results) { 6376 if (!ignoreInput && term !== keyword || !results) return []; 6377 var geoResultPromises = results.map(function (result) { 6378 return resolveGeoBounds(result.place_id, result.description); 6379 }); 6380 return Promise.all(geoResultPromises).then(function (geoLinks) { 6381 return geoLinks.filter(function (i) { 6382 return i; 6383 }).slice(0, 3); 6384 }); 6385 }) 6386 }; 6387 } 6388 return placesResult.result; 6389 } 6390 function propertiesSearch(term) { 6391 var payload = { 6392 companyId: window.luxuryPresence.defaults.companyId, 6393 search: term, 6394 offset: 0, 6395 limit: 4, 6396 statusIds: ["5f528253-abb7-484e-95c3-330269ac1105", "959c11cf-8655-4f91-874c-292b0ab7ea6b", "a0012964-4f51-4430-abf8-6547c5ab6441", "df04ccbe-4621-4140-a504-ee1a17430bb7", "88b4ace6-f39b-4b25-a051-8f6dba976833", "96031d77-bbe5-4de3-90d8-1e4e70de8ca8"] 6397 }; 6398 return superagent.post(window.luxuryPresence.defaults.apiGatewayUrl + '/graphql').send({ 6399 query: window.luxuryPresence.defaults.gql.properties, 6400 variables: payload 6401 }).then(function (res) { 6402 return (res.body.data || {}).properties || []; 6403 }, function (err) { 6404 return []; 6405 }); 6406 } 6407 function doSearch(term, leaseProperty) { 6408 isLoading = true; 6409 updateSearchDisplay(); 6410 Promise.all([searchMls ? mlsSearch(term, leaseProperty) : [], searchMls ? placesSearch(term) : [], searchProperties ? propertiesSearch(term, leaseProperty) : []]).then(function (res) { 6411 if (term !== keyword) return; 6412 setResults(res[0], res[1], res[2], leaseProperty); 6413 }); 6414 } 6415 function makeResultsBlockHtml(title, elementClass, links) { 6416 if (!links || !links.length) return '';else return '<div class="header-search-results__block"><div class="header-search-results__title">' + title + '</div><div class="' + elementClass + '">' + links.join('') + '</div></div>'; 6417 } 6418 function setResults(mlsResults, placesResults, propertiesResults, leaseProperty) { 6419 searchResultsSelector.querySelector('.header-search-results__link--selected')?.classList.remove('header-search-results__link--selected'); 6420 isLoading = false;
6421 updateSearchDisplay(); 6422 if (!mlsResults) { 6423 resultsContainerSelector.innerHTML = ''; 6424 updateSearchDisplay(); 6425 return; 6426 } 6427 if (!mlsResults.length && !placesResults.length && !propertiesResults.length) { 6428 updateSearchDisplay(); 6429 return; 6430 } 6431 var propertiesHtml = makeResultsBlockHtml('Featured Properties', 'header-properties', propertiesResults.map(function (result) { 6432 return itemTemplate(result); 6433 })); 6434 var placesHtml = makeResultsBlockHtml('Locations', '', placesResults.map(function (result) { 6435 return `<a class="header-search-results__link lp-icon" href="${result.placeUrl}${leaseProperty ? '&lease_property=true' : ''}">${result.description}</a>`; 6436 })); 6437 var mlsHtml = makeResultsBlockHtml('Address', '', mlsResults.map(function (result) { 6438 return `<a href="/home-search/listings/${result.id}" class="header-search-results__link lp-icon">${result.display_address}</a>`; 6439 })); 6440 resultsContainerSelector.innerHTML = propertiesHtml + placesHtml + mlsHtml; 6441 updateSearchDisplay(); 6442 } 6443 var neighborhoods = null; 6444 function fetchNeighborhoods() { 6445 var url = window.luxuryPresence.defaults.apiGatewayUrl; 6446 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {neighborhoods (companyId: $companyId, offset: $offset, limit: $limit) {id, name, mlsLink, googlePlaceData}}'; 6447 var payload = {}; 6448 payload.offset = 0; 6449 payload.limit = 100; 6450 payload.companyId = window.luxuryPresence.defaults.companyId; 6451 return superagent.post(url + '/graphql').withCredentials().send({ 6452 query: gqlQuery, 6453 variables: payload 6454 }).then(function (res) { 6455 neighborhoods = res.body.data && res.body.data.neighborhoods; 6456 if (neighborhoods && !neighborhoods.length) { 6457 neighborhoods = null; 6458 } 6459 }); 6460 } 6461 function setPredefinedNeighborhoods() { 6462 return fetchNeighborhoods().then(function () { 6463 if (!neighborhoods) return; 6464 var placesHtml = makeResultsBlockHtml('Locations', neighborhoods.map(function (item) { 6465 return `<a class="header-search-results__link lp-icon" href="${item.mlsLink}${leasePropertyState ? '&lease_property=true' : ''}">${item.googlePlaceData && item.googlePlaceData.formatted_address || item.name}</a>`; 6466 })); 6467 predefinedSelector.innerHTML = placesHtml; 6468 updateSearchDisplay(); 6469 }); 6470 } 6471 if (searchMls) setPredefinedNeighborhoods(); 6472 closeIconSelector.style.opacity = '0'; 6473 closeIconSelector.style.pointerEvents = 'none'; 6474 searchInputSelector.setAttribute('inputmode', 'search'); 6475 searchInputSelector.addEventListener('input', function () { 6476 if (this.value === '') { 6477 closeIconSelector.style.opacity = '0'; 6478 closeIconSelector.style.pointerEvents = 'none'; 6479 searchIconSelector.style.opacity = '1'; 6480 searchIconSelector.style.pointerEvents = 'auto'; 6481 } else { 6482 closeIconSelector.style.opacity = '1'; 6483 closeIconSelector.style.pointerEvents = 'auto'; 6484 searchIconSelector.style.opacity = '0'; 6485 searchIconSelector.style.pointerEvents = 'none'; 6486 } 6487 window.clearTimeout(newSearchTimer); 6488 newSearchTimer = window.setTimeout(startSearch, 300); 6489 }); 6490 searchInputSelector.addEventListener('keydown', function (ev) { 6491 if (ev.which === 38) { 6492 ev.preventDefault(); 6493 moveSelection(true); 6494 return false; 6495 } 6496 if (ev.which === 40) { 6497 ev.preventDefault(); 6498 moveSelection(); 6499 return false; 6500 } 6501 if (ev.which === 13) { 6502 let $lnk = searchResultsSelector.querySelector('.header-search-results__link--selected'); 6503 if ($lnk) { 6504 $lnk.click(); 6505 searchActive = false; 6506 updateSearchDisplay(); 6507 } else { 6508 $searchBtn.click(); 6509 } 6510 } 6511 }); 6512 closeIconSelector.addEventListener('click', function () { 6513 searchInputSelector.value = ''; 6514 searchInputSelector.dispatchEvent(new Event('input')); 6515 searchActive = false;
6516 updateSearchDisplay(); 6517 }); 6518 searchResultsSelector.addEventListener('mouseenter', function (ev) { 6519 searchResultsSelector.querySelector('.header-search-results__link--selected')?.classList.remove('header-search-results__link--selected'); 6520 }); 6521 function outsideClickHandler(e) { 6522 if (searchWrapperSelector.contains(e.target)) return; 6523 searchActive = false; 6524 updateSearchDisplay(); 6525 } 6526 document.addEventListener('click', outsideClickHandler); 6527 }); 6528})('global-navbar'); 6529} catch(e) { 6530console.log(e); 6531} 6532 try { 6533(function init(sectionDomId, options) { 6534 var elementSelector = options.elementSelector || 'nav'; 6535 var element = document.querySelector(elementSelector); 6536 var sections = document.querySelectorAll('body > section'); 6537 var subNavContainers = element.querySelectorAll('.sub-nav'); 6538 var initialBackgroundColor = options.initialBackgroundColor || element.style.backgroundColor; 6539 var backgroundColorScroll = options.backgroundColorScroll || '#191919'; 6540 var fontColor = options.fontColor || '#fff'; 6541 var fontColorScroll = options.fontColorScroll || '#000'; 6542 var didScroll = false; 6543 var lastScrollTop = 0; 6544 var delta = 5; 6545 6546 // eslint-disable-next-line sonarjs/no-collapsible-if 6547 if (wow) { 6548 // Wowjs might not trigger for position fixed elements 6549 if (window.scrollY > 0) { 6550 element.querySelectorAll('.wow').forEach(el => wow.show(el)); 6551 } 6552 } 6553 function adjustNavPinState() { 6554 // Remove 'visible' class from all subNavContainers 6555 for (let i = 0; i < subNavContainers.length; i++) { 6556 subNavContainers[i].classList.remove('visible'); 6557 } 6558 if (window.scrollY > 0) { 6559 element.style.backgroundColor = backgroundColorScroll; 6560 element.style.color = fontColorScroll; 6561 element.classList.add('scroll'); 6562 } else { 6563 element.style.backgroundColor = initialBackgroundColor; 6564 element.style.color = fontColor; 6565 element.classList.remove('scroll'); 6566 } 6567 } 6568 adjustNavPinState(); 6569 function onScroll() { 6570 didScroll = true; 6571 } 6572 window.addEventListener('scroll', onScroll); 6573 6574 // eslint-disable-next-line 6575 var debounceInterval = setInterval(() => { 6576 if (didScroll) { 6577 // eslint-disable-next-line 6578 hasScrolled(); 6579 didScroll = false; 6580 } 6581 }, 250); 6582 function hasScrolled() { 6583 adjustNavPinState(); 6584 // eslint-disable-next-line 6585 var navbarHeight = element.offsetHeight; 6586 // eslint-disable-next-line 6587 var st = window.scrollY; 6588 6589 // Make sure they scroll more than delta 6590 if (Math.abs(lastScrollTop - st) <= delta) { 6591 return; 6592 } 6593 if (st > lastScrollTop && st > navbarHeight) { 6594 // Scroll Down 6595 element.style.transform = `translateY(-${navbarHeight}px) scaleY(0)`; 6596 document.documentElement.style.setProperty('--global-header-height', '0px'); 6597 6598 // Some sections "follow" the navbar with class sticky (i.e. Producer search) 6599 sections.forEach(section => { 6600 if (section.classList.contains('sticky')) { 6601 section.style.top = '0px'; 6602 } 6603 }); 6604 } else { 6605 // Scroll Up 6606 // eslint-disable-next-line 6607 if (st + window.innerHeight < document.documentElement.scrollHeight) { 6608 element.style.transform = `translateY(0) scaleY(1)`; 6609 document.documentElement.style.setProperty('--global-header-height', `${navbarHeight}px`); 6610 sections.forEach(section => { 6611 if (section.classList.contains('sticky')) { 6612 section.style.top = `${navbarHeight}px`; 6613 } 6614 }); 6615 } 6616 } 6617 lastScrollTop = st; 6618 } 6619})('NAVBAR', JSON.parse("{\"fontColor\":\"#161515\",\"elementSelector\":\".header\",\"fontColorScroll\":\"#fff\",\"backgroundColorScroll\":\"rgba(26,26,26, .8)\",\"initialBackgroundColor\":\"#fff\"}")); 6620} catch(e) { 6621console.log(e); 6622} 6623 try { 6624(function init(sectionDomId) { 6625 if (sectionDomId === "main-section") sectionDomId = "global-sidemenu"; 6626 var sidemenu = document.getElementById(sectionDomId); 6627 var openers = sidemenu.querySelectorAll('.js-sub-nav-opener'); 6628 var items = sidemenu.querySelectorAll('.js-idemenu-navigation-item'); 6629 var watermark = document.querySelector('.js-sidemenu-watermark'); 6630 var onShow = function () { 6631 window.lpUI.requestScrollLock(sectionDomId); 6632 }; 6633 var onHide = function () { 6634 window.lpUI.releaseScrollLock(sectionDomId); 6635 }; 6636 sidemenu.addEventListener('lpui-show', onShow); 6637 sidemenu.addEventListener('lpui-hide', onHide);
6638 openers.forEach(function (opener) { 6639 opener.addEventListener('click', function (event) { 6640 event.preventDefault(); 6641 this.classList.toggle('sidemenu-navigation__link--active'); 6642 this.classList.toggle('sidemenu-navigation__link--sub-nav-active'); 6643 }); 6644 }); 6645 document.body.addEventListener('click', function (evt) { 6646 if (!evt.target.closest('.sidemenu') && !evt.target.closest('.js-sidemenu-opener')) { 6647 window.lpUI.sideMenuHide(); 6648 } 6649 }); 6650 const handleLinkClick = function (href) { 6651 if (href && href.match(/^#[^#].*$/)) { 6652 window.lpUI.sideMenuHide(); 6653 } 6654 }; 6655 const mainNavLinks = sidemenu.querySelectorAll('.sidemenu-navigation__link'); 6656 mainNavLinks.forEach(function (link) { 6657 link.addEventListener('click', function (event) { 6658 const href = this.getAttribute('href'); 6659 handleLinkClick(href); 6660 }); 6661 }); 6662 const subNavLinks = sidemenu.querySelectorAll('.sidemenu-navigation-sub-nav__link'); 6663 subNavLinks.forEach(function (link) { 6664 link.addEventListener('click', function (event) { 6665 const href = this.getAttribute('href'); 6666 handleLinkClick(href); 6667 }); 6668 }); 6669 if (watermark) { 6670 items.forEach(function (item, index) { 6671 item.setAttribute("data-id", index <= 9 ? "0" + (index + 1) : index + 1); 6672 }); 6673 items.forEach(function (item) { 6674 item.addEventListener('mouseenter', function () { 6675 watermark.setAttribute("data-id", item.getAttribute("data-id")); 6676 watermark.style.opacity = 1; 6677 }); 6678 item.addEventListener('mouseleave', function () { 6679 watermark.style.opacity = 0; 6680 }); 6681 }); 6682 } 6683})('global-sidemenu'); 6684} catch(e) { 6685console.log(e); 6686} 6687 6688 try { 6689(function init(sectionDomId) { 6690 const sectionDom = document.getElementById(sectionDomId); 6691 const backToTopBtn = sectionDom.querySelector('.js-back-to-top'); 6692 backToTopBtn?.addEventListener('click', () => { 6693 window.scrollTo({ 6694 top: 0, 6695 behavior: 'smooth' 6696 }); 6697 }); 6698})('global-footer'); 6699} catch(e) { 6700console.log(e); 6701} 6702 6703 try { 6704(function init(sectionDomId) { 6705 const customDropdownHTML = ` 6706 <div class="custom-select" role="presentation"> 6707 <button class="custom-select-toggle" type="button" aria-haspopup="true" aria-expanded="false" role="combobox"></button> 6708 <div class="custom-select-menu"><div class="custom-select-content"></div></div> 6709 </div>`; 6710 const hideOriginalSelect = originalSelect => { 6711 originalSelect.classList.remove(...originalSelect.classList); 6712 originalSelect.classList.add("sr-only"); 6713 }; 6714 const openCustomSelect = customSelectToggle => {
6715 let customSelectMenu = customSelectToggle.nextElementSibling; 6716 customSelectMenu.classList.add("open"); 6717 customSelectToggle.classList.add("open"); 6718 customSelectToggle.ariaExpanded = true; 6719 }; 6720 const closeCustomSelect = customSelectToggle => { 6721 let customSelectMenu = customSelectToggle.nextElementSibling; 6722 customSelectMenu.classList.remove("open"); 6723 customSelectToggle.classList.remove("open"); 6724 customSelectToggle.ariaExpanded = false; 6725 }; 6726 const inheritAttributes = (originalSelect, customSelectToggle, customSelectMenu) => { 6727 customSelectToggle.className = customSelectToggle.className.concat(' ', originalSelect.className); 6728 customSelectToggle.id = originalSelect.getAttribute("id") + "custom"; 6729 customSelectToggle.dataset.name = originalSelect.getAttribute("name"); 6730 customSelectMenu.setAttribute("aria-labelledby", customSelectToggle.id); 6731 }; 6732 const initiateQuerySelectors = customSelect => { 6733 let customSelectToggle = customSelect.querySelector('.custom-select-toggle'), 6734 customSelectMenu = customSelect.querySelector('.custom-select-menu'), 6735 customSelectContent = customSelect.querySelector('.custom-select-content'); 6736 return { 6737 customSelectToggle, 6738 customSelectMenu, 6739 customSelectContent 6740 }; 6741 }; 6742 const initiateToggleText = (originalSelect, customSelectToggle) => { 6743 const dataLabelOption = originalSelect.querySelector("[data-label]"); 6744 const isMultiple = originalSelect?.multiple; 6745 if (!isMultiple) { 6746 const toggleText = dataLabelOption && dataLabelOption.dataset.label || originalSelect.querySelector(":checked")?.textContent || originalSelect.querySelector("option")?.textContent; 6747 customSelectToggle.classList.add("is-placeholder"); 6748 customSelectToggle.innerHTML = toggleText; 6749 } else { 6750 const placeholderText = dataLabelOption && dataLabelOption.dataset.label || originalSelect.querySelector("option")?.textContent || "Select"; 6751 const selectedOptions = Array.from(originalSelect.querySelectorAll("option:checked")).filter(opt => opt.value !== ""); 6752 if (selectedOptions.length === 0) { 6753 customSelectToggle.innerHTML = placeholderText; 6754 customSelectToggle.classList.add("is-placeholder"); 6755 } else if (selectedOptions.length === 1) { 6756 customSelectToggle.innerHTML = selectedOptions[0].textContent; 6757 customSelectToggle.classList.remove("is-placeholder"); 6758 } else { 6759 customSelectToggle.innerHTML = `${selectedOptions[0].textContent} +${selectedOptions.length - 1}`; 6760 customSelectToggle.classList.remove("is-placeholder"); 6761 } 6762 } 6763 }; 6764 const convertOptionsToButtons = (originalSelect, customSelectContent) => { 6765 originalSelect.querySelectorAll('option').forEach(option => { 6766 const button = createButtonFromOption(option); 6767 customSelectContent.appendChild(button); 6768 }); 6769 }; 6770 const createButtonFromOption = option => { 6771 let button = document.createElement('button'); 6772 button.className = "custom-select-item"; 6773 button.setAttribute("role", "option"); 6774 button.setAttribute("type", "button"); 6775 button.textContent = option.textContent; 6776 button.setAttribute('value', option.value); 6777 if (option.disabled) { 6778 button.setAttribute("disabled", "true"); 6779 } 6780 if (option.selected && option.value !== "") { 6781 button.classList.add("is-selected"); 6782 button.setAttribute("aria-selected", "true"); 6783 } 6784 return button; 6785 }; 6786 const initiateEventListeners = (originalSelect, customSelectToggle, customSelectMenu) => { 6787 const isMultiple = originalSelect?.multiple; 6788 originalSelect.addEventListener("focus", function () { 6789 openCustomSelect(customSelectToggle); 6790 customSelectToggle.focus(); 6791 }); 6792 customSelectToggle.addEventListener("click", function () { 6793 if (customSelectToggle.getAttribute("aria-expanded") === "true") { 6794 closeCustomSelect(customSelectToggle); 6795 } else { 6796 openCustomSelect(customSelectToggle); 6797 } 6798 }); 6799 customSelectMenu.addEventListener("click", function (event) { 6800 if (event.target.classList.contains('custom-select-item')) { 6801 const clickedValue = event.target.value; 6802 const clickedOption = originalSelect.querySelector(`option[value="${clickedValue}"]`); 6803 const placeholderOption = originalSelect.querySelector("[data-label]"); 6804 const placeholderText = placeholderOption?.dataset.label || originalSelect.querySelector("option")?.textContent || "Select"; 6805 if (!isMultiple) { 6806 let opts = customSelectMenu.querySelectorAll(".is-selected"); 6807 [].forEach.call(opts, function (el) { 6808 el.classList.remove("is-selected"); 6809 }); 6810 event.target.classList.add("is-selected"); 6811 clickedOption.selected = true; 6812 customSelectToggle.innerHTML = clickedOption.textContent; 6813 if (clickedValue === "" || event.target.classList.contains("is-placeholder")) { 6814 customSelectToggle.classList.add("is-placeholder"); 6815 } else { 6816 customSelectToggle.classList.remove("is-placeholder"); 6817 } 6818 let changeEvent = new Event('change', { 6819 bubbles: true 6820 }); 6821 originalSelect.dispatchEvent(changeEvent); 6822 closeCustomSelect(customSelectToggle); 6823 } else { 6824 if (clickedValue === "") { 6825 customSelectMenu.querySelectorAll(".is-selected").forEach(el => { 6826 el.classList.remove("is-selected"); 6827 el.setAttribute("aria-selected", "false"); 6828 }); 6829 originalSelect.querySelectorAll("option").forEach(opt => opt.selected = false); 6830 customSelectToggle.innerHTML = placeholderText; 6831 customSelectToggle.classList.add("is-placeholder"); 6832 } else { 6833 const isCurrentlySelected = event.target.classList.contains("is-selected"); 6834 event.target.classList.toggle("is-selected"); 6835 event.target.setAttribute("aria-selected", (!isCurrentlySelected).toString()); 6836 clickedOption.selected = !isCurrentlySelected; 6837 const allItem = customSelectMenu.querySelector('.custom-select-item[value=""]'); 6838 if (allItem) { 6839 allItem.classList.remove("is-selected"); 6840 allItem.setAttribute("aria-selected", "false"); 6841 } 6842 const allOption = originalSelect.querySelector('option[value=""]'); 6843 if (allOption) allOption.selected = false;
6844 const selectedItems = customSelectMenu.querySelectorAll(".custom-select-item.is-selected"); 6845 if (selectedItems.length === 0) { 6846 customSelectToggle.innerHTML = placeholderText; 6847 customSelectToggle.classList.add("is-placeholder"); 6848 } else if (selectedItems.length === 1) { 6849 customSelectToggle.innerHTML = selectedItems[0].textContent; 6850 customSelectToggle.classList.remove("is-placeholder"); 6851 } else { 6852 customSelectToggle.innerHTML = `${selectedItems[0].textContent} +${selectedItems.length - 1}`; 6853 customSelectToggle.classList.remove("is-placeholder"); 6854 } 6855 } 6856 let changeEvent = new Event('change', { 6857 bubbles: true 6858 }); 6859 originalSelect.dispatchEvent(changeEvent); 6860 } 6861 } 6862 }); 6863 document.addEventListener("click", function (event) { 6864 if (!customSelectMenu.contains(event.target) && !customSelectToggle.contains(event.target)) { 6865 closeCustomSelect(customSelectToggle); 6866 } 6867 }); 6868 }; 6869 const createCustomSelect = originalSelect => { 6870 const customSelect = document.createElement('div'); 6871 customSelect.innerHTML = customDropdownHTML; 6872 const { 6873 customSelectToggle, 6874 customSelectMenu, 6875 customSelectContent 6876 } = initiateQuerySelectors(customSelect); 6877 inheritAttributes(originalSelect, customSelectToggle, customSelectMenu); 6878 hideOriginalSelect(originalSelect); 6879 initiateToggleText(originalSelect, customSelectToggle); 6880 convertOptionsToButtons(originalSelect, customSelectContent); 6881 initiateEventListeners(originalSelect, customSelectToggle, customSelectMenu); 6882 return customSelect; 6883 }; 6884 const setCustomSelectValue = (originalSelect, value) => {
6885 let customSelect = originalSelect.nextElementSibling; 6886 let customSelectToggle = customSelect.querySelector(".custom-select-toggle"); 6887 let customSelectContent = customSelect.querySelector(".custom-select-content"); 6888 if (!customSelectContent) return; 6889 let selectedOption = value ? Array.from(customSelectContent.children).find(function (b) { 6890 return b.value === value; 6891 }) : customSelectContent.firstChild; 6892 if (!selectedOption) { 6893 return; 6894 } 6895 if (selectedOption.classList.contains("is-disabled")) return; 6896 let elementNumber = Array.from(selectedOption.parentNode.children).indexOf(selectedOption); 6897 let linkedOption = originalSelect.querySelectorAll("option")[elementNumber]; 6898 linkedOption.selected = true; 6899 selectedOption.classList.add("is-selected"); 6900 selectedOption.parentNode.querySelectorAll(".custom-select-item").forEach(el => { 6901 if (el !== selectedOption) { 6902 el.classList.remove("is-selected"); 6903 } 6904 }); 6905 closeCustomSelect(customSelectToggle); 6906 customSelectToggle.innerHTML = linkedOption.innerHTML; 6907 if (linkedOption.value) { 6908 customSelectToggle.classList.remove("is-placeholder"); 6909 if (linkedOption.value !== "") { 6910 customSelect.classList.add("has-selection"); 6911 } else { 6912 customSelect.classList.remove("has-selection"); 6913 } 6914 } else { 6915 customSelect.classList.remove("has-selection"); 6916 customSelectToggle.classList.add("is-placeholder"); 6917 } 6918 }; 6919 const customSelect = originalSelect => { 6920 const customSelect = createCustomSelect(originalSelect); 6921 originalSelect.insertAdjacentElement('afterend', customSelect); 6922 return customSelect; 6923 }; 6924 const resetCustomSelect = originalSelect => { 6925 const customSelectEl = originalSelect.nextElementSibling; 6926 const customSelectToggle = customSelectEl?.querySelector('.custom-select-toggle'); 6927 const customSelectContent = customSelectEl?.querySelector('.custom-select-content'); 6928 if (!customSelectToggle || !customSelectContent) return; 6929 const placeholderOption = originalSelect.querySelector("[data-label]"); 6930 const placeholderText = placeholderOption?.dataset.label || originalSelect.querySelector("option")?.textContent || "Select"; 6931 customSelectContent.querySelectorAll('.custom-select-item').forEach(item => { 6932 item.classList.remove('is-selected'); 6933 item.setAttribute('aria-selected', 'false'); 6934 }); 6935 originalSelect.querySelectorAll('option').forEach(opt => opt.selected = false); 6936 customSelectToggle.innerHTML = placeholderText; 6937 customSelectToggle.classList.add('is-placeholder'); 6938 const customSelectWrapper = customSelectEl.querySelector('.custom-select'); 6939 if (customSelectWrapper) { 6940 customSelectWrapper.classList.remove('has-selection'); 6941 } 6942 }; 6943 const section = document.getElementById(sectionDomId); 6944 const closeBtn = section.querySelector(".js-close-btn"); 6945 const connectBtn = section.querySelector(".js-btn-connect"); 6946 const popUp = section.querySelector(".js-pop-up"); 6947 const btnHolder = section.querySelector(".js-btn-holder"); 6948 const form = section.querySelector(".js-form"); 6949 const successMessage = section.querySelector(".js-success"); 6950 const selects = document.querySelectorAll(".floating-contact-button select.js-select"); 6951 selects.forEach(function (el) { 6952 customSelect(el); 6953 }); 6954 const inputName = form.querySelector('.js-input-name'); 6955 inputName.addEventListener('keyup', function () { 6956 if (this.value.replaceAll(/\d/g, "").trim().split(" ").length < 2 || this.value.replaceAll(/\d/g, "").trim() === "") { 6957 this.setCustomValidity("Please input your first name and last name."); 6958 } else { 6959 this.setCustomValidity(""); 6960 } 6961 }); 6962 form.addEventListener("reset", function () { 6963 successMessage.style.display = "block"; 6964 form.style.display = "none"; 6965 }); 6966 window.addEventListener("scroll", function () { 6967 if (window.scrollY >= 150 && window.getComputedStyle(popUp).display !== "block") btnHolder.classList.add("floating-contact-button__btn-holder--visible");else btnHolder.classList.remove("floating-contact-button__btn-holder--visible"); 6968 }); 6969 closeBtn.addEventListener("click", function () { 6970 popUp.style.display = "none"; 6971 btnHolder.classList.add("floating-contact-button__btn-holder--visible"); 6972 }); 6973 connectBtn.addEventListener("click", function () { 6974 popUp.style.display = "block"; 6975 btnHolder.classList.remove("floating-contact-button__btn-holder--visible"); 6976 }); 6977})('global-mobile-contact'); 6978} catch(e) { 6979console.log(e); 6980} 6981 6982 try { 6983(function init(sectionDomId) { 6984 const scrollSection = document.getElementById(`${sectionDomId}`).querySelector('.js-scroll-section'); 6985 const introBtn = document.getElementById(`${sectionDomId}`).querySelector('.jsScrollBtn'); 6986 introBtn.addEventListener("click", function (e) { 6987 e.preventDefault(); 6988 window.scrollTo({ 6989 top: scrollSection.offsetTop + scrollSection.offsetHeight, 6990 behavior: "smooth" 6991 }); 6992 }); 6993})('section-2d2580ef-5c09-4535-96fc-598bf76a02df', 'undefined'); 6994} catch(e) { 6995console.log(e); 6996} 6997 try { 6998(function init(sectionDomId) { 6999 const section = document.getElementById(sectionDomId); 7000 const emailInput = section.querySelector('.js-email-input'); 7001 const emailRegex = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~\-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+/; 7002 emailInput.addEventListener('input', function () { 7003 if (emailRegex.test(this.value)) { 7004 this.setCustomValidity(''); 7005 } else { 7006 this.setCustomValidity('Please enter a valid email address.'); 7007 } 7008 this.reportValidity(); 7009 }); 7010 const inputName = section.querySelector('.js-input-name'); 7011 inputName.addEventListener('keyup', function () { 7012 if (this.value.replaceAll(/\d/g, "").trim().split(" ").length < 2 || this.value.replaceAll(/\d/g, "").trim() === "") { 7013 this.setCustomValidity("Please input your first name and last name."); 7014 } else { 7015 this.setCustomValidity(""); 7016 } 7017 }); 7018 const addRedirectToForm = () => { 7019 const redirectLink = document.getElementById(`${sectionDomId}`).querySelector('.js-redirect-link'); 7020 const form = document.getElementById(`${sectionDomId}`).querySelector('.js-form'); 7021 function redirectPage(link) { 7022 if (link) { 7023 const rawHref = link.getAttribute('href'); 7024 link.setAttribute('href', setHttp(rawHref)); 7025 link.click(); 7026 } 7027 } 7028 function setHttp(link) { 7029 if (link.search(/^http[s]?\:\/\/|\//) == -1) { 7030 link = 'http://' + link; 7031 } 7032 return link; 7033 } 7034 form.addEventListener("reset", function () { 7035 redirectPage(redirectLink); 7036 }); 7037 }; 7038 addRedirectToForm(); 7039})('section-38f572f9-08ff-41a8-be87-094800542ec9', 'undefined'); 7040} catch(e) { 7041console.log(e); 7042} 7043 try { 7044(function init(sectionDomId) {})('section-8a4a6b14-8bd9-4c11-bcb3-41a529fb04d0', 'undefined'); 7045} catch(e) { 7046console.log(e); 7047} 7048 try { 7049(function init(sectionDomId, options) { 7050 let section = null; 7051 switch (sectionDomId) { 7052 case 'NAVBAR': 7053 section = document.querySelector('body > nav, #global-navbar'); 7054 break; 7055 case 'FOOTER': 7056 section = document.querySelector('body > footer, #global-footer'); 7057 break; 7058 case 'SIDEMENU': 7059 section = document.querySelector('body > div.sidemenu, #global-sidemenu'); 7060 break; 7061 case 'CONTACT':
7062 section = document.querySelector('#modal-global-contact-us'); 7063 break; 7064 case 'MOBILE_CONTACT': 7065 section = document.querySelector('body > div.mobile-contact-wrapper, #global-mobile-contact'); 7066 break; 7067 case 'SUBSCRIBE': 7068 // Subscribe Not Implemented 7069 section = null; 7070 break; 7071 case 'ACCOUNT': 7072 // Account Not Implemented 7073 section = null; 7074 break; 7075 default: 7076 section = document.querySelector('#' + sectionDomId); 7077 } 7078 if (!section) { 7079 return; 7080 } 7081 var url = window.luxuryPresence.defaults.routerUrl; 7082 var companyId = window.luxuryPresence.defaults.companyId; 7083 var limit = options.limit || 8; 7084 if (typeof limit === 'string') { 7085 limit = parseInt(limit, 10) || 8; // this NaN protection also filters out 0 - this is expected 7086 } 7087 var templateSelector = options.templateSelector || '.jsIGTemplate'; 7088 var containerSelector = options.containerSelector || '.jsIGContainer'; 7089 var hasDataClass = options.hasDataClass || 'has-data'; 7090 var hideActivateMessage = options.hasDataClass || false; 7091 var payload = { 7092 companyId: companyId, 7093 limit: limit 7094 }; 7095 if (options.resourceId && typeof options.resourceId === 'string' && !options.resourceId.match(/^{{.*}}$/)) { 7096 payload.resourceId = options.resourceId; 7097 // Federated enum values are uppercase (AGENT/TEAM); element configs carry lowercase 7098 payload.resource = String(options.resource || 'agent').toUpperCase(); 7099 payload.fallbackToNoResource = options.fallbackToNoResource === 'true' || options.fallbackToNoResource === true; 7100 } 7101 var query = 'query InstagramFeed (\n' + ' $companyId: ID!\n' + ' $instagramAuthorizationId: ID\n' + ' $username: String\n' + (payload.resourceId ? '$resource: InstagramContentAuthResourceEnum\n $resourceId: ID\n $fallbackToNoResource: Boolean' : '') + ' $limit: Int\n' + ') {\n' + ' instagramContentFeed (\n' + ' companyId: $companyId\n' + ' instagramAuthorizationId: $instagramAuthorizationId\n' + ' username: $username\n' + (payload.resourceId ? 'resource: $resource\n resourceId: $resourceId\n fallbackToNoResource: $fallbackToNoResource' : '') + ' limit: $limit\n' + ' ) {\n' + ' active\n' + ' feedUrl\n' + ' username\n' + ' items {\n' + ' id\n' + ' caption\n' + ' commentsCount\n' + ' likeCount\n' + ' mediaType\n' + ' mediaUrl\n' + ' permalink\n' + ' shortcode\n' + ' thumbnailUrl\n' + ' timestamp\n' + ' username\n' + ' }\n' + ' }\n' + '}\n'; 7102 var templateHTML = section.querySelector(templateSelector)?.innerHTML || ''; 7103 var template = Handlebars.compile(templateHTML); 7104 var container = section.querySelector(containerSelector); 7105 if (options.variables) { 7106 Object.keys(options.variables).forEach(function (key) { 7107 if (options.variables.hasOwnProperty(key) && (typeof options.variables[key] !== 'string' || !options.variables[key].match(/^{{.*}}$/))) { 7108 payload[key] = options.variables[key]; 7109 } 7110 }); 7111 } 7112 function getData() { 7113 var feed = { 7114 error: true 7115 }; 7116 if (payload.companyId == null || payload.companyId === '') { 7117 // instagramContentFeed requires companyId (ID!) â nothing to fetch without it. 7118 // Checked after the options.variables merge, which may override the default. 7119 // Deferred so element listeners (attached after this template runs) still receive it. 7120 setTimeout(function () { 7121 section.dispatchEvent(new CustomEvent('ig-data', { 7122 detail: feed 7123 })); 7124 }, 0); 7125 return; 7126 } 7127 superagent.post(url + '/graphql').send({ 7128 query: query, 7129 variables: payload 7130 }).then(res => { 7131 try { 7132 if (!res.body.errors) { 7133 feed = res.body.data.instagramContentFeed; 7134 if (!feed.active) { 7135 if (!hideActivateMessage && sectionDomId === 'section-temp' && container) { 7136 if (payload.resourceId) { 7137 container.innerHTML = '<div style="text-align: center">Instagram account is not connected to selected ' + payload.resource + '.<br /> Connect it on the ' + payload.resource + ' page</div>'; 7138 } else { 7139 container.innerHTML = '<div style="text-align: center">Instagram account is not connected.<br /> Connect it on <a href="/profile">Profile page</a></div>'; 7140 } 7141 } 7142 } else { 7143 if (feed.items.length) { 7144 if (hasDataClass) { 7145 section.classList.add(hasDataClass); 7146 } 7147 const feedName = feed.username; 7148 section.querySelectorAll('.jsIGLink').forEach(link => link.setAttribute('href', feed.feedUrl)); 7149 section.querySelectorAll('.jsIGName').forEach(name => { 7150 name.textContent = '@' + feedName; 7151 name.setAttribute('title', '@' + feedName); 7152 }); 7153 if (container && template) { 7154 container.innerHTML = template(feed); 7155 } 7156 } 7157 } 7158 } 7159 } catch (e) {} 7160 section.dispatchEvent(new CustomEvent('ig-data', { 7161 detail: feed 7162 })); 7163 }).catch(e => { 7164 console.log('err', e); 7165 section.dispatchEvent(new CustomEvent('ig-data', { 7166 detail: feed 7167 })); 7168 }); 7169 } 7170 getData(); 7171})('section-bdc20bac-df92-415d-b33f-0bbba2991620', JSON.parse("{\"limit\":11,\"resource\":\"agent\",\"resourceId\":\"{{id}}\",\"fallbackToNoResource\":\"{{{fallbackToNoResource}}}\",\"companyId\":\"bba607a7-df71-48c9-84ae-e87d443d6b5b\",\"usePageResource\":false,\"sectionIndex\":4}")); 7172} catch(e) { 7173console.log(e); 7174} 7175 try { 7176(function init(sectionDomId) { 7177 const section = document.getElementById(sectionDomId); 7178 const slider = section.querySelector(".js-slider"); 7179 section.addEventListener("ig-data", function () { 7180 var splide = new Splide(slider, { 7181 rewind: true,
7182 arrows: false, 7183 type: 'loop', 7184 padding: '20%', 7185 gap: '16px', 7186 pagination: false, 7187 clones: 1, 7188 perPage: 1, 7189 perMove: 1, 7190 destroy: true, 7191 breakpoints: { 7192 768: { 7193 destroy: false 7194 } 7195 } 7196 }); 7197 splide.mount(); 7198 }); 7199})('section-bdc20bac-df92-415d-b33f-0bbba2991620', 'undefined'); 7200} catch(e) { 7201console.log(e); 7202} 7203 7204 var { body } = document; 7205 var globalNav = body.querySelector(':scope > nav'); 7206 var globalSideMenu = body.querySelector(':scope > div.sidemenu'); 7207 7208 document.querySelectorAll('body > .modal .close').forEach(modalCloseBtn => { 7209 modalCloseBtn.addEventListener('click', function() { 7210 window.lpUI.hideClosestModal(modalCloseBtn); 7211 }); 7212 }); 7213 7214 window.addEventListener('click', function() { 7215 document.querySelectorAll('.share-popup').forEach(sharedPopup => { 7216 sharedPopup.style.display = 'none' ; 7217 }); 7218 }); 7219 7220 /* Misc Partials / Addons */ 7221 function initWOW() { 7222 if (wow) { 7223 wow.init(); 7224 wow.scrollHandler(); 7225 setTimeout(function () { 7226 const wowHideStyle = document.getElementById('wow-hide-elements'); 7227 if(wowHideStyle) { 7228 wowHideStyle.disabled = true; 7229 } 7230 }, 1); 7231 } 7232 } 7233 7234 const wowHideElements = document.getElementById('wow-hide-elements'); 7235 if (wowHideElements) { 7236 wowHideElements.disabled = false; 7237 } 7238 7239 initWOW(); 7240 7241 /* Generic Navbar Handling */ 7242 globalNav?.querySelectorAll('.hamburger-component').forEach(hamburgerComponent => { 7243 hamburgerComponent.addEventListener('click', function() { 7244 window.lpUI.sideMenuToggle(); 7245 }); 7246 }); 7247 globalSideMenu?.querySelectorAll('.toggle').forEach(toggleEl => { 7248 toggleEl.addEventListener('click', function() { 7249 window.lpUI.sideMenuToggle(); 7250 }); 7251 }); 7252 7253 document.querySelectorAll('body > nav .sub-nav-container > a, body > div.sidemenu .sub-nav-container > a').forEach(linkEl => { 7254 linkEl.addEventListener('click', function(e) { 7255 e.preventDefault(); 7256 e.stopPropagation(); 7257 var subNav = linkEl.parentElement.querySelector(':scope > .sub-nav'); 7258 if (subNav && subNav.classList.contains('visible')){ 7259 subNav.classList.remove('visible'); 7260 } else { 7261 globalNav?.querySelectorAll('.sub-nav-container .sub-nav').forEach(globalSubNav => { 7262 globalSubNav.classList.remove('visible'); 7263 }); 7264 globalSideMenu?.querySelectorAll('.sub-nav').forEach(sideMenuSubNav => { 7265 sideMenuSubNav.classList.remove('visible'); 7266 }); 7267 subNav?.classList.add('visible'); 7268 } 7269 }); 7270 }); 7271 window.addEventListener('click', function() { 7272 document.querySelectorAll('nav .sub-nav.visible, .sidemenu .sub-nav.visible').forEach(subNavEl => { 7273 subNavEl.classList.remove('visible'); 7274 }); 7275 }); 7276 7277 } 7278 7279 document.addEventListener('DOMContentLoaded', lpGlobalInit); 7280 7281 </script>
7281 7282 7283
7283<script type="module"> 7284 function selectorExists(selector) { 7285 return document.querySelector(selector) !== null; 7286 } 7287 7288 // Function to initialize the third-party library 7289 function initParoller(selector = '.my-paroller') { 7290 var $parollerElements = $(selector); 7291 if ($parollerElements.length > 0 && typeof $parollerElements.paroller === 'function') { 7292 $parollerElements.paroller(); 7293 } 7294 } 7295 7296 const parollerSelector = '.my-paroller'; 7297 7298 if (selectorExists(parollerSelector)) { 7299 window.luxuryPresence.libs.loadJs(window.luxuryPresence.libs.jsLibsMap.paroller.jsSrc, function() { 7300 initParoller(parollerSelector); 7301 window.luxuryPresence.libs.jsLibsMap.paroller.isLoaded = true; 7302 }); 7303 } 7304 </script>
7304 7305 7306 <!-- Google Tag Manager (noscript) -->
vendor: 80 bytes, lines 7306-7307
7306 7307 <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
7307GTM-T9SFQGHZ
vendor: 107 bytes, lines 7307-7309
7307" 7308 height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 7309
7309<!-- End Google Tag Manager --> 7310 7311
7311<script 7312 src="https://cdn.luxurypresence.com/scripts/a11y-widget/latest/widget.js" 7313 data-website-id="ae45f4e2-58fe-4d52-921e-febac4ac146e" 7314 7315 defer></script>
vendor: 1 bytes, line 7315
7315
7316<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a46729c16b31d94c',t:'MTc5MTMxNTg5MQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
7316<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"d2a8f4b3d65944edb8fcd6ffce49ee5e","spa":2}' crossorigin="anonymous"></script>
7316 7317</body> 7318</html>
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.