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>Blog - Trillion Real Estate</title> 10 <meta property="og:title" content="Blog - Trillion Real Estate"> 11 <meta name="twitter:title" content="Blog - Trillion Real Estate"> 12 <meta name="description" content="Keep up with the latest real estate trends and news happening in California. For even faster service, call Trillion Real Estate and get on board with the team!"> 13 <meta property="og:description" content="Keep up with the latest real estate trends and news happening in California. For even faster service, call Trillion Real Estate and get on board with the team!"> 14 <meta name="twitter:description" content="Keep up with the latest real estate trends and news happening in California. For even faster service, call Trillion Real Estate and get on board with the team!"> 15 <meta property="og:image" content="https://media-production.lp-cdn.com/media/48787991-3ed4-4fb4-9169-d1ada07c0646"> 16 <meta name="twitter:image" content="https://media-production.lp-cdn.com/media/48787991-3ed4-4fb4-9169-d1ada07c0646"> 17 <meta property="og:url" content="https://trillionrealestate.com/blog"> 18 <link rel="canonical" href="https://trillionrealestate.com/blog"> 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"> 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:Montserrat,sans-serif;--global-secondary-font-family-short:Montserrat;--global-body-padding:0px;--global-background-color:#fff;--global-body-font-size:16px;--global-h1-font-size:45px;--global-h2-font-size:40px;--global-h3-font-size:35px;--global-h4-font-size:28px;--global-h5-font-size:22px;--global-h6-font-size:20px;--global-section-padding:96px;}@media (max-width: 768px){:root{--global-section-padding:64px;}}body{padding:0px;background-color:#fff;font-family:Montserrat,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:45px;}h2{font-size:40px;}h3{font-size:35px;}h4{font-size:28px;}h5{font-size:22px;}h6{font-size:20px;}</style> 30 <link rel="stylesheet" type="text/css" href="https://styles.luxurypresence.com/masterpiece/index.css" /> 31 <style id="website-css">body>div.mobile-contact-wrapper .mobile-contact.with-search div.buttons-holder{bottom:150px;}@media only screen and (max-width: 768px){iframe[name='widgetCta']{bottom:55px!important;right:10px!important;}}iframe[name='widgetCta']{bottom:50px!important;right:15px!important;}nav#global-navbar .header.theme-light .header__hamburger circle{fill:#fff;}nav#global-navbar .header.theme-light .header__hamburger svg rect{fill:#4a7da6!important;}nav#global-navbar .header.theme-light ul.navigation li.navigation__item{color:#fff;}nav#global-navbar .header.theme-light ul.navigation li.navigation__item .sub-nav__link{color:#000;background-color:#fff;}nav#global-navbar .header.theme-light ul.navigation li.navigation__item .sub-nav__link:hover{color:#000;background-color:#4a7da6;}nav#global-navbar .header.theme-light ul.navigation li.navigation__item .navigation__link:after{background:#fff;}nav#global-navbar .header.theme-light .search__btn svg{color:#fff;}nav#global-navbar .header.theme-light a.header__btn-contact{background:transparent;color:#fff!important;border-color:#fff!important;}nav#global-navbar .header.theme-light a.header__btn-contact:hover{background:#fff!important;color:#000!important;border-color:#fff!important;}nav#global-navbar .header .header__hamburger circle{fill:#4a7da6;}nav#global-navbar .header .header__hamburger svg rect{fill:#fff!important;}nav#global-navbar .header ul.navigation li.navigation__item{color:#000;}nav#global-navbar .header ul.navigation li.navigation__item .sub-nav__link{color:#FFF;background-color:#000;}nav#global-navbar .header ul.navigation li.navigation__item .sub-nav__link:hover{color:#FFF;background-color:#2f4f6a;}nav#global-navbar .header ul.navigation li.navigation__item .navigation__link:after{background:#4a7da6;}nav#global-navbar .header .search__btn svg{color:#000;}nav#global-navbar.scroll .header{background-color:#fff;}nav#global-navbar.scroll .header .header__hamburger circle{fill:#4a7da6;}nav#global-navbar.scroll .header .header__hamburger svg rect{fill:#fff!important;}nav#global-navbar.scroll .header ul.navigation li.navigation__item{color:#000;}nav#global-navbar.scroll .header ul.navigation li.navigation__item .sub-nav__link{color:#FFF;background-color:#000;}nav#global-navbar.scroll .header ul.navigation li.navigation__item .sub-nav__link:hover{color:#FFF;background-color:#2f4f6a;}nav#global-navbar.scroll .header ul.navigation li.navigation__item .navigation__link:after{background:#4a7da6;}nav#global-navbar.scroll .header .search__btn svg{color:#FFF;}nav#global-navbar.scroll .header a.header__btn-contact{background:transparent;color:#fff!important;border-color:#fff!important;}nav#global-navbar.scroll .header a.header__btn-contact:hover{background:#fff!important;color:#000!important;border-color:#fff!important;}@media only screen and (min-width:534px){nav#global-navbar .header__logo a.logo{width:350px;height:70px;max-width:350px;}nav#global-navbar .header__logo a.logo .logo__img{width:350px;height:70px;}}nav#global-navbar.scroll .header.theme-light .logo__img.light{display:none;}nav#global-navbar.scroll .header.theme-light .logo__img.dark{display:block;}nav#global-navbar.scroll .header.theme-auto .logo__img.light{display:none;}nav#global-navbar.scroll .header.theme-auto .logo__img.dark{display:block;}#modal-global-contact-us .contact-form__submit{background-color:transparent;border-color:#fff;}#modal-global-contact-us .contact-form__submit:hover{background-color:#fff;color:#000;border-color:#fff;}@media only screen and (max-width: 1024px){#modal-global-contact-us div.contact-form__container{width:100%;}#modal-global-contact-us div.contact-form__close.close{color:#fff;opacity:1;}}footer#global-footer a{color:#000;}footer#global-footer a:hover{color:#4a7da6;}
31footer#global-footer div.site-footer__col-content.lp-a{opacity:1;}footer#global-footer .site-footer__logo-container img{max-height:80px;}#global-footer .site-footer__disclaimer{opacity:1;color:#000;}#global-mobile-contact .mobile-contact .item{background-color:#4a7da6;color:#fff;}#global-mobile-contact .mobile-contact .item svg{fill:#fff;}</style> 32 <style id="wow-hide-elements">.wow{visibility:hidden!important}</style> 33 <style id="page-css"> 34 #section-28f45f08-c7d4-49ae-b152-9a02d549e538{color:#ffffff;--fontColor:#ffffff;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:100%;--fontColor_A:1;--fontColor_darkenDir:-1;--bgColor:#000000;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:0%;--bgColor_A:1;--bgColor_darkenDir:1;background-color:#000000;--gradient-color:var(--overlay-color, rgba(0, 0, 0, 0.45));background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('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/vn5berhnrnmdykj03bky');background-repeat:no-repeat;-webkit-background-position:center;background-position:center;-webkit-background-size:cover;background-size:cover;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .video-wrapper .full-screen-video-component .video-wrapper::before{content:'';position:absolute;width:100%;height:100%;top:0;left:0;background-color:#000;z-index:-1000;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .video-wrapper .full-screen-video-component .video-wrapper video{-webkit-transition:opacity 2s cubic-bezier(0.835, 0, 0.19, 1);transition:opacity 2s cubic-bezier(0.835, 0, 0.19, 1);opacity:0!important;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .video-wrapper .full-screen-video-component .video-wrapper.playing video{opacity:1!important;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .lp-play{opacity:0;background-color:transparent;display:inline-block;border:none;position:absolute;bottom:50px;right:50px;width:27px;height:24px;z-index:3;-webkit-transition:opacity .2s ease-in-out;transition:opacity .2s ease-in-out;cursor:unset;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .lp-play.visible{opacity:1;cursor:pointer;}@media (max-width: 900px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .lp-play.visible{display:none;}}@media (max-width: 900px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .lp-play{display:none;}}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled{z-index:unset;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled .lp-container{position:relative;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled>div.video-wrapper{position:fixed;left:var(--global-body-padding, 0);top:var(--global-body-padding, 0);width:calc(100% - var(--global-body-padding, 0px) * 2);z-index:-1001;}@media (max-width: 767px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled>div.video-wrapper{left:0;top:0;width:100%;}}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled>div.overlay-component{position:fixed;left:var(--global-body-padding, 0);top:var(--global-body-padding, 0);right:unset;width:calc(100% - var(--global-body-padding, 0px) * 2);}@media (max-width: 767px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled>div.overlay-component{left:0;top:0;width:100%;}}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled .opening-with-search:before{display:block;content:'';left:var(--global-body-padding, 0);top:var(--global-body-padding, 0);width:calc(100% - var(--global-body-padding, 0px) * 2);background:var(--sectionBackground) center no-repeat;-webkit-background-size:cover;background-size:cover;background-position-y:22%;position:fixed;z-index:-1001;height:100%;}@media (max-width: 767px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538.parallax-enabled .opening-with-search:before{left:0;top:0;width:100%;}}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search{position:relative;--top-padding:170px;--bottom-padding:30px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-height:770px;padding-top:var(--top-padding);padding-bottom:var(--bottom-padding);}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search:before{display:none;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-Full{min-height:100vh;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-Full>div.video-wrapper{height:100vh;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-Full:before{min-height:100vh;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-770px{min-height:770px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-770px>div.video-wrapper{height:770px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-770px:before{min-height:770px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-600px{min-height:600px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-600px>div.video-wrapper{height:600px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search.height-class-600px:before{min-height:600px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .bottom-sp
34acer{-webkit-flex:1 0 0%;-ms-flex:1 0 0%;flex:1 0 0%;max-height:calc(var(--top-padding) - var(--bottom-padding));}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .center-block{-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;text-align:center;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-description{letter-spacing:.1em;margin-bottom:15px;max-width:640px;margin-left:auto;margin-right:auto;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-description a,#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-description a[x-apple-data-detectors]{color:inherit!important;-webkit-text-decoration:none!important;text-decoration:none!important;font-size:inherit!important;font-family:inherit!important;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-pretitle{margin-bottom:15px;max-width:860px;margin-left:auto;margin-right:auto;white-space:pre-line;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-title{margin-bottom:15px;max-width:860px;margin-left:auto;margin-right:auto;white-space:pre-line;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-button{margin:0 15px;}@media (max-width: 768px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .opening-button{margin:0;}}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-form{margin-top:30px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:stretch;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-form.with-filters{padding-top:32px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-button{margin:5px;border:none;border-radius:0;outline:none;background-color:#000;color:#fff;height:44px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding-left:50px;padding-right:50px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;cursor:pointer;-webkit-transition:all .3s ease;transition:all .3s ease;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-button:hover{background-color:#fff;color:#000;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-lease-tabs{position:absolute;left:0;bottom:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-lease-tab{padding:4px 20px;background-color:#000;color:#fff;cursor:pointer;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-lease-tab.active{background-color:#fff;color:#000;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-wrapper{margin:5px;letter-spacing:normal;width:100%;max-width:444px;position:relative;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-input-container{width:100%;border:none;height:43px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:0 16px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background-color:#fff;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-input-container .search-icon{color:#c0c0c0;margin-right:10px;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;font-size:20px;line-height:0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-input-container .search-input{color:#000;-webkit-flex:1 0 auto;-ms-flex:1 0 auto;flex:1 0 auto;width:50px;border:none;border-radius:0;outline:none;background:transparent;font-size:16px;padding:0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-results{line-height:normal;text-align:left;background-color:rgba(0, 0, 0, 0.8);position:absolute;width:100%;left:0;top:100%;border:none;overflow:hidden;-webkit-transition:height 0.3s ease;transition:height 0.3s ease;height:0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-container,#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .predefined-links{padding:10px 0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-container a,#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .predefined-links a{color:#b6b6b6;-webkit-transition:color 0.3s ease;transition:color 0.3s ease;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-container a:hover,#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .predefined-links a:hover{color:#fff;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .predefined-links{max-height:400px;overflow:auto;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-block{margin:15px 0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-block-title{margin:0;padding:0 48px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-block-title .results-title-label{margin:0 20px 10px 0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-block-title .search-near-container{margin-bottom:15px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-block:nth-child(n + 2) .search-near-container{display:none;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-link{display:block;padding:4px 48px;width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .results-link.selected-link{color:#fff;background-color:rgba(255, 255, 255, 0.2);}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-in-progress{padding:30px 0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;
34-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:30px;opacity:0.5;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .not-visible{display:none!important;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .nothing-found{width:100%;padding:20px;}@media (max-width: 560px){#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-form{-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-button{width:100%;max-width:444px;}}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .opening-with-search .search-near{cursor:pointer;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;-webkit-transition:background-color 0.3s ease;transition:background-color 0.3s ease;background-color:transparent;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-wrap{width:100%;height:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;padding:20px;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-clip-path:polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%);clip-path:polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%);-webkit-transition:clip-path 0.4s ease;transition:clip-path 0.4s ease;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-modal{border-radius:20px;padding:10px 20px 20px;max-width:400px;min-width:100px;background-color:rgba(0,0,0,0.9);color:#fff;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;position:relative;-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;max-height:100%;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-close{position:absolute;width:40px;height:40px;top:10px;right:10px;cursor:pointer;-webkit-transition:all 0.3s ease;transition:all 0.3s ease;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-close:before{content:'';width:20px;height:2px;background-color:#fff;position:absolute;left:10px;top:19px;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg);}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-close:after{content:'';width:20px;height:2px;background-color:#fff;position:absolute;left:10px;top:19px;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);transform:rotate(-45deg);}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-close:hover{-webkit-transform:scale(1.2);-moz-transform:scale(1.2);-ms-transform:scale(1.2);transform:scale(1.2);}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .search-near-hidden .search-near-container{display:none;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-title{-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;margin-bottom:20px;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-container{-webkit-flex:0 1 auto;-ms-flex:0 1 auto;flex:0 1 auto;overflow:auto;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-container p{margin:0;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538 .loc-msg-container p+p{margin-top:1em;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.visible-loc-msg{z-index:1000;}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.visible-loc-msg .loc-msg-backdrop{background-color:rgba(255,255,255,0.4);}#section-28f45f08-c7d4-49ae-b152-9a02d549e538.visible-loc-msg .loc-msg-wrap{-webkit-clip-path:polygon(0 0, 0 100%, 100% 100%, 100% 0%);clip-path:polygon(0 0, 0 100%, 100% 100%, 100% 0%);}@media (min-width: 960px) and (orientation: landscape){#section-28f45f08-c7d4-49ae-b152-9a02d549e538{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('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/vn5berhnrnmdykj03bky');}}@media (min-width: 1280px) and (orientation: landscape){#section-28f45f08-c7d4-49ae-b152-9a02d549e538{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('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/vn5berhnrnmdykj03bky');}}@media (min-width: 1920px) and (orientation: landscape){#section-28f45f08-c7d4-49ae-b152-9a02d549e538{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/vn5berhnrnmdykj03bky');}}@media (min-width: 2560px) and (orientation: landscape){#section-28f45f08-c7d4-49ae-b152-9a02d549e538{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/vn5berhnrnmdykj03bky');}}
34#section-3087e602-065d-462f-8f31-f4819d6fc3cd{color:#211f1f;--fontColor:#211f1f;--fontColor_H:0;--fontColor_S:3%;--fontColor_L:13%;--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(4 / 4 * var(--global-section-padding));--section-padding-bottom:calc(2 / 4 * var(--global-section-padding));background-color:#fff;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .collection{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0 -10px;}@media (min-width: 1024px){#section-3087e602-065d-462f-8f31-f4819d6fc3cd .collection:not(.collection--3) .item{width:calc(50% - 20px);}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .collection:not(.collection--3) .item figure{height:462px;}}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item{width:calc(33.33333% - 20px);margin:0 10px 48px;-webkit-text-decoration:none;text-decoration:none;color:inherit;}@media (max-width: 991px){#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item{width:calc(50% - 20px);}}@media (max-width: 767px){#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item{margin-bottom:24px;width:100%;}}@media (hover: hover) and (pointer: fine){#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item:hover img{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);}}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item figure{position:relative;overflow:hidden;margin:0 0 24px 0;background-color:#f2f2f2;height:310px;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item img{-webkit-transition:all .3s;transition:all .3s;height:100%;width:100%;object-fit:cover;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item .label{text-transform:uppercase;position:absolute;top:10px;right:10px;padding:6px 10px;background-color:rgba(255,255,255, .8);margin:0;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item .lp-h5{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;margin:0 0 8px;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item .bf1{margin:0 0 8px;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .item-text{margin:0;}#section-3087e602-065d-462f-8f31-f4819d6fc3cd .lp-pagination{margin-top:16px;}@media (max-width: 767px){#section-3087e602-065d-462f-8f31-f4819d6fc3cd .lp-pagination{margin-top:8px;}}#section-3087e602-065d-462f-8f31-f4819d6fc3cd>div:last-child{padding-top:calc(4 / 4 * var(--global-section-padding))!important;padding-bottom:calc(2 / 4 * var(--global-section-padding))!important;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85{color:#211f1f;--fontColor:#211f1f;--fontColor_H:0;--fontColor_S:3%;--fontColor_L:13%;--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(4 / 4 * var(--global-section-padding));--section-padding-bottom:calc(2 / 4 * var(--global-section-padding));background-color:#fff;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .collection{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0 -10px;}@media (min-width: 1024px){#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .collection:not(.collection--3) .item{width:calc(50% - 20px);}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .collection:not(.collection--3) .item figure{height:462px;}}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item{width:calc(33.33333% - 20px);margin:0 10px 48px;-webkit-text-decoration:none;text-decoration:none;color:inherit;}@media (max-width: 991px){#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item{width:calc(50% - 20px);}}@media (max-width: 767px){#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item{margin-bottom:24px;width:100%;}}@media (hover: hover) and (pointer: fine){#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item:hover img{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);}}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item figure{position:relative;overflow:hidden;margin:0 0 24px 0;background-color:#f2f2f2;height:310px;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item img{-webkit-transition:all .3s;transition:all .3s;height:100%;width:100%;object-fit:cover;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item .label{text-transform:uppercase;position:absolute;top:10px;right:10px;padding:6px 10px;background-color:rgba(255,255,255, .8);margin:0;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item .lp-h5{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;margin:0 0 8px;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item .bf1{margin:0 0 8px;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .item-text{margin:0;}#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .lp-pagination{margin-top:16px;}@media (max-width: 767px){#section-5d01005b-e93f-48f9-92b7-4822576c8b85 .lp-pagination{margin-top:8px;}}#section-5d01005b-e93f-48f9-92b7-4822576c8b85>div:last-child{padding-top:calc(4 / 4 * var(--global-section-padding))!important;padding-bottom:calc(2 / 4 * var(--global-section-padding))!important;}
34#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda{color:#211f1f;--fontColor:#211f1f;--fontColor_H:0;--fontColor_S:3%;--fontColor_L:13%;--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(4 / 4 * var(--global-section-padding));--section-padding-bottom:calc(2 / 4 * var(--global-section-padding));background-color:#fff;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .lp-pagination.pagination-container .paginationjs .paginationjs-pages ul li a:after{border-right:2px solid #36454d;border-top:2px solid #36454d;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .lp-pagination.pagination-container .paginationjs .paginationjs-pages ul li a:hover::after{border-right:2px solid #1f282d;border-top:2px solid #1f282d;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .collection{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0 -10px;}@media (min-width: 1024px){#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .collection:not(.collection--3) .item{width:calc(50% - 20px);}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .collection:not(.collection--3) .item figure{height:462px;}}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item{width:calc(33.33333% - 20px);margin:0 10px 48px;-webkit-text-decoration:none;text-decoration:none;color:inherit;}@media (max-width: 991px){#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item{width:calc(50% - 20px);}}@media (max-width: 767px){#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item{margin-bottom:24px;width:100%;}}@media (hover: hover) and (pointer: fine){#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item:hover img{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1);}}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item figure{position:relative;overflow:hidden;margin:0 0 24px 0;background-color:#f2f2f2;height:310px;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item img{-webkit-transition:all .3s;transition:all .3s;height:100%;width:100%;object-fit:cover;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item .label{text-transform:uppercase;position:absolute;top:10px;right:10px;padding:6px 10px;background-color:rgba(255,255,255, .8);margin:0;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item .lp-h5{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis;margin:0 0 8px;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item .bf1{margin:0 0 8px;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .item-text{margin:0;}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .lp-pagination{margin-top:16px;}@media (max-width: 767px){#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda .lp-pagination{margin-top:8px;}}#section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda>div:last-child{padding-top:calc(4 / 4 * var(--global-section-padding))!important;padding-bottom:calc(2 / 4 * var(--global-section-padding))!important;}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d{color:#ffffff;--fontColor:#ffffff;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:100%;--fontColor_A:1;--fontColor_darkenDir:-1;--bgColor:#fff;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;background-color:#fff;--gradient-color:var(--overlay-color, rgba(0, 0, 0, 0.45));background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('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/kpojpqpzs2ffhzfjpbp2');background-repeat:no-repeat;-webkit-background-position:center;background-position:center;-webkit-background-size:cover;background-size:cover;}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d div.lp-container.lp-vertical-paddings{z-index:9;}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d.parallax-enabled{-webkit-clip-path:polygon(0px 0px, 100% 0%, 100% 100%, 0% 100%);clip-path:polygon(0px 0px, 100% 0%, 100% 100%, 0% 100%);}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d.parallax-enabled>div.video-wrapper{position:fixed;left:0;top:0;width:100vw;height:100vh;z-index:-9999;}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d.parallax-enabled .work-with-us:before{display:block;content:'';left:0;top:0;width:100%;
34background:var(--sectionBackground) center no-repeat;-webkit-background-size:cover;background-size:cover;position:fixed;z-index:-1;height:100%;}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d .section{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;min-height:780px;text-align:center;position:relative;z-index:2;}@media (max-width: 767px){#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d .section{min-height:100vh;}}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d .section:before{content:'';position:absolute;bottom:0;left:0;height:50%;width:100%;pointer-events:none;z-index:3;-webkit-transform:translateZ(1px);-moz-transform:translateZ(1px);-ms-transform:translateZ(1px);transform:translateZ(1px);background:linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, var(--opacityColor) 100%);}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d .section .btn-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;gap:23px;margin-top:32px;}@media(max-width: 560px){#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d .section .btn-container{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;gap:10px;}}#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d .lp-container{-webkit-transform:translateZ(1px);-moz-transform:translateZ(1px);-ms-transform:translateZ(1px);transform:translateZ(1px);}@media (min-width: 960px) and (orientation: landscape){#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('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/kpojpqpzs2ffhzfjpbp2');}}@media (min-width: 1280px) and (orientation: landscape){#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d{background-image:linear-gradient(var(--gradient-color), var(--gradient-color)),url('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/kpojpqpzs2ffhzfjpbp2');}}@media (min-width: 1920px) and (orientation: landscape){#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d{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/kpojpqpzs2ffhzfjpbp2');}}@media (min-width: 2560px) and (orientation: landscape){#section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d{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/kpojpqpzs2ffhzfjpbp2');}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8{color:#ffffff;--fontColor:#ffffff;--fontColor_H:0;--fontColor_S:0%;--fontColor_L:100%;--fontColor_A:1;--fontColor_darkenDir:-1;--bgColor:rgba(89, 123, 164, 1);--bgColor_H:213;--bgColor_S:30%;--bgColor_L:50%;--bgColor_A:1;--bgColor_darkenDir:1;background-color:rgba(89, 123, 164, 1);}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .section{display:none;overflow:hidden;}@media(min-width: 993px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .lp-container{padding-bottom:200px;}}@media(min-width: 769px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .lp-container{padding-bottom:194px;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .title-group{-webkit-flex-basis:100%;-ms-flex-preferred-size:100%;flex-basis:100%;display:block;margin-bottom:48px;}@media(min-width: 769px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .title-group{dis
34play:none;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection{width:100%;min-width:400px;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection:not(.slick-initialized){display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection:not(.slick-initialized) .item-box:nth-child(n+6){display:none;}@media(max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection:not(.slick-initialized){-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-track{margin-left:0;margin-right:0;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-list{overflow:visible;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection img{max-width:100%;}@media(min-width: 1025px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:not(:nth-child(4n-2)){opacity:.3;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(4n-2) .item__img{opacity:.3;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;-webkit-transform:translate(-50%, 45px);-moz-transform:translate(-50%, 45px);-ms-transform:translate(-50%, 45px);transform:translate(-50%, 45px);}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(4n-2) .item__img svg{display:block;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(4n-2) .item__info{-webkit-transform:translate(0, calc(50% + 45px));-moz-transform:translate(0, calc(50% + 45px));-ms-transform:translate(0, calc(50% + 45px));transform:translate(0, calc(50% + 45px));}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(4n-2) .media-type-icon{top:50%;-webkit-transform:translate(0, calc(50% + 35px));-moz-transform:translate(0, calc(50% + 35px));-ms-transform:translate(0, calc(50% + 35px));transform:translate(0, calc(50% + 35px));}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-active:not(:nth-child(4n-2)){opacity:1;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-active:nth-child(4n-2) .item__img{opacity:1;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}}@media (min-width: 769px) and (max-width: 1025px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:not(:nth-child(even)){opacity:.3;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(even) .item__img{opacity:.3;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;-webkit-transform:translate(-50%, 45px);-moz-transform:translate(-50%, 45px);-ms-transform:translate(-50%, 45px);transform:translate(-50%, 45px);}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(even) .item__img svg{display:block;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(even) .item__info{-webkit-transform:translate(0, calc(50% + 45px));-moz-transform:translate(0, calc(50% + 45px));-ms-transform:translate(0, calc(50% + 45px));transform:translate(0, calc(50% + 45px));}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(even) .media-type-icon{top:50%;-webkit-transform:translate(0, calc(50% + 35px));-moz-transform:translate(0, calc(50% + 35px));-ms-transform:translate(0, calc(50% + 35px));transform:translate(0, calc(50% + 35px));}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-active:not(:nth-child(even)){opacity:1;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-active:nth-child(even) .item__img{opacity:1;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}}@media(max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide{opacity:.3;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-active{opacity:1;-webkit-transition:opacity .4s ease-in-out;transition:opacity .4s ease-in-out;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(4n-1),#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection .slick-slide:nth-child(4n){-webkit-transform:translate(-200%, 100%);-moz-transform:translate(-200%, 100%);-ms-transform:translate(-200%, 100%);transform:translate(-200%, 100%);}}@media(min-width: 1440px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .collection{overflow:hidden;height:514px;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .slider-holder{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}@media(max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .slider-holder{height:calc(100vw - 24px);}}@media(min-width: 1440px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .slider-holder{width:1440px;margin-left:auto;margin-right:auto;height:310px;}}
34#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;z-index:1;width:310px;height:310px;overflow:visible;margin-right:67px;cursor:default;}@media(max-width: 1024px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item{width:298px;height:298px;margin-right:44px;}}@media(max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item{margin-right:0;margin-bottom:0;width:calc(50vw - 48px);height:calc(50vw - 48px);}}@media(max-width: 560px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item{margin-right:0;margin-bottom:0;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__img{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);-moz-transform:translate(-50%, -50%);-ms-transform:translate(-50%, -50%);transform:translate(-50%, -50%);position:absolute;height:100%;width:100%;background-color:gray;-webkit-background-size:cover;background-size:cover;-webkit-background-position:center center;background-position:center center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:all linear 0.2s;transition:all linear 0.2s;transform-origin:bottom;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__img svg{display:none;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text{top:50%;left:50%;-webkit-transform:translate(-50%, -50%);-moz-transform:translate(-50%, -50%);-ms-transform:translate(-50%, -50%);transform:translate(-50%, -50%);position:absolute;overflow:hidden;height:100%;width:100%;word-break:break-all;}@media (max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text{display:none;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text .username-group{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text .username-group .lp-h5{cursor:pointer;display:inline-block;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text .username-group .lp-h5 span{-webkit-transition:0.2s all;transition:0.2s all;display:inline-block;}@media (hover: hover) and (pointer: fine){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text .username-group .lp-h5:hover span{-webkit-transform:translateX(5px);-moz-transform:translateX(5px);-ms-transform:translateX(5px);transform:translateX(5px);}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__text .username-group .arrow-icon{display:inline-block;margin-top:9px;width:21px;}@media (max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item-box{padding:13px;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__info{position:absolute;top:0;left:0;bottom:0;right:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;padding:10%;overflow:hidden;color:#fff;text-align:center;background-color:rgba(0, 0, 0, 0.7);opacity:0;-webkit-transition:opacity .3s ease;transition:opacity .3s ease;cursor:pointer;}@media (hover: hover){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__info:hover{opacity:1;}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .item__caption{text-overflow:ellipsis;-webkit-line-clamp:5;display:-webkit-box;-webkit-box-orient:vertical;overflow:hidden;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 h5{color:var(--userColor);text-transform:uppercase;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 h3{color:var(--fontColor);margin-bottom:16px;white-space:pre-line;}@media (max-width: 768px){#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 h3{width:calc(100vw - 50px);}}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 a{-webkit-text-decoration:none;text-decoration:none;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .media-type-icon{position:absolute;top:10px;right:10px;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8 .has-data .section{dis
34play:block;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8.has-data .section{display:block;}#section-dc35c8ba-2e7a-45c2-9887-4d2125099be8#section-temp .section{display:block;}#modal-global-contact-us>.close{display:none;}#modal-global-contact-us .contact-form{--colorLighter1:hsla(var(--fontColor_H), var(--fontColor_S), var(--fontColor_L), 0.5);--colorLighter2:hsla(var(--fontColor_H), var(--fontColor_S), var(--fontColor_L), 0.3);width:100%;background:center no-repeat;-webkit-background-size:cover;background-size:cover;min-height:800px;height:100vh;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background-color:#fff;color:#000;-webkit-box-pack:space-around;-ms-flex-pack:space-around;-webkit-justify-content:space-around;justify-content:space-around;}#modal-global-contact-us .contact-form ::parent{-webkit-align-items:unset;-webkit-box-align:unset;-ms-flex-align:unset;align-items:unset;}@media (max-width: 1024px){#modal-global-contact-us .contact-form{min-height:100vh;height:auto;display:block;}}#modal-global-contact-us .contact-form .input-container{width:100%;}#modal-global-contact-us .contact-form .input-container+.input-container{margin-top:8px;}#modal-global-contact-us .contact-form__container{z-index:2;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;}@media (max-width: 1024px){#modal-global-contact-us .contact-form__container{padding:0;width:100%;display:block;}}#modal-global-contact-us .contact-form__close{position:absolute;right:5px;top:5px;z-index:10;background-color:transparent;border:none;width:40px;height:40px;cursor:pointer;opacity:0.2;}#modal-global-contact-us .contact-form__close.close{color:#000;}#modal-global-contact-us .contact-form__close:before,#modal-global-contact-us .contact-form__close:after{display:block;content:'';position:absolute;width:20px;height:2px;background-color:currentColor;left:50%;top:50%;}@media (max-width: 768px){#modal-global-contact-us .contact-form__close{top:1vh;}}@media (min-width: 768px) and (max-width: 810px),(min-height: 1024px) and (max-height: 1080px){#modal-global-contact-us .contact-form__close{top:3vh;}}#modal-global-contact-us .contact-form__close:before{-webkit-transform:translate(-50%, -50%) rotate(45deg);-moz-transform:translate(-50%, -50%) rotate(45deg);-ms-transform:translate(-50%, -50%) rotate(45deg);transform:translate(-50%, -50%) rotate(45deg);}#modal-global-contact-us .contact-form__close:after{-webkit-transform:translate(-50%, -50%) rotate(-45deg);-moz-transform:translate(-50%, -50%) rotate(-45deg);-ms-transform:translate(-50%, -50%) rotate(-45deg);transform:translate(-50%, -50%) rotate(-45deg);}#modal-global-contact-us .contact-form__form{width:100%;max-width:637px;text-align:center;min-height:463px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;position:relative;margin-right:110px;padding:64px 65px;margin-bottom:0;}@media (max-width: 1024px){#modal-global-contact-us .contact-form__form{width:100%;max-width:100%;margin-right:0;padding:44px 24px;}}#modal-global-contact-us .contact-form__details{width:100%;max-width:510px;margin-top:66px;}@media (max-width: 1024px){#modal-global-contact-us .contact-form__details{width:100%;max-width:100%;margin-top:48px;padding-left:24px;padding-right:24px;}}#modal-global-contact-us .contact-form__input{width:100%;}#modal-global-contact-us .contact-form__submit{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;
34-webkit-justify-content:center;justify-content:center;margin-top:16px;width:100%;padding-top:5px;padding-bottom:5px;height:50px;}#modal-global-contact-us .contact-form .thank-you{display:none;padding:24px;}#modal-global-contact-us .contact-form .success .thank-you{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;}#modal-global-contact-us .contact-form .success .form-group{display:none;}#modal-global-contact-us .contact-form .success .image{display:none;}#modal-global-contact-us .contact-form__title1{margin-bottom:16px;text-align:left;}#modal-global-contact-us .contact-form__title2{margin-bottom:16px;}#modal-global-contact-us .contact-form__description{margin-bottom:48px;}@media (max-width: 1024px){#modal-global-contact-us .contact-form__description{margin-bottom:32px;}}#modal-global-contact-us .form-inputs-wrap.center-aligned{margin-left:auto;margin-right:auto;}#modal-global-contact-us .company{padding-top:24px;padding-bottom:48px;border-top:1px solid rgba(0, 0, 0, 0.5);color:rgba(0, 0, 0, 0.5);}@media (max-width: 1024px){#modal-global-contact-us .company{padding-bottom:32px;}}#modal-global-contact-us .company_title{margin-bottom:0;}#modal-global-contact-us .company .lp-a a{white-space:nowrap;}#modal-global-contact-us .company p{margin-bottom:0;}#modal-global-contact-us .socials{margin:-8px;margin-bottom:20px;}@media (max-width: 767px){#modal-global-contact-us .socials{margin-left:auto;width:-webkit-max-content;width:-moz-max-content;width:max-content;margin-right:auto;max-width:100%;}}#modal-global-contact-us .socials li{margin:0;padding:8px;}#modal-global-contact-us .contact-form__submit{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;}#modal-global-contact-us .sync-icon{display:none;margin-right:10px;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}#modal-global-contact-us .success-icon{display:none;margin-right:10px;-webkit-transition:0s;transition:0s;visibility:visible;}#modal-global-contact-us .loading{pointer-events:none;}#modal-global-contact-us .loading .sync-icon{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;}#modal-global-contact-us .success-text{display:none;}#modal-global-contact-us .success{pointer-events:none;}#modal-global-contact-us .success .success-icon{display:block;}#modal-global-contact-us .success .success-text{display:inline;}nav{-webkit-transition:0.2s all;transition:0.2s all;color:#fff;z-index:102;}nav nav{-webkit-transition:0.2s all;transition:0.2s all;color:#fff;}nav nav.scroll .header__logo .dark{display:block;}nav nav.scroll .header__logo .light{display:none;}nav.scroll .header__logo .dark{display:block;}nav.scroll .header__logo .light{display:none;}nav .header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;padding:24px 30px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}@media (max-width: 560px){nav .header{padding:10px 24px;}}nav .header__hamburger{background-color:transparent;cursor:pointer;outline:none;position:relative;padding:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;margin-left:61px;width:60px;height:60px;border:none;}@media (max-width: 768px){nav .header__hamburger{width:40px;height:40px;margin-left:15px;}}@media (hover: hover) and (pointer: fine){nav .header__hamburger:hover .line--top{width:12px;x:30px;}nav .header__hamburger:hover .line--bottom{width:24px;x:18px;}}nav .header__hamburger svg{width:100%;height:100%;}
34nav .header__hamburger circle{fill:var(--themeBeige);}nav .header__hamburger .line{fill:var(--themeWhite);height:2px;width:24px;x:18px;y:26px;-webkit-transition:0.2s all;transition:0.2s all;}nav .header__hamburger .line--bottom{width:12px;x:30px;y:32px;}nav .header__left{-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;}nav .header__left .navigation{-webkit-box-pack:start;-ms-flex-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;}nav .header__logo{display:block;cursor:pointer;}nav .header__logo img{display:block;max-height:52px;max-width:250px;}nav .header__logo .dark{display:none;}nav .header__logo--center{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;}nav .header__right{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-webkit-flex-basis:50%;-ms-flex-preferred-size:50%;flex-basis:50%;-webkit-box-pack:end;-ms-flex-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}nav .header__right .navigation__item:nth-last-child(-n + 1) .sub-nav{left:auto;right:0;}nav .sub-nav{position:absolute;left:0;top:100%;padding:0;max-height:0;z-index:5;margin:0;list-style-type:none;overflow:hidden;visibility:hidden;-webkit-transition:visibility 0s linear 0.3s,max-height 0.3s ease;transition:visibility 0s linear 0.3s,max-height 0.3s ease;}nav .sub-nav.visible{-webkit-transition:visibility 0s linear 0s,max-height 0.3s ease-in;transition:visibility 0s linear 0s,max-height 0.3s ease-in;max-height:100vh;visibility:visible;}nav .sub-nav__item{position:relative;display:block;margin:0;text-align:left;}nav .sub-nav__link{display:block;color:var(--themeWhite, #fff);font-size:14px;font-weight:600;padding:10px 18px;position:relative;width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:178px;min-width:100%;cursor:pointer;-webkit-text-decoration:none;text-decoration:none;background-color:var(--themeBlack, #211f1f);}@media (hover: hover) and (pointer: fine){nav .sub-nav__link:hover{color:var(--themeWhite, #fff);background-color:rgba(75, 75, 75, 1);}}nav .navigation{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;list-style-type:none;margin:0;padding:0;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-flex:1;-ms-flex:1;flex:1;}@media (max-width: 1200px){nav .navigation{display:none;}}nav .navigation__item{padding:0;margin:0;}nav .navigation__item+.navigation__item{padding-left:20px;}@media (hover: hover) and (pointer: fine){nav .navigation .navigation__item:not(.sub-nav-container) .navigation__link:after{content:'';position:absolute;display:block;width:0;height:2px;-webkit-transition:.3s linear;transition:.3s linear;background:var(--themeBeige, #ccb091);bottom:0;left:0;}nav .navigation .navigation__item:not(.sub-nav-container) .navigation__link:hover:after{width:100%;}}nav .navigation__link{display:block;position:relative;font-size:18px;line-height:1;background-color:transparent;border:none;border-bottom:2px solid transparent;border-top:2px solid transparent;cursor:pointer;padding:21px 10px;max-width:180px;z-index:2;outline:none;color:inherit;-webkit-text-decoration:none;text-decoration:none;max-width:180px;-webkit-font-smoothing:unset;-webkit-transition:.3s linear;transition:.3s linear;}nav .navigation__link:focus,nav .navigation__link:active{outline:none;}nav .sub-nav-container{position:relative;padding-right:21px;}nav .sub-nav-container:after{display:block;content:'';width:8px;height:8px;border-right:2px solid;border-bottom:2px solid;position:absolute;right:3px;top:50%;-webkit-transform:translate(0, calc(-50% - 3px)) rotate(45deg);-moz-transform:translate(0, calc(-50% - 3px)) rotate(45deg);-ms-transform:translate(0, calc(-50% - 3px)) rotate(45deg);transform:translate(0, calc(-50% - 3px)) rotate(45deg);-webkit-transition:0.2s all;transition:0.2s all;}nav .sub-nav-container.active:after{-webkit-transform:translate(0, calc(-50% + 3px)) rotate(-135deg);-moz-transform:translate(0, calc(-50% + 3px)) rotate(-135deg);-ms-transform:translate(0, calc(-50% + 3px)) rotate(-135deg);transform:translate(0, calc(-50% + 3px)) rotate(-135deg);}nav .search{position:relative;}nav .search__btn{border:none;background-color:transparent;margin-left:20px;cursor:pointer;display:block;color:inherit;}@media (max-width: 768px){nav .search__btn{margin-left:0;}}nav .search__btn svg{display:block;}
34nav .search.hide-desktop{left:24px;position:absolute;}nav .hide-desktop{display:block;}@media (min-width: 769px){nav .hide-desktop{display:none;}}nav .hide-mobile{display:none;}@media (min-width: 769px){nav .hide-mobile{display:block;}}nav .header.theme-light{color:#fff;}nav .header.theme-light .header__logo.dark{display:none;}nav .header.theme-light .header__logo.light{display:block;}body>div.sidemenu{background-color:rgba(255, 255, 255, 1);color:rgba(18, 18, 18, 1);--fontColor:rgba(18, 18, 18, 1);--fontColor_H:0;--fontColor_S:0%;--fontColor_L:7%;--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;}body>div.sidemenu{--colorLighter1:hsla(var(--fontColor_H), var(--fontColor_S), var(--fontColor_L), 0.1);width:100%;display:block;-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-ms-transform:translateY(-100%);transform:translateY(-100%);-webkit-transition:.1s all ease-out 2s;transition:.1s all ease-out 2s;background:transparent;}body>div.sidemenu .sidemenu__container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}body>div.sidemenu.visible{-webkit-transition:0s all;transition:0s all;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0);z-index:900;background-color:transparent;}body>div.sidemenu.visible .sidemenu__img{-webkit-transform:translateY(0%);-moz-transform:translateY(0%);-ms-transform:translateY(0%);transform:translateY(0%);-webkit-transition:.7s cubic-bezier(0.645, 0.045, 0.355, 1.000);transition:.7s cubic-bezier(0.645, 0.045, 0.355, 1.000);}body>div.sidemenu.visible .sidemenu__wrapper{-webkit-transform:translateY(0%);-moz-transform:translateY(0%);-ms-transform:translateY(0%);transform:translateY(0%);-webkit-transition:1s cubic-bezier(0.645, 0.045, 0.355, 1.000);transition:1s cubic-bezier(0.645, 0.045, 0.355, 1.000);}body>div.sidemenu.visible .sidemenu__toggle{pointer-events:auto;}body>div.sidemenu .sidemenu__img{background:center no-repeat;-webkit-background-size:cover;background-size:cover;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-ms-transform:translateY(-100%);transform:translateY(-100%);-webkit-transition:.7s cubic-bezier(0.645, 0.045, 0.355, 1.000) .5s;transition:.7s cubic-bezier(0.645, 0.045, 0.355, 1.000) .5s;}@media (max-width: 768px){body>div.sidemenu .sidemenu__img{display:none;}}body>div.sidemenu .sidemenu__wrapper{background-color:var(--bgColor, #211F1F);-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;height:100vh;padding:80px 0 66px 0;overflow-y:auto;max-width:700px;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-ms-transform:translateY(-100%);transform:translateY(-100%);-webkit-transition:1s cubic-bezier(0.645, 0.045, 0.355, 1.000) .5s;transition:1s cubic-bezier(0.645, 0.045, 0.355, 1.000) .5s;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}body>div.sidemenu .sidemenu__wrapper::-webkit-scrollbar{width:4px;background-color:transparent;}body>div.sidemenu .sidemenu__wrapper::-webkit-scrollbar-thumb{background-color:var(--fontColor, #ffffff);}body>div.sidemenu .sidemenu__wrapper::-webkit-scrollbar-track{-webkit-box-shadow:inset 0 0 4px rgba(0,0,0,0.3);background-color:var(--bgColor, #211F1F);}@media (max-width: 768px){body>div.sidemenu .sidemenu__wrapper{padding:40px 0 66px 0;}}body>div.sidemenu .sidemenu__toggle{height:48px;margin-right:auto;margin-left:70px;position:relative;border:none;background-color:transparent;outline:none;color:var(--fontColor, #ffffff);padding-right:50px;font-family:inherit;pointer-events:none;cursor:pointer;}@media (max-width: 768px){body>div.sidemenu .sidemenu__toggle{margin-left:20px;}}body>div.sidemenu .sidemenu__toggle--right{margin-left:auto;margin-right:70px;}@media (max-width: 768px){body>div.sidemenu .sidemenu__toggle--right{margin-right:20px;}}body>div.sidemenu .sidemenu__toggle:focus{outline:none;}body>div.sidemenu .sidemenu__toggle:before,body>div.sidemenu .sidemenu__toggle:after{content:'';display:block;position:absolute;width:20px;height:2px;background-color:currentColor;right:0;top:50%;}body>div.sidemenu .sidemenu__toggle:before{-webkit-transform:translate(-50%, -50%) rotate(45deg);-moz-transform:translate(-50%, -50%) rotate(45deg);-ms-transform:translate(-50%, -50%) rotate(45deg);transform:translate(-50%, -50%) rotate(45deg);}body>div.sidemenu .sidemenu__toggle:after{-webkit-transform:translate(-50%, -50%) rotate(-225deg);-moz-transform:translate(-50%, -50%) rotate(-225deg);-ms-transform:translate(-50%, -50%) rotate(-225deg);transform:translate(-50%, -50%) rotate(-225deg);}body>div.sidemenu .sidemenu__nav{margin-bottom:auto;padding:0;}body>div.sidemenu .sidemenu__socials{margin-left:auto;padding-top:30px;padding-right:70px;}@media (max-width: 768px){body>div.sidemenu .sidemenu__socials{padding-right:20px;padding-left:20px;}}body>div.sidemenu .sidemenu__socials .lp-socials li{margin-bottom:14px;}body>div.sidemenu .sidemenu__header{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;
34-webkit-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-bottom:93px;}@media (max-width: 768px){body>div.sidemenu .sidemenu__header{padding-bottom:59px;}}body>div.sidemenu .sidemenu__logo{display:none;}@media (max-width: 768px){body>div.sidemenu .sidemenu__logo{display:block;}}body>div.sidemenu .sidemenu__logo-img{display:block;max-height:50px;}body>div.sidemenu .navigation{list-style-type:none;margin:0;padding:0;max-width:100%;}body>div.sidemenu .navigation__item{border-bottom:1px solid var(--colorLighter1);margin:0;overflow:hidden;}body>div.sidemenu .navigation__link{padding:12px 12px 12px 70px;display:block;font-size:22px;font-weight:400;line-height:48px;color:var(--fontColor, #ffffff);-webkit-transition:0.2s all;transition:0.2s all;cursor:pointer;-webkit-text-decoration:none;text-decoration:none;font-family:var(--global-primary-font-family);-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-ms-transform:translateY(100%);transform:translateY(100%);opacity:0;position:relative;transition-property:transform,opacity,color;transition-duration:0.55s,0.55s,0.3s;transition-timing-function:cubic-bezier(0.645, 0.045, 0.355, 1),ease-in-out,ease-in-out;}@media(max-width: 768px){body>div.sidemenu .navigation__link{padding-left:20px;padding-right:20px;padding-bottom:16px;}}@media (hover: hover) and (pointer: fine){body>div.sidemenu .navigation__link:hover{color:var(--fontColor, #ffffff);}}body>div.sidemenu .sub-nav{list-style-type:none;display:none;margin:0;padding:10px 0 30px 85px;}@media(max-width: 768px){body>div.sidemenu .sub-nav{padding-bottom:16px;padding-left:35px;}}body>div.sidemenu .sub-nav__item{padding-bottom:10px;margin:0;opacity:0;}body>div.sidemenu .sub-nav__item.fadeInUp{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:var(--delay);animation-delay:var(--delay);}body>div.sidemenu .sub-nav__link{font-size:16px;font-weight:400;cursor:pointer;color:var(--fontColor, #ffffff);font-family:var(--global-secondary-font-family);-webkit-text-decoration:none;text-decoration:none;}body>div.sidemenu .sub-nav.visible{display:block;}body>div.sidemenu .lp-socials__link{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px);position:relative;transition-property:transform,opacity,color;transition-duration:0.55s,0.55s,0.3s;transition-timing-function:cubic-bezier(0.645, 0.045, 0.355, 1),ease-in-out,ease-in-out;}body>div.sidemenu body>div.sidemenu{width:100%;}body>div.sidemenu body>div.sidemenu.visible{-webkit-transition:0s all;transition:0s all;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0);z-index:900;background-color:transparent;}body>div.sidemenu body>div.sidemenu.visible .sidemenu__img{-webkit-transform:translateY(0%);-moz-transform:translateY(0%);-ms-transform:translateY(0%);transform:translateY(0%);-webkit-transition:.7s cubic-bezier(0.645, 0.045, 0.355, 1.000);transition:.7s cubic-bezier(0.645, 0.045, 0.355, 1.000);}body>div.sidemenu body>div.sidemenu.visible .sidemenu__wrapper{-webkit-transform:translateY(0%);-moz-transform:translateY(0%);-ms-transform:translateY(0%);transform:translateY(0%);-webkit-transition:1s cubic-bezier(0.645, 0.045, 0.355, 1.000);transition:1s cubic-bezier(0.645, 0.045, 0.355, 1.000);}body>div.sidemenu body>div.sidemenu.visible .sidemenu__toggle{pointer-events:auto;}body>div.sidemenu .fade-in{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0);}body>div.sidemenu .arrow{vertical-align:middle;margin-bottom:10px;-webkit-transition:.4s;transition:.4s;}body>div.sidemenu .arrow.active{-webkit-transform:rotate(-180deg);-moz-transform:rotate(-180deg);-ms-transform:rotate(-180deg);transform:rotate(-180deg);-webkit-transition:.4s;transition:.4s;}footer{background-color:#ffffff;color:#14171A;--fontColor:#14171A;--fontColor_H:210;--fontColor_S:13%;--fontColor_L:9%;--fontColor_A:1;--fontColor_darkenDir:1;--bgColor:#ffffff;--bgColor_H:0;--bgColor_S:0%;--bgColor_L:100%;--bgColor_A:1;--bgColor_darkenDir:-1;}footer .lp-input--dark{background-color:#fff;}footer .site-footer__subscribe{padding-top:92px;padding-bottom:92px;background-color:rgba(0,0,0,0.1);}@media (max-width: 768px){footer .site-footer__subscribe{padding-top:64px;padding-bottom:64px;}}footer .site-footer__logo-bar{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-bottom:64px;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}@media (max-width: 768px){footer .site-footer__logo-bar{padding-bottom:24px;}}footer .site-footer__logo-container img{max-width:268px;max-height:56px;object-fit:contain;display:block;object-position:left top;}footer .site-footer__logo-container+.site-footer__logo-container{margin-left:50px;}@media (max-width: 768px){footer .site-footer__logo-container+.site-footer__logo-container{margin-left:0;margin-top:16px;width:100%;}}footer .site-footer__info{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;}@media (max-width: 768px){footer .site-footer__info{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}}footer .site-footer__info .lp-a a{white-space:nowrap;}
34footer .site-footer__col{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding-right:32px;}footer .site-footer__col-title{width:100%;margin-bottom:16px;min-height:29px;}footer .site-footer__col-content{opacity:0.5;padding-bottom:16px;}footer .site-footer__col-content p{margin-bottom:0;}footer .site-footer__col-content a{color:inherit;}footer .site-footer__col-content+.site-footer__col-content{padding-left:40px;}@media (max-width: 768px){footer .site-footer__col-content+.site-footer__col-content{padding-left:0;}}footer .site-footer__navigation-wrap{width:100%;padding-top:32px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;}@media (max-width: 992px){footer .site-footer__navigation-wrap{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;padding-bottom:18px;}}@media (max-width: 768px){footer .site-footer__navigation-wrap{padding-top:24px;padding-bottom:0;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;}}footer .site-footer__socials{margin-left:auto;}@media (max-width: 992px){footer .site-footer__socials{margin-left:0;}}@media (max-width: 768px){footer .site-footer__socials{margin-right:auto;}}footer .site-footer__socials li{margin-bottom:0;}footer .site-footer__disclaimer{padding-top:64px;padding-bottom:32px;position:relative;opacity:0.5;}@media (max-width: 768px){footer .site-footer__disclaimer{padding-top:18px;padding-bottom:25px;}}footer .site-footer__disclaimer:after{content:'';display:block;position:absolute;left:0;bottom:0;right:0;height:1px;background-color:currentColor;opacity:0.2;}footer .site-footer__copy{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding-top:32px;font-size:18px;}@media (max-width: 768px){footer .site-footer__copy{padding-top:15px;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;}footer .site-footer__copy>div:first-child{margin-bottom:15px;}}footer .site-footer__copy a{color:inherit;}footer .site-footer__copy-lp span{margin-right:5px;}footer .site-footer__copy-lp img{margin-right:30px;}@media (max-width: 768px){footer .site-footer__copy-lp img{margin-right:0;margin-bottom:15px;}}@media (min-width: 769px){footer .site-footer__copy-lp{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}}footer .site-footer__bottom{padding-top:96px;padding-bottom:96px;}@media (max-width: 768px){footer .site-footer__bottom{padding-top:64px;padding-bottom:64px;}}footer .footer-contact{margin-bottom:13px;}footer .footer-contact p{margin-bottom:0;opacity:.7;}footer .footer-contact ul{list-style:none;padding-left:0;margin:0;}footer .footer-subscribe{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;justify-content:space-between;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}@media (max-width: 768px){footer .footer-subscribe{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}}footer .footer-subscribe__title-wrap{padding-right:50px;}@media (max-width: 768px){footer .footer-subscribe__title-wrap{padding-right:0;padding-bottom:32px;}}footer .footer-subscribe__title{margin-bottom:8px;}footer .footer-subscribe__sub-title{margin-bottom:0;}footer .footer-subscribe__form{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;width:100%;max-width:330px;margin-bottom:0;position:relative;}footer .footer-subscribe__submit{margin-top:16px;}footer .footer-subscribe__success-msg{position:absolute;top:100%;width:100%;text-align:center;opacity:.7;padding-top:10px;}footer .footer-navigation{list-style:none;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex-wrap:wrap;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 20px 0 0;margin:0 -30px;}@media (max-width: 992px){footer .footer-navigation{margin-bottom:36px;}}@media (max-width: 768px){footer .footer-navigation{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;margin:0 0 36px;}}footer .footer-navigation__item{margin:0 30px;}@media (max-width: 768px){footer .footer-navigation__item{margin:0 0 10px;}}
34footer .footer-navigation__link{color:inherit;-webkit-text-decoration:none;text-decoration:none;font-size:14px;line-height:14px;background-color:transparent;border:none;font-family:inherit;cursor:pointer;padding-left:0;padding-right:0;-webkit-transition:0.2s all;transition:0.2s all;}@media (hover: hover) and (pointer: fine){footer .footer-navigation__link:hover{color:var(--themeBeige);}}footer .lp-socials li{margin-bottom:14px;}footer .sync-icon{display:none;margin-right:10px;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}footer .success-icon{display:none;margin-right:10px;-webkit-transition:0s;transition:0s;visibility:visible;}footer .loading{pointer-events:none;}footer .loading .sync-icon{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;}footer .success .success-icon{display:block;}footer .lp-footer-disclaimers{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;gap:20px;margin-top:32px;padding-top:32px;}@media (max-width: 768px){footer .lp-footer-disclaimers{padding-top:15px;margin-top:15px;}}body>div.mobile-contact-wrapper{z-index:200;position:relative;}body>div.mobile-contact-wrapper .lp-ico:before{content:'';width:1em;height:1em;background-color:currentColor;-webkit-background-size:cover;background-size:cover;display:block;-webkit-mask-position:center;-webkit-mask-position:center;mask-position:center;-webkit-mask-size:contain;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-transition:all .3s;transition:all .3s;}body>div.mobile-contact-wrapper .lp-ico-search:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='25' height='25' viewB
34ox='0 0 25 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.6231 21.3002L17.1372 16.8143M19.5606 10.9877C19.5606 15.5441 15.8669 19.2377 11.3106 19.2377C6.75421 19.2377 3.06055 15.5441 3.06055 10.9877C3.06055 6.43135 6.75421 2.73769 11.3106 2.73769C15.8669 2.73769 19.5606 6.43135 19.5606 10.9877Z' stroke='black' stroke-width='1.82813' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='25' height='25' viewBox='0 0 25 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.6231 21.3002L17.1372 16.8143M19.5606 10.9877C19.5606 15.5441 15.8669 19.2377 11.3106 19.2377C6.75421 19.2377 3.06055 15.5441 3.06055 10.9877C3.06055 6.43135 6.75421 2.73769 11.3106 2.73769C15.8669 2.73769 19.5606 6.43135 19.5606 10.9877Z' stroke='black' stroke-width='1.82813' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='25' height='25' viewBox='0 0 25 25' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.6231 21.3002L17.1372 16.8143M19.5606 10.9877C19.5606 15.5441 15.8669 19.2377 11.3106 19.2377C6.75421 19.2377 3.06055 15.5441 3.06055 10.9877C3.06055 6.43135 6.75421 2.73769 11.3106 2.73769C15.8669 2.73769 19.5606 6.43135 19.5606 10.9877Z' stroke='black' stroke-width='1.82813' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}body>div.mobile-contact-wrapper .lp-ico-arrow-top:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='17' height='16' viewBox='0 0 17 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.8716 9.99976L8.87158 5.99976L4.87158 9.99976' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='17' height='16' viewBox='0 0 17 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.8716 9.99976L8.87158 5.99976L4.87158 9.99976' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='17' height='16' viewBox='0 0 17 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.8716 9.99976L8.87158 5.99976L4.87158 9.99976' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}body>div.mobile-contact-wrapper .lp-ico-scroll-top:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 3H3M12 21V7M12 7L5 14M12 7L19 14' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E ");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 3H3M12 21V7M12 7L5 14M12 7L19 14' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E ");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 3H3M12 21V7M12 7L5 14M12 7L19 14' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E ");}body>div.mobile-contact-wrapper .lp-ico-close:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.5986 6.32617L5.59863 18.3262M5.59863 6.32617L17.5986 18.3262' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E ");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.5986 6.32617L5.59863 18.3262M5.59863 6.32617L17.5986 18.3262' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E ");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M17.5986 6.32617L5.59863 18.3262M5.59863 6.32617L17.5986 18.3262' stroke='white' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E ");}body>div.mobile-contact-wrapper .lp-ico-phone:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.8
34699 6.57142C14.7459 6.74157 15.551 7.16808 16.1821 7.79637C16.8132 8.42465 17.2416 9.22612 17.4126 10.0982M13.8699 3C15.6899 3.20128 17.3871 4.01265 18.6827 5.3009C19.9784 6.58914 20.7955 8.27768 21 10.0893M10.4412 13.592C9.36357 12.5192 8.51264 11.3061 7.88843 10.0118C7.83474 9.90047 7.80789 9.84481 7.78726 9.77437C7.71397 9.52405 7.76662 9.21668 7.91909 9.00469C7.962 8.94504 8.01326 8.894 8.11578 8.79194C8.42933 8.47979 8.58611 8.32372 8.6886 8.16677C9.07515 7.57491 9.07515 6.81189 8.6886 6.22002C8.58611 6.06308 8.42933 5.90701 8.11578 5.59486L7.94101 5.42087C7.46438 4.94637 7.22607 4.70912 6.97012 4.58024C6.4611 4.32392 5.85996 4.32392 5.35093 4.58024C5.09499 4.70912 4.85667 4.94637 4.38004 5.42087L4.23867 5.56161C3.76367 6.03449 3.52617 6.27093 3.34478 6.59239C3.1435 6.94909 2.99878 7.5031 3.00001 7.91222C3.00111 8.28092 3.07295 8.53291 3.21664 9.03688C3.98881 11.7453 5.44574 14.3009 7.58743 16.4331C9.72911 18.5652 12.2963 20.0156 15.0168 20.7843C15.523 20.9274 15.7761 20.9989 16.1465 21C16.5575 21.0012 17.114 20.8571 17.4723 20.6568C17.7952 20.4762 18.0327 20.2397 18.5077 19.7669L18.649 19.6261C19.1257 19.1516 19.364 18.9144 19.4934 18.6596C19.7509 18.1528 19.7509 17.5544 19.4934 17.0476C19.364 16.7928 19.1257 16.5556 18.649 16.0811L18.4743 15.9071C18.1607 15.5949 18.0039 15.4388 17.8463 15.3368C17.2518 14.952 16.4853 14.952 15.8908 15.3368C15.7332 15.4388 15.5764 15.5949 15.2628 15.9071C15.1603 16.0091 15.1091 16.0602 15.0491 16.1029C14.8362 16.2547 14.5274 16.3071 14.276 16.2341C14.2052 16.2136 14.1493 16.1869 14.0375 16.1334C12.7374 15.512 11.5189 14.6648 10.4412 13.592Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.8699 6.57142C14.7459 6.74157 15.551 7.16808 16.1821 7.79637C16.8132 8.42465 17.2416 9.22612 17.4126 10.0982M13.8699 3C15.6899 3.20128 17.3871 4.01265 18.6827 5.3009C19.9784 6.58914 20.7955 8.27768 21 10.0893M10.4412 13.592C9.36357 12.5192 8.51264 11.3061 7.88843 10.0118C7.83474 9.90047 7.80789 9.84481 7.78726 9.77437C7.71397 9.52405 7.76662 9.21668 7.91909 9.00469C7.962 8.94504 8.01326 8.894 8.11578 8.79194C8.42933 8.47979 8.58611 8.32372 8.6886 8.16677C9.07515 7.57491 9.07515 6.81189 8.6886 6.22002C8.58611 6.06308 8.42933 5.90701 8.11578 5.59486L7.94101 5.42087C7.46438 4.94637 7.22607 4.70912 6.97012 4.58024C6.4611 4.32392 5.85996 4.32392 5.35093 4.58024C5.09499 4.70912 4.85667 4.94637 4.38004 5.42087L4.23867 5.56161C3.76367 6.03449 3.52617 6.27093 3.34478 6.59239C3.1435 6.94909 2.99878 7.5031 3.00001 7.91222C3.00111 8.28092 3.07295 8.53291 3.21664 9.03688C3.98881 11.7453 5.44574 14.3009 7.58743 16.4331C9.72911 18.5652 12.2963 20.0156 15.0168 20.7843C15.523 20.9274 15.7761 20.9989 16.1465 21C16.5575 21.0012 17.114 20.8571 17.4723 20.6568C17.7952 20.4762 18.0327 20.2397 18.5077 19.7669L18.649 19.6261C19.1257 19.1516 19.364 18.9144 19.4934 18.6596C19.7509 18.1528 19.7509 17.5544 19.4934 17.0476C19.364 16.7928 19.1257 16.5556 18.649 16.0811L18.4743 15.9071C18.1607 15.5949 18.0039 15.4388 17.8463 15.3368C17.2518 14.952 16.4853 14.952 15.8908 15.3368C15.7332 15.4388 15.5764 15.5949 15.2628 15.9071C15.1603 16.0091 15.1091 16.0602 15.0491 16.1029C14.8362 16.2547 14.5274 16.3071 14.276 16.2341C14.2052 16.2136 14.1493 16.1869 14.0375 16.1334C12.7374 15.512 11.5189 14.6648 10.4412 13.592Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13.8699 6.57142C14.7459 6.74157 15.551 7.16808 16.1821 7.79637C16.8132 8.42465 17.2416 9.22612 17.4126 10.0982M13.8699 3C15.6899 3.20128 17.3871 4.01265 18.6827 5.3009C19.9784 6.58914 20.7955 8.27768 21 10.0893M10.4412 13.592C9.36357 12.5192 8.51264 11.3061 7.88843 10.0118C7.83474 9.90047 7.80789 9.84481 7.78726 9.77437C7.71397 9.52405 7.76662 9.21668 7.91909 9.00469C7.962 8.94504 8.01326 8.894 8.11578 8.79194C8.42933 8.47979 8.58611 8.32372 8.6886 8.16677C9.07515 7.57491 9.07515 6.81189 8.6886 6.22002C8.58611 6.06308 8.42933 5.90701 8.11578 5.59486L7.94101 5.42087C7.46438 4.94637 7.22607 4.70912 6.97012 4.58024C6.4611 4.32392 5.85996 4.32392 5.35093 4.58024C5.09499 4.70912 4.85667 4.94637 4.38004 5.42087L4.23867 5.56161C3.76367 6.03449 3.52617 6.27093 3.34478 6.59239C3.1435 6.94909 2.99878 7.5031 3.00001 7.91222C3.00111 8.28092 3.07295 8.53291 3.21664 9.03688C3.98881 11.7453 5.44574 14.3009 7.58743 16.4331C9.72911 18.5652 12.2963 20.0156 15.0168 20.7843C15.523 20.9274 15.7761 20.9989 16.1465 21C16.5575 21.0012 17.114 20.8571 17.4723 20.6568C17.7952 20.4762 18.0327 20.2397 18.5077 19.7669L18.649 19.6261C19.1257 19.1516 19.364 18.9144 19.4934 18.6596C19.7509 18.1528 19.7509 17.5544 19.4934 17.0476C19.364 16.7928 19.1257 16.5556 18.649 16.0811L18.4743 15.9071C18.1607 15.5949 18.0039 15.4388 17.8463 15.3368C17.2518 14.952 16.4853 14.952 15.8908 15.3368C15.7332 15.4388 15.5764 15.5949 15.2628 15.9071C15.1603 16.0091 15.1091 16.0602 15.0491 16.1029C14.8362 16.2547 14.5274 16.3071 14.276 16.2341C14.2052 16.2136 14.1493 16.1869 14.0375 16.1334C12.7374 15.512 11.5189 14.6648 10.4412 13.592Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}body>div.mobile-contact-wrapper .lp-ico-sms:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 11.5C21.0034 12.8199 20.6951 14.1219 20.1 15.3C19.3944 16.7117 18.3098 17.8992 16.9674 18.7293C15.6251 19.5594 14.0782 19.9994 12.5 20C11.1801 20.0034 9.87812 19.6951 8.7 19.1L3 21L4.9 15.3C4.30493 14.1219 3.99656 12.8199 4 11.5C4.00061 9.92176 4.44061 8.37485 5.27072 7.03255C6.10083 5.69025 7.28825 4.60557 8.7 3.9C9.87812 3.30493 11.1801 2.99656 12.5 3H13C15.0843 3.11499 17.053 3.99476 18.5291 5.47086C20.0052 6.94695 20.885 8.91565 21 11V11.5Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 11.5C21.0034 12.8199 20.6951 14.1219 20.1 15.3C19.3944 16.7117 18.3098 17.8992 16.9674 18.7293C15.6251 19.5594 14.0782 19.9994 12.5 20C11.1801 20.0034 9.87812 19.6951 8.7 19.1L3 21L4.9 15.3C4.30493 14.1219 3.99656 12.8199 4 11.5C4.00061 9.92176 4.44061 8.37485 5.27072 7.03255C6.10083 5.69025 7.28825 4.60557 8.7 3.9C9.87812 3.30493 11.1801 2.99656 12.5 3H13C15.0843 3.11499 17.053 3.99476 18.5291 5.47086C20.0052 6.94695 20.885 8.91565 21 11V11.5Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21 11.5C21.0034 12.8199 20.6951 14.1219 20.1 15.3C19.3944 16.7117 18.3098 17.8992 16.9674 18.7293C15.6251 19.5594 14.0782 19.9994 12.5 20C11.1801 20.0034 9.87812 19.6951 8.7 19.1L3 21L4.9 15.3C4.30493 14.1219 3.99656 12.8199 4 11.5C4.00061 9.92176 4.44061 8.37485 5.27072 7.03255C6.10083 5.69025 7.28825 4.60557 8.7 3.9C9.87812 3.30493 11.1801 2.99656 12.5 3H13C15.0843 3.11499 17.053 3.99476 18.5291 5.47086C20.0052 6.94695 20.885 8.91565 21 11V11.5Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}body>div.mobile-contact-wrapper .lp-ico-email:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.5 18L14.85
3471 12M9.14286 12L2.50003 18M2 7L10.1649 12.7154C10.8261 13.1783 11.1567 13.4097 11.5163 13.4993C11.8339 13.5785 12.1661 13.5785 12.4837 13.4993C12.8433 13.4097 13.1739 13.1783 13.8351 12.7154L22 7M6.8 20H17.2C18.8802 20 19.7202 20 20.362 19.673C20.9265 19.3854 21.3854 18.9265 21.673 18.362C22 17.7202 22 16.8802 22 15.2V8.8C22 7.11984 22 6.27976 21.673 5.63803C21.3854 5.07354 20.9265 4.6146 20.362 4.32698C19.7202 4 18.8802 4 17.2 4H6.8C5.11984 4 4.27976 4 3.63803 4.32698C3.07354 4.6146 2.6146 5.07354 2.32698 5.63803C2 6.27976 2 7.11984 2 8.8V15.2C2 16.8802 2 17.7202 2.32698 18.362C2.6146 18.9265 3.07354 19.3854 3.63803 19.673C4.27976 20 5.11984 20 6.8 20Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.5 18L14.8571 12M9.14286 12L2.50003 18M2 7L10.1649 12.7154C10.8261 13.1783 11.1567 13.4097 11.5163 13.4993C11.8339 13.5785 12.1661 13.5785 12.4837 13.4993C12.8433 13.4097 13.1739 13.1783 13.8351 12.7154L22 7M6.8 20H17.2C18.8802 20 19.7202 20 20.362 19.673C20.9265 19.3854 21.3854 18.9265 21.673 18.362C22 17.7202 22 16.8802 22 15.2V8.8C22 7.11984 22 6.27976 21.673 5.63803C21.3854 5.07354 20.9265 4.6146 20.362 4.32698C19.7202 4 18.8802 4 17.2 4H6.8C5.11984 4 4.27976 4 3.63803 4.32698C3.07354 4.6146 2.6146 5.07354 2.32698 5.63803C2 6.27976 2 7.11984 2 8.8V15.2C2 16.8802 2 17.7202 2.32698 18.362C2.6146 18.9265 3.07354 19.3854 3.63803 19.673C4.27976 20 5.11984 20 6.8 20Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M21.5 18L14.8571 12M9.14286 12L2.50003 18M2 7L10.1649 12.7154C10.8261 13.1783 11.1567 13.4097 11.5163 13.4993C11.8339 13.5785 12.1661 13.5785 12.4837 13.4993C12.8433 13.4097 13.1739 13.1783 13.8351 12.7154L22 7M6.8 20H17.2C18.8802 20 19.7202 20 20.362 19.673C20.9265 19.3854 21.3854 18.9265 21.673 18.362C22 17.7202 22 16.8802 22 15.2V8.8C22 7.11984 22 6.27976 21.673 5.63803C21.3854 5.07354 20.9265 4.6146 20.362 4.32698C19.7202 4 18.8802 4 17.2 4H6.8C5.11984 4 4.27976 4 3.63803 4.32698C3.07354 4.6146 2.6146 5.07354 2.32698 5.63803C2 6.27976 2 7.11984 2 8.8V15.2C2 16.8802 2 17.7202 2.32698 18.362C2.6146 18.9265 3.07354 19.3854 3.63803 19.673C4.27976 20 5.11984 20 6.8 20Z' stroke='white' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");}body>div.mobile-contact-wrapper a{-webkit-text-decoration:none;text-decoration:none;}body>div.mobile-contact-wrapper .btn-holder{bottom:-50px;left:19px;position:fixed;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:13px;opacity:0;-webkit-transition:all .3s;transition:all .3s;}@media (max-width: 1024px){body>div.mobile-contact-wrapper .btn-holder{right:19px;padding:0 48px;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;}}body>div.mobile-contact-wrapper .btn-holder.visible{bottom:28px;opacity:1;}body>div.mobile-contact-wrapper .search-btn{width:48px;height:48px;border-radius:999px;font-size:0;line-height:0;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-transition:all .3s;transition:all .3s;padding:12px;color:#141414;background:#fff;}@media (max-width: 1024px){body>div.mobile-contact-wrapper .search-btn{position:absolute;left:0;bottom:0;}}body>div.mobile-contact-wrapper .search-btn:before{width:24px;height:24px;}body>div.mobile-contact-wrapper .connect-btn{height:48px;padding:0 24px;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:4px;text-transform:uppercase;border-radius:25px;color:var(--pillTextColor);background:var(--pillBackgroundColor);border:1px solid rgba(255, 255, 255, 0.2);cursor:pointer;font-size:10px;font-style:normal;font-weight:500;line-height:16px;letter-spacing:0.4px;-webkit-flex-direction:row-reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse;-webkit-transition:all .4s;transition:all .4s;box-shadow:0 0 0 0 rgba(255, 255, 255, 0.2);-webkit-animation:pulsate 2s ease infinite;animation:pulsate 2s ease infinite;}body>div.mobile-contact-wrapper .connect-btn:before{width:16px;height:16px;}body>div.mobile-contact-wrapper .pop-up{display:none;position:fixed;bottom:0;left:0;width:100%;-webkit-transition:all .3s;transition:all .3s;border-radius:16px 16px 0 0;color:var(--textColor);background:var(--backgroundColor);}@media (min-width: 1025px){body>div.mobile-contact-wrapper .pop-up{max-width:637px;border-radius:16px;bottom:28px;left:19px;}}body>div.mobile-contact-wrapper .pop-up .lp-container{padding-top:24px;padding-bottom:24px;-webkit-box-pack:center;-ms-flex-pack:center;
34-webkit-justify-content:center;justify-content:center;}@media (min-width: 1025px){body>div.mobile-contact-wrapper .pop-up .lp-container{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;padding-top:40px;padding-bottom:40px;min-height:450px;padding-left:56px;padding-right:56px;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}}body>div.mobile-contact-wrapper .pop-up .close-btn{width:44px;height:44px;background-color:transparent;border-color:transparent;display:block;margin:24px auto 0;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;color:var(--textColor);cursor:pointer;padding:10px;border:0;}body>div.mobile-contact-wrapper .pop-up .close-btn:before{width:24px;height:24px;}@media (min-width: 1025px){body>div.mobile-contact-wrapper .pop-up .close-btn{position:absolute;top:14px;right:12px;margin:0;}}body>div.mobile-contact-wrapper .pop-up .icon-btn{width:44px;height:44px;border-radius:999px;display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;margin-left:8px;color:var(--textColor);position:relative;}body>div.mobile-contact-wrapper .pop-up .icon-btn:after{position:absolute;bottom:0;width:100%;left:0;border-radius:999px;
34height:100%;content:'';border:solid 1px var(--textColor);opacity:.3;}body>div.mobile-contact-wrapper .pop-up .icon-btn:before{width:24px;height:24px;}body>div.mobile-contact-wrapper .success-block{display:none;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}@media (min-width: 1025px){body>div.mobile-contact-wrapper .success-block{-webkit-align-items:flex-start;-webkit-box-align:flex-start;-ms-flex-align:flex-start;align-items:flex-start;}}body>div.mobile-contact-wrapper .success-block .lp-btn{margin-top:24px;}body>div.mobile-contact-wrapper .contact-form{margin:0;}@media (max-width: 1024px){body>div.mobile-contact-wrapper .contact-form__item{width:100%;}}body>div.mobile-contact-wrapper .contact-form__item .lp-input{width:100%;}body>div.mobile-contact-wrapper .contact-form__item .is-placeholder{color:#818181;}body>div.mobile-contact-wrapper .contact-form__item+.contact-form__item{margin-top:16px;}body>div.mobile-contact-wrapper .contact-form.show-success .contact-form__container,body>div.mobile-contact-wrapper .contact-form.success .contact-form__container{display:none;}body>div.mobile-contact-wrapper .contact-form.show-success .success-block,body>div.mobile-contact-wrapper .contact-form.success .success-block{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}body>div.mobile-contact-wrapper .contact-form .lp-disclaimer{margin-top:16px;}body>div.mobile-contact-wrapper .contact-form .lp-h3{margin-bottom:16px;}body>div.mobile-contact-wrapper .success-message{font-size:22px;}@-webkit-keyframes pulsate{0%{box-shadow:0 0 0 0 rgba(255, 255, 255, 0.2);}50%{box-shadow:0 0 4px 6px rgba(255, 255, 255, .5);}100%{box-shadow:0 0 12px 12px rgba(255, 255, 255, 0);}} 35 </style> 36 <style id="custom-style-css">.social_icons-style-1 a{border-color:rgba(89, 123, 164, 1)!important;background-color:rgba(89, 123, 164, 1)!important;color:rgba(255, 255, 255, 1)!important;}.social_icons-style-1 a:hover{border-color:rgba(47, 79, 106, 1)!important;background-color:rgba(47, 79, 106, 1)!important;color:rgba(255, 255, 255, 1)!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;}.hyperlink-style-1{color:rgba(33, 31, 31, 1)!important;}.hyperlink-style-1:hover{color:rgba(74, 125, 166, 1)!important;}.pagination-style-1 li a{color:rgba(89, 123, 164, 1)!important;background-color:rgba(255, 255, 255, 0)!important;border-color:rgba(89, 123, 164, 1)!important;}.pagination-style-1 li a:hover{color:rgba(255, 255, 255, 1)!important;background-color:rgba(89, 123, 164, 1)!important;border-color:rgba(89, 123, 164, 1)!important;}.pagination-style-1 li.active a{color:rgba(255, 255, 255, 1)!important;background-color:rgba(89, 123, 164, 1)!important;border-color:rgba(89, 123, 164, 1)!important;}.button-style-1{color:rgba(255, 255, 255, 1)!important;background-color:rgba(89, 123, 164, 1)!important;border-color:rgba(89, 123, 164, 1)!important;}.button-style-1:hover{color:rgba(255, 255, 255, 1)!important;background-color:rgba(47, 79, 106, 1)!important;border-color:rgba(47, 79, 106, 1)!important;}.button-style-2{color:rgba(89, 123, 164, 1)!important;background-color:rgba(255, 255, 255, 1)!important;border-color:rgba(255, 255, 255, 1)!important;}.button-style-2:hover{color:rgba(255, 255, 255, 1)!important;background-color:rgba(255, 255, 255, 0)!important;border-color:rgba(255,255,255,1)!important;}.social_icons-style-2 a{border-color:rgba(255, 255, 255, 1)!important;background-color:rgba(0, 0, 0, 0)!important;color:rgba(255, 255, 255, 1)!important;}.social_icons-style-2 a:hover{border-color:rgba(255,255,255,1)!important;background-color:rgba(255, 255, 255, 1)!important;color:rgba(89, 123, 164, 1)!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:rgba(255, 255, 255, 1)!important;background-color:rgba(0, 0, 0, 0)!important;border-color:rgba(255, 255, 255, 1)!important;}.pagination-style-2 li a:hover{color:rgba(89, 123, 164, 1)!important;background-color:rgba(255, 255, 255, 1)!important;border-color:rgba(255, 255, 255, 1)!important;}.pagination-style-2 li.active a{color:rgba(89, 123, 164, 1)!important;background-color:rgba(255, 255, 255, 1)!important;border-color:rgba(255, 255, 255, 1)!important;}.hyperlink-style-2{color:rgba(255, 255, 255, 1)!important;}.hyperlink-style-2:hover{color:rgba(74, 125, 166, 1)!important;}.hyperlink-style-3{color:rgba(0, 0, 0, 1)!important;}.hyperlink-style-3:hover{color:rgba(0, 0, 0, 1)!important;}.button-style-3{color:rgba(255, 255, 255, 1)!important;background-color:rgba(255, 255, 255, 0)!important;border-color:rgba(255, 255, 255, 1)!important;}.button-style-3:hover{color:rgba(89, 123, 164, 1)!important;background-color:rgba(255, 255, 255, 1)!important;border-color:rgba(255, 255, 255, 1)!important;}.hyperlink-style-4{color:rgba(255, 255, 255, 1)!important;}.hyperlink-style-4:hover{color:rgba(255,255,255,1)!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 <meta name="google-site-verification" content="wlEdJ5t9tMmCNjqDuHqXYjnPHrZs9ZxKa0oDcyWWkc0" /> 76 <!-- GOOGLE TRANSLATE STYLES are moved to external CSS --> 77</head> 78<body> 79 <div id="google_translate_element" style="display: none"></div> 80 81 <nav id="global-navbar" data-el-id="618af1e3-0371-4759-8d9c-a88aded9bfde"> 82 <div class="header theme-auto"> 83 <div class="header__left"> 84 <ul class="navigation"> 85 </ul> 86 </div> 87 <a href="/" class="header__logo header__logo--center"> 88 <img src="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/ntbrpekrfgwsgpcr8skr" alt="" class="dark"> 89 <img src="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/ec8b1zyvwotmpto6hfr8" alt="" class="light"> 90 </a> 91 <div class="header__right"> 92 <ul class="navigation"> 93 <li class="navigation__item sub-nav-container"> 94 <a href="#" class="navigation__link">Properties</a> 95 <ul class="sub-nav"> 96 <li class="sub-nav__item"> 97 <a href="/properties/sale" class="sub-nav__link">Active</a> 98 </li> 99 <li class="sub-nav__item"> 100 <a href="/properties/sold" class="sub-nav__link">Sold</a> 101 </li> 102 </ul> 103 </li> 104 <li class="navigation__item"> 105 <a href="/blog" class="navigation__link">Blog</a> 106 </li> 107 <li class="navigation__item"> 108 <a href="/testimonials" class="navigation__link">Testimonials</a> 109 </li> 110 </ul> 111 112 113 <button class="hamburger-component header__hamburger">
114 <svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg"> 115 <circle cx="30" cy="30" r="30" /> 116 <rect class="line line--top" x="18" y="26" width="24" height="2" /> 117 <rect class="line line--bottom" x="30" y="32" width="12" height="2" /> 118 </svg> 119 </button> 120 </div> 121</div> 122 </nav> 123 <div id="global-sidemenu" class="sidemenu" data-el-id="1575f740-92ae-4292-919d-936adffb0f46"> 124 <div class="sidemenu__container"> 125 126 127 <div class="sidemenu__img" style="background-image: url('https://media-production.lp-cdn.com/media/2e6942d9-a651-4b10-a97a-10b6c52c33a0');"> 128 </div> 129 130 <div class="sidemenu__wrapper"> 131 <div class="sidemenu__header"> 132 <button class="sidemenu__toggle toggle sidemenu__toggle--right">Close Menu</button> 133 </div> 134 135 <div class="sidemenu__nav"> 136 <ul class="navigation"> 137 <li class="navigation__item" > 138 <a href="/" class="navigation__link" >Home</a> 139 </li> 140 <li class="navigation__item" > 141 <a href="/properties" class="navigation__link" >Properties</a> 142 </li> 143 <li class="navigation__item sub-nav-container" data-wow-delay="2.2s"> 144 <a href="#" class="navigation__link"> 145 Communities 146 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="arrow" xmlns="http://www.w3.org/2000/svg"> 147 <path d="M7.41 8.58984L12 13.1698L16.59 8.58984L18 9.99984L12 15.9998L6 9.99984L7.41 8.58984Z" fill="currentColor"/> 148 </svg> 149 </a> 150 <ul class="sub-nav"> 151 <li class="sub-nav__item"><a href="/communities" class="sub-nav__link feature">All Communities</a></li> 152 <li class="sub-nav__item"><a href="/temecula-new-homes/temecula" class="sub-nav__link feature">Temecula</a></li> 153 <li class="sub-nav__item"><a href="/temecula-new-homes/murrieta" class="sub-nav__link feature">Murrieta</a></li> 154 <li class="sub-nav__item"><a href="/temecula-new-homes/menifee" class="sub-nav__link feature">Menifee</a></li> 155 <li class="sub-nav__item"><a href="/temecula-new-homes/winchester" class="sub-nav__link feature">Winchester</a></li> 156 <li class="sub-nav__item"><a href="/temecula-new-homes/wildomar" class="sub-nav__link feature">Wildomar</a></li> 157 <li class="sub-nav__item"><a href="/temecula-new-homes/lake-elsinore" class="sub-nav__link feature">Lake Elsinore</a></li> 158 <li class="sub-nav__item"><a href="/temecula-new-homes/riverside-county" class="sub-nav__link feature">Riverside County</a></li> 159 <li class="sub-nav__item"><a href="/temecula-new-homes/san-diego-county" class="sub-nav__link feature">San Diego County</a></li> 160 </ul> 161 162 </li> 163 <li class="navigation__item" > 164 <a href="/buying" class="navigation__link" >Buying</a> 165 </li> 166 <li class="navigation__item" > 167 <a href="/selling" class="navigation__link" >Selling</a> 168 </li> 169 <li class="navigation__item" > 170 <a href="/trade-in" class="navigation__link" >Trade In</a> 171 </li> 172 <li class="navigation__item" > 173 <a href="/cash-offer" class="navigation__link" >Cash Offer</a> 174 </li> 175 <li class="navigation__item" > 176 <a href="/home-search/listings?search_type=envelope&south=33.49325661696914&west=-117.2894014548828&north=33.55936994697054&east=-117.04632894511718&property_type%5B%5D=Co-op&property_type%5B%5D=Condo&property_type%5B%5D=Commercial&property_type%5B%5D=Land&property_type%5B%5D=Multi-family&property_type%5B%5D=Manufactured&property_type%5B%5D=Residential&property_type%5B%5D=Townhouse&property_type%5B%5D=Other&property_type%5B%5D=Co-op&property_type%5B%5D=Condo&property_type%5B%5D=Commercial&property_type%5B%5D=Land&property_type%5B%5D=Multi-family&property_type%5B%5D=Manufactured&property_type%5B%5D=Residential&property_type%5B%5D=Townhouse&property_type%5B%5D=Other&status%5B%5D=Active&status_days%5BActive%5D=36500&status%5B%5D=Under+Contract&status_days%5BUnder+Contract%5D=36500&status%5B%5D=Pending&status_days%5BPending%5D=36500&status_days%5BSold%5D=180&mailer_schedule=instant&order=status_modified_at%3Adesc&page=1" class="navigation__link" >Search Homes </a> 177 </li> 178 <li class="navigation__item sub-nav-container" data-wow-delay="2.8s"> 179 <a href="#" class="navigation__link"> 180 About Us 181 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="arrow" xmlns="http://www.w3.org/2000/svg"> 182 <path d="M7.41 8.58984L12 13.1698L16.59 8.58984L18 9.99984L12 15.9998L6 9.99984L7.41 8.58984Z" fill="currentColor"/> 183 </svg> 184 </a> 185 <ul class="sub-nav"> 186 <li class="sub-nav__item"><a href="/about-us" class="sub-nav__link feature">Meet the Team</a></li> 187 <li class="sub-nav__item"><a href="/testimonials" class="sub-nav__link feature">Testimonials</a></li> 188 <li class="sub-nav__item"><a href="/blog" class="sub-nav__link feature">Blog</a></li> 189 <li class="sub-nav__item"><a href="/past-sales" class="sub-nav__link feature">Past Sales</a></li> 190 </ul> 191 192 </li> 193 <li class="navigation__item" > 194 <a href="/contact" class="navigation__link" >Contact Us</a> 195 </li> 196 </ul> 197 </div> 198 199 <div class="sidemenu__socials"> 200 <ul class="lp-socials social_icons-style-1 sidemenu-social-links"> 201 202 <li> 203 <a 204 href="https://www.facebook.com/TrillionRE/" 205 class="lp-socials__link socials__item" 206 data-wow-delay="0s" 207 target="_blank" 208 aria-label="FACEBOOK" 209 >
210 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 211 </a> 212 </li> 213 <li> 214 <a 215 href="https://www.instagram.com/trillionrealestate/" 216 class="lp-socials__link socials__item" 217 data-wow-delay="0.1s" 218 target="_blank" 219 aria-label="INSTAGRAM" 220 > 221 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 222 </a> 223 </li> 224 <li> 225 <a 226 href="https://www.zillow.com/profile/tysonrobinson" 227 class="lp-socials__link socials__item" 228 data-wow-delay="0.2s" 229 target="_blank" 230 aria-label="ZILLOW" 231 > 232 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 233 </a> 234 </li> 235 <li> 236 <a 237 href="https://www.yelp.com/biz/tyson-robinson-temecula-real-estate-team-temecula?osq=tyson+Trillion" 238 class="lp-socials__link socials__item" 239 data-wow-delay="0.3s" 240 target="_blank" 241 aria-label="YELP" 242 > 243 <span class="lp-i lp-i--yelp" aria-hidden="true"></span> 244 </a> 245 </li> 246 </ul> </div> 247 </div> 248</div> 249 250 </div> 251 <div id="global-mobile-contact" class="mobile-contact-wrapper" data-el-id="befe5af2-a6ca-4119-924a-a85d96d36b51"> 252 <div class="btn-holder" 253style=" 254--backgroundColor: rgba(20, 20, 20, 1); 255--textColor: rgba(255, 255, 255, 1); 256--pillTextColor: rgba(26, 26, 26, 1); 257--pillBackgroundColor: rgba(255, 255, 255, 1);"> 258 259 <button type="button" class="connect-btn lp-ico lp-ico-arrow-top visible">Let's Connect</button> 260</div> 261 262 263<div class="pop-up" style="--backgroundColor: rgba(20, 20, 20, 1); --textColor: rgba(255, 255, 255, 1);"> 264 <div class="lp-container"> 265 <form data-type="contact-form" class="contact-form"> 266 <div class="contact-form__container"> 267 <h3 class="lp-h3">Leave a Message</h3> 268 269 <div class="contact-form__content"> 270 <div class="contact-form__inputs-container"> 271 <div class="contact-form__item"> 272 <input 273 id="__FullName" 274 type="text" name="name" 275 class="lp-input lp-input--light" 276 required 277 placeholder="Full Name"> 278 </div> 279 <div class="contact-form__item"> 280 <input 281 id="__Email" 282 type="email" name="email" 283 class="lp-input js-email-input lp-input--light" 284 required 285 placeholder="Email"> 286 </div> 287 <div class="contact-form__item"> 288 <input 289 class="lp-input lp-input--light" 290 type="tel" 291 placeholder="Phone" 292 id="__Phone" 293 name="phone" 294 required> 295 </div> 296 297 <div class="contact-form__item input-group-select"> 298 <select name="interest" id="-field-interest" placeholder="Interest" 299 class="lp-input dropdown-select js-select js-form-element lp-input--light" required> 300 <option value="" disabled selected data-label="Interest">Interested in...</option> 301 <option value="Selling & Buying">Selling & Buying</option> 302 <option value="Selling">Selling</option> 303 <option value="Buying">Buying</option> 304 <option value="Renting">Renting</option> 305 <option value="Other">Other</option> 306 </select> 307 <label for="-field-interest" class="lp-label sr-only">Interest</label> 308 </div> 309 310 <div class="contact-form__item"> 311 <textarea 312 name="message" 313 rows="4" 314 id="__Message"
315 class="lp-input lp-input--light" 316 placeholder="Message" 317 required></textarea> 318 </div> 319 <div class="lp-disclaimer"> <label style='display: none;' for='-termsAccepted'>Opt In/Disclaimer Consent:</label> 320 <label class='lp-tcr-content'> 321 <span class='lp-tcr-checkbox'> 322 <input 323 type='checkbox' 324 id='-termsAccepted' 325 name='termsAccepted' 326 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 327 oninput="this.setCustomValidity('')" 328 required 329 /></span> 330 <span class='lp-tcr-message'> 331 332 I agree to be contacted by 333 Trillion Real Estate 334 via call, email, and text for real estate services. To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 335 You can also click the unsubscribe link in the emails. Message and data rates may apply. Message frequency may vary. 336 <a href='/terms-and-conditions' target='_blank'>Privacy Policy</a>. 337 </span> 338 </label> 339 340</div> 341 <button type="submit" class="lp-btn lp-btn--filled lp-btn--dark submit contact-form__item">Submit</button> 342 343 <div class="lp-h-pot"> 344 <input id="__newsSignMiddleName" name="middleName" value="" autocomplete="off" tabindex="-1"> 345 </div> 346 347 <input style="display: none;" type="text" name="source" value="CONTACT_INQUIRY" /> 348 <input type="hidden" name="button_text" value="Let's Connect"> 349 </div> 350 </div> 351 </div> 352 353 354 <div class="success-block"> 355 <h3 class="lp-h3 success-message">Thank you for your message. We will be in touch with you shortly.</h3> 356 357 </div> 358 </form> 359 360 <button type="button" class="close-btn lp-ico lp-ico-close"></button> 361 362 </div> 363</div> 364 365 366 </div> 367 <section class="image-section is-font-color-light is-background-color-dark" id="section-28f45f08-c7d4-49ae-b152-9a02d549e538" data-el-id="bce33bae-7388-4ccc-abff-e0ce18ec46d2"> 368
369<script> 370 (function(){ 371 const sectionId = 'section-28f45f08-c7d4-49ae-b152-9a02d549e538' 372 const section = document.querySelector(`#${sectionId}`); 373 var sectionBg = window.getComputedStyle(section, null).getPropertyValue('background-image'); 374 375 if('false' === 'true') { 376 section.classList.add('parallax-enabled'); 377 section.style.setProperty('--sectionBackground', sectionBg); 378 section.style.setProperty('background', 'transparent'); 379 } 380 })() 381</script>
381 382 383 384<div class="opening-with-search height-class-770px "> 385 <div class="center-block"> 386 <button class='lp-play video-section__play-button js-play-btn'></button> 387 <div class="container lp-container"> 388 389 <div class="lp-title-group"> 390 391 392 <h1 class="h1 lp-h1 opening-title lp-pre-line" style="color: rgba(255, 255, 255, 1)">Blog</h1> 393 394 395 </div> 396 397 <div class="buttons"></div> 398 399 </div> 400 </div> 401 <div class="bottom-spacer"></div> 402</div> 403</section> 404<section id="section-3087e602-065d-462f-8f31-f4819d6fc3cd" class="solid-section is-font-color-dark is-background-color-light" data-el-id="fbce30da-e561-48a0-b724-32c0e1f42ba6">
405<script type="text/x-handlebars-template" class="blogItem pagination-script"> <a class="item" href="/blog/{{slug}}"> 406 <figure> 407 {{#if media.[0]}} 408 {{{mediaImage media.[0] sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" alt=../lastName}}} 409 {{/if}} 410 </figure> 411 <div class="text-wrap"> 412 <h5 class="lp-h5">{{title}}</h5> 413 <p class="bf1"> 414 {{hbFormatDate publishedAt}} 415 </p> 416 <p class="item-text"> 417 {{truncate subtitle 168 "â¦"}} 418 {{#numCompare subtitle.length 168 ">"}} 419 <span class="hyperlink-style-3 link">Read more</span> 420 {{/numCompare}} 421 </p> 422 </div> 423 </a> 424</script>
424 425 426<div class="section lp-vertical-paddings"> 427 <div class="lp-container"> 428 429 <div class="lp-title-group"> 430 <h2 class="lp-h2">Featured</h2> 431 432 </div> 433 434 <div class="collection "> 435 </div> 436 </div> 437</div> 438 439</section> 440<section id="section-5d01005b-e93f-48f9-92b7-4822576c8b85" class="solid-section is-font-color-dark is-background-color-light" data-el-id="fbce30da-e561-48a0-b724-32c0e1f42ba6">
441<script type="text/x-handlebars-template" class="blogItem pagination-script"> <a class="item" href="/blog/{{slug}}"> 442 <figure> 443 {{#if media.[0]}} 444 {{{mediaImage media.[0] sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" alt=../lastName}}} 445 {{/if}} 446 </figure> 447 <div class="text-wrap"> 448 <h5 class="lp-h5">{{title}}</h5> 449 <p class="bf1"> 450 {{hbFormatDate publishedAt}} 451 </p> 452 <p class="item-text"> 453 {{truncate subtitle 168 "â¦"}} 454 {{#numCompare subtitle.length 168 ">"}} 455 <span class="hyperlink-style-1 link">Read more</span> 456 {{/numCompare}} 457 </p> 458 </div> 459 </a> 460</script>
460 461 462<div class="section lp-vertical-paddings"> 463 <div class="lp-container"> 464 465 <div class="lp-title-group"> 466 <h2 class="lp-h2">Most Popular</h2> 467 468 </div> 469 470 <div class="collection collection--3 "> 471 </div> 472 </div> 473</div> 474 475</section> 476<section id="section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda" class="solid-section is-font-color-dark is-background-color-light" data-el-id="fbce30da-e561-48a0-b724-32c0e1f42ba6">
477<script type="text/x-handlebars-template" class="blogItem pagination-script"> <a class="item" href="/blog/{{slug}}"> 478 <figure> 479 {{#if media.[0]}} 480 {{{mediaImage media.[0] sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" alt=../lastName}}} 481 {{/if}} 482 </figure> 483 <div class="text-wrap"> 484 <h5 class="lp-h5">{{title}}</h5> 485 <p class="bf1"> 486 {{hbFormatDate publishedAt}} 487 </p> 488 <p class="item-text"> 489 {{truncate subtitle 168 "â¦"}} 490 {{#numCompare subtitle.length 168 ">"}} 491 <span class=" link">Read more</span> 492 {{/numCompare}} 493 </p> 494 </div> 495 </a> 496</script>
496 497 498<div class="section lp-vertical-paddings"> 499 <div class="lp-container"> 500 501 <div class="lp-title-group"> 502 <h2 class="lp-h2">Recently Posted</h2> 503 504 </div> 505 506 <div class="collection collection--3 data-container"> 507 <a class="item" href="/blog/pros-cons-of-online-real-estate-services-tysons-tips"> 508 <figure> 509 <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/dmvdvt6cmraxm1ttle5z34j2" srcset="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/dmvdvt6cmraxm1ttle5z34j2 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/dmvdvt6cmraxm1ttle5z34j2 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/dmvdvt6cmraxm1ttle5z34j2 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 510 </figure> 511 <div class="text-wrap"> 512 <h5 class="lp-h5">Pros & Cons of Online Real Estate Services | Tysonâs Tips</h5> 513 <p class="bf1"> 514 September 3, 2025 515 </p> 516 <p class="item-text"> 517 If youâre thinking of using a non-traditional real estate service to sell your home (i.e. new online brokerages, Redfin, Zillow, etc) and want a little more insight, c⦠518 <span class=" link">Read more</span> 519 </p> 520 </div> 521 </a> 522 <a class="item" href="/blog/becoming-a-real-estate-agent-in-2021-how-hard-work-pays-off-josh-sutton-tyson-robinson"> 523 <figure> 524 <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/dcl6ig7xf224zo533f3bwnm2" srcset="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/dcl6ig7xf224zo533f3bwnm2 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/dcl6ig7xf224zo533f3bwnm2 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/dcl6ig7xf224zo533f3bwnm2 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 525 </figure> 526 <div class="text-wrap"> 527 <h5 class="lp-h5">Becoming a real estate agent in 2021 & how hard work pays off | Josh Sutton & Tyson Robinson</h5> 528 <p class="bf1"> 529 September 3, 2025 530 </p> 531 <p class="item-text"> 532 Itâs no secret that 2020 was a chaotic market for real estate and that has carried on into 2021. Being an intense sellers market, it has made it a super tough time t⦠533 <span class=" link">Read more</span> 534 </p> 535 </div> 536 </a> 537 <a class="item" href="/blog/how-long-it-takes-to-find-a-home-tysons-tips"> 538 <figure> 539 <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/yp256lwpgb0sujld5rw4twr1" srcset="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/yp256lwpgb0sujld5rw4twr1 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/yp256lwpgb0sujld5rw4twr1 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/yp256lwpgb0sujld5rw4twr1 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 540 </figure> 541 <div class="text-wrap"> 542 <h5 class="lp-h5">How Long It Takes to Find a Home | Tysonâs Tips</h5> 543 <p class="bf1"> 544 September 3, 2025 545 </p> 546 <p class="item-text"> 547 Get a general idea of how long it takes to find a home when you are shopping around. Itâs best to have a realistic idea/timeline so you can plan accordingly. Give us aâ¦
548 <span class=" link">Read more</span> 549 </p> 550 </div> 551 </a> 552 <a class="item" href="/blog/i-was-in-a-real-estate-pickleball-commercial-and-its-awesome"> 553 <figure> 554 <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/wq60xtjtnwnk1b7cotjgg1ki" srcset="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/wq60xtjtnwnk1b7cotjgg1ki 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/wq60xtjtnwnk1b7cotjgg1ki 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/wq60xtjtnwnk1b7cotjgg1ki 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 555 </figure> 556 <div class="text-wrap"> 557 <h5 class="lp-h5">I Was in a Real Estate Pickleball Commercial and Its AWESOME!</h5> 558 <p class="bf1"> 559 September 3, 2025 560 </p> 561 <p class="item-text"> 562 Tyson is a top agent in Temecula Valley, broker owner of Trillion Real Estate!â¡ï¸ At Trillion we take the time to teach you the rules and the strategies you need to kno⦠563 <span class=" link">Read more</span> 564 </p> 565 </div> 566 </a> 567 <a class="item" href="/blog/life-balance-success-in-real-estate-brittany-nicola"> 568 <figure> 569 <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/dg2whafwrbtakc16dapztjk6" srcset="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/dg2whafwrbtakc16dapztjk6 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/dg2whafwrbtakc16dapztjk6 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/dg2whafwrbtakc16dapztjk6 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 570 </figure> 571 <div class="text-wrap"> 572 <h5 class="lp-h5">Life Balance & Success in Real Estate | Brittany Nicola</h5> 573 <p class="bf1"> 574 September 3, 2025 575 </p> 576 <p class="item-text"> 577 Listen in to Tyson and Brittany as they discuss how Brittany not only kills it as a single mom of two, but is crushing the real estate game. One huge success Tyson & B⦠578 <span class=" link">Read more</span> 579 </p> 580 </div> 581 </a> 582 <a class="item" href="/blog/key-elements-to-being-a-solo-agent-trillion-real-estate"> 583 <figure> 584 <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/gr0rtmqehpbz0fa8b7x7wkvg" srcset="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/gr0rtmqehpbz0fa8b7x7wkvg 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/gr0rtmqehpbz0fa8b7x7wkvg 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/gr0rtmqehpbz0fa8b7x7wkvg 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 585 </figure> 586 <div class="text-wrap"> 587 <h5 class="lp-h5">Key Elements to Being a Solo Agent | Trillion Real Estate</h5> 588 <p class="bf1"> 589 September 3, 2025 590 </p> 591 <p class="item-text"> 592 What is a key element to being a solo agent? ð¤ At our monthly mastermind we had the pleasure of learning from @andrew_b_lewis & @justinpatrickmccabe on all things reaâ¦
593 <span class=" link">Read more</span> 594 </p> 595 </div> 596 </a> 597 <a class="item" href="/blog/how-to-build-wealth-with-real-estate"> 598 <figure> 599 <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/ixc6fu8gcuzkqpersyv6bvvr" srcset="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/ixc6fu8gcuzkqpersyv6bvvr 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/ixc6fu8gcuzkqpersyv6bvvr 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/ixc6fu8gcuzkqpersyv6bvvr 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 600 </figure> 601 <div class="text-wrap"> 602 <h5 class="lp-h5">How to Build Wealth With Real Estate</h5> 603 <p class="bf1"> 604 September 3, 2025 605 </p> 606 <p class="item-text"> 607 I own 6+ investment properties and plan to grow this number each year. Listen in as I discuss building long-term wealth with investment properties and taking advantage⦠608 <span class=" link">Read more</span> 609 </p> 610 </div> 611 </a> 612 <a class="item" href="/blog/intro-to-dave-ramseys-7-baby-steps-to-financial-stability-elp-agent"> 613 <figure> 614 <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/naj2xblxtmwbygwkr5kq153t" srcset="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/naj2xblxtmwbygwkr5kq153t 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/naj2xblxtmwbygwkr5kq153t 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/naj2xblxtmwbygwkr5kq153t 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 615 </figure> 616 <div class="text-wrap"> 617 <h5 class="lp-h5">Intro to Dave Ramseyâs 7 Baby Steps to Financial Stability â ELP Agent</h5> 618 <p class="bf1"> 619 September 3, 2025 620 </p> 621 <p class="item-text"> 622 Tyson Robinson in a Dave Ramsey trusted, fully vetted endorsed local provider (ELP) in the Temecula Valley. In this video he briefly introduces an overview of Dave Ram⦠623 <span class=" link">Read more</span> 624 </p> 625 </div> 626 </a> 627 <a class="item" href="/blog/sports-cards-investing-in-2021-sports-cards-with-tyson"> 628 <figure> 629 <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/lb77lm9zincgtzgyiej3ies9" srcset="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/lb77lm9zincgtzgyiej3ies9 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/lb77lm9zincgtzgyiej3ies9 1280w, https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/lb77lm9zincgtzgyiej3ies9 1408w" alt="" sizes="(min-width: 769px) 381px, (min-width: 561px) 450px, 100vw" loading="lazy"> 630 </figure> 631 <div class="text-wrap"> 632 <h5 class="lp-h5">Sports Cards Investing in 2021 | Sports Cards with Tyson</h5> 633 <p class="bf1"> 634 September 3, 2025 635 </p> 636 <p class="item-text"> 637 Discussing all things sports cards, what I have, investing tips and answering questions! Visit my instagram @sportscardswithtyson to keep up with my latest new cards! â¦
638 <span class=" link">Read more</span> 639 </p> 640 </div> 641 </a> 642 </div> 643 <div class="lp-pagination pagination-container pagination-style-1"></div> 644 </div> 645</div> 646 647</section> 648<section class="image-section is-font-color-light is-background-color-light" id="section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d" data-el-id="60f303de-57c9-4a3b-87cf-c1b576e15623"> 649
649<script> 650 (function(){ 651 const sectionId = 'section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d' 652 const section = document.querySelector(`#${sectionId}`); 653 var sectionBg = window.getComputedStyle(section, null).getPropertyValue('background-image'); 654 655 if('true' === 'true') { 656 section.classList.add('parallax-enabled'); 657 section.style.setProperty('--sectionBackground', sectionBg); 658 section.style.setProperty('background', 'none'); 659 } 660 })() 661</script>
661 662 663<div class="section work-with-us parallax" style="--opacityColor:rgba(0, 0, 0, 0);"> 664 <div class="lp-container lp-vertical-paddings"> 665 666 <div class="lp-title-group"> 667 <h2 class="lp-h2">Let's Talk</h2> 668 669 <p class="pre-line">Youâve got questions and we canât wait to answer them.</p> 670 </div> 671 672 673 674 675 676 677 678 <div class="btn-container"> 679 <a 680 role="button" 681 data-type="CONTACT_US" 682 class="btn btn--link lp-btn lp-btn--dark lp-btn--outline lp-btn--mob-full-width button-style-2" 683 684 685 686 687 688 > 689 Contact Us 690 </a> 691 </div> 692 </div> 693</div> 694</section> 695<section id="section-dc35c8ba-2e7a-45c2-9887-4d2125099be8" class="solid-section is-font-color-light is-background-color-dark" data-el-id="3614c9e1-cfd9-4976-8483-8f4b825fd098"> 696
696<script type="text/x-handlebars-template" class="jsIGTemplate"> 697 <a href="https://www.instagram.com/{{username}}" target="_blank" class="title-group js-title-group"> 698 <h3 class="lp-h3 js-h3"></h3> 699 <div class="username-group"><h5 class="lp-h5">@{{username}}</h5><svg class="arrow-icon" width="18" height="12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M-.002 7h14.085l-3.558 3.59L11.926 12l5.965-6-5.965-6-1.401 1.41L14.084 5H-.002v2Z" fill="#CCB091"/></svg></div> 700 </a> 701 702 {{#each items}} 703 {{#if mediaUrl}} 704 <div class="item-box"> 705 <a href="https://www.instagram.com/{{../username}}" target="_blank" class="item insta-type-{{mediaType}}"> 706 <div class="item__text"> 707 <h3 class="lp-h3 js-h3"></h3> 708 <div class="username-group"> <h5 class="lp-h5">@{{../username}}</h5> <svg class="arrow-icon" width="18" height="12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M-.002 7h14.085l-3.558 3.59L11.926 12l5.965-6-5.965-6-1.401 1.41L14.084 5H-.002v2Z" fill="#CCB091"/></svg></div> 709 </div> 710 <div class="item__img" style="background-image: url('{{#ifEquals mediaType 'VIDEO'}}{{thumbnailUrl}}{{else}}{{mediaUrl}}{{/ifEquals}}');"></div> 711 {{#ifEquals mediaType 'VIDEO'}} 712 <div class="media-type-icon"> 713 <svg width="30px" height="30px" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M19.369 6.042H2.082A2.086 2.086 0 000 8.125v13.71a2.086 2.086 0 002.083 2.083h17.285a2.086 2.086 0 002.084-2.083V8.125a2.086 2.086 0 00-2.084-2.083zM22.84 17.833l7.16 3.91V8.262l-7.16 3.908v5.662z"/></svg> 714 </div> 715 {{/ifEquals}} 716 {{#if caption}} 717 <div class="item__info"><div class="item__caption">{{caption}}</div></div> 718 {{/if}} 719 </a> 720 </div> 721 {{/if}} 722 {{/each}} 723</script>
723 724 725 726<div class="section"> 727 <div class="lp-container lp-container--l lp-vertical-paddings" style="--userColor: rgba(54, 69, 77, 1);"> 728 <div class="slider-holder js-slider-holder" data-title="Follow Us on Instagram" > 729 <div class="collection js-collection jsIGContainer"></div> 730 </div> 731 </div> 732</div> 733 734 735</section> 736 737 <div id="modals" class="modals"></div> 738 <footer id="global-footer" class="is-font-color-dark is-background-color-light" data-el-id="f3892c1c-f128-483a-b998-cb1ea48c25c1"> 739 <div class="site-footer"> 740 741 <div class="site-footer__bottom"> 742 <div class="lp-container"> 743 744 <div class="site-footer__logo-bar"> 745 <div class="site-footer__logo-container"> 746 <img src="https://media-production.lp-cdn.com/media/4cac18a3-82a3-4329-bd72-a9c00dde8378" alt=""> 747 </div> 748 <div class="site-footer__logo-container"> 749 750 </div> 751 </div> 752 753 <div class="site-footer__info"> 754 <div class="site-footer__col"> 755 <h5 class="lp-h5 site-footer__col-title">Trillion Real Estate </h5> 756 757 <div class="site-footer__col-content lp-a"><p><a href="/cdn-cgi/l/email-protection#5c28252f33321c282e3530303533322e393d30392f283d2839723f3331"><span class="__cf_email__" data-cfemail="77030e0418193703051e1b1b1e18190512161b1204031603125914181a">[email protected]</span></a></p></div> 758 </div> 759 760 761 <div class="site-footer__col"> 762 <h5 class="lp-h5 site-footer__col-title">Address</h5> 763 764 <div class="site-footer__col-content lp-a"><p>41689 Enterprise Circle N, Suite 218</p> 765<p>Temecula, CA 92590</p></div> 766 </div> 767 768 <div class="site-footer__navigation-wrap"> 769 <ul class="footer-navigation"> 770 <li class="footer-navigation__item"> 771 <a href="/" class="footer-navigation__link">Home</a> 772 </li> 773 <li class="footer-navigation__item"> 774 <a href="/properties" class="footer-navigation__link">Properties</a> 775 </li> 776 <li class="footer-navigation__item"> 777 <a href="/communities" class="footer-navigation__link">Communities</a> 778 </li> 779 <li class="footer-navigation__item"> 780 <a href="/home-search/listings?search_type=envelope&south=33.49325661696914&west=-117.2894014548828&north=33.55936994697054&east=-117.04632894511718&property_type%5B%5D=Co-op&property_type%5B%5D=Condo&property_type%5B%5D=Commercial&property_type%5B%5D=Land&property_type%5B%5D=Multi-family&property_type%5B%5D=Manufactured&property_type%5B%5D=Residential&property_type%5B%5D=Townhouse&property_type%5B%5D=Other&property_type%5B%5D=Co-op&property_type%5B%5D=Condo&property_type%5B%5D=Commercial&property_type%5B%5D=Land&property_type%5B%5D=Multi-family&property_type%5B%5D=Manufactured&property_type%5B%5D=Residential&property_type%5B%5D=Townhouse&property_type%5B%5D=Other&status%5B%5D=Active&status_days%5BActive%5D=36500&status%5B%5D=Under+Contract&status_days%5BUnder+Contract%5D=36500&status%5B%5D=Pending&status_days%5BPending%5D=36500&status_days%5BSold%5D=180&mailer_schedule=instant&order=status_modified_at%3Adesc&page=1" class="footer-navigation__link">Search Homes</a> 781 </li> 782 <li class="footer-navigation__item"> 783 <a href="/about-us" class="footer-navigation__link">Meet the Team</a> 784 </li> 785 <li class="footer-navigation__item"> 786 <a href="/contact" class="footer-navigation__link">Contact Us</a> 787 </li> 788 </ul> 789 790 <ul class="lp-socials social_icons-style-1 site-footer__socials socials"> 791 792 <li> 793 <a 794 href="https://www.facebook.com/TrillionRE/" 795 class="lp-socials__link socials__item" 796 data-wow-delay="0s" 797 target="_blank" 798 aria-label="FACEBOOK" 799 >
800 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 801 </a> 802 </li> 803 <li> 804 <a 805 href="https://www.instagram.com/trillionrealestate/" 806 class="lp-socials__link socials__item" 807 data-wow-delay="0.1s" 808 target="_blank" 809 aria-label="INSTAGRAM" 810 > 811 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 812 </a> 813 </li> 814 <li> 815 <a 816 href="https://www.zillow.com/profile/tysonrobinson" 817 class="lp-socials__link socials__item" 818 data-wow-delay="0.2s" 819 target="_blank" 820 aria-label="ZILLOW" 821 > 822 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 823 </a> 824 </li> 825 <li> 826 <a 827 href="https://www.yelp.com/biz/tyson-robinson-temecula-real-estate-team-temecula?osq=tyson+Trillion" 828 class="lp-socials__link socials__item" 829 data-wow-delay="0.3s" 830 target="_blank" 831 aria-label="YELP" 832 > 833 <span class="lp-i lp-i--yelp" aria-hidden="true"></span> 834 </a> 835 </li> 836 </ul> 837 838 </div> 839 </div> 840 841 <div class="site-footer__disclaimer"><p style="font-size: 12px;">All information is deemed reliable but not guaranteed and should be independently reviewed and verified.<br><img style="margin-top: 10px;" src="https://media-production.lp-cdn.com/media/47161e93-f284-42a5-9511-41c21270ea27" width="100px;" height="auto;"> </p></div> 842 843 <div class="site-footer__copy"> 844 <div class="lp-a site-footer__copy-lp"> 845 <span>Powered by</span> 846 <a href="https://www.luxurypresence.com/" class="" target="_blank">Luxury Presence</a> 847 </div> 848 <div>Copyright © <span class="auto-year-update">2026</span> | <a href="/terms-and-conditions">Privacy Policy</a></div> 849 </div> 850 851 </div> 852 </div> 853 854 855 856</div> 857 858 </footer> 859 860 <div id="modal-global-contact-us" class="modal " 861 data-el-id="" 862 style="background-color: ;" 863 > 864 <div class="modal-content"> 865 <div class="contact-form"> 866 <div class="contact-form__close close"></div> 867 <div class="contact-form__container"> 868 869 <form class="contact-form__form" data-type="contact-form" style="color: #FFFFFF; background: rgba(54, 69, 77, 1); fill: #FFFFFF;"> 870 <h5 class="lp-h5 contact-form__title1">Get in Touch</h5> 871 <div class="input-container"> 872 <input id="-cmp-name" name="name" type="text" class="lp-input lp-input--light contact-form__input" placeholder="Name" required> 873 <span class="input-icon"></span> 874 </div> 875 876 <div class="input-container"> 877 <input id="-cmp-email" name="email" type="email" class="lp-input lp-input--light contact-form__input" placeholder="Email" required pattern="[a-zA-Z0-9.!#$%&â*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+"> 878 <span class="input-icon"></span> 879 </div> 880 881 <div class="input-container"> 882 <input id="-cmp-phone" name="phone" type="tel" class="lp-input lp-input--light contact-form__input" placeholder="Phone" required> 883 <span class="input-icon"></span> 884 </div> 885 886 <div class="input-container"> 887 <textarea id="-cmp-contactMessage" placeholder="Message" name="message" required type="text" class="lp-input lp-input--light contact-form__input"></textarea>
888 <span class="input-icon"></span> 889 </div> 890 891 <div class="h-pot"> 892 <label for="-cmp-middleName">Middle Name</label> 893 <input id="-cmp-middleName" name="middleName" value="" autocomplete="off" tabindex="-1"> 894 </div> 895 896 <div class="lp-disclaimer"> <label style='display: none;' for='-termsAccepted'>Opt In/Disclaimer Consent:</label> 897 <label class='lp-tcr-content'> 898 <span class='lp-tcr-checkbox'> 899 <input 900 type='checkbox' 901 id='-termsAccepted' 902 name='termsAccepted' 903 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 904 oninput="this.setCustomValidity('')" 905 required 906 /></span> 907 <span class='lp-tcr-message'> 908 909 I agree to be contacted by 910 Trillion Real Estate 911 via call, email, and text for real estate services. To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 912 You can also click the unsubscribe link in the emails. Message and data rates may apply. Message frequency may vary. 913 <a href='/terms-and-conditions' target='_blank'>Privacy Policy</a>. 914 </span> 915 </label> 916 917 </div> 918 919 <input type="hidden" name="source" value="CONTACT_INQUIRY"> 920 <button class="contact-form__submit btn lp-btn lp-btn--filled button-style-1" type="submit"> 921 922 <span class="sync-icon lp-i--spin"> 923 <svg width="20" height="15" viewBox="0 0 20 15" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> 924 <path d="M16.3636 3.66667L12.7273 7.33333H15.4545C15.4545 10.3675 13.0091 12.8333 10 12.8333C9.08182 12.8333 8.20909 12.6042 7.45455 12.1917L6.12727 13.53C7.24545 14.245 8.57273 14.6667 10 14.6667C14.0182 14.6667 17.2727 11.385 17.2727 7.33333H20L16.3636 3.66667ZM4.54545 7.33333C4.54545 4.29917 6.99091 1.83333 10 1.83333C10.9182 1.83333 11.7909 2.0625 12.5455 2.475L13.8727 1.13667C12.7545 0.421666 11.4273 0 10 0C5.98182 0 2.72727 3.28167 2.72727 7.33333H0L3.63636 11L7.27273 7.33333H4.54545Z" /> 925 </svg> 926 </span> 927 928 <span class="success-icon"> 929 <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> 930 <path d="M10 0C4.48 0 0 4.48 0 10C0 15.52 4.48 20 10 20C15.52 20 20 15.52 20 10C20 4.48 15.52 0 10 0ZM10 18C5.59 18 2 14.41 2 10C2 5.59 5.59 2 10 2C14.41 2 18 5.59 18 10C18 14.41 14.41 18 10 18ZM14.59 5.58L8 12.17L5.41 9.59L4 11L8 15L16 7L14.59 5.58Z" /> 931 </svg> 932 </span> 933 934 Submit<span class="success-text">ted</span> 935 </button> 936 </form> 937 938 <div class="contact-form__details"> 939 <h5 class="lp-h5 contact-form__title2">Contact Details</h5> 940 <div class="company"> 941 <div class="company_contact lp-a"> 942 <p><strong>Trillion Real Estate</strong></p> 943 <p><a href="/cdn-cgi/l/email-protection#a1d5d8d2cecfe1d5d3c8cdcdc8cecfd3c4c0cdc4d2d5c0d5c48fc2cecc"><span class="__cf_email__" data-cfemail="b7c3cec4d8d9f7c3c5dedbdbded8d9c5d2d6dbd2c4c3d6c3d299d4d8da">[email protected]</span></a></p> 944 <p><br /><br /></p> 945 <p>27247 Madison Ave., #300<br />Temecula, CA 92590</p> 946 </div> 947 </div> 948 949 <div class="socials"> 950 <ul class="lp-socials social_icons-style-1 lp-socials socials--theme-1"> 951 952 <li> 953 <a 954 href="https://www.facebook.com/TrillionRE/" 955 class="lp-socials__link lp-socials__link" 956 data-wow-delay="0s" 957 target="_blank" 958 aria-label="FACEBOOK" 959 >
960 <span class="lp-i lp-i--facebook" aria-hidden="true"></span> 961 </a> 962 </li> 963 <li> 964 <a 965 href="https://www.instagram.com/trillionrealestate/" 966 class="lp-socials__link lp-socials__link" 967 data-wow-delay="0.1s" 968 target="_blank" 969 aria-label="INSTAGRAM" 970 > 971 <span class="lp-i lp-i--instagram" aria-hidden="true"></span> 972 </a> 973 </li> 974 <li> 975 <a 976 href="https://www.zillow.com/profile/tysonrobinson" 977 class="lp-socials__link lp-socials__link" 978 data-wow-delay="0.2s" 979 target="_blank" 980 aria-label="ZILLOW" 981 > 982 <span class="lp-i lp-i--zillow" aria-hidden="true"></span> 983 </a> 984 </li> 985 <li> 986 <a 987 href="https://www.yelp.com/biz/tyson-robinson-temecula-real-estate-team-temecula?osq=tyson+Trillion" 988 class="lp-socials__link lp-socials__link" 989 data-wow-delay="0.3s" 990 target="_blank" 991 aria-label="YELP" 992 > 993 <span class="lp-i lp-i--yelp" aria-hidden="true"></span> 994 </a> 995 </li> 996 </ul> </div> 997 </div> 998 </div> 999 </div> 1000 <!-- version 1 --> 1001 </div> 1002 <button class="close lp-close" type="button" aria-label="close modal"> 1003 <span class="lp-close-icon" aria-hidden="true"></span> 1004 </button> 1005 </div> 1006 <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;"> 1007 <style> 1008 #qualifying-questions-modal .custom-select-toggle { 1009 border: 1px solid #848484; 1010 border-radius: 8px; 1011 padding: 12px 16px; 1012 } 1013 1014 #qualifying-questions-modal .custom-select-toggle.is-placeholder { 1015 color: #848484; 1016 } 1017 1018 #qualifying-questions-modal #qualifying-questions-modal-phoneNumber::placeholder { 1019 color: #848484; 1020 opacity: 1; 1021 } 1022 1023 #qualifying-questions-modal .disclaimer-more { 1024 display: none; 1025 } 1026 1027 #qualifying-questions-modal #qualifying-questions-modal-disclaimer-text:not(.collapsed) .disclaimer-more { 1028 display: inline; 1029 } 1030 1031 #qualifying-questions-modal #qualifying-questions-modal-disclaimer-text:not(.collapsed) .disclaimer-ellipsis { 1032 display: none; 1033 } 1034 </style>
vendor: 119 bytes, lines 1034-1035
1034 1035 <script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
1035<script> 1036 function toggleDisclaimerQualifyingQuestionsModal() { 1037 const disclaimerText = document.getElementById('qualifying-questions-modal-disclaimer-text'); 1038 const toggleButton = document.getElementById('qualifying-questions-modal-toggle-disclaimer'); 1039 1040 if (disclaimerText.classList.contains('collapsed')) { 1041 disclaimerText.classList.remove('collapsed'); 1042 toggleButton.textContent = 'Read less'; 1043 toggleButton.setAttribute('aria-expanded', 'true'); 1044 } else { 1045 disclaimerText.classList.add('collapsed'); 1046 toggleButton.textContent = 'Read more'; 1047 toggleButton.setAttribute('aria-expanded', 'false'); 1048 } 1049 } 1050 </script>
1050 1051 <div style="max-width: 400px; width: 95%; margin: 0 auto;"> 1052 <div style="background: #fff; border: none; border-radius: 16px; color: black; box-shadow: none; position: relative;"> 1053 <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;"> 1054 <span aria-hidden="true">×</span> 1055 </button> 1056 <div style="padding: 40px 30px 32px;"> 1057 <div style="text-align: left; margin-bottom: 32px;"> 1058 <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;"> 1059 Thanks, please provide more information to help serve you 1060 </h2> 1061 </div> 1062 <form id="qualifying-questions-modal-form" data-type="contact-form"> 1063 <input type="hidden" name="source" value="PROFILE_UPDATE" /> 1064 <input type="hidden" name="email" value="" id="qualifying-questions-modal-email" /> 1065 <input type="hidden" name="triggerSource" id="qualifying-questions-modal-triggerSource" value="" /> 1066 <label for="qualifying-questions-modal-email" style="display:none;"> 1067 Email 1068 </label> 1069 <label for="qualifying-questions-modal-triggerSource" style="display:none;"> 1070 Trigger Source 1071 </label> 1072 1073 <div style="margin-bottom: 20px;"> 1074 <label for="qualifying-questions-modal-phoneNumber" style="display:none;"> 1075 Phone Number 1076 </label> 1077 <input 1078 type="tel" 1079 id="qualifying-questions-modal-phoneNumber" 1080 name="phoneNumber" 1081 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;" 1082 pattern="^\+?1?[-.\s]?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}$" 1083 required 1084 placeholder="Phone" 1085 aria-label="Phone Number" 1086 inputmode="tel" 1087 autocomplete="tel" 1088 onfocus="this.style.borderColor='black';" 1089 onblur="this.style.borderColor='#848484';" 1090 /> 1091 </div> 1092 1093 <div style="margin-bottom: 20px;"> 1094 <label for="qualifying-questions-modal-interest" style="display:none;"> 1095 Interest 1096 </label> 1097 <select 1098 id="qualifying-questions-modal-interest" 1099 name="interest" 1100 required 1101 data-label="Interest" 1102 aria-label="Interest" 1103 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;" 1104 onfocus="this.style.borderColor='black'; this.style.color='black';" 1105 onblur="this.style.borderColor='#848484'; if(this.value === '') this.style.color='#848484'; else this.style.color='black';" 1106 > 1107 <option value="" style="background: #fff; color: #848484;" disabled selected>Interested in...</option> 1108 <option value="Selling & Buying" style="background: #fff; color: black;">Selling & Buying</option> 1109 <option value="Selling" style="background: #fff; color: black;">Selling</option> 1110 <option value="Buying" style="background: #fff; color: black;">Buying</option> 1111 <option value="Renting" style="background: #fff; color: black;">Renting</option> 1112 <option value="Other" style="background: #fff; color: black;">Other</option> 1113 </select> 1114 </div> 1115 1116 <div style="margin-bottom: 32px; font-size: 12px; line-height: 1.2;"> 1117 <label style='display: none;' for='qualifying-questions-modal-qualifying-questions-termsAccepted'>Opt In/Disclaimer Consent:</label> 1118 <label class='lp-tcr-content'>
1119 <span class='lp-tcr-checkbox'> 1120 <input 1121 type='checkbox' 1122 id='qualifying-questions-modal-qualifying-questions-termsAccepted' 1123 name='termsAccepted' 1124 aria-describedby='qualifying-questions-modal-disclaimer-text' 1125 oninvalid="this.setCustomValidity('Please accept the terms and conditions')" 1126 oninput="this.setCustomValidity('')" 1127 required 1128 /></span> 1129 <span class='lp-tcr-message'> 1130 <div id="qualifying-questions-modal-disclaimer-text" class="collapsed" style="color: #848484;"> 1131 1132 I agree to be contacted by 1133 <strong>Trillion Real Estate</strong> 1134 via call, email, and text <span class="disclaimer-more">for real estate services. 1135 To opt out, you can reply 'stop' at any time or reply 'help' for assistance. 1136 You can also click the unsubscribe link in the emails. 1137 Message and data rates may apply. Message frequency may vary.</span> 1138 <span class="disclaimer-ellipsis">...</span> 1139 <a href='/terms-and-conditions' target='_blank' rel='noopener noreferrer' style='text-decoration: underline;'>Privacy Policy</a>. 1140 </div> 1141 <button 1142 id="qualifying-questions-modal-toggle-disclaimer" 1143 type="button" 1144 style="background: none; border: none; color: #848484; font-size: inherit; font-weight: bold; padding: 0; margin-top: 4px; cursor: pointer;" 1145 aria-expanded="false" 1146 onclick="toggleDisclaimerQualifyingQuestionsModal()" 1147 > 1148 Read more 1149 </button> 1150 </span> 1151 </label> 1152 </div> 1153 1154 <div> 1155 <button 1156 type="submit" 1157 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;" 1158 onmouseover="this.style.background='transparent'; this.style.color='black';" 1159 onmouseout="this.style.background='black'; this.style.color='white';" 1160 > 1161 Submit 1162 </button> 1163 </div> 1164 </form> 1165 </div> 1166 </div> 1167 </div> 1168 </div> 1169 1170
1170<script> 1171 (function() { 1172 var lazyVideos = [].slice.call(document.querySelectorAll("video.lazy")); 1173 1174 if ("IntersectionObserver" in window && lazyVideos.length > 0) { 1175 var lazyVideoObserver = new IntersectionObserver(function(entries, observer) { 1176 entries.forEach(function(video) { 1177 if (video.isIntersecting) { 1178 for (var source in video.target.children) { 1179 var videoSource = video.target.children[source]; 1180 if (typeof videoSource.tagName === "string" && videoSource.tagName === "SOURCE") { 1181 videoSource.src = videoSource.dataset.src; 1182 } 1183 } 1184 1185 video.target.load(); 1186 video.target.classList.remove("lazy"); 1187 lazyVideoObserver.unobserve(video.target); 1188 } 1189 }); 1190 }); 1191 1192 lazyVideos.forEach(function(lazyVideo) { 1193 lazyVideoObserver.observe(lazyVideo); 1194 }); 1195 } 1196 })(); 1197 </script>
1197 1198 1199 <!-- Global site tag (gtag.js) - Google Analytics --> 1200
1200<script defer src="https://www.googletagmanager.com/gtag/js"></script>
1200 1201 1202 <!-- Google Tag Manager --> 1203
1203<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 1204 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 1205 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.defer=true;j.async=false;j.src= 1206 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 1207 })(window,document,'script','dataLayer','GTM-5P9WP78');</script>
1207 1208 <!-- End Google Tag Manager --> 1209 <!-- Google Tag Manager --> 1210
1210<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 1211 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 1212 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.defer=true;j.async=false;j.src= 1213 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 1214 })(window,document,'script','dataLayer','GTM-T9SFQGHZ');</script>
1214 1215 <!-- End Google Tag Manager --> 1216 1217 1218 <!-- LP Tracker (divolte.js) - LP Divolte --> 1219
1219<script>window.divolteLp=window.divolteLp||function(){(divolteLp.q=divolteLp.q||[]).push(arguments)}</script>
1219 1220 <!-- LP Tracker (eventBus) - LP EventBus --> 1221
1221<script>window.eventBusLp=window.eventBusLp||function(){(eventBusLp.q=eventBusLp.q||[]).push(arguments)}</script>
1221 1222 1223
1223<script> 1224 window.dataLayer = window.dataLayer || []; 1225 document.sendGoogleTagManagerEvent = function() { 1226 /* Honor GPC-enabled browsers and prevent GTM event if GPC is enabled. More details: https://global-privacy-control.glitch.me/ */ 1227 if (window.navigator.globalPrivacyControl) { 1228 return; 1229 } 1230 1231 dataLayer.push(arguments); 1232 } 1233 1234 document.sendGoogleTagManagerEvent('js', new Date()); 1235 document.sendGoogleAnalyticsEvent = function(googleEvent) { 1236 document.sendGoogleTagManagerEvent('event', googleEvent.eventAction || googleEvent.hitType, { 1237 event_category: googleEvent.eventCategory, 1238 event_label: googleEvent.eventLabel, 1239 value: googleEvent.eventValue, 1240 page: googleEvent.page, 1241 location: googleEvent.location 1242 }) 1243 }; 1244 1245 document.addEventListener("DOMContentLoaded", function() { 1246 (function() { 1247 //If the page has an element with ID of auto-year-update the element will be populated with the current year. 1248 var date = new Date(); 1249 var elements = document.getElementsByClassName('auto-year-update'); 1250 var i; 1251 for (i = 0; i < elements.length; i++) { 1252 elements[i].innerText = date.getFullYear(); 1253 } 1254 })(); 1255 if (window.divolteLp) { 1256 window.divolteLp('sendLpPageView', {}); 1257 } 1258 if (window.eventBusLp) { 1259 window.eventBusLp('sendLpPageView', {}); 1260 } 1261 }); 1262 </script>
1262 1263 1264 <!-- Facebook Pixel Code --> 1265
1265<script>
vendor: 575 bytes, lines 1265-1274
1265 1266 !function(f,b,e,v,n,t,s) 1267 {if(f.fbq)return;n=f.fbq=function(){n.callMethod? 1268 n.callMethod.apply(n,arguments):n.queue.push(arguments)}; 1269 if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; 1270 n.queue=[];t=b.createElement(e);t.async=!0; 1271 t.src=v;s=b.getElementsByTagName(e)[0]; 1272 s.parentNode.insertBefore(t,s)}(window, document,'script', 1273 'https://connect.facebook.net/en_US/fbevents.js'); 1274 fbq('init', '
1274762334809718416
vendor: 58 bytes, lines 1274-1276
1274'); 1275 fbq('track', 'PageView'); 1276
1276</script>
1276 1277 <noscript><img height="1" width="1" style="display:none" 1278 src="https://www.facebook.com/tr?id=762334809718416&ev=PageView&noscript=1" 1279 /></noscript> 1280 <!-- End Facebook Pixel Code --> 1281 1282
1282<script> 1283 // Function to dynamically load a script 1284 function loadScript(url, callback) { 1285 var script = document.createElement('script'); 1286 script.type = 'text/javascript'; 1287 script.src = url; 1288 script.defer = true; 1289 script.async = true; 1290 script.onload = callback; 1291 document.head.appendChild(script); 1292 } 1293 1294 // Function to dynamically load CSS file 1295 function loadCSS(url) { 1296 var link = document.createElement("link"); 1297 link.rel = "stylesheet"; 1298 link.href = url; 1299 document.head.appendChild(link); 1300 } 1301 1302 // Global methods / variables that can be accessed by element js 1303 window.luxuryPresence = { 1304 libs: { 1305 loadJs: loadScript, 1306 loadCss: loadCSS, 1307 jsLibsMap: { 1308 paroller: { 1309 jsSrc: 'https://d1e1jt2fj4r8r.cloudfront.net/javascript/jquery.paroller.min.js', 1310 isLoaded: false, 1311 }, 1312 ionRangeSlider: { 1313 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/js/ion.rangeSlider.min.js', 1314 cssSrc: 'https://cdnjs.cloudflare.com/ajax/libs/ion-rangeslider/2.3.1/css/ion.rangeSlider.min.css', 1315 isLoaded: false, 1316 requires: ['jquery'], 1317 }, 1318 splide: { 1319 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/splidejs/4.1.4/js/splide.min.js', 1320 cssSrc: 'https://cdnjs.cloudflare.com/ajax/libs/splidejs/4.1.4/css/splide-core.min.css', 1321 isLoaded: false, 1322 }, 1323 wow: { 1324 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/wow/1.1.2/wow.min.js', 1325 isLoaded: false, 1326 }, 1327 jquery: { 1328 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js', 1329 isLoaded: false, 1330 }, 1331 slick: { 1332 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js', 1333 isLoaded: false, 1334 requires: ['jquery'], 1335 }, 1336 libphonenumber: { 1337 jsSrc: 'https://cdnjs.cloudflare.com/ajax/libs/libphonenumber-js/1.11.8/libphonenumber-js.min.js', 1338 isLoaded: false, 1339 }, 1340 }, 1341 }, 1342 }; 1343 </script>
1343 1344
1344<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>
1344 1345
1345<script defer src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.8/handlebars.min.js"></script>
1345 1346
1346<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/handlebars-intl.min.js"></script>
1346 1347
1347<script defer src="https://cdnjs.cloudflare.com/ajax/libs/superagent/4.1.0/superagent.min.js"></script>
1347 1348
1348<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>
1348 1349
1349<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>
1349 1350
1350<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>
1350 1351 1352 <!-- GOOGLE TRANSLATE SCRIPT --> 1353
1353<script> 1354 function googleTranslateElementInit() { 1355 // list of languages can be overridden by setting window.customLanguages in the head JS. example `window.customLanguages = 'en,de,fr,ro,sk,pl'` 1356 // Language codes can be taken from here https://cloud.google.com/translate/docs/languages 1357 const DEFAULT_LANGUAGES = 'en,es,zh-CN,zh-TW,tl,vi,ar,ko,ru,de,fr,hi,pt'; 1358 document.querySelectorAll('[data-type="TRANSLATE"]').forEach(function(translate){ 1359 translate.innerText = ''; 1360 new google.translate.TranslateElement({ 1361 pageLanguage: 'en', 1362 layout: google.translate.TranslateElement.InlineLayout.SIMPLE, 1363 includedLanguages: window.customLanguages || DEFAULT_LANGUAGES, 1364 }, translate); 1365 }); 1366 } 1367 </script>
1367 1368
1368<script defer src="//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit"></script>
1368 1369 1370 <!-- GOOGLE TRANSLATE SCRIPT END--> 1371
1371<script> 1372 1373 // UI helpers 1374 (function () { 1375 1376 function getSideMenu () { 1377 return document.querySelector('body > div.sidemenu'); 1378 } 1379 1380 function sideMenuHide (instant) { 1381 var sideMenu = getSideMenu(); 1382 if (!sideMenu || !sideMenu.classList.contains('visible')) { 1383 return; 1384 } 1385 sideMenu.classList.remove('visible'); 1386 sideMenu.dispatchEvent(new CustomEvent('lpui-hide', { detail: { instant: !!instant } })); 1387 window.dispatchEvent(new CustomEvent('lpui-sidemenu-hide', { detail: {instant: !!instant} })); 1388 } 1389 1390 function sideMenuShow () { 1391 var sideMenu = getSideMenu(); 1392 if (!sideMenu || sideMenu.classList.contains('visible')) { 1393 return; 1394 } 1395 sideMenu.classList.add('visible'); 1396 sideMenu.dispatchEvent(new Event('lpui-show')); 1397 window.dispatchEvent(new Event('lpui-sidemenu-show')); 1398 } 1399 1400 function sideMenuToggle () { 1401 var sideMenu = getSideMenu(); 1402 if (!sideMenu) { 1403 return; 1404 } 1405 var isSideMenuVisible = sideMenu.classList.contains('visible'); 1406 if (isSideMenuVisible) { 1407 sideMenuHide(); 1408 } else { 1409 sideMenuShow(); 1410 } 1411 } 1412 1413 var scrollLockRequests = {}; 1414 window.__debug_scrollLockRequests = scrollLockRequests; 1415 var hideScrollStyles = ''+ 1416 'body {'+ 1417 'overflow: hidden;' + 1418 '}'; 1419 var hideScrollId = 'hide-body-scroll-style'; 1420 1421 function scrollLock () { 1422 var style = document.createElement('style'); 1423 style.id = hideScrollId; 1424 style.textContent = hideScrollStyles; 1425 document.head.appendChild(style); 1426 window.dispatchEvent(new Event('lpui-scroll-locked')); 1427 } 1428 1429 function scrollUnlock () { 1430 document.getElementById(hideScrollId)?.remove(); 1431 window.dispatchEvent(new Event('lpui-scroll-unlocked')); 1432 } 1433 1434 function requestScrollLock (lockId) { 1435 var wasLocked = Object.keys(scrollLockRequests).length > 0; 1436 scrollLockRequests[lockId || '__anonymous_lock'] = true; 1437 if (!wasLocked) { 1438 scrollLock(); 1439 } 1440 } 1441 1442 function releaseScrollLock (lockId) { 1443 if (lockId) { 1444 delete scrollLockRequests[lockId]; 1445 if (!Object.keys(scrollLockRequests).length) { 1446 scrollUnlock(); 1447 } 1448 } else { 1449 scrollLockRequests = {}; 1450 window.__debug_scrollLockRequests = scrollLockRequests; 1451 scrollUnlock(); 1452 } 1453 } 1454 1455 function showModal (modalId, options) { 1456 var modal = document.getElementById(modalId); 1457 if (!modal) { 1458 return; 1459 } 1460 1461 if (!modal.classList.contains('visible')) { 1462 modal.classList.add('visible'); 1463 if (options && options.scrollLock) { 1464 window.lpUI.requestScrollLock(modalId); 1465 } 1466 modal.dispatchEvent(new Event('lpui-show')); 1467 window.dispatchEvent(new CustomEvent('lpui-modal-show', { detail: { id: modalId } })); 1468 } 1469 1470 const visibleModals = document.querySelectorAll('.modal.visible'); 1471 1472 if (visibleModals.length > 1) { 1473 1474 const maxZIndex = Array.from(visibleModals).reduce((maxZIndex, modal) => { 1475 const zIndex = window.getComputedStyle(modal).zIndex; 1476 const parsedZIndex = zIndex !== 'auto' ? parseInt(zIndex) : 0; 1477 return Math.max(maxZIndex, parsedZIndex); 1478 }, 0); 1479 1480 modal.style.zIndex = maxZIndex + 1; 1481 } 1482 1483 } 1484 1485 function _hideModal (modalEl) { 1486 if (!modalEl) { 1487 return; 1488 } 1489 if (!modalEl.classList.contains('visible')) { 1490 return; 1491 } 1492 modalEl.classList.remove('visible'); 1493 modalEl.style.zIndex = '';
1494 modalEl.dispatchEvent(new Event('lpui-hide')); 1495 var modalId = modalEl.getAttribute('id'); 1496 if (modalId) { 1497 window.lpUI.releaseScrollLock(modalId); 1498 window.dispatchEvent(new CustomEvent('lpui-modal-hide', { detail: { id: modalId } })); 1499 } 1500 } 1501 1502 function hideModal (modalId) { 1503 _hideModal(document.getElementById(modalId)); 1504 } 1505 1506 function hideClosestModal (el) { 1507 const modal = el?.closest('.modal'); 1508 if (!modal) { 1509 return; 1510 } 1511 if (modal.classList) { 1512 _hideModal(modal); 1513 return; 1514 } 1515 if (modal.length && modal[0].classList) { 1516 _hideModal(modal[0]); 1517 } 1518 } 1519 1520 window.lpUI = { 1521 sideMenuHide: sideMenuHide, 1522 sideMenuShow: sideMenuShow, 1523 sideMenuToggle: sideMenuToggle, 1524 requestScrollLock: requestScrollLock, 1525 releaseScrollLock: releaseScrollLock, 1526 showModal: showModal, 1527 hideModal: hideModal, 1528 hideClosestModal: hideClosestModal 1529 } 1530 })(); 1531 1532 var stringifyPrimitive = function(v) { 1533 switch (typeof v) { 1534 case 'string': 1535 return v; 1536 1537 case 'boolean': 1538 return v ? 'true' : 'false'; 1539 1540 case 'number': 1541 return isFinite(v) ? v : ''; 1542 1543 default: 1544 return ''; 1545 } 1546 }; 1547 1548 function objectToQuerystring(obj, sep, eq, name) { 1549 // https://github.com/Gozala/querystring/blob/master/encode.js 1550 sep = sep || '&'; 1551 eq = eq || '='; 1552 if (obj === null) { 1553 obj = undefined; 1554 } 1555 1556 if (typeof obj === 'object') { 1557 return Object.keys(obj).map(function(k) { 1558 var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; 1559 if (Array.isArray(obj[k])) { 1560 return obj[k].map(function(v) { 1561 return ks + encodeURIComponent(stringifyPrimitive(v)); 1562 }).join(sep); 1563 } else { 1564 return ks + encodeURIComponent(stringifyPrimitive(obj[k])); 1565 } 1566 }).filter(Boolean).join(sep); 1567 1568 } 1569 1570 if (!name) return ''; 1571 return encodeURIComponent(stringifyPrimitive(name)) + eq + 1572 encodeURIComponent(stringifyPrimitive(obj)); 1573 } 1574 1575 function getPath(url, options) { 1576 var parser = document.createElement('a'); 1577 parser.href = url || ''; 1578 var path = parser.pathname || ''; 1579 if (path[0] !== '/') { 1580 // IE does not return path starting with a slash 1581 path = '/' + path; 1582 } 1583 1584 if (options && options.queryString) { 1585 path += parser.search; 1586 } 1587 1588 return path; 1589 } 1590 1591 function getMLSAutocomplete(keyword) { 1592 var searchURL = window.location.protocol + '//' + window.location.hostname + '/home-search/auto_complete'; 1593 var query = { 1594 kind: 'listings', 1595 query: keyword 1596 }; 1597 1598 return superagent 1599 .get(searchURL) 1600 .query(query) 1601 .then(function(res) { 1602 var data = res.body || []; 1603 /* 1604 Of the format: 1605 [{ 1606 "_index": "listing_auto_completes_production_20190723232720775", 1607 "_type": "listing_auto_complete", 1608 "_id": "235875624", 1609 "_score": 0, 1610 "status_modified_at": 1553250708, 1611 "city": "Jupiter", 1612 "price": 1335743, 1613 "display_address": "10088 Calabrese Trail Unit: 9 Jupiter FL 33478", 1614 "sold_price": null, 1615 "id": 235875624, 1616 "state": "FL", 1617 "photo_urls": [ 1618 "http://cdn.photos.sparkplatform.com/fl/20190322153144208228000000-o.jpg" 1619 ], 1620 "status": "Pending" 1621 }, ...] 1622 */ 1623 1624 return data; 1625 }); 1626 } 1627 1628 function getMLSAutocompleteWithFilters(keyword, filters = {}) { 1629 var searchURL = window.location.protocol + '//' + window.location.hostname + '/home-search/auto_complete'; 1630 1631 return superagent 1632 .get(searchURL) 1633 .query({ kind: 'listings', query: keyword, ...filters }) 1634 .then(function(res) { 1635 /* 1636 Of the format: 1637 [{ 1638 "_index": "listing_auto_completes_production_20190723232720775", 1639 "_type": "listing_auto_complete", 1640 "_id": "235875624", 1641 "_score": 0, 1642 "status_modified_at": 1553250708, 1643 "city": "Jupiter", 1644 "price": 1335743, 1645 "display_address": "10088 Calabrese Trail Unit: 9 Jupiter FL 33478", 1646 "sold_price": null, 1647 "id": 235875624, 1648 "state": "FL", 1649 "photo_urls": [ 1650 "http://cdn.photos.sparkplatform.com/fl/20190322153144208228000000-o.jpg" 1651 ], 1652 "status": "Pending" 1653 }, ...] 1654 */ 1655 return res.body || []; 1656 }); 1657 } 1658 1659 function makeMlsLink(data) { 1660 // Code Dupe from utils/googlePlace 1661 // mapping of google data type to mls data type 1662 var mapping = { 1663 neighborhood: 'neighborhood', 1664 locality: 'place', 1665 administrative_area_level_2: 'county', 1666 administrative_area_level_1: 'state', 1667 postal_code: 'postalcode', 1668 }; 1669 1670 function _filterTypes(types) { 1671 return types.filter(function(type) { return type !== 'political' }); 1672 } 1673 1674 if (!data || !Object.keys(data).length) { 1675 return '/home-search/listings'; 1676 } 1677 1678 var newData = {};
1679 _filterTypes(data.types).forEach(function(type) { 1680 if (mapping[type]) { 1681 newData.search_type = mapping[type]; 1682 } 1683 }); 1684 newData.omnibox = data.formatted_address; 1685 data.address_components.forEach(function(component) { 1686 _filterTypes(component.types).forEach(function(type) { 1687 if (mapping[type]) { 1688 newData[mapping[type]] = component.long_name; 1689 } 1690 }); 1691 }); 1692 var bounds = data.geometry.bounds; 1693 var convertedData = Object.assign(bounds, newData); 1694 1695 return '/home-search/listings?' + objectToQuerystring(convertedData); 1696 } 1697 1698 function debounce(func, wait) { 1699 let timeout; 1700 return function(...args) { 1701 const context = this; 1702 clearTimeout(timeout); 1703 timeout = setTimeout(() => func.apply(context, args), wait) 1704 } 1705 } 1706 1707 // Global methods / variables that can be accessed by element js 1708 window.luxuryPresence = { 1709 ...window.luxuryPresence, 1710 defaults: { 1711 companyId: '2c02232f-2988-4a2c-a284-887b44b8c4fc', 1712 apiGatewayUrl: '/api-gw', 1713 routerUrl: '/api-nv', 1714 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\
1714n }\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-
1714abb7-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
1714onId,\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}"}, 1715 propertyPlaceholderImage: {"id":"bb5bdaa1-db39-4d77-81d2-681e739efdf2","alt":null,"bytes":15291,"width":1500,"format":"png","height":500,"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/mvhcuodaq6xmb56rdja9","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/mvhcuodaq6xmb56rdja9","authorUrl":null,"createdAt":"2023-03-23T16:05:55.316Z","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/mvhcuodaq6xmb56rdja9","sourceUrl":null,"updatedAt":"2023-03-23T16:05:55.316Z","xLargeUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/mvhcuodaq6xmb56rdja9","__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/mvhcuodaq6xmb56rdja9","description":null,"displayName":"pink-pastel-gradient-twitter-header","originalUrl":"https://media-production.lp-cdn.com/cdn-cgi/image/format=auto,quality=85/https://media-production.lp-cdn.com/media/mvhcuodaq6xmb56rdja9","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/mvhcuodaq6xmb56rdja9","originalFileName":"Pink Pastel Gradient Twitter Header"} 1716 }, 1717 objectToQuerystring: objectToQuerystring, 1718 getPath: getPath, 1719 getMLSAutocomplete: getMLSAutocomplete, 1720 getMLSAutocompleteWithFilters: getMLSAutocompleteWithFilters, 1721 makeMlsLink: makeMlsLink, 1722 debounce: debounce, 1723 }; 1724 1725 var wowOffset = 100; 1726 </script>
1726 1727
1727<script> 1728 (() => { 1729 1730function $parcel$interopDefault(a) { 1731 return a && a.__esModule ? a.default : a; 1732} 1733// Entry point for lp-sdk bundle 1734// This file exists to ensure Parcel v2 outputs the correct filename 1735/* global window */ var $53de3c160faf5a81$exports = {}; 1736/* 1737WARNING!!! This file is used both on the server and in client's web-browser 1738please don't require/import any server-side related config/functions here 1739If we need something, let's pass it as a parameter of the factory 1740*/ var $0ce6edea77f7b81a$exports = {}; 1741$0ce6edea77f7b81a$exports = { 1742 getVideoFirstFrame (videoUrl) { 1743 // 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> 1744 // https://cloudinary.com/documentation/video_manipulation_and_delivery#transformation_url_structure 1745 // The problem is that <transformations> and <version> are optional, and <transformations> can be chained separated by '/' 1746 // One more issue to be handled https://cloudinary.com/documentation/advanced_url_delivery_options#seo_friendly_media_asset_urls 1747 const CLOUDINARY_URL = 'https://res.cloudinary.com/'; 1748 const invalid = !videoUrl || !videoUrl.startsWith(CLOUDINARY_URL); 1749 if (invalid) return null; 1750 const urlComponents = videoUrl.replace(CLOUDINARY_URL, '').replace('f_auto:video', 'f_auto').split('/'); 1751 // checking if <asset_type> is not video or videos 1752 if (urlComponents[1].toLowerCase().indexOf('video') === -1) return null; 1753 // Filename is last urlComponent <public_id>.<extension> 1754 let filename = urlComponents[urlComponents.length - 1]; 1755 if (filename.lastIndexOf('.') > -1) filename = `${filename.slice(0, filename.lastIndexOf('.'))}.jpg`; 1756 else filename += '.jpg'; 1757 urlComponents[urlComponents.length - 1] = filename; 1758 // We need to add so_0,eo_0 transformation to the url to target the first frame. 1759 // we want to find the position at which we want to insert it as a chained transformation 1760 const firstFrameTransformation = 'so_0,eo_0'; 1761 let firstFrameTransformationPosition = 3; 1762 // checking cases for SEO-friendly URLs 1763 if (urlComponents[1].toLowerCase() === 'videos' && urlComponents[2] !== '') firstFrameTransformationPosition = 2; 1764 // checking the last transformation position in the url 1765 // https://cloudinary.com/documentation/transformation_reference 1766 // https://regex101.com/r/lE3W6u/1 1767 const transformationRegex = /^(?:[a-z]{1,3}|\$\w+)_\S+$/; 1768 while(firstFrameTransformationPosition < urlComponents.length - 1 && transformationRegex.test(urlComponents[firstFrameTransformationPosition]))firstFrameTransformationPosition++; 1769 urlComponents.splice(firstFrameTransformationPosition, 0, firstFrameTransformation); 1770 return CLOUDINARY_URL + urlComponents.join('/'); 1771 } 1772}; 1773 1774 1775/** 1776 * Create helpers with given Handlebars instance 1777 * 1778 * @param {Object} HandlebarsInstance 1779 * @param {Function} isEqual - we use different implementation if isEqual on server and on client 1780 * @return {Object} 1781 */ function $53de3c160faf5a81$var$handlebarsHelpersFactory(HandlebarsInstance, { isEqual: isEqual }) { 1782 // notice!: code below assumes list is ordered lowest to highest width 1783 const MEDIA_SIZES = [ 1784 { 1785 field: 'thumbnailUrl', 1786 w: 320 1787 }, 1788 { 1789 field: 'smallUrl', 1790 w: 960 1791 }, 1792 { 1793 field: 'mediumUrl', 1794 w: 1280 1795 }, 1796 { 1797 field: 'largeUrl', 1798 w: 1920 1799 }, 1800 { 1801 field: 'xLargeUrl', 1802 w: 2560 1803 }, 1804 { 1805 field: 'xxLargeUrl', 1806 w: 3456 1807 } 1808 ]; 1809 const MEDIA_SIZE_FOR_SRC = 1280; 1810 const lpFormatNumber = function(number, unit, precision) { 1811 const localization = 'en-US'; 1812 if (!number) return ''; 1813 if (typeof number === 'string') { 1814 const trimmed = number.trim(); 1815 if (/[^+\-0-9.]/.test(trimmed)) return number; // returning text 'as is' assuming it already has all formatting 1816 // eslint-disable-next-line 1817 number = parseFloat(trimmed); 1818 if (!number) return ''; 1819 } 1820 const currencyPrecision = typeof precision === 'number' ? precision : 0; 1821 const currencyFractionDigits = Number.isInteger(number) ? 0 : currencyPrecision; 1822 const currencyFractionOptions = { 1823 minimumFractionDigits: currencyFractionDigits, 1824 maximumFractionDigits: currencyFractionDigits 1825 };
1826 if (unit === 'USD') return `$${number.toLocaleString(localization, currencyFractionOptions)}`; 1827 if (unit === 'CAD') return `$${number.toLocaleString(localization, currencyFractionOptions)} CAD`; 1828 if (unit === 'EUR') return `\u{20AC}${number.toLocaleString(localization, currencyFractionOptions)}`; 1829 if (unit === 'GBP') return `\xa3${number.toLocaleString(localization, currencyFractionOptions)}`; 1830 if (unit === 'PERCENT') return `${number}%`; 1831 if (typeof precision === 'number' && precision > -1) return number.toLocaleString(localization, { 1832 maximumFractionDigits: precision 1833 }); 1834 return number.toLocaleString(localization); 1835 }; 1836 const formatFrequency = function(freqUnit) { 1837 switch(freqUnit){ 1838 case 'DAY': 1839 return 'daily'; 1840 case 'MONTH': 1841 return 'monthly'; 1842 case 'WEEK': 1843 return 'weekly'; 1844 case 'YEAR': 1845 return 'yearly'; 1846 case 'QUARTER': 1847 return 'quarterly'; 1848 case 'HALF_YEAR': 1849 return 'semi-annually'; 1850 case 'NIGHTLY': 1851 return 'nightly'; 1852 case 'SEASONAL': 1853 return 'seasonally'; 1854 default: 1855 return ''; 1856 } 1857 }; 1858 const formatFrequencyShort = function(freqUnit) { 1859 switch(freqUnit){ 1860 case 'DAY': 1861 return 'day'; 1862 case 'WEEK': 1863 return 'wk'; 1864 case 'MONTH': 1865 return 'mo'; 1866 case 'YEAR': 1867 return 'yr'; 1868 case 'QUARTER': 1869 return '3 mos'; 1870 case 'HALF_YEAR': 1871 return '6 mos'; 1872 case 'NIGHTLY': 1873 return 'night'; 1874 case 'SEASONAL': 1875 return 'season'; 1876 default: 1877 return ''; 1878 } 1879 }; 1880 const formatFrequencyStd = function(freqUnit) { 1881 switch(freqUnit){ 1882 case 'DAY': 1883 return 'day'; 1884 case 'WEEK': 1885 return 'week'; 1886 case 'MONTH': 1887 return 'month'; 1888 case 'YEAR': 1889 return 'year'; 1890 case 'QUARTER': 1891 return 'quarter'; 1892 case 'HALF_YEAR': 1893 return '6 months'; 1894 case 'NIGHTLY': 1895 return 'night'; 1896 case 'SEASONAL': 1897 return 'season'; 1898 default: 1899 return ''; 1900 } 1901 }; 1902 const formatFrequencyExt = function(unit, options) { 1903 let val; 1904 let separator; 1905 const opts = options || {}; 1906 switch(opts.mode){ 1907 case 'short': 1908 val = formatFrequencyShort(unit); 1909 separator = opts.frequencySeparator || '/'; 1910 break; 1911 case 'standard': 1912 case 'std': 1913 val = formatFrequencyStd(unit); 1914 separator = opts.frequencySeparator || '/'; 1915 break; 1916 case 'long': 1917 default: 1918 val = formatFrequency(unit); 1919 separator = opts.frequencySeparator || ' '; 1920 } 1921 if (!val) return ''; 1922 if (!opts.frequencyTag && !opts.frequencyClass) return separator + val; 1923 return `${separator}<${opts.frequencyTag || 'span'}${opts.frequencyClass ? ` class="${opts.frequencyClass}"` : ''}>${val}</${opts.frequencyTag || 'span'}>`; 1924 }; 1925 return { 1926 isEmpty (arg1, options) { 1927 const result = !arg1 || (typeof arg1 === 'string' ? !arg1.trim() : false) || !Object.keys(arg1).length; 1928 if (options.fn) return result ? options.fn(this) : options.inverse(this); 1929 return result; 1930 }, 1931 ifEquals (arg1, arg2, options) { 1932 return isEqual(arg1, arg2) ? options.fn(this) : options.inverse(this); 1933 }, 1934 'raw-helper': function(options) { 1935 return options.fn(); 1936 }, 1937 striptags (txt) { 1938 if (!txt) return ''; 1939 // Remove HTML Comments (escaping something to not make real html comment by expression) 1940 // eslint-disable-next-line no-useless-escape,no-param-reassign 1941 txt = txt.replace(/<\!--(.|[\r\n])*?-\->/g, ''); 1942 // Remove XML fragments and <style> and <svg> tags with content 1943 // eslint-disable-next-line 1944 txt = txt.replace(/<(xml|style|svg)("[^"]*"|'[^']*'|[^>])*>(.|[\r\n])*?<\/\1>/gi, ''); 1945 // Replace with space 1946 // eslint-disable-next-line no-param-reassign 1947 txt = txt.replace(/ /g, ' '); 1948 // Remove HTML Tags 1949 const regexp = /<\/?("[^"]*"|'[^']*'|[^>])*(>|$)/g; 1950 return txt.replace(regexp, ''); 1951 },
1952 striptagsLineBreaks (txt) { 1953 if (!txt) return ''; 1954 // Remove HTML Comments (escaping something to not make real html comment by expression) 1955 // eslint-disable-next-line no-useless-escape,no-param-reassign 1956 txt = txt.replace(/<\!--(.|[\r\n])*?-\->/g, ''); 1957 // Remove XML fragments and <style> and <svg> tags with content 1958 // eslint-disable-next-line no-param-reassign 1959 txt = txt.replace(/<(xml|style|svg)("[^"]*"|'[^']*'|[^>])*>(.|[\r\n])*?<\/\1>/gi, ''); 1960 // Replace <br> and </p> tags with line break char, skipping last </p> which is not followed by <br> or </p> 1961 // eslint-disable-next-line no-param-reassign 1962 txt = txt.replace(/(<br\s*\/?>)|(<\/p\s*>)(?=.*(<\/p\s*>|<br\s*\/?>))/g, ' '); 1963 // Replace with space 1964 // eslint-disable-next-line no-param-reassign 1965 txt = txt.replace(/ /g, ' '); 1966 // Remove HTML Tags 1967 const regexp = /<\/?("[^"]*"|'[^']*'|[^>])*(>|$)/g; 1968 return txt.replace(regexp, ''); 1969 }, 1970 truncate (text, limit, suffix) { 1971 if (!text) return ''; 1972 if (text.length > limit) return typeof suffix === 'string' ? text.slice(0, limit - suffix.length) + suffix : text.slice(0, limit); 1973 return text; 1974 }, 1975 // eslint-disable-next-line 1976 setSubArray (items = [], start, end, subArrayName, options) { 1977 // eslint-disable-next-line 1978 options.data.root[subArrayName] = items.slice(start, end); 1979 }, 1980 formatAreaUnit (areaUnit) { 1981 switch(areaUnit){ 1982 case 'ACRE': 1983 return 'Acres'; 1984 case 'SQUARE_METER': 1985 return 'Sq.M.'; 1986 case 'SQUARE_YARD': 1987 return 'Sq.Yd.'; 1988 default: 1989 return 'Sq.Ft.'; 1990 } 1991 }, 1992 formatFrequency: formatFrequency, 1993 formatFrequencyShort: formatFrequencyShort, 1994 lpFormatFrequency (freqUnit, options) { 1995 return formatFrequencyExt(freqUnit, options && options.hash); 1996 }, 1997 subArray (index, start, end, options) { 1998 return index >= start && index < end ? options.fn(this) : options.inverse(this); 1999 }, 2000 formatPropertyStatus (status, showSoldDate, date) { 2001 let formattedStatus = ''; 2002 switch(status){ 2003 case 'SOLD': 2004 formattedStatus = 'Sold'; 2005 break; 2006 case 'FOR_SALE': 2007 formattedStatus = 'For Sale'; 2008 break; 2009 case 'COMING_SOON': 2010 formattedStatus = 'Coming Soon'; 2011 break; 2012 case 'INACTIVE': 2013 formattedStatus = 'Inactive'; 2014 break; 2015 case 'POCKET_LISTING': 2016 formattedStatus = 'Pocket Listing'; 2017 break; 2018 case 'PENDING': 2019 formattedStatus = 'Pending'; 2020 break; 2021 case 'UNDER_CONTRACT': 2022 formattedStatus = 'Under Contract'; 2023 break; 2024 case 'ACTIVE_UNDER_CONTRACT': 2025 formattedStatus = 'Active Under Contract'; 2026 break; 2027 default: 2028 formattedStatus = status; 2029 } 2030 // `typeof date === 'string'` matters: when called as 2031 // `{{formatPropertyStatus status}}` Handlebars passes its options object 2032 // as `showSoldDate`/`date`, and this guard keeps the legacy output. 2033 if ((formattedStatus === 'Sold' || formattedStatus === 'Leased') && showSoldDate && typeof date === 'string' && date) formattedStatus += ` on ${date}`; 2034 return formattedStatus; 2035 }, 2036 formatGrade (gradeValue) { 2037 switch(gradeValue){ 2038 case 'UNDERGRADUATE_COURSES': 2039 return 'Undergrad'; 2040 case 'PRE_KINDERGARTEN': 2041 return 'PreK'; 2042 case 'KINDERGARTEN': 2043 return 'K'; 2044 case 'FIRST_GRADE': 2045 return '1st'; 2046 case 'SECOND_GRADE': 2047 return '2nd'; 2048 case 'THIRD_GRADE': 2049 return '3rd'; 2050 case 'FOURTH_GRADE': 2051 return '4th'; 2052 case 'FIFTH_GRADE': 2053 return '5th'; 2054 case 'SIXTH_GRADE': 2055 return '6th'; 2056 case 'SEVENTH_GRADE': 2057 return '7th'; 2058 case 'EIGHTH_GRADE': 2059 return '8th'; 2060 case 'NINTH_GRADE': 2061 return '9th'; 2062 case 'TENTH_GRADE': 2063 return '10th'; 2064 case 'ELEVENTH_GRADE': 2065 return '11th'; 2066 case 'TWELFTH_GRADE': 2067 return '12th'; 2068 default: 2069 return ''; 2070 } 2071 }, 2072 encodeURI (uri) { 2073 return encodeURI(uri); 2074 }, 2075 encodeURIComponent (txt) { 2076 return encodeURIComponent(txt); 2077 }, 2078 hbFormatDate (date, type) { 2079 if (!date) return ''; 2080 const newDate = date === 'current' ? new Date() : new Date(date); 2081 if (Number.isNaN(newDate.getTime())) return ''; 2082 const months = [ 2083 'January', 2084 'February', 2085 'March', 2086 'April', 2087 'May', 2088 'June', 2089 'July', 2090 'August', 2091 'September', 2092 'October',
2093 'November', 2094 'December' 2095 ]; 2096 if (type === 'YYYY' || type === 'year') return `${newDate.getFullYear()}`; 2097 if (type === 'MM/DD/YY') { 2098 let month = newDate.getMonth() + 1; 2099 if (month < 10) month = `0${month.toString()}`; 2100 return `${month}/${newDate.getDate()}/${newDate.getFullYear().toString().substr(-2)}`; 2101 } 2102 return `${months[newDate.getMonth()]} ${newDate.getDate()}, ${newDate.getFullYear()}`; 2103 }, 2104 openHousesFormat (startDate, endDate, timeZone) { 2105 if (!startDate || !endDate) return ''; 2106 /** 2107 * return formatted time. 2108 * @param {date} date 2109 * @return {time} time. 2110 */ const getTime = (date)=>{ 2111 let hours = date.getHours(); 2112 if (hours > 12) hours -= 12; 2113 const prefix = '0'; 2114 let time = `${hours}:${date.getMinutes() > 9 ? date.getMinutes() : prefix + date.getMinutes()}`; 2115 if (date.getHours() > 11) time += 'PM'; 2116 else time += 'AM'; 2117 return time; 2118 }; 2119 let tz = ''; 2120 let startFDate = startDate; 2121 let endFDate = endDate; 2122 if (timeZone) { 2123 startFDate = new Date(startDate).toLocaleString('en-US', { 2124 timeZone: timeZone 2125 }); 2126 endFDate = new Date(endDate).toLocaleString('en-US', { 2127 timeZone: timeZone 2128 }); 2129 [, , , tz] = new Date(endDate).toLocaleString('en-US', { 2130 timeZone: timeZone, 2131 timeZoneName: 'short' 2132 }).split(' '); 2133 } 2134 const date = new Date(startFDate); 2135 const openHouseDate = date.toLocaleDateString('en-US'); 2136 const time1 = getTime(date); 2137 const date2 = new Date(endFDate); 2138 const time2 = getTime(date2); 2139 return `${openHouseDate} ${time1} - ${time2} ${tz}`; 2140 }, 2141 math (lvalue, operator, rvalue) { 2142 // eslint-disable-next-line 2143 lvalue = parseFloat(lvalue); 2144 // eslint-disable-next-line 2145 rvalue = parseFloat(rvalue); 2146 return ({ 2147 '+': lvalue + rvalue, 2148 '-': lvalue - rvalue, 2149 '*': lvalue * rvalue, 2150 '/': lvalue / rvalue, 2151 '%': lvalue % rvalue 2152 })[operator]; 2153 }, 2154 findByKey (values, key, value, options) { 2155 const result = (values || []).find((entry)=>entry[key] === value); 2156 return options.fn(result); 2157 }, 2158 capitalize (string, split, options) { 2159 let capitalized; 2160 if (typeof string === 'string' && string.length) capitalized = string.toLowerCase().split(split).map((w)=>w.charAt(0).toUpperCase() + w.slice(1)).join(' ').toString(); 2161 return capitalized; 2162 }, 2163 and (...arg) { 2164 return Array.prototype.slice.call(arg).every(Boolean); 2165 }, 2166 or (...arg) { 2167 return Array.prototype.slice.call(arg, 0, -1).some(Boolean); 2168 }, 2169 not (arg) { 2170 return !arg; 2171 }, 2172 orderByIds (items, orderedIds, options) { 2173 let ret = ''; 2174 let data = null; 2175 if (options.data) data = HandlebarsInstance.createFrame(options.data); 2176 // When passed orderedIds, filter out empty items including empty strings and 2177 // only show items (and in that order) in a loop 2178 // If no orderedIds provided, show all items 2179 const filteredIds = (orderedIds || []).filter(Boolean); 2180 if (filteredIds && filteredIds.length) { 2181 let skippedItems = 0; 2182 ret = filteredIds.reduce((res, id, idx)=>{ 2183 const item = (items || []).find((item)=>item.id === id); 2184 // ID doesn't exist so ditch it 2185 if (!item) { 2186 skippedItems += 1; 2187 return res; 2188 } 2189 if (data) data.index = idx - skippedItems; 2190 return res + options.fn(item, { 2191 data: data 2192 }); 2193 }, ''); 2194 } else for(let i = 0, j = (items || []).length; i < j; i++){ 2195 if (data) data.index = i; 2196 ret += options.fn(items[i], { 2197 data: data 2198 }); 2199 } 2200 return ret; 2201 }, 2202 ifIn (elem, list, options) { 2203 if (list && list.indexOf(elem) > -1) return options.fn(this); 2204 return options.inverse(this); 2205 }, 2206 partial (name, options) { 2207 // Register a partial and output the content in a script for clientside rendering 2208 HandlebarsInstance.registerPartial(name, HandlebarsInstance.compile(options.fn())); 2209 // eslint-disable-next-line 2210 const script = `
2210<script type="text/x-handlebars-template" class="${name} pagination-script">${options.fn()}</${'script'}>`; 2211 return script; 2212 }, 2213 numCompare (l, r, operator, options) { 2214 const operators = { 2215 '==': function(l, r) { 2216 // eslint-disable-next-line 2217 return l == r; 2218 }, 2219 '===': function(l, r) { 2220 return l === r; 2221 }, 2222 '!=': function(l, r) { 2223 // eslint-disable-next-line 2224 return l != r; 2225 }, 2226 '<': function(l, r) { 2227 return l < r; 2228 }, 2229 '>': function(l, r) { 2230 return l > r; 2231 }, 2232 '<=': function(l, r) { 2233 return l <= r; 2234 }, 2235 '>=': function(l, r) { 2236 return l >= r; 2237 } 2238 }; 2239 const result = operators[operator](l, r); 2240 if (result) return options.fn ? options.fn(this) : true; 2241 return options.fn ? options.inverse(this) : false; 2242 }, 2243 lpFormatNumber: lpFormatNumber, 2244 arrayToHtmlList (arr, options) { 2245 // eslint-disable-next-line 2246 return arr.reduce((acc, current)=>{ 2247 try { 2248 if (typeof current === 'object' || current === null) return acc; 2249 // eslint-disable-next-line 2250 return acc += `<li>${current}</li>`; 2251 } catch (e) { 2252 /* empty */ } 2253 }, ''); 2254 }, 2255 join (array, separator, options) { 2256 if (!array || !array.join) return ''; 2257 return array.join(typeof separator === 'string' ? separator : ', '); 2258 }, 2259 dedupe (items, key, options) { 2260 let data = null; 2261 let skippedItems = 0; 2262 const list = new Set(); 2263 if (options.data) data = HandlebarsInstance.createFrame(options.data); 2264 return (items || []).reduce((res, item, idx)=>{ 2265 const val = key ? item[key] : item; 2266 // Item is unique if we're passed a key that is not defined 2267 const isUnique = !list.has(val) || key && val === undefined; 2268 if (isUnique) list.add(val); 2269 else { 2270 skippedItems += 1; 2271 return res; 2272 } 2273 if (data) data.index = idx - skippedItems; 2274 return res + options.fn(item, { 2275 data: data 2276 }); 2277 }, ''); 2278 }, 2279 JSONstringify (obj, options) { 2280 return JSON.stringify(obj); 2281 }, 2282 // eslint-disable-next-line sonarjs/cognitive-complexity 2283 formatPropertyPrice (...args) { 2284 const options = args[args.length - 1]; 2285 const opts = options.hash; 2286 const property = args.length > 1 ? args[0] : opts.property; 2287 const precision = opts.precision || 0; 2288 if (args.length > 2) // eslint-disable-next-line 2289 opts.mode = args[1]; 2290 if (!opts.mode) opts.mode = 'short'; 2291 let separator = args.length > 3 ? args[2] : opts.separator; 2292 if (typeof separator !== 'string') separator = ' | '; 2293 if (property.isPasswordProtected) return 'Exclusive Listing'; 2294 let priceUponRequestText = args.length > 4 ? args[3] : opts.priceUponRequestText; 2295 if (typeof priceUponRequestText !== 'string') priceUponRequestText = 'Price Upon Request'; 2296 if (property.priceUponRequest) return priceUponRequestText; 2297 let price = property.salesPrice; 2298 let reduced = null; 2299 // eslint-disable-next-line sonarjs/no-collapsible-if 2300 if (property.status !== 'SOLD' && property.status !== 'Leased') { 2301 if (property.reducedPrice) { 2302 if (!opts.reducedClass) price = property.reducedPrice; 2303 else reduced = property.reducedPrice; 2304 } 2305 } 2306 let result = ''; 2307 if (reduced) result += `<${opts.reducedTag || 'span'} class="${opts.reducedClass}">${lpFormatNumber(reduced, property.currency, precision)}</${opts.reducedTag || 'span'}>`; 2308 if (price) { 2309 price = lpFormatNumber(price, property.currency, precision); 2310 if (opts.priceTag || opts.priceClass) result += `<${opts.priceTag || 'span'}${opts.priceClass ? ` class="${opts.priceClass}"` : ''}>${price}</${opts.priceTag || 'span'}>`; 2311 else result += price; 2312 } 2313 const leasePrice = property.leasePrice && lpFormatNumber(property.leasePrice, property.currency, precision); 2314 if (leasePrice) { 2315 if (result) result += separator; 2316 const leaseText = `${leasePrice}${formatFrequencyExt(property.leasePeriod, opts)}`; 2317 if (opts.leaseTag || opts.leaseClass) result += `<${opts.leaseTag || 'span'}${opts.leaseClass ? ` class="${opts.leaseClass}"` : ''}>${leaseText}</${opts.leaseTag || 'span'}>`; 2318 else result += leaseText; 2319 } 2320 return new HandlebarsInstance.SafeString(result); 2321 }, 2322 // eslint-disable-next-line sonarjs/cognitive-complexity 2323 mediaImage (mediaSource, options = {}) { 2324 if (!mediaSource) return; 2325 const { lazy: lazy, ...attributes } = options.hash; 2326 let src = ''; 2327 let srcset = ''; 2328 if (typeof mediaSource === 'string') src = mediaSource; 2329 else { 2330 const srcMap = {}; 2331 const { height: height, width: width, altTagText: altTagText } = mediaSource; 2332 MEDIA_SIZES.forEach((src)=>{ 2333 if (mediaSource[src.field] && !srcMap[mediaSource[src.field]]) srcMap[mediaSource[src.field]] = src.w; 2334 }); 2335 const srcs = Object.keys(srcMap); 2336 if (!srcs.length) return; 2337 if (altTagText) attributes.alt = altTagText; 2338 if (height && width) { 2339 if (height >= width) { 2340 if (attributes.class) attributes.class += ' portrait'; 2341 else attributes.class = 'portrait'; 2342 } 2343 if ('setDims' in options.hash) { 2344 attributes.width = width; 2345 attributes.height = height; 2346 } 2347 } 2348 if (srcs.length > 1 && width && width < srcMap[srcs[srcs.length - 1]]) { 2349 const maxSrc = srcs.findIndex((src)=>
2349srcMap[src] >= width); 2350 srcs.splice(maxSrc + 1); 2351 srcMap[srcs[srcs.length - 1]] = width; 2352 } 2353 if (srcs.length > 1) { 2354 srcset = srcs.map((s)=>`${s} ${srcMap[s]}w`).join(', '); 2355 srcs.sort((a, b)=>Math.abs(srcMap[a] - MEDIA_SIZE_FOR_SRC) - Math.abs(srcMap[b] - MEDIA_SIZE_FOR_SRC)); 2356 } 2357 // eslint-disable-next-line 2358 src = srcs[0]; 2359 } 2360 delete attributes.setDims; 2361 if (!srcset) delete attributes.sizes; 2362 if (!('alt' in attributes)) attributes.alt = ''; 2363 if (!('loading' in attributes) && (options?.data?.root?.sectionIndex > 1 || lazy)) attributes.loading = 'lazy'; 2364 const attrs = Object.keys(attributes).map((k)=>`${HandlebarsInstance.escapeExpression(k)}="${HandlebarsInstance.escapeExpression(attributes[k])}"`).join(' '); 2365 return new HandlebarsInstance.SafeString(// eslint-disable-next-line sonarjs/no-nested-template-literals 2366 `<img src="${src}"${srcset ? ` srcset="${srcset}"` : ''}${attrs ? ` ${attrs}` : ''}>`); 2367 }, 2368 bgImage (mediaSource) { 2369 if (!mediaSource) return ''; 2370 if (typeof mediaSource === 'string') return mediaSource; 2371 return mediaSource.largeUrl || mediaSource.url || ''; 2372 }, 2373 concat (...rest) { 2374 let outStr = ''; 2375 // rest params are Array instances while arguments object is not 2376 for(const arg in rest)if (rest[arg] !== null && rest[arg] !== undefined && typeof rest[arg] !== 'object') outStr += rest[arg]; 2377 return outStr; 2378 }, 2379 setVideoExtension (uri, format) { 2380 if (!uri || !format) return ''; 2381 const decodedUri = new URL(uri); 2382 if (decodedUri.host !== 'res.cloudinary.com') { 2383 // Not a cloudinary uri, do not rewrite 2384 if (decodedUri.pathname.endsWith(`.${format}`)) return uri; 2385 return ''; 2386 } 2387 // Rewrite the cloudinary uri 2388 const lastPeriodIdx = decodedUri.pathname.lastIndexOf('.'); 2389 let pathWithoutFormat = decodedUri.pathname; 2390 if (lastPeriodIdx !== -1) pathWithoutFormat = decodedUri.pathname.substr(0, lastPeriodIdx); 2391 decodedUri.pathname = `${pathWithoutFormat}.${format}`; 2392 return decodedUri.toString(); 2393 }, 2394 addVideoSources (uri) { 2395 if (!uri) return ''; 2396 const VIDEO_VARIANTS = [ 2397 { 2398 format: 'webm', 2399 codec: 'vp9', 2400 htmlCodecs: ';codecs=vp9' 2401 }, 2402 { 2403 format: 'mp4', 2404 codec: 'h265', 2405 htmlCodecs: ';codecs=hvc1' 2406 }, 2407 { 2408 format: 'mp4', 2409 codec: 'h264', 2410 htmlCodecs: '' 2411 } 2412 ]; 2413 const VIDEO_EXT_REGEXP = /\.(?:3gp|mpeg|mp4|m4v|m4p|ogv|ogg|mov|webm)$/; 2414 if (!uri.includes('res.cloudinary.com')) // keep existing or no extension for non Cloudinary urls 2415 return `<source data-src="${uri}">`; 2416 // Cap delivery at 1080p (1920px wide) while preserving aspect ratio. Width-only: 2417 // c_limit never upscales, so portrait sources are left at their native size. 2418 // No q_auto: quality is left to Cloudinary's default rather than forced down. 2419 // Note the regex below consumes any q_auto in the source URL, so it is dropped 2420 // rather than carried through. 2421 const CONSTRAINED_DELIVERY = 'w_1920,c_limit'; 2422 const uriNoExt = uri.replace(VIDEO_EXT_REGEXP, ''); 2423 const sources = VIDEO_VARIANTS.map((type)=>{ 2424 const cloudinaryFormatUrl = `${uriNoExt.replace(/f_auto(?::video)?(?:,q_auto(?::\w+)?)?/, `f_${type.format},vc_${type.codec},${CONSTRAINED_DELIVERY}`)}.${type.format}`; 2425 return `<source data-src="${cloudinaryFormatUrl}" type="video/${type.format}${type.htmlCodecs}">`; 2426 }); 2427 return sources.join('\n'); 2428 }, 2429 cloudinaryVideoFirstFrame: $0ce6edea77f7b81a$exports.getVideoFirstFrame, 2430 initials (text, options) { 2431 if (!text) return ''; 2432 if (options?.hash?.endChar) // eslint-disable-next-line 2433 text = text.split(options.hash.endChar)[0]; 2434 // eslint-disable-next-line 2435 const maxChars = +options?.hash?.maxChars || 2; // we do not allow this to be 0 2436 let result = text.match(/\b\w/g)?.slice(0, maxChars); 2437 if (!result) return ''; 2438 if (options?.hash?.addText) result = result.map((item)=>item + options.hash.addText); 2439 return result.join(options?.hash?.joinWith || '').toUpperCase(); 2440 }, 2441 normalizePhone (phone) { 2442 if (!phone) return ''; 2443 const str = String(phone).trim(); 2444 return (str.startsWith('+') ? '+' : '') + str.replace(/\D/g, ''); 2445 } 2446 }; 2447} 2448$53de3c160faf5a81$exports = $53de3c160faf5a81$var$handlebarsHelpersFactory; 2449 2450 2451/* global window, document */ // eslint-disable-next-line import/prefer-default-export 2452class $dc851cf3939b6ef5$export$ff0712c26d5458f7 { 2453 /** 2454 * Initialize Modal Controller 2455 * @param {Object} options 2456 */ constructor(options){ 2457 this.isBuilder = false;
2458 this.companyId = null; 2459 this.websiteId = null; 2460 this.pageId = null; 2461 this.shareId = null; 2462 this.pageMeta = null; 2463 this.websiteApiGatewayUrl = null; 2464 this.buyerSellerServiceUrl = null; 2465 this.leadIngesterUrl = null; 2466 this.divolteServiceUrl = null; 2467 this.eventBus = null; 2468 this.templateId = null; 2469 this.myhomeDashboardEnabled = false; 2470 this.mlsEnabled = false; 2471 this.hstValuationRedirectEnabled = false; 2472 this.googleMapsAPIKey = null; 2473 } 2474 /** 2475 * Controllers must implement a "destroy" method to be called 2476 * after page navigation to remove event handlers / cleanup 2477 */ destroy() {} 2478 /** 2479 * Set page and supporting data 2480 * @param {string} pageId 2481 * @param {Object} options 2482 */ setPage(pageId, options = {}) { 2483 this.pageId = pageId; 2484 this.shareId = options.shareId || null; 2485 this.pageMeta = { 2486 sourceResource: options.sourceResource, 2487 pageElementId: options.pageElementId, 2488 pageQueryVariables: options.pageQueryVariables || {} 2489 }; 2490 } 2491 /** 2492 * Sets if we are in pagebuilder context or not 2493 * @param {boolean} isBuilder 2494 */ setIsBuilder(isBuilder) { 2495 this.isBuilder = isBuilder; 2496 } 2497 /** 2498 * Set Company and supporting data 2499 * @param {string} companyId 2500 * @param {Object} options 2501 */ setCompany(companyId, options = {}) { 2502 this.companyId = companyId; 2503 } 2504 /** 2505 * Set website and supporting data 2506 * @param {string} websiteId 2507 * @param {Object} options 2508 */ setWebsite(websiteId, options = {}) { 2509 this.websiteId = websiteId; 2510 if (options.myhomeDashboardEnabled) this.myhomeDashboardEnabled = options.myhomeDashboardEnabled; 2511 if (options.mlsEnabled) this.mlsEnabled = options.mlsEnabled; 2512 if (options.hstValuationRedirectEnabled) this.hstValuationRedirectEnabled = options.hstValuationRedirectEnabled; 2513 } 2514 /** 2515 * Set websiteApiGatewayUrl 2516 * @param {string} websiteApiGatewayUrl 2517 * @param {Object} options 2518 */ setWebsiteApiGatewayUrl(websiteApiGatewayUrl, options = {}) { 2519 this.websiteApiGatewayUrl = websiteApiGatewayUrl; 2520 } 2521 /** 2522 * Set buyerSellerServiceUrl 2523 * @param {string} buyerSellerServiceUrl 2524 * @param {Object} options 2525 */ setBuyerSellerServiceUrl(buyerSellerServiceUrl, options = {}) { 2526 this.buyerSellerServiceUrl = buyerSellerServiceUrl; 2527 } 2528 /** 2529 * Set divolteServiceUrl and supporting data 2530 * @param {string} divolteServiceUrl 2531 * @param {Object} options 2532 */ setDivolteServiceUrl(divolteServiceUrl, options = {}) { 2533 this.divolteServiceUrl = divolteServiceUrl; 2534 } 2535 /** 2536 * Set eventBusUrl 2537 * @param {string} eventBusUrl 2538 * @param {Object} options 2539 */ setEventBusUrl(eventBusUrl, options = {}) { 2540 this.eventBusUrl = eventBusUrl; 2541 } 2542 /** 2543 * Set templateId and supporting data 2544 * @param {string} templateId 2545 * @param {Object} options 2546 */ setTemplateId(templateId, options = {}) { 2547 this.templateId = templateId; 2548 } 2549 /** 2550 * Set leadIngesterUrl 2551 * @param {string} leadIngesterUrl 2552 * @param {Object} options 2553 */ setLeadIngesterUrl(leadIngesterUrl, options = {}) { 2554 this.leadIngesterUrl = leadIngesterUrl; 2555 } 2556 /** 2557 * Set googleMapsAPIKey 2558 * @param {string} googleMapsAPIKey 2559 * @param {Object} options 2560 */ setGoogleMapsAPIKey(googleMapsAPIKey, options = {}) { 2561 this.googleMapsAPIKey = googleMapsAPIKey; 2562 } 2563 /** 2564 * Set eventBus 2565 * @param {Object} options 2566 */ setEventBus({ enabled: enabled, apiKey: apiKey, url: url }) { 2567 this.eventBus = { 2568 enabled: enabled, 2569 apiKey: apiKey, 2570 url: url 2571 }; 2572 } 2573} 2574 2575 2576/* eslint-disable import/prefer-default-export */ // eslint-disable-next-line import/prefer-default-export 2577class $7c391e2f7931d60d$export$3b5bd9381a52554c { 2578 /** 2579 * Initialize Base Controller at build time 2580 * @param { ConfigController } config 2581 * @param {Window} window 2582 * @param {Document} document 2583 */ constructor(config, window, document){ 2584 // TODO: Listen for events from configController to update configuration 2585 this.config = config; 2586 this.window = window; 2587 this.document = document; 2588 if (!this.destroy) // With turbolinks, we want to ensure that on each page load we have the latest version of the sdk 2589 // Controllers are deconstructed and replaced (on the window object) per request 2590 // Controllers must use the destory method to remove event handlers to prevent undefined behavior 2591 throw new Error('Controllers must implement a deconstructor'); 2592 } 2593} 2594 2595 2596// eslint-disable-next-line import/prefer-default-export 2597const $a3e97e89bbc2a366$export$f037eeffb5561205 = 2000; 2598 2599 2600class $0ead03fdb95fa9ce$export$d507f25e63650365 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 2601 /** 2602 * Initialize Modal Controller at build time 2603 * @param { ConfigController } config 2604 * @param {Window} window 2605 * @param {Document} document 2606 */ constructor(config, window, document){ 2607 super(config, window, document); 2608 this.setLoginState = this.setLoginState.bind(this); 2609 this.pageId = null; 2610 this.modals = []; 2611 this.exitModalTriggered = {}; 2612 this.exitModalEventListeners = []; 2613 } 2614 /** 2615 * Initialize Modal Controller at run time 2616 * Attach handlers and events to DOM 2617 * @param {Object} options 2618 */ initialize(options) { 2619 try { 2620 const { config: config, document: document } = this; 2621 this.pageId = config.pageId; 2622 this.modals = document.querySelectorAll('body > #modals > .modal');
2623 options.modals.forEach((m)=>{ 2624 this._attachModalHandler(m); 2625 }); 2626 } catch (e) { 2627 // eslint-disable-next-line no-console 2628 console.log(e); 2629 } 2630 } 2631 /** 2632 * Controllers must implement a "destroy" method to be called 2633 * after page navigation to remove event handlers / cleanup 2634 */ destroy() { 2635 const { document: document } = this; 2636 this.exitModalEventListeners.forEach((listener)=>{ 2637 document.removeEventListener('mouseout', listener); 2638 }); 2639 this.modals = []; 2640 this.exitModalTriggered = {}; 2641 this.exitModalEventListeners = []; 2642 } 2643 /** 2644 * Set modal to local storage with expiration 2645 * @param {string} modalId 2646 */ setModalWithExpiry(modalId) { 2647 const { window: window } = this; 2648 const TTL = 86400000; // 24 hours 2649 const now = new Date(); 2650 const expiry = now.getTime() + TTL; 2651 const key = `modal-controller-${modalId}`; 2652 try { 2653 window.localStorage.setItem(key, JSON.stringify(expiry)); 2654 } catch (err) { 2655 // catch error when setting item to storage on iOS private browser 2656 } 2657 } 2658 /** 2659 * Save success login modal to local storage 2660 */ setLoginState() { 2661 const { window: window } = this; 2662 try { 2663 window.localStorage.setItem('isSignedUp', 'true'); 2664 } catch (err) { 2665 // catch error when setting item to storage on iOS private browser 2666 } 2667 } 2668 /** 2669 * Get modal and check expiration 2670 * @param {string} modalId 2671 * @return {boolean} 2672 */ getModalWithExpiry(modalId) { 2673 const { window: window } = this; 2674 const storage = window.localStorage; 2675 const expirationTime = storage.getItem(`modal-controller-${modalId}`); 2676 if (!expirationTime) return false; 2677 const expiry = JSON.parse(expirationTime); 2678 const now = new Date(); 2679 if (now.getTime() > expiry) { 2680 storage.removeItem(`modal-controller-${modalId}`); 2681 return false; 2682 } 2683 return true; 2684 } 2685 /** 2686 * Make a modal visible 2687 * @param {string} modalId 2688 * @param {Object} options 2689 */ show(modalId, options = {}) { 2690 // TODO: migrate logic from page.handlebars 2691 const { window: window } = this; 2692 const modalViewed = this.getModalWithExpiry(modalId); 2693 // Only show a modal if it has not been seen 2694 // Does not affect "show modal on button click" 2695 if (modalId && !modalViewed || options.forced === true) { 2696 window.lpUI.showModal(`modal-${modalId}`, options); 2697 this.setModalWithExpiry(modalId); 2698 } 2699 } 2700 /** 2701 * Attach events based on Modal type and page settings 2702 * @param {Modal} modal 2703 */ _attachModalHandler(modal) { 2704 const { pageId: pageId } = this; 2705 const { trigger: trigger, pageSpecific: pageSpecific } = modal; 2706 if (pageSpecific) { 2707 const pageIds = modal.pages.map((p)=>p.pageId); 2708 if (!pageIds.includes(pageId)) // Modal does not belong to this page 2709 return; 2710 } 2711 if (trigger === 'EXIT') this._exitModalHandler(modal); 2712 else if (trigger === 'TIMER') this._timerModalHandler(modal); 2713 else if (trigger === 'FORCED') this._forcedModalHandler(modal); 2714 } 2715 /** 2716 * Attach events for exit Modals 2717 * @param {Modal} modal 2718 */ _exitModalHandler(modal) { 2719 // eslint-disable-next-line 2720 const that = this; 2721 const { document: document, exitModalTriggered: exitModalTriggered } = this; 2722 const { modalId: modalId } = modal; 2723 /** 2724 * Handle the mouseout dom event 2725 * @param {Event} event 2726 */ function triggerExitModal(event) { 2727 if (exitModalTriggered[modalId]) return; 2728 if (!event.toElement && !event.relatedTarget) { 2729 that.show(modalId); 2730 exitModalTriggered[modalId] = true; 2731 } 2732 } 2733 document.addEventListener('mouseout', triggerExitModal); 2734 this.exitModalEventListeners.push(triggerExitModal); 2735 } 2736 /** 2737 * Attach events for timer Modals
2738 * @param {Modal} modal 2739 */ _timerModalHandler(modal) { 2740 const { modalId: modalId, timerDelay: timerDelay } = modal; 2741 setTimeout(()=>{ 2742 this.show(modalId); 2743 }, timerDelay); 2744 } 2745 /** 2746 * Attach events for Forced Modals 2747 * @param {Modal} modal 2748 */ _forcedModalHandler(modal) { 2749 const { modalId: modalId } = modal; 2750 const { window: window, document: document } = this; 2751 const isLoggedIn = window.localStorage.getItem('isSignedUp') === 'true'; 2752 if (!isLoggedIn) { 2753 // watch for event triggering success login 2754 const forcedModal = document.querySelector(`#modal-${modalId}`); 2755 const contactForm = forcedModal?.querySelector('form'); 2756 const MODAL_CLOSE_DELAY = contactForm?.dataset.delay || (0, $a3e97e89bbc2a366$export$f037eeffb5561205); 2757 // form submit autoclose is handled by contactForms controller 2758 contactForm.addEventListener('formsubmitted', ()=>{ 2759 this.setLoginState(); 2760 }); 2761 window.addEventListener('oauthSuccess', ()=>{ 2762 this.setLoginState(); 2763 contactForm.classList.add('success'); 2764 setTimeout(()=>{ 2765 window.lpUI.hideModal(`modal-${modalId}`); 2766 contactForm.reset(); 2767 contactForm.classList.remove('success'); 2768 }, MODAL_CLOSE_DELAY); 2769 }); 2770 this.show(modalId, { 2771 forced: true 2772 }); 2773 } 2774 } 2775} 2776 2777 2778/* eslint-disable import/prefer-default-export */ /* global window */ /* eslint-disable import/prefer-default-export */ /** 2779 * Handle navigation push 2780 * @param {string} href 2781 * @param {boolean} _blank 2782 */ const $fc0a058ae42af628$export$95e8d77caafa3e06 = (href, _blank = false)=>{ 2783 if (!href) return; 2784 if (_blank) { 2785 window.open(href); 2786 return; 2787 } 2788 window.location.assign(href); 2789}; 2790const $fc0a058ae42af628$export$267a1435fe18b8d4 = (obj, path, defValue)=>{ 2791 if (!path) return undefined; 2792 const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); 2793 const result = pathArray.reduce((prevObj, key)=>prevObj && prevObj[key], obj); 2794 return result === undefined ? defValue : result; 2795}; 2796 2797 2798 2799class $d48f9f944810f613$export$ca3218b7861b9cb0 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 2800 /** 2801 * Initialize Button Controller at build time 2802 * @param { ConfigController } config 2803 * @param {Window} window 2804 * @param {Document} document 2805 */ constructor(config, window, document){ 2806 super(config, window, document); 2807 this._boundClickHandler = this._buttonClickHandler.bind(this); 2808 } 2809 /** 2810 * Initialize Button Controller at run time 2811 * Attach handlers and events to DOM 2812 * @param {Object} options 2813 */ initialize(options = {}) { 2814 try { 2815 const { document: document } = this; 2816 document.body.removeEventListener('click', this._boundClickHandler); 2817 document.body.addEventListener('click', this._boundClickHandler); 2818 } catch (e) { 2819 // eslint-disable-next-line no-console 2820 console.error(e); 2821 } 2822 } 2823 /** 2824 * Controllers must implement a "destroy" method to be called 2825 * after page navigation to remove event handlers / cleanup 2826 */ destroy() { 2827 const { document: document } = this; 2828 document.body.removeEventListener('click', this._boundClickHandler); 2829 } 2830 /** 2831 * Buttons click handler 2832 * @param {event} e 2833 */ _buttonClickHandler(e) { 2834 this._handleButtonClick(e).catch((e)=>console.error(e)); 2835 } 2836 /** 2837 * Handle button click 2838 * We need special function to have easy way to detach listener 2839 * @param {Event} e 2840 */ async _handleButtonClick(e) { 2841 const { window: window, document: document } = this; 2842 const button = e.target.closest('button, a'); 2843 if (!button) return; 2844 const type = button.dataset.type || ''; 2845 const href = button.href || button.dataset.href || ''; 2846 const target = button.target || ''; 2847 const { submit: submit } = button.dataset; 2848 if (submit) { 2849 try { 2850 await this._handleSubmit(button); 2851 } catch (err) { 2852 // eslint-disable-next-line no-console 2853 console.error(err); 2854 } 2855 (0, $fc0a058ae42af628$export$95e8d77caafa3e06)(href, target === '_blank'); 2856 } 2857 this._trackConversion(button); 2858 if (type === 'VIDEO') { 2859 this._handleVideoButton(button); 2860 return; 2861 } 2862 if (type === 'CONTACT_US') { 2863 this._handleContactUsButton(button); 2864 return; 2865 } 2866 if (type === 'SUBSCRIBE') { 2867 // TODO: Extend for an overwritten subscribe 2868 window.lpUI.showModal('modal-global-subscribe'); 2869 return; 2870 } 2871 if (type === 'MY_ACCOUNT') { 2872 // TODO: Extend for an overwritten subscribe 2873 window.lpUI.showModal('modal-global-my-account'); 2874 return; 2875 } 2876 if (type === 'SHARE_POPUP') { 2877 // TODO: Extend for an overwritten subscribe 2878 const sharePopup = button.querySelector('.share-popup'); 2879 if (sharePopup) sharePopup.style.display = 'flex'; 2880 e.stopPropagation(); // this may not work in current 'delegate' mode 2881 return; 2882 } 2883 // Custom modals 2884 if (type.substring(0, 6) === 'MODAL_') { 2885 const modalId = type.substring(6); 2886 window.lpUI.showModal(`modal-${modalId}`); 2887 return; 2888 } 2889 // MAILTO / TEL Event 2890 if (href?.startsWith('mailto:')) { 2891 const email = href.substring(7); 2892 // TODO: Replace with SDK method 2893 document.sendGoogleAnalyticsEvent({ 2894 hitType: 'event', 2895 eventCategory: 'button', 2896 eventAction: 'click', 2897 eventLabel: 'Email', 2898 eventValue: email 2899 }); 2900 } else if (href?.startsWith('tel:')) { 2901 const phoneNumber = href.substring(4); 2902 // TODO: Replace with SDK method 2903 document.sendGoogleAnalyticsEvent({ 2904 hitType: 'event', 2905 eventCategory: 'button', 2906 eventAction: 'click', 2907 eventLabel: 'Phone', 2908 eventValue: phoneNumber 2909 }); 2910 } 2911 } 2912 /** 2913 * Handle submit 2914 * @param {HTMLButtonElement|HTMLAnchorElement} button 2915 */ async _handleSubmit(button) { 2916 const form = button.closest('form'); 2917 const { window: window } = this; 2918 if (form) return await window.luxuryPresence.contactForms.submitForm(form); 2919 return null; 2920 } 2921 _getVimeoSrc(link) { 2922 if (!link || !link.includes('vimeo.com') || link.length > 2000) return null; 2923 const matchResult = link.match(/(?:(?:www\.)?vimeo\.com\/(?:showcase\/\d+(?:\/video\/|\?video[=/])|channels\/(?:\w+\/)?|groups\/(?:[^/]*)\/videos\/|)|player\.vimeo\.com\/video\/)(\d+)(?:(?:\?.*&*h=|\/)+([\d,a-f]+))?/); 2924 if (!matchResult) return null; 2925 const [, vimeoId, vimeoHashParameter] = matchResult; 2926 let src = `https://player.vimeo.com/video/${vimeoId}?`; 2927 if (vimeoHashParameter) src += `h=${vimeoHashParameter}&`; 2928 src += 'autoplay=1'; 2929 return src; 2930 } 2931 /** 2932 * Handle type video click 2933 * @param {HTMLElement} button 2934 */ // eslint-disable-next-line sonarjs/cognitive-complexity 2935 _handleVideoButton(button) { 2936 const { document: document } = this; 2937 const section = button.closest('section'); 2938 // Embed youtube/vimeo video url 2939 let { src: src } = button.dataset; 2940 let youtubeId = ''; 2941 if (src && (src.includes('youtube.com') || src.includes('youtu.be')) && !src.includes('youtube.com/embed')) { 2942 // test link: https://regexr.com/3anm9 2943 const youtubeRegex = /(?:youtube\.com\/\S*(?:(?:\/e(?:mbed))?\/|watch\?(?:\S*?&?v=)
2943)|youtu.be\/)([a-zA-Z0-9_-]{6,11})/; 2944 const youtubeResult = src.match(youtubeRegex); 2945 // eslint-disable-next-line 2946 if (youtubeResult) youtubeId = youtubeResult[1]; 2947 } 2948 const vimeoSrc = this._getVimeoSrc(src); 2949 if (youtubeId) src = `https://youtube.com/embed/${youtubeId}?autoplay=1&controls=0`; 2950 else if (vimeoSrc) src = vimeoSrc; 2951 if (!src || !section) return; 2952 const isVideoFile = src.endsWith('.mp4') || src.endsWith('.webm') || src.includes('cloudinary.com'); 2953 const modalHtml = ` 2954 <div id="modal-${section.id}" class="modal visible"> 2955 <div class="modal-content"> 2956 <div class="lightbox-video"> 2957 <div class="video-player"> 2958 <div class="embed-container"> 2959 ${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>`} 2960 </div> 2961 </div> 2962 </div> 2963 </div> 2964 <button class="close lp-close" type="button" aria-label="close modal"><span class="lp-close-icon" aria-hidden="true"></span></button> 2965 </div> 2966 `; 2967 document.body.insertAdjacentHTML('beforeend', modalHtml); 2968 const modal = document.getElementById(`modal-${section.id}`); 2969 const closeModal = modal.querySelector('.close'); 2970 closeModal.addEventListener('click', ()=>{ 2971 modal.remove(); 2972 }); 2973 } 2974 /** 2975 * Handle type contact us click 2976 * @param {HTMLElement} button 2977 */ _handleContactUsButton(button) { 2978 const { window: window, document: document } = this; 2979 // TODO: Extend for an overwritten contact us 2980 document.sendGoogleAnalyticsEvent({ 2981 hitType: 'event', 2982 eventCategory: 'button', 2983 eventAction: 'click', 2984 eventLabel: 'contact' 2985 }); 2986 window.lpUI.showModal('modal-global-contact-us', { 2987 scrollLock: true 2988 }); 2989 } 2990 /** 2991 * Track conversion 2992 * @param {HTMLElement} button 2993 */ _trackConversion(button) { 2994 const { window: window, document: document } = this; 2995 // Conversion Tracking Options 2996 const { gtagConversionId: gtagConversionId, gtagConversionLabel: gtagConversionLabel, fbEventName: fbEventName } = button.dataset; 2997 if (gtagConversionId && gtagConversionLabel) { 2998 if (window.dataLayer && window.dataLayer.length) { 2999 const configured = window.dataLayer.some((layer)=>layer.length && layer[0] === 'config' && layer[1] === gtagConversionId); 3000 if (!configured) document.sendGoogleTagManagerEvent('config', gtagConversionId); 3001 } 3002 const sentTo = `${gtagConversionId}/${gtagConversionLabel}`; 3003 document.sendGoogleTagManagerEvent('event', 'conversion', { 3004 send_to: sentTo 3005 }); 3006 } 3007 if (window.fbq && fbEventName) window.fbq('track', fbEventName, {}); 3008 } 3009} 3010 3011 3012/* eslint-disable import/prefer-default-export */ 3013class $68a2d4aa500c7a05$export$61ec8f2b6291ec31 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 3014 /** 3015 * Initialize Validation Controller at build time 3016 * @param { ConfigController } config 3017 * @param {Window} window 3018 * @param {Document} document 3019 */ constructor(config, window, document){ 3020 super(config, window, document); 3021 this.validatedForms = []; 3022 } 3023 /** 3024 * Initialize Contact Form Controller at run time 3025 * Attach handlers and events to DOM 3026 * @param {Object} options 3027 */ initialize(options = {}) { 3028 try { 3029 const { window: window, document: document } = this; 3030 const { selector: selector } = options; 3031 let validatedForms = []; 3032 if (selector) validatedForms = document.querySelectorAll(`${selector} form[data-type="contact-form"]`); 3033 else validatedForms = document.querySelectorAll('form[data-type="contact-form"]'); 3034 const isPhoneValidationLibRequired = !!document.querySelectorAll('input[type="tel"]').length; 3035 // we are loading the phone validation lib from the globally defined list of libs 3036 if (isPhoneValidationLibRequired && !window.libphonenumber && window?.luxuryPresence?.libs) { 3037 const { loadJs: loadJs, jsLibsMap: jsLibsMap } = window.luxuryPresence.libs; 3038 const { libphonenumber: libphonenumber } = jsLibsMap; 3039 loadJs(libphonenumber.jsSrc, this.attachValidation.bind(this, validatedForms)); 3040 }
3040 else this.attachValidation(validatedForms); 3041 this.validatedForms = this.validatedForms.concat(validatedForms); 3042 } catch (e) { 3043 // eslint-disable-next-line no-console 3044 console.log(e); 3045 } 3046 } 3047 /** 3048 * Controllers must implement a "destroy" method to be called 3049 * after page navigation to remove event handlers / cleanup 3050 */ destroy() { 3051 this.validatedForms = []; 3052 } 3053 /** 3054 * Validate all forms in the document 3055 * @param {NodeListOf<HTMLFormElement>} forms 3056 */ attachValidation(forms) { 3057 forms.forEach((form)=>{ 3058 const inputs = form.querySelectorAll('input, select, textarea'); 3059 inputs.forEach((input)=>{ 3060 this.applyFieldValidation(input); 3061 }); 3062 }); 3063 } 3064 /** 3065 * Field Validation Handler from global context 3066 * @param {HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement} inputField 3067 */ applyFieldValidation(inputField) { 3068 const fieldType = inputField.getAttribute('type'); 3069 if (fieldType === 'tel') this._applyPhoneValidation(inputField); 3070 } 3071 /** 3072 * Phone Field Validation Handler 3073 * @param {HTMLInputElement} inputField 3074 */ _applyPhoneValidation(inputField) { 3075 const { window: window } = this; 3076 if (window.libphonenumber) { 3077 // https://codepen.io/coinscope/pen/BaXBvBp?editors=1010 3078 const phoneUtil = window.libphonenumber.isValidPhoneNumber; 3079 inputField.addEventListener('keyup', function() { 3080 if (this.hasAttribute('required') || this.value.replace(/\s/g, '').length) try { 3081 const phoneNumber = this.value; 3082 const region = 'US'; 3083 const isValidNumber = phoneUtil(phoneNumber, region); 3084 if (!isValidNumber) this.setCustomValidity('Please enter a valid phone number.'); 3085 else this.setCustomValidity(''); 3086 } catch (e) { 3087 this.setCustomValidity('Please enter a valid phone number.'); 3088 } 3089 else this.setCustomValidity(''); 3090 }); 3091 } 3092 } 3093} 3094 3095 3096 3097 3098/** 3099 * Fires the `generate_lead` Google Ads conversion on any marketing-site lead 3100 * event â lead-form submits (all routed through ContactFormController) and 3101 * social sign-in (oauth.js) â at most once per session. 3102 * 3103 * Reuses the same `generate_lead` event the Home Search Tool fires; the page 3104 * template already configures the LP Google tags, so no new Google Ads setup is 3105 * needed. Cross-tab/visitor dedupe is left to the conversion action's 3106 * Count="One" â localStorage is intentionally avoided as it would suppress a 3107 * returning visitor's later lead. 3108 */ const $3ec2bf3965f87ecc$export$78b64890ea40f4e4 = 'generate_lead'; 3109const $3ec2bf3965f87ecc$export$568da12be2433b06 = 'lp_lead_form_conversion_fired'; 3110function $3ec2bf3965f87ecc$var$alreadyFired(window) { 3111 try { 3112 return window.sessionStorage.getItem($3ec2bf3965f87ecc$export$568da12be2433b06) === 'true'; 3113 } catch { 3114 // sessionStorage can throw in private browsing; fail open so we still fire. 3115 return false; 3116 } 3117} 3118function $3ec2bf3965f87ecc$var$markFired(window) { 3119 try { 3120 window.sessionStorage.setItem($3ec2bf3965f87ecc$export$568da12be2433b06, 'true'); 3121 } catch { 3122 // Ignore â see alreadyFired(). 3123 } 3124} 3125function $3ec2bf3965f87ecc$export$e70565180c0e6950(window, document) { 3126 if (!window || !document) return; 3127 // GA bootstrap may not have run yet (or no pixel on this page); don't throw, 3128 // and don't burn the once-per-session flag if we can't actually fire. 3129 if (typeof document.sendGoogleTagManagerEvent !== 'function') return; 3130 if ($3ec2bf3965f87ecc$var$alreadyFired(window)) return; 3131 try { 3132 // Mark fired only AFTER a successful send, so a thrown GTM call doesn't 3133 // permanently suppress the conversion for the rest of the session â and 3134 // never let an analytics failure break the caller's success flow. 3135 document.sendGoogleTagManagerEvent('event', $3ec2bf3965f87ecc$export$78b64890ea40f4e4); 3136 $3ec2bf3965f87ecc$var$markFired(window); 3137 } catch { 3138 // Swallow â analytics must not break form submit / sign-in. 3139 } 3140} 3141 3142 3143 3144const $2aefad83322125c9$var$homeValuationRoute = '/api/v1/home-valuation'; 3145class $2aefad83322125c9$export$2e2bcd8739ae039 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 3146 destroy() { 3147 // 3148 } 3149 /** 3150 * Clean address string by removing call time frame suffix and extra whitespace 3151 * 3152 * Handles 17 templates that append ", call time frame: [value]" to the address: 3153 * - TheTrailblazerSir_2, TheMasterpiece (2x), TheInspirationSir_1, TheIconSir_3
3154 * - Influencer, GlobalCollective, Evolution, CorcoranAgent_1 3155 * - CompassSE (2x), CompassPrestige (2x), CompassNavigator 3156 * - ColdwellBanker (2x), Classic 3157 * 3158 * @param {string} address - Raw address string that may contain time frame info 3159 * @return {string} - Cleaned address string 3160 */ _cleanAddressString(address) { 3161 if (!address || typeof address !== 'string') return ''; 3162 let cleaned = address.trim(); 3163 // Fix 1: Handle "User entered address:\n..." pattern from TheProducer templates 3164 // Extract only the user entered address, ignore geocoded part 3165 if (cleaned.includes('User entered address:')) { 3166 const match = cleaned.match(/User entered address:\s*\n?\s*([^\n]+)/); 3167 if (match && match[1]) cleaned = match[1].trim(); 3168 } 3169 // Fix 2: Remove ", call time frame: ..." suffix if present (affects 17 templates) 3170 const timeFrameIndex = cleaned.indexOf(', call time frame:'); 3171 if (timeFrameIndex !== -1) cleaned = cleaned.substring(0, timeFrameIndex).trim(); 3172 return cleaned; 3173 } 3174 /** 3175 * Transform Google Maps address components to structured format 3176 * @param {Array} addressComponents - Google Maps address components 3177 * @return {Object} - Structured address object 3178 */ _transformAddressComponents(addressComponents) { 3179 const get = (t)=>(addressComponents.find((c)=>c.componentType === t) || {}).componentName?.text; 3180 const structuredAddress = {}; 3181 const streetNumber = get('street_number'); 3182 const streetName = get('route'); 3183 const state = get('administrative_area_level_1'); 3184 const zipcode = get('postal_code'); 3185 const addressLine2 = get('subpremise'); 3186 // explicit precedence for city 3187 const city = get('locality') || get('sublocality_level_1') || get('neighborhood'); 3188 if (streetNumber) structuredAddress.streetNumber = streetNumber; 3189 if (streetName) structuredAddress.streetName = streetName; 3190 if (city) structuredAddress.city = city; 3191 if (state) structuredAddress.state = state; 3192 if (zipcode) structuredAddress.zipcode = zipcode; 3193 if (addressLine2) structuredAddress.addressLine2 = addressLine2; 3194 return structuredAddress; 3195 } 3196 /** 3197 * Extract address and coordinates from Google Maps validation data 3198 * @param {Object} validatedAddressData - Google Maps validation result 3199 * @return {Object} - Object containing structuredAddress and coordinates 3200 */ _extractAddressAndCoordinates(validatedAddressData) { 3201 let structuredAddress = null; 3202 let coordinates = null; 3203 if (validatedAddressData?.result) { 3204 const { result: result } = validatedAddressData; 3205 // Extract structured address components 3206 if (result.address?.addressComponents) structuredAddress = this._transformAddressComponents(result.address.addressComponents); 3207 // Extract coordinates 3208 if (result.geocode?.location) coordinates = { 3209 latitude: result.geocode.location.latitude, 3210 longitude: result.geocode.location.longitude 3211 }; 3212 } 3213 return { 3214 structuredAddress: structuredAddress, 3215 coordinates: coordinates 3216 }; 3217 } 3218 /** 3219 * Filter out empty string values from an object, keeping only non-empty values 3220 * @param {Object} obj - Object to filter 3221 * @return {Object} - Filtered object without empty strings 3222 */ _filterEmptyStrings(obj) { 3223 const filtered = {}; 3224 for (const [key, value] of Object.entries(obj))if (value !== '' && value !== null && value !== undefined) filtered[key] = value; 3225 return filtered; 3226 } 3227 /** 3228 * Extract raw address from form data with priority fallback chain. 3229 * Order is critical to handle 38 home valuation templates with different field naming: 3230 * 3231 * IMPORTANT: contactForms.js (line 189) stores name="message" in comments_attributes[0].text 3232 * because 'message' is in DEFAULT_LEAD_FIELDS. Fields NOT in DEFAULT_LEAD_FIELDS go to content. 3233 * 3234 * Priority (most specific â most generic): 3235 * 1. address - Pattern 1: 9 templates (explicit address, stored in content) 3236 * 2. property-address - Pattern 3: 4 templates (dash variant with JS, stored in content) 3237 * 3. property_address - Pattern 4: 3 templates (underscore, stored in content - handles VanguardProperties!) 3238 * 4. Property_Address - Pattern 5: 1 template (capital P - TheProducer/HomeValuationRansomHouse, stored in content)
3239 * 5. comments_attributes[0].text - Pattern 2: ALL 17 templates with name="message" store HERE! 3240 * (e.g., Evolution/HomeValuation, TheProducer/HomeValuation, etc.) 3241 * 6. message - Pattern 6: 4 templates with JS-populated message (rare, checked last) 3242 * 7-8. homeValuationAddress, propertyAddress - Legacy/unknown formats 3243 * 3244 * Critical: Each field is cleaned BEFORE checking, so dropdown values are skipped and we try next field 3245 * 3246 * @param {Object} data - Form data 3247 * @return {string} - Raw address string (may contain trailing text like ", call time frame: ...") 3248 */ _getRawAddress(data) { 3249 const content = data.activity.content || {}; 3250 const comments = data.activity.comments_attributes?.[0]?.text; 3251 const possibleFields = [ 3252 content.address, 3253 content['property-address'], 3254 content.property_address, 3255 content.Property_Address, 3256 comments, 3257 content.message, 3258 content.homeValuationAddress, 3259 content.propertyAddress 3260 ]; 3261 return possibleFields.map((field)=>field && this._cleanAddressString(field)).find(Boolean) || ''; 3262 } 3263 /** 3264 * Logs lead-related events for monitoring 3265 * @param {string} eventType - Type of event 3266 * @param {Object} data - Form data 3267 * @param {Error} error - Optional error object 3268 */ _logLeadEvent(eventType, data, error = null) { 3269 const eventPayload = { 3270 event: eventType, 3271 companyId: this.config.companyId, 3272 websiteId: this.config.websiteId, 3273 timestamp: new Date().toISOString(), 3274 hasEmail: !!data.activity?.author_email, 3275 hasAddress: !!this._getRawAddress(data) 3276 }; 3277 if (error) { 3278 // Use userMessage (sanitized) instead of raw message to prevent PII exposure 3279 // userMessage is set by _getUserFriendlyErrorMessage() which provides safe messages 3280 eventPayload.errorMessage = error.userMessage || (error.status ? `Request failed with status ${error.status}` : 'An error occurred'); 3281 eventPayload.errorName = error.name; 3282 eventPayload.errorStatus = error.status; 3283 } 3284 // Log to console with structured format for log aggregation 3285 // eslint-disable-next-line no-console 3286 console.error(`[HomeValuation] ${eventType}`, JSON.stringify(eventPayload)); 3287 // If PostHog is available, send event for analytics 3288 // Wrap in try/catch to prevent SDK failures from blocking submissions 3289 if (this.window.posthog) try { 3290 this.window.posthog.capture(eventType, eventPayload); 3291 } catch (posthogError) { 3292 // eslint-disable-next-line no-console 3293 console.error('[HomeValuation] PostHog capture failed:', posthogError); 3294 } 3295 } 3296 /** 3297 * Maps error types to user-friendly messages 3298 * @param {Error} error - The error object 3299 * @returns {string} - User-friendly message 3300 */ _getUserFriendlyErrorMessage(error) { 3301 if (error.name === 'AbortError' || error.message?.includes('timeout')) return 'Connection timed out. Please try again.'; 3302 if (error.name === 'TypeError' && error.message?.includes('fetch')) return 'Unable to connect. Please check your internet connection and try again.'; 3303 if (error.status === 503 || error.status === 502 || error.status === 504) return 'Service temporarily unavailable. Please try again in a moment.'; 3304 if (error.status === 400) return 'Please check your information and try again.'; 3305 return 'Something went wrong. Please try again or contact us directly.'; 3306 } 3307 /** 3308 * Build formatted address string from structured address components 3309 * @param {Object} structuredAddress - Structured address object 3310 * @return {string} - Formatted address string 3311 */ _formatStructuredAddress(structuredAddress) { 3312 if (!structuredAddress || Object.keys(structuredAddress).length === 0) return ''; 3313 const parts = []; 3314 // Line 1: streetNumber streetName addressLine2 3315 const line1 = [ 3316 structuredAddress.streetNumber, 3317 structuredAddress.streetName, 3318 structuredAddress.addressLine2
3319 ].filter(Boolean).join(' '); 3320 parts.push(line1); 3321 // Line 2: city, state zipcode 3322 const cityState = [ 3323 structuredAddress.city, 3324 structuredAddress.state 3325 ].filter(Boolean).join(', '); 3326 const line2 = [ 3327 cityState, 3328 structuredAddress.zipcode 3329 ].filter(Boolean).join(' '); 3330 parts.push(line2); 3331 return parts.filter(Boolean).join(', '); 3332 } 3333 /** 3334 * Build home valuation data object 3335 * @param {Object} data - Form data 3336 * @param {string} homeValuationEstimate - Home valuation estimate 3337 * @param {Object} structuredAddress - Structured address object 3338 * @param {Object} coordinates - Coordinates object 3339 * @return {Object} - Home valuation data object 3340 */ _buildHomeValuationData(data, homeValuationEstimate, structuredAddress, coordinates) { 3341 // Build homeValuationAddress from structured address (validated by Google) if available 3342 // Format: streetNumber streetName, city, state zipcode, addressLine2 3343 const homeValuationAddress = this._formatStructuredAddress(structuredAddress) || this._getRawAddress(data); 3344 // Validate that we have an address before proceeding 3345 if (!homeValuationAddress) throw new Error('Unable to extract home valuation address from form data'); 3346 // Required fields that must always be included 3347 const requiredFields = { 3348 companyId: data.companyId, 3349 homeValuationAddress: homeValuationAddress, 3350 homeValuationEstimate: homeValuationEstimate ? new Intl.NumberFormat('en-US', { 3351 style: 'currency', 3352 currency: 'USD', 3353 maximumFractionDigits: 0, 3354 minimumFractionDigits: 0 3355 }).format(homeValuationEstimate) : null, 3356 websiteId: data.page.websiteId, 3357 sourceUrl: data.activity.source_url 3358 }; 3359 // Optional fields that will be filtered if empty 3360 const optionalFields = { 3361 leadEmailAddress: data.activity.author_email, 3362 leadFullName: data.activity.author_name, 3363 leadPhoneNumber: data.activity.author_phone, 3364 parcelId: data.activity.content?.parcelId, 3365 resourceId: data.page.pageMeta?.resourceId, 3366 pageId: data.page.pageId,
3367 sourceResource: data.page.pageMeta?.sourceResource, 3368 pageElementId: data.page.pageMeta?.pageElementId, 3369 formId: data.page.pageMeta?.formId, 3370 utmSource: data.activity.content?.utm_source, 3371 utmMedium: data.activity.content?.utm_medium, 3372 utmCampaign: data.activity.content?.utm_campaign, 3373 utmTerm: data.activity.content?.utm_term, 3374 utmContent: data.activity.content?.utm_content, 3375 utmId: data.activity.content?.utm_id, 3376 timeToSell: data.activity.content?.timeToSell, 3377 lookingToBuy: data.activity.content?.lookingToBuy, 3378 referrer: data.referrer, 3379 content: data.activity.content || {}, 3380 fields: data.fields || {} 3381 }; 3382 // Filter out empty strings from optional fields 3383 const filteredOptionalFields = this._filterEmptyStrings(optionalFields); 3384 return { 3385 ...requiredFields, 3386 ...filteredOptionalFields, 3387 // Add structured address and coordinates if they exist 3388 ...structuredAddress && { 3389 address: structuredAddress 3390 }, 3391 ...coordinates && { 3392 coordinates: coordinates 3393 } 3394 }; 3395 } 3396 /** 3397 * Validate address with Google Maps 3398 * @param {string} rawAddress - Raw address string 3399 * @returns {Promise<Object|null>} - Validated address data or null 3400 * @throws {Error} - If validation fails, error is thrown with userMessage 3401 */ async _validateAddressSafely(rawAddress) { 3402 if (!rawAddress) { 3403 // eslint-disable-next-line no-console 3404 console.error('Home valuation: No address found in form data'); 3405 return null; 3406 } 3407 try { 3408 return await this._validateAddressWithGoogleMaps(rawAddress); 3409 } catch (error) { 3410 // eslint-disable-next-line no-console 3411 console.error('Error validating address with Google Maps:', error); 3412 // Attach user-friendly message and throw so user sees the error 3413 error.userMessage = this._getUserFriendlyErrorMessage(error); 3414 throw error; 3415 } 3416 } 3417 /** 3418 * Get home valuation estimate 3419 * @param {Object} validatedAddressData - Validated address data 3420 * @returns {Promise<number|null>} - Home valuation estimate or null 3421 * @throws {Error} - If estimate fetch fails, error is thrown with userMessage 3422 */ async _getHomeValuationEstimateSafely(validatedAddressData) { 3423 try { 3424 return await this._getHomeValuationEstimate(validatedAddressData); 3425 } catch (error) { 3426 // eslint-disable-next-line no-console 3427 console.error('Error getting home valuation estimate:', error); 3428 // Attach user-friendly message and throw so user sees the error 3429 error.userMessage = this._getUserFriendlyErrorMessage(error); 3430 throw error; 3431 } 3432 } 3433 /** 3434 * Parse response text into payload object 3435 * @param {string} text - Response text 3436 * @returns {Object} - Parsed payload 3437 */ _parseResponsePayload(text) { 3438 try { 3439 return text ? JSON.parse(text) : {}; 3440 } catch { 3441 return { 3442 raw: text 3443 }; 3444 } 3445 } 3446 /** 3447 * Create error from failed response 3448 * @param {Object} payload - Response payload 3449 * @param {number} status - HTTP status code 3450 * @returns {Error} - Error object with status and payload 3451 */ _createResponseError(payload, status) { 3452 const errorMessage = payload && typeof payload === 'object' && Object.keys(payload).length ? payload.message || JSON.stringify(payload) : 'Request failed'; 3453 const error = new Error(errorMessage); 3454 // Preserve payload properties if it's an object 3455 if (payload && typeof payload === 'object' && Object.keys(payload).length) Object.assign(error, payload); 3456 // Preserve numeric HTTP status - must be set after Object.assign 3457 // to ensure it takes precedence over any status in payload 3458 error.status = status; 3459 return error; 3460 } 3461 /** 3462 * Submit lead to API endpoint 3463 * @param {string} leadRoute - API endpoint URL 3464 * @param {Object} homeValuationData - Data to submit 3465 * @returns {Promise<Object>} - Submission result 3466 */ async _submitLead(leadRoute, homeValuationData) { 3467 const controller = new AbortController(); 3468 const timeoutId = setTimeout(()=>controller.abort(), 10000); 3469 try { 3470 const response = await fetch(leadRoute, { 3471 method: 'POST', 3472 headers: { 3473 'Content-Type': 'application/json' 3474 }, 3475 body: JSON.stringify(homeValuationData), 3476 signal: controller.signal 3477 }); 3478 clearTimeout(timeoutId); 3479 const text = await response.text(); 3480 const payload = this._parseResponsePayload(text); 3481 if (!response.ok) throw this._createResponseError(payload, response.status); 3482 if (payload && payload.status === 'success' && payload.redirectUrl) { 3483 this.window.location.href = payload.redirectUrl; 3484 return { 3485 redirected: true, 3486 url: payload.redirectUrl 3487 }; 3488 } 3489 return payload; 3490 } catch (error) { 3491 clearTimeout(timeoutId); 3492 throw error; 3493 } 3494 } 3495 /** 3496 * Submit home valuation request with Google Maps validation 3497 * @param {Object} data 3498 * @return {Promise<Object>} 3499 */ async _submitHomeValuationRequest(data) { 3500 const { config: config } = this; 3501 // Use websiteApiGatewayUrl from config 3502 if (!config.websiteApiGatewayUrl) throw new Error('websiteApiGatewayUrl not configured for home valuation'); 3503 // Log submission started 3504 this._logLeadEvent('home_valuation_submission_started', data); 3505 try { 3506 // Validate address with Google Maps 3507 const rawAddress = this._getRawAddress(data); 3508 let validatedAddressData = null; 3509 try {
3510 validatedAddressData = await this._validateAddressSafely(rawAddress); 3511 } catch (error) { 3512 // Error already has userMessage from _validateAddressSafely 3513 this._logLeadEvent('home_valuation_address_validation_failed', data, error); 3514 throw error; 3515 } 3516 // Get home valuation estimate. 3517 // The estimate is best-effort: many valid properties (e.g. commercial 3518 // parcels or addresses without AVM coverage) have no estimate and the 3519 // estimate endpoint returns a non-OK status for them. A missing estimate 3520 // must never block lead capture, so we log the failure and continue with 3521 // a null estimate rather than aborting the whole submission. 3522 let homeValuationEstimate = null; 3523 try { 3524 homeValuationEstimate = await this._getHomeValuationEstimateSafely(validatedAddressData); 3525 } catch (error) { 3526 // Error already has userMessage from _getHomeValuationEstimateSafely 3527 this._logLeadEvent('home_valuation_estimate_failed', data, error); 3528 homeValuationEstimate = null; 3529 } 3530 // Extract structured address and coordinates from Google Maps validation 3531 const { structuredAddress: structuredAddress, coordinates: coordinates } = this._extractAddressAndCoordinates(validatedAddressData); 3532 const leadRoute = `${config.websiteApiGatewayUrl}${$2aefad83322125c9$var$homeValuationRoute}`; 3533 const homeValuationData = this._buildHomeValuationData(data, homeValuationEstimate, structuredAddress, coordinates); 3534 const result = await this._submitLead(leadRoute, homeValuationData); 3535 // Log success 3536 this._logLeadEvent('home_valuation_submission_success', data); 3537 return result; 3538 } catch (error) { 3539 // Ensure error has user-friendly message (may already be set by helper methods) 3540 if (!error.userMessage) error.userMessage = this._getUserFriendlyErrorMessage(error); 3541 this._logLeadEvent('home_valuation_submission_failed', data, error); 3542 throw error; 3543 } 3544 } 3545 /** 3546 * Validate address using Google Maps Address Validation API 3547 * @param {string} addressStr - The address string to validate 3548 * @returns {Promise<Object|null>} - Validated address data or null if validation fails 3549 */ async _validateAddressWithGoogleMaps(addressStr) { 3550 const { config: config, window: window } = this; 3551 // Ensure Google Maps API key is loaded 3552 let apiKey = config.googleMapsAPIKey; 3553 if (!apiKey && window.luxuryPresence?.mapApiLoader) apiKey = await window.luxuryPresence.mapApiLoader.getMapApiKey(); 3554 if (!apiKey) throw new Error('Google Maps API key not configured'); 3555 const controller = new AbortController(); 3556 const timeoutId = setTimeout(()=>controller.abort(), 10000); 3557 try { 3558 const response = await fetch(`https://addressvalidation.googleapis.com/v1:validateAddress?key=${apiKey}`, { 3559 method: 'POST', 3560 headers: { 3561 'Content-Type': 'application/json' 3562 }, 3563 body: JSON.stringify({ 3564 address: { 3565 addressLines: [ 3566 addressStr 3567 ] 3568 }, 3569 enableUspsCass: false 3570 }), 3571 signal: controller.signal 3572 }); 3573 clearTimeout(timeoutId); 3574 if (!response.ok) { 3575 const error = new Error(`Address validation failed: ${response.status}`); 3576 error.status = response.status; 3577 throw error; 3578 } 3579 return response.json(); 3580 } catch (error) { 3581 clearTimeout(timeoutId); 3582 throw error; 3583 } 3584 } 3585 /** 3586 * Check if structured address has all mandatory fields 3587 * @param {Object} structuredAddress - Structured address object 3588 * @return {boolean} 3589 */ _hasMandatoryAddressFields(structuredAddress) { 3590 return !!(structuredAddress.streetNumber && structuredAddress.streetName && structuredAddress.city && structuredAddress.state && structuredAddress.zipcode); 3591 } 3592 /** 3593 * Log missing mandatory address fields 3594 * @param {Object} structuredAddress - Structured address object 3595 */ _logMissingAddressFields(structuredAddress) { 3596 const missingFields = [];
3597 if (!structuredAddress.streetNumber) missingFields.push('streetNumber'); 3598 if (!structuredAddress.streetName) missingFields.push('streetName'); 3599 if (!structuredAddress.city) missingFields.push('city'); 3600 if (!structuredAddress.state) missingFields.push('state'); 3601 if (!structuredAddress.zipcode) missingFields.push('zipcode'); 3602 // eslint-disable-next-line no-console 3603 console.log('Home valuation estimate skipped - missing required fields:', missingFields.join(', '), 'Available fields:', Object.keys(structuredAddress).join(', ')); 3604 } 3605 /** 3606 * Build query string from structured address 3607 * @param {Object} structuredAddress - Structured address object 3608 * @return {string} 3609 */ _buildAddressQueryString(structuredAddress) { 3610 return Object.entries(structuredAddress).map(([key, value])=>`${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&'); 3611 } 3612 /** 3613 * GET home valuation estimate 3614 * @param {string} address 3615 * @param {Object} validatedAddressData - Google Maps validated address data 3616 * @return {Promise<number|null>} 3617 */ async _getHomeValuationEstimate(validatedAddressData = null) { 3618 const { config: config } = this; 3619 if (!config.websiteApiGatewayUrl) throw new Error('websiteApiGatewayUrl not configured'); 3620 const base = `${config.websiteApiGatewayUrl}${$2aefad83322125c9$var$homeValuationRoute}`; 3621 // Check if we have validated address data 3622 if (!validatedAddressData?.result?.address?.addressComponents) return null; 3623 const structuredAddress = this._transformAddressComponents(validatedAddressData.result.address.addressComponents); 3624 // Check if all mandatory fields exist 3625 if (!this._hasMandatoryAddressFields(structuredAddress)) { 3626 this._logMissingAddressFields(structuredAddress); 3627 return null; 3628 } 3629 // Build URL with query string 3630 const qs = this._buildAddressQueryString(structuredAddress); 3631 const homeValURL = `${base}?${qs}`; 3632 const controller = new AbortController(); 3633 const timeoutId = setTimeout(()=>controller.abort(), 10000); 3634 try { 3635 const response = await fetch(homeValURL, { 3636 headers: { 3637 Accept: 'application/json' 3638 }, 3639 signal: controller.signal 3640 }); 3641 clearTimeout(timeoutId); 3642 if (!response.ok) { 3643 const error = new Error(`Home valuation estimate failed: ${response.status}`); 3644 error.status = response.status; 3645 throw error; 3646 } 3647 const data = await response.json(); 3648 return data?.valueDollars ?? null; 3649 } catch (error) { 3650 clearTimeout(timeoutId); 3651 // Re-throw to allow retry logic to handle it 3652 throw error; 3653 } 3654 } 3655} 3656 3657 3658const $e10bd00497be8876$export$97de66002300ed70 = 'middleName'; 3659// Default lead fields 3660const $e10bd00497be8876$var$DEFAULT_LEAD_FIELDS = [ 3661 'name', 3662 'firstName', 3663 'lastName', 3664 $e10bd00497be8876$export$97de66002300ed70, 3665 'email', 3666 'phone', 3667 'phoneNumber', 3668 'source', 3669 'message', 3670 'mlsId', 3671 'sourceUrl' 3672]; 3673class $e10bd00497be8876$export$3c5380623cf51249 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 3674 /** 3675 * Initialize Contact Form Controller at build time 3676 * @param { ConfigController } config 3677 * @param {Window} window 3678 * @param {Document} document 3679 */ constructor(config, window, document){ 3680 super(config, window, document); 3681 this.forms = []; 3682 this.homeValuationService = new (0, $2aefad83322125c9$export$2e2bcd8739ae039)(config, window, document); 3683 } 3684 /** 3685 * Initialize Contact Form Controller at run time 3686 * Attach handlers and events to DOM 3687 * @param {Object} options 3688 */ initialize(options = {}) { 3689 try { 3690 const { document: document } = this; 3691 const { selector: selector } = options; 3692 let forms = []; 3693 if (selector) forms = document.querySelectorAll(`${selector} form[data-type="contact-form"]`); 3694 else forms = document.querySelectorAll('form[data-type="contact-form"]');
3695 forms.forEach((form)=>{ 3696 this._attachSubmitHandler(form); 3697 // this._attachValidation(form); 3698 }); 3699 this.forms = this.forms.concat(forms); 3700 } catch (e) { 3701 // eslint-disable-next-line no-console 3702 console.log(e); 3703 } 3704 } 3705 /** 3706 * Controllers must implement a "destroy" method to be called 3707 * after page navigation to remove event handlers / cleanup 3708 */ destroy() { 3709 this.forms = []; 3710 } 3711 /** 3712 * Handle submit from global context 3713 * @param {HTMLFormElement} form 3714 */ submitForm(form) { 3715 this._handleSubmit(null, form).catch((e)=>console.error(e)); 3716 } 3717 /** 3718 * Attach submit event for form 3719 * @param {HTMLFormElement} contactForm 3720 */ _attachValidation(contactForm) { 3721 const { window: window } = this; 3722 const inputs = contactForm.querySelectorAll('input, select, textarea'); 3723 inputs.forEach((input)=>{ 3724 window.luxuryPresence.validation.applyFieldValidation(input); 3725 }); 3726 } 3727 /** 3728 * Attach submit event for form 3729 * @param {HTMLFormElement} contactForm 3730 */ _attachSubmitHandler(contactForm) { 3731 contactForm.addEventListener('submit', (e)=>this._handleSubmit(e, contactForm)); 3732 } 3733 /** 3734 * Extract and process form values 3735 * @param {HTMLFormElement} contactForm 3736 * @return {Object} - Processed form values 3737 */ _extractFormValues(contactForm) { 3738 const formData = new FormData(contactForm); 3739 const values = {}; 3740 const nameParts = []; 3741 const nameKeyRegex = /^name(?:-\d+)?$/; 3742 formData.forEach((value, key)=>{ 3743 if (typeof value === 'string') { 3744 const curr = value.trim(); 3745 if (nameKeyRegex.test(key) && curr) nameParts.push(curr); 3746 const prev = values[key]; 3747 values[key] = prev ? `${prev} ${curr}` : curr; 3748 } else values[key] = value; 3749 }); 3750 if (nameParts.length) values.name = nameParts.join(' ').trim(); 3751 else if (!values.name && values.firstName) { 3752 values.name = values.firstName; 3753 if (values.lastName) values.name += ` ${values.lastName}`; 3754 } 3755 return values; 3756 } 3757 /** 3758 * Build submission data object 3759 * @param {Object} values - Form values 3760 * @param {Object} fields - Form fields 3761 * @param {HTMLFormElement} contactForm - Form element 3762 * @return {Object} - Submission data 3763 */ _buildSubmissionData(values, fields, contactForm) { 3764 const { window: window, config: config } = this; 3765 const content = Object.keys(values).filter((key)=>!$e10bd00497be8876$var$DEFAULT_LEAD_FIELDS.includes(key)).reduce((result, key)=>{ 3766 // eslint-disable-next-line 3767 result[key] = values[key]; 3768 return result; 3769 }, {}); 3770 const { pageId: pageId, companyId: companyId, websiteId: websiteId, pageMeta: pageMeta } = config; 3771 const utm = window.localStorage.getItem('utm_vars') || '{}'; 3772 const referrer = window.localStorage.getItem('referrer') || ''; 3773 const formId = this._getFormId(contactForm); 3774 return { 3775 activity: { 3776 author_email: values.email, 3777 author_name: values.name, 3778 author_phone: values.phone || values.phoneNumber, 3779 activity_source: values.source || 'CONTACT_INQUIRY', 3780 comments_attributes: [ 3781 { 3782 text: values.message || undefined 3783 } 3784 ], 3785 source_url: window.location.href, 3786 content: { 3787 ...content, 3788 ...JSON.parse(utm) 3789 } 3790 }, 3791 page: { 3792 pageId: pageId, 3793 companyId: companyId, 3794 websiteId: websiteId, 3795 pageMeta: { 3796 ...pageMeta, 3797 formId: formId 3798 } 3799 }, 3800 companyId: companyId, 3801 referrer: referrer, 3802 fields: fields 3803 }; 3804 } 3805 /** 3806 * Handle successful form submission 3807 * @param {HTMLFormElement} contactForm - Form element 3808 */ _handleSubmissionSuccess(contactForm) { 3809 const { window: window } = this; 3810 const closeDelay = contactForm.dataset.delay || (0, $a3e97e89bbc2a366$export$f037eeffb5561205); 3811 contactForm.classList.remove('loading'); 3812 contactForm.classList.remove('error'); 3813 contactForm.classList.add('success'); 3814 // TODO: Review producer and classic to deprecate "hide" 3815 contactForm.querySelectorAll('.success').forEach((el)=>el.classList.remove('hide')); 3816 contactForm.dispatchEvent(new Event('formsubmitted')); 3817 // Any successful lead submit credits the Google Ads lead conversion 3818 // (reuses the existing `generate_lead` action), once per session. 3819 (0, $3ec2bf3965f87ecc$export$e70565180c0e6950)(this.window, this.document); 3820 setTimeout(()=>{ 3821 window.lpUI.hideClosestModal(contactForm); 3822 contactForm.reset(); 3823 window.lpUI.sideMenuHide(); 3824 contactForm.querySelectorAll('.success').forEach((el)=>el.classList.add('hide')); 3825 contactForm.classList.remove('success'); 3826 }, closeDelay); 3827 } 3828 /** 3829 * Submit Contact Form Handler 3830 * @param {Event} e 3831 * @param {HTMLFormElement} contactForm 3832 * @return {Promise<Object>} 3833 */ async _handleSubmit(e, contactForm) { 3834 if (e) e.preventDefault(); 3835 if (contactForm.classList.contains('loading')) // Form already submitting 3836 return; 3837 // Hide any previous error message and clear error state 3838 this._hideSubmissionError(contactForm); 3839 contactForm.classList.remove('error'); 3840 contactForm.classList.add('loading'); 3841 try { 3842 // Extract form values first (before async operations) 3843 const values = this._extractFormValues(contactForm); 3844 // TODO: HoneyPot should still send request 3845 if (values.middleName) { 3846 contactForm.classList.remove('loading'); 3847 return; 3848 } 3849 const fields = this._getFields(contactForm); 3850 const data = this._buildSubmissionData(values, fields, contactForm); 3851 await this._submitLeadRequest(data); 3852 this._sendGAEvent(data); 3853 this._handleSubmissionSuccess(contactForm); 3854 } catch (err) { 3855 // eslint-disable-next-line no-console 3856 console.error('Form submission failed:', err); 3857 contactForm.dispatchEvent(new Event('formsubmiterror')); 3858 contactForm.classList.remove('loading'); 3859 contactForm.classList.add('error'); 3860 // Show user-friendly error message if available (from home valuation service)
3861 if (err.userMessage) this._showSubmissionError(contactForm, err.userMessage); 3862 else this._showSubmissionError(contactForm, 'Something went wrong. Please try again.'); 3863 } 3864 } 3865 /** 3866 * Alias anonymous user with canonical ID using event bus 3867 * Links the user's anonymous browsing session to their canonical identity 3868 * @param {Object} data - Form submission data 3869 * @private 3870 */ _aliasUserWithEventBus(data) { 3871 try { 3872 // Check if event bus library and required methods are available 3873 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 3874 // Skip if user is already identified 3875 if (this.window.eventBusProvider.isIdentified()) { 3876 console.log('[EventBus] User already identified, skipping alias'); 3877 return; 3878 } 3879 const email = data.activity?.author_email; 3880 const { companyId: companyId } = this.config; 3881 if (!email || !companyId) { 3882 console.warn('[EventBus] Missing required data for user alias'); 3883 return; 3884 } 3885 // Get the anonymous distinct ID from PostHog 3886 const anonId = this.window.eventBusProvider.getDistinctId(); 3887 if (!anonId) { 3888 console.warn('[EventBus] No anonymous ID available for alias'); 3889 return; 3890 } 3891 // Generate deterministic canonical ID from company + email 3892 const canonicalId = this.window.LuxuryPresenceEventBus.generateUUIDv5FromCompanyEmail(companyId, email); 3893 // Create alias linking anonymous ID to canonical ID 3894 this.window.eventBusProvider.alias({ 3895 anonId: anonId, 3896 canonicalId: canonicalId 3897 }); 3898 console.log('[EventBus] User aliased with canonical ID'); 3899 } catch (error) { 3900 // Never block form submission due to analytics errors 3901 console.error('[EventBus] User alias failed:', error); 3902 } 3903 } 3904 /** 3905 * Send Lead Request 3906 * @param {Object} data 3907 * @return {Promise<Object>} 3908 */ async _submitLeadRequest(data) { 3909 const { config: config } = this; 3910 // Alias user with event bus before submitting the lead 3911 this._aliasUserWithEventBus(data); 3912 if (config.myhomeDashboardEnabled && (data.activity?.activity_source === 'HOME_VALUATION' || data.activity?.activity_source === 'HOME_VALUE')) return this.homeValuationService._submitHomeValuationRequest(data); 3913 const leadRoute = `${config.leadIngesterUrl}/api/v1/leads`; 3914 return fetch(leadRoute, { 3915 method: 'POST', 3916 headers: { 3917 'Content-Type': 'application/json' 3918 }, 3919 body: JSON.stringify(data) 3920 }).then((response)=>{ 3921 if (!response.ok) return response.json().then((error)=>{ 3922 throw error; 3923 }); 3924 return response.json(); 3925 }); 3926 } 3927 /** 3928 * Send GA event 3929 * @param {Object} data 3930 */ _sendGAEvent(data) { 3931 const { document: document } = this; 3932 let event = { 3933 hitType: 'event', 3934 eventCategory: 'button', 3935 eventAction: 'click' 3936 }; 3937 switch(data.activity.activity_source){ 3938 case 'NEWSLETTER_SIGNUP': 3939 event.eventLabel = 'Newsletter'; 3940 document.sendGoogleTagManagerEvent('event', 'newsletter_signup'); 3941 break; 3942 case 'HOME_SEARCH': 3943 event.eventLabel = 'HomeSearch'; 3944 document.sendGoogleTagManagerEvent('event', 'home_search'); 3945 break; 3946 case 'HOME_VALUE': 3947 case 'HOME_VALUATION': 3948 event.eventLabel = 'HomeValue'; 3949 document.sendGoogleTagManagerEvent('event', 'home_valuation'); 3950 break; 3951 case 'EBOOK': 3952 event.eventLabel = 'Ebook'; 3953 document.sendGoogleTagManagerEvent('event', 'ebook_download'); 3954 break; 3955 case 'CONTACT_INQUIRY': 3956 default: 3957 // for contact forms, Marketing has requested that they see a page-view of 'thank-you' as indication of success 3958 // Note that this will break if we have actual page with url of /thank-you 3959 event = { 3960 hitType: 'pageview', 3961 page: '/thank-you' 3962 }; 3963 document.sendGoogleTagManagerEvent('event', 'contact_inquiry'); 3964 break; 3965 } 3966 document.sendGoogleAnalyticsEvent(event); 3967 } 3968 /** 3969 * Gather fields / schema for form, used for display in CRM 3970 * @param {HTMLFormElement} form 3971 * @return {Object} 3972 */ _getFields(form) { 3973 const inputs = form.querySelectorAll('input, select, textarea'); 3974 const fields = {};
3975 inputs.forEach((input)=>{ 3976 const { id: id, name: name, type: inputType } = input; 3977 if (name === $e10bd00497be8876$export$97de66002300ed70) // Honeypot continue 3978 return true; 3979 let label = ''; 3980 if (id) { 3981 const labelNode = form.querySelector(`label[for='${id}']`); 3982 if (labelNode) label = labelNode.textContent.trim(); 3983 else if (input.placeholder) label = input.placeholder.trim(); 3984 } 3985 fields[name] = { 3986 label: label, 3987 type: input.tagName, 3988 inputType: input.tagName === 'INPUT' ? inputType : 'text' 3989 }; 3990 }); 3991 return fields; 3992 } 3993 /** 3994 * Figure out relevant section or div ID for form 3995 * @param {HTMLFormElement} form 3996 * @return {string} 3997 */ _getFormId(form) { 3998 const SELECTORS = "section[id^='section-'], footer[id], .modal[id^='modal-']"; 3999 const parent = form.closest(SELECTORS); 4000 return parent?.id ? this._cleanFormId(parent.id) : ''; 4001 } 4002 /** 4003 * Remove prefix from formID 4004 * @param {string} formId 4005 * @return {string} 4006 */ _cleanFormId(formId) { 4007 const PREFIXES = [ 4008 'section-', 4009 'modal-' 4010 ]; 4011 for (const prefix of PREFIXES){ 4012 if (formId.startsWith(prefix)) return formId.substring(prefix.length); 4013 } 4014 return formId; 4015 } 4016 /** 4017 * Show submission error message on form 4018 * @param {HTMLFormElement} form 4019 * @param {string} message 4020 */ _showSubmissionError(form, message) { 4021 const errorEl = form.querySelector('.submission-error-message'); 4022 if (errorEl) { 4023 errorEl.textContent = message || 'Something went wrong. Please try again.'; 4024 errorEl.style.display = 'block'; 4025 } 4026 } 4027 /** 4028 * Hide submission error message on form 4029 * @param {HTMLFormElement} form 4030 */ _hideSubmissionError(form) { 4031 const errorEl = form.querySelector('.submission-error-message'); 4032 if (errorEl) errorEl.style.display = 'none'; 4033 } 4034} 4035 4036 4037 4038const $ea6901fdf47e0ec1$var$CALLBACK = '__googleMapsCallback'; 4039const $ea6901fdf47e0ec1$var$GOOGLE_MAPS_URL = 'https://maps.googleapis.com/maps/api/js'; 4040const $ea6901fdf47e0ec1$export$76299006759be400 = 1024; 4041const $ea6901fdf47e0ec1$var$KEY_FETCH_ATTEMPTS = 3; 4042const $ea6901fdf47e0ec1$var$KEY_RETRY_BASE_DELAY_MS = 300; 4043const $ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG = 'lp_map_key_failed'; 4044class $ea6901fdf47e0ec1$export$3c19195edca3fa5c extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4045 /** 4046 * Initialize Button Controller at build time 4047 * @param { ConfigController } config 4048 * @param {Window} window 4049 * @param {Document} document 4050 */ constructor(config, window, document){ 4051 super(config, window, document); 4052 this.retries = 3; 4053 this.callbacks = []; 4054 this.id = `__googleMapsScriptId${Date.now()}`; 4055 this._apiKey = ''; 4056 this.reset(); 4057 } 4058 /** 4059 * Set the Google Maps api key 4060 * @param {string} apiKey 4061 */ set apiKey(apiKey) { 4062 this._apiKey = apiKey; 4063 this.config.setGoogleMapsAPIKey(apiKey); 4064 } 4065 /** 4066 * Get the Google Maps api key 4067 * @return {string} 4068 */ get apiKey() { 4069 return this._apiKey; 4070 } 4071 /** 4072 * Reset the loader state. 4073 */ reset() { 4074 this.deleteScript(); 4075 this.done = false; 4076 this.loading = false; 4077 this.failed = false; 4078 this.errors = []; 4079 this.onerrorEvent = null; 4080 } 4081 /** 4082 * Destroy 4083 */ destroy() { 4084 this.reset(); 4085 } 4086 /** 4087 * get map api Key 4088 * @return {Promise<string>} 4089 */ async getMapApiKey() { 4090 if (this.apiKey) return this.apiKey; 4091 const hadPreviousFailure = this._hadPreviousFailure(); 4092 let recoveryReported = false; 4093 for(let attempt = 1; attempt <= $ea6901fdf47e0ec1$var$KEY_FETCH_ATTEMPTS; attempt += 1){ 4094 const reportRecovery = hadPreviousFailure && !recoveryReported; 4095 try { 4096 // eslint-disable-next-line no-await-in-loop 4097 const response = await fetch(this._getMapKeyUrl(attempt, reportRecovery));
4098 // The request reached the server, so a recovery marker on it was 4099 // delivered and must not be repeated on later attempts or pages. A 4100 // rejected fetch never arrived, so the marker moves to the next 4101 // attempt. 4102 recoveryReported = recoveryReported || reportRecovery; 4103 this._setFailureFlag(false); 4104 if (!response.ok) throw new Error(`Failed to fetch API key: ${response.status}`); 4105 // eslint-disable-next-line no-await-in-loop 4106 const code = await response.text(); 4107 if (!code) return ''; 4108 this.apiKey = code; 4109 return this.apiKey; 4110 } catch (e) { 4111 // eslint-disable-next-line no-console 4112 console.error(e); 4113 if (attempt < $ea6901fdf47e0ec1$var$KEY_FETCH_ATTEMPTS) // eslint-disable-next-line no-await-in-loop 4114 await this._delay($ea6901fdf47e0ec1$var$KEY_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1)); 4115 } 4116 } 4117 this._setFailureFlag(true); 4118 return ''; 4119 } 4120 /** 4121 * Build the map key request url. Retries carry their attempt number, and a 4122 * single request per session carries the recovery marker, so both are 4123 * countable in existing request logs without new infrastructure. 4124 * @param {number} attempt 1-based attempt number 4125 * @param {boolean} reportRecovery tag this request as following a session 4126 * in which every attempt failed 4127 * @return {string} 4128 */ _getMapKeyUrl(attempt, reportRecovery) { 4129 const params = []; 4130 if (attempt > 1) params.push(`attempt=${attempt}`); 4131 if (reportRecovery) params.push('recovered_after_failure=1'); 4132 const query = params.length ? `?${params.join('&')}` : ''; 4133 return `${this._getBaseUrl()}/api/v1/map${query}`; 4134 } 4135 /** 4136 * Whether an earlier attempt in this session exhausted all retries. 4137 * @return {boolean} 4138 */ _hadPreviousFailure() { 4139 try { 4140 return this.window.sessionStorage.getItem($ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG) === '1'; 4141 } catch (e) { 4142 // Storage can throw when disabled (private browsing); absence of the 4143 // flag is the safe default. 4144 return false; 4145 } 4146 } 4147 /** 4148 * Record or clear the session-level key fetch failure flag. 4149 * @param {boolean} failed 4150 */ _setFailureFlag(failed) { 4151 try { 4152 if (failed) this.window.sessionStorage.setItem($ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG, '1'); 4153 else this.window.sessionStorage.removeItem($ea6901fdf47e0ec1$var$KEY_FAILURE_FLAG); 4154 } catch (e) { 4155 // Storage unavailable - reporting is best effort, never fatal. 4156 } 4157 } 4158 /** 4159 * @param {number} ms 4160 * @return {Promise} 4161 */ _delay(ms) { 4162 return new Promise((resolve)=>{ 4163 this.window.setTimeout(resolve, ms); 4164 }); 4165 } 4166 /** 4167 * CreateUrl returns the Google Maps JavaScript API script url ]. 4168 * @return {string} 4169 */ async createUrl() { 4170 const apiKey = await this.getMapApiKey(); 4171 // Google Maps loads successfully without a key and then fails at call time 4172 // with NoApiKeys, which produces a broken map and no load error. Refuse 4173 // instead, so the failure surfaces to the caller. 4174 if (!apiKey) throw new Error('Refusing to load Google Maps without an API key'); 4175 let url = $ea6901fdf47e0ec1$var$GOOGLE_MAPS_URL; 4176 url += `?callback=${$ea6901fdf47e0ec1$var$CALLBACK}`; 4177 url += `&key=${apiKey}`; 4178 url += `&libraries=places`; 4179 return url; 4180 } 4181 /** 4182 * Set the script on document. 4183 */ async setScript() { 4184 if (this.document.getElementById(this.id)) { 4185 this.callback(); 4186 return; 4187 } 4188 let url; 4189 try { 4190 url = await this.createUrl(); 4191 } catch (e) { 4192 // eslint-disable-next-line no-console 4193 console.error(e); 4194 this.failed = true; 4195 this.loading = false; 4196 this.onerrorEvent = e; 4197 this.callback(); 4198 return; 4199 } 4200 const script = this.document.createElement('script'); 4201 script.id = this.id; 4202 script.type = 'text/javascript'; 4203 script.src = url; 4204 script.onerror = this.loadErrorCallback.bind(this); 4205 script.defer = true; 4206 script.async = true; 4207 this.document.head.appendChild(script); 4208 } 4209 /** 4210 * Function to be called if script tag fails 4211 * @param {Error} e 4212 */ loadErrorCallback(e) { 4213 this.errors.push(e); 4214 if (this.errors.length <= this.retries) {
4215 const delay = this.errors.length * 2 ** this.errors.length; 4216 // eslint-disable-next-line no-console 4217 console.log(`Failed to load Google Maps script, retrying in ${delay} ms.`); 4218 setTimeout(()=>{ 4219 this.deleteScript(); 4220 this.setScript().catch((e)=>console.error(e)); 4221 }, delay); 4222 } else { 4223 this.failed = true; 4224 this.onerrorEvent = e; 4225 this.callback(); 4226 } 4227 } 4228 /** 4229 * Delete the script tag 4230 */ deleteScript() { 4231 const script = this.document.getElementById(this.id); 4232 if (script) script.remove(); 4233 } 4234 /** 4235 * Load the Google Maps JavaScript API script and return a Promise. 4236 * @return {Promise} 4237 */ load() { 4238 return this.loadPromise(); 4239 } 4240 /** 4241 * Load the Google Maps JavaScript API script and return a Promise. 4242 * @return {Promise} 4243 */ loadPromise() { 4244 return new Promise((resolve, reject)=>{ 4245 this.loadCallback((err)=>{ 4246 if (!err) resolve(); 4247 else reject(err); 4248 }); 4249 }); 4250 } 4251 /** 4252 * Load the Google Maps JavaScript API script with a callback. 4253 * @param {Function<Event>} fn 4254 */ loadCallback(fn) { 4255 this.callbacks.push(fn); 4256 this.execute(); 4257 } 4258 /** 4259 * Sets the callback function that will be sent as part of the script URL. 4260 */ setCallback() { 4261 this.window.__googleMapsCallback = this.callback.bind(this); 4262 } 4263 /** 4264 * Reset if failed 4265 */ resetIfRetryingFailed() { 4266 if (this.failed) this.reset(); 4267 } 4268 /** 4269 * This is the function that we set the callback parameter to. 4270 */ callback() { 4271 this.done = true; 4272 this.loading = false; 4273 this.callbacks.forEach((cb)=>{ 4274 cb(this.onerrorEvent); 4275 }); 4276 this.callbacks = []; 4277 } 4278 /** 4279 * Set the script a if needed; set the callback 4280 */ execute() { 4281 if (this.window.google && this.window.google.maps && this.window.google.maps.version) // Google Maps already loaded 4282 this.callback(); 4283 this.resetIfRetryingFailed(); 4284 if (this.done) this.callback(); 4285 else if (this.loading) ; 4286 else { 4287 this.loading = true; 4288 this.setCallback(); 4289 this.setScript().catch((e)=>console.error(e)); 4290 } 4291 } 4292 /** 4293 * get base url based on current config 4294 * @return {string} 4295 */ _getBaseUrl() { 4296 // Special handling for page builder since no wag available 4297 const { isBuilder: isBuilder, websiteApiGatewayUrl: websiteApiGatewayUrl } = this.config; 4298 return isBuilder ? websiteApiGatewayUrl : ''; 4299 } 4300 /** 4301 * Resize static map requests to the max 1024px while preserving aspect ratio 4302 * @param {number} width 4303 * @param {number} height 4304 * @return {{width, height}} 4305 */ resizeStaticMapDimensions(width, height) { 4306 if (width <= $ea6901fdf47e0ec1$export$76299006759be400 && height <= $ea6901fdf47e0ec1$export$76299006759be400) return { 4307 width: width, 4308 height: height 4309 }; 4310 // Size param ignored if dimensions exceed STATIC_MAP_MAX_DIM 4311 const ratio = Math.min($ea6901fdf47e0ec1$export$76299006759be400 / width, $ea6901fdf47e0ec1$export$76299006759be400 / height); 4312 return { 4313 width: parseInt(width * ratio), 4314 height: parseInt(height * ratio) 4315 }; 4316 } 4317 /** 4318 * Convert theme to static api param object 4319 * @param {Object} theme 4320 * @return {Object} 4321 */ convertThemeToStaticApiUrl(theme) { 4322 // https://developers.google.com/maps/documentation/maps-static/style-reference 4323 // Google Maps Static API uses a special syntax for it's urls based on the interactive map styles 4324 return theme.map((t)=>{ 4325 const styles = t.stylers.reduce((acc, s)=>{ 4326 const visibility = s.visibility ? `visibility:${s.visibility}` : ''; 4327 const color = s.color ? `color:${s.color.replace('#', '0x')}` : ''; 4328 const lightness = s.lightness ? `lightness:${s.lightness}` : ''; 4329 return `${acc}|${visibility}${color}${lightness}`; 4330 }, ''); 4331 const element = t.elementType ? `|element:${t.elementType}` : ''; 4332 return `feature:${t.featureType}${element}${styles}`; 4333 }); 4334 } 4335 /** 4336 * generate digital signature for url 4337 * @param {string} queryString to sign 4338 * @return {Promise<string>} digitally signed url 4339 */ async getMapApiSignedUrl(queryString) { 4340 try { 4341 const response = await fetch(`${this._getBaseUrl()}/api/v1/map/signature?${queryString}`); 4342 if (!response.ok) throw new Error('Failed to fetch signed URL'); 4343 const { url: url } = await response.json(); 4344 return url; 4345 } catch (e) { 4346 // eslint-disable-next-line no-console 4347 console.error(e); 4348 return ''; 4349 } 4350 } 4351 /** 4352 * Generate static map url 4353 * @param {number} lat 4354 * @param {number} lng 4355 * @param {number} zoom 4356 * @param {Object} theme 4357 * @param {number} srcWidth 4358 * @param {number} srcHeight 4359 * @return {string} 4360 */ generateStaticMapUrl(lat, lng, zoom, theme, srcWidth, srcHeight) {
4361 const baseUrl = 'https://maps.googleapis.com/maps/api/staticmap'; 4362 const { width: width, height: height } = this.resizeStaticMapDimensions(srcWidth, srcHeight); 4363 const query = { 4364 center: `${lat},${lng}`, 4365 zoom: zoom, 4366 scale: 2, 4367 size: `${width}x${height}`, 4368 key: this.apiKey 4369 }; 4370 if (theme) query.style = this.convertThemeToStaticApiUrl(theme); 4371 const params = new URLSearchParams(); 4372 Object.entries(query).forEach(([key, value])=>{ 4373 if (Array.isArray(value)) value.forEach((v)=>params.append(key, v.toString())); 4374 else params.append(key, value.toString()); 4375 }); 4376 return `${baseUrl}?${params.toString()}`; 4377 } 4378 /** 4379 * Generate static map url with digital signature 4380 * @param {Object} { 4381 * srcWidth {number}, required 4382 * srcHeight {number}, required 4383 * theme {array}, 4384 * center: `${lat},${lng}`, required 4385 * zoom: number, required 4386 * markers: `${lat},${lng}`, 4387 * scale: number, 4388 * otherQueryParams... (these must match what is compatible with google maps static api query params) 4389 * } 4390 * @return {Promise<string>} digitally signed url 4391 */ async generateDigitallySignedStaticMapUrl({ srcWidth: srcWidth, srcHeight: srcHeight, theme: theme, ...query }) { 4392 const { width: width, height: height } = this.resizeStaticMapDimensions(srcWidth, srcHeight); 4393 const queryParams = { 4394 ...query, 4395 size: `${width}x${height}` 4396 }; 4397 if (theme) queryParams.style = this.convertThemeToStaticApiUrl(theme); 4398 const params = new URLSearchParams(); 4399 Object.entries(queryParams).forEach(([key, value])=>{ 4400 if (Array.isArray(value)) value.forEach((v)=>params.append(key, v.toString())); 4401 else params.append(key, value.toString()); 4402 }); 4403 const queryString = params.toString(); 4404 try { 4405 // fetch digitally signed url 4406 return await this.getMapApiSignedUrl(queryString); 4407 } catch (e) { 4408 console.error(e); 4409 return ''; 4410 } 4411 } 4412} 4413 4414 4415/* eslint-disable import/prefer-default-export */ 4416 4417class $0a0ad49f13c0b8fd$export$ba9afeb75f20f616 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4418 // constructor is not needed as it is inherited from BaseController 4419 /** 4420 * Controllers must implement a "destroy" method to be called 4421 * after page navigation to remove event handlers / cleanup 4422 */ destroy() {} 4423 /** 4424 * Places (re-places) a value into the query string under specified key 4425 * or removes it if new value is null or undefined 4426 * @param {string} key 4427 * @param {string|null|undefined|object|array} value 4428 * @param {string} url - defaults to window.location.href 4429 * @return {string} 4430 */ // eslint-disable-next-line sonarjs/cognitive-complexity 4431 updateQueryString(key, value, url) { 4432 // https://stackoverflow.com/questions/5999118/how-can-i-add-or-update-a-query-string-parameter 4433 if (!url) // eslint-disable-next-line 4434 url = this.window.location.href; 4435 let re = new RegExp(`([?&])${key}=.*?(&|#|$)`, 'i'); 4436 let replacement = ''; 4437 if (typeof value !== 'undefined' && value !== null) { 4438 if (Array.isArray(value)) { 4439 if (value.length) replacement = this.serializeArray(key, value); 4440 } else if (typeof value === 'object') { 4441 re = new RegExp(`([?&])${key}\\[[^=]*\\]=.*?(&|#|$)`, 'i'); 4442 replacement = this.serializeObj(value, '', key); 4443 } else replacement = `${key}=${encodeURIComponent(value)}`; 4444 } 4445 const hash = url.split('#'); 4446 let result = hash[0]; 4447 if (re.test(url)) { 4448 let clean = result; 4449 replacement = replacement ? `$1${replacement}$2` : '$1'; 4450 while(re.test(clean)){ 4451 result = clean.replace(re, replacement); 4452 clean = clean.replace(re, '$1'); 4453 } 4454 result = result.replace(/(&|\?)$/, ''); 4455 } else if (replacement) { 4456 const separator = result.indexOf('?') !== -1 ? '&' : '?'; 4457 result += `${separator}${replacement}`; 4458 } 4459 if (typeof hash[1] !== 'undefined' && hash[1] !== null) result += `#${hash[1]}`; 4460 return result; 4461 } 4462 /** 4463 * Returns variable from query string specified by parameter name 4464 * @param {string} name 4465 * @param {string} url defaults to window.location.href 4466 * @return {string|null} 4467 */ getParameterByName(name, url) { 4468 if (!url) // eslint-disable-next-line 4469 url = this.window.location.href; 4470 // eslint-disable-next-line 4471 name = name.replace(/[[\]]/g, '\\$&'); 4472 const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); 4473 let results = Array.from(RegExp.prototype[Symbol.matchAll].call(regex, url)).flat(); 4474 // check for object like syntax where the last value can be an array eg. advancedFilters[query][statusIds][in][0] or advancedFilters[query][city] 4475 if (!results.length) { 4476 const objRegex = new RegExp(`[?&]${name}(\\[.*?\\])=(.*?)(?=&|#|$)`, 'g'); 4477 results = Array.from(RegExp.prototype[Symbol.matchAll].call(objRegex, url)).flat(); 4478 return this._parseParamToObject(results, name === 'advancedFilters'); 4479 } 4480 // For multiple param values, eg arrays 4481 /* Results from regex match all come in this form: 4482 [ '&advancedFilters[query][statusIds][in]=uuid-segundo-123', '[query][statusIds][in]', 'uuid-segundo-123' ] 4483 So using every third value will be the actual value and the second value will be the path 4484 */ if (results.length > 3) return results.reduce((acc, value, index)=>{ 4485 if ((index + 1) % 3 === 0) acc.push(decodeURIComponent(value.replace(/\+/g, ' '))); 4486 return acc; 4487 }, []); 4488 if (!results[2]) return ''; 4489 return decodeURIComponent(results[2].replace(/\+/g, ' ')); 4490 } 4491 /** 4492 * Generates section specific key to store page number in the URL 4493 * @param {string} sectionDomId 4494 * @return {string} 4495 */ makePageKey(sectionDomId) { 4496 return sectionDomId.replace('section-', '').replace(/-/g, ''); 4497 } 4498 /** 4499 * Generates specific key to store parameter in the URL 4500 * uses prevsiouly calculated baseKey for section specific parameters 4501 * @param {Object} formParam 4502 * @param {string} baseKey pre-calculated base for section specific parameters 4503 * @return {string} 4504 */ makeParamKey(formParam, baseKey) { 4505 return formParam.global ? formParam.urlKey : `${baseKey}-${formParam.urlKey}`; 4506 } 4507 /** 4508 * Gets a set of parameters from window query string 4509 * always returns value of "page" parameter if present 4510 * @param {Object[]} formConfig 4511 * @param {string} sectionDomId 4512 * @return {Object} 4513 */ deserializeParams(formConfig, sectionDomId) { 4514 const params = {}; 4515 const urlPageKey = this.makePageKey(sectionDomId); 4516 const page = this.getParameterByName(urlPageKey); 4517 if (page) params.page = parseInt(page); 4518 if (Array.isArray(formConfig)) formConfig.forEach((formParam)=>{
4519 const key = this.makeParamKey(formParam, urlPageKey); 4520 let urlValue = this.getParameterByName(key); 4521 if (formParam.type === 'int') { 4522 urlValue = parseInt(urlValue); 4523 // eslint-disable-next-line 4524 if (!isFinite(urlValue)) urlValue = null; 4525 } else if (formParam.type === 'bool' || formParam.type === 'boolean') urlValue = // eslint-disable-next-line 4526 urlValue === 'true' ? true : urlValue === 'false' ? false : null; 4527 else if (formParam.type === 'json') try { 4528 urlValue = JSON.parse(urlValue); 4529 } catch (e) { 4530 // no need to do anything 4531 } 4532 params[formParam.formKey || formParam.param] = urlValue; 4533 }); 4534 return params; 4535 } 4536 /** 4537 * Serializes set of parameters defined by formConfig into the URL 4538 * adding or replacing or removing required parameters 4539 * @param {Object[]} formConfig 4540 * @param {string} sectionDomId 4541 * @param {Object} params 4542 * @return {string} 4543 */ serializeParams(formConfig, sectionDomId, params) { 4544 let result = this.window.location.href; 4545 const urlPageKey = this.makePageKey(sectionDomId); 4546 result = this.updateQueryString(urlPageKey, params.page, result); 4547 if (Array.isArray(formConfig)) formConfig.forEach((formParam)=>{ 4548 const key = this.makeParamKey(formParam, urlPageKey); 4549 let value = params[formParam.formKey || formParam.param]; 4550 if (formParam.type === 'json' && value !== null && typeof value !== 'undefined') value = JSON.stringify(value); 4551 result = this.updateQueryString(key, value, result); 4552 }); 4553 return result; 4554 } 4555 /** 4556 * Formats small number for slider values 4557 * @param {number} value 4558 * @param {string} currency 4559 * @return {string} 4560 */ formatSliderAtom(value, currency = '') { 4561 const formattedValue = Number.isInteger(value) ? value : value.toFixed(2); 4562 return currency + formattedValue; 4563 } 4564 /** 4565 * Formats a number for slider values 4566 * @param {number} value 4567 * @param {string} currency 4568 * @param {string} delimiter 4569 * @return {string} 4570 */ formatSliderValue(value, currency = '', delimiter = '') { 4571 if (value < 1000) return this.formatSliderAtom(value, currency); 4572 if (value < 1000000) return `${this.formatSliderAtom(value / 1000, currency)}${delimiter}K`; 4573 return `${this.formatSliderAtom(value / 1000000, currency)}${delimiter}M`; 4574 } 4575 /** 4576 * Format price slider range 4577 * @param {number} value 4578 * @param {Object} range 4579 * @param {string} currency 4580 * @param {Object} options 4581 * @return {string} 4582 */ formatSliderPriceRange(value, range, currency, options) { 4583 if (range.min === value && value !== 0) return `${options.lowPrefix}${this.formatSliderValue(value, currency, ' ')}`; 4584 if (range.max === value) return `${this.formatSliderValue(value, currency, ' ')}${options.highSuffix}`; 4585 return `${this.formatSliderValue(value, currency, ' ')}`; 4586 } 4587 /** 4588 * Format area slider range 4589 * @param {number} value 4590 * @param {Object} range 4591 * @param {string} suffix 4592 * @param {Object} options 4593 * @return {string} 4594 */ formatSliderAreaRange(value, range, suffix, options) { 4595 if (range.min === value && value !== 0) return `${options.lowPrefix}${this.formatSliderValue(value)} ${suffix}`; 4596 if (range.max === value) return `${this.formatSliderValue(value)}${options.highSuffix} ${suffix}`; 4597 return `${this.formatSliderValue(value)} ${suffix}`; 4598 } 4599 /** 4600 * Utility function to get properties search parameters from a set of jQuery objects 4601 * @param {Object} options 4602 * @param {jQuery|HTMLElement} [options.queryElement] 4603 * @param {jQuery|HTMLElement} [options.priceSliderElement] 4604 * @param {jQuery|HTMLElement} [options.areaSliderElement] 4605 * @param {jQuery|HTMLElement} [options.neighborhoodSelect] 4606 * @return {Object} 4607 */ // eslint-disable-next-line sonarjs/cognitive-complexity 4608 getSearchFormValues(options) { 4609 const { queryElement: queryElement, priceSliderElement: priceSliderElement, areaSli
4609derElement: areaSliderElement, neighborhoodSelect: neighborhoodSelect } = options; 4610 const result = {}; 4611 if (queryElement) result.search = queryElement.value || null; 4612 if (priceSliderElement) { 4613 const priceValues = priceSliderElement.value.split(';'); 4614 const priceMinLimit = parseInt(priceSliderElement.dataset.min); 4615 const priceMaxLimit = parseInt(priceSliderElement.dataset.max); 4616 const priceMin = parseInt(priceValues[0]); 4617 const priceMax = parseInt(priceValues[1]); 4618 result.priceMin = priceMin !== priceMinLimit ? priceMin : null; 4619 result.priceMax = priceMax !== priceMaxLimit ? priceMax : null; 4620 } 4621 if (areaSliderElement) { 4622 const areaValues = areaSliderElement.value.split(';'); 4623 const areaMinLimit = parseInt(areaSliderElement.dataset.min); 4624 const areaMaxLimit = parseInt(areaSliderElement.dataset.max); 4625 const areaMin = parseInt(areaValues[0]); 4626 const areaMax = parseInt(areaValues[1]); 4627 result.areaMin = areaMin !== areaMinLimit ? areaMin : null; 4628 result.areaMax = areaMax !== areaMaxLimit ? areaMax : null; 4629 } 4630 if (neighborhoodSelect) result.neighborhoodId = neighborhoodSelect.value || null; 4631 return result; 4632 } 4633 /** 4634 * Add supports for object typeof values to be encoded on URL 4635 * Ref: https://stackoverflow.com/questions/1714786/query-string-encoding-of-a-javascript-object 4636 * @param {*} obj 4637 * @param {string} prefix 4638 * @param {string} baseParam used for grouping in case of nested objects 4639 * @returns 4640 */ // eslint-disable-next-line sonarjs/cognitive-complexity 4641 serializeObj(obj, prefix = '', baseParam = '') { 4642 const result = []; 4643 for(const prop in obj)if (obj.hasOwnProperty(prop)) { 4644 const value = obj[prop]; 4645 let k; 4646 if (baseParam) { 4647 const newPrefix = prefix ? `[${prefix}][${prop}]` : `[${prop}]`; 4648 k = `${baseParam}${newPrefix}`; 4649 } else k = prefix ? `${prefix}[${prop}]` : prop; 4650 if (value !== null && Array.isArray(value)) { 4651 if (value.length) result.push(this.serializeArray(k, value)); 4652 } else if (value !== null && typeof value === 'object') result.push(this.serializeObj(value, k)); 4653 else result.push(`${k}=${encodeURIComponent(value)}`); 4654 } 4655 return result.join('&'); 4656 } 4657 /** 4658 * Adds support for array values to be encoded on URL 4659 * @param {string} key 4660 * @param {Array} value 4661 * @param {string} baseParam 4662 */ serializeArray(key, value, baseParam = '') { 4663 const result = []; 4664 value.forEach((val)=>{ 4665 const paramName = baseParam ? `${baseParam}${key}` : key; 4666 result.push(`${paramName}=${encodeURIComponent(val)}`); 4667 }); 4668 return result.join('&'); 4669 } 4670 /** 4671 * Private method to process string search values. 4672 * @param {Array} results comes from regex matchAll. 4673 * @param {Boolean} isAdvancedFilters flag to determine if the search is for advanced filters. 4674 * @returns 4675 */ _parseParamToObject(results, isAdvancedFilters = false) { 4676 if (!results.length) return null; 4677 const obj = {}; 4678 for(let i = 0; i < results.length; i++)/* Results from regex match all come in this form: 4679 [ '&advancedFilters[query][statusIds][in]=uuid-segundo-123', '[query][statusIds][in]', 'uuid-segundo-123' ] 4680 So using every third value will be the actual value and the second value will be the path 4681 */ if ((i + 1) % 3 === 0) { 4682 const value = decodeURIComponent(results[i].replace(/\+/g, ' ')); 4683 const path = results[i - 1]; 4684 Object.assign(obj, this._setObjectPath(path, value, obj, isAdvancedFilters)); 4685 } 4686 return obj; 4687 } 4688 /** * 4689 * Private method to set object value given a path 4690 * @param {String} path 4691 * @param {*} value 4692 * @param {Object} baseObj 4693 * @param {Boolean} isAdvancedFilters flag to determine if the search is for advanced filters. 4694 */ _setObjectPath(path, value, baseObj, isAdvancedFilters = false) { 4695 // eslint-disable-next-line
4696 const keys = path.split(/[\[\]]/).filter(Boolean); 4697 const afArrayKeys = [ 4698 'in', 4699 'nin', 4700 'btw', 4701 'ciin', 4702 'cinin' 4703 ]; 4704 const obj = {}; 4705 let ref = obj; 4706 const valueExists = (0, $fc0a058ae42af628$export$267a1435fe18b8d4)(baseObj, keys.join('.')); 4707 keys.forEach((key, index)=>{ 4708 if (index === keys.length - 1) { 4709 if (valueExists) ref[key] = Array.isArray(valueExists) ? [ 4710 ...valueExists, 4711 value 4712 ] : [ 4713 valueExists, 4714 value 4715 ]; 4716 else ref[key] = isAdvancedFilters && afArrayKeys.includes(key) ? [ 4717 value 4718 ] : value; 4719 } else { 4720 ref[key] = baseObj[key] ? baseObj[key] : {}; 4721 ref = ref[key]; 4722 } 4723 }); 4724 return obj; 4725 } 4726} 4727 4728 4729/* eslint-disable import/prefer-default-export */ 4730class $2b1790aa32f109d1$export$43e435c053a109f6 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4731 /** 4732 * Initialize Expand Slider Controller at build time 4733 * @param { ConfigController } config 4734 * @param {Window} window 4735 * @param {Document} document 4736 */ constructor(config, window, document){ 4737 super(config, window, document); 4738 this._createLightbox = this._createLightbox.bind(this); 4739 this.expandSliders = []; 4740 } 4741 /** 4742 * Initialize Expand Slider Controller at run time 4743 * Attach handlers and events to DOM 4744 * @param {Object} options 4745 */ initialize(options = {}) { 4746 try { 4747 const { document: document } = this; 4748 const expandSliders = document.querySelectorAll('.has-mobile-expand'); 4749 expandSliders.forEach((block)=>{ 4750 this._addExpandButton(block); 4751 }); 4752 this.expandSliders = this.expandSliders.concat(expandSliders); 4753 } catch (e) { 4754 // eslint-disable-next-line no-console 4755 console.log(e); 4756 } 4757 } 4758 /** 4759 * Controllers must implement a "destroy" method to be called 4760 * after page navigation to remove event handlers / cleanup 4761 */ destroy() { 4762 const { document: document } = this; 4763 document.querySelectorAll('.lp-expand-btn, .lp-expand-btn-wrapper').forEach((element)=>element.remove()); 4764 this.expandSliders = []; 4765 } 4766 /** 4767 * Create expand button for sliders 4768 * @param {HTMLElement} slider 4769 */ _addExpandButton(slider) { 4770 const { document: document } = this; 4771 const section = slider.closest('section[id]'); 4772 const appendTargetSelector = slider.dataset.append; 4773 let expandBtn = section.querySelector('.lp-expand-btn'); 4774 if (!expandBtn) { 4775 expandBtn = document.createElement('button'); 4776 expandBtn.className = 'lp-expand-btn'; 4777 expandBtn.setAttribute('aria-label', 'Open a lightbox'); 4778 expandBtn.innerHTML = `<span class="lp-i lp-i--expand-alt" aria-hidden="true"></span>`; 4779 if (appendTargetSelector) { 4780 const appendTarget = section.querySelector(appendTargetSelector); 4781 if (appendTarget) appendTarget.appendChild(expandBtn); 4782 } else { 4783 const wrapper = document.createElement('div'); 4784 wrapper.className = 'lp-expand-btn-wrapper'; 4785 wrapper.style.position = 'relative'; 4786 wrapper.appendChild(expandBtn); 4787 slider.parentNode.insertBefore(wrapper, slider.nextSibling); 4788 } 4789 } 4790 expandBtn.addEventListener('click', ()=>{ 4791 this._createLightbox({ 4792 slider: slider, 4793 section: section 4794 }); 4795 }); 4796 } 4797 /** 4798 * Creates a lightbox modal for displaying slider images in a larger view. 4799 * 4800 * @param {Object} params - The parameters for the function. 4801 * @param {HTMLElement} params.slider - The slider element containing images. 4802 * @param {HTMLElement} params.section - The section element related to the slider. 4803 * @param {number} [params.initialIndex] - Optional index of the image to show initially. 4804 */ _createLightbox({ slider: slider, section: section, initialIndex: initialIndex }) { 4805 const { window: window, document: document } = this; 4806 const modalId = `lightbox-for-${section?.id}`; 4807 const isSlickAvailable = window.$ && window.$.fn.slick; 4808 const isSectionSlickInitialized = isSlickAvailable && slider.slick; 4809 const isSplideAvailable = typeof window.Splide === 'function'; 4810 const isSectionSplideInitialized = slider.classList.contains('is-initialized') && slider.classList.contains('splide'); 4811 if (typeof window.lpUI.requestScrollLock === 'function') window.lpUI.requestScrollLock(modalId); 4812 else document.body.style.overflow = 'hidden'; 4813 function createSlickLightbox() { 4814 const slickOverlayOptions = { 4815 arrows: true, 4816 dots: false, 4817 infinite: true, 4818 draggable: true, 4819 prevArrow: '<button type="button" class="lp-lightbox-arrow--prev lp-lightbox-arrow" aria-label="Previous"></button>', 4820 nextArrow: '<button type="button" class="lp-lightbox-arrow--next lp-lightbox-arrow" aria-label="Next"></button>' 4821 }; 4822 const modal = document.createElement('div'); 4823 modal.id = modalId; 4824 modal.className = 'lp-lightbox'; 4825 modal.setAttribute('tabindex', '-1'); 4826 modal.setAttribute('role', 'dialog'); 4827 modal.setAttribute('aria-modal', 'true'); 4828 modal.innerHTML = ` 4829 <button class="lp-lightbox__close" aria-label="close modal"></button> 4830 <div class="lp-lightbox__content"> 4831 <div class="lp-lightbox__carousel"></div> 4832 </div> 4833 `; 4834 document.body.appendChild(modal); 4835 const overlaySlider = modal.querySelector('.lp-lightbox__carousel'); 4836 modal.classList.add('show'); 4837 let sourceImages; 4838 if (isSectionSlickInitialized) { 4839 sourceImages = slider.slick.$slides.find('img'); 4840 slickOverlayOptions.initialSlide = slider.slick?.currentSlide || 0; 4841 sourceImages.each(function() { 4842 window.$('.lp-lightbox__carousel').append(window.$(this).clone(false).removeAttr('style').wrap('<div class="lp-lightbox__slide"></div>').parent()); 4843 }); 4844 } else { 4845 sourceImages = slider.querySelectorAll('img');
4846 sourceImages.forEach((img)=>{ 4847 const imgClone = img.cloneNode(true); 4848 imgClone.removeAttribute('style'); 4849 const slide = document.createElement('div'); 4850 slide.className = 'lp-lightbox__slide'; 4851 slide.appendChild(imgClone); 4852 overlaySlider.appendChild(slide); 4853 }); 4854 } 4855 window.$(overlaySlider).slick(slickOverlayOptions); 4856 // Focus modal after it's created 4857 setTimeout(()=>{ 4858 modal.focus(); 4859 }, 100); 4860 // Keyboard navigation handler 4861 const handleKeydown = (e)=>{ 4862 if (!modal.classList.contains('show')) return; 4863 const prevArrow = modal.querySelector('.lp-lightbox-arrow--prev'); 4864 const nextArrow = modal.querySelector('.lp-lightbox-arrow--next'); 4865 const closeBtn = modal.querySelector('.lp-lightbox__close'); 4866 if (e.key === 'ArrowLeft' && prevArrow) { 4867 e.preventDefault(); 4868 prevArrow.click(); 4869 } else if (e.key === 'ArrowRight' && nextArrow) { 4870 e.preventDefault(); 4871 nextArrow.click(); 4872 } else if (e.key === 'Escape' && closeBtn) { 4873 e.preventDefault(); 4874 closeBtn.click(); 4875 } 4876 }; 4877 document.addEventListener('keydown', handleKeydown); 4878 modal.querySelector('.lp-lightbox__close').addEventListener('click', ()=>{ 4879 document.removeEventListener('keydown', handleKeydown); 4880 modal.classList.remove('show'); 4881 const currentSlide = overlaySlider?.slick ? window.$(overlaySlider).slick('slickCurrentSlide') : 0; 4882 if (overlaySlider?.slick) window.$(overlaySlider).slick('unslick'); 4883 if (isSectionSlickInitialized) window.$(slider).slick('slickGoTo', currentSlide); 4884 if (typeof window.lpUI.releaseScrollLock === 'function') window.lpUI.releaseScrollLock(modalId); 4885 else document.body.style.overflow = ''; 4886 modal.remove(); 4887 }); 4888 } 4889 function createSplideLightbox() { 4890 const splideOptions = { 4891 rewind: true, 4892 arrows: true, 4893 arrowPath: '', 4894 type: 'loop', 4895 clones: 1, 4896 pagination: false, 4897 perPage: 1, 4898 perMove: 1 4899 }; 4900 const modal = document.createElement('div'); 4901 modal.id = modalId; 4902 modal.className = 'lp-lightbox'; 4903 modal.setAttribute('tabindex', '-1'); 4904 modal.setAttribute('role', 'dialog'); 4905 modal.setAttribute('aria-modal', 'true'); 4906 modal.innerHTML = ` 4907 <button class="lp-lightbox__close" aria-label="close modal"></button> 4908 <div class="lp-lightbox__content"> 4909 <div class="lp-lightbox__carousel splide" role="group" aria-label="Image Gallery"> 4910 <div class="splide__arrows"> 4911 <button class="splide__arrow splide__arrow--prev lp-lightbox-arrow lp-lightbox-arrow--prev"></button> 4912 <button class="splide__arrow splide__arrow--next lp-lightbox-arrow lp-lightbox-arrow--next"></button> 4913 </div> 4914 <div class="splide__track"> 4915 <ul class="splide__list"></ul> 4916 </div> 4917 </div> 4918 </div> 4919 `; 4920 if (typeof window.lpUI.requestScrollLock === 'function') window.lpUI.requestScrollLock(modalId); 4921 else document.body.style.overflow = 'hidden'; 4922 document.body.appendChild(modal); 4923 const overlaySlider = modal.querySelector('.lp-lightbox__carousel'); 4924 const overlaySliderList = overlaySlider.querySelector('.splide__list'); 4925 modal.classList.add('show'); 4926 let sourceImages; 4927 if (isSectionSplideInitialized) sourceImages = slider.querySelectorAll('.splide__slide:not(.splide__slide--clone) img'); 4928 else sourceImages = slider.querySelectorAll('img');
4929 sourceImages.forEach((img)=>{ 4930 const imgClone = img.cloneNode(true); 4931 imgClone.removeAttribute('style'); 4932 const slide = document.createElement('li'); 4933 slide.className = 'lp-lightbox__slide splide__slide'; 4934 slide.appendChild(imgClone); 4935 overlaySliderList.appendChild(slide); 4936 }); 4937 const overlaySplide = new window.Splide(overlaySlider, splideOptions); 4938 overlaySplide.mount(); 4939 // Navigate to initial index if provided 4940 if (typeof initialIndex !== 'undefined' && initialIndex >= 0) overlaySplide.go(initialIndex); 4941 // Focus modal after it's created and mounted 4942 setTimeout(()=>{ 4943 modal.focus(); 4944 }, 100); 4945 // Keyboard navigation handler 4946 const handleKeydown = (e)=>{ 4947 if (!modal.classList.contains('show')) return; 4948 const prevArrow = modal.querySelector('.splide__arrow--prev'); 4949 const nextArrow = modal.querySelector('.splide__arrow--next'); 4950 const closeBtn = modal.querySelector('.lp-lightbox__close'); 4951 if (e.key === 'ArrowLeft' && prevArrow) { 4952 e.preventDefault(); 4953 prevArrow.click(); 4954 } else if (e.key === 'ArrowRight' && nextArrow) { 4955 e.preventDefault(); 4956 nextArrow.click(); 4957 } else if (e.key === 'Escape' && closeBtn) { 4958 e.preventDefault(); 4959 closeBtn.click(); 4960 } 4961 }; 4962 document.addEventListener('keydown', handleKeydown); 4963 modal.querySelector('.lp-lightbox__close').addEventListener('click', ()=>{ 4964 document.removeEventListener('keydown', handleKeydown); 4965 modal.classList.remove('show'); 4966 if (overlaySplide) overlaySplide.destroy(); 4967 if (typeof window.lpUI.releaseScrollLock === 'function') window.lpUI.releaseScrollLock(modalId); 4968 else document.body.style.overflow = ''; 4969 modal.remove(); 4970 }); 4971 } 4972 if (isSlickAvailable && !isSectionSplideInitialized) createSlickLightbox(); 4973 else if (isSplideAvailable) createSplideLightbox(); 4974 else { 4975 window.luxuryPresence.libs.loadCss(window.luxuryPresence.libs.jsLibsMap.splide.cssSrc); 4976 window.luxuryPresence.libs.loadJs(window.luxuryPresence.libs.jsLibsMap.splide.jsSrc, ()=>{ 4977 createSplideLightbox(); 4978 window.luxuryPresence.libs.jsLibsMap.splide.isLoaded = true; 4979 }); 4980 } 4981 } 4982} 4983 4984 4985/* eslint-disable import/prefer-default-export */ 4986class $4f184d9bd3037f15$export$7a6215890afba974 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 4987 // constructor is not needed as it is inherited from BaseController 4988 /** 4989 * Destroy 4990 */ destroy() { 4991 if (!this.window.divolte) this.window.divolteLp = function() { 4992 (this.window.divolteLp.q = this.window.divolteLp.q || []).push(// eslint-disable-next-line prefer-rest-params 4993 arguments); 4994 }; 4995 } 4996 /** 4997 * Initialize divolte controller 4998 * @param {Object} options 4999 */ initialize(options = {}) { 5000 const { divolteServiceUrl: divolteServiceUrl } = this.config; 5001 if (!divolteServiceUrl || this.window.divolte) return; 5002 this.loadScript(); 5003 } 5004 /** 5005 * loads divolte 5006 */ loadScript() { 5007 const { divolteServiceUrl: divolteServiceUrl } = this.config; 5008 const script = this.document.createElement('script'); 5009 script.id = 'divolte-script'; 5010 script.type = 'text/javascript'; 5011 script.defer = true; 5012 script.async = true; 5013 script.src = `${divolteServiceUrl}/divolte.js`; 5014 this.document.head.appendChild(script); 5015 script.addEventListener('load', this.executeQueue.bind(this)); 5016 } 5017 /** 5018 * executeQueue 5019 */ executeQueue() { 5020 (this.window.divolteLp.q || []).forEach(([methodName, params])=>{ 5021 this[methodName](params); 5022 }); 5023 this.window.divolteLp = this.divolteLp.bind(this); 5024 } 5025 /** 5026 * divolteLp 5027 * @param {string} name 5028 * @param {Object} params 5029 */ divolteLp(name, params) { 5030 try { 5031 this[name](params); 5032 } catch (error) { 5033 // eslint-disable-next-line no-console 5034 console.log(error); 5035 } 5036 } 5037 /** 5038 * sendLpEvent 5039 * @param {string} name 5040 * @param {Object} params 5041 */ sendLpEvent(name, params) { 5042 // Events must be defined in divolte schema! To ensure correct usage, event names are validated here. 5043 if (!name.includes('pageView')) { 5044 // Event not defined 5045 // eslint-disable-next-line no-console 5046 console.log(`${name} IS NOT SUPPORTED`); 5047 return; 5048 } 5049 const { companyId: companyId, templateId: templateId, websiteId: websiteId, pageId: pageId, shareId: shareId, pageMeta: pageMeta } = this.config; 5050 const baseParams = { 5051 companyId: companyId, 5052 templateId: templateId, 5053 websiteId: websiteId, 5054 pageId: pageId, 5055 shareId: shareId, 5056 pageMeta: pageMeta 5057 }; 5058 this.window.divolte.signal(name, Object.assign(baseParams, params)); 5059 } 5060 /** 5061 * sendLpPageView 5062 */ sendLpPageView() { 5063 this.sendLpEvent('pageView', {}); 5064 } 5065} 5066 5067 5068/* eslint-disable import/prefer-default-export */ 5069const $102039871668a1df$var$EVENT_BUS_SCRIPT = 'event-bus-js.js'; 5070const $102039871668a1df$var$EVENT_BUS_VERSION = 'v2.8.0'; 5071const $102039871668a1df$var$EVENT_BUS_DEFAULT_SOURCE = 'frontend://website-render-engine'; 5072class $102039871668a1df$export$e22e4b1be0bb6c61 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5073 // eslint-disable-next-line @typescript-eslint/no-useless-constructor 5074 constructor(config, window, document){ 5075 super(config, window, document); 5076 } 5077 /** 5078 * Initialize EventBus controller 5079 * @param {Object} options 5080 */ initialize(options = {}) { 5081 const enabled = options.enabled !== undefined ? options.enabled : this.config.eventBus?.enabled; 5082 if (enabled && this.config.eventBus?.apiKey && this.config.eventBus?.url) this.loadScript(); 5083 } 5084 /** 5085 * loads eventBus script 5086 */ loadScript() { 5087 const { eventBusUrl: eventBusUrl } = this.config; 5088 const script = this.document.createElement('script'); 5089 script.id = 'posthog-script'; 5090 script.type = 'text/javascript'; 5091 script.defer = true; 5092 script.async = true; 5093 script.src = `${eventBusUrl}/${$102039871668a1df$var$EVENT_BUS_VERSION}/${$102039871668a1df$var$EVENT_BUS_SCRIPT}`; 5094 this.document.head.appendChild(script); 5095 script.addEventListener('load', this.executeQueue.bind(this)); 5096 } 5097 /** 5098 * executeQueue - Process queued eventBusLp calls 5099 */ executeQueue() { 5100 // Initialize the event bus provider using the global EventBusJS 5101 if (this.window.LuxuryPresenceEventBus) this.window.eventBusProvider = this.window.LuxuryPresenceEventBus.createEventBusProvider({ 5102 apiKey: this.config.eventBus.apiKey, 5103 host: this.config.eventBus.url, 5104 defaultSource: $102039871668a1df$var$EVENT_BUS_DEFAULT_SOURCE 5105 });
5106 (this.window.eventBusLp.q || []).forEach(([methodName, params])=>{ 5107 this[methodName](params); 5108 }); 5109 // Replace queue function with real function 5110 this.window.eventBusLp = this.eventBusLp.bind(this); 5111 } 5112 /** 5113 * Destroy 5114 */ destroy() { 5115 if (!this.window.eventBusProvider) { 5116 // Reset to queue function if no provider exists 5117 this.window.eventBusLp = function() { 5118 (this.window.eventBusLp.q = this.window.eventBusLp.q || []).push(// eslint-disable-next-line prefer-rest-params 5119 arguments); 5120 }; 5121 return; 5122 } 5123 this.window.eventBusProvider.shutdown(); 5124 } 5125 /** 5126 * sendLpEvent 5127 * @param {string} eventType 5128 * @param {Object} data 5129 */ sendLpEvent(eventType, data) { 5130 // Events must be defined in event-bus schema! To ensure correct usage, event types are validated here. 5131 if (eventType !== 'user.activity.page.viewed') { 5132 // Event type not defined 5133 // eslint-disable-next-line no-console 5134 console.log(`${eventType} IS NOT SUPPORTED`); 5135 return; 5136 } 5137 if (!this.window.eventBusProvider) return; 5138 const { companyId: companyId } = this.config; 5139 const eventData = { 5140 company: companyId, 5141 data: {} 5142 }; 5143 try { 5144 this.window.eventBusProvider.capture(eventType, eventData); 5145 } catch (error) { 5146 // eslint-disable-next-line no-console 5147 console.log('EventBus capture error:', error); 5148 } 5149 } 5150 /** 5151 * eventBusLp 5152 * @param {string} name 5153 * @param {Object} params 5154 */ eventBusLp(name, params) { 5155 try { 5156 this[name](params); 5157 } catch (error) { 5158 // eslint-disable-next-line no-console 5159 console.log(error); 5160 } 5161 } 5162 /** 5163 * sendLpPageView 5164 */ sendLpPageView() { 5165 this.sendLpEvent('user.activity.page.viewed', {}); 5166 } 5167} 5168 5169 5170/* eslint-disable import/prefer-default-export */ 5171 5172const $0d25a3fa2e0f4f92$var$EVENT_NAME = 'oauthSuccess'; 5173class $0d25a3fa2e0f4f92$export$b718a981691eae5c extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5174 /** 5175 * Initialize Button Controller at build time 5176 * @param { ConfigController } config 5177 * @param {Window} window 5178 * @param {Document} document 5179 */ constructor(config, window, document){ 5180 super(config, window, document); 5181 this.successListeners = []; 5182 this._listenToMessages = this._listenToMessages.bind(this); 5183 this._listenToOneTapMessage = this._listenToOneTapMessage.bind(this); 5184 } 5185 /** 5186 * Initialize OAuth Controller at run time 5187 * Swap out special buttons for the iframe 5188 * @param {Object} options 5189 */ initialize(options = {}) { 5190 try { 5191 // handle google one tap 5192 if (options.googleSignOnEnabled && !options.iframe) this._renderGoogleOneTap(); 5193 // handle google sign in buttons 5194 this.document.querySelectorAll('div[data-signin-type="google-sign-in"]').forEach((el)=>this._convertGoogleSignIn(el)); 5195 // handle facebook sign in buttons 5196 this.document.querySelectorAll('div[data-signin-type="facebook-sign-in"]').forEach((el)=>this._convertFacebookSignIn(el)); 5197 } catch (e) { 5198 // eslint-disable-next-line no-console 5199 console.log(e); 5200 } 5201 } 5202 /** 5203 * Controllers must implement a "destroy" method to be called 5204 * after page navigation to remove event handlers / cleanup 5205 */ destroy() { 5206 this.successListeners.forEach((callback)=>{ 5207 this.window.removeEventListener('message', this._listenToMessages); 5208 this.window.removeEventListener('message', this._listenToOneTapMessage); 5209 this.window.removeEventListener($0d25a3fa2e0f4f92$var$EVENT_NAME, this.window[callback]); 5210 }); 5211 } 5212 /** 5213 * Convert google one tap 5214 */ _renderGoogleOneTap() { 5215 if (!this._isGoogleOneTapOnCooldown()) { 5216 const dummyEl = this.document.createElement('div'); 5217 // eslint-disable-next-line no-undef 5218 google.accounts.id.initializeIntermediate({ 5219 src: this._buildUrlAndCopyAttributes(dummyEl, dummyEl, 'googleOneTap') 5220 }); 5221 this.window.localStorage.removeItem('googleOneTapExpiration'); 5222 this.window.addEventListener('message', this._listenToOneTapMessage); 5223 } 5224 } 5225 /** 5226 * Listening for messages from BSS one tap iframes 5227 * @param {MessageEvent} e 5228 */ _listenToOneTapMessage(e) { 5229 const messageTypes = { 5230 SHOW_INTERMEDIATE_IFRAME: 'show_intermediate_iframe', 5231 HIDE_INTERMEDIATE_IFRAME: 'hide_intermediate_iframe' 5232 };
5233 const iframeId = 'onetap_google_intermediate_iframe'; 5234 const { buyerSellerServiceUrl: buyerSellerServiceUrl } = this.config; 5235 const { data: data, origin: origin } = e; 5236 const { type: type, height: height, event: event, source: source, token: token } = data; 5237 if (type === messageTypes.SHOW_INTERMEDIATE_IFRAME || type === messageTypes.HIDE_INTERMEDIATE_IFRAME) { 5238 const bssIframe = this.document.getElementById(iframeId); 5239 if (bssIframe) { 5240 bssIframe.style.height = height; 5241 bssIframe.style.display = type === messageTypes.SHOW_INTERMEDIATE_IFRAME ? 'unset' : 'none'; 5242 } 5243 } 5244 // these events are generated by our code in the bss iframe 5245 if (origin === buyerSellerServiceUrl && source === 'GOOGLE_SIGN_ON') { 5246 if (event === 'success') { 5247 this._setExpirationLsItem(86400000); // 24 hours 5248 this._handleQualifyingQuestionsModal(token); 5249 } else if (event === 'cancel') this._setExpirationLsItem(7200000); // 2 hours 5250 } 5251 } 5252 /** 5253 * Set expiration local storage item for google one tap 5254 * @param {number} ms 5255 */ _setExpirationLsItem(ms) { 5256 const expiration = new Date().getTime() + ms; 5257 this.window.localStorage.setItem('googleOneTapExpiration', expiration); 5258 } 5259 /** 5260 * Check if google one tap is expired 5261 * @return {boolean} 5262 */ _isGoogleOneTapOnCooldown() { 5263 const expirationTime = this.window.localStorage.getItem('googleOneTapExpiration'); 5264 return Number(expirationTime) > new Date().getTime(); 5265 } 5266 /** 5267 * Convert google sign in buttons 5268 * @param {HTMLElement} el 5269 */ _convertGoogleSignIn(el) { 5270 this._replaceWithIframe('googleSignIn', el); 5271 } 5272 /** 5273 * Convert google sign in buttons 5274 * @param {HTMLElement} el 5275 */ _convertFacebookSignIn(el) { 5276 this._replaceWithIframe('facebookSignIn', el); 5277 } 5278 /** 5279 * Convert google sign in buttons 5280 * @param {'googleSignIn' | 'facebookSignIn'} provider 5281 * @param {HTMLElement} el 5282 */ _replaceWithIframe(provider, el) { 5283 this._attachSuccessCallback(el); 5284 const iframe = this.document.createElement('iframe'); 5285 iframe.src = this._buildUrlAndCopyAttributes(el, iframe, provider); 5286 iframe.dataset.isSocialOauthButton = true; 5287 el.replaceWith(iframe); 5288 } 5289 /** 5290 * Listening for messages from BSS iframes 5291 * @param {Event} e 5292 */ _listenToMessages(e) { 5293 const { buyerSellerServiceUrl: buyerSellerServiceUrl } = this.config; 5294 // eslint-disable-next-line sonarjs/no-collapsible-if 5295 if (e.origin === buyerSellerServiceUrl) { 5296 if (e.data.event === 'success') { 5297 const event = new Event($0d25a3fa2e0f4f92$var$EVENT_NAME); 5298 this.window.dispatchEvent(event); 5299 // Registration / social sign-in is a lead event â credit the conversion. 5300 (0, $3ec2bf3965f87ecc$export$e70565180c0e6950)(this.window, this.document); 5301 this.successListeners.forEach((callback)=>{ 5302 if (this.window[callback]) this.window[callback](event); 5303 else // eslint-disable-next-line no-console 5304 console.error(`Callback function ${callback} not found on window object`); 5305 }); 5306 this.document.documentElement.classList.add('is-login-success'); 5307 } 5308 } 5309 } 5310 /** 5311 * Attach callback to iframe 5312 * @param {HTMLElement} el 5313 */ _attachSuccessCallback(el) { 5314 this.window.addEventListener('message', this._listenToMessages); 5315 const callbackFunction = el.dataset.onSuccessFunctionName; 5316 if (callbackFunction) this.successListeners.push(callbackFunction); 5317 } 5318 /** 5319 * Build Iframe URL 5320 * @param {HTMLElement} source 5321 * @param {HTMLElement} target 5322 * @param {string} provider 5323 * @return {string} 5324 */ _buildUrlAndCopyAttributes(source, target, provider) { 5325 const { buyerSellerServiceUrl: buyerSellerServiceUrl, companyId: companyId, websiteId: websiteId, pageId: pageId, pageMeta: pageMeta } = this.config; 5326 const { location: location } = this.window; 5327 const sourceUrl = location.origin + location.pathname; 5328 const destination = new URL(`${buyerSellerServiceUrl}/buttons/${provider}`); 5329 const utm = this.window.localStorage.getItem('utm_vars'); 5330 const referrer = this.window.localStorage.getItem('referrer'); 5331 destination.searchParams.append('companyId', companyId); 5332 destination.searchParams.append('websiteId', websiteId); 5333 destination.searchParams.append('pageId', pageId); 5334 destination.searchParams.append('sourceUrl', sourceUrl); 5335 destination.searchParams.append('pageMeta', JSON.stringify(pageMeta)); 5336 if (utm) destination.searchParams.append('utm', utm); 5337 if (referrer) destination.searchParams.append('referrer', referrer); 5338 // Google and Facebook Attributes used to style the social button 5339 const RESERVED_ATTRIBUTES = [ 5340 'data-size', 5341 'data-width', 5342 'data-text', 5343 'data-button-type', 5344 'data-shape', 5345 'data-layout', 5346 'data-max-rows', 5347 'data-use-continue-as', 5348 'data-type', 5349 'data-theme', 5350 'data-logo_alignment', 5351 'data-locale' 5352 ];
5353 Array.from(source.attributes).forEach((attribute)=>{ 5354 if (RESERVED_ATTRIBUTES.includes(attribute.nodeName)) destination.searchParams.append(attribute.nodeName, attribute.nodeValue); 5355 else target.setAttribute(attribute.nodeName === 'id' ? 'data-id' : attribute.nodeName, attribute.nodeValue); 5356 }); 5357 return destination.toString(); 5358 } 5359 /** 5360 * Decode a JWT token to extract payload information 5361 * @param {string} token - The JWT token to decode 5362 * @return {Object|null} The decoded payload object or null if decoding fails 5363 */ _decodeToken(token) { 5364 try { 5365 const parts = token.split('.'); 5366 const payload = parts[1]; 5367 const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); 5368 const padding = 4 - base64.length % 4; 5369 const padded = padding === 4 ? base64 : base64 + '='.repeat(padding); 5370 return JSON.parse(atob(padded)); 5371 } catch (error) { 5372 return null; 5373 } 5374 } 5375 /** 5376 * Handle qualifying questions modal showing for Google Sign On 5377 * Decodes the JWT token to extract user email and shows the modal if available 5378 * @param {string} token - The JWT token containing user information 5379 */ _handleQualifyingQuestionsModal(token) { 5380 if (token && this.window.luxuryPresence && this.window.luxuryPresence.qualifyingQuestionsModal) { 5381 // Decoding only for lead attribution, fine to skip signature verification 5382 const decoded = this._decodeToken(token); 5383 if (decoded && decoded.email) this.window.luxuryPresence.qualifyingQuestionsModal.showModal(decoded.email, 'Google Sign On'); 5384 } 5385 } 5386} 5387 5388 5389/* eslint-disable import/prefer-default-export */ 5390class $dcf72769f009ff26$export$b900d6fe921fd49c extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5391 /** 5392 * Initialize Qualifying Questions Modal Controller at build time 5393 * @param { ConfigController } config 5394 * @param {Window} window 5395 * @param {Document} document 5396 */ constructor(config, window, document){ 5397 super(config, window, document); 5398 this.showModal = this.showModal.bind(this); 5399 this.hasModalBeenSubmitted = this.hasModalBeenSubmitted.bind(this); 5400 this.modalId = 'qualifying-questions-modal'; 5401 this.cookieKey = 'qualifyingQuestionsModalSubmitted'; 5402 this.qualifyingQuestionsModalEnabled = true; 5403 // Store references for cleanup 5404 this.eventListeners = []; 5405 this.timeouts = []; 5406 } 5407 initialize(options = {}) { 5408 this.qualifyingQuestionsModalEnabled = options.qualifyingQuestionsModalEnabled ?? this.qualifyingQuestionsModalEnabled; 5409 if (!this.qualifyingQuestionsModalEnabled) return; 5410 this.attachModalCloseListeners(); 5411 } 5412 destroy() { 5413 // Remove all stored event listeners 5414 this.eventListeners.forEach(({ element: element, event: event, handler: handler })=>{ 5415 if (element && element.removeEventListener) element.removeEventListener(event, handler); 5416 }); 5417 this.timeouts.forEach((timeoutId)=>{ 5418 clearTimeout(timeoutId); 5419 }); 5420 this.restoreBodyScroll(); 5421 this.eventListeners = []; 5422 this.timeouts = []; 5423 } 5424 /** 5425 * Check if modal has been submitted using cookie-based deduplication 5426 * @return {boolean} 5427 */ hasModalBeenSubmitted() { 5428 const cookieValue = this.getCookie(this.cookieKey); 5429 return cookieValue === 'true'; 5430 } 5431 /** 5432 * Set cookie to prevent modal from showing again 5433 */ setModalSubmittedCookie() { 5434 this.setCookie(this.cookieKey, 'true', 365); // 1 year 5435 } 5436 showModal(email, triggerSource) { 5437 if (!this.qualifyingQuestionsModalEnabled || this.hasModalBeenSubmitted()) return; 5438 this.updateEmailValue(email); 5439 this.updateTriggerSourceValue(triggerSource); 5440 this.showModalElement(); 5441 } 5442 /** 5443 * Get modal element with error handling 5444 * @return {Element|null} 5445 */ getModalElement() { 5446 const modal = this.document.getElementById(this.modalId); 5447 if (!modal) console.error('Modal element not found:', this.modalId); 5448 return modal; 5449 } 5450 updateEmailValue(email) { 5451 const modal = this.getModalElement(); 5452 if (!modal) return; 5453 const emailInput = modal.querySelector('input[name="email"]'); 5454 if (emailInput && typeof email === 'string') emailInput.value = email; 5455 } 5456 updateTriggerSourceValue(triggerSource) { 5457 const modal = this.getModalElement(); 5458 if (!modal) return;
5459 const triggerSourceInput = modal.querySelector(`#${this.modalId}-triggerSource`); 5460 if (triggerSourceInput && typeof triggerSource === 'string') triggerSourceInput.value = triggerSource; 5461 } 5462 showModalElement() { 5463 const modal = this.getModalElement(); 5464 if (!modal) return; 5465 modal.style.display = 'flex'; 5466 this.blockBodyScroll(); 5467 } 5468 /** 5469 * Helper method to get cookie value 5470 * @param {string} name 5471 * @return {string|null} 5472 */ getCookie(name) { 5473 const value = `; ${this.document.cookie}`; 5474 const parts = value.split(`; ${name}=`); 5475 if (parts.length === 2) return decodeURIComponent(parts.pop().split(';').shift()); 5476 return null; 5477 } 5478 /** 5479 * Helper method to set cookie 5480 * @param {string} name 5481 * @param {string} value 5482 * @param {number} days 5483 */ setCookie(name, value, days) { 5484 let expires = ''; 5485 if (days) { 5486 const date = new Date(); 5487 date.setTime(date.getTime() + days * 86400000); 5488 expires = `; expires=${date.toUTCString()}`; 5489 } 5490 let attrs = '; path=/; SameSite=Lax'; 5491 if (this.window && this.window.location && this.window.location.protocol === 'https:') attrs += '; Secure'; 5492 this.document.cookie = `${name}=${encodeURIComponent(value || '')}${expires}${attrs}`; 5493 } 5494 blockBodyScroll() { 5495 this._prevOverflow = this.document.body.style.overflow; 5496 this.document.body.style.overflow = 'hidden'; 5497 } 5498 restoreBodyScroll() { 5499 this.document.body.style.overflow = this._prevOverflow || ''; 5500 this._prevOverflow = null; 5501 } 5502 attachModalCloseListeners() { 5503 const modal = this.getModalElement(); 5504 if (!modal) return; 5505 // Listen for form submission success (when modal closes automatically) 5506 const form = this.document.getElementById(`${this.modalId}-form`); 5507 if (form) { 5508 const formSubmittedHandler = ()=>{ 5509 // Set cookie to prevent showing again and hide modal 5510 this.setModalSubmittedCookie(); 5511 const timeoutId = setTimeout(()=>{ 5512 modal.style.display = 'none'; 5513 this.restoreBodyScroll(); 5514 }, 100); 5515 this.timeouts.push(timeoutId); 5516 }; 5517 form.addEventListener('formsubmitted', formSubmittedHandler); 5518 this.eventListeners.push({ 5519 element: form, 5520 event: 'formsubmitted', 5521 handler: formSubmittedHandler 5522 }); 5523 } 5524 // Listen for close button clicks 5525 const closeButton = modal.querySelector('[data-dismiss="modal"]'); 5526 if (closeButton) { 5527 const closeButtonHandler = ()=>{ 5528 modal.style.display = 'none'; 5529 this.restoreBodyScroll(); 5530 }; 5531 closeButton.addEventListener('click', closeButtonHandler); 5532 this.eventListeners.push({ 5533 element: closeButton, 5534 event: 'click', 5535 handler: closeButtonHandler 5536 }); 5537 } 5538 // Listen for backdrop clicks 5539 const backdropClickHandler = (e)=>{ 5540 // Check if click is on the modal backdrop (not on the modal content) 5541 if (e.target === modal || e.target.id === this.modalId) { 5542 modal.style.display = 'none'; 5543 this.restoreBodyScroll(); 5544 } 5545 }; 5546 modal.addEventListener('click', backdropClickHandler); 5547 this.eventListeners.push({ 5548 element: modal, 5549 event: 'click', 5550 handler: backdropClickHandler 5551 }); 5552 } 5553} 5554 5555 5556/* eslint-disable import/prefer-default-export */ 5557class $50eefeef8d1f2372$export$8a2bf06fbae4dde extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5558 constructor(configController, window, document){ 5559 super(configController, window, document); 5560 this.isValidating = false; 5561 this.lastValidationResult = null; 5562 this.lastValidationTime = null; 5563 this.validationCacheDuration = 300000; // 5 minutes 5564 } 5565 /** 5566 * Destroy 5567 */ destroy() { 5568 this.isValidating = false;
5569 this.lastValidationResult = null; 5570 this.lastValidationTime = null; 5571 } 5572 /** 5573 * Validate user session using GraphQL validSession query 5574 * @param {Object} options - Options for session validation 5575 * @param {boolean} options.useCache - Whether to use cached validation result 5576 * @returns {Promise<boolean>} - true if session is valid, false otherwise 5577 */ async validateSession(options = {}) { 5578 const { useCache: useCache = true } = options; 5579 if (useCache && this.lastValidationResult !== null && this.lastValidationTime) { 5580 const timeSinceLastValidation = Date.now() - this.lastValidationTime; 5581 if (timeSinceLastValidation < this.validationCacheDuration) return this.lastValidationResult; 5582 } 5583 if (this.isValidating) return this.lastValidationResult !== null ? this.lastValidationResult : false; 5584 this.isValidating = true; 5585 const query = ` 5586 query { 5587 validSession { 5588 success 5589 } 5590 } 5591 `; 5592 try { 5593 // query from same domain as the website 5594 const response = await this.window.fetch(`/graphql`, { 5595 method: 'POST', 5596 headers: { 5597 'Content-Type': 'application/json' 5598 }, 5599 credentials: 'include', 5600 body: JSON.stringify({ 5601 query: query 5602 }) 5603 }); 5604 if (!response.ok) { 5605 // eslint-disable-next-line no-console 5606 console.error('SessionController: HTTP error during session validation', response.status); 5607 this.lastValidationResult = false;
5608 this.lastValidationTime = Date.now(); 5609 return false; 5610 } 5611 const data = await response.json(); 5612 if (data.errors) { 5613 this.lastValidationResult = false; 5614 this.lastValidationTime = Date.now(); 5615 return false; 5616 } 5617 const isValid = data.data?.validSession?.success === true; 5618 this.lastValidationResult = isValid; 5619 this.lastValidationTime = Date.now(); 5620 return isValid; 5621 } catch (error) { 5622 // eslint-disable-next-line no-console 5623 console.error('SessionController: Error validating session', error); 5624 this.lastValidationResult = false; 5625 this.lastValidationTime = Date.now(); 5626 return false; 5627 } finally{ 5628 this.isValidating = false; 5629 } 5630 } 5631 /** 5632 * Clear validation cache 5633 */ clearCache() { 5634 this.lastValidationResult = null; 5635 this.lastValidationTime = null; 5636 } 5637} 5638 5639 5640/* eslint-disable import/prefer-default-export */ 5641function $396d9f0ca7465335$export$48d5542b9e3ba39(document) { 5642 const match = document.cookie.match(/cookieyes-consent=([^;]+)/); 5643 if (!match) return false; 5644 try { 5645 const cookieValue = decodeURIComponent(match[1]); 5646 if (cookieValue.includes(',')) { 5647 const segments = cookieValue.split(',').map((c)=>c.trim()); 5648 if (segments.some((s)=>s.includes(':'))) return segments.some((s)=>s === 'analytics:yes'); 5649 return segments.includes('analytics'); 5650 } 5651 return cookieValue.includes('analytics'); 5652 } catch (error) { 5653 return false; 5654 } 5655} 5656/** 5657 * Consent providers. Each entry isolates the only things that differ between 5658 * integrations: the change-event name, how to read the *initial* analytics 5659 * consent, and how to interpret a change event's detail. Everything else 5660 * (loading/removing trackers, lifecycle) is shared by the controller. 5661 */ const $396d9f0ca7465335$var$PROVIDERS = { 5662 // Third-party CookieYes banner. 5663 cookieyes: { 5664 eventName: 'cookieyes_consent_update', 5665 hasAnalyticsConsent: (document)=>$396d9f0ca7465335$export$48d5542b9e3ba39(document), 5666 isAnalyticsAccepted: (detail)=>Array.isArray(detail?.accepted) && detail.accepted.includes('analytics') 5667 }, 5668 // LP in-house consent widget (lp-consent-widget). It exposes resolved state on 5669 // window.__lpConsent and emits `lp_consent_update` with detail.choices. 5670 'lp-consent': { 5671 eventName: 'lp_consent_update', 5672 hasAnalyticsConsent: (_document, window)=>!!window.__lpConsent?.state?.c?.analytics, 5673 isAnalyticsAccepted: (detail)=>!!detail?.choices?.analytics 5674 } 5675}; 5676class $396d9f0ca7465335$export$bb018224876724b9 extends (0, $7c391e2f7931d60d$export$3b5bd9381a52554c) { 5677 constructor(config, window, document){ 5678 super(config, window, document); 5679 this.consentListener = null; 5680 this.eventName = null; 5681 } 5682 /** 5683 * Destroy and cleanup event listeners 5684 */ destroy() { 5685 if (this.consentListener && this.eventName) { 5686 this.document.removeEventListener(this.eventName, this.consentListener); 5687 this.consentListener = null; 5688 } 5689 } 5690 /** 5691 * Load PostHog and Divolte scripts (idempotent â guarded by element id). 5692 */ loadScript() { 5693 if (!this.document.getElementById('posthog-script')) this.window.luxuryPresence.eventBus.initialize({ 5694 enabled: this.config.eventBus?.enabled 5695 }); 5696 if (!this.document.getElementById('divolte-script')) this.window.luxuryPresence.divolte.initialize({}); 5697 } 5698 /** 5699 * Remove PostHog and Divolte scripts (used on consent withdrawal). 5700 */ removeScripts() { 5701 this.window.luxuryPresence?.eventBus?.destroy?.(); 5702 this.window.luxuryPresence?.divolte?.destroy?.(); 5703 this.document.getElementById('posthog-script')?.remove(); 5704 this.document.getElementById('divolte-script')?.remove(); 5705 } 5706 /** 5707 * Initialize the consent controller for a given provider. 5708 * @param {{ provider: 'cookieyes' | 'lp-consent' }} options 5709 */ async initialize(options = {}) { 5710 const provider = $396d9f0ca7465335$var$PROVIDERS[options.provider]; 5711 if (!provider) return; 5712 this.eventName = provider.eventName; 5713 const isAuthenticated = await this.window.luxuryPresence.session.validateSession(); 5714 if (provider.hasAnalyticsConsent(this.document, this.window) || isAuthenticated) this.loadScript(); 5715 if (!this.consentListener) { 5716 this.consentListener = (eventData)=>{ 5717 if (provider.isAnalyticsAccepted(eventData.detail)) this.loadScript(); 5718 else this.removeScripts(); 5719 }; 5720 this.document.addEventListener(provider.eventName, this.consentListener); 5721 } 5722 } 5723} 5724 5725 5726/* global window */ /* eslint-disable import/prefer-default-export */ /** 5727 * Checks if the current site should redirect homeowner valuation submissions to HST. 5728 * Returns true and performs the redirect if all three gate conditions are met. 5729 * Returns false if the redirect did not fire (caller should continue with existing flow). 5730 * 5731 * @param {string} address - The human-readable address string from the form 5732 * @returns {boolean} 5733 */ function $0ae243a777665fd3$var$checkHstRedirect(address) { 5734 const { config: config } = window.luxuryPresence; 5735 if (config.hstValuationRedirectEnabled && config.mlsEnabled && config.myhomeDashboardEnabled) { 5736 window.location.href = `/home-search/home-valuation?address=${encodeURIComponent(address)}`; 5737 return true; 5738 } 5739 return false;
5740} 5741const $0ae243a777665fd3$export$4225ff17959c7313 = { 5742 checkHstRedirect: $0ae243a777665fd3$var$checkHstRedirect, 5743 destroy () {} 5744}; 5745 5746 5747const $fa0280e505379701$export$1fd2b467a3644d37 = new (0, $dc851cf3939b6ef5$export$ff0712c26d5458f7)({}); 5748const $fa0280e505379701$export$e12694e911a05f40 = new (0, $0ead03fdb95fa9ce$export$d507f25e63650365)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5749const $fa0280e505379701$export$3de614e59a842917 = new (0, $d48f9f944810f613$export$ca3218b7861b9cb0)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5750const $fa0280e505379701$export$fb9b298fdbf31455 = new (0, $68a2d4aa500c7a05$export$61ec8f2b6291ec31)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5751const $fa0280e505379701$export$178cc3119b453c2a = new (0, $e10bd00497be8876$export$3c5380623cf51249)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5752const $fa0280e505379701$export$9376af1a01686f2 = new (0, $ea6901fdf47e0ec1$export$3c19195edca3fa5c)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5753const $fa0280e505379701$export$94d36f806562393e = new (0, $0a0ad49f13c0b8fd$export$ba9afeb75f20f616)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5754const $fa0280e505379701$export$89e66d1faf94651e = new (0, $2b1790aa32f109d1$export$43e435c053a109f6)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5755const $fa0280e505379701$export$7743b07270cd6d6c = new (0, $4f184d9bd3037f15$export$7a6215890afba974)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5756const $fa0280e505379701$export$40a7039551318c37 = new (0, $102039871668a1df$export$e22e4b1be0bb6c61)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5757const $fa0280e505379701$export$8a0bbf7e7652fcfc = new (0, $0d25a3fa2e0f4f92$export$b718a981691eae5c)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5758const $fa0280e505379701$export$b313b3f6544c97a3 = new (0, $dcf72769f009ff26$export$b900d6fe921fd49c)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5759const $fa0280e505379701$export$b526bc4ac03268ef = new (0, $50eefeef8d1f2372$export$8a2bf06fbae4dde)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5760const $fa0280e505379701$export$c0affdb0a7a2ef6 = new (0, $396d9f0ca7465335$export$bb018224876724b9)($fa0280e505379701$export$1fd2b467a3644d37, window, document); 5761 5762 5763/** 5764 * Attach controller to window for usage 5765 * @param {string} key 5766 * @param {any} controller 5767 */ function $89a8a84b77163415$var$attachController(key, controller) { 5768 if (window.luxuryPresence[key]) try { 5769 // New version of sdk, destroy previous 5770 window.luxuryPresence[key].destroy(); 5771 } catch (e) { 5772 // eslint-disable-next-line no-console 5773 console.log(e); 5774 } 5775 window.luxuryPresence[key] = controller; 5776} 5777$89a8a84b77163415$var$attachController('config', (0, $fa0280e505379701$export$1fd2b467a3644d37)); 5778$89a8a84b77163415$var$attachController('modals', (0, $fa0280e505379701$export$e12694e911a05f40)); 5779$89a8a84b77163415$var$attachController('buttons', (0, $fa0280e505379701$export$3de614e59a842917)); 5780$89a8a84b77163415$var$attachController('validation', (0, $fa0280e505379701$export$fb9b298fdbf31455)); 5781$89a8a84b77163415$var$attachController('contactForms', (0, $fa0280e505379701$export$178cc3119b453c2a)); 5782$89a8a84b77163415$var$attachController('mapApiLoader', (0, $fa0280e505379701$export$9376af1a01686f2)); 5783$89a8a84b77163415$var$attachController('pagination', (0, $fa0280e505379701$export$94d36f806562393e)); 5784$89a8a84b77163415$var$attachController('expandSliders', (0, $fa0280e505379701$export$89e66d1faf94651e)); 5785$89a8a84b77163415$var$attachController('divolte', (0, $fa0280e505379701$export$7743b07270cd6d6c)); 5786$89a8a84b77163415$var$attachController('eventBus', (0, $fa0280e505379701$export$40a7039551318c37)); 5787$89a8a84b77163415$var$attachController('oauth', (0, $fa0280e505379701$export$8a0bbf7e7652fcfc)); 5788$89a8a84b77163415$var$attachController('qualifyingQuestionsModal', (0, $fa0280e505379701$export$b313b3f6544c97a3)); 5789$89a8a84b77163415$var$attachController('session', (0, $fa0280e505379701$export$b526bc4ac03268ef)); 5790$89a8a84b77163415$var$attachController('consent', (0, $fa0280e505379701$export$c0affdb0a7a2ef6)); 5791$89a8a84b77163415$var$attachController('redirects', (0, $0ae243a777665fd3$export$4225ff17959c7313)); 5792// methods for existing elements (can be removed later after elements updated) 5793window.luxuryPresence.formatSliderPriceRange = (0, $fa0280e505379701$export$94d36f806562393e).formatSliderPriceRange.bind((0, $fa0280e505379701$export$94d36f806562393e)); 5794window.luxuryPresence.formatSliderAreaRange = (0, $fa0280e505379701$export$94d36f806562393e).formatSliderAreaRange.bind((0, $fa0280e505379701$export$94d36f806562393e)); 5795// method below doesn't use 'this', but just to be safe in case of further method updates 5796window.luxuryPresence.getSearchFormValues = (0, $fa0280e505379701$export$94d36f806562393e).getSearchFormValues.bind((0, $fa0280e505379701$export$94d36f806562393e)); 5797window.luxuryPresence.handlebarsHelpersFactory = function(handlebarsInstance) { 5798 return (0, (/*@__PURE__*/$parcel$interopDefault($53de3c160faf5a81$exports
5798)))(handlebarsInstance, { 5799 isEqual (a, b) { 5800 // eslint-disable-next-line 5801 return a == b; 5802 } 5803 }); 5804}; 5805 5806 5807 5808})(); 5809//# sourceMappingURL=lp-sdk.js.map 5810 5811 </script>
5811 5812 5813
5813<script> 5814 var lpGlobalInitialized = false; 5815 5816 function lpGlobalInit () { 5817 if (lpGlobalInitialized) { 5818 return; 5819 } 5820 lpGlobalInitialized = true; 5821 // Initialize wow animation plugin only according to turbolinks 5822 var wow; 5823 if (window.WOW) { 5824 wow = new WOW({ 5825 animateClass: 'animated', 5826 offset: wowOffset, 5827 live: false, 5828 callback: function(box) {} 5829 }); 5830 } 5831 // Initialize handlebar helpers only once 5832 function handlebarsInit() { 5833 if (!window.Handlebars) { 5834 return; 5835 } 5836 HandlebarsIntl.registerWith(Handlebars); 5837 Handlebars.registerHelper(window.luxuryPresence.handlebarsHelpersFactory(Handlebars)); 5838 } 5839 5840 handlebarsInit(); 5841 5842 var modalContentUrl = "/modals.html"; 5843 const modalsContainer = document.getElementById('modals'); 5844 if (modalsContainer) { 5845 fetch(modalContentUrl).then(function(res) { 5846 if (!res.ok) { 5847 throw new Error(`HTTP error! Status: ${res.status}`); 5848 } 5849 return res.text(); 5850 }).then(function(resultText) { 5851 modalsContainer.innerHTML = resultText; 5852 5853 // now we need to execute all script elements 5854 Array.from(modalsContainer.getElementsByTagName('script')).forEach(function (s) { 5855 const script = document.createElement("script"); 5856 script.text = s.innerHTML; 5857 Array.from(s.attributes).forEach(function (a) { 5858 script.setAttribute(a.name, a.value); 5859 }) 5860 s.parentNode.replaceChild(script, s); 5861 }); 5862 5863 try { 5864 window.luxuryPresence.modals.initialize(window.luxuryPresence._modalIncludes); 5865 window.luxuryPresence.contactForms.initialize({ selector: '#modals' }); 5866 window.luxuryPresence.validation.initialize({ selector: '#modals' }); 5867 window.luxuryPresence.oauth.initialize({ 5868 googleSignOnEnabled: false, 5869 iframe: false }); 5870 } catch (e) { 5871 console.log(e); 5872 } 5873 document.querySelectorAll('.modals > .modal .close').forEach(closeModalBtn => { 5874 closeModalBtn.addEventListener('click', function() { 5875 window.lpUI.hideClosestModal(closeModalBtn); 5876 }); 5877 }); 5878 }).catch((e) => console.log(e)); 5879 } 5880 /* Initialize Luxury Presence SDK */ 5881 try { 5882 window.luxuryPresence.config.setCompany("2c02232f-2988-4a2c-a284-887b44b8c4fc", {}); 5883 window.luxuryPresence.config.setWebsite("47a83415-cce5-4158-a944-4eb949b3b156", { 5884 myhomeDashboardEnabled: false, 5885 mlsEnabled: true, 5886 hstValuationRedirectEnabled: true 5887 }); 5888 const url = new URL(window.location.href); 5889 const shareId = url.searchParams.get('shareId'); 5890 window.luxuryPresence.config.setPage("e6c68775-b811-4906-b042-f13e4ef1b35d", { 5891 sourceResource: "", 5892 pageElementId: "", 5893 pageQueryVariables: {}, 5894 shareId: "", 5895 }); 5896 window.luxuryPresence.config.setWebsiteApiGatewayUrl("https://wgw.luxurypresence.com", {}); 5897 window.luxuryPresence.config.setBuyerSellerServiceUrl("https://bss.luxurypresence.com", {}); 5898 window.luxuryPresence.config.setDivolteServiceUrl("https://t.luxurypresence.com", {}); 5899 window.luxuryPresence.config.setEventBusUrl("https://cdn.luxurypresence.com/scripts/event-bus-js", {}); 5900 window.luxuryPresence.config.setEventBus({ 5901 enabled: true, 5902 apiKey: "phc_PMYT31xlmXBaTCwpIRx1v6RrpLVZIuP47N3mOT6KLnr", 5903 url: "https://p.luxurypresence.com", 5904 }); 5905 window.luxuryPresence.config.setTemplateId("e3960201-1a19-4660-a991-8658653d81e9", {}); 5906 window.luxuryPresence.config.setLeadIngesterUrl("https://leads.luxurypresence.com", {}); 5907 window.luxuryPresence.buttons.initialize({}); 5908 window.luxuryPresence.eventBus.initialize({}); 5909 window.luxuryPresence.contactForms.initialize({}); 5910 window.luxuryPresence.validation.initialize({}); 5911 window.luxuryPresence.expandSliders.initialize({}); 5912 window.luxuryPresence.oauth.initialize({ googleSignOnEnabled: false, iframe: false }); 5913 window.luxuryPresence.qualifyingQuestionsModal.initialize({ qualifyingQuestionsModalEnabled: true }); 5914 window.luxuryPresence.divolte.initialize({}); 5915 } catch (e) { 5916 console.log(e); 5917 } 5918 5919 /* Inject Section Specific Javascript */ 5920 try { 5921(function init(sectionDomId) { 5922 let address = $('#' + sectionDomId + " .company_address").text().trim(); 5923 $('#' + sectionDomId + " .company_address").text(address); 5924 function setHttp(link) { 5925 if (link.search(/^http[s]?\:\/\/|\//) == -1) { 5926 link = 'http://' + link; 5927 } 5928 return link; 5929 } 5930 $(`#${sectionDomId} .contact-form__form`).on('reset', () => { 5931 const redirectLink = $('#' + sectionDomId + ' .redirect-link'); 5932 if (redirectLink.length) { 5933 const rawHref = redirectLink.attr('href'); 5934 redirectLink.attr('href', setHttp(rawHref)); 5935 redirectLink[0].click(); 5936 } 5937 }); 5938})('modal-global-contact-us'); 5939} catch(e) { 5940console.log(e); 5941} 5942 5943 try { 5944(function init(sectionDomId) { 5945 const mediaQuery = window.matchMedia('(hover: hover) and (pointer: fine)'); 5946 var $subNavTrigger = $('.header .sub-nav-container a'); 5947 var $subNavContainer = $('.header .sub-nav-container'); 5948 if (mediaQuery.matches) { 5949 $subNavTrigger.off('click'); 5950 $subNavContainer.mouseout(function () { 5951 var self = $(this); 5952 self.removeClass('active').find('.sub-nav').removeClass('visible'); 5953 }).mouseover(function () { 5954 $(this).siblings('.sub-nav-container').removeClass('active').find('.sub-nav').removeClass('visible'); 5955 $(this).addClass('active').find('.sub-nav').addClass('visible'); 5956 }).mouseup(function () { 5957 var self = $(this); 5958 self.toggleClass('active'); 5959 }); 5960 } 5961 function isSectionLight(sect) { 5962 if (!sect || !sect.style) { 5963 return true; 5964 } 5965 if (sect.style.backgroundImage) { 5966 return false;
5967 } 5968 if ($(sect).children('.video-wrapper').length) { 5969 return false; 5970 } 5971 return sect.classList.contains('is-background-color-light'); 5972 } 5973 const $header = $(`#${sectionDomId} > .header`); 5974 if ($header.hasClass('theme-auto')) { 5975 if (isSectionLight($('body > section')[0])) { 5976 $header.removeClass('theme-light'); 5977 } else { 5978 $header.addClass('theme-light'); 5979 } 5980 } 5981})('global-navbar'); 5982} catch(e) { 5983console.log(e); 5984} 5985 try { 5986(function init(sectionDomId, options) { 5987 var elementSelector = options.elementSelector || 'nav'; 5988 var element = document.querySelector(elementSelector); 5989 var sections = document.querySelectorAll('body > section'); 5990 var subNavContainers = element.querySelectorAll('.sub-nav'); 5991 var initialBackgroundColor = options.initialBackgroundColor || element.style.backgroundColor; 5992 var backgroundColorScroll = options.backgroundColorScroll || '#191919'; 5993 var fontColor = options.fontColor || '#fff'; 5994 var fontColorScroll = options.fontColorScroll || '#000'; 5995 var didScroll = false; 5996 var lastScrollTop = 0; 5997 var delta = 5; 5998 5999 // eslint-disable-next-line sonarjs/no-collapsible-if 6000 if (wow) { 6001 // Wowjs might not trigger for position fixed elements 6002 if (window.scrollY > 0) { 6003 element.querySelectorAll('.wow').forEach(el => wow.show(el)); 6004 } 6005 } 6006 function adjustNavPinState() { 6007 // Remove 'visible' class from all subNavContainers 6008 for (let i = 0; i < subNavContainers.length; i++) { 6009 subNavContainers[i].classList.remove('visible'); 6010 } 6011 if (window.scrollY > 0) { 6012 element.style.backgroundColor = backgroundColorScroll; 6013 element.style.color = fontColorScroll; 6014 element.classList.add('scroll'); 6015 } else { 6016 element.style.backgroundColor = initialBackgroundColor; 6017 element.style.color = fontColor; 6018 element.classList.remove('scroll'); 6019 } 6020 } 6021 adjustNavPinState(); 6022 function onScroll() { 6023 didScroll = true; 6024 } 6025 window.addEventListener('scroll', onScroll); 6026 6027 // eslint-disable-next-line 6028 var debounceInterval = setInterval(() => { 6029 if (didScroll) { 6030 // eslint-disable-next-line 6031 hasScrolled(); 6032 didScroll = false; 6033 } 6034 }, 250); 6035 function hasScrolled() { 6036 adjustNavPinState(); 6037 // eslint-disable-next-line 6038 var navbarHeight = element.offsetHeight; 6039 // eslint-disable-next-line 6040 var st = window.scrollY; 6041 6042 // Make sure they scroll more than delta 6043 if (Math.abs(lastScrollTop - st) <= delta) { 6044 return; 6045 } 6046 if (st > lastScrollTop && st > navbarHeight) { 6047 // Scroll Down 6048 element.style.transform = `translateY(-${navbarHeight}px) scaleY(0)`; 6049 document.documentElement.style.setProperty('--global-header-height', '0px'); 6050 6051 // Some sections "follow" the navbar with class sticky (i.e. Producer search) 6052 sections.forEach(section => { 6053 if (section.classList.contains('sticky')) { 6054 section.style.top = '0px'; 6055 } 6056 }); 6057 } else { 6058 // Scroll Up 6059 // eslint-disable-next-line 6060 if (st + window.innerHeight < document.documentElement.scrollHeight) { 6061 element.style.transform = `translateY(0) scaleY(1)`; 6062 document.documentElement.style.setProperty('--global-header-height', `${navbarHeight}px`); 6063 sections.forEach(section => { 6064 if (section.classList.contains('sticky')) { 6065 section.style.top = `${navbarHeight}px`; 6066 } 6067 }); 6068 } 6069 } 6070 lastScrollTop = st; 6071 } 6072})('NAVBAR', JSON.parse("{\"fontColor\":\"#ffffff\",\"elementSelector\":\"nav\",\"fontColorScroll\":\"#000000\",\"backgroundColorScroll\":\"#ffffff\",\"initialBackgroundColor\":\"rgba(0,0,0,0)\"}")); 6073} catch(e) { 6074console.log(e); 6075} 6076 try { 6077(function init(sectionDomId) { 6078 sectionDomId = "global-sidemenu"; 6079 var $sidemenu = $('#' + sectionDomId); 6080 const subNavItems = $('body > div.sidemenu .sub-nav-container .sub-nav__item'); 6081 const hideSubNavItems = () => { 6082 subNavItems.removeClass('fadeInUp'); 6083 }; 6084 const showSubNavItems = subnav => { 6085 if ($(subnav).find('.arrow').hasClass('active')) { 6086 $(".sidemenu").find('.arrow').removeClass('active'); 6087 } else { 6088 $(".sidemenu").find('.arrow').removeClass('active'); 6089 $(subnav).find('.arrow').addClass('active'); 6090 } 6091 let i = 0; 6092 subnav.find('.sub-nav__item').each((idx, item) => { 6093 i++; 6094 var timeout; 6095 clearTimeout(timeout); 6096 timeout = setTimeout(() => { 6097 $(item).addClass('fadeInUp'); 6098 }, 100 + i * 50); 6099 }); 6100 }; 6101 $(document).on("click", function (e) {
6102 if ($(e.target).closest(".navigation").length < 1) { 6103 hideSubNavItems(); 6104 } 6105 }); 6106 $('body > div.sidemenu .sub-nav-container > a').click(function (e) { 6107 const subnavContainer = $(this).closest('.sub-nav-container'); 6108 hideSubNavItems(); 6109 showSubNavItems(subnavContainer); 6110 }); 6111 const hideSideMenu = () => { 6112 $('body > div.sidemenu').removeClass('visible'); 6113 }; 6114 $('body > div.sidemenu .navigation .navigation__item:not(.sub-nav-container) .navigation__link').on('click', function (e) { 6115 onHide(); 6116 hideSideMenu(); 6117 }); 6118 $('body > div.sidemenu .sub-nav-container .sub-nav__link').on('click', function (e) { 6119 onHide(); 6120 setTimeout(() => { 6121 hideSideMenu(); 6122 }, 1601); 6123 }); 6124 var shown = false; 6125 function onShow() { 6126 if (shown) { 6127 return; 6128 } 6129 shown = true; 6130 window.lpUI.requestScrollLock(sectionDomId); 6131 $('#' + sectionDomId + ' .navigation__link').removeClass('fade-in'); 6132 $('#' + sectionDomId + ' .lp-socials__link').removeClass('fade-in'); 6133 let i = 0; 6134 $('#' + sectionDomId + ' .navigation__link').each(function () { 6135 i++; 6136 setTimeout(() => { 6137 $(this).addClass('fade-in'); 6138 }, 900 + i * 50); 6139 }); 6140 $('#' + sectionDomId + ' .lp-socials__link').each(function () { 6141 i++; 6142 setTimeout(() => { 6143 $(this).addClass('fade-in'); 6144 }, 900 + i * 50); 6145 }); 6146 } 6147 function onHide() { 6148 hideSubNavItems(); 6149 if (!shown) { 6150 return; 6151 } 6152 $('#' + sectionDomId + ' .lp-socials__link').removeClass('fade-in'); 6153 let i = 0; 6154 $($('#' + sectionDomId + ' .navigation__link').get().reverse()).each(function () { 6155 i++; 6156 setTimeout(() => { 6157 $(this).removeClass('fade-in'); 6158 }, i * 50); 6159 }); 6160 shown = false; 6161 setTimeout(() => { 6162 window.lpUI.releaseScrollLock(sectionDomId); 6163 }, 1600); 6164 } 6165 $sidemenu.on('lpui-show', onShow); 6166 $sidemenu.on('lpui-hide', onHide); 6167})('global-sidemenu'); 6168} catch(e) { 6169console.log(e); 6170} 6171 6172 try { 6173(function init(sectionDomId) { 6174 function setHttp(link) { 6175 if (link.search(/^http[s]?\:\/\/|\//) == -1) { 6176 link = 'http://' + link; 6177 } 6178 return link; 6179 } 6180 $(`#${sectionDomId} form`).on("reset", function () { 6181 const redirectLink = $('#' + sectionDomId + ' .redirect-link'); 6182 if (redirectLink.length) { 6183 const rawHref = redirectLink.attr('href'); 6184 redirectLink.attr('href', setHttp(rawHref)); 6185 redirectLink[0].click(); 6186 } 6187 }); 6188})('global-footer'); 6189} catch(e) { 6190console.log(e); 6191} 6192 6193 try { 6194(function init(sectionDomId) { 6195 var _section = $('#' + sectionDomId); 6196 var _footer = document.getElementById('global-footer'); 6197 const emailInput = _section.find('.js-email-input'); 6198 const emailRegex = /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~\-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+/; 6199 emailInput.on('input', function () { 6200 if (emailRegex.test(this.value)) { 6201 this.setCustomValidity(''); 6202 } else { 6203 this.setCustomValidity('Please enter a valid email address.'); 6204 } 6205 this.reportValidity(); 6206 }); 6207 _section.find('input[name="name"]').on("keyup", function () { 6208 if (this.value.replaceAll(/\d/g, "").trim().split(" ").length < 2 || this.value.replaceAll(/\d/g, "").trim() === "") { 6209 this.setCustomValidity("Please input your first name and last name."); 6210 } else { 6211 this.setCustomValidity(""); 6212 } 6213 }); 6214 _section.find('form').on("reset", function () { 6215 $(this).addClass("show-success"); 6216 }); 6217 $(window).scroll(function () { 6218 if ($(this).scrollTop() >= 150) _section.find('.btn-holder').addClass('visible');else _section.find('.btn-holder').removeClass('visible'); 6219 }); 6220 _section.find('.close-btn, .connect-btn').click(function () { 6221 _section.find('.pop-up').toggle(); 6222 _section.find('.btn-holder').toggle(); 6223 }); 6224 if (!_footer) return; 6225 const observer = new IntersectionObserver(entries => { 6226 entries.forEach(entry => { 6227 if (entry.isIntersecting) _section.hide();else _section.show(); 6228 }); 6229 }, { 6230 threshold: 0.1 6231 }); 6232 observer.observe(_footer); 6233 function selectToDropdown(selectItem) {
6234 let customDropdown = ` 6235 <div class="custom-select" role="presentation"> 6236 <button class="custom-select-toggle is-placeholder" type="button" aria-haspopup="true" aria-expanded="false"></button> 6237 <div class="custom-select-menu" style="display: none;max-height: none;"> 6238 <div class="custom-select-content"> 6239 </div> 6240 </div> 6241 </div>`; 6242 $(customDropdown).insertAfter(selectItem); 6243 let $dropdown = $($(selectItem).next()); 6244 let $dropdownToggle = $dropdown.find(".custom-select-toggle"); 6245 let $dropdownMenu = $dropdown.find(".custom-select-menu"); 6246 let $dropdownContent = $dropdown.find(".custom-select-content"); 6247 $dropdownToggle.addClass($(selectItem).attr("class")).removeClass("dropdown-select"); 6248 $dropdownToggle.attr("id", $(selectItem).attr("id") + "custom"); 6249 $dropdownMenu.attr("aria-labelledby", $dropdownToggle.attr("id")); 6250 appendOptionButtons(selectItem, $dropdownContent); 6251 $(selectItem).removeClass().addClass("sr-only"); 6252 var toggleText = $(selectItem).find(":selected").first().text() || $(selectItem).children().first().text(); 6253 $dropdownToggle.html(toggleText); 6254 $dropdown.on("click", ".custom-select-item", function (e) { 6255 if ($(this).hasClass("is-disabled")) return; 6256 let elementNumber = $(this).index(); 6257 let $originalSelect = $(this).closest(".custom-select").siblings("select"); 6258 let $linkedOption = $originalSelect.find("option").eq(elementNumber); 6259 if (!$linkedOption.prop("selected")) { 6260 $linkedOption.prop("selected", true); 6261 $originalSelect.change(); 6262 $(this).addClass("is-selected").siblings(".custom-select-item").removeClass("is-selected"); 6263 $dropdownToggle.html($(this).html()); 6264 closeSelectMenu($(this).closest(".custom-select-menu")); 6265 } 6266 $dropdownToggle.removeClass("is-placeholder"); 6267 }); 6268 function convertOptionToButton(option) { 6269 var optionText = $(option).text(); 6270 var optionValue = $(option).attr("value") || $(option).text(); 6271 var optionIsDisabled = $(option).prop("disabled"); 6272 var outputButton = `<button value="${optionValue}" class="custom-select-item" type="button" ${optionIsDisabled ? "disabled" : ""}>${optionText}</button>`; 6273 return outputButton; 6274 } 6275 function appendOptionButtons(selectItem, appendTarget) { 6276 $(selectItem).find("option").each(function () { 6277 let currentButton = convertOptionToButton(this); 6278 $(appendTarget).append(currentButton); 6279 }); 6280 } 6281 function showSelectMenu(selectMenu) { 6282 var customSelect = $(selectMenu).closest(".custom-select")[0]; 6283 $(customSelect).addClass("open"); 6284 $(selectMenu).slideDown(); 6285 } 6286 function closeSelectMenu(selectMenu) { 6287 var customSelect = $(selectMenu).closest(".custom-select")[0]; 6288 $(customSelect).removeClass("open"); 6289 $(selectMenu).slideUp(); 6290 } 6291 $dropdown.on("click", ".custom-select-toggle", function (e) { 6292 var $customSelect = $(this).closest(".custom-select"); 6293 var selectMenu = $customSelect.find(".custom-select-menu")[0]; 6294 if ($customSelect.hasClass("open")) { 6295 closeSelectMenu(selectMenu); 6296 } else { 6297 showSelectMenu(selectMenu); 6298 } 6299 }); 6300 $(document).on("click", function (e) { 6301 if ($(e.target).closest(".custom-select").length < 1 && $(".custom-select").hasClass("open")) { 6302 $(".custom-select.open").each(function () { 6303 var selectMenu = $(this).find(".custom-select-menu")[0]; 6304 closeSelectMenu(selectMenu); 6305 }); 6306 } 6307 }); 6308 } 6309 $(".pop-up select[name='interest']").each(function () { 6310 selectToDropdown(this); 6311 }); 6312})('global-mobile-contact'); 6313} catch(e) { 6314console.log(e); 6315} 6316 6317 try { 6318(function init(sectionDomId) { 6319 const $section = $('#' + sectionDomId); 6320 const $videoWrap = $section.find('.full-screen-video-component > .video-wrapper'); 6321 const $playButton = $section.find('.js-play-btn'); 6322 if ($videoWrap.length) { 6323 const $video = $videoWrap.find('video'); 6324 $video.each(function () { 6325 var v = this; 6326 if (v.readyState > 0) { 6327 $videoWrap.addClass('playing'); 6328 var promise = v.play(); 6329 if (promise !== undefined) { 6330 promise.catch(error => { 6331 $playButton.addClass('visible'); 6332 }).then(() => {}); 6333 } 6334 } else { 6335 v.addEventListener('canplay', function () { 6336 $videoWrap.addClass('playing'); 6337 }, { 6338 once: true 6339 }); 6340 } 6341 }); 6342 } 6343 $playButton.on('click', function () { 6344 const $video = $videoWrap.find('video'); 6345 this.classList.remove('visible'); 6346 if ($video[0].currentTime === 0) { 6347 $video[0].load(); 6348 $video[0].play(); 6349 } 6350 }); 6351 window.luxuryPresence.mapApiLoader.load().then(() => {
6352 const $searchWrapper = $section.find('.search-wrapper'); 6353 if (!$searchWrapper.length) { 6354 return; 6355 } 6356 const searchMls = $searchWrapper[0].dataset.searchMls === 'true'; 6357 const searchProperties = $searchWrapper[0].dataset.searchProperties === 'true'; 6358 const searchPlaces = searchMls; 6359 const showNeighborhoods = searchMls; 6360 const searchNearEnabled = searchMls; 6361 const $searchInput = $section.find('.search-input'); 6362 const $searchResults = $section.find('.search-results'); 6363 const $resultsContainer = $section.find('.results-container'); 6364 const $loader = $section.find('.search-in-progress'); 6365 const $predefined = $section.find('.predefined-links'); 6366 const $searchBtn = $section.find('.search-button'); 6367 const $leaseFalseButton = $section.find('.jsLeaseFalse'); 6368 const $leaseTrueButton = $section.find('.jsLeaseTrue'); 6369 const $nextSection = $section.next(); 6370 const fixZIndex = $nextSection.length && parseInt($nextSection.css('z-index')) || 1; 6371 const searchDelay = 300; 6372 const maxPlaces = 3; 6373 const maxMls = 5; 6374 const leaseFilter = $leaseFalseButton.length > 0 && $leaseTrueButton.length > 0; 6375 const service = new google.maps.places.AutocompleteService(); 6376 const geocoder = new google.maps.Geocoder(); 6377 var keyword; 6378 var leasePropertyState = false; 6379 var leasePropertySearched; 6380 var searchActive = false; 6381 var isLoading = false; 6382 function showSearchResults() { 6383 searchActive = true; 6384 updateSearchDisplay(); 6385 } 6386 function hideSearchResults() { 6387 searchActive = false; 6388 updateSearchDisplay(); 6389 } 6390 function showLoading() { 6391 isLoading = true; 6392 updateSearchDisplay(); 6393 } 6394 function hideLoading() { 6395 isLoading = false; 6396 updateSearchDisplay(); 6397 } 6398 function updateSearchDisplay() { 6399 if (leaseFilter) { 6400 if (leasePropertyState) { 6401 $leaseTrueButton.addClass('active'); 6402 $leaseFalseButton.removeClass('active'); 6403 } else { 6404 $leaseFalseButton.addClass('active'); 6405 $leaseTrueButton.removeClass('active'); 6406 } 6407 } 6408 if (!searchActive) { 6409 $searchResults.removeClass('show'); 6410 $searchResults.css('height', '0'); 6411 return; 6412 } 6413 var hasResults = !!$resultsContainer.html(); 6414 var hasPredefined = $predefined.find('a:not(.not-processed)').length > 0; 6415 if (hasResults) { 6416 $predefined.addClass('not-visible'); 6417 $loader.addClass('not-visible'); 6418 $resultsContainer.removeClass('not-visible'); 6419 } else if (isLoading) { 6420 $resultsContainer.addClass('not-visible'); 6421 $predefined.addClass('not-visible'); 6422 $loader.removeClass('not-visible'); 6423 } else if (hasPredefined) { 6424 $resultsContainer.addClass('not-visible'); 6425 $loader.addClass('not-visible'); 6426 $predefined.removeClass('not-visible'); 6427 } else { 6428 $resultsContainer.addClass('not-visible'); 6429 $loader.addClass('not-visible'); 6430 $predefined.addClass('not-visible'); 6431 } 6432 var height = 0; 6433 height += $predefined[0].getBoundingClientRect().height; 6434 height += $resultsContainer[0].getBoundingClientRect().height; 6435 height += $loader[0].getBoundingClientRect().height; 6436 if (height > 0) { 6437 $searchResults.addClass('show'); 6438 } else { 6439 $searchResults.removeClass('show'); 6440 } 6441 $searchResults.css('height', height + 'px'); 6442 } 6443 function clearSelection() { 6444 $searchResults.find('.selected-link').removeClass('selected-link'); 6445 } 6446 function moveSelection(up) { 6447 var $cont = $resultsContainer.html() ? $resultsContainer : $predefined; 6448 var $links = $cont.find('.results-link'); 6449 if (!$links.length) { 6450 return; 6451 } 6452 var idx = -1; 6453 $links.each(function (i) { 6454 if (idx === -1 && $(this).is('.selected-link')) { 6455 idx = i; 6456 } 6457 }); 6458 var newIdx; 6459 if (up) { 6460 newIdx = idx - 1; 6461 if (newIdx < 0) { 6462 newIdx = $links.length - 1; 6463 } 6464 } else { 6465 newIdx = idx + 1; 6466 if (newIdx > $links.length - 1) { 6467 newIdx = 0; 6468 } 6469 } 6470 if (newIdx !== idx) { 6471 $searchResults.find('.selected-link').removeClass('selected-link'); 6472 $($links.get(newIdx)).addClass('selected-link'); 6473 } 6474 } 6475 var newSearchTimer; 6476 function inputChanged(ev) { 6477 if (ev.which === 38) { 6478 ev.preventDefault(); 6479 moveSelection(true); 6480 return false;
6481 } 6482 if (ev.which === 40) { 6483 ev.preventDefault(); 6484 moveSelection(); 6485 return false; 6486 } 6487 if (ev.which === 13) { 6488 $lnk = $searchResults.find('.selected-link'); 6489 if ($lnk.length) { 6490 $lnk[0].click(); 6491 hideSearchResults(); 6492 } else { 6493 $searchBtn.click(); 6494 } 6495 } 6496 window.clearTimeout(newSearchTimer); 6497 newSearchTimer = window.setTimeout(startSearch, searchDelay); 6498 } 6499 function startSearch() { 6500 const term = $searchInput.val(); 6501 if (term === keyword && leasePropertySearched === leasePropertyState) { 6502 return; 6503 } 6504 leasePropertySearched = leasePropertyState; 6505 keyword = term; 6506 if (term) { 6507 doSearch(term, leasePropertySearched); 6508 } else { 6509 setResults(null); 6510 } 6511 } 6512 function mlsSearch(term, leaseProperty) { 6513 var query = { 6514 keyword_search: term, 6515 limit: maxMls 6516 }; 6517 if (leaseFilter && leaseProperty) { 6518 query.lease_property = true; 6519 } 6520 return superagent.get(window.location.protocol + '//' + window.location.host + '/home-search/listings.json').query(query).then(function (res) { 6521 if (!Array.isArray(res.body)) { 6522 return []; 6523 } 6524 return res.body.map(function (item) { 6525 return { 6526 id: item.id, 6527 display_address: [item.address_1, item.city, [item.state, item.postal_code].filter(function (i) { 6528 return i; 6529 }).join(' ')].filter(function (i) { 6530 return i; 6531 }).join(', ') 6532 }; 6533 }); 6534 }, function (e) { 6535 console.log(e); 6536 return []; 6537 }); 6538 } 6539 function resolveGeoBounds(placeId, description, terms) { 6540 return new Promise(function (resolve, reject) { 6541 geocoder.geocode({ 6542 'placeId': placeId 6543 }, function (results, status) { 6544 if (status != 'OK') { 6545 return resolve(null); 6546 } 6547 var result = results[0]; 6548 var bounds = new google.maps.LatLngBounds();
6549 bounds.extend(result.geometry.viewport.getSouthWest()); 6550 bounds.extend(result.geometry.viewport.getNorthEast()); 6551 result.geometry.bounds = bounds.toJSON(); 6552 var placeUrl = window.luxuryPresence.makeMlsLink(result); 6553 return resolve({ 6554 placeUrl: placeUrl, 6555 description: description, 6556 cityName: terms[0].value, 6557 stateName: terms[1].value 6558 }); 6559 }); 6560 }); 6561 } 6562 function resolveAutocomplete(term, deeper) { 6563 var request = { 6564 input: term, 6565 componentRestrictions: { 6566 country: 'us' 6567 }, 6568 types: deeper ? ['geocode'] : ['(regions)'], 6569 limit: 1 6570 }; 6571 return new Promise(function (resolve, reject) { 6572 service.getPlacePredictions(request, function (results, status) { 6573 if (status !== google.maps.places.PlacesServiceStatus.OK) { 6574 resolve(null); 6575 return; 6576 } 6577 if (!Array.isArray(results)) { 6578 resolve(null); 6579 return; 6580 } 6581 if (deeper) { 6582 var allowedTypes = { 6583 'neighborhood': true, 6584 'administrative_area_level_3': true, 6585 'administrative_area_level_4': true, 6586 'administrative_area_level_5': true, 6587 'sublocality_level_1': true, 6588 'sublocality_level_2': true, 6589 'sublocality_level_3': true 6590 }; 6591 results = results.filter(function (res) { 6592 return res.types.find(t => allowedTypes[t]); 6593 }); 6594 } 6595 if (!results.length) { 6596 resolve(null); 6597 return; 6598 } 6599 resolve(results); 6600 }); 6601 }); 6602 } 6603 var placesResult = {}; 6604 function placesSearch(term, ignoreInput) { 6605 if (placesResult.term !== term) { 6606 placesResult = { 6607 term: term, 6608 result: resolveAutocomplete(term).then(function (results) { 6609 if (!ignoreInput && term !== keyword) { 6610 return null; 6611 } 6612 if (ignoreInput && !results) { 6613 return resolveAutocomplete(term, true); 6614 } 6615 return results; 6616 }).then(function (results) { 6617 if (!ignoreInput && term !== keyword) { 6618 return []; 6619 } 6620 if (!results) { 6621 return []; 6622 } 6623 var geoResultPromises = results.map(function (result) { 6624 return resolveGeoBounds(result.place_id, result.description, result.terms); 6625 }); 6626 return Promise.all(geoResultPromises).then(function (geoLinks) { 6627 return geoLinks.filter(function (i) { 6628 return i; 6629 }).slice(0, maxPlaces); 6630 }); 6631 }) 6632 }; 6633 } 6634 return placesResult.result; 6635 } 6636 function propertiesSearch(term, leaseProperty) { 6637 var payload = { 6638 companyId: window.luxuryPresence.defaults.companyId, 6639 search: term, 6640 offset: 0, 6641 limit: 6, 6642 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"] 6643 }; 6644 if (leaseFilter) { 6645 payload.leaseProperty = !!leaseProperty; 6646 } 6647 return superagent.post(window.luxuryPresence.defaults.apiGatewayUrl + '/graphql').send({ 6648 query: window.luxuryPresence.defaults.gql.properties, 6649 variables: payload 6650 }).then(function (res) { 6651 return (res.body.data || {}).properties || []; 6652 }, function (err) { 6653 return []; 6654 }); 6655 } 6656 function doSearch(term, leaseProperty) { 6657 showLoading(); 6658 Promise.all([searchMls ? mlsSearch(term, leaseProperty) : [], searchPlaces ? placesSearch(term) : [], searchProperties ? propertiesSearch(term, leaseProperty) : []]).then(function (res) { 6659 if (term !== keyword) { 6660 return; 6661 } 6662 setResults(res[0], res[1], res[2], leaseProperty); 6663 }); 6664 } 6665 function makeResultsBlockHtml(title, links) { 6666 if (!links || !links.length) { 6667 return ''; 6668 } 6669 var html = '<div class="results-block">'; 6670 html += '<div class="results-block-title">'; 6671 if (searchNearEnabled) { 6672 html += '<div class="search-near-container"><a class="search-near lp-link lp-link--light">Search Near Me</a></div>'; 6673 } 6674 html += '<div class="results-title-label">' + title + '</div></div>'; 6675 html += links.join('') + '</div>'; 6676 return html; 6677 } 6678 function setResults(mlsResults, placesResults, propertiesResults, leaseProperty) { 6679 clearSelection(); 6680 hideLoading(); 6681 if (!mlsResults) { 6682 $resultsContainer.html(''); 6683 updateSearchDisplay(); 6684 return; 6685 } 6686 if (!mlsResults.length && !placesResults.length && !propertiesResults.length) { 6687 $resultsContainer.html('<div class="nothing-found">No results found</div>'); 6688 updateSearchDisplay(); 6689 return; 6690 } 6691 var propertiesHtml = makeResultsBlockHtml('Featured Properties', propertiesResults.map(function (result) { 6692 return `<a href="/properties/${result.slug}" class="results-link">${result.privateAddress ? result.name : result.fullAddress}</a>`; 6693 })); 6694 var placesHtml = makeResultsBlockHtml('Locations', placesResults.map(function (result) { 6695 return `<a class="results-link" href="${result.placeUrl}${leaseProperty ? '&lease_property=true' : ''}&cityName=${result.cityName}&stateName=${result.stateName}">${result.description}</a>`; 6696 })); 6697 var mlsHtml = makeResultsBlockHtml('Address', mlsResults.map(function (result) { 6698 return `<a href="/home-search/listings/${result.id}" class="results-link">${result.display_address}</a>`; 6699 }));
6700 $resultsContainer.html(propertiesHtml + placesHtml + mlsHtml); 6701 updateSearchDisplay(); 6702 } 6703 function searchBtnClicked() { 6704 var resultsBlockHref = $("#" + sectionDomId + " .results-block .results-link").attr("href"); 6705 const term = $searchInput.val(); 6706 if (!term) { 6707 return; 6708 } 6709 if (searchMls) { 6710 window.location.href = resultsBlockHref; 6711 return; 6712 } 6713 if (searchProperties) { 6714 window.location.href = "/properties/sale"; 6715 } 6716 } 6717 var neighborhoods = null; 6718 function fetchNeighborhoods() { 6719 var url = window.luxuryPresence.defaults.apiGatewayUrl; 6720 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'neighborhoods (companyId: $companyId, offset: $offset, limit: $limit) {id, name, mlsLink, googlePlaceData}}'; 6721 var payload = {}; 6722 payload.offset = 0; 6723 payload.limit = 100; 6724 payload.companyId = window.luxuryPresence.defaults.companyId; 6725 return superagent.post(url + '/graphql').withCredentials().send({ 6726 query: gqlQuery, 6727 variables: payload 6728 }).then(function (res) { 6729 neighborhoods = res.body.data && res.body.data.neighborhoods; 6730 if (neighborhoods && !neighborhoods.length) { 6731 neighborhoods = null; 6732 } 6733 }); 6734 } 6735 function setPredefinedNeighborhoods() { 6736 return fetchNeighborhoods().then(function () { 6737 if (!neighborhoods) { 6738 return; 6739 } 6740 var leaseProperty = leaseFilter && leasePropertyState; 6741 var placesHtml = makeResultsBlockHtml('Locations', neighborhoods.map(function (item) { 6742 return `<a class="results-link" href="${item.mlsLink}${leaseProperty ? '&lease_property=true' : ''}">${item.googlePlaceData && item.googlePlaceData.formatted_address || item.name}</a>`; 6743 })); 6744 $predefined.html(placesHtml); 6745 updateSearchDisplay(); 6746 }); 6747 } 6748 if (showNeighborhoods) { 6749 setPredefinedNeighborhoods(); 6750 } 6751 $searchInput.on('focus', showSearchResults); 6752 $searchInput.on('keyup focus', inputChanged); 6753 $searchResults.on('mouseenter', '.results-link', function (ev) { 6754 clearSelection(); 6755 }); 6756 $searchBtn.on('click', searchBtnClicked); 6757 $leaseFalseButton.on('click', function () { 6758 if (leasePropertyState) { 6759 leasePropertyState = false; 6760 if (showNeighborhoods) { 6761 setPredefinedNeighborhoods(); 6762 } 6763 updateSearchDisplay(); 6764 setResults(null); 6765 startSearch(); 6766 } 6767 }); 6768 $leaseTrueButton.on('click', function () { 6769 if (!leasePropertyState) { 6770 leasePropertyState = true; 6771 if (showNeighborhoods) { 6772 setPredefinedNeighborhoods(); 6773 } 6774 updateSearchDisplay(); 6775 setResults(null); 6776 startSearch(); 6777 } 6778 }); 6779 function createPolygonUrl(center, radius, n) { 6780 var step = Math.PI * 2 / n; 6781 var angle = 0 - step / 2; 6782 var points = []; 6783 var kx = 1.0 / Math.cos(center[1] * Math.PI / 180); 6784 for (var i = 0; i < n; i++) { 6785 points.push('[' + (center[0] + radius * Math.sin(angle) * kx).toFixed(5) + ',' + (center[1] - radius * Math.cos(angle)).toFixed(5) + ']'); 6786 angle += step; 6787 } 6788 points.push(points[0]); 6789 var str = '[[' + points.join(',') + ']]'; 6790 return '&polygon=' + encodeURIComponent(str); 6791 } 6792 function makeNearMeUrl(center, leaseProperty) { 6793 var url = '/home-search/listings?search_type=envelope'; 6794 if (leaseProperty) { 6795 url += '&lease_property=true'; 6796 } 6797 var distance = 0.11; 6798 var lat = center[1]; 6799 var lng = center[0]; 6800 url += '&south=' + (lat - distance); 6801 url += '&north=' + (lat + distance); 6802 url += '&west=' + (lng - distance); 6803 url += '&east=' + (lng + distance); 6804 url += createPolygonUrl(center, distance, 54); 6805 return url; 6806 } 6807 var $locMsg = null; 6808 $section.on('click', '.search-near', function () { 6809 showLoading(); 6810 navigator.geolocation.getCurrentPosition(function (position) { 6811 hideLoading(); 6812 hideSearchResults(); 6813 if (!position || !position.coords || !position.coords.latitude || !position.coords.longitude) { 6814 showLocationMessage('Unknown Location', '<p>Getting position of your device failed.</p><p>
6814You can type your address manually in the search field</p>'); 6815 return; 6816 } 6817 var url = makeNearMeUrl([position.coords.longitude, position.coords.latitude], leaseFilter && leasePropertyState); 6818 var link = document.createElement('a'); 6819 link.href = url; 6820 link.class = 'lp-visually-hidden'; 6821 $section[0].appendChild(link); 6822 link.click(); 6823 setTimeout(function () { 6824 $section[0].removeChild(link); 6825 }, 100); 6826 }, function (geoError) { 6827 hideLoading(); 6828 hideSearchResults(); 6829 var title = 'Unknown Location'; 6830 var msg; 6831 if (geoError.code === 1) { 6832 msg = '<p>To be able to search near you we need to know your current location.</p><p>If you don\'t want to give access to your device\'s location, you can type your address in the search field</p>'; 6833 } else { 6834 msg = '<p>Getting position of your device failed.</p><p>You can type your address manually in the search field</p>'; 6835 } 6836 showLocationMessage(title, msg); 6837 }); 6838 }); 6839 var locHideTimer = null; 6840 function showLocationMessage(title, message) { 6841 clearTimeout(locHideTimer); 6842 if (!$locMsg) { 6843 var html = `<div class="loc-msg-backdrop"> 6844 <div class="loc-msg-wrap"> 6845 <div class="loc-msg-modal"> 6846 <div class="loc-msg-close"></div> 6847 <h4 class="loc-msg-title lp-h4"></h4> 6848 <div class="loc-msg-container"></div> 6849 </div> 6850 </div> 6851 </div> 6852 `; 6853 $locMsg = $(html); 6854 $locMsg.on('click', hideLocationMessage); 6855 $section.append($locMsg); 6856 } 6857 $locMsg.find('.loc-msg-container').html(message); 6858 $locMsg.find('.loc-msg-title').html(title); 6859 setTimeout(function () { 6860 if ($locMsg) { 6861 $section.addClass('visible-loc-msg'); 6862 } 6863 }); 6864 } 6865 function hideLocationMessage() { 6866 $section.removeClass('visible-loc-msg'); 6867 locHideTimer = setTimeout(function () { 6868 $locMsg.remove(); 6869 $locMsg = null; 6870 }, 400); 6871 } 6872 function outsideClickHandler(e) { 6873 if ($searchWrapper.find($(e.target)).length > 0) { 6874 return; 6875 } 6876 hideSearchResults(); 6877 } 6878 $(document).on('click', outsideClickHandler); 6879 if ('geolocation' in navigator) { 6880 $searchWrapper.removeClass('search-near-hidden'); 6881 } 6882 updateSearchDisplay(); 6883 }); 6884})('section-28f45f08-c7d4-49ae-b152-9a02d549e538', 'undefined'); 6885} catch(e) { 6886console.log(e); 6887} 6888 try { 6889(function init(sectionDomId, options) { 6890 let section = null; 6891 switch (sectionDomId) { 6892 case 'NAVBAR': 6893 section = document.querySelector('body > nav, #global-navbar'); 6894 break; 6895 case 'FOOTER': 6896 section = document.querySelector('body > footer, #global-footer'); 6897 break; 6898 case 'SIDEMENU': 6899 section = document.querySelector('body > div.sidemenu, #global-sidemenu'); 6900 break; 6901 case 'CONTACT': 6902 section = document.querySelector('#modal-global-contact-us'); 6903 break; 6904 case 'MOBILE_CONTACT': 6905 section = document.querySelector('body > div.mobile-contact-wrapper, #global-mobile-contact'); 6906 break; 6907 case 'SUBSCRIBE': 6908 // Subscribe Not Implemented 6909 section = null; 6910 break; 6911 case 'ACCOUNT': 6912 // Account Not Implemented 6913 section = null; 6914 break; 6915 default: 6916 section = document.querySelector('#' + sectionDomId); 6917 } 6918 if (!section) { 6919 return; 6920 } 6921 6922 // Where content / items are rendered 6923 const dataContainer = section.querySelector('.data-container'); 6924 if (!dataContainer) { 6925 // no place to render items = no need to query server or do anything else 6926 return; 6927 } 6928 let variables = options.variables || {}; 6929 6930 // Some variables in options may not have been injected - remove 6931 Object.keys(variables).forEach(function (variableName) { 6932 if (typeof variables[variableName] === 'string' && variables[variableName].match(/^{{.*}}$/)) { 6933 delete variables[variableName]; 6934 } 6935 }); 6936 6937 // Merge page query variables 6938 if (options.usePageResource) { 6939 variables = { 6940 ...variables, 6941 ...window.luxuryPresence.config.pageMeta.pageQueryVariables[options.resource] 6942 }; 6943 } 6944 6945 // Pagination Executes on a Numbered Pagination Container 6946 // Or via a load more button 6947 let mode = 'BUTTONLESS_PAGINATION'; 6948 const container = section.querySelector('.pagination-container');
6949 const loadMoreButton = section.querySelector(options?.loadMoreSelector); 6950 if (container) { 6951 mode = 'NUMBERED_PAGINATION'; 6952 } 6953 if (loadMoreButton) { 6954 mode = 'LOAD_MORE_BUTTON'; 6955 } 6956 let searchContainer = section.querySelector('.search-container'); 6957 const hidePaginationOnFirstPage = options.hidePaginationOnFirstPage || false; 6958 const eventPrefix = '__pagination-'; 6959 const searchFormParams = options.searchFormParams || (options.resource === 'properties' ? [{ 6960 param: 'neighborhoodId', 6961 urlKey: 'neighborhood' 6962 }, { 6963 param: 'search', 6964 urlKey: 'search' 6965 }, { 6966 param: 'salesPriceGTE', 6967 urlKey: 'pricemin', 6968 formKey: 'priceMin', 6969 type: 'int' 6970 }, { 6971 param: 'salesPriceLTE', 6972 urlKey: 'pricemax', 6973 formKey: 'priceMax', 6974 type: 'int' 6975 }, { 6976 param: 'livingSpaceSizeGTE', 6977 urlKey: 'areamin', 6978 formKey: 'areaMin', 6979 type: 'int' 6980 }, { 6981 param: 'livingSpaceSizeLTE', 6982 urlKey: 'areamax', 6983 formKey: 'areaMax', 6984 type: 'int' 6985 }] : []); 6986 6987 // Total entries - initialized in first render 6988 let totalNumber = 0; 6989 const DefaultSearchParams = { 6990 page: 1 6991 }; 6992 searchFormParams.forEach(function (formParam) { 6993 DefaultSearchParams[formParam.formKey || formParam.param] = null; 6994 }); 6995 let searchParams = { 6996 ...DefaultSearchParams 6997 }; 6998 const offset = options.variables?.offset || 0; 6999 let scrollTarget = options.scrollTargetSelector ? section.querySelector(options.scrollTargetSelector) || section : section; 7000 let scrollOffset = (parseInt(options.scrollOffset, 10) || 0) ?? 100; 7001 7002 // entries of per page 7003 // when query variables for a given section would't be filled options.pageSize can store 7004 // template string, ex: "{{variables.limit}}", below code handles that case and set 9 as a default value 7005 let pageSize = parseInt(options.pageSize, 10) || 9; 7006 7007 // Adding mobile pageRange support 7008 var NARROW_BREAKPOINT = 600; 7009 var mobileBreakpoint = options.mobileBreakpoint > 0 ? options.mobileBreakpoint : NARROW_BREAKPOINT; 7010 var paginationMql = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`); 7011 7012 // Page range (pages on both sides of the current page) 7013 var mobilePageRange = options.mobilePageRange > 0 ? options.mobilePageRange : 0; 7014 var desktopPageRange = options.pageRange >= 0 ? options.pageRange : 2; 7015 var pageRange = paginationMql.matches ? mobilePageRange : desktopPageRange; 7016 7017 // Whether to display the 'Previous' button 7018 var showPrevious = true; 7019 7020 // Whether to display the 'Next' button 7021 var showNext = true; 7022 7023 // Whether to display the page buttons 7024 var showPageNumbers = true; 7025 7026 // 'Previous' text 7027 var prevText = options.prevText || '«'; 7028 7029 // 'Next' text 7030 var nextText = options.nextText || '»'; 7031 7032 // Ellipsis text 7033 var ellipsisText = '…'; 7034 var classPrefix = 'paginationjs'; 7035 7036 // Default active class 7037 var activeClassName = 'active'; 7038 7039 // Default disable class 7040 var disableClassName = 'disabled'; 7041 7042 // Pagination element's position in the container 7043 var position = 'bottom'; 7044 7045 // Whether to trigger pagination at initialization 7046 var triggerPagingOnInit = !options.skipInitialLoad; 7047 var showFirstOnEllipsisShow = true; 7048 var showLastOnEllipsisShow = true; 7049 7050 // Instantiated in init() 7051 var el = null; 7052 7053 // Controls scrollTop logic 7054 var firstRender = true; 7055 var renderBySearch = false; 7056 var disabled = false; 7057 7058 // Pagination script class selector 7059 var paginationScriptSelector = options.paginationPartialName ? `.${options.paginationPartialName}` : '.pagination-script'; 7060 7061 // Property Specific Image Override if No Image 7062 var propertyPlaceholderImage = window.luxuryPresence.defaults.propertyPlaceholderImage; 7063 7064 // Construct template 7065 var templateHTML = section.querySelector(`script${paginationScriptSelector}`)?.innerHTML || ''; 7066 var template = Handlebars.compile(templateHTML); 7067 7068 // Initialize 7069 init(); 7070 function init() { 7071 Object.assign(searchParams, window.luxuryPresence.pagination.deserializeParams(searchFormParams, sectionDomId)); 7072 7073 // Append/prepend pagination element to the pagination container 7074 if (mode === 'NUMBERED_PAGINATION') { 7075 el = document.createElement('div'); 7076 el.className = 'paginationjs';
7077 container[position === 'bottom' ? 'append' : 'prepend'](el); 7078 } 7079 if (searchContainer) { 7080 initSearchContainer({ 7081 fetchNeighborhoods: searchContainer.dataset.fetchNeighborhoods === 'true', 7082 fetchPostCategories: searchContainer.dataset.fetchPostCategories === 'true', 7083 fetchOffices: searchContainer.dataset.fetchOffices === 'true', 7084 fetchRentalRates: searchContainer.dataset.fetchRentalRates === 'true' 7085 }, function () { 7086 syncSearchFormState(searchParams); 7087 }); 7088 } 7089 7090 // Bind events 7091 observer(); 7092 } 7093 function syncSearchFormState(params) { 7094 searchContainer.dispatchEvent(new CustomEvent('sync', { 7095 detail: params 7096 })); 7097 } 7098 function initSearchContainer(options, done) { 7099 var promises = []; 7100 if (options.fetchNeighborhoods) { 7101 promises.push(fetchAllNeighborhoods(function (neighborhoods) { 7102 searchContainer.dispatchEvent(new CustomEvent('neighborhoods-data', { 7103 detail: { 7104 data: neighborhoods.map(function (item) { 7105 return { 7106 value: item.id, 7107 label: item.name 7108 }; 7109 }) 7110 } 7111 })); 7112 })); 7113 } 7114 if (options.fetchPostCategories) { 7115 promises.push(fetchPostCategories(function (categories) { 7116 searchContainer.dispatchEvent(new CustomEvent('post-categories-data', { 7117 detail: { 7118 data: categories 7119 } 7120 })); 7121 })); 7122 } 7123 if (options.fetchOffices) { 7124 promises.push(fetchOffices(function (offices) { 7125 searchContainer.dispatchEvent(new CustomEvent('offices-data', { 7126 detail: { 7127 data: offices 7128 } 7129 })); 7130 })); 7131 } 7132 if (options.fetchRentalRates) { 7133 promises.push(fetchAllRentalRates(function (rentalRates) { 7134 searchContainer.dispatchEvent(new CustomEvent('rental-rates-data', { 7135 detail: { 7136 data: rentalRates 7137 } 7138 })); 7139 })); 7140 } 7141 if (promises.length > 0) { 7142 Promise.all(promises).then(function () { 7143 done(); 7144 }); 7145 } else { 7146 // element's custom code is not executed yet, so we're scheduling callback to after element's code 7147 setTimeout(done); 7148 } 7149 } 7150 function fetchAllNeighborhoods(done) { 7151 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 7152 var gqlQuery = 'query ($companyId: String, $withProperties: NeighborhoodPropertiesFilter, $withBlogPosts: NeighborhoodBlogPostsFilter, $offset: Int, $limit: Int) {' + 'neighborhoods (companyId: $companyId, withProperties: $withProperties, withBlogPosts: $withBlogPosts, offset: $offset, limit: $limit) {id, name}}'; 7153 var payload = {}; 7154 payload.offset = 0; 7155 payload.limit = 100; 7156 payload.companyId = variables.companyId; 7157 if (options.resource === 'properties') { 7158 payload.withProperties = { 7159 active: true 7160 }; 7161 ['agentIds', 'tag', 'propertyTypeId', 'statusIds', 'architectureStyle', 'lifestyle', 'salesPriceGTE', 'salesPriceLTE', 'leasePriceGTE', 'leasePriceLTE', 'livingSpaceSizeGTE', 'livingSpaceSizeLTE', 'bathCountGTE', 'bathCountLTE', 'bedroomCountGTE', 'bedroomCountLTE', 'bathCountGTE', 'bathCountLTE', 'bedroomCountGTE', 'bedroomCountLTE', 'featuredListing', 'leaseProperty', 'neighborhoodId', 'developmentId', 'openHouse'].forEach(function (field) { 7162 if (variables[field] !== undefined && variables[field] !== null) { 7163 payload.withProperties[field] = variables[field]; 7164 } 7165 }); 7166 } 7167 if (options.resource === 'posts') { 7168 payload.withBlogPosts = { 7169 active: true 7170 }; 7171 ['search', 'postStatusId', 'featured', 'categoryId', 'categoryIds', 'neighborhoodId', 'slug'].forEach(function (field) { 7172 if (variables[field] !== undefined && variables[field] !== null) { 7173 payload.withBlogPosts[field] = variables[field]; 7174 } 7175 }); 7176 } 7177 superagent.post(url + '/graphql').withCredentials().send({ 7178 query: gqlQuery, 7179 variables: payload 7180 }).then(function (res) { 7181 var items = res.body.data && res.body.data.neighborhoods || []; 7182 done(items, items.length); 7183 }); 7184 } 7185 function fetchPostCategories(done) { 7186 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 7187 var featured = options.resource === 'posts' && !!variables.featured; 7188 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'postCategories (companyId: $companyId, offset: $offset, limit: $limit ' + (featured ? ' withFeaturedPublishedOnly: true' : ' withPublishedOnly: true') + ') {id, name, publishedCount, featuredPublishedCount}}'; 7189 var payload = {}; 7190 payload.offset = 0; 7191 payload.limit = 100; 7192 payload.companyId = variables.companyId; 7193 superagent.post(url + '/graphql').withCredentials().send({ 7194 query: gqlQuery, 7195 variables: payload 7196 }).then(function (res) { 7197 var items = res.body.data && res.body.data.postCategories || []; 7198 done(items.map(function (item) { 7199 item.count = featured ? item.featuredPublishedCount : item.publishedCount; 7200 return item; 7201 }), items.length); 7202 }); 7203 } 7204 function fetchOffices(done) { 7205 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 7206 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'offices (companyId: $companyId, offset: $offset, limit: $limit) {' + 'id, name, tags}}'; 7207 var payload = { 7208 offset: 0, 7209 limit: 100, 7210 companyId: variables.companyId 7211 }; 7212 superagent.post(url + '/graphql').withCredentials().send({ 7213 query: gqlQuery, 7214 variables: payload 7215 }).then(function (res) { 7216 var items = res.body.data && res.body.data.offices || []; 7217 done(items, items.length); 7218 }); 7219 } 7220 function fetchAllRentalRates(done) { 7221 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 7222 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'rentalRates (companyId: $companyId, offset: $offset, limit: $limit) {rentalRateId, label}}'; 7223 var payload = { 7224 offset: 0, 7225 limit: 100, 7226 companyId: variables.companyId 7227 }; 7228 superagent.post(url + '/graphql').withCredentials().send({ 7229 query: gqlQuery, 7230 variables: payload 7231 }).then(function (res) { 7232 var items = res.body.data && res.body.data.rentalRates || []; 7233 done(items, items.length); 7234 }).catch(function (err) { 7235 console.error('Error fetching rental rates:', err); 7236 done([], 0); 7237 }); 7238 } 7239 function fetchData(params, done) { 7240 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 7241 var resource = options.resource; 7242 var gqlQuery = options.query || window.luxuryPresence.defaults.gql[resource]; 7243 var payload = {}; 7244 try { 7245 payload = JSON.parse(JSON.stringify(variables)); 7246 } catch (e) {
7247 Object.keys(variables).forEach(function (variableName) { 7248 payload[variableName] = variables[variableName]; 7249 }); 7250 } 7251 payload.offset = (params.page - 1) * pageSize + offset; 7252 payload.limit = pageSize; 7253 payload.companyId = options.companyId; 7254 searchFormParams.forEach(function (formParam) { 7255 var val = params[formParam.formKey || formParam.param]; 7256 if (val !== null && val !== undefined) { 7257 objSet(payload, formParam.param, val); 7258 } 7259 }); 7260 7261 // TODO: Disabled loading due to jarring effect 7262 // dataContainer.html('Loading...'); 7263 superagent.post(url + '/graphql').withCredentials().send({ 7264 query: gqlQuery, 7265 variables: payload 7266 }).then(function (res) { 7267 if (res.body.errors) { 7268 done(res.body.errors); 7269 return; 7270 } 7271 var data = res.body.data || {}; 7272 var resources = data[resource] || []; 7273 var resourceCount = data[resource + 'Count'] && data[resource + 'Count'].count ? data[resource + 'Count'].count : 0; 7274 if (resource === 'properties' && propertyPlaceholderImage?.id) { 7275 resources = resources.map(resource => { 7276 if (resource.media?.length === 0) { 7277 return Object.assign(resource, { 7278 media: [propertyPlaceholderImage] 7279 }); 7280 } 7281 return resource; 7282 }); 7283 } 7284 done(null, resources, resourceCount); 7285 }); 7286 } 7287 function getTotalPage() { 7288 return Math.ceil(totalNumber / pageSize); 7289 } 7290 function renderPaginationList() { 7291 var totalPages = getTotalPage(); 7292 var rangeStart = searchParams.page - pageRange; 7293 var rangeEnd = searchParams.page + pageRange; 7294 if (rangeEnd > totalPages) { 7295 rangeEnd = totalPages; 7296 rangeStart = totalPages - pageRange * 2; 7297 rangeStart = rangeStart < 1 ? 1 : rangeStart; 7298 } 7299 if (rangeStart <= 1) { 7300 rangeStart = 1; 7301 rangeEnd = Math.min(pageRange * 2 + 1, totalPages); 7302 } 7303 7304 // Making sure that 1 more page button is shown in edge positions 7305 if (pageRange === 0) { 7306 if (searchParams.page === totalPages) { 7307 rangeStart = totalPages <= 1 ? 1 : totalPages - 1; 7308 } 7309 if (searchParams.page === 1) { 7310 rangeEnd = Math.min(2, totalPages); 7311 } 7312 } 7313 7314 // Render pagination list 7315 if (mode === 'NUMBERED_PAGINATION') { 7316 el.innerHTML = generateHTML({ 7317 currentPage: searchParams.page, 7318 pageRange: pageRange, 7319 rangeStart: rangeStart, 7320 rangeEnd: rangeEnd 7321 }); 7322 if (totalPages < 2 && hidePaginationOnFirstPage) { 7323 container.style.display = 'none'; 7324 } else { 7325 container.style.display = ''; 7326 } 7327 } else if (mode === 'LOAD_MORE_BUTTON') { 7328 if (searchParams.page >= totalPages) { 7329 loadMoreButton.style.display = 'none'; 7330 } else { 7331 loadMoreButton.style.display = ''; 7332 } 7333 } 7334 } 7335 function render(data) { 7336 renderPaginationList(); 7337 7338 // Render new content with handlebars 7339 var html = ''; 7340 if (mode === 'LOAD_MORE_BUTTON' && searchParams.page > 1) { 7341 // If using load more, append search results to existing results 7342 html = dataContainer.innerHTML; 7343 } 7344 for (var i = 0; i < data.length; i++) { 7345 var itemData = recursivelyParse(data[i]); 7346 itemData.sectionIndex = options.sectionIndex; 7347 itemData.renderOptions = options.renderOptions; 7348 html += template(itemData); 7349 } 7350 dataContainer.innerHTML = html; 7351 7352 // Scroll Top 7353 if (!firstRender && mode === 'NUMBERED_PAGINATION' && (!renderBySearch || !options.skipScrollOnSearch)) { 7354 // Only animate to the top if we replace dataContainer (numbered pagination) 7355 setTimeout(() => { 7356 const scrollTargetOffsetTop = scrollTarget.getBoundingClientRect().top + window.scrollY - scrollOffset; 7357 window.scrollTo({ 7358 top: scrollTargetOffsetTop, 7359 behavior: 'smooth' 7360 }); 7361 }, 250); 7362 } 7363 return true; 7364 } 7365 function generateHTML(args) { 7366 const totalPages = getTotalPage(); 7367 const { 7368 rangeStart, 7369 rangeEnd 7370 } = args; 7371 const makeLnk = page =>
7371 window.luxuryPresence.pagination.serializeParams(searchFormParams, sectionDomId, { 7372 ...searchParams, 7373 page 7374 }); 7375 let html = ''; 7376 if (showPrevious || showPageNumbers || showNext) { 7377 html += '<div class="paginationjs-pages"><ul>'; 7378 7379 // Previous button 7380 if (showPrevious) { 7381 if (searchParams.page <= 1) { 7382 html += `<li class="${classPrefix}-prev ${disableClassName}"><a>${prevText}</a></li>`; 7383 } else { 7384 html += `<li class="${classPrefix}-prev J-paginationjs-previous" data-num="${searchParams.page - 1}" title="Previous page"><a href="${makeLnk(searchParams.page - 1)}">${prevText}</a></li>`; 7385 } 7386 } 7387 7388 // Pages 7389 if (showPageNumbers) { 7390 if (rangeStart <= 3) { 7391 for (let i = 1; i < rangeStart; i++) { 7392 html += `<li class="${classPrefix}-page J-paginationjs-page ${i === searchParams.page ? activeClassName : ''}" data-num="${i}"><a ${i === searchParams.page ? '' : `href="${makeLnk(i)}"`}>${i}</a></li>`; 7393 } 7394 } else { 7395 if (showFirstOnEllipsisShow) { 7396 html += `<li class="${classPrefix}-page ${classPrefix}-first J-paginationjs-page" data-num="1"><a href="${makeLnk(1)}">1</a></li>`; 7397 } 7398 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`; 7399 } 7400 for (let i = rangeStart; i <= rangeEnd; i++) { 7401 html += `<li class="${classPrefix}-page J-paginationjs-page ${i === searchParams.page ? activeClassName : ''}" data-num="${i}"><a ${i === searchParams.page ? '' : `href="${makeLnk(i)}"`}>${i}</a></li>`; 7402 } 7403 if (rangeEnd < totalPages - 2) { 7404 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`; 7405 if (showLastOnEllipsisShow) { 7406 html += `<li class="${classPrefix}-page ${classPrefix}-last J-paginationjs-page" data-num="${totalPages}"><a href="${makeLnk(totalPages)}">${totalPages}</a></li>`; 7407 } 7408 } else { 7409 for (let i = rangeEnd + 1; i <= totalPages; i++) { 7410 html += `<li class="${classPrefix}-page J-paginationjs-page" data-num="${i}"><a href="${makeLnk(i)}">${i}</a></li>`; 7411 } 7412 } 7413 } 7414 7415 // Next button 7416 if (showNext) { 7417 if (searchParams.page >= totalPages) { 7418 html += `<li class="${classPrefix}-next ${disableClassName}"><a>${nextText}</a></li>`; 7419 } else { 7420 html += `<li class="${classPrefix}-next J-paginationjs-next" data-num="${searchParams.page + 1}" title="Next page"><a href="${makeLnk(searchParams.page + 1)}">${nextText}</a></li>`; 7421 } 7422 } 7423 html += '</ul></div>'; 7424 } 7425 return html; 7426 } 7427 function go(newSearchParams, callback, restorationVisit) { 7428 if (disabled) return; 7429 var goToPageNumber = newSearchParams.page; 7430 7431 // Page number is out of bounds 7432 if (!goToPageNumber || goToPageNumber < 1) return; 7433 var totalPage = getTotalPage(); 7434 7435 // Page number is out of bounds 7436 if (!restorationVisit && totalNumber > 0) { 7437 if (goToPageNumber > totalPage) return; 7438 } 7439 if (!options.skipSerialization && (mode === 'NUMBERED_PAGINATION' || searchContainer)) { 7440 var newURL = window.luxuryPresence.pagination.serializeParams(searchFormParams, sectionDomId, { 7441 ...newSearchParams, 7442 page: goToPageNumber !== 1 && mode === 'NUMBERED_PAGINATION' ? newSearchParams.page : null 7443 }); 7444 if (!areQueryParamsEqual(newSearchParams, searchParams) && !restorationVisit) { 7445 window.history.pushState({}, '', window.luxuryPresence.getPath(newURL, { 7446 queryString: true 7447 })); 7448 } 7449 } 7450 7451 // Fetch and update page 7452 fetchData(newSearchParams, renderWithData); 7453 disabled = true; 7454 function renderWithData(err, data, count) { 7455 if (err) { 7456 dataContainer.dispatchEvent(new Event('error')); 7457 } else { 7458 searchParams = { 7459 ...newSearchParams 7460 }; 7461 totalNumber = count - offset; 7462 if (data.length === 0) { 7463 dataContainer.dispatchEvent(new Event('no-data')); 7464 } else {
7465 dataContainer.dispatchEvent(new Event('data')); 7466 } 7467 render(data); 7468 firstRender = false; 7469 } 7470 disabled = false; 7471 renderBySearch = false; 7472 } 7473 } 7474 function areQueryParamsEqual(first, second) { 7475 var differ = Object.keys(first).find(function (key) { 7476 return first[key] !== undefined && first[key] !== null && first[key] !== second[key]; 7477 }); 7478 if (differ) { 7479 return false; 7480 } 7481 differ = Object.keys(second).find(function (key) { 7482 return second[key] !== undefined && second[key] !== null && second[key] !== first[key]; 7483 }); 7484 return !differ; 7485 } 7486 function next(callback) { 7487 go({ 7488 ...searchParams, 7489 page: searchParams.page + 1 7490 }, callback); 7491 } 7492 function previous(callback) { 7493 go({ 7494 ...searchParams, 7495 page: searchParams.page - 1 7496 }, callback); 7497 } 7498 function refresh(callback) { 7499 go(searchParams, callback); 7500 } 7501 function update(params, callback) { 7502 renderBySearch = true; 7503 go(params, callback); 7504 } 7505 function observer() { 7506 if (searchContainer) { 7507 searchContainer.addEventListener('search', function (event, newSearchParams) { 7508 newSearchParams = event.detail || newSearchParams; 7509 update({ 7510 ...searchParams, 7511 ...newSearchParams, 7512 page: 1 7513 }); 7514 }); 7515 searchContainer.addEventListener('clear-search', function () { 7516 const defaultSearchParams = { 7517 ...DefaultSearchParams 7518 }; 7519 const isCurrentSearchFormStateDefault = areQueryParamsEqual({ 7520 ...defaultSearchParams, 7521 page: null 7522 }, { 7523 ...searchParams, 7524 page: null 7525 }); 7526 7527 // if mode is LOAD_MORE_BUTTON we should't make anything on clear 7528 // when search form have default state 7529 if (isCurrentSearchFormStateDefault && mode !== 'NUMBERED_PAGINATION') { 7530 return; 7531 } 7532 7533 // if someone initially go to some page and click 'Clear' on search from 7534 // we should't navigate to first page. We should preserve page value 7535 if (isCurrentSearchFormStateDefault) { 7536 defaultSearchParams.page = searchParams.page; 7537 } 7538 syncSearchFormState(defaultSearchParams); 7539 update(defaultSearchParams); 7540 }); 7541 } 7542 if (mode === 'LOAD_MORE_BUTTON') { 7543 loadMoreButton.addEventListener('click', function (event) { 7544 go({ 7545 ...searchParams, 7546 page: searchParams.page + 1 7547 }); 7548 return false; 7549 }); 7550 } 7551 if (mode === 'LOAD_MORE_BUTTON' || mode === 'BUTTONLESS_PAGINATION') { 7552 // Do initial render 7553 go(Object.assign({}, searchParams, { 7554 page: 1 7555 })); 7556 return; 7557 } 7558 if (mode === 'NUMBERED_PAGINATION') { 7559 // Go to specified page number 7560 container.addEventListener(eventPrefix + 'go', function (event) { 7561 let { 7562 pageNumber, 7563 done 7564 } = event.detail || {}; 7565 pageNumber = parseInt(pageNumber, 10); 7566 if (!pageNumber) { 7567 return; 7568 } 7569 go({ 7570 ...searchParams, 7571 page: pageNumber 7572 }, done); 7573 }); 7574 7575 // Page number button click 7576 el.addEventListener('click', function (event) { 7577 let clickTarget = event.target; 7578 let current = clickTarget.closest('.J-paginationjs-page, .J-paginationjs-previous, .J-paginationjs-next'); 7579 let pageNumber = current?.getAttribute('data-num'); 7580 if (!pageNumber || current.classList.contains(disableClassName) || current.classList.contains(activeClassName)) { 7581 return; 7582 } else { 7583 go({ 7584 ...searchParams, 7585 page: parseInt(pageNumber, 10) 7586 }); 7587 } 7588 event.preventDefault(); 7589 }); 7590 7591 // Whether to load the default page 7592 var defaultPageNumber = searchParams.page; 7593 if (triggerPagingOnInit) { 7594 container.dispatchEvent(new CustomEvent(eventPrefix + 'go', { 7595 detail: { 7596 pageNumber: defaultPageNumber 7597 } 7598 })); 7599 } 7600 } 7601 window.addEventListener('popstate', function (event) { 7602 var newParams = Object.assign({}, DefaultSearchParams, window.luxuryPresence.pagination.deserializeParams(searchFormParams, sectionDomId)); 7603 if (!areQueryParamsEqual(newParams, searchParams)) { 7604 go(newParams, null, true); 7605 if (searchContainer) { 7606 syncSearchFormState(newParams); 7607 } 7608 } 7609 }); 7610 } 7611 function replaceVariables(template, variables) { 7612 var formattedString; 7613 for (var key in variables) { 7614 var value = variables[key]; 7615 var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img'); 7616 formattedString = (formattedString || template).replace(regexp, value); 7617 } 7618 return formattedString; 7619 } 7620 function isTextEditorValue(obj) { 7621 return 'raw' in obj || 'gjs' in obj; 7622 } 7623 function getTextEditorValue(obj) { 7624 if (!obj.html) { 7625 return ''; 7626 } 7627 var data = obj.html; 7628 var css = (obj.raw || obj.gjs || {}).css; 7629 if (css) { 7630 data = data + '<style>' + css + '</style>'; 7631 } 7632 return data; 7633 } 7634 7635 /** 7636 * check rendered resources for html and raw properties that could indicate if resource is from an advanced text editor. 7637 * strips out raw proprties and returns only HTML if it exists. 7638 * @param {Object} resources a collection retrieved from the database 7639 * @return {Object} the newly modified object. 7640 */ 7641 function recursivelyParse(resources) {
7642 forEach(resources, function (value, key) { 7643 if (!value) { 7644 return; 7645 } 7646 if (value instanceof Object) { 7647 if (isTextEditorValue(value)) { 7648 resources[key] = getTextEditorValue(value); 7649 return; 7650 } else { 7651 return recursivelyParse(value); 7652 } 7653 } 7654 try { 7655 var parsedData = JSON.parse(value); 7656 if (isTextEditorValue(parsedData)) { 7657 resources[key] = getTextEditorValue(parsedData); 7658 } 7659 } catch (e) { 7660 // do nothing if we failed to parse value as JSON 7661 } 7662 }); 7663 return resources; 7664 } 7665 function forEach(collection, iteratee) { 7666 var func = Array.isArray(collection) ? arrayEach : baseFor; 7667 return func(collection, iteratee); 7668 } 7669 function baseFor(object, iteratee) { 7670 if (!object) { 7671 return; 7672 } 7673 var iterable = Object(object); 7674 var props = Object.keys(object); 7675 var length = props.length; 7676 var index = -1; 7677 while (length--) { 7678 var key = props[++index]; 7679 if (iteratee(iterable[key], key, iterable) === false) { 7680 break; 7681 } 7682 } 7683 return object; 7684 } 7685 function objSet(obj, path, value) { 7686 const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); 7687 pathArray.reduce((acc, key, i) => { 7688 if (acc[key] === undefined) acc[key] = {}; 7689 if (i === pathArray.length - 1) acc[key] = value; 7690 return acc[key]; 7691 }, obj); 7692 } 7693 function arrayEach(array, iteratee) { 7694 var index = -1; 7695 var length = array.length; 7696 while (++index < length) { 7697 if (iteratee(array[index], index, array) === false) { 7698 break; 7699 } 7700 } 7701 return array; 7702 } 7703 function selectPageRangeMode(e) { 7704 pageRange = e.matches ? mobilePageRange : desktopPageRange; 7705 renderPaginationList(); 7706 } 7707 paginationMql.addEventListener('change', selectPageRangeMode); 7708})('section-3087e602-065d-462f-8f31-f4819d6fc3cd', JSON.parse("{\"pageSize\":\"9\",\"resource\":\"posts\",\"pageRange\":1,\"variables\":{\"limit\":9,\"offset\":0,\"sortDir\":\"DESC\",\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"categoryId\":\"{{pageElement.categoryId}}\",\"order\":\"publishedAt\"},\"hidePaginationOnFirstPage\":true,\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"usePageResource\":false,\"sectionIndex\":2}")); 7709} catch(e) { 7710console.log(e); 7711} 7712 try { 7713(function init(sectionDomId) {})('section-3087e602-065d-462f-8f31-f4819d6fc3cd', 'undefined'); 7714} catch(e) { 7715console.log(e); 7716} 7717 try { 7718(function init(sectionDomId, options) { 7719 let section = null; 7720 switch (sectionDomId) { 7721 case 'NAVBAR': 7722 section = document.querySelector('body > nav, #global-navbar'); 7723 break; 7724 case 'FOOTER': 7725 section = document.querySelector('body > footer, #global-footer'); 7726 break; 7727 case 'SIDEMENU': 7728 section = document.querySelector('body > div.sidemenu, #global-sidemenu'); 7729 break; 7730 case 'CONTACT': 7731 section = document.querySelector('#modal-global-contact-us'); 7732 break; 7733 case 'MOBILE_CONTACT': 7734 section = document.querySelector('body > div.mobile-contact-wrapper, #global-mobile-contact'); 7735 break; 7736 case 'SUBSCRIBE': 7737 // Subscribe Not Implemented 7738 section = null; 7739 break; 7740 case 'ACCOUNT': 7741 // Account Not Implemented 7742 section = null; 7743 break; 7744 default: 7745 section = document.querySelector('#' + sectionDomId); 7746 } 7747 if (!section) { 7748 return; 7749 } 7750 7751 // Where content / items are rendered 7752 const dataContainer = section.querySelector('.data-container'); 7753 if (!dataContainer) { 7754 // no place to render items = no need to query server or do anything else 7755 return; 7756 } 7757 let variables = options.variables || {}; 7758 7759 // Some variables in options may not have been injected - remove 7760 Object.keys(variables).forEach(function (variableName) { 7761 if (typeof variables[variableName] === 'string' && variables[variableName].match(/^{{.*}}$/)) { 7762 delete variables[variableName]; 7763 } 7764 }); 7765 7766 // Merge page query variables 7767 if (options.usePageResource) { 7768 variables = { 7769 ...variables, 7770 ...window.luxuryPresence.config.pageMeta.pageQueryVariables[options.resource] 7771 }; 7772 } 7773 7774 // Pagination Executes on a Numbered Pagination Container 7775 // Or via a load more button 7776 let mode = 'BUTTONLESS_PAGINATION'; 7777 const container = section.querySelector('.pagination-container');
7778 const loadMoreButton = section.querySelector(options?.loadMoreSelector); 7779 if (container) { 7780 mode = 'NUMBERED_PAGINATION'; 7781 } 7782 if (loadMoreButton) { 7783 mode = 'LOAD_MORE_BUTTON'; 7784 } 7785 let searchContainer = section.querySelector('.search-container'); 7786 const hidePaginationOnFirstPage = options.hidePaginationOnFirstPage || false; 7787 const eventPrefix = '__pagination-'; 7788 const searchFormParams = options.searchFormParams || (options.resource === 'properties' ? [{ 7789 param: 'neighborhoodId', 7790 urlKey: 'neighborhood' 7791 }, { 7792 param: 'search', 7793 urlKey: 'search' 7794 }, { 7795 param: 'salesPriceGTE', 7796 urlKey: 'pricemin', 7797 formKey: 'priceMin', 7798 type: 'int' 7799 }, { 7800 param: 'salesPriceLTE', 7801 urlKey: 'pricemax', 7802 formKey: 'priceMax', 7803 type: 'int' 7804 }, { 7805 param: 'livingSpaceSizeGTE', 7806 urlKey: 'areamin', 7807 formKey: 'areaMin', 7808 type: 'int' 7809 }, { 7810 param: 'livingSpaceSizeLTE', 7811 urlKey: 'areamax', 7812 formKey: 'areaMax', 7813 type: 'int' 7814 }] : []); 7815 7816 // Total entries - initialized in first render 7817 let totalNumber = 0; 7818 const DefaultSearchParams = { 7819 page: 1 7820 }; 7821 searchFormParams.forEach(function (formParam) { 7822 DefaultSearchParams[formParam.formKey || formParam.param] = null; 7823 }); 7824 let searchParams = { 7825 ...DefaultSearchParams 7826 }; 7827 const offset = options.variables?.offset || 0; 7828 let scrollTarget = options.scrollTargetSelector ? section.querySelector(options.scrollTargetSelector) || section : section; 7829 let scrollOffset = (parseInt(options.scrollOffset, 10) || 0) ?? 100; 7830 7831 // entries of per page 7832 // when query variables for a given section would't be filled options.pageSize can store 7833 // template string, ex: "{{variables.limit}}", below code handles that case and set 9 as a default value 7834 let pageSize = parseInt(options.pageSize, 10) || 9; 7835 7836 // Adding mobile pageRange support 7837 var NARROW_BREAKPOINT = 600; 7838 var mobileBreakpoint = options.mobileBreakpoint > 0 ? options.mobileBreakpoint : NARROW_BREAKPOINT; 7839 var paginationMql = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`); 7840 7841 // Page range (pages on both sides of the current page) 7842 var mobilePageRange = options.mobilePageRange > 0 ? options.mobilePageRange : 0; 7843 var desktopPageRange = options.pageRange >= 0 ? options.pageRange : 2; 7844 var pageRange = paginationMql.matches ? mobilePageRange : desktopPageRange; 7845 7846 // Whether to display the 'Previous' button 7847 var showPrevious = true; 7848 7849 // Whether to display the 'Next' button 7850 var showNext = true; 7851 7852 // Whether to display the page buttons 7853 var showPageNumbers = true; 7854 7855 // 'Previous' text 7856 var prevText = options.prevText || '«'; 7857 7858 // 'Next' text 7859 var nextText = options.nextText || '»'; 7860 7861 // Ellipsis text 7862 var ellipsisText = '…'; 7863 var classPrefix = 'paginationjs'; 7864 7865 // Default active class 7866 var activeClassName = 'active'; 7867 7868 // Default disable class 7869 var disableClassName = 'disabled'; 7870 7871 // Pagination element's position in the container 7872 var position = 'bottom'; 7873 7874 // Whether to trigger pagination at initialization 7875 var triggerPagingOnInit = !options.skipInitialLoad; 7876 var showFirstOnEllipsisShow = true; 7877 var showLastOnEllipsisShow = true; 7878 7879 // Instantiated in init() 7880 var el = null; 7881 7882 // Controls scrollTop logic 7883 var firstRender = true; 7884 var renderBySearch = false; 7885 var disabled = false; 7886 7887 // Pagination script class selector 7888 var paginationScriptSelector = options.paginationPartialName ? `.${options.paginationPartialName}` : '.pagination-script'; 7889 7890 // Property Specific Image Override if No Image 7891 var propertyPlaceholderImage = window.luxuryPresence.defaults.propertyPlaceholderImage; 7892 7893 // Construct template 7894 var templateHTML = section.querySelector(`script${paginationScriptSelector}`)?.innerHTML || ''; 7895 var template = Handlebars.compile(templateHTML); 7896 7897 // Initialize 7898 init(); 7899 function init() { 7900 Object.assign(searchParams, window.luxuryPresence.pagination.deserializeParams(searchFormParams, sectionDomId)); 7901 7902 // Append/prepend pagination element to the pagination container 7903 if (mode === 'NUMBERED_PAGINATION') { 7904 el = document.createElement('div'); 7905 el.className = 'paginationjs';
7906 container[position === 'bottom' ? 'append' : 'prepend'](el); 7907 } 7908 if (searchContainer) { 7909 initSearchContainer({ 7910 fetchNeighborhoods: searchContainer.dataset.fetchNeighborhoods === 'true', 7911 fetchPostCategories: searchContainer.dataset.fetchPostCategories === 'true', 7912 fetchOffices: searchContainer.dataset.fetchOffices === 'true', 7913 fetchRentalRates: searchContainer.dataset.fetchRentalRates === 'true' 7914 }, function () { 7915 syncSearchFormState(searchParams); 7916 }); 7917 } 7918 7919 // Bind events 7920 observer(); 7921 } 7922 function syncSearchFormState(params) { 7923 searchContainer.dispatchEvent(new CustomEvent('sync', { 7924 detail: params 7925 })); 7926 } 7927 function initSearchContainer(options, done) { 7928 var promises = []; 7929 if (options.fetchNeighborhoods) { 7930 promises.push(fetchAllNeighborhoods(function (neighborhoods) { 7931 searchContainer.dispatchEvent(new CustomEvent('neighborhoods-data', { 7932 detail: { 7933 data: neighborhoods.map(function (item) { 7934 return { 7935 value: item.id, 7936 label: item.name 7937 }; 7938 }) 7939 } 7940 })); 7941 })); 7942 } 7943 if (options.fetchPostCategories) { 7944 promises.push(fetchPostCategories(function (categories) { 7945 searchContainer.dispatchEvent(new CustomEvent('post-categories-data', { 7946 detail: { 7947 data: categories 7948 } 7949 })); 7950 })); 7951 } 7952 if (options.fetchOffices) { 7953 promises.push(fetchOffices(function (offices) { 7954 searchContainer.dispatchEvent(new CustomEvent('offices-data', { 7955 detail: { 7956 data: offices 7957 } 7958 })); 7959 })); 7960 } 7961 if (options.fetchRentalRates) { 7962 promises.push(fetchAllRentalRates(function (rentalRates) { 7963 searchContainer.dispatchEvent(new CustomEvent('rental-rates-data', { 7964 detail: { 7965 data: rentalRates 7966 } 7967 })); 7968 })); 7969 } 7970 if (promises.length > 0) { 7971 Promise.all(promises).then(function () { 7972 done(); 7973 }); 7974 } else { 7975 // element's custom code is not executed yet, so we're scheduling callback to after element's code 7976 setTimeout(done); 7977 } 7978 } 7979 function fetchAllNeighborhoods(done) { 7980 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 7981 var gqlQuery = 'query ($companyId: String, $withProperties: NeighborhoodPropertiesFilter, $withBlogPosts: NeighborhoodBlogPostsFilter, $offset: Int, $limit: Int) {' + 'neighborhoods (companyId: $companyId, withProperties: $withProperties, withBlogPosts: $withBlogPosts, offset: $offset, limit: $limit) {id, name}}'; 7982 var payload = {}; 7983 payload.offset = 0; 7984 payload.limit = 100; 7985 payload.companyId = variables.companyId; 7986 if (options.resource === 'properties') { 7987 payload.withProperties = { 7988 active: true 7989 }; 7990 ['agentIds', 'tag', 'propertyTypeId', 'statusIds', 'architectureStyle', 'lifestyle', 'salesPriceGTE', 'salesPriceLTE', 'leasePriceGTE', 'leasePriceLTE', 'livingSpaceSizeGTE', 'livingSpaceSizeLTE', 'bathCountGTE', 'bathCountLTE', 'bedroomCountGTE', 'bedroomCountLTE', 'bathCountGTE', 'bathCountLTE', 'bedroomCountGTE', 'bedroomCountLTE', 'featuredListing', 'leaseProperty', 'neighborhoodId', 'developmentId', 'openHouse'].forEach(function (field) { 7991 if (variables[field] !== undefined && variables[field] !== null) { 7992 payload.withProperties[field] = variables[field]; 7993 } 7994 }); 7995 } 7996 if (options.resource === 'posts') { 7997 payload.withBlogPosts = { 7998 active: true 7999 }; 8000 ['search', 'postStatusId', 'featured', 'categoryId', 'categoryIds', 'neighborhoodId', 'slug'].forEach(function (field) { 8001 if (variables[field] !== undefined && variables[field] !== null) { 8002 payload.withBlogPosts[field] = variables[field]; 8003 } 8004 }); 8005 } 8006 superagent.post(url + '/graphql').withCredentials().send({ 8007 query: gqlQuery, 8008 variables: payload 8009 }).then(function (res) { 8010 var items = res.body.data && res.body.data.neighborhoods || []; 8011 done(items, items.length); 8012 }); 8013 } 8014 function fetchPostCategories(done) { 8015 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8016 var featured = options.resource === 'posts' && !!variables.featured; 8017 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'postCategories (companyId: $companyId, offset: $offset, limit: $limit ' + (featured ? ' withFeaturedPublishedOnly: true' : ' withPublishedOnly: true') + ') {id, name, publishedCount, featuredPublishedCount}}'; 8018 var payload = {}; 8019 payload.offset = 0; 8020 payload.limit = 100; 8021 payload.companyId = variables.companyId; 8022 superagent.post(url + '/graphql').withCredentials().send({ 8023 query: gqlQuery, 8024 variables: payload 8025 }).then(function (res) { 8026 var items = res.body.data && res.body.data.postCategories || []; 8027 done(items.map(function (item) { 8028 item.count = featured ? item.featuredPublishedCount : item.publishedCount; 8029 return item; 8030 }), items.length); 8031 }); 8032 } 8033 function fetchOffices(done) { 8034 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8035 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'offices (companyId: $companyId, offset: $offset, limit: $limit) {' + 'id, name, tags}}'; 8036 var payload = { 8037 offset: 0, 8038 limit: 100, 8039 companyId: variables.companyId 8040 }; 8041 superagent.post(url + '/graphql').withCredentials().send({ 8042 query: gqlQuery, 8043 variables: payload 8044 }).then(function (res) { 8045 var items = res.body.data && res.body.data.offices || []; 8046 done(items, items.length); 8047 }); 8048 } 8049 function fetchAllRentalRates(done) { 8050 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8051 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'rentalRates (companyId: $companyId, offset: $offset, limit: $limit) {rentalRateId, label}}'; 8052 var payload = { 8053 offset: 0, 8054 limit: 100, 8055 companyId: variables.companyId 8056 }; 8057 superagent.post(url + '/graphql').withCredentials().send({ 8058 query: gqlQuery, 8059 variables: payload 8060 }).then(function (res) { 8061 var items = res.body.data && res.body.data.rentalRates || []; 8062 done(items, items.length); 8063 }).catch(function (err) { 8064 console.error('Error fetching rental rates:', err); 8065 done([], 0); 8066 }); 8067 } 8068 function fetchData(params, done) { 8069 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8070 var resource = options.resource; 8071 var gqlQuery = options.query || window.luxuryPresence.defaults.gql[resource]; 8072 var payload = {}; 8073 try { 8074 payload = JSON.parse(JSON.stringify(variables)); 8075 } catch (e) {
8076 Object.keys(variables).forEach(function (variableName) { 8077 payload[variableName] = variables[variableName]; 8078 }); 8079 } 8080 payload.offset = (params.page - 1) * pageSize + offset; 8081 payload.limit = pageSize; 8082 payload.companyId = options.companyId; 8083 searchFormParams.forEach(function (formParam) { 8084 var val = params[formParam.formKey || formParam.param]; 8085 if (val !== null && val !== undefined) { 8086 objSet(payload, formParam.param, val); 8087 } 8088 }); 8089 8090 // TODO: Disabled loading due to jarring effect 8091 // dataContainer.html('Loading...'); 8092 superagent.post(url + '/graphql').withCredentials().send({ 8093 query: gqlQuery, 8094 variables: payload 8095 }).then(function (res) { 8096 if (res.body.errors) { 8097 done(res.body.errors); 8098 return; 8099 } 8100 var data = res.body.data || {}; 8101 var resources = data[resource] || []; 8102 var resourceCount = data[resource + 'Count'] && data[resource + 'Count'].count ? data[resource + 'Count'].count : 0; 8103 if (resource === 'properties' && propertyPlaceholderImage?.id) { 8104 resources = resources.map(resource => { 8105 if (resource.media?.length === 0) { 8106 return Object.assign(resource, { 8107 media: [propertyPlaceholderImage] 8108 }); 8109 } 8110 return resource; 8111 }); 8112 } 8113 done(null, resources, resourceCount); 8114 }); 8115 } 8116 function getTotalPage() { 8117 return Math.ceil(totalNumber / pageSize); 8118 } 8119 function renderPaginationList() { 8120 var totalPages = getTotalPage(); 8121 var rangeStart = searchParams.page - pageRange; 8122 var rangeEnd = searchParams.page + pageRange; 8123 if (rangeEnd > totalPages) { 8124 rangeEnd = totalPages; 8125 rangeStart = totalPages - pageRange * 2; 8126 rangeStart = rangeStart < 1 ? 1 : rangeStart; 8127 } 8128 if (rangeStart <= 1) { 8129 rangeStart = 1; 8130 rangeEnd = Math.min(pageRange * 2 + 1, totalPages); 8131 } 8132 8133 // Making sure that 1 more page button is shown in edge positions 8134 if (pageRange === 0) { 8135 if (searchParams.page === totalPages) { 8136 rangeStart = totalPages <= 1 ? 1 : totalPages - 1; 8137 } 8138 if (searchParams.page === 1) { 8139 rangeEnd = Math.min(2, totalPages); 8140 } 8141 } 8142 8143 // Render pagination list 8144 if (mode === 'NUMBERED_PAGINATION') { 8145 el.innerHTML = generateHTML({ 8146 currentPage: searchParams.page, 8147 pageRange: pageRange, 8148 rangeStart: rangeStart, 8149 rangeEnd: rangeEnd 8150 }); 8151 if (totalPages < 2 && hidePaginationOnFirstPage) { 8152 container.style.display = 'none'; 8153 } else { 8154 container.style.display = ''; 8155 } 8156 } else if (mode === 'LOAD_MORE_BUTTON') { 8157 if (searchParams.page >= totalPages) { 8158 loadMoreButton.style.display = 'none'; 8159 } else { 8160 loadMoreButton.style.display = ''; 8161 } 8162 } 8163 } 8164 function render(data) { 8165 renderPaginationList(); 8166 8167 // Render new content with handlebars 8168 var html = ''; 8169 if (mode === 'LOAD_MORE_BUTTON' && searchParams.page > 1) { 8170 // If using load more, append search results to existing results 8171 html = dataContainer.innerHTML; 8172 } 8173 for (var i = 0; i < data.length; i++) { 8174 var itemData = recursivelyParse(data[i]); 8175 itemData.sectionIndex = options.sectionIndex; 8176 itemData.renderOptions = options.renderOptions; 8177 html += template(itemData); 8178 } 8179 dataContainer.innerHTML = html; 8180 8181 // Scroll Top 8182 if (!firstRender && mode === 'NUMBERED_PAGINATION' && (!renderBySearch || !options.skipScrollOnSearch)) { 8183 // Only animate to the top if we replace dataContainer (numbered pagination) 8184 setTimeout(() => { 8185 const scrollTargetOffsetTop = scrollTarget.getBoundingClientRect().top + window.scrollY - scrollOffset; 8186 window.scrollTo({ 8187 top: scrollTargetOffsetTop, 8188 behavior: 'smooth' 8189 }); 8190 }, 250); 8191 } 8192 return true; 8193 } 8194 function generateHTML(args) { 8195 const totalPages = getTotalPage(); 8196 const { 8197 rangeStart, 8198 rangeEnd 8199 } = args; 8200 const makeLnk = page =>
8200 window.luxuryPresence.pagination.serializeParams(searchFormParams, sectionDomId, { 8201 ...searchParams, 8202 page 8203 }); 8204 let html = ''; 8205 if (showPrevious || showPageNumbers || showNext) { 8206 html += '<div class="paginationjs-pages"><ul>'; 8207 8208 // Previous button 8209 if (showPrevious) { 8210 if (searchParams.page <= 1) { 8211 html += `<li class="${classPrefix}-prev ${disableClassName}"><a>${prevText}</a></li>`; 8212 } else { 8213 html += `<li class="${classPrefix}-prev J-paginationjs-previous" data-num="${searchParams.page - 1}" title="Previous page"><a href="${makeLnk(searchParams.page - 1)}">${prevText}</a></li>`; 8214 } 8215 } 8216 8217 // Pages 8218 if (showPageNumbers) { 8219 if (rangeStart <= 3) { 8220 for (let i = 1; i < rangeStart; i++) { 8221 html += `<li class="${classPrefix}-page J-paginationjs-page ${i === searchParams.page ? activeClassName : ''}" data-num="${i}"><a ${i === searchParams.page ? '' : `href="${makeLnk(i)}"`}>${i}</a></li>`; 8222 } 8223 } else { 8224 if (showFirstOnEllipsisShow) { 8225 html += `<li class="${classPrefix}-page ${classPrefix}-first J-paginationjs-page" data-num="1"><a href="${makeLnk(1)}">1</a></li>`; 8226 } 8227 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`; 8228 } 8229 for (let i = rangeStart; i <= rangeEnd; i++) { 8230 html += `<li class="${classPrefix}-page J-paginationjs-page ${i === searchParams.page ? activeClassName : ''}" data-num="${i}"><a ${i === searchParams.page ? '' : `href="${makeLnk(i)}"`}>${i}</a></li>`; 8231 } 8232 if (rangeEnd < totalPages - 2) { 8233 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`; 8234 if (showLastOnEllipsisShow) { 8235 html += `<li class="${classPrefix}-page ${classPrefix}-last J-paginationjs-page" data-num="${totalPages}"><a href="${makeLnk(totalPages)}">${totalPages}</a></li>`; 8236 } 8237 } else { 8238 for (let i = rangeEnd + 1; i <= totalPages; i++) { 8239 html += `<li class="${classPrefix}-page J-paginationjs-page" data-num="${i}"><a href="${makeLnk(i)}">${i}</a></li>`; 8240 } 8241 } 8242 } 8243 8244 // Next button 8245 if (showNext) { 8246 if (searchParams.page >= totalPages) { 8247 html += `<li class="${classPrefix}-next ${disableClassName}"><a>${nextText}</a></li>`; 8248 } else { 8249 html += `<li class="${classPrefix}-next J-paginationjs-next" data-num="${searchParams.page + 1}" title="Next page"><a href="${makeLnk(searchParams.page + 1)}">${nextText}</a></li>`; 8250 } 8251 } 8252 html += '</ul></div>'; 8253 } 8254 return html; 8255 } 8256 function go(newSearchParams, callback, restorationVisit) { 8257 if (disabled) return; 8258 var goToPageNumber = newSearchParams.page; 8259 8260 // Page number is out of bounds 8261 if (!goToPageNumber || goToPageNumber < 1) return; 8262 var totalPage = getTotalPage(); 8263 8264 // Page number is out of bounds 8265 if (!restorationVisit && totalNumber > 0) { 8266 if (goToPageNumber > totalPage) return; 8267 } 8268 if (!options.skipSerialization && (mode === 'NUMBERED_PAGINATION' || searchContainer)) { 8269 var newURL = window.luxuryPresence.pagination.serializeParams(searchFormParams, sectionDomId, { 8270 ...newSearchParams, 8271 page: goToPageNumber !== 1 && mode === 'NUMBERED_PAGINATION' ? newSearchParams.page : null 8272 }); 8273 if (!areQueryParamsEqual(newSearchParams, searchParams) && !restorationVisit) { 8274 window.history.pushState({}, '', window.luxuryPresence.getPath(newURL, { 8275 queryString: true 8276 })); 8277 } 8278 } 8279 8280 // Fetch and update page 8281 fetchData(newSearchParams, renderWithData); 8282 disabled = true; 8283 function renderWithData(err, data, count) { 8284 if (err) { 8285 dataContainer.dispatchEvent(new Event('error')); 8286 } else { 8287 searchParams = { 8288 ...newSearchParams 8289 }; 8290 totalNumber = count - offset; 8291 if (data.length === 0) { 8292 dataContainer.dispatchEvent(new Event('no-data')); 8293 } else {
8294 dataContainer.dispatchEvent(new Event('data')); 8295 } 8296 render(data); 8297 firstRender = false; 8298 } 8299 disabled = false; 8300 renderBySearch = false; 8301 } 8302 } 8303 function areQueryParamsEqual(first, second) { 8304 var differ = Object.keys(first).find(function (key) { 8305 return first[key] !== undefined && first[key] !== null && first[key] !== second[key]; 8306 }); 8307 if (differ) { 8308 return false; 8309 } 8310 differ = Object.keys(second).find(function (key) { 8311 return second[key] !== undefined && second[key] !== null && second[key] !== first[key]; 8312 }); 8313 return !differ; 8314 } 8315 function next(callback) { 8316 go({ 8317 ...searchParams, 8318 page: searchParams.page + 1 8319 }, callback); 8320 } 8321 function previous(callback) { 8322 go({ 8323 ...searchParams, 8324 page: searchParams.page - 1 8325 }, callback); 8326 } 8327 function refresh(callback) { 8328 go(searchParams, callback); 8329 } 8330 function update(params, callback) { 8331 renderBySearch = true; 8332 go(params, callback); 8333 } 8334 function observer() { 8335 if (searchContainer) { 8336 searchContainer.addEventListener('search', function (event, newSearchParams) { 8337 newSearchParams = event.detail || newSearchParams; 8338 update({ 8339 ...searchParams, 8340 ...newSearchParams, 8341 page: 1 8342 }); 8343 }); 8344 searchContainer.addEventListener('clear-search', function () { 8345 const defaultSearchParams = { 8346 ...DefaultSearchParams 8347 }; 8348 const isCurrentSearchFormStateDefault = areQueryParamsEqual({ 8349 ...defaultSearchParams, 8350 page: null 8351 }, { 8352 ...searchParams, 8353 page: null 8354 }); 8355 8356 // if mode is LOAD_MORE_BUTTON we should't make anything on clear 8357 // when search form have default state 8358 if (isCurrentSearchFormStateDefault && mode !== 'NUMBERED_PAGINATION') { 8359 return; 8360 } 8361 8362 // if someone initially go to some page and click 'Clear' on search from 8363 // we should't navigate to first page. We should preserve page value 8364 if (isCurrentSearchFormStateDefault) { 8365 defaultSearchParams.page = searchParams.page; 8366 } 8367 syncSearchFormState(defaultSearchParams); 8368 update(defaultSearchParams); 8369 }); 8370 } 8371 if (mode === 'LOAD_MORE_BUTTON') { 8372 loadMoreButton.addEventListener('click', function (event) { 8373 go({ 8374 ...searchParams, 8375 page: searchParams.page + 1 8376 }); 8377 return false; 8378 }); 8379 } 8380 if (mode === 'LOAD_MORE_BUTTON' || mode === 'BUTTONLESS_PAGINATION') { 8381 // Do initial render 8382 go(Object.assign({}, searchParams, { 8383 page: 1 8384 })); 8385 return; 8386 } 8387 if (mode === 'NUMBERED_PAGINATION') { 8388 // Go to specified page number 8389 container.addEventListener(eventPrefix + 'go', function (event) { 8390 let { 8391 pageNumber, 8392 done 8393 } = event.detail || {}; 8394 pageNumber = parseInt(pageNumber, 10); 8395 if (!pageNumber) { 8396 return; 8397 } 8398 go({ 8399 ...searchParams, 8400 page: pageNumber 8401 }, done); 8402 }); 8403 8404 // Page number button click 8405 el.addEventListener('click', function (event) { 8406 let clickTarget = event.target; 8407 let current = clickTarget.closest('.J-paginationjs-page, .J-paginationjs-previous, .J-paginationjs-next'); 8408 let pageNumber = current?.getAttribute('data-num'); 8409 if (!pageNumber || current.classList.contains(disableClassName) || current.classList.contains(activeClassName)) { 8410 return; 8411 } else { 8412 go({ 8413 ...searchParams, 8414 page: parseInt(pageNumber, 10) 8415 }); 8416 } 8417 event.preventDefault(); 8418 }); 8419 8420 // Whether to load the default page 8421 var defaultPageNumber = searchParams.page; 8422 if (triggerPagingOnInit) { 8423 container.dispatchEvent(new CustomEvent(eventPrefix + 'go', { 8424 detail: { 8425 pageNumber: defaultPageNumber 8426 } 8427 })); 8428 } 8429 } 8430 window.addEventListener('popstate', function (event) { 8431 var newParams = Object.assign({}, DefaultSearchParams, window.luxuryPresence.pagination.deserializeParams(searchFormParams, sectionDomId)); 8432 if (!areQueryParamsEqual(newParams, searchParams)) { 8433 go(newParams, null, true); 8434 if (searchContainer) { 8435 syncSearchFormState(newParams); 8436 } 8437 } 8438 }); 8439 } 8440 function replaceVariables(template, variables) { 8441 var formattedString; 8442 for (var key in variables) { 8443 var value = variables[key]; 8444 var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img'); 8445 formattedString = (formattedString || template).replace(regexp, value); 8446 } 8447 return formattedString; 8448 } 8449 function isTextEditorValue(obj) { 8450 return 'raw' in obj || 'gjs' in obj; 8451 } 8452 function getTextEditorValue(obj) { 8453 if (!obj.html) { 8454 return ''; 8455 } 8456 var data = obj.html; 8457 var css = (obj.raw || obj.gjs || {}).css; 8458 if (css) { 8459 data = data + '<style>' + css + '</style>'; 8460 } 8461 return data; 8462 } 8463 8464 /** 8465 * check rendered resources for html and raw properties that could indicate if resource is from an advanced text editor. 8466 * strips out raw proprties and returns only HTML if it exists. 8467 * @param {Object} resources a collection retrieved from the database 8468 * @return {Object} the newly modified object. 8469 */ 8470 function recursivelyParse(resources) {
8471 forEach(resources, function (value, key) { 8472 if (!value) { 8473 return; 8474 } 8475 if (value instanceof Object) { 8476 if (isTextEditorValue(value)) { 8477 resources[key] = getTextEditorValue(value); 8478 return; 8479 } else { 8480 return recursivelyParse(value); 8481 } 8482 } 8483 try { 8484 var parsedData = JSON.parse(value); 8485 if (isTextEditorValue(parsedData)) { 8486 resources[key] = getTextEditorValue(parsedData); 8487 } 8488 } catch (e) { 8489 // do nothing if we failed to parse value as JSON 8490 } 8491 }); 8492 return resources; 8493 } 8494 function forEach(collection, iteratee) { 8495 var func = Array.isArray(collection) ? arrayEach : baseFor; 8496 return func(collection, iteratee); 8497 } 8498 function baseFor(object, iteratee) { 8499 if (!object) { 8500 return; 8501 } 8502 var iterable = Object(object); 8503 var props = Object.keys(object); 8504 var length = props.length; 8505 var index = -1; 8506 while (length--) { 8507 var key = props[++index]; 8508 if (iteratee(iterable[key], key, iterable) === false) { 8509 break; 8510 } 8511 } 8512 return object; 8513 } 8514 function objSet(obj, path, value) { 8515 const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); 8516 pathArray.reduce((acc, key, i) => { 8517 if (acc[key] === undefined) acc[key] = {}; 8518 if (i === pathArray.length - 1) acc[key] = value; 8519 return acc[key]; 8520 }, obj); 8521 } 8522 function arrayEach(array, iteratee) { 8523 var index = -1; 8524 var length = array.length; 8525 while (++index < length) { 8526 if (iteratee(array[index], index, array) === false) { 8527 break; 8528 } 8529 } 8530 return array; 8531 } 8532 function selectPageRangeMode(e) { 8533 pageRange = e.matches ? mobilePageRange : desktopPageRange; 8534 renderPaginationList(); 8535 } 8536 paginationMql.addEventListener('change', selectPageRangeMode); 8537})('section-5d01005b-e93f-48f9-92b7-4822576c8b85', JSON.parse("{\"pageSize\":\"9\",\"resource\":\"posts\",\"pageRange\":1,\"variables\":{\"limit\":9,\"offset\":0,\"sortDir\":\"DESC\",\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"categoryId\":\"{{pageElement.categoryId}}\",\"order\":\"publishedAt\"},\"hidePaginationOnFirstPage\":true,\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"usePageResource\":false,\"sectionIndex\":3}")); 8538} catch(e) { 8539console.log(e); 8540} 8541 try { 8542(function init(sectionDomId) {})('section-5d01005b-e93f-48f9-92b7-4822576c8b85', 'undefined'); 8543} catch(e) { 8544console.log(e); 8545} 8546 try { 8547(function init(sectionDomId, options) { 8548 let section = null; 8549 switch (sectionDomId) { 8550 case 'NAVBAR': 8551 section = document.querySelector('body > nav, #global-navbar'); 8552 break; 8553 case 'FOOTER': 8554 section = document.querySelector('body > footer, #global-footer'); 8555 break; 8556 case 'SIDEMENU': 8557 section = document.querySelector('body > div.sidemenu, #global-sidemenu'); 8558 break; 8559 case 'CONTACT': 8560 section = document.querySelector('#modal-global-contact-us'); 8561 break; 8562 case 'MOBILE_CONTACT': 8563 section = document.querySelector('body > div.mobile-contact-wrapper, #global-mobile-contact'); 8564 break; 8565 case 'SUBSCRIBE': 8566 // Subscribe Not Implemented 8567 section = null; 8568 break; 8569 case 'ACCOUNT': 8570 // Account Not Implemented 8571 section = null; 8572 break; 8573 default: 8574 section = document.querySelector('#' + sectionDomId); 8575 } 8576 if (!section) { 8577 return; 8578 } 8579 8580 // Where content / items are rendered 8581 const dataContainer = section.querySelector('.data-container'); 8582 if (!dataContainer) { 8583 // no place to render items = no need to query server or do anything else 8584 return; 8585 } 8586 let variables = options.variables || {}; 8587 8588 // Some variables in options may not have been injected - remove 8589 Object.keys(variables).forEach(function (variableName) { 8590 if (typeof variables[variableName] === 'string' && variables[variableName].match(/^{{.*}}$/)) { 8591 delete variables[variableName]; 8592 } 8593 }); 8594 8595 // Merge page query variables 8596 if (options.usePageResource) { 8597 variables = { 8598 ...variables, 8599 ...window.luxuryPresence.config.pageMeta.pageQueryVariables[options.resource] 8600 }; 8601 } 8602 8603 // Pagination Executes on a Numbered Pagination Container 8604 // Or via a load more button 8605 let mode = 'BUTTONLESS_PAGINATION'; 8606 const container = section.querySelector('.pagination-container');
8607 const loadMoreButton = section.querySelector(options?.loadMoreSelector); 8608 if (container) { 8609 mode = 'NUMBERED_PAGINATION'; 8610 } 8611 if (loadMoreButton) { 8612 mode = 'LOAD_MORE_BUTTON'; 8613 } 8614 let searchContainer = section.querySelector('.search-container'); 8615 const hidePaginationOnFirstPage = options.hidePaginationOnFirstPage || false; 8616 const eventPrefix = '__pagination-'; 8617 const searchFormParams = options.searchFormParams || (options.resource === 'properties' ? [{ 8618 param: 'neighborhoodId', 8619 urlKey: 'neighborhood' 8620 }, { 8621 param: 'search', 8622 urlKey: 'search' 8623 }, { 8624 param: 'salesPriceGTE', 8625 urlKey: 'pricemin', 8626 formKey: 'priceMin', 8627 type: 'int' 8628 }, { 8629 param: 'salesPriceLTE', 8630 urlKey: 'pricemax', 8631 formKey: 'priceMax', 8632 type: 'int' 8633 }, { 8634 param: 'livingSpaceSizeGTE', 8635 urlKey: 'areamin', 8636 formKey: 'areaMin', 8637 type: 'int' 8638 }, { 8639 param: 'livingSpaceSizeLTE', 8640 urlKey: 'areamax', 8641 formKey: 'areaMax', 8642 type: 'int' 8643 }] : []); 8644 8645 // Total entries - initialized in first render 8646 let totalNumber = 0; 8647 const DefaultSearchParams = { 8648 page: 1 8649 }; 8650 searchFormParams.forEach(function (formParam) { 8651 DefaultSearchParams[formParam.formKey || formParam.param] = null; 8652 }); 8653 let searchParams = { 8654 ...DefaultSearchParams 8655 }; 8656 const offset = options.variables?.offset || 0; 8657 let scrollTarget = options.scrollTargetSelector ? section.querySelector(options.scrollTargetSelector) || section : section; 8658 let scrollOffset = (parseInt(options.scrollOffset, 10) || 0) ?? 100; 8659 8660 // entries of per page 8661 // when query variables for a given section would't be filled options.pageSize can store 8662 // template string, ex: "{{variables.limit}}", below code handles that case and set 9 as a default value 8663 let pageSize = parseInt(options.pageSize, 10) || 9; 8664 8665 // Adding mobile pageRange support 8666 var NARROW_BREAKPOINT = 600; 8667 var mobileBreakpoint = options.mobileBreakpoint > 0 ? options.mobileBreakpoint : NARROW_BREAKPOINT; 8668 var paginationMql = window.matchMedia(`(max-width: ${mobileBreakpoint}px)`); 8669 8670 // Page range (pages on both sides of the current page) 8671 var mobilePageRange = options.mobilePageRange > 0 ? options.mobilePageRange : 0; 8672 var desktopPageRange = options.pageRange >= 0 ? options.pageRange : 2; 8673 var pageRange = paginationMql.matches ? mobilePageRange : desktopPageRange; 8674 8675 // Whether to display the 'Previous' button 8676 var showPrevious = true; 8677 8678 // Whether to display the 'Next' button 8679 var showNext = true; 8680 8681 // Whether to display the page buttons 8682 var showPageNumbers = true; 8683 8684 // 'Previous' text 8685 var prevText = options.prevText || '«'; 8686 8687 // 'Next' text 8688 var nextText = options.nextText || '»'; 8689 8690 // Ellipsis text 8691 var ellipsisText = '…'; 8692 var classPrefix = 'paginationjs'; 8693 8694 // Default active class 8695 var activeClassName = 'active'; 8696 8697 // Default disable class 8698 var disableClassName = 'disabled'; 8699 8700 // Pagination element's position in the container 8701 var position = 'bottom'; 8702 8703 // Whether to trigger pagination at initialization 8704 var triggerPagingOnInit = !options.skipInitialLoad; 8705 var showFirstOnEllipsisShow = true; 8706 var showLastOnEllipsisShow = true; 8707 8708 // Instantiated in init() 8709 var el = null; 8710 8711 // Controls scrollTop logic 8712 var firstRender = true; 8713 var renderBySearch = false; 8714 var disabled = false; 8715 8716 // Pagination script class selector 8717 var paginationScriptSelector = options.paginationPartialName ? `.${options.paginationPartialName}` : '.pagination-script'; 8718 8719 // Property Specific Image Override if No Image 8720 var propertyPlaceholderImage = window.luxuryPresence.defaults.propertyPlaceholderImage; 8721 8722 // Construct template 8723 var templateHTML = section.querySelector(`script${paginationScriptSelector}`)?.innerHTML || ''; 8724 var template = Handlebars.compile(templateHTML); 8725 8726 // Initialize 8727 init(); 8728 function init() { 8729 Object.assign(searchParams, window.luxuryPresence.pagination.deserializeParams(searchFormParams, sectionDomId)); 8730 8731 // Append/prepend pagination element to the pagination container 8732 if (mode === 'NUMBERED_PAGINATION') { 8733 el = document.createElement('div'); 8734 el.className = 'paginationjs';
8735 container[position === 'bottom' ? 'append' : 'prepend'](el); 8736 } 8737 if (searchContainer) { 8738 initSearchContainer({ 8739 fetchNeighborhoods: searchContainer.dataset.fetchNeighborhoods === 'true', 8740 fetchPostCategories: searchContainer.dataset.fetchPostCategories === 'true', 8741 fetchOffices: searchContainer.dataset.fetchOffices === 'true', 8742 fetchRentalRates: searchContainer.dataset.fetchRentalRates === 'true' 8743 }, function () { 8744 syncSearchFormState(searchParams); 8745 }); 8746 } 8747 8748 // Bind events 8749 observer(); 8750 } 8751 function syncSearchFormState(params) { 8752 searchContainer.dispatchEvent(new CustomEvent('sync', { 8753 detail: params 8754 })); 8755 } 8756 function initSearchContainer(options, done) { 8757 var promises = []; 8758 if (options.fetchNeighborhoods) { 8759 promises.push(fetchAllNeighborhoods(function (neighborhoods) { 8760 searchContainer.dispatchEvent(new CustomEvent('neighborhoods-data', { 8761 detail: { 8762 data: neighborhoods.map(function (item) { 8763 return { 8764 value: item.id, 8765 label: item.name 8766 }; 8767 }) 8768 } 8769 })); 8770 })); 8771 } 8772 if (options.fetchPostCategories) { 8773 promises.push(fetchPostCategories(function (categories) { 8774 searchContainer.dispatchEvent(new CustomEvent('post-categories-data', { 8775 detail: { 8776 data: categories 8777 } 8778 })); 8779 })); 8780 } 8781 if (options.fetchOffices) { 8782 promises.push(fetchOffices(function (offices) { 8783 searchContainer.dispatchEvent(new CustomEvent('offices-data', { 8784 detail: { 8785 data: offices 8786 } 8787 })); 8788 })); 8789 } 8790 if (options.fetchRentalRates) { 8791 promises.push(fetchAllRentalRates(function (rentalRates) { 8792 searchContainer.dispatchEvent(new CustomEvent('rental-rates-data', { 8793 detail: { 8794 data: rentalRates 8795 } 8796 })); 8797 })); 8798 } 8799 if (promises.length > 0) { 8800 Promise.all(promises).then(function () { 8801 done(); 8802 }); 8803 } else { 8804 // element's custom code is not executed yet, so we're scheduling callback to after element's code 8805 setTimeout(done); 8806 } 8807 } 8808 function fetchAllNeighborhoods(done) { 8809 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8810 var gqlQuery = 'query ($companyId: String, $withProperties: NeighborhoodPropertiesFilter, $withBlogPosts: NeighborhoodBlogPostsFilter, $offset: Int, $limit: Int) {' + 'neighborhoods (companyId: $companyId, withProperties: $withProperties, withBlogPosts: $withBlogPosts, offset: $offset, limit: $limit) {id, name}}'; 8811 var payload = {}; 8812 payload.offset = 0; 8813 payload.limit = 100; 8814 payload.companyId = variables.companyId; 8815 if (options.resource === 'properties') { 8816 payload.withProperties = { 8817 active: true 8818 }; 8819 ['agentIds', 'tag', 'propertyTypeId', 'statusIds', 'architectureStyle', 'lifestyle', 'salesPriceGTE', 'salesPriceLTE', 'leasePriceGTE', 'leasePriceLTE', 'livingSpaceSizeGTE', 'livingSpaceSizeLTE', 'bathCountGTE', 'bathCountLTE', 'bedroomCountGTE', 'bedroomCountLTE', 'bathCountGTE', 'bathCountLTE', 'bedroomCountGTE', 'bedroomCountLTE', 'featuredListing', 'leaseProperty', 'neighborhoodId', 'developmentId', 'openHouse'].forEach(function (field) { 8820 if (variables[field] !== undefined && variables[field] !== null) { 8821 payload.withProperties[field] = variables[field]; 8822 } 8823 }); 8824 } 8825 if (options.resource === 'posts') { 8826 payload.withBlogPosts = { 8827 active: true 8828 }; 8829 ['search', 'postStatusId', 'featured', 'categoryId', 'categoryIds', 'neighborhoodId', 'slug'].forEach(function (field) { 8830 if (variables[field] !== undefined && variables[field] !== null) { 8831 payload.withBlogPosts[field] = variables[field]; 8832 } 8833 }); 8834 } 8835 superagent.post(url + '/graphql').withCredentials().send({ 8836 query: gqlQuery, 8837 variables: payload 8838 }).then(function (res) { 8839 var items = res.body.data && res.body.data.neighborhoods || []; 8840 done(items, items.length); 8841 }); 8842 } 8843 function fetchPostCategories(done) { 8844 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8845 var featured = options.resource === 'posts' && !!variables.featured; 8846 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'postCategories (companyId: $companyId, offset: $offset, limit: $limit ' + (featured ? ' withFeaturedPublishedOnly: true' : ' withPublishedOnly: true') + ') {id, name, publishedCount, featuredPublishedCount}}'; 8847 var payload = {}; 8848 payload.offset = 0; 8849 payload.limit = 100; 8850 payload.companyId = variables.companyId; 8851 superagent.post(url + '/graphql').withCredentials().send({ 8852 query: gqlQuery, 8853 variables: payload 8854 }).then(function (res) { 8855 var items = res.body.data && res.body.data.postCategories || []; 8856 done(items.map(function (item) { 8857 item.count = featured ? item.featuredPublishedCount : item.publishedCount; 8858 return item; 8859 }), items.length); 8860 }); 8861 } 8862 function fetchOffices(done) { 8863 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8864 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'offices (companyId: $companyId, offset: $offset, limit: $limit) {' + 'id, name, tags}}'; 8865 var payload = { 8866 offset: 0, 8867 limit: 100, 8868 companyId: variables.companyId 8869 }; 8870 superagent.post(url + '/graphql').withCredentials().send({ 8871 query: gqlQuery, 8872 variables: payload 8873 }).then(function (res) { 8874 var items = res.body.data && res.body.data.offices || []; 8875 done(items, items.length); 8876 }); 8877 } 8878 function fetchAllRentalRates(done) { 8879 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8880 var gqlQuery = 'query ($companyId: String, $offset: Int, $limit: Int) {' + 'rentalRates (companyId: $companyId, offset: $offset, limit: $limit) {rentalRateId, label}}'; 8881 var payload = { 8882 offset: 0, 8883 limit: 100, 8884 companyId: variables.companyId 8885 }; 8886 superagent.post(url + '/graphql').withCredentials().send({ 8887 query: gqlQuery, 8888 variables: payload 8889 }).then(function (res) { 8890 var items = res.body.data && res.body.data.rentalRates || []; 8891 done(items, items.length); 8892 }).catch(function (err) { 8893 console.error('Error fetching rental rates:', err); 8894 done([], 0); 8895 }); 8896 } 8897 function fetchData(params, done) { 8898 var url = options.url || window.luxuryPresence.defaults.apiGatewayUrl; 8899 var resource = options.resource; 8900 var gqlQuery = options.query || window.luxuryPresence.defaults.gql[resource]; 8901 var payload = {}; 8902 try { 8903 payload = JSON.parse(JSON.stringify(variables)); 8904 } catch (e) {
8905 Object.keys(variables).forEach(function (variableName) { 8906 payload[variableName] = variables[variableName]; 8907 }); 8908 } 8909 payload.offset = (params.page - 1) * pageSize + offset; 8910 payload.limit = pageSize; 8911 payload.companyId = options.companyId; 8912 searchFormParams.forEach(function (formParam) { 8913 var val = params[formParam.formKey || formParam.param]; 8914 if (val !== null && val !== undefined) { 8915 objSet(payload, formParam.param, val); 8916 } 8917 }); 8918 8919 // TODO: Disabled loading due to jarring effect 8920 // dataContainer.html('Loading...'); 8921 superagent.post(url + '/graphql').withCredentials().send({ 8922 query: gqlQuery, 8923 variables: payload 8924 }).then(function (res) { 8925 if (res.body.errors) { 8926 done(res.body.errors); 8927 return; 8928 } 8929 var data = res.body.data || {}; 8930 var resources = data[resource] || []; 8931 var resourceCount = data[resource + 'Count'] && data[resource + 'Count'].count ? data[resource + 'Count'].count : 0; 8932 if (resource === 'properties' && propertyPlaceholderImage?.id) { 8933 resources = resources.map(resource => { 8934 if (resource.media?.length === 0) { 8935 return Object.assign(resource, { 8936 media: [propertyPlaceholderImage] 8937 }); 8938 } 8939 return resource; 8940 }); 8941 } 8942 done(null, resources, resourceCount); 8943 }); 8944 } 8945 function getTotalPage() { 8946 return Math.ceil(totalNumber / pageSize); 8947 } 8948 function renderPaginationList() { 8949 var totalPages = getTotalPage(); 8950 var rangeStart = searchParams.page - pageRange; 8951 var rangeEnd = searchParams.page + pageRange; 8952 if (rangeEnd > totalPages) { 8953 rangeEnd = totalPages; 8954 rangeStart = totalPages - pageRange * 2; 8955 rangeStart = rangeStart < 1 ? 1 : rangeStart; 8956 } 8957 if (rangeStart <= 1) { 8958 rangeStart = 1; 8959 rangeEnd = Math.min(pageRange * 2 + 1, totalPages); 8960 } 8961 8962 // Making sure that 1 more page button is shown in edge positions 8963 if (pageRange === 0) { 8964 if (searchParams.page === totalPages) { 8965 rangeStart = totalPages <= 1 ? 1 : totalPages - 1; 8966 } 8967 if (searchParams.page === 1) { 8968 rangeEnd = Math.min(2, totalPages); 8969 } 8970 } 8971 8972 // Render pagination list 8973 if (mode === 'NUMBERED_PAGINATION') { 8974 el.innerHTML = generateHTML({ 8975 currentPage: searchParams.page, 8976 pageRange: pageRange, 8977 rangeStart: rangeStart, 8978 rangeEnd: rangeEnd 8979 }); 8980 if (totalPages < 2 && hidePaginationOnFirstPage) { 8981 container.style.display = 'none'; 8982 } else { 8983 container.style.display = ''; 8984 } 8985 } else if (mode === 'LOAD_MORE_BUTTON') { 8986 if (searchParams.page >= totalPages) { 8987 loadMoreButton.style.display = 'none'; 8988 } else { 8989 loadMoreButton.style.display = ''; 8990 } 8991 } 8992 } 8993 function render(data) { 8994 renderPaginationList(); 8995 8996 // Render new content with handlebars 8997 var html = ''; 8998 if (mode === 'LOAD_MORE_BUTTON' && searchParams.page > 1) { 8999 // If using load more, append search results to existing results 9000 html = dataContainer.innerHTML; 9001 } 9002 for (var i = 0; i < data.length; i++) { 9003 var itemData = recursivelyParse(data[i]); 9004 itemData.sectionIndex = options.sectionIndex; 9005 itemData.renderOptions = options.renderOptions; 9006 html += template(itemData); 9007 } 9008 dataContainer.innerHTML = html; 9009 9010 // Scroll Top 9011 if (!firstRender && mode === 'NUMBERED_PAGINATION' && (!renderBySearch || !options.skipScrollOnSearch)) { 9012 // Only animate to the top if we replace dataContainer (numbered pagination) 9013 setTimeout(() => { 9014 const scrollTargetOffsetTop = scrollTarget.getBoundingClientRect().top + window.scrollY - scrollOffset; 9015 window.scrollTo({ 9016 top: scrollTargetOffsetTop, 9017 behavior: 'smooth' 9018 }); 9019 }, 250); 9020 } 9021 return true; 9022 } 9023 function generateHTML(args) { 9024 const totalPages = getTotalPage(); 9025 const { 9026 rangeStart, 9027 rangeEnd 9028 } = args; 9029 const makeLnk = page =>
9029 window.luxuryPresence.pagination.serializeParams(searchFormParams, sectionDomId, { 9030 ...searchParams, 9031 page 9032 }); 9033 let html = ''; 9034 if (showPrevious || showPageNumbers || showNext) { 9035 html += '<div class="paginationjs-pages"><ul>'; 9036 9037 // Previous button 9038 if (showPrevious) { 9039 if (searchParams.page <= 1) { 9040 html += `<li class="${classPrefix}-prev ${disableClassName}"><a>${prevText}</a></li>`; 9041 } else { 9042 html += `<li class="${classPrefix}-prev J-paginationjs-previous" data-num="${searchParams.page - 1}" title="Previous page"><a href="${makeLnk(searchParams.page - 1)}">${prevText}</a></li>`; 9043 } 9044 } 9045 9046 // Pages 9047 if (showPageNumbers) { 9048 if (rangeStart <= 3) { 9049 for (let i = 1; i < rangeStart; i++) { 9050 html += `<li class="${classPrefix}-page J-paginationjs-page ${i === searchParams.page ? activeClassName : ''}" data-num="${i}"><a ${i === searchParams.page ? '' : `href="${makeLnk(i)}"`}>${i}</a></li>`; 9051 } 9052 } else { 9053 if (showFirstOnEllipsisShow) { 9054 html += `<li class="${classPrefix}-page ${classPrefix}-first J-paginationjs-page" data-num="1"><a href="${makeLnk(1)}">1</a></li>`; 9055 } 9056 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`; 9057 } 9058 for (let i = rangeStart; i <= rangeEnd; i++) { 9059 html += `<li class="${classPrefix}-page J-paginationjs-page ${i === searchParams.page ? activeClassName : ''}" data-num="${i}"><a ${i === searchParams.page ? '' : `href="${makeLnk(i)}"`}>${i}</a></li>`; 9060 } 9061 if (rangeEnd < totalPages - 2) { 9062 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`; 9063 if (showLastOnEllipsisShow) { 9064 html += `<li class="${classPrefix}-page ${classPrefix}-last J-paginationjs-page" data-num="${totalPages}"><a href="${makeLnk(totalPages)}">${totalPages}</a></li>`; 9065 } 9066 } else { 9067 for (let i = rangeEnd + 1; i <= totalPages; i++) { 9068 html += `<li class="${classPrefix}-page J-paginationjs-page" data-num="${i}"><a href="${makeLnk(i)}">${i}</a></li>`; 9069 } 9070 } 9071 } 9072 9073 // Next button 9074 if (showNext) { 9075 if (searchParams.page >= totalPages) { 9076 html += `<li class="${classPrefix}-next ${disableClassName}"><a>${nextText}</a></li>`; 9077 } else { 9078 html += `<li class="${classPrefix}-next J-paginationjs-next" data-num="${searchParams.page + 1}" title="Next page"><a href="${makeLnk(searchParams.page + 1)}">${nextText}</a></li>`; 9079 } 9080 } 9081 html += '</ul></div>'; 9082 } 9083 return html; 9084 } 9085 function go(newSearchParams, callback, restorationVisit) { 9086 if (disabled) return; 9087 var goToPageNumber = newSearchParams.page; 9088 9089 // Page number is out of bounds 9090 if (!goToPageNumber || goToPageNumber < 1) return; 9091 var totalPage = getTotalPage(); 9092 9093 // Page number is out of bounds 9094 if (!restorationVisit && totalNumber > 0) { 9095 if (goToPageNumber > totalPage) return; 9096 } 9097 if (!options.skipSerialization && (mode === 'NUMBERED_PAGINATION' || searchContainer)) { 9098 var newURL = window.luxuryPresence.pagination.serializeParams(searchFormParams, sectionDomId, { 9099 ...newSearchParams, 9100 page: goToPageNumber !== 1 && mode === 'NUMBERED_PAGINATION' ? newSearchParams.page : null 9101 }); 9102 if (!areQueryParamsEqual(newSearchParams, searchParams) && !restorationVisit) { 9103 window.history.pushState({}, '', window.luxuryPresence.getPath(newURL, { 9104 queryString: true 9105 })); 9106 } 9107 } 9108 9109 // Fetch and update page 9110 fetchData(newSearchParams, renderWithData); 9111 disabled = true; 9112 function renderWithData(err, data, count) { 9113 if (err) { 9114 dataContainer.dispatchEvent(new Event('error')); 9115 } else { 9116 searchParams = { 9117 ...newSearchParams 9118 }; 9119 totalNumber = count - offset; 9120 if (data.length === 0) { 9121 dataContainer.dispatchEvent(new Event('no-data')); 9122 } else {
9123 dataContainer.dispatchEvent(new Event('data')); 9124 } 9125 render(data); 9126 firstRender = false; 9127 } 9128 disabled = false; 9129 renderBySearch = false; 9130 } 9131 } 9132 function areQueryParamsEqual(first, second) { 9133 var differ = Object.keys(first).find(function (key) { 9134 return first[key] !== undefined && first[key] !== null && first[key] !== second[key]; 9135 }); 9136 if (differ) { 9137 return false; 9138 } 9139 differ = Object.keys(second).find(function (key) { 9140 return second[key] !== undefined && second[key] !== null && second[key] !== first[key]; 9141 }); 9142 return !differ; 9143 } 9144 function next(callback) { 9145 go({ 9146 ...searchParams, 9147 page: searchParams.page + 1 9148 }, callback); 9149 } 9150 function previous(callback) { 9151 go({ 9152 ...searchParams, 9153 page: searchParams.page - 1 9154 }, callback); 9155 } 9156 function refresh(callback) { 9157 go(searchParams, callback); 9158 } 9159 function update(params, callback) { 9160 renderBySearch = true; 9161 go(params, callback); 9162 } 9163 function observer() { 9164 if (searchContainer) { 9165 searchContainer.addEventListener('search', function (event, newSearchParams) { 9166 newSearchParams = event.detail || newSearchParams; 9167 update({ 9168 ...searchParams, 9169 ...newSearchParams, 9170 page: 1 9171 }); 9172 }); 9173 searchContainer.addEventListener('clear-search', function () { 9174 const defaultSearchParams = { 9175 ...DefaultSearchParams 9176 }; 9177 const isCurrentSearchFormStateDefault = areQueryParamsEqual({ 9178 ...defaultSearchParams, 9179 page: null 9180 }, { 9181 ...searchParams, 9182 page: null 9183 }); 9184 9185 // if mode is LOAD_MORE_BUTTON we should't make anything on clear 9186 // when search form have default state 9187 if (isCurrentSearchFormStateDefault && mode !== 'NUMBERED_PAGINATION') { 9188 return; 9189 } 9190 9191 // if someone initially go to some page and click 'Clear' on search from 9192 // we should't navigate to first page. We should preserve page value 9193 if (isCurrentSearchFormStateDefault) { 9194 defaultSearchParams.page = searchParams.page; 9195 } 9196 syncSearchFormState(defaultSearchParams); 9197 update(defaultSearchParams); 9198 }); 9199 } 9200 if (mode === 'LOAD_MORE_BUTTON') { 9201 loadMoreButton.addEventListener('click', function (event) { 9202 go({ 9203 ...searchParams, 9204 page: searchParams.page + 1 9205 }); 9206 return false; 9207 }); 9208 } 9209 if (mode === 'LOAD_MORE_BUTTON' || mode === 'BUTTONLESS_PAGINATION') { 9210 // Do initial render 9211 go(Object.assign({}, searchParams, { 9212 page: 1 9213 })); 9214 return; 9215 } 9216 if (mode === 'NUMBERED_PAGINATION') { 9217 // Go to specified page number 9218 container.addEventListener(eventPrefix + 'go', function (event) { 9219 let { 9220 pageNumber, 9221 done 9222 } = event.detail || {}; 9223 pageNumber = parseInt(pageNumber, 10); 9224 if (!pageNumber) { 9225 return; 9226 } 9227 go({ 9228 ...searchParams, 9229 page: pageNumber 9230 }, done); 9231 }); 9232 9233 // Page number button click 9234 el.addEventListener('click', function (event) { 9235 let clickTarget = event.target; 9236 let current = clickTarget.closest('.J-paginationjs-page, .J-paginationjs-previous, .J-paginationjs-next'); 9237 let pageNumber = current?.getAttribute('data-num'); 9238 if (!pageNumber || current.classList.contains(disableClassName) || current.classList.contains(activeClassName)) { 9239 return; 9240 } else { 9241 go({ 9242 ...searchParams, 9243 page: parseInt(pageNumber, 10) 9244 }); 9245 } 9246 event.preventDefault(); 9247 }); 9248 9249 // Whether to load the default page 9250 var defaultPageNumber = searchParams.page; 9251 if (triggerPagingOnInit) { 9252 container.dispatchEvent(new CustomEvent(eventPrefix + 'go', { 9253 detail: { 9254 pageNumber: defaultPageNumber 9255 } 9256 })); 9257 } 9258 } 9259 window.addEventListener('popstate', function (event) { 9260 var newParams = Object.assign({}, DefaultSearchParams, window.luxuryPresence.pagination.deserializeParams(searchFormParams, sectionDomId)); 9261 if (!areQueryParamsEqual(newParams, searchParams)) { 9262 go(newParams, null, true); 9263 if (searchContainer) { 9264 syncSearchFormState(newParams); 9265 } 9266 } 9267 }); 9268 } 9269 function replaceVariables(template, variables) { 9270 var formattedString; 9271 for (var key in variables) { 9272 var value = variables[key]; 9273 var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img'); 9274 formattedString = (formattedString || template).replace(regexp, value); 9275 } 9276 return formattedString; 9277 } 9278 function isTextEditorValue(obj) { 9279 return 'raw' in obj || 'gjs' in obj; 9280 } 9281 function getTextEditorValue(obj) { 9282 if (!obj.html) { 9283 return ''; 9284 } 9285 var data = obj.html; 9286 var css = (obj.raw || obj.gjs || {}).css; 9287 if (css) { 9288 data = data + '<style>' + css + '</style>'; 9289 } 9290 return data; 9291 } 9292 9293 /** 9294 * check rendered resources for html and raw properties that could indicate if resource is from an advanced text editor. 9295 * strips out raw proprties and returns only HTML if it exists. 9296 * @param {Object} resources a collection retrieved from the database 9297 * @return {Object} the newly modified object. 9298 */ 9299 function recursivelyParse(resources) {
9300 forEach(resources, function (value, key) { 9301 if (!value) { 9302 return; 9303 } 9304 if (value instanceof Object) { 9305 if (isTextEditorValue(value)) { 9306 resources[key] = getTextEditorValue(value); 9307 return; 9308 } else { 9309 return recursivelyParse(value); 9310 } 9311 } 9312 try { 9313 var parsedData = JSON.parse(value); 9314 if (isTextEditorValue(parsedData)) { 9315 resources[key] = getTextEditorValue(parsedData); 9316 } 9317 } catch (e) { 9318 // do nothing if we failed to parse value as JSON 9319 } 9320 }); 9321 return resources; 9322 } 9323 function forEach(collection, iteratee) { 9324 var func = Array.isArray(collection) ? arrayEach : baseFor; 9325 return func(collection, iteratee); 9326 } 9327 function baseFor(object, iteratee) { 9328 if (!object) { 9329 return; 9330 } 9331 var iterable = Object(object); 9332 var props = Object.keys(object); 9333 var length = props.length; 9334 var index = -1; 9335 while (length--) { 9336 var key = props[++index]; 9337 if (iteratee(iterable[key], key, iterable) === false) { 9338 break; 9339 } 9340 } 9341 return object; 9342 } 9343 function objSet(obj, path, value) { 9344 const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); 9345 pathArray.reduce((acc, key, i) => { 9346 if (acc[key] === undefined) acc[key] = {}; 9347 if (i === pathArray.length - 1) acc[key] = value; 9348 return acc[key]; 9349 }, obj); 9350 } 9351 function arrayEach(array, iteratee) { 9352 var index = -1; 9353 var length = array.length; 9354 while (++index < length) { 9355 if (iteratee(array[index], index, array) === false) { 9356 break; 9357 } 9358 } 9359 return array; 9360 } 9361 function selectPageRangeMode(e) { 9362 pageRange = e.matches ? mobilePageRange : desktopPageRange; 9363 renderPaginationList(); 9364 } 9365 paginationMql.addEventListener('change', selectPageRangeMode); 9366})('section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda', JSON.parse("{\"pageSize\":\"9\",\"resource\":\"posts\",\"pageRange\":1,\"variables\":{\"limit\":9,\"offset\":0,\"sortDir\":\"DESC\",\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"categoryId\":\"{{pageElement.categoryId}}\",\"order\":\"publishedAt\"},\"hidePaginationOnFirstPage\":true,\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"usePageResource\":false,\"sectionIndex\":4}")); 9367} catch(e) { 9368console.log(e); 9369} 9370 try { 9371(function init(sectionDomId) {})('section-c0d58c8d-bb8c-453f-9168-aa08a1cecdda', 'undefined'); 9372} catch(e) { 9373console.log(e); 9374} 9375 try { 9376(function init(sectionDomId) { 9377 const $section = $('#' + sectionDomId); 9378 if ($section.find('.parallax').length) $section.addClass('parallax-enabled'); 9379 if ($section.find('.video-wrapper').length) $section.addClass('video'); 9380})('section-42ff6c98-9b79-4ecf-b1d4-503fd5a6e14d', 'undefined'); 9381} catch(e) { 9382console.log(e); 9383} 9384 try { 9385(function init(sectionDomId, options) { 9386 let section = null; 9387 switch (sectionDomId) { 9388 case 'NAVBAR': 9389 section = document.querySelector('body > nav, #global-navbar'); 9390 break; 9391 case 'FOOTER': 9392 section = document.querySelector('body > footer, #global-footer'); 9393 break; 9394 case 'SIDEMENU': 9395 section = document.querySelector('body > div.sidemenu, #global-sidemenu'); 9396 break; 9397 case 'CONTACT': 9398 section = document.querySelector('#modal-global-contact-us'); 9399 break; 9400 case 'MOBILE_CONTACT': 9401 section = document.querySelector('body > div.mobile-contact-wrapper, #global-mobile-contact'); 9402 break; 9403 case 'SUBSCRIBE': 9404 // Subscribe Not Implemented 9405 section = null; 9406 break; 9407 case 'ACCOUNT': 9408 // Account Not Implemented 9409 section = null; 9410 break; 9411 default: 9412 section = document.querySelector('#' + sectionDomId); 9413 } 9414 if (!section) { 9415 return; 9416 } 9417 var url = window.luxuryPresence.defaults.routerUrl; 9418 var companyId = window.luxuryPresence.defaults.companyId; 9419 var limit = options.limit || 8; 9420 if (typeof limit === 'string') { 9421 limit = parseInt(limit, 10) || 8; // this NaN protection also filters out 0 - this is expected 9422 } 9423 var templateSelector = options.templateSelector || '.jsIGTemplate'; 9424 var containerSelector = options.containerSelector || '.jsIGContainer'; 9425 var hasDataClass = options.hasDataClass || 'has-data'; 9426 var hideActivateMessage = options.hasDataClass || false; 9427 var payload = { 9428 companyId: companyId, 9429 limit: limit 9430 }; 9431 if (options.resourceId && typeof options.resourceId === 'string' && !
9431options.resourceId.match(/^{{.*}}$/)) { 9432 payload.resourceId = options.resourceId; 9433 // Federated enum values are uppercase (AGENT/TEAM); element configs carry lowercase 9434 payload.resource = String(options.resource || 'agent').toUpperCase(); 9435 payload.fallbackToNoResource = options.fallbackToNoResource === 'true' || options.fallbackToNoResource === true; 9436 } 9437 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'; 9438 var templateHTML = section.querySelector(templateSelector)?.innerHTML || ''; 9439 var template = Handlebars.compile(templateHTML); 9440 var container = section.querySelector(containerSelector); 9441 if (options.variables) { 9442 Object.keys(options.variables).forEach(function (key) { 9443 if (options.variables.hasOwnProperty(key) && (typeof options.variables[key] !== 'string' || !options.variables[key].match(/^{{.*}}$/))) { 9444 payload[key] = options.variables[key]; 9445 } 9446 }); 9447 } 9448 function getData() { 9449 var feed = { 9450 error: true 9451 }; 9452 if (payload.companyId == null || payload.companyId === '') { 9453 // instagramContentFeed requires companyId (ID!) â nothing to fetch without it. 9454 // Checked after the options.variables merge, which may override the default. 9455 // Deferred so element listeners (attached after this template runs) still receive it. 9456 setTimeout(function () { 9457 section.dispatchEvent(new CustomEvent('ig-data', { 9458 detail: feed 9459 })); 9460 }, 0); 9461 return; 9462 } 9463 superagent.post(url + '/graphql').send({ 9464 query: query, 9465 variables: payload 9466 }).then(res => { 9467 try { 9468 if (!res.body.errors) { 9469 feed = res.body.data.instagramContentFeed; 9470 if (!feed.active) { 9471 if (!hideActivateMessage && sectionDomId === 'section-temp' && container) { 9472 if (payload.resourceId) { 9473 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>'; 9474 } else { 9475 container.innerHTML = '<div style="text-align: center">Instagram account is not connected.<br /> Connect it on <a href="/profile">Profile page</a></div>'; 9476 } 9477 } 9478 } else { 9479 if (feed.items.length) { 9480 if (hasDataClass) { 9481 section.classList.add(hasDataClass); 9482 } 9483 const feedName = feed.username; 9484 section.querySelectorAll('.jsIGLink').forEach(link => link.setAttribute('href', feed.feedUrl)); 9485 section.querySelectorAll('.jsIGName').forEach(name => { 9486 name.textContent = '@' + feedName; 9487 name.setAttribute('title', '@' + feedName); 9488 }); 9489 if (container && template) { 9490 container.innerHTML = template(feed); 9491 } 9492 } 9493 } 9494 } 9495 } catch (e) {} 9496 section.dispatchEvent(new CustomEvent('ig-data', { 9497 detail: feed 9498 })); 9499 }).catch(e => { 9500 console.log('err', e); 9501 section.dispatchEvent(new CustomEvent('ig-data', { 9502 detail: feed 9503 })); 9504 }); 9505 } 9506 getData(); 9507})('section-dc35c8ba-2e7a-45c2-9887-4d2125099be8', JSON.parse("{\"companyId\":\"2c02232f-2988-4a2c-a284-887b44b8c4fc\",\"sectionIndex\":6}")); 9508} catch(e) { 9509console.log(e); 9510} 9511 try { 9512(function init(sectionDomId) { 9513 $("#" + sectionDomId).on("ig-data", function () { 9514 let title = $("#" + sectionDomId + " .js-slider-holder").data("title"); 9515 if (title) { 9516 $("#" + sectionDomId + " .js-h3").text(title); 9517 } 9518 if (window.matchMedia("(max-width: 768px)").matches) { 9519 $("#" + sectionDomId + " .js-slider-holder").before($("#" + sectionDomId + " .js-title-group").clone()); 9520 } 9521 $("#" + sectionDomId + " .js-collection .js-title-group").remove(); 9522 function buildSlick() { 9523 $('#' + sectionDomId + ' .js-collection').slick({ 9524 autoplay: true,
9525 arrows: false, 9526 speed: 0, 9527 autoplaySpeed: 6000, 9528 dots: false, 9529 infinite: true, 9530 slidesToShow: 4, 9531 slidesToScroll: 4, 9532 cssEase: 'linear', 9533 variableWidth: true, 9534 centerMode: false, 9535 swipe: false, 9536 responsive: [{ 9537 breakpoint: 1024, 9538 settings: { 9539 slidesToShow: 2, 9540 slidesToScroll: 2 9541 } 9542 }, { 9543 breakpoint: 768, 9544 settings: { 9545 slidesToShow: 4, 9546 slidesToScroll: 4 9547 } 9548 }] 9549 }); 9550 } 9551 if ($("#" + sectionDomId + " .js-collection").length) { 9552 if ($("#" + sectionDomId + " .js-collection").hasClass('slick-initialized')) { 9553 $("#" + sectionDomId + " .js-collection").slick('unslick'); 9554 buildSlick(); 9555 } else { 9556 buildSlick(); 9557 } 9558 } 9559 }); 9560})('section-dc35c8ba-2e7a-45c2-9887-4d2125099be8', 'undefined'); 9561} catch(e) { 9562console.log(e); 9563} 9564 9565 var { body } = document; 9566 var globalNav = body.querySelector(':scope > nav'); 9567 var globalSideMenu = body.querySelector(':scope > div.sidemenu'); 9568 9569 document.querySelectorAll('body > .modal .close').forEach(modalCloseBtn => { 9570 modalCloseBtn.addEventListener('click', function() { 9571 window.lpUI.hideClosestModal(modalCloseBtn); 9572 }); 9573 }); 9574 9575 window.addEventListener('click', function() { 9576 document.querySelectorAll('.share-popup').forEach(sharedPopup => { 9577 sharedPopup.style.display = 'none' ; 9578 }); 9579 }); 9580 9581 /* Misc Partials / Addons */ 9582 function initWOW() { 9583 if (wow) { 9584 wow.init(); 9585 wow.scrollHandler(); 9586 setTimeout(function () { 9587 const wowHideStyle = document.getElementById('wow-hide-elements'); 9588 if(wowHideStyle) { 9589 wowHideStyle.disabled = true; 9590 } 9591 }, 1); 9592 } 9593 } 9594 9595 const wowHideElements = document.getElementById('wow-hide-elements'); 9596 if (wowHideElements) { 9597 wowHideElements.disabled = false; 9598 } 9599 9600 initWOW(); 9601 9602 /* Generic Navbar Handling */ 9603 globalNav?.querySelectorAll('.hamburger-component').forEach(hamburgerComponent => { 9604 hamburgerComponent.addEventListener('click', function() { 9605 window.lpUI.sideMenuToggle(); 9606 }); 9607 }); 9608 globalSideMenu?.querySelectorAll('.toggle').forEach(toggleEl => { 9609 toggleEl.addEventListener('click', function() { 9610 window.lpUI.sideMenuToggle(); 9611 }); 9612 }); 9613 9614 document.querySelectorAll('body > nav .sub-nav-container > a, body > div.sidemenu .sub-nav-container > a').forEach(linkEl => { 9615 linkEl.addEventListener('click', function(e) { 9616 e.preventDefault(); 9617 e.stopPropagation(); 9618 var subNav = linkEl.parentElement.querySelector(':scope > .sub-nav'); 9619 if (subNav && subNav.classList.contains('visible')){ 9620 subNav.classList.remove('visible'); 9621 } else { 9622 globalNav?.querySelectorAll('.sub-nav-container .sub-nav').forEach(globalSubNav => { 9623 globalSubNav.classList.remove('visible'); 9624 }); 9625 globalSideMenu?.querySelectorAll('.sub-nav').forEach(sideMenuSubNav => { 9626 sideMenuSubNav.classList.remove('visible'); 9627 }); 9628 subNav?.classList.add('visible'); 9629 } 9630 }); 9631 }); 9632 window.addEventListener('click', function() { 9633 document.querySelectorAll('nav .sub-nav.visible, .sidemenu .sub-nav.visible').forEach(subNavEl => { 9634 subNavEl.classList.remove('visible'); 9635 }); 9636 }); 9637 9638 } 9639 9640 document.addEventListener('DOMContentLoaded', lpGlobalInit); 9641 9642 </script>
9642 9643 9644
9644<script type="module"> 9645 function selectorExists(selector) { 9646 return document.querySelector(selector) !== null; 9647 } 9648 9649 // Function to initialize the third-party library 9650 function initParoller(selector = '.my-paroller') { 9651 var $parollerElements = $(selector); 9652 if ($parollerElements.length > 0 && typeof $parollerElements.paroller === 'function') { 9653 $parollerElements.paroller(); 9654 } 9655 } 9656 9657 const parollerSelector = '.my-paroller'; 9658 9659 if (selectorExists(parollerSelector)) { 9660 window.luxuryPresence.libs.loadJs(window.luxuryPresence.libs.jsLibsMap.paroller.jsSrc, function() { 9661 initParoller(parollerSelector); 9662 window.luxuryPresence.libs.jsLibsMap.paroller.isLoaded = true; 9663 }); 9664 } 9665 </script>
9665 9666 9667 <!-- Google Tag Manager (noscript) -->
vendor: 80 bytes, lines 9667-9668
9667 9668 <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
9668GTM-5P9WP78
vendor: 107 bytes, lines 9668-9670
9668" 9669 height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 9670
9670<!-- End Google Tag Manager --> 9671 <!-- Google Tag Manager (noscript) -->
vendor: 80 bytes, lines 9671-9672
9671 9672 <noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
9672GTM-T9SFQGHZ
vendor: 107 bytes, lines 9672-9674
9672" 9673 height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> 9674
9674<!-- End Google Tag Manager --> 9675
9676<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:'a4816c15ff5532f4',t:'MTc5MTU5MTIzOA=='};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>
9676<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v4bc70e2c01a94c73b74392e4234840661791215815920" integrity="sha512-L0ha0OXavK/8okipN9F8BtP84dg9DUhPERbBXzwI6dgTA55d2+yweo3pn5CSFYs45/r8md2+xvUPtTdvTNRfjA==" data-cf-beacon='{"version":"2024.11.0","token":"d2a8f4b3d65944edb8fcd6ffce49ee5e","spa":2}' crossorigin="anonymous"></script>
9676 9677</body> 9678</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.