PageSourceSearch

https://www.rupcoeplumbing.com/

html rupcoeplumbing.com collected 2026-10-09 01:00:46 UTC 736,250 bytes, 17,639 lines download raw bytes

1<!doctype html >
2<html xmlns="http://www.w3.org/1999/xhtml" lang="en"
3      class="">
4<head>
5    <meta charset="utf-8">
6    
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43<script type="text/javascript">
44    window._currentDevice = 'desktop';
45    window.Parameters = window.Parameters || {
46        HomeUrl: 'https://www.rupcoeplumbing.com/',
47
48        SystemID: 'US_HIBU_PRODUCTION',
49        SiteAlias: 'b4d59456c20346899f3ab9056d40b845',
50        SiteType: atob('RFVEQU9ORQ=='),
51        PublicationDate: 'Oct 08, 2026',
52        ExternalUid: '700152202',
53        IsSiteMultilingual: false,
54        InitialPostAlias: '',
55        InitialPostPageUuid: '',
56        InitialDynamicItem: '',
57        DynamicPageInfo: {
58            isDynamicPage: false,
59            base64JsonRowData: 'null',
60        },
61        InitialPageAlias: 'home',
62        InitialPageUuid: 'b196ca30b540442ba4addf5cc10c1c6b',
63        InitialPageId: '117972428',
64        InitialEncodedPageAlias: 'aG9tZQ==',
65        InitialHeaderUuid: '2eaf9b234a934b2ab0d86a93f4be699c',
66        CurrentPageUrl: '',
67        IsCurrentHomePage: true,
68        ThemeVersion: '500000',
69        DefaultPageAlias: '',
70        RemoveDID: true,
71        WidgetStyleID: null,
72        IsHeaderFixed: false,
73        IsHeaderSkinny: false,
74        StorePagesUrls: 'e30=',
75        IsNewStore: 'false',
76        StorePath: '',
77        StoreId: 'null',
78        StoreVersion: 0,
79        StoreBaseUrl: '',
80        StoreCleanUrl: true,
81        StoreDisableScrolling: true,
82        IsStoreSuspended: false,
83        showCookieNotification: false,
84        cookiesNotificationMarkup: 'null',
85        translatedPageUrl: '',
86        sidebarPosition: 'NA',
87        currentLanguage: 'en',
88        currentLocale: 'en',
89        NavItems: '{}',
90        
91        mapConsent: {
92            message: 'This content is served by a third party, {0}. If enabled, {0} may collect information about your activity.',
93            enable: 'Enable'
94        },
95        NavigationAreaParams: {
96            ShowBackToHomeOnInnerPages: true,
97            NavbarSize: -1,
98            NavbarLiveHomePage: 'https://www.rupcoeplumbing.com/',
99            NavbarSelector: '#dmNav:has(a)',
100            SubNavbarSelector: '#subnav_main'
101        },
102        planID: '771',
103        disableTracking: false,
104        pageType: 'FROM_SCRATCH',
105        isRuntimeServer: true,
106        isInEditor: false,
107        isInPreview: false,
108        hasNativeStore: false,
109        defaultLang: 'en',
110        hamburgerMigration: null,
111        isFlexSite: true
112    };
113
114    window.Parameters.LayoutID = {};
115    window.Parameters.LayoutID[window._currentDevice] = 30;
116    window.Parameters.LayoutVariationID = {};
117    window.Parameters.LayoutVariationID[window._currentDevice] = 5;
118</script>
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141<!-- Injecting site-wide to the head -->
142
143
144<meta name="google-site-verification" content="CCSqMzMmZlae021Ky_3Qo73NhXQ-oRxyFRYi1NwnzDU" data-swh-uuid="aa68b5f8e18b476dad208183b1fd0011"/>
145
146
147
148<!-- End Injecting site-wide to the head -->
149
150
151
152
153
154<meta name="d-service-worker" content="register">
155
156
157
158<link rel="canonical" href="https://www.rupcoeplumbing.com/">
159
160<meta id="view" name="viewport" content="initial-scale=1, minimum-scale=1, maximum-scale=5, viewport-fit=cover">
161
162<meta name="apple-mobile-web-app-capable" content="yes">
163
164
165
166<link rel="icon" type="image/x-icon" href="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/site_favicon_16_1790958469378.ico"/>
167
168
169<link rel="preconnect" href="https://le-cdn.hibuwebsites.com"/>
170<link rel="preconnect" href="https://cdn.hibuwebsites.com" crossorigin/>
171<link rel="preconnect" href="https://static-res-cdn.websites.hibu.com"/>
172<link rel="preconnect" href="https://cdn.hibuwebsites.com"/>
173
174
175
176
177<script id='d-js-dmapi'>
178    window.SystemID = 'US_HIBU_PRODUCTION';
179
180    if (!window.dmAPI) {
181        window.dmAPI = {
182            registerExternalRuntimeComponent: function () {
183            },
184            getCurrentDeviceType: function () {
185                return window._currentDevice;
186            },
187            runOnReady: (ns, fn) => {
188                const safeFn = dmAPI.toSafeFn(fn);
189                ns = ns || 'global_' + Math.random().toString(36).slice(2, 11);
190                const eventName = 'afterAjax.' + ns;
191
192                if (document.readyState === 'complete') {
193                    $.DM.events.off(eventName).on(eventName, safeFn);
194                    setTimeout(function () {
195                        safeFn({
196                            isAjax: false,
197                        });
198                    }, 0);
199                } else {
200                    window?.waitForDeferred?.('dmAjax', () => {
201                        $.DM.events.off(eventName).on(eventName, safeFn);
202                        safeFn({
203                            isAjax: false,
204                        });
205                    });
206                }
207            },
208            toSafeFn: (fn) => {
209                if (fn?.safe) {
210                    return fn;
211                }
212                const safeFn = function (...args) {
213                    try {
214                        return fn?.apply(null, args);
215                    } catch (e) {
216                        console.log('function failed ' + e.message);
217                    }
218                };
219                safeFn.safe = true;
220                return safeFn;
221            }
222        };
223    }
224
225    if (!window.requestIdleCallback) {
226        window.requestIdleCallback = function (fn) {
227            setTimeout(fn, 0);
228        }
229    }
230</script>
230
231
232
233
234
235
236
237
238
239<script id="d-js-load-css">
240/**
241 * There are a few <link> tags with CSS resource in them that are preloaded in the page
242 * in each of those there is a "onload" handler which invokes the loadCSS callback
243 * defined here.
244 * We are monitoring 3 main CSS files - the runtime, the global and the page.
245 * When each load we check to see if we can append them all in a batch. If threre
246 * is no page css (which may happen on inner pages) then we do not wait for it
247 */
248(function () {
249  let cssLinks = {};
250  function loadCssLink(link) {
251    link.onload = null;
252    link.rel = "stylesheet";
253    link.type = "text/css";
254  }
255  
256    function checkCss() {
257      const pageCssLink = document.querySelector("[id*='CssLink']");
258      const widgetCssLink = document.querySelector("[id*='widgetCSS']");
259
260        if (cssLinks && cssLinks.runtime && cssLinks.global && (!pageCssLink || cssLinks.page) && (!widgetCssLink || cssLinks.widget)) {
261            const storedRuntimeCssLink = cssLinks.runtime;
262            const storedPageCssLink = cssLinks.page;
263            const storedGlobalCssLink = cssLinks.global;
264            const storedWidgetCssLink = cssLinks.widget;
265
266            storedGlobalCssLink.disabled = true;
267            loadCssLink(storedGlobalCssLink);
268
269            if (storedPageCssLink) {
270                storedPageCssLink.disabled = true;
271                loadCssLink(storedPageCssLink);
272            }
273
274            if(storedWidgetCssLink) {
275                storedWidgetCssLink.disabled = true;
276                loadCssLink(storedWidgetCssLink);
277            }
278
279            storedRuntimeCssLink.disabled = true;
280            loadCssLink(storedRuntimeCssLink);
281
282            requestAnimationFrame(() => {
283                setTimeout(() => {
284                    storedRuntimeCssLink.disabled = false;
285                    storedGlobalCssLink.disabled = false;
286                    if (storedPageCssLink) {
287                      storedPageCssLink.disabled = false;
288                    }
289                    if (storedWidgetCssLink) {
290                      storedWidgetCssLink.disabled = false;
291                    }
292                    // (SUP-4179) Clear the accumulated cssLinks only when we're
293                    // sure that the document has finished loading and the document 
294                    // has been parsed.
295                    if(document.readyState === 'interactive') {
296                      cssLinks = null;
297                    }
298                }, 0);
299            });
300        }
301    }
302  
303
304  function loadCSS(link) {
305    try {
306      var urlParams = new URLSearchParams(window.location.search);
307      var noCSS = !!urlParams.get("nocss");
308      var cssTimeout = urlParams.get("cssTimeout") || 0;
309
310      if (noCSS) {
311        return;
312      }
313      if (link.href && link.href.includes("d-css-runtime")) {
314        cssLinks.runtime = link;
315        checkCss();
316      } else if (link.id === "siteGlobalCss") {
317        cssLinks.global = link;
318        checkCss();
319      } 
320      
321      else if (link.id && link.id.includes("CssLink")) {
322        cssLinks.page = link;
323        checkCss();
324      } else if (link.id && link.id.includes("widgetCSS")) {
325        cssLinks.widget = link;
326        checkCss();
327      }
328      
329      else {
330        requestIdleCallback(function () {
331          window.setTimeout(function () {
332            loadCssLink(link);
333          }, parseInt(cssTimeout, 10));
334        });
335      }
336    } catch (e) {
337      throw e
338    }
339  }
340  window.loadCSS = window.loadCSS || loadCSS;
341})();
342</script>
342
343
344
345
346<script data-role="deferred-init" type="text/javascript">
347    /* usage: window.getDeferred(<deferred name>).resolve() or window.getDeferred(<deferred name>).promise.then(...)*/
348    function Def() {
349        this.promise = new Promise((function (a, b) {
350            this.resolve = a, this.reject = b
351        }).bind(this))
352    }
353
354    const defs = {};
355    window.getDeferred = function (a) {
356        return null == defs[a] && (defs[a] = new Def), defs[a]
357    }
358    window.waitForDeferred = function (b, a, c) {
359        let d = window?.getDeferred?.(b);
360        d
361            ? d.promise.then(a)
362            : c && ["complete", "interactive"].includes(document.readyState)
363                ? setTimeout(a, 1)
364                : c
365                    ? document.addEventListener("DOMContentLoaded", a)
366                    : console.error(`Deferred  does not exist`);
367    };
368</script>
368
369<style id="forceCssIncludes">
370    /* This file is auto-generated from a `scss` file with the same name */
371
372.videobgwrapper{overflow:hidden;position:absolute;z-index:0;width:100%;height:100%;top:0;left:0;pointer-events:none;border-radius:inherit}.videobgframe{position:absolute;width:101%;height:100%;top:50%;left:50%;transform:translateY(-50%) translateX(-50%);object-fit:fill}#dm video.videobgframe{margin:0}@media (max-width:767px){.dmRoot .dmPhotoGallery.newPhotoGallery:not(.photo-gallery-done){min-height:80vh}}@media (min-width:1025px){.dmRoot .dmPhotoGallery.newPhotoGallery:not(.photo-gallery-done){min-height:45vh}}@media (min-width:768px) and (max-width:1024px){.responsiveTablet .dmPhotoGallery.newPhotoGallery:not(.photo-gallery-done){min-height:45vh}}#dm [data-show-on-page-only]{display:none!important}#dmRoot div.stickyHeaderFix div.site_content{margin-top:0!important}#dmRoot div.stickyHeaderFix div.hamburger-header-container{position:relative}@media (min-width:768px) and (max-width:1024px){.responsiveTablet #dm .dmInner .hide-for-medium,[data-hidden-on-tablet]{display:none!important}}
373    
374
375</style>
376<style id="cssVariables" type="text/css">
377    :root {
378  --color_1: rgba(0, 0, 0, 1);
379  --color_10: rgba(255,255,255,1);
380  --color_11: rgba(0,0,0,1);
381  --color_12: rgba(235,32,36,1);
382  --color_13: rgba(235,32,36,1);
383  --color_14: rgba(255, 200, 52, 1);
384  --color_15: rgba(138,138,138,1);
385  --color_16: rgba(0,0,0,1);
386  --color_17: rgba(0,0,0,1);
387  --color_18: rgba(235,32,36,1);
388  --color_19: rgba(0,0,0,1);
389  --color_2: rgba(255, 255, 255, 1);
390  --color_20: rgba(42, 235, 21, 1);
391  --color_21: rgba(255, 255, 255, 1);
392  --color_22: rgba(255, 255, 255, 1);
393  --color_23: rgba(246, 246, 246, 1);
394  --color_3: rgba(235, 32, 36, 1);
395  --color_4: rgba(235,32,36,1);
396  --color_5: rgba(138,138,138,1);
397  --color_6: rgba(0,0,0,1);
398  --color_7: rgba(255,255,255,1);
399  --color_8: rgba(255,255,255,1);
400  --color_9: rgba(0,0,0,1);
401  --max-content-width_1: 1200px;
402}
403</style>
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447<link type="text/css" rel="stylesheet" href="https://cdn.hibuwebsites.com/fonts/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Be+Vietnam:ital,wght@0,100;0,200;0,300;0,400;0,600;0,700;0,800;0,900&family=Kumbh+Sans:ital,wght@0,100..900;1,100..900&family=Oswald:ital,wght@0,200..700;1,200..700&amp;subset=latin-ext&amp;display=swap"  />
448
449
450
451
452
453
454<link rel="stylesheet" type="text/css" href="https://static-res-cdn.websites.hibu.com/mnlt/production/6839/_dm/s/rt/dist/css/d-css-runtime-flex-new-nav.min.css" />
455
456
457
458<link type="text/css" rel="stylesheet" href="https://cdn.hibuwebsites.com/WIDGET_CSS/5bc32e11c74b2333b552b3c99b5d1dfc.css" id="widgetCSS" />
459
460
461<style>img[width][height] {
462  height: auto;
463}</style>
464
465
466<style>
467  body[data-page-alias="home"] #dm [data-show-on-page-only="home"] {
468    display: block !important;
469  }
470</style>
471
472
473<style id="pageAdditionalWidgetsCss" type="text/css">
474</style>
475
476
477
478
479
480<link type="text/css" rel="stylesheet" href="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/files/b4d59456c20346899f3ab9056d40b845_withFlex_1.min.css?v=00d29607b12999e00b800fd44869ceb3" id="siteGlobalCss" />
481
482
483
484<link
485        type="text/css" rel="stylesheet"
486        href="/_dm/s/rt/css/hibu/hibu-runtime.css?version=2026-10-08T12_48_49"
487/>
488
489
490<style id="customWidgetStyle" type="text/css">
491    
492</style>
493<style id="innerPagesStyle" type="text/css">
494    
495</style>
496
497
498<style
499        id="additionalGlobalCss" type="text/css"
500>
501</style>
502
503
504<link type="text/css" rel="stylesheet" href="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/files/b4d59456c20346899f3ab9056d40b845_home_withFlex_1.min.css?v=d94ae2bbf36a6588058920aad0b0ed98" id="homeCssLink" />
505
506<style id="pagestyle" type="text/css">
507    
508</style>
509
510<style id="pagestyleDevice" type="text/css">
511    
512</style>
513
514
515
516
517
518
519
520<style id="globalFontSizeStyle" type="text/css">
521    @media (min-width: 1025px) { [data-version] .size-18, [data-version] .font-size-18 {--font-size: 18;}[data-version] .size-24, [data-version] .font-size-24 {--font-size: 24;}[data-version] .size-40, [data-version] .font-size-40 {--font-size: 40;}[data-version] .size-30, [data-version] .font-size-30 {--font-size: 30;}[data-version] .size-36, [data-version] .font-size-36 {--font-size: 36;}[data-version] .size-15, [data-version] .font-size-15 {--font-size: 15;}[data-version] .size-12, [data-version] .font-size-12 {--font-size: 12;} }@media (min-width: 768px) and (max-width: 1024px) { [data-version] .t-size-16, [data-version] .t-font-size-16 {--font-size: 16;}[data-version] .t-size-20, [data-version] .t-font-size-20 {--font-size: 20;}[data-version] .t-size-24, [data-version] .t-font-size-24 {--font-size: 24;}[data-version] .t-size-26, [data-version] .t-font-size-26 {--font-size: 26;}[data-version] .t-size-31, [data-version] .t-font-size-31 {--font-size: 31;}[data-version] .t-size-15, [data-version] .t-font-size-15 {--font-size: 15;}[data-version] .t-size-12, [data-version] .t-font-size-12 {--font-size: 12;} }@media (max-width: 767px) { [data-version] .m-size-16, [data-version] .m-font-size-16 {--font-size: 16;}[data-version] .m-size-19, [data-version] .m-font-size-19 {--font-size: 19;}[data-version] .m-size-32, [data-version] .m-font-size-32 {--font-size: 32;}[data-version] .m-size-24, [data-version] .m-font-size-24 {--font-size: 24;}[data-version] .m-size-29, [data-version] .m-font-size-29 {--font-size: 29;}[data-version] .m-size-15, [data-version] .m-font-size-15 {--font-size: 15;}[data-version] .m-size-12, [data-version] .m-font-size-12 {--font-size: 12;} }
522</style>
523<style id="pageFontSizeStyle" type="text/css">
524</style>
525
526
527
528
529<style id="hideAnimFix">
530  .dmDesktopBody:not(.editGrid) [data-anim-desktop]:not([data-anim-desktop='none']), .dmDesktopBody:not(.editGrid) [data-anim-extended] {
531    visibility: hidden;
532  }
533
534  .dmDesktopBody:not(.editGrid) .dmNewParagraph[data-anim-desktop]:not([data-anim-desktop='none']), .dmDesktopBody:not(.editGrid) .dmNewParagraph[data-anim-extended] {
535    visibility: hidden !important;
536  }
537
538  #dmRoot:not(.editGrid) .flex-element [data-anim-extended] {
539    visibility: hidden;
540  }
541
542</style>
543
544
545
546
547<style id="fontFallbacks">
548    @font-face {
549  font-family: "Poppins Fallback";
550  src: local('Arial');
551  ascent-override: 92.326%;
552  descent-override: 30.7753%;
553  line-gap-override: 8.793%;
554  size-adjust: 113.7274%;
555}@font-face {
556  font-family: "Kumbh Sans Fallback";
557  src: local('Arial');
558  ascent-override: 93.9558%;
559  descent-override: 24.1866%;
560  size-adjust: 104.9779%;
561  line-gap-override: 0%;
562 }@font-face {
563  font-family: "Oswald Fallback";
564  src: local('Arial');
565  ascent-override: 145.0682%;
566  descent-override: 35.1423%;
567  size-adjust: 82.2372%;
568  line-gap-override: 0%;
569 }
570</style>
571
572
573
574
575
576
577
578
579
580
581
582<meta property="og:type" content="website">
583<meta property="og:url" content="https://www.rupcoeplumbing.com/">
584<script type="application/ld+json">
585    {
586        "@context" : "https://schema.org",
587        "@type" : "WebSite",
588        "name" : "RupCoe Plumbing, Heating & Air Conditioning Inc.",
589        "url" : "https://www.rupcoeplumbing.com/"
590    }
591</script>
591
592  <title>
593    Heating &amp; Air South Plainfield NJ RupCoe Plumbing, Heating &amp; Air Conditioning
594  </title>
595  <meta name="description" content="We offer AC, air quality, commercial HVAC &amp; plumbing, heating, home plumbing, remodeling &amp; whole-house generator services. 24/7 emergency service. Call us."/>
596
597  <meta name="twitter:card" content="summary"/>
598  <meta name="twitter:title" content="Heating &amp; Air South Plainfield NJ RupCoe Plumbing, Heating &amp; Air Conditioning"/>
599  <meta name="twitter:description" content="We offer AC, air quality, commercial HVAC &amp; plumbing, heating, home plumbing, remodeling &amp; whole-house generator services. 24/7 emergency service. Call us."/>
600  <meta name="twitter:image" content="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/home-hero-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg"/>
601  <meta property="og:description" content="We offer AC, air quality, commercial HVAC &amp; plumbing, heating, home plumbing, remodeling &amp; whole-house generator services. 24/7 emergency service. Call us."/>
602  <meta property="og:title" content="Heating &amp; Air South Plainfield NJ RupCoe Plumbing, Heating &amp; Air Conditioning"/>
603  <meta property="og:image" content="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/home-hero-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg"/>
604<script type="application/ld+json">
605{
606  "@context" : {
607    "@vocab" : "http://schema.org/"
608  },
609  "@type" : "LocalBusiness",
610  "name" : "RupCoe Plumbing, Heating & Air Conditioning Inc.",
611  "address" : {
612    "@type" : "PostalAddress",
613    "streetAddress" : "210 New Market Ave",
614    "addressLocality" : "South Plainfield",
615    "addressRegion" : "NJ",
616    "postalCode" : "07080",
617    "addressCountry" : "US"
618  },
619  "geo" : {
620    "@type" : "GeoCoordinates",
621    "latitude" : "40.5770822",
622    "longitude" : "-74.4157046"
623  },
624  "url" : "https://www.rupcoeplumbing.com",
625  "telephone" : "(908) 226-1616",
626  "email" : "[email protected]",
627  "logo" : "https://a.mktgcdn.com/p/j46RWPFOye9X3Y58HDWIHjodo8HMBVJKvdUe2UJ_I68/400x400.jpg",
628  "sameAs" : [ "https://instagram.com/rupcoe" ],
629  "image" : [ "https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/video-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg", "https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp", "https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp", "https://a.mktgcdn.com/p/WL8_IJvMkXSUoxRlOhhpVZDmKf3s3q0jpwr92AQ23eY/500x500.jpg", "https://a.mktgcdn.com/p/WIz-ywTDkf3ShMmtKfjtTY38thx8ysG7w3kswi8Jrm4/284x177.jpg" ],
630  "openingHoursSpecification" : [ {
631    "@type" : "OpeningHoursSpecification",
632    "dayOfWeek" : [ "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ],
633    "opens" : "06:00",
634    "closes" : "00:00"
635  } ],
636  "@id" : "https://www.rupcoeplumbing.com",
637  "foundingDate" : "1985",
638  "description" : "Did your AC quit working? Is your furnace making funny noises? Do you need to fix a broken water heater? Plumbing and HVAC problems can be a headache! That’s why finding a reliable South Plainfield company to handle the issue and get the job done right is crucial.\n\nRupCoe Plumbing, Heating & Air Conditioning is your trusted local resource for all things plumbing and indoor comfort. Whether it’s a large project like whole-home repiping, a quick AC repair, or something in between, we’ll provide a solution tailored to your unique needs.",
639  "makesOffer" : "[{\"@type\":\"Offer\",\"name\":\"Water Heater Repair\"},{\"@type\":\"Offer\",\"name\":\"Water Heater Installation\"},{\"@type\":\"Offer\",\"name\":\"Tankless Water Heaters\"},{\"@type\":\"Offer\",\"name\":\"Drain Cleaning\"},{\"@type\":\"Offer\",\"name\":\"Sewer Line Repair\"},{\"@type\":\"Offer\",\"name\":\"Sewer Video Inspections\"},{\"@type\":\"Offer\",\"name\":\"Hydro Jetting\"},{\"@type\":\"Offer\",\"name\":\"Leak Detection\"},{\"@type\":\"Offer\",\"name\":\"Burst Pipe Repair\"},{\"@type\":\"Offer\",\"name\":\"Sump pump services\"},{\"@type\":\"Offer\",\"name\":\"Gas Line Repair\"},{\"@type\":\"Offer\",\"name\":\"Gas Line Installation\"},{\"@type\":\"Offer\",\"name\":\"Water Filtration Systems\"},{\"@type\":\"Offer\",\"name\":\"Water Softeners\"},{\"@type\":\"Offer\",\"name\":\"Heating Repair\"},{\"@type\":\"Offer\",\"name\":\"Heating Installation\"},{\"@type\":\"Offer\",\"name\":\"Furnace Services\"},{\"@type\":\"Offer\",\"name\":\"Boiler Services\"},{\"@type\":\"Offer\",\"name\":\"Heat Pumps\"},{\"@type\":\"Offer\",\"name\":\"Air Conditioning Repair\"},{\"@type\":\"Offer\",\"name\":\"Air Conditioning Installation\"},{\"@type\":\"Offer\",\"name\":\"AC Maintenance\"},{\"@type\":\"Offer\",\"name\":\"Ductless Mini-Splits\"},{\"@type\":\"Offer\",\"name\":\"Indoor Air Quality Solutions\"},{\"@type\":\"Offer\",\"name\":\"Air Handlers\"},{\"@type\":\"Offer\",\"name\":\"Evaporator Coil Services\"},{\"@type\":\"Offer\",\"name\":\"Whole House Generators\"}]",
640  "brand" : "[{\"@type\":\"Brand\",\"name\":\"Trane\"},{\"@type\":\"Brand\",\"name\":\"RUUD\"},{\"@type\":\"Brand\",\"name\":\"Rheen\"},{\"@type\":\"Brand\",\"name\":\"Bradford White\"},{\"@type\":\"Brand\",\"name\":\"Rinnai\"},{\"@type\":\"Brand\",\"name\":\"Fujitsu\"}]"
641}
642</script>
642
643
644
645
646<!-- SYS- VVNfSElCVV9QUk9EVUNUSU9O -->
647</head>
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669<body id="dmRoot" data-page-alias="home"  class="dmRoot fix-mobile-scrolling flex-site dmResellerSite "
670      style="padding:0;margin:0;"
671      
672     data-flex-site >
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689<div id="dm" class='dmwr'>
690    
691    <div class="dm_wrapper -var5 null ">
692         <div dmwrapped="true" id="1901957768" class="dm-home-page" themewaschanged="true"> <div dmtemplateid="FlexHeader" data-responsive-name="FlexLayout" class="runtime-module-container dm-bfs dm-layout-home hasAnimations hasStickyHeader inMiniHeaderMode rows-1200 hamburger-reverse dmPageBody d-page-1716942098 inputs-css-clean dmDHeader" id="dm-outer-wrapper" data-page-class="1716942098" data-soch="true" data-background-parallax-selector=".dmHomeSection1, .dmSectionParallex"> <div id="dmStyle_outerContainer" class="dmOuter"> <div id="dmStyle_innerContainer" class="dmInner" data-auto="page-inner"> <div class="dmLayoutWrapper standard-var dmStandardDesktop"> <div id="site_content"> <div id="dmFlexHeaderContainer" class="flex_hfcontainer"> <div id="flex-header" data-variation-id="variation0" data-sticky="true" data-changeable-on-scroll-properties="|text-color|" data-changeable-on-scroll="true" data-overlapping-common="false" role="banner"> <div data-layout-section="" data-auto="flex-section" id="1956345911" data-flex-id="405ddf11" class="" data-hidden-on-tablet="true" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1211899340"></div> 
693 <section id="6c317a78" class="flex-element section" data-auto="flex-element-section"> <div id="11fd5064" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="1d3acdb9" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-desktop=""> <div id="9f3f031f" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet=""> <div id="98702d80" class="flex-element group" data-auto="flex-element-group"> <div id="4cb86e4c" class="flex-element group" data-auto="flex-element-group"> <div id="9e5ef93d" class="flex-element group" data-auto="flex-element-group"> <div id="ec5d46f2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1652217484"> <div class="widget-9015bc u_1652217484 dmCustomWidget" data-lazy-load="true" data-title="" id="1652217484" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtZXJjaGFudElkIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJSZXZpZXdzIFdpZGdldHNcIixcImZuXCI6XCJNZXJjaGFudCBJRFwifSJ9XQ==" icon="false" surround="false" data-widget-id="9015bcbf1ace437aa6f0e66d5bb2e7d3" data-widget-version="25" data-widget-config="eyJtZXJjaGFudElkIjoiODA0MzAiLCJtaW4tcmV2aWV3cyI6IjMiLCJtaW4tcmF0aW5nIjoiNCIsInNob3ctd3JpdGUiOnRydWUsImhpZGUtc3VtbWFyeSI6ZmFsc2UsInRoZW1lIjoibGlnaHQifQ=="> <div class="headerWidget" data-merchant="80430" data-theme="light" data-break="" data-count-limit="3" data-rating-limit="4" data-show-write="true" data-hide-summary="false"></div> 
694</div> 
695</div> 
696</div> 
697</div> 
698 <div id="2e9284f4" class="flex-element group" data-auto="flex-element-group"> <div id="da0131a7" class="flex-element group" data-auto="flex-element-group"> <div id="bff1bb66" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-keep-proportion="true" data-external-id="1301876968"> <div class="imageWidget align-center u_1301876968" data-element-type="image" data-widget-type="image" id="1301876968" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="> <a href="/" id="1415691439"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-1920w.webp" alt="RupCoe Plumbing, Heating &amp; Air Conditioning Inc. logo with gray pipe and red text" id="1635795816" class="" width="0.0" height="0.0" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" data-diy-image="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
699</div> 
700</div> 
701</div> 
702 <div id="11d216b4" class="flex-element group" data-auto="flex-element-group"> <div id="07327faa" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="spacer" data-external-id="1269358429"> <div data-element-type="spacer" class="dmSpacer" id="1269358429"></div> 
703</div> 
704</div> 
705 <div id="3aa37ebb" class="flex-element group" data-auto="flex-element-group"> <div id="5801a4e8" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="hamburgerButton" data-external-id="1460227856"> <button class="hamburgerButton" data-element-type="hamburgerButton" id="1460227856" aria-label="Open menu" aria-controls="hamburger-drawer" aria-expanded="false"> <svg width="100pt" height="100pt" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1111394517"> <path d="m3.6172 10.754h-0.050782c-1.9727 0-3.5703 1.5977-3.5703 3.5703 0 1.9727 1.5977 3.5703 3.5703 3.5703h0.054688 92.758 0.050782c1.9727 0 3.5703-1.5977 3.5703-3.5703 0-1.9727-1.5977-3.5703-3.5703-3.5703h-0.054688zm0 35.676h-0.050782c-1.9727 0-3.5703 1.5977-3.5703 3.5703s1.5977 3.5703 3.5703 3.5703h0.054688 92.758 0.050782c1.9727 0 3.5703-1.5977 3.5703-3.5703s-1.5977-3.5703-3.5703-3.5703h-0.054688zm0 35.68h-0.050782c-1.9727 0-3.5703 1.5977-3.5703 3.5703 0 1.9727 1.5977 3.5703 3.5703 3.5703h0.054688 92.758 0.050782c1.9727 0 3.5703-1.5977 3.5703-3.5703 0-1.9727-1.5977-3.5703-3.5703-3.5703h-0.054688z"></path> 
706</svg> 
707</button> 
708</div> 
709</div> 
710</div> 
711 <div id="148da2d9" class="flex-element group" data-auto="flex-element-group"> <div id="0ece3d12" class="flex-element group" data-auto="flex-element-group"> <div id="daf81bcc" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1105358164"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1105358164" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1105358164"> <span class="iconBg" aria-hidden="true" id="1528562180"> <span class="icon hasFontIcon icon-star" id="1809587092"></span> 
712</span> 
713 <span class="text" id="1035871641">Call or Text</span> 
714</a> 
715</div> 
716</div> 
717 <div id="a052d7aa" class="flex-element group" data-auto="flex-element-group"> <div id="df845449" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1524906790"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1524906790 align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1524906790" file="false" href="/request-service-form-page" data-element-type="dButtonLinkId" id="1524906790" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIk1vYmlsZSBCdXR0b25cIn0ifSx7ImJpbmRpbmdOYW1lIjoibGluayIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIlJlcXVlc3QgQ1RBIExpbmtcIn0ifV0=" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1847377451"> <span class="icon hasFontIcon icon-star" id="1804309602"></span> 
718</span> 
719 <span class="text" id="1149277214" data-diy-text="">Request Service</span> 
720</a> 
721</div> 
722</div> 
723</div> 
724</div> 
725</div> 
726</div> 
727</div> 
728</section> 
729</div> 
730 <div data-layout-section="" data-auto="flex-section" id="1368876750" data-flex-id="157b4fff" class="" data-hidden-on-desktop="true" data-hidden-on-mobile="true"> <div class="flex-widgets-container" id="1825413659"></div> 
731 <section id="726ad2b1" class="flex-element section" data-auto="flex-element-section"> <div id="30295aaf" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="32767c2f" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-mobile=""> <div id="2fd7b414" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop=""> <div id="widget_ie6" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-mobile="" data-external-id="1288312793"> <div class="widget-9015bc u_1288312793 dmCustomWidget" data-lazy-load="true" data-title="" id="1288312793" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtZXJjaGFudElkIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJSZXZpZXdzIFdpZGdldHNcIixcImZuXCI6XCJNZXJjaGFudCBJRFwifSJ9XQ==" icon="false" surround="false" data-widget-id="9015bcbf1ace437aa6f0e66d5bb2e7d3" data-widget-version="25" data-widget-config="eyJtZXJjaGFudElkIjoiODA0MzAiLCJtaW4tcmV2aWV3cyI6IjMiLCJtaW4tcmF0aW5nIjoiNCIsInNob3ctd3JpdGUiOnRydWUsImhpZGUtc3VtbWFyeSI6dHJ1ZSwidGhlbWUiOiJsaWdodCJ9"> <div class="headerWidget" data-merchant="80430" data-theme="light" data-break="" data-count-limit="3" data-rating-limit="4" data-show-write="true" data-hide-summary="true"></div> 
732</div> 
733</div> 
734</div> 
735 <div id="group_jjh" class="flex-element group" data-auto="flex-element-group"> <div id="widget_5h5" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-keep-proportion="true" data-external-id="1303640445"> <div class="imageWidget align-center u_1303640445" data-element-type="image" data-widget-type="image" id="1303640445" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="> <a href="/" id="1035792501"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-1920w.webp" alt="RupCoe Plumbing, Heating &amp;
735 Air Conditioning Inc. logo with gray pipe and red text" id="1107341754" class="" width="0.0" height="0.0" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" data-diy-image="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
736</div> 
737</div> 
738</div> 
739 <div id="group_7in" class="flex-element group" data-auto="flex-element-group"> <div id="widget_vej" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1997567173"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1997567173 align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1997567173" file="false" href="/contact" data-element-type="dButtonLinkId" id="1997567173" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiVGFiIC0gQ29udCAtIExpbmtcIn0ifSx7ImJpbmRpbmdOYW1lIjoiYnV0dG9uX3RleHQiLCJ2YWx1ZSI6IlNUQVRJQ19QQUdFX0NPTExFQ1RJT04ue1wiY25cIjpcImJ1dHRvbnNcIixcImZuXCI6XCJUYWIgLSBDb250IEJ1dHRvblwifSJ9XQ==" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1066072120"> <span class="icon hasFontIcon icon-star" id="1327803529"></span> 
740</span> 
741 <span class="text" id="1641422126" data-diy-text="">Contact Us</span> 
742</a> 
743</div> 
744</div> 
745 <div id="group_23b" class="flex-element group" data-auto="flex-element-group"> <div id="4598469b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="hamburgerButton" data-external-id="1761070024"> <button class="hamburgerButton" data-element-type="hamburgerButton" id="1761070024" aria-label="Open menu" aria-controls="hamburger-drawer" aria-expanded="false"> <svg width="100pt" height="100pt" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1639062494"> <path d="m3.6172 10.754h-0.050782c-1.9727 0-3.5703 1.5977-3.5703 3.5703 0 1.9727 1.5977 3.5703 3.5703 3.5703h0.054688 92.758 0.050782c1.9727 0 3.5703-1.5977 3.5703-3.5703 0-1.9727-1.5977-3.5703-3.5703-3.5703h-0.054688zm0 35.676h-0.050782c-1.9727 0-3.5703 1.5977-3.5703 3.5703s1.5977 3.5703 3.5703 3.5703h0.054688 92.758 0.050782c1.9727 0 3.5703-1.5977 3.5703-3.5703s-1.5977-3.5703-3.5703-3.5703h-0.054688zm0 35.68h-0.050782c-1.9727 0-3.5703 1.5977-3.5703 3.5703 0 1.9727 1.5977 3.5703 3.5703 3.5703h0.054688 92.758 0.050782c1.9727 0 3.5703-1.5977 3.5703-3.5703 0-1.9727-1.5977-3.5703-3.5703-3.5703h-0.054688z"></path> 
746</svg> 
747</button> 
748</div> 
749</div> 
750 <div id="group_tbn" class="flex-element group" data-auto="flex-element-group"> <div id="widget_qp9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="spacer" data-external-id="1685550797"> <div data-element-type="spacer" class="dmSpacer" id="1685550797"></div> 
751</div> 
752</div> 
753</div> 
754</div> 
755</section> 
756</div> 
757 <div data-layout-section="" data-auto="flex-section" id="1058362846" data-flex-id="c0ae99ba" class="" data-hidden-on-mobile="true" data-hidden-on-tablet="true"> <div class="flex-widgets-container" id="1227336095"></div> 
758 <section id="ac810934" class="flex-element section" data-auto="flex-element-section"> <div id="b7dee1fb" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="9360b347" class="flex-element group" data-auto="flex-element-group" data-hidden-on-mobile="" data-hidden-on-tablet=""> <div id="3710bc01" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet=""> <div id="3a2d0b90" class="flex-element group" data-auto="flex-element-group" data-hidden-on-mobile="" data-hidden-on-tablet=""> <div id="be1b9f91" class="flex-element group" data-auto="flex-element-group"> <div id="423fc4a2" class="flex-element group" data-auto="flex-element-group"> <div id="3b22d9b0" class="flex-element group" data-auto="flex-element-group"> <div id="519ddbdc" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1642529323"> <div class="widget-9015bc u_1642529323 dmCustomWidget" data-lazy-load="true" data-title="" id="1642529323" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtZXJjaGFudElkIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJSZXZpZXdzIFdpZGdldHNcIixcImZuXCI6XCJNZXJjaGFudCBJRFwifSJ9XQ==" icon="false" surround="false" data-widget-id="9015bcbf1ace437aa6f0e66d5bb2e7d3" data-widget-version="25" data-widget-config="eyJtZXJjaGFudElkIjoiODA0MzAiLCJtaW4tcmV2aWV3cyI6IjMiLCJtaW4tcmF0aW5nIjoiNCIsInNob3ctd3JpdGUiOnRydWUsImhpZGUtc3VtbWFyeSI6ZmFsc2UsInRoZW1lIjoibGlnaHQifQ=="> <div class="headerWidget" data-merchant="80430" data-theme="light" data-break="" data-count-limit="3" data-rating-limit="4" data-show-write="true" data-hide-summary="false"></div> 
759</div> 
760</div> 
761</div> 
762 <div id="8caf0a70" class="flex-element group" data-auto="flex-element-group"> <div id="a8c6072d" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1857856301"> <div class="dmNewParagraph u_1857856301" data-element-type="paragraph" data-version="5" id="1857856301" style=""><p class="text-align-left size-18 t-size-16 m-size-16"><span class="font-size-18 t-font-size-16 m-font-size-16" style="display: unset; font-style: normal;
762 color: var(--color_7);">Serving</span></p></div> 
763</div> 
764 <div id="3ad8c19c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1153192226"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1153192226" data-diy-text="" style=""><p class="t-size-20 text-align-left m-size-19 size-24" style="line-height: 1.1;"><strong class="font-size-24 t-font-size-20 m-font-size-19" style="display: initial; font-weight: bold; color: var(--color_2);">Middlesex, Somerset, Hunterdon, Essex, Union, Morris, Mercer &amp; Monmouth Counties</strong></p></div> 
765</div> 
766</div> 
767</div> 
768 <div id="e3dfb55d" class="flex-element group" data-auto="flex-element-group"> <div id="6881434d" class="flex-element group" data-auto="flex-element-group"> <div id="5bfb3718" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-keep-proportion="true" data-external-id="1220116170"> <div class="imageWidget align-center u_1220116170" data-element-type="image" data-widget-type="image" id="1220116170" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="> <a href="/" id="1909084297"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-324w.webp" alt="Rupcoe Plumbing, Heating & Air Conditioning Inc. - logo" id="1563401796" class="" width="270.0" height="145.75" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" data-diy-image="" onerror="handleImageLoadError(this)"/></a> 
769</div> 
770</div> 
771</div> 
772</div> 
773 <div id="5cbebde5" class="flex-element group" data-auto="flex-element-group"> <div id="d3a805c8" class="flex-element group" data-auto="flex-element-group"> <div id="e77f39e9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1234177366"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1234177366 align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1234177366" file="false" href="/request-service-form-page" data-element-type="dButtonLinkId" id="1234177366" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIkRlc2sgLyBUYWIgQnV0dG9uXCJ9In0seyJiaW5kaW5nTmFtZSI6ImxpbmsiLCJ2YWx1ZSI6IlNUQVRJQ19QQUdFX0NPTExFQ1RJT04ue1wiY25cIjpcImJ1dHRvbnNcIixcImZuXCI6XCJSZXF1ZXN0IENUQSBMaW5rXCJ9In1d" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1281457075"> <span class="icon hasFontIcon icon-star" id="1446615084"></span> 
774</span> 
775 <span class="text" id="1422705019" data-diy-text="">Request Service</span> 
776</a> 
777</div> 
778</div> 
779 <div id="6a5bfb4c" class="flex-element group" data-auto="flex-element-group"> <div id="746860ee" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1791170611"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1791170611" style=""><p class="m-size-16 t-size-16 size-18 text-align-right"><span class="m-font-size-16 t-font-size-16 font-size-18" style="display: unset; font-style: normal; color: var(--color_7);">Call or Text</span></p></div> 
780</div> 
781 <div id="0ad35782" class="flex-element widget-wrapper YXTPH" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1127636214"> <div class="dmNewParagraph u_1127636214" data-element-type="paragraph" data-version="5" id="1127636214" data-diy-text="" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJjb250ZW50X2xpYnJhcnkuZ2xvYmFsLnBob25lLmxvYy0wIC0gbWFpbiJ9XQ==">(908) 259-4326</div> 
782</div> 
783</div> 
784</div> 
785</div> 
786</div> 
787 <div id="bd946f29" class="flex-element group" data-auto="flex-element-group" data-hidden-on-mobile="" data-hidden-on-tablet=""> <div id="51d6dafc" class="flex-element widget-wrapper navmasterdefault" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-hidden-on-tablet="" data-hidden-on-mobile="" data-hidden-on-desktop="" data-external-id="1398948466"> <nav class="effect-bottom2 u_1398948466 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="horizontal_nav_layout_1" layout-sub="submenu_horizontal_1" data-show-vertical-sub-items="HOVER" id="1398948466" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="HORIZONTAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/" class="unifiednav__item unav-item unav-top-item  dmNavItemSelected  dmUDNavigationItem_00  " target="" data-target-page-alias="" aria-current="page" data-auto="selected-page"> <span class="nav-item-text " data-link-text="
788         Home
789        " data-auto="page-text-style">Home<span class="icon icon-angle-down"></span> 
790</span> 
791</a> 
792</li> 
793 <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/about" class="unifiednav__item unav-item unav-top-item  dmUDNavigationItem_010101599305  unifiednav__item_has-sub-nav" target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="About" data-auto="page-text-style">About<span class="icon icon-angle-down"></span> 
794</span> 
795</a> 
796 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/careers" class="unifiednav__item unav-item   dmUDNavigationItem_010101402669  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Careers" data-auto="page-text-style">Careers<span class="icon icon-angle-right"></span> 
797</span> 
798</a> 
799</li> 
800 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/faqs" class="unifiednav__item unav-item   dmUDNavigationItem_010101347615  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="FAQs" data-auto="page-text-style">FAQs<span class="icon icon-angle-right"></span> 
801</span> 
802</a> 
803</li> 
804 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/reviews" class="unifiednav__item unav-item   dmUDNavigationItem_010101840192  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Reviews" data-auto="page-text-style">Reviews<span class="icon icon-angle-right"></span> 
805</span> 
806</a> 
807</li> 
808 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/finance" class="unifiednav__item unav-item   dmUDNavigationItem_0101016848  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Finance" data-auto="page-text-style">Finance<span class="icon icon-angle-right"></span> 
809</span> 
810</a> 
811</li> 
812 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/special-offers" class="unifiednav__item unav-item   dmUDNavigationItem_010101324312  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Special Offers" data-auto="page-text-style">Special Offers<span class="icon icon-angle-right"></span> 
813</span> 
814</a> 
815</li> 
816</ul> 
817</li> 
818 <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/contact" class="unifiednav__item unav-item unav-top-item  dmUDNavigationItem_010101152840  unifiednav__item_has-sub-nav" target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Contact" data-auto="page-text-style">Contact<span class="icon icon-angle-down"></span> 
819</span> 
820</a> 
821 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/request-service-form-page" class="unifiednav__item unav-item   dmUDNavigationItem_010101150108  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Request Service" data-auto="page-text-style">Request Service<span class="icon icon-angle-right"></span> 
822</span> 
823</a> 
824</li> 
825</ul> 
826</li> 
827 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/empty" class="unifiednav__item unav-item unav-top-item  dmUDNavigationItem_010101434187  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Poly Social Test" data-auto="page-text-style">Poly Social Test<span class="icon icon-angle-down"></span> 
828</span> 
829</a> 
830</li> 
831</ul> 
832</nav> 
833</div> 
834 <div id="widget_7d2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1963014488"> <div class="widget-5b093d u_1963014488 dmCustomWidget" data-lazy-load="true" data-title="" id="1963014488" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="5b093d0fb9474c1dabfa7807be463b12" data-widget-version="52" data-widget-config="eyJjaGlsZFVybFByZWZpeCI6InNlcnZpY2VzIiwidXNlVW5pdmVyc2FsQ29sb3JzIjp0cnVlLCJsb2FkaW5nRGlzcGxheUdyb3VwIjpudWxsLCJ1c2VVbml2ZXJzYWxUeXBvZ3JhcGh5IjpmYWxzZSwibWVnYUNoaWxkRm9udFNpemUiOiIxNCIsInJlc3RvcmVGdWxsRGVmYXVsdHMiOmZhbHNlLCJtZWdhM0xhYmVsIjoiTWVnYSAzIiwidG9wTGV2ZWxHYXAiOiIzMCIsIm1lZ2FUcmlnZ2VyQ2hldnJvbkdhcCI6IjEyIiwibWVnYTJBbHBoYWJldGl6ZSI6ZmFsc2UsIm1lZ2ExR3JvdXAiOm51bGwsImFuaW1hdGVTdWJOYXYiOmZhbHNlLCJtZWdhMkxhYmVsIjoiTWVnYSAyIiwibWVnYTRMYWJlbCI6Ik1lZ2EgNCIsIm1lZ2EyTGF5b3V0IjoiZ3JpZCIsIm1lZ2ExTGFiZWwiOiJQbHVtYmluZyAmIEhWQUMgU2VydmljZXMiLCJhbmltYXRlSG92ZXJFZmZlY3RzIjpmYWxzZSwiY2FwaXRhbGl6ZU5hdiI6ZmFsc2UsInVuaXZlcnNhbEZvbnRXZWlnaHQiOiI1MDAiLCJtZWdhMk1heFdpZHRoIjoiMTYwMCIsInVuaXZlcnNhbEZvbnRTaXplIjoiMTYiLCJhbmltYXRlVG9wQ2hldnJvbnMiOmZhbHNlLCJsb2FkaW5nSXRlbUNvdW50IjoiNCIsImFuaW1hdGVNaXJyb3IiOmZhbHNlLCJ0b3BOYXZCYWNrZ3JvdW5k
834Q29sb3IiOiIiLCJtYW51YWxOYXZpZ2F0aW9uSXRlbXMiOltdLCJtZWdhRmFtaWx5QmFja2dyb3VuZENvbG9yIjoidmFyKC0tY29sb3JfMTApIiwiYWN0aXZlVGV4dENvbG9yIjoidmFyKC0tY29sb3JfMTEpIiwidG9wQ2hldnJvblN0eWxlIjoiZmlsbGVkIiwiYW5pbWF0ZVRvcE5hdiI6ZmFsc2UsImFuaW1hdGVNZWdhRmFtaWxpZXMiOmZhbHNlLCJ0b3BDaGV2cm9uQ29sb3IiOiIjMjIyMjIyIiwibWVnYUNoZXZyb25Db2xvciI6IiMyMjIyMjIiLCJtZWdhQ2hpbGRGb250V2VpZ2h0IjoiNDAwIiwic3ViU3ViRm9udFdlaWdodCI6IjQwMCIsIm5hdGl2ZVBhcmVudHMiOlt7ImxhYmVsIjoiIiwic2hvd1BhcmVudCI6dHJ1ZSwicHVsbENoaWxkcmVuIjpmYWxzZSwidXJsIjp7InZhbHVlIjoic3BlY2lhbC1vZmZlcnMiLCJ0eXBlIjoicGFnZSIsImhyZWYiOiIvc3BlY2lhbC1vZmZlcnMiLCJyYXdfdXJsIjoiL3NpdGUvYjRkNTk0NTZjMjAzNDY4OTlmM2FiOTA1NmQ0MGI4NDUvc3BlY2lhbC1vZmZlcnM/bmVlPXRydWUmZWQ9dHJ1ZSZzaG93T3JpZ2luYWw9dHJ1ZSZwcmV2aWV3PXRydWUmZG1fdHJ5X21vZGU9dHJ1ZSZkbV9jaGVja1N5bmM9MSJ9fSx7ImxhYmVsIjoiIiwic2hvd1BhcmVudCI6dHJ1ZSwicHVsbENoaWxkcmVuIjpmYWxzZSwidXJsIjp7InZhbHVlIjoiZmluYW5jZSIsInR5cGUiOiJwYWdlIiwiaHJlZiI6Ii9maW5hbmNlIiwicmF3X3VybCI6Ii9zaXRlL2I0ZDU5NDU2YzIwMzQ2ODk5ZjNhYjkwNTZkNDBiODQ1L2ZpbmFuY2U/bmVlPXRydWUmZWQ9dHJ1ZSZzaG93T3JpZ2luYWw9dHJ1ZSZwcmV2aWV3PXRydWUmZG1fdHJ5X21vZGU9dHJ1ZSZkbV9jaGVja1N5bmM9MSJ9fSx7InNob3dQYXJlbnQiOnRydWUsInB1bGxDaGlsZHJlbiI6dHJ1ZSwibGFiZWwiOiIiLCJ1cmwiOnsidmFsdWUiOiJhYm91dCIsInR5cGUiOiJwYWdlIiwiaHJlZiI6Ii9hYm91dCIsInJhd191cmwiOiIvc2l0ZS9iNGQ1OTQ1NmMyMDM0Njg5OWYzYWI5MDU2ZDQwYjg0NS9hYm91dD9uZWU9dHJ1ZSZlZD10cnVlJnNob3dPcmlnaW5hbD10cnVlJnByZXZpZXc9dHJ1ZSZkbV90cnlfbW9kZT10cnVlJmRtX2NoZWNrU3luYz0xIn19LHsic2hvd1BhcmVudCI6dHJ1ZSwicHVsbENoaWxkcmVuIjp0cnVlLCJsYWJlbCI6IiIsInVybCI6eyJ2YWx1ZSI6ImNvbnRhY3QiLCJ0eXBlIjoicGFnZSIsImhyZWYiOiIvY29udGFjdCIsInJhd191cmwiOiIvc2l0ZS9iNGQ1OTQ1NmMyMDM0Njg5OWYzYWI5MDU2ZDQwYjg0NS9jb250YWN0P25lZT10cnVlJmVkPXRydWUmc2hvd09yaWdpbmFsPXRydWUmcHJldmlldz10cnVlJmRtX3RyeV9tb2RlPXRydWUmZG1fY2hlY2tTeW5jPTEifX1dLCJ1c2VVbml2ZXJzYWxCYWNrZ3JvdW5kcyI6dHJ1ZSwidXNlQ3VzdG9tTmF2T25seSI6ZmFsc2UsInN1YlN1YlRleHRDb2xvciI6IiMyMjIyMjIiLCJtZWdhMU1heFdpZHRoIjoiMTYwMCIsIm1lZ2E0R3JvdXAiOm51bGwsInRvcEZvbnRTaXplIjoiMTgiLCJtaXJyb3JBbGlnbm1lbnQiOiJzdGFydCIsIm1lZ2E0RXhwYW5kQWxsIjpmYWxzZSwidGV4dENvbG9yc0dyb3VwIjpudWxsLCJzdWJGb250U2l6ZSI6IjE0IiwidHlwb2dyYXBoeUdyb3VwIjpudWxsLCJtaXJyb3JJdGVtR2FwIjoiMCIsImFuaW1hdGVNZWdhTWVudSI6ZmFsc2UsIm1lZ2FHcmFuZGNoaWxkVGV4dENvbG9yIjoiIzIyMjIyMiIsIm1lZ2FQYXJlbnRUZXh0Q29sb3IiOiIjMjIyMjIyIiwibWlycm9yU3R5bGVzIjpudWxsLCJtZWdhMUxheW91dCI6ImdyaWQiLCJtZWdhR3JhbmRjaGlsZEZvbnRTaXplIjoiMTQiLCJob3ZlclRleHRDb2xvciI6InZhcigtLWNvbG9yXzExKSIsIm1lZ2E0Q29sdW1ucyI6IjMiLCJtZWdhMkV4cGFuZEFsbCI6ZmFsc2UsIm1lZ2EzQ29sdW1ucyI6IjMiLCJjaGV2cm9uc0dyb3VwIjpudWxsLCJtZWdhMUNvbHVtbnMiOiIzIiwibWVnYTJDb2x1bW5zIjoiMyIsImxheW91dFByZXNldCI6ImNsYXNzaWMiLCJzdWJTdWJOYXZCYWNrZ3JvdW5kQ29sb3IiOiJ2YXIoLS1jb2xvcl8xMCkiLCJyZXN0b3JlTWlycm9yRGVmYXVsdHMiOmZhbHNlLCJzdWJGb250V2VpZ2h0IjoiNDAwIiwidG9wRm9udFdlaWdodCI6IjUwMCIsIm1lZ2ExRXhwYW5kQWxsIjpmYWxzZSwibmF2QWxpZ25tZW50IjoiY2VudGVyIiwiYW5pbWF0ZU1lZ2FDaGV2cm9ucyI6ZmFsc2UsIndpZGdldE1vZGUiOiJmdWxsIiwic3ViVGV4dENvbG9yIjoiIzIyMjIyMiIsInN1Yk5hdkJhY2tncm91bmRDb2xvciI6InZhcigtLWNvbG9yXzEwKSIsIm1lZ2E0TWF4V2lkdGgiOiIxNjAwIiwiYmFja2dyb3VuZENvbG9yc0dyb3VwIjpudWxsLCJ0b3BUZXh0Q29sb3IiOiIjMjIyMjIyIiwibWlycm9yT3Blbk1lbnVzT25Ib3ZlciI6IiIsIm1lZ2FQYXJlbnRGb250U2l6ZSI6IjE2IiwibWVudUludGVyYWN0aW9uR3JvdXAiOm51bGwsIm1pcnJvclN0eWxpbmdHcm91cCI6bnVsbCwiZW5hYmxlQ3VzdG9tTmF2aWdhdGlvbkJ1aWxkZXIiOmZhbHNlLCJtZWdhMUFscGhhYmV0aXplIjpmYWxzZSwibWVnYTNHcm91cCI6bnVsbCwibWVnYTRBbHBoYWJldGl6ZSI6ZmFsc2UsImFuaW1hdGlvbnNFbmFibGVkIjpmYWxzZSwiY2F0ZWdvcnlVcmxQcmVmaXgiOiJ3aGF0LXdlLWRvIiwidW5pdmVyc2FsQ2hldnJvblN0eWxlIjoiZmlsbGVkIiwidW5pdmVyc2FsVGV4dENvbG9yIjoidmFyKC0tY29sb3JfMTEpIiwidXNlU2VwYXJhdGVNaXJyb3JTdHlsaW5nIjpmYWxzZSwibG9hZGluZ0FuaW1hdGlvblN0eWxlIjoic2hpbW1lciIsIm1lZ2EyR3JvdXAiOm51bGwsIm1lZ2FDaGV2cm9uU3R5bGUiOiJmaWxsZWQiLCJ1bml2ZXJzYWxDaGV2cm9uQ29sb3IiOiJ2YXIoLS1jb2xvcl8xMSkiLCJob3ZlckJhY2tncm91bmRDb2xvciI6InZhcigtLWNvbG9yXzEwKSIsIm1lZ2FNZW51QmFja2dyb3VuZENvbG9yIjoidmFyKC0tY29sb3JfMTApIiwidW5pdmVyc2FsQmFja2dyb3VuZENvbG9yIjoidmFyKC0tY29sb3JfMTApIiwibWVnYTNMYXlvdXQiOiJncmlkIiwibmF2aWdhdGlvblRvb2xzR3JvdXAiOm51bGwsIm1lZ2FQYXJlbnRGb250V2VpZ2h0IjoiNjAwIiwibWVnYUNoaWxkVGV4dENvbG9yIjoiIzIyMjIyMiIsInN1YlN1YkZvbnRTaXplIjoiMTQiLCJtZWdhM0FscGhhYmV0aXplIjpmYWxzZSwibGF5b3V0R3JvdXAiOm51bGwsInVuaXZlcnNhbFVuZGVybGluZSI6dHJ1ZSwiZW5hYmxlTmF2aWdhdGlvblRyZWVQcmV2aWV3IjpmYWxzZSwibWVnYUdyYW5kY2hpbGRGb250V2VpZ2h0IjoiNDAwIiwid2lkZ2V0TW9kZVVybHNHcm91cCI6bnVsbCwiYW5pbWF0aW9uc0dyb3VwIjpudWxsLCJvcGVuTWVudXNPbkhvdmVyIjp0cnVlLCJ1bml2ZXJzYWxDaGV2cm9uV2VpZ2h0IjoiMiIsIm1lZ2EzTWF4V2lkdGgiOiIxNjAwIiwibWVnYTRMYXlvdXQiOiJncmlkIiwiYW5pbWF0ZVN1YlN1Yk5hdiI6ZmFsc2UsIm1lZ2EzRXhwYW5kQWxsIjpmYWxzZSwidW5pdmVyc2FsQ2hldnJvblNpemUiOiIxMCIsInVzZVVuaXZlcnNhbENoZXZyb25TZXR0aW5ncyI6dHJ1ZX0="><!-- NMW Full Feature Stable: Manual Navigation Items live-update fix --><!--
835  Use this same HTML for both widget instances.
836
837  Content Panel widgetMode:
838    full   = loads Collection, Native, CUSTOMNAV, and Manual navigation data
839    mirror = renders the Full widget's shared navigation vertically
840
841  The hidden manual source is required because Duda link inputs must be output
842  through the custom_link helper. JavaScript reads the Duda-generated anchor
843  attributes and moves them into the rendered navigation.
844
845  Manual Folder items do not require a link. An empty manual Folder is shown as
846  a disabled preview inside the Duda editor, then omitted from preview/live pages
847  until a valid child is assigned.
848--> <div class="nmw-root" data-nmw-mode="full"> <a class="nmw-skip-link" href="#nmw-full-main-nav">Skip to navigation</a> 
849 <div class="nmw-loading" data-nmw-loading-view="" role="status" aria-l
849ive="polite" aria-label="Loading navigation" hidden=""></div> 
850 <div class="nmw-mobile-nav-wrapper nmw-render" data-nmw-render=""></div> 
851 <div class="nmw-tools" data-nmw-tools="" aria-live="polite"></div> 
852 <div class="nmw-manual-items-source" data-nmw-manual-source="" aria-hidden="true" hidden=""></div> 
853</div> 
854</div> 
855</div> 
856</div> 
857 <div id="a8f32aec" class="flex-element group" data-auto="flex-element-group" data-hidden-on-mobile="" data-hidden-on-tablet="" data-hidden-on-desktop=""> <div id="392df140" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-hidden-on-tablet="" data-hidden-on-desktop="" data-external-id="1915042179"> <nav class="effect-bottom2 u_1915042179 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="horizontal_nav_layout_1" layout-sub="submenu_horizontal_1" data-show-vertical-sub-items="HOVER" id="1915042179" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="HORIZONTAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/" class="unifiednav__item unav-item unav-top-item  dmNavItemSelected    " target="" data-target-page-alias="home" aria-current="page" data-auto="selected-page"> <span class="nav-item-text " data-link-text="
858         Home
859        " data-auto="page-text-style">Home<span class="icon icon-angle-down" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
860</span> 
861</a> 
862</li> 
863 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <button type="button" class="unifiednav__item unav-item unav-top-item    " data-target-page-alias="mega-menu-4dc8a903"> <span class="nav-item-text " data-link-text="Services" data-auto="page-text-style">Services<span class="icon icon-angle-down" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
864</span> 
865</button> 
866</li> 
867 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/reviews" class="unifiednav__item unav-item unav-top-item    " target="" data-target-page-alias="reviews"> <span class="nav-item-text " data-link-text="Reviews" data-auto="page-text-style">Reviews<span class="icon icon-angle-down" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
868</span> 
869</a> 
870</li> 
871 <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/about" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="about"> <span class="nav-item-text " data-link-text="About" data-auto="page-text-style">About<span class="icon icon-angle-down"></span> 
872</span> 
873</a> 
874 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/faqs" class="unifiednav__item unav-item     " target="" data-target-page-alias="faqs"> <span class="nav-item-text " data-link-text="FAQs" data-auto="page-text-style">FAQs<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
875</span> 
876</a> 
877</li> 
878</ul> 
879</li> 
880 <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/contact" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="contact"> <span class="nav-item-text " data-link-text="Contact" data-auto="page-text-style">Contact<span class="icon icon-angle-down"></span> 
881</span> 
882</a> 
883 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/request-service-form-page" class="unifiednav__item unav-item     " target="" data-target-page-alias="request-form-page"> <span class="nav-item-text " data-link-text="Request Service" data-auto="page-text-style">Request Service<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
884</span> 
885</a> 
886</li> 
887 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/service-area" class="unifiednav__item unav-item     " target="" data-target-page-alias="service-area"> <span class="nav-item-text " data-link-text="Serving Area" data-auto="page-text-style">Serving Area<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
888</span> 
889</a> 
890</li> 
891</ul> 
892</li> 
893 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/locations" class="unifiednav__item unav-item unav-top-item    " target="" data-target-page-alias="locations"> <span class="nav-item-text " data-link-text="Locations" data-auto="page-text-style">Locations<span class="icon icon-angle-down" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
894</span> 
895</a> 
896</li> 
897</ul> 
898</nav> 
899</div> 
900</div> 
901</div> 
902</div> 
903</div> 
904</section> 
905</div> 
906</div> 
907 <div id="hamburger-drawer" class="hamburger-drawer layout-drawer" layout="5ff40d2f36ae4e5ea1bc96b2e2fcf67e===header" data-origin="side-reverse"> <div data-layout-section="" data-auto="flex-section" id="1825997552" data-flex-id="4a710abd"> <div class="flex-widgets-container" id="1217094164"></div> 
908 <section id="bb7ac507" class="flex-element section" data-auto="flex-element-section"> <div id="d3f5447c" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="78db3927" class="flex-element group" data-auto="flex-element-group"> <div id="a7708a16" class="flex-element group" data-auto="flex-element-group"> <div id="8e45e82b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="hamburgerButton" data-external-id="1202219406"> <button class="hamburgerButton" data-element-type="hamburgerButton" id="1202219406" aria-label="Close menu" aria-controls="hamburger-drawer" aria-expanded="false"> <svg width="100pt" height="100pt" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1928507629"> <path d="m82 87.883c1.625 1.625 4.2656 1.625 5.8906 0 1.6289-1.6289 1.6289-4.2656 0-5.8945l-32.414-32.41 32.406-32.406c1.6289-1.625 1.6289-4.2656
909 0-5.8906-1.6289-1.6289-4.2656-1.6289-5.8906 0l-32.406 32.402-32.406-32.406c-1.625-1.625-4.2656-1.625-5.8906 0-1.6289 1.6289-1.6289 4.2656 0
910 5.8945l32.402 32.406-32.41 32.414c-1.6289 1.625-1.6289 4.2617 0 5.8906 1.625 1.6289 4.2656 1.6289 5.8906 0l32.414-32.414z" fill-rule="evenodd"></path> 
911</svg> 
912</button> 
913</div> 
914</div> 
915</div> 
916</div> 
917</section> 
918</div> 
919 <div data-auto="flex-section" id="1099442592" data-flex-id="63a049320d35b55b4ef2a372" data-layout-section=""> <div class="flex-widgets-container" id="1247649854"></div> 
920 <section id="63a049320d35b55b4ef2a373" class="flex-element section" data-auto="flex-element-section"> <div id="63a049320d35b55b4ef2a376" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="63a049320d35b55b4ef2a377" class="flex-element group" data-auto="flex-element-group"> <div id="63a049320d35b55b4ef2a374" class="flex-element group" data-auto="flex-element-group"> <div id="widget_la2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="4003453145"> <div class="widget-5b093d dmCustomWidget" data-lazy-load="true" data-title="" id="4003453145" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="5b093d0fb9474c1dabfa7807be463b12" data-widget-version="52" data-widget-config="eyJjaGlsZFVybFByZWZpeCI6InNlcnZpY2VzIiwidXNlVW5pdmVyc2FsQ29sb3JzIjp0cnVlLCJsb2FkaW5nRGlzcGxheUdyb3VwIjpudWxsLCJ1c2VVbml2ZXJzYWxUeXBvZ3JhcGh5Ijp0cnVlLCJtZWdhQ2hpbGRGb250U2l6ZSI6IjE0IiwicmVzdG9yZUZ1bGxEZWZhdWx0cyI6ZmFsc2UsIm1lZ2EzTGFiZWwiOiJNZWdhIDMiLCJ0b3BMZXZlbEdhcCI6IjMyIiwibWVnYVRyaWdnZXJDaGV2cm9uR2FwIjoiMTIiLCJtZWdhMkFscGhhYmV0aXplIjpmYWxzZSwibWVnYTFHcm91cCI6bnVsbCwiYW5pbWF0ZVN1Yk5hdiI6dHJ1ZSwibWVnYTJMYWJlbCI6Ik1lZ2EgMiIsIm1lZ2E0TGFiZWwiOiJNZWdhIDQiLCJtZWdhMkxheW91dCI6ImdyaWQiLCJtZWdhMUxhYmVsIjoiTWVnYSAxIiwiYW5pbWF0ZUhvdmVyRWZmZWN0cyI6dHJ1ZSwiY2FwaXRhbGl6ZU5hdiI6ZmFsc2UsInVuaXZlcnNhbEZvbnRXZWlnaHQiOiI1MDAiLCJtZWdhMk1heFdpZHRoIjoiMTYwMCIsInVuaXZlcnNhbEZvbnRTaXplIjoiMTYiLCJhbmltYXRlVG9wQ2hldnJvbnMiOnRydWUsImxvYWRpbmdJdGVtQ291bnQiOiI0IiwiYW5pbWF0ZU1pcnJvciI6dHJ1ZSwidG9wTmF2QmFja2dyb3VuZENvbG9yIjoiIiwibWFudWFsTmF2aWdhdGlvbkl0ZW1zIjpbXSwibWVnYUZhbWlseUJhY2tncm91bmRDb2xvciI6IiIsImFjdGl2ZVRleHRDb2xvciI6ImN1cnJlbnRDb2xvciIsInRvcENoZXZyb25TdHlsZSI6ImZpbGxlZCIsImFuaW1hdGVUb3BOYXYiOnRydWUsImFuaW1hdGVNZWdhRmFtaWxpZXMiOnRydWUsInRvcENoZXZyb25Db2xvciI6IiMyMjIyMjIiLCJtZWdhQ2hldnJvbkNvbG9yIjoiIzIyMjIyMiIsIm1lZ2FDaGlsZEZvbnRXZWlnaHQiOiI0MDAiLCJzdWJTdWJGb250V2VpZ2h0IjoiNDAwIiwibmF0aXZlUGFyZW50cyI6W10sInVzZVVuaXZlcnNhbEJhY2tncm91bmRzIjp0cnVlLCJ1c2VDdXN0b21OYXZPbmx5IjpmYWxzZSwic3ViU3ViVGV4dENvbG9yIjoiIzIyMjIyMiIsIm1lZ2ExTWF4V2lkdGgiOiIxNjAwIiwibWVnYTRHcm91cCI6bnVsbCwidG9wRm9udFNpemUiOiIxNiIsIm1pcnJvckFsaWdubWVudCI6InN0YXJ0IiwibWVnYTRFeHBhbmRBbGwiOmZhbHNlLCJ0ZXh0Q29sb3JzR3JvdXAiOm51bGwsInN1YkZvbnRTaXplIjoiMTQiLCJ0eXBvZ3JhcGh5R3JvdXAiOm51bGwsIm1pcnJvckl0ZW1HYXAiOiIwIiwiYW5pbWF0ZU1lZ2FNZW51Ijp0cnVlLCJtZWdhR3JhbmRjaGlsZFRleHRDb2xvciI6IiMyMjIyMjIiLCJtZWdhUGFyZW50VGV4dENvbG9yIjoiIzIyMjIyMiIsIm1pcnJvclN0eWxlcyI6bnVsbCwibWVnYTFMYXlvdXQiOiJncmlkIiwibWVnYUdyYW5kY2hpbGRGb250U2l6ZSI6IjE0IiwiaG92ZXJUZXh0Q29sb3IiOiJjdXJyZW50Q29sb3IiLCJtZWdhNENvbHVtbnMiOiIzIiwibWVnYTJFeHBhbmRBbGwiOmZhbHNlLCJtZWdhM0NvbHVtbnMiOiIzIiwiY2hldnJvbnNHcm91cCI6bnVsbCwibWVnYTFDb2x1bW5zIjoiMyIsIm1lZ2EyQ29sdW1ucyI6IjMiLCJsYXlvdXRQcmVzZXQiOiJjbGFzc2ljIiwic3ViU3ViTmF2QmFja2dyb3VuZENvbG9yIjoiI2ZmZmZmZiIsInJlc3RvcmVNaXJyb3JEZWZhdWx0cyI6ZmFsc2UsInN1YkZvbnRXZWlnaHQiOiI0MDAiLCJ0b3BGb250V2VpZ2h0IjoiNTAwIiwibWVnYTFFeHBhbmRBbGwiOmZhbHNlLCJuYXZBbGlnbm1lbnQiOiJjZW50ZXIiLCJhbmltYXRlTWVnYUNoZXZyb25zIjp0cnVlLCJ3aWRnZXRNb2RlIjoibWlycm9yIiwic3ViVGV4dENvbG9yIjoiIzIyMjIyMiIsInN1Yk5hdkJhY2tncm91bmRDb2xvciI6IiNmZmZmZmYiLCJtZWdhNE1heFdpZHRoIjoiMTYwMCIsImJhY2tncm91bmRDb2xvcnNHcm91cCI6bnVsbCwidG9wVGV4dENvbG9yIjoiIzIyMjIyMiIsIm1pcnJvck9wZW5NZW51c09uSG92ZXIiOiIiLCJtZWdhUGFyZW50Rm9udFNpemUiOiIxNiIsIm1lbnVJbnRlcmFjdGlvbkdyb3VwIjpudWxsLCJtaXJyb3JTdHlsaW5nR3JvdXAiOm51bGwsImVuYWJsZUN1c3RvbU5hdmlnYXRpb25CdWlsZGVyIjpmYWxzZSwibWVnYTFBbHBoYWJldGl6ZSI6ZmFsc2UsIm1lZ2EzR3JvdXAiOm51bGwsIm1lZ2E0QWxwaGFiZXRpemUiOmZ
920hbHNlLCJhbmltYXRpb25zRW5hYmxlZCI6dHJ1ZSwiY2F0ZWdvcnlVcmxQcmVmaXgiOiJ3aGF0LXdlLWRvIiwidW5pdmVyc2FsQ2hldnJvblN0eWxlIjoiZmlsbGVkIiwidW5pdmVyc2FsVGV4dENvbG9yIjoiIzExMTExMSIsInVzZVNlcGFyYXRlTWlycm9yU3R5bGluZyI6ZmFsc2UsImxvYWRpbmdBbmltYXRpb25TdHlsZSI6InNoaW1tZXIiLCJtZWdhMkdyb3VwIjpudWxsLCJtZWdhQ2hldnJvblN0eWxlIjoiZmlsbGVkIiwidW5pdmVyc2FsQ2hldnJvbkNvbG9yIjoiIzIyMjIyMiIsImhvdmVyQmFja2dyb3VuZENvbG9yIjoiIiwibWVnYU1lbnVCYWNrZ3JvdW5kQ29sb3IiOiIjZmZmZmZmIiwidW5pdmVyc2FsQmFja2dyb3VuZENvbG9yIjoiI2ZmZmZmZiIsIm1lZ2EzTGF5b3V0IjoiZ3JpZCIsIm5hdmlnYXRpb25Ub29sc0dyb3VwIjpudWxsLCJtZWdhUGFyZW50Rm9udFdlaWdodCI6IjYwMCIsIm1lZ2FDaGlsZFRleHRDb2xvciI6IiMyMjIyMjIiLCJzdWJTdWJGb250U2l6ZSI6IjE0IiwibWVnYTNBbHBoYWJldGl6ZSI6ZmFsc2UsImxheW91dEdyb3VwIjpudWxsLCJ1bml2ZXJzYWxVbmRlcmxpbmUiOmZhbHNlLCJlbmFibGVOYXZpZ2F0aW9uVHJlZVByZXZpZXciOmZhbHNlLCJtZWdhR3JhbmRjaGlsZEZvbnRXZWlnaHQiOiI0MDAiLCJ3aWRnZXRNb2RlVXJsc0dyb3VwIjpudWxsLCJhbmltYXRpb25zR3JvdXAiOm51bGwsIm9wZW5NZW51c09uSG92ZXIiOmZhbHNlLCJ1bml2ZXJzYWxDaGV2cm9uV2VpZ2h0IjoiMiIsIm1lZ2EzTWF4V2lkdGgiOiIxNjAwIiwibWVnYTRMYXlvdXQiOiJncmlkIiwiYW5pbWF0ZVN1YlN1Yk5hdiI6dHJ1ZSwibWVnYTNFeHBhbmRBbGwiOmZhbHNlLCJ1bml2ZXJzYWxDaGV2cm9uU2l6ZSI6IjE0IiwidXNlVW5pdmVyc2FsQ2hldnJvblNldHRpbmdzIjp0cnVlfQ=="><!-- NMW Full Feature Stable: Manual Navigation Items live-update fix --><!--
921  Use this same HTML for both widget instances.
922
923  Content Panel widgetMode:
924    full   = loads Collection, Native, CUSTOMNAV, and Manual navigation data
925    mirror = renders the Full widget's shared navigation vertically
926
927  The hidden manual source is required because Duda link inputs must be output
928  through the custom_link helper. JavaScript reads the Duda-generated anchor
929  attributes and moves them into the rendered navigation.
930
931  Manual Folder items do not require a link. An empty manual Folder is shown as
932  a disabled preview inside the Duda editor, then omitted from preview/live pages
933  until a valid child is assigned.
934--> <div class="nmw-root" data-nmw-mode="full"> <a class="nmw-skip-link" href="#nmw-full-main-nav">Skip to navigation</a> 
935 <div class="nmw-loading" data-nmw-loading-view="" role="status" aria-live="polite" aria-label="Loading navigation" hidden=""></div> 
936 <div class="nmw-mobile-nav-wrapper nmw-render" data-nmw-render=""></div> 
937 <div class="nmw-tools" data-nmw-tools="" aria-live="polite"></div> 
938 <div class="nmw-manual-items-source" data-nmw-manual-source="" aria-hidden="true" hidden=""></div> 
939</div> 
940</div> 
941</div> 
942</div> 
943</div> 
944</div> 
945</section> 
946</div> 
947 <div data-auto="flex-section" id="1029527021" data-flex-id="6406026c90e1521f6ecb0eca" data-layout-section=""> <div class="flex-widgets-container" id="1659590659"></div> 
948 <section id="6406026c90e1521f6ecb0ec9" class="flex-element section" data-auto="flex-element-section"> <div id="6406026c90e1521f6ecb0ecb" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="6406026c90e1521f6ecb0ecc" class="flex-element group" data-auto="flex-element-group"> <div id="6406026c90e1521f6ecb0ecd" class="flex-element group" data-auto="flex-element-group"></div> 
949</div> 
950</div> 
951</section> 
952</div> 
953</div> 
954 <div class="layout-drawer-overlay" id="layout-drawer-overlay"></div> 
955</div> 
956 <div dmwrapped="true" id="dmFirstContainer" class="dmBody u_dmStyle_template_home dm-home-page" themewaschanged="true"> <div id="allWrapper" class="allWrapper" data-auto="all-wrapper"><!-- navigation placeholders --> <div id="dm_content" class="dmContent" role="main"> <div dm:templateorder="170" class="dmHomeRespTmpl mainBorder dmRespRowsWrapper dmFullRowRespTmpl" id="1716942098"> <div data-layout-section="" data-auto="flex-section" id="1352321863" data-flex-id="fa95c160" class="" data-hidden-on-tablet="true" data-hidden-on-desktop="true" data-hidden-on-mobile="true"> <div class="flex-widgets-container" id="1806718005"></div> 
957 <section id="b64ec8bb" class="flex-element section" data-auto="flex-element-section"> <div id="c877aa44" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="c1277cd5" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="b63d2dd0" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop=""></div> 
958 <div id="87457d0a" class="flex-element group" data-auto="flex-element-group"></div> 
959</div> 
960</div> 
961</section> 
962</div> 
963 <div data-layout-section="" data-auto="flex-section" id="1883436679" data-flex-id="060524e0" class="" data-hidden-on-tablet="true" data-hidden-on-mobile="true" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1224393650"></div> 
964 <section id="a214224e" class="flex-element section" data-auto="flex-element-section"> <div id="b9331987" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="102296bd" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-mobile="" data-hidden-on-desktop=""> <div id="db4fad4c" class="flex-element group" data-auto="flex-element-group"> <div id="widget_oo7" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1877817532"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1877817532" style=""> <h2><span style="display: unset;">Recent Blog Posts</span></h2> 
965</div> 
966</div> 
967 <div id="group_69j" class="flex-element group" data-auto="flex-element-group"> <div id="widget_r6f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="internal_blog_list" data-external-id="1550316043"> <div class="default u_1550316043 mainBlog layout4" style="display:none" id="1550316043" dmle_extension="internal_blog_list" data-element-type="internal_blog_list" show-images="true" wr="true" trim-title="false" show-blog-title="false" show-action-text="true" text-align="left" icon="true" surround="true" adwords="" list-layout="layout4" visible-items="10" visible-post-lines="4" search-tags="" search-term="" more-posts-text="Show More" header-type="H2" posts-p
967adding="10" posts-per-row="3" show-author="false" text-box-padding="true" action-text="Read more →" skip-post-index="-1" blog-posts-feature-flag="true" data-image-hover-effect="zoomout" show-more-posts-text="true" data-paginate-total-elements="0"></div> 
968</div> 
969</div> 
970</div> 
971</div> 
972</div> 
973</section> 
974</div> 
975 <div data-flex-id="80091d02" data-auto="flex-section" id="1353459198" data-layout-section="" data-page-element-type="section" data-page-element-id="1975745003" class="homeherogeo" data-custom-class-name="homeherogeo" data-hidden-on-tablet="true"> <div class="flex-widgets-container" id="1344935523"></div> 
976 <section id="8ee02dbb" class="flex-element section" data-auto="flex-element-section"> <div id="8a298522" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid="" data-binding="eyJiaW5kaW5nTmFtZSI6ImJhY2tncm91bmQiLCJ2YWx1ZSI6IlNUQVRJQ19QQUdFX0NPTExFQ1RJT04ue1wiY25cIjpcIjAxIEhPTUVcIixcImZuXCI6XCJIRVJPIElNQUdFXCJ9In0=" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/home-hero-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png') !important;"> <div id="7e1edc8e" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="9a93c39e" class="flex-element group" data-auto="flex-element-group"> <div id="widget_lav" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="495176596"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="495176596" style=""><p class="m-text-align-center text-align-center m-size-19 size-24 t-size-24"><span class="font-size-24 m-font-size-19 t-font-size-24" style="color:var(--color_2);display:initial;font-family:Oswald, 'Oswald Fallback';font-style:italic;" t-font-size-set="true">We know what we're doing and we guarantee what we do</span></p></div> 
977</div> 
978 <div id="d02f159c" class="flex-element widget-wrapper HH1" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1808999171"> <div class="u_1808999171 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1808999171" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiSEVSTyBIRUFETElORVwifSJ9XQ==" data-diy-text="" style="">Plumbing, Heating, &amp; Air Conditioning Specialists</div> 
979</div> 
980 <div id="bee2be20" class="flex-element widget-wrapper HH2" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1519924509"> <div class="u_1519924509 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1519924509" data-diy-text="" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiSEVSTyBHRU9cIn0ifV0=">Central New Jersey</div> 
981</div> 
982 <div id="ecb6a7f9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1716721511"> <div class="u_1716721511 widget-c4b2f9 dmCustomWidget" data-lazy-load="true" data-title="" id="1716721511" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtb3RGYWN0VGV4dCIsInZhbHVlIjoic2l0ZV90ZXh0Lm1vdGl2YXRpbmcgZmFjdG9ycyJ9XQ==" icon="false" surround="false" data-widget-id="c4b2f903d115403c8c21e9ec6044a4fe" data-widget-version="59" data-widget-config="eyJtb3RGYWN0VGV4dCI6IjxwIGNsYXNzPVwicnRlQmxvY2tcIj5FbWVyZ2VuY3kgU2VydmljZSBBdmFpbGFibGU8L3A+PHAgY2xhc3M9XCJydGVCbG9ja1wiPkxvY2FsbHkgT3duZWQgJmFtcDsgT3BlcmF0ZWQgU2luY2UgMTk4NTwvcD48cCBjbGFzcz1cInJ0ZUJsb2NrXCI+QWxsIFdvcmsgSXMgR3VhcmFudGVlZDwvcD48cCBjbGFzcz1cInJ0ZUJsb2NrXCI+U2FtZS1EYXkgRXN0aW1hdGVzPC9wPiIsImhvcml6VG9nZ2xlIjpmYWxzZSwibWZBbGlnbiI6Ik1GYWxpZ25DIiwiZGl2U3R5bGUiOiJtZi1ob3Jpei1idWwifQ=="> <div class="motFactCont MFalignC "> <p class="rteBlock">Emergency Service Available</p><p class="rteBlock">Locally Owned &amp; Operated Since 1985</p><p class="rteBlock">All Work Is Guaranteed</p><p class="rteBlock">Same-Day Estimates</p> 
983</div> 
984</div> 
985</div> 
986</div> 
987 <div id="1af2c86c" class="flex-element group" data-auto="flex-element-group"> <div id="af69bc42" class="flex-element group" data-auto="flex-element-group" data-anim-extended="eyJkZXNrdG9wIjp7InRyaWdnZXIiOiJlbnRyYW5jZSIsImFuaW1hdGlvbiI6ImZhZGVJbkNvbWJvIiwiZHVyYXRpb24iOjEsImRlbGF5IjowLCJpbnRlbnNpdHkiOjEsImRpciI6ImluLXBsYWNlIn19"> <div id="4eeabad8" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1103017058"> <div class="widget-6f306f u_1103017058 dmCustomWidget" data-lazy-load="true" data-title="" id="1103017058" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="6f306f3f1793463e9c5243713002bc78" data-widget-version="4" data-widget-config="eyJoZWFkaW5nVGFnIjoiaDUiLCJmYWxsYmFja1RleHQiOiJSZXF1ZXN0IExvcmVtIEVwc29tIn0="> <div class="form-title-widget"> <h5 class="form-title-output">Request Lorem Epsom</h5> 
988</div> 
989</div> 
990</div> 
991 <div id="47cf22a1" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="form" lock-element-for-editing="true" data-external-id="1150796797"> <div class="dmform default native-inputs u_1150796797 form-ltr-direction" data-element-type="dContactUsRespId" captcha="true" data-require-captcha="true" data-captcha-position="bottomleft" id="1150796797" data-captcha-message="PHAgY2xhc3M9InJ0ZUJsb2NrIj5UaGlzIHNpdGUgaXMgcHJvdGVjdGVkIGJ5IHJlQ0FQVENIQS48L3A+" data-layout="layout-1" alignment="form-ltr-direction" data-binding="W3siYmluZGluZ05hbWUiOiJlbWFpbCIsInZhbHVlIjoiY29udGVudF9saWJyYXJ5Lmdsb2JhbC5lbWFpbC5lbWFpbCAtIGxvYyAxIn1d"> <h3 class="dmform-title dmwidget-title" id="1150005117" hide="true">Hero Request Form</h3> 
992 <div class="dmform-wrapper" id="1409781386" captcha-lang="en"> <form method="post" class="dmRespDesignRow" locale="ENGLISH" id="1323328356"> <div class="spacing-container" id="1075440636">
992 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1493910956"> <label for="1002472461" id="1638296468" class="" hide="true" data-dm-for="dmform-0">Name:</label> 
993 <input type="text" class="" name="dmform-0" id="1002472461" placeholder="Full Name*" data-placeholder-original="Full Name*"/>  <input type="hidden" name="label-dmform-0" value="Name:" id="1958467359" class=""/> </div> 
994 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1250382854"> <label for="1711628564" id="1196706414" class="" hide="true" data-dm-for="dmform-2">Phone:</label> 
995 <input type="tel" class="" name="dmform-2" id="1711628564" placeholder="Phone*" data-placeholder-original="Phone*"/>  <input type="hidden" name="label-dmform-2" value="Phone" id="1821251936"/> </div> 
996 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1134372323"> <label for="1680337740" id="1610547876" class="" hide="true" data-dm-for="dmform-1">Email:</label> 
997 <input type="email" class="" name="dmform-1" id="1680337740" placeholder="Email*" data-placeholder-original="Email*"/>  <input type="hidden" name="label-dmform-1" value="Email" id="1561482475"/> </div> 
998 <div class="dmforminput small-12 dmRespDesignCol medium-12 large-12 required" id="1787228816"> <label for="1811439473" id="1078628703" class="" hide="true" data-dm-for="dmform-3">Services</label> 
999<input type="text" name="dmform-3" id="1811439473"/><input type="hidden" name="label-dmform-3" value="Services" id="1022792676" class=""/></div> 
1000</div> 
1001 <div class="dmformsubmit dmWidget R" id="1256652768"> <input class="" name="submit" type="submit" value="Form Button" id="1312556995"/> </div> 
1002 <input name="dmformsendto" type="hidden" id="1897328134" class="" value="O0wrseIfFLsSlLR4GAldFxB1Sr68H7msKcdcaddm0B3jZ7FqDnEw1jdymC3Zzixv" data-dec="true"/>  <input class="dmActionInput" type="hidden" name="action" value="/_dm/s/rt/widgets/dmform.submit.jsp" id="1805970770"/>  <input name="dmformsubject" type="hidden" value="Hibu Website Form Submission" id="1917053137" class="" data-email-subject="Hibu Website Form Submission"/>  <input name="dmformfrom" type="hidden" value="" id="1518513217"/>  <input name="dmformautoreplyenabled" type="hidden" value="false" id="1967926011" class=""/>  <input name="dmformautoreplyfrom" type="hidden" value="" id="1503953154"/>  <input name="dmformautoreplysubject" type="hidden" value="Thank you for your submission" id="1418156533"/>  <input name="dmformautoreplymsg" type="hidden" value="V2UndmUgcmVjZWl2ZWQgeW91ciBtZXNzYWdlIC0gdGhhbmsgeW91LiBPbmUgb2Ygb3VyIHRlYW0gbWVtYmVycyB3aWxsIGdldCBiYWNrIHRvIHlvdSBzb29uLg==" id="1493233311"/>  <input name="dmformautoreplyincludeformcopy" type="hidden" value="true" id="1858917062"/> <input name="dmformsubmitparams" type="hidden" value="8mpKnCSiNQXK/d9M7IDrSzRkXjNcoSR4otjhyuJzlevtwXXoEguImDRndSDnOmf2sfMRaz5MAUXOw6lZuZAWrON/2FkPs4NwirBgV6k3yEeQdfiJQxyAfUEh+9y1iVN9P02ng7bXFGPYhEC18MKaU+ufRWgRmY7csfP/44Df6nMIcN1pH0iILsbxuKKEkt2NkyLOnmZaBfYiaWTLkceS85c95LrdFW7TCOta+TWc27OQGJLDXEN4fN9Bvju8oYehftFNagxrSdqYFN8w9aWJd3JNjtCb3Afwq88J8ZzfIeFoIDARQZTckoRtgT69Qvz2/8GN1M+DDAAVsCxVpPIACVYxUpNEW4ol9Q3qkrNXXhk+MS3YYKwf/hmh7WSQZCv2+8NBatnUEgNpl+VmUEDof72wpbAkigYomJM4Uu2ykLOpm2YnRixN0BQa8NB2jlvn5az5NHcF7x9NxuuQvvxrnvbFLAj+vvzVrIQIHcLaZF0QQciF2lBXFL1T2st2kt2mf7oO1cywG1hmcclnMo9pzw==" data-dec="true"/><input type="hidden" name="page_uuid" value="b196ca30b540442ba4addf5cc10c1c6b"/></form> 
1003</div> 
1004 <div class="dmform-success" style="display:none" id="1270715357"> <br id="1940137331"/>  <p class="rteBlock" id="1104277100">Thank you for contacting us.</p> 
1005 <p class="rteBlock" id="1720249121">We will get back to you as soon as possible.</p> 
1006</div> 
1007 <div class="dmform-error" style="display:none" id="1718556316">Oops, there was an error sending your message. <br id="1620515584"/> Please try again later.</div> 
1008</div> 
1009</div> 
1010</div> 
1011 <div id="741807bc" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-hidden-on-mobile="" data-external-id="1957072
1011079"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1957072079" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJjb250ZW50X2xpYnJhcnkuZ2xvYmFsLnBob25lLmxvYy0wIC0gbWFpbiJ9LHsiYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiY29udGVudF9saWJyYXJ5Lmdsb2JhbC5waG9uZS5sb2MtMCAtIG1haW4ifV0=" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1694304316"> <span class="icon hasFontIcon icon-star" id="1053731962"></span> 
1012</span> 
1013 <span class="text" id="1629283249" data-diy-text="">(908) 259-4326</span> 
1014</a> 
1015</div> 
1016</div> 
1017</div> 
1018</div> 
1019</section> 
1020</div> 
1021 <div data-flex-id="ccff46fc" data-auto="flex-section" id="1940594125" data-layout-section="" data-page-element-type="section" data-page-element-id="1163138511"> <div class="flex-widgets-container" id="1345657087"></div> 
1022 <section id="7d86ca39" class="flex-element section" data-auto="flex-element-section"> <div id="f6126fc0" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid="" data-binding="eyJiaW5kaW5nTmFtZSI6ImJhY2tncm91bmQiLCJ2YWx1ZSI6IlNUQVRJQ19QQUdFX0NPTExFQ1RJT04ue1wiY25cIjpcIjAxIEhPTUVcIixcImZuXCI6XCJIRVJPIElNQUdFXCJ9In0=" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/home-hero-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png') !important;"> <div id="bd2dec40" class="flex-element group" data-auto="flex-element-group"> <div id="33cb6497" class="flex-element group" data-auto="flex-element-group"> <div id="d8b3e84e" class="flex-element group" data-auto="flex-element-group"> <div id="f9231ae8" class="flex-element group" data-auto="flex-element-group"> <div id="2983c3b4" class="flex-element group" data-auto="flex-element-group"> <div id="430ce717" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1584368393"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1584368393" style=""><p class="t-size-24 text-align-center size-40 m-size-32"><strong class="t-font-size-24 font-size-40 m-font-size-32" t-font-size-set="true" style="color:var(--color_18);font-weight:bold;display:initial;font-family:Oswald, 'Oswald Fallback';font-style:normal;">Sudden Service with a Smile!</strong></p></div> 
1023</div> 
1024 <div id="07a61ad2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1623915418"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1623915418" style=""><p class="t-size-24 m-size-19 text-align-center m-text-align-center size-24"><span class="t-font-size-24 m-font-size-19 font-size-24" t-font-size-set="true" style="color:var(--color_2);display:initial;font-family:Oswald, 'Oswald Fallback';font-style:italic;">We know what we're doing and we guarantee what we do!</span></p></div> 
1025</div> 
1026 <div id="5b37d01d" class="flex-element widget-wrapper HH1" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1580577466"> <div class="u_1580577466 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1580577466" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiSEVSTyBIRUFETElORVwifSJ9XQ==" data-diy-text="" style="">Plumbing, Heating, &amp; Air Conditioning Specialists</div> 
1027</div> 
1028 <div id="32743346" class="flex-element widget-wrapper HH2" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1015158955"> <div class="u_1015158955 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1015158955" data-diy-text="" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiSEVSTyBHRU9cIn0ifV0=">Central New Jersey</div> 
1029</div> 
1030 <div id="5d4d0290" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1905390760"> <div class="u_1905390760 widget-c4b2f9 dmCustomWidget" data-lazy-load="true" data-title="" id="1905390760" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtb3RGYWN0VGV4dCIsInZhbHVlIjoic2l0ZV90ZXh0Lm1vdGl2YXRpbmcgZmFjdG9ycyJ9XQ==" icon="false" surround="false" data-widget-id="c4b2f903d115403c8c21e9ec6044a4fe" data-widget-version="59" data-widget-config="eyJtb3RGYWN0VGV4dCI6IjxwIGNsYXNzPVwicnRlQmxvY2tcIj5FbWVyZ2VuY3kgU2VydmljZSBBdmFpbGFibGU8L3A+PHAgY2xhc3M9XCJydGVCbG9ja1wiPkxvY2FsbHkgT3duZWQgJmFtcDsgT3BlcmF0ZWQgU2luY2UgMTk4NTwvcD48cCBjbGFzcz1cInJ0ZUJsb2NrXCI+QWxsIFdvcmsgSXMgR3VhcmFudGVlZDwvcD48cCBjbGFzcz1cInJ0ZUJsb2NrXCI+U2FtZS1EYXkgRXN0aW1hdGVzPC9wPiIsImhvcml6VG9nZ2xlIjpmYWxzZSwibWZBbGlnbiI6Ik1GYWxpZ25DIiwiZGl2U3R5bGUiOiJtZi1ob3Jpei1idWwifQ=="> <div class="motFactCont MFalignC "> <p class="rteBlock">Emergency Service Available</p><p class="rteBlock">Locally Owned &amp; Operated Since 1985</p><p class="rteBlock">All Work Is Guaranteed</p><p class="rteBlock">Same-Day Estimates</p> 
1031</div> 
1032</div> 
1033</div> 
1034</div> 
1035 <div id="7b1f87d6" class="flex-element group" data-auto="flex-element-group"></div> 
1036</div> 
1037 <div id="5845d28b" class="flex-element group" data-auto="flex-element-group"> <div id="5293a1fb" class="flex-element group" data-auto="flex-element-group" data-anim-extended="eyJkZXNrdG9wIjp7InRyaWdnZXIiOiJlbnRyYW5jZSIsImFuaW1hdGlvbiI6ImZhZGVJbkNvbWJvIiwiZHVyYXRpb24iOjEsImRlbGF5IjowLCJpbnRlbnNpdHkiOjEsImRpciI6ImluLXBsYWNlIn19"> <div id="d85d1420" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1895740635"> <div class="widget-6f306f u_1895740635 dmCustomWidget" data-lazy-load="true" data-title="" id="1895740635" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="6f306f3f1793463e9c5243713002bc78" data-widget-version="4" data-widget-config="eyJoZWFkaW5nVGFnIjoiaDUiLCJmYWxsYmFja1RleHQiOiJSZXF1ZXN0IExvcmVtIEVwc29tIn0="> <div class="form-title-widget"> <h5 class="form-title-output">Request Lorem Epsom</h5> 
1038</div> 
1039</div> 
1040</div> 
1041 <div id="477665ed" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="form" lock-element-for-editing="true" data-external-id="1695506483"> <div class="dmform default native-inputs u_1695506483 form-ltr-direction" data-element-type="dContactUsRespId" captcha="true" data-require-captcha="true" data-captcha-position="bottomleft" id="1695506483" data-captcha-message="PHAgY2xhc3M9InJ0ZUJsb2NrIj5UaGlzIHNpdGUgaXMgcHJvdGVjdGVkIGJ5IHJlQ0FQVENIQS48L3A+" data-layout="layout-1" alignment="form-ltr-direction" data-binding="W3siYmluZGluZ05hbWUiOiJlbWFpbCIsInZhbHVlIjoiY29udGVudF9saWJyYXJ5Lmdsb2JhbC5lbWFpbC5lbWFpbCAtIGxvYyAxIn1d"> <h3 class="dmform-title dmwidget-title" id="1599904224" hide="true">Hero Request Form</h3> 
1042 <div class="dmform-wrapper" id="1682359653" captcha-lang="en"> <form method="post" class="dmRespDesignRow" locale="ENGLISH" id="1228968678"> <div class="spacing-container" id="1201393751">
1042 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1656689656"> <label for="1876953781" id="1966752440" class="" hide="true" data-dm-for="dmform-0">Name:</label> 
1043 <input type="text" class="" name="dmform-0" id="1876953781" placeholder="Full Name*" data-placeholder-original="Full Name*"/>  <input type="hidden" name="label-dmform-0" value="Name:" id="1114591778" class=""/> </div> 
1044 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1562854542"> <label for="1421327511" id="1661348607" class="" hide="true" data-dm-for="dmform-2">Phone:</label> 
1045 <input type="tel" class="" name="dmform-2" id="1421327511" placeholder="Phone*" data-placeholder-original="Phone*"/>  <input type="hidden" name="label-dmform-2" value="Phone" id="1046137098"/> </div> 
1046 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1292234150"> <label for="1164579845" id="1059241548" class="" hide="true" data-dm-for="dmform-1">Email:</label> 
1047 <input type="email" class="" name="dmform-1" id="1164579845" placeholder="Email*" data-placeholder-original="Email*"/>  <input type="hidden" name="label-dmform-1" value="Email" id="1715615544"/> </div> 
1048 <div class="dmforminput small-12 dmRespDesignCol medium-12 large-12 required" id="1255268547"> <label for="1038747556" id="1167640918" class="" hide="true" data-dm-for="dmform-3">Services</label> 
1049<input type="text" name="dmform-3" id="1038747556"/><input type="hidden" name="label-dmform-3" value="Services" id="1210107458" class=""/></div> 
1050</div> 
1051 <div class="dmformsubmit dmWidget R" id="1173164259"> <input class="" name="submit" type="submit" value="Form Button" id="1364714395"/> </div> 
1052 <input name="dmformsendto" type="hidden" id="1541630791" class="" value="O0wrseIfFLsSlLR4GAldFxB1Sr68H7msKcdcaddm0B3jZ7FqDnEw1jdymC3Zzixv" data-dec="true"/>  <input class="dmActionInput" type="hidden" name="action" value="/_dm/s/rt/widgets/dmform.submit.jsp" id="1515400453"/>  <input name="dmformsubject" type="hidden" value="Hibu Website Form Submission" id="1294056588" class="" data-email-subject="Hibu Website Form Submission"/>  <input name="dmformfrom" type="hidden" value="" id="1249267336"/>  <input name="dmformautoreplyenabled" type="hidden" value="false" id="1247622011" class=""/>  <input name="dmformautoreplyfrom" type="hidden" value="" id="1897501722"/>  <input name="dmformautoreplysubject" type="hidden" value="Thank you for your submission" id="1429180330"/>  <input name="dmformautoreplymsg" type="hidden" value="V2UndmUgcmVjZWl2ZWQgeW91ciBtZXNzYWdlIC0gdGhhbmsgeW91LiBPbmUgb2Ygb3VyIHRlYW0gbWVtYmVycyB3aWxsIGdldCBiYWNrIHRvIHlvdSBzb29uLg==" id="1289178872"/>  <input name="dmformautoreplyincludeformcopy" type="hidden" value="true" id="1653992201"/> <input name="dmformsubmitparams" type="hidden" value="8mpKnCSiNQXK/d9M7IDrSzRkXjNcoSR4otjhyuJzlevtwXXoEguImDRndSDnOmf2sfMRaz5MAUXOw6lZuZAWrON/2FkPs4NwirBgV6k3yEeQdfiJQxyAfUEh+9y1iVN9P02ng7bXFGPYhEC18MKaU+ufRWgRmY7csfP/44Df6nMIcN1pH0iILsbxuKKEkt2NkyLOnmZaBfYiaWTLkceS85c95LrdFW7TCOta+TWc27OQGJLDXEN4fN9Bvju8oYehftFNagxrSdqYFN8w9aWJd3JNjtCb3Afwq88J8ZzfIeFoIDARQZTckoRtgT69Qvz2/8GN1M+DDAAVsCxVpPIACVYxUpNEW4ol9Q3qkrNXXhk+MS3YYKwf/hmh7WSQZCv2+8NBatnUEgNpl+VmUEDof72wpbAkigYomJM4Uu2ykLOpm2YnRixN0BQa8NB2jlvn5az5NHcF7x9NxuuQvvxrnvbFLAj+vvzVrIQIHcLaZF0QQciF2lBXFL1T2st2kt2mf7oO1cywG1hmcclnMo9pzw==" data-dec="true"/><input type="hidden" name="page_uuid" value="b196ca30b540442ba4addf5cc10c1c6b"/></form> 
1053</div> 
1054 <div class="dmform-success" style="display:none" id="1744782499"> <br id="1149270014"/>  <p class="rteBlock" id="1207146278">Thank you for contacting us.</p> 
1055 <p class="rteBlock" id="1946070174">We will get back to you as soon as possible.</p> 
1056</div> 
1057 <div class="dmform-error" style="display:none" id="1257041976">Oops, there was an error sending your message. <br id="1707348827"/> Please try again later.</div> 
1058</div> 
1059</div> 
1060</div> 
1061 <div id="4d1153e1" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-hidden-on-mobile="" data-external-id="1555321268"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1555321268" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJjb250ZW50X2xpYnJhcnkuZ2xvYmFsLnBob25lLmxvYy0wIC0gbWFpbiJ9LHsiYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiY29udGVudF9saWJyYXJ5Lmdsb2JhbC5waG9uZS5sb2MtMCAtIG1haW4ifV0=" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1329207953"> <span class="icon hasFontIcon icon-star" id="1978862163"></span> 
1062</span> 
1063 <span class="text" id="1092060644" data-diy-text="">(908) 259-4326</span> 
1064</a> 
1065</div> 
1066</div> 
1067</div> 
1068 <div id="f84ea0c1" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1843650934"> <div class="widget-22eebe u_1843650934 dmCustomWidget" data-lazy-load="true" data-title="" id="1843650934" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="22eebe93636d4d1295bb7b863c039867" data-widget-version="28" data-widget-config="eyJzbGFudCI6IndhdmUtNSIsImFuZ2xlQ29sb3IiOiJ2YXIoLS1jb2xvcl8xKSIsImRyb3BTaGFkb3ciOiJub25lIn0="> <div class="svgCont"> <svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 1580 134.23" preserveaspectratio="none" style="fill:var
1068(--color_1);" class="svgFull"> <path d="M9.85,668.77V551.91c78.22,21.63,193.75,45.16,326.4,47.82,270.28,5.4,326.48-55,708.42-64.84,139.41-3.6,236.14,21.61,352.82,48,80.8,18.27,148.71,20.9,177.53,21.08l14.83-1.61v66.42Z" transform="translate(-9.85 -534.54)"></path> 
1069</svg> 
1070</div> 
1071</div> 
1072</div> 
1073</div> 
1074</div> 
1075</div> 
1076</section> 
1077</div> 
1078 <div data-flex-id="69684484" data-auto="flex-section" id="1966481060" data-layout-section="" data-page-element-type="section" data-page-element-id="1975745003" class="collectionherogeo" data-custom-class-name="collectionherogeo"> <div class="flex-widgets-container" id="1863211010"></div> 
1079 <section id="0a193907" class="flex-element section" data-auto="flex-element-section"> <div id="cf8a52cb" class="flex-element grid lazy" data-auto="flex-element-grid" data-layout-grid="" data-binding="eyJiaW5kaW5nTmFtZSI6ImJhY2tncm91bmQiLCJ2YWx1ZSI6ImR5bmFtaWNfcGFnZV9jb2xsZWN0aW9uLkhFUk8gSU1BR0UifQ==" data-background-image="https://le-cdn.hibuwebsites.com/md/dmip/dms3rep/multi/opt/black-jeans-1920w.png"> <div id="469f4440" class="flex-element group" data-auto="flex-element-group"> <div id="1bbc2121" class="flex-element group" data-auto="flex-element-group"> <div id="42ecfc84" class="flex-element widget-wrapper HH1" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1978469502"> <div class="u_1978469502 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1978469502" data-diy-text="" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJkeW5hbWljX3BhZ2
1079VfY29sbGVjdGlvbi5IRVJPIEhFQURMSU5FIn1d"> <h1 class="text-align-center" id="1061337039">New Title</h1> 
1080</div> 
1081</div> 
1082 <div id="8f1592ea" class="flex-element widget-wrapper HH2" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1400565967"> <div class="u_1400565967 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1400565967" data-diy-text="" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJkeW5hbWljX3BhZ2VfY29sbGVjdGlvbi5IRVJPIEdFTyJ9XQ=="> <h2 class="text-align-center t-size-31" id="1543043716"><span style="display: initial; color: var(--color_2);" id="1959321702">New Title</span></h2> 
1083</div> 
1084</div> 
1085 <div id="c2f88a8a" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1055630957"> <div class="u_1055630957 widget-c4b2f9 dmCustomWidget" data-lazy-load="true" data-title="" id="1055630957" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtb3RGYWN0VGV4dCIsInZhbHVlIjoic2l0ZV90ZXh0Lm1vdGl2YXRpbmcgZmFjdG9ycyJ9XQ==" icon="false" surround="false" data-widget-id="c4b2f903d115403c8c21e9ec6044a4fe" data-widget-version="59" data-widget-config="eyJtb3RGYWN0VGV4dCI6IjxwIGNsYXNzPVwicnRlQmxvY2tcIj5FbWVyZ2VuY3kgU2VydmljZSBBdmFpbGFibGU8L3A+PHAgY2xhc3M9XCJydGVCbG9ja1wiPkxvY2FsbHkgT3duZWQgJmFtcDsgT3BlcmF0ZWQgU2luY2UgMTk4NTwvcD48cCBjbGFzcz1cInJ0ZUJsb2NrXCI+QWxsIFdvcmsgSXMgR3VhcmFudGVlZDwvcD48cCBjbGFzcz1cInJ0ZUJsb2NrXCI+U2FtZS1EYXkgRXN0aW1hdGVzPC9wPiIsImhvcml6VG9nZ2xlIjpmYWxzZSwibWZBbGlnbiI6Ik1GYWxpZ25DIiwiZGl2U3R5bGUiOiJtZi1ob3Jpei1idWwifQ=="> <div class="motFactCont MFalignC "> <p class="rteBlock">Emergency Service Available</p><p class="rteBlock">Locally Owned &amp; Operated Since 1985</p><p class="rteBlock">All Work Is Guaranteed</p><p class="rteBlock">Same-Day Estimates</p> 
1086</div> 
1087</div> 
1088</div> 
1089 <div id="dfd8574d" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-hidden-on-mobile="" data-external-id="1663082492"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1663082492" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJjb250ZW50X2xpYnJhcnkuZ2xvYmFsLnBob25lLmxvYy0wIC0gbWFpbiJ9LHsiYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiY29udGVudF9saWJyYXJ5Lmdsb2JhbC5waG9uZS5sb2MtMCAtIG1haW4ifV0=" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1741311102"> <span class="icon hasFontIcon icon-star" id="1691189620"></span> 
1090</span> 
1091 <span class="text" id="1143008578" data-diy-text="">(908) 259-4326</span> 
1092</a> 
1093</div> 
1094 <div id="7c37966c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-hidden-on-mobile="" data-external-id="1235187992"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="/contact" data-element-type="dButtonLinkId" id="1235187992" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiVmlzaXQgLyBDb250YWN0IExpbmtcIn0ifSx7ImJpbmRpbmdOYW1lIjoiYnV0dG9uX3RleHQiLCJ2YWx1ZSI6IlNUQVRJQ19QQUdFX0NPTExFQ1RJT04ue1wiY25cIjpcImJ1dHRvbnNcIixcImZuXCI6XCJWaXNpdCAvIENvbnRhY3QgQnV0dG9uXCJ9In1d" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1156813547"> <span class="icon hasFontIcon icon-star" id="1701166175"></span> 
1095</span> 
1096 <span class="text" id="1667013445" data-diy-text="">DO NOT USE</span> 
1097</a> 
1098</div> 
1099</div> 
1100 <div id="f98f75f9" class="flex-element group" data-auto="flex-element-group"> <div id="d3a7d435" class="flex-element group" data-auto="flex-element-group" data-anim-extended="eyJkZXNrdG9wIjp7InRyaWdnZXIiOiJlbnRyYW5jZSIsImFuaW1hdGlvbiI6ImZhZGVJbkNvbWJvIiwiZHVyYXRpb24iOjEsImRlbGF5IjowLCJpbnRlbnNpdHkiOjEsImRpciI6ImluLXBsYWNlIn19"> <div id="26e458fe" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1559812349"> <div class="widget-6f306f dmCustomWidget" data-lazy-load="true" data-title="" id="1559812349" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="6f306f3f1793463e9c5243713002bc78" data-widget-version="4" data-widget-config="eyJoZWFkaW5nVGFnIjoiaDUiLCJmYWxsYmFja1RleHQiOiJSZXF1ZXN0IExvcmVtIEVwc29tIn0="> <div class="form-title-widget"> <h5 class="form-title-output">Request Lorem Epsom</h5> 
1101</div> 
1102</div> 
1103</div> 
1104 <div id="02a6e698" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="form" lock-element-for-editing="true" data-external-id="1321736646"> <div class="dmform default native-inputs u_1321736646 form-ltr-direction" data-element-type="dContactUsRespId" captcha="true" data-require-captcha="true" data-captcha-position="bottomleft" id="1321736646" data-captcha-message="PHAgY2xhc3M9InJ0ZUJsb2NrIj5UaGlzIHNpdGUgaXMgcHJvdGVjdGVkIGJ5IHJlQ0FQVENIQS48L3A+" data-layout="layout-1" alignment="form-ltr-direction" data-binding="W3siYmluZGluZ05hbWUiOiJlbWFpbCIsInZhbHVlIjoiY29udGVudF9saWJyYXJ5Lmdsb2JhbC5lbWFpbC5lbWFpbCAtIGxvYyAxIn1d"> <h3 class="dmform-title dmwidget-title" id="1761066701" hide="true">Hero Request Form</h3> 
1105 <div class="dmform-wrapper" id="1614599118" captcha-lang="en"> <form method="post" class="dmRespDesignRow" locale="ENGLISH" id="1027096042"> <div class="spacing-container" id="1699881382">
1105 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1424562268"> <label for="1222025205" id="1067313116" class="" hide="true" data-dm-for="dmform-0">Name:</label> 
1106 <input type="text" class="" name="dmform-0" id="1222025205" placeholder="Full Name*" data-placeholder-original="Full Name*"/>  <input type="hidden" name="label-dmform-0" value="Name:" id="1233222310" class=""/> </div> 
1107 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1710634734"> <label for="1366650964" id="1717674707" class="" hide="true" data-dm-for="dmform-2">Phone:</label> 
1108 <input type="tel" class="" name="dmform-2" id="1366650964" placeholder="Phone*" data-placeholder-original="Phone*"/>  <input type="hidden" name="label-dmform-2" value="Phone" id="1613386489"/> </div> 
1109 <div class="dmforminput required  small-12 dmRespDesignCol medium-12 large-12" id="1933944557"> <label for="1703274902" id="1250879473" class="" hide="true" data-dm-for="dmform-1">Email:</label> 
1110 <input type="email" class="" name="dmform-1" id="1703274902" placeholder="Email*" data-placeholder-original="Email*"/>  <input type="hidden" name="label-dmform-1" value="Email" id="1550571125"/> </div> 
1111 <div class="dmforminput small-12 dmRespDesignCol medium-12 large-12 required" id="1545571701"> <label for="1926368438" id="1017295523" class="" hide="true" data-dm-for="dmform-3">Services</label> 
1112<input type="text" name="dmform-3" id="1926368438"/><input type="hidden" name="label-dmform-3" value="Services" id="1443569467" class=""/></div> 
1113</div> 
1114 <div class="dmformsubmit dmWidget R" id="1211187141"> <input class="" name="submit" type="submit" value="Form Button" id="1238862474"/> </div> 
1115 <input name="dmformsendto" type="hidden" id="1106649378" class="" value="O0wrseIfFLsSlLR4GAldFxB1Sr68H7msKcdcaddm0B3jZ7FqDnEw1jdymC3Zzixv" data-dec="true"/>  <input class="dmActionInput" type="hidden" name="action" value="/_dm/s/rt/widgets/dmform.submit.jsp" id="1928417552"/>  <input name="dmformsubject" type="hidden" value="Hibu Website Form Submission" id="1258263438" class="" data-email-subject="Hibu Website Form Submission"/>  <input name="dmformfrom" type="hidden" value="" id="1088638354"/>  <input name="dmformautoreplyenabled" type="hidden" value="false" id="1070248204" class=""/>  <input name="dmformautoreplyfrom" type="hidden" value="" id="1998617402"/>  <input name="dmformautoreplysubject" type="hidden" value="Thank you for your submission" id="1596679825"/>  <input name="dmformautoreplymsg" type="hidden" value="V2UndmUgcmVjZWl2ZWQgeW91ciBtZXNzYWdlIC0gdGhhbmsgeW91LiBPbmUgb2Ygb3VyIHRlYW0gbWVtYmVycyB3aWxsIGdldCBiYWNrIHRvIHlvdSBzb29uLg==" id="1331238599"/>  <input name="dmformautoreplyincludeformcopy" type="hidden" value="true" id="1985972765"/> <input name="dmformsubmitparams" type="hidden" value="8mpKnCSiNQXK/d9M7IDrSzRkXjNcoSR4otjhyuJzlevtwXXoEguImDRndSDnOmf2sfMRaz5MAUXOw6lZuZAWrON/2FkPs4NwirBgV6k3yEeQdfiJQxyAfUEh+9y1iVN9P02ng7bXFGPYhEC18MKaU+ufRWgRmY7csfP/44Df6nMIcN1pH0iILsbxuKKEkt2NkyLOnmZaBfYiaWTLkceS85c95LrdFW7TCOta+TWc27OQGJLDXEN4fN9Bvju8oYehftFNagxrSdqYFN8w9aWJd3JNjtCb3Afwq88J8ZzfIeFoIDARQZTckoRtgT69Qvz2/8GN1M+DDAAVsCxVpPIACVYxUpNEW4ol9Q3qkrNXXhk+MS3YYKwf/hmh7WSQZCv2+8NBatnUEgNpl+VmUEDof72wpbAkigYomJM4Uu2ykLOpm2YnRixN0BQa8NB2jlvn5az5NHcF7x9NxuuQvvxrnvbFLAj+vvzVrIQIHcLaZF0QQciF2lBXFL1T2st2kt2mf7oO1cywG1hmcclnMo9pzw==" data-dec="true"/><input type="hidden" name="page_uuid" value="b196ca30b540442ba4addf5cc10c1c6b"/></form> 
1116</div> 
1117 <div class="dmform-success" style="display:none" id="1153841940"> <br id="1898162193"/>  <p class="rteBlock" id="1095947191">Thank you for contacting us.</p> 
1118 <p class="rteBlock" id="1004578128">We will get back to you as soon as possible.</p> 
1119</div> 
1120 <div class="dmform-error" style="display:none" id="1402735413">Oops, there was an error sending your message. <br id="1117231105"/> Please try again later.</div> 
1121</div> 
1122</div> 
1123</div> 
1124</div> 
1125</div> 
1126</div> 
1127</section> 
1128</div> 
1129 <div data-layout-section="" data-auto="flex-section" id="1829975374" data-flex-id="b358edf0" data-page-element-type="section" data-page-element-id="1860339542" class="" data-hidden-on-mobile="true" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1980156883"></div> 
1130 <section id="505385d6" class="flex-element section" data-auto="flex-element-section"> <div id="e5e08c9e" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="a19d8796" class="flex-element group" data-auto="flex-element-group" data-hidden-on-mobile="" data-hidden-on-tablet="" data-hidden-on-desktop=""> <div id="08d58ef2" class="flex-element group" data-auto="flex-element-group"> <div id="f0ab9cce" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1797000732"> <div class="widget-eaabc5 dmCustomWidget" data-lazy-load="true" data-title="" id="1797000732" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="eaabc54e7df84833ad831aa9c55a4ffa" data-widget-version="5" data-widget-config="eyJjaGlsZFVybFByZWZpeCI6InNlcnZpY2VzIiwiaG92ZXJVbmRlcmxpbmUiOnRydWUsImhvdmVySXRhbGljIjpmYWxzZSwicGFyZW50VXJ
1130sUHJlZml4Ijoid2hhdC13ZS1kbyIsImRpdmlkZXJEZXNpZ24iOm51bGwsImxpbmtzRGVzaWduIjpudWxsLCJob3ZlckRlc2lnbiI6bnVsbCwiY3VycmVudFBhZ2VCb2xkIjpmYWxzZSwic2VwYXJhdG9yU3R5bGUiOiJncmVhdGVyIiwiZm9sZGVyQm9sZCI6ZmFsc2UsImhvdmVyQm9sZCI6ZmFsc2UsImZvbGRlclVuZGVybGluZSI6dHJ1ZSwic2hvd0hvbWUiOnRydWUsImN1cnJlbnRQYWdlSXRhbGljIjpmYWxzZSwiYnJlYWRjcnVtYlNldHRpbmdzIjpudWxsLCJmb2xkZXJEZXNpZ24iOm51bGwsImZvbGRlckl0YWxpYyI6ZmFsc2UsImhvbWVMYWJlbCI6IkhvbWUiLCJjdXJyZW50UGFnZVVuZGVybGluZSI6dHJ1ZSwiY3VycmVudFBhZ2VEZXNpZ24iOm51bGx9"> <div class="bc-root"> <nav class="bc-nav" aria-label="Breadcrumb"> <ol class="bc-list" itemscope="" itemtype="https://schema.org/BreadcrumbList"></ol> 
1131</nav> 
1132</div> 
1133</div> 
1134</div> 
1135</div> 
1136</div> 
1137</div> 
1138</section> 
1139</div> 
1140 <div data-layout-section="" data-auto="flex-section" id="1543621202" data-flex-id="4844eee8"> <div class="flex-widgets-container" id="1438128713"></div> 
1141 <section id="9780b0ed" class="flex-element section" data-auto="flex-element-section"> <div id="c5aca7b8" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="5bb78f97" class="flex-element group" data-auto="flex-element-group"> <div id="21a92624" class="flex-element group" data-auto="flex-element-group"> <div id="widget_8kq" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="2038779363"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="2038779363" style=""><p class="text-align-center t-size-26 m-size-24 size-30" style="line-height: initial;"><strong class="font-size-30 t-font-size-26 m-font-size-24" style="display: unset; font-weight: bold; color: var(--color_2);">Plumbing, Heating and Air Conditioning Service in South Plainfield Since 1985</strong><span style="display: initial;"><br/></span></p></div> 
1142</div> 
1143 <div id="widget_j4f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="3315710554"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="3315710554" style=""><p class="text-align-center"><span style="display: unset; color: var(--color_2);">RupCoe Plumbing, Heating &amp; Air Conditioning Inc has been providing reliable heating, cooling, and plumbing solutions for homeowners and businesses in Middlesex, Somerset, Hunterdon, Essex, Union, Morris, Mercer &amp; Monmouth counties for over 41 years. As a family-owned and locally-operated company, we understand what matters most to our customers. You need dependable service, honest pricing, and technicians you can trust in your home.</span><span style="display: initial;"><br/></span></p></div> 
1144</div> 
1145</div> 
1146</div> 
1147</div> 
1148</section> 
1149</div> 
1150 <div data-layout-section="" data-auto="flex-section" id="1977775878" data-flex-id="32508d8c" class="" data-hidden-on-mobile="true"> <div class="flex-widgets-container" id="1996539666"></div> 
1151 <section id="6f475ca8" class="flex-element section" data-auto="flex-element-section"> <div id="d623e09b" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="18c18f88" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="a5185c68" class="flex-element group" data-auto="flex-element-group"> <div id="group_h9b" class="flex-element group" data-auto="flex-element-group"> <div id="4404f4ea" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1696335593"> <div class="widget-8ff527 u_1696335593 dmCustomWidget" data-lazy-load="true" data-title="" id="1696335593" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8ff5270554664bddaecbe7a0b9e45c81" data-widget-version="14" data-widget-config="eyJjYWxsb3V0TWF4V2lkdGgiOiI3MjAiLCJlbmFibGVCdXR0b25SaXBwbGUiOmZhbHNlLCJidXR0b25BbGlnbiI6ImNlbnRlciIsImNhcmRWZXJ0aWNhbEFsaWduIjoibWlkZGxlIiwic3BhY2luZ0Rlc2lnbiI6bnVsbCwiY2FyZENvbnRlbnRBbGlnbiI6ImNlbnRlciIsInR5cG9ncmFwaHlIZWFkIjpudWxsLCJjb3Vwb25HYXAiOiIyMCIsInNob3dTdGFuZGFyZEJ1dHRvbiI6ZmFsc2UsImNvdXBvblNldHRpbmdzIjpudWxsLCJlbmFibGVFbnRyYW5jZUFuaW1hdGlvbiI6ZmFsc2UsImhlYWRsaW5lQm90dG9tR2FwIjoiMTIiLCJ3aWRnZXRNb2RlIjoiY291cG9uIiwibG9nb0Rlc2lnbiI6bnVsbCwidHlwb2dyYXBoe
1151UZpbmUiOm51bGwsInVzZURlZmF1bHRMb2dvIjp0cnVlLCJlbmFibGVMb2dvSG92ZXIiOmZhbHNlLCJmaWx0ZXJWYWx1ZSI6IjEiLCJ0eXBvZ3JhcGh5Q3RhIjpudWxsLCJjYXJkRGVzaWduIjpudWxsLCJnZW5lcmFsU2V0dGluZ3MiOm51bGwsImFsbG93ZWRTbHVncyI6IiIsImJvZHlCb3R0b21HYXAiOiIxMiIsImJ1dHRvbkRlc2lnbiI6bnVsbCwiZW5hYmxlQ2FyZEhvdmVyIjpmYWxzZSwiY2FyZFNoYWRvdyI6Im5vbmUiLCJsb2dvQm90dG9tR2FwIjoiMjQiLCJjYWxsb3V0U2V0dGluZ3MiOm51bGwsImxvZ29BbGlnbiI6ImNlbnRlciIsImNvdXBvbkNvbHVtbnMiOiIxIiwiY2FsbG91dEZpbHRlclZhbHVlIjoiIiwiZnVsbFdpZHRoQnV0dG9uc01vYmlsZSI6dHJ1ZSwibG9nb1NwYWNpbmciOiIxMiIsInR5cG9ncmFwaHlCb2R5IjpudWxsLCJhbmltYXRpb25EZXNpZ24iOm51bGwsImxvZ29XaWR0aCI6IjIyMCIsInNob3dBbGwiOmZhbHNlLCJlbmFibGVQaG9uZVB1bHNlIjpmYWxzZSwiYnV0dG9uQm90dG9tR2FwIjoiMCIsImxvZ29NYXhIZWlnaHQiOiIxMDAiLCJmaWx0ZXJCeVNsdWciOmZhbHNlLCJidXR0b25MYXlvdXQiOiJyb3ciLCJjb3Vwb25GaWx0ZXJCeVNsdWciOnRydWUsImNvdXBvbkxheW91dCI6bnVsbCwiY2FsbG91dExheW91dCI6bnVsbCwidGVybXNCb3R0b21HYXAiOiIxMiIsInNob3dQaG9uZUJ1dHRvbiI6dHJ1ZSwidGVybXNEaXNwbGF5TW9kZSI6ImZ1bGwiLCJjdXN0b21pemVTcGFjaW5nIjpmYWxzZSwiYnV0dG9uR2FwIjoiMTIiLCJjdGFCb3R0b21HYXAiOiIxMiIsImVuYWJsZUJ1dHRvbkhvdmVyIjpmYWxzZSwiY2FsbG91dEFsaWduIjoiZnVsbCJ9"> <div class="ucw-wrapper" role="region" aria-label="Promotions" aria-busy="true"> <div class="ucw-callout" hidden=""> <div class="ucw-callout__mount"></div> 
1152</div> 
1153 <div class="ucw-coupon" hidden=""> <div class="ucw-coupon__grid" role="list"></div> 
1154</div> 
1155<!--
1156    Hidden Duda button template.
1157
1158    JavaScript clones this markup for both the collection
1159    button and the phone button. This allows one standard
1160    Duda Button design control to style every generated action.
1161  --> <div class="ucw-action-template" hidden="" aria-hidden="true"> <a class="ucw-card__actionLink" href="#"> <div class="dmWidget ucw-card__action"> <span class="text">Button</span> 
1162 <span class="iconBg"></span> 
1163</div> 
1164</a> 
1165</div> 
1166</div> 
1167</div> 
1168</div> 
1169</div> 
1170 <div id="5893f035" class="flex-element group" data-auto="flex-element-group"> <div id="b8133d1b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1775643184"> <div class="widget-8ff527 u_1775643184 dmCustomWidget" data-lazy-load="true" data-title="" id="1775643184" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8ff5270554664bddaecbe7a0b9e45c81" data-widget-version="14" data-widget-config="eyJjYWxsb3V0TWF4V2lkdGgiOiI3MjAiLCJlbmFibGVCdXR0b25SaXBwbGUiOmZhbHNlLCJidXR0b25BbGlnbiI6ImNlbnRlciIsImNhcmRWZXJ0aWNhbEFsaWduIjoibWlkZGxlIiwic3BhY2luZ0Rlc2lnbiI6bnVsbCwiY2FyZENvbnRlbnRBbGlnbiI6ImNlbnRlciIsInR5cG9ncmFwaHlIZWFkIjpudWxsLCJjb3Vwb25HYXAiOiIyMCIsInNob3dTdGFuZGFyZEJ1dHRvbiI6ZmFsc2UsImNvdXBvblNldHRpbmdzIjpudWxsLCJlbmFibGVFbnRyYW5jZUFuaW1hdGlvbiI6ZmFsc2UsImhlYWRsaW5lQm90dG9tR2FwIjoiMTIiLCJ3aWRnZXRNb2RlIjoiY291cG9uIiwibG9nb0Rlc2lnbiI6bnVsbCwidHlwb2dyYXBoeUZpbmUiOm51bGwsInVzZURlZmF1bHRMb2dvIjp0cnVlLCJlbmFibGVMb2dvSG92ZXIiOmZhbHNlLCJmaWx0ZXJWYWx1ZSI6IjQiLCJ0eXBvZ3JhcGh5Q3RhIjpudWxsLCJjYXJkRGVzaWduIjpudWxsLCJnZW5lcmFsU2V0dGluZ3MiOm51bGwsImFsbG93ZWRTbHVncyI6IiIsImJvZHlCb3R0b21HYXAiOiIxMiIsImJ1dHRvbkRlc2lnbiI6bnVsbCwiZW5hYmxlQ2FyZEhvdmVyIjpmYWxzZSwiY2FyZFNoYWRvdyI6Im5vbmUiLCJsb2dvQm90dG9tR2FwIjoiMjQiLCJjYWxsb3V0U2V0dGluZ3MiOm51bGwsImxvZ29BbGlnbiI6ImNlbnRlciIsImNvdXBvbkNvbHVtbnMiOiIxIiwiY2FsbG91dEZpbHRlclZhbHVlIjoiIiwiZnVsbFdpZHRoQnV0dG9uc01vYmlsZSI6dHJ1ZSwibG9nb1NwYWNpbmciOiIxMiIsInR5cG9ncmFwaHlCb2R5IjpudWxsLCJhbmltYXRpb25EZXNpZ24iOm51bGwsImxvZ29XaWR0aCI6IjIyMCIsInNob3dBbGwiOmZhbHNlLCJlbmFibGVQaG9uZVB1bHNlIjpmYWxzZSwiYnV0dG9uQm90dG9tR2FwIjoiMCIsImxvZ29NYXhIZWlnaHQiOiIxMDAiLCJmaWx0ZXJCeVNsdWciOmZhbHNlLCJidXR0b25MYXlvdXQiOiJyb3ciLCJjb3Vwb25GaWx0ZXJCeVNsdWciOnRydWUsImNvdXBvbkxheW91dCI6bnVsbCwiY2FsbG91dExheW91dCI6bnVsbCwidGVybXNCb3R0b21HYXAiOiIxMiIsInNob3dQaG9uZUJ1dHRvbiI6dHJ1ZSwidGVybXNEaXNwbGF5TW9kZSI6ImZ1bGwiLCJjdXN0b21pemVTcGFjaW5nIjpmYWxzZSwiYnV0dG9uR2FwIjoiMTIiLCJjdGFCb3R0b21HYXAiOiIxMiIsImVuYWJsZUJ1dHRvbkhvdmVyIjpmYWxzZSwiY2FsbG91dEFsaWduIjoiZnVsbCJ9"> <div class="ucw-wrapper" role="region" aria-label="Promotions" aria-busy="true"> <div class="ucw-callout" hidden="">
1170 <div class="ucw-callout__mount"></div> 
1171</div> 
1172 <div class="ucw-coupon" hidden=""> <div class="ucw-coupon__grid" role="list"></div> 
1173</div> 
1174<!--
1175    Hidden Duda button template.
1176
1177    JavaScript clones this markup for both the collection
1178    button and the phone button. This allows one standard
1179    Duda Button design control to style every generated action.
1180  --> <div class="ucw-action-template" hidden="" aria-hidden="true"> <a class="ucw-card__actionLink" href="#"> <div class="dmWidget ucw-card__action"> <span class="text">Button</span> 
1181 <span class="iconBg"></span> 
1182</div> 
1183</a> 
1184</div> 
1185</div> 
1186</div> 
1187</div> 
1188</div> 
1189 <div id="4f1100e4" class="flex-element group" data-auto="flex-element-group"> <div id="a5beea4b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1283408563"> <div class="widget-8ff527 u_1283408563 dmCustomWidget" data-lazy-load="true" data-title="" id="1283408563" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8ff5270554664bddaecbe7a0b9e45c81" data-widget-version="14" data-widget-config="eyJjYWxsb3V0TWF4V2lkdGgiOiI3MjAiLCJlbmFibGVCdXR0b25SaXBwbGUiOmZhbHNlLCJidXR0b25BbGlnbiI6ImNlbnRlciIsImNhcmRWZXJ0aWNhbEFsaWduIjoibWlkZGxlIiwic3BhY2luZ0Rlc2lnbiI6bnVsbCwiY2FyZENvbnRlbnRBbGlnbiI6ImNlbnRlciIsInR5cG9ncmFwaHlIZWFkIjpudWxsLCJjb3Vwb25HYXAiOiIyMCIsInNob3dTdGFuZGFyZEJ1dHRvbiI6ZmFsc2UsImNvdXBvblNldHRpbmdzIjpudWxsLCJlbmFibGVFbnRyYW5jZUFuaW1hdGlvbiI6ZmFsc2UsImhlYWRsaW5lQm90dG9tR2FwIjoiMTIiLCJ3aWRnZXRNb2RlIjoiY291cG9uIiwibG9nb0Rlc2lnbiI6bnVsbCwidHlwb2dyYXBoeUZpbmUiOm51bGwsInVzZURlZmF1bHRMb2dvIjp0cnVlLCJlbmFibGVMb2dvSG92ZXIiOmZhbHNlLCJmaWx0ZXJWYWx1ZSI6IjIiLCJ0eXBvZ3JhcGh5Q3RhIjpudWxsLCJjYXJkRGVzaWduIjpudWxsLCJnZW5lcmFsU2V0dGluZ3MiOm51bGwsImFsbG93ZWRTbHVncyI6IiIsImJvZHlCb3R0b21HYXAiOiIxMiIsImJ1dHRvbkRlc2lnbiI6bnVsbCwiZW5hYmxlQ2FyZEhvdmVyIjpmYWxzZSwiY2FyZFNoYWRvdyI6Im5vbmUiLCJsb2dvQm90dG9tR2FwIjoiMjQiLCJjYWxsb3V0U2V0dGluZ3MiOm51bGwsImxvZ29BbGlnbiI6ImNlbnRlciIsImNvdXBvbkNvbHVtbnMiOiIxIiwiY2FsbG91dEZpbHRlclZhbHVlIjoiIiwiZnVsbFdpZHRoQnV0dG9uc01vYmlsZSI6dHJ1ZSwibG9nb1NwYWNpbmciOiIxMiIsInR5cG9ncmFwaHlCb2R5IjpudWxsLCJhbmltYXRpb25EZXNpZ24iOm51bGwsImxvZ29XaWR0aCI6IjIyMCIsInNob3dBbGwiOmZhbHNlLCJlbmFibGVQaG9uZVB1bHNlIjpmYWxzZSwiYnV0dG9uQm90dG9tR2FwIjoiMCIsImxvZ29NYXhIZWlnaHQiOiIxMDAiLCJmaWx0ZXJCeVNsdWciOmZhbHNlLCJidXR0b25MYXlvdXQiOiJyb3ciLCJjb3Vwb25GaWx0ZXJCeVNsdWciOnRydWUsImNvdXBvbkxheW91dCI6bnVsbCwiY2FsbG91dExheW91dCI6bnVsbCwidGVybXNCb3R0b21HYXAiOiIxMiIsInNob3dQaG9uZUJ1dHRvbiI6dHJ1ZSwidGVybXNEaXNwbGF5TW9kZSI6ImZ1bGwiLCJjdXN0b21pemVTcGFjaW5nIjpmYWxzZSwiYnV0dG9uR2FwIjoiMTIiLCJjdGFCb3R0b21HYXAiOiIxMiIsImVuYWJsZUJ1dHRvbkhvdmVyIjpmYWxzZSwiY2FsbG91dEFsaWduIjoiZnVsbCJ9"> <div class="ucw-wrapper" role="region" aria-label="Promotions" aria-busy="true"> <div class="ucw-callout" hidden=""> <div class="ucw-callout__mount"></div> 
1190</div> 
1191 <div class="ucw-coupon" hidden=""> <div class="ucw-coupon__grid" role="list"></div> 
1192</div> 
1193<!--
1194    Hidden Duda button template.
1195
1196    JavaScript clones this markup for both the collection
1197    button and the phone button. This allows one standard
1198    Duda Button design control to style every generated action.
1199  --> <div class="ucw-action-template" hidden="" aria-hidden="true"> <a class="ucw-card__actionLink" href="#"> <div class="dmWidget ucw-card__action"> <span class="text">Button</span> 
1200 <span class="iconBg"></span> 
1201</div> 
1202</a> 
1203</div> 
1204</div> 
1205</div> 
1206</div> 
1207</div> 
1208 <div id="297a5406" class="flex-element group" data-auto="flex-element-group"> <div id="77a46421" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1096494896"> <div class="widget-8ff527 u_1096494896 dmCustomWidget" data-lazy-load="true" data-title="" id="1096494896" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8ff5270554664bddaecbe7a0b9e45c81" data-widget-version="14" data-widget-config="eyJjYWxsb3V0TWF4V2lkdGgiOiI3MjAiLCJlbmFibGVCdXR0b25SaXBwbGUiOmZhbHNlLCJidXR0b25BbGlnbiI6ImNlbnRlciIsImNhcmRWZXJ0aWNhbEFsaWduIjoibWlkZGxlIiwic3BhY2luZ0Rlc2lnbiI6bnVsbCwiY2FyZENvbnRlbnRBbGlnbiI6ImNlbnRlciIsInR5cG9ncmFwaHlIZWFkIjpudWxsLCJjb3Vwb25HYXAiOiIyMCIsInNob3dTdGFuZGFyZEJ1dHRvbiI6ZmFsc2UsImNvdXBvblNldHRpbmdzIjpudWxsLCJlbmFibGVFbnRyYW5jZUFuaW1hdGlvbiI6ZmFsc2UsImhlYWRsaW5lQm90dG9tR2FwIjoiMTIiLCJ3aWRnZXRNb2RlIjoiY291cG9uIiwibG9nb0Rlc2lnbiI6bnVsbCwidHlwb2dyYXBoe
1208UZpbmUiOm51bGwsInVzZURlZmF1bHRMb2dvIjp0cnVlLCJlbmFibGVMb2dvSG92ZXIiOmZhbHNlLCJmaWx0ZXJWYWx1ZSI6IjMiLCJ0eXBvZ3JhcGh5Q3RhIjpudWxsLCJjYXJkRGVzaWduIjpudWxsLCJnZW5lcmFsU2V0dGluZ3MiOm51bGwsImFsbG93ZWRTbHVncyI6IiIsImJvZHlCb3R0b21HYXAiOiIxMiIsImJ1dHRvbkRlc2lnbiI6bnVsbCwiZW5hYmxlQ2FyZEhvdmVyIjpmYWxzZSwiY2FyZFNoYWRvdyI6Im5vbmUiLCJsb2dvQm90dG9tR2FwIjoiMjQiLCJjYWxsb3V0U2V0dGluZ3MiOm51bGwsImxvZ29BbGlnbiI6ImNlbnRlciIsImNvdXBvbkNvbHVtbnMiOiIxIiwiY2FsbG91dEZpbHRlclZhbHVlIjoiIiwiZnVsbFdpZHRoQnV0dG9uc01vYmlsZSI6dHJ1ZSwibG9nb1NwYWNpbmciOiIxMiIsInR5cG9ncmFwaHlCb2R5IjpudWxsLCJhbmltYXRpb25EZXNpZ24iOm51bGwsImxvZ29XaWR0aCI6IjIyMCIsInNob3dBbGwiOmZhbHNlLCJlbmFibGVQaG9uZVB1bHNlIjpmYWxzZSwiYnV0dG9uQm90dG9tR2FwIjoiMCIsImxvZ29NYXhIZWlnaHQiOiIxMDAiLCJmaWx0ZXJCeVNsdWciOmZhbHNlLCJidXR0b25MYXlvdXQiOiJyb3ciLCJjb3Vwb25GaWx0ZXJCeVNsdWciOnRydWUsImNvdXBvbkxheW91dCI6bnVsbCwiY2FsbG91dExheW91dCI6bnVsbCwidGVybXNCb3R0b21HYXAiOiIxMiIsInNob3dQaG9uZUJ1dHRvbiI6dHJ1ZSwidGVybXNEaXNwbGF5TW9kZSI6ImZ1bGwiLCJjdXN0b21pemVTcGFjaW5nIjpmYWxzZSwiYnV0dG9uR2FwIjoiMTIiLCJjdGFCb3R0b21HYXAiOiIxMiIsImVuYWJsZUJ1dHRvbkhvdmVyIjpmYWxzZSwiY2FsbG91dEFsaWduIjoiZnVsbCJ9"> <div class="ucw-wrapper" role="region" aria-label="Promotions" aria-busy="true"> <div class="ucw-callout" hidden=""> <div class="ucw-callout__mount"></div> 
1209</div> 
1210 <div class="ucw-coupon" hidden=""> <div class="ucw-coupon__grid" role="list"></div> 
1211</div> 
1212<!--
1213    Hidden Duda button template.
1214
1215    JavaScript clones this markup for both the collection
1216    button and the phone button. This allows one standard
1217    Duda Button design control to style every generated action.
1218  --> <div class="ucw-action-template" hidden="" aria-hidden="true"> <a class="ucw-card__actionLink" href="#"> <div class="dmWidget ucw-card__action"> <span class="text">Button</span> 
1219 <span class="iconBg"></span> 
1220</div> 
1221</a> 
1222</div> 
1223</div> 
1224</div> 
1225</div> 
1226</div> 
1227</div> 
1228 <div id="a3d28b70" class="flex-element group" data-auto="flex-element-group"> <div id="1c8949b9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1930046399"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton button_2 u_1930046399 align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton button_2 u_1930046399" file="false" href="/special-offers" data-element-type="dButtonLinkId" id="1930046399" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiTW9yZSBPZmZlcnMgTGlua1wifSJ9LHsiYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIk1vcmUgT2ZmZXJzIEJ1dHRvblwifSJ9XQ==" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1255586188"> <span class="icon hasFontIcon icon-star" id="1255676345"></span> 
1229</span> 
1230 <span class="text" id="1623250562" data-diy-text="">View More Offers</span> 
1231</a> 
1232</div> 
1233</div> 
1234</div> 
1235</div> 
1236</section> 
1237</div> 
1238 <div data-layout-section="" data-auto="flex-section" id="1563796375" data-flex-id="94e8c8ef"> <div class="flex-widgets-container" id="1476440167"></div> 
1239 <section id="862b1297" class="flex-element section" data-auto="flex-element-section"> <div id="20db10e8" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="1c2dc098" class="flex-element group" data-auto="flex-element-group"> <div id="39188e73" class="flex-element group" data-auto="flex-element-group"> <div id="group_ajr" class="flex-element group" data-auto="flex-element-group"> <div id="4e38e46e" class="flex-element group" data-auto="flex-element-group"> <div id="9f701ee6" class="flex-element group" data-auto="flex-element-group"> <div id="617b12a8" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1950703317"> <div class="imageWidget align-center u_1950703317" data-element-type="image" data-widget-type="image" id="1950703317" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-168w.webp" alt="RupCoe Plumbing, Heating &amp;
1239 Air Conditioning Inc. logo with gray pipe and red text" id="1643135607" data-diy-image="" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" onerror="handleImageLoadError(this)" loading="lazy" height="112.0" width="140.71875"/></div> 
1240</div> 
1241</div> 
1242 <div id="03d3a7f5" class="flex-element group" data-auto="flex-element-group"> <div id="9aba2470" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1407556904"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1407556904" style=""><p class="m-size-29 t-size-31 text-align-center m-text-align-center size-36" style="line-height: 1;"><strong class="m-font-size-29 t-font-size-31 font-size-36" style="font-weight: bold; display: unset;">$50 OFF</strong></p></div> 
1243</div> 
1244 <div id="bdc78e59" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1047311451"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1047311451" style=""><p class="text-align-center m-text-align-center size-15 t-size-15 m-size-15"><strong class="font-size-15 t-font-size-15 m-font-size-15" style="font-weight: bold; display: unset;">Any Plumbing Service</strong></p></div> 
1245</div> 
1246 <div id="2735abdd" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1757397012"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1757397012" style=""><p class="text-align-center m-text-align-center size-12 t-size-12 m-size-12"><span class="font-size-12 t-font-size-12 m-font-size-12" style="font-weight: normal; display: unset; font-style: italic;">Not valid with any other offers or promotions. Restrictions apply. Must mention this coupon at the time of scheduling.</span></p></div> 
1247</div> 
1248 <div id="337b2f33" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1190303657"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1190303657"> <span class="iconBg" aria-hidden="true" id="1135382458"> <span class="icon hasFontIcon icon-star" id="1864143674"></span> 
1249</span> 
1250 <span class="text" id="1746216363">(908) 259-4326</span> 
1251</a> 
1252</div> 
1253</div> 
1254</div> 
1255 <div id="3c92fca2" class="flex-element group" data-auto="flex-element-group"> <div id="168efc98" class="flex-element group" data-auto="flex-element-group"> <div id="103ebf7c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1661050126"> <div class="imageWidget align-center u_1661050126" data-element-type="image" data-widget-type="image" id="1661050126" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-168w.webp" alt="RupCoe Plumbing, Heating &amp; Air Conditioning Inc. logo with gray pipe and red text" id="1173133619" data-diy-image="" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" onerror="handleImageLoadError(this)" loading="lazy" height="112.0" width="140.71875"/></div> 
1256</div> 
1257</div> 
1258 <div id="e7429a07" class="flex-element group" data-auto="flex-element-group"> <div id="dbbdbaa8" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1968462056"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1968462056" style=""><p class="m-text-align-center text-align-center t-size-31 m-size-29 size-36" style="line-height: 1;"><strong class="font-size-36 t-font-size-31 m-font-size-29" style="display: unset; font-weight: bold;">$500 OFF</strong></p></div> 
1259</div> 
1260 <div id="b0641e5a" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1323187348"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1323187348" style=""><p class="m-text-align-center text-align-center size-15 t-size-15 m-size-15"><strong class="font-size-15 t-font-size-15 m-font-size-15" style="display: unset; font-weight: bold;">Any Complete Heating Replacement System</strong></p></div> 
1261</div> 
1262 <div id="48be08f5" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1039526633"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1039526633" style=""><p class="text-align-center m-text-align-center size-12 t-size-12 m-size-12"><span class="font-size-12 t-font-size-12 m-font-size-12" style="font-weight: normal; display: unset; font-style: italic;">Not valid with any other offers or promotions. Restrictions apply. Must mention this coupon at the time of scheduling.</span></p></div> 
1263</div> 
1264 <div id="86b5e6df" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1648909565"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1648909565"> <span class="iconBg" aria-hidden="true" id="1003171546"> <span class="icon hasFontIcon icon-star" id="1182223433"></span> 
1265</span> 
1266 <span class="text" id="1837212278">(908) 259-4326</span> 
1267</a> 
1268</div> 
1269</div> 
1270</div> 
1271 <div id="ce87d017" class="flex-element group" data-auto="flex-element-group"> <div id="0a813e55" class="flex-element group" data-auto="flex-element-group"> <div id="b7dd87cb" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1512671970"> <div class="imageWidget align-center u_1512671970" data-element-type="image" data-widget-type="image" id="1512671970" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-168w.webp" alt="RupCoe Plumbing, Heating &amp;
1271 Air Conditioning Inc. logo with gray pipe and red text" id="1765365958" data-diy-image="" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" onerror="handleImageLoadError(this)" loading="lazy" height="112.0" width="140.71875"/></div> 
1272</div> 
1273</div> 
1274 <div id="0757953d" class="flex-element group" data-auto="flex-element-group"> <div id="bb334acb" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1746646243"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1746646243" style=""><p class="m-size-29 t-size-31 text-align-center m-text-align-center size-36" style="line-height: 1;"><strong class="m-font-size-29 t-font-size-31 font-size-36" style="font-weight: bold; display: unset;">$50 OFF</strong></p></div> 
1275</div> 
1276 <div id="b1c77dd0" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1599685003"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1599685003" style=""><p class="text-align-center m-text-align-center size-15 t-size-15 m-size-15"><strong class="font-size-15 t-font-size-15 m-font-size-15" style="font-weight: bold; display: unset;">Any Heating Tune-Up</strong></p></div> 
1277</div> 
1278 <div id="84ffc053" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1301103310"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1301103310" style=""><p class="text-align-center m-text-align-center size-12 t-size-12 m-size-12"><span class="font-size-12 t-font-size-12 m-font-size-12" style="font-weight: normal; display: unset; font-style: italic;">Not valid with any other offers or promotions. Restrictions apply. Must mention this coupon at the time of scheduling.</span></p></div> 
1279</div> 
1280 <div id="8e05397e" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1173696138"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1173696138"> <span class="iconBg" aria-hidden="true" id="1599273340"> <span class="icon hasFontIcon icon-star" id="1082979425"></span> 
1281</span> 
1282 <span class="text" id="1750064488">(908) 259-4326</span> 
1283</a> 
1284</div> 
1285</div> 
1286</div> 
1287 <div id="5862f2fc" class="flex-element group" data-auto="flex-element-group"> <div id="ff9b04bf" class="flex-element group" data-auto="flex-element-group"> <div id="ac111079" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1255881368"> <div class="imageWidget align-center u_1255881368" data-element-type="image" data-widget-type="image" id="1255881368" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLmRhcmstbG9nbyJ9XQ=="><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257-168w.webp" alt="RupCoe Plumbing, Heating &amp; Air Conditioning Inc. logo with gray pipe and red text" id="1617018032" data-diy-image="" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/logo-rupcoe-plumbing-heating-and-air-conditioning-inc-0c649257.webp" onerror="handleImageLoadError(this)" loading="lazy" height="112.0" width="140.71875"/></div> 
1288</div> 
1289</div> 
1290 <div id="59c27e2b" class="flex-element group" data-auto="flex-element-group"> <div id="19999ebe" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1283998882"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1283998882" style=""><p class="m-size-29 t-size-31 text-align-center m-text-align-center size-36" style="line-height: 1;"><strong class="m-font-size-29 t-font-size-31 font-size-36" style="font-weight: bold; display: unset;">
1290$50 OFF</strong></p></div> 
1291</div> 
1292 <div id="c9a55ee4" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1324649419"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1324649419" style=""><p class="m-text-align-center text-align-center size-15 t-size-15 m-size-15"><strong class="font-size-15 t-font-size-15 m-font-size-15" style="display: unset; font-weight: bold;">Any Drain Cleaning + Free Camera Inspection</strong></p></div> 
1293</div> 
1294 <div id="0533f173" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1139076172"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1139076172" style=""><p class="text-align-center m-text-align-center size-12 t-size-12 m-size-12"><span class="font-size-12 t-font-size-12 m-font-size-12" style="font-weight: normal; display: unset; font-style: italic;">Not valid with any other offers or promotions. Restrictions apply. Must mention this coupon at the time of scheduling.</span></p></div> 
1295</div> 
1296 <div id="e1433e65" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1446080216"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="tel:(908) 259-4326" data-element-type="dButtonLinkId" id="1446080216"> <span class="iconBg" aria-hidden="true" id="1060839555"> <span class="icon hasFontIcon icon-star" id="1925740308"></span> 
1297</span> 
1298 <span class="text" id="1955720587">(908) 259-4326</span> 
1299</a> 
1300</div> 
1301</div> 
1302</div> 
1303</div> 
1304</div> 
1305 <div id="be540449" class="flex-element group" data-auto="flex-element-group"> <div id="a328392c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1750528161"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton button_2 u_1750528161 align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton button_2 u_1750528161" file="false" href="/special-offers" data-element-type="dButtonLinkId" id="1750528161" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiTW9yZSBPZmZlcnMgTGlua1wifSJ9LHsiYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIk1vcmUgT2ZmZXJzIEJ1dHRvblwifSJ9XQ==" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1587791505"> <span class="icon hasFontIcon icon-star" id="1968840831"></span> 
1306</span> 
1307 <span class="text" id="1475930667" data-diy-text="">View More Offers</span> 
1308</a> 
1309</div> 
1310</div> 
1311</div> 
1312</div> 
1313</section> 
1314</div> 
1315 <div data-layout-section="" data-auto="flex-section" id="1148027098" data-flex-id="94bb750a" data-page-element-type="section" data-page-element-id="1927781104" class="" data-hidden-on-mobile="true" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1636393354"></div> 
1316 <section id="77698520" class="flex-element section" data-auto="flex-element-section"> <div id="9f5834e9" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="2ba3ba97" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-mobile="" data-hidden-on-desktop=""> <div id="3f8f7519" class="flex-element group" data-auto="flex-element-group"> <div id="16ff5593" class="flex-element widget-wrapper DCBH2" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1539995574"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1539995574" style="" data-inject="introheading1" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiSU5UUk8gSEVBRExJTkVcIn0ifV0=" data-diy-text="">Your Trusted HVAC and Plumbing Partner Since 1985</div> 
1317</div> 
1318 <div id="3df466c4" class="flex-element group" data-auto="flex-element-group"> <div id="48b7b03e" class="flex-element group" data-auto="flex-element-group"> <div id="db13747c" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1792283908"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1792283908" style="" data-inject="introcontent1" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiSU5UUk8gUEFSQUdSQVBIIChDT0xVTU4gMSlcIn0ifV0=" data-diy-text=""><p class="rteBlock">RupCoe Plumbing, Heating &amp; Air Conditioning Inc has been providing reliable heating, cooling, and plumbing solutions for homeowners and businesses in Middlesex, Somerset, Hunterdon, Essex, Union, Morris, Mercer &amp; Monmouth counties for over 41 years. As a family-owned and locally-operated company, we understand what matters most to our customers. You need dependable service, honest pricing, and technicians you can trust in your home.</p><br/><p class="rteBlock">We stand behind every service call with licensed, insured, and background-checked technicians who arrive on time in fully stocked vehicles ready to complete repairs on the first visit. Our fixed-rate pricing means no surprises, and with 24/7 emergency service available, we're here when you need us most. From routine maintenance to emergency repairs and complete system installations, <a value="contact" label="" type="page" href="/contact" data-runtime-url="/contact">contact us</a> 
1319to work with a company that's large enough to serve you but small enough to know y
1319ou.</p></div> 
1320</div> 
1321</div> 
1322 <div id="82141402" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet=""> <div id="240bd281" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1955999276"> <div class="imageWidget align-center" data-element-type="image" data-widget-type="image" id="1955999276" data-binding="W3siYmluZGluZ05hbWUiOiJpbWFnZSIsInZhbHVlIjoic2l0ZV9pbWFnZXMucGVyc2lzdGVkLkludHJvIC8gV2h5IENob29zZSBJbWFnZSJ9XQ=="><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/why-choose-us-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg" alt="Three outdoor air-conditioning units on concrete pads against a brick wall" id="1860491311" class="" width="0.0" height="0.0" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/why-choose-us-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg" data-diy-image="" onerror="handleImageLoadError(this)" loading="lazy"/></div> 
1323</div> 
1324</div> 
1325</div> 
1326</div> 
1327</div> 
1328</div> 
1329</section> 
1330</div> 
1331 <div data-layout-section="" data-auto="flex-section" id="1687614555" data-flex-id="fc07ddff" data-page-element-type="section" data-page-element-id="1927781104"> <div class="flex-widgets-container" id="1372647527"></div> 
1332 <section id="ca768f9c" class="flex-element section" data-auto="flex-element-section"> <div id="d330846d" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="dc3611db" class="flex-element group" data-auto="flex-element-group"> <div id="d0a91b32" class="flex-element group" data-auto="flex-element-group"> <div id="55e4e31b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1053713239"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1053713239" data-diy-text="" data-inject="whychooseheading1" style=""> <h2 class="text-align-center"><span style="display: unset;">Why Choose RupCoe for Your Plumbing, Heating and&nbsp;</span></h2> 
1333 <h2 class="text-align-center"><span style="display: unset;">Air Conditioning Service in South Plainfield, NJ</span><span style="display: initial;"><br/></span></h2> 
1334</div> 
1335</div> 
1336 <div id="e793537a" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1460681929"> <div class="dmDividerWrapper clearfix u_1460681929" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1460681929" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1637196558"/></div> 
1337</div> 
1338 <div id="b67e80a0" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1051406894"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1051406894" data-diy-text="" data-inject="whychooseheading1" style=""><p class="text-align-center"><span style="display: unset;">We stand behind every service call with licensed, insured, and background-checked technicians who arrive on time in fully stocked vehicles ready to complete repairs on the first visit. Our fixed-rate pricing means no surprises, and with 24/7 emergency service available, we're here when you need us most.</span><span style="display: initial;"><br/></span></p></div> 
1339</div> 
1340 <div id="46800cd3" class="flex-element group" data-auto="flex-element-group"> <div id="80bbce1c" class="flex-element group" data-auto="flex-element-group"> <div id="014132bd" class="flex-element group" data-auto="flex-element-group"> <div id="f8e8650a" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1060210614"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1060210614" data-element-type="graphic" data-widget-type="graphic" id="1060210614" data-hover-effect="zoomout"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1608726771" class="svg u_1608726771" data-icon-custom="true"> <path d="m63.543 68.012c4.6211 1.2539 9.1758-2.0508 9.4102-6.8359 0.14453-2.9766 1.9922-5.5156 4.7773-6.5742 4.4766-1.7031 6.2148-7.0547 3.5938-11.062-1.6289-2.4922-1.6289-5.6328 0-8.125 2.6211-4.0078 0.88281-9.3594-3.5938-11.062-2.7852-1.0586-4.6289-3.5977-4.7773-6.5742-0.23438-4.7852-4.7852-8.0898-9.4102-6.8359-2.875 0.78125-5.8594
1340-0.19141-7.7266-2.5117-3.0039-3.7344-8.6289-3.7344-11.633 0-1.8672 2.3203-4.8516 3.293-7.7266 2.5117-4.625-1.2539-9.1758 2.0508-9.4102 6.8359-0.14453 2.9766-1.9922 5.5156-4.7773 6.5742-4.4766 1.7031-6.2148 7.0547-3.5938 11.062 1.6289 2.4922 1.6289 5.6328 0 8.125-2.6211 4.0078-0.88281 9.3594 3.5938 11.062 2.7852 1.0586 4.6289 3.5977 4.7773 6.5742 0.23438 4.7852 4.7852 8.0898 9.4102 6.8359 2.875-0.78125 5.8594 0.19141 7.7266 2.5117 3.0039 3.7344 8.6289 3.7344 11.633 0 1.8672-2.3203 4.8555-3.293 7.7266-2.5117zm-13.543-5.1797c-12.898 0-23.355-10.457-23.355-23.355 0-12.898 10.457-23.355 23.355-23.355s23.355 10.457 23.355 23.355c0 12.898-10.457 23.355-23.355 23.355zm-7.3945 8.6797-10.324 22.012c-0.56641 1.207-2.3242 1.0859-2.7148-0.1875l-3.1367-10.16c-0.28125-0.91406-1.2148-1.4531-2.1484-1.2422l-10.367 2.3633c-1.3008 0.29688-2.2852-1.1641-1.5234-2.2578l13.113-18.816c0.54297 2.0469 1.7617 3.8555 3.5156 5.1289 2.2969 1.668 5.1836 2.1836 7.9219 1.4414 2.0938-0.56641 4.2539 0.09375 5.6641 1.7188zm43.484 12.785-10.367-2.3633c-0.93359-0.21094-1.8672 0.32812-2.1484 1.2422l-3.1367 10.16c-0.39453 1.2734-2.1523 1.3945-2.7148 0.1875l-10.324-22.012c1.4102-1.625 3.5703-2.2891 5.6641-1.7188 2.7383 0.74219 5.625 0.22656 7.9219-1.4414 1.7539-1.2734 2.9727-3.082 3.5156-5.1289l13.113 18.816c0.76172 1.0938-0.22266 2.5547-1.5234 2.2578zm-36.09-66.328c-11.879 0-21.508 9.6289-21.508 21.508 0 11.879 9.6289 21.508 21.508 21.508s21.508-9.6289 21.508-21.508c0-11.879-9.6289-21.508-21.508-21.508zm15.043 17.102-7.0195 5.9375 2.1914 8.9336c0.33984 1.3906-1.1758 2.4922-2.3945 1.7383l-7.8164-4.8438-7.8164 4.8438c-1.2148 0.75391-2.7344-0.35156-2.3945-1.7383l2.1914-8.9336-7.0195-5.9375c-1.0898-0.92187-0.51172-2.707 0.91406-2.8125l9.1719-0.67969 3.4805-8.5117c0.54297-1.3242 2.418-1.3242 2.957 0l3.4805 8.5117 9.1719 0.67969c1.4258 0.10547 2.0039 1.8906 0.91406 2.8125z" fill-rule="evenodd"></path> 
1341</svg> 
1342</div> 
1343</div> 
1344 <div id="02c67015" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1225876299"> <div class="dmNewParagraph ql-disabled" data-element-type="paragraph" data-version="5" id="1225876299" style="" new-inline-bind-applied="true" data-diy-text=""><p class="m-text-align-center text-align-center" style="line-height: 1.2;"><span style="color: var(--color_16); display: initial;"><span class="inline-data-binding" data-encoded-value="TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3QgdGVzdCB0ZXN0" data-inline-binding="STATIC_PAGE_COLLECTION.{"cn":"Why Choose","fn":"Why Choose 1"}" data-inline-binding-encoded="U1RBVElDX1BBR0VfQ09MTEVDVElPTi57ImNuIjoiV2h5IENob29zZSIsImZuIjoiV2h5IENob29zZSAxIn0=">Over 40 Years in Business</span></span></p></div> 
1345</div> 
1346</div> 
1347 <div id="e8366ed9" class="flex-element group" data-auto="flex-element-group"> <div id="2c4c27c7" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1947895510"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1947895510" data-element-type="graphic" data-widget-type="graphic" id="1947895510" data-hover-effect="zoomout"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1063318646" class="svg u_1063318646" data-icon-custom="true"> <path d="m16.48 40.336c2.3633 0 4.6914-0.6875 6.7344-1.9922 0.042968-0.027344 0.09375-0.035156 0.14062-0.054688 0.066406-0.03125 0.12891-0.0625 0.20312-0.078124 0.066406-0.015626 0.13281-0.011719 0.19922-0.011719 0.066407 0 0.12891-0.007813 0.19531 0.003906 0.074219 0.011719 0.14062 0.042969 0.21094 0.070313 0.046876 0.019531 0.09375 0.023437 0.13672 0.046874l6.2305 3.6328c1.0508 0.58203 1.75 1.5703 1.9492 2.7656l0.61719 3.6953 8.2383-4.293-14.086-7.8867c-0.011719-0.007813-0.019531-0.019531-0.027344-0.023437-0.011718-0.007813-0.023437-0.007813-0.035156-0.015626-0.050781-0.03125-0.078125-0.082031-0.12109-0.12109-0.050781-0.046875-0.10547-0.089844-0.14453-0.14453-0.039063-0.054688-0.0625-0.11719-0.089844-0.17969-0.027343-0.058594-0.058593-0.11719-0.074219-0.17969-0.015624-0.066406-0.011718-0.13281-0.015624-0.19922-0.003907-0.066406-0.011719-0.12891-0.003907-0.19531 0.011719-0.070312 0.039063-0.13281 0.0625-0.19922 0.019531-0.054687 0.023438-0.
134710937 0.050781-0.16016 0.007813-0.011718 0.019532-0.019531 0.023438-0.027344 0.007812-0.011718 0.007812-0.023437 0.015625-0.035156 1.3555-2.0586 2.0742-4.457 2.0742-6.9258 0-6.8828-5.5977-12.48-12.48-12.48-6.8984 0-12.512 5.5977-12.512 12.48-0.003906 6.8945 5.6094 12.508 12.508 12.508zm0-21.406c4.9062 0 8.8945 3.9922 8.8945 8.8945 0 4.9062-3.9922 8.8945-8.8945 8.8945-4.9219 0-8.9258-3.9922-8.9258-8.8945 0-4.9062 4.0039-8.8945 8.9258-8.8945zm6.8125 53.246c0 3.7578-3.0547 6.8125-6.8125 6.8125-3.7734 0-6.8398-3.0547-6.8398-6.8125 0-3.7578 3.0703-6.8125 6.8398-6.8125 3.7578 0 6.8125 3.0547 6.8125 6.8125zm72.73-46.844-3.9453 2.1992v-3.2578zm-35.496 27.121-4.9727-2.7812v-34.328h34.441v53.594l-4.9727-2.7812v-41.227c0-0.57422-0.46484-1.043-1.043-1.043-1.3789 0-2.5-1.1367-2.5-2.5312 0-0.57422-0.46484-1.043-1.043-1.043l-15.324 0.003906c-0.57422 0-1.043 0.46484-1.043 1.043 0 1.3945-1.1211 2.5312-2.5 2.5312-0.57422 0-1.043 0.46484-1.043 1.043zm22.414-26.602v39.137l-20.328-11.371v-27.766c1.6953-0.39844 3.0312-1.7461 3.4258-3.4531h13.477c0.39062 1.707 1.7266 3.0586 3.4258 3.4531zm-73.305 1.9727c0-3.7578 3.0703-6.8125 6.8398-6.8125 3.7578 0 6.8125 3.0547 6.8125 6.8125 0 3.7578-3.0547 6.8125-6.8125 6.8125-3.7695 0-6.8398-3.0547-6.8398-6.8125zm78.242 50.746c0.69922 0 1.4062-0.066406 2.1211-0.17969v6.2656h-34.445v-21.328l4.9727 2.5977v9.1719c0 0.57422 0.46484 1.043 1.043 1.043 1.3789 0 2.5 1.1211 2.5 2.5 0 0.57422 0.46484 1.043 1.043 1.043h15.324c0.57422 0 1.043-0.46484 1.043-1.043 0-0.55078 0.19141-1.0859 0.51562-1.5195 1.8906 0.96094 3.8594 1.4492 5.8828 1.4492zm-25.266-4.3945v-7.1641l17.516 9.1445c-0.28125 0.44141-0.48828 0.92969-0.60938 1.4414l-13.484 0.003906c-0.39453-1.6953-1.7305-3.0312-3.4219-3.4258zm33.418 0.52344-5.1172 1.3711c-2.8594 0.77344-5.5117 0.48828-8.1055-0.86328l-0.45703-0.23828c-0.046874-0.027344-0.089843-0.050781-0.14062-0.074219l-27.273-14.238c-0.011719-0.003906-0.019531-0.011719-0.027344-0.015625l-9.1875-4.7969 8.3008-4.6445 29.543 16.527c0.003906 0 0.003906 0.003907 0.007813 0.003907l6.9727 3.9023c0.035156 0.019532 0.070313 0.042969 0.10938 0.058594zm-69.148-9.4453c-0.007812-0.015625-0.023437-0.023437-0.03125-0.039062-0.027343-0.050782-0.03125-0.10547-0.050781-0.16016-0.023437-0.066407-0.054687-0.12891-0.0625-0.19531-0.011719-0.066406-0.003906-0.13281 0-0.19922 0.003907-0.066406 0-0.12891 0.015625-0.19531s0.046875-0.125 0.074219-0.18359 0.050781-0.12109 0.085937-0.17578c0.039063-0.054688 0.09375-0.097657 0.14844-0.14844 0.039062-0.039062 0.070312-0.085937 0.11719-0.11719 0.011718-0.007813 0.027343-0.007813 0.039062-0.015625 0.011719-0.007813 0.019531-0.019531 0.03125-0.027344l26.113-14.613c0.019531-0.015625 0.042969-0.019532 0.058594-0.035156l0.042969-0.023438v-8.9805l-9.4141 4.9062c-0.007812 0.003907-0.019531 0.007813-0.027343 0.015625l-10.781 5.6211-0.76953 4.5977c-0.19141 1.1836-0.86328 2.1523-1.9453 2.793l-6.2383 3.6094c-0.089843 0.050781-0.18359 0.085938-0.28125 0.10938-0.023437 0.003906-0.042968 0.003906-0.066406 0.007813-0.058594 0.011718-0.11719 0.023437-0.17578 0.023437-0.039063 0-0.078125-0.011719-0.12109-0.015625-0.03125-0.003906-0.058593-0.003906-0.089843-0.011719-0.11719-0.023437-0.23047-0.066406-0.33203-0.12891-0.007812-0.003906-0.011718-0.003906-0.019531-0.007813-2.0078-1.2852-4.3359-1.9609-6.7305-1.9609-6.8984 0-12.512 5.5977-12.512 12.48 0 6.8828 5.6133 12.48 12.512 12.48 6.8828 0 12.48-5.5977 12.48-12.48 0-2.4805-0.71484-4.8633-2.0703-6.8945 0.003907-0.015624 0.003907-0.027343 0-0.035156zm16.293-16.762c0.82812 0 1.5078 0.67969 1.5078 1.5078 0 0.85547-0.67969 1.5352-1.5078 1.5352-0.85547 0-1.5352-0.67969-1.5352-1.5352-0.003906-0.82422 0.67578-1.5078 1.5352-1.5078zm-26.695 32.578c-4.9219 0-8.9258-3.9922-8.9258-8.8984s4.0039-8.8945 8.9258-8.8945c4.9062 0 8.8945 3.9922 8.8945 8.8945 0.003906 4.9102-3.9883 8.8984-8.8945 8.8984z"></path> 
1348</svg> 
1349</div> 
1350</div> 
1351 <div id="8c92f4e9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1557258577"> <div class="dmNewParagraph ql-disabled" data-element-type="paragraph" data-version="5" id="1557258577" style="" new-inline-bind-applied="true" data-diy-text=""><p class="text-align-center m-text-align-center" style="line-height: 1.2;"><span style="color: var(--color_16); display: initial;"><span class="inline-data-binding" data-encoded-value="TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4=" data-inline-binding="STATIC_PAGE_COLLECTION.{"cn":"Why Choose","fn":"Why Choose 2"}" data-inline-binding-encoded="U1RBVElDX1BBR0VfQ09MTEVDVElPTi57ImNuIjoiV2h5IENob29zZSIsImZuIjoiV2h5IENob29zZSAyIn0=">Money-Saving Offers</span></span></p></div> 
1352</div> 
1353</div> 
1354 <div id="36e9d1ab" class="flex-element group" data-auto="flex-element-group"> <div id="9896a114" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1550059885"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_15500
135459885" data-element-type="graphic" data-widget-type="graphic" id="1550059885" data-hover-effect="zoomout"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1187472297" class="svg u_1187472297" data-icon-custom="true"> <path d="m34.887 73.465-2.5 4.3281c-0.63281 1.0977-2.043 1.4766-3.1406 0.84375-1.0938-0.63281-1.4727-2.0469-0.84375-3.1406l2.5-4.3281c0.63281-1.0977 2.043-1.4766 3.1445-0.84375 1.0977 0.63672 1.4766 2.043 0.83984 3.1406zm5.1836 0.33594c-0.35937-0.20703-0.75391-0.30859-1.1445-0.30859-0.79688 0-1.5703 0.41406-1.9922 1.1484l-4.0547 7.0195c-0.63281 1.0977-0.25391 2.5078 0.84375 3.1406 1.0977 0.63281 2.5078 0.25781 3.1406-0.84375l4.0547-7.0195c0.63281-1.0977 0.25781-2.5078-0.84375-3.1406zm-16.059-28.641c0.09375-0.078125 0.078125-0.21484-0.027344-0.26953l-4.2969-2.3906c-0.3125-0.17188-0.64844-0.14453-0.92578 0.074219-6.3555 5.0117-9.9023 13.867-8.4336 21.055 0.058594 0.28906 0.21875 0.48828 0.49219 0.60156l3.3711 1.4336c0.11328 0.046876 0.23047-0.027343 0.23047-0.14844 0.082031-8.1016 3.3984-15.141 9.5898-20.352zm23.152 33.52c-0.16016-0.59766-0.53906-1.0938-1.0664-1.3984-0.35938-0.20703-0.75781-0.30469-1.1445-0.30469-0.79688 0-1.5703 0.41406-1.9961 1.1484l-3.3789 5.8555c-0.58594 1.0898-0.20312 2.4609 0.87891 3.082 0.52734 0.30469 1.1484 0.38672 1.7422 0.22656 0.59766-0.16016 1.0898-0.53906 1.3984-1.0664l3.3438-5.793c0.30469-0.52734 0.38672-1.1484 0.22656-1.7422zm24.527-9.4141-10.781-6.2266c-2.1055-1.2188-4.0703-2.3594-6.5859-2.3594-2.2461 0.011718-4.2812 1.0078-6.25 1.9688-3.1055 1.5156-6.6211 3.2344-10.418 0.089843-1.0781-0.89453-1.6953-2.2305-1.6094-3.4961 0.078125-1.1602 0.71875-2.1719 1.8086-2.8516l8.8633-5.5234c-2.5859-1.2852-4.9688-1.0781-7.6562-0.84766-3.5508 0.30469-7.5742 0.65625-12.77-2.2656l-1.0156-0.57031c-5.4336 4.6836-8.2852 10.684-8.4844 17.848l8.5742 4.4219c0.12109 0.0625 0.23047 0.14453 0.32422 0.24219l2.0586 2.2148 1.1133-1.9258c1.2852-2.2227 4.1367-2.9883 6.3633-1.7031 1.1953 0.69141 1.9688 1.8359 2.2305 3.0938 1.2188-0.40234 2.5977-0.30469 3.793 0.38672 1.1992 0.69141 1.9727 1.8359 2.2305 3.0938 1.2188-0.40234 2.5977-0.30469 3.793 0.38672 1.0742 0.62109 1.8438 1.625 2.1641 2.8281 0.023438 0.089844 0.046875 0.17578 0.0625 0.26563 1.2188-0.40234 2.5977-0.30859 3.7969 0.38281 0.88672 0.51172 1.5625 1.2852 1.957 2.2188l5.1953 3c1.0078 0.58594 2.3203 0.23438 2.9023-0.77734 0.57031-0.99219 0.24609-2.2773-0.73047-2.875l-6.3633-3.6758c-0.5625-0.32422-0.75781-1.0469-0.43359-1.6094 0.32812-0.56641 1.0469-0.75781 1.6133-0.43359 2.4609 1.4219 4.918 2.8398 7.3789 4.2617 1.1172 0.64453 2.5508 0.26172 3.1953-0.85547 0.63672-1.1016 0.26172-2.5312-0.83203-3.1797l-6.2461-3.6055c-0.5625-0.32422-0.75781-1.0469-0.42969-1.6133 0.32422-0.5625 1.0469-0.75781 1.6094-0.42969 2.4297 1.4023 4.8594 2.8047 7.2852 4.207 1.1133 0.64063 2.5547 0.25781 3.1953-0.85547 0.71484-1.2344 0.15625-2.4453-0.91016-3.2266zm9.5469-26.691c-0.16016-0.12109-0.33594-0.1875-0.51562-0.1875-0.14062 0-0.27734 0.039062-0.41406 0.11328l-4.3008 2.3906c-0.058593 0.03125-0.078124 0.0625-0.085937 0.12891-0.007813 0.066407 0.007813 0.097657 0.058594 0.14062 6.1914 5.2148 9.5078 12.25 9.5898 20.352 0 0.125 0.11719 0.19531 0.23047 0.14844l3.3711-1.4336c0.27344-0.11328 0.43359-0.3125 0.49219-0.60156 1.4688-7.1875-2.0781-16.043-8.4297-21.055zm-6.543 4.5898-2.1094 1.1758c-3.4805 1.9375-7.3594 2.2852-11.219 1.0156-3.418-1.125-7-0.47266-10.961 1.9922l-11.305 7.0469c-0.44531 0.27734-0.67578 0.60547-0.70312 1.0078-0.035157 0.51172 0.26562 1.1094 0.76172 1.5195 2.5039 2.0742 4.5234 1.2461 7.8789-0.39453 2.1016-1.0273 4.4883-2.1914 7.2695-2.2109 3.0391 0 5.2383 1.207 7.7812 2.6797l10.836 6.2578c0.54688 0.30859 1.2695 1.0586 1.6094 1.5977l8.668-4.0117c-0.24219-7.0898-3.1016-13.035-8.5039-17.68zm-17.348 37.703-1.8359-1.0586c-0.10156 0.44141-0.26953 0.87109-0.50391 1.2773l-1.2461 2.1602 0.98438 0.55469c1.7266 0.99609 2.8516 0.45312 3.332-0.37891 0.4375-0.75781 0.39062-1.8711-0.72656-2.5547zm-5.2266-4.1055c-1.0977-0.625-2.5039-0.26172-3.1406 0.84375l-2.5 4.332c-0.63281 1.0977-0.25391 2.5078 0.84375 3.1406 1.0977 0.63281 2.5078 0.25781 3.1406-0.83984l2.5-4.332c0.63672-1.0938 0.25-2.5078-0.83984-3.1406zm-18.996-53.266c0-9.3203 7.5547-16.875 16.875-16.875s16.875 7.5547 16.875 16.875c0 9.3203-7.5547 16.875-16.875 16.875s-16.875-7.5547-16.875-16.875zm6.9844 0.078125 3.9414 6.8281c0.39453 0.68359 1.0234 1.1055 1.8086 1.207 0.78125 0.10156 1.5-0.14062 2.0586-0.69922l11.605-11.605c0.90234-0.90234 0.90234-2.375 0-3.2773
1354-0.90234-0.90234-2.375-0.90234-3.2773 0l-9.6016 9.6016-2.5234-4.375c-0.63672-1.1016-2.0625-1.4844-3.1641-0.84766-1.1016 0.63672-1.4844 2.0625-0.84766 3.1641zm34.234-4.5469c0.32422-3.2773 2.918-5.8672 6.1953-6.1953-3.2773-0.32422-5.8672-2.918-6.1953-6.1953-0.32422 3.2773-2.918 5.8672-6.1953 6.1953 3.2773 0.32422 5.8672 2.918 6.1953 6.1953zm-49.637 14.266c0.26562-2.6797 2.3867-4.8008 5.0664-5.0664-2.6797-0.26562-4.8008-2.3867-5.0664-5.0664-0.26562 2.6797-2.3867 4.8008-5.0664 5.0664 2.6797 0.26563 4.8008 2.3867 5.0664 5.0664z" fill-rule="evenodd"></path> 
1355</svg> 
1356</div> 
1357</div> 
1358 <div id="0fe11a31" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1350997461"> <div class="dmNewParagraph u_1350997461" data-element-type="paragraph" data-version="5" id="1350997461" style="" data-diy-text="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJXaHkgQ2hvb3NlXCIsXCJmblwiOlwiV2h5IENob29zZSAzXCJ9In1d"><p class="rteBlock">Trustworthy and Honest</p><br/></div> 
1359</div> 
1360</div> 
1361</div> 
1362 <div id="2feb2b48" class="flex-element group" data-auto="flex-element-group"> <div id="779db9a9" class="flex-element group" data-auto="flex-element-group"> <div id="184e6ded" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1118053298"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1118053298" data-element-type="graphic" data-widget-type="graphic" id="1118053298" data-hover-effect="zoomout"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1612283424" class="svg u_1612283424" data-icon-custom="true"> <path d="m50.031 19c-7.7305 0-14 6.2695-14 14s6.2695 14 14 14 14-6.2695 14-14-6.2695-14-14-14zm-29.035 8c-6.0742 0-11 4.9258-11 11s4.9258 11 11 11c6.0742 0 11-4.9258 11-11s-4.9258-11-11-11zm58 0c-6.0742 0-11 4.9258-11 11s4.9258 11 11 11c6.0742 0 11-4.9258 11-11s-4.9258-11-11-11zm-56.137 28c-14.969 0-20.863 12.836-20.863 26l18.039-0.54297c0.16016-7.8672 3.7969-14.918 9.5117-19.902-0.058594-2.6953-0.11719-4.5664-0.17578-4.582-2.3125-0.64844-4.875-0.97266-6.5117-0.97266zm27.168 0c-15.465 0-28 11.641-28 26h56c0-14.359-12.535-26-28-26zm27.102 0c-1.6523 0-4.2422 0.32812-6.5664 0.98828-0.054688 0.015625-0.10547 1.8359-0.15625 4.4766 5.7734 4.9922 9.457 12.078 9.6172 19.996l17.973 0.53906c0-13.164-5.8945-26-20.863-26z"></path> 
1363</svg> 
1364</div> 
1365</div> 
1366 <div id="512b7b4e" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1029252727"> <div class="dmNewParagraph u_1029252727" data-element-type="paragraph" data-version="5" id="1029252727" style="" data-diy-text="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJXaHkgQ2hvb3NlXCIsXCJmblwiOlwiV2h5IENob29zZSA0XCJ9In1d"><p class="rteBlock">Privately Owned and Operated</p><br/></div> 
1367</div> 
1368</div> 
1369 <div id="9e72910f" class="flex-element group" data-auto="flex-element-group"> <div id="66c628f6" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1843638588"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1843638588" data-element-type="graphic" data-widget-type="graphic" id="1843638588" data-hover-effect="zoomout"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1509203598" class="svg u_1509203598" data-icon-custom="true"> <path d="m53.805 33.059c8.2461-7.168 20.754-6.293 27.922 1.9492 7.168 8.2461 6.293 20.758-1.9531 27.922-8.2422 7.168-20.754 6.293-27.922-1.9531-7.1641-8.2422-6.2891-20.754 1.9531-27.918zm13.703 17.707c2.0703 0 3.7539-1.6797 3.75-3.75v-10.156c0-2.0703-1.6797-3.75-3.75-3.75h-1.4375c-2.0703 0-3.75 1.6797-3.75 3.75v10.156c0.003907 2.0703 1.6797 3.75 3.75 3.75zm-0.71875 10.172c2.4688 0 4.4727-2.0039 4.4688-4.4688 0-2.4648-2-4.4688-4.4688-4.4688-2.4648 0-4.4688 2.0039-4.4688 4.4688 0 2.4688 2.0039 4.4688 4.4688 4.4688zm0.47656 8.4023-0.015625 19.723c-0.003906 0.86328-0.70312 1.5625-1.5664 1.5625h-
136957.742c-0.86719 0-1.5664-0.69922-1.5664-1.5625v-68.75h40.629c0.85938 0 1.5586-0.69922 1.5586-1.5625 0.003906-0.86328-0.69922-1.5625-1.5625-1.5625h-40.621l-0.003906-6.25c0.003906-0.86328 0.70312-1.5625 1.5664-1.5625h45.598v13.816c0 0.86328 0.69922 1.5625 1.5625 1.5664l12.211-0.003906v1.8984c-5.1445-0.12891-10.348 1.5898-14.531 5.2266-8.8945 7.7305-9.8398 21.23-2.1055 30.125 4.3281 4.9805 10.465 7.4688 16.59 7.3359zm-37.277-42.777c-7.7617 0-14.062 6.3008-14.059 14.062-0.003907 7.7617 6.2969 14.062 14.059 14.062s14.062-6.3008 14.062-14.062-6.3008-14.062-14.062-14.062zm0 3.125c6.0352 0 10.938 4.8984 10.938 10.938 0 6.0352-4.8984 10.938-10.938 10.938-6.0352 0-10.938-4.9023-10.938-10.938s4.9023-10.938 10.938-10.938zm0 12.246c0.78125 0 1.4922 0.20312 2.0039 0.55859 0.28516 0.19922 0.53125 0.43359 0.53125 0.73828 0 0.30859-0.24609 0.53906-0.53125 0.73828-0.51172 0.35938-1.2227 0.55859-2.0039 0.55859s-1.4922-0.19922-2.0039-0.55859c-0.28516-0.19922-0.53125-0.43359-0.53125-0.73828 0-0.86328-0.69922-1.5625-1.5625-1.5625s-1.5625 0.69922-1.5625 1.5625c0 1.0469 0.44922 2.0352 1.2773 2.8203 0.69531 0.66016 1.6758 1.1758 2.8203 1.4297v1.3398c0 0.86328 0.69922 1.5625 1.5625 1.5625s1.5625-0.69922 1.5625-1.5625v-1.3359c1.1445-0.25391 2.1289-0.77734 2.8242-1.4336 0.82422-0.78516 1.2734-1.7734 1.2734-2.8203s-0.44922-2.0352-1.2773-2.8203c-0.98828-0.94531-2.5742-1.6016-4.3828-1.6016-0.80078 0-1.5312-0.18359-2.0586-0.51953-0.23828-0.15234-0.47656-0.29297-0.47656-0.52344s0.23828-0.37109 0.47656-0.51953c0.52734-0.33594 1.2578-0.52344 2.0586-0.52344h0.023438c0.79297 0.003906 1.5156 0.19141 2.0352 0.51953 0.24219 0.15234 0.47656 0.29297 0.47656 0.52344 0 0.86328 0.69922 1.5625 1.5625 1.5625 0.86328 0 1.5625-0.69922 1.5625-1.5625 0-1.0039-0.45703-1.9609-1.3281-2.7188-0.67578-0.58594-1.6328-1.0508-2.7422-1.2812l0.003906-1.332c0-0.86328-0.69922-1.5664-1.5625-1.5664-0.86328-0.003907-1.5664 0.69922-1.5625 1.5586l-0.003906 1.3242c-1.1328 0.23047-2.1094 0.70312-2.793 1.2969-0.87109 0.75391-1.332 1.7148-1.332 2.7188 0 1.0078 0.46094 1.9648 1.332 2.7188 0.97656 0.85156 2.543 1.4492 4.3281 1.4492zm-15.801 39.316h34.375c0.86719 0 1.5625-0.69922 1.5625-1.5625 0.003906-0.86328-0.69922-1.5625-1.5586-1.5625h-34.375c-0.86719 0-1.5625 0.69922-1.5664 1.5625 0 0.86328 0.70312 1.5625 1.5625 1.5625zm21.812-12.633c0.84766-2.207 1.2383-4.4922 1.3086-4.9258 0.10156-0.60938-0.16406-1.2188-0.67578-1.5586-0.51172-0.33984-1.1758-0.34766-1.6953-0.019531-1.6094 1.0117-12.254 7.7539-21.973 8.9688-0.85547 0.10547-1.4609 0.88672-1.3555 1.7422 0.10938 0.85547 0.89062 1.4648 1.7461 1.3594 4.332-0.54297 8.8398-2.1133 12.734-3.8281l-0.015625 0.23828c0.070312 0.73438 0.66406 2.125 2.2031 2.793 0.81641 0.35547 1.9375 0.53125 3.4219 0.14062 0.98047-0.26172 1.8555-0.89844 2.5859-1.793 1.25 0.027344 2.4922 0.007813 3.7305-0.14844 0.24609-0.03125 0.48047-0.09375 0.70703-0.18359l-0.003906 0.1875c-0.019531 0.59766 0.30469 1.1523 0.83594 1.4297 0.52734 0.27734 1.168 0.23047 1.6484-0.12109 1.2109-0.89063 2.2344-1.625 3.0156-2.1836l0.125 0.30859 0.37109 0.4375 0.56641 0.32422 0.48047 0.09375 0.57422-0.054687c0.99219-0.25 1.8164-0.87891 2.5625-1.6445 0.082031-0.082032 0.16016-0.16797 0.24219-0.25391 0.015625 0.82812 0.1875 1.5977 0.65625 2.2383l0.41797 0.42578 0.52734 0.28516 0.52344 0.12109h0.45312c0.64062-0.0625 1.5078-0.57031 2.0977-0.86719 0.76953-0.38281 1.082-1.3242 0.69531-2.0938-0.38281-0.77344-1.3242-1.0859-2.0938-0.69922-0.039063 0.019531-0.085938 0.042968-0.14062 0.070312 0.011718-0.22656 0.03125-0.45312 0.039062-0.67578 0.035156-0.97266-0.11328-1.8906-0.65234-2.625-0.375-0.51172-1.0234-0.75-1.6406-0.59375-0.99219 0.24609-1.8086 0.87891-2.5586 1.6406-0.12109 0.125-0.23828 0.25-0.35938 0.37891l-0.070313-0.18359-0.22656-0.33203-0.26562-0.24219-0.33984-0.19141-0.60938-0.13281-0.62891 0.085937c-0.35156 0.10938-1.1562 0.62109-3.0352 1.9688-0.015626-0.41797-0.19922-0.81641-0.50781-1.0977-0.33984-0.30469-0.79297-0.45312-1.2461-0.39062-0.67969 0.082031-1.2812 0.42188-1.8359 0.875-0.18359 0.15234-0.36328 0.31641-0.54688 0.46094-0.058594 0.046876-0.17188 0.12109-0.20703 0.14063-0.52734 0.0625-1.0586 0.10156-1.5859 0.125zm-6.0273-0.10547c1.2812-0.65234 2.4336-1.2773 3.4102-1.832
1369-0.16797 0.50781-0.35937 1.0234-0.58984 1.5234-0.058594 0.12891-0.12109 0.25781-0.1875 0.38672-0.875-0.035156-1.7578-0.070313-2.6328-0.078125zm26.691-58.156 9.8008 11.273-9.8008 0.003906zm26.961 50.781 8.0039 6.625c1.3281 1.0938 2.1016 2.7148 2.1211 4.4336 0.023438 1.7227-0.71094 3.3594-2.0117 4.4883l-0.007812 0.007812c-1.2969 1.125-3.0234 1.625-4.7227 1.3672-1.7031-0.26172-3.1992-1.2578-4.1016-2.7227l-5.4297-8.8516c1.1602-0.67188 2.2734-1.4609 3.3242-2.375s1.9922-1.9141 2.8242-2.9727z" fill-rule="evenodd"></path> 
1370</svg> 
1371</div> 
1372</div> 
1373 <div id="3e19a937" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1093674591"> <div class="dmNewParagraph u_1093674591" data-element-type="paragraph" data-version="5" id="1093674591" style="" data-diy-text="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJXaHkgQ2hvb3NlXCIsXCJmblwiOlwiV2h5IENob29zZSA1XCJ9In1d"><p class="rteBlock">We'll Beat Any Written Estimate</p><br/></div> 
1374</div> 
1375</div> 
1376 <div id="e7551c00" class="flex-element group" data-auto="flex-element-group"> <div id="e15e114e" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1068091964"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1068091964" data-element-type="graphic" data-widget-type="graphic" id="1068091964" data-hover-effect="zoomout"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1789562057" class="svg u_1789562057" data-icon-custom="true"> <g> <path d="m70.625 44.664h-1.9766c-0.38672-1.3633-0.92969-2.6758-1.6172-3.9141l1.4062-1.4141c0.73438-0.75391 1.1172-1.7852 1.0469-2.8359-0.070313-1.0312-0.51172-2-1.2422-2.7344l-1.7734-1.7812c-0.70703-0.76172-1.6875-1.2148-2.7266-1.25-1.0352-0.035156-2.0469 0.34375-2.8047 1.0547l-1.3984 1.4062c-1.2305-0.68359-2.5352-1.2266-3.8906-1.6172v-2c-0.074219-2.168-1.8867-3.8672-4.0547-3.7969h-2.4922c-2.168-0.066406-3.9766 1.6328-4.0547 3.7969v2c-1.3516 0.39062-2.6523 0.93359-3.8828 1.6172l-1.3984-1.4062c-0.75781-0.71094-1.7695-1.0898-2.8086-1.0547-1.043 0.035156-2.0234 0.48828-2.7305 1.25l-1.7656 1.7812c-0.73438 0.73047-1.1758 1.7031-1.2422 2.7344-0.070312 1.0508 0.3125 2.082 1.0469 2.8359l1.3984 1.4141c-0.67969 1.2422-1.2188 2.5547-1.6094 3.9141h-1.9844c-2.1641 0.089844-3.8516 1.9141-3.7734 4.0781v2.5156c-0.078125 2.1641 1.6094 3.9883 3.7734 4.0781h1.9844c0.39062 1.3594 0.92969 2.6719 1.6094 3.9141l-1.4062 1.4141c-0.73047 0.75781-1.1055 1.7852-1.0391 2.8359 0.066406 1.0312 0.50781 2.0039 1.2422 2.7344l1.7656 1.7812c0.70703 0.76172 1.6875 1.2148 2.7266 1.25 1.0352 0.035156 2.0469-0.34375 2.8047-1.0547l1.4062-1.4062c1.2266 0.6875 2.5312 1.2305 3.8828 1.6172v2c0.078125 2.1641 1.8867 3.8633 4.0547 3.7969h2.4922c2.168 0.070312 3.9805-1.6289 4.0547-3.7969v-2c1.3555-0.38672 2.6602-0.92969 3.8906-1.6172l1.3984 1.4062c0.76172 0.71094 1.7695 1.0898 2.8086 1.0508 1.0391-0.035157 2.0234-0.48438 2.7305-1.2461l1.7578-1.7812c0.73438-0.73047 1.1797-1.6992 1.25-2.7344 0.070313-1.0508-0.3125-2.082-1.0469-2.8359l-1.4062-1.4141c0.68359-1.2383 1.2266-2.5508 1.6172-3.9141h1.9766c2.1641-0.089844 3.8516-1.9141 3.7734-4.0781v-2.5156c0.078124-2.1641-1.6094-3.9883-3.7734-4.0781zm-20.273 14.711c-3.7969 0.027344-7.2383-2.2383-8.7109-5.7383s-0.69141-7.5469 1.9844-10.242c2.6758-2.6953 6.7148-3.5117 10.227-2.0625 3.5117 1.4453 5.8047 4.8711 5.8047 8.668 0.015625 5.1562-4.1484 9.3516-9.3047 9.375z"></path> 
1377 <path d="m88.062 9.1055c0.54688-1.8438 0.37109-3.8242-0.49219-5.5391-1.1836-2.2266-3.5117-3.6016-6.0312-3.5664-1.6562 0.011719-3.2656 0.52734-4.625 1.4766-3.8008 2.625-2.1641 5.8789-2.1641 5.8789 1.0703 1.8555 2.9062 3.3672 4.457 5.4766v-0.003906c-0.40234 0.28125-0.76562 0.60938-1.0859 0.98047-0.625-0.36328-1.3789-0.72656-1.5703-0.82422h0.003907c-0.76953-0.41016-1.582-0.73047-2.4219-0.96094-0.74219-0.19922-1.5039-0.30078-2.2695-0.30469-1.1289-0.015625-2.2422 0.25781-3.2344 0.80078-4.0547 2.2812-4.4531 7.2422-2.0742 10.73 1.3984 2.0547 2.9805 2.5312 4.1445 2.5312 0.58203 0.003906 1.1602-0.11328 1.6914-0.35156 1.8398-1.0742 3.3359-2.918 5.4219-4.4766 0.28516 0.39844 0.61719 0.75781 0.99219 1.0703-0.36328 0.63281-0.72266 1.3945-0.82031 1.5859-0.40625 0.77734-0.72656 1.5977-0.95703 2.4414-0.54688 1.8438-0.37109 3.8242 0.49219 5.5391 1.1836 2.2266 3.5117 3.6016 6.0312 3.5664 1.6562-0.011719 3.2656-0.52734 4.625-1.4766 3.8008-2.625 2.1641-5.8789 2.1641-5.8789-1.0703-1.8555-2.9062-3.3672-4.457-5.4766v0.003906c0.40234-0.28125 0.76563-0.60938 1.0859-0.98047 0.625 0.36328 1.3789 0.72656 1.5703 0.82422h-0.003906c0.76953 0.41016 1.582 0.73047 2.4219 0.96094 0.74219 0.19922 1.5039 0.30078 2.2695 0.30469 1.1289 0.015625 2.2422-0.25781 3.2344-0.80078 4.0547-2.2812 4.4531-7.2422 2.0742-10.73-1.4023-2.0586-2.9805-2.5312-4.1445-2.5312-0.58203-0.003906-1.1602 0.11328-1.6914 0.35156-1.8398 1.0742-3.3359 2.918-5.4219 4.4766-0.28516-0.39844-0.61719-0.75781-0.99219-1.0703 0.36328-0.63281 0.72266-1.3945 0.82031-1.5859 0.40625-0.77734 0.72656-1.5977 0.95703-2.4414zm-5.5156 11.145h-0.003906c-1.0742-0.019531-2.0352-0.68359-2.4336-1.6836-0.39453-1-0.15625-2.1445 0.61328-2.8984 0.76953-0.75391 1.9141-0.97266 2.9062-0.55469 0.99609 0.41797 1.6406 1.3867 1.6406 2.4648-0.015625 1.4883-1.2344 2.6875-2.7266 2.6719z"></path> 
1378 <path d="m3.2383 65.832c0.21094 0.63672 0.80469 1.0664 1.4727 1.0664 0.16797 0 0.33203-0.027344 0.49219-0.078126 0.8125-0.27734 1.2539-1.1602 0.98047-1.9766-0.078125-0.23828-0.15625-0.47656-0.23047-0.71484v-0.003906c-0.125-0.39453-0.39844-0.72266-0.76562-0.91406-0.36719-0.187
13785-0.79297-0.22656-1.1875-0.10156-0.39453 0.12891-0.72266 0.40625-0.91016 0.77344-0.1875 0.36719-0.22266 0.79688-0.09375 1.1875 0.078125 0.25391 0.16016 0.50781 0.24219 0.76172z"></path> 
1379 <path d="m33.844 2.8281c-0.25 0.089844-0.5 0.17969-0.75 0.27344-0.70312 0.26563-1.1172 0.99609-0.98828 1.7383 0.13281 0.74219 0.77734 1.2852 1.5312 1.2891 0.18359 0 0.36719-0.03125 0.53906-0.097656 0.23438-0.085938 0.46484-0.17188 0.69922-0.25391 0.8125-0.29297 1.2344-1.1836 0.94922-1.9961-0.13672-0.38672-0.42188-0.70703-0.79297-0.88672-0.37109-0.17969-0.80078-0.20312-1.1875-0.066406z"></path> 
1380 <path d="m1.6211 59.637c0.078125 0.40625 0.3125 0.76562 0.65625 0.99609 0.34375 0.23047 0.76172 0.31641 1.168 0.23828 0.40625-0.082032 0.76172-0.32031 0.99219-0.66406s0.3125-0.76562 0.23047-1.1719c-0.046875-0.24219-0.09375-0.48828-0.13672-0.73438-0.070312-0.40625-0.30078-0.76953-0.63672-1.0078-0.33984-0.23828-0.75781-0.32812-1.1641-0.25781-0.40625 0.074219-0.76953 0.30469-1.0039 0.64453-0.23828 0.33984-0.32812 0.75781-0.25391 1.1641 0.046875 0.26562 0.097656 0.53125 0.14844 0.79297z"></path> 
1381 <path d="m27.992 5.3438c-0.23438 0.12109-0.46875 0.24219-0.70313 0.36719-0.76172 0.40234-1.0508 1.3438-0.65234 2.1094 0.26562 0.51172 0.79688 0.83594 1.375 0.83594 0.25 0 0.49609-0.0625 0.71875-0.17969 0.21875-0.11719 0.44141-0.23047 0.66406-0.34375 0.76562-0.39453 1.0703-1.332 0.68359-2.1016-0.18359-0.36719-0.50781-0.64844-0.90234-0.77734-0.39062-0.12891-0.81641-0.097656-1.1836 0.089844z"></path> 
1382 <path d="m93.934 26.035c-0.125-0.23438-0.25391-0.46484-0.38672-0.69922-0.19922-0.35938-0.53516-0.625-0.93359-0.73438-0.39844-0.10937-0.82031-0.058593-1.1797 0.14844-0.74609 0.42969-1.0078 1.3789-0.58203 2.1289 0.12109 0.21484 0.24219 0.43359 0.36328 0.65234l-0.003906 0.003906c0.19531 0.36328 0.53125 0.63281 0.92578 0.74609 0.39453 0.11719 0.82031 0.074219 1.1797-0.125 0.75391-0.41797 1.0312-1.3672 0.61719-2.1211z"></path> 
1383 <path d="m0.80469 53.285c0.050781 0.82031 0.72656 1.457 1.5469 1.4609 0.035157 0 0.066407 0 0.10156-0.003906 0.85938-0.058594 1.5078-0.80078 1.4492-1.6602-0.015625-0.25-0.03125-0.5-0.042969-0.75-0.019531-0.41016-0.19922-0.80078-0.50781-1.0781-0.30469-0.27734-0.70703-0.42188-1.1172-0.40234-0.41406 0.019532-0.80078 0.20313-1.0781 0.51172-0.27344 0.30469-0.41797 0.71094-0.39453 1.1211 0.011719 0.26562 0.027343 0.53516 0.042969 0.80078z"></path> 
1384 <path d="m98.133 45.984c0.44531 0.003906 0.86719-0.1875 1.1641-0.51953 0.29297-0.33594 0.43359-0.77734 0.37891-1.2188-0.03125-0.26562-0.0625-0.53125-0.097656-0.79688-0.050781-0.41016-0.26562-0.78125-0.58984-1.0352-0.32812-0.25391-0.74219-0.36719-1.1523-0.3125-0.41016 0.054688-0.78125 0.26953-1.0352 0.59766-0.25 0.33203-0.35547 0.74609-0.30078 1.1562 0.03125 0.24609 0.0625 0.49609 0.089844 0.74609h0.003906c0.085938 0.78516 0.74609 1.3789 1.5391 1.3828z"></path> 
1385 <path d="m95.348 37.965c0.0625 0.24219 0.125 0.48438 0.1875 0.73047 0.16797 0.69531 0.78906 1.1836 1.5039 1.1836 0.12891 0 0.25391-0.015625 0.375-0.046875 0.83594-0.21094 1.3398-1.0586 1.1328-1.8906-0.0625-0.25781-0.12891-0.51562-0.19531-0.77344h-0.003906c-0.10156-0.40234-0.36328-0.74219-0.71875-0.95312-0.35547-0.20703-0.78125-0.26562-1.1836-0.15625-0.39844 0.10547-0.73828 0.36719-0.94531 0.72656-0.20703 0.35547-0.26172 0.78125-0.15234 1.1797z"></path> 
1386 <path d="m40.035 95.719c-0.24219-0.054688-0.48047-0.11328-0.72266-0.17188v0.003906c-0.83594-0.19922-1.6758 0.3125-1.8789 1.1484-0.20312 0.83203 0.30469 1.6758 1.1406 1.8867 0.25781 0.0625 0.51953 0.125 0.77734 0.18359 0.11328 0.023438 0.22656 0.039063 0.34375 0.039063 0.79297-0.007813 1.4531-0.60547 1.5391-1.3945 0.089844-0.78516-0.42578-1.5156-1.1992-1.6953z"></path> 
1387 <path d="m0.85156 46.086c-0.019531 0.26562-0.039062 0.53125-0.054687 0.79687h-0.003906c-0.054688 0.85938 0.59766 1.5977 1.4531 1.6562 0.03125 0.003907 0.066406 0.003907 0.097656 0.003907 0.82422-0.003907 1.5-0.64453 1.5469-1.4648 0.015625-0.25 0.03125-0.5 0.050781-0.75l0.003906-0.003906c0.035157-0.41016-0.097656-0.82031-0.36719-1.1367-0.26562-0.3125-0.64844-0.50781-1.0625-0.53906-0.41016-0.03125-0.81641 0.10156-1.1328 0.37109-0.3125 0.26953-0.50391 0.65234-0.53125 1.0664z"></path> 
1388 <path d="m3.1172 42.387c0.74219-0.003907 1.3828-0.53516 1.5195-1.2656 0.046875-0.24609 0.09375-0.49219 0.14844-0.73438v-0.003907c0.085938-0.40234 0.011719-0.82422-0.21484-1.1719-0.22266-0.34766-0.57812-0.58984-0.98047-0.67578-0.40625-0.085937-0.82812-0.007812-1.1719 0.21875-0.34766 0.22656-0.58984 0.58203-0.67188 0.98828-0.054688 0.26172-0.10547 0.52344-0.15625 0.78516-0.089844 0.45703 0.027344 0.92969 0.32422 1.2891 0.29297 0.36328 0.73438 0.57031 1.2031 0.57031z"></path> 
1389 <path d="m24.434 11.031c0.56641-0.52734 0.66797-1.3867 0.23438-2.0273-0.23047-0.34375-0.58594-0.57812-0.99219-0.65625-0.40234-0.078125-0.82031 0.011719-1.1602 0.24219-0.21875 0.14844-0.43359 0.30078-0.65234 0.45312-0.085937 0.0625-0.16406 0.12891-0.23828 0.20703 1.1094 0.26562 2.0938 0.89062 2.8086 1.7812z"></path> 
1390 <path d="m39.973 1.0938c-0.26172 0.054688-0.52344 0.11328-0.78125 0.17188v0.003906c-0.84375 0.19141-1.3672 1.0273-1.1758 1.8711 0.19141 0.83984 1.0312 1.3672 1.8711 1.1719 0.24219-0.054688 0.48047-0.10938 0.72266-0.16016 0.83984-0.17969 1.3789-1.0039 1.2031-1.8477-0.17578-0.83984-1-1.3789-1.8398-1.2109z"></path> 
1391 <path d="m98.449 48.281c-0.85938 0.003906-1.5547 0.70312-1.5547 1.5625l0.003907 0.15625c0 0.25 0 0.5-0.003907 0.75h-0.003906c-0.015625 0.85938 0.66797 1.5703 1.5273 1.5898h0.027343-0.003906c0.851
139156-0.003906 1.543-0.6875 1.5508-1.5391 0.003906-0.26562 0.007812-0.53516 0.007812-0.8125v-0.14453c0.003906-0.85938-0.69141-1.5586-1.5508-1.5625z"></path> 
1392 <path d="m98.184 63.449c0.070312-0.25781 0.14062-0.51172 0.20703-0.76953 0.10547-0.39844 0.050781-0.82422-0.15625-1.1836s-0.54688-0.61719-0.94922-0.72266c-0.39844-0.10547-0.82422-0.046876-1.1797 0.16016-0.35547 0.21094-0.61328 0.55469-0.71875 0.95312-0.0625 0.24219-0.12891 0.48438-0.19531 0.73047v-0.003907c-0.11328 0.39844-0.0625 0.82812 0.13672 1.1875 0.20312 0.35938 0.54297 0.625 0.94141 0.73828 0.39844 0.10938 0.82422 0.058594 1.1836-0.14844 0.35938-0.20312 0.62109-0.54297 0.73047-0.94141z"></path> 
1393 <path d="m99.492 57.184c0.039062-0.26562 0.074218-0.53125 0.10547-0.79688v0.003906c0.054688-0.41016-0.054687-0.82812-0.30469-1.1562-0.25391-0.32812-0.625-0.53906-1.0352-0.59375-0.41016-0.050781-0.82422 0.0625-1.1484 0.31641-0.32812 0.25391-0.53906 0.625-0.58984 1.0352-0.03125 0.25-0.066406 0.49609-0.10156 0.74219v0.003906c-0.12109 0.85156 0.46484 1.6406 1.3164 1.7695 0.074219 0.011719 0.14844 0.015626 0.22266 0.015626 0.77344-0.003907 1.4258-0.57422 1.5352-1.3398z"></path> 
1394 <path d="m57.816 96.273c-0.24609 0.039062-0.49219 0.078124-0.73828 0.11328v0.003906c-0.80469 0.12109-1.3828 0.84375-1.3242 1.6562s0.73047 1.4453 1.5469 1.4492c0.074219 0 0.14844-0.003906 0.22656-0.015625 0.26172-0.039063 0.52344-0.078125 0.78516-0.12109 0.84375-0.14062 1.418-0.9375 1.2852-1.7852-0.13672-0.85156-0.93359-1.4297-1.7812-1.3008z"></path> 
1395 <path d="m51.883 96.852c-0.25 0.007813-0.49609 0.015626-0.74609 0.019532v-0.003906c-0.85156 0.023437-1.5312 0.72266-1.5234 1.5781 0.007813 0.85156 0.69531 1.543 1.5508 1.5469h0.027344c0.26562-0.003907 0.52734-0.011719 0.79297-0.019532 0.85547-0.035156 1.5234-0.75391 1.4961-1.6094-0.027344-0.85938-0.73828-1.5312-1.5977-1.5117z"></path> 
1396 <path d="m51.82 0.019531c-0.41406-0.011719-0.81641 0.14062-1.1133 0.42578-0.30078 0.28516-0.47656 0.67969-0.48047 1.0938-0.03125 0.85938 0.64453 1.5781 1.5039 1.6055 0.25 0.007813 0.5 0.015625 0.74609 0.027344h0.070313v0.003906c0.84375-0.007812 1.5312-0.68359 1.5508-1.5312 0.015625-0.84375-0.64062-1.5508-1.4844-1.5937-0.26172-0.011719-0.52734-0.023437-0.79297-0.03125z"></path> 
1397 <path d="m45.922 96.664c-0.25-0.023437-0.49609-0.050781-0.74219-0.078124v0.003906c-0.85547-0.089844-1.6172 0.53125-1.7109 1.3828-0.09375 0.85547 0.51953 1.625 1.3711 1.7227 0.26172 0.027344 0.52734 0.054688 0.78906 0.082032 0.050782 0.003906 0.097656 0.007812 0.14844 0.007812 0.83203-0.007812 1.5117-0.66406 1.5508-1.4922s-0.57812-1.543-1.4062-1.6289z"></path> 
1398 <path d="m45.617 3.3555c0.050781 0 0.10156 0 0.15234-0.007813 0.24609-0.023437 0.49609-0.046875 0.74609-0.066406 0.85547-0.078125 1.4883-0.82812 1.418-1.6836-0.070313-0.85547-0.82031-1.4961-1.6758-1.4297-0.26562 0.023437-0.52734 0.046875-0.78906 0.070312-0.82422 0.085938-1.4414 0.80078-1.3984 1.6289 0.039063 0.82812 0.71875 1.4805 1.5469 1.4883z"></path> 
1399 <path d="m63.48 5.0156c0.23828 0.070313 0.47656 0.14453 0.71484 0.21875 0.82422 0.25 1.6914-0.21094 1.9453-1.0312s-0.19922-1.6953-1.0195-1.9531c-0.25391-0.078125-0.50781-0.15625-0.76172-0.23047-0.82422-0.23828-1.6875 0.23828-1.9297 1.0625-0.24219 0.82422 0.23047 1.6875 1.0508 1.9336z"></path> 
1400 <path d="m69.219 92.863c-0.22656 0.10156-0.45312 0.19922-0.67969 0.29688v0.003906c-0.68359 0.28906-1.0625 1.0195-0.91797 1.7461 0.14844 0.72656 0.78125 1.25 1.5234 1.2539 0.20703 0 0.41406-0.042968 0.60547-0.125 0.24219-0.10547 0.48828-0.21094 0.72656-0.31641v-0.003906c0.78516-0.35156 1.1367-1.2734 0.78906-2.0586-0.16406-0.37891-0.47266-0.67578-0.85938-0.82422-0.38281-0.14844-0.8125-0.14063-1.1875 0.027343z"></path> 
1401 <path d="m63.629 94.941c-0.23828 0.070313-0.47656 0.14062-0.71484 0.20703-0.75 0.21094-1.2266 0.94141-1.1211 1.7148 0.10156 0.76953 0.75781 1.3477 1.5352 1.3516 0.14062 0 0.28125-0.019532 0.41797-0.054688 0.25781-0.070312 0.51172-0.14453 0.76562-0.22266 0.82422-0.24219 1.3008-1.1133 1.0586-1.9375-0.24609-0.82812-1.1133-1.3047-1.9414-1.0586z"></path> 
1402 <path d="m58.938 0.74609c-0.26172-0.046875-0.52344-0.089844-0.78516-0.13281-0.84766-0.125-1.6406 0.45703-1.7773 1.3047-0.13281 0.84766 0.44531 1.6445 1.293 1.7812 0.24609 0.039062 0.49219 0.082031 0.73828 0.125
1402h-0.003906c0.089844 0.015625 0.17969 0.023437 0.26953 0.023437 0.80859-0.007812 1.4766-0.62891 1.543-1.4297 0.070312-0.80469-0.48438-1.5273-1.2773-1.6719z"></path> 
1403 <path d="m12.973 35.941c3.1211 0.03125 6.1367-1.1406 8.4258-3.2656 4.293-4.1562 5.3125-10.062 2.7227-15.801-0.54688-1.2227-1.1641-2.4062-1.8555-3.5508-0.36328-0.75-1.1719-1.1797-1.9961-1.0625-0.82422 0.12109-1.4805 0.76172-1.6133 1.5859-0.0625 0.23047-0.13672 0.45312-0.22656 0.67188-0.089844-0.58594-0.11719-1.1836-0.082032-1.7812 0.011719-0.39844 0.015625-0.76562 0-1.0703-0.26953-4.4727-2.0039-8.4922-4.7617-11.031-0.55078-0.60547-1.4219-0.80078-2.1758-0.48828-0.75 0.35547-1.1992 1.1406-1.1289 1.9648-0.003906 0.89453-0.39844 3.5117-3.9492 8.4258-3.4648 4.7969-7.6992 11.965-4.5 18.848h0.003907c1.6875 3.3672 4.8945 5.7188 8.6133 6.3164 0.83203 0.15625 1.6758 0.23828 2.5234 0.23828zm0.41406-33.816v0.007812l-1.5508-0.007812zm-7.1562 28.117v-0.003907c-0.66016-0.62109-1.1953-1.3594-1.5859-2.1797-2.5039-5.3828 1.1953-11.527 4.1992-15.68 1.7969-2.3203 3.1914-4.9258 4.125-7.707 1.3867 2.1484 2.1719 4.6289 2.2812 7.1836 0.015625 0.23047 0.003906 0.50391-0.003906 0.80469-0.039063 1.5703-0.11328 4.4961 2.5664 5.3047v-0.003906c0.625 0.19141 1.3008 0.11719 1.8711-0.19922 0.39062-0.22656 0.72656-0.53516 0.98828-0.90234 0.21094 0.41797 0.41797 0.85547 0.625 1.3086 1.8828 4.168 1.4023 8.2695-1.2773 11.422 0.20312-1.5898-0.03125-3.2031-0.6875-4.6641-0.28125-0.66406-0.60547-1.3125-0.96484-1.9414-0.33984-0.64453-1.0469-1.0156-1.7695-0.92969-0.039062 0.003906-0.078125 0.011718-0.11719 0.019531h-0.003907c-0.13281-2.207-1.0469-4.2969-2.5781-5.8984-0.49219-0.52344-1.2617-0.69141-1.9297-0.41797-0.67188 0.30078-1.0859 0.98047-1.0469 1.7148 0 0.31641-0.13672 1.5312-1.8438 4.0078-1.5508 2.2461-3.3789 5.4453-2.8477 8.7578z"></path> 
1404 <path d="m34.316 94.02c-0.046875-0.015625-0.089844-0.035156-0.13672-0.050781-0.74219 0.64844-1.6758 1.043-2.6602 1.1289-0.054687 0.68359 0.34375 1.3242 0.98047 1.5742 0.25 0.097656 0.49609 0.19141 0.74609 0.28125h0.003906c0.38672 0.14453 0.8125 0.12891 1.1875-0.046875s0.66406-0.49219 0.80078-0.88281c0.29688-0.80859-0.11719-1.7031-0.92188-2.0039z"></path> 
1405 <path d="m32.465 91.234c0.4375-0.74219 0.19531-1.6953-0.54688-2.1406l-2.4258-1.4414 2.3711-0.20703c0.85938-0.078124 1.4883-0.83203 1.4141-1.6875-0.03125-0.41406-0.22656-0.79687-0.54297-1.0664-0.3125-0.26562-0.72266-0.39453-1.1367-0.35938l-6.707 0.58203-4.8516-2.8828 4.8516-2.8828 6.707 0.58203c0.046875 0.003906 0.089844 0.003906 0.13672 0.003906 0.83203-0.007813 1.5117-0.66406 1.5469-1.4961s-0.58984-1.5469-1.418-1.6211l-2.3711-0.20703 2.1992-1.3086h0.003906c0.73828-0.44141 0.98047-1.3984 0.54688-2.1406-0.21094-0.35547-0.55078-0.61328-0.94922-0.71875-0.39844-0.10156-0.82422-0.042969-1.1758 0.16797l-2.4219 1.4414 0.87109-2.1719c0.32422-0.79688-0.058594-1.707-0.85156-2.0352-0.38281-0.15625-0.8125-0.15234-1.1914 0.011719-0.37891 0.16016-0.67969 0.46484-0.83203 0.85156l-2.4023 5.9648-5.3438 3.1797v-5.75l4.3047-5.3008c0.54297-0.66797 0.44531-1.6484-0.21875-2.1953-0.31641-0.26562-0.72656-0.39062-1.1367-0.34766-0.41016 0.042968-0.78906 0.24609-1.0469 0.56641l-1.9023 2.3438v-3.3438c0.003907-0.41406-0.15625-0.8125-0.44922-1.1055-0.29297-0.29297-0.6875-0.46094-1.1016-0.46094-0.41406 0-0.8125 0.16797-1.1016 0.46094-0.29297 0.29297-0.45312 0.69141-0.44922 1.1055v3.3398l-1.9023-2.3398c-0.26172-0.32031-0.63672-0.52344-1.0469-0.56641-0.41016-0.039063-0.82031 0.082031-1.1367 0.34766-0.66406 0.54688-0.76172 1.5273-0.21875 2.1953l4.3047 5.3008v5.0352l-5.0703-3.0156-2.1836-5.4141c-0.15234-0.38281-0.45312-0.69141-0.83203-0.85156-0.37891-0.16016-0.80469-0.16406-1.1875-0.011719-0.79687 0.32812-1.1797 1.2383-0.85547 2.0352l0.65234 1.625-1.5039-0.89453h0.003907c-0.35547-0.21094-0.77734-0.26953-1.1797-0.16797-0.39844 0.10547-0.73828 0.36328-0.94531 0.71875-0.4375 0.74219-0.19141 1.6992 0.54687 2.1406l2.1094 1.2539-2.9805 0.25781v0.003907c-0.82812 0.074218-1.4531 0.78906-1.418 1.6211s0.71875 1.4883 1.5508 1.4961c0.042969 0 0.089844 0 0.13281-0.003906l7.1992-0.625c0.035157-0.003907 0.066407-0.015625 0.097657-0.019531l4.957 2.9492-4.8516 2.8828-6.707-0.58203 0.003906-0.003907c-0.85547-0.066406-1.6055 0.57031-1.6758 1.4258-0.074219 0.85547 0.55469 1.6094 1.4102 1.6875l2.3711 0.20703-2.4258 1.4414c-0.73828 0.44531-0.98438 1.3984-0.54688 2.1406 0.20703 0.35547 0.54688 0.61719 0.94922 0.71875 0.39844 0.10547 0.82031 0.042969 1.1758-0.16797l2.6484-1.5742-0.875 2.1719c-0.32031 0.79688 0.058594 1.707 0.85547 2.0352 0.38281 0.15625 0.80859 0.15234 1.1914-0.007813 0.37891-0.16406 0.67578-0.46875 0.82812-0.85547l2.4023-5.9648 4.1484-2.4688v5.3008l-4.3008 5.3008c-0.54297 0.67188-0.44531 1.6523 0.21875 2.1992 0.31641 0.26172 0.72656 0.38672 1.1367 0.34766 0.41016-0.042969 0.78906-0.24609 
14051.0469-0.56641l1.9023-2.3438v3.0781c-0.003906 0.41406 0.15625 0.8125 0.44922 1.1055 0.28906 0.29297 0.6875 0.46094 1.1016 0.46094 0.41406 0 0.80859-0.16797 1.1016-0.46094 0.29297-0.29297 0.45312-0.69141 0.44922-1.1055v-3.0781l1.9023 2.3438c0.25781 0.32031 0.63672 0.52344 1.0469 0.56641 0.41016 0.039063 0.82031-0.085937 1.1367-0.34766 0.66406-0.54688 0.76172-1.5273 0.21875-2.1992l-4.3047-5.3008v-6.0117l5.3438 3.1797 2.4023 5.9648c0.15625 0.38672 0.45703 0.69141 0.83594 0.85156 0.38281 0.16016 0.80859 0.16406 1.1914 0.007812s0.6875-0.45703 0.84766-0.83984c0.16016-0.38281 0.16016-0.8125 0-1.1914l-0.875-2.1719 2.6484 1.5742c0.23828 0.14453 0.51172 0.21875 0.78906 0.21875 0.55078 0 1.0586-0.29297 1.3359-0.76953z"></path> 
1406 <path d="m73.305 92.605c0.19922 0.34375 0.52344 0.59375 0.90625 0.70312-0.26563-0.88672-0.4375-1.7969-0.51953-2.7188-0.61328 0.48047-0.77734 1.3398-0.38672 2.0156z"></path> 
1407 <path d="m96.082 69.504c0.10156-0.24609 0.20703-0.49219 0.30469-0.74219 0.32031-0.80078-0.058594-1.707-0.85547-2.0352-0.67969-0.27734-1.457-0.039062-1.8672 0.57031 0.44531 1.0469 0.95703 2.0664 1.5352 3.0469 0.39844-0.14844 0.71484-0.45312 0.88281-0.83984z"></path> 
1408 <path d="m90.129 66.539c-0.28516-1.0977-0.64062-2.4648-2.0352-2.4766h-0.015625c-1.3789 0-1.7578 1.3711-2.0352 2.3711-0.76953 2.7891-2.3867 5.582-4.0977 8.543-2.7695 4.7852-5.6328 9.7344-5.1758 15.277 0.41797 5.0547 4.2266 8.8242 9.7031 9.6055l0.003906 0.003906c0.62109 0.085938 1.25 0.13281 1.8789 0.12891 4.2695 0 9.0195-1.9844 10.836-6.7852 2.4883-6.5781-1.3008-12.625-4.6445-17.965-1.8945-3.0234-3.6836-5.8789-4.418-8.7031zm2.2852 25.465h0.003907c-0.15625 0.38281-0.46094 0.6875-0.84375 0.84375-0.38281 0.16016-0.80859 0.16016-1.1914 0-0.38281-0.16016-0.68359-0.46484-0.83984-0.84766s-0.15234-0.8125 0.007812-1.1914c1.1875-2.8828-0.92188-6.3398-0.94141-6.3711v-0.003906c-0.46484-0.72656-0.25391-1.6875 0.46875-2.1562 0.34766-0.22266 0.76953-0.30078 1.1719-0.21094s0.75391 0.33594 0.97266 0.6875c0.125 0.19531 3.0156 4.832 1.1953 9.25z"></path> 
1409 <path d="m71.059 4.543c-0.24219-0.10938-0.48047-0.21875-0.72266-0.32812-0.37891-0.16797-0.80469-0.17578-1.1914-0.023438-0.38281 0.14844-0.69141 0.44531-0.85547 0.82422-0.34375 0.78906 0.011719 1.7109 0.79688 2.0625 0.23047 0.10156 0.45703 0.20312 0.6875 0.30859h-0.003907c0.55859 0.26172 1.2227 0.15625 1.6719-0.26953-0.15625-0.78906-0.1875-1.5977-0.089844-2.3945-0.089843-0.070312-0.1875-0.12891-0.29297-0.17969z"></path> 
1410 <path d="m93.73 32.973c0.14844 0.38672 0.44531 0.69531 0.82422 0.86328 0.37891 0.16406 0.80859 0.17187 1.1914 0.023437 0.38672-0.15234 0.69531-0.44922 0.85938-0.82812 0.16406-0.37891 0.16797-0.80859 0.015625-1.1914-0.097656-0.25-0.19531-0.49609-0.29297-0.74219v-0.003906c-0.19922-0.51172-0.65625-0.875-1.1992-0.95703-0.54297-0.082031-1.0898 0.12891-1.4297 0.55859-0.070313 0.44141-0.18359 0.87109-0.32812 1.2891 0.015625 0.10156 0.046875 0.19531 0.082031 0.28906 0.09375 0.23047 0.1875 0.46484 0.27734 0.69922z"></path> 
1411</g> 
1412</svg> 
1413</div> 
1414</div> 
1415 <div id="a5c30ddf" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1436842867"> <div class="dmNewParagraph u_1436842867" data-element-type="paragraph" data-version="5" id="1436842867" style="" data-diy-text="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJXaHkgQ2hvb3NlXCIsXCJmblwiOlwiV2h5IENob29zZSA2XCJ9In1d"><p class="rteBlock">We Service Any Brand</p><br/></div> 
1416</div> 
1417</div> 
1418</div> 
1419</div> 
1420</div> 
1421</div> 
1422</div> 
1423</section> 
1424</div> 
1425 <div data-flex-id="0c743062" data-auto="flex-section" id="1612545681" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104"> <div class="flex-widgets-container" id="1219944483"></div> 
1426 <section id="b770173d" class="flex-element section" data-auto="flex-element-section"> <div id="f0c02524" class="flex-element grid lazy" data-auto="flex-element-grid" data-layout-grid="" data-background-image="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/reviews-content-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg"> <div id="ff7b0e40" class="flex-element group" data-auto="flex-element-group"> <div id="7043db3c" class="flex-element group" data-auto="flex-element-group"> <div id="215d0554" class="flex-element group" data-auto="flex-element-group"> <div id="96bb3562" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1415392517"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1415392517" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI1IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1427</div> 
1428</div> 
1429</div> 
1430 <div id="aedecc5f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1982899465"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1982899465" data-element-type="graphic" data-widget-type="graphic" id="1982899465"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1569861248" class="svg u_1569861248" data-icon-custom="true"> <path d="m70.172 56.906h-2.457c0.035156-0.30078 0.054687-0.60156 0.054687-0.90234v-8.9297c0-0.30469-0.019531-0.60547-0.054687-0.90234h2.457zm9.8203-10.738h6.0273c5.8945 0 10.699 4.8086 10.699 10.699v21.762l-10.738 0.035156 0.003906-21.758h-5.9922zm-29.301 10.738h-3.8164v4.5078h-10.738v-4.543c0-5.8945 4.8086-10.699 10.699-10.699h3.8516c-0.035156 0.30078-0.054688 0.60156-0.054688 0.90234v8.9297c0.003907 0.30078 0.023438 0.60156 0.058594 0.90234zm-15.488 6.4531h12.605c0.73828 0 1.3359 0.53516 1.3359 1.1875v2.5742c0 0.65234-0.60156 1.1875-1.3359 1.1875h-12.605c-0.73828 0-1.3359-0.53516-1.3359-1.187
14305v-2.5742c-0.003907-0.65234 0.59766-1.1875 1.3359-1.1875zm22.066-21.637h3.8672c2.582 0 4.6875 2.4023 4.6875 5.3516v8.9297c0 2.9453-2.1055 5.3516-4.6875 5.3516l-3.8672-0.003907c-2.582 0-4.6875-2.4023-4.6875-5.3516l0.003907-8.9258c0-2.9453 2.1055-5.3516 4.6836-5.3516zm14.848 16.117v-12.605c0-0.73828 0.63672-1.3359 1.4219-1.3359h3.0859c0.78516 0 1.4219 0.60156 1.4219 1.3359v12.605c0 0.73828-0.63672 1.3359-1.4219 1.3359l-3.082 0.003907c-0.78516 0-1.4258-0.60156-1.4258-1.3398zm-17.227-26.824h8.625c0.30859 0 0.56641 0.30078 0.56641 0.66797v4.7695c0 0.36719-0.25391 0.66797-0.56641 0.66797h-8.625c-0.30859 0-0.5625-0.30078-0.5625-0.66797v-4.7695c0-0.36719 0.25391-0.66797 0.5625-0.66797zm1.3984 6.1055v2.7305c0.32031-0.050781 0.64453-0.078124 0.98438-0.078124h3.8672c0.33594 0 0.66406 0.027343 0.98438 0.078124v-2.7305zm9.7383-5.1641 6.8438-1.8789c0.85547-0.23438 1.6914 0.40234 1.6914 1.2891v3.1445c0 0.73828-0.60156 1.3359-1.3359 1.3359h-7.1953zm-13.645 0-6.8438-1.8789c-0.85547-0.23438-1.6914 0.40234-1.6914 1.2891v3.1445c0 0.73828 0.60156 1.3359 1.3359 1.3359h7.1953zm12.43-18.656c-0.51172-0.21094-1.0273-0.41016-1.5508-0.59766-1.4844-0.52734-2.5039-1.707-2.8086-3.2539l-0.75391-3.8359c-0.22656-1.1406-1.2539-1.9453-2.4766-1.9453h-15.223c-1.2227 0-2.2461 0.80469-2.4766 1.9453l-0.75781 3.8398c-0.30469 1.5469-1.3203 2.7227-2.8086 3.2539-1.0469 0.37109-2.0703 0.79688-3.0664 1.2695-1.4219 0.67969-2.9766 0.5625-4.2852-0.3125l-3.25-2.1797c-0.96875-0.64453-2.2617-0.49219-3.1289 0.375l-10.762 10.762c-0.86328 0.86328-1.0195 2.1602-0.375 3.1289l2.1797 3.25c0.87891 1.3086 0.99219 2.8633 0.3125 4.2852-0.47266 0.99609-0.89844 2.0195-1.2695 3.0664-0.52734 1.4844-1.707 2.5039-3.2539 2.8086l-3.8398 0.75781c-1.1406 0.22656-1.9453 1.2539-1.9453 2.4766v15.223c0 1.2227 0.80469 2.2461 1.9453 2.4766l3.8398 0.75781c1.5469 0.30469 2.7227 1.3242 3.2539 2.8086 0.37109 1.0469 0.79688 2.0703 1.2695 3.0664 0.67969 1.4219 0.5625 2.9766-0.3125 4.2852l-2.1797 3.25c-0.64453 0.96875-0.48828 2.2617 0.375 3.1289l10.766 10.766c0.86328 0.86328 2.1602 1.0195 3.1289 0.375l3.25-2.1797c1.3086-0.87891 2.8633-0.99219 4.2852-0.31641 0.99609 0.47266 2.0195 0.89844 3.0664 1.2695 1.4844 0.52734 2.5039 1.707 2.8086 3.2539l0.75781 3.8398c0.22656 1.1406 1.2539 1.9453 2.4766 1.9453h15.223c1.2227 0 2.2461-0.80469 2.4766-1.9453l0.75781-3.8398c0.30469-1.5469 1.3203-2.7266 2.8086-3.2539 0.52344-0.18359 1.0391-0.38281 1.5508-0.59766v-9.9453c-4.4844 2.5547-9.6719 4.0117-15.199 4.0117-16.988 0-30.762-13.773-30.762-30.762s13.773-30.762 30.762-30.762c5.5273 0 10.715 1.457 15.199 4.0117z" fill-rule="evenodd"></path> 
1431</svg> 
1432</div> 
1433</div> 
1434 <div id="957c687b" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1782039092"> <div class="dmNewParagraph u_1782039092" data-element-type="paragraph" data-version="5" id="1782039092" data-inject="topicheading5" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA1IEhFQURMSU5FXCJ9In1d" data-diy-text="">Plumbing</div> 
1435</div> 
1436 <div id="a317e2e6" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1451762313"> <div class="dmDividerWrapper clearfix u_1451762313" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1451762313" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1841985758"/></div> 
1437</div> 
1438 <div id="0b661df8" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1203957309"> <div class="dmNewParagraph u_1203957309" data-element-type="paragraph" data-version="5" id="1203957309" style="" data-inject="topiccontent5" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA1IFRFWFRcIn0ifV0=" data-diy-text="">Our plumbing services address everything from minor leaks to complete water heater installations with accurate solutions and honest pricing. We handle emergency plumbing situations quickly and help you avoid costly water damage with reliable repairs that last.</div> 
1439</div> 
1440</div> 
1441 <div id="fda5d045" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1333337127"> <div class="widget-901b1c dmCustomWidget" data-lazy-load="true" data-title="" id="1333337127" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="901b1c7285754fceac707cdcb08aa571" data-widget-version="7" data-widget-config="eyJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUiLCJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNSBIRUFETElORSJ9"> <div class="hide-column-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 5 HEADLINE" style="display:none;"></div> 
1442</div> 
1443</div> 
1444 <div id="6bf72316" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1640870598"> <div class="widget-f19f9b u_1640870598 dmCustomWidget" data-lazy-load="true" data-title="" id="1640870598" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1444NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjUiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1445 <span class="iconBg"></span> 
1446</div> 
1447</a> 
1448</div> 
1449</div> 
1450</div> 
1451</div> 
1452 <div id="4ca11f0c" class="flex-element group" data-auto="flex-element-group"> <div id="11cd0eaf" class="flex-element group" data-auto="flex-element-group"> <div id="dd6d627c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1163383559"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1163383559" data-element-type="graphic" data-widget-type="graphic" id="1163383559"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1444294740" class="svg u_1444294740" data-icon-custom="true"> <g> <path d="m88.891 38.891h-34.723c0.003906 1.5-0.48438 2.9648-1.3906 4.1641h2.7773c2.3008 0 4.168 1.8672 4.168 4.168 0 2.3008-1.8672 4.168-4.168 4.168h-7.1953c0.375 2.0234-0.17578 4.1055-1.4961 5.6836-1.3242 1.5742-3.2773 2.4805-5.3359 2.4688-1.2383 0-2.4531-0.32812-3.5273-0.94531-1.1211 0.57812-2.2852 1.0664-3.4844 1.457-0.79297 3.0742-3.5625 5.2227-6.7383 5.2227-3.1719 0-5.9453-2.1484-6.7344-5.2227-1.1992-0.39063-2.3672-0.87891-3.4883-1.457-2.7344 1.5781-6.1836 1.1484-8.4453-1.0547-2.2344-2.2461-2.668-5.7188-1.0547-8.4453-0.57422-1.1172-1.0586-2.2773-1.4453-3.4727-0.88281-0.23047-1.7109-0.63281-2.4414-1.1797v33.332c0 3.8359 3.1094 6.9453 6.9414 6.9453h77.781c3.832 0 6.9414-3.1094 6.9414-6.9453v-31.945c0-3.832-3.1094-6.9414-6.9414-6.9414zm-6.9453 38.887c-0.003906 0.76562-0.625 1.3867-1.3906 1.3906h-61.109c-0.76562-0.003907-1.3867-0.625-1.3906-1.3906v-5.5547c0.003906-0.76562 0.625-1.3867 1.3906-1.3906h61.109c0.76562 0.003907 1.3867 0.625 1.3906 1.3906zm4.168-25h-0.003906c-1.6836 0-3.2031-1.0156-3.8477-2.5703-0.64453-1.5586-0.28906-3.3516 0.90234-4.543 1.1914-1.1914 2.9844-1.5469 4.543-0.90234 1.5547 0.64453 2.5703 2.1641 2.5703 3.8477-0.007813 2.3008-1.8672 4.1602-4.168 4.168zm-29.168-5.5547c-0.003906-0.76562-0.625-1.3867-1.3906-1.3906h-6.6797c-0.32031 0.94922-0.69922 1.8789-1.1406 2.7773h7.8203c0.76562 0 1.3867-0.62109 1.3906-1.3867z"></path> 
1453 <path d="m87.5 48.609c0 1.8555-2.7773 1.8555-2.7773 0 0-1.8516 2.7773-1.8516 2.7773 0"></path> 
1454 <path d="m20.832 73.609h58.332v2.7773h-58.332z"></path> 
1455 <path d="m47.223 34.723h-0.47266c-0.48828-2.2422-1.375-4.375-2.6094-6.3047l0.33203-0.33203v-0.003907c1.625-1.625 1.625-4.2617 0-5.8867-1.6484-1.5703-4.2383-1.5703-5.8906 0l-0.33203 0.33203c-1.9297-1.2383-4.0664-2.1211-6.3047-2.6094v-0.47266c0-2.3008-1.8672-4.168-4.168-4.168-2.3008 0-4.168 1.8672-4.168 4.168v0.47266c-2.2383 0.48828-4.375 1.3711-6.3047 2.6094l-0.33203-0.33203c-1.6484-1.5703-4.2383-1.5703-5.8906 0-1.625 1.625-1.625 4.2617 0 5.8867l0.33594 0.33594c-1.2383 1.9297-2.125 4.0625-2.6133 6.3047h-0.47266c-2.3008 0-4.1641 1.8633-4.1641 4.168 0 2.3008 1.8633 4.1641 4.1641 4.1641h0.47266c0.48828 2.2422 1.375 4.375 2.6133 6.3047l-0.33594 0.33594c-1.625 1.625-1.625 4.2617 0 5.8867 1.6523 1.5703 4.2422 1.5703 5.8906 0l0.33203-0.33203c1.9297 1.2383 4.0664 2.1211 6.3047 2.6094v0.47266c0 2.3008 1.8672 4.168 4.168 4.168 2.3008 0 4.168-1.8672 4.168-4.168v-0.47266c2.2383-0.48828 4.375-1.3711 6.3047-2.6094l0.33203 0.33203c1.6523 1.5703 4.2422 1.5703 5.8906 0 1.625-1.625 1.625-4.2617 0-5.8867l-0.33203-0.33203v-0.003906c1.2344-1.9297 2.1211-4.0625 2.6094-6.3047h0.47266c2.3008 0 4.168-1.8633 4.168-4.1641 0-2.3047-1.8672-4.168-4.168-4.168zm-13.598 9.1406v-0.003906c-1.0195 0.71484-1.6406 1.8672-1.6797 3.1133v4.418c0 0.76562-0.62109 1.3867-1.3906 1.3867-0.76563 0-1.3867-0.62109-1.3867-1.3867v-4.418c0.03125-2.1172 1.0664-4.0898 2.7891-5.3203 1.7461-1.3125 2.7695-3.3711 2.7656-5.5508-0.003906-2.1836-1.0312-4.2383-2.7773-5.5469v5.5547c-0.003906 0.37109-0.14844 0.72266-0.40234 0.98828l-2.7773 2.7773c-0.54688 0.54297-1.4297 0.54297-1.9727 0l-2.7773-2.7773c-0.25781-0.26562-0.40234-0.61719-0.40625-0.98828v-5.5547c-1.7461 1.3086-2.7773 3.3672-2.7773 5.5508 0 2.1836 1.0312 4.2383 2.7773 5.5469 1.7188 1.2344 2.7461 3.207 2.7812 5.3203v4.418c0 0.76562-0.625 1.3867-1.3906 1.3867s-1.3906-0.62109-1.3906-1.3867v-4.418c-0.035156-1.2461-0.66016-2.3984-1.6797-3.1133-2.8594-2.1445-4.3086-5.6953-3.7617-9.2305 0.54688-3.5391 2.9961-6.4844 6.375-7.6719 0.42188-0.14844 0.89062-0.085937 1.2617 0.16797 0.36328 0.26562 0.58203 0.6875 0.58594 1.1406v7.2773l1.3906 1.3906 1.3906-1.3906-0.003906-7.2773c0.003906-0.45312 0.21875-0.875 0.58203-1.1406 0.37109-0.25391 0.83984-0.31641 1.2656-0.16797 3.375 1.1875 5.8242 4.1328 6.3711 7.6719 0.54688 3.5352-0.89844 7.0859-3.7617 9.2305z"></path> 
1456</g> 
1457</svg> 
1458</div> 
1459</div> 
1460 <div id="370271a1" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1187960083"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1187960083" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiIxIiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1461</div> 
1462</div> 
1463</div> 
1464 <div id="104204d3" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1275894240"> <div class="dmNewParagraph u_1275894240" data-element-type="paragraph" data-version="5" id="1275894240" style="" data-inject="topicheading1" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyAxIEhFQURMSU5FXCJ9In1d" data-diy-text="">AC Services</div> 
1465</div> 
1466 <div id="cf93d818" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1189000645"> <div class="dmDividerWrapper clearfix u_1189000645" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1189000645" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1890565960"/></div> 
1467</div> 
1468 <div id="408e4093" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1095300496"> <div class="dmNewParagraph u_1095300496" data-element-type="paragraph" data-version="5" id="1095300496" style="" data-inject="topiccontent1" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyAxIFRFWFRcIn0ifV0=" data-diy-text="">Our air conditioning services keep your home comfortable during the hottest months with professional installations, repairs, and seasonal maintenance. We specialize in central AC systems and offer Trane Certified service to keep your cooling system running efficiently and reliably year after year.</div> 
1469</div> 
1470</div> 
1471 <div id="1a201296" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1140680309"> <div class="widget-f19f9b u_1140680309 dmCustomWidget" data-lazy-load="true" data-title="" id="1140680309" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1471NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjEiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1472 <span class="iconBg"></span> 
1473</div> 
1474</a> 
1475</div> 
1476</div> 
1477</div> 
1478 <div id="9a24a1f5" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1604099830"> <div class="widget-ab442a dmCustomWidget" data-lazy-load="true" data-title="" id="1604099830" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="ab442ac754384e98a02c4f630b481f5e" data-widget-version="9" data-widget-config="eyJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNCBIRUFETElORSIsImNvbGxlY3Rpb25OYW1lIjoiMDEgSE9NRSJ9"> <div class="hide-section-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 4 HEADLINE" style="display:none;"></div> 
1479</div> 
1480</div> 
1481</div> 
1482 <div id="33c2d413" class="flex-element group" data-auto="flex-element-group"> <div id="19594c1b" class="flex-element group" data-auto="flex-element-group"> <div id="239f1478" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1018259860"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1018259860" data-element-type="graphic" data-widget-type="graphic" id="1018259860"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1965476530" class="svg u_1965476530" data-icon-custom="true"> <path d="m80.859 15.77h-7.9375v7.6758l7.9375 7.9375z"></path> 
1483 <path d="m81.344 36.285-11.109-11.109-18.09-18.086c-0.49219-0.5-1.1602-0.79688-1.8633-0.82422h-0.082031c-0.054688-0.003906-0.10547-0.003906-0.15625 0-0.17969 0-0.36328 0.015625-0.53906 0.046875-0.074218 0.011719-0.15234 0.019531-0.22656 0.019531-0.53906 0.10938-1.0312 0.375-1.418 0.76953l-39.973 39.953c-1.1289 1.1289-1.1289 2.957-0.003907 4.0859s2.9531 1.1289 4.082 0.003906l3.6484-3.6445 0.027344-0.027344 33.266-33.254c0.60938-0.60938 1.5977-0.60938 2.2109 0l36.91 36.922c1.1289 1.125 2.957 1.1211 4.0859-0.007813 0.53906-0.54297 0.84375-1.2773 0.83984-2.043 0-0.76562-0.30469-1.5-0.84766-2.0391z"></path> 
1484 <path d="m56.488 60.18c-0.046875-0.039063-0.089843-0.078126-0.12891-0.12109-0.16797-0.12109-0.30469-0.27734-0.39844-0.46094-0.050782-0.085937-0.09375-0.17578-0.125-0.26953-0.03125-0.10156-0.054688-0.20312-0.0625-0.3125-0.015626-0.054687-0.027344-0.11328-0.035157-0.17188v-22.387c0-0.95312-0.375-1.8672-1.0508-2.543-0.67578-0.67578-1.5898-1.0508-2.543-1.0508-1.9844 0.007813-3.5859 1.6133-3.5938 3.5938v22.387c-0.007812 0.0625-0.019531 0.12109-0.035156 0.17969-0.011719 0.10547-0.03125 0.21094-0.0625 0.3125-0.035156 0.09375-0.074219 0.18359-0.125 0.26953-0.046875 0.085937-0.097656 0.16406-0.15625 0.24219-0.070313 0.078125-0.15234 0.15234-0.23828 0.21484-0.042969 0.042969-0.085938 0.085938-0.13281 0.125-3.2383 1.9531-4.7773 5.8281-3.7617 9.4727 1.0156 3.6406 4.332 6.1641 8.1172 6.1641 3.7812 0 7.1016-2.5234 8.1133-6.1641 1.0156-3.6445-0.52344-7.5195-3.7617-9.4727zm-4.3516 13.082-0.003907-0.003907c-3.2422 0-5.8711-2.6289-5.875-5.875 0-0.86328 0.69922-1.5625 1.5625-1.5625 0.86328 0 1.5625 0.69922 1.5625 1.5625 0.003907 1.5195 1.2344 2.75 2.75 2.75 0.86328 0 1.5625 0.69922 1.5625 1.5625 0 0.86328-0.69922 1.5625-1.5625 1.5625z"></path> 
1485 <path d="m50.008 17.539-31.719 31.699v33.281l63.43 0.003907v-33.273zm-12.133 18.031h4.6328c0.86328 0 1.5625 0.69922 1.5625 1.5625 0 0.86328-0.69922 1.5625-1.5625 1.5625h-4.6328c-0.86328 0-1.5625-0.69922-1.5625-1.5625 0-0.86328 0.69922-1.5625 1.5625-1.5625zm0 5.5547h4.6328c0.86328 0 1.5625 0.69922 1.5625 1.5625s-0.69922 1.5625-1.5625 1.5625h-4.6328c-0.86328 0-1.5625-0.69922-1.5625-1.5625s0.69922-1.5625 1.5625-1.5625zm0 5.5547h4.6328c0.86328 0 1.5625 0.69922 1.5625 1.5625 0 0.86328-0.69922 1.5625-1.5625 1.5625h-4.6328c-0.86328 0-1.5625-0.69922-1.5625-1.5625 0-0.86328 0.69922-1.5625 1.5625-1.5625zm-1.5625 7.1172v-0.003906c0-0.86328 0.69922-1.5625 1.5625-1.5625h4.6328c0.86328 0 1.5625 0.69922 1.5625 1.5625s-0.69922 1.5625-1.5625 1.5625h-4.6328c-0.86328 0-1.5625-0.69922-1.5625-1.5625zm15.824 25.133c-5.0117 0.007812-9.457-3.2266-10.988-7.9961-1.5352-4.7734 0.19141-9.9883 4.2695-12.902v-21.574c0-3.7148 3.0117-6.7266 6.7266-6.7266 3.7148 0 6.7266 3.0117 6.7266 6.7266v21.574c4.0781 2.9141 5.8047 8.1328 4.2695 12.902-1.5352 4.7734-5.9766 8.0039-10.988 7.9961z"></path> 
1486 <path d="m83.285 85.652-0.015625-0.003906h-66.523l-0.015625 0.003906-0.015625-0.003906h-5.4062v8.1016h77.383v-8.1016h-5.3906z"></path> 
1487</svg> 
1488</div> 
1489</div> 
1490 <div id="1286110f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1900759204"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1900759204" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI0IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1491</div> 
1492</div> 
1493</div> 
1494 <div id="56651e47" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1146735742"> <div class="dmNewParagraph u_1146735742" data-element-type="paragraph" data-version="5" id="1146735742" style="" data-inject="topicheading4" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA0IEhFQURMSU5FXCJ9In1d" data-diy-text="">Heating Services</div> 
1495</div> 
1496 <div id="a6b24236" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1662402204"> <div class="dmDividerWrapper clearfix u_1662402204" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1662402204" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1938878603"/></div> 
1497</div> 
1498 <div id="71953155" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1115355416"> <div class="dmNewParagraph u_1115355416" data-element-type="paragraph" data-version="5" id="1115355416" style="" data-inject="topiccontent4" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA0IFRFWFRcIn0ifV0=" data-diy-text="">From furnace installations to boiler repairs, we deliver heating solutions that keep your property warm and safe all winter long. As an authorized Rinnai dealer, we offer quality equipment backed by manufacturer warranties and our guarantee of workmanship.</div> 
1499</div> 
1500</div> 
1501 <div id="89892db9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1291432522"> <div class="widget-f19f9b u_1291432522 dmCustomWidget" data-lazy-load="true" data-title="" id="1291432522" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1501NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjQiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1502 <span class="iconBg"></span> 
1503</div> 
1504</a> 
1505</div> 
1506</div> 
1507</div> 
1508 <div id="cc212e16" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1461092985"> <div class="widget-901b1c dmCustomWidget" data-lazy-load="true" data-title="" id="1461092985" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="901b1c7285754fceac707cdcb08aa571" data-widget-version="7" data-widget-config="eyJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUiLCJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNiBIRUFETElORSJ9"> <div class="hide-column-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 6 HEADLINE" style="display:none;"></div> 
1509</div> 
1510</div> 
1511</div> 
1512</div> 
1513</div> 
1514</section> 
1515</div> 
1516 <div data-flex-id="ff34c4f0" data-auto="flex-section" id="1748632556" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104"> <div class="flex-widgets-container" id="1135169205"></div> 
1517 <section id="e811a1df" class="flex-element section" data-auto="flex-element-section"> <div id="40f2f732" class="flex-element grid lazy" data-auto="flex-element-grid" data-layout-grid="" data-background-image="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/reviews-content-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg"> <div id="d564400f" class="flex-element group" data-auto="flex-element-group"> <div id="2895ecee" class="flex-element group" data-auto="flex-element-group"> <div id="1315e35a" class="flex-element group" data-auto="flex-element-group"> <div id="3977f88c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1532165639"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1532165639" data-element-type="graphic" data-widget-type="graphic" id="1532165639"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1654504457" class="svg u_1654504457" data-icon-custom="true"> <path d="m50.246 89.672c1.5859 0.13281 3.1797 0.25 4.7852 0.33594v9.7969h-4.7852zm24.945 10.133h4.7773v-10.129c-1.5859 0.13281-3.1758 0.24609-4.7773 0.33594zm-9.5781-57.293c0.16016 0 0.32031-0.003907 0.48047-0.011719 3.418-0.16016 6.1328-1.9766 7.4492-4.9805 0.92188-2.0977 1.3867-4.0547 1.3867-5.9062 0-2.2656-0.69531-4.375-2.0977-6.3867-0.73828 1.8945-1.8789 3.3281-3.418 4.2734-0.46484 0.28516-1.0352 0.32422-1.5312 0.10938-0.49609-0.21875-0.85547-0.66797-0.96094-1.1992-0.96094-4.9219-2.5039-8.4883-4.8555-11.082-0.17969 3.6914-2.0898 6.3867-3.8281 8.8438-2.5195 3.5547-4.3359 6.1211-1.5078 11.215 1.7422 3.1523 5.1914 5.125 8.8828 5.125zm-28.391 50.121c-0.27344 0.25391-0.62891 0.40234-1 0.42969-0.57422 0.042969-1.1836 0.089844-1.8164 0.14062-1.0117 0.082031-2.0742 0.16797-3.1719 0.23438v5.0469c0 0.72656 0.59375 1.3203 1.3203 1.3203h5.5508c0.71875 0 1.3281-0.60547 1.3281-1.3203v-8.0781c-0.68359 0.76172-1.418 1.5039-2.2109 2.2266zm7.3086-10.812c-0.42969 1.2773-0.97266 2.5195-1.6289 3.7227 7.1992 0.95703 14.664 1.4375 22.207 1.4375 7.332 0 14.594-0.45703 21.613-1.3594v-3.9375c-7.0195 0.9375-14.254 1.4062-21.613 1.4062-6.9844 0.003906-13.891-0.42578-20.578-1.2695zm-3.4414-10.727h-11.512c-0.91406 0-1.6562-0.74219-1.6562-1.6562v-4.7109c-2.8242 0.72656-5.5352 2.2812-8.4062 4.793-0.30078 0.26562-0.69141 0.41016-1.0898 0.41016l-12.102-0.003907v15.445h8.7656c0.57031 0 1.1016 0.29297 1.4023 0.77734 3.0898 4.9258 11.504 4.25 17.648 3.7539 0.43359-0.03125 0.85547-0.070313 1.2617-0.097656 5.207-4.9375 7.5273-10.852 6.9023-17.594-0.054688-0.63672-0.57813-1.1172-1.2148-1.1172zm10.828-22.645c0.52734-1.6719 0.79688-3.4258 0.79688-5.2109 0-6.2852-3.3633-12-8.7227-15.074l-0.003907-18.605c0-0.26172 0.21875-0.48438 0.47656-0.48438h41.285c0.26172 0 0.48438 0.22266 0.48438 0.48438v38.414c0 0.25781-0.22266 0.47266-0.48438 0.47266zm1.9102-9.457c2.332 4.1992 6.9062 6.832 11.789 6.832 0.21094 0 0.42187-0.007813 0.63281-0.015625 2.3242-0.10938 4.4531-0.8125 6.2266-2.0078 1.7734-1.1953 3.1953-2.8789 4.1016-4.9492 3.0312-6.9
1517062 1.8828-13.07-3.4102-18.324-0.44531-0.44141-1.1055-0.59375-1.6992-0.39062s-1.0234 0.72656-1.1055 1.3516c-0.17188 1.2891-0.47266 2.3555-0.91016 3.2109-1.6484-5.957-4.4414-10.043-8.6562-12.73-0.58203-0.37109-1.332-0.34375-1.8828 0.070312-0.55469 0.41406-0.78906 1.125-0.59766 1.7891 1.3438 4.6055-0.56641 7.3047-2.7812 10.43-2.5977 3.6602-5.543 7.8164-1.707 14.734zm-23.645 17.344c0.63281 0.25 1.0508 0.85937 1.0508 1.5391v9.9062h8.1992v-9.9062c0-0.67969 0.41797-1.293 1.0469-1.5391 5.418-2.1367 8.918-7.2773 8.918-13.102 0-4.7617-2.3867-9.1211-6.25-11.703v8.2422c0 2.8203-2.293 5.1133-5.1133 5.1133h-5.4102c-2.8164 0-5.1094-2.293-5.1094-5.1133v-8.2422c-3.8672 2.5781-6.2578 6.9375-6.2578 11.703 0.003907 5.8203 3.5039 10.965 8.9258 13.102zm63.492-53.586v74.477c-9.1328 1.6953-18.738 2.5547-28.562 2.5547-6.707 0-13.336-0.40234-19.758-1.1953 0.37891-2.1367 0.46484-4.3672 0.25-6.6797-0.16016-1.7695-1.3008-3.2109-2.8594-3.8125v-9.1328c2.7578-1.3008 5.1211-3.3008 6.875-5.832 0.30859-0.44531 0.59766-0.90234 0.85938-1.3711h35.27c2.0898 0 3.793-1.6992 3.793-3.7852l-0.003907-38.414c0-2.0898-1.6992-3.793-3.793-3.793h-41.281c-2.0859 0-3.7852 1.7031-3.7852 3.793v17.766c-0.03125 0.019531-0.066407 0.039062-0.097657 0.058593-0.46484 0.30469-0.74219 0.82422-0.74219 1.3828v11.012c0 0.99219-0.80859 1.8047-1.8008 1.8047h-1.4922v-38.832c9.1367-1.6953 18.734-2.5547 28.562-2.5547s19.43 0.85938 28.566 2.5547zm-17.648 62.793c0-6.0234-4.8984-10.926-10.918-10.926-6.0234 0-10.926 4.9023-10.926 10.926 0 6.0195 4.9023 10.918 10.926 10.918s10.918-4.8984 10.918-10.918zm-3.3086 0c0 4.1953-3.4141 7.6055-7.6055 7.6055-4.1992 0-7.6133-3.4141-7.6133-7.6055 0-4.1992 3.4141-7.6133 7.6133-7.6133 4.1914 0 7.6055 3.4141 7.6055 7.6133zm-3.8828-4.0547c-0.67188-0.61719-1.7227-0.57422-2.3359 0.097657l-2.6055 2.8359c-0.61719 0.67188-0.57422 1.7188 0.097657 2.3359 0.31641 0.29297 0.71875 0.4375 1.1172 0.4375 0.44531 0 0.89062-0.17969 1.2188-0.53516l2.6055-2.8359c0.62109-0.67188 0.57422-1.7188-0.097656-2.3359z"></path> 
1518</svg> 
1519</div> 
1520</div> 
1521 <div id="b5281589" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1716109945"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1716109945" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI0IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1522</div> 
1523</div> 
1524</div> 
1525 <div id="047f3898" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1247724007"> <div class="dmNewParagraph u_1247724007" data-element-type="paragraph" data-version="5" id="1247724007" style="" data-inject="topicheading4" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA4IEhFQURMSU5FXCJ9In1d" data-diy-text=""><p class="rteBlock">Heater and Furnace Repair</p></div> 
1526</div> 
1527 <div id="4f5439f7" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1752609488"> <div class="dmDividerWrapper clearfix u_1752609488" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1752609488" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1563751989"/></div> 
1528</div> 
1529 <div id="4cc9793f" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1178177094"> <div class="dmNewParagraph u_1178177094" data-element-type="paragraph" data-version="5" id="1178177094" style="" data-inject="topiccontent4" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA4IFRFWFRcIn0ifV0=" data-diy-text=""><p class="rteBlock">When your heater or furnace stops working properly, you need reliable repairs fast. RupCoe Plumbing, Heating &amp; Air Conditioning Inc delivers prompt, professional heating system repairs to keep your home comfortable all winter long.</p></div> 
1530</div> 
1531</div> 
1532 <div id="f68f02ae" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1100778156"> <div class="widget-ab442a dmCustomWidget" data-lazy-load="true" data-title="" id="1100778156" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="ab442ac754384e98a02c4f630b481f5e" data-widget-version="9" data-widget-config="eyJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNCBIRUFETElORSIsImNvbGxlY3Rpb25OYW1lIjoiMDEgSE9NRSJ9"> <div class="hide-section-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 4 HEADLINE" style="display:none;"></div> 
1533</div> 
1534</div> 
1535 <div id="fe3edf98" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1241170093"> <div class="widget-f19f9b u_1241170093 dmCustomWidget" data-lazy-load="true" data-title="" id="1241170093" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1535NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjgiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiIifQ=="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1536 <span class="iconBg"></span> 
1537</div> 
1538</a> 
1539</div> 
1540</div> 
1541</div> 
1542</div> 
1543 <div id="b609ca77" class="flex-element group" data-auto="flex-element-group"> <div id="9d134f9e" class="flex-element group" data-auto="flex-element-group"> <div id="6b704a40" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1541958589"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1541958589" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI1IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1544</div> 
1545</div> 
1546</div> 
1547 <div id="widget_sl3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1353949391"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1353949391" data-element-type="graphic" data-widget-type="graphic" id="1353949391"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1701237851" class="svg u_1701237851" data-icon-custom="true"> <path d="m47.781 62.984c0.23438 0.46875 0.73438 1.0781 2.2188 1.0781s1.9844-0.60938 2.2188-1.0781c1.0156-1.9219 0.0625-5.9844-2.2188-10.031-2.2812 4.0469-3.2344 8.1094-2.2188 10.031z"></path> 
1548 <path d="m68.125 12.578c-12.031-6.3438-24.219-6.3438-36.25 0-2.3125 1.2188-3.75 3.5938-3.75 6.2031v57.016c0 4.7344 3.8438 8.5781 8.5781 8.5781h3.9219v3.9062c0 2.1562 1.75 3.9062 3.9062 3.9062s3.9062-1.75 3.9062-3.9062v-3.9062h3.125v3.9062c0 2.1562 1.75 3.9062 3.9062 3.9062s3.9062-1.75 3.9062-3.9062v-3.9062h3.9219c4.7344 0 8.5781-3.8438 8.5781-8.5781v-57.016c0-2.6094-1.4375-4.9844-3.75-6.2031zm-18.125 54.609c-2.2812 0-4.0625-0.96875-4.9844-2.75-2.0469-3.8594 0.375-10.484 3.6875-15.312 0.59375-0.85938 2-0.85938 2.5938 0 3.3125 4.8281 5.7344 11.453 3.6875 15.312-0.92188 1.7812-2.7031 2.75-4.9844 2.75zm12.5-32.812h-25c-0.85938 0-1.5625-0.70312-1.5625-1.5625s0.70312-1.5625 1.5625-1.5625h3.2656c-0.09375-0.51562-0.14062-1.0312-0.14062-1.5625 0-5.1719 4.2031-9.375 9.375-9.375s9.375 4.2031 9.375 9.375c0 0.53125-0.046875 1.0469-0.14062 1.5625h3.2656c0.85938 0 1.5625 0.70312 1.5625 1.5625s-0.70312 1.5625-1.5625 1.5625z"></path> 
1549 <path d="m50 23.438c-3.4531 0-6.25 2.7969-6.25 6.25 0 0.53125 0.0625 1.0469 0.20312 1.5625h5.2188l2.6562-3.9844c0.46875-0.71875 1.4375-0.92188 2.1562-0.4375 0.71875 0.46875 0.92188 1.4375 0.4375 2.1562l-1.5156 2.2656h3.1406c0.14062-0.51562 0.20312-1.0312 0.20312-1.5625 0-3.4531-2.7969-6.25-6.25-6.25z"></path> 
1550</svg> 
1551</div> 
1552</div> 
1553 <div id="113823fc" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1004200801"> <div class="dmNewParagraph u_1004200801" data-element-type="paragraph" data-version="5" id="1004200801" data-inject="topicheading5" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA5IEhFQURMSU5FXCJ9In1d" data-diy-text=""><p class="rteBlock">Water Heaters</p></div> 
1554</div> 
1555 <div id="c3bcc402" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1973562023"> <div class="dmDividerWrapper clearfix u_1973562023" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1973562023" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1835839478"/></div> 
1556</div> 
1557 <div id="d2089111" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1781832502"> <div class="dmNewParagraph u_1781832502" data-element-type="paragraph" data-version="5" id="1781832502" style="" data-inject="topiccontent5" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA5IFRFWFRcIn0ifV0=" data-diy-text=""><p class="rteBlock">Your water heater works hard every day, and when problems arise, you need fast solutions. Serving the areas of Middlesex, Somerset, Hunterdon, Essex, Union, Morris, Mercer &amp; Monmouth counties, we understand the inconvenience of water heater issues.</p></div> 
1558</div> 
1559</div> 
1560 <div id="7425dc8b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1900720060"> <div class="widget-901b1c dmCustomWidget" data-lazy-load="true" data-title="" id="1900720060" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="901b1c7285754fceac707cdcb08aa571" data-widget-version="7" data-widget-config="eyJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUiLCJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNSBIRUFETElORSJ9"> <div class="hide-column-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 5 HEADLINE" style="display:none;"></div> 
1561</div> 
1562</div> 
1563 <div id="2a21d5e3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1575031382"> <div class="widget-f19f9b u_1575031382 dmCustomWidget" data-lazy-load="true" data-title="" id="1575031382" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1563NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjkiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1564 <span class="iconBg"></span> 
1565</div> 
1566</a> 
1567</div> 
1568</div> 
1569</div> 
1570</div> 
1571 <div id="b0699b6b" class="flex-element group" data-auto="flex-element-group"> <div id="0d8eafe2" class="flex-element group" data-auto="flex-element-group"> <div id="05355adc" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1404936186"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1404936186" data-element-type="graphic" data-widget-type="graphic" id="1404936186"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1010976752" class="svg u_1010976752" data-icon-custom="true"> <g> <path d="m46.891 38.859c0 1.7227 1.3945 3.125 3.1055 3.125 1.7227 0 3.125-1.4023 3.125-3.125 0-1.7109-1.4023-3.1055-3.125-3.1055-1.7109 0.003906-3.1055 1.3945-3.1055 3.1055z"></path> 
1572 <path d="m36.758 70.539h5.207v-9.5664c0-0.80078 0.64844-1.4492 1.4492-1.4492h13.172c0.80078 0 1.4492 0.64844 1.4492 1.4492v9.5664h5.207l0.003906-21.414h-26.488z"></path> 
1573 <path d="m44.867 62.422h10.273v8.1172h-10.273z"></path> 
1574 <path d="m95.246 42.957-9.1602-1.9844c-0.88281-3.5664-2.2773-6.957-4.1758-10.102l5.0625-7.8555c0.375-0.57812 0.28906-1.332-0.1875-1.8125l-7.9727-7.9727c-0.49219-0.49219-1.2461-0.56641-1.8125-0.20312l-7.8711 5.0742c-3.1289-1.8984-6.5234-3.3047-10.086-4.1758l-1.9844-9.1602c-0.14453-0.66406-0.73828-1.1406-1.4219-1.1406h-11.277c-0.67969 0-1.2617 0.47656-1.4062 1.1445l-1.9844 9.1602c-3.5664 0.87109-6.957 2.2734-10.102 4.1758l-7.8555-5.0742c-0.57812-0.36328-1.332-0.28906-1.8125 0.20312l-7.9727 7.9727c-0.49219 0.47656-0.56641 1.2305-0.20312 1.8125l5.0742 7.8555c-1.8984 3.1445-3.3047 6.5234-4.1875 10.102l-9.1445 1.9844c-0.66797 0.14453-1.1445 0.72656-1.1445 1.4062v11.277c0 0.67969 0.47656 1.2734 1.1445 1.4219l9.1445 1.9844c0.88281 3.5508 2.2891 6.9414 4.1875 10.086l-5.0742 7.8711c-0.36328 0.56641-0.28906 1.3203 0.20312 1.8125l7.9727 7.9727c0.47656 0.49219 1.2305 0.56641 1.8125 0.20312l7.8555-5.0742c3.1445 1.8984 6.5352 3.2891 10.102 4.1758l1.9844 9.1602c0.14453 0.66797 0.72656 1.1289 1.4062 1.1289h11.277c0.67969 0 1.2734-0.46484 1.4219-1.1289l1.9844-9.1602c3.5664-0.88281 6.957-2.2734 10.086-4.1758l7.8711 5.0742c0.56641 0.36328 1.3203 0.28906 1.8125-0.20312l7.9727-7.9727c0.47656-0.49219 0.56641-1.2461 0.1875-1.8125l-5.0586-7.8711c1.8984-3.1445 3.3047-6.5352 4.1758-10.086l9.1602-1.9844c0.66797-0.14453 1.1289-0.73828 1.1289-1.4219v-11.277c-0.003906-0.6875-0.46484-1.2656-1.1328-1.4102zm-22.148 5.2383c-0.21484 0.55859-0.75391 0.92969-1.3516 0.92969h-5.5977v22.863c0 0.80078-0.64844 1.4492-1.4492 1.4492h-29.387c-0.80078 0-1.4492-0.64844-1.4492-1.4492l-0.003906-22.863h-5.5938c-0.60156 0-1.1367-0.37109-1.3516-0.92969-0.21484-0.55859-0.0625-1.1953 0.37891-1.5977l21.738-19.648c0.55078-0.5 1.3945-0.5 1.9453 0l21.738 19.648c0.44922 0.40234 0.59766 1.0391 0.38281 1.5977z"></path> 
1575 <path d="m32.031 46.227c0.14844 0 38.359-0.003906 35.949 0l-17.973-16.246zm23.988-7.3672c0 3.3203-2.7031 6.0234-6.0234 6.0234-3.3086 0-6.0039-2.7031-6.0039-6.0234 0-3.3086 2.6914-6.0039 6.0039-6.0039 3.3203 0.003906 6.0234 2.6953 6.0234 6.0039z"></path> 
1576</g> 
1577</svg> 
1578</div> 
1579</div> 
1580 <div id="50871307" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1911977242"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1911977242" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI2IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1581</div> 
1582</div> 
1583</div> 
1584 <div id="e73d1888" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1211893982"> <div class="dmNewParagraph u_1211893982" data-element-type="paragraph" data-version="5" id="1211893982" style="" data-inject="topicheading6" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA2IEhFQURMSU5FXCJ9In1d" data-diy-text="">Remodeling</div> 
1585</div> 
1586 <div id="3f3e02c3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1450241237"> <div class="dmDividerWrapper clearfix u_1450241237" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1450241237" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1913834287"/></div> 
1587</div> 
1588 <div id="07b8fc68" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1581486438"> <div class="dmNewParagraph u_1581486438" data-element-type="paragraph" data-version="5" id="1581486438" style="" data-inject="topiccontent6" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA2IFRFWFRcIn0ifV0=" data-diy-text="">Update your bathroom with our remodeling services that combine functionality with design to increase your home's value and your daily comfort. Our experienced team manages every aspect of your project with attention to detail and respect for your property.</div> 
1589</div> 
1590</div> 
1591 <div id="d46e494e" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1925286086"> <div class="widget-901b1c dmCustomWidget" data-lazy-load="true" data-title="" id="1925286086" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="901b1c7285754fceac707cdcb08aa571" data-widget-version="7" data-widget-config="eyJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUiLCJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNiBIRUFETElORSJ9"> <div class="hide-column-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 6 HEADLINE" style="display:none;"></div> 
1592</div> 
1593</div> 
1594 <div id="250a425b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1115142810"> <div class="widget-f19f9b u_1115142810 dmCustomWidget" data-lazy-load="true" data-title="" id="1115142810" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1594NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjYiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1595 <span class="iconBg"></span> 
1596</div> 
1597</a> 
1598</div> 
1599</div> 
1600</div> 
1601</div> 
1602</div> 
1603</div> 
1604</section> 
1605</div> 
1606 <div data-flex-id="3de900f4" data-auto="flex-section" id="1720945405" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104" class="" data-hidden-on-tablet="true"> <div class="flex-widgets-container" id="1412923353"></div> 
1607 <section id="e9d13250" class="flex-element section" data-auto="flex-element-section"> <div id="5673032b" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="ab1543de" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="c26c436b" class="flex-element group" data-auto="flex-element-group"> <div id="5f902f9a" class="flex-element group" data-auto="flex-element-group"> <div id="5da6d9e3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-external-id="1808828608"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1808828608" data-element-type="graphic" data-widget-type="graphic" id="1808828608"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1799185539" class="svg u_1799185539" data-icon-custom="true"> <path d="m91.668 40.367h-83.336v43.785h83.336zm-20.453-2.5039h5.4844l0.003906-15.398c0-0.69141 0.55859-1.2539 1.25-1.2539h13.715v-5.4844h-18.125c-0.64062 0-1.2188 0.26172-1.6406 0.68359s-0.68359 1.0039-0.68359 1.6406zm-26.703 0v-16.91c0-0.69141 0.55859-1.25 1.2539-1.25h14.023c0.64063 0 1.2188-0.26172 1.6406-0.68359 0.42187-0.42188 0.68359-1.0039 0.68359-1.6406v-9.0469h-5.4844v4.6328c0 0.69141-0.55859 1.2539-1.2539 1.2539l-14.023-0.003906c-0.64062 0-1.2188 0.26172-1.6406 0.68359-0.42188 0.42187-0.68359 1.0039-0.68359 1.6406v21.324zm39.09 10.168v17.457h-34.516v-17.457zm-35.77-2.5039h37.02c0.69141 0 1.2539 0.55859 1.2539 1.25v19.961c0 0.69141-0.5625 1.2539-1.2539 1.2539h-37.02c-0.69141 0-1.2539-0.55859-1.2539-1.2539l0.003906-19.957c0-0.69141 0.55859-1.2539 1.25-1.2539zm-13.922 5.1172c1.6133 1.6133 2.6094 3.8438 2.6094 6.3047 0 2.4609-0.99609 4.6914-2.6133 6.3047-1.6133 1.6133-3.8438 2.6094-6.3047 2.6094-2.4609 0-4.6914-0.99609-6.3047-2.6094-1.6133-1.6133-2.6094-3.8438-2.6094-6.3047 0-2.4609 0.99609-4.6914 2.6094-6.3047s3.8438-2.6133 6.3047-2.6133c2.4688 0 4.6953 1 6.3086 2.6133zm-6.3047-5.1172c3.1523 0 6.0078 1.2773 8.0742 3.3438 2.0664 2.0664 3.3438 4.9219 3.3438 8.0742 0 3.1523-1.2773 6.0078-3.3438 8.0742-2.0664 2.0664-4.9219 3.3438-8.0742 3.3438-3.1523 0-6.0078-1.2773-8.0742-3.3438-2.0664-2.0664-3.3438-4.9219-3.3438-8.0742 0-3.1523 1.2773-6.0078 3.3438-8.0742s4.9219-3.3438 8.0742-3.3438zm-10.262 28.727c-0.69141 0-1.25-0.55859-1.25-1.2539 0-0.69141 0.55859-1.2539 1.25-1.2539h20.527c0.69141 0 1.25 0.5625 1.25 1.2539s-0.55859 1.2539-1.25 1.2539zm0 6.0469c-0.69141 0-1.25-0.55859-1.25-1.2539 0-0.69141 0.55859-1.25 1.25-1.25h20.527c0.69141 0 1.25 0.55859 1.25 1.25s-0.55859 1.2539-1.25 1.2539zm35.398-27.98c0-0.69141 0.55859-1.2539 1.2539-1.2539 0.69141 0 1.25 0.55859 1.25 1.2539v8.8789c0 0.69141-0.55859 1.25-1.25 1.25s-1.2539-0.55859-1.2539-1.25zm6.1758 0c0-0.69141 0.55859-1.2539 1.25-1.2539 0.69141 0 1.25 0.55859 1.25 1.2539v8.8789c0 0.69141-0.55859 1.25-1.25 1.25-0.69141 0-1.25-0.55859-1.25-1.25zm6.1719 0c0-0.69141 0.55859-1.2539 1.2539-1.2539 0.69141 0 1.25 0.55859 1.25 1.2539v8.8789c0 0.69141-0.55859 1.25-1.25 1.25s-1.2539-0.55859-1.2539-1.25zm6.1758 0c0-0.69141 0.5625-1.2539 1.2539-1.2539 0.69141 0 1.2539 0.55859 1.2539 1.2539v8.8789c0 0.69141-0.5625 1.25-1.2539 1.25-0.69141 0-1.2539-0.55859-1.2539-1.25zm6.1719 0c0-0.69141 0.55859-1.2539 1.25-1.2539s1.2539 0.55859 1.2539 1.2539v8.8789c0 0.69141-0.55859 1.25-1.2539 1.25-0.69141 0-1.25-0.55859-1.25-1.25zm-54.141-14.457h5.4844v-11.191c0-0.64062-0.26172-1.2188-0.68359-1.6406s-1.0039-0.68359-1.6406-0.68359h-18.125v5.4844h13.715c0.69141 0 1.2539 0.55859 1.2539 1.2539zm-1.4023 48.793h-6.2305v5.0117h6.2305zm62.441 0h-6.2305v5.0117h6.2305zm-55.629-24.883c0.32422 0.60547 0.09375 1.3633-0.51172 1.6875-0.60547 0.32422-1.3633 0.09375-1.6875-0.51172l-2.8984-5.4141c-0.32422-0.60938-0.09375-1.3633 0.51172-1.6875 0.1875-0.10156 0.38672-0.14844 0.58594-0.14844v-0.003907h3.7109l-1.9141-3.5742c-0.32422-0.60938-0.09375-1.3633 0.51172-1.6875 0.60547-0.32422 1.3633-0.09375 1.6875 0.51172l2.8438 5.3164c0.12891 0.19531 0.20312 0.43359 0.20312 0.6875 0 0.69141-0.55859 1.2539-1.25 1.2539h-3.7109z" fill-rule="evenodd"></path> 
1608</svg> 
1609</div> 
1610</div> 
1611 <div id="c4a28c78" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile="" data-external-id="1287579879"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1287579879" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI3IiwiaW1hZ2VIZWlnaHQiOiIyNTAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1612</div> 
1613</div> 
1614</div> 
1615 <div id="2b2e8acd" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1138736641"> <div class="dmNewParagraph u_1138736641" data-element-type="paragraph" data-version="5" id="1138736641" style="" data-inject="topicheading7" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA3IEhFQURMSU5FXCJ9In1d" data-diy-text="">Whole-House Generators</div> 
1616</div> 
1617 <div id="b365bf46" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1483710429"> <div class="dmNewParagraph u_1483710429" data-element-type="paragraph" data-version="5" id="1483710429" style="" data-inject="topiccontent7" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA3IFRFWFRcIn0ifV0=" data-diy-text="">Protect your home from power outages with professionally installed whole-house generators that provide automatic backup power when you need it most. We'll help you select the right system for your needs and handle proper installation for peace of mind during storms and emergencie
1617s.</div> 
1618</div> 
1619</div> 
1620 <div id="f4eb0ba0" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1222678196"> <div class="widget-ab442a dmCustomWidget" data-lazy-load="true" data-title="" id="1222678196" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="ab442ac754384e98a02c4f630b481f5e" data-widget-version="9" data-widget-config="eyJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgNyBIRUFETElORSIsImNvbGxlY3Rpb25OYW1lIjoiMDEgSE9NRSJ9"> <div class="hide-section-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 7 HEADLINE" style="display:none;"></div> 
1621</div> 
1622</div> 
1623 <div id="0b01334b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1818168553"> <div class="widget-f19f9b u_1818168553 dmCustomWidget" data-lazy-load="true" data-title="" id="1818168553" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsImNvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjciLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1624 <span class="iconBg"></span> 
1625</div> 
1626</a> 
1627</div> 
1628</div> 
1629</div> 
1630</div> 
1631 <div id="366f5f33" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop=""> <div id="3f715605" class="flex-element group" data-auto="flex-element-group"> <div id="8c177a72" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1453286229"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1453286229" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI4IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1632</div> 
1633</div> 
1634</div> 
1635 <div id="13d9a0b3" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1050033785"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1050033785" style="" data-inject="topicheading8" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA4IEhFQURMSU5FXCJ9In1d" data-diy-text=""><p class="rteBlock">Heater and Furnace Repair</p></div> 
1636</div> 
1637 <div id="7eae04c5" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1812680712"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1812680712" style="" data-inject="topiccontent8" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA4IFRFWFRcIn0ifV0=" data-diy-text=""><p class="rteBlock">When your heater or furnace stops working properly, you need reliable repairs fast. RupCoe Plumbing, Heating &amp; Air Conditioning Inc delivers prompt, professional heating system repairs to keep your home comfortable all winter long.</p></div> 
1638</div> 
1639</div> 
1640 <div id="6a5377a0" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1430136103"> <div class="widget-901b1c dmCustomWidget" data-lazy-load="true" data-title="" id="1430136103" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="901b1c7285754fceac707cdcb08aa571" data-widget-version="7" data-widget-config="eyJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUiLCJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgOCBIRUFETElORSJ9"> <div class="hide-column-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 8 HEADLINE" style="display:none;"></div> 
1641</div> 
1642</div> 
1643 <div id="d247602f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1910218096"> <div class="widget-f19f9b u_1910218096 dmCustomWidget" data-lazy-load="true" data-title="" id="1910218096" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsIm
1643NvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjgiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1644 <span class="iconBg"></span> 
1645</div> 
1646</a> 
1647</div> 
1648</div> 
1649</div> 
1650</div> 
1651 <div id="e40b3bc7" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop=""> <div id="f6c1ee4f" class="flex-element group" data-auto="flex-element-group"> <div id="56296b4f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1950599147"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1950599147" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiI5IiwiaW1hZ2VIZWlnaHQiOiIyMDAiLCJiYWNrZ3JvdW5kU2l6ZSI6ImNvdmVyIiwiYmFja2dyb3VuZFBvc2l0aW9uIjoiY2VudGVyIGNlbnRlciIsImJvcmRlclJhZGl1cyI6IjAiLCJvdmVybGF5Q29sb3IiOiIjMDAwMDAwIiwib3ZlcmxheU9wYWNpdHkiOiIwIiwiZW5hYmxlSG92ZXJMaWZ0IjpmYWxzZX0="> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1652</div> 
1653</div> 
1654</div> 
1655 <div id="15b3c36c" class="flex-element widget-wrapper DCBH3" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1171662472"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1171662472" style="" data-inject="topicheading9" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA5IEhFQURMSU5FXCJ9In1d" data-diy-text=""><p class="rteBlock">Water Heaters</p></div> 
1656</div> 
1657 <div id="ef370221" class="flex-element widget-wrapper DCBP" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1499263341"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1499263341" style="" data-inject="topiccontent9" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCIwMSBIT01FXCIsXCJmblwiOlwiQ09QWSBCTE9DSyA5IFRFWFRcIn0ifV0=" data-diy-text=""><p class="rteBlock">Your water heater works hard every day, and when problems arise, you need fast solutions. Serving the areas of Middlesex, Somerset, Hunterdon, Essex, Union, Morris, Mercer &amp; Monmouth counties, we understand the inconvenience of water heater issues.</p></div> 
1658</div> 
1659</div> 
1660 <div id="3111f6f4" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1588936545"> <div class="widget-901b1c dmCustomWidget" data-lazy-load="true" data-title="" id="1588936545" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="901b1c7285754fceac707cdcb08aa571" data-widget-version="7" data-widget-config="eyJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUiLCJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgOSBIRUFETElORSJ9"> <div class="hide-column-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 9 HEADLINE" style="display:none;"></div> 
1661</div> 
1662</div> 
1663 <div id="8d867a87" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1731838880"> <div class="widget-f19f9b u_1731838880 dmCustomWidget" data-lazy-load="true" data-title="" id="1731838880" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsImNvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjkiLCJidXR0b25Sb3V0aW5nT3B0aW9ucyI6bnVsbCwiY2F0ZWdvcnlQcmVmaXgiOiJ3aGF0LXdlLWRvIn0="> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1664 <span class="iconBg"></span> 
1665</div> 
1666</a> 
1667</div> 
1668</div> 
1669</div> 
1670</div> 
1671</div> 
1672</div> 
1673</section> 
1674</div> 
1675 <div data-flex-id="fae00d4f" data-auto="flex-section" id="1266250843" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104"> <div class="flex-widgets-container" id="1559818558"></div> 
1676 <section id="04369674" class="flex-element section" data-auto="flex-element-section"> <div id="3c1e0904" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="01b4040e" class="flex-element group" data-auto="flex-element-group"> <div id="94324aeb" class="flex-element group" data-auto="flex-element-group"> <div id="329b07b6" class="flex-element group" data-auto="flex-element-group"> <div id="245230ec" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1488836918"> <div class="widget-31ea1e dmCustomWidget" data-lazy-load="true" data-title="" id="1488836918" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="31ea1ee9b19c4368abcc1dbd5ec6a0d6" data-widget-version="2" data-widget-config="eyJzb3VyY2VQYWdlVHlwZSI6ImhvbWUiLCJjb3B5QmxvY2tOdW1iZXIiOiIxMCIsImltYWdlSGVpZ2h0IjoiMjAwIiwiYmFja2dyb3VuZFNpemUiOiJjb3ZlciIsImJhY2tncm91bmRQb3NpdGlvbiI6ImNlbnRlciBjZW50ZXIiLCJib3JkZXJSYWR
1676pdXMiOiIwIiwib3ZlcmxheUNvbG9yIjoiIzAwMDAwMCIsIm92ZXJsYXlPcGFjaXR5IjoiMCIsImVuYWJsZUhvdmVyTGlmdCI6ZmFsc2V9"> <div class="cross-collection-image" role="img" aria-busy="true" aria-hidden="true"> <div class="cross-collection-image-overlay" aria-hidden="true"></div> 
1677</div> 
1678</div> 
1679</div> 
1680</div> 
1681 <div id="6abed359" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1880319912"> <div class="widget-ab442a dmCustomWidget" data-lazy-load="true" data-title="" id="1880319912" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="ab442ac754384e98a02c4f630b481f5e" data-widget-version="9" data-widget-config="eyJyZXF1aXJlZEZpZWxkTmFtZSI6IkNPUFkgQkxPQ0sgMTAgSEVBRExJTkUiLCJjb2xsZWN0aW9uTmFtZSI6IjAxIEhPTUUifQ=="> <div class="hide-section-if-category-field-empty" data-collection-name="01 HOME" data-required-field="COPY BLOCK 10 HEADLINE" style="display:none;"></div> 
1682</div> 
1683</div> 
1684 <div id="89680ce7" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1873668855"> <div class="widget-f19f9b u_1873668855 dmCustomWidget" data-lazy-load="true" data-title="" id="1873668855" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="f19f9b2806104edb81a7816f71967887" data-widget-version="13" data-widget-config="eyJidXR0b25OYW1lIjoiTGVhcm4gTW9yZSIsInNvdXJjZVBhZ2VUeXBlIjoiaG9tZSIsImNvcmVQcmVmaXgiOiJzZXJ2aWNlcyIsImNvcHlCbG9ja051bWJlciI6IjEwIiwiYnV0dG9uUm91dGluZ09wdGlvbnMiOm51bGwsImNhdGVnb3J5UHJlZml4Ijoid2hhdC13ZS1kbyJ9"> <div class="copy-block-link-widget"> <a class="copy-block-link-anchor" aria-label="Learn More" aria-disabled="true" aria-busy="true"> <div class="dmWidget copy-block-link-button"> <span class="text">Learn More</span> 
1685 <span class="iconBg"></span> 
1686</div> 
1687</a> 
1688</div> 
1689</div> 
1690</div> 
1691</div> 
1692 <div id="8bb71fdb" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="3f4255ca" class="flex-element group" data-auto="flex-element-group"> <div id="935e95b1" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1412544286"> <div class="imageWidget align-center u_1412544286" data-element-type="image" data-widget-type="image" id="1412544286"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." id="1713712785" class="" width="598" height="598" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" data-hover-effect="zoomout" onerror="handleImageLoadError(this)" loading="lazy"/></div> 
1693</div> 
1694 <div id="d2205783" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1667816543"> <div class="dmNewParagraph u_1667816543" data-element-type="paragraph" data-version="5" id="1667816543" style="" data-inject="topicheading11"> <h3><span style="display: unset;">Short title 11</span></h3> 
1695</div> 
1696</div> 
1697 <div id="6e9754fa" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1815007866"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1815007866" style="" data-inject="topiccontent11"><p><span style="display: unset;">Describe some quality or feature of the company. Write a short paragraph about it and choose an appropriate icon.</span></p></div> 
1698</div> 
1699</div> 
1700 <div id="eef13827" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1288007709"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient" file="false" href="/" data-element-type="dButtonLinkId" id="1288007709" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIkxlYXJuIE1vcmVcIn0ifV0=" data-diy-link=""> <span class="iconBg" id="1850616232"> <span class="icon hasFontIcon icon-star" id="1868640764"></span> 
1701</span> 
1702 <span class="text" id="1125281715" data-diy-text="">Learn More</span> 
1703</a> 
1704</div> 
1705</div> 
1706 <div id="c638110b" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="b56e4f96" class="flex-element group" data-auto="flex-element-group"> <div id="f4cb7c38" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1737756475"> <div class="imageWidget align-center u_1737756475" data-element-type="image" data-widget-type="image" id="1737756475"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." id="1305766217" class="" width="598" height="598" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" data-hover-effect="zoomout" onerror="handleImageLoadError(this)" loading="lazy"/></div> 
1707</div> 
1708 <div id="e6d1b777" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1836481097"> <div class="dmNewParagraph u_1836481097" data-element-type="paragraph" data-version="5" id="1836481097" style="" data-inject="topicheading12"> <h3><span style="display: unset;">Short title 12</span></h3> 
1709</div> 
1710</div> 
1711 <div id="a4cae16e" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1852460114"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1852460114" style="" data-inject="topiccontent12"><p><span style="display: unset;">Describe some quality or feature of the company. Write a short paragraph about it and choose an appropriate icon.</span></p></div> 
1712</div> 
1713</div> 
1714 <div id="f3072622" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1788142745"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient" file="false" href="/" data-element-type="dButtonLinkId" id="1788142745" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIkxlYXJuIE1vcmVcIn0ifV0=" data-diy-link=""> <span class="iconBg" id="1663528206"> <span class="icon hasFontIcon icon-star" id="1211520021"></span> 
1715</span> 
1716 <span class="text" id="1984177295" data-diy-text="">Learn More</span> 
1717</a> 
1718</div> 
1719</div> 
1720</div> 
1721</div> 
1722</section> 
1723</div> 
1724 <div data-flex-id="14dade1e" data-auto="flex-section" id="1964351622" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104" class="" data-hidden-on-tablet="true" data-hidden-on-mobile="true" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1296980162"></div> 
1725 <section id="72b926f4" class="flex-element section" data-auto="flex-element-section"> <div id="944bd1d5" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="9e1cc445" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="d053724d" class="flex-element group" data-auto="flex-element-group"> <div id="38585760" class="flex-element group" data-auto="flex-element-group"> <div id="f8968f2f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1049162952"> <div class="imageWidget align-center u_1049162952" data-element-type="image" data-widget-type="image" id="1049162952"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." id="1283655531" class="" width="598" height="598" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" data-hover-effect="zoomout" onerror="handleImageLoadError(this)" loading="lazy"/></div> 
1726</div> 
1727 <div id="ed295c04" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1372226423"> <div class="dmNewParagraph u_1372226423" data-element-type="paragraph" data-version="5" id="1372226423" style="" data-inject="topicheading13"> <h3><span style="display: unset;">Short title 13</span></h3> 
1728</div> 
1729</div> 
1730 <div id="0a1e4f7e" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1266275061"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1266275061" style="" data-inject="topiccontent13"><p><span style="display: unset;">Describe some quality or feature of the company. Write a short paragraph about it and choose an appropriate icon.</span></p></div> 
1731</div> 
1732</div> 
1733 <div id="3e93053a" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1692868058"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient" file="false" href="/" data-element-type="dButtonLinkId" id="1692868058" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIkxlYXJuIE1vcmVcIn0ifV0=" data-diy-link=""> <span class="iconBg" id="1640026967"> <span class="icon hasFontIcon icon-star" id="1002406236"></span> 
1734</span> 
1735 <span class="text" id="1117327613" data-diy-text="">Learn More</span> 
1736</a> 
1737</div> 
1738</div> 
1739 <div id="c84f3f41" class="flex-element group" data-auto="flex-element-group"> <div id="1c9c12ea" class="flex-element group" data-auto="flex-element-group"> <div id="2c486de1" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1998434563"> <div class="imageWidget align-center u_1998434563" data-element-type="image" data-widget-type="image" id="1998434563"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." id="1256126397" class="" width="598" height="598" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" data-hover-effect="zoomout" onerror="handleImageLoadError(this)" loading="lazy"/></div> 
1740</div> 
1741 <div id="4e6b661d" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1108982204"> <div class="dmNewParagraph u_1108982204" data-element-type="paragraph" data-version="5" id="1108982204" style="" data-inject="topicheading14"> <h3><span style="display: unset;">Short title 14</span></h3> 
1742</div> 
1743</div> 
1744 <div id="bbb709d2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1382683992"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1382683992" style="" data-inject="topiccontent14"><p><span style="display: unset;">Describe some quality or feature of the company. Write a short paragraph about it and choose an appropriate icon.</span></p></div> 
1745</div> 
1746</div> 
1747 <div id="8f537d1d" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1016938817"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient" file="false" href="/" data-element-type="dButtonLinkId" id="1016938817" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIkxlYXJuIE1vcmVcIn0ifV0=" data-diy-link=""> <span class="iconBg" id="1420747211"> <span class="icon hasFontIcon icon-star" id="1796409820"></span> 
1748</span> 
1749 <span class="text" id="1826185243" data-diy-text="">Learn More</span> 
1750</a> 
1751</div> 
1752</div> 
1753 <div id="db278998" class="flex-element group" data-auto="flex-element-group"> <div id="f1b2f41c" class="flex-element group" data-auto="flex-element-group"> <div id="30b343be" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="image" data-external-id="1508144627"> <div class="imageWidget align-center u_1508144627" data-element-type="image" data-widget-type="image" id="1508144627"><img src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." id="1660073910" class="" width="598" height="598" data-dm-image-path="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" data-hover-effect="zoomout" onerror="handleImageLoadError(this)" loading="lazy"/></div> 
1754</div> 
1755 <div id="8c0c731a" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1698565400"> <div class="dmNewParagraph u_1698565400" data-element-type="paragraph" data-version="5" id="1698565400" style="" data-inject="topicheading15"> <h3><span style="display: unset;">Short title 15</span></h3> 
1756</div> 
1757</div> 
1758 <div id="d25b51db" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1095170295"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1095170295" style="" data-inject="topiccontent15"><p><span style="display: unset;">Describe some quality or feature of the company. Write a short paragraph about it and choose an appropriate icon.</span></p></div> 
1759</div> 
1760</div> 
1761 <div id="ebba869b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1611207030"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient" file="false" href="/" data-element-type="dButtonLinkId" id="1611207030" data-binding="W3siYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIkxlYXJuIE1vcmVcIn0ifV0=" data-diy-link=""> <span class="iconBg" id="1464083998"> <span class="icon hasFontIcon icon-star" id="1079708868"></span> 
1762</span> 
1763 <span class="text" id="1741396850" data-diy-text="">Learn More</span> 
1764</a> 
1765</div> 
1766</div> 
1767</div> 
1768</div> 
1769</section> 
1770</div> 
1771 <div data-auto="flex-section" id="1143226197" data-flex-id="20949c9a" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104" class="" data-hidden-on-mobile="true"> <div class="flex-widgets-container" id="1510216086"></div> 
1772 <section id="b4298b67" class="flex-element section" data-auto="flex-element-section"> <div id="b3a40527" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="7f4d5075" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="b7d5a347" class="flex-ele
1772ment group" data-auto="flex-element-group"> <div id="1ea0a01c" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1221809728"> <div class="widget-8ff527 u_1221809728 dmCustomWidget" data-lazy-load="true" data-title="" id="1221809728" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8ff5270554664bddaecbe7a0b9e45c81" data-widget-version="14" data-widget-config="eyJjYWxsb3V0TWF4V2lkdGgiOiI3MjAiLCJlbmFibGVCdXR0b25SaXBwbGUiOmZhbHNlLCJidXR0b25BbGlnbiI6ImNlbnRlciIsImNhcmRWZXJ0aWNhbEFsaWduIjoidG9wIiwic3BhY2luZ0Rlc2lnbiI6bnVsbCwiY2FyZENvbnRlbnRBbGlnbiI6ImNlbnRlciIsInR5cG9ncmFwaHlIZWFkIjpudWxsLCJjb3Vwb25HYXAiOiIyMCIsInNob3dTdGFuZGFyZEJ1dHRvbiI6ZmFsc2UsImNvdXBvblNldHRpbmdzIjpudWxsLCJlbmFibGVFbnRyYW5jZUFuaW1hdGlvbiI6ZmFsc2UsImhlYWRsaW5lQm90dG9tR2FwIjoiMTIiLCJ3aWRnZXRNb2RlIjoiY291cG9uIiwibG9nb0Rlc2lnbiI6bnVsbCwidHlwb2dyYXBoeUZpbmUiOm51bGwsInVzZURlZmF1bHRMb2dvIjp0cnVlLCJlbmFibGVMb2dvSG92ZXIiOmZhbHNlLCJmaWx0ZXJWYWx1ZSI6IjIiLCJ0eXBvZ3JhcGh5Q3RhIjpudWxsLCJjYXJkRGVzaWduIjpudWxsLCJnZW5lcmFsU2V0dGluZ3MiOm51bGwsImFsbG93ZWRTbHVncyI6IiIsImJvZHlCb3R0b21HYXAiOiIxMiIsImJ1dHRvbkRlc2lnbiI6bnVsbCwiZW5hYmxlQ2FyZEhvdmVyIjpmYWxzZSwiY2FyZFNoYWRvdyI6Im5vbmUiLCJsb2dvQm90dG9tR2FwIjoiMjQiLCJjYWxsb3V0U2V0dGluZ3MiOm51bGwsImxvZ29BbGlnbiI6ImNlbnRlciIsImNvdXBvbkNvbHVtbnMiOiIxIiwiY2FsbG91dEZpbHRlclZhbHVlIjoiIiwiZnVsbFdpZHRoQnV0dG9uc01vYmlsZSI6dHJ1ZSwibG9nb1NwYWNpbmciOiIxMiIsInR5cG9ncmFwaHlCb2R5IjpudWxsLCJhbmltYXRpb25EZXNpZ24iOm51bGwsImxvZ29XaWR0aCI6IjIyMCIsInNob3dBbGwiOmZhbHNlLCJlbmFibGVQaG9uZVB1bHNlIjpmYWxzZSwiYnV0dG9uQm90dG9tR2FwIjoiMCIsImxvZ29NYXhIZWlnaHQiOiIxMDAiLCJmaWx0ZXJCeVNsdWciOmZhbHNlLCJidXR0b25MYXlvdXQiOiJyb3ciLCJjb3Vwb25GaWx0ZXJCeVNsdWciOnRydWUsImNvdXBvbkxheW91dCI6bnVsbCwiY2FsbG91dExheW91dCI6bnVsbCwidGVybXNCb3R0b21HYXAiOiIxMiIsInNob3dQaG9uZUJ1dHRvbiI6dHJ1ZSwidGVybXNEaXNwbGF5TW9kZSI6ImZ1bGwiLCJjdXN0b21pemVTcGFjaW5nIjpmYWxzZSwiYnV0dG9uR2FwIjoiMTIiLCJjdGFCb3R0b21HYXAiOiIxMiIsImVuYWJsZUJ1dHRvbkhvdmVyIjpmYWxzZSwiY2FsbG91dEFsaWduIjoiZnVsbCJ9"> <div class="ucw-wrapper" role="region" aria-label="Promotions" aria-busy="true"> <div class="ucw-callout" hidden=""> <div class="ucw-callout__mount"></div> 
1773</div> 
1774 <div class="ucw-coupon" hidden=""> <div class="ucw-coupon__grid" role="list"></div> 
1775</div> 
1776<!--
1777    Hidden Duda button template.
1778
1779    JavaScript clones this markup for both the collection
1780    button and the phone button. This allows one standard
1781    Duda Button design control to style every generated action.
1782  --> <div class="ucw-action-template" hidden="" aria-hidden="true"> <a class="ucw-card__actionLink" href="#"> <div class="dmWidget ucw-card__action"> <span class="text">Button</span> 
1783 <span class="iconBg"></span> 
1784</div> 
1785</a> 
1786</div> 
1787</div> 
1788</div> 
1789</div> 
1790</div> 
1791 <div id="e0e7ea56" class="flex-element group" data-auto="flex-element-group"> <div id="ab09fe54" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1710114320"> <div class="widget-8ff527 u_1710114320 dmCustomWidget" data-lazy-load="true" data-title="" id="1710114320" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8ff5270554664bddaecbe7a0b9e45c81" data-widget-version="14" data-widget-config="eyJjYWxsb3V0TWF4V2lkdGgiOiI3MjAiLCJlbmFibGVCdXR0b25SaXBwbGUiOmZhbHNlLCJidXR0b25BbGlnbiI6ImNlbnRlciIsImNhcmRWZXJ0aWNhbEFsaWduIjoidG9wIiwic3BhY2luZ0Rlc2lnbiI6bnVsbCwiY2FyZENvbnRlbnRBbGlnbiI6ImNlbnRlciIsInR5cG9ncmFwaHlIZWFkIjpudWxsLCJjb3Vwb25HYXAiOiIyMCIsInNob3dTdGFuZGFyZEJ1dHRvbiI6ZmFsc2UsImNvdXBvblNldHRpbmdzIjpudWxsLCJlbmFibGVFbnRyYW5jZUFuaW1hdGlvbiI6ZmFsc2UsImhlYWRsaW5lQm90dG9tR2FwIjoiMTIiLCJ3aWRnZXRNb2RlIjoiY291cG9uIiwibG9nb0Rlc2lnbiI6bnVsbCwidHlwb2dyYXBoeUZpbmUiOm51bGwsInVzZURlZmF1bHRMb2dvIjp0cnVlLCJlbmFibGVMb2dvSG92ZXIiOmZhbHNlLCJmaWx0ZXJWYWx1ZSI6IjEiLCJ0eXBvZ3JhcGh5Q3RhIjpudWxsLCJjYXJkRGVzaWduIjpudWxsLCJnZW5lcmFsU2V0dGluZ3MiOm51bGwsImFsbG93ZWRTbHVncyI6IiIsImJvZHlCb3R0b21HYXAiOiIxMiIsImJ1dHRvbkRlc2lnbiI6bnVsbCwiZW5hYmxlQ2FyZEhvdmVyIjpmYWxzZSwiY2FyZFNoYWRvdyI6Im5vbmUiLCJsb2dvQm90dG9tR2FwIjoiMjQiLCJjYWxsb3V0U2V0dGluZ3MiOm51bGwsImxvZ29BbGlnbiI6ImNlbnRlciIsImNvdXBvbkNvbHVtbnMiOiIxIiwiY2FsbG91dEZpbHRlclZhbHVlIjoiIiwi
1791ZnVsbFdpZHRoQnV0dG9uc01vYmlsZSI6dHJ1ZSwibG9nb1NwYWNpbmciOiIxMiIsInR5cG9ncmFwaHlCb2R5IjpudWxsLCJhbmltYXRpb25EZXNpZ24iOm51bGwsImxvZ29XaWR0aCI6IjIyMCIsInNob3dBbGwiOmZhbHNlLCJlbmFibGVQaG9uZVB1bHNlIjpmYWxzZSwiYnV0dG9uQm90dG9tR2FwIjoiMCIsImxvZ29NYXhIZWlnaHQiOiIxMDAiLCJmaWx0ZXJCeVNsdWciOmZhbHNlLCJidXR0b25MYXlvdXQiOiJyb3ciLCJjb3Vwb25GaWx0ZXJCeVNsdWciOnRydWUsImNvdXBvbkxheW91dCI6bnVsbCwiY2FsbG91dExheW91dCI6bnVsbCwidGVybXNCb3R0b21HYXAiOiIxMiIsInNob3dQaG9uZUJ1dHRvbiI6dHJ1ZSwidGVybXNEaXNwbGF5TW9kZSI6ImZ1bGwiLCJjdXN0b21pemVTcGFjaW5nIjpmYWxzZSwiYnV0dG9uR2FwIjoiMTIiLCJjdGFCb3R0b21HYXAiOiIxMiIsImVuYWJsZUJ1dHRvbkhvdmVyIjpmYWxzZSwiY2FsbG91dEFsaWduIjoiZnVsbCJ9"> <div class="ucw-wrapper" role="region" aria-label="Promotions" aria-busy="true"> <div class="ucw-callout" hidden=""> <div class="ucw-callout__mount"></div> 
1792</div> 
1793 <div class="ucw-coupon" hidden=""> <div class="ucw-coupon__grid" role="list"></div> 
1794</div> 
1795<!--
1796    Hidden Duda button template.
1797
1798    JavaScript clones this markup for both the collection
1799    button and the phone button. This allows one standard
1800    Duda Button design control to style every generated action.
1801  --> <div class="ucw-action-template" hidden="" aria-hidden="true"> <a class="ucw-card__actionLink" href="#"> <div class="dmWidget ucw-card__action"> <span class="text">Button</span> 
1802 <span class="iconBg"></span> 
1803</div> 
1804</a> 
1805</div> 
1806</div> 
1807</div> 
1808</div> 
1809</div> 
1810</div> 
1811</div> 
1812</section> 
1813</div> 
1814 <div data-layout-section="" data-auto="flex-section" id="1116954811" data-flex-id="915d572d" data-page-element-type="section" data-page-element-id="1927781104" class="" data-hidden-on-mobile="true"> <div class="flex-widgets-container" id="1357289424"></div> 
1815 <section id="9aaf2097" class="flex-element section" data-auto="flex-element-section"> <div id="05b0640d" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="83bb388e" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop="" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="144ca53d" class="flex-element group" data-auto="flex-element-group"> <div id="c0816246" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1136100820"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="/special-offers" data-element-type="dButtonLinkId" id="1136100820" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiTW9yZSBPZmZlcnMgTGlua1wifSJ9LHsiYmluZGluZ05hbWUiOiJidXR0b25fdGV4dCIsInZhbHVlIjoiU1RBVElDX1BBR0VfQ09MTEVDVElPTi57XCJjblwiOlwiYnV0dG9uc1wiLFwiZm5cIjpcIk1vcmUgT2ZmZXJzIEJ1dHRvblwifSJ9XQ==" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1575920418"> <span class="icon hasFontIcon icon-star" id="1214695561"></span> 
1816</span> 
1817 <span class="text" id="1407286975" data-diy-text="">View More Offers</span> 
1818</a> 
1819</div> 
1820</div> 
1821</div> 
1822</div> 
1823</section> 
1824</div> 
1825 <div data-layout-section="" data-auto="flex-section" id="1976730119" data-flex-id="c28373f0" data-page-element-type="section" data-page-element-id="1927781104" class="" data-hidden-on-tablet="true" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1043356399"></div> 
1826 <section id="e35e3c9c" class="flex-element section" data-auto="flex-element-section"> <div id="2d02c516" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="4f637013" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-mobile="" data-hidden-on-desktop=""> <div id="e0a1b825" class="flex-element group" data-auto="flex-element-group"> <div id="4ad1ed5d" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1445046919"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1445046919" style=""> <h2 class="text-align-center"><span style="display: initial;">Recent Projects</span></h2> 
1827</div> 
1828</div> 
1829 <div id="9e49c27b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="photoGallery" data-external-id="1371892140"> <div class="dmPhotoGallery newPhotoGallery dmPhotoGalleryResp u_1371892140 photo-gallery-done text-layout-over captionAlignment-center_center photoGallery" galleryoptionsparams="{thumbnailsPerRow: 3, rowsToShow: 3, imageScaleMethod: true}" data-desktop-layout="square" data-desktop-columns="4" data-element-type="dPhotoGalleryId" data-desktop-text-layout="over" id="1371892140" data-placeholder="false" data-image-hover-effect="zoomout" data-image-animation="none"> <div class="dmPhotoGalleryHolder clearfix gallery shadowEffectToChildren gallery4inArow" id="1362986591"></div> 
1830 <div class="layout-container square"> <div class="photogallery-row " data-index=""> <div class="photogallery-column column-4" data-index="0"> <div index="0" class="photoGalleryThumbs animated  " data-index="0"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1913981796" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1408174563" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1831</div> 
1832 <div id="1873311184" class="caption-container u_1873311184" style=""> <span class="caption-inner"> <a id="1062249715" class="caption-button dmWidget  clearfix u_1062249715" data-auto="c
1832aption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1833</span> 
1834 <span class="text" data-auto="button-text">Button</span> 
1835</a> 
1836</span> 
1837</div> 
1838</div> 
1839</div> 
1840</div> 
1841 <div class="photogallery-column column-4" data-index="1"> <div index="1" class="photoGalleryThumbs animated  " data-index="1"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1771181155" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1359767175" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1842</div> 
1843 <div id="1312188585" class="caption-container u_1312188585" style="display:none"> <span class="caption-inner"> <a id="1875381288" class="caption-button dmWidget  clearfix u_1875381288" data-auto="caption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1844</span> 
1845 <span class="text" data-auto="button-text">Button</span> 
1846</a> 
1847</span> 
1848</div> 
1849</div> 
1850</div> 
1851</div> 
1852 <div class="photogallery-column column-4" data-index="2"> <div index="2" class="photoGalleryThumbs animated  " data-index="2"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1169915625" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1378404927" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1853</div> 
1854 <div id="1545194236" class="caption-container u_1545194236" style="display:none"> <span class="caption-inner"> <a id="1516774716" class="caption-button dmWidget  clearfix u_1516774716" data-auto="caption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1855</span> 
1856 <span class="text" data-auto="button-text">Button</span> 
1857</a> 
1858</span> 
1859</div> 
1860</div> 
1861</div> 
1862</div> 
1863 <div class="photogallery-column column-4" data-index="3"> <div index="3" class="photoGalleryThumbs animated  " data-index="3"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1715774396" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1468845780" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1864</div> 
1865 <div id="1280990072" class="caption-container u_1280990072" style="display:none"> <span class="caption-inner"> <a id="1185058053" class="caption-button dmWidget  clearfix u_1185058053" data-auto="c
1865aption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1866</span> 
1867 <span class="text" data-auto="button-text">Button</span> 
1868</a> 
1869</span> 
1870</div> 
1871</div> 
1872</div> 
1873</div> 
1874</div> 
1875 <div class="photogallery-row  photogallery-hidden-row " data-index=""> <div class="photogallery-column column-4" data-index="4"> <div index="4" class="photoGalleryThumbs animated  " data-index="4"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1612775533" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1384508436" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1876</div> 
1877 <div id="1821324669" class="caption-container u_1821324669" style="display:none"> <span class="caption-inner"> <a id="1121451469" class="caption-button dmWidget  clearfix u_1121451469" data-auto="caption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1878</span> 
1879 <span class="text" data-auto="button-text">Button</span> 
1880</a> 
1881</span> 
1882</div> 
1883</div> 
1884</div> 
1885</div> 
1886 <div class="photogallery-column column-4" data-index="5"> <div index="5" class="photoGalleryThumbs animated  " data-index="5"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1875652952" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1247617013" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1887</div> 
1888 <div id="1351515309" class="caption-container u_1351515309" style="display:none"> <span class="caption-inner"> <a id="1181807921" class="caption-button dmWidget  clearfix u_1181807921" data-auto="caption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1889</span> 
1890 <span class="text" data-auto="button-text">Button</span> 
1891</a> 
1892</span> 
1893</div> 
1894</div> 
1895</div> 
1896</div> 
1897 <div class="photogallery-column column-4" data-index="6"> <div index="6" class="photoGalleryThumbs animated  " data-index="6"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1565530853" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1879454595" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1898</div> 
1899 <div id="1569630685" class="caption-container u_1569630685" style="display:none"> <span class="caption-inner"> <a id="1246095010" class="caption-button dmWidget  clearfix u_1246095010" data-auto="c
1899aption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1900</span> 
1901 <span class="text" data-auto="button-text">Button</span> 
1902</a> 
1903</span> 
1904</div> 
1905</div> 
1906</div> 
1907</div> 
1908 <div class="photogallery-column column-4" data-index="7"> <div index="7" class="photoGalleryThumbs animated  " data-index="7"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." data-dm-force-device="mobile" class="u_1065452950" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/Screen+Shot+2024-10-04+at+4.03.11+PM.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-640w.png');"><img id="1951703431" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/Screen+Shot+2024-10-04+at+4.03.11+PM-1920w.png" alt="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." aria-label="Gray placeholder graphic with the words “IMAGE PLACEHOLDER” centered." onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1909</div> 
1910 <div id="1900329691" class="caption-container u_1900329691" style="display:none"> <span class="caption-inner"> <a id="1988727417" class="caption-button dmWidget  clearfix u_1988727417" data-auto="caption-button" href="" style=""> <span class="iconBg" data-auto="button-icon-bg"> <span class="icon hasFontIcon " data-auto="button-icon"></span> 
1911</span> 
1912 <span class="text" data-auto="button-text">Button</span> 
1913</a> 
1914</span> 
1915</div> 
1916</div> 
1917</div> 
1918</div> 
1919</div> 
1920</div> 
1921 <div class="photoGalleryViewAll link" data-auto="photo-gallery-view-all" isall="true" data-viewall="View more" data-viewless="View less">View more</div> 
1922</div> 
1923</div> 
1924 <div id="fa468f90" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1180742707"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton" file="false" href="/gallery" data-element-type="dButtonLinkId" id="1180742707" data-binding="W3siYmluZGluZ05hbWUiOiJsaW5rIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiTW9yZSBQcm9qZWN0cyBMaW5rXCJ9In0seyJiaW5kaW5nTmFtZSI6ImJ1dHRvbl90ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJidXR0b25zXCIsXCJmblwiOlwiTW9yZSBQcm9qZWN0cyBCdXR0b25cIn0ifV0=" data-diy-link=""> <span class="iconBg" aria-hidden="true" id="1082786938"> <span class="icon hasFontIcon icon-star" id="1847780512"></span> 
1925</span> 
1926 <span class="text" id="1954293470" data-diy-text="">View More Projects</span> 
1927</a> 
1928</div> 
1929</div> 
1930</div> 
1931</div> 
1932</section> 
1933</div> 
1934 <div data-auto="flex-section" id="1031630919" data-flex-id="9f905d9c" data-layout-section="" data-page-element-type="section" data-page-element-id="1927781104"> <div class="flex-widgets-container" id="1601386914"></div> 
1935 <section id="2815ffcf" class="flex-element section" data-auto="flex-element-section"> <div id="d43161ce" class="flex-element grid lazy" data-auto="flex-element-grid" data-layout-grid="" data-binding="eyJiaW5kaW5nTmFtZSI6ImJhY2tncm91bmQiLCJ2YWx1ZSI6InNpdGVfaW1hZ2VzLnBlcnNpc3RlZC5SZWNlbnQgUmV2aWV3cyBCRyBJbWFnZSJ9" style="background-image: url('data:image/jpeg; base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAHAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAUG/8QAHhAAAQMEAwAAAAAAAAAAAAAAAQACAwQFERIiIzH/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//EABcRAQEBAQAAAAAAAAAAAAAAAAEAAiH/2gAMAwEAAhEDEQA/ANBXy08bTCzZshB6wMNUQWq6EZbHHqfOYREcbaicL//Z') !important;" data-background-image="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/reviews-background-01-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg"> <div id="4e93ed72" class="flex-element group" data-auto="flex-element-group"> <div id="5c909fd1" class="flex-element group" data-auto="flex-element-group"> <div id="498eddb3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-anim-extended="eyJkZXNrdG9wIjp7InRyaWdnZXIiOiJlbnRyYW5jZSIsImFuaW1hdGlvbiI6InJvdGF0ZUluQ29tYm8iLCJkdXJhdGlvbiI6MC41LCJkZWxheSI6MCwiaW50ZW5zaXR5IjowLjQsImRpciI6ImluLXBsYWNlIn19" data-external-id="1387851697"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1387851697" data-element-type="graphic" data-widget-type="graphic" id="1387851697"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1479544772" class="svg u_1479544772" data-icon-custom="true" data-icon-name="speech_7848508.svg"> <title id="1996517037">Black outlined chat bubble with three horizontal lines inside.</title> 
1936 <path d="m22.16 26.074c-1.2656 0-2.2969-1.0273-2.2969-2.2969 0-1.2656 1.0273-2.2969 2.2969-2.2969h55.676c1.2656 0 2.2969 1.0273 2.2969 2.2969 0 1.2656-1.0273 2.2969-2.2969 2.2969zm-18.836-19.984h93.348c1.2656 0 2.2969 1.0273 2.2969 2.2969v
193666.566c0 1.2656-1.0273 2.2969-2.2969 2.2969h-14.484c-3.3828 4.4492-7.0156 8.8281-12.277 12.027-5.6523 3.4336-13.016 5.3867-23.605 4.3711-1.2578-0.11719-2.1797-1.2344-2.0625-2.4922 0.050781-0.53516 0.28125-1.0117 0.62891-1.3711 0.56641-0.64062 5.4375-6.1992 8.332-12.535h-49.879c-1.2656 0-2.2969-1.0273-2.2969-2.2969v-66.566c0-1.2695 1.0273-2.2969 2.2969-2.2969zm91.051 4.5898h-88.758v61.977h50.945v0.003906c0.23438 0 0.47266 0.035156 0.70703 0.11328 1.2031 0.39062 1.8594 1.6836 1.4688 2.8867-1.7305 5.2695-5.1562 10.402-7.6289 13.676 7.1445 0.089843 12.344-1.4883 16.43-3.9688 4.8438-2.9414 8.2852-7.2734 11.566-11.625 0.40625-0.65234 1.1289-1.082 1.9492-1.082h13.32zm-72.215 51.176c-1.2656 0-2.2969-1.0273-2.2969-2.2969 0-1.2695 1.0273-2.2969 2.2969-2.2969h55.676c1.2656 0 2.2969 1.0273 2.2969 2.2969 0 1.2656-1.0273 2.2969-2.2969 2.2969zm0-17.891c-1.2656 0-2.2969-1.0273-2.2969-2.2969 0-1.2656 1.0273-2.2969 2.2969-2.2969h55.676c1.2656 0 2.2969 1.0273 2.2969 2.2969 0 1.2656-1.0273 2.2969-2.2969 2.2969z"></path> 
1937</svg> 
1938</div> 
1939</div> 
1940 <div id="dc78df13" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1395177125"> <div class="dmNewParagraph u_1395177125" data-element-type="paragraph" data-version="5" id="1395177125" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJzaXRlX3RleHQucmVjZW50IHJldmlld3MgbGVhZC1pbiB0ZXh0In1d"> <h3 class="m-text-align-left" id="1252156874"><span style="color: var(--color_2); display: unset;" id="1710038180">Here's what our satisfied customers are saying...</span></h3> 
1941</div> 
1942</div> 
1943 <div id="e16b1cb2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1202426832"> <div class="u_1202426832 dmNewParagraph" data-element-type="paragraph" data-version="5" id="1202426832" data-diy-text="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJzaXRlX3RleHQuaG9tZSAvIGNhdCByZWNlbnQgcmV2aWV3cyB0ZXh0In1d" style=""><p>At RupCoe Plumbing, Heating &amp; Air Conditioning Inc, we take pride in providing exceptional residential and commercial HVAC and plumbing services to our customers. We would be grateful if you could share your thoughts about our business with others. Your feedback helps us improve and helps others make informed decisions. Please take a moment to <a href="/site/b4d59456c20346899f3ab9056d40b845/reviews?preview=true&nee=true&showOriginal=true&dm_checkSync=1&dm_try_mode=true&dm_device=desktop" runtime_url="/site/b4d59456c20346899f3ab9056d40b845/reviews?preview=true&nee=true&showOriginal=true&dm_checkSync=1&dm_try_mode=true&dm_device=desktop" type="page">leave a review</a> 
1944of RupCoe Plumbing, Heating &amp; Air Conditioning Inc and let others know what you think.</p></div> 
1945</div> 
1946</div> 
1947 <div id="be0d716f" class="flex-element group" data-auto="flex-element-group"> <div id="d494db35" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1504916694"> <div class="widget-328e6f u_1504916694 dmCustomWidget" data-lazy-load="true" data-title="" id="1504916694" dmle_extension="custom_extension" data-element-type="custom_extension" data-binding="W3siYmluZGluZ05hbWUiOiJtZXJjaGFudElkIiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJSZXZpZXdzIFdpZGdldHNcIixcImZuXCI6XCJNZXJjaGFudCBJRFwifSJ9XQ==" icon="false" surround="false" data-widget-id="328e6f1fbcec4101ac4f8d9184710a14" data-widget-version="7" data-widget-config="eyJtZXJjaGFudElkIjoiODA0MzAiLCJEYXRhTGluZXMiOiIyIiwiRGF0YU51bSI6IjIiLCJUcnVuY2F0ZURlc3QiOiIvcmV2aWV3cyIsIkRhdGFUaGlyZFBhcnR5IjoidHJ1ZSIsIkRhdGFSYW5kb21pemUiOiJmYWxzZSIsInJldi1kYXJrIjp0cnVlfQ=="> <div id="recentReviewsWidget" class="rev-dark fb-dark" data-lines="2" data-num="2" data-truncate-dest="/reviews" data-randomize="false" data-third-party="true" data-merchant="80430"></div> 
1948</div> 
1949</div> 
1950</div> 
1951</div> 
1952</div> 
1953</section> 
1954</div> 
1955</div> 
1956</div> 
1957</div> 
1958</div> 
1959 <div class="dmFooterContainer flex_hfcontainer"> <div id="flex-footer" role="contentinfo"> <div data-layout-section="" data-auto="flex-section" id="1959439239" data-flex-id="2c8e291c" data-page-element-type="section" data-page-element-id="1601394852"> <div class="flex-widgets-container" id="1357377095"></div> 
1960 <section id="21379086" class="flex-element section" data-auto="flex-element-section"> <div id="b5a3be9f" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="b7a700bf" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="6405d24c" class="flex-element group" data-auto="flex-element-group"> <div id="f9c6e236" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="photoGallery" data-external-id="1289582650"> <div class="dmPhotoGallery newPhotoGallery dmPhotoGalleryResp u_1289582650 photo-gallery-done text-layout-over captionAlignment-center_center photoGallery" galleryoptionsparams="{thumbnailsPerRow: 3, rowsToShow: 3, imageScaleMethod: true}" data-desktop-layout="square" data-desktop-columns="4" data-element-type="dPhotoGalleryId" data-desktop-text-layout="over" id="1289582650" data-auto-adjust-columns="true" data-image-hover-effect="zoomout" data-binding="W3siYmluZGluZ05hbWUiOiJwaG90b19nYWxsZXJ5IiwidmFsdWUiOiJzaXRlX2NvbGxlY3Rpb24uQnJhbmQgSWNvbnMifV0=" data-binding-metadata="eyJsaW5rIjoiIiwiaW1hZ2UiOiJpbWFnZSIsImRlc2NyaXB0aW9uIjoiIiwidGl0bGUiOiIiLCJhbHRfdGV4dCI6IiIsImxpbmtfdGV4dCI6IiJ9" data-paginate-total-elements="10"> <div class="dmPhotoGalleryHolder clearfix gallery shadowEffectToChildren gallery4inArow" id="1362986591"></div> 
1961 <div class="layout-container square"> <div class="photogallery-row " data-index=""> <div class="photogallery-column column-5" data-index="0"> <div index="0" class="photoGalleryThumbs animated  " data-index="0"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/carrier-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.webp" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/carrier-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.webp');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/carrier-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.webp" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1962</div> 
1963</div> 
1964</div> 
1965</div> 
1966 <div class="photogallery-column column-5" data-index="1"> <div index="1" class="photoGalleryThumbs animated  " data-index="1"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/york-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/york-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.jpg');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/york-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1967</div> 
1968</div> 
1969</div> 
1970</div> 
1971 <div class="photogallery-column column-5" data-index="2"> <div index="2" class="photoGalleryThumbs animated  " data-index="2"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/amana-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/amana-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.jpg');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/amana-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1972</div> 
1973</div> 
1974</div> 
1975</div> 
1976 <div class="photogallery-column column-5" data-index="3"> <div index="3" class="photoGalleryThumbs animated  " data-index="3"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/trane-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/trane-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.png');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/trane-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1977</div> 
1978</div> 
1979</div> 
1980</div> 
1981 <div class="photogallery-column column-5" data-index="4"> <div index="4" class="photoGalleryThumbs animated  " data-index="4"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/ruud-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/ruud-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.jpg');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/ruud-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1982</div> 
1983</div> 
1984</div> 
1985</div> 
1986</div> 
1987 <div class="photogallery-row  photogallery-hidden-row " data-index=""> <div class="photogallery-column column-5" data-index="5"> <div index="5" class="photoGalleryThumbs animated  " data-index="5"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/rheem-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/rheem-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.png');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/rheem-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1988</div> 
1989</div> 
1990</div> 
1991</div> 
1992 <div class="photogallery-column column-5" data-index="6"> <div index="6" class="photoGalleryThumbs animated  " data-index="6"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/goodman-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/goodman-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.png');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/goodman-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1993</div> 
1994</div> 
1995</div> 
1996</div> 
1997 <div class="photogallery-column column-5" data-index="7"> <div index="7" class="photoGalleryThumbs animated  " data-index="7"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/lennox-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/lennox-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.jpg');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/lennox-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
1998</div> 
1999</div> 
2000</div> 
2001</div> 
2002 <div class="photogallery-column column-5" data-index="8"> <div index="8" class="photoGalleryThumbs animated  " data-index="8"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/bryant-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/bryant-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.png');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/bryant-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
2003</div> 
2004</div> 
2005</div> 
2006</div> 
2007 <div class="photogallery-column column-5" data-index="9"> <div index="9" class="photoGalleryThumbs animated  " data-index="9"> <div class="thumbnailInnerWrapper" style="opacity: 1;"> <div class="image-container revealed"> <a data-dm-multisize-attr="temp" data-dm-force-device="mobile" class="u_" data-image-url="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/american-standard-badge-rupcoe-plumbing-heating-and-air-conditioning-inc.png" style="background-image: url('https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/american-standard-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-640w.png');"><img id="" data-src="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/american-standard-badge-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.png" alt="" onerror="handleImageLoadError(this)" loading="lazy"/></a> 
2008</div> 
2009</div> 
2010</div> 
2011</div> 
2012</div> 
2013</div> 
2014 <div class="photoGalleryViewAll link" data-auto="photo-gallery-view-all" isall="true" data-viewall="View more" data-viewless="View less">View more</div> 
2015</div> 
2016</div> 
2017</div> 
2018</div> 
2019</div> 
2020</section> 
2021</div> 
2022 <div data-auto="flex-section" id="1518098037" data-flex-id="7b00a5de" data-layout-section="" data-page-element-type="section" data-page-element-id="1761552858"> <div class="flex-widgets-container" id="1613662762"></div> 
2023 <section id="9328cbef" class="flex-element section" data-auto="flex-element-section"> <div id="1ebb7f19" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="6e06962d" class="flex-element group" data-auto="flex-element-group"> <div id="a019b36e" class="flex-element group" data-auto="flex-element-group"> <div id="934bad03" class="flex-element widget-wrapper YXTBUSNAME" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1506966255"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1506966255" new-inline-bind-applied="true" data-diy-text="" style=""> <h3 class="m-size-16 t-size-16 size-18"><span class="m-font-size-16 t-font-size-16 font-size-18" style="display: unset; color: var(--color_2);">Learn more about</span></h3> 
2024 <h3><span style="color: v
2024ar(--color_2); display: initial;"><span class="inline-data-binding" data-encoded-value="UnVwQ29lIFBsdW1iaW5nLCBIZWF0aW5nICYgQWlyIENvbmRpdGlvbmluZyBJbmM=" data-inline-binding="" data-inline-binding-encoded="Y29udGVudF9saWJyYXJ5Lmdsb2JhbC5jb21wYW55X25hbWU=">RupCoe Plumbing, Heating &amp; Air Conditioning Inc.</span></span></h3> 
2025</div> 
2026</div> 
2027 <div id="6f8e19aa" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1661506068"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1661506068" new-inline-bind-applied="true" data-diy-text="" style=""><p class="m-text-align-left text-align-left"><span style="color: var(--color_2); display: initial;"><span class="inline-data-binding" data-encoded-value="U2VydmluZyBTb3V0aCBQbGFpbmZpZWxkLCBOSiwgYW5kIENlbnRyYWwgTkosIFJ1cENvZSBQbHVtYmluZywgSGVhdGluZyAmIEFpciBDb25kaXRpb25pbmcgSW5jIHNwZWNpYWxpemVzIGluIHBsdW1iaW5nIGFuZCBIVkFDIGluc3RhbGxhdGlvbiwgc2VydmljZSwgYW5kIHJlcGFpci4gMjQvNyBlbWVyZ2VuY3kgc2VydmljZS4gTW9uZXktc2F2aW5nIHNwZWNpYWwgb2ZmZXJzLiBGaW5hbmNpbmcgYXZhaWxhYmxlLiBDYWxsIHRvZGF5Lg==" data-inline-binding="" data-inline-binding-encoded="c2l0ZV90ZXh0LnZpZGVvIHRyYW5zY3JpcHQ=">Serving South Plainfield, NJ, and Central NJ, RupCoe Plumbing, Heating &amp; Air Conditioning Inc specializes in plumbing and HVAC installation, service, and repair. 24/7 emergency service. Money-saving special offers. Financing available. Call today.</span></span></p></div> 
2028</div> 
2029 <div id="bd7b2692" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1035845042"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1035845042" file="false" href="/hibu-video-splash" data-element-type="dButtonLinkId" id="1035845042" link_type="popup" popup_target="hibu-video-splash"> <span class="iconBg" aria-hidden="true" id="1299251327"> <span class="icon hasFontIcon icon-star" id="1881817341"></span> 
2030</span> 
2031 <span class="text" id="1018125776">Watch Video</span> 
2032</a> 
2033</div> 
2034</div> 
2035 <div id="05e0ee19" class="flex-element group lazy" data-auto="flex-element-group" data-binding="eyJiaW5kaW5nTmFtZSI6ImJhY2tncm91bmQiLCJ2YWx1ZSI6InNpdGVfaW1hZ2VzLnBlcnNpc3RlZC5WaWRlbyBTcGxhc2ggSW1hZ2UifQ==" style="background-image: url('data:image/jpeg; base64,/9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAHAAoDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAMF/8QAHxAAAQMDBQAAAAAAAAAAAAAAAQADBQIEEQYTITEy/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAL/xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAMAwEAAhEDEQA/ANuZ1jJSN2ww4zRsVcYB6VBdHHsoiSqx/9k=') !important;" data-background-image="https://le-cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/opt/video-rupcoe-plumbing-heating-and-air-conditioning-inc-1920w.jpg"> <div id="a1b6269f" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1518332866"> <div class="widget-ca4188 u_1518332866 dmCustomWidget" data-lazy-load="" data-title="" id="1518332866" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="ca4188fe92024514a23a858f63ab6658" data-widget-version="18" data-widget-config="eyJ2aWRUaXRsZTEiOiIiLCJ2aWRUaXRsZTIiOiIiLCJwbGF5SWNvbiI6IjxzdmcgeG1sbnM9XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Z1wiIHdpZHRoPVwiMTAwXCIgaGVpZ2h0PVwiMTAwXCIgdmlld0JveD1cIjAgMCAxMDAgMTAwXCI+XG4gIDxwYXRoIGlkPVwiSWNvbl9mYS1yZWd1bGFyLWNpcmNsZS1wbGF5XCIgZGF0YS1uYW1lPVwiSWNvbiBmYS1yZWd1bGFyLWNpcmNsZS1wbGF5XCIgZD1cIk05MC42MjUsNTBBNDAuNjI1LDQwLjYyNSwwLDEsMCw1MCw5MC42MjUsNDAuNjI1LDQwLjYyNSwwLDAsMCw5MC42MjUsNTBaTTAsNTBhNTAsNTAsMCwxLDEsNTAsNTBBNTAsNTAsMCwwLDEsMCw1MFpNMzYuNzc3LDI4LjczYTQuNjkzLDQuNjkzLDAsMCwxLDQuNzQ2LjFMNjkuNjQ4LDQ2LjAxNmE0LjY5Miw0LjY5MiwwLDAsMSwwLDguMDA4TDQxLjUyMyw3MS4yMTFhNC43MzgsNC43MzgsMCwwLDEtNC43NDYuMSw0LjY1OCw0LjY1OCwwLDAsMS0yLjQtNC4wODJWMzIuODEzQTQuNjU4LDQuNjU4LDAsMCwxLDM2Ljc3NywyOC43M1pcIi8+XG48L3N2Zz5cbiIsInBsYXlMaW5rIjp7InZhbHVlIjoiaGlidS12aWRlby1zcGxhc2giLCJ0eXBlIjoicG9wdXAiLCJocmVmIjoiL2hpYnUtdmlkZW8tc3BsYXNoIiwicmF3X3VybCI6Ii9zaXRlL2I0ZDU5NDU2YzIwMzQ2ODk5ZjNhYjkwNTZkNDBiODQ1L2hpYnUtdmlkZW8tc3BsYXNoP25lZT10cnVlJmVkPXRydWUmc2hvd09yaWdpbmFsPXRydWUmcHJldmlldz10cnVlJmRtX3RyeV9tb2RlPXRydWUmZG1fY2hlY2tTeW5jPTEiLCJwb3B1cF90YXJnZXQiOiJoaWJ1LXZpZGVvLXNwbGFzaCIsImxpbmtfdHlwZSI6InBvcHVwIn0sInRyYW5zY3JpcHRpb24iOiJTZXJ2aW5nIFNvdXRoIFBsYWluZmllbGQsIE5KLCBhbmQgQ2VudHJhbCBOSiwgUnVwQ29lIFBsdW1iaW5nLCBIZWF0aW5nICYgQWlyIENvbmRpdGlvbmluZyBJbmMgc3BlY2lhbGl6ZXMgaW4gcGx1bWJpbmcgYW5kIEhWQUMgaW5zdGFsbGF0aW9uLCBzZXJ2aWNlLCBhbmQgcmVwYWlyLiAyNC83IGVtZXJnZW5jeSBzZXJ2aWNlLiBNb25leS1zYXZpbmcgc3BlY2lhbCBvZmZlcnMuIEZpbmFuY2luZyBhdmFpbGFibGUuIENhbGwgdG9kYXkuIiwibmFtZSI6IlJ1cGNvZSBQbHVtYmluZywgSGVhdGluZyAmIEFpciBDb25kaXRpb25pbmcgSW5jIiwiZGVzY3JpcHRpb24iOiJXZSBvZmZlciBBQywgYWlyIHF1YWxpdHksIGNvbW1lcmNpYWwgSFZBQyAmIHBsdW1iaW5nLCBoZWF0aW5nLCBob21lIHBsdW1iaW5nLCByZW1vZGVsaW5nLCAmIHdob2xlLWhvdXNlIGdlbmVyYXRvciBzZXJ2aWNlcy4gMjQvNyBlbWVyZ2VuY3kgc2VydmljZS4gQ2FsbCB1cy4iLCJ0aHVtYm5haWxVcmwiOiJodHRwczovL2Nkbi5oaWJ1d2Vic2l0ZXMuY29tL2I0ZDU5NDU2YzIwMzQ2ODk5ZjNhYjkwNTZkNDBiODQ1L2RtczNyZXAvbXVsdGkvdmlkZW8tcnVwY29lLXBsdW1iaW5nLWhlYXRpbmctYW5kLWFpci1jb25kaXRpb25pbmctaW5jLmpwZyIsInVwbG9hZERhdGUiOiIyMDI2LTA4LTMxIiwiY29udGVudFVybCI6InZpZGVvcy5oaWJ1c3R1ZGlvLmNvbS8yMDI2L3J1cGNvZS1wbHVtYmluZy1oZWF0aW5nLWFuZC1haXItY29uZGl0aW9uaW5nLWluYy03MDA0NDU4MjUyLm1wNCJ9"> <div class="vidWrapper" itemprop="video" itemscope="" itemtype="http://schema.org/VideoObject"> <meta itemprop="name" content="Rupcoe Plumbing, Heating & Air Conditioning Inc"></meta> 
2036 <meta itemprop="duration" content="T1M36S"></meta> 
2037 <meta itemprop="uploadDate" content="2026-08-31"></meta> 
2038 <meta itemprop="thumbnailUrl" content="https://cdn.hibuwebsites.com/b4d59456c20346899f3ab9056d40b845/dms3rep/multi/video-rupcoe-plumbing-heating-and-air-conditioning-inc.jpg"></meta> 
2039 <meta itemprop="contentURL" content="videos.hibustudio.com/2026/rupcoe-plumbing-heating-and-air-conditioning-inc-7004458252.mp4"></meta> 
2040 <meta itemprop="description" content="We offer AC, air quality, commercial HVAC & plumbing, heating, home plumbing, remodeling, & whole-house generator services. 24/7 emergency service. Call us."></meta> 
2041 <meta itemprop="transcript" content=""></meta> 
2042 <div class="vidInner"> <center> <p class="vidTitle1"></p> 
2043 <p class="vidTitle2"></p> 
2044 <div class="playIcon"> <a value="hibu-video-splash" type="popup" href="/hibu-video-splash" raw_url="/site/b4d59456c20346899f3ab9056d40b845/hibu-video-splash?nee=true&ed=true&showOriginal=true&preview=true&dm_try_mode=true&dm_checkSync=1" popup_target="hibu-video-splash" link_type="popup"> <svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewbox="0 0 100 100"> <path id="Icon_fa-regular-circle-play" data-name="Icon fa-regular-circle-play" d="M90.625,50A40.625,40.625,0,1,0,50,90.625,40.625,40.625,0,0,0,90.625,50ZM0,50a50,50,0,1,1,50,50A50,50,0,0,1,0,50ZM36.777,28.73a4.693,4.693,0,0,1,4.746.1L69.648,46.016a4.692,4.692,0,0,1,0,8.008L41.523,71.211a4.738,4.738,0,0,1-4.746.1,4.658,4.658,0,0,1-2.4-4.082V32.813A4.658,4.658,0,0,1,36.777,28.73Z"></path> 
2045</svg> 
2046</a> 
2047</div> 
2048</center> 
2049</div> 
2050 <div class="vidOverlay"></div> 
2051</div> 
2052</div> 
2053</div> 
2054</div> 
2055</div> 
2056</div> 
2057</section> 
2058</div> 
2059 <div data-layout-section="" data-auto="flex-section" id="1248306277" data-flex-id="b6b5923e"> <div class="flex-widgets-container" id="1193597235"></div> 
2060 <section id="e4ad708c" class="flex-element section" data-auto="flex-element-section"> <div id="2bcd41ae" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="4efeffee" class="flex-element group" data-auto="flex-element-group"> <div id="group_b0q" class="flex-element group" data-auto="flex-element-group"> <div id="widget_1j0" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1411819092"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1411819092" style=""> <h6 class="m-size-16"><span style="display: initial; color: var(--color_9);" class="m-font-size-16" m-font-size-set="true">SERVING AREA</span></h6> 
2061</div> 
2062</div> 
2063 <div id="widget_mse" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1644223884"> <div class="dmDividerWrapper clearfix u_1644223884" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1644223884" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1089896382"/></div> 
2064</div> 
2065 <div id="87a53ad2" class="flex-element widget-wrapper YXTSERVICEAREA" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1928174169">
2065 <div class="dmNewParagraph u_1928174169" data-element-type="paragraph" data-version="5" id="1928174169" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJMb2MtMFwiLFwiZm5cIjpcIkZvb3Rlcl9TZXJ2aWNlX0FyZWFzXCJ9In1d" data-diy-text=""><p class="rteBlock">South Plainfield, NJ</p><p class="rteBlock">Middlesex County, NJ</p><p class="rteBlock">Somerset County, NJ</p><p class="rteBlock">Hunterdon County, NJ</p><p class="rteBlock">Essex County, NJ</p><p class="rteBlock">Union County, NJ</p><p class="rteBlock">Morris County, NJ</p><p class="rteBlock">Mercer County, NJ</p><p class="rteBlock">Ocean County, NJ</p><p class="rteBlock">Monmouth County, NJ</p></div> 
2066</div> 
2067 <div id="6efb2802" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1673689127"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1673689127" style=""><p class="m-size-15 t-size-15 m-text-align-left text-align-left size-15"><span class="m-font-size-15 t-font-size-15 font-size-15" style="display: unset; color: var(--color_1);">and surrounding areas</span></p></div> 
2068</div> 
2069 <div id="widget_p1h" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="spacer" data-external-id="1070152015"> <div data-element-type="spacer" class="dmSpacer" id="1070152015"></div> 
2070</div> 
2071</div> 
2072 <div id="d0234170" class="flex-element group" data-auto="flex-element-group"> <div id="cbc8b291" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1563947725"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1563947725" style=""> <h6 class="m-size-16"><span style="display: unset; color: var(--color_9);" class="m-font-size-16" m-font-size-set="true">CONTACT US</span></h6> 
2073</div> 
2074</div> 
2075 <div id="0b55734b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1078075131"> <div class="dmDividerWrapper clearfix u_1078075131" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1078075131" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1927534912"/></div> 
2076</div> 
2077 <div id="502c2597" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="spacer" data-external-id="1918773725"> <div data-element-type="spacer" class="dmSpacer" id="1918773725"></div> 
2078</div> 
2079 <div id="widget_k5l" class="flex-element widget-wrapper YXTPH" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1675397205"> <div class="dmNewParagraph u_1675397205" data-element-type="paragraph" data-version="5" id="1675397205" data-diy-text="" style="" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJjb250ZW50X2xpYnJhcnkuZ2xvYmFsLnBob25lLmxvYy0wIC0gbWFpbiJ9XQ==">(908) 259-4326</div> 
2080</div> 
2081 <div id="1db7e0c7" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="link" data-external-id="1199369758"> <a data-display-type="block" class="align-center dmButtonLink dmWidget dmWwr default dmOnlyButton dmDefaultGradient flexButton u_1199369758" file="false" href="/contact#ICcontactform" data-element-type="dButtonLinkId" id="1199369758"> <span class="iconBg" aria-hidden="true" id="1508234354"> <span class="icon hasFontIcon icon-star" id="1201354356"></span> 
2082</span> 
2083 <span class="text" id="1706213487">Send a Message</span> 
2084</a> 
2085</div> 
2086 <div id="widget_391" class="flex-element widget-wrapper YXTSOCIAL" data-auto="flex-element-widget-wrapper" data-widget-type="socialhub" data-external-id="1064639846"> <div class="align-center text-align-center u_1064639846 dmSocialHub gapSpacing" id="1064639846" dmle_extension="social_hub" data-element-type="social_hub" data-binding="W3siYmluZGluZ05hbWUiOiJzb2NpYWwiLCJ2YWx1ZSI6ImNvbnRlbnRfbGlicmFyeS5nbG9iYWwuc29jaWFsIn1d" wr="true" networks="" icon="true" surround="true" adwords=""> <div class="socialHubWrapper"> <div class="socialHubInnerDiv " data-auto="social-hub-inner"> <a href="https://instagram.com/rupcoe" target="_blank" dm_dont_rewrite_url="true" aria-label="instagram" onclick="dm_gaq_push_event &amp;&amp;
2086 dm_gaq_push_event(&apos;socialLink&apos;, &apos;click&apos;, &apos;Instagram&apos;)"> <span class="dmSocialInstagram dm-social-icons-instagram oneIcon socialHubIcon style5" aria-hidden="true" data-hover-effect="zoomout"></span> 
2087</a> 
2088</div> 
2089</div> 
2090</div> 
2091</div> 
2092</div> 
2093 <div id="f2debc4b" class="flex-element group" data-auto="flex-element-group"> <div id="65e14480" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="1332286019"> <div class="dmNewParagraph" data-element-type="paragraph" data-version="5" id="1332286019" style=""> <h6 class="m-size-16"><span style="color: var(--color_9); display: unset;" class="m-font-size-16" m-font-size-set="true">BUSINESS HOURS</span></h6> 
2094</div> 
2095</div> 
2096 <div id="c7f3ddf3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="divider" data-external-id="1463975724"> <div class="dmDividerWrapper clearfix u_1463975724" data-element-type="dDividerId" data-layout="divider-style-1" data-widget-version="2" id="1463975724" layout="divider-gradient-line"><hr class="dmDivider" style="border-width:2px; border-top-style:solid; color:grey;" id="1907202694"/></div> 
2097</div> 
2098 <div id="618f39ae" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="spacer" data-external-id="1700615418"> <div data-element-type="spacer" class="dmSpacer" id="1700615418"></div> 
2099</div> 
2100 <div id="widget_9dv" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="paragraph" data-external-id="4279627036"> <div class="dmNewParagraph u_4279627036" data-element-type="paragraph" data-version="5" id="4279627036" data-binding="W3siYmluZGluZ05hbWUiOiJ0ZXh0IiwidmFsdWUiOiJTVEFUSUNfUEFHRV9DT0xMRUNUSU9OLntcImNuXCI6XCJMb2MtMFwiLFwiZm5cIjpcIkFkZGl0aW9uYWxfSG91cnNcIn0ifV0=" data-diy-text=""><p class="rteBlock">Phones Answered 24/7 - Emergency Service Available 24/7</p></div> 
2101</div> 
2102</div> 
2103</div> 
2104</div> 
2105</section> 
2106</div> 
2107 <div data-layout-section="" data-auto="flex-section" id="1344609592" data-flex-id="2f831c0f"> <div class="flex-widgets-container" id="1374258866"></div> 
2108 <section id="71171ca4" class="flex-element section" data-auto="flex-element-section"> <div id="0253b4c6" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="d027d36f" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-mobile=""> <div id="00841c9d" class="flex-element group" data-auto="flex-element-group"> <div id="7e326d80" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-hidden-on-desktop="" data-external-id="1869341664"> <nav class="effect-bottom2 u_1869341664 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="horizontal_nav_layout_1" layout-sub="submenu_horizontal_1" data-show-vertical-sub-items="HOVER" id="1869341664" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="HORIZONTAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/" class="unifiednav__item unav-item unav-top-item  dmNavItemSelected  dmUDNavigationItem_00  " target="" data-target-page-alias="" aria-current="page" data-auto="selected-page"> <span class="nav-item-text " data-link-text="
2109         Home
2110        " data-auto="page-text-style">Home<span class="icon icon-angle-down"></span> 
2111</span> 
2112</a> 
2113</li> 
2114 <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/about" class="unifiednav__item unav-item unav-top-item  dmUDNavigationItem_010101599305  unifiednav__item_has-sub-nav" target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="About" data-auto="page-text-style">About<span class="icon icon-angle-down"></span> 
2115</span> 
2116</a> 
2117 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/careers" class="unifiednav__item unav-item   dmUDNavigationItem_010101402669  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Careers" data-auto="page-text-style">Careers<span class="icon icon-angle-right"></span> 
2118</span> 
2119</a> 
2120</li> 
2121 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/faqs" class="unifiednav__item unav-item   dmUDNavigationItem_010101347615  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="FAQs" data-auto="page-text-style">FAQs<span class="icon icon-angle-right"></span> 
2122</span> 
2123</a> 
2124</li> 
2125 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/reviews" class="unifiednav__item unav-item   dmUDNavigationItem_010101840192  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Reviews" data-auto="page-text-style">Reviews<span class="icon icon-angle-right"></span> 
2126</span> 
2127</a> 
2128</li> 
2129 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/finance" class="unifiednav__item unav-item   dmUDNavigationItem_0101016848  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Finance" data-auto="page-text-style">Finance<span class="icon icon-angle-right"></span> 
2130</span> 
2131</a> 
2132</li> 
2133 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/special-offers" class="unifiednav__item unav-item   dmUDNavigationItem_010101324312  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Special Offers" data-auto="page-text-style">Special Offers<span class="icon icon-angle-right"></span> 
2134</span> 
2135</a> 
2136</li> 
2137</ul> 
2138</li> 
2139 <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/contact" class="unifiednav__item unav-item unav-top-item  dmUDNavigationItem_010101152840  unifiednav__item_has-sub-nav" target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Contact" data-auto="page-text-style">Contact<span class="icon icon-angle-down"></span> 
2140</span> 
2141</a> 
2142 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/request-service-form-page" class="unifiednav__item unav-item   dmUDNavigationItem_010101150108  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Request Service" data-auto="page-text-style">Request Service<span class="icon icon-angle-right"></span> 
2143</span> 
2144</a> 
2145</li> 
2146</ul> 
2147</li> 
2148 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/empty" class="unifiednav__item unav-item unav-top-item  dmUDNavigationItem_010101434187  " target="" data-target-page-alias=""> <span class="nav-item-text " data-link-text="Poly Social Test" data-auto="page-text-style">Poly Social Test<span class="icon icon-angle-down"></span> 
2149</span> 
2150</a> 
2151</li> 
2152</ul> 
2153</nav> 
2154</div> 
2155</div> 
2156</div> 
2157</div> 
2158</section> 
2159</div> 
2160 <div data-layout-section="" data-auto="flex-section" id="1741943752" data-flex-id="54290af5" class="" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1020340060"></div> 
2161 <section id="0c6cf26f" class="flex-element section" data-auto="flex-element-section"> <div id="3989af5b" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="24eff763" class="flex-element group" data-auto="flex-element-group" data-hidden-on-tablet="" data-hidden-on-desktop="">
2161 <div id="7ca01c83" class="flex-element group" data-auto="flex-element-group"> <div id="34b9f983" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ssrbreadcrumbs" data-external-id="1424178368"> <div id="1424178368" dmle_extension="ssrbreadcrumbs" data-element-type="ssrbreadcrumbs" class=""><span id="ssrWrap-1424178368" ><style data-styled="true" data-styled-version="5.3.11">.gCAEgf{color:rgba(102,102,102,1);}/*!sc*/
2162data-styled.g18[id="sc-dLMFU"]{content:"gCAEgf,"}/*!sc*/
2163#dm .dewijn.dewijn{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;gap:10px;}/*!sc*/
2164@media (min-width:768px){#dm .dewijn.dewijn{font-size:11px;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;}}/*!sc*/
2165@media (max-width:767px){#dm .dewijn.dewijn{font-size:11px;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;}}/*!sc*/
2166data-styled.g19[id="sc-eDPEul"]{content:"dewijn,"}/*!sc*/
2167</style><div data-auto="runtime-breadcrumbs-widget"><nav class="sc-eDPEul dewijn bc-root dmBlockElement" aria-label="Breadcrumbs"><div class="sc-dLMFU gCAEgf bc-current-item" data-auto="bc-current-item">Home</div></nav></div></span></div> 
2168</div> 
2169</div> 
2170</div> 
2171</div> 
2172</section> 
2173</div> 
2174</div> 
2175 <div id="poweredBy" class="poweredBy poweredByHibu"> <div class="poweredLine1"> <a href="http://budurl.com/hibuprivacy" target="_blank">Privacy Policy</a>| <a href="http://budurl.com/hibucookie" target="_blank">Do Not Share My Information</a>| <a href="http://budurl.com/hibuconditionsofuse" target="_blank">Conditions of Use</a>| <a href="http://budurl.com/hibunotice" target="_blank">Notice and Take Down Policy</a>| <a href="http://b.link/accessibility" target="_blank">Website Accessibility Policy</a> 
2176</div> 
2177 <div class="poweredLine2">&copy; <span id="hibuYear">2026</span> 
2178The content on this website is owned by us and our licensors. Do not copy any content (including images) without our consent.</div> 
2179</div> 
2180</div> 
2181 <div id="dmFlexStickyContainer" class="flex_hfcontainer"> <div id="flex-sticky"> <div data-layout-section="" data-auto="flex-section" id="1801865596" data-flex-id="8a9cfcfc"> <div class="flex-widgets-container" id="1548435334"></div> 
2182 <section id="4a1fde56" class="flex-element section" data-auto="flex-element-section"> <div id="381d99f1" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="75ba1c41" class="flex-element group" data-auto="flex-element-group"> <div id="4f4d3298" class="flex-element group" data-auto="flex-element-group"> <div id="72168835" class="flex-element inner-grid" data-auto="flex-element-inner-grid"> <div id="widget_21v" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="graphic" data-keep-proportion="true" data-hidden-on-tablet="" data-hidden-on-desktop="" data-external-id="1389791254"> <div class="graphicWidget graphicWidgetV2 graphicWidgetV3 u_1389791254" data-element-type="graphic" data-widget-type="graphic" id="1389791254" data-layout="graphic-style-5"> <a href="/hibu-eng-menu" id="1685777839" link_type="popup" file="false" popup_target="hibu-eng-menu" aria-label="Contact Us"> <svg width="100%" height="100%" version="1.1" viewbox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" id="1047488693" class="svg u_1047488693" data-icon-custom="true" aria-labelledby="1665903033"> <title id="1665903033">Contact Us</title> 
2183 <path d="m90.793 43.289h-34.086v-34.078c0-3.707-3.0039-6.707-6.707-6.707s-6.707 3-6.707 6.707v34.078h-34.086c-3.7031 0-6.707 3.0039-6.707 6.707s3.0039 6.707 6.707 6.707h34.086v34.086c0 3.707 3.0039 6.707 6.707 6.707s6.707-3 6.707-6.707v-34.086h34.086c3.7031 0 6.707-3 6.707-6.707 0-3.707-3.0039-6.707-6.707-6.707z"></path> 
2184</svg> 
2185</a> 
2186</div> 
2187</div> 
2188 <div id="widget_fpt" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="3053959938"> <div class="widget-0c06e7 dmCustomWidget" data-lazy-load="true" data-title="" id="3053959938" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="0c06e70da5474879a21daa0d3b1192c0" data-widget-version="3" data-widget-config="e30="> <div class="cb1-theme-injector"></div> 
2189</div> 
2190</div> 
2191 <div id="widget_mnf" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="3931136021"> <div class="widget-8eedf9 dmCustomWidget" data-lazy-load="true" data-title="" id="3931136021" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="8eedf98f346a42e0a55f9d6152653187" data-widget-version="2" data-widget-config="e30="> <div class="cb0-theme-injector"></div> 
2192</div> 
2193</div> 
2194 <div id="widget_c5v" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="3659398620"> <div class="widget-1c20f5 dmCustomWidget" data-lazy-load="true" data-title="" id="3659398620" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="1c20f508b5474ea2b929959f7d5eff3d" data-widget-version="3" data-widget-config="e30="> <div class="dcbh3-theme-injector"></div> 
2195</div> 
2196</div> 
2197 <div id="widget_doa" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="1889303332"> <div class="widget-403a95 dmCustomWidget" data-lazy-load="true" data-title="" id="1889303332" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="403a957a34f84bc8a342137c59c93b31" data-widget-version="3" data-widget-config="e30="> <div class="cb2-theme-injector"></div> 
2198</div> 
2199</div> 
2200 <div id="widget_301" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="986065271"> <div class="widget-e0b1e5 dmCustomWidget" data-lazy-load="true" data-title="" id="986065271" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="e0b1e5b2d4394e69a98bd276fd6afe8b" data-widget-version="8" data-widget-config="e30="> <div class="cb1-theme-injector"></div> 
2201</div> 
2202</div> 
2203 <div id="widget_61p" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="3078440210"> <div class="widget-2133bf dmCustomWidget" data-lazy-load="true" data-title="" id="3078440210" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="2133bfee2cb24e0c9db9468154881b94" data-widget-version="3" data-widget-config="e30="> <div class="cb1-theme-injector"></div> 
2204</div> 
2205</div> 
2206</div> 
2207</div> 
2208</div> 
2209</div> 
2210</section> 
2211</div> 
2212</div> 
2213</div> 
2214 <div id="dmFlexMegaMenuContainer" class="flex_hfcontainer"> <div id="multi-mega-menu"> <div id="mega-menu-4dc8a903" name="Services"> <div data-flex-id="4dc8a903" data-auto="flex-section" id="1276875426" data-layout-section="" data-page-element-type="section" data-page-element-id="1232093299" class="" data-hidden-on-desktop="true"> <div class="flex-widgets-container" id="1497300766"></div> 
2215 <section id="6c5257e3" class="flex-element section" data-auto="flex-element-section"> <div id="2cb3e9f9" class="flex-element grid lazy" data-auto="flex-element-grid" data-layout-grid="" data-background-image=""> <div id="27581a62" class="flex-element group" data-auto="flex-element-group" data-hidden-on-desktop=""> <div id="d200b2f7" class="flex-element group" data-auto="flex-element-group"> <div id="widget_cq9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1459911792"> <nav class="u_1459911792 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1459911792" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2216</span> 
2217</a> 
2218 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">
2218Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2219</span> 
2220</a> 
2221</li> 
2222</ul> 
2223</li> 
2224</ul> 
2225</nav> 
2226</div> 
2227 <div id="b51d88ee" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1723750523"> <nav class="u_1723750523 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1723750523" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2228</span> 
2229</a> 
2230 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2231</span> 
2232</a> 
2233</li> 
2234</ul> 
2235</li> 
2236</ul> 
2237</nav> 
2238</div> 
2239 <div id="5aa033c8" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1610592242"> <nav class="u_1610592242 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1610592242" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2240</span> 
2241</a> 
2242 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2243</span> 
2244</a> 
2245</li> 
2246</ul> 
2247</li> 
2248</ul> 
2249</nav> 
2250</div> 
2251</div> 
2252 <div id="ad9cb284" class="flex-element group" data-auto="flex-element-group"> <div id="04df1da2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1136789265"> <nav class="u_1136789265 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1136789265" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2253</span> 
2254</a> 
2255 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">
2255Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2256</span> 
2257</a> 
2258</li> 
2259</ul> 
2260</li> 
2261</ul> 
2262</nav> 
2263</div> 
2264 <div id="f09d8243" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1968129260"> <nav class="u_1968129260 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1968129260" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2265</span> 
2266</a> 
2267 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2268</span> 
2269</a> 
2270</li> 
2271</ul> 
2272</li> 
2273</ul> 
2274</nav> 
2275</div> 
2276 <div id="899c2eb2" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1507374835"> <nav class="u_1507374835 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1507374835" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2277</span> 
2278</a> 
2279 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2280</span> 
2281</a> 
2282</li> 
2283</ul> 
2284</li> 
2285</ul> 
2286</nav> 
2287</div> 
2288</div> 
2289 <div id="0190983d" class="flex-element group" data-auto="flex-element-group"> <div id="7b991c9b" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1801032432"> <nav class="u_1801032432 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1801032432" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2290</span> 
2291</a> 
2292 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">
2292Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2293</span> 
2294</a> 
2295</li> 
2296</ul> 
2297</li> 
2298</ul> 
2299</nav> 
2300</div> 
2301 <div id="39a585e9" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1338154768"> <nav class="u_1338154768 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1338154768" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2302</span> 
2303</a> 
2304 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2305</span> 
2306</a> 
2307</li> 
2308</ul> 
2309</li> 
2310</ul> 
2311</nav> 
2312</div> 
2313 <div id="4310a1b0" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1201338092"> <nav class="u_1201338092 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1201338092" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" aria-haspopup="true" aria-expanded="false" data-sub-nav-menu="true" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/what-we-do/~page-item~" class="unifiednav__item unav-item unav-top-item    unifiednav__item_has-sub-nav" target="" data-target-page-alias="cat-landing"> <span class="nav-item-text " data-link-text="Cat-Landing" data-auto="page-text-style">Cat-Landing<span class="icon icon-angle-down"></span> 
2314</span> 
2315</a> 
2316 <ul role="menu" class="unifiednav__container unifiednav__container_sub-nav unav-sub" data-depth="0" data-auto="sub-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="1"> <a href="/services/~page-item~" class="unifiednav__item unav-item     " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-right" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2317</span> 
2318</a> 
2319</li> 
2320</ul> 
2321</li> 
2322</ul> 
2323</nav> 
2324</div> 
2325</div> 
2326 <div id="group_9n6" class="flex-element group" data-auto="flex-element-group"> <div id="fe7f7167" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="ONELinksMenu" data-external-id="1401180572"> <nav class="u_1401180572 unifiednav_vertical effect-bottom2 main-navigation unifiednav dmLinksMenu" data-auto="links-menu" role="navigation" layout-main="vertical_nav_layout_2" layout-sub="" data-show-vertical-sub-items="SHOW" id="1401180572" dmle_extension="onelinksmenu" data-element-type="onelinksmenu" data-logo-src="" alt="" data-nav-structure="VERTICAL" wr="true" icon="true" surround="true" adwords="" navigation-id="unifiedNav"> <ul role="menubar" class="unifiednav__container unav-top  " data-auto="navigation-pages"> <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/services/~page-item~" class="unifiednav__item unav-item unav-top-item    " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">
2326Core Page<span class="icon icon-angle-down" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2327</span> 
2328</a> 
2329</li> 
2330 <li role="menuitem" class=" unifiednav__item-wrap " data-auto="more-pages" data-depth="0"> <a href="/services/~page-item~" class="unifiednav__item unav-item unav-top-item    " target="" data-target-page-alias="core-page"> <span class="nav-item-text " data-link-text="Core Page" data-auto="page-text-style">Core Page<span class="icon icon-angle-down" data-hidden-on-mobile="" data-hidden-on-desktop="" data-hidden-on-tablet=""></span> 
2331</span> 
2332</a> 
2333</li> 
2334</ul> 
2335</nav> 
2336</div> 
2337</div> 
2338</div> 
2339</div> 
2340</section> 
2341</div> 
2342 <div data-layout-section="" data-auto="flex-section" id="1582186687" data-flex-id="2a501ecf"> <div class="flex-widgets-container" id="1924641454"></div> 
2343 <section id="c55100d8" class="flex-element section" data-auto="flex-element-section"> <div id="57b153fb" class="flex-element grid" data-auto="flex-element-grid" data-layout-grid=""> <div id="08465346" class="flex-element group" data-auto="flex-element-group"> <div id="5a572945" class="flex-element group" data-auto="flex-element-group"> <div id="widget_gf3" class="flex-element widget-wrapper" data-auto="flex-element-widget-wrapper" data-widget-type="custom" data-external-id="224877366"> <div class="widget-3e38ec u_224877366 dmCustomWidget" data-lazy-load="true" data-title="" id="224877366" dmle_extension="custom_extension" data-element-type="custom_extension" icon="false" surround="false" data-widget-id="3e38ec713838428586c343d139994b6f" data-widget-version="58" data-widget-config="eyJjb2x1bW5Db3VudCI6IjMiLCJ1bmRlcmxpbmVTZWxlY3RlZCI6dHJ1ZSwic2VsZWN0ZWRCZ0NvbG9yIjoidHJhbnNwYXJlbnQiLCJleGNsdWRlUGFyZW50cyI6ImhvbWUsIGFib3V0LCBjb250YWN0LCBnYWxsZXJ5IiwiaW5jbHVkZVBhcmVudHMiOiIiLCJjaGV2cm9uSWNvbiI6IjxzdmcgeG1sbnM9XCJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Z1wiIHZpZXdCb3g9XCIwIDAgMjQgMjRcIiBmaWxsPVwiY3VycmVudENvbG9yXCIgd2lkdGg9XCIxNlwiIGhlaWdodD1cIjE2XCI+PHBhdGggZD1cIk03LjQxIDguNTlMMTIgMTMuMTdsNC41OS00LjU4TDE4IDEwbC02IDYtNi02IDEuNDEtMS40MXpcIi8+PC9zdmc+IiwiZXhwYW5kQWxsIjp0cnVlfQ=="><!-- === Real-Data Navigation Grid Widget (parent remains a hyperlink) === --> <div id="nav-grid-widget" data-columns="3" data-source="auto"></div> 
2344</div> 
2345</div> 
2346</div> 
2347</div> 
2348</div> 
2349</section> 
2350</div> 
2351</div> 
2352</div> 
2353</div> 
2354 <div id="swh-ed083e545ae345f5a0b33af6b05a305a"> <div data-wrapper-source="appstore"><!--audioeye-managed:accessibility--> 
2354<script data-audioeye-managed="accessibility">    if (window.location.search.match(/[?,&]preview=true/) && window.top !== window) {      window.__AudioEyePreviewMode=true;    }  </script>
2354 
2355 
2355<script type="text/javascript">!function(){    var b=function(){      window.__AudioEyeSiteHash = "275a630b9b76cdce5a7b995c9b2dcdd5";      window.__AudioEyeInstallSource = "duda";      var a=document.createElement("script");      a.src="https://wsv3cdn.audioeye-services.com/aem.js";      a.type="text/javascript";      a.setAttribute("async","");      document.getElementsByTagName("body")[0].appendChild(a)};      "complete"!==document.readyState?window.addEventListener?window.addEventListener("load",b):window.attachEvent&&window.attachEvent("onload",b):b()}();    </script>
2355 
2356</div> 
2357</div> 
2358</div> 
2359</div> 
2360</div> 
2361</div> 
2362</div> 
2363</div> 
2364
2365    </div>
2366</div>
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406<script>
2407    var isWLR = true;
2408
2409    window.customWidgetsFunctions = {};
2410    window.customWidgetsStrings = {};
2411    window.collections = {};
2412    window.currentLanguage = "ENGLISH"
2413    window.isSitePreview = false;
2414</script>
2414
2415
2416
2417
2418<script>
2419    window.customWidgetsFunctions["8ff5270554664bddaecbe7a0b9e45c81~14"] = function (element, data, api) {
2420        window.dmAPI.runOnReady(
2421
2422  'ucw-unified-' +
2423
2424    (
2425
2426      element.id ||
2427
2428      Math.random().toString(36).slice(2)
2429
2430    ),
2431
2432  async () => {
2433
2434    const root = element.querySelector('.ucw-wrapper');
2435
2436
2437
2438    if (!root) return;
2439
2440
2441
2442    const calloutEl = root.querySelector('.ucw-callout');
2443
2444    const calloutMount = root.querySelector('.ucw-callout__mount');
2445
2446    const couponEl = root.querySelector('.ucw-coupon');
2447
2448    const couponGrid = root.querySelector('.ucw-coupon__grid');
2449
2450    const actionTemplate = root.querySelector('.ucw-action-template');
2451
2452
2453
2454    if (
2455
2456      !calloutEl ||
2457
2458      !calloutMount ||
2459
2460      !couponEl ||
2461
2462      !couponGrid ||
2463
2464      !actionTemplate
2465
2466    ) {
2467
2468      root.hidden = true;
2469
2470      return;
2471
2472    }
2473
2474
2475
2476    const config = data.config || {};
2477
2478
2479
2480    /*
2481
2482      Prevent an older async run from overwriting a newer
2483
2484      Duda rerender of the same widget instance.
2485
2486    */
2487
2488    const instanceToken = {};
2489
2490    root.__ucwInstanceToken = instanceToken;
2491
2492
2493
2494    function isCurrentInstance() {
2495
2496      return root.__ucwInstanceToken === instanceToken;
2497
2498    }
2499
2500
2501
2502    if (root.__ucwEntranceObserver) {
2503
2504      root.__ucwEntranceObserver.disconnect();
2505
2506      root.__ucwEntranceObserver = null;
2507
2508    }
2509
2510
2511
2512    function safeTrim(value) {
2513
2514      return value == null
2515
2516        ? ''
2517
2518        : String(value).trim();
2519
2520    }
2521
2522
2523
2524    function stripHtml(value) {
2525
2526      const temporaryElement =
2527
2528        document.createElement('div');
2529
2530
2531
2532      temporaryElement.innerHTML =
2533
2534        safeTrim(value);
2535
2536
2537
2538      return safeTrim(
2539
2540        temporaryElement.textContent ||
2541
2542        temporaryElement.innerText ||
2543
2544        ''
2545
2546      );
2547
2548    }
2549
2550
2551
2552    function normalizeBoolean(value, fallbackValue) {
2553
2554      if (typeof value === 'boolean') {
2555
2556        return value;
2557
2558      }
2559
2560
2561
2562      if (value === 1 || value === '1') {
2563
2564        return true;
2565
2566      }
2567
2568
2569
2570      if (value === 0 || value === '0') {
2571
2572        return false;
2573
2574      }
2575
2576
2577
2578      const normalizedValue =
2579
2580        safeTrim(value).toLowerCase();
2581
2582
2583
2584      if (
2585
2586        [
2587
2588          'true',
2589
2590          'yes',
2591
2592          'on',
2593
2594          'enabled'
2595
2596        ].includes(normalizedValue)
2597
2598      ) {
2599
2600        return true;
2601
2602      }
2603
2604
2605
2606      if (
2607
2608        [
2609
2610          'false',
2611
2612          'no',
2613
2614          'off',
2615
2616          'disabled'
2617
2618        ].includes(normalizedValue)
2619
2620      ) {
2621
2622        return false;
2623
2624      }
2625
2626
2627
2628      return fallbackValue;
2629
2630    }
2631
2632
2633
2634    function clampNumber(
2635
2636      value,
2637
2638      minimum,
2639
2640      maximum,
2641
2642      fallbackValue
2643
2644    ) {
2645
2646      const parsedValue = Number(value);
2647
2648
2649
2650      if (!Number.isFinite(parsedValue)) {
2651
2652        return fallbackValue;
2653
2654      }
2655
2656
2657
2658      return Math.min(
2659
2660        Math.max(parsedValue, minimum),
2661
2662        maximum
2663
2664      );
2665
2666    }
2667
2668
2669
2670    function extractImageUrl(imageValue) {
2671
2672      if (!imageValue) return '';
2673
2674
2675
2676      if (typeof imageValue === 'string') {
2677
2678        return safeTrim(imageValue);
2679
2680      }
2681
2682
2683
2684      if (typeof imageValue !== 'object') {
2685
2686        return '';
2687
2688      }
2689
2690
2691
2692      const possibleValues = [
2693
2694        imageValue.url,
2695
2696        imageValue.src,
2697
2698        imageValue.image_url,
2699
2700        imageValue.original_url,
2701
2702        imageValue.originalUrl,
2703
2704        imageValue.full,
2705
2706        imageValue.data && imageValue.data.url,
2707
2708        imageValue.data && imageValue.data.src
2709
2710      ];
2711
2712
2713
2714      for (const possibleValue of possibleValues) {
2715
2716        const imageUrl =
2717
2718          safeTrim(possibleValue);
2719
2720
2721
2722        if (imageUrl) {
2723
2724          return imageUrl;
2725
2726        }
2727
2728      }
2729
2730
2731
2732      return '';
2733
2734    }
2735
2736
2737
2738    function extractLinkValue(linkValue) {
2739
2740      if (!linkValue) return '';
2741
2742
2743
2744      if (typeof linkValue === 'string') {
2745
2746        return safeTrim(linkValue);
2747
2748      }
2749
2750
2751
2752      if (typeof linkValue !== 'object') {
2753
2754        return '';
2755
2756      }
2757
2758
2759
2760      const possibleValues = [
2761
2762        linkValue.url,
2763
2764        linkValue.href,
2765
2766        linkValue.runtime_url,
2767
2768        linkValue.raw_url,
2769
2770        linkValue.value
2771
2772      ];
2773
2774
2775
2776      for (const possibleValue of possibleValues) {
2777
2778        const link =
2779
2780          safeTrim(possibleValue);
2781
2782
2783
2784        if (link) {
2785
2786          return link;
2787
2788        }
2789
2790      }
2791
2792
2793
2794      return '';
2795
2796    }
2797
2798
2799
2800    function getLastSlug(value) {
2801
2802      let normalizedValue =
2803
2804        safeTrim(value).toLowerCase();
2805
2806
2807
2808      if (!normalizedValue) return '';
2809
2810
2811
2812      try {
2813
2814        if (
2815
2816          /^https?:\/\//i.test(normalizedValue)
2817
2818        ) {
2819
2820          normalizedValue =
2821
2822            new URL(normalizedValue).pathname;
2823
2824        }
2825
2826      } catch (e) {
2827
2828        // Retain the original value.
2829
2830      }
2831
2832
2833
2834      normalizedValue = normalizedValue
2835
2836        .split('?')[0]
2837
2838        .split('#')[0]
2839
2840        .replace(/^\/+|\/+$/g, '');
2841
2842
2843
2844      const pathParts =
2845
2846        normalizedValue
2847
2848          .split('/')
2849
2850          .filter(Boolean);
2851
2852
2853
2854      return pathParts.length
2855
2856        ? pathParts[pathParts.length - 1]
2857
2858        : '';
2859
2860    }
2861
2862
2863
2864    function getCurrentSlug() {
2865
2866      return getLastSlug(
2867
2868        window.location.pathname || ''
2869
2870      );
2871
2872    }
2873
2874
2875
2876    function getSitePrefixFromOwnFrame() {
2877
2878      const ownPath =
2879
2880        window.location.pathname || '';
2881
2882
2883
2884      const match = ownPath.match(
2885
2886        /^(\/site\/[^/]+)/
2887
2888      );
2889
2890
2891
2892      return match ? match[1] : '';
2893
2894    }
2895
2896
2897
2898    function getPreviewParameters() {
2899
2900      const output = new URLSearchParams();
2901
2902
2903
2904      try {
2905
2906        const currentParameters =
2907
2908          new URLSearchParams(
2909
2910            window.location.search || ''
2911
2912          );
2913
2914
2915
2916        [
2917
2918          'showOriginal',
2919
2920          'preview',
2921
2922          'insitepreview',
2923
2924          'dm_device',
2925
2926          't',
2927
2928          'dm_try_mode',
2929
2930          'dm_checkSync'
2931
2932        ].forEach(key => {
2933
2934          if (currentParameters.has(key)) {
2935
2936            output.set(
2937
2938              key,
2939
2940              currentParameters.get(key)
2941
2942            );
2943
2944          }
2945
2946        });
2947
2948      } catch (e) {
2949
2950        // Return an empty parameter set.
2951
2952      }
2953
2954
2955
2956      return output;
2957
2958    }
2959
2960
2961
2962    function normalizeButtonLink(linkValue) {
2963
2964      const rawLink =
2965
2966        extractLinkValue(linkValue);
2967
2968
2969
2970      if (!rawLink) return '';
2971
2972
2973
2974      if (
2975
2976        /^(javascript|data|vbscript):/i.test(
2977
2978          rawLink
2979
2980        )
2981
2982      ) {
2983
2984        return '';
2985
2986      }
2987
2988
2989
2990      if (
2991
2992        /^(https?:|tel:|mailto:|sms:)/i.test(
2993
2994          rawLink
2995
2996        ) ||
2997
2998        rawLink.startsWith('//') ||
2999
3000        rawLink.startsWith('#')
3001
3002      ) {
3003
3004        return rawLink;
3005
3006      }
3007
3008
3009
3010      let pathPart = rawLink;
3011
3012      let hashPart = '';
3013
3014
3015
3016      const hashIndex =
3017
3018        pathPart.indexOf('#');
3019
3020
3021
3022      if (hashIndex !== -1) {
3023
3024        hashPart = pathPart.slice(hashIndex);
3025
3026        pathPart = pathPart.slice(0, hashIndex);
3027
3028      }
3029
3030
3031
3032      let queryString = '';
3033
3034      const queryIndex =
3035
3036        pathPart.indexOf('?');
3037
3038
3039
3040      if (queryIndex !== -1) {
3041
3042        queryString = pathPart.slice(queryIndex + 1);
3043
3044        pathPart = pathPart.slice(0, queryIndex);
3045
3046      }
3047
3048
3049
3050      pathPart = safeTrim(pathPart);
3051
3052
3053
3054      if (!pathPart.startsWith('/')) {
3055
3056        pathPart = '/' + pathPart;
3057
3058      }
3059
3060
3061
3062      const sitePrefix =
3063
3064        getSitePrefixFromOwnFrame();
3065
3066
3067
3068      if (
3069
3070        sitePrefix &&
3071
3072        !pathPart.startsWith(sitePrefix + '/')
3073
3074      ) {
3075
3076        pathPart = sitePrefix + pathPart;
3077
3078      }
3079
3080
3081
3082      const combinedParameters =
3083
3084        new URLSearchParams(queryString);
3085
3086
3087
3088      getPreviewParameters().forEach(
3089
3090        (parameterValue, parameterName) => {
3091
3092          if (
3093
3094            !combinedParameters.has(
3095
3096              parameterName
3097
3098            )
3099
3100          ) {
3101
3102            combinedParameters.set(
3103
3104              parameterName,
3105
3106              parameterValue
3107
3108            );
3109
3110          }
3111
3112        }
3113
3114      );
3115
3116
3117
3118      const combinedQuery =
3119
3120        combinedParameters.toString();
3121
3122
3123
3124      return (
3125
3126        pathPart +
3127
3128        (
3129
3130          combinedQuery
3131
3132            ? '?' + combinedQuery
3133
3134            : ''
3135
3136        ) +
3137
3138        hashPart
3139
3140      );
3141
3142    }
3143
3144
3145
3146    function isEditorCanvasOnly() {
3147
3148      try {
3149
3150        const topHref =
3151
3152          window.top.location.href || '';
3153
3154
3155
3156        if (topHref.includes('#preview')) {
3157
3158          return false;
3159
3160        }
3161
3162
3163
3164        if (topHref.includes('/home/site/')) {
3165
3166          return true;
3167
3168        }
3169
3170      } catch (e) {
3171
3172        return Boolean(data.inEditor);
3173
3174      }
3175
3176
3177
3178      return Boolean(data.inEditor);
3179
3180    }
3181
3182
3183
3184    if (root.__ucwEditorClickHandler) {
3185
3186      root.removeEventListener(
3187
3188        'click',
3189
3190        root.__ucwEditorClickHandler,
3191
3192        true
3193
3194      );
3195
3196    }
3197
3198
3199
3200    root.__ucwEditorClickHandler =
3201
3202      event => {
3203
3204        const actionLink =
3205
3206          event.target.closest(
3207
3208            '.ucw-card__actionLink'
3209
3210          );
3211
3212
3213
3214        if (
3215
3216          actionLink &&
3217
3218          root.contains(actionLink) &&
3219
3220          isEditorCanvasOnly()
3221
3222        ) {
3223
3224          event.preventDefault();
3225
3226          event.stopPropagation();
3227
3228          event.stopImmediatePropagation();
3229
3230        }
3231
3232      };
3233
3234
3235
3236    root.addEventListener(
3237
3238      'click',
3239
3240      root.__ucwEditorClickHandler,
3241
3242      true
3243
3244    );
3245
3246
3247
3248    const prefersReducedMotion =
3249
3250      window.matchMedia
3251
3252        ? window
3253
3254            .matchMedia(
3255
3256              '(prefers-reduced-motion: reduce)'
3257
3258            )
3259
3260            .matches
3261
3262        : false;
3263
3264
3265
3266    /*
3267
3268      Shared across every copy of the widget on the page.
3269
3270    */
3271
3272    function getSharedStore() {
3273
3274      if (!window.__ucwUnifiedSharedStore) {
3275
3276        window.__ucwUnifiedSharedStore = {
3277
3278          collectionsApiPromise: null,
3279
3280          contentLibraryPromise: null,
3281
3282          collectionPromises: {},
3283
3284          defaultLogoPromise: null
3285
3286        };
3287
3288      }
3289
3290
3291
3292      return window.__ucwUnifiedSharedStore;
3293
3294    }
3295
3296
3297
3298    function getCollectionsApi() {
3299
3300      const sharedStore =
3301
3302        getSharedStore();
3303
3304
3305
3306      if (!sharedStore.collectionsApiPromise) {
3307
3308        sharedStore.collectionsApiPromise =
3309
3310          window.dmAPI
3311
3312            .loadCollectionsAPI()
3313
3314            .catch(error => {
3315
3316              sharedStore.collectionsApiPromise =
3317
3318                null;
3319
3320
3321
3322              throw error;
3323
3324            });
3325
3326      }
3327
3328
3329
3330      return sharedStore.collectionsApiPromise;
3331
3332    }
3333
3334
3335
3336    function loadCollection(collectionName) {
3337
3338      const sharedStore =
3339
3340        getSharedStore();
3341
3342
3343
3344      if (
3345
3346        !sharedStore.collectionPromises[
3347
3348          collectionName
3349
3350        ]
3351
3352      ) {
3353
3354        sharedStore.collectionPromises[
3355
3356          collectionName
3357
3358        ] = getCollectionsApi()
3359
3360          .then(collections =>
3361
3362            collections
3363
3364              .data(collectionName)
3365
3366              .get()
3367
3368          )
3369
3370          .then(result => {
3371
3372            if (
3373
3374              result &&
3375
3376              Array.isArray(result.values)
3377
3378            ) {
3379
3380              return result.values;
3381
3382            }
3383
3384
3385
3386            if (Array.isArray(result)) {
3387
3388              return result;
3389
3390            }
3391
3392
3393
3394            return [];
3395
3396          })
3397
3398          .catch(error => {
3399
3400            delete sharedStore
3401
3402              .collectionPromises[
3403
3404                collectionName
3405
3406              ];
3407
3408
3409
3410            throw error;
3411
3412          });
3413
3414      }
3415
3416
3417
3418      return sharedStore.collectionPromises[
3419
3420        collectionName
3421
3422      ];
3423
3424    }
3425
3426
3427
3428    function loadContentLibrary() {
3429
3430      const sharedStore =
3431
3432        getSharedStore();
3433
3434
3435
3436      if (!sharedStore.contentLibraryPromise) {
3437
3438        sharedStore.contentLibraryPromise =
3439
3440          window.dmAPI
3441
3442            .loadContentLibrary()
3443
3444            .catch(error => {
3445
3446              sharedStore.contentLibraryPromise =
3447
3448                null;
3449
3450
3451
3452              throw error;
3453
3454            });
3455
3456      }
3457
3458
3459
3460      return sharedStore.contentLibraryPromise;
3461
3462    }
3463
3464
3465
3466    function loadDefaultLogo() {
3467
3468      const sharedStore =
3469
3470        getSharedStore();
3471
3472
3473
3474      if (!sharedStore.defaultLogoPromise) {
3475
3476        sharedStore.defaultLogoPromise =
3477
3478          loadContentLibrary()
3479
3480            .then(contentLibrary => {
3481
3482              const siteImages =
3483
3484                contentLibrary &&
3485
3486                Array.isArray(
3487
3488                  contentLibrary.site_images
3489
3490                )
3491
3492                  ? contentLibrary.site_images
3493
3494                  : [];
3495
3496
3497
3498              const logoEntry =
3499
3500                siteImages.find(
3501
3502                  image =>
3503
3504                    image &&
3505
3506                    image.label ===
3507
3508                      'dark-logo'
3509
3510                );
3511
3512
3513
3514              return logoEntry
3515
3516                ? extractImageUrl(logoEntry)
3517
3518                : '';
3519
3520            })
3521
3522            .catch(error => {
3523
3524              sharedStore.defaultLogoPromise =
3525
3526                null;
3527
3528
3529
3530              throw error;
3531
3532            });
3533
3534      }
3535
3536
3537
3538      return sharedStore.defaultLogoPromise;
3539
3540    }
3541
3542
3543
3544    function getRowData(row) {
3545
3546      return row && row.data
3547
3548        ? row.data
3549
3550        : row || {};
3551
3552    }
3553
3554
3555
3556    /*
3557      Notify the site's Dynamic Phone Number Insertion (DPNI) script
3558      after this widget has placed a phone number into the live DOM.
3559
3560      The site-level DPNI integration already listens for
3561      YextPhoneChangeEvent and reruns its replacement scan when that
3562      event is fired. Keeping this notification here makes the widget
3563      compatible with any phone number returned by Yext_Phones rather
3564      than tying it to a specific number or site.
3565    */
3566    function notifyDynamicPhoneInsertion(phone) {
3567      if (!safeTrim(phone)) return;
3568
3569      const dispatchPhoneChangeEvent = () => {
3570        if (!isCurrentInstance()) return;
3571
3572        try {
3573          document.dispatchEvent(
3574            new Event('YextPhoneChangeEvent')
3575          );
3576        } catch (e) {
3577          try {
3578            const phoneChangeEvent =
3579              document.createEvent('Event');
3580
3581            phoneChangeEvent.initEvent(
3582              'YextPhoneChangeEvent',
3583              true,
3584              true
3585            );
3586
3587            document.dispatchEvent(
3588              phoneChangeEvent
3589            );
3590          } catch (legacyEventError) {
3591            // DPNI notification is optional; widget rendering continues.
3592          }
3593        }
3594      };
3595
3596      /*
3597        Wait until the generated card/button has been committed to the
3598        DOM before asking DPNI to rescan it. A short delayed second pass
3599        also covers Duda/site scripts that finish their own ready work
3600        immediately after this widget renders.
3601      */
3602      requestAnimationFrame(() => {
3603        dispatchPhoneChangeEvent();
3604
3605        window.setTimeout(
3606          dispatchPhoneChangeEvent,
3607          100
3608        );
3609      });
3610    }
3611
3612    function loadPhone() {
3613
3614      return loadCollection('Yext_Phones')
3615
3616        .then(phoneRows => {
3617
3618          const mainPhoneRow =
3619
3620            phoneRows.find(row => {
3621
3622              const phoneData =
3623
3624                getRowData(row);
3625
3626
3627
3628              return safeTrim(
3629
3630                phoneData.Type
3631
3632              ).toLowerCase() ===
3633
3634                'mainphone';
3635
3636            });
3637
3638
3639
3640          if (!mainPhoneRow) {
3641
3642            return '';
3643
3644          }
3645
3646
3647
3648          const mainPhoneData =
3649
3650            getRowData(mainPhoneRow);
3651
3652
3653
3654          return safeTrim(
3655
3656            mainPhoneData.Phone
3657
3658          );
3659
3660        })
3661
3662        .catch(() => '');
3663
3664    }
3665
3666
3667
3668    const cardDesign =
3669
3670      config.cardDesign || {};
3671
3672
3673
3674    const logoDesign =
3675
3676      config.logoDesign || {};
3677
3678
3679
3680    const buttonDesign =
3681
3682      config.buttonDesign || {};
3683
3684
3685
3686    const animationDesign =
3687
3688      config.animationDesign || {};
3689
3690
3691
3692    const calloutLayout =
3693
3694      config.calloutLayout || {};
3695
3696
3697
3698    /*
3699
3700      Optional advanced spacing controls.
3701
3702
3703
3704      The master toggle is off by default so the original responsive
3705
3706      spacing remains completely unchanged until a designer enables it.
3707
3708      Values may live inside spacingDesign or directly on data.config.
3709
3710    */
3711
3712    const spacingDesign =
3713
3714      config.spacingDesign ||
3715
3716      config.spacing ||
3717
3718      {};
3719
3720
3721
3722    const cardContentAlign =
3723
3724      [
3725
3726        'left',
3727
3728        'center',
3729
3730        'right'
3731
3732      ].includes(
3733
3734        safeTrim(
3735
3736          cardDesign.cardContentAlign ||
3737
3738          config.cardContentAlign
3739
3740        ).toLowerCase()
3741
3742      )
3743
3744        ? safeTrim(
3745
3746            cardDesign.cardContentAlign ||
3747
3748            config.cardContentAlign
3749
3750          ).toLowerCase()
3751
3752        : 'center';
3753
3754
3755
3756    const cardVerticalAlign =
3757
3758      [
3759
3760        'top',
3761
3762        'middle',
3763
3764        'bottom'
3765
3766      ].includes(
3767
3768        safeTrim(
3769
3770          cardDesign.cardVerticalAlign ||
3771
3772          config.cardVerticalAlign
3773
3774        ).toLowerCase()
3775
3776      )
3777
3778        ? safeTrim(
3779
3780            cardDesign.cardVerticalAlign ||
3781
3782            config.cardVerticalAlign
3783
3784          ).toLowerCase()
3785
3786        : 'top';
3787
3788
3789
3790    function applyCardShadowTokens() {
3791
3792      const shadowPreset =
3793
3794        safeTrim(
3795
3796          cardDesign.cardShadow ||
3797
3798          config.cardShadow ||
3799
3800          'none'
3801
3802        ).toLowerCase();
3803
3804
3805
3806      const shadowMap = {
3807
3808        none: 'none',
3809
3810        soft:
3811
3812          '0 2px 8px rgba(0, 0, 0, 0.08)',
3813
3814        medium:
3815
3816          '0 4px 16px rgba(0, 0, 0, 0.14)',
3817
3818        strong:
3819
3820          '0 8px 30px rgba(0, 0, 0, 0.22)',
3821
3822        accent:
3823
3824          '0 4px 20px var(--ucw-accent-shadow)'
3825
3826      };
3827
3828
3829
3830      const hoverMap = {
3831
3832        none:
3833
3834          '0 8px 32px var(--ucw-accent-glow)',
3835
3836        soft:
3837
3838          '0 6px 20px rgba(0, 0, 0, 0.14)',
3839
3840        medium:
3841
3842          '0 10px 28px rgba(0, 0, 0, 0.20)',
3843
3844        strong:
3845
3846          '0 16px 40px rgba(0, 0, 0, 0.28)',
3847
3848        accent:
3849
3850          '0 8px 32px var(--ucw-accent-glow)'
3851
3852      };
3853
3854
3855
3856      root.style.setProperty(
3857
3858        '--ucw-card-shadow',
3859
3860        shadowMap[shadowPreset] ||
3861
3862          shadowMap.none
3863
3864      );
3865
3866
3867
3868      root.style.setProperty(
3869
3870        '--ucw-card-shadow-hover',
3871
3872        hoverMap[shadowPreset] ||
3873
3874          hoverMap.none
3875
3876      );
3877
3878    }
3879
3880
3881
3882    const enableEntranceAnimation =
3883
3884      normalizeBoolean(
3885
3886        animationDesign.enableEntranceAnimation,
3887
3888        normalizeBoolean(
3889
3890          config.enableEntranceAnimation,
3891
3892          false
3893
3894        )
3895
3896      );
3897
3898
3899
3900    const enableCardHover =
3901
3902      normalizeBoolean(
3903
3904        animationDesign.enableCardHover,
3905
3906        normalizeBoolean(
3907
3908          config.enableCardHover,
3909
3910          false
3911
3912        )
3913
3914      );
3915
3916
3917
3918    const enableLogoHover =
3919
3920      normalizeBoolean(
3921
3922        animationDesign.enableLogoHover,
3923
3924        normalizeBoolean(
3925
3926          config.enableLogoHover,
3927
3928          false
3929
3930        )
3931
3932      );
3933
3934
3935
3936    const enableButtonHover =
3937
3938      normalizeBoolean(
3939
3940        animationDesign.enableButtonHover,
3941
3942        normalizeBoolean(
3943
3944          config.enableButtonHover,
3945
3946          false
3947
3948        )
3949
3950      );
3951
3952
3953
3954    const enableButtonRipple =
3955
3956      normalizeBoolean(
3957
3958        animationDesign.enableButtonRipple,
3959
3960        normalizeBoolean(
3961
3962          config.enableButtonRipple,
3963
3964          false
3965
3966        )
3967
3968      );
3969
3970
3971
3972    const enablePhonePulse =
3973
3974      normalizeBoolean(
3975
3976        animationDesign.enablePhonePulse,
3977
3978        normalizeBoolean(
3979
3980          config.enablePhonePulse,
3981
3982          false
3983
3984        )
3985
3986      );
3987
3988
3989
3990    root.classList.toggle(
3991
3992      'ucw-animate-entrance',
3993
3994      enableEntranceAnimation
3995
3996    );
3997
3998
3999
4000    root.classList.toggle(
4001
4002      'ucw-animate-card-hover',
4003
4004      enableCardHover
4005
4006    );
4007
4008
4009
4010    root.classList.toggle(
4011
4012      'ucw-animate-logo-hover',
4013
4014      enableLogoHover
4015
4016    );
4017
4018
4019
4020    root.classList.toggle(
4021
4022      'ucw-animate-button-hover',
4023
4024      enableButtonHover
4025
4026    );
4027
4028
4029
4030    root.classList.toggle(
4031
4032      'ucw-animate-phone-pulse',
4033
4034      enablePhonePulse
4035
4036    );
4037
4038
4039
4040    const logoWidth =
4041
4042      Math.round(
4043
4044        clampNumber(
4045
4046          logoDesign.logoWidth ||
4047
4048          config.logoWidth,
4049
4050          20,
4051
4052          1000,
4053
4054          220
4055
4056        )
4057
4058      );
4059
4060
4061
4062    const logoMaxHeight =
4063
4064      Math.round(
4065
4066        clampNumber(
4067
4068          logoDesign.logoMaxHeight ||
4069
4070          config.logoMaxHeight,
4071
4072          20,
4073
4074          500,
4075
4076          100
4077
4078        )
4079
4080      );
4081
4082
4083
4084    const logoSpacing =
4085
4086      Math.round(
4087
4088        clampNumber(
4089
4090          logoDesign.logoSpacing ||
4091
4092          config.logoSpacing,
4093
4094          0,
4095
4096          200,
4097
4098          12
4099
4100        )
4101
4102      );
4103
4104
4105
4106    const logoAlign =
4107
4108      [
4109
4110        'left',
4111
4112        'center',
4113
4114        'right'
4115
4116      ].includes(
4117
4118        safeTrim(
4119
4120          logoDesign.logoAlign ||
4121
4122          config.logoAlign ||
4123
4124          'center'
4125
4126        ).toLowerCase()
4127
4128      )
4129
4130        ? safeTrim(
4131
4132            logoDesign.logoAlign ||
4133
4134            config.logoAlign ||
4135
4136            'center'
4137
4138          ).toLowerCase()
4139
4140        : 'center';
4141
4142
4143
4144    root.style.setProperty(
4145
4146      '--ucw-logo-width',
4147
4148      logoWidth + 'px'
4149
4150    );
4151
4152
4153
4154    root.style.setProperty(
4155
4156      '--ucw-logo-max-height',
4157
4158      logoMaxHeight + 'px'
4159
4160    );
4161
4162
4163
4164    root.style.setProperty(
4165
4166      '--ucw-logo-spacing',
4167
4168      logoSpacing + 'px'
4169
4170    );
4171
4172
4173
4174    root.style.setProperty(
4175
4176      '--ucw-logo-align',
4177
4178      logoAlign
4179
4180    );
4181
4182
4183
4184    const buttonLayout =
4185
4186      [
4187
4188        'row',
4189
4190        'stacked'
4191
4192      ].includes(
4193
4194        safeTrim(
4195
4196          buttonDesign.buttonLayout ||
4197
4198          config.buttonLayout ||
4199
4200          'row'
4201
4202        ).toLowerCase()
4203
4204      )
4205
4206        ? safeTrim(
4207
4208            buttonDesign.buttonLayout ||
4209
4210            config.buttonLayout ||
4211
4212            'row'
4213
4214          ).toLowerCase()
4215
4216        : 'row';
4217
4218
4219
4220    const buttonAlign =
4221
4222      [
4223
4224        'left',
4225
4226        'center',
4227
4228        'right'
4229
4230      ].includes(
4231
4232        safeTrim(
4233
4234          buttonDesign.buttonAlign ||
4235
4236          config.buttonAlign ||
4237
4238          'center'
4239
4240        ).toLowerCase()
4241
4242      )
4243
4244        ? safeTrim(
4245
4246            buttonDesign.buttonAlign ||
4247
4248            config.buttonAlign ||
4249
4250            'center'
4251
4252          ).toLowerCase()
4253
4254        : 'center';
4255
4256
4257
4258    const buttonGap =
4259
4260      Math.round(
4261
4262        clampNumber(
4263
4264          buttonDesign.buttonGap ||
4265
4266          config.buttonGap,
4267
4268          0,
4269
4270          100,
4271
4272          12
4273
4274        )
4275
4276      );
4277
4278
4279
4280    const fullWidthButtonsMobile =
4281
4282      normalizeBoolean(
4283
4284        buttonDesign.fullWidthButtonsMobile,
4285
4286        normalizeBoolean(
4287
4288          config.fullWidthButtonsMobile,
4289
4290          true
4291
4292        )
4293
4294      );
4295
4296
4297
4298    root.style.setProperty(
4299
4300      '--ucw-button-gap',
4301
4302      buttonGap + 'px'
4303
4304    );
4305
4306
4307
4308    root.classList.toggle(
4309
4310      'ucw-actions-stacked',
4311
4312      buttonLayout === 'stacked'
4313
4314    );
4315
4316
4317
4318    root.classList.toggle(
4319
4320      'ucw-actions-align-left',
4321
4322      buttonAlign === 'left'
4323
4324    );
4325
4326
4327
4328    root.classList.toggle(
4329
4330      'ucw-actions-align-center',
4331
4332      buttonAlign === 'center'
4333
4334    );
4335
4336
4337
4338    root.classList.toggle(
4339
4340      'ucw-actions-align-right',
4341
4342      buttonAlign === 'right'
4343
4344    );
4345
4346
4347
4348    root.classList.toggle(
4349
4350      'ucw-mobile-buttons-full',
4351
4352      fullWidthButtonsMobile
4353
4354    );
4355
4356
4357
4358    /*
4359
4360      Advanced spacing is opt-in. When disabled, the CSS continues using
4361
4362      the widget's original desktop, tablet, and mobile spacing values.
4363
4364    */
4365
4366    const customizeSpacing =
4367
4368      normalizeBoolean(
4369
4370        spacingDesign.customizeSpacing ??
4371
4372          spacingDesign.enableCustomSpacing ??
4373
4374          config.customizeSpacing ??
4375
4376          config.enableCustomSpacing,
4377
4378        false
4379
4380      );
4381
4382
4383
4384    const couponGap =
4385
4386      Math.round(
4387
4388        clampNumber(
4389
4390          spacingDesign.couponGap ??
4391
4392            config.couponGap,
4393
4394          0,
4395
4396          100,
4397
4398          20
4399
4400        )
4401
4402      );
4403
4404
4405
4406    const logoBottomGap =
4407
4408      Math.round(
4409
4410        clampNumber(
4411
4412          spacingDesign.logoBottomGap ??
4413
4414            config.logoBottomGap,
4415
4416          0,
4417
4418          100,
4419
4420          24
4421
4422        )
4423
4424      );
4425
4426
4427
4428    const headlineBottomGap =
4429
4430      Math.round(
4431
4432        clampNumber(
4433
4434          spacingDesign.headlineBottomGap ??
4435
4436            config.headlineBottomGap,
4437
4438          0,
4439
4440          100,
4441
4442          12
4443
4444        )
4445
4446      );
4447
4448
4449
4450    const bodyBottomGap =
4451
4452      Math.round(
4453
4454        clampNumber(
4455
4456          spacingDesign.bodyBottomGap ??
4457
4458            config.bodyBottomGap,
4459
4460          0,
4461
4462          100,
4463
4464          12
4465
4466        )
4467
4468      );
4469
4470
4471
4472    const ctaBottomGap =
4473
4474      Math.round(
4475
4476        clampNumber(
4477
4478          spacingDesign.ctaBottomGap ??
4479
4480            config.ctaBottomGap,
4481
4482          0,
4483
4484          100,
4485
4486          12
4487
4488        )
4489
4490      );
4491
4492
4493
4494    const termsBottomGap =
4495
4496      Math.round(
4497
4498        clampNumber(
4499
4500          spacingDesign.termsBottomGap ??
4501
4502            config.termsBottomGap,
4503
4504          0,
4505
4506          100,
4507
4508          12
4509
4510        )
4511
4512      );
4513
4514
4515
4516    const buttonBottomGap =
4517
4518      Math.round(
4519
4520        clampNumber(
4521
4522          spacingDesign.buttonBottomGap ??
4523
4524            config.buttonBottomGap,
4525
4526          0,
4527
4528          100,
4529
4530          0
4531
4532        )
4533
4534      );
4535
4536
4537
4538    root.classList.toggle(
4539
4540      'ucw-custom-spacing',
4541
4542      customizeSpacing
4543
4544    );
4545
4546
4547
4548    root.style.setProperty(
4549
4550      '--ucw-custom-coupon-gap',
4551
4552      couponGap + 'px'
4553
4554    );
4555
4556
4557
4558    root.style.setProperty(
4559
4560      '--ucw-logo-bottom-gap',
4561
4562      logoBottomGap + 'px'
4563
4564    );
4565
4566
4567
4568    root.style.setProperty(
4569
4570      '--ucw-headline-bottom-gap',
4571
4572      headlineBottomGap + 'px'
4573
4574    );
4575
4576
4577
4578    root.style.setProperty(
4579
4580      '--ucw-body-bottom-gap',
4581
4582      bodyBottomGap + 'px'
4583
4584    );
4585
4586
4587
4588    root.style.setProperty(
4589
4590      '--ucw-cta-bottom-gap',
4591
4592      ctaBottomGap + 'px'
4593
4594    );
4595
4596
4597
4598    root.style.setProperty(
4599
4600      '--ucw-terms-bottom-gap',
4601
4602      termsBottomGap + 'px'
4603
4604    );
4605
4606
4607
4608    root.style.setProperty(
4609
4610      '--ucw-button-bottom-gap',
4611
4612      buttonBottomGap + 'px'
4613
4614    );
4615
4616
4617
4618    function attachRipple(buttonElement) {
4619
4620      if (
4621
4622        prefersReducedMotion ||
4623
4624        !enableButtonRipple ||
4625
4626        !buttonElement ||
4627
4628        buttonElement.dataset
4629
4630          .ucwRippleInitialized === 'true'
4631
4632      ) {
4633
4634        return;
4635
4636      }
4637
4638
4639
4640      buttonElement.dataset
4641
4642        .ucwRippleInitialized = 'true';
4643
4644
4645
4646      buttonElement.addEventListener(
4647
4648        'pointerdown',
4649
4650        event => {
4651
4652          if (
4653
4654            event.pointerType === 'mouse' &&
4655
4656            event.button !== 0
4657
4658          ) {
4659
4660            return;
4661
4662          }
4663
4664
4665
4666          const buttonRectangle =
4667
4668            buttonElement
4669
4670              .getBoundingClientRect();
4671
4672
4673
4674          const rippleSize = Math.max(
4675
4676            buttonRectangle.width,
4677
4678            buttonRectangle.height
4679
4680          );
4681
4682
4683
4684          const ripple =
4685
4686            document.createElement('span');
4687
4688
4689
4690          ripple.className = 'ucw-ripple';
4691
4692
4693
4694          ripple.style.width =
4695
4696            rippleSize + 'px';
4697
4698
4699
4700          ripple.style.height =
4701
4702            rippleSize + 'px';
4703
4704
4705
4706          ripple.style.left =
4707
4708            (
4709
4710              event.clientX -
4711
4712              buttonRectangle.left -
4713
4714              rippleSize / 2
4715
4716            ) + 'px';
4717
4718
4719
4720          ripple.style.top =
4721
4722            (
4723
4724              event.clientY -
4725
4726              buttonRectangle.top -
4727
4728              rippleSize / 2
4729
4730            ) + 'px';
4731
4732
4733
4734          buttonElement.appendChild(
4735
4736            ripple
4737
4738          );
4739
4740
4741
4742          ripple.addEventListener(
4743
4744            'animationend',
4745
4746            () => ripple.remove(),
4747
4748            { once: true }
4749
4750          );
4751
4752        }
4753
4754      );
4755
4756    }
4757
4758
4759
4760    let cardIdCounter = 0;
4761
4762
4763
4764    function createTextElement(
4765
4766      tagName,
4767
4768      className,
4769
4770      value
4771
4772    ) {
4773
4774      const text = stripHtml(value);
4775
4776
4777
4778      if (!text) return null;
4779
4780
4781
4782      const textElement =
4783
4784        document.createElement(tagName);
4785
4786
4787
4788      textElement.className = className;
4789
4790      textElement.textContent = text;
4791
4792
4793
4794      return textElement;
4795
4796    }
4797
4798
4799
4800    function createLogoElement(
4801
4802      rowLogoValue,
4803
4804      defaultLogoUrl,
4805
4806      useDefaultLogo
4807
4808    ) {
4809
4810      const rowLogoUrl =
4811
4812        extractImageUrl(rowLogoValue);
4813
4814
4815
4816      const logoUrl =
4817
4818        useDefaultLogo
4819
4820          ? defaultLogoUrl
4821
4822          : rowLogoUrl;
4823
4824
4825
4826      if (!logoUrl) return null;
4827
4828
4829
4830      const logoWrapper =
4831
4832        document.createElement('div');
4833
4834
4835
4836      logoWrapper.className =
4837
4838        'ucw-card__logoWrap ' +
4839
4840        'ucw-card__logoWrap--' +
4841
4842        logoAlign;
4843
4844
4845
4846      logoWrapper.setAttribute(
4847
4848        'aria-hidden',
4849
4850        'true'
4851
4852      );
4853
4854
4855
4856      const logoImage =
4857
4858        document.createElement('img');
4859
4860
4861
4862      logoImage.className =
4863
4864        'ucw-card__logo';
4865
4866
4867
4868      logoImage.src = logoUrl;
4869
4870      logoImage.alt = '';
4871
4872      logoImage.loading = 'lazy';
4873
4874      logoImage.decoding = 'async';
4875
4876
4877
4878      logoWrapper.appendChild(
4879
4880        logoImage
4881
4882      );
4883
4884
4885
4886      return logoWrapper;
4887
4888    }
4889
4890
4891
4892    function setDudaButtonLabel(
4893
4894      actionLink,
4895
4896      labelText
4897
4898    ) {
4899
4900      const buttonElement =
4901
4902        actionLink.querySelector(
4903
4904          '.ucw-card__action'
4905
4906        );
4907
4908
4909
4910      if (!buttonElement) {
4911
4912        return false;
4913
4914      }
4915
4916
4917
4918      const labelElement =
4919
4920        buttonElement.querySelector(
4921
4922          '.text, .dmButtonLabel, .button-text, [data-button-text]'
4923
4924        );
4925
4926
4927
4928      if (labelElement) {
4929
4930        labelElement.textContent =
4931
4932          labelText;
4933
4934      } else {
4935
4936        buttonElement.textContent =
4937
4938          labelText;
4939
4940      }
4941
4942
4943
4944      return true;
4945
4946    }
4947
4948
4949
4950    function cloneActionButton(
4951
4952      modifierClass,
4953
4954      labelText,
4955
4956      href,
4957
4958      ariaLabel
4959
4960    ) {
4961
4962      const templateLink =
4963
4964        actionTemplate.querySelector(
4965
4966          '.ucw-card__actionLink'
4967
4968        );
4969
4970
4971
4972      if (!templateLink) {
4973
4974        return null;
4975
4976      }
4977
4978
4979
4980      const actionLink =
4981
4982        templateLink.cloneNode(true);
4983
4984
4985
4986      actionLink.classList.add(
4987
4988        modifierClass
4989
4990      );
4991
4992
4993
4994      actionLink.href = href;
4995
4996
4997
4998      actionLink.setAttribute(
4999
5000        'aria-label',
5001
5002        ariaLabel
5003
5004      );
5005
5006
5007
5008      if (
5009
5010        !setDudaButtonLabel(
5011
5012          actionLink,
5013
5014          labelText
5015
5016        )
5017
5018      ) {
5019
5020        return null;
5021
5022      }
5023
5024
5025
5026      const buttonElement =
5027
5028        actionLink.querySelector(
5029
5030          '.ucw-card__action'
5031
5032        );
5033
5034
5035
5036      if (buttonElement) {
5037
5038        attachRipple(buttonElement);
5039
5040      }
5041
5042
5043
5044      return actionLink;
5045
5046    }
5047
5048
5049
5050    function createPhoneButton(
5051
5052      phone,
5053
5054      headline
5055
5056    ) {
5057
5058      const visiblePhone =
5059
5060        safeTrim(phone);
5061
5062
5063
5064      const cleanPhone =
5065
5066        visiblePhone.replace(
5067
5068          /[^0-9+]/g,
5069
5070          ''
5071
5072        );
5073
5074
5075
5076      if (!visiblePhone || !cleanPhone) {
5077
5078        return null;
5079
5080      }
5081
5082
5083
5084      return cloneActionButton(
5085
5086        'ucw-card__phoneBtn',
5087
5088        visiblePhone,
5089
5090        'tel:' + cleanPhone,
5091
5092        'Call ' +
5093
5094          visiblePhone +
5095
5096          (
5097
5098            headline
5099
5100              ? ' — ' + headline
5101
5102              : ''
5103
5104          )
5105
5106      );
5107
5108    }
5109
5110
5111
5112    function createStandardButton(
5113
5114      rowData,
5115
5116      headline
5117
5118    ) {
5119
5120      const buttonText =
5121
5122        stripHtml(
5123
5124          rowData['BUTTON NAME']
5125
5126        );
5127
5128
5129
5130      const buttonLink =
5131
5132        normalizeButtonLink(
5133
5134          rowData['BUTTON LINK']
5135
5136        );
5137
5138
5139
5140      if (!buttonText || !buttonLink) {
5141
5142        return null;
5143
5144      }
5145
5146
5147
5148      return cloneActionButton(
5149
5150        'ucw-card__btn',
5151
5152        buttonText,
5153
5154        buttonLink,
5155
5156        headline
5157
5158          ? buttonText +
5159
5160              ' — ' +
5161
5162              headline
5163
5164          : buttonText
5165
5166      );
5167
5168    }
5169
5170
5171
5172    function createActions(
5173
5174      rowData,
5175
5176      phone,
5177
5178      headline,
5179
5180      showStandardButton,
5181
5182      showPhoneButton
5183
5184    ) {
5185
5186      const actions =
5187
5188        document.createElement('div');
5189
5190
5191
5192      actions.className =
5193
5194        'ucw-card__actions';
5195
5196
5197
5198      if (showStandardButton) {
5199
5200        const standardButton =
5201
5202          createStandardButton(
5203
5204            rowData,
5205
5206            headline
5207
5208          );
5209
5210
5211
5212        if (standardButton) {
5213
5214          actions.appendChild(
5215
5216            standardButton
5217
5218          );
5219
5220        }
5221
5222      }
5223
5224
5225
5226      if (showPhoneButton) {
5227
5228        const phoneButton =
5229
5230          createPhoneButton(
5231
5232            phone,
5233
5234            headline
5235
5236          );
5237
5238
5239
5240        if (phoneButton) {
5241
5242          actions.appendChild(
5243
5244            phoneButton
5245
5246          );
5247
5248        }
5249
5250      }
5251
5252
5253
5254      return actions.childElementCount
5255
5256        ? actions
5257
5258        : null;
5259
5260    }
5261
5262
5263
5264    function createCard(
5265
5266      rowData,
5267
5268      options
5269
5270    ) {
5271
5272      cardIdCounter += 1;
5273
5274
5275
5276      const card =
5277
5278        document.createElement('article');
5279
5280
5281
5282      card.className = [
5283
5284        'ucw-card',
5285
5286        options.isCoupon
5287
5288          ? 'ucw-card--coupon'
5289
5290          : 'ucw-card--callout',
5291
5292        'ucw-card--align-' +
5293
5294          cardContentAlign,
5295
5296        'ucw-card--valign-' +
5297
5298          cardVerticalAlign
5299
5300      ].join(' ');
5301
5302
5303
5304      if (options.isCoupon) {
5305
5306        card.setAttribute(
5307
5308          'role',
5309
5310          'listitem'
5311
5312        );
5313
5314      }
5315
5316
5317
5318      const cardInner =
5319
5320        document.createElement('div');
5321
5322
5323
5324      cardInner.className =
5325
5326        'ucw-card__inner';
5327
5328
5329
5330      const logoElement =
5331
5332        createLogoElement(
5333
5334          rowData.LOGO,
5335
5336          options.defaultLogoUrl,
5337
5338          options.useDefaultLogo
5339
5340        );
5341
5342
5343
5344      if (logoElement) {
5345
5346        cardInner.appendChild(
5347
5348          logoElement
5349
5350        );
5351
5352      }
5353
5354
5355
5356      const headlineElement =
5357
5358        createTextElement(
5359
5360          'h3',
5361
5362          'ucw-card__head',
5363
5364          rowData.HEADLINE
5365
5366        );
5367
5368
5369
5370      const headlineText =
5371
5372        headlineElement
5373
5374          ? headlineElement.textContent
5375
5376          : '';
5377
5378
5379
5380      if (headlineElement) {
5381
5382        headlineElement.id =
5383
5384          'ucw-heading-' +
5385
5386          (
5387
5388            element.id ||
5389
5390            'widget'
5391
5392          ) +
5393
5394          '-' +
5395
5396          cardIdCounter;
5397
5398
5399
5400        card.setAttribute(
5401
5402          'aria-labelledby',
5403
5404          headlineElement.id
5405
5406        );
5407
5408
5409
5410        cardInner.appendChild(
5411
5412          headlineElement
5413
5414        );
5415
5416      }
5417
5418
5419
5420      const bodyElement =
5421
5422        createTextElement(
5423
5424          'p',
5425
5426          'ucw-card__body',
5427
5428          rowData.BODY
5429
5430        );
5431
5432
5433
5434      if (bodyElement) {
5435
5436        cardInner.appendChild(
5437
5438          bodyElement
5439
5440        );
5441
5442      }
5443
5444
5445
5446      const ctaElement =
5447
5448        createTextElement(
5449
5450          'p',
5451
5452          'ucw-card__cta',
5453
5454          rowData['CTA LEAD IN']
5455
5456        );
5457
5458
5459
5460      if (ctaElement) {
5461
5462        cardInner.appendChild(
5463
5464          ctaElement
5465
5466        );
5467
5468      }
5469
5470
5471
5472      if (
5473
5474        options.termsDisplayMode !==
5475
5476        'hidden'
5477
5478      ) {
5479
5480        const finePrintElement =
5481
5482          createTextElement(
5483
5484            'p',
5485
5486            'ucw-card__fine',
5487
5488            rowData[
5489
5490              'TERMS OF CONDITION'
5491
5492            ]
5493
5494          );
5495
5496
5497
5498        if (finePrintElement) {
5499
5500          finePrintElement.id =
5501
5502            'ucw-fine-' +
5503
5504            (
5505
5506              element.id ||
5507
5508              'widget'
5509
5510            ) +
5511
5512            '-' +
5513
5514            cardIdCounter;
5515
5516
5517
5518          if (
5519
5520            options.termsDisplayMode ===
5521
5522            'abbreviated'
5523
5524          ) {
5525
5526            finePrintElement.classList.add(
5527
5528              'ucw-card__fine--collapsible'
5529
5530            );
5531
5532          }
5533
5534
5535
5536          /*
5537
5538            Keep the terms text and its optional See terms control
5539
5540            together so Gap Below Terms applies below the complete
5541
5542            terms section instead of between those two elements.
5543
5544          */
5545
5546          const termsGroup =
5547
5548            document.createElement('div');
5549
5550
5551
5552          termsGroup.className =
5553
5554            'ucw-card__termsGroup';
5555
5556
5557
5558          termsGroup.appendChild(
5559
5560            finePrintElement
5561
5562          );
5563
5564
5565
5566          cardInner.appendChild(
5567
5568            termsGroup
5569
5570          );
5571
5572        }
5573
5574      }
5575
5576
5577
5578      const actions =
5579
5580        createActions(
5581
5582          rowData,
5583
5584          options.phone,
5585
5586          headlineText,
5587
5588          options.showStandardButton,
5589
5590          options.showPhoneButton
5591
5592        );
5593
5594
5595
5596      if (actions) {
5597
5598        cardInner.appendChild(actions);
5599
5600      }
5601
5602
5603
5604      card.appendChild(cardInner);
5605
5606
5607
5608      return card;
5609
5610    }
5611
5612
5613
5614    function initializeFinePrint(card) {
5615
5616      const finePrint =
5617
5618        card.querySelector(
5619
5620          '.ucw-card__fine--collapsible'
5621
5622        );
5623
5624
5625
5626      if (
5627
5628        !finePrint ||
5629
5630        finePrint.dataset
5631
5632          .ucwFineInitialized === 'true'
5633
5634      ) {
5635
5636        return;
5637
5638      }
5639
5640
5641
5642      finePrint.dataset
5643
5644        .ucwFineInitialized = 'true';
5645
5646
5647
5648      const toggle =
5649
5650        document.createElement('button');
5651
5652
5653
5654      toggle.className =
5655
5656        'ucw-fine-toggle';
5657
5658
5659
5660      toggle.type = 'button';
5661
5662      toggle.textContent =
5663
5664        'See terms ▾';
5665
5666
5667
5668      toggle.setAttribute(
5669
5670        'aria-expanded',
5671
5672        'false'
5673
5674      );
5675
5676
5677
5678      toggle.setAttribute(
5679
5680        'aria-controls',
5681
5682        finePrint.id
5683
5684      );
5685
5686
5687
5688      toggle.addEventListener(
5689
5690        'click',
5691
5692        () => {
5693
5694          const expanded =
5695
5696            finePrint.classList.toggle(
5697
5698              'ucw-card__fine--expanded'
5699
5700            );
5701
5702
5703
5704          toggle.textContent =
5705
5706            expanded
5707
5708              ? 'Hide terms ▴'
5709
5710              : 'See terms ▾';
5711
5712
5713
5714          toggle.setAttribute(
5715
5716            'aria-expanded',
5717
5718            String(expanded)
5719
5720          );
5721
5722        }
5723
5724      );
5725
5726
5727
5728      finePrint.insertAdjacentElement(
5729
5730        'afterend',
5731
5732        toggle
5733
5734      );
5735
5736    }
5737
5738
5739
5740    function initializeFinePrintForCards(
5741
5742      cards
5743
5744    ) {
5745
5746      requestAnimationFrame(() => {
5747
5748        cards.forEach(
5749
5750          initializeFinePrint
5751
5752        );
5753
5754      });
5755
5756    }
5757
5758
5759
5760    function initializeEntrance(cards) {
5761
5762      if (!cards.length) return;
5763
5764
5765
5766      if (
5767
5768        prefersReducedMotion ||
5769
5770        !enableEntranceAnimation ||
5771
5772        !window.IntersectionObserver
5773
5774      ) {
5775
5776        cards.forEach(card => {
5777
5778          card.classList.add(
5779
5780            'ucw-card--visible',
5781
5782            'ucw-card--settled'
5783
5784          );
5785
5786        });
5787
5788
5789
5790        return;
5791
5792      }
5793
5794
5795
5796      const observer =
5797
5798        new IntersectionObserver(
5799
5800          entries => {
5801
5802            entries.forEach(entry => {
5803
5804              if (!entry.isIntersecting) {
5805
5806                return;
5807
5808              }
5809
5810
5811
5812              const card = entry.target;
5813
5814
5815
5816              card.classList.add(
5817
5818                'ucw-card--visible'
5819
5820              );
5821
5822
5823
5824              const settleCard = () => {
5825
5826                card.classList.add(
5827
5828                  'ucw-card--settled'
5829
5830                );
5831
5832              };
5833
5834
5835
5836              card.addEventListener(
5837
5838                'transitionend',
5839
5840                settleCard,
5841
5842                { once: true }
5843
5844              );
5845
5846
5847
5848              window.setTimeout(
5849
5850                settleCard,
5851
5852                750
5853
5854              );
5855
5856
5857
5858              observer.unobserve(card);
5859
5860            });
5861
5862          },
5863
5864          {
5865
5866            threshold: 0.12
5867
5868          }
5869
5870        );
5871
5872
5873
5874      root.__ucwEntranceObserver =
5875
5876        observer;
5877
5878
5879
5880      cards.forEach(
5881
5882        (card, cardIndex) => {
5883
5884          card.style.setProperty(
5885
5886            '--ucw-delay',
5887
5888            cardIndex * 80 + 'ms'
5889
5890          );
5891
5892
5893
5894          observer.observe(card);
5895
5896        }
5897
5898      );
5899
5900    }
5901
5902
5903
5904    function createSkeleton(
5905
5906      alignment
5907
5908    ) {
5909
5910      const skeleton =
5911
5912        document.createElement('div');
5913
5914
5915
5916      skeleton.className =
5917
5918        'ucw-skeleton ucw-skeleton--' +
5919
5920        alignment;
5921
5922
5923
5924      skeleton.setAttribute(
5925
5926        'aria-hidden',
5927
5928        'true'
5929
5930      );
5931
5932
5933
5934      [
5935
5936        'title',
5937
5938        'body',
5939
5940        'body2',
5941
5942        'btn'
5943
5944      ].forEach(lineType => {
5945
5946        const line =
5947
5948          document.createElement('div');
5949
5950
5951
5952        line.className =
5953
5954          'ucw-skeleton__line ' +
5955
5956          'ucw-skeleton__line--' +
5957
5958          lineType;
5959
5960
5961
5962        skeleton.appendChild(line);
5963
5964      });
5965
5966
5967
5968      return skeleton;
5969
5970    }
5971
5972
5973
5974    function showSkeletons(
5975
5976      container,
5977
5978      count
5979
5980    ) {
5981
5982      container.replaceChildren();
5983
5984
5985
5986      for (
5987
5988        let index = 0;
5989
5990        index < count;
5991
5992        index += 1
5993
5994      ) {
5995
5996        container.appendChild(
5997
5998          createSkeleton(
5999
6000            cardContentAlign
6001
6002          )
6003
6004        );
6005
6006      }
6007
6008    }
6009
6010
6011
6012    function showEmptyState(
6013
6014      container,
6015
6016      message
6017
6018    ) {
6019
6020      container.replaceChildren();
6021
6022
6023
6024      const emptyState =
6025
6026        document.createElement('p');
6027
6028
6029
6030      emptyState.className =
6031
6032        'ucw-empty';
6033
6034
6035
6036      emptyState.textContent =
6037
6038        message ||
6039
6040        'Check back soon for offers.';
6041
6042
6043
6044      container.appendChild(
6045
6046        emptyState
6047
6048      );
6049
6050    }
6051
6052
6053
6054    function hideWidget() {
6055
6056      root.hidden = true;
6057
6058      root.setAttribute(
6059
6060        'aria-busy',
6061
6062        'false'
6063
6064      );
6065
6066    }
6067
6068
6069
6070    function showMode(mode) {
6071
6072      root.hidden = false;
6073
6074
6075
6076      if (mode === 'callout') {
6077
6078        calloutEl.hidden = false;
6079
6080        couponEl.hidden = true;
6081
6082      } else {
6083
6084        calloutEl.hidden = true;
6085
6086        couponEl.hidden = false;
6087
6088      }
6089
6090    }
6091
6092
6093
6094    function parseAllowedSlugs(value) {
6095
6096      const plainValue =
6097
6098        stripHtml(
6099
6100          safeTrim(value)
6101
6102            .replace(
6103
6104              /<br\s*\/?>/gi,
6105
6106              ','
6107
6108            )
6109
6110        );
6111
6112
6113
6114      if (!plainValue) return [];
6115
6116
6117
6118      return plainValue
6119
6120        .split(/[\n,]+/)
6121
6122        .map(getLastSlug)
6123
6124        .filter(Boolean);
6125
6126    }
6127
6128
6129
6130    function filterCouponRows(
6131
6132      rows,
6133
6134      options
6135
6136    ) {
6137
6138      if (options.showAll) {
6139
6140        return rows;
6141
6142      }
6143
6144
6145
6146      const manualSlugs =
6147
6148        safeTrim(options.filterValue)
6149
6150          .split(',')
6151
6152          .map(getLastSlug)
6153
6154          .filter(Boolean);
6155
6156
6157
6158      if (options.filterBySlug) {
6159
6160        const currentSlug =
6161
6162          getCurrentSlug();
6163
6164
6165
6166        if (currentSlug) {
6167
6168          const currentPageMatches =
6169
6170            rows.filter(row =>
6171
6172              getLastSlug(
6173
6174                row &&
6175
6176                  row.page_item_url
6177
6178              ) === currentSlug
6179
6180            );
6181
6182
6183
6184          if (
6185
6186            currentPageMatches.length
6187
6188          ) {
6189
6190            return currentPageMatches;
6191
6192          }
6193
6194        }
6195
6196      }
6197
6198
6199
6200      if (!manualSlugs.length) {
6201
6202        return rows;
6203
6204      }
6205
6206
6207
6208      return rows.filter(row =>
6209
6210        manualSlugs.includes(
6211
6212          getLastSlug(
6213
6214            row &&
6215
6216              row.page_item_url
6217
6218          )
6219
6220        )
6221
6222      );
6223
6224    }
6225
6226
6227
6228    function selectCalloutRow(
6229
6230      rows,
6231
6232      options
6233
6234    ) {
6235
6236      if (!rows.length) {
6237
6238        return null;
6239
6240      }
6241
6242
6243
6244      if (options.explicitSlug) {
6245
6246        return (
6247
6248          rows.find(row =>
6249
6250            getLastSlug(
6251
6252              row &&
6253
6254                row.page_item_url
6255
6256            ) === options.explicitSlug
6257
6258          ) || null
6259
6260        );
6261
6262      }
6263
6264
6265
6266      if (options.filterBySlug) {
6267
6268        const currentSlug =
6269
6270          getCurrentSlug();
6271
6272
6273
6274        if (currentSlug) {
6275
6276          const currentMatch =
6277
6278            rows.find(row =>
6279
6280              getLastSlug(
6281
6282                row &&
6283
6284                  row.page_item_url
6285
6286              ) === currentSlug
6287
6288            );
6289
6290
6291
6292          if (currentMatch) {
6293
6294            return currentMatch;
6295
6296          }
6297
6298        }
6299
6300
6301
6302        const allPagesMatch =
6303
6304          rows.find(row =>
6305
6306            safeTrim(
6307
6308              row &&
6309
6310                row.page_item_url
6311
6312            )
6313
6314              .toLowerCase() ===
6315
6316            '_all_'
6317
6318          );
6319
6320
6321
6322        if (allPagesMatch) {
6323
6324          return allPagesMatch;
6325
6326        }
6327
6328      }
6329
6330
6331
6332      return rows[0];
6333
6334    }
6335
6336
6337
6338    applyCardShadowTokens();
6339
6340
6341
6342    const widgetMode =
6343
6344      safeTrim(
6345
6346        config.widgetMode ||
6347
6348        'callout'
6349
6350      ).toLowerCase() === 'coupon'
6351
6352        ? 'coupon'
6353
6354        : 'callout';
6355
6356
6357
6358    const useDefaultLogo =
6359
6360      normalizeBoolean(
6361
6362        config.useDefaultLogo,
6363
6364        false
6365
6366      );
6367
6368
6369
6370    const showStandardButton =
6371
6372      normalizeBoolean(
6373
6374        config.showStandardButton,
6375
6376        true
6377
6378      );
6379
6380
6381
6382    const showPhoneButton =
6383
6384      normalizeBoolean(
6385
6386        config.showPhoneButton,
6387
6388        true
6389
6390      );
6391
6392
6393
6394    const termsDisplayMode =
6395
6396      [
6397
6398        'full',
6399
6400        'abbreviated',
6401
6402        'hidden'
6403
6404      ].includes(
6405
6406        safeTrim(
6407
6408          config.termsDisplayMode ||
6409
6410          'abbreviated'
6411
6412        ).toLowerCase()
6413
6414      )
6415
6416        ? safeTrim(
6417
6418            config.termsDisplayMode ||
6419
6420            'abbreviated'
6421
6422          ).toLowerCase()
6423
6424        : 'abbreviated';
6425
6426
6427
6428    const couponColumns =
6429
6430      Math.round(
6431
6432        clampNumber(
6433
6434          config.couponColumns,
6435
6436          1,
6437
6438          4,
6439
6440          2
6441
6442        )
6443
6444      );
6445
6446
6447
6448    const showAll =
6449
6450      normalizeBoolean(
6451
6452        config.showAll,
6453
6454        false
6455
6456      );
6457
6458
6459
6460    const couponFilterBySlug =
6461
6462      normalizeBoolean(
6463
6464        config.couponFilterBySlug,
6465
6466        false
6467
6468      );
6469
6470
6471
6472    const calloutFilterBySlug =
6473
6474      normalizeBoolean(
6475
6476        config.filterBySlug,
6477
6478        false
6479
6480      );
6481
6482
6483
6484    const calloutExplicitSlug =
6485
6486      getLastSlug(
6487
6488        config.calloutFilterValue
6489
6490      );
6491
6492
6493
6494    const allowedSlugs =
6495
6496      parseAllowedSlugs(
6497
6498        config.allowedSlugs
6499
6500      );
6501
6502
6503
6504    if (
6505
6506      allowedSlugs.length &&
6507
6508      !allowedSlugs.includes(
6509
6510        getCurrentSlug()
6511
6512      )
6513
6514    ) {
6515
6516      hideWidget();
6517
6518      return;
6519
6520    }
6521
6522
6523
6524    root.setAttribute(
6525
6526      'aria-busy',
6527
6528      'true'
6529
6530    );
6531
6532
6533
6534    root.hidden = false;
6535
6536
6537
6538    const needsPhone =
6539
6540      showPhoneButton;
6541
6542
6543
6544    if (widgetMode === 'callout') {
6545
6546      showMode('callout');
6547
6548
6549
6550      const calloutAlignValue =
6551
6552        safeTrim(
6553
6554          calloutLayout.calloutAlign ||
6555
6556          config.calloutAlign ||
6557
6558          'full'
6559
6560        ).toLowerCase();
6561
6562
6563
6564      const calloutAlign =
6565
6566        [
6567
6568          'full',
6569
6570          'left',
6571
6572          'center',
6573
6574          'right'
6575
6576        ].includes(calloutAlignValue)
6577
6578          ? calloutAlignValue
6579
6580          : 'full';
6581
6582
6583
6584      const calloutMaxWidth =
6585
6586        Math.round(
6587
6588          clampNumber(
6589
6590            calloutLayout.calloutMaxWidth ||
6591
6592            config.calloutMaxWidth,
6593
6594            200,
6595
6596            2000,
6597
6598            720
6599
6600          )
6601
6602        );
6603
6604
6605
6606      calloutEl.className =
6607
6608        'ucw-callout ' +
6609
6610        'ucw-callout--align-' +
6611
6612        calloutAlign;
6613
6614
6615
6616      calloutEl.style.setProperty(
6617
6618        '--callout-max-width',
6619
6620        calloutMaxWidth + 'px'
6621
6622      );
6623
6624
6625
6626      showSkeletons(
6627
6628        calloutMount,
6629
6630        1
6631
6632      );
6633
6634
6635
6636      try {
6637
6638        const [
6639
6640          calloutRows,
6641
6642          defaultLogoUrl,
6643
6644          phone
6645
6646        ] = await Promise.all([
6647
6648          loadCollection('CALLOUT 1'),
6649
6650          useDefaultLogo
6651
6652            ? loadDefaultLogo()
6653
6654                .catch(() => '')
6655
6656            : Promise.resolve(''),
6657
6658          needsPhone
6659
6660            ? loadPhone()
6661
6662            : Promise.resolve('')
6663
6664        ]);
6665
6666
6667
6668        if (!isCurrentInstance()) {
6669
6670          return;
6671
6672        }
6673
6674
6675
6676        const selectedRow =
6677
6678          selectCalloutRow(
6679
6680            calloutRows,
6681
6682            {
6683
6684              explicitSlug:
6685
6686                calloutExplicitSlug,
6687
6688              filterBySlug:
6689
6690                calloutFilterBySlug
6691
6692            }
6693
6694          );
6695
6696
6697
6698        if (!selectedRow) {
6699
6700          hideWidget();
6701
6702          return;
6703
6704        }
6705
6706
6707
6708        const card = createCard(
6709
6710          getRowData(selectedRow),
6711
6712          {
6713
6714            isCoupon: false,
6715
6716            defaultLogoUrl,
6717
6718            useDefaultLogo,
6719
6720            phone,
6721
6722            showStandardButton,
6723
6724            showPhoneButton,
6725
6726            termsDisplayMode
6727
6728          }
6729
6730        );
6731
6732
6733
6734        calloutMount.replaceChildren(card);
6735
6736
6737
6738        notifyDynamicPhoneInsertion(phone);
6739
6740
6741
6742        initializeFinePrintForCards([
6743
6744          card
6745
6746        ]);
6747
6748
6749
6750        initializeEntrance([
6751
6752          card
6753
6754        ]);
6755
6756
6757
6758        root.setAttribute(
6759
6760          'aria-busy',
6761
6762          'false'
6763
6764        );
6765
6766      } catch (e) {
6767
6768        if (isCurrentInstance()) {
6769
6770          hideWidget();
6771
6772        }
6773
6774      }
6775
6776
6777
6778      return;
6779
6780    }
6781
6782
6783
6784    showMode('coupon');
6785
6786
6787
6788    couponGrid.className =
6789
6790      'ucw-coupon__grid cols-' +
6791
6792      couponColumns;
6793
6794
6795
6796    couponGrid.style.setProperty(
6797
6798      '--coupon-cols',
6799
6800      String(couponColumns)
6801
6802    );
6803
6804
6805
6806    showSkeletons(
6807
6808      couponGrid,
6809
6810      couponColumns
6811
6812    );
6813
6814
6815
6816    try {
6817
6818      const [
6819
6820        couponRows,
6821
6822        defaultLogoUrl,
6823
6824        phone
6825
6826      ] = await Promise.all([
6827
6828        loadCollection('COUPON 1'),
6829
6830        useDefaultLogo
6831
6832          ? loadDefaultLogo()
6833
6834              .catch(() => '')
6835
6836          : Promise.resolve(''),
6837
6838        needsPhone
6839
6840          ? loadPhone()
6841
6842          : Promise.resolve('')
6843
6844      ]);
6845
6846
6847
6848      if (!isCurrentInstance()) {
6849
6850        return;
6851
6852      }
6853
6854
6855
6856      let visibleRows =
6857
6858        filterCouponRows(
6859
6860          couponRows,
6861
6862          {
6863
6864            showAll,
6865
6866            filterBySlug:
6867
6868              couponFilterBySlug,
6869
6870            filterValue:
6871
6872              config.filterValue
6873
6874          }
6875
6876        );
6877
6878
6879
6880      if (!showAll) {
6881
6882        visibleRows =
6883
6884          visibleRows.slice(
6885
6886            0,
6887
6888            couponColumns
6889
6890          );
6891
6892      }
6893
6894
6895
6896      if (!visibleRows.length) {
6897
6898        showEmptyState(
6899
6900          couponGrid
6901
6902        );
6903
6904
6905
6906        root.setAttribute(
6907
6908          'aria-busy',
6909
6910          'false'
6911
6912        );
6913
6914
6915
6916        return;
6917
6918      }
6919
6920
6921
6922      couponGrid.classList.toggle(
6923
6924        'single-card',
6925
6926        visibleRows.length === 1
6927
6928      );
6929
6930
6931
6932      const cards =
6933
6934        visibleRows.map(row =>
6935
6936          createCard(
6937
6938            getRowData(row),
6939
6940            {
6941
6942              isCoupon: true,
6943
6944              defaultLogoUrl,
6945
6946              useDefaultLogo,
6947
6948              phone,
6949
6950              showStandardButton,
6951
6952              showPhoneButton,
6953
6954              termsDisplayMode
6955
6956            }
6957
6958          )
6959
6960        );
6961
6962
6963
6964      couponGrid.replaceChildren(
6965
6966        ...cards
6967
6968      );
6969
6970
6971
6972      notifyDynamicPhoneInsertion(phone);
6973
6974
6975
6976      initializeFinePrintForCards(
6977
6978        cards
6979
6980      );
6981
6982
6983
6984      initializeEntrance(cards);
6985
6986
6987
6988      root.setAttribute(
6989
6990        'aria-busy',
6991
6992        'false'
6993
6994      );
6995
6996    } catch (e) {
6997
6998      if (isCurrentInstance()) {
6999
7000        showEmptyState(
7001
7002          couponGrid
7003
7004        );
7005
7006
7007
7008        root.setAttribute(
7009
7010          'aria-busy',
7011
7012          'false'
7013
7014        );
7015
7016      }
7017
7018    }
7019
7020  }
7021
7022);
7023    };
7024</script>
7024
7025
7026<script>
7027    window.customWidgetsFunctions["0c06e70da5474879a21daa0d3b1192c0~3"] = function (element, data, api) {
7028        window.dmAPI.runOnReady('cb1-h1-theme', () => {
7029
7030  function applyTheme() {
7031    // Try multiple candidate selectors for the h1 style source
7032    // to work in both the editor and the published/preview environment
7033    const candidates = [
7034      '#dm h1',
7035      'h1',
7036    ];
7037
7038    let h1Sample = null;
7039    for (const sel of candidates) {
7040      h1Sample = document.querySelector(sel);
7041      if (h1Sample) break;
7042    }
7043
7044    if (!h1Sample) return false;
7045
7046    const computed = window.getComputedStyle(h1Sample);
7047
7048    const fontFamily    = computed.fontFamily;
7049    const fontSize      = computed.fontSize;
7050    const fontWeight    = computed.fontWeight;
7051    const lineHeight    = computed.lineHeight;
7052    const color         = computed.color;
7053    const letterSpacing = computed.letterSpacing;
7054    const textTransform = computed.textTransform;
7055    const fontStyle     = computed.fontStyle;
7056
7057    // Skip if nothing meaningful was computed yet
7058    if (!fontFamily || fontFamily === '') return false;
7059
7060    const baseSelectors = [
7061      `#dm .dmInner .widget-wrapper.DCBH1 .dmNewParagraph`,
7062      `#dm .dmInner .widget-wrapper.DCBH1 .dmNewParagraph p`,
7063      `#dm .dmInner .widget-wrapper.DCBH1 .dmNewParagraph .rteBlock`,
7064      `.dmInner .widget-wrapper.DCBH1 .dmNewParagraph`,
7065      `.dmInner .widget-wrapper.DCBH1 .dmNewParagraph p`,
7066      `.dmInner .widget-wrapper.DCBH1 .dmNewParagraph .rteBlock`,
7067      `.widget-wrapper.DCBH1 .dmNewParagraph`,
7068      `.widget-wrapper.DCBH1 .dmNewParagraph p`,
7069      `.widget-wrapper.DCBH1 .dmNewParagraph .rteBlock`,
7070    ];
7071
7072    const allSelectors = baseSelectors.join(',\n    ');
7073
7074    const css = `
7075    ${allSelectors} {
7076      font-family: ${fontFamily} !important;
7077      font-size: ${fontSize} !important;
7078      font-weight: ${fontWeight} !important;
7079      line-height: ${lineHeight} !important;
7080      color: ${color} !important;
7081      letter-spacing: ${letterSpacing} !important;
7082      text-transform: ${textTransform} !important;
7083      font-style: ${fontStyle} !important;
7084    }
7085  `;
7086
7087    // Remove any previously injected tag before re-injecting
7088    const existing = document.querySelector('style[data-cb1-h1-theme]');
7089    if (existing) existing.remove();
7090
7091    const styleTag = document.createElement('style');
7092    styleTag.setAttribute('data-cb1-h1-theme', 'true');
7093    styleTag.textContent = css;
7094    document.head.appendChild(styleTag);
7095
7096    return true;
7097  }
7098
7099  // Try immediately
7100  if (applyTheme()) return;
7101
7102  // If not ready yet, retry with increasing delays to handle
7103  // async rendering in preview / published environments
7104  let attempts = 0;
7105  const maxAttempts = 20;
7106  const interval = setInterval(() => {
7107    attempts++;
7108    if (applyTheme() || attempts >= maxAttempts) {
7109      clearInterval(interval);
7110    }
7111  }, 300);
7112});
7113
7114    };
7115</script>
7115
7116
7117<script>
7118    window.customWidgetsFunctions["3e38ec713838428586c343d139994b6f~58"] = function (element, data, api) {
7119        window.dmAPI.runOnReady('navGridWidget', () => {
7120  const container = element.querySelector('#nav-grid-widget');
7121  if(!container) return;
7122
7123  const cleanText = (el)=> (el?.textContent||'').trim().replace(/\s+/g,' ');
7124  const cleanUrl  = (href)=>{
7125    try{
7126      const u=new URL(href,location.origin);
7127      const path = u.pathname.replace(/\/+$/,'')||'/';
7128      return path + u.search; // Preserve query parameters for preview mode
7129    }
7130    catch{ return '#'; }
7131  };
7132
7133  // Get column count from config (grid uses this; masonry uses this)
7134  const columnCount = parseInt(data.config['columnCount'] || '3', 10);
7135
7136  // Get exclusion list from config
7137  const excludeList = (data.config['excludeParents'] || '')
7138    .split(',')
7139    .map(s => s.trim().toLowerCase())
7140    .filter(s => s.length > 0);
7141
7142  // Get inclusion list from config
7143  const includeList = (data.config['includeParents'] || '')
7144    .split(',')
7145    .map(s => s.trim().toLowerCase())
7146    .filter(s => s.length > 0);
7147
7148  // Get chevron icon from config (Option B default: right-pointing)
7149  const chevronIcon = data.config['chevronIcon'] || '▸';
7150
7151  // Get hover setting from config
7152  const openOnHover = data.config['openOnHover'] || false;
7153
7154  // Get expand all setting from config
7155  const expandAll = data.config['expandAll'] || false;
7156
7157  // Get masonry setting from config (NEW - separate from expandAll)
7158  const useMasonry = data.config['useMasonry'] || false;
7159
7160  // Set CSS custom property for column count - this works for both grid AND masonry
7161  container.style.setProperty('--column-count', String(columnCount));
7162
7163  // Masonry is now controlled by its own toggle
7164  if(useMasonry) {
7165    container.setAttribute('data-masonry', 'true');
7166  } else {
7167    container.removeAttribute('data-masonry');
7168  }
7169
7170  // Set uppercase data attributes from config
7171  const uppercaseParents = data.config['uppercaseParents'] || false;
7172  const uppercaseChildren = data.config['uppercaseChildren'] || false;
7173
7174  // Get selected item styling from config
7175  const underlineSelected = data.config['underlineSelected'] !== false;
7176  const selectedBgColor = data.config['selectedBgColor'] || 'transparent';
7177
7178  if(uppercaseParents) container.setAttribute('data-uppercase-parents', 'true');
7179  else container.removeAttribute('data-uppercase-parents');
7180
7181  if(uppercaseChildren) container.setAttribute('data-uppercase-children', 'true');
7182  else container.removeAttribute('data-uppercase-children');
7183
7184  if(underlineSelected) container.setAttribute('data-underline-selected', 'true');
7185  else container.removeAttribute('data-underline-selected');
7186
7187  container.style.setProperty('--selected-bg-color', selectedBgColor);
7188
7189  // Helpful attribute only
7190  container.setAttribute('data-columns', String(columnCount));
7191
7192  // Get current page URL for active state detection
7193  const currentPagePath = cleanUrl(window.location.href);
7194
7195  // Recursive function to extract nested children
7196  function extractChildren(parentLi, level = 1) {
7197    const childUL = parentLi.querySelector(':scope > ul, :scope .sub-nav ul, :scope .submenu ul, :scope .dropdown ul, :scope .children, :scope ul');
7198    const children = [];
7199
7200    if(childUL){
7201      Array.from(childUL.querySelectorAll(':scope > li')).forEach(cli => {
7202        const a = cli.querySelector('a');
7203        if(!a) return;
7204        const title = cleanText(a);
7205        const url = cleanUrl(a.getAttribute('href')||'#');
7206        if(!title || !url || url === '#') return;
7207
7208        const nestedChildren = extractChildren(cli, level + 1);
7209
7210        children.push({
7211          title,
7212          url,
7213          children: nestedChildren,
7214          level
7215        });
7216      });
7217    }
7218
7219    return children;
7220  }
7221
7222  function extractNavFromDOM(){
7223    const navCandidates = [
7224      'nav[role="navigation"]','.dmNavWrapper','.dmNav','.navigation','nav','header nav'
7225    ];
7226    let navRoot=null;
7227
7228    for(const sel of navCandidates){
7229      const el=document.querySelector(sel);
7230      if(!el) continue;
7231      if(el.querySelector('li ul, li .sub-nav, li .submenu, li .dropdown')){ navRoot=el; break; }
7232      if(!navRoot) navRoot=el;
7233    }
7234    if(!navRoot) return [];
7235
7236    const topUL = navRoot.querySelector('ul, .menu, .dmNav');
7237    const topLIs = topUL
7238      ? Array.from(topUL.children).filter(li=>li.tagName==='LI')
7239      : Array.from(navRoot.querySelectorAll(':scope > li, :scope ul > li'));
7240
7241    const parents=[];
7242    topLIs.forEach(li=>{
7243      const parentLink =
7244        li.querySelector(':scope > a, :scope > .nav-item > a, :scope > .menu-item > a') ||
7245        li.querySelector('a');
7246
7247      const parentTitle = cleanText(parentLink);
7248      const parentUrl   = cleanUrl(parentLink?.getAttribute('href')||'#');
7249
7250      if(!parentTitle) return;
7251
7252      // Skip if parent title is in exclusion list
7253      if(excludeList.includes(parentTitle.toLowerCase())) return;
7254
7255      const children = extractChildren(li, 1);
7256
7257      // Include if: has children OR is in inclusion list
7258      const isInIncludeList = includeList.length > 0 && includeList.includes(parentTitle.toLowerCase());
7259
7260      if(children.length > 0 || isInIncludeList) {
7261        parents.push({ title: parentTitle, url: parentUrl, children, level: 0 });
7262      }
7263    });
7264
7265    return parents;
7266  }
7267
7268  // Recursive function to render nested children
7269  function renderNestedChildren(children, parentPath = '') {
7270    return children.map((child, childIndex) => {
7271      const nodePath = `${parentPath}-${childIndex}`;
7272      const hasChildren = child.children && child.children.length > 0;
7273      const level = child.level || 1;
7274      const isActive = child.url === currentPagePath;
7275      const activeClass = isActive ? ' ngw-active' : '';
7276
7277      if(hasChildren) {
7278        return `
7279          <div class="ngw-nested-group" data-level="${level}" data-path="${nodePath}">
7280            <div class="ngw-nested-parent" data-path="${nodePath}">
7281              <a href="${child.url}" class="ngw-child-link${activeClass}">${child.title}</a>
7282              <button class="ngw-nested-toggle" type="button" aria-label="Toggle ${child.title}">
7283                <span class="ngw-chevron" aria-hidden="true">${chevronIcon}</span>
7284              </button>
7285            </div>
7286            <div class="ngw-nested-children" data-path="${nodePath}">
7287              ${renderNestedChildren(child.children, nodePath)}
7288            </div>
7289          </div>
7290        `;
7291      }
7292
7293      return `<a href="${child.url}" class="ngw-child-link${activeClass}" data-level="${level}">${child.title}</a>`;
7294    }).join('');
7295  }
7296
7297  function render(gridData){
7298    container.innerHTML = gridData.map((parent, idx) => {
7299      const kids = renderNestedChildren(parent.children, `${idx}`);
7300      const hasChildren = parent.children && parent.children.length > 0;
7301
7302      const isParentActive = parent.url === currentPagePath;
7303      const activeClass = isParentActive ? ' ngw-active' : '';
7304
7305      if(!hasChildren) {
7306        return `
7307          <section class="ngw-group ngw-no-children" role="group" data-index="${idx}">
7308            <div class="ngw-parent" data-index="${idx}">
7309              <a href="${parent.url}" class="ngw-parent-link${activeClass}">${parent.title}</a>
7310            </div>
7311          </section>
7312        `;
7313      }
7314
7315      const initialExpanded = expandAll ? 'true' : 'false';
7316      return `
7317        <section class="ngw-group" role="group" aria-expanded="${initialExpanded}" data-index="${idx}">
7318          <div class="ngw-parent" data-index="${idx}" aria-controls="ngw-children-${idx}">
7319            <a href="${parent.url}" class="ngw-parent-link${activeClass}">${parent.title}</a>
7320            <button class="ngw-toggle" type="button" aria-label="Toggle ${parent.title}">
7321              <span class="ngw-chevron" aria-hidden="true">${chevronIcon}</span>
7322            </button>
7323          </div>
7324          <div id="ngw-children-${idx}" class="ngw-children" role="region" aria-label="${parent.title} subpages">
7325            ${kids}
7326          </div>
7327        </section>
7328      `;
7329    }).join('');
7330
7331    // If expandAll is enabled, also expand all nested groups
7332    if(expandAll) {
7333      container.querySelectorAll('.ngw-nested-group').forEach(g => {
7334        g.setAttribute('aria-expanded', 'true');
7335      });
7336    }
7337
7338    function collapseAll(){
7339      container.querySelectorAll('.ngw-group').forEach(g=>g.setAttribute('aria-expanded','false'));
7340      container.querySelectorAll('.ngw-nested-group').forEach(g=>g.removeAttribute('aria-expanded'));
7341    }
7342    function expandOnly(index){
7343      container.querySelectorAll('.ngw-group').forEach((g)=> {
7344        const i = parseInt(g.getAttribute('data-index')||'-1',10);
7345        g.setAttribute('aria-expanded', String(i===index));
7346      });
7347    }
7348
7349    // Hover functionality - only if not in expandAll mode
7350    if(openOnHover && !expandAll) {
7351      container.addEventListener('mouseenter', (e) => {
7352        const group = e.target.closest('.ngw-group');
7353        if(!group || group.classList.contains('ngw-no-children')) return;
7354        const index = parseInt(group.querySelector('.ngw-parent')?.getAttribute('data-index') || '-1', 10);
7355        if(index >= 0) expandOnly(index);
7356      }, true);
7357
7358      container.addEventListener('mouseleave', (e) => {
7359        if(e.target.closest('.ngw-group')) return;
7360        collapseAll();
7361      }, true);
7362
7363      // Nested hover
7364      container.addEventListener('mouseenter', (e) => {
7365        const nestedGroup = e.target.closest('.ngw-nested-group');
7366        if(!nestedGroup) return;
7367        nestedGroup.setAttribute('aria-expanded', 'true');
7368      }, true);
7369
7370      container.addEventListener('mouseleave', (e) => {
7371        const nestedGroup = e.target.closest('.ngw-nested-group');
7372        if(!nestedGroup) return;
7373        nestedGroup.removeAttribute('aria-expanded');
7374      }, true);
7375    }
7376
7377    // ----------------------------
7378    // Toggle behavior (works in BOTH modes)
7379    // - expandAll=false: row click toggles + only one open at a time
7380    // - expandAll=true : ONLY chevron buttons toggle; everything starts expanded
7381    // ----------------------------
7382    container.addEventListener('click', (e) => {
7383      // 1) Parent chevron button toggles its group (works even when expandAll=true)
7384      const parentToggleBtn = e.target.closest('.ngw-toggle');
7385      if (parentToggleBtn) {
7386        const group = parentToggleBtn.closest('.ngw-group');
7387        if (!group || group.classList.contains('ngw-no-children')) return;
7388
7389        const isOpen = group.getAttribute('aria-expanded') === 'true';
7390
7391        // In expandAll mode: toggle ONLY this group
7392        if (expandAll) {
7393          group.setAttribute('aria-expanded', String(!isOpen));
7394        } else {
7395          // In normal mode: preserve "only one open at a time"
7396          const index = parseInt(group.getAttribute('data-index') || '-1', 10);
7397          if (index >= 0) {
7398            if (isOpen) collapseAll();
7399            else expandOnly(index);
7400          }
7401        }
7402
7403        e.preventDefault();
7404        e.stopPropagation();
7405        return;
7406      }
7407
7408      // 2) Nested chevron button toggles its nested group (works even when expandAll=true)
7409      const nestedToggleBtn = e.target.closest('.ngw-nested-toggle');
7410      if (nestedToggleBtn) {
7411        const nestedGroup = nestedToggleBtn.closest('.ngw-nested-group');
7412        if (!nestedGroup) return;
7413
7414        const isOpen = nestedGroup.getAttribute('aria-expanded') === 'true';
7415        if (isOpen) nestedGroup.removeAttribute('aria-expanded');
7416        else nestedGroup.setAttribute('aria-expanded', 'true');
7417
7418        e.preventDefault();
7419        e.stopPropagation();
7420        return;
7421      }
7422
7423      // 3) In expandAll mode, do NOT allow row-click toggling (only chevrons toggle)
7424      if (expandAll) return;
7425
7426      // 4) Normal mode: clicking anywhere on parent row toggles (except the parent hyperlink)
7427      const parentRow = e.target.closest('.ngw-parent');
7428      if (!parentRow) return;
7429
7430      // If they clicked the parent hyperlink, let the browser navigate naturally
7431      if (e.target.closest('.ngw-parent-link')) return;
7432
7433      const index = parseInt(parentRow.getAttribute('data-index') || '-1', 10);
7434      if (index < 0) return;
7435
7436      const group = parentRow.closest('.ngw-group');
7437      if (!group || group.classList.contains('ngw-no-children')) return;
7438
7439      const isOpen = group.getAttribute('aria-expanded') === 'true';
7440      if (isOpen) collapseAll();
7441      else expandOnly(index);
7442    });
7443
7444    // Keyboard support
7445    container.addEventListener('keydown', (e) => {
7446      if (expandAll) return; // chevron buttons already work via native button behavior
7447
7448      const parentRow = e.target.closest('.ngw-parent');
7449      const nestedParent = e.target.closest('.ngw-nested-parent');
7450
7451      if (parentRow && !nestedParent) {
7452        if (document.activeElement && document.activeElement.classList.contains('ngw-parent-link') && e.key === 'Enter') {
7453          return;
7454        }
7455        const group = parentRow.closest('.ngw-group');
7456        if (group && group.classList.contains('ngw-no-children')) return;
7457
7458        if (e.key === ' ' || e.key === 'Enter') {
7459          e.preventDefault();
7460          const index = parseInt(parentRow.getAttribute('data-index') || '-1', 10);
7461          if (index < 0) return;
7462          const isOpen = group.getAttribute('aria-expanded') === 'true';
7463          if (isOpen) collapseAll();
7464          else expandOnly(index);
7465        }
7466      }
7467
7468      if (nestedParent) {
7469        if (document.activeElement && document.activeElement.classList.contains('ngw-child-link') && e.key === 'Enter') {
7470          return;
7471        }
7472        if (e.key === ' ' || e.key === 'Enter') {
7473          e.preventDefault();
7474          const nestedGroup = nestedParent.closest('.ngw-nested-group');
7475          if (!nestedGroup) return;
7476          const isOpen = nestedGroup.getAttribute('aria-expanded') === 'true';
7477          if (isOpen) nestedGroup.removeAttribute('aria-expanded');
7478          else nestedGroup.setAttribute('aria-expanded', 'true');
7479        }
7480      }
7481    });
7482  }
7483
7484  function init(){
7485    const build = ()=>{
7486      const navData = extractNavFromDOM();
7487      if(navData && navData.length){
7488        render(navData);
7489        return true;
7490      }
7491      return false;
7492    };
7493    const okNow = build();
7494    if(!okNow){ setTimeout(build, 350); } // retry once for late-loaded nav
7495  }
7496
7497  init();
7498});
7499    };
7500</script>
7500
7501
7502<script>
7503    window.customWidgetsFunctions["31ea1ee9b19c4368abcc1dbd5ec6a0d6~2"] = function (element, data, api) {
7504        window.dmAPI.runOnReady('cross-collection-image', async () => {
7505  const containerEl = element.querySelector('.cross-collection-image');
7506  const overlayEl = element.querySelector('.cross-collection-image-overlay');
7507
7508  if (!containerEl) return;
7509
7510  const config = data.config || {};
7511
7512  function safeTrim(value) {
7513    return value == null ? '' : String(value).trim();
7514  }
7515
7516  function stripHtml(value) {
7517    const temporaryElement = document.createElement('div');
7518    temporaryElement.innerHTML = safeTrim(value);
7519
7520    return safeTrim(
7521      temporaryElement.textContent ||
7522      temporaryElement.innerText ||
7523      ''
7524    );
7525  }
7526
7527  function removeDiacritics(value) {
7528    const text = safeTrim(value);
7529
7530    if (typeof text.normalize !== 'function') {
7531      return text;
7532    }
7533
7534    return text
7535      .normalize('NFKD')
7536      .replace(/[\u0300-\u036f]/g, '');
7537  }
7538
7539  function slugify(value) {
7540    return removeDiacritics(value)
7541      .toLowerCase()
7542      .replace(/[’']/g, '')
7543      .replace(/[^a-z0-9]+/g, '-')
7544      .replace(/-+/g, '-')
7545      .replace(/^-+|-+$/g, '');
7546  }
7547
7548  /*
7549    Creates alternate URL slug versions.
7550
7551    Showers & Doors:
7552    - showers-and-doors
7553    - showers-doors
7554
7555    Showers and Doors:
7556    - showers-and-doors
7557    - showers-doors
7558
7559    A/C Repair:
7560    - a-c-repair
7561    - ac-repair
7562
7563    Heating/Cooling:
7564    - heating-cooling
7565    - heatingcooling
7566  */
7567  function createSlugCandidates(value) {
7568    const originalText = stripHtml(value);
7569    const candidates = [];
7570    const seen = new Set();
7571
7572    function addCandidate(candidateText) {
7573      const candidate = slugify(candidateText);
7574
7575      if (candidate && !seen.has(candidate)) {
7576        seen.add(candidate);
7577        candidates.push(candidate);
7578      }
7579    }
7580
7581    if (!originalText) {
7582      return candidates;
7583    }
7584
7585    const ampersandVariants = [
7586      originalText.replace(/&/g, ' and '),
7587      originalText.replace(/&/g, ' '),
7588      originalText
7589    ];
7590
7591    ampersandVariants.forEach(ampersandVariant => {
7592      const andVariants = [
7593        ampersandVariant,
7594        ampersandVariant.replace(/\band\b/gi, ' ')
7595      ];
7596
7597      andVariants.forEach(andVariant => {
7598        /*
7599          Treat a slash as a separator.
7600
7601          A/C Repair becomes a-c-repair.
7602        */
7603        addCandidate(
7604          andVariant.replace(/\s*[\\/]+\s*/g, ' ')
7605        );
7606
7607        /*
7608          Remove a slash completely.
7609
7610          A/C Repair becomes ac-repair.
7611        */
7612        addCandidate(
7613          andVariant.replace(/\s*[\\/]+\s*/g, '')
7614        );
7615      });
7616    });
7617
7618    return candidates;
7619  }
7620
7621  /*
7622    Supports stored values such as:
7623
7624    college-transcripts
7625    /category/college-transcripts/
7626    https://example.com/category/college-transcripts/
7627  */
7628  function normalizeStoredSlug(value) {
7629    let slug = safeTrim(value);
7630
7631    if (!slug) return '';
7632
7633    try {
7634      slug = decodeURIComponent(slug);
7635    } catch (e) {
7636      // Retain the original value.
7637    }
7638
7639    slug = slug
7640      .split('#')[0]
7641      .split('?')[0]
7642      .replace(/^https?:\/\/[^/]+/i, '')
7643      .replace(/^\/+|\/+$/g, '');
7644
7645    if (slug.includes('/')) {
7646      slug = slug
7647        .split('/')
7648        .filter(Boolean)
7649        .pop() || '';
7650    }
7651
7652    return safeTrim(slug).toLowerCase();
7653  }
7654
7655  function getCurrentSlug() {
7656    const pathParts = (window.location.pathname || '')
7657      .split('/')
7658      .filter(Boolean);
7659
7660    return normalizeStoredSlug(
7661      pathParts.pop() || ''
7662    );
7663  }
7664
7665  /*
7666    Used to find the collection row representing
7667    the current page.
7668  */
7669  function getSourceEntrySlugs(entry) {
7670    if (!entry) return [];
7671
7672    const row = entry.data || {};
7673
7674    return [
7675      entry.page_item_url,
7676      row.page_item_url,
7677      entry.item,
7678      row.item,
7679      entry.name,
7680      row.name,
7681      entry.url,
7682      row.url
7683    ]
7684      .map(normalizeStoredSlug)
7685      .filter(Boolean);
7686  }
7687
7688  /*
7689    Destination matching is based on page_item_url.
7690  */
7691  function getDestinationEntrySlugs(entry) {
7692    if (!entry) return [];
7693
7694    const row = entry.data || {};
7695
7696    return [
7697      entry.page_item_url,
7698      row.page_item_url
7699    ]
7700      .map(normalizeStoredSlug)
7701      .filter(Boolean);
7702  }
7703
7704  function findSourceEntry(
7705    entries,
7706    currentSlug,
7707    sourcePageType
7708  ) {
7709    if (
7710      !Array.isArray(entries) ||
7711      entries.length === 0
7712    ) {
7713      return null;
7714    }
7715
7716    if (
7717      currentSlug &&
7718      currentSlug !== '~page-item~'
7719    ) {
7720      const matchedEntry = entries.find(entry =>
7721        getSourceEntrySlugs(entry).includes(
7722          currentSlug
7723        )
7724      );
7725
7726      if (matchedEntry) {
7727        return matchedEntry;
7728      }
7729    }
7730
7731    /*
7732      01 HOME normally contains one row.
7733
7734      Only Home Page mode may use the first row.
7735      Category Page mode must match the current URL slug.
7736    */
7737    if (sourcePageType === 'home') {
7738      return entries[0];
7739    }
7740
7741    return null;
7742  }
7743
7744  function findDestination(
7745    entries,
7746    slugCandidates
7747  ) {
7748    if (
7749      !Array.isArray(entries) ||
7750      !Array.isArray(slugCandidates)
7751    ) {
7752      return null;
7753    }
7754
7755    for (const candidate of slugCandidates) {
7756      const matchedEntry = entries.find(entry =>
7757        getDestinationEntrySlugs(entry).includes(
7758          candidate
7759        )
7760      );
7761
7762      if (matchedEntry) {
7763        return {
7764          entry: matchedEntry,
7765          slug: candidate
7766        };
7767      }
7768    }
7769
7770    return null;
7771  }
7772
7773  /*
7774    Supports either a plain image URL or an image object
7775    returned by a Duda collection image field.
7776  */
7777  function extractImageUrl(imageValue) {
7778    if (!imageValue) return '';
7779
7780    if (typeof imageValue === 'string') {
7781      return safeTrim(imageValue);
7782    }
7783
7784    if (typeof imageValue !== 'object') {
7785      return '';
7786    }
7787
7788    const possibleValues = [
7789      imageValue.url,
7790      imageValue.src,
7791      imageValue.image_url,
7792      imageValue.original_url,
7793      imageValue.originalUrl,
7794      imageValue.full,
7795      imageValue.data && imageValue.data.url,
7796      imageValue.data && imageValue.data.src
7797    ];
7798
7799    for (const possibleValue of possibleValues) {
7800      const imageUrl = safeTrim(possibleValue);
7801
7802      if (imageUrl) {
7803        return imageUrl;
7804      }
7805    }
7806
7807    return '';
7808  }
7809
7810  function normalizePageType(value) {
7811    const normalizedValue =
7812      safeTrim(value).toLowerCase();
7813
7814    if (
7815      normalizedValue === 'category' ||
7816      normalizedValue === 'category page'
7817    ) {
7818      return 'category';
7819    }
7820
7821    return 'home';
7822  }
7823
7824  function getSelectedBlockNumber(value) {
7825    const configuredValue = safeTrim(value);
7826
7827    const numberMatch = configuredValue.match(
7828      /(?:^|\D)(10|[1-9])(?:\D|$)/
7829    );
7830
7831    return numberMatch
7832      ? numberMatch[1]
7833      : '1';
7834  }
7835
7836  function normalizeDimension(value, fallbackValue) {
7837    const configuredValue = safeTrim(value);
7838
7839    if (!configuredValue) {
7840      return fallbackValue;
7841    }
7842
7843    if (/^\d+(\.\d+)?$/.test(configuredValue)) {
7844      return configuredValue + 'px';
7845    }
7846
7847    if (
7848      /^\d+(\.\d+)?(px|rem|em|vh|vw|%)$/i.test(
7849        configuredValue
7850      )
7851    ) {
7852      return configuredValue;
7853    }
7854
7855    return fallbackValue;
7856  }
7857
7858  function normalizeBackgroundSize(value) {
7859    const configuredValue =
7860      safeTrim(value).toLowerCase();
7861
7862    if (
7863      configuredValue === 'contain' ||
7864      configuredValue === 'auto'
7865    ) {
7866      return configuredValue;
7867    }
7868
7869    return 'cover';
7870  }
7871
7872  function normalizeBackgroundPosition(value) {
7873    const configuredValue =
7874      safeTrim(value).toLowerCase();
7875
7876    const allowedPositions = [
7877      'center center',
7878      'top center',
7879      'bottom center',
7880      'center left',
7881      'center right',
7882      'top left',
7883      'top right',
7884      'bottom left',
7885      'bottom right'
7886    ];
7887
7888    return allowedPositions.includes(configuredValue)
7889      ? configuredValue
7890      : 'center center';
7891  }
7892
7893  function normalizeOpacity(value) {
7894    const parsedValue = Number(value);
7895
7896    if (!Number.isFinite(parsedValue)) {
7897      return 0;
7898    }
7899
7900    if (parsedValue > 1) {
7901      return Math.min(
7902        Math.max(parsedValue / 100, 0),
7903        1
7904      );
7905    }
7906
7907    return Math.min(
7908      Math.max(parsedValue, 0),
7909      1
7910    );
7911  }
7912
7913  function normalizeBoolean(value) {
7914    if (typeof value === 'boolean') {
7915      return value;
7916    }
7917
7918    const normalizedValue =
7919      safeTrim(value).toLowerCase();
7920
7921    return [
7922      'true',
7923      '1',
7924      'yes',
7925      'on',
7926      'enabled'
7927    ].includes(normalizedValue);
7928  }
7929
7930  function applyDesignSettings() {
7931    const imageHeight = normalizeDimension(
7932      config.imageHeight,
7933      '400px'
7934    );
7935
7936    const borderRadius = normalizeDimension(
7937      config.borderRadius,
7938      '0px'
7939    );
7940
7941    const backgroundSize =
7942      normalizeBackgroundSize(
7943        config.backgroundSize
7944      );
7945
7946    const backgroundPosition =
7947      normalizeBackgroundPosition(
7948        config.backgroundPosition
7949      );
7950
7951    const overlayColor =
7952      safeTrim(config.overlayColor) ||
7953      '#000000';
7954
7955    const overlayOpacity =
7956      normalizeOpacity(
7957        config.overlayOpacity
7958      );
7959
7960    const enableHoverLift =
7961      normalizeBoolean(
7962        config.enableHoverLift
7963      );
7964
7965    containerEl.style.setProperty(
7966      '--cross-image-height',
7967      imageHeight
7968    );
7969
7970    containerEl.style.setProperty(
7971      '--cross-image-border-radius',
7972      borderRadius
7973    );
7974
7975    containerEl.style.setProperty(
7976      '--cross-image-background-size',
7977      backgroundSize
7978    );
7979
7980    containerEl.style.setProperty(
7981      '--cross-image-background-position',
7982      backgroundPosition
7983    );
7984
7985    containerEl.classList.toggle(
7986      'hover-lift-enabled',
7987      enableHoverLift
7988    );
7989
7990    if (overlayEl) {
7991      overlayEl.style.setProperty(
7992        '--cross-image-overlay-color',
7993        overlayColor
7994      );
7995
7996      overlayEl.style.setProperty(
7997        '--cross-image-overlay-opacity',
7998        String(overlayOpacity)
7999      );
8000    }
8001  }
8002
8003  function createCssBackgroundImage(imageUrl) {
8004    const escapedUrl = imageUrl
8005      .replace(/\\/g, '\\\\')
8006      .replace(/"/g, '\\"');
8007
8008    return `url("${escapedUrl}")`;
8009  }
8010
8011  function showImage(imageUrl, accessibleLabel) {
8012    containerEl.style.backgroundImage =
8013      createCssBackgroundImage(imageUrl);
8014
8015    containerEl.setAttribute(
8016      'aria-busy',
8017      'false'
8018    );
8019
8020    containerEl.setAttribute(
8021      'aria-hidden',
8022      'false'
8023    );
8024
8025    containerEl.setAttribute(
8026      'aria-label',
8027      accessibleLabel || 'Page image'
8028    );
8029
8030    containerEl.classList.remove(
8031      'is-hidden'
8032    );
8033
8034    containerEl.classList.add(
8035      'is-loaded'
8036    );
8037  }
8038
8039  function hideImage() {
8040    containerEl.style.backgroundImage = '';
8041
8042    containerEl.setAttribute(
8043      'aria-busy',
8044      'false'
8045    );
8046
8047    containerEl.setAttribute(
8048      'aria-hidden',
8049      'true'
8050    );
8051
8052    containerEl.removeAttribute(
8053      'aria-label'
8054    );
8055
8056    containerEl.classList.remove(
8057      'is-loaded'
8058    );
8059
8060    containerEl.classList.add(
8061      'is-hidden'
8062    );
8063  }
8064
8065  /*
8066    Collection requests are shared between multiple
8067    copies of this widget on the same page.
8068  */
8069  function getCollectionCache() {
8070    if (
8071      !window.__crossCollectionImageCache
8072    ) {
8073      window.__crossCollectionImageCache = {};
8074    }
8075
8076    return window.__crossCollectionImageCache;
8077  }
8078
8079  function getCollectionsApiPromise() {
8080    if (
8081      !window.__crossCollectionImageApiPromise
8082    ) {
8083      window.__crossCollectionImageApiPromise =
8084        dmAPI
8085          .loadCollectionsAPI()
8086          .catch(error => {
8087            delete window
8088              .__crossCollectionImageApiPromise;
8089
8090            throw error;
8091          });
8092    }
8093
8094    return window.__crossCollectionImageApiPromise;
8095  }
8096
8097  function loadCollectionEntries(collectionName) {
8098    const collectionCache =
8099      getCollectionCache();
8100
8101    if (!collectionCache[collectionName]) {
8102      collectionCache[collectionName] =
8103        getCollectionsApiPromise()
8104          .then(collectionApi =>
8105            collectionApi
8106              .data(collectionName)
8107              .get()
8108          )
8109          .then(result => {
8110            return Array.isArray(
8111              result && result.values
8112            )
8113              ? result.values
8114              : [];
8115          })
8116          .catch(error => {
8117            delete collectionCache[
8118              collectionName
8119            ];
8120
8121            throw error;
8122          });
8123    }
8124
8125    return collectionCache[
8126      collectionName
8127    ];
8128  }
8129
8130  applyDesignSettings();
8131
8132  try {
8133    const sourcePageType =
8134      normalizePageType(
8135        config.sourcePageType
8136      );
8137
8138    const sourceCollectionName =
8139      sourcePageType === 'category'
8140        ? '02 CATEGORY'
8141        : '01 HOME';
8142
8143    const currentSlug =
8144      getCurrentSlug();
8145
8146    const sourceEntries =
8147      await loadCollectionEntries(
8148        sourceCollectionName
8149      );
8150
8151    const sourceEntry =
8152      findSourceEntry(
8153        sourceEntries,
8154        currentSlug,
8155        sourcePageType
8156      );
8157
8158    if (!sourceEntry) {
8159      hideImage();
8160      return;
8161    }
8162
8163    const blockNumber =
8164      getSelectedBlockNumber(
8165        config.copyBlockNumber
8166      );
8167
8168    const headlineFieldName =
8169      'COPY BLOCK ' +
8170      blockNumber +
8171      ' HEADLINE';
8172
8173    const sourceRow =
8174      sourceEntry.data ||
8175      sourceEntry;
8176
8177    const rawHeadline =
8178      sourceRow[headlineFieldName];
8179
8180    const plainHeadline =
8181      stripHtml(rawHeadline);
8182
8183    const slugCandidates =
8184      createSlugCandidates(rawHeadline);
8185
8186    if (
8187      slugCandidates.length === 0
8188    ) {
8189      hideImage();
8190      return;
8191    }
8192
8193    /*
8194      Reuse 02 CATEGORY when it was already loaded
8195      as the source collection.
8196    */
8197    const categoryEntriesPromise =
8198      sourceCollectionName ===
8199      '02 CATEGORY'
8200        ? Promise.resolve(sourceEntries)
8201        : loadCollectionEntries(
8202            '02 CATEGORY'
8203          ).catch(() => []);
8204
8205    const coreEntriesPromise =
8206      loadCollectionEntries(
8207        '03 CORE'
8208      ).catch(() => []);
8209
8210    const [
8211      categoryEntries,
8212      coreEntries
8213    ] = await Promise.all([
8214      categoryEntriesPromise,
8215      coreEntriesPromise
8216    ]);
8217
8218    /*
8219      Home Page:
8220      1. Search 02 CATEGORY
8221      2. Search 03 CORE
8222
8223      Category Page:
8224      1. Search 03 CORE
8225      2. Search 02 CATEGORY
8226    */
8227    const destinationSearchOrder =
8228      sourcePageType === 'home'
8229        ? [
8230            categoryEntries,
8231            coreEntries
8232          ]
8233        : [
8234            coreEntries,
8235            categoryEntries
8236          ];
8237
8238    let matchedDestination = null;
8239
8240    for (
8241      const destinationEntries
8242      of destinationSearchOrder
8243    ) {
8244      const match = findDestination(
8245        destinationEntries,
8246        slugCandidates
8247      );
8248
8249      if (match) {
8250        matchedDestination = match;
8251        break;
8252      }
8253    }
8254
8255    if (!matchedDestination) {
8256      hideImage();
8257      return;
8258    }
8259
8260    const destinationRow =
8261      matchedDestination.entry.data ||
8262      matchedDestination.entry;
8263
8264    const heroImageUrl =
8265      extractImageUrl(
8266        destinationRow['HERO IMAGE']
8267      );
8268
8269    if (!heroImageUrl) {
8270      hideImage();
8271      return;
8272    }
8273
8274    showImage(
8275      heroImageUrl,
8276      plainHeadline
8277    );
8278  } catch (e) {
8279    hideImage();
8280  }
8281});
8282    };
8283</script>
8283
8284
8285<script>
8286    window.customWidgetsFunctions["901b1c7285754fceac707cdcb08aa571~7"] = function (element, data, api) {
8287        window.dmAPI.runOnReady('hide-column-if-category-field-empty', function () {
8288
8289  var shouldHide = false;   // track the decision so we can re-apply it
8290  var observer = null;      // MutationObserver reference
8291
8292  function getLastSlug(value) {
8293    if (!value) return '';
8294
8295    value = String(value).trim().toLowerCase();
8296
8297    try {
8298      if (value.indexOf('http://') === 0 || value.indexOf('https://') === 0) {
8299        value = new URL(value).pathname;
8300      }
8301    } catch (e) {}
8302
8303    value = value.split('?')[0].split('#')[0];
8304    value = value.replace(/\/+$/, '');
8305
8306    var parts = value.split('/');
8307    return parts[parts.length - 1] || '';
8308  }
8309
8310  function getCurrentColumn() {
8311    var node = element.parentElement; // flex-element widget-wrapper
8312
8313    while (node && node !== document.body) {
8314      var cls = node.className || '';
8315
8316      if (cls.indexOf('flex-element') !== -1 && cls.indexOf('group') !== -1) {
8317        return node;
8318      }
8319
8320      if (
8321        node.matches('[data-layout-column]') ||
8322        node.matches('.dmRespCol') ||
8323        node.matches('[data-page-element-type="column"]')
8324      ) {
8325        return node;
8326      }
8327
8328      node = node.parentElement;
8329    }
8330
8331    return element.parentElement || null;
8332  }
8333
8334  function applyHide() {
8335    var column = getCurrentColumn();
8336
8337    if (!column) return;
8338
8339    // Only touch the style if it isn't already correct — avoids triggering the observer
8340    if (shouldHide) {
8341      if (column.style.display !== 'none') {
8342        column.style.setProperty('display', 'none', 'important');
8343        console.log('[HideColumn] (re-)applied hide to column:', column);
8344      }
8345    } else {
8346      column.style.removeProperty('display');
8347    }
8348  }
8349
8350  function startObserver() {
8351    var column = getCurrentColumn();
8352
8353    if (!column || observer) return; // don't double-attach
8354
8355    observer = new MutationObserver(function (mutations) {
8356      if (!shouldHide) return;
8357
8358      for (var i = 0; i < mutations.length; i++) {
8359        var m = mutations[i];
8360
8361        if (m.type === 'attributes' && m.attributeName === 'style') {
8362          var col = getCurrentColumn();
8363
8364          if (col && col.style.display !== 'none') {
8365            // Something reset the display — put it back
8366            col.style.setProperty('display', 'none', 'important');
8367            console.log('[HideColumn] Observer re-hid column after external style reset');
8368          }
8369        }
8370      }
8371    });
8372
8373    observer.observe(column, { attributes: true, attributeFilter: ['style'] });
8374    console.log('[HideColumn] MutationObserver attached to column:', column);
8375  }
8376
8377  function checkCollection(marker) {
8378    var collectionName = marker.getAttribute('data-collection-name') || '02 CATEGORY';
8379    var requiredFieldName = marker.getAttribute('data-required-field') || 'COPY BLOCK 1 HEADLINE';
8380    var currentSlug = getLastSlug(window.location.href);
8381
8382    console.log('[HideColumn] Current slug:', currentSlug);
8383    console.log('[HideColumn] Collection:', collectionName);
8384    console.log('[HideColumn] Required field:', requiredFieldName);
8385
8386    if (!window.dmAPI || typeof dmAPI.loadCollectionsAPI !== 'function') {
8387      console.warn('[HideColumn] Duda Collections API not available.');
8388      return;
8389    }
8390
8391    dmAPI.loadCollectionsAPI()
8392      .then(function (collection) {
8393        return collection
8394          .data(collectionName)
8395          .pageSize(100)
8396          .get();
8397      })
8398      .then(function (result) {
8399        console.log('[HideColumn] Raw result:', result);
8400
8401        var rows = [];
8402
8403        if (result && Array.isArray(result.values)) {
8404          rows = result.values;
8405        } else if (Array.isArray(result)) {
8406          rows = result;
8407        }
8408
8409        console.log('[HideColumn] Total rows:', rows.length);
8410
8411        if (rows.length > 0) {
8412          console.log('[HideColumn] First row dump:', rows[0]);
8413          console.log('[HideColumn] First row keys:', Object.keys(rows[0] || {}));
8414
8415          if (rows[0] && rows[0].data) {
8416            console.log('[HideColumn] First row data keys:', Object.keys(rows[0].data || {}));
8417          }
8418        }
8419
8420        var matchingRow = null;
8421
8422        /*
8423          Special handling for 01 HOME:
8424          Do not match against the current URL slug.
8425          Just read the first row in the collection.
8426          Expected page_item_url is /home.
8427        */
8428        if (collectionName === '01 HOME') {
8429          matchingRow = rows[0] || null;
8430
8431          if (matchingRow) {
8432            console.log('[HideColumn] 01 HOME detected — using first row:', matchingRow);
8433          } else {
8434            console.warn('[HideColumn] 01 HOME detected, but no rows were found.');
8435          }
8436
8437        } else {
8438
8439          for (var i = 0; i < rows.length; i++) {
8440            var row = rows[i];
8441            var rowData = row.data || row;
8442
8443            var rawSlugValue =
8444              row.page_item_url ||
8445              rowData.page_item_url ||
8446              rowData['page_item_url'] ||
8447              rowData['item'] ||
8448              rowData['slug'] ||
8449              row.item ||
8450              row.slug ||
8451              '';
8452
8453            var rowSlug = getLastSlug(rawSlugValue);
8454
8455            console.log('[HideColumn] Row', i, 'raw slug candidate:', rawSlugValue);
8456            console.log('[HideColumn] Row', i, 'normalized slug:', rowSlug);
8457
8458            if (rowSlug === currentSlug) {
8459              matchingRow = row;
8460              console.log('[HideColumn] Matching row found:', matchingRow);
8461              break;
8462            }
8463          }
8464        }
8465
8466        if (!matchingRow) {
8467          console.warn('[HideColumn] No matching row found for collection:', collectionName);
8468          return;
8469        }
8470
8471        var matchingRowData = matchingRow.data || matchingRow;
8472        var fieldValue = matchingRowData[requiredFieldName];
8473
8474        console.log('[HideColumn] Field "' + requiredFieldName + '" value:', fieldValue);
8475
8476        var hasValue =
8477          fieldValue !== null &&
8478          fieldValue !== undefined &&
8479          String(fieldValue).trim() !== '';
8480
8481        shouldHide = !hasValue;
8482
8483        if (shouldHide) {
8484          console.log('[HideColumn] Field is empty — hiding column.');
8485          applyHide();
8486          startObserver();
8487        } else {
8488          console.log('[HideColumn] Field has value — column stays visible.');
8489          applyHide();
8490        }
8491      })
8492      .catch(function (error) {
8493        console.warn('[HideColumn] Collection check failed:', error);
8494      });
8495  }
8496
8497  function run() {
8498    var markers = element.querySelectorAll('.hide-column-if-category-field-empty');
8499
8500    for (var i = 0; i < markers.length; i++) {
8501      checkCollection(markers[i]);
8502    }
8503
8504    // Retry after 1.5s and again after 4s to cover slow lazy-load scenarios
8505    setTimeout(function () {
8506      var markers2 = element.querySelectorAll('.hide-column-if-category-field-empty');
8507
8508      for (var j = 0; j < markers2.length; j++) {
8509        checkCollection(markers2[j]);
8510      }
8511    }, 1500);
8512
8513    setTimeout(function () {
8514      // By now the decision is known — just re-apply in case something reset it
8515      applyHide();
8516    }, 4000);
8517  }
8518
8519  run();
8520
8521});
8522    };
8523</script>
8523
8524
8525<script>
8526    window.customWidgetsFunctions["6f306f3f1793463e9c5243713002bc78~4"] = function (element, data, api) {
8527        window.dmAPI.runOnReady('formTitleWidget', () => {
8528  const COLLECTION_NAME = 'form';
8529  const FIELD_LABEL = 'Form Title';
8530  const fallbackText = data.config['fallbackText'] || 'Request A Callback';
8531  const outputElement = element.querySelector('.form-title-output');
8532
8533  function htmlToPlain(textOrHtml) {
8534    if (textOrHtml == null) return '';
8535    const div = document.createElement('div');
8536    div.innerHTML = String(textOrHtml).replace(/<br\s*\/?>/gi, '\n');
8537    const blocks = div.querySelectorAll('p, li, h1, h2, h3, h4, h5, h6, div');
8538    if (blocks.length) {
8539      return Array.from(blocks).map(el => el.textContent.trim()).filter(Boolean).join(' ');
8540    }
8541    return div.textContent.trim();
8542  }
8543
8544  function setOutput(text) {
8545    if (!outputElement) return;
8546    outputElement.textContent = text || fallbackText;
8547  }
8548
8549  function pickFirstNonEmpty(values) {
8550    for (const row of values || []) {
8551      const val = row && row.data ? row.data[FIELD_LABEL] : null;
8552      const plain = htmlToPlain(val);
8553      if (plain) return plain;
8554    }
8555    return '';
8556  }
8557
8558  function loadAndRender() {
8559    if (typeof dmAPI === 'undefined' || !dmAPI.loadCollectionsAPI) {
8560      setOutput(fallbackText);
8561      return;
8562    }
8563    
8564    dmAPI.loadCollectionsAPI()
8565      .then(collectionApi => collectionApi.data(COLLECTION_NAME).get())
8566      .then(res => {
8567        const values = Array.isArray(res && res.values) ? res.values : [];
8568        const title = pickFirstNonEmpty(values);
8569        setOutput(title || fallbackText);
8570      })
8571      .catch(() => setOutput(fallbackText));
8572  }
8573
8574  loadAndRender();
8575});
8576    };
8577</script>
8577
8578
8579<script>
8580    window.customWidgetsFunctions["1c20f508b5474ea2b929959f7d5eff3d~3"] = function (element, data, api) {
8581        window.dmAPI.runOnReady('dcbh3-h3-theme', () => {
8582
8583  function applyTheme() {
8584    // Try multiple candidate selectors for the H3 style source
8585    // to work in both the editor and the published/preview environment
8586    const candidates = [
8587      '#dm h3',
8588      'h3',
8589    ];
8590
8591    let h3Sample = null;
8592    for (const sel of candidates) {
8593      h3Sample = document.querySelector(sel);
8594      if (h3Sample) break;
8595    }
8596
8597    if (!h3Sample) return false;
8598
8599    const computed = window.getComputedStyle(h3Sample);
8600
8601    const fontFamily    = computed.fontFamily;
8602    const fontSize      = computed.fontSize;
8603    const fontWeight    = computed.fontWeight;
8604    const lineHeight    = computed.lineHeight;
8605    const color         = computed.color;
8606    const letterSpacing = computed.letterSpacing;
8607    const textTransform = computed.textTransform;
8608    const fontStyle     = computed.fontStyle;
8609
8610    // Skip if nothing meaningful was computed yet
8611    if (!fontFamily || fontFamily === '') return false;
8612
8613    // Build selectors for every u_ class found on CB1 dmNewParagraph divs
8614    const uSelectors = [];
8615    const paragraphEls = document.querySelectorAll(
8616      '#dm .dmInner .widget-wrapper.DCBH3 .dmNewParagraph, ' +
8617      '.dmInner .widget-wrapper.DCBH3 .dmNewParagraph, ' +
8618      '.widget-wrapper.DCBH3 .dmNewParagraph'
8619    );
8620    paragraphEls.forEach(el => {
8621      el.classList.forEach(cls => {
8622        if (cls.startsWith('u_')) {
8623          uSelectors.push(`div.${cls} p`);
8624          uSelectors.push(`div.${cls} li`);
8625          uSelectors.push(`*#dm .dmInner div.${cls} p`);
8626          uSelectors.push(`*#dm .dmInner div.${cls} li`);
8627        }
8628      });
8629    });
8630
8631    const baseSelectors = [
8632      `#dm .dmInner .widget-wrapper.DCBH3 .dmNewParagraph`,
8633      `#dm .dmInner .widget-wrapper.DCBH3 .dmNewParagraph p`,
8634      `#dm .dmInner .widget-wrapper.DCBH3 .dmNewParagraph li`,
8635      `#dm .dmInner .widget-wrapper.DCBH3 .dmNewParagraph .rteBlock`,
8636      `.dmInner .widget-wrapper.DCBH3 .dmNewParagraph`,
8637      `.dmInner .widget-wrapper.DCBH3 .dmNewParagraph p`,
8638      `.dmInner .widget-wrapper.DCBH3 .dmNewParagraph li`,
8639      `.dmInner .widget-wrapper.DCBH3 .dmNewParagraph .rteBlock`,
8640      `.widget-wrapper.DCBH3 .dmNewParagraph`,
8641      `.widget-wrapper.DCBH3 .dmNewParagraph p`,
8642      `.widget-wrapper.DCBH3 .dmNewParagraph li`,
8643      `.widget-wrapper.DCBH3 .dmNewParagraph .rteBlock`,
8644    ];
8645
8646    const allSelectors = [...baseSelectors, ...uSelectors].join(',\n    ');
8647
8648    const css = `
8649    ${allSelectors} {
8650      font-family: ${fontFamily} !important;
8651      font-size: ${fontSize} !important;
8652      font-weight: ${fontWeight} !important;
8653      line-height: ${lineHeight} !important;
8654      color: ${color} !important;
8655      letter-spacing: ${letterSpacing} !important;
8656      text-transform: ${textTransform} !important;
8657      font-style: ${fontStyle} !important;
8658    }
8659  `;
8660
8661    // Remove any previously injected tag before re-injecting
8662    const existing = document.querySelector('style[data-dcbh3-h3-theme]');
8663    if (existing) existing.remove();
8664
8665    const styleTag = document.createElement('style');
8666    styleTag.setAttribute('data-dcbh3-h3-theme', 'true');
8667    styleTag.textContent = css;
8668    document.head.appendChild(styleTag);
8669
8670    return true;
8671  }
8672
8673  // Try immediately
8674  if (applyTheme()) return;
8675
8676  // If not ready yet, retry with increasing delays to handle
8677  // async rendering in preview / published environments
8678  let attempts = 0;
8679  const maxAttempts = 40;
8680  const interval = setInterval(() => {
8681    attempts++;
8682    if (applyTheme() || attempts >= maxAttempts) {
8683      clearInterval(interval);
8684    }
8685  }, 500);
8686});
8687
8688    };
8689</script>
8689
8690
8691<script>
8692    window.customWidgetsFunctions["5b093d0fb9474c1dabfa7807be463b12~52"] = function (element, data, api) {
8693        window.dmAPI.runOnReady('nmw-mega-nav', () => {
8694  'use strict';
8695
8696  /*
8697    NMW NAVIGATION ARCHITECTURE
8698
8699    FULL WIDGET
8700    - Fetches 06 NAV MASTER, 02 CATEGORY, 03 CORE, and optionally CUSTOMNAV.
8701    - Reads configured native pages from the hidden .navmasterdefault Duda nav.
8702    - Builds one normalized three-level navigation model.
8703    - Renders the Full navigation and publishes the shared model through window.NMW.
8704
8705    MIRROR WIDGET
8706    - Never fetches collections.
8707    - Waits for the Full widget to publish shared data.
8708    - Renders the same data vertically and stays synchronized.
8709
8710    SHARED BEHAVIOR
8711    - Click and optional hover opening.
8712    - 200 ms hover-close delay.
8713    - Escape, focus-leave, link-click, and outside-click closing.
8714    - Active-page detection from the actual current URL.
8715    - Prefix-aware dynamic matching (for example /category/x and /services/x).
8716    - JavaScript owns menu state; CSS owns appearance, positioning, and animation.
8717  */
8718
8719  const root = element.querySelector('.nmw-root');
8720  if (!root) return;
8721
8722  /*
8723    DUDA EDITOR LIFECYCLE
8724
8725    Duda can execute the widget JavaScript again against the same DOM root when
8726    a panel value changes. The prior closure is not automatically destroyed.
8727    Without explicit cleanup, the old instance continues observing/fetching and
8728    can overwrite the navigation built with the new configuration.
8729  */
8730  window.NMW_RUNTIME = window.NMW_RUNTIME || { instances: new WeakMap() };
8731  const previousInstanceCleanup = window.NMW_RUNTIME.instances.get(root);
8732  if (typeof previousInstanceCleanup === 'function') {
8733    try {
8734      previousInstanceCleanup('reinitialized');
8735    } catch (cleanupError) {
8736      console.warn('[NMW] Previous instance cleanup failed', cleanupError);
8737    }
8738  }
8739
8740  // Support both the rebuilt HTML and the original widget HTML.
8741  // Rebuilt HTML uses [data-nmw-render]; the original widget uses
8742  // .nmw-mobile-nav-wrapper as its render container.
8743  let renderHost = root.querySelector('[data-nmw-render]');
8744  if (!renderHost) renderHost = root.querySelector('.nmw-mobile-nav-wrapper');
8745  if (!renderHost) {
8746    renderHost = document.createElement('div');
8747    renderHost.className = 'nmw-render';
8748    renderHost.setAttribute('data-nmw-render', '');
8749    root.appendChild(renderHost);
8750  }
8751
8752  // Startup marker used to confirm that Duda executed the runOnReady callback.
8753  root.dataset.nmwScriptStarted = 'true';
8754
8755  // Show an immediate status only when the HTML does not provide a separate
8756  // loading host. The supplied Full/feature HTML uses [data-nmw-loading-view].
8757  const separateLoadingHost = root.querySelector('[data-nmw-loading-view]');
8758  if (renderHost && !renderHost.innerHTML.trim() && !separateLoadingHost) {
8759    renderHost.innerHTML = '<div class="nmw-runtime-status" data-nmw-runtime-status>Loading navigation…</div>';
8760  }
8761
8762  const loadingHost = separateLoadingHost || root.querySelector('.nmw-skeleton-nav');
8763  const toolsHost = root.querySelector('[data-nmw-tools]');
8764  const cfg = data.config || {};
8765  const widgetMode = readString(configValue('widgetMode'), 'full').toLowerCase() === 'mirror' ? 'mirror' : 'full';
8766  const instanceId = createInstanceId(widgetMode);
8767  const HOVER_CLOSE_DELAY = 200;
8768  const REBUILD_DEBOUNCE = 200;
8769  const NATIVE_RETRY_INTERVAL = 500;
8770  const NATIVE_MAX_WAIT = 15000;
8771  const MAX_DEPTH = 2;
8772
8773  // Dynamic page path controls from the widget panel.
8774  // Example: childUrlPrefix = "services" resolves HVAC against /services/hvac.
8775  // Multiple aliases are supported so existing or AI-created panel fields work.
8776  const CATEGORY_URL_PREFIX = normalizeUrlPrefix(
8777    configValue('categoryUrlPrefix', 'categoryPrefix', 'categoryPagePrefix'),
8778    'category'
8779  );
8780  const CHILD_URL_PREFIX = normalizeUrlPrefix(
8781    configValue('childUrlPrefix', 'childPrefix', 'childPagePrefix', 'coreUrlPrefix', 'corePrefix'),
8782    'services'
8783  );
8784
8785  root.dataset.nmwMode = widgetMode;
8786  root.classList.toggle('nmw-mirror', widgetMode === 'mirror');
8787  root.classList.toggle('nmw-vertical', widgetMode === 'mirror');
8788  root.dataset.nmwCategoryPrefix = CATEGORY_URL_PREFIX;
8789  root.dataset.nmwChildPrefix = CHILD_URL_PREFIX;
8790
8791  // The shared namespace survives Duda widget rerenders so Mirror can keep using
8792  // the last valid navigation while Full reinitializes.
8793  window.NMW = window.NMW || {};
8794  Object.assign(window.NMW, {
8795    loading: Boolean(window.NMW.loading),
8796    ready: Boolean(window.NMW.ready),
8797    version: Number(window.NMW.version) || 0,
8798    data: Array.isArray(window.NMW.data) ? window.NMW.data : null,
8799    error: Boolean(window.NMW.error),
8800    warnings: Array.isArray(window.NMW.warnings) ? window.NMW.warnings : [],
8801    ownerId: window.NMW.ownerId || null,
8802    mirrorOwnerId: window.NMW.mirrorOwnerId || null,
8803    fullConfig: window.NMW.fullConfig || null,
8804    fullConfigSignature: window.NMW.fullConfigSignature || '',
8805    presentationVersion: Number(window.NMW.presentationVersion) || 0,
8806    megaGroupsUsed: Array.isArray(window.NMW.megaGroupsUsed) ? window.NMW.megaGroupsUsed : [],
8807    megaGroupCount: Number(window.NMW.megaGroupCount) || 0,
8808    megaLabels: window.NMW.megaLabels || {},
8809    slugWarnings: Array.isArray(window.NMW.slugWarnings) ? window.NMW.slugWarnings : []
8810  });
8811
8812  const local = {
8813    cleaned: false,
8814    listenersAttached: false,
8815    hoverTimers: new Map(),
8816    anchorTimers: new Set(),
8817    closeJobs: new WeakMap(),
8818    closeTimers: new Set(),
8819    rebuildTimer: null,
8820    rebuildRunning: false,
8821    pendingRebuild: false,
8822    nativeObserver: null,
8823    nativeFinderObserver: null,
8824    manualObserver: null,
8825    nativeRetryTimer: null,
8826    nativeRetryStarted: 0,
8827    historyCleanup: null,
8828    customDraft: null,
8829    latestBuildWarnings: [],
8830    slugValidationWarnings: [],
8831    eventCleanups: [],
8832    removalObserver: null
8833  };
8834
8835  function listen(targetNode, eventName, handler, options) {
8836    if (!targetNode || typeof targetNode.addEventListener !== 'function') return;
8837    targetNode.addEventListener(eventName, handler, options);
8838    local.eventCleanups.push(() => {
8839      try { targetNode.removeEventListener(eventName, handler, options); }
8840      catch (error) {}
8841    });
8842  }
8843
8844  // ---------------------------------------------------------------------------
8845  // Generic helpers
8846  // ---------------------------------------------------------------------------
8847
8848  function readString(value, fallback = '') {
8849    if (value == null) return fallback;
8850    return String(value).trim() || fallback;
8851  }
8852
8853  function readBoolean(value, fallback = false) {
8854    if (value == null || value === '') return fallback;
8855    if (typeof value === 'boolean') return value;
8856    const normalized = String(value).trim().toLowerCase();
8857    if (['true', '1', 'yes', 'on'].includes(normalized)) return true;
8858    if (['false', '0', 'no', 'off'].includes(normalized)) return false;
8859    return fallback;
8860  }
8861
8862  function readNumber(value, fallback, min = -Infinity, max = Infinity) {
8863    const parsed = Number(value);
8864    if (!Number.isFinite(parsed)) return fallback;
8865    return Math.min(max, Math.max(min, parsed));
8866  }
8867
8868  function escapeHtml(value) {
8869    return String(value == null ? '' : value)
8870      .replace(/&/g, '&amp;')
8871      .replace(/</g, '&lt;')
8872      .replace(/>/g, '&gt;')
8873      .replace(/"/g, '&quot;')
8874      .replace(/'/g, '&#39;');
8875  }
8876
8877  function cssValue(value, fallback) {
8878    let candidate = value;
8879    if (candidate && typeof candidate === 'object') {
8880      candidate = candidate.value
8881        ?? candidate.css
8882        ?? candidate.color
8883        ?? candidate.hex
8884        ?? candidate.fontFamily
8885        ?? candidate.family
8886        ?? candidate.name
8887        ?? '';
8888    }
8889    const result = readString(candidate, '');
8890    return result || fallback;
8891  }
8892
8893  function cssLength(value, fallback) {
8894    const result = cssValue(value, '');
8895    if (!result) return fallback;
8896    return /^-?(?:\d+|\d*\.\d+)$/.test(result) ? `${result}px` : result;
8897  }
8898
8899  // Duda font controls may return a plain string, a number, or a nested
8900  // typography object. Read the requested part without assuming one exact shape.
8901  function readFontPart(value, part, fallback) {
8902    const visited = new Set();
8903
8904    function parse(candidate, depth = 0) {
8905      if (candidate == null || depth > 6) return undefined;
8906
8907      if (typeof candidate === 'string') {
8908        const text = candidate.trim();
8909        if (!text) return undefined;
8910
8911        // Some Duda controls serialize their configuration as JSON text.
8912        if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
8913          try {
8914            const parsed = JSON.parse(text);
8915            const nested = parse(parsed, depth + 1);
8916            if (nested !== undefined) return nested;
8917          } catch (error) {}
8918        }
8919
8920        if (part === 'size') {
8921          const directLength = text.match(/^-?(?:\d+|\d*\.\d+)(?:px|rem|em|%|vw|vh|pt)?$/i);
8922          if (directLength) return cssLength(text, fallback);
8923          const embeddedLength = text.match(/(?:^|\s)(\d+(?:\.\d+)?(?:px|rem|em|%|vw|vh|pt))(?:\s|\/|$)/i);
8924          return embeddedLength ? embeddedLength[1] : undefined;
8925        }
8926
8927        if (part === 'weight') {
8928          if (/^(?:normal|bold|bolder|lighter|[1-9]00)$/i.test(text)) return text;
8929          return undefined;
8930        }
8931
8932        // A family string should not be mistaken for a size or weight.
8933        if (/^-?(?:\d+|\d*\.\d+)(?:px|rem|em|%|vw|vh|pt)?$/i.test(text)) return undefined;
8934        if (/^(?:normal|bold|bolder|lighter|[1-9]00)$/i.test(text)) return undefined;
8935        return text;
8936      }
8937
8938      if (typeof candidate === 'number') {
8939        if (part === 'size') return `${candidate}px`;
8940        if (part === 'weight') return String(candidate);
8941        return undefined;
8942      }
8943
8944      if (typeof candidate !== 'object') return undefined;
8945      if (visited.has(candidate)) return undefined;
8946      visited.add(candidate);
8947
8948      const keyMap = {
8949        family: ['fontFamily', 'font-family', 'family', 'fontName', 'font', 'name'],
8950        size: ['fontSize', 'font-size', 'size', 'textSize', 'text-size'],
8951        weight: ['fontWeight', 'font-weight', 'weight']
8952      };
8953
8954      for (const key of keyMap[part] || []) {
8955        if (Object.prototype.hasOwnProperty.call(candidate, key)) {
8956          const found = parse(candidate[key], depth + 1);
8957          if (found !== undefined) return found;
8958        }
8959      }
8960
8961      // Duda controls may wrap the typography value inside one of these keys.
8962      for (const key of ['value', 'typography', 'style', 'styles', 'settings', 'fontSettings', 'data']) {
8963        if (Object.prototype.hasOwnProperty.call(candidate, key)) {
8964          const found = parse(candidate[key], depth + 1);
8965          if (found !== undefined) return found;
8966        }
8967      }
8968
8969      // Last-resort recursive scan for an unfamiliar wrapper object.
8970      for (const child of Object.values(candidate)) {
8971        if (child && typeof child === 'object') {
8972          const found = parse(child, depth + 1);
8973          if (found !== undefined) return found;
8974        }
8975      }
8976
8977      return undefined;
8978    }
8979
8980    const result = parse(value);
8981    return result === undefined || result === '' ? fallback : result;
8982  }
8983
8984  function fontFamilyValue(value, fallback = 'inherit') {
8985    return readFontPart(value, 'family', fallback);
8986  }
8987
8988  function fontSizeValue(value, fallback = '16px') {
8989    return readFontPart(value, 'size', fallback);
8990  }
8991
8992  function fontWeightValue(value, fallback = '400') {
8993    return readFontPart(value, 'weight', fallback);
8994  }
8995
8996  function normalizeChevronStyle(value, fallback = 'filled') {
8997    const normalized = readString(value, fallback).toLowerCase();
8998    if (['none', 'off', 'hidden'].includes(normalized)) return 'none';
8999    if (['chevron', 'outline'].includes(normalized)) return 'chevron';
9000    if (normalized === 'caret') return 'caret';
9001    if (normalized === 'triangle') return 'triangle';
9002    return 'filled';
9003  }
9004
9005  function createInstanceId(mode) {
9006    return `nmw-${mode}-${Math.random().toString(36).slice(2, 9)}`;
9007  }
9008
9009  function field(row, ...names) {
9010    if (!row || typeof row !== 'object') return '';
9011    for (const name of names) {
9012      if (Object.prototype.hasOwnProperty.call(row, name)) return row[name];
9013      const key = Object.keys(row).find(item => item.toLowerCase() === String(name).toLowerCase());
9014      if (key) return row[key];
9015    }
9016    return '';
9017  }
9018
9019  // Read a widget configuration value by key, including values placed inside
9020  // Duda panel groups. This makes fields such as childUrlPrefix work whether the
9021  // panel stores them directly on data.config or inside a grouped object.
9022  function configValue(...names) {
9023    const wanted = names.map(name => String(name).toLowerCase());
9024    const visited = new Set();
9025
9026    function search(value, depth) {
9027      if (!value || typeof value !== 'object' || Array.isArray(value) || depth > 6 || visited.has(value)) {
9028        return undefined;
9029      }
9030      visited.add(value);
9031
9032      for (const key of Object.keys(value)) {
9033        if (wanted.includes(key.toLowerCase()) && value[key] !== undefined) {
9034          return value[key];
9035        }
9036      }
9037
9038      for (const key of Object.keys(value)) {
9039        const found = search(value[key], depth + 1);
9040        if (found !== undefined) return found;
9041      }
9042
9043      return undefined;
9044    }
9045
9046    return search(cfg, 0);
9047  }
9048
9049
9050  function configValueFrom(source, ...names) {
9051    const wanted = names.map(name => String(name).toLowerCase());
9052    const visited = new Set();
9053
9054    function search(value, depth) {
9055      if (!value || typeof value !== 'object' || depth > 8 || visited.has(value)) return undefined;
9056      visited.add(value);
9057
9058      if (!Array.isArray(value)) {
9059        for (const key of Object.keys(value)) {
9060          if (wanted.includes(key.toLowerCase()) && value[key] !== undefined) return value[key];
9061        }
9062      }
9063
9064      for (const child of Object.values(value)) {
9065        if (child && typeof child === 'object') {
9066          const found = search(child, depth + 1);
9067          if (found !== undefined) return found;
9068        }
9069      }
9070      return undefined;
9071    }
9072
9073    return search(source, 0);
9074  }
9075
9076  // Search a configuration object while refusing to descend into named groups.
9077  // This is important for Duda Design Panels because Full and Mirror can contain
9078  // identical child keys (for example universalFontSize). Without excluding
9079  // mirrorStyles, the Full widget may accidentally read the hidden Mirror default.
9080  function configValueFromExcluding(source, excludedNames, ...names) {
9081    const wanted = names.map(name => String(name).toLowerCase());
9082    const excluded = new Set((excludedNames || []).map(name => String(name).toLowerCase()));
9083    const visited = new Set();
9084
9085    function search(value, depth) {
9086      if (!value || typeof value !== 'object' || depth > 8 || visited.has(value)) return undefined;
9087      visited.add(value);
9088
9089      if (!Array.isArray(value)) {
9090        for (const key of Object.keys(value)) {
9091          const lower = key.toLowerCase();
9092          if (excluded.has(lower)) continue;
9093          if (wanted.includes(lower) && value[key] !== undefined) return value[key];
9094        }
9095      }
9096
9097      for (const [key, child] of Object.entries(value)) {
9098        if (excluded.has(String(key).toLowerCase())) continue;
9099        if (child && typeof child === 'object') {
9100          const found = search(child, depth + 1);
9101          if (found !== undefined) return found;
9102        }
9103      }
9104      return undefined;
9105    }
9106
9107    return search(source, 0);
9108  }
9109
9110  function cloneSerializable(value) {
9111    try { return JSON.parse(JSON.stringify(value)); }
9112    catch (error) { return value; }
9113  }
9114
9115  function fullConfigRoot() {
9116    return widgetMode === 'mirror' && window.NMW.fullConfig ? window.NMW.fullConfig : cfg;
9117  }
9118
9119  function fullConfigValue(...names) {
9120    const found = configValueFrom(fullConfigRoot(), ...names);
9121    return found;
9122  }
9123
9124  function mirrorUsesSeparateStyling() {
9125    return readBoolean(configValue('useSeparateMirrorStyling'), false);
9126  }
9127
9128  function designConfigRoot(mode) {
9129    if (mode === 'mirror' && mirrorUsesSeparateStyling()) {
9130      // Search only inside the dedicated Mirror group when separate styling is on.
9131      return configValueFrom(cfg, 'mirrorStyles') || {};
9132    }
9133
9134    // Full styling, and Mirror inheritance, must never descend into mirrorStyles.
9135    const base = mode === 'mirror' ? fullConfigRoot() : cfg;
9136    const fullStyles = configValueFromExcluding(base, ['mirrorStyles'], 'fullStyles');
9137    return fullStyles && typeof fullStyles === 'object' ? fullStyles : base;
9138  }
9139
9140  function designValue(mode, names, fallback) {
9141    const list = Array.isArray(names) ? names : [names];
9142
9143    if (mode === 'mirror' && mirrorUsesSeparateStyling()) {
9144      const mirrorRoot = designConfigRoot(mode);
9145      const mirrorFound = configValueFrom(mirrorRoot, ...list);
9146      return mirrorFound === undefined ? fallback : mirrorFound;
9147    }
9148
9149    // Exclude mirrorStyles so identically named Mirror fields cannot shadow the
9150    // active Full Design Panel values. This applies to Full and inherited Mirror.
9151    const base = mode === 'mirror' ? fullConfigRoot() : cfg;
9152    const designRoot = designConfigRoot(mode);
9153    let found = configValueFromExcluding(designRoot, ['mirrorStyles'], ...list);
9154    if (found === undefined && designRoot !== base) {
9155      found = configValueFromExcluding(base, ['mirrorStyles'], ...list);
9156    }
9157    return found === undefined ? fallback : found;
9158  }
9159
9160  // URL prefixes are entered in the Duda panel as simple values such as
9161  // "category" or "services". Leading/trailing slashes are removed, but an
9162  // intentionally blank prefix is allowed and resolves pages from the site root.
9163  function normalizeUrlPrefix(value, fallback) {
9164    const supplied = value === undefined || value === null ? fallback : String(value).trim();
9165    return supplied.replace(/^\/+|\/+$/g, '');
9166  }
9167
9168  function unique(values) {
9169    return [...new Set(values.filter(Boolean))];
9170  }
9171
9172  // Display labels preserve their original punctuation. Slug candidates are used
9173  // only for collection matching and stable keys.
9174  function createSlugCandidates(value) {
9175    const original = readString(value, '').toLowerCase().replace(/\//g, '-');
9176    const withAnd = original
9177      .replace(/\s*&\s*/g, '-and-')
9178      .replace(/[^a-z0-9\s-]/g, '')
9179      .replace(/\s+/g, '-')
9180      .replace(/-+/g, '-')
9181      .replace(/^-+|-+$/g, '');
9182    const withoutAnd = original
9183      .replace(/\s*&\s*/g, '-')
9184      .replace(/[^a-z0-9\s-]/g, '')
9185      .replace(/\s+/g, '-')
9186      .replace(/-+/g, '-')
9187      .replace(/^-+|-+$/g, '');
9188    return unique([withAnd, withoutAnd]);
9189  }
9190
9191  function slugify(value) {
9192    return createSlugCandidates(value)[0] || '';
9193  }
9194
9195  function canonicalPath(value) {
9196    const raw = readString(value, '');
9197    if (!raw) return '';
9198    if (raw.startsWith('#')) return `${canonicalPathWithoutHash(window.location.href)}${raw}`;
9199    try {
9200      const url = new URL(raw, window.location.origin);
9201      let path = url.pathname || '/';
9202      path = path.replace(/^\/(?:home\/)?site\/[^/]+/i, '') || '/';
9203      path = path.replace(/\/+$/, '') || '/';
9204      return `${path.toLowerCase()}${url.hash || ''}`;
9205    } catch (error) {
9206      const [pathPart, hashPart = ''] = raw.split('#');
9207      let path = pathPart.split('?')[0].replace(/^\/(?:home\/)?site\/[^/]+/i, '').replace(/\/+$/, '') || '/';
9208      return `${path.toLowerCase()}${hashPart ? `#${hashPart}` : ''}`;
9209    }
9210  }
9211
9212  function canonicalPathWithoutHash(value) {
9213    return canonicalPath(value).split('#')[0] || '/';
9214  }
9215
9216  function getDudaBasePath() {
9217    try {
9218      const match = (window.location.pathname || '').match(/^\/site\/([0-9a-f]{32})(?:\/.*)?$/i);
9219      return match ? `/site/${match[1]}` : '';
9220    } catch (error) {
9221      return '';
9222    }
9223  }
9224
9225  function getDudaPreviewQuery() {
9226    try {
9227      const input = new URLSearchParams(window.location.search || '');
9228      const output = new URLSearchParams();
9229      ['showOriginal', 'preview', 'insitepreview', 'dm_device', 't', 'dm_try_mode', 'dm_checkSync'].forEach(key => {
9230        if (input.has(key)) output.set(key, input.get(key));
9231      });
9232      const value = output.toString();
9233      return value ? `?${value}` : '';
9234    } catch (error) {
9235      return '';
9236    }
9237  }
9238
9239  function buildRuntimeFallbackUrl(pathValue) {
9240    const raw = readString(pathValue, '/') || '/';
9241    if (/^(https?:)?\/\//i.test(raw) || raw.startsWith('#')) return raw;
9242    const hashIndex = raw.indexOf('#');
9243    const hash = hashIndex >= 0 ? raw.slice(hashIndex) : '';
9244    const withoutHash = hashIndex >= 0 ? raw.slice(0, hashIndex) : raw;
9245    const queryIndex = withoutHash.indexOf('?');
9246    const existingQuery = queryIndex >= 0 ? withoutHash.slice(queryIndex) : '';
9247    const pathOnly = queryIndex >= 0 ? withoutHash.slice(0, queryIndex) : withoutHash;
9248    const normalized = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
9249    const basePath = getDudaBasePath();
9250    const prefix = basePath && !normalized.startsWith(basePath + '/') && normalized !== basePath ? basePath 
9250: '';
9251    const previewQuery = existingQuery ? '' : getDudaPreviewQuery();
9252    return `${prefix}${normalized}${existingQuery || previewQuery}${hash}`;
9253  }
9254
9255  function getLastPathSegment(value) {
9256    const path = canonicalPathWithoutHash(value);
9257    return path.split('/').filter(Boolean).pop() || '';
9258  }
9259
9260  function isExternalUrl(value) {
9261    const raw = readString(value, '');
9262    if (!raw || raw.startsWith('/') || raw.startsWith('#')) return false;
9263    try {
9264      return new URL(raw, window.location.origin).origin !== window.location.origin;
9265    } catch (error) {
9266      return false;
9267    }
9268  }
9269
9270  function stableStringify(value) {
9271    return JSON.stringify(value);
9272  }
9273
9274  function warn(code, message, context) {
9275    const warning = { code, message, context: context || null };
9276    local.latestBuildWarnings.push(warning);
9277    console.warn(`[NMW:${code}] ${message}`, context || '');
9278  }
9279
9280  // ---------------------------------------------------------------------------
9281  // Editor, preview, and Site Comments environment handling
9282  // ---------------------------------------------------------------------------
9283
9284  function isEditorCanvas() {
9285    try {
9286      const topHref = window.top && window.top.location ? window.top.location.href : '';
9287      return topHref.includes('/home/site/') && !topHref.includes('#preview');
9288    } catch (error) {
9289      return false;
9290    }
9291  }
9292
9293  /*
9294    Site Comments customer state may be written into the parent editor
9295    document after this widget has already rendered. Inspect the current
9296    document and every same-origin ancestor document each time the state
9297    is requested.
9298
9299    Do not depend on console output such as "Customer Detected". Console
9300    messages are not a reliable application-state API and may be emitted
9301    before this widget initializes.
9302  */
9303  function isSiteCommentsCustomerMode() {
9304    try {
9305      const documents = [];
9306      const seenDocuments = new Set();
9307
9308      function addDocument(candidateDocument) {
9309        if (
9310          !candidateDocument ||
9311          !candidateDocument.documentElement ||
9312          seenDocuments.has(candidateDocument)
9313        ) {
9314          return;
9315        }
9316
9317        seenDocuments.add(candidateDocument);
9318        documents.push(candidateDocument);
9319      }
9320
9321      addDocument(document);
9322
9323      /*
9324        Walk upward only while the parent documents are same-origin.
9325        Cross-origin access is safely ignored.
9326      */
9327      try {
9328        let currentWindow = window;
9329
9330        while (
9331          currentWindow.parent &&
9332          currentWindow.parent !== currentWindow
9333        ) {
9334          currentWindow = currentWindow.parent;
9335          addDocument(currentWindow.document);
9336        }
9337      } catch (error) {
9338        // A cross-origin ancestor cannot be inspected.
9339      }
9340
9341      const html = documents
9342        .map(candidateDocument => {
9343          try {
9344            return (
9345              candidateDocument.documentElement.innerHTML ||
9346              ''
9347            );
9348          } catch (error) {
9349            return '';
9350          }
9351        })
9352        .join('\n');
9353
9354      const hasSiteCommentsMarker =
9355        /SITE[_\s-]?COMMENTS/i.test(html);
9356
9357      const isCustomer =
9358        /["']?isCustomer["']?\s*:\s*true/i
9359          .test(html);
9360
9361      const isNonStaff =
9362        /["']?isStaffMember["']?\s*:\s*false/i
9363          .test(html);
9364
9365      const isStaff =
9366        /["']?isStaffMember["']?\s*:\s*true/i
9367          .test(html);
9368
9369      return (
9370        hasSiteCommentsMarker &&
9371        isCustomer &&
9372        isNonStaff &&
9373        !isStaff
9374      );
9375    } catch (error) {
9376      return false;
9377    }
9378  }
9379
9380  function navigateSiteComments(link, event) {
9381    if (!isSiteCommentsCustomerMode()) return false;
9382    const href = link.getAttribute('href') || '';
9383    const target = (link.getAttribute('target') || '_self').toLowerCase();
9384    if (!href || href.startsWith('#') || target === '_blank' || /^(?:mailto:|tel:|sms:|javascript:|data:|blob:)/i.test(href)) return false;
9385    try {
9386      const parsed = new URL(href, window.location.origin);
9387      let topOrigin = window.location.origin;
9388      let topPath = '';
9389      try {
9390        topOrigin = window.top.location.origin || topOrigin;
9391        topPath = window.top.location.pathname || '';
9392      } catch (error) {}
9393      // External web addresses must retain their original domain. Only rewrite
9394      // same-site links that need to escape the Site Comments preview iframe.
9395      if (parsed.origin !== window.location.origin && parsed.origin !== topOrigin) return false;
9396      const needsHomePrefix = parsed.pathname.startsWith('/site/') && topPath.startsWith('/home/site/');
9397      const destination = `${topOrigin}${needsHomePrefix ? '/home' : ''}${parsed.pathname}${parsed.hash || ''}`;
9398      event.preventDefault();
9399      event.stopPropagation();
9400      try {
9401        window.top.location.href = destination;
9402      } catch (error) {
9403        window.location.href = destination;
9404      }
9405      return true;
9406    } catch (error) {
9407      return false;
9408    }
9409  }
9410
9411  // ---------------------------------------------------------------------------
9412  // Same-page anchor navigation
9413  // ---------------------------------------------------------------------------
9414
9415  /*
9416    Duda's native navigation can contain links to anchors on the current page.
9417    When the hash changes for the first time, normal browser navigation usually
9418    scrolls correctly. Once the visitor is already on that page, however, Duda's
9419    copied/native link behavior is not guaranteed to re-scroll for a second hash
9420    selection (or for the same hash selected again).
9421
9422    NMW therefore owns same-page anchor clicks. Cross-page anchor links continue
9423    to use normal navigation so Duda can load the destination page first.
9424  */
9425  function decodeAnchorHash(hash) {
9426    const raw = readString(hash, '').replace(/^#/, '');
9427    if (!raw) return '';
9428    try {
9429      return decodeURIComponent(raw);
9430    } catch (error) {
9431      return raw;
9432    }
9433  }
9434
9435  function findAnchorTarget(hash) {
9436    const decoded = decodeAnchorHash(hash);
9437    if (!decoded) return null;
9438
9439    // IDs are the modern/default anchor mechanism. Check the decoded value first
9440    // and the literal hash value second in case an authored ID contains % signs.
9441    let target = document.getElementById(decoded);
9442    if (!target) {
9443      const literal = readString(hash, '').replace(/^#/, '');
9444      if (literal && literal !== decoded) target = document.getElementById(literal);
9445    }
9446
9447    // Legacy named anchors are still supported by browsers and may exist on
9448    // older Duda sites.
9449    if (!target) {
9450      const named = document.getElementsByName(decoded);
9451      if (named && named.length) target = named[0];
9452    }
9453
9454    return target || null;
9455  }
9456
9457  function collectTopOverlayCandidates() {
9458    const candidates = new Set();
9459
9460    // Common Duda/site header selectors plus generic semantic header selectors.
9461    document.querySelectorAll([
9462      'header',
9463      '[role="banner"]',
9464      '.dmHeader',
9465      '.dmHeaderContainer',
9466      '[data-element-type="header"]',
9467      '[class*="sticky"]',
9468      '[class*="fixed"]'
9469    ].join(','))
9470      .forEach(node => candidates.add(node));
9471
9472    // Also inspect what is physically covering the top-center of the viewport.
9473    // This catches custom sticky/fixed wrappers whose class names are unknown.
9474    try {
9475      const viewportWidth = document.documentElement.clientWidth || window.innerWidth || 0;
9476      const x = Math.max(1, Math.min(viewportWidth - 1, Math.round(viewportWidth / 2)));
9477      [1, 8, 16, 32].forEach(y => {
9478        const stack = typeof document.elementsFromPoint === 'function'
9479          ? document.elementsFromPoint(x, y)
9480          : [];
9481
9482        stack.forEach(node => {
9483          let current = node;
9484          while (current && current !== document.documentElement) {
9485            candidates.add(current);
9486            current = current.parentElement;
9487          }
9488        });
9489      });
9490    } catch (error) {}
9491
9492    return candidates;
9493  }
9494
9495  function getAnchorScrollOffset(target) {
9496    const viewportWidth = document.documentElement.clientWidth || window.innerWidth || 0;
9497    const viewportHeight = document.documentElement.clientHeight || window.innerHeight || 0;
9498    let overlayBottom = 0;
9499
9500    collectTopOverlayCandidates().forEach(node => {
9501      if (!node || node === target || node.contains?.(target)) return;
9502
9503      let style;
9504      let rect;
9505      try {
9506        style = window.getComputedStyle(node);
9507        rect = node.getBoundingClientRect();
9508      } catch (error) {
9509        return;
9510      }
9511
9512      if (!style || !rect) return;
9513      if (!['fixed', 'sticky'].includes(style.position)) return;
9514      if (style.display === 'none' || style.visibility === 'hidden') return;
9515      if (Number(style.opacity) === 0) return;
9516      if (rect.height <= 0 || rect.width <= 0) return;
9517      if (rect.bottom <= 0 || rect.top > 12) return;
9518
9519      // Ignore small floating controls at the top edge; a navigation/header
9520      // overlay should span a meaningful portion of the viewport.
9521      if (viewportWidth > 0 && rect.width < viewportWidth * 0.30) return;
9522
9523      // Ignore pathological full-screen overlays. Those are not header offsets.
9524      if (viewportHeight > 0 && rect.height > viewportHeight * 0.60) return;
9525
9526      overlayBottom = Math.max(overlayBottom, Math.max(0, rect.bottom));
9527    });
9528
9529    let scrollMarginTop = 0;
9530    try {
9531      const targetStyle = window.getComputedStyle(target);
9532      scrollMarginTop = parseFloat(targetStyle.scrollMarginTop) || 0;
9533    } catch (error) {}
9534
9535    // Respect an authored scroll-margin if it is larger than the detected header.
9536    // Add a small breathing room so headings do not sit flush against the header.
9537    return Math.max(0, Math.max(overlayBottom, scrollMarginTop) + 8);
9538  }
9539
9540  function scrollAnchorTargetIntoPlace(target) {
9541    if (!target || !document.documentElement.contains(target)) return false;
9542
9543    const rect = target.getBoundingClientRect();
9544    const currentScroll = window.pageYOffset
9545      || document.documentElement.scrollTop
9546      || document.body.scrollTop
9547      || 0;
9548    const offset = getAnchorScrollOffset(target);
9549    const absoluteTargetTop = currentScroll + rect.top;
9550    const scrollingElement = document.scrollingElement || document.documentElement;
9551    const maxScroll = Math.max(0, scrollingElement.scrollHeight - window.innerHeight);
9552    const desiredTop = Math.max(0, Math.min(maxScroll, absoluteTargetTop - offset));
9553
9554    try {
9555      window.scrollTo({ top: desiredTop, left: window.pageXOffset || 0, behavior: 'auto' });
9556    } catch (error) {
9557      window.scrollTo(window.pageXOffset || 0, desiredTop);
9558    }
9559
9560    return true;
9561  }
9562
9563  function scheduleAnchorCorrection(target) {
9564    // First pass occurs immediately after the click. A second pass happens after
9565    // menu close animations/layout changes so Mirror/mobile menus cannot shift the
9566    // target after it was measured. A final lightweight pass handles late sticky
9567    // header changes that appear only after scrolling.
9568    const delays = [0, 260, 520];
9569
9570    delays.forEach(delay => {
9571      const timer = setTimeout(() => {
9572        local.anchorTimers.delete(timer);
9573        if (local.cleaned || !document.documentElement.contains(target)) return;
9574        scrollAnchorTargetIntoPlace(target);
9575      }, delay);
9576
9577      local.anchorTimers.add(timer);
9578    });
9579  }
9580
9581  function samePageAnchorInfo(link) {
9582    if (!link) return null;
9583
9584    const href = readString(link.getAttribute('href'), '');
9585    const target = readString(link.getAttribute('target'), '_self').toLowerCase();
9586
9587    if (
9588      !href ||
9589      target === '_blank' ||
9590      link.hasAttribute('download') ||
9591      /^(?:mailto:|tel:|sms:|javascript:|data:|blob:)/i.test(href)
9592    ) {
9593      return null;
9594    }
9595
9596    let destination;
9597    try {
9598      destination = new URL(href, window.location.href);
9599    } catch (error) {
9600      return null;
9601    }
9602
9603    if (destination.origin !== window.location.origin || !destination.hash) return null;
9604
9605    const destinationPath = canonicalPathWithoutHash(destination.href);
9606    const currentPath = canonicalPathWithoutHash(window.location.href);
9607    if (!destinationPath || destinationPath !== currentPath) return null;
9608
9609    const anchorTarget = findAnchorTarget(destination.hash);
9610    if (!anchorTarget) return null;
9611
9612    return {
9613      destination,
9614      hash: destination.hash,
9615      target: anchorTarget
9616    };
9617  }
9618
9619  function handleSamePageAnchorClick(link, event) {
9620    // Preserve normal browser behavior for modified clicks such as Ctrl/Cmd-click.
9621    if (
9622      event &&
9623      (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (typeof event.button === 'number' && event.button !== 0))
9624    ) {
9625      return false;
9626    }
9627
9628    const info = samePageAnchorInfo(link);
9629    if (!info) return false;
9630
9631    event.preventDefault();
9632    event.stopPropagation();
9633
9634    closeAllMenus(root, false);
9635
9636    // pushState changes the URL without triggering the browser's unreliable
9637    // one-time hash scrolling. Selecting the same anchor again still scrolls
9638    // because the explicit scroll routine below always runs.
9639    try {
9640      if (window.location.hash !== info.hash) {
9641        history.pushState(history.state, '', info.destination.href);
9642      } else {
9643        refreshActiveStates(root);
9644      }
9645    } catch (error) {
9646      // If history manipulation is unavailable, keep the current URL and still
9647      // perform the requested scroll.
9648      refreshActiveStates(root);
9649    }
9650
9651    scheduleAnchorCorrection(info.target);
9652    return true;
9653  }
9654
9655  // ---------------------------------------------------------------------------
9656  // Configuration model
9657  // ---------------------------------------------------------------------------
9658
9659  function getModeConfig(mode) {
9660    return designConfigRoot(mode);
9661  }
9662
9663  function behaviorValue(mode, names, fallback) {
9664    const list = Array.isArray(names) ? names : [names];
9665    const fullFound = configValueFrom(fullConfigRoot(), ...list);
9666
9667    if (mode === 'mirror' && mirrorUsesSeparateStyling()) {
9668      const mirrorFound = configValueFrom(designConfigRoot('mirror'), ...list);
9669      if (mirrorFound !== undefined) return mirrorFound;
9670    }
9671
9672    return fullFound === undefined ? fallback : fullFound;
9673  }
9674
9675  function getBehaviorConfig(mode) {
9676    const fullHover = readBoolean(behaviorValue('full', 'openMenusOnHover', false), false);
9677    const mirrorOverride = mode === 'mirror'
9678      ? configValue('mirrorOpenMenusOnHover')
9679      : undefined;
9680
9681    return {
9682      hoverEnabled: mode === 'mirror' && mirrorOverride !== undefined && mirrorOverride !== null && mirrorOverride !== ''
9683        ? readBoolean(mirrorOverride, fullHover)
9684        : fullHover,
9685      animationsEnabled: readBoolean(behaviorValue(mode, ['animationsEnabled', 'animatedEffectsEnabled'], true), true),
9686      animateTopNav: readBoolean(behaviorValue(mode, 'animateTopNav', true), true),
9687      animateSubNav: readBoolean(behaviorValue(mode, 'animateSubNav', true), true),
9688      animateSubSubNav: readBoolean(behaviorValue(mode, 'animateSubSubNav', true), true),
9689      animateMegaMenu: readBoolean(behaviorValue(mode, 'animateMegaMenu', true), true),
9690      animateMegaFamilies: readBoolean(behaviorValue(mode, 'animateMegaFamilies', true), true),
9691      animateMirror: readBoolean(behaviorValue(mode, 'animateMirror', true), true),
9692      animateTopChevrons: readBoolean(behaviorValue(mode, 'animateTopChevrons', true), true),
9693      animateMegaChevrons: readBoolean(behaviorValue(mode, 'animateMegaChevrons', true), true),
9694      animateHoverEffects: readBoolean(behaviorValue(mode, 'animateHoverEffects', true), true)
9695    };
9696  }
9697
9698  function getMegaGroupObject(groupNumber) {
9699    const safeGroup = Math.min(4, Math.max(1, Math.round(Number(groupNumber) || 1)));
9700    const key = `mega${safeGroup}`;
9701    const rootConfig = fullConfigRoot();
9702    const direct = rootConfig && typeof rootConfig[key] === 'object' ? rootConfig[key] : null;
9703    return direct || configValueFrom(rootConfig, key) || {};
9704  }
9705
9706  function getMegaLabel(groupNumber) {
9707    const safeGroup = Math.min(4, Math.max(1, Math.round(Number(groupNumber) || 1)));
9708    const key = `mega${safeGroup}`;
9709    const rootConfig = fullConfigRoot();
9710    const group = getMegaGroupObject(safeGroup);
9711    const configured = group.label
9712      ?? configValueFrom(rootConfig, `${key}Label`, `megaLabel${safeGroup}`, `mega${safeGroup}Name`)
9713      ?? (safeGroup === 1 ? configValueFrom(rootConfig, 'servicesLabel') : undefined);
9714
9715    return readString(configured, `Mega ${safeGroup}`) || `Mega ${safeGroup}`;
9716  }
9717
9718  function getMegaExpandAll(groupNumber) {
9719    const safeGroup = Math.min(4, Math.max(1, Math.round(Number(groupNumber) || 1)));
9720    const key = `mega${safeGroup}`;
9721    const rootConfig = fullConfigRoot();
9722    const group = getMegaGroupObject(safeGroup);
9723    const candidates = [
9724      group.expandAll,
9725      group.childrenExpanded,
9726      group.childrenOpen,
9727      group.openChildren,
9728      configValueFrom(rootConfig,
9729        `${key}ExpandAll`, `${key}Expanded`, `${key}ChildrenExpanded`,
9730        `${key}ChildrenOpen`, `${key}OpenChildren`,
9731        `expandMega${safeGroup}`, `expandedMega${safeGroup}`
9732      )
9733    ];
9734
9735    if (safeGroup === 1) candidates.push(configValueFrom(rootConfig, 'expandedServices'));
9736    const configured = candidates.find(value => value !== undefined && value !== null && value !== '');
9737    return readBoolean(configured, false);
9738  }
9739
9740  function getMegaConfig(groupNumber) {
9741    const safeGroup = Math.min(4, Math.max(1, Math.round(Number(groupNumber) || 1)));
9742    const key = `mega${safeGroup}`;
9743    const rootConfig = fullConfigRoot();
9744    const group = getMegaGroupObject(safeGroup);
9745
9746    const columnsValue = group.columns ?? group.columnCount ?? configValueFrom(
9747      rootConfig,
9748      `${key}Columns`, `${key}ColumnCount`, `megaColumns${safeGroup}`,
9749      safeGroup === 1 ? 'compactColumnCount' : '__unused__',
9750      'megaColumns'
9751    );
9752    const layoutValue = group.layout ?? group.layoutMode ?? configValueFrom(
9753      rootConfig,
9754      `${key}Layout`, `${key}LayoutMode`, `megaLayout${safeGroup}`, 'megaLayoutMode'
9755    );
9756    const maxWidthValue = group.maxWidth ?? group.menuMaxWidth ?? configValueFrom(
9757      rootConfig,
9758      `${key}MaxWidth`, `${key}MenuMaxWidth`, `megaMaxWidth${safeGroup}`, 'megaMenuMaxWidth'
9759    );
9760    const alphabetizeValue = group.alphabetize ?? configValueFrom(
9761      rootConfig,
9762      `${key}Alphabetize`, `alphabetizeMega${safeGroup}`, 'alphabetizeMega'
9763    );
9764    const layout = readString(layoutValue, 'grid').toLowerCase();
9765
9766    return {
9767      label: getMegaLabel(safeGroup),
9768      columns: Math.round(readNumber(columnsValue, 3, 1, 6)),
9769      layout: ['grid', 'masonry'].includes(layout) ? layout : 'grid',
9770      expandAll: getMegaExpandAll(safeGroup),
9771      maxWidth: readNumber(maxWidthValue, 1600, 320, 3000),
9772      alphabetize: readBoolean(alphabetizeValue, false)
9773    };
9774  }
9775
9776  function updateMegaIndicators(items) {
9777    const groups = new Set();
9778
9779    function walk(list) {
9780      (list || []).forEach(item => {
9781        if (item && item.megaGroup >= 1 && item.megaGroup <= 4) groups.add(item.megaGroup);
9782        walk(item && item.children);
9783      });
9784    }
9785
9786    walk(items);
9787    const used = [...groups].sort((a, b) => a - b);
9788    const labels = Object.fromEntries([1, 2, 3, 4].map(number => [number, getMegaLabel(number)]));
9789
9790    window.NMW.megaGroupsUsed = used;
9791    window.NMW.megaGroupCount = used.length;
9792    window.NMW.megaLabels = labels;
9793
9794    root.dataset.nmwMegaCount = String(used.length);
9795    root.dataset.nmwMegaGroups = used.map(number => `mega${number}`).join(',');
9796    root.dataset.nmwMegaLabels = used.map(number => labels[number]).join(',');
9797
9798    [1, 2, 3, 4].forEach(number => {
9799      root.dataset[`nmwMega${number}Label`] = labels[number];
9800    });
9801  }
9802
9803  function applyDesignSettings(mode) {
9804    const behavior = getBehaviorConfig(mode);
9805    const universalColors = readBoolean(designValue(mode, 'useUniversalColors', false), false);
9806    // A separate background toggle is required because Duda color controls often
9807    // save individual default values (for example #ffffff). Without this switch,
9808    // those saved defaults always override universalBackgroundColor.
9809    const universalBackgrounds = readBoolean(
9810      designValue(mode, ['useUniversalBackgrounds', 'useUniversalBackground'], true),
9811      true
9812    );
9813    const universalTypography = readBoolean(designValue(mode, 'useUniversalTypography', true), true);
9814    const universalChevrons = readBoolean(designValue(mode, 'useUniversalChevronSettings', false), false);
9815
9816    root.dataset.nmwUniversalColors = universalColors ? 'true' : 'false';
9817    root.dataset.nmwUniversalBackgrounds = universalBackgrounds ? 'true' : 'false';
9818    root.dataset.nmwUniversalTypography = universalTypography ? 'true' : 'false';
9819    root.dataset.nmwUniversalChevrons = universalChevrons ? 'true' : 'false';
9820    root.dataset.nmwAnimations = behavior.animationsEnabled ? 'on' : 'off';
9821    root.dataset.nmwAnimateTop = behavior.animateTopNav ? 'on' : 'off';
9822    root.dataset.nmwAnimateSub = behavior.animateSubNav ? 'on' : 'off';
9823    root.dataset.nmwAnimateSubsub = behavior.animateSubSubNav ? 'on' : 'off';
9824    root.dataset.nmwAnimateMega = behavior.animateMegaMenu ? 'on' : 'off';
9825    root.dataset.nmwAnimateMegaFamilies = behavior.animateMegaFamilies ? 'on' : 'off';
9826    root.dataset.nmwAnimateMirror = behavior.animateMirror ? 'on' : 'off';
9827    root.dataset.nmwAnimateTopChevrons = behavior.animateTopChevrons ? 'on' : 'off';
9828    root.dataset.nmwAnimateMegaChevrons = behavior.animateMegaChevrons ? 'on' : 'off';
9829    root.dataset.nmwAnimateHover = behavior.animateHoverEffects ? 'on' : 'off';
9830    root.dataset.nmwHoverEnabled = behavior.hoverEnabled ? 'true' : 'false';
9831    root.dataset.nmwAlignment = mode === 'mirror'
9832      ? readString(designValue(mode, 'mirrorAlignment', 'start'), 'start')
9833      : readString(designValue(mode, 'navAlignment', 'center'), 'center');
9834    root.dataset.nmwPreset = readString(
9835      designValue(
9836        mode,
9837        mode === 'mirror'
9838          ? ['layoutPreset', 'mirrorLayoutPreset', 'mirrorPreset']
9839          : ['layoutPreset', 'desktopPresetValue', 'desktopPreset'],
9840        mode === 'mirror' ? 'vertical-standard' : 'classic'
9841      ),
9842      mode === 'mirror' ? 'vertical-standard' : 'classic'
9843    ).toLowerCase();
9844    root.dataset.nmwSeparateMirrorStyling = mode === 'mirror' && mirrorUsesSeparateStyling() ? 'true' : 'false';
9845    root.classList.toggle('nmw-capitalize', readBoolean(behaviorValue(mode, 'capitalizeNav', false), false));
9846    root.dataset.nmwUniversalUnderline = readBoolean(
9847      designValue(
9848        mode,
9849        ['universalUnderline', 'universalAutomaticUnderline', 'underlineLinksOnHover'],
9850        false
9851      ),
9852      false
9853    ) ? 'true' : 'false';
9854
9855    const universalChevronStyle = normalizeChevronStyle(
9856      designValue(mode, ['universalChevronStyle', 'universalChevronType', 'chevronStyle', 'chevronType'], 'filled'),
9857      'filled'
9858    );
9859    const topChevronStyle = universalChevrons
9860      ? universalChevronStyle
9861      : normalizeChevronStyle(designValue(mode, ['topChevronStyle', 'topChevronType', 'chevronType'], universalChevronStyle), universalChevronStyle);
9862    const megaChevronStyle = universalChevrons
9863      ? universalChevronStyle
9864      : normalizeChevronStyle(designValue(mode, ['megaChevronStyle', 'megaChevronType'], universalChevronStyle), universalChevronStyle);
9865    root.dataset.nmwTopChevronStyle = topChevronStyle;
9866    root.dataset.nmwMegaChevronStyle = megaChevronStyle;
9867
9868    const universalText = cssValue(designValue(mode, 'universalTextColor', '#111111'), '#111111');
9869    const universalBg = cssValue(designValue(mode, 'universalBackgroundColor', '#ffffff'), '#ffffff');
9870
9871    // The universal background controls every dropdown surface, including
9872    // Mega family/cards. The top navigation background remains independent.
9873    const subBackground = universalBackgrounds
9874      ? universalBg
9875      : cssValue(designValue(mode, 'subNavBackgroundColor', '#ffffff'), '#ffffff');
9876    const subSubBackground = universalBackgrounds
9877      ? universalBg
9878      : cssValue(designValue(mode, 'subSubNavBackgroundColor', '#ffffff'), '#ffffff');
9879    const megaBackground = universalBackgrounds
9880      ? universalBg
9881      : cssValue(designValue(mode, 'megaMenuBackgroundColor', '#ffffff'), '#ffffff');
9882    const megaFamilyBackground = universalBackgrounds
9883      ? universalBg
9884      : cssValue(designValue(mode, 'megaFamilyBackgroundColor', 'transparent'), 'transparent');
9885
9886    const universalFontSource = designValue(
9887      mode,
9888      ['universalFontFamily', 'universalFont', 'universalTypographyFont'],
9889      'inherit'
9890    );
9891    const universalSizeSource = designValue(
9892      mode,
9893      ['universalFontSize', 'universalTextSize', 'universalSize', 'universalFontFamily', 'universalFont'],
9894      '16px'
9895    );
9896    const universalWeightSource = designValue(
9897      mode,
9898      ['universalFontWeight', 'universalWeight', 'universalFontFamily', 'universalFont'],
9899      '500'
9900    );
9901
9902    const universalFont = fontFamilyValue(universalFontSource, 'inherit');
9903    const universalSize = fontSizeValue(universalSizeSource, '16px');
9904    const universalWeight = fontWeightValue(universalWeightSource, '500');
9905    const universalChevronColor = cssValue(designValue(mode, 'universalChevronColor', universalText), universalText);
9906    const universalChevronSize = cssLength(designValue(mode, ['universalChevronSize', 'chevronSize'], '10px'), '10px');
9907    const universalChevronWeight = cssLength(designValue(mode, ['universalChevronWeight', 'chevronWeight'], '1.5px'), '1.5px');
9908
9909    const variables = {
9910      '--nmw-universal-text': universalText,
9911      '--nmw-top-text': universalColors ? universalText : cssValue(designValue(mode, 'topTextColor', universalText), universalText),
9912      '--nmw-sub-text': universalColors ? universalText : cssValue(designValue(mode, 'subTextColor', universalText), universalText),
9913      '--nmw-subsub-text': universalColors ? universalText : cssValue(designValue(mode, 'subSubTextColor', universalText), universalText),
9914      '--nmw-mega-parent-text': universalColors ? universalText : cssValue(designValue(mode, 'megaParentTextColor', universalText), universalText),
9915      '--nmw-mega-child-text': universalColors ? universalText : cssValue(designValue(mode, 'megaChildTextColor', universalText), universalText),
9916      '--nmw-mega-grandchild-text': universalColors ? universalText : cssValue(designValue(mode, 'megaGrandchildTextColor', universalText), universalText),
9917      '--nmw-top-bg': cssValue(designValue(mode, 'topNavBackgroun
9917dColor', 'transparent'), 'transparent'),
9918      '--nmw-universal-bg': universalBg,
9919      '--nmw-sub-bg': subBackground,
9920      '--nmw-subsub-bg': subSubBackground,
9921      '--nmw-mega-bg': megaBackground,
9922      '--nmw-mega-family-bg': megaFamilyBackground,
9923      '--nmw-hover-text': cssValue(designValue(mode, ['hoverTextColor', 'globalHoverTextColor'], 'currentColor'), 'currentColor'),
9924      '--nmw-hover-bg': cssValue(designValue(mode, ['hoverBackgroundColor', 'globalSubNavHoverBg'], 'transparent'), 'transparent'),
9925      '--nmw-active-text': cssValue(designValue(mode, 'activeTextColor', 'currentColor'), 'currentColor'),
9926      '--nmw-universal-font': universalFont,
9927      '--nmw-universal-size': universalSize,
9928      '--nmw-universal-weight': universalWeight,
9929      '--nmw-top-font': universalTypography ? universalFont : fontFamilyValue(designValue(mode, ['topFontFamily', 'topFont'], universalFont), universalFont),
9930      '--nmw-top-size': universalTypography ? universalSize : fontSizeValue(designValue(mode, ['topFontSize', 'topTextSize', 'topFontFamily', 'topFont'], universalSize), universalSize),
9931      '--nmw-top-weight': universalTypography ? universalWeight : fontWeightValue(designValue(mode, ['topFontWeight', 'topWeight', 'topFontFamily', 'topFont'], universalWeight), universalWeight),
9932      '--nmw-sub-font': universalTypography ? universalFont : fontFamilyValue(designValue(mode, ['subFontFamily', 'subFont'], universalFont), universalFont),
9933      '--nmw-sub-size': universalTypography ? universalSize : fontSizeValue(designValue(mode, ['subFontSize', 'subTextSize', 'subFontFamily', 'subFont'], universalSize), universalSize),
9934      '--nmw-sub-weight': universalTypography ? universalWeight : fontWeightValue(designValue(mode, ['subFontWeight', 'subWeight', 'subFontFamily', 'subFont'], universalWeight), universalWeight),
9935      '--nmw-subsub-font': universalTypography ? universalFont : fontFamilyValue(designValue(mode, ['subSubFontFamily', 'subSubFont'], universalFont), universalFont),
9936      '--nmw-subsub-size': universalTypography ? universalSize : fontSizeValue(designValue(mode, ['subSubFontSize', 'subSubTextSize', 'subSubFontFamily', 'subSubFont'], universalSize), universalSize),
9937      '--nmw-subsub-weight': universalTypography ? universalWeight : fontWeightValue(designValue(mode, ['subSubFontWeight', 'subSubWeight', 'subSubFontFamily', 'subSubFont'], universalWeight), universalWeight),
9938      '--nmw-mega-parent-font': universalTypography ? universalFont : fontFamilyValue(designValue(mode, ['megaParentFontFamily', 'megaParentFont'], universalFont), universalFont),
9939      '--nmw-mega-parent-size': universalTypography ? universalSize : fontSizeValue(designValue(mode, ['megaParentFontSize', 'megaParentTextSize', 'megaParentFontFamily', 'megaParentFont'], universalSize), universalSize),
9940      '--nmw-mega-parent-weight': universalTypography ? universalWeight : fontWeightValue(designValue(mode, ['megaParentFontWeight', 'megaParentWeight', 'megaParentFontFamily', 'megaParentFont'], universalWeight), universalWeight),
9941      '--nmw-mega-child-font': universalTypography ? universalFont : fontFamilyValue(designValue(mode, ['megaChildFontFamily', 'megaChildFont'], universalFont), universalFont),
9942      '--nmw-mega-child-size': universalTypography ? universalSize : fontSizeValue(designValue(mode, ['megaChildFontSize', 'megaChildTextSize', 'megaChildFontFamily', 'megaChildFont'], universalSize), universalSize),
9943      '--nmw-mega-child-weight': universalTypography ? universalWeight : fontWeightValue(designValue(mode, ['megaChildFontWeight', 'megaChildWeight', 'megaChildFontFamily', 'megaChildFont'], universalWeight), universalWeight),
9944      '--nmw-mega-grandchild-font': universalTypography ? universalFont : fontFamilyValue(designValue(mode, ['megaGrandchildFontFamily', 'megaGrandchildFont'], universalFont), universalFont),
9945      '--nmw-mega-grandchild-size': universalTypography ? universalSize : fontSizeValue(designValue(mode, ['megaGrandchildFontSize', 'megaGrandchildTextSize', 'megaGrandchildFontFamily', 'megaGrandchildFont'], universalSize), universalSize),
9946      '--nmw-mega-grandchild-weight': universalTypography ? universalWeight : fontWeightV
9946alue(designValue(mode, ['megaGrandchildFontWeight', 'megaGrandchildWeight', 'megaGrandchildFontFamily', 'megaGrandchildFont'], universalWeight), universalWeight),
9947      '--nmw-top-chevron-color': universalChevrons ? universalChevronColor : cssValue(designValue(mode, 'topChevronColor', universalChevronColor), universalChevronColor),
9948      '--nmw-mega-chevron-color': universalChevrons ? universalChevronColor : cssValue(designValue(mode, 'megaChevronColor', universalChevronColor), universalChevronColor),
9949      '--nmw-chevron-size': universalChevronSize,
9950      '--nmw-chevron-weight': universalChevronWeight,
9951      '--nmw-top-gap': `${readNumber(designValue(mode, 'topLevelGap', 32), 32, 0, 120)}px`,
9952      // Controls the spacing between a synthetic Mega 1–4 label and its chevron.
9953      // Duda Design Panel field: megaTriggerChevronGap
9954      '--nmw-mega-trigger-chevron-gap': `${readNumber(designValue(mode, 'megaTriggerChevronGap', 12), 12, 0, 60)}px`,
9955      '--nmw-mirror-gap': `${readNumber(designValue(mode, 'mirrorItemGap', 0), 0, 0, 80)}px`
9956    };
9957
9958    Object.entries(variables).forEach(([name, value]) => root.style.setProperty(name, value));
9959  }
9960
9961  // ---------------------------------------------------------------------------
9962  // Shared state and root status
9963  // ---------------------------------------------------------------------------
9964
9965  function updateRootStatus() {
9966    const state = window.NMW;
9967    root.dataset.nmwLoading = state.loading ? 'true' : 'false';
9968    root.dataset.nmwReady = state.ready ? 'true' : 'false';
9969    root.dataset.nmwError = state.error ? 'true' : 'false';
9970    root.dataset.nmwEmpty = Array.isArray(state.data) && state.data.length === 0 ? 'true' : 'false';
9971    updateLoadingView();
9972  }
9973
9974  function dispatchStateChanged() {
9975    window.dispatchEvent(new CustomEvent('nmw:state-changed', {
9976      detail: { loading: window.NMW.loading, ready: window.NMW.ready, error: window.NMW.error }
9977    }));
9978  }
9979
9980  function dispatchDataChanged() {
9981    window.dispatchEvent(new CustomEvent('nmw:data-changed', {
9982      detail: { version: window.NMW.version }
9983    }));
9984  }
9985
9986  function updateLoadingView() {
9987    if (!loadingHost) return;
9988    const initialLoading = window.NMW.loading && !window.NMW.ready;
9989    loadingHost.hidden = !initialLoading;
9990    if (!initialLoading) {
9991      loadingHost.innerHTML = '';
9992      return;
9993    }
9994    const style = readString(configValueFrom(fullConfigRoot(), 'loadingAnimationStyle') ?? 'shimmer', 'shimmer').toLowerCase();
9995    const count = Math.round(readNumber(configValueFrom(fullConfigRoot(), 'loadingItemCount'), 4, 1, 12));
9996    if (style === 'spinner') {
9997      loadingHost.innerHTML = '<span class="nmw-loading-spinner" aria-hidden="true"></span>';
9998      return;
9999    }
10000    loadingHost.innerHTML = `<div class="nmw-loading-items nmw-loading-${escapeHtml(style)}">${Array.from({ length: count }, (_, index) => `<span class="nmw-loading-item" style="--nmw-load-width:${55 + ((index * 17) % 40)}px"></span>`).join('')}</div>`;
10001  }
10002
10003  function showEditorMessage(message, duplicate = false) {
10004    if (!renderHost) return;
10005    if (!isEditorCanvas()) {
10006      renderHost.innerHTML = '';
10007      return;
10008    }
10009    renderHost.innerHTML = `<div class="nmw-editor-warning${duplicate ? ' nmw-editor-warning-duplicate' : ''}">${escapeHtml(message)}</div>`;
10010  }
10011
10012  // ---------------------------------------------------------------------------
10013  // Ownership: exactly one Full and one Mirror are expected on a page
10014  // ---------------------------------------------------------------------------
10015
10016  function claimOwnership() {
10017    const key = widgetMode === 'full' ? 'ownerId' : 'mirrorOwnerId';
10018    const existingId = window.NMW[key];
10019
10020    // Duda can rerun widget JavaScript against the same existing DOM root when
10021    // code or panel settings change. In that case the old instance ID remains
10022    // on this same root even though its previous script lifecycle is obsolete.
10023    // Treat that as a replacement, not as a duplicate widget.
10024    if (existingId && existingId !== instanceId) {
10025      const existingRoot = Array.from(document.querySelectorAll('[data-nm
10025w-instance]'))
10026        .find(node => node.dataset.nmwInstance === existingId);
10027
10028      if (existingRoot && existingRoot !== root && document.documentElement.contains(existingRoot)) {
10029        warn('DUPLICATE_INSTANCE', `A second ${widgetMode} widget was found. Only one is allowed.`);
10030        showEditorMessage(`Duplicate ${widgetMode} navigation widget. Only one instance is allowed.`, true);
10031        return false;
10032      }
10033
10034      // The recorded owner is stale or belongs to this same widget root.
10035      // Clear it so the newly executed script can initialize normally.
10036      window.NMW[key] = null;
10037    }
10038
10039    window.NMW[key] = instanceId;
10040    root.dataset.nmwInstance = instanceId;
10041    return true;
10042  }
10043
10044  function releaseOwnership() {
10045    const key = widgetMode === 'full' ? 'ownerId' : 'mirrorOwnerId';
10046    if (window.NMW[key] === instanceId) window.NMW[key] = null;
10047  }
10048
10049  // ---------------------------------------------------------------------------
10050  // Duda collection fetching
10051  // ---------------------------------------------------------------------------
10052
10053  let collectionsApiPromise = null;
10054
10055  /*
10056    COLLECTION REQUEST CACHE
10057
10058    A single Full-widget lifecycle can rebuild several times because Duda changes
10059    native-nav attributes or the hidden manual-item source. Those rebuilds do not
10060    mean the underlying collections changed, so repeating the same collection API
10061    requests only adds latency.
10062
10063    Cache the request Promise per collection for this JavaScript instance. Duda
10064    reruns the widget JavaScript when its panel/configuration lifecycle is replaced,
10065    which naturally creates a fresh cache. Failed requests are removed immediately
10066    so a later rebuild can retry. The debug helper can explicitly force a refresh.
10067  */
10068  const collectionRequestCache = new Map();
10069
10070  // Load Duda's collection API once and reuse the same API object for every
10071  // collection request. The original working widget followed this pattern.
10072  function getCollectionsApi() {
10073    if (!collectionsApiPromise) {
10074      collectionsApiPromise = dmAPI.loadCollectionsAPI();
10075    }
10076    return collectionsApiPromise;
10077  }
10078
10079  // Preserve both layers of a Duda collection result. Dynamic collection
10080  // identifiers such as item, slug, uuid, and page_item_url may live on the
10081  // outer entry while editable collection fields live inside entry.data.
10082  // Flattening only entry.data discards the slug metadata and causes every
10083  // Navigation Tree validation check to report a false missing-slug warning.
10084  function normalizeFetchedCollectionEntry(entry) {
10085    if (!entry || typeof entry !== 'object') return entry;
10086    const nestedData = entry.data;
10087    if (!nestedData || typeof nestedData !== 'object' || Array.isArray(nestedData)) {
10088      return entry;
10089    }
10090
10091    // Nested collection fields are copied first and outer Duda metadata wins
10092    // when the same key exists at both levels. Keep data itself as well so any
10093    // future code can still inspect the original nested object.
10094    return { ...nestedData, ...entry };
10095  }
10096
10097  async function fetchCollection(name, options = {}) {
10098    const forceRefresh = Boolean(options && options.forceRefresh);
10099
10100    if (!forceRefresh && collectionRequestCache.has(name)) {
10101      return collectionRequestCache.get(name);
10102    }
10103
10104    const request = (async () => {
10105      const api = await getCollectionsApi();
10106
10107      // Duda allows a maximum page size of 100. 02 CATEGORY intentionally
10108      // stays capped at 100 rows. Other collections may load a second page,
10109      // giving them a maximum of 200 rows without an unnecessary extra request.
10110      const maxRows = name === '02 CATEGORY' ? 100 : 200;
10111
10112      const firstResult = await api
10113        .data(name)
10114        .pageSize(100)
10115        .pageNumber(0)
10116        .get();
10117
10118      const firstRows = firstResult && Array.isArray(firstResult.values)
10119        ? firstResult.values
10120        : [];
10121
10122      let rows = [...firstRows];
10123
10124      if (maxRows > 100 && firstRows.length === 100) {
10125        const secondResult = await api
10126          .data(name)
10127          .pageSize(100)
10128          .pageNumber(1)
10129          .get();
10130
10131        const secondRows = secondResult && Array.isArray(secondResult.values)
10132          ? secondResult.values
10133          : [];
10134
10135        rows = rows.concat(secondRows);
10136      }
10137
10138      return rows
10139        .slice(0, maxRows)
10140        .map(normalizeFetchedCollectionEntry)
10141        .filter(Boolean);
10142    })();
10143
10144    // Cache the Promise itself. This also coalesces two overlapping requests for
10145    // the same collection into one network request.
10146    collectionRequestCache.set(name, request);
10147
10148    try {
10149      return await request;
10150    } catch (error) {
10151      // Never cache a failed request. The next rebuild should be able to retry.
10152      if (collectionRequestCache.get(name) === request) {
10153        collectionRequestCache.delete(name);
10154      }
10155      throw error;
10156    }
10157  }
10158
10159  /*
10160    PROGRESSIVE FIRST RENDER
10161
10162    On the first Full-widget build, 06 NAV MASTER is enough to construct a
10163    working provisional navigation model. Render that model as soon as NAV
10164    MASTER is available instead of making the visitor wait for 02 CATEGORY and
10165    03 CORE validation to finish.
10166
10167    The provisional model is intentionally NOT published to window.NMW.data.
10168    Mirror therefore continues to wait for the final validated model. Once all
10169    required collections finish loading, runRebuild() replaces this provisional
10170    render with the normal final navigation.
10171  */
10172  function renderProvisionalNavigation(navMasterRows) {
10173    if (
10174      widgetMode !== 'full' ||
10175      window.NMW.ready ||
10176      !isCurrentInstance() ||
10177      !Array.isArray(navMasterRows) ||
10178      !navMasterRows.length
10179    ) {
10180      return;
10181    }
10182
10183    try {
10184      let provisionalModel = normalizeNavMasterFallback(navMasterRows);
10185
10186      // Include any configured native navigation that is already available.
10187      // If Duda inserts the native wrapper later, the existing discovery
10188      // observer will schedule the normal rebuild and the final model will pick
10189      // it up without delaying this first visible render.
10190      const nativeItems = normalizeConfiguredNativeNavigation();
10191      provisionalModel = [...provisionalModel, ...nativeItems];
10192
10193      // Manual Content Panel items are available without waiting for Category
10194      // or Core and should appear in the same first render when configured.
10195      provisionalModel = layerManualNavigationItems(provisionalModel, false);
10196
10197      // Preserve Mega grouping and the configured top-line presentation so the
10198      // provisional navigation visually matches the final navigation.
10199      provisionalModel = groupMegaNavigationItems(provisionalModel);
10200
10201      if (
10202        !provisionalModel.length ||
10203        !isCurrentInstance() ||
10204        window.NMW.ready
10205      ) {
10206        return;
10207      }
10208
10209      root.dataset.nmwProvisional = 'true';
10210      renderNavigation(provisionalModel, 'full');
10211    } catch (error) {
10212      // A provisional-render failure must never prevent the normal final build.
10213      console.warn('[NMW] Provisional navigation render failed', error);
10214    }
10215  }
10216
10217  async function fetchRequiredCollections() {
10218    const useCustomNavOnly = readBoolean(
10219      configValueFrom(cfg, 'useCustomNavOnly'),
10220      false
10221    );
10222
10223    const output = {};
10224    let hadError = false;
10225
10226    // CUSTOMNAV mode retains its existing loading behavior because 06 NAV
10227    // MASTER is not the active source in that mode.
10228    if (useCustomNavOnly) {
10229      try {
10230        output.CUSTOMNAV = await fetchCollection('CUSTOMNAV');
10231      } catch (error) {
10232        output.CUSTOMNAV = [];
10233        hadError = true;
10234        warn(
10235          'COLLECTION_FETCH_FAILED',
10236          'Could not fetch collection "CUSTOMNAV".',
10237          error
10238        );
10239      }
10240
10241      return { collections: output, hadError };
10242    }
10243
10244    // Normal mode deliberately loads NAV MASTER first. As soon as it resolves,
10245    // render a provisional top navigation before waiting for validation data.
10246    try {
10247      output['06 NAV MASTER'] = await fetchCollection('06 NAV MASTER');
10248    } catch (error) {
10249      output['06 NAV MASTER'] = [];
10250      hadError = true;
10251      warn(
10252        'COLLECTION_FETCH_FAILED',
10253        'Could not fetch collection "06 NAV MASTER".',
10254        error
10255      );
10256    }
10257
10258    if (output['06 NAV MASTER'].length) {
10259      renderProvisionalNavigation(output['06 NAV MASTER']);
10260    }
10261
10262    // Preserve the existing sequential loading behavior for Category and Core.
10263    // This remains safer in the Duda editor than starting simultaneous loaders.
10264    for (const name of ['02 CATEGORY', '03 CORE']) {
10265      try {
10266        output[name] = await fetchCollection(name);
10267      } catch (error) {
10268        output[name] = [];
10269        hadError = true;
10270        warn(
10271          'COLLECTION_FETCH_FAILED',
10272          `Could not fetch collection "${name}".`,
10273          error
10274        );
10275      }
10276    }
10277
10278    return { collections: output, hadError };
10279  }
10280
10281
10282  // Lightweight runtime diagnostic available from the browser console.
10283  // Run nmwDebug() to confirm startup, render-host detection, shared state,
10284  // and whether the required Duda collections can be loaded.
10285  const runtimeDebug = async function nmwRuntimeDebug() {
10286    const report = {
10287      widgetMode,
10288      instanceId,
10289      rootFound: Boolean(root),
10290      renderHostFound: Boolean(renderHost),
10291      renderHostClass: renderHost ? renderHost.className : '',
10292      categoryUrlPrefix: CATEGORY_URL_PREFIX,
10293      childUrlPrefix: CHILD_URL_PREFIX,
10294      design: {
10295        useUniversalBackgrounds: readBoolean(
10296          designValue(widgetMode, ['useUniversalBackgrounds', 'useUniversalBackground'], true),
10297          true
10298        ),
10299        universalBackgroundColor: designValue(widgetMode, 'universalBackgroundColor', '#ffffff'),
10300        resolvedSubmenuBackground: root.style.getPropertyValue('--nmw-sub-bg'),
10301        resolvedThirdLevelBackground: root.style.getPropertyValue('--nmw-subsub-bg'),
10302        resolvedMegaBackground: root.style.getPropertyValue('--nmw-mega-bg'),
10303        resolvedTopBackground: root.style.getPropertyValue('--nmw-top-bg'),
10304        resolvedMegaFamilyBackground: root.style.getPropertyValue('--nmw-mega-family-bg'),
10305        useUniversalTypography: readBoolean(
10306          designValue(widgetMode, 'useUniversalTypography', true),
10307          true
10308        ),
10309        universalFontFamily: designValue(widgetMode, ['universalFontFamily', 'universalFont'], 'inherit'),
10310        universalFontSize: designValue(widgetMode, ['universalFontSize', 'universalTextSize', 'universalFontFamily', 'universalFont'], '16px'),
10311        universalFontWeight: designValue(widgetMode, ['universalFontWeight', 'universalFontFamily', 'universalFont'], '500'),
10312        resolvedUniversalFontFamily: root.style.getPropertyValue('--nmw-universal-font'),
10313        resolvedUniversalFontSize: root.style.getPropertyValue('--nmw-universal-size'),
10314        resolvedUniversalFontWeight: root.style.getPropertyValue('--nmw-universal-weight')
10315      },
10316      sharedState: window.NMW
10317    };
10318
10319    console.group(`NMW Runtime Debug (${widgetMode})`);
10320    console.log('Startup report:', report);
10321
10322    // Full is the collection owner. Mirror intentionally does not fetch.
10323    if (widgetMode === 'full') {
10324      const collectionNames = readBoolean(configValueFrom(cfg, 'useCustomNavOnly'), false)
10325        ? ['CUSTOMNAV']
10326        : ['06 NAV MASTER', '02 CATEGORY', '03 CORE'];
10327
10328      for (const name of collectionNames) {
10329        try {
10330          const rows = await fetchCollection(name, { forceRefresh: true });
10331          const sampleSlugs = name === '02 CATEGORY' || name === '03 CORE'
10332            ? rows.slice(0, 10).flatMap(extractCollectionSlugs)
10333            : [];
10334          console.log(`${name}: ${rows.length} row(s) loaded`, {
10335            sampleRows: rows.slice(0, 3),
10336            sampleSlugs
10337          });
10338        } catch (error) {
10339          console.error(`${name}: FAILED`, error);
10340        }
10341      }
10342    } else {
10343      console.log('Mirror uses Full shared data and does not fetch collections.');
10344    }
10345
10346    console.groupEnd();
10347    return report;
10348  };
10349
10350  window[`nmwDebug${widgetMode === 'full' ? 'Full' : 'Mirror'}`] = runtimeDebug;
10351  // Do not let a later Mirror initialization replace the primary Full debug helper.
10352  if (widgetMode === 'full' || typeof window.nmwDebug !== 'function') {
10353    window.nmwDebug = runtimeDebug;
10354  }
10355
10356  // ---------------------------------------------------------------------------
10357  // Dynamic collection indexes
10358  // ---------------------------------------------------------------------------
10359
10360  function extractPageItemUrl(row) {
10361    const raw = field(row, 'page_item_url', 'Page Item URL', 'PAGE ITEM URL', 'item_url', 'URL');
10362    if (raw && typeof raw === 'object') return readString(raw.href || raw.url || '', '');
10363    return readString(raw, '');
10364  }
10365
10366  function buildDynamicIndex(rows) {
10367    const items = (rows || []).map((row, order) => {
10368      const url = extractPageItemUrl(row);
10369      return {
10370        row,
10371        order,
10372        url,
10373        canonicalPath: canonicalPathWithoutHash(url),
10374        slug: getLastPathSegment(url)
10375      };
10376    }).filter(item => item.url);
10377
10378    /*
10379      Keep the original ordered items for compatibility, but also build an O(1)
10380      path lookup. If duplicate canonical paths exist, preserve the first row just
10381      as Array.find() did in the original implementation.
10382    */
10383    const byCanonicalPath = new Map();
10384    items.forEach(item => {
10385      if (item.canonicalPath && !byCanonicalPath.has(item.canonicalPath)) {
10386        byCanonicalPath.set(item.canonicalPath, item);
10387      }
10388    });
10389
10390    return { items, byCanonicalPath };
10391  }
10392
10393
10394  // Build a simple combined slug inventory for Navigation Tree warnings.
10395  // This intentionally ignores which collection the slug is stored in. A slug
10396  // is considered valid when it exists in either 02 CATEGORY or 03 CORE.
10397  function normalizeCollectionSlug(value) {
10398    let raw = value;
10399    if (raw && typeof raw === 'object') {
10400      raw = raw.item
10401        ?? raw.slug
10402        ?? raw.value
10403        ?? raw.href
10404        ?? raw.url
10405        ?? raw.name
10406        ?? '';
10407    }
10408
10409    const textValue = readString(raw, '');
10410    if (!textValue) return '';
10411
10412    // URLs and paths use their final segment. Bare collection item values are
10413    // already slugs and are normalized directly.
10414    if (/^(https?:)?\/\//i.test(textValue) || textValue.includes('/') || textValue.includes('?') || textValue.includes('#')) {
10415      const segment = getLastPathSegment(textValue);
10416      if (segment) return segment.toLowerCase();
10417    }
10418
10419    return textValue
10420      .toLowerCase()
10421      .split(/[?#]/)[0]
10422      .replace(/^\/+|\/+$/g, '')
10423      .split('/')
10424      .filter(Boolean)
10425      .pop() || '';
10426  }
10427
10428  function extractCollectionSlugs(row) {
10429    if (!row || typeof row !== 'object') return [];
10430
10431    // Inspect both the flattened row and the original nested data object. This
10432    // supports either Duda response shape without depending on where the item
10433    // slug or dynamic page URL happens to be stored.
10434    const sources = [row];
10435    if (row.data && typeof row.data === 'object' && !Array.isArray(row.data)) {
10436      sources.push(row.data);
10437    }
10438
10439    const rawValues = [];
10440    sources.forEach(source => {
10441      rawValues.push(
10442        field(source, 'item', 'Item', 'ITEM'),
10443        field(source, 'slug', 'Slug', 'SLUG'),
10444        field(source, 'name', 'Name', 'NAME'),
10445        extractPageItemUrl(source)
10446      );
10447    });
10448
10449    return unique(rawValues.map(normalizeCollectionSlug));
10450  }
10451
10452  function buildCombinedCollectionSlugSet(categoryRows, coreRows) {
10453    const slugs = new Set();
10454    [...(categoryRows || []), ...(coreRows || [])].forEach(row => {
10455      extractCollectionSlugs(row).forEach(slug => {
10456        if (slug) slugs.add(slug);
10457      });
10458    });
10459    return slugs;
10460  }
10461
10462  function buildSlugValidationWarnings(tree, categoryRows, coreRows) {
10463    const availableSlugs = buildCombinedCollectionSlugSet(categoryRows, coreRows);
10464    const warnings = [];
10465
10466    function walk(nodes) {
10467      (nodes || []).forEach(node => {
10468        if (!node || node.invalid || node.depth > MAX_DEPTH) return;
10469
10470        const isHome = node.depth === 0 && node.label.toLowerCase() === 'home';
10471        if (!isHome) {
10472          const candidates = createSlugCandidates(node.label);
10473          const found = candidates.some(slug => availableSlugs.has(slug));
10474
10475          if (!found) {
10476            const validChildren = (node.children || []).filter(child => child && !child.invalid && child.depth <= MAX_DEPTH);
10477            const suggestedCollection = usesCategorySource(node.depth, validChildren)
10478              ? '02 CATEGORY'
10479              : '03 CORE';
10480
10481            warnings.push({
10482              label: node.label,
10483              slugs: candidates,
10484              suggestedCollection
10485            });
10486          }
10487        }
10488
10489        walk(node.children || []);
10490      });
10491    }
10492
10493    walk(tree || []);
10494    return warnings;
10495  }
10496
10497  // Build complete expected paths from Page Name and the configured prefix.
10498  // The display label is never changed. Only lookup candidates are normalized.
10499  // "Heating/Cooling" with prefix "services" becomes /services/heating-cooling.
10500  // "Boats & Cars" checks both /.../boats-and-cars and /.../boats-cars.
10501  function buildExpectedDynamicPaths(pageName, prefix) {
10502    return createSlugCandidates(pageName).map(slug => {
10503      const path = prefix ? `/${prefix}/${slug}` : `/${slug}`;
10504      return canonicalPathWithoutHash(path);
10505    });
10506  }
10507
10508  // Match only the complete expected dynamic-page path. The configured prefix
10509  // is authoritative. A row with the same final slug under a different prefix
10510  // must not be selected, because that would make categoryUrlPrefix and
10511  // childUrlPrefix appear to be ignored. The Map lookup keeps the original
10512  // collection-order behavior when both supported ampersand variants exist.
10513  function findDynamicMatch(index, pageName, prefix) {
10514    const expectedPaths = buildExpectedDynamicPaths(pageName, prefix);
10515    if (!index || !expectedPaths.length) return null;
10516
10517    if (index.byCanonicalPath instanceof Map) {
10518      let earliest = null;
10519
10520      expectedPaths.forEach(path => {
10521        const match = index.byCanonicalPath.get(path);
10522        if (match && (!earliest || match.order < earliest.order)) {
10523          earliest = match;
10524        }
10525      });
10526
10527      return earliest;
10528    }
10529
10530    // Defensive compatibility fallback for any older array-shaped index.
10531    const items = Array.isArray(index)
10532      ? index
10533      : (Array.isArray(index.items) ? index.items : []);
10534
10535    return items.find(item => expectedPaths.includes(item.canonicalPath)) || null;
10536  }
10537
10538  // Build the internal dynamic URL dictated by the configured Category/Core
10539  // prefix. This is the fallback when the corresponding collection does not yet
10540  // contain an exact page_item_url. It keeps the item visible without allowing a
10541  // legacy Page Link or a same-slug row under another prefix to override the panel.
10542  function buildConfiguredDynamicUrl(pageName, prefix) {
10543    const slug = slugify(pageName);
10544    const path = `/${prefix ? `${prefix}/` : ''}${slug}`;
10545    return buildRuntimeFallbackUrl(path);
10546  }
10547
10548  // Category pages are structural navigation parents. Any navigation item with
10549  // no valid children is treated as a Core page, even when it is top-level or
10550  // assigned to a Mega group. Mega controls presentation only.
10551  function usesCategorySource(depth, children) {
10552    const childCount = Array.isArray(children) ? children.length : 0;
10553    return Number(depth) < MAX_DEPTH && childCount > 0;
10554  }
10555
10556  // ---------------------------------------------------------------------------
10557  // 06 NAV MASTER normalization
10558  // ---------------------------------------------------------------------------
10559
10560  function parseMegaGroup(value) {
10561    // Duda collection fields may arrive as strings, booleans, or small objects
10562    // depending on whether the Mega column is configured as text, select, or switch.
10563    let raw = value;
10564    if (raw && typeof raw === 'object') {
10565      raw = raw.value
10566        ?? raw.label
10567        ?? raw.name
10568        ?? raw.text
10569        ?? raw.selected
10570        ?? raw.checked
10571        ?? raw.id
10572        ?? '';
10573    }
10574
10575    if (raw === true) return 1;
10576    if (raw === false || raw == null) return 0;
10577
10578    const normalized = readString(raw, 'No')
10579      .toLowerCase()
10580      .replace(/[\s_-]+/g, '');
10581
10582    if (['yes', 'true', 'on', 'mega', 'mega1', '1'].includes(normalized)) return 1;
10583    if (['mega2', '2'].includes(normalized)) return 2;
10584    if (['mega3', '3'].includes(normalized)) return 3;
10585    if (['mega4', '4'].includes(normalized)) return 4;
10586    return 0;
10587  }
10588
10589  function extractNavMasterPageLink(row) {
10590    const raw = field(
10591      row,
10592      'Page Link',
10593      'page_link',
10594      'PAGE LINK',
10595      'Page URL',
10596      'page_url',
10597      'Link'
10598    );
10599
10600    if (raw && typeof raw === 'object') {
10601      return readString(raw.href ?? raw.url ?? raw.value ?? '', '');
10602    }
10603
10604    return readString(raw, '');
10605  }
10606
10607  function buildCollectionTree(navRows) {
10608    const nodes = [];
10609    const byName = new Map();
10610
10611    (navRows || []).forEach((row, order) => {
10612      const label = readString(field(row, 'Page Name'), '');
10613      if (!label) return;
10614      const nameKey = label.toLowerCase();
10615      if (byName.has(nameKey)) {
10616        warn('DUPLICATE_PAGE_NAME', `Duplicate Page Name "${label}" was ignored.`, row);
10617        return;
10618      }
10619      const node = {
10620        raw: row,
10621        label,
10622        parentName: readString(field(row, 'Parent 1'), ''),
10623        topMegaValue: field(row, 'Mega'),
10624        pageLink: extractNavMasterPageLink(row),
10625        order,
10626        children: [],
10627        parent: null
10628      };
10629      nodes.push(node);
10630      byName.set(nameKey, node);
10631    });
10632
10633    const roots = [];
10634    nodes.forEach(node => {
10635      if (!node.parentName) {
10636        roots.push(node);
10637        return;
10638      }
10639      const parent = byName.get(node.parentName.toLowerCase());
10640      if (!parent || parent === node) {
10641        warn('MISSING_PARENT', `"${node.label}" was skipped because Parent 1 "${node.parentName}" was not found.`, node.raw);
10642        node.invalid = true;
10643        return;
10644      }
10645      node.parent = parent;
10646      parent.children.push(node);
10647    });
10648
10649    function sortAndValidate(list, depth) {
10650      list.sort((a, b) => a.order - b.order);
10651      list.forEach(node => {
10652        node.depth = depth;
10653        if (depth > MAX_DEPTH) {
10654          node.invalid = true;
10655          warn('MAX_DEPTH_EXCEEDED', `"${node.label}" was ignored because navigation supports only three levels.`, node.raw);
10656        }
10657        sortAndValidate(node.children, depth + 1);
10658      });
10659    }
10660    sortAndValidate(roots, 0);
10661    return roots.filter(node => !node.invalid);
10662  }
10663
10664  function normalizeCollectionNavigation(navRows, categoryRows, coreRows) {
10665    const tree = buildCollectionTree(navRows);
10666    const categoryIndex = buildDynamicIndex(categoryRows);
10667    const coreIndex = buildDynamicIndex(coreRows);
10668
10669    // Navigation Tree warnings use one combined slug inventory. The actual
10670    // collection only matters as a suggestion when a slug is absent from both.
10671    local.slugValidationWarnings = buildSlugValidationWarnings(tree, categoryRows, coreRows);
10672
10673    function normalizeNode(node, ancestors, inheritedMegaGroup) {
10674      if (node.invalid || node.depth > MAX_DEPTH) return null;
10675      const isRoot = node.depth === 0;
10676      const megaGroup = isRoot ? parseMegaGroup(node.topMegaValue) : inheritedMegaGroup;
10677      if (!isRoot && parseMegaGroup(node.topMegaValue) && parseMegaGroup(node.topMegaValue) !== inheritedMegaGroup) {
10678        warn('DESCENDANT_MEGA_IGNORED', `Mega value on descendant "${node.label}" was ignored; descendants inherit the top-level mega group.`, node.raw);
10679      }
10680
10681      const validChildren = node.children
10682        .map(child => normalizeNode(child, [...ancestors, node], megaGroup))
10683        .filter(Boolean);
10684      const hasGrandchildren = node.depth === 1 && validChildren.length > 0;
10685
10686      const isHome = node.depth === 0 && node.label.toLowerCase() === 'home';
10687      const expectsCategory = usesCategorySource(node.depth, validChildren);
10688      const expectedIndex = expectsCategory ? categoryIndex : coreIndex;
10689      const expectedPrefix = expectsCategory ? CATEGORY_URL_PREFIX : CHILD_URL_PREFIX;
10690      const match = isHome ? null : findDynamicMatch(expectedIndex || [], node.label, expectedPrefix);
10691      const navMasterUrl = node.pageLink ? buildRuntimeFallbackUrl(node.pageLink) : '';
10692      let type = 'page';
10693      let url = '';
10694
10695      if (isHome) {
10696        const nativeHome = findTreeItem(parseNativeNavigation(), item => item.type === 'page' && item.canonicalPath === '/');
10697        url = nativeHome && nativeHome.url
10698          ? nativeHome.url
10699          : (navMasterUrl || buildRuntimeFallbackUrl('/'));
10700      } else {
10701        // Prefix controls are authoritative for collection navigation. Prefer the
10702        // exact page_item_url only when it matches the configured full path. If
10703        // that row is missing, construct the URL from the configured prefix.
10704        // Page Link remains structure/reference data and no longer overrides the
10705        // Category/Core prefix fields for internal dynamic pages.
10706        url = match ? match.url : buildConfiguredDynamicUrl(node.label, expectedPrefix);
10707      }
10708
10709      if (!isHome && !match) {
10710        warn('DYNAMIC_PREFIX_CONSTRUCTED_FALLBACK', `"${node.label}" used the configured prefix because no exact dynamic collection path was found.`, {
10711          depth: node.depth,
10712          expectedCollection: expectsCategory ? '02 CATEGORY' : '03 CORE',
10713          configuredPrefix: expectedPrefix,
10714          ignoredPageLink: node.pageLink || '',
10715          expectedPaths: buildExpectedDynamicPaths(node.label, expectedPrefix),
10716          constructedUrl: url
10717        });
10718      }
10719
10720      // Standard top-level structural parents without a matching Category page
10721      // may still act as folders when they have valid children and no explicit
10722      // Mega assignment. Mega families remain clickable as Category parents.
10723      if (!isHome && node.depth === 0 && megaGroup === 0 && !match && validChildren.length && !node.pageLink) {
10724        type = 'folder';
10725        url = '';
10726      }
10727
10728      const hierarchySlugs = [...ancestors.map(item => slugify(item.label)), slugify(node.label)];
10729      const key = `collection:${hierarchySlugs.join('/')}`;
10730      const presentation = megaGroup ? 'mega' : 'standard';
10731      return {
10732        key,
10733        parentKey: ancestors.length ? `collection:${ancestors.map(item => slugify(item.label)).join('/')}` : null,
10734        source: 'collection',
10735        type,
10736        presentation,
10737        megaGroup,
10738        depth: node.depth,
10739        order: node.order,
10740        label: node.label,
10741        slug: slugify(node.label),
10742        canonicalPath: url ? canonicalPath(url) : '',
10743        url,
10744        target: '_self',
10745        isExternal: isExternalUrl(url),
10746        hasChildren: validChildren.length > 0,
10747        children: validChildren
10748      };
10749    }
10750
10751    let normalized = tree.map(rootNode =>
10751 normalizeNode(rootNode, [], 0)).filter(Boolean);
10752
10753    // Mega family alphabetization is applied only after top-level families are
10754    // grouped under their synthetic Mega trigger. Child and grandchild order is
10755    // always preserved from 06 NAV MASTER.
10756    return normalized;
10757  }
10758
10759  // ---------------------------------------------------------------------------
10760  // Native Duda navigation from .navmasterdefault
10761  // ---------------------------------------------------------------------------
10762
10763  function getNativeWrapper() {
10764    return document.querySelector('.navmasterdefault');
10765  }
10766
10767  function nativeAnchorLabel(anchor) {
10768    if (!anchor) return '';
10769    const textNode = anchor.querySelector('[data-link-text]');
10770    return readString(textNode?.getAttribute('data-link-text') || textNode?.textContent || anchor.textContent, '');
10771  }
10772
10773  function nativeAnchorHref(anchor) {
10774    if (!anchor) return '';
10775    const values = [
10776      anchor.getAttribute('href'),
10777      anchor.getAttribute('runtime_url'),
10778      anchor.getAttribute('data-href'),
10779      anchor.getAttribute('raw_url')
10780    ].map(value => readString(value, ''));
10781
10782    // Preserve Duda's current rendered href whenever it is usable. If href is
10783    // only a folder placeholder, fall through to runtime_url/raw_url.
10784    return values.find(value => value && value !== '#' && !value.toLowerCase().startsWith('javascript:'))
10785      || values.find(Boolean)
10786      || '';
10787  }
10788
10789  function nativeAnchorRawUrl(anchor) {
10790    if (!anchor) return '';
10791    return readString(
10792      anchor.getAttribute('raw_url') ||
10793      anchor.getAttribute('runtime_url') ||
10794      anchor.getAttribute('data-href') ||
10795      anchor.getAttribute('href'),
10796      ''
10797    );
10798  }
10799
10800  function readNativeNode(li, depth, parentKey) {
10801    if (!li || depth > MAX_DEPTH) return null;
10802    const anchor = li.querySelector(':scope > a, :scope > span > a, :scope > div > a');
10803    const href = nativeAnchorHref(anchor);
10804    const rawUrl = nativeAnchorRawUrl(anchor);
10805    // Preserve Duda-generated link metadata/classes from the native navigation.
10806    // Anchor links can rely on these attributes for Duda's own scrolling and
10807    // fixed-header compensation behavior. linkHtml() already knows how to carry
10808    // linkAttributes/linkClassName into the rebuilt NMW anchor.
10809    const nativeLinkData = anchor ? manualLinkFromAnchor(anchor) : { attributes: {}, className: '' };
10810    const label = nativeAnchorLabel(anchor) || readString(li.getAttribute('data-link-text'), '');
10811    const isFolder = !href || href === '#' || !anchor;
10812    const canonical = isFolder ? '' : canonicalPath(rawUrl || href);
10813    const key = isFolder
10814      ? `native-folder:${parentKey || 'root'}/${slugify(label)}`
10815      : `native:${canonical || canonicalPath(href)}`;
10816    const childList = li.querySelector(':scope > ul[data-auto="sub-pages"], :scope > ul.unav-sub, :scope > ul');
10817    const childLis = childList ? Array.from(childList.querySelectorAll(':scope > li')) : [];
10818    const children = childLis.map(child => readNativeNode(child, depth + 1, key)).filter(Boolean);
10819    return {
10820      key,
10821      parentKey,
10822      source: 'native',
10823      type: isFolder ? 'folder' : 'page',
10824      presentation: 'standard',
10825      megaGroup: 0,
10826      depth,
10827      order: 0,
10828      label,
10829      slug: slugify(label),
10830      canonicalPath: canonical,
10831      url: isFolder ? '' : href,
10832      target: anchor ? readString(anchor.getAttribute('target'), '_self') : '_self',
10833      isExternal: isExternalUrl(href),
10834      linkAttributes: nativeLinkData.attributes || {},
10835      linkClassName: readString(nativeLinkData.className, ''),
10836      hasChildren: children.length > 0,
10837      children
10838    };
10839  }
10840
10841  function parseNativeNavigation() {
10842    const wrapper = getNativeWrapper();
10843    if (!wrapper) return [];
10844    const nav = wrapper.querySelector('nav[data-element-type="onelinksmenu"], nav.unifiednav, nav');
10845    if (!nav) return [];
10846    const topList = nav.querySelector(':scope > ul[data-auto="navigation-pages"], :scope > ul');
10847    if (!topList) return [];
10848    return Array.from(topList.querySelectorAll(':scope > li')).map(li =>
10848 readNativeNode(li, 0, null)).filter(Boolean);
10849  }
10850
10851  function configuredNativeParents() {
10852    const configured = configValueFrom(cfg, 'nativeParents');
10853    const list = Array.isArray(configured) ? configured : [];
10854    return list.map((item, order) => ({
10855      url: readString(item.url?.href || item.url, ''),
10856      canonicalPath: canonicalPath(item.url?.href || item.url),
10857      label: readString(item.label, ''),
10858      showParent: readBoolean(item.showParent, false),
10859      pullChildren: readBoolean(item.pullChildren, false),
10860      order
10861    }));
10862  }
10863
10864  function cloneNativeBranch(node, config, depth = 0, parentKey = null) {
10865    if (!node || depth > MAX_DEPTH) return null;
10866    const children = config.pullChildren
10867      ? node.children.map(child => cloneNativeBranch(child, { ...config, label: '', showParent: true, pullChildren: true }, depth + 1, node.key)).filter(Boolean)
10868      : [];
10869
10870    // A folder without pulled valid children has no useful action and is omitted.
10871    if (node.type === 'folder' && !children.length) return null;
10872
10873    return {
10874      ...node,
10875      parentKey,
10876      depth,
10877      order: config.order,
10878      label: depth === 0 && config.label ? config.label : node.label,
10879      hasChildren: children.length > 0,
10880      children
10881    };
10882  }
10883
10884  function normalizeConfiguredNativeNavigation() {
10885    const parsed = parseNativeNavigation();
10886    const configs = configuredNativeParents();
10887    const result = [];
10888    const seen = new Set();
10889
10890    configs.forEach(config => {
10891      if (!config.showParent) return;
10892      if (!config.canonicalPath || seen.has(config.canonicalPath)) {
10893        if (seen.has(config.canonicalPath)) warn('DUPLICATE_NATIVE_CONFIG', `Native URL "${config.url}" was configured more than once; the first entry was kept.`);
10894        return;
10895      }
10896      seen.add(config.canonicalPath);
10897      const match = parsed.find(item => item.canonicalPath === config.canonicalPath);
10898      if (!match) {
10899        warn('NATIVE_URL_NOT_FOUND', `Configured native URL "${config.url}" was not found in .navmasterdefault.`);
10900        return;
10901      }
10902      const normalized = cloneNativeBranch(match, config, 0, null);
10903      if (normalized) result.push(normalized);
10904    });
10905
10906    return result;
10907  }
10908
10909  // ---------------------------------------------------------------------------
10910  // Manual navigation items from the Duda Content Panel
10911  // ---------------------------------------------------------------------------
10912
10913  /*
10914    Manual items are layered onto either navigation source:
10915    - Normal mode: 06 NAV MASTER + configured native pages + manual items.
10916    - CUSTOMNAV-only mode: CUSTOMNAV collection + manual items.
10917
10918    Duda link inputs must be rendered with the custom_link Handlebars helper so
10919    page, dynamic-page, popup, anchor, email, telephone, and download behavior
10920    retains the attributes Duda generates. The matching HTML writes those links
10921    into a hidden manual-item source. JavaScript reads the generated anchors and
10922    carries their attributes into the final navigation markup.
10923  */
10924
10925  function normalizeManualLevel(value) {
10926    const normalized = readString(value, 'top').toLowerCase().replace(/[\s_-]+/g, '');
10927    if (['2', 'grandchild', 'subsub', 'third', 'thirdlevel'].includes(normalized)) return 2;
10928    if (['1', 'child', 'sub', 'second', 'secondlevel'].includes(normalized)) return 1;
10929    return 0;
10930  }
10931
10932  function normalizeManualPlacement(value) {
10933    const normalized = readString(value, 'after-native').toLowerCase().replace(/[\s_]+/g, '-');
10934    if (['afterhome', 'after-home', 'home'].includes(normalized)) return 'after-home';
10935    if (['aftercollection', 'after-collection', 'collection'].includes(normalized)) return 'after-collection';
10936    return 'after-native';
10937  }
10938
10939  function resolveManualRuntimeHref(value) {
10940    const raw = readString(value, '');
10941    if (!raw) return '';
10942
10943    // Duda-generated special links already contain the required scheme/action.
10944    if (/^(?:https?:|mailto:|tel:|sms:|javascript:|data:|blob:)/i.test(raw) || raw.startsWith('//') || raw.startsWith('#')) {
10945      return raw;
10946    }
10947
10948    return buildRuntimeFallbackUrl(raw);
10949  }
10950
10951  function manualLinkValue(value) {
10952    if (value == null) return { href: '', target: '_self', attributes: {}, className: '' };
10953    if (typeof value === 'string') {
10954      return { href: resolveManualRuntimeHref(value), target: '_self', attributes: {}, className: '' };
10955    }
10956
10957    if (typeof value !== 'object') {
10958      return { href: '', target: '_self', attributes: {}, className: '' };
10959    }
10960
10961    const href = readString(
10962      value.href
10963        ?? value.url
10964        ?? value.runtime_url
10965        ?? value.runtimeUrl
10966        ?? value.raw_url
10967        ?? value.rawUrl
10968        ?? value.value
10969        ?? value.path
10970        ?? '',
10971      ''
10972    );
10973    const opensNewWindow = readBoolean(value.newWindow ?? value.openInNewWindow ?? value.new_window, false);
10974    const target = readString(value.target, opensNewWindow ? '_blank' : '_self');
10975    const attributes = {};
10976
10977    // Preserve common Duda link metadata when the configuration object exposes it.
10978    Object.entries(value).forEach(([name, attributeValue]) => {
10979      const lower = String(name).toLowerCase();
10980      if (!attributeValue && attributeValue !== 0) return;
10981      if (['href', 'url', 'runtime_url', 'runtimeurl', 'raw_url', 'rawurl', 'value', 'path', 'target', 'class', 'classname', 'style', 'id'].includes(lower)) return;
10982      if (lower.startsWith('data-') || ['rel', 'download', 'title', 'onclick'].includes(lower)) {
10983        attributes[name] = String(attributeValue);
10984      }
10985    });
10986
10987    return {
10988      href: resolveManualRuntimeHref(href),
10989      target,
10990      attributes,
10991      className: readString(value.className ?? value.class, '')
10992    };
10993  }
10994
10995  function manualLinkFromAnchor(anchor) {
10996    if (!anchor) return { href: '', target: '_self', attributes: {}, className: '' };
10997    const attributes = {};
10998
10999    Array.from(anchor.attributes || []).forEach(attribute => {
11000      const name = attribute.name;
11001      const lower = name.toLowerCase();
11002      if (['href', 'target', 'class', 'style', 'id', 'role', 'aria-current'].includes(lower)) return;
11003      // Retain Duda's link behavior metadata, including popup/download handlers.
11004      if (lower.startsWith('data-') || lower.startsWith('aria-') || lower.startsWith('on') || ['rel', 'download', 'title', 'name', 'type'].includes(lower)) {
11005        attributes[name] = attribute.value;
11006      }
11007    });
11008
11009    return {
11010      href: readString(anchor.getAttribute('href'), ''),
11011      target: readString(anchor.getAttribute('target'), '_self'),
11012      attributes,
11013      className: readString(anchor.getAttribute('class'), '')
11014    };
11015  }
11016
11017  function manualRowFromDom(node, index) {
11018    const anchor = node.querySelector('a');
11019    return {
11020      manualItemKey: node.getAttribute('data-manual-item-key') || '',
11021      manualLabel: node.getAttribute('data-manual-label') || '',
11022      manualType: node.getAttribute('data-manual-type') || 'page',
11023      manualLevel: node.getAttribute('data-manual-level') || 'top',
11024      manualParent: node.getAttribute('data-manual-parent') || '',
11025      manualPlacement: node.getAttribute('data-manual-placement') || 'after-native',
11026      manualOrder: node.getAttribute('data-manual-order') || index,
11027      manualMegaGroup: node.getAttribute('data-manual-mega-group') || '0',
11028      manualShowItem: node.getAttribute('data-manual-show-item'),
11029      // Return null until Duda's custom_link helper has produced a real anchor.
11030      // This allows normalizeManualPanelItems() to fall back to data.config.
11031      __manualLink: anchor ? manualLinkFromAnchor(anchor) : null
11032    };
11033  }
11034
11035  function configuredManualRows() {
11036    const configured = configValueFrom(cfg, 'manualNavigationItems');
11037    const configRows = Array.isArray(configured) ? configured : [];
11038    const domNodes = Array.from(root.querySelectorAll('[data-nmw-manual-item]'));
11039    const domRows = domNodes.map(manualRowFromDom);
11040
11041    if (!domRows.length) return configRows;
11042    if (!configRows.length) return domRows;
11043
11044    // Duda can update the repeat-group configuration and the hidden Handlebars
11045    // output a few milliseconds apart. Merge both representations so a new row
11046    // can appear from data.config immediately, then receive the final custom_link
11047    // attributes as soon as Duda writes them into the hidden source.
11048    const configByKey = new Map();
11049    configRows.forEach((row, index) => {
11050      const key = readString(row && (row.manualItemKey ?? row.itemKey ?? row.key), '').toLowerCase();
11051      if (key && !configByKey.has(key)) configByKey.set(key, { row, index });
11052    });
11053
11054    const usedConfigIndexes = new Set();
11055    const merged = domRows.map((domRow, index) => {
11056      const key = readString(domRow.manualItemKey, '').toLowerCase();
11057      const keyed = key ? configByKey.get(key) : null;
11058      const configIndex = keyed ? keyed.index : index;
11059      const configRow = keyed ? keyed.row : (configRows[configIndex] || {});
11060      usedConfigIndexes.add(configIndex);
11061
11062      // DOM attributes are the newest visible Handlebars output. Config values
11063      // supply fields that are not yet represented in the DOM, especially links.
11064      return { ...configRow, ...domRow };
11065    });
11066
11067    configRows.forEach((row, index) => {
11068      if (!usedConfigIndexes.has(index)) merged.push(row);
11069    });
11070
11071    return merged;
11072  }
11073
11074  function normalizeManualPanelItems() {
11075    const output = [];
11076    const usedKeys = new Set();
11077
11078    configuredManualRows().forEach((row, index) => {
11079      const showValue = row.manualShowItem ?? row.showItem ?? row.show;
11080      if (showValue !== undefined && showValue !== null && showValue !== '' && !readBoolean(showValue, true)) return;
11081
11082      const label = readString(row.manualLabel ?? row.label ?? row.pageName, '');
11083      if (!label) return;
11084
11085      const type = readString(row.manualType ?? row.type, 'page').toLowerCase() === 'folder' ? 'folder' : 'page';
11086      const requestedDepth = normalizeManualLevel(row.manualLevel ?? row.level);
11087      const parentReference = readString(row.manualParent ?? row.parentReference ?? row.parent, '');
11088      const placement = normalizeManualPlacement(row.manualPlacement ?? row.placement);
11089      const order = readNumber(row.manualOrder ?? row.order, index, -100000, 100000);
11090      const configuredMegaGroup = parseMegaGroup(row.manualMegaGroup ?? row.megaGroup ?? 0);
11091      const configuredLinkData = manualLinkValue(row.manualLink ?? row.link ?? row.url);
11092      const domLinkData = row.__manualLink && readString(row.__manualLink.href, '')
11093        ? row.__manualLink
11094        : null;
11095      const linkData = domLinkData || configuredLinkData;
11096      // Folder items are structural controls and never require or output a link.
11097      const url = type === 'folder' ? '' : resolveManualRuntimeHref(linkData.href);
11098
11099      if (type === 'page' && !url) {
11100        warn('MANUAL_LINK_MISSING', `Manual navigation item "${label}" was skipped because it has no configured link.`);
11101        return;
11102      }
11103
11104      const suppliedKey = readString(row.manualItemKey ?? row.itemKey ?? row.key, '');
11105      let key = suppliedKey || `manual:${slugify(label) || 'item'}-${index + 1}`;
11106      if (!key.toLowerCase().startsWith('manual:')) key = `manual:${key}`;
11107      if (usedKeys.has(key.toLowerCase())) {
11108        warn('MANUAL_DUPLICATE_KEY', `Manual navigation key "${key}" was used more than once; the later item was skipped.`);
11109        return;
11110      }
11111      usedKeys.add(key.toLowerCase());
11112
11113      output.push({
11114        key,
11115        parentKey: null,
11116        source: 'manual',
11117        type,
11118        presentation: configuredMegaGroup ? 'mega' : 'standard',
11119        megaGroup: configuredMegaGroup,
11120        depth: requestedDepth,
11121        order,
11122        label,
11123        slug: slugify(label),
11124        canonicalPath: url && !/^(?:mailto:|tel:|sms:|javascript:)/i.test(url) ? canonicalPath(url) : '',
11125        url,
11126        target: readString(linkData.target, '_self'),
11127        isExternal: isExternalUrl(url),
11128        hasChildren: false,
11129        children: [],
11130        linkAttributes: linkData.attributes || {},
11131        linkClassName: readString(linkData.className, ''),
11132        _manualRequestedDepth: requestedDepth,
11133        _manualParentReference: parentReference,
11134        _manualPlacement: placement
11135      });
11136    });
11137
11138    return output;
11139  }
11140
11141  function buildManualParentIndex(baseItems, manualItems) {
11142    const byKey = new Map();
11143    const byLabel = new Map();
11144    const bySlug = new Map();
11145
11146    function add(item) {
11147      if (!item) return;
11148      const key = readString(item.key, '').toLowerCase();
11149      const label = readString(item.label, '').toLowerCase();
11150      const slug = readString(item.slug, slugify(item.label)).toLowerCase();
11151      if (key && !byKey.has(key)) byKey.set(key, item);
11152      if (label) {
11153        if (!byLabel.has(label)) byLabel.set(label, []);
11154        byLabel.get(label).push(item);
11155      }
11156      if (slug) {
11157        if (!bySlug.has(slug)) bySlug.set(slug, []);
11158        bySlug.get(slug).push(item);
11159      }
11160      (item.children || []).forEach(add);
11161    }
11162
11163    (baseItems || []).forEach(add);
11164    (manualItems || []).forEach(add);
11165    return { byKey, byLabel, bySlug };
11166  }
11167
11168  function resolveManualParent(reference, index) {
11169    const raw = readString(reference, '');
11170    if (!raw) return null;
11171    const lower = raw.toLowerCase();
11172    const keyCandidates = unique([lower, lower.startsWith('manual:') ? lower : `manual:${lower}`]);
11173    for (const key of keyCandidates) {
11174      if (index.byKey.has(key)) return index.byKey.get(key);
11175    }
11176
11177    const labelMatches = index.byLabel.get(lower) || [];
11178    if (labelMatches.length) {
11179      if (labelMatches.length > 1) {
11180        warn('MANUAL_PARENT_AMBIGUOUS', `Parent reference "${raw}" matched more than one Page Name. The first match was used; use an Item Key for an exact match.`);
11181      }
11182      return labelMatches[0];
11183    }
11184
11185    const slugMatches = index.bySlug.get(slugify(raw)) || [];
11186    if (slugMatches.length) {
11187      if (slugMatches.length > 1) {
11188        warn('MANUAL_PARENT_AMBIGUOUS', `Parent reference "${raw}" matched more than one slug. The first match was used; use an Item Key for an exact match.`);
11189      }
11190      return slugMatches[0];
11191    }
11192
11193    return null;
11194  }
11195
11196  function manualParentCreatesCycle(item, parent, manualByKey) {
11197    let current = parent;
11198    const seen = new Set([item.key]);
11199    while (current) {
11200      if (seen.has(current.key)) return true;
11201      seen.add(current.key);
11202      const parentReference = current._manualParentReference;
11203      if (!parentReference) return false;
11204      current = manualByKey.get(readString(parentReference, '').toLowerCase())
11205        || manualByKey.get(`manual:${readString(parentReference, '').toLowerCase()}`)
11206        || null;
11207    }
11208    return false;
11209  }
11210
11211  function finalizeManualLayer(items, depth = 0, parentKey = null, inheritedMegaGroup = 0) {
11212    const sourceItems = (items || []).filter(Boolean);
11213    // Top-level placement is already explicitly assembled as After Home,
11214    // After Collection, and After Native. Preserve that order. Child and
11215    // grandchild siblings use the numeric Order field.
11216    if (depth > 0) sourceItems.sort((a, b) => Number(a.order || 0) - Number(b.order || 0));
11217    return sourceItems.map(item => {
11218        item.depth = depth;
11219        item.parentKey = parentKey;
11220        const group = depth === 0 ? Math.round(Number(item.megaGroup) || 0) : inheritedMegaGroup;
11221        item.megaGroup = group >= 1 && group <= 4 ? group : 0;
11222        item.presentation = item.megaGroup ? 'mega' : 'standard';
11223        item.children = finalizeManualLayer(item.children || [], depth + 1, item.key, item.megaGroup);
11224        item.hasChildren = item.children.length > 0;
11225        delete item._manualRequestedDepth;
11226        delete item._manualParentReference;
11227        delete item._manualPlacement;
11228        return item;
11229      });
11230  }
11231
11232  function insertManualRoots(baseItems, manualRoots, customNavMode) {
11233    const afterHome = manualRoots.filter(item => item._manualPlacement === 'after-home').sort((a, b) => a.order - b.order);
11234    const afterCollection = manualRoots.filter(item => item._manualPlacement === 'after-collection').sort((a, b) => a.order - b.order);
11235    const afterNative = manualRoots.filter(item => item._manualPlacement === 'after-native').sort((a, b) => a.order - b.order);
11236
11237    // CUSTOMNAV is itself the collection-backed base. Preserve its authored order
11238    // and layer the panel items around that complete base rather than re-sorting it
11239    // by each row's SOURCE metadata.
11240    if (customNavMode) {
11241      const base = [...baseItems];
11242      const homeIndex = base.findIndex(item => readString(item.label, '').toLowerCase() === 'home' || item.canonicalPath === '/');
11243      if (afterHome.length) base.splice(homeIndex >= 0 ? homeIndex + 1 : 0, 0, ...afterHome);
11244      return [...base, ...afterCollection, ...afterNative];
11245    }
11246
11247    const firstNativeIndex = baseItems.findIndex(item => item.source === 'native');
11248    const collectionBlock = firstNativeIndex >= 0 ? baseItems.slice(0, firstNativeIndex) : [...baseItems];
11249    const nativeBlock = firstNativeIndex >= 0 ? baseItems.slice(firstNativeIndex) : [];
11250    const homeIndex = collectionBlock.findIndex(item => readString(item.label, '').toLowerCase() === 'home' || item.canonicalPath === '/');
11251    if (afterHome.length) collectionBlock.splice(homeIndex >= 0 ? homeIndex + 1 : 0, 0, ...afterHome);
11252    return [...collectionBlock, ...afterCollection, ...nativeBlock, ...afterNative];
11253  }
11254
11255  function layerManualNavigationItems(baseItems, customNavMode) {
11256    const manualItems = normalizeManualPanelItems();
11257    if (!manualItems.length) return baseItems;
11258
11259    const manualByKey = new Map(manualItems.map(item => [item.key.toLowerCase(), item]));
11260    const index = buildManualParentIndex(baseItems, manualItems);
11261    const roots = [];
11262
11263    manualItems.forEach(item => {
11264      const requestedDepth = item._manualRequestedDepth;
11265      if (requestedDepth === 0) {
11266        if (item._manualParentReference) {
11267          warn('MANUAL_TOP_PARENT_IGNORED', `Manual top-level item "${item.label}" has a Parent value; the Parent value was ignored.`);
11268        }
11269        roots.push(item);
11270        return;
11271      }
11272
11273      const parent = resolveManualParent(item._manualParentReference, index);
11274      if (!parent) {
11275        warn('MANUAL_PARENT_NOT_FOUND', `Manual navigation item "${item.label}" was skipped because parent "${item._manualParentReference}" was not found.`);
11276        item.invalid = true;
11277        return;
11278      }
11279      if (manualParentCreatesCycle(item, parent, manualByKey)) {
11280        warn('MANUAL_PARENT_CYCLE', `Manual navigation item "${item.label}" was skipped because its parent configuration creates a cycle.`);
11281        item.invalid = true;
11282        return;
11283      }
11284
11285      const requiredParentDepth = requestedDepth - 1;
11286      const parentDepth = Number(parent._manualRequestedDepth ?? parent.depth ?? 0);
11287      if (parentDepth !== requiredParentDepth) {
11288        warn('MANUAL_LEVEL_MISMATCH', `Manual navigation item "${item.label}" was skipped because its selected level does not immediately follow parent "${parent.label}".`);
11289        item.invalid = true;
11290        return;
11291      }
11292
11293      item.parentKey = parent.key;
11294      parent.children = parent.children || [];
11295      parent.children.push(item);
11296      parent.hasChildren = true;
11297    });
11298
11299    const validRoots = roots.filter(item =>
11299 !item.invalid);
11300    let merged = insertManualRoots(baseItems, validRoots, customNavMode);
11301    merged = finalizeManualLayer(merged, 0, null, 0);
11302
11303    // On live/preview pages an empty folder has no useful navigation action, so
11304    // omit it. In the editor, retain it as a disabled preview row so changing a
11305    // manual item from Page to Folder produces an immediate visible response and
11306    // clearly indicates that a child still needs to be assigned.
11307    function removeEmptyFolders(items) {
11308      return (items || []).filter(item => {
11309        item.children = removeEmptyFolders(item.children || []);
11310        item.hasChildren = item.children.length > 0;
11311        if (item.source === 'manual' && item.type === 'folder' && !item.hasChildren) {
11312          if (isEditorCanvas()) {
11313            item._manualEmptyFolderPreview = true;
11314            return true;
11315          }
11316          warn('MANUAL_EMPTY_FOLDER', `Manual folder "${item.label}" was skipped because it has no valid children.`);
11317          return false;
11318        }
11319        return true;
11320      });
11321    }
11322
11323    return removeEmptyFolders(merged);
11324  }
11325
11326  // ---------------------------------------------------------------------------
11327  // CUSTOMNAV mode and export-compatible normalization
11328  // ---------------------------------------------------------------------------
11329
11330  function normalizeCustomNavRows(rows) {
11331    const flat = [];
11332    const byKey = new Map();
11333    (rows || []).forEach((row, index) => {
11334      const showRaw = field(row, 'SHOW ITEM', 'show_item', 'SHOW', 'show');
11335      if (showRaw !== '' && showRaw !== undefined && showRaw !== null && !readBoolean(showRaw, true)) return;
11336
11337      const key = readString(field(row, 'ITEM KEY', 'item_key'), `custom:${index}`);
11338      if (byKey.has(key)) {
11339        warn('CUSTOMNAV_DUPLICATE_KEY', `CUSTOMNAV key "${key}" was used more than once; the later row was skipped.`);
11340        return;
11341      }
11342      const source = readString(field(row, 'SOURCE', 'source'), 'manual').toLowerCase();
11343      const type = readString(field(row, 'TYPE', 'type'), 'page').toLowerCase() === 'folder' ? 'folder' : 'page';
11344      const label = readString(field(row, 'PAGE NAME', 'LABEL', 'page_name', 'label'), '');
11345      if (!label) return;
11346      const rawUrl = type === 'folder' ? '' : readString(field(row, 'URL', 'url'), '');
11347      const url = source === 'manual' && rawUrl ? buildRuntimeFallbackUrl(rawUrl) : rawUrl;
11348      const item = {
11349        key,
11350        parentKey: readString(field(row, 'PARENT KEY', 'parent_key'), '') || null,
11351        source: ['native', 'collection', 'manual'].includes(source) ? source : 'manual',
11352        type,
11353        presentation: readString(field(row, 'PRESENTATION', 'presentation'), 'standard').toLowerCase() === 'mega' ? 'mega' : 'standard',
11354        megaGroup: Math.round(readNumber(field(row, 'MEGA GROUP', 'mega_group'), 0, 0, 4)),
11355        depth: Math.round(readNumber(field(row, 'DEPTH', 'depth'), 0, 0, 2)),
11356        order: Math.round(readNumber(field(row, 'ORDER', 'order'), index, 0, 100000)),
11357        label,
11358        slug: readString(field(row, 'SLUG', 'slug'), slugify(label)),
11359        canonicalPath: readString(field(row, 'CANONICAL PATH', 'canonical_path'), canonicalPath(url)),
11360        url,
11361        target: readString(field(row, 'TARGET', 'target'), '_self'),
11362        isExternal: readBoolean(field(row, 'IS EXTERNAL', 'is_external'), isExternalUrl(url)),
11363        hasChildren: false,
11364        children: []
11365      };
11366      flat.push(item);
11367      byKey.set(key, item);
11368    });
11369
11370    const roots = [];
11371    flat.sort((a, b) => a.order - b.order).forEach(item => {
11372      if (!item.parentKey) {
11373        roots.push(item);
11374        return;
11375      }
11376      const parent = byKey.get(item.parentKey);
11377      if (!parent || parent === item) {
11378        warn('CUSTOMNAV_PARENT_INVALID', `CUSTOMNAV item "${item.label}" was skipped because its parent is invalid.`);
11379        item.invalid = true;
11380        return;
11381      }
11382      parent.children.push(item);
11383      parent.hasChildren = true;
11384    });
11385
11386    function normalizeDepth(items, depth = 0, parentKey = null) {
11387      return items
11388        .filter(item =>
11388 !item.invalid)
11389        .sort((a, b) => a.order - b.order)
11390        .map(item => {
11391          if (depth > MAX_DEPTH) {
11392            warn('CUSTOMNAV_DEPTH_INVALID', `CUSTOMNAV item "${item.label}" was skipped because navigation supports three content levels.`);
11393            return null;
11394          }
11395          item.depth = depth;
11396          item.parentKey = parentKey;
11397          item.children = normalizeDepth(item.children || [], depth + 1, item.key);
11398          item.hasChildren = item.children.length > 0;
11399          return item;
11400        })
11401        .filter(Boolean);
11402    }
11403
11404    return normalizeDepth(roots);
11405  }
11406
11407  // Re-resolve source-backed CUSTOMNAV links so preview/live URLs do not become stale.
11408  function refreshCustomUrls(items, categoryRows, coreRows) {
11409    const native = parseNativeNavigation();
11410    const categoryIndex = buildDynamicIndex(categoryRows || []);
11411    const coreIndex = buildDynamicIndex(coreRows || []);
11412    function walk(item) {
11413      const copy = { ...item, children: item.children.map(walk) };
11414      if (item.source === 'native' && item.canonicalPath) {
11415        const match = findTreeItem(native, candidate => candidate.canonicalPath === item.canonicalPath);
11416        if (match && match.url) copy.url = match.url;
11417      }
11418      if (item.source === 'collection' && item.slug) {
11419        const isHome = item.depth === 0 && (
11420          readString(item.label, '').toLowerCase() === 'home' ||
11421          canonicalPathWithoutHash(item.canonicalPath || item.url) === '/'
11422        );
11423
11424        if (isHome) {
11425          copy.url = item.url || buildRuntimeFallbackUrl('/');
11426          copy.canonicalPath = '/';
11427        } else {
11428          const expectsCategory = usesCategorySource(item.depth, item.children || []);
11429          const index = expectsCategory ? categoryIndex : coreIndex;
11430          const prefix = expectsCategory ? CATEGORY_URL_PREFIX : CHILD_URL_PREFIX;
11431          const match = findDynamicMatch(index, item.label || item.slug, prefix);
11432          copy.url = match
11433            ? match.url
11434            : buildConfiguredDynamicUrl(item.label || item.slug, prefix);
11435          copy.canonicalPath = canonicalPath(copy.url);
11436        }
11437      }
11438      copy.hasChildren = copy.children.length > 0;
11439      return copy;
11440    }
11441    return items.map(walk);
11442  }
11443
11444  function findTreeItem(items, predicate) {
11445    for (const item of items || []) {
11446      if (predicate(item)) return item;
11447      const nested = findTreeItem(item.children, predicate);
11448      if (nested) return nested;
11449    }
11450    return null;
11451  }
11452
11453
11454  // Build a guaranteed fallback from 06 NAV MASTER alone. Structural items
11455  // with children use the Category prefix; leaf items use the Core prefix, even
11456  // when they are top-level or assigned to a Mega group. Category/Core
11457  // collections are still preferred for exact page_item_url values, but a
11458  // temporarily unavailable validation collection no longer blanks the nav.
11459  function normalizeNavMasterFallback(navRows) {
11460    const tree = buildCollectionTree(navRows || []);
11461
11462    function walk(node, ancestors, inheritedMegaGroup) {
11463      if (!node || node.invalid || node.depth > MAX_DEPTH) return null;
11464
11465      const megaGroup = node.depth === 0
11466        ? parseMegaGroup(node.topMegaValue)
11467        : inheritedMegaGroup;
11468
11469      const children = node.children
11470        .map(child => walk(child, [...ancestors, node], megaGroup))
11471        .filter(Boolean);
11472
11473      const slug = slugify(node.label);
11474      const isHome = node.depth === 0 && node.label.toLowerCase() === 'home';
11475      const expectsCategory = usesCategorySource(node.depth, children);
11476      const prefix = expectsCategory ? CATEGORY_URL_PREFIX : CHILD_URL_PREFIX;
11477      const configuredPageLink = node.pageLink ? buildRuntimeFallbackUrl(node.pageLink) : '';
11478      const url = isHome
11479        ? (configuredPageLink || buildRuntimeFallbackUrl('/'))
11480        : buildConfiguredDynamicUrl(node.label, prefix);
11481      const hierarchySlugs = [...ancestors.map(item => slugify(item.label)), slug];
11482
11483      return {
11484        key: `collection:${hierarchySlugs.join('/')}`,
11485        parentKey: ancestors.length
11486          ? `collection:${ancestors.map(item => slugify(item.label)).join('/')}`
11487          : null,
11488        source: 'collection',
11489        type: 'page',
11490        presentation: megaGroup ? 'mega' : 'standard',
11491        megaGroup,
11492        depth: node.depth,
11493        order: node.order,
11494        label: node.label,
11495        slug,
11496        canonicalPath: canonicalPath(url),
11497        url,
11498        target: '_self',
11499        isExternal: false,
11500        hasChildren: children.length > 0,
11501        children
11502      };
11503    }
11504
11505    return tree.map(rootNode =>
11505 walk(rootNode, [], 0)).filter(Boolean);
11506  }
11507
11508  // ---------------------------------------------------------------------------
11509  // Group top-level mega families under one synthetic trigger per Mega group
11510  // ---------------------------------------------------------------------------
11511
11512  function shiftMegaFamilyDepth(item, parentKey, depthOffset = 1) {
11513    const nextDepth = Math.max(0, Number(item.depth) || 0) + depthOffset;
11514    const shifted = {
11515      ...item,
11516      parentKey,
11517      depth: nextDepth,
11518      presentation: 'mega',
11519      children: []
11520    };
11521
11522    shifted.children = (item.children || []).map(child =>
11523      shiftMegaFamilyDepth(child, shifted.key, depthOffset)
11524    );
11525    shifted.hasChildren = shifted.children.length > 0;
11526    return shifted;
11527  }
11528
11529  function groupMegaNavigationItems(items) {
11530    const sourceItems = Array.isArray(items) ? items : [];
11531    const groupedFamilies = new Map();
11532    const firstIndexes = new Map();
11533
11534    sourceItems.forEach((item, index) => {
11535      const groupNumber = Math.round(Number(item && item.megaGroup) || 0);
11536      const isSyntheticGroup = readString(item && item.key, '').startsWith('mega-group:');
11537      if (groupNumber < 1 || groupNumber > 4 || isSyntheticGroup) return;
11538
11539      if (!groupedFamilies.has(groupNumber)) {
11540        groupedFamilies.set(groupNumber, []);
11541        firstIndexes.set(groupNumber, index);
11542      }
11543      groupedFamilies.get(groupNumber).push(item);
11544    });
11545
11546    if (!groupedFamilies.size) return sourceItems;
11547
11548    const output = [];
11549    const insertedGroups = new Set();
11550
11551    sourceItems.forEach((item, index) => {
11552      const groupNumber = Math.round(Number(item && item.megaGroup) || 0);
11553      const isSyntheticGroup = readString(item && item.key, '').startsWith('mega-group:');
11554
11555      if (groupNumber >= 1 && groupNumber <= 4 && !isSyntheticGroup) {
11556        if (insertedGroups.has(groupNumber)) return;
11557        if (firstIndexes.get(groupNumber) !== index) return;
11558
11559        const key = `mega-group:${groupNumber}`;
11560        const megaCfg = getMegaConfig(groupNumber);
11561        const sourceFamilies = [...(groupedFamilies.get(groupNumber) || [])];
11562        if (megaCfg.alphabetize) {
11563          sourceFamilies.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }));
11564        }
11565        const families = sourceFamilies.map(family =>
11566          shiftMegaFamilyDepth(family, key, 1)
11567        );
11568
11569        output.push({
11570          key,
11571          parentKey: null,
11572          source: 'generated',
11573          type: 'folder',
11574          presentation: 'mega',
11575          megaGroup: groupNumber,
11576          depth: 0,
11577          order: index,
11578          label: megaCfg.label,
11579          slug: slugify(megaCfg.label),
11580          canonicalPath: '',
11581          url: '',
11582          target: '_self',
11583          isExternal: false,
11584          hasChildren: families.length > 0,
11585          children: families
11586        });
11587        insertedGroups.add(groupNumber);
11588        return;
11589      }
11590
11591      output.push(item);
11592    });
11593
11594    return output;
11595  }
11596
11597  // ---------------------------------------------------------------------------
11598  // Build and publish the final shared model
11599  // ---------------------------------------------------------------------------
11600
11601  async function buildNavigationModel() {
11602    local.latestBuildWarnings = [];
11603    local.slugValidationWarnings = [];
11604    const fetched = await fetchRequiredCollections();
11605    const collections = fetched.collections;
11606    let hadError = fetched.hadError;
11607    let model;
11608
11609    if (readBoolean(configValueFrom(cfg, 'useCustomNavOnly'), false)) {
11610      model = normalizeCustomNavRows(collections.CUSTOMNAV || []);
11611      // Optional URL refresh data is fetched when any CUSTOMNAV item at any
11612      // hierarchy level is backed by a native or collection source.
11613      const needsRefresh = Boolean(findTreeItem(model, item => item.source !== 'manual'));
11614      if (needsRefresh) {
11615        const secondary = await Promise.allSettled([fetchCollection('02 CATEGORY'), fetchCollection('03 CORE')]);
11616        const categories = secondary[0].status === 'fulfilled' ? secondary[0].value : [];
11617        const cores = secondary[1].status === 'fulfilled' ? secondary[1].value : [];
11618        if (secondary.some(result => result.status === 'rejected')) {
11619          hadError = true;
11620          warn('CUSTOMNAV_URL_REFRESH_FAILED', 'One or more source collections could not be loaded while refreshing CUSTOMNAV URLs.');
11621        }
11622        model = refreshCustomUrls(model, categories, cores);
11623      }
11624    } else {
11625      const navMasterRows = collections['06 NAV MASTER'] || [];
11626      let collectionItems;
11627
11628      if (hadError && navMasterRows.length) {
11629        // Do not report missing slugs when one of the source collections failed
11630        // to load; absence cannot be confirmed until both collections are available.
11631        local.slugValidationWarnings = [];
11632        warn(
11633          'COLLECTION_VALIDATION_UNAVAILABLE',
11634          'One or more validation collections could not be loaded. Rendering NAV MASTER with constructed URLs for this build.'
11635        );
11636        collectionItems = normalizeNavMasterFallback(navMasterRows);
11637      } else {
11638        collectionItems = normalizeCollectionNavigation(
11639          navMasterRows,
11640          collections['02 CATEGORY'] || [],
11641          collections['03 CORE'] || []
11642        );
11643
11644        if (!collectionItems.length && navMasterRows.length) {
11645          warn(
11646            'VALIDATION_FALLBACK',
11647            'Category/Core validation produced no items. Rendering NAV MASTER with constructed URLs instead.'
11648          );
11649          collectionItems = normalizeNavMasterFallback(navMasterRows);
11650        }
11651      }
11652
11653      const nativeItems = normalizeConfiguredNativeNavigation();
11654      model = [...collectionItems, ...nativeItems];
11655    }
11656
11657    // Panel-created manual items are additive in both modes. In CUSTOMNAV-only
11658    // mode they are layered on top of the CUSTOMNAV collection; in normal mode
11659    // they are layered on top of Collection + Native navigation.
11660    model = layerManualNavigationItems(
11661      model,
11662      readBoolean(configValueFrom(cfg, 'useCustomNavOnly'), false)
11663    );
11664
11665    // A Mega value assigns a top-level family to a shared group. The visible
11666    // trigger is the configured Mega label, while assigned parents become
11667    // families inside that single dropdown. Yes and Mega1 both map to group 1.
11668    model = groupMegaNavigationItems(model);
11669
11670    return {
11671      model,
11672      hadError,
11673      warnings: local.latestBuildWarnings,
11674      slugWarnings: local.slugValidationWarnings
11675    };
11676  }
11677
11678  function isCurrentInstance() {
11679    const ownerKey = widgetMode === 'full' ? 'ownerId' : 'mirrorOwnerId';
11680    return !local.cleaned
11681      && root.dataset.nmwInstance === instanceId
11682      && window.NMW[ownerKey] === instanceId;
11683  }
11684
11685  async function runRebuild() {
11686    if (!isCurrentInstance()) return;
11687    if (local.rebuildRunning) {
11688      local.pendingRebuild = true;
11689      return;
11690    }
11691
11692    local.rebuildRunning = true;
11693    window.NMW.loading = true;
11694    updateRootStatus();
11695    dispatchStateChanged();
11696
11697    try {
11698      const result = await buildNavigationModel();
11699
11700      // A newer Duda panel execution may have replaced this instance while the
11701      // asynchronous collection requests were running. Never allow a stale
11702      // build to render or alter shared state.
11703      if (!isCurrentInstance()) return;
11704
11705      const nextData = result.model || [];
11706      const previousSignature = stableStringify(window.NMW.data || []);
11707      const nextSignature = stableStringify(nextData);
11708      const dataChanged = previousSignature !== nextSignature;
11709
11710      const nextFullConfig = cloneSerializable(cfg) || {};
11711      let nextConfigSignature = '';
11712      try { nextConfigSignature = stableStringify(nextFullConfig); }
11713      catch (error) { nextConfigSignature = String(Date.now()); }
11714      const presentationChanged = window.NMW.fullConfigSignature !== nextConfigSignature;
11715
11716      window.NMW.error = result.hadError;
11717      window.NMW.warnings = result.warnings;
11718      window.NMW.slugWarnings = Array.isArray(result.slugWarnings) ? result.slugWarnings : [];
11719      window.NMW.ready = true;
11720      window.NMW.data = nextData;
11721      window.NMW.fullConfig = nextFullConfig;
11722      window.NMW.fullConfigSignature = nextConfigSignature;
11723
11724      // Always render the current Full instance. Duda Design/Content Panel
11725      // changes such as Mega columns, expanded/collapsed families, max width,
11726      // hover behavior, or tool visibility may not alter the normalized model.
11727      // Skipping render when data is unchanged is what previously caused panel
11728      // changes to appear stuck until a refresh. This also replaces any
11729      // NAV-MASTER-only provisional render with the final validated model.
11730      root.removeAttribute('data-nmw-provisional');
11731      renderNavigation(nextData, 'full');
11732
11733      if (dataChanged || presentationChanged) {
11734        window.NMW.version += 1;
11735        if (presentationChanged) window.NMW.presentationVersion += 1;
11736        dispatchDataChanged();
11737      }
11738    } catch (error) {
11739      if (!isCurrentInstance()) return;
11740      window.NMW.error = true;
11741      window.NMW.ready = true;
11742      if (!Array.isArray(window.NMW.data)) window.NMW.data = [];
11743      warn('BUILD_FAILED', 'Navigation build failed.', error);
11744      if (!window.NMW.data.length && renderHost) {
11745        renderHost.innerHTML = `<div class="nmw-empty">Navigation build failed: ${escapeHtml(error && error.message ? error.message : 
11745String(error))}</div>`;
11746      }
11747    } finally {
11748      local.rebuildRunning = false;
11749
11750      // Only the currently registered instance may clear the shared loading
11751      // state or launch a queued rebuild. This prevents a replaced instance
11752      // from leaving the editor stuck on Loading navigation….
11753      if (isCurrentInstance()) {
11754        window.NMW.loading = false;
11755        updateRootStatus();
11756        updateTools();
11757        dispatchStateChanged();
11758        if (local.pendingRebuild) {
11759          local.pendingRebuild = false;
11760          runRebuild();
11761        }
11762      }
11763    }
11764  }
11765
11766  function scheduleRebuild(immediate = false) {
11767    if (local.cleaned) return;
11768    clearTimeout(local.rebuildTimer);
11769    if (immediate) {
11770      runRebuild();
11771      return;
11772    }
11773    local.rebuildTimer = setTimeout(runRebuild, REBUILD_DEBOUNCE);
11774  }
11775
11776  // ---------------------------------------------------------------------------
11777  // Renderers
11778  // ---------------------------------------------------------------------------
11779
11780  function itemClasses(item) {
11781    return [
11782      'nmw-item',
11783      `nmw-source-${item.source}`,
11784      `nmw-type-${item.type}`,
11785      `nmw-presentation-${item.presentation}`,
11786      `nmw-depth-${item.depth}`,
11787      item.megaGroup ? `nmw-mega-group-${item.megaGroup}` : ''
11788    ].filter(Boolean).join(' ');
11789  }
11790
11791  function linkHtml(item, mode, extraClass = '') {
11792    const editorDisabled = isEditorCanvas() && !isSiteCommentsCustomerMode();
11793    const inheritedClasses = readString(item.linkClassName, '')
11794      .split(/\s+/)
11795      .filter(className => className && className !== 'nmw-link')
11796      .join(' ');
11797    const attributes = item.linkAttributes && typeof item.linkAttributes === 'object'
11798      ? Object.entries(item.linkAttributes).map(([name, value]) => {
11799          const safeName = String(name).trim();
11800          if (!/^(?:data-[\w:-]+|aria-[\w:-]+|rel|download|title|name|type|onclick)$/i.test(safeName)) return '';
11801          return value === '' ? ` ${safeName}` : ` ${safeName}="${escapeHtml(value)}"`;
11802        }).join('')
11803      : '';
11804    const className = ['nmw-link', extraClass, inheritedClasses].filter(Boolean).join(' ');
11805    return `<a class="${escapeHtml(className)}" href="${escapeHtml(item.url)}" target="${escapeHtml(item.target || '_self')}" data-nmw-key="${escapeHtml(item.key)}" data-nmw-path="${escapeHtml(item.canonicalPath)}"${attributes}${editorDisabled ? ' data-nmw-editor-disabled="true"' : ''}><span class="nmw-label">${escapeHtml(item.label)}</span></a>`;
11806  }
11807
11808  function toggleHtml(item, submenuId, folder = false, megaFamily = false) {
11809    const label = folder ? item.label : `${item.label} submenu`;
11810    const className = folder ? 'nmw-folder-button' : `nmw-toggle${megaFamily ? ' nmw-mega-family-toggle' : ''}`;
11811    return `<button class="${className}" type="button" data-nmw-toggle data-nmw-key="${escapeHtml(item.key)}" aria-label="Toggle ${escapeHtml(label)}" aria-expanded="false" aria-controls="${escapeHtml(submenuId)}"><span class="nmw-label">${folder ? escapeHtml(item.label) : ''}</span><span class="nmw-toggle-icon" aria-hidden="true"></span></button>`;
11812  }
11813
11814  function emptyFolderPreviewHtml(item) {
11815    return `<button class="nmw-folder-button nmw-empty-folder-preview" type="button" disabled aria-disabled="true" title="Add a valid child item to this folder"><span class="nmw-label">${escapeHtml(item.label)}</span><span class="nmw-empty-folder-note" aria-hidden="true">Add child</span></button>`;
11816  }
11817
11818  function renderStandardItem(item, mode, idState) {
11819    const submenuId = item.hasChildren ? `nmw-${mode}-submenu-${++idState.value}` : '';
11820    let row;
11821    if (item.type === 'folder' && item._manualEmptyFolderPreview) {
11822      row = `<div class="nmw-row">${emptyFolderPreviewHtml(item)}</div>`;
11823    } else if (item.type === 'folder') {
11824      row = `<div class="nmw-row">${toggleHtml(item, submenuId, true)}</div>`;
11825    } else if (item.hasChildren) {
11826      row = `<div class="nmw-row">${linkHtml(item, mode)}${toggleHtml(item, submenuId, false)}</div>`;
11827    } else {
11828      row = `<div class="nmw-row">${linkHtml(item, mode)}</div>`;
11829    }
11830
11831    const children = item.hasChildren
11832      ? `<ul class="nmw-submenu nmw-standard-submenu nmw-depth-${item.depth + 1}" id="${submenuId}" data-nmw-parent-key="${escapeHtml(item.key)}" hidden>${item.children.map(child => renderStandardItem(child, mode, idState)).join('')}</ul>`
11833      : '';
11834    return `<li class="${itemClasses(item)}" data-nmw-item data-nmw-key="${escapeHtml(item.key)}">${row}${children}</li>`;
11835  }
11836
11837  function familySize(item) {
11838    return 1 + (item.children || []).reduce((sum, child) => sum + familySize(child), 0);
11839  }
11840
11841  function distributeFamilies(families, columns) {
11842    const output = Array.from({ length: columns }, () => ({ weight: 0, items: [] }));
11843    families.forEach(family => {
11844      let targetIndex = 0;
11845      for (let index = 1; index < output.length; index += 1) {
11846        if (output[index].weight < output[targetIndex].weight) targetIndex = index;
11847      }
11848      output[targetIndex].items.push(family);
11849      output[targetIndex].weight += familySize(family);
11850    });
11851    return output.map(column => column.items);
11852  }
11853
11854  function renderMegaGrandchild(item, mode) {
11855    const content = item.type === 'folder'
11856      ? `<span class="nmw-mega-grandchild-label"><span class="nmw-label">${escapeHtml(item.label)}</span></span>`
11857      : linkHtml(item, mode, 'nmw-mega-grandchild-link');
11858    return `<li class="${itemClasses(item)} nmw-mega-grandchild" data-nmw-item data-nmw-key="${escapeHtml(item.key)}"><div class="nmw-row">${content}</div></li>`;
11859  }
11860
11861  function renderMegaChild(item, mode) {
11862    const grandchildren = item.children || [];
11863    const content = item.type === 'folder'
11864      ? `<span class="nmw-mega-child-label"><span class="nmw-label">${escapeHtml(item.label)}</span></span>`
11865      : linkHtml(item, mode, 'nmw-mega-child-link');
11866    const nested = grandchildren.length
11867      ? `<ul class="nmw-mega-grandchildren" data-nmw-parent-key="${escapeHtml(item.key)}">${grandchildren.map(child => renderMegaGrandchild(child, mode)).join('')}</ul>`
11868      : '';
11869    return `<li class="${itemClasses(item)} nmw-mega-child${grandchildren.length ? ' nmw-has-grandchildren' : ''}" data-nmw-item data-nmw-key="${escapeHtml(item.key)}"><div class="nmw-row">${content}</div>${nested}</li>`;
11870  }
11871
11872  function renderMegaFamily(family, mode, idState, expandAll) {
11873    const hasChildren = family.children.length > 0;
11874    const familyId = hasChildren ? `nmw-${mode}-mega-family-${++idState.value}` : '';
11875    const familyLink = family.type === 'page' ? linkHtml(family, mode, 'nmw-mega-family-link') : '';
11876    let row;
11877
11878    if (!hasChildren || expandAll) {
11879      row = `<div class="nmw-row">${family.type === 'folder' ? `<span class="nmw-folder-label"><span class="nmw-label">${escapeHtml(family.label)}</span></span>` : familyLink}</div>`;
11880    } else if (family.type === 'folder') {
11881      row = `<div class="nmw-row">${toggleHtml(family, familyId, true, true)}</div>`;
11882    } else {
11883      row = `<div class="nmw-row">${familyLink}${toggleHtml(family, familyId, false, true)}</div>`;
11884    }
11885
11886    const children = hasChildren
11887      ? `<ul class="nmw-mega-family-children${expandAll ? ' nmw-open' : ''}" id="${familyId}" data-nmw-parent-key="${escapeHtml(family.key)}"${expandAll ? '' : ' hidden'}>${family.children.map(child => renderMegaChild(child, mode)).join('')}</ul>`
11888      : '';
11889
11890    return `<div class="nmw-mega-family${expandAll ? ' nmw-open' : ''}" data-nmw-item data-nmw-key="${escapeHtml(family.key)}">${row}${children}</div>`;
11891  }
11892
11893  function renderMegaItem(item, mode, idState) {
11894    const submenuId = `nmw-${mode}-mega-${++idState.value}`;
11895    const row = item.type === 'folder'
11896      ? `<div class="nmw-row">${toggleHtml(item, submenuId, true)}</div>`
11897      : `<div class="nmw-row">${linkHtml(item, mode)}${toggleHtml(item, submenuId, false)}</div>`;
11898    const megaCfg = getMegaConfig(item.megaGroup || 1);
11899
11900    let content;
11901    if (mode === 'mirror') {
11902      // Mirror always uses a single vertical list — layout setting does not apply.
11903      content = `<div class="nmw-mega-vertical">${item.children.map(family => renderMegaFamily(family, mode, idState, megaCfg.expandAll)).join('')}</div>`;
11904    } else if (megaCfg.layout === 'masonry') {
11905      // Masonry: shortest-column balancing. distributeFamilies() assigns each
11906      // family to the column with the lowest accumulated navigation weight so
11907      // differently sized families remain visually balanced across columns.
11908      // Each resulting group is wrapped in a .nmw-mega-column flex container.
11909      const columns = distributeFamilies(item.children, megaCfg.columns);
11910      content = `<div class="nmw-mega-inner" style="--nmw-mega-max-width:${megaCfg.maxWidth}px;--nmw-mega-columns:${megaCfg.columns}" data-nmw-mega-layout="masonry"><div class="nmw-mega-columns">${columns.map(column => `<div class="nmw-mega-column">${column.map(family => renderMegaFamily(family, mode, idState, megaCfg.expandAll)).join('')}</div>`).join('')}</div></div>`;
11911    } else {
11912      // Grid: families are placed directly inside the CSS Grid container in
11913      // their current (or alphabetized) source order. CSS grid-auto-flow: row
11914      // fills each row from left to right before beginning the next row.
11915      // No column wrappers are emitted — each .nmw-mega-family is a direct
11916      // grid child and occupies exactly one grid cell.
11917      const families = item.children
11918        .map(family => renderMegaFamily(family, mode, idState, megaCfg.expandAll))
11919        .join('');
11920      content = `<div class="nmw-mega-inner" style="--nmw-mega-max-width:${megaCfg.maxWidth}px;--nmw-mega-columns:${megaCfg.columns}" data-nmw-mega-layout="grid"><div class="nmw-mega-columns">${families}</div></div>`;
11921    }
11922
11923    return `<li class="${itemClasses(item)}" data-nmw-item data-nmw-key="${escapeHtml(item.key)}" data-nmw-mega-label="${escapeHtml(megaCfg.label)}">${row}<div class="nmw-submenu nmw-mega-menu" id="${submenuId}" data-nmw-parent-key="${escapeHtml(item.key)}" data-nmw-mega-group="${item.megaGroup || 1}" data-nmw-mega-label="${escapeHtml(megaCfg.label)}" aria-label="${escapeHtml(megaCfg.label)}" hidden>${content}</div></li>`;
11924  }
11925
11926  function renderNavigation(items, mode) {
11927    if (!renderHost) return;
11928    applyDesignSettings(mode);
11929    updateMegaIndicators(items);
11930    if (!Array.isArray(items) || !items.length) {
11931      renderHost.innerHTML = '<div class="nmw-empty">Navigation could not be built. Check collection data and native navigation configuration.</div>';
11932      updateTools();
11933      return;
11934    }
11935    const idState = { value: 0 };
11936    const navId = mode === 'full' ? 'nmw-full-main-nav' : `nmw-mirror-main-nav-${instanceId}`;
11937    const list = items.map(item => item.presentation === 'mega'
11938      ? renderMegaItem(item, mode, idState)
11939      : renderStandardItem(item, mode, idState)).join('');
11940    if (loadingHost && loadingHost !== renderHost) loadingHost.hidden = true;
11941    renderHost.innerHTML = `<nav class="nmw-nav nmw-topnav" id="${escapeHtml(navId)}" aria-label="Main navigation"><ul class="nmw-list nmw-top-list">${list}</ul></nav>`;
11942    closeAllMenus(root, false);
11943    refreshActiveStates(root);
11944    updateTools();
11945  }
11946
11947  // ---------------------------------------------------------------------------
11948  // Menu opening, closing, animation, hover, and delegated interaction
11949  // ---------------------------------------------------------------------------
11950
11951  function findControlledSubmenu(toggle) {
11952    const id = toggle?.getAttribute('aria-controls');
11953    return id ? root.querySelector(`#${CSS.escape(id)}`) : null;
11954  }
11955
11956  function getTransitionMs(elementNode) {
11957    if (!elementNode || getBehaviorConfig(widgetMode).animationsEnabled === false) return 0;
11958    const style = window.getComputedStyle(elementNode);
11959    if (style.transitionProperty === 'none') return 0;
11960    const durations = style.transitionDuration.split(',').map(value => parseFloat(value) * (value.includes('ms') ? 1 : 1000));
11961    const delays = style.transitionDelay.split(',').map(value => parseFloat(value) * (value.includes('ms') ? 1 : 1000));
11962    const measured = Math.max(0, ...durations.map((duration, index) => duration + (delays[index] || delays[0] || 0)));
11963    return measured > 0 ? measured : 0;
11964  }
11965
11966  function cancelCloseJob(submenu) {
11967    const job = local.closeJobs.get(submenu);
11968    if (!job) return;
11969    clearTimeout(job.timer);
11970    local.closeTimers.delete(job.timer);
11971    submenu.removeEventListener('transitionend', job.finish);
11972    local.closeJobs.delete(submenu);
11973  }
11974
11975  function updateFlyoutDirection(submenu) {
11976    if (!submenu || widgetMode === 'mirror' || !submenu.classList.contains('nmw-standard-submenu')) return;
11977    const nested = submenu.parentElement?.closest?.('.nmw-standard-submenu');
11978    if (!nested) {
11979      submenu.classList.remove('nmw-flip-left');
11980      return;
11981    }
11982
11983    submenu.classList.remove('nmw-flip-left');
11984    const rect = submenu.getBoundingClientRect();
11985    const viewportWidth = document.documentElement.clientWidth || window.innerWidth || 0;
11986    if (rect.right > viewportWidth - 8) submenu.classList.add('nmw-flip-left');
11987  }
11988
11989  function openMenu(item, toggle, submenu) {
11990    if (!item || !toggle || !submenu) return;
11991    cancelCloseJob(submenu);
11992    closeSiblingMenus(item);
11993    item.classList.remove('nmw-closing');
11994    submenu.classList.remove('nmw-closing');
11995    submenu.hidden = false;
11996    requestAnimationFrame(() => {
11997      if (local.cleaned || !document.documentElement.contains(submenu)) return;
11998      updateFlyoutDirection(submenu);
11999      item.classList.add('nmw-open');
12000      submenu.classList.add('nmw-open');
12001      toggle.setAttribute('aria-expanded', 'true');
12002    });
12003  }
12004
12005  function closeMenu(item, toggle, submenu, returnFocus = false) {
12006    if (!item || !toggle || !submenu || submenu.hidden) return;
12007    cancelCloseJob(submenu);
12008    item.classList.remove('nmw-open');
12009    submenu.classList.remove('nmw-open');
12010    item.classList.add('nmw-closing');
12011    submenu.classList.add('nmw-closing');
12012    toggle.setAttribute('aria-expanded', 'false');
12013
12014    let jobTimer = null;
12015    const finish = event => {
12016      if (event && event.target !== submenu) return;
12017      submenu.hidden = true;
12018      item.classList.remove('nmw-closing');
12019      submenu.classList.remove('nmw-closing');
12020      submenu.removeEventListener('transitionend', finish);
12021      local.closeJobs.delete(submenu);
12022      if (jobTimer) local.closeTimers.delete(jobTimer);
12023      if (returnFocus) toggle.focus();
12024    };
12025    const duration = getTransitionMs(submenu);
12026    if (!duration) {
12027      finish();
12028      return;
12029    }
12030    submenu.addEventListener('transitionend', finish);
12031    jobTimer = setTimeout(finish, duration + 50);
12032    local.closeTimers.add(jobTimer);
12033    local.closeJobs.set(submenu, { timer: jobTimer, finish });
12034  }
12035
12036  function closeSiblingMenus(item) {
12037    let siblings = [];
12038
12039    if (item.classList.contains('nmw-mega-family')) {
12040      const megaMenu = item.closest('.nmw-mega-menu');
12041      siblings = megaMenu
12042        ? Array.from(megaMenu.querySelectorAll('.nmw-mega-family[data-nmw-item]'))
12043        : [];
12044    } else {
12045      const parent = item.parentElement;
12046      siblings = parent ? Array.from(parent.children).filter(node => node.matches?.('[data-nmw-item]')) : [];
12047    }
12048
12049    siblings.forEach(sibling => {
12050      if (sibling === item) return;
12051      const toggle = sibling.querySelector(':scope > .nmw-row [data-nmw-toggle]');
12052      const submenu = toggle ? findControlledSubmenu(toggle) : null;
12053      if (toggle && submenu) closeMenu(sibling, toggle, submenu, false);
12054    });
12055  }
12056
12057  function closeAllMenus(scope, returnFocus = false) {
12058    scope.querySelectorAll('[data-nmw-toggle][aria-expanded="true"]').forEach(toggle => {
12059      const item = toggle.closest('[data-nmw-item]');
12060      const submenu = findControlledSubmenu(toggle);
12061      if (item && submenu) closeMenu(item, toggle, submenu, returnFocus);
12062    });
12063    local.hoverTimers.forEach(timer => clearTimeout(timer));
12064    local.hoverTimers.clear();
12065  }
12066
12067  function toggleMenu(toggle) {
12068    const item = toggle.closest('[data-nmw-item]');
12069    const submenu = findControlledSubmenu(toggle);
12070    if (!item || !submenu) return;
12071    const isOpen = toggle.getAttribute('aria-expanded') === 'true';
12072    if (isOpen) closeMenu(item, toggle, submenu, false);
12073    else openMenu(item, toggle, submenu);
12074  }
12075
12076  function nearestOpenToggle(target) {
12077    let node = target && target.closest ? target.closest('[data-nmw-item], .nmw-submenu, .nmw-mega-family-children') : null;
12078
12079    while (node && root.contains(node)) {
12080      if (node.matches?.('[data-nmw-item]')) {
12081        const ownToggle = node.querySelector(':scope > .nmw-row [data-nmw-toggle][aria-expanded="true"]');
12082        if (ownToggle) return ownToggle;
12083      }
12084
12085      const submenu = node.matches?.('.nmw-submenu, .nmw-mega-family-children')
12086        ? node
12087        : node.closest?.('.nmw-submenu, .nmw-mega-family-children');
12088      if (!submenu) return null;
12089
12090      const parentKey = submenu.getAttribute('data-nmw-parent-key');
12091      if (parentKey) {
12092        const parentToggle = root.querySelector(`[data-nmw-toggle][data-nmw-key="${CSS.escape(parentKey)}"][aria-expanded="true"]`);
12093        if (parentToggle) return parentToggle;
12094      }
12095
12096      node = submenu.parentElement?.closest?.('[data-nmw-item], .nmw-submenu, .nmw-mega-family-children') || null;
12097    }
12098
12099    return null;
12100  }
12101
12102  function attachDelegatedInteractions() {
12103    if (local.listenersAttached) return;
12104    local.listenersAttached = true;
12105
12106    listen(root, 'click', event => {
12107      const toggle = event.target.closest('[data-nmw-toggle]');
12108      if (toggle && root.contains(toggle)) {
12109        event.preventDefault();
12110        toggleMenu(toggle);
12111        return;
12112      }
12113
12114      const link = event.target.closest('a.nmw-link');
12115      if (!link || !root.contains(link)) return;
12116
12117      /*
12118        Recheck Site Comments at click time.
12119
12120        The navigation may have rendered before Duda finished identifying
12121        the visitor as a customer. In that case the link can still contain
12122        a stale data-nmw-editor-disabled attribute even though the console
12123        later reports "Customer Detected".
12124      */
12125      if (isSiteCommentsCustomerMode()) {
12126        /*
12127          Remove stale editor-disabled markers from every rendered link so
12128          the Full and Mirror navigation instances immediately reflect the
12129          confirmed customer state.
12130        */
12131        root
12132          .querySelectorAll(
12133            'a.nmw-link[data-nmw-editor-disabled="true"]'
12134          )
12135          .forEach(customerLink => {
12136            customerLink.removeAttribute(
12137              'data-nmw-editor-disabled'
12138            );
12139          });
12140
12141        /*
12142          Same-site links use the existing Site Comments top-window
12143          navigation. Links intentionally excluded by that function—
12144          anchors, target="_blank", external sites, telephone, email,
12145          SMS, downloads, and similar links—retain normal browser behavior.
12146        */
12147        // Same-page anchors are handled inside the current site document so
12148        // repeated anchor selections work without reloading the Site Comments shell.
12149        if (handleSamePageAnchorClick(link, event)) return;
12150
12151        if (navigateSiteComments(link, event)) return;
12152
12153        closeAllMenus(root, false);
12154        return;
12155      }
12156
12157      /*
12158        Outside confirmed customer Site Comments mode, preserve the
12159        original editor safety behavior.
12160      */
12161      if (link.dataset.nmwEditorDisabled === 'true') {
12162        event.preventDefault();
12163        return;
12164      }
12165
12166      // Explicitly own same-page anchor navigation. This fixes repeated anchor
12167      // selections on the same page and corrects the final position for sticky
12168      // or fixed headers. Cross-page anchors continue with normal navigation.
12169      if (handleSamePageAnchorClick(link, event)) return;
12170
12171      closeAllMenus(root, false);
12172    });
12173
12174    listen(root, 'keydown', event => {
12175      if (event.key !== 'Escape') return;
12176      const toggle = nearestOpenToggle(event.target);
12177      if (!toggle) return;
12178      event.preventDefault();
12179      const item = toggle.closest('[data-nmw-item]');
12180      const submenu = findControlledSubmenu(toggle);
12181      if (item && submenu) closeMenu(item, toggle, submenu, true);
12182    });
12183
12184    listen(root, 'focusout', event => {
12185      requestAnimationFrame(() => {
12186        if (local.cleaned) return;
12187        if (!root.contains(document.activeElement)) closeAllMenus(root, false);
12188      });
12189    });
12190
12191    // Hover uses the same JavaScript-owned state as click. CSS never opens menus
12192    // independently, which keeps hidden and aria-expanded synchronized.
12193    listen(root, 'pointerover', event => {
12194      if (!getBehaviorConfig(widgetMode).hoverEnabled || event.pointerType === 'touch') return;
12195      const item = event.target.closest('[data-nmw-item]');
12196      if (!item || !root.contains(item)) return;
12197      const toggle = item.querySelector(':scope > .nmw-row [data-nmw-toggle]');
12198      const submenu = toggle ? findControlledSubmenu(toggle) : null;
12199      if (!toggle || !submenu) return;
12200      const timer = local.hoverTimers.get(item);
12201      if (timer) clearTimeout(timer);
12202      local.hoverTimers.delete(item);
12203      openMenu(item, toggle, submenu);
12204    });
12205
12206    listen(root, 'pointerout', event => {
12207      if (!getBehaviorConfig(widgetMode).hoverEnabled || event.pointerType === 'touch') return;
12208      const item = event.target.closest('[data-nmw-item]');
12209      if (!item || !root.contains(item)) return;
12210      const related = event.relatedTarget;
12211      if (related && item.contains(related)) return;
12212      const toggle = item.querySelector(':scope > .nmw-row [data-nmw-toggle]');
12213      const submenu = toggle ? findControlledSubmenu(toggle) : null;
12214      if (!toggle || !submenu) return;
12215      const timer = setTimeout(() => {
12216        if (!item.matches(':hover')) closeMenu(item, toggle, submenu, false);
12217        local.hoverTimers.delete(item);
12218      }, HOVER_CLOSE_DELAY);
12219      local.hoverTimers.set(item, timer);
12220    });
12221
12222    listen(document, 'pointerdown', handleOutsidePointer, true);
12223  }
12224
12225  function handleOutsidePointer(event) {
12226    if (!root.contains(event.target)) closeAllMenus(root, false);
12227  }
12228
12229  // ---------------------------------------------------------------------------
12230  // Active-page state from the actual current URL
12231  // ---------------------------------------------------------------------------
12232
12233  function currentComparablePath() {
12234    return canonicalPath(window.location.href);
12235  }
12236
12237  function refreshActiveStates(scope) {
12238    const current = currentComparablePath();
12239    scope.querySelectorAll('a[data-nmw-path]').forEach(link => {
12240      const path = link.getAttribute('data-nmw-path') || '';
12241      const active = path && path === current;
12242      link.classList.toggle('nmw-active', active);
12243      if (active) link.setAttribute('aria-current', 'page');
12244      else link.removeAttribute('aria-current');
12245    });
12246    scope.querySelectorAll('[data-nmw-item]').forEach(item => {
12247      const ownLink = item.querySelector(':scope > .nmw-row a.nmw-active');
12248      const activeDescendant = item.querySelector('.nmw-submenu a.nmw-active, .nmw-mega-family-children a.nmw-active');
12249      item.classList.toggle('nmw-child-active', !ownLink && Boolean(activeDescendant));
12250    });
12251  }
12252
12253  function observeHistoryChanges() {
12254    const marker = '__nmwHistoryWrapped';
12255    if (history[marker]) return () => {};
12256    history[marker] = true;
12257    const originalPush = history.pushState;
12258    const originalReplace = history.replaceState;
12259    const notify = () => window.dispatchEvent(new Event('nmw:url-changed'));
12260    history.pushState = function (...args) {
12261      const result = originalPush.apply(this, args);
12262      notify();
12263      return result;
12264    };
12265    history.replaceState = function (...args) {
12266      const result = originalReplace.apply(this, args);
12267      notify();
12268      return result;
12269    };
12270    return () => {};
12271  }
12272
12273  function handleUrlChanged() {
12274    refreshActiveStates(root);
12275  }
12276
12277  // ---------------------------------------------------------------------------
12278  // Manual-item source observation
12279  // ---------------------------------------------------------------------------
12280
12281  function attachManualSourceObserver() {
12282    const source = root.querySelector('[data-nmw-manual-source]');
12283    if (!source || local.manualObserver) return false;
12284
12285    local.manualObserver = new MutationObserver(mutations => {
12286      const relevant = mutations.some(mutation => {
12287        if (mutation.type === 'childList') return true;
12288        if (mutation.type !== 'attributes') return false;
12289        const name = mutation.attributeName || '';
12290        return name === 'href'
12291          || name === 'target'
12292          || name === 'class'
12293          || name.startsWith('data-manual-')
12294          || name.startsWith('data-')
12295          || name.startsWith('aria-');
12296      });
12297
12298      if (relevant) scheduleRebuild(false);
12299    });
12300
12301    local.manualObserver.observe(source, {
12302      childList: true,
12303      subtree: true,
12304      attributes: true
12305    });
12306
12307    return true;
12308  }
12309
12310  // ---------------------------------------------------------------------------
12311  // Native-nav observation and startup discovery
12312  // ---------------------------------------------------------------------------
12313
12314  function attachNativeObserver() {
12315    const wrapper = getNativeWrapper();
12316    if (!wrapper || local.nativeObserver) return false;
12317    local.nativeObserver = new MutationObserver(() => scheduleRebuild(false));
12318    local.nativeObserver.observe(wrapper, {
12319      childList: true,
12320      subtree: true,
12321      attributes: true,
12322      attributeFilter: ['href', 'raw_url', 'runtime_url', 'aria-current', 'class']
12323    });
12324    if (local.nativeFinderObserver) {
12325      local.nativeFinderObserver.disconnect();
12326      local.nativeFinderObserver = null;
12327    }
12328    return true;
12329  }
12330
12331  function beginNativeDiscovery() {
12332    local.nativeRetryStarted = Date.now();
12333    let isFirstAttempt = true;
12334
12335    const attempt = () => {
12336      if (local.cleaned) return;
12337
12338      // If the document-level finder already attached the native observer, the
12339      // retry loop has nothing left to do. This prevents a later timeout rebuild.
12340      if (local.nativeObserver) return;
12341
12342      if (attachNativeObserver()) {
12343        /*
12344          initializeFull() performs an immediate rebuild right after starting
12345          discovery. When .navmasterdefault already exists on the first attempt,
12346          scheduling another rebuild here caused the collections to be processed
12347          twice during startup. Only rebuild here when native navigation appeared
12348          after that initial attempt.
12349        */
12350        if (!isFirstAttempt) scheduleRebuild(false);
12351        isFirstAttempt = false;
12352        return;
12353      }
12354
12355      isFirstAttempt = false;
12356
12357      if (Date.now() - local.nativeRetryStarted >= NATIVE_MAX_WAIT) {
12358        warn('NATIVE_NAV_TIMEOUT', '.navmasterdefault was not found within 15 seconds. Collection navigation will still render.');
12359        scheduleRebuild(true);
12360        return;
12361      }
12362
12363      local.nativeRetryTimer = setTimeout(attempt, NATIVE_RETRY_INTERVAL);
12364    };
12365
12366    attempt();
12367
12368    // If the native wrapper already existed, no document-wide finder is needed.
12369    if (local.nativeObserver || local.cleaned) return;
12370
12371    // During delayed Duda insertion, this lightweight observer notices the
12372    // wrapper, attaches the targeted observer, rebuilds once, then disconnects.
12373    local.nativeFinderObserver = new MutationObserver(() => {
12374      if (local.cleaned || local.nativeObserver) return;
12375      if (attachNativeObserver()) scheduleRebuild(false);
12376    });
12377    local.nativeFinderObserver.observe(document.body, { childList: true, subtree: true });
12378  }
12379
12380  // ---------------------------------------------------------------------------
12381  // Tree preview and CUSTOMNAV builder
12382  // ---------------------------------------------------------------------------
12383
12384  function updateTools() {
12385    if (!toolsHost) return;
12386    const treeEnabled = readBoolean(configValueFrom(cfg, 'enableNavigationTreePreview'), false);
12387    const builderEnabled = readBoolean(configValueFrom(cfg, 'enableCustomNavigationBuilder'), false);
12388    const visibleEnvironment = isEditorCanvas() || location.href.includes('/preview/') || location.hash.includes('preview');
12389    if (!visibleEnvironment || (!treeEnabled && !builderEnabled)) {
12390      toolsHost.innerHTML = '';
12391      return;
12392    }
12393    toolsHost.innerHTML = [
12394      treeEnabled ? '<button type="button" class="nmw-tool-button" data-nmw-open-tree>View Navigation Tree</button>' : '',
12395      builderEnabled ? '<button type="button" class="nmw-tool-button" data-nmw-open-builder>Open Custom Navigation Builder</button>' : ''
12396    ].join('');
12397  }
12398
12399  function treeLines(items, prefix = '') {
12400    const output = [];
12401    (items || []).forEach(item => {
12402      output.push(`${prefix}${item.label}`);
12403      output.push(...treeLines(item.children || [], `${prefix}  `));
12404    });
12405    return output;
12406  }
12407
12408  function renderSlugWarning(item) {
12409    const slugText = (item.slugs || []).join(' or ');
12410    return `<li><strong>${escapeHtml(item.label)}</strong><br>` +
12411      `Slug not found: ${escapeHtml(slugText)}<br>` +
12412      `Suggested collection: ${escapeHtml(item.suggestedCollection)}</li>`;
12413  }
12414
12415  function openTreePreview() {
12416    const warnings = Array.isArray(window.NMW.slugWarnings) ? window.NMW.slugWarnings : [];
12417    const treeOutput = treeLines(window.NMW.data || []).join('\n') || 'No navigation items.';
12418    const warningSection = warnings.length
12419      ? `<section class="nmw-tree-warnings"><h3>Warnings</h3><ul>${warnings.map(renderSlugWarning).join('')}</ul></section>`
12420      : '';
12421    const body = `<pre class="nmw-tree-output">${escapeHtml(treeOutput)}</pre>${warningSection}`;
12422    openModal('Navigation Tree Preview', body, 'tree');
12423  }
12424
12425  function cloneData(value) {
12426    return JSON.parse(JSON.stringify(value || []));
12427  }
12428
12429  function shiftDraftDepth(item, offset, parentKey = null) {
12430    const copy = cloneData(item);
12431    copy.depth = Math.max(0, (Number(copy.depth) || 0) + offset);
12432    copy.parentKey = parentKey;
12433    copy.children = (copy.children || []).map(child => shiftDraftDepth(child, offset, copy.key));
12434    copy.hasChildren = copy.children.length > 0;
12435    return copy;
12436  }
12437
12438  // The runtime model contains synthetic Mega trigger rows. CUSTOMNAV should
12439  // store the actual family roots with MEGA GROUP values so loading CUSTOMNAV
12440  // can recreate the trigger without adding an extra persisted hierarchy level.
12441  function draftSourceFromSharedModel() {
12442    const output = [];
12443    (window.NMW.data || []).forEach(item => {
12444      if (readString(item.key, '').startsWith('mega-group:')) {
12445        (item.children || []).forEach(family => {
12446          const copy = shiftDraftDepth(family, -1, null);
12447          copy.megaGroup = item.megaGroup || copy.megaGroup || 0;
12448          copy.presentation = copy.megaGroup ? 'mega' : copy.presentation;
12449          output.push(copy);
12450        });
12451      } else {
12452        output.push(shiftDraftDepth(item, 0, null));
12453      }
12454    });
12455    return output;
12456  }
12457
12458  function flattenDraft(items, output = []) {
12459    items.forEach(item => {
12460      output.push(item);
12461      flattenDraft(item.children, output);
12462    });
12463    return output;
12464  }
12465
12466  function renderBuilderRows(items) {
12467    return items.map(item => `<div class="nmw-builder-row" draggable="true" data-builder-key="${escapeHtml(item.key)}" style="--nmw-builder-depth:${item.depth}"><span class="nmw-builder-handle" aria-hidden="true">⋮⋮</span><span class="nmw-builder-label">${escapeHtml(item.label)}</span><span class="nmw-builder-meta">${escapeHtml(item.source)} · ${escapeHtml(item.type)}${item.megaGroup ? ` · ${escapeHtml(getMegaLabel(item.megaGroup))}` : ''}</span><button type="button" data-builder-left data-key="${escapeHtml(item.key)}" aria-label="Move ${escapeHtml(item.label)} left">←</button><button type="button" data-builder-right data-key="${escapeHtml(item.key)}" aria-label="Move ${escapeHtml(item.label)} right">→</button><button type="button" data-builder-remove data-key="${escapeHtml(item.key)}" aria-label="Remove ${escapeHtml(item.label)}">Remove</button></div>`).join('');
12468  }
12469
12470  function openBuilder() {
12471    local.customDraft = draftSourceFromSharedModel();
12472    const body = `<div class="nmw-builder-toolbar"><button type="button" data-builder-add>Add Manual Item</button><button type="button" data-builder-reset>Reset Draft</button><button type="button" data-builder-validate>Validate</button><button type="button" data-builder-export="json">Export JSON</button><button type="button" data-builder-export="csv">Export CSV</button><button type="button" data-builder-export="payload">Export Duda Payload</button></div><p class="nmw-builder-help">Drag rows to reorder. Use ← and → to promote or demote. Maximum depth is three levels.</p><div class="nmw-builder-list" data-builder-list>${renderBuilderRows(flattenDraft(local.customDraft))}</div><div class="nmw-builder-status" data-builder-status></div>`;
12473    openModal('Custom Navigation Builder', body, 'builder');
12474  }
12475
12476  function openModal(title, body, type) {
12477    closeModal();
12478    const modal = document.createElement('div');
12479    modal.className = 'nmw-modal-backdrop';
12480    modal.dataset.nmwModal = type;
12481    modal.innerHTML = `<section class="nmw-modal" role="dialog" aria-modal="true" aria-labelledby="nmw-modal-title-${instanceId}"><header class="nmw-modal-header"><h2 id="nmw-modal-title-${instanceId}">${escapeHtml(title)}</h2><button type="button" class="nmw-modal-close" data-nmw-close-modal aria-label="Close">×</button></header><div class="nmw-modal-body">${body}</div></section>`;
12482    document.body.appendChild(modal);
12483    modal.querySelector('[data-nmw-close-modal]').focus();
12484    if (type === 'builder') attachBuilderInteractions(modal);
12485  }
12486
12487  function closeModal() {
12488    document.querySelectorAll('.nmw-modal-backdrop[data-nmw-modal]').forEach(node => node.remove());
12489  }
12490
12491  function findDraftLocation(key, items = local.customDraft, parent = null) {
12492    for (let index = 0; index < items.length; index += 1) {
12493      if (items[index].key === key) return { item: items[index], items, index, parent };
12494      const found = findDraftLocation(key, items[index].children, items[index]);
12495      if (found) return found;
12496    }
12497    return null;
12498  }
12499
12500  function recalculateDraft(items = local.customDraft, depth = 0, parentKey = null) {
12501    items.forEach((item, order) => {
12502      item.depth = depth;
12503      item.parentKey = parentKey;
12504      item.order = order;
12505      item.hasChildren = item.children.length > 0;
12506      recalculateDraft(item.children, depth + 1, item.key);
12507    });
12508  }
12509
12510  function refreshBuilder(modal, message = '') {
12511    recalculateDraft();
12512    const list = modal.querySelector('[data-builder-list]');
12513    if (list) list.innerHTML = renderBuilderRows(flattenDraft(local.customDraft));
12514    const status = modal.querySelector('[data-builder-status]');
12515    if (status) status.textContent = message;
12516  }
12517
12518  function validateDraft() {
12519    const errors = [];
12520    const keys = new Set();
12521    function walk(items, ancestors = []) {
12522      items.forEach(item => {
12523        if (keys.has(item.key)) errors.push(`Duplicate key: ${item.key}`);
12524        keys.add(item.key);
12525        if (item.depth > MAX_DEPTH) errors.push(`${item.label} exceeds three levels.`);
12526        if (ancestors.includes(item.key)) errors.push(`${item.label} is nested beneath itself.`);
12527        if (item.type === 'page' && !item.url) errors.push(`${item.label} is a page without a URL.`);
12528        walk(item.children, [...ancestors, item.key]);
12529      });
12530    }
12531    walk(local.customDraft || []);
12532    return errors;
12533  }
12534
12535  function addManualDraftItem() {
12536    const label = window.prompt('Manual item label:');
12537    if (!readString(label, '')) return;
12538    const type = window.prompt('Type "page" or "folder":', 'page');
12539    const isFolder = readString(type, 'page').toLowerCase() === 'folder';
12540    const url = isFolder ? '' : readString(window.prompt('URL:', '/'), '');
12541    const key = `manual:${slugify(label)}-${Math.random().toString(36).slice(2, 6)}`;
12542    local.customDraft.push({
12543      key,
12544      parentKey: null,
12545      source: 'manual',
12546      type: isFolder ? 'folder' : 'page',
12547      presentation: 'standard',
12548      megaGroup: 0,
12549      depth: 0,
12550      order: local.customDraft.length,
12551      label,
12552      slug: slugify(label),
12553      canonicalPath: isFolder ? '' : canonicalPath(url),
12554      url,
12555      target: '_self',
12556      isExternal: isExternalUrl(url),
12557      hasChildren: false,
12558      children: []
12559    });
12560  }
12561
12562  function moveDraftLeft(key) {
12563    const location = findDraftLocation(key);
12564    if (!location || !location.parent) return;
12565    const parentLocation = findDraftLocation(location.parent.key);
12566    location.items.splice(location.index, 1);
12567    parentLocation.items.splice(parentLocation.index + 1, 0, location.item);
12568  }
12569
12570  function moveDraftRight(key) {
12571    const location = findDraftLocation(key);
12572    if (!location || location.index === 0 || location.item.depth >= MAX_DEPTH) return;
12573    const previousSibling = location.items[location.index - 1];
12574    location.items.splice(location.index, 1);
12575    previousSibling.children.push(location.item);
12576  }
12577
12578  function removeDraftItem(key) {
12579    const location = findDraftLocation(key);
12580    if (!location) return;
12581    location.items.splice(location.index, 1);
12582  }
12583
12584  function reorderDraft(dragKey, targetKey) {
12585    if (!dragKey || dragKey === targetKey) return;
12586    const drag = findDraftLocation(dragKey);
12587    const target = findDraftLocation(targetKey);
12588    if (!drag || !target || drag.items !== target.items) return;
12589    drag.items.splice(drag.index, 1);
12590    const newTarget = findDraftLocation(targetKey);
12591    newTarget.items.splice(newTarget.index, 0, drag.item);
12592  }
12593
12594  function customRowsFromDraft() {
12595    recalculateDraft();
12596    return flattenDraft(local.customDraft).map(item => ({
12597      'ITEM KEY': item.key,
12598      'PARENT KEY': item.parentKey || '',
12599      'PAGE NAME': item.label,
12600      SOURCE: item.source,
12601      TYPE: item.type,
12602      PRESENTATION: item.presentation,
12603      'MEGA GROUP': item.megaGroup,
12604      DEPTH: item.depth,
12605      ORDER: item.order,
12606      URL: item.url || '',
12607      'CANONICAL PATH': item.canonicalPath || '',
12608      SLUG: item.slug || '',
12609      TARGET: item.target || '_self',
12610      'IS EXTERNAL': item.isExternal,
12611      'SHOW ITEM': true
12612    }));
12613  }
12614
12615  function downloadText(filename, content, mime = 'text/plain') {
12616    const blob = new Blob([content], { type: `${mime};charset=utf-8` });
12617    const url = URL.createObjectURL(blob);
12618    const anchor = document.createElement('a');
12619    anchor.href = url;
12620    anchor.download = filename;
12621    document.body.appendChild(anchor);
12622    anchor.click();
12623    anchor.remove();
12624    URL.revokeObjectURL(url);
12625  }
12626
12627  function exportDraft(format) {
12628    const errors = validateDraft();
12629    if (errors.length) {
12630      window.alert(`Fix these issues before exporting:\n\n${errors.join('\n')}`);
12631      return;
12632    }
12633    const rows = customRowsFromDraft();
12634    if (format === 'json') {
12635      downloadText('CUSTOMNAV.json', JSON.stringify(rows, null, 2), 'application/json');
12636      return;
12637    }
12638    if (format === 'payload') {
12639      downloadText('CUSTOMNAV-duda-payload.json', JSON.stringify({ data: rows }, null, 2), 'application/json');
12640      return;
12641    }
12642    const headers = Object.keys(rows[0] || {});
12643    const escapeCsv = value => `"${String(value == null ? '' : value).replace(/"/g, '""')}"`;
12644    const csv = [headers.map(escapeCsv).join(','), ...rows.map(row => headers.map(header => escapeCsv(row[header])).join(','))].join('\n');
12645    downloadText('CUSTOMNAV.csv', csv, 'text/csv');
12646  }
12647
12648  function attachBuilderInteractions(modal) {
12649    let draggedKey = '';
12650    modal.addEventListener('dragstart', event => {
12651      const row = event.target.closest('[data-builder-key]');
12652      if (!row) return;
12653      draggedKey = row.dataset.builderKey;
12654      event.dataTransfer.effectAllowed = 'move';
12655    });
12656    modal.addEventListener('dragover', event => {
12657      if (event.target.closest('[data-builder-key]')) event.preventDefault();
12658    });
12659    modal.addEventListener('drop', event => {
12660      const target = event.target.closest('[data-builder-key]');
12661      if (!target) return;
12662      event.preventDefault();
12663      reorderDraft(draggedKey, target.dataset.builderKey);
12664      refreshBuilder(modal, 'Draft reordered.');
12665    });
12666    modal.addEventListener('click', event => {
12667      const left = event.target.closest('[data-builder-left]');
12668      const right = event.target.closest('[data-builder-right]');
12669      const remove = event.target.closest('[data-builder-remove]');
12670      if (left) { moveDraftLeft(left.dataset.key); refreshBuilder(modal, 'Item promoted.'); return; }
12671      if (right) { moveDraftRight(right.dataset.key); refreshBuilder(modal, 'Item demoted.'); return; }
12672      if (remove) { removeDraftItem(remove.dataset.key); refreshBuilder(modal, 'Item removed from draft.'); return; }
12673      if (event.target.closest('[data-builder-add]')) { addManualDraftItem(); refreshBuilder(modal, 'Manual item added.'); return; }
12674      if (event.target.closest('[data-builder-reset]')) { local.customDraft = draftSourceFromSharedModel(); refreshBuilder(modal, 'Draft reset.'); return; }
12675      if (event.target.closest('[data-builder-validate]')) {
12676        const errors = validateDraft();
12677        refreshBuilder(modal, errors.length ? errors.join(' ') : 'Structure is valid.');
12678        return;
12679      }
12680      const exportButton = event.target.closest('[data-builder-export]');
12681      if (exportButton) exportDraft(exportButton.dataset.builderExport);
12682    });
12683  }
12684
12685  function handleToolClick(event) {
12686    if (event.target.closest('[data-nmw-open-tree]')) openTreePreview();
12687    if (event.target.closest('[data-nmw-open-builder]')) openBuilder();
12688  }
12689
12690  function handleModalClick(event) {
12691    const backdrop = event.target.closest('.nmw-modal-backdrop');
12692    if (!backdrop) return;
12693    if (event.target.matches('.nmw-modal-backdrop') || event.target.closest('[data-nm
12693w-close-modal]')) closeModal();
12694  }
12695
12696  function handleModalKeydown(event) {
12697    if (event.key === 'Escape' && document.querySelector('.nmw-modal-backdrop')) closeModal();
12698  }
12699
12700  // ---------------------------------------------------------------------------
12701  // Reset action hooks
12702  // ---------------------------------------------------------------------------
12703
12704  // Duda panel switches cannot directly erase saved panel values. These hooks
12705  // expose one-shot reset requests as data attributes/classes so the panel or a
12706  // future config migration can repopulate defaults without touching structure.
12707  function applyResetRequests() {
12708    if (readBoolean(configValueFrom(cfg, 'restoreFullDefaults'), false) && widgetMode === 'full') root.dataset.nmwRestoreDefaults = 'full-requested';
12709    if (readBoolean(configValueFrom(cfg, 'restoreMirrorDefaults'), false) && widgetMode === 'mirror') root.dataset.nmwRestoreDefaults = 'mirror-requested';
12710  }
12711
12712  // ---------------------------------------------------------------------------
12713  // Cleanup
12714  // ---------------------------------------------------------------------------
12715
12716  function cleanup(reason = 'removed') {
12717    if (local.cleaned) return;
12718    local.cleaned = true;
12719    clearTimeout(local.rebuildTimer);
12720    clearTimeout(local.nativeRetryTimer);
12721    local.hoverTimers.forEach(timer => clearTimeout(timer));
12722    local.anchorTimers.forEach(timer => clearTimeout(timer));
12723    local.anchorTimers.clear();
12724    local.closeTimers.forEach(timer => clearTimeout(timer));
12725    local.closeTimers.clear();
12726    local.nativeObserver?.disconnect();
12727    local.nativeFinderObserver?.disconnect();
12728    local.manualObserver?.disconnect();
12729    local.removalObserver?.disconnect();
12730    if (typeof local.historyCleanup === 'function') local.historyCleanup();
12731    local.eventCleanups.splice(0).forEach(remove => {
12732      try { remove(); } catch (error) {}
12733    });
12734    window.removeEventListener('nmw:data-changed', handleMirrorDataChanged);
12735    window.removeEventListener('nmw:state-changed', handleSharedStateChanged);
12736    window.removeEventListener('nmw:url-changed', handleUrlChanged);
12737    window.removeEventListener('popstate', handleUrlChanged);
12738    window.removeEventListener('hashchange', handleUrlChanged);
12739    document.removeEventListener('pointerdown', handleOutsidePointer, true);
12740    document.removeEventListener('click', handleModalClick);
12741    document.removeEventListener('keydown', handleModalKeydown);
12742    toolsHost?.removeEventListener('click', handleToolClick);
12743    closeModal();
12744    releaseOwnership();
12745
12746    // Remove this closure only when it is still the registered lifecycle for
12747    // the root. A newer execution may already have replaced it.
12748    if (window.NMW_RUNTIME?.instances?.get(root) === cleanup) {
12749      window.NMW_RUNTIME.instances.delete(root);
12750    }
12751
12752    console.info('[NMW] Cleaned up instance', { widgetMode, instanceId, reason });
12753  }
12754
12755  // Duda does not provide a universal destroy callback for all widget contexts,
12756  // so this observer performs cleanup if the widget root is removed.
12757  local.removalObserver = new MutationObserver(() => {
12758    if (!document.documentElement.contains(root)) {
12759      local.removalObserver.disconnect();
12760      cleanup();
12761    }
12762  });
12763  local.removalObserver.observe(document.documentElement, { childList: true, subtree: true });
12764
12765  // ---------------------------------------------------------------------------
12766  // Full and Mirror initialization
12767  // ---------------------------------------------------------------------------
12768
12769  function handleMirrorDataChanged() {
12770    if (widgetMode !== 'mirror') return;
12771    // Full may have changed Design Panel values without changing collection
12772    // structure. Refresh inherited Mirror classes and CSS variables before
12773    // rendering the shared model.
12774    applyDesignSettings('mirror');
12775    renderNavigation(window.NMW.data || [], 'mirror');
12776    updateRootStatus();
12777  }
12778
12779  function handleSharedStateChanged() {
12780    updateRootStatus();
12781  }
12782
12783  function initializeFull() {
12784    // This instance now owns the Full widget. Reset stale editor state left by
12785    // an interrupted prior execution, then rebuild with the current panel data.
12786    window.NMW.loading = false;
12787    applyDesignSettings('full');
12788    applyResetRequests();
12789    attachDelegatedInteractions();
12790    attachManualSourceObserver();
12791    beginNativeDiscovery();
12792    local.historyCleanup = observeHistoryChanges();
12793    listen(window, 'nmw:url-changed', handleUrlChanged);
12794    listen(window, 'popstate', handleUrlChanged);
12795    listen(window, 'hashchange', handleUrlChanged);
12796    if (toolsHost) listen(toolsHost, 'click', handleToolClick);
12797    listen(document, 'click', handleModalClick);
12798    listen(document, 'keydown', handleModalKeydown);
12799    scheduleRebuild(true);
12800  }
12801
12802  function initializeMirror() {
12803    applyDesignSettings('mirror');
12804    applyResetRequests();
12805    attachDelegatedInteractions();
12806    local.historyCleanup = observeHistoryChanges();
12807    listen(window, 'nmw:data-changed', handleMirrorDataChanged);
12808    listen(window, 'nmw:state-changed', handleSharedStateChanged);
12809    listen(window, 'nmw:url-changed', handleUrlChanged);
12810    listen(window, 'popstate', handleUrlChanged);
12811    listen(window, 'hashchange', handleUrlChanged);
12812    if (toolsHost) listen(toolsHost, 'click', handleToolClick);
12813    listen(document, 'click', handleModalClick);
12814    listen(document, 'keydown', handleModalKeydown);
12815    updateRootStatus();
12816    if (window.NMW.ready) renderNavigation(window.NMW.data || [], 'mirror');
12817  }
12818
12819  try {
12820    if (!claimOwnership()) return;
12821    root.dataset.nmwInstance = instanceId;
12822
12823    // Register this lifecycle only after ownership succeeds. The next Duda
12824    // panel execution on the same root will call this cleanup before starting.
12825    window.NMW_RUNTIME.instances.set(root, cleanup);
12826    console.info('[NMW] Initialized', { widgetMode, instanceId, renderHost });
12827    updateRootStatus();
12828    if (widgetMode === 'full') initializeFull();
12829    else initializeMirror();
12830  } catch (error) {
12831    console.error('[NMW] Startup failed', error);
12832    if (renderHost) {
12833      renderHost.innerHTML = `<div class="nmw-empty">Navigation startup failed: ${escapeHtml(error && error.message ? error.message : String(error))}</div>`;
12834    }
12835  }
12836});
12837    };
12838</script>
12838
12839
12840<script>
12841    window.customWidgetsFunctions["e0b1e5b2d4394e69a98bd276fd6afe8b~8"] = function (element, data, api) {
12842        window.dmAPI.runOnReady('HH1', () => {
12843
12844  const STYLE_ID = 'HH1-theme-style';
12845  const HH1_SELECTOR = '.widget-wrapper.HH1';
12846
12847  let updateTimer = null;
12848  let lastSignature = '';
12849
12850  /**
12851   * Find a usable H1 that is NOT inside the HH1 widget.
12852   */
12853  function findThemeH1() {
12854    const candidates = Array.from(
12855      document.querySelectorAll('#dm h1, h1')
12856    );
12857
12858    for (const el of candidates) {
12859
12860      // Do not use our own HH1 widget as the source
12861      if (el.closest(HH1_SELECTOR)) {
12862        continue;
12863      }
12864
12865      // Ignore disconnected elements
12866      if (!el.isConnected) {
12867        continue;
12868      }
12869
12870      const computed = window.getComputedStyle(el);
12871
12872      // Ignore elements that are completely unavailable
12873      if (
12874        computed.display === 'none' ||
12875        computed.visibility === 'hidden'
12876      ) {
12877        continue;
12878      }
12879
12880      // Make sure we actually got useful typography
12881      if (!computed.fontFamily || !computed.fontSize) {
12882        continue;
12883      }
12884
12885      return el;
12886    }
12887
12888    return null;
12889  }
12890
12891
12892  /**
12893   * Read H1 typography and apply it to HH1 widgets.
12894   */
12895  function applyTheme() {
12896
12897    const h1Sample = findThemeH1();
12898
12899    if (!h1Sample) {
12900      return false;
12901    }
12902
12903    const computed = window.getComputedStyle(h1Sample);
12904
12905    const styles = {
12906      fontFamily: computed.fontFamily,
12907      fontSize: computed.fontSize,
12908      fontWeight: computed.fontWeight,
12909      lineHeight: computed.lineHeight,
12910      letterSpacing: computed.letterSpacing,
12911      textTransform: computed.textTransform,
12912      fontStyle: computed.fontStyle,
12913      fontVariant: computed.fontVariant,
12914      fontStretch: computed.fontStretch
12915    };
12916
12917    /*
12918     * Create a signature so we're not constantly rewriting
12919     * the stylesheet when nothing has actually changed.
12920     */
12921    const signature = JSON.stringify(styles);
12922
12923    if (signature === lastSignature) {
12924      return true;
12925    }
12926
12927    lastSignature = signature;
12928
12929
12930    /*
12931     * Target the widget plus all common Duda RTE descendants.
12932     * The wildcard selector is intentional so inline/nested
12933     * typography cannot override the inherited H1 theme.
12934     */
12935    const selectors = [
12936      '#dm .dmInner .widget-wrapper.HH1 .dmNewParagraph',
12937      '#dm .dmInner .widget-wrapper.HH1 .dmNewParagraph p',
12938      '#dm .dmInner .widget-wrapper.HH1 .dmNewParagraph span',
12939      '#dm .dmInner .widget-wrapper.HH1 .dmNewParagraph .rteBlock',
12940      '#dm .dmInner .widget-wrapper.HH1 .dmNewParagraph .rteBlock *',
12941
12942      '.dmInner .widget-wrapper.HH1 .dmNewParagraph',
12943      '.dmInner .widget-wrapper.HH1 .dmNewParagraph p',
12944      '.dmInner .widget-wrapper.HH1 .dmNewParagraph span',
12945      '.dmInner .widget-wrapper.HH1 .dmNewParagraph .rteBlock',
12946      '.dmInner .widget-wrapper.HH1 .dmNewParagraph .rteBlock *',
12947
12948      '.widget-wrapper.HH1 .dmNewParagraph',
12949      '.widget-wrapper.HH1 .dmNewParagraph p',
12950      '.widget-wrapper.HH1 .dmNewParagraph span',
12951      '.widget-wrapper.HH1 .dmNewParagraph .rteBlock',
12952      '.widget-wrapper.HH1 .dmNewParagraph .rteBlock *'
12953    ];
12954
12955    const css = `
12956${selectors.join(',\n')} {
12957  font-family: ${styles.fontFamily} !important;
12958  font-size: ${styles.fontSize} !important;
12959  font-weight: ${styles.fontWeight} !important;
12960  line-height: ${styles.lineHeight} !important;
12961  letter-spacing: ${styles.letterSpacing} !important;
12962  text-transform: ${styles.textTransform} !important;
12963  font-style: ${styles.fontStyle} !important;
12964  font-variant: ${styles.fontVariant} !important;
12965  font-stretch: ${styles.fontStretch} !important;
12966}
12967`;
12968
12969    let styleTag = document.getElementById(STYLE_ID);
12970
12971    if (!styleTag) {
12972      styleTag = document.createElement('style');
12973      styleTag.id = STYLE_ID;
12974      styleTag.setAttribute('data-HH1', 'true');
12975      document.head.appendChild(styleTag);
12976    }
12977
12978    styleTag.textContent = css;
12979
12980    return true;
12981  }
12982
12983
12984  /**
12985   * Debounce updates so Duda can make several DOM changes
12986   * without us recalculating after every individual mutation.
12987   */
12988  function scheduleUpdate(delay = 100) {
12989
12990    clearTimeout(updateTimer);
12991
12992    updateTimer = setTimeout(() => {
12993      applyTheme();
12994    }, delay);
12995  }
12996
12997
12998  /**
12999   * INITIAL LOAD
13000   *
13001   * Run several times because Duda/theme/font loading can occur
13002   * after dmAPI.runOnReady().
13003   */
13004  applyTheme();
13005
13006  setTimeout(applyTheme, 250);
13007  setTimeout(applyTheme, 500);
13008  setTimeout(applyTheme, 1000);
13009  setTimeout(applyTheme, 2000);
13010  setTimeout(applyTheme, 4000);
13011
13012
13013  /**
13014   * WATCH DUDA DOM CHANGES
13015   *
13016   * Dynamic collections, responsive rendering, widgets, and
13017   * editor/preview changes can alter the page after initial load.
13018   */
13019  const observer = new MutationObserver((mutations) => {
13020
13021    let relevantChange = false;
13022
13023    for (const mutation of mutations) {
13024
13025      /*
13026       * Ignore mutations caused only by our own style tag.
13027       */
13028      if (
13029        mutation.target &&
13030        (
13031          mutation.target.id === STYLE_ID ||
13032          mutation.target.closest?.(`#${STYLE_ID}`)
13033        )
13034      ) {
13035        continue;
13036      }
13037
13038      relevantChange = true;
13039      break;
13040    }
13041
13042    if (relevantChange) {
13043      scheduleUpdate(150);
13044    }
13045
13046  });
13047
13048  observer.observe(document.body, {
13049    childList: true,
13050    subtree: true,
13051    attributes: true,
13052    attributeFilter: [
13053      'class',
13054      'style'
13055    ]
13056  });
13057
13058
13059  /**
13060   * RESPONSIVE CHANGES
13061   *
13062   * H1 typography may change between desktop/tablet/mobile.
13063   */
13064  window.addEventListener('resize', () => {
13065    scheduleUpdate(150);
13066  });
13067
13068
13069  /**
13070   * ORIENTATION CHANGE
13071   */
13072  window.addEventListener('orientationchange', () => {
13073    scheduleUpdate(250);
13074  });
13075
13076
13077  /**
13078   * FONT LOADING
13079   *
13080   * Reapply after web fonts finish loading.
13081   */
13082  if (document.fonts && document.fonts.ready) {
13083
13084    document.fonts.ready.then(() => {
13085
13086      // Force refresh even if we previously captured fallback font
13087      lastSignature = '';
13088
13089      applyTheme();
13090
13091    }).catch(() => {
13092      // Nothing required
13093    });
13094
13095  }
13096
13097});
13098    };
13099</script>
13099
13100
13101<script>
13102    window.customWidgetsFunctions["eaabc54e7df84833ad831aa9c55a4ffa~5"] = function (element, data, api) {
13103        window.dmAPI.runOnReady('breadcrumb-widget', () => {
13104
13105  'use strict';
13106
13107  const rootEl = element.querySelector('.bc-root');
13108  const listEl = element.querySelector('.bc-list');
13109
13110  if (!rootEl || !listEl) return;
13111
13112  const config = data.config || {};
13113
13114
13115  /* ============================================================
13116     BASIC HELPERS
13117     ============================================================ */
13118
13119  function safeTrim(value) {
13120    return value == null ? '' : String(value).trim();
13121  }
13122
13123
13124  function normalizeBoolean(value, fallback = false) {
13125    if (typeof value === 'boolean') return value;
13126
13127    if (value === 1 || value === '1') return true;
13128    if (value === 0 || value === '0') return false;
13129
13130    const normalized = safeTrim(value).toLowerCase();
13131
13132    if (['true', 'yes', 'on', 'enabled'].includes(normalized)) {
13133      return true;
13134    }
13135
13136    if (['false', 'no', 'off', 'disabled'].includes(normalized)) {
13137      return false;
13138    }
13139
13140    return fallback;
13141  }
13142
13143
13144  function escapeHtml(value) {
13145    return String(value == null ? '' : value)
13146      .replace(/&/g, '&amp;')
13147      .replace(/</g, '&lt;')
13148      .replace(/>/g, '&gt;')
13149      .replace(/"/g, '&quot;')
13150      .replace(/'/g, '&#039;');
13151  }
13152
13153
13154  function stripHtml(value) {
13155    const holder = document.createElement('div');
13156
13157    holder.innerHTML = safeTrim(value);
13158
13159    return safeTrim(
13160      holder.textContent ||
13161      holder.innerText ||
13162      ''
13163    );
13164  }
13165
13166
13167  function removeDiacritics(value) {
13168    const text = safeTrim(value);
13169
13170    if (typeof text.normalize !== 'function') {
13171      return text;
13172    }
13173
13174    return text
13175      .normalize('NFKD')
13176      .replace(/[\u0300-\u036f]/g, '');
13177  }
13178
13179
13180  function slugify(value) {
13181    return removeDiacritics(stripHtml(value))
13182      .toLowerCase()
13183      .replace(/[’']/g, '')
13184      .replace(/[^a-z0-9]+/g, '-')
13185      .replace(/-+/g, '-')
13186      .replace(/^-+|-+$/g, '');
13187  }
13188
13189
13190  function createSlugCandidates(value) {
13191    const originalText = stripHtml(value);
13192
13193    const output = [];
13194    const seen = new Set();
13195
13196    function add(candidateText) {
13197      const slug = slugify(candidateText);
13198
13199      if (slug && !seen.has(slug)) {
13200        seen.add(slug);
13201        output.push(slug);
13202      }
13203    }
13204
13205    if (!originalText) {
13206      return output;
13207    }
13208
13209    [
13210      originalText.replace(/&/g, ' and '),
13211      originalText.replace(/&/g, ' '),
13212      originalText
13213    ].forEach(ampersandVariant => {
13214
13215      [
13216        ampersandVariant,
13217        ampersandVariant.replace(/\band\b/gi, ' ')
13218      ].forEach(andVariant => {
13219
13220        add(
13221          andVariant.replace(/\s*[\\/]+\s*/g, ' ')
13222        );
13223
13224        add(
13225          andVariant.replace(/\s*[\\/]+\s*/g, '')
13226        );
13227
13228      });
13229
13230    });
13231
13232    return output;
13233  }
13234
13235
13236  function normalizePrefix(value, fallback) {
13237    const supplied = value == null
13238      ? fallback
13239      : safeTrim(value);
13240
13241    return supplied.replace(/^\/+|\/+$/g, '');
13242  }
13243
13244
13245  function normalizeDimension(value, fallback) {
13246    const supplied = safeTrim(value);
13247
13248    if (!supplied) {
13249      return fallback;
13250    }
13251
13252    if (/^-?\d+(?:\.\d+)?$/.test(supplied)) {
13253      return supplied + 'px';
13254    }
13255
13256    return supplied;
13257  }
13258
13259
13260  function normalizeTextDecoration(value, fallback) {
13261    const normalized = safeTrim(value).toLowerCase();
13262
13263    return [
13264      'underline',
13265      'none',
13266      'line-through',
13267      'overline'
13268    ].includes(normalized)
13269      ? normalized
13270      : fallback;
13271  }
13272
13273
13274  function normalizeFontStyle(value, fallback) {
13275    const normalized = safeTrim(value).toLowerCase();
13276
13277    return [
13278      'normal',
13279      'italic',
13280      'oblique'
13281    ].includes(normalized)
13282      ? normalized
13283      : fallback;
13284  }
13285
13286
13287  function normalizeFontWeight(value, fallback) {
13288    const normalized = safeTrim(value).toLowerCase();
13289
13290    if (/^[1-9]00$/.test(normalized)) {
13291      return normalized;
13292    }
13293
13294    if ([
13295      'normal',
13296      'bold',
13297      'bolder',
13298      'lighter'
13299    ].includes(normalized)) {
13300      return normalized;
13301    }
13302
13303    return fallback;
13304  }
13305
13306
13307  function extractConfigValue(value, fallback) {
13308
13309    if (
13310      value &&
13311      typeof value === 'object'
13312    ) {
13313
13314      return safeTrim(
13315        value.value ??
13316        value.css ??
13317        value.color ??
13318        value.hex ??
13319        value.fontFamily ??
13320        value.family ??
13321        value.name ??
13322        fallback
13323      );
13324
13325    }
13326
13327    return safeTrim(value || fallback);
13328  }
13329
13330
13331  function configValue(...names) {
13332
13333    const wanted = names.map(
13334      name => String(name).toLowerCase()
13335    );
13336
13337    const visited = new Set();
13338
13339
13340    function search(value, depth) {
13341
13342      if (
13343        !value ||
13344        typeof value !== 'object' ||
13345        depth > 6 ||
13346        visited.has(value)
13347      ) {
13348        return undefined;
13349      }
13350
13351      visited.add(value);
13352
13353
13354      for (const key of Object.keys(value)) {
13355
13356        if (
13357          wanted.includes(key.toLowerCase()) &&
13358          value[key] !== undefined
13359        ) {
13360          return value[key];
13361        }
13362
13363      }
13364
13365
13366      for (const child of Object.values(value)) {
13367
13368        const found = search(
13369          child,
13370          depth + 1
13371        );
13372
13373        if (found !== undefined) {
13374          return found;
13375        }
13376
13377      }
13378
13379      return undefined;
13380    }
13381
13382
13383    return search(config, 0);
13384  }
13385
13386
13387  function readRowData(entry) {
13388
13389    if (
13390      !entry ||
13391      typeof entry !== 'object'
13392    ) {
13393      return {};
13394    }
13395
13396    return {
13397      ...entry,
13398      ...(
13399        entry.data &&
13400        typeof entry.data === 'object'
13401          ? entry.data
13402          : {}
13403      )
13404    };
13405  }
13406
13407
13408  function field(row, ...names) {
13409
13410    if (
13411      !row ||
13412      typeof row !== 'object'
13413    ) {
13414      return '';
13415    }
13416
13417    for (const name of names) {
13418
13419      if (
13420        Object.prototype.hasOwnProperty.call(
13421          row,
13422          name
13423        )
13424      ) {
13425        return row[name];
13426      }
13427
13428      const matchingKey = Object.keys(row).find(
13429        key =>
13430          key.toLowerCase() ===
13431          String(name).toLowerCase()
13432      );
13433
13434      if (matchingKey) {
13435        return row[matchingKey];
13436      }
13437
13438    }
13439
13440    return '';
13441  }
13442
13443
13444  /* ============================================================
13445     CONFIGURATION
13446     ============================================================ */
13447
13448  const homeLabel = safeTrim(
13449    configValue('homeLabel') ||
13450    'Home'
13451  );
13452
13453  const parentUrlPrefix = normalizePrefix(
13454    configValue('parentUrlPrefix'),
13455    'category'
13456  );
13457
13458  const childUrlPrefix = normalizePrefix(
13459    configValue('childUrlPrefix'),
13460    'core'
13461  );
13462
13463  const separatorStyle = safeTrim(
13464    configValue('separatorStyle') ||
13465    'greater'
13466  ).toLowerCase();
13467
13468  const showHome = normalizeBoolean(
13469    configValue('showHome'),
13470    true
13471  );
13472
13473
13474  /* ============================================================
13475     DESIGN SETTINGS
13476     ============================================================ */
13477
13478  function applyDesignSettings() {
13479
13480    const alignmentValue = safeTrim(
13481      configValue(
13482        'linkAlignment',
13483        'alignment'
13484      ) || 'left'
13485    ).toLowerCase();
13486
13487    const alignmentMap = {
13488      left: 'flex-start',
13489      center: 'center',
13490      right: 'flex-end'
13491    };
13492
13493    const directionValue = safeTrim(
13494      configValue(
13495        'textDirection',
13496        'direction'
13497      ) || 'ltr'
13498    ).toLowerCase();
13499
13500
13501    rootEl.style.setProperty(
13502      '--bc-align',
13503      alignmentMap[alignmentValue] ||
13504      'flex-start'
13505    );
13506
13507    rootEl.style.setProperty(
13508      '--bc-direction',
13509      directionValue === 'rtl'
13510        ? 'rtl'
13511        : 'ltr'
13512    );
13513
13514    rootEl.style.setProperty(
13515      '--bc-horizontal-spacing',
13516      normalizeDimension(
13517        configValue('horizontalSpacing'),
13518        '10px'
13519      )
13520    );
13521
13522    rootEl.style.setProperty(
13523      '--bc-link-font-family',
13524      extractConfigValue(
13525        configValue(
13526          'linkFont',
13527          'linkFontFamily'
13528        ),
13529        'inherit'
13530      )
13531    );
13532
13533    rootEl.style.setProperty(
13534      '--bc-link-font-size',
13535      normalizeDimension(
13536        extractConfigValue(
13537          configValue(
13538            'linkFontSize',
13539            'linkSize'
13540          ),
13541          '11px'
13542        ),
13543        '11px'
13544      )
13545    );
13546
13547    rootEl.style.setProperty(
13548      '--bc-link-color',
13549      extractConfigValue(
13550        configValue('linkColor'),
13551        '#000000'
13552      )
13553    );
13554
13555    rootEl.style.setProperty(
13556      '--bc-link-font-weight',
13557      normalizeFontWeight(
13558        configValue('linkFontWeight'),
13559        normalizeBoolean(
13560          configValue('linkBold'),
13561          false
13562        )
13563          ? '700'
13564          : '400'
13565      )
13566    );
13567
13568    rootEl.style.setProperty(
13569      '--bc-link-text-decoration',
13570      normalizeTextDecoration(
13571        configValue('linkTextDecoration'),
13572        normalizeBoolean(
13573          configValue('linkUnderline'),
13574          true
13575        )
13576          ? 'underline'
13577          : 'none'
13578      )
13579    );
13580
13581    rootEl.style.setProperty(
13582      '--bc-link-font-style',
13583      normalizeFontStyle(
13584        configValue('linkFontStyle'),
13585        normalizeBoolean(
13586          configValue('linkItalic'),
13587          false
13588        )
13589          ? 'italic'
13590          : 'normal'
13591      )
13592    );
13593
13594    rootEl.style.setProperty(
13595      '--bc-hover-color',
13596      extractConfigValue(
13597        configValue(
13598          'hoverFontColor',
13599          'linkHoverColor'
13600        ),
13601        '#0000ff'
13602      )
13603    );
13604
13605    rootEl.style.setProperty(
13606      '--bc-hover-font-weight',
13607      normalizeFontWeight(
13608        configValue('hoverFontWeight'),
13609        normalizeBoolean(
13610          configValue('hoverBold'),
13611          false
13612        )
13613          ? '700'
13614          : '400'
13615      )
13616    );
13617
13618    rootEl.style.setProperty(
13619      '--bc-hover-text-decoration',
13620      normalizeTextDecoration(
13621        configValue('hoverTextDecoration'),
13622        normalizeBoolean(
13623          configValue('hoverUnderline'),
13624          true
13625        )
13626          ? 'underline'
13627          : 'none'
13628      )
13629    );
13630
13631    rootEl.style.setProperty(
13632      '--bc-hover-font-style',
13633      normalizeFontStyle(
13634        configValue('hoverFontStyle'),
13635        normalizeBoolean(
13636          configValue('hoverItalic'),
13637          false
13638        )
13639          ? 'italic'
13640          : 'normal'
13641      )
13642    );
13643
13644    rootEl.style.setProperty(
13645      '--bc-current-color',
13646      extractConfigValue(
13647        configValue('currentPageColor'),
13648        '#000000'
13649      )
13650    );
13651
13652    rootEl.style.setProperty(
13653      '--bc-current-font-weight',
13654      normalizeFontWeight(
13655        configValue(
13656          'currentPageFontWeight'
13657        ),
13658        normalizeBoolean(
13659          configValue('currentPageBold'),
13660          false
13661        )
13662          ? '700'
13663          : '400'
13664      )
13665    );
13666
13667    rootEl.style.setProperty(
13668      '--bc-current-text-decoration',
13669      normalizeTextDecoration(
13670        configValue(
13671          'currentPageTextDecoration'
13672        ),
13673        normalizeBoolean(
13674          configValue(
13675            'currentPageUnderline'
13676          ),
13677          true
13678        )
13679          ? 'underline'
13680          : 'none'
13681      )
13682    );
13683
13684    rootEl.style.setProperty(
13685      '--bc-current-font-style',
13686      normalizeFontStyle(
13687        configValue(
13688          'currentPageFontStyle'
13689        ),
13690        normalizeBoolean(
13691          configValue('currentPageItalic'),
13692          false
13693        )
13694          ? 'italic'
13695          : 'normal'
13696      )
13697    );
13698
13699    rootEl.style.setProperty(
13700      '--bc-folder-color',
13701      extractConfigValue(
13702        configValue('folderColor'),
13703        '#000000'
13704      )
13705    );
13706
13707    rootEl.style.setProperty(
13708      '--bc-folder-font-weight',
13709      normalizeFontWeight(
13710        configValue('folderFontWeight'),
13711        normalizeBoolean(
13712          configValue('folderBold'),
13713          false
13714        )
13715          ? '700'
13716          : '400'
13717      )
13718    );
13719
13720    rootEl.style.setProperty(
13721      '--bc-folder-text-decoration',
13722      normalizeTextDecoration(
13723        configValue(
13724          'folderTextDecoration'
13725        ),
13726        normalizeBoolean(
13727          configValue('folderUnderline'),
13728          true
13729        )
13730          ? 'underline'
13731          : 'none'
13732      )
13733    );
13734
13735    rootEl.style.setProperty(
13736      '--bc-folder-font-style',
13737      normalizeFontStyle(
13738        configValue('folderFontStyle'),
13739        normalizeBoolean(
13740          configValue('folderItalic'),
13741          false
13742        )
13743          ? 'italic'
13744          : 'normal'
13745      )
13746    );
13747
13748    rootEl.style.setProperty(
13749      '--bc-divider-color',
13750      extractConfigValue(
13751        configValue(
13752          'dividerColor',
13753          'iconColor'
13754        ),
13755        '#000000'
13756      )
13757    );
13758
13759    rootEl.style.setProperty(
13760      '--bc-divider-size',
13761      normalizeDimension(
13762        configValue(
13763          'dividerSize',
13764          'iconSize'
13765        ),
13766        '16px'
13767      )
13768    );
13769
13770  }
13771
13772
13773  applyDesignSettings();
13774
13775
13776  /* ============================================================
13777     URL / PAGE HELPERS
13778     ============================================================ */
13779
13780  function getBasePath() {
13781
13782    const candidates = [];
13783
13784    try {
13785      candidates.push(
13786        window.location.pathname || ''
13787      );
13788    } catch (error) {}
13789
13790    try {
13791      if (window.parent !== window) {
13792        candidates.push(
13793          window.parent.location.pathname || ''
13794        );
13795      }
13796    } catch (error) {}
13797
13798    try {
13799      if (window.top !== window) {
13800        candidates.push(
13801          window.top.location.pathname || ''
13802        );
13803      }
13804    } catch (error) {}
13805
13806
13807    for (const path of candidates) {
13808
13809      const directMatch = path.match(
13810        /^(\/(?:home\/)?site\/[0-9a-f]{32})/i
13811      );
13812
13813      if (directMatch) {
13814        return directMatch[1];
13815      }
13816
13817
13818      const legacyMatch = path.match(
13819        /^((?:\/[^/]+)*\/[0-9a-f]{32})/i
13820      );
13821
13822      if (legacyMatch) {
13823        return legacyMatch[1];
13824      }
13825
13826    }
13827
13828    return '';
13829  }
13830
13831
13832  const basePath = getBasePath();
13833
13834
13835  function buildPageUrl(pageName, type) {
13836
13837    const cleanName = stripHtml(pageName);
13838
13839    if (
13840      cleanName.toLowerCase() === 'home'
13841    ) {
13842      return basePath + '/';
13843    }
13844
13845    const slug =
13846      createSlugCandidates(cleanName)[0] ||
13847      slugify(cleanName);
13848
13849    const prefix =
13850      type === 'parent'
13851        ? parentUrlPrefix
13852        : childUrlPrefix;
13853
13854    const parts = [
13855      basePath,
13856      prefix,
13857      slug
13858    ].filter(Boolean);
13859
13860    return '/' +
13861      parts
13862        .join('/')
13863        .replace(/^\/+|\/+$/g, '') +
13864      '/';
13865  }
13866
13867
13868  function getCurrentPageSlug() {
13869
13870    const candidates = [];
13871
13872    try {
13873      candidates.push(
13874        window.location.pathname || ''
13875      );
13876    } catch (error) {}
13877
13878    try {
13879      if (window.parent !== window) {
13880        candidates.push(
13881          window.parent.location.pathname || ''
13882        );
13883      }
13884    } catch (error) {}
13885
13886    try {
13887      if (window.top !== window) {
13888        candidates.push(
13889          window.top.location.pathname || ''
13890        );
13891      }
13892    } catch (error) {}
13893
13894
13895    for (const path of candidates) {
13896
13897      const segments =
13898        path
13899          .split('/')
13900          .filter(Boolean);
13901
13902      if (!segments.length) {
13903        continue;
13904      }
13905
13906      const lastSegment =
13907        segments[segments.length - 1];
13908
13909      if (
13910        !/^[0-9a-f]{32}$/i.test(
13911          lastSegment
13912        ) &&
13913        ![
13914          'home',
13915          'site'
13916        ].includes(
13917          lastSegment.toLowerCase()
13918        )
13919      ) {
13920
13921        return safeTrim(
13922          lastSegment
13923        ).toLowerCase();
13924
13925      }
13926
13927    }
13928
13929    return null;
13930  }
13931
13932
13933  /* ============================================================
13934     NAVIGATION MAP
13935
13936     IMPORTANT:
13937     Parent 1 now means IMMEDIATE PARENT.
13938
13939     Example:
13940
13941     Color        Parent 1 = blank
13942     Red          Parent 1 = Color
13943     Blue         Parent 1 = Red
13944     Navy         Parent 1 = Blue
13945
13946     This allows unlimited nesting.
13947     ============================================================ */
13948
13949  function normalizePageKey(value) {
13950
13951    return stripHtml(value)
13952      .toLowerCase()
13953      .replace(/\s+/g, ' ')
13954      .trim();
13955
13956  }
13957
13958
13959  function buildNavMap(entries) {
13960
13961    const pages = [];
13962    const pagesByName = new Map();
13963
13964
13965    /* ----------------------------------------------------------
13966       FIRST PASS
13967
13968       Create one object for EVERY page.
13969
13970       We no longer separate the collection into only
13971       "parents" and "children".
13972       ---------------------------------------------------------- */
13973
13974    entries.forEach(entry => {
13975
13976      const row = readRowData(entry);
13977
13978      const pageName = stripHtml(
13979        field(
13980          row,
13981          'Page Name',
13982          'PAGE NAME',
13983          'pageName'
13984        )
13985      );
13986
13987      const parentName = stripHtml(
13988        field(
13989          row,
13990          'Parent 1',
13991          'PARENT 1',
13992          'parent1',
13993          'Parent',
13994          'PARENT',
13995          'parent'
13996        )
13997      );
13998
13999
14000      if (!pageName) {
14001        return;
14002      }
14003
14004
14005      const page = {
14006
14007        pageName: pageName,
14008
14009        parentName: parentName,
14010
14011        slugCandidates:
14012          createSlugCandidates(pageName),
14013
14014        children: [],
14015
14016        link: null
14017
14018      };
14019
14020
14021      pages.push(page);
14022
14023      pagesByName.set(
14024        normalizePageKey(pageName),
14025        page
14026      );
14027
14028    });
14029
14030
14031    /* ----------------------------------------------------------
14032       SECOND PASS
14033
14034       Connect every page to its immediate parent.
14035       ---------------------------------------------------------- */
14036
14037    pages.forEach(page => {
14038
14039      if (!page.parentName) {
14040        return;
14041      }
14042
14043      const parent = pagesByName.get(
14044        normalizePageKey(page.parentName)
14045      );
14046
14047      if (!parent) {
14048
14049        console.warn(
14050          '[Breadcrumb Widget] Parent page not found:',
14051          page.parentName,
14052          'for',
14053          page.pageName
14054        );
14055
14056        return;
14057      }
14058
14059
14060      parent.children.push(page);
14061
14062      page.parent = parent;
14063
14064    });
14065
14066
14067    /* ----------------------------------------------------------
14068       THIRD PASS
14069
14070       Determine links.
14071
14072       IMPORTANT:
14073       This preserves the old URL behavior.
14074
14075       A TOP-LEVEL page with children uses the parent/category
14076       URL prefix.
14077
14078       Anything that itself has a Parent 1 value continues using
14079       the child/core URL prefix.
14080
14081       Example:
14082
14083       Color:
14084       /category/color/
14085
14086       Red:
14087       /core/red/
14088
14089       Blue:
14090       /core/blue/
14091
14092       This means adding grandchildren does NOT unexpectedly
14093       change existing CORE page URLs.
14094       ---------------------------------------------------------- */
14095
14096    pages.forEach(page => {
14097
14098      const isTopLevel =
14099        !page.parentName;
14100
14101      const isTopLevelFolder =
14102        isTopLevel &&
14103        page.children.length > 0;
14104
14105      page.link = buildPageUrl(
14106        page.pageName,
14107        isTopLevelFolder
14108          ? 'parent'
14109          : 'child'
14110      );
14111
14112    });
14113
14114
14115    return {
14116      pages: pages,
14117      pagesByName: pagesByName
14118    };
14119
14120  }
14121
14122
14123  function slugMatches(
14124    candidates,
14125    currentSlug
14126  ) {
14127
14128    return (
14129      Array.isArray(candidates) &&
14130      candidates.includes(currentSlug)
14131    );
14132
14133  }
14134
14135
14136  /* ============================================================
14137     BUILD BREADCRUMB TRAIL
14138
14139     This walks UP the tree until there is no parent.
14140
14141     There is intentionally no fixed depth.
14142
14143     Example:
14144
14145     Dark Navy
14146       -> Navy
14147       -> Blue
14148       -> Red
14149       -> Color
14150       -> STOP
14151
14152     Then the result is reversed:
14153
14154     Color
14155       -> Red
14156       -> Blue
14157       -> Navy
14158       -> Dark Navy
14159     ============================================================ */
14160
14161  function buildTrail(
14162    navMap,
14163    currentSlug
14164  ) {
14165
14166    if (!currentSlug) {
14167      return null;
14168    }
14169
14170
14171    const currentPage =
14172      navMap.pages.find(
14173        page =>
14174          slugMatches(
14175            page.slugCandidates,
14176            currentSlug
14177          )
14178      );
14179
14180
14181    /* ----------------------------------------------------------
14182       Current page was not found in NAV MASTER.
14183       Preserve the original fallback behavior.
14184       ---------------------------------------------------------- */
14185
14186    if (!currentPage) {
14187
14188      return [{
14189        label: currentSlug
14190          .replace(/-/g, ' ')
14191          .replace(
14192            /\b\w/g,
14193            character =>
14194              character.toUpperCase()
14195          ),
14196
14197        href: null,
14198
14199        isCurrent: true,
14200
14201        noParent: true
14202      }];
14203
14204    }
14205
14206
14207    const chain = [];
14208
14209    const visited = new Set();
14210
14211    let page = currentPage;
14212
14213
14214    /* ----------------------------------------------------------
14215       WALK UP THROUGH THE PARENTS
14216       ---------------------------------------------------------- */
14217
14218    while (page) {
14219
14220      const pageKey =
14221        normalizePageKey(
14222          page.pageName
14223        );
14224
14225
14226      /* --------------------------------------------------------
14227         Cycle protection.
14228
14229         Example of bad collection data:
14230
14231         Red -> Blue
14232         Blue -> Red
14233
14234         Without this protection the widget could loop forever.
14235         -------------------------------------------------------- */
14236
14237      if (visited.has(pageKey)) {
14238
14239        console.warn(
14240          '[Breadcrumb Widget] Circular parent relationship detected at:',
14241          page.pageName
14242        );
14243
14244        break;
14245      }
14246
14247
14248      visited.add(pageKey);
14249
14250      chain.push(page);
14251
14252
14253      if (!page.parentName) {
14254        break;
14255      }
14256
14257
14258      const parent =
14259        navMap.pagesByName.get(
14260          normalizePageKey(
14261            page.parentName
14262          )
14263        );
14264
14265
14266      if (!parent) {
14267
14268        console.warn(
14269          '[Breadcrumb Widget] Parent page not found:',
14270          page.parentName,
14271          'for',
14272          page.pageName
14273        );
14274
14275        break;
14276      }
14277
14278
14279      page = parent;
14280
14281    }
14282
14283
14284    /* ----------------------------------------------------------
14285       We collected:
14286
14287       Current -> Parent -> Grandparent
14288
14289       Reverse it to:
14290
14291       Grandparent -> Parent -> Current
14292       ---------------------------------------------------------- */
14293
14294    chain.reverse();
14295
14296
14297    return chain.map(
14298      (chainPage, index) => {
14299
14300        const isCurrent =
14301          index === chain.length - 1;
14302
14303        return {
14304
14305          label:
14306            chainPage.pageName,
14307
14308          href:
14309            isCurrent
14310              ? null
14311              : chainPage.link,
14312
14313          isCurrent:
14314            isCurrent,
14315
14316          /*
14317           Any ancestor that contains children may use
14318           the folder styling.
14319          */
14320          isFolder:
14321            !isCurrent &&
14322            chainPage.children.length > 0
14323
14324        };
14325
14326      }
14327    );
14328
14329  }
14330
14331
14332  /* ============================================================
14333     SEPARATOR
14334     ============================================================ */
14335
14336  function getSeparator() {
14337
14338    const separatorMap = {
14339      greater: '>',
14340      doublegreater: '>>',
14341      slash: '/',
14342      pipe: '|',
14343      dash: '-',
14344      arrow: '→',
14345      dot: '·'
14346    };
14347
14348
14349    if (
14350      separatorMap[separatorStyle]
14351    ) {
14352
14353      return (
14354        '<span class="bc-separator" aria-hidden="true">' +
14355        escapeHtml(
14356          separatorMap[separatorStyle]
14357        ) +
14358        '</span>'
14359      );
14360
14361    }
14362
14363
14364    return (
14365      '<span class="bc-separator" aria-hidden="true">' +
14366        '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">' +
14367          '<polyline points="9 18 15 12 9 6"></polyline>' +
14368        '</svg>' +
14369      '</span>'
14370    );
14371
14372  }
14373
14374
14375  /* ============================================================
14376     CRUMB HTML
14377     ============================================================ */
14378
14379  function createCrumbMarkup(
14380    crumb,
14381    position,
14382    separator
14383  ) {
14384
14385    const className =
14386      crumb.isFolder
14387        ? 'bc-link bc-folder'
14388        : 'bc-link';
14389
14390
14391    if (crumb.href) {
14392
14393      return (
14394        '<li class="bc-item" ' +
14395          'itemprop="itemListElement" ' +
14396          'itemscope ' +
14397          'itemtype="https://schema.org/ListItem">' +
14398
14399          separator +
14400
14401          '<a class="' +
14402            className +
14403            '" href="' +
14404            escapeHtml(crumb.href) +
14405            '" itemprop="item">' +
14406
14407            '<span itemprop="name">' +
14408              escapeHtml(crumb.label) +
14409            '</span>' +
14410
14411          '</a>' +
14412
14413          '<meta itemprop="position" content="' +
14414            position +
14415          '">' +
14416
14417        '</li>'
14418      );
14419
14420    }
14421
14422
14423    return (
14424      '<li class="bc-item" ' +
14425        'itemprop="itemListElement" ' +
14426        'itemscope ' +
14427        'itemtype="https://schema.org/ListItem">' +
14428
14429        separator +
14430
14431        '<span class="bc-current" ' +
14432          'itemprop="name" ' +
14433          'aria-current="page">' +
14434
14435          escapeHtml(crumb.label) +
14436
14437        '</span>' +
14438
14439        '<meta itemprop="position" content="' +
14440          position +
14441        '">' +
14442
14443      '</li>'
14444    );
14445
14446  }
14447
14448
14449  /* ============================================================
14450     RENDER
14451     ============================================================ */
14452
14453  function render(
14454    trail,
14455    currentSlug
14456  ) {
14457
14458    const separator =
14459      getSeparator();
14460
14461    const items = [];
14462
14463
14464    if (showHome) {
14465
14466      items.push(
14467        '<li class="bc-item" ' +
14468          'itemprop="itemListElement" ' +
14469          'itemscope ' +
14470          'itemtype="https://schema.org/ListItem">' +
14471
14472          '<a class="bc-link" ' +
14473            'href="' +
14474            escapeHtml(basePath + '/') +
14475            '" itemprop="item">' +
14476
14477            '<span itemprop="name">' +
14478              escapeHtml(homeLabel) +
14479            '</span>' +
14480
14481          '</a>' +
14482
14483          '<meta itemprop="position" content="1">' +
14484
14485        '</li>'
14486      );
14487
14488    }
14489
14490
14491    if (
14492      !trail ||
14493      !trail.length
14494    ) {
14495
14496      if (currentSlug) {
14497
14498        const label =
14499          currentSlug
14500            .replace(/-/g, ' ')
14501            .replace(
14502              /\b\w/g,
14503              character =>
14504                character.toUpperCase()
14505            );
14506
14507
14508        items.push(
14509          createCrumbMarkup(
14510            {
14511              label: label,
14512              href: null,
14513              isCurrent: true
14514            },
14515            showHome ? 2 : 1,
14516            showHome
14517              ? separator
14518              : ''
14519          )
14520        );
14521
14522
14523        listEl.innerHTML =
14524          items.join('');
14525
14526        rootEl.hidden = false;
14527
14528        return;
14529      }
14530
14531
14532      rootEl.hidden = true;
14533
14534      return;
14535    }
14536
14537
14538    trail.forEach(
14539      (crumb, index) => {
14540
14541        items.push(
14542          createCrumbMarkup(
14543
14544            crumb,
14545
14546            (showHome ? 2 : 1) +
14547              index,
14548
14549            (
14550              showHome ||
14551              index > 0
14552            )
14553              ? separator
14554              : ''
14555
14556          )
14557        );
14558
14559      }
14560    );
14561
14562
14563    listEl.innerHTML =
14564      items.join('');
14565
14566    rootEl.hidden = false;
14567
14568  }
14569
14570
14571  /* ============================================================
14572     DUDA EDITOR DETECTION
14573     ============================================================ */
14574
14575  function isEditorCanvas() {
14576
14577    try {
14578
14579      const topHref =
14580        window.top.location.href ||
14581        '';
14582
14583      return (
14584        topHref.includes('/home/site/') &&
14585        !topHref.includes('#preview')
14586      );
14587
14588    } catch (error) {
14589
14590      return Boolean(
14591        data.inEditor
14592      );
14593
14594    }
14595
14596  }
14597
14598
14599  /* ============================================================
14600     EDITOR PREVIEW
14601     ============================================================ */
14602
14603  function renderEditorPreview() {
14604
14605    const separator =
14606      getSeparator();
14607
14608    const items = [];
14609
14610
14611    if (showHome) {
14612
14613      items.push(
14614        '<li class="bc-item">' +
14615          '<a class="bc-link" href="#">' +
14616            escapeHtml(homeLabel) +
14617          '</a>' +
14618        '</li>'
14619      );
14620
14621    }
14622
14623
14624    /*
14625      Show three levels in the editor preview so the
14626      new hierarchy is visible while designing.
14627    */
14628
14629    items.push(
14630      '<li class="bc-item">' +
14631        (showHome ? separator : '') +
14632        '<a class="bc-link bc-folder" href="#">' +
14633          'Parent Page' +
14634        '</a>' +
14635      '</li>'
14636    );
14637
14638
14639    items.push(
14640      '<li class="bc-item">' +
14641        separator +
14642        '<a class="bc-link bc-folder" href="#">' +
14643          'Child Page' +
14644        '</a>' +
14645      '</li>'
14646    );
14647
14648
14649    items.push(
14650      '<li class="bc-item">' +
14651        separator +
14652        '<span class="bc-current" aria-current="page">' +
14653          'Current Page' +
14654        '</span>' +
14655      '</li>'
14656    );
14657
14658
14659    listEl.innerHTML =
14660      items.join('');
14661
14662    rootEl.hidden = false;
14663
14664  }
14665
14666
14667  /* ============================================================
14668     LOAD NAV MASTER
14669     ============================================================ */
14670
14671  dmAPI
14672    .loadCollectionsAPI()
14673
14674    .then(
14675      collectionApi =>
14676        collectionApi
14677          .data('06 NAV MASTER')
14678          .pageSize(100)
14679          .get()
14680    )
14681
14682    .then(result => {
14683
14684      const entries =
14685        Array.isArray(
14686          result &&
14687          result.values
14688        )
14689          ? result.values
14690          : [];
14691
14692
14693      const navMap =
14694        buildNavMap(entries);
14695
14696
14697      const currentSlug =
14698        getCurrentPageSlug();
14699
14700
14701      const trail =
14702        buildTrail(
14703          navMap,
14704          currentSlug
14705        );
14706
14707
14708      if (
14709        isEditorCanvas() &&
14710        !trail
14711      ) {
14712
14713        renderEditorPreview();
14714
14715      } else {
14716
14717        render(
14718          trail,
14719          currentSlug
14720        );
14721
14722      }
14723
14724    })
14725
14726    .catch(error => {
14727
14728      console.warn(
14729        '[Breadcrumb Widget] Could not load 06 NAV MASTER.',
14730        error
14731      );
14732
14733
14734      if (isEditorCanvas()) {
14735
14736        renderEditorPreview();
14737
14738      } else {
14739
14740        rootEl.hidden = true;
14741
14742      }
14743
14744    });
14745
14746});
14747    };
14748</script>
14748
14749
14750<script>
14751    window.customWidgetsFunctions["f19f9b2806104edb81a7816f71967887~13"] = function (element, data, api) {
14752        window.dmAPI.runOnReady('copy-block-link-widget', async () => {
14753  const anchor = element.querySelector('.copy-block-link-anchor');
14754  const buttonElement = element.querySelector('.copy-block-link-button');
14755
14756  if (!anchor || !buttonElement) return;
14757
14758  const config = data.config || {};
14759
14760  function safeTrim(value) {
14761    return value == null ? '' : String(value).trim();
14762  }
14763
14764  function stripHtml(value) {
14765    const temporaryElement = document.createElement('div');
14766    temporaryElement.innerHTML = safeTrim(value);
14767
14768    return safeTrim(
14769      temporaryElement.textContent ||
14770      temporaryElement.innerText ||
14771      ''
14772    );
14773  }
14774
14775  function removeDiacritics(value) {
14776    const text = safeTrim(value);
14777
14778    if (typeof text.normalize !== 'function') {
14779      return text;
14780    }
14781
14782    return text
14783      .normalize('NFKD')
14784      .replace(/[\u0300-\u036f]/g, '');
14785  }
14786
14787  function slugify(value) {
14788    return removeDiacritics(value)
14789      .toLowerCase()
14790      .replace(/[’']/g, '')
14791      .replace(/[^a-z0-9]+/g, '-')
14792      .replace(/-+/g, '-')
14793      .replace(/^-+|-+$/g, '');
14794  }
14795
14796  /*
14797    Creates possible URL versions.
14798
14799    Showers & Doors:
14800    - showers-and-doors
14801    - showers-doors
14802
14803    Showers and Doors:
14804    - showers-and-doors
14805    - showers-doors
14806
14807    A/C Repair:
14808    - a-c-repair
14809    - ac-repair
14810  */
14811  function createSlugCandidates(value) {
14812    const originalText = stripHtml(value);
14813    const candidates = [];
14814    const seen = new Set();
14815
14816    function addCandidate(candidateText) {
14817      const candidate = slugify(candidateText);
14818
14819      if (candidate && !seen.has(candidate)) {
14820        seen.add(candidate);
14821        candidates.push(candidate);
14822      }
14823    }
14824
14825    if (!originalText) {
14826      return candidates;
14827    }
14828
14829    const ampersandVariants = [
14830      originalText.replace(/&/g, ' and '),
14831      originalText.replace(/&/g, ' '),
14832      originalText
14833    ];
14834
14835    ampersandVariants.forEach(ampersandVariant => {
14836      const andVariants = [
14837        ampersandVariant,
14838        ampersandVariant.replace(/\band\b/gi, ' ')
14839      ];
14840
14841      andVariants.forEach(andVariant => {
14842        /*
14843          Treat the slash as a separator.
14844
14845          A/C Repair becomes a-c-repair.
14846        */
14847        addCandidate(
14848          andVariant.replace(/\s*[\\/]+\s*/g, ' ')
14849        );
14850
14851        /*
14852          Remove the slash completely.
14853
14854          A/C Repair becomes ac-repair.
14855        */
14856        addCandidate(
14857          andVariant.replace(/\s*[\\/]+\s*/g, '')
14858        );
14859      });
14860    });
14861
14862    return candidates;
14863  }
14864
14865  /*
14866    Accepts any of these page_item_url formats:
14867
14868    college-transcripts
14869    /category/college-transcripts/
14870    https://example.com/category/college-transcripts/
14871  */
14872  function normalizeStoredSlug(value) {
14873    let slug = safeTrim(value);
14874
14875    if (!slug) return '';
14876
14877    try {
14878      slug = decodeURIComponent(slug);
14879    } catch (e) {
14880      // Keep the original value.
14881    }
14882
14883    slug = slug
14884      .split('#')[0]
14885      .split('?')[0]
14886      .replace(/^https?:\/\/[^/]+/i, '')
14887      .replace(/^\/+|\/+$/g, '');
14888
14889    if (slug.includes('/')) {
14890      slug = slug
14891        .split('/')
14892        .filter(Boolean)
14893        .pop() || '';
14894    }
14895
14896    return safeTrim(slug).toLowerCase();
14897  }
14898
14899  function getCurrentSlug() {
14900    const pathParts = (window.location.pathname || '')
14901      .split('/')
14902      .filter(Boolean);
14903
14904    return normalizeStoredSlug(
14905      pathParts.pop() || ''
14906    );
14907  }
14908
14909  /*
14910    Checks several properties when finding the source row.
14911
14912    page_item_url is preferred, but the other values help
14913    with differences in Duda collection response structures.
14914  */
14915  function getSourceEntrySlugs(entry) {
14916    if (!entry) return [];
14917
14918    const row = entry.data || {};
14919
14920    return [
14921      entry.page_item_url,
14922      row.page_item_url,
14923      entry.item,
14924      row.item,
14925      entry.name,
14926      row.name,
14927      entry.url,
14928      row.url
14929    ]
14930      .map(normalizeStoredSlug)
14931      .filter(Boolean);
14932  }
14933
14934  /*
14935    Destination pages must match page_item_url.
14936  */
14937  function getDestinationEntrySlugs(entry) {
14938    if (!entry) return [];
14939
14940    const row = entry.data || {};
14941
14942    return [
14943      entry.page_item_url,
14944      row.page_item_url
14945    ]
14946      .map(normalizeStoredSlug)
14947      .filter(Boolean);
14948  }
14949
14950  function findSourceEntry(
14951    entries,
14952    currentSlug,
14953    sourcePageType
14954  ) {
14955    if (
14956      !Array.isArray(entries) ||
14957      entries.length === 0
14958    ) {
14959      return null;
14960    }
14961
14962    /*
14963      Match the current page slug when possible.
14964    */
14965    if (
14966      currentSlug &&
14967      currentSlug !== '~page-item~'
14968    ) {
14969      const matchedEntry = entries.find(entry =>
14970        getSourceEntrySlugs(entry).includes(
14971          currentSlug
14972        )
14973      );
14974
14975      if (matchedEntry) {
14976        return matchedEntry;
14977      }
14978    }
14979
14980    /*
14981      01 HOME is expected to have one row.
14982
14983      Only Home Page mode may use the first row.
14984      Category Page mode must match the current slug.
14985    */
14986    if (sourcePageType === 'home') {
14987      return entries[0];
14988    }
14989
14990    return null;
14991  }
14992
14993  function findDestination(
14994    entries,
14995    slugCandidates
14996  ) {
14997    if (
14998      !Array.isArray(entries) ||
14999      !Array.isArray(slugCandidates)
15000    ) {
15001      return null;
15002    }
15003
15004    /*
15005      Candidate order is preserved.
15006
15007      This means the more natural slug is checked before
15008      alternate ampersand and slash versions.
15009    */
15010    for (const candidate of slugCandidates) {
15011      const matchedEntry = entries.find(entry =>
15012        getDestinationEntrySlugs(entry).includes(
15013          candidate
15014        )
15015      );
15016
15017      if (matchedEntry) {
15018        return {
15019          entry: matchedEntry,
15020          slug: candidate
15021        };
15022      }
15023    }
15024
15025    return null;
15026  }
15027
15028  function cleanPrefix(value, fallbackValue) {
15029    const configuredValue = safeTrim(value);
15030    const prefix =
15031      configuredValue || fallbackValue;
15032
15033    return safeTrim(prefix)
15034      .replace(/^\/+|\/+$/g, '');
15035  }
15036
15037  function getSitePrefixFromOwnFrame() {
15038    const ownPath =
15039      window.location.pathname || '';
15040
15041    const match = ownPath.match(
15042      /^(\/site\/[^/]+)/
15043    );
15044
15045    return match ? match[1] : '';
15046  }
15047
15048  function getPreviewQueryString() {
15049    try {
15050      const currentParameters =
15051        new URLSearchParams(
15052          window.location.search || ''
15053        );
15054
15055      const outputParameters =
15056        new URLSearchParams();
15057
15058      [
15059        'showOriginal',
15060        'preview',
15061        'insitepreview',
15062        'dm_device',
15063        't',
15064        'dm_try_mode',
15065        'dm_checkSync'
15066      ].forEach(key => {
15067        if (currentParameters.has(key)) {
15068          outputParameters.set(
15069            key,
15070            currentParameters.get(key)
15071          );
15072        }
15073      });
15074
15075      const queryString =
15076        outputParameters.toString();
15077
15078      return queryString
15079        ? '?' + queryString
15080        : '';
15081    } catch (e) {
15082      return '';
15083    }
15084  }
15085
15086  /*
15087    Block navigation only in the normal editor canvas.
15088
15089    Navigation remains available in:
15090    - Editor Preview
15091    - Preview links
15092    - Published websites
15093  */
15094  function isEditorCanvasOnly() {
15095    if (!data.inEditor) {
15096      return false;
15097    }
15098
15099    try {
15100      const topHref =
15101        window.top.location.href || '';
15102
15103      if (topHref.includes('#preview')) {
15104        return false;
15105      }
15106
15107      return true;
15108    } catch (e) {
15109      return true;
15110    }
15111  }
15112
15113  function setPendingState() {
15114    anchor.removeAttribute('href');
15115    anchor.setAttribute(
15116      'aria-disabled',
15117      'true'
15118    );
15119    anchor.setAttribute(
15120      'aria-busy',
15121      'true'
15122    );
15123    anchor.setAttribute(
15124      'tabindex',
15125      '-1'
15126    );
15127
15128    anchor.classList.remove('is-disabled');
15129    buttonElement.classList.remove(
15130      'is-disabled'
15131    );
15132  }
15133
15134  function disableButton() {
15135    anchor.removeAttribute('href');
15136    anchor.setAttribute(
15137      'aria-disabled',
15138      'true'
15139    );
15140    anchor.setAttribute(
15141      'aria-busy',
15142      'false'
15143    );
15144    anchor.setAttribute(
15145      'tabindex',
15146      '-1'
15147    );
15148
15149    anchor.classList.add('is-disabled');
15150    buttonElement.classList.add(
15151      'is-disabled'
15152    );
15153  }
15154
15155  function enableButton(destination) {
15156    anchor.setAttribute(
15157      'href',
15158      destination
15159    );
15160
15161    anchor.setAttribute(
15162      'aria-disabled',
15163      'false'
15164    );
15165
15166    anchor.setAttribute(
15167      'aria-busy',
15168      'false'
15169    );
15170
15171    anchor.removeAttribute('tabindex');
15172
15173    anchor.classList.remove('is-disabled');
15174    buttonElement.classList.remove(
15175      'is-disabled'
15176    );
15177  }
15178
15179  /*
15180    Use one click handler for both disabled-state
15181    protection and editor-canvas protection.
15182  */
15183  anchor.addEventListener(
15184    'click',
15185    event => {
15186      const isDisabled =
15187        anchor.getAttribute(
15188          'aria-disabled'
15189        ) === 'true';
15190
15191      if (
15192        isDisabled ||
15193        isEditorCanvasOnly()
15194      ) {
15195        event.preventDefault();
15196        event.stopPropagation();
15197        event.stopImmediatePropagation();
15198
15199        return false;
15200      }
15201    },
15202    true
15203  );
15204
15205  /*
15206    Share collection requests between multiple copies
15207    of this widget on the same page.
15208  */
15209  function getCollectionCache() {
15210    if (
15211      !window.__copyBlockLinkCollectionCache
15212    ) {
15213      window.__copyBlockLinkCollectionCache =
15214        {};
15215    }
15216
15217    return window
15218      .__copyBlockLinkCollectionCache;
15219  }
15220
15221  function getCollectionsApiPromise() {
15222    if (
15223      !window.__copyBlockLinkCollectionsApiPromise
15224    ) {
15225      window
15226        .__copyBlockLinkCollectionsApiPromise =
15227        dmAPI
15228          .loadCollectionsAPI()
15229          .catch(error => {
15230            delete window
15231              .__copyBlockLinkCollectionsApiPromise;
15232
15233            throw error;
15234          });
15235    }
15236
15237    return window
15238      .__copyBlockLinkCollectionsApiPromise;
15239  }
15240
15241  function loadCollectionEntries(
15242    collectionName
15243  ) {
15244    const collectionCache =
15245      getCollectionCache();
15246
15247    if (!collectionCache[collectionName]) {
15248      collectionCache[collectionName] =
15249        getCollectionsApiPromise()
15250          .then(async collectionApi => {
15251            const PAGE_SIZE = 100;
15252            const MAX_RECORDS = 200;
15253
15254            async function getPage(pageNumber) {
15255              let request =
15256                collectionApi
15257                  .data(collectionName);
15258
15259              if (
15260                request &&
15261                typeof request.pageSize === 'function'
15262              ) {
15263                request =
15264                  request.pageSize(PAGE_SIZE);
15265              }
15266
15267              if (
15268                request &&
15269                typeof request.pageNumber === 'function'
15270              ) {
15271                request =
15272                  request.pageNumber(pageNumber);
15273              }
15274
15275              const result =
15276                await request.get();
15277
15278              return Array.isArray(
15279                result && result.values
15280              )
15281                ? result.values
15282                : [];
15283            }
15284
15285            /*
15286              Duda collection pages are capped at 100 records.
15287
15288              Load page 0 first. If it is full, load page 1 and
15289              combine both pages so this widget can search up to
15290              200 records.
15291            */
15292            const firstPage =
15293              await getPage(0);
15294
15295            let entries =
15296              [...firstPage];
15297
15298            if (
15299              firstPage.length === PAGE_SIZE &&
15300              entries.length < MAX_RECORDS
15301            ) {
15302              const secondPage =
15303                await getPage(1);
15304
15305              entries =
15306                entries.concat(secondPage);
15307            }
15308
15309            return entries.slice(
15310              0,
15311              MAX_RECORDS
15312            );
15313          })
15314          .catch(error => {
15315            delete collectionCache[
15316              collectionName
15317            ];
15318
15319            throw error;
15320          });
15321    }
15322
15323    return collectionCache[
15324      collectionName
15325    ];
15326  }
15327
15328  setPendingState();
15329
15330  const buttonName =
15331    safeTrim(config.buttonName) ||
15332    'Learn More';
15333
15334  anchor.setAttribute(
15335    'aria-label',
15336    buttonName
15337  );
15338
15339  try {
15340    /*
15341      Accepted Page Type values:
15342
15343      home
15344      Home Page
15345      category
15346      Category Page
15347    */
15348    const sourcePageTypeValue =
15349      safeTrim(
15350        config.sourcePageType
15351      ).toLowerCase();
15352
15353    const sourcePageType =
15354      sourcePageTypeValue === 'category' ||
15355      sourcePageTypeValue ===
15356        'category page'
15357        ? 'category'
15358        : 'home';
15359
15360    const sourceCollectionName =
15361      sourcePageType === 'category'
15362        ? '02 CATEGORY'
15363        : '01 HOME';
15364
15365    const currentSlug =
15366      getCurrentSlug();
15367
15368    /*
15369      Load the source collection.
15370    */
15371    const sourceEntries =
15372      await loadCollectionEntries(
15373        sourceCollectionName
15374      );
15375
15376    const sourceEntry =
15377      findSourceEntry(
15378        sourceEntries,
15379        currentSlug,
15380        sourcePageType
15381      );
15382
15383    if (!sourceEntry) {
15384      disableButton();
15385      return;
15386    }
15387
15388    /*
15389      Accepts either:
15390
15391      1
15392      COPY BLOCK HEADLINE 1
15393      COPY BLOCK 1 HEADLINE
15394    */
15395    const configuredBlockValue =
15396      safeTrim(
15397        config.copyBlockNumber
15398      );
15399
15400    const blockNumberMatch =
15401      configuredBlockValue.match(
15402        /(?:^|\D)(10|[1-9])(?:\D|$)/
15403      );
15404
15405    const blockNumber =
15406      blockNumberMatch
15407        ? blockNumberMatch[1]
15408        : '1';
15409
15410    const fieldName =
15411      'COPY BLOCK ' +
15412      blockNumber +
15413      ' HEADLINE';
15414
15415    const sourceRow =
15416      sourceEntry.data ||
15417      sourceEntry;
15418
15419    const rawHeadline =
15420      sourceRow[fieldName];
15421
15422    const slugCandidates =
15423      createSlugCandidates(
15424        rawHeadline
15425      );
15426
15427    if (
15428      slugCandidates.length === 0
15429    ) {
15430      disableButton();
15431      return;
15432    }
15433
15434    /*
15435      Load both possible destination collections.
15436
15437      Reuse 02 CATEGORY if it was already loaded
15438      as the source collection.
15439    */
15440    const categoryEntriesPromise =
15441      sourceCollectionName ===
15442      '02 CATEGORY'
15443        ? Promise.resolve(sourceEntries)
15444        : loadCollectionEntries(
15445            '02 CATEGORY'
15446          ).catch(() => []);
15447
15448    const coreEntriesPromise =
15449      loadCollectionEntries(
15450        '03 CORE'
15451      ).catch(() => []);
15452
15453    const [
15454      categoryEntries,
15455      coreEntries
15456    ] = await Promise.all([
15457      categoryEntriesPromise,
15458      coreEntriesPromise
15459    ]);
15460
15461    const categoryPrefix =
15462      cleanPrefix(
15463        config.categoryPrefix,
15464        'category'
15465      );
15466
15467    const corePrefix =
15468      cleanPrefix(
15469        config.corePrefix,
15470        'core'
15471      );
15472
15473    /*
15474      Home Page destination priority:
15475
15476      1. 02 CATEGORY
15477      2. 03 CORE
15478
15479      Category Page destination priority:
15480
15481      1. 03 CORE
15482      2. 02 CATEGORY
15483    */
15484    const destinationSearchOrder =
15485      sourcePageType === 'home'
15486        ? [
15487            {
15488              entries:
15489                categoryEntries,
15490              prefix:
15491                categoryPrefix
15492            },
15493            {
15494              entries:
15495                coreEntries,
15496              prefix:
15497                corePrefix
15498            }
15499          ]
15500        : [
15501            {
15502              entries:
15503                coreEntries,
15504              prefix:
15505                corePrefix
15506            },
15507            {
15508              entries:
15509                categoryEntries,
15510              prefix:
15511                categoryPrefix
15512            }
15513          ];
15514
15515    let destinationMatch = null;
15516    let destinationPrefix = '';
15517
15518    for (
15519      const destinationCollection
15520      of destinationSearchOrder
15521    ) {
15522      const match =
15523        findDestination(
15524          destinationCollection.entries,
15525          slugCandidates
15526        );
15527
15528      if (match) {
15529        destinationMatch = match;
15530        destinationPrefix =
15531          destinationCollection.prefix;
15532
15533        break;
15534      }
15535    }
15536
15537    /*
15538      Never create an unverified URL.
15539
15540      The generated slug must exist as a
15541      page_item_url in 02 CATEGORY or 03 CORE.
15542    */
15543    if (
15544      !destinationMatch ||
15545      !destinationMatch.slug
15546    ) {
15547      disableButton();
15548      return;
15549    }
15550
15551    const destinationPath =
15552      '/' +
15553      [
15554        destinationPrefix,
15555        destinationMatch.slug
15556      ]
15557        .filter(Boolean)
15558        .join('/') +
15559      '/';
15560
15561    const destination =
15562      getSitePrefixFromOwnFrame() +
15563      destinationPath +
15564      getPreviewQueryString();
15565
15566    enableButton(
15567      destination || destinationPath
15568    );
15569  } catch (e) {
15570    disableButton();
15571  }
15572});
15573    };
15574</script>
15574
15575
15576<script>
15577    window.customWidgetsFunctions["9015bcbf1ace437aa6f0e66d5bb2e7d3~25"] = function (element, data, api) {
15578        $(document).ready(function(){
15579    $.getScript('https://widget.hibu.us/static/js/index.js')
15580    //$.getScript('//survlywidget-uat.firebaseapp.com/static/js/index.js')
15581    .done(function(){
15582        dmAPI.runOnReady('hibu_header_reviews',function(){
15583            window.renderApp();
15584        });
15585        
15586    })
15587    .fail(function(){
15588        console.log("failed to load header reviews js file")
15589    });
15590    
15591    element.children[0].removeAttribute(`rteblock"`)
15592    var merchantId = data.config.merchantId
15593    merchantId = merchantId.replace(/<p.*?>/g, '')
15594    merchantId = merchantId.replace(/&quot;/g, '"')
15595    merchantId = merchantId.replace(/\<\/p\>/g, '')
15596    
15597    element.children[0].setAttribute('data-merchant', merchantId)
15598})
15599
15600
15601    };
15602</script>
15602
15603
15604<script>
15605    window.customWidgetsFunctions["22eebe93636d4d1295bb7b863c039867~28"] = function (element, data, api) {
15606        null
15607    };
15608</script>
15608
15609
15610<script>
15611    window.customWidgetsFunctions["2133bfee2cb24e0c9db9468154881b94~3"] = function (element, data, api) {
15612        window.dmAPI.runOnReady('HH2', () => {
15613
15614  const STYLE_ID = 'HH2-theme-style';
15615  const HH2_SELECTOR = '.widget-wrapper.HH2';
15616
15617  let updateTimer = null;
15618  let lastSignature = '';
15619
15620  /**
15621   * Find a usable H2 that is NOT inside the HH2 widget.
15622   */
15623  function findThemeH2() {
15624    const candidates = Array.from(
15625      document.querySelectorAll('#dm h2, h2')
15626    );
15627
15628    for (const el of candidates) {
15629
15630      // Do not use our own HH2 widget as the source
15631      if (el.closest(HH2_SELECTOR)) {
15632        continue;
15633      }
15634
15635      // Ignore disconnected elements
15636      if (!el.isConnected) {
15637        continue;
15638      }
15639
15640      const computed = window.getComputedStyle(el);
15641
15642      // Ignore elements that are completely unavailable
15643      if (
15644        computed.display === 'none' ||
15645        computed.visibility === 'hidden'
15646      ) {
15647        continue;
15648      }
15649
15650      // Make sure we actually got useful typography
15651      if (!computed.fontFamily || !computed.fontSize) {
15652        continue;
15653      }
15654
15655      return el;
15656    }
15657
15658    return null;
15659  }
15660
15661
15662  /**
15663   * Read H2 typography and apply it to HH2 widgets.
15664   */
15665  function applyTheme() {
15666
15667    const h2Sample = findThemeH2();
15668
15669    if (!h2Sample) {
15670      return false;
15671    }
15672
15673    const computed = window.getComputedStyle(h2Sample);
15674
15675    const styles = {
15676      fontFamily: computed.fontFamily,
15677      fontSize: computed.fontSize,
15678      fontWeight: computed.fontWeight,
15679      lineHeight: computed.lineHeight,
15680      letterSpacing: computed.letterSpacing,
15681      textTransform: computed.textTransform,
15682      fontStyle: computed.fontStyle,
15683      fontVariant: computed.fontVariant,
15684      fontStretch: computed.fontStretch
15685    };
15686
15687    /*
15688     * Create a signature so we're not constantly rewriting
15689     * the stylesheet when nothing has actually changed.
15690     */
15691    const signature = JSON.stringify(styles);
15692
15693    if (signature === lastSignature) {
15694      return true;
15695    }
15696
15697    lastSignature = signature;
15698
15699
15700    /*
15701     * Target the widget plus all common Duda RTE descendants.
15702     * The wildcard selector is intentional so inline/nested
15703     * typography cannot override the inherited H2 theme.
15704     */
15705    const selectors = [
15706      '#dm .dmInner .widget-wrapper.HH2 .dmNewParagraph',
15707      '#dm .dmInner .widget-wrapper.HH2 .dmNewParagraph p',
15708      '#dm .dmInner .widget-wrapper.HH2 .dmNewParagraph span',
15709      '#dm .dmInner .widget-wrapper.HH2 .dmNewParagraph .rteBlock',
15710      '#dm .dmInner .widget-wrapper.HH2 .dmNewParagraph .rteBlock *',
15711
15712      '.dmInner .widget-wrapper.HH2 .dmNewParagraph',
15713      '.dmInner .widget-wrapper.HH2 .dmNewParagraph p',
15714      '.dmInner .widget-wrapper.HH2 .dmNewParagraph span',
15715      '.dmInner .widget-wrapper.HH2 .dmNewParagraph .rteBlock',
15716      '.dmInner .widget-wrapper.HH2 .dmNewParagraph .rteBlock *',
15717
15718      '.widget-wrapper.HH2 .dmNewParagraph',
15719      '.widget-wrapper.HH2 .dmNewParagraph p',
15720      '.widget-wrapper.HH2 .dmNewParagraph span',
15721      '.widget-wrapper.HH2 .dmNewParagraph .rteBlock',
15722      '.widget-wrapper.HH2 .dmNewParagraph .rteBlock *'
15723    ];
15724
15725    const css = `
15726${selectors.join(',\n')} {
15727  font-family: ${styles.fontFamily} !important;
15728  font-size: ${styles.fontSize} !important;
15729  font-weight: ${styles.fontWeight} !important;
15730  line-height: ${styles.lineHeight} !important;
15731  letter-spacing: ${styles.letterSpacing} !important;
15732  text-transform: ${styles.textTransform} !important;
15733  font-style: ${styles.fontStyle} !important;
15734  font-variant: ${styles.fontVariant} !important;
15735  font-stretch: ${styles.fontStretch} !important;
15736}
15737`;
15738
15739    let styleTag = document.getElementById(STYLE_ID);
15740
15741    if (!styleTag) {
15742      styleTag = document.createElement('style');
15743      styleTag.id = STYLE_ID;
15744      styleTag.setAttribute('data-HH2', 'true');
15745      document.head.appendChild(styleTag);
15746    }
15747
15748    styleTag.textContent = css;
15749
15750    return true;
15751  }
15752
15753
15754  /**
15755   * Debounce updates so Duda can make several DOM changes
15756   * without us recalculating after every individual mutation.
15757   */
15758  function scheduleUpdate(delay = 100) {
15759
15760    clearTimeout(updateTimer);
15761
15762    updateTimer = setTimeout(() => {
15763      applyTheme();
15764    }, delay);
15765  }
15766
15767
15768  /**
15769   * INITIAL LOAD
15770   *
15771   * Run several times because Duda/theme/font loading can occur
15772   * after dmAPI.runOnReady().
15773   */
15774  applyTheme();
15775
15776  setTimeout(applyTheme, 250);
15777  setTimeout(applyTheme, 500);
15778  setTimeout(applyTheme, 1000);
15779  setTimeout(applyTheme, 2000);
15780  setTimeout(applyTheme, 4000);
15781
15782
15783  /**
15784   * WATCH DUDA DOM CHANGES
15785   */
15786  const observer = new MutationObserver((mutations) => {
15787
15788    let relevantChange = false;
15789
15790    for (const mutation of mutations) {
15791
15792      /*
15793       * Ignore mutations caused only by our own style tag.
15794       */
15795      if (
15796        mutation.target &&
15797        (
15798          mutation.target.id === STYLE_ID ||
15799          mutation.target.closest?.(`#${STYLE_ID}`)
15800        )
15801      ) {
15802        continue;
15803      }
15804
15805      relevantChange = true;
15806      break;
15807    }
15808
15809    if (relevantChange) {
15810      scheduleUpdate(150);
15811    }
15812
15813  });
15814
15815  observer.observe(document.body, {
15816    childList: true,
15817    subtree: true,
15818    attributes: true,
15819    attributeFilter: [
15820      'class',
15821      'style'
15822    ]
15823  });
15824
15825
15826  /**
15827   * RESPONSIVE CHANGES
15828   *
15829   * H2 typography may change between desktop/tablet/mobile.
15830   */
15831  window.addEventListener('resize', () => {
15832    scheduleUpdate(150);
15833  });
15834
15835
15836  /**
15837   * ORIENTATION CHANGE
15838   */
15839  window.addEventListener('orientationchange', () => {
15840    scheduleUpdate(250);
15841  });
15842
15843
15844  /**
15845   * FONT LOADING
15846   *
15847   * Reapply after web fonts finish loading.
15848   */
15849  if (document.fonts && document.fonts.ready) {
15850
15851    document.fonts.ready.then(() => {
15852
15853      // Force refresh even if we previously captured fallback font
15854      lastSignature = '';
15855
15856      applyTheme();
15857
15858    }).catch(() => {
15859      // Nothing required
15860    });
15861
15862  }
15863
15864});
15865    };
15866</script>
15866
15867
15868<script>
15869    window.customWidgetsFunctions["ca4188fe92024514a23a858f63ab6658~18"] = function (element, data, api) {
15870        null
15871    };
15872</script>
15872
15873
15874<script>
15875    window.customWidgetsFunctions["c4b2f903d115403c8c21e9ec6044a4fe~59"] = function (element, data, api) {
15876        null
15877    };
15878</script>
15878
15879
15880<script>
15881    window.customWidgetsFunctions["ab442ac754384e98a02c4f630b481f5e~9"] = function (element, data, api) {
15882        window.dmAPI.runOnReady('hide-section-if-category-field-empty', function () {
15883
15884  function getLastSlug(value) {
15885    if (!value) return '';
15886
15887    value = String(value).trim().toLowerCase();
15888
15889    try {
15890      if (value.indexOf('http://') === 0 || value.indexOf('https://') === 0) {
15891        value = new URL(value).pathname;
15892      }
15893    } catch (e) {}
15894
15895    value = value.split('?')[0].split('#')[0];
15896    value = value.replace(/\/+$/, '');
15897
15898    var parts = value.split('/');
15899    return parts[parts.length - 1] || '';
15900  }
15901
15902  function hasVisibleText(value) {
15903    if (value === null || value === undefined) {
15904      return false;
15905    }
15906
15907    var temporaryElement = document.createElement('div');
15908    temporaryElement.innerHTML = String(value);
15909
15910    var text =
15911      temporaryElement.textContent ||
15912      temporaryElement.innerText ||
15913      '';
15914
15915    text = text
15916      .replace(/\u00A0/g, ' ')
15917      .replace(/[\u200B-\u200D\uFEFF]/g, '')
15918      .trim();
15919
15920    return text.length > 0;
15921  }
15922
15923  function getCurrentSection(marker) {
15924    return (
15925      marker.closest('[data-layout-section]') ||
15926      marker.closest('section.flex-element') ||
15927      marker.closest("[data-page-element-type='section']") ||
15928      marker.closest('.dmRespRow') ||
15929      marker.closest('.flex-section') ||
15930      marker.closest('section')
15931    );
15932  }
15933
15934  function hideSectionForMarker(marker) {
15935    var section = getCurrentSection(marker);
15936
15937    if (section) {
15938      section.style.setProperty('display', 'none', 'important');
15939      console.log('[HideSection] Section hidden:', section);
15940    } else {
15941      console.warn('[HideSection] Could not find parent section for marker:', marker);
15942    }
15943  }
15944
15945  function showSectionForMarker(marker) {
15946    var section = getCurrentSection(marker);
15947
15948    if (section) {
15949      section.style.removeProperty('display');
15950      console.log('[HideSection] Section shown:', section);
15951    }
15952  }
15953
15954  function checkCollection(marker) {
15955    var collectionName = marker.getAttribute('data-collection-name') || '02 CATEGORY';
15956    var requiredFieldName = marker.getAttribute('data-required-field') || 'COPY BLOCK 1 HEADLINE';
15957    var currentSlug = getLastSlug(window.location.href);
15958
15959    console.log('[HideSection] Current slug:', currentSlug);
15960    console.log('[HideSection] Collection:', collectionName);
15961    console.log('[HideSection] Required field:', requiredFieldName);
15962
15963    if (!window.dmAPI || typeof dmAPI.loadCollectionsAPI !== 'function') {
15964      console.warn('[HideSection] Duda Collections API not available.');
15965      return;
15966    }
15967
15968    dmAPI.loadCollectionsAPI()
15969      .then(function (collection) {
15970        return collection
15971          .data(collectionName)
15972          .pageSize(100)
15973          .get();
15974      })
15975      .then(function (result) {
15976        console.log('[HideSection] Raw result:', result);
15977
15978        var rows = [];
15979
15980        if (result && Array.isArray(result.values)) {
15981          rows = result.values;
15982        } else if (Array.isArray(result)) {
15983          rows = result;
15984        }
15985
15986        console.log('[HideSection] Total rows:', rows.length);
15987
15988        if (rows.length > 0) {
15989          console.log('[HideSection] First row dump:', rows[0]);
15990          console.log('[HideSection] First row keys:', Object.keys(rows[0] || {}));
15991
15992          if (rows[0] && rows[0].data) {
15993            console.log('[HideSection] First row data keys:', Object.keys(rows[0].data || {}));
15994          }
15995        }
15996
15997        var matchingRow = null;
15998
15999        if (collectionName === '01 HOME') {
16000          matchingRow = rows[0] || null;
16001
16002          if (matchingRow) {
16003            console.log('[HideSection] 01 HOME detected — using first row:', matchingRow);
16004          } else {
16005            console.warn('[HideSection] 01 HOME detected, but no rows were found.');
16006          }
16007
16008        } else {
16009
16010          for (var i = 0; i < rows.length; i++) {
16011            var row = rows[i];
16012            var rowData = row.data || row;
16013
16014            var rawSlugValue =
16015              row.page_item_url ||
16016              rowData.page_item_url ||
16017              rowData['page_item_url'] ||
16018              rowData['item'] ||
16019              rowData['slug'] ||
16020              row.item ||
16021              row.slug ||
16022              '';
16023
16024            var rowSlug = getLastSlug(rawSlugValue);
16025
16026            console.log('[HideSection] Row', i, 'raw slug candidate:', rawSlugValue);
16027            console.log('[HideSection] Row', i, 'normalized slug:', rowSlug);
16028
16029            if (rowSlug === currentSlug) {
16030              matchingRow = row;
16031              console.log('[HideSection] Matching row found:', matchingRow);
16032              break;
16033            }
16034          }
16035        }
16036
16037        if (!matchingRow) {
16038          console.warn('[HideSection] No matching row found for collection:', collectionName);
16039          return;
16040        }
16041
16042        var matchingRowData = matchingRow.data || matchingRow;
16043        var fieldValue = matchingRowData[requiredFieldName];
16044
16045        console.log('[HideSection] Field "' + requiredFieldName + '" value:', fieldValue);
16046
16047        var hasValue = hasVisibleText(fieldValue);
16048
16049        if (!hasValue) {
16050          console.log('[HideSection] Field has no visible text — hiding section.');
16051          hideSectionForMarker(marker);
16052        } else {
16053          console.log('[HideSection] Field has visible text — section stays visible.');
16054          showSectionForMarker(marker);
16055        }
16056      })
16057      .catch(function (error) {
16058        console.warn('[HideSection] Collection check failed:', error);
16059      });
16060  }
16061
16062  function run() {
16063    var markers = element.querySelectorAll('.hide-section-if-category-field-empty');
16064
16065    for (var i = 0; i < markers.length; i++) {
16066      checkCollection(markers[i]);
16067    }
16068
16069    setTimeout(function () {
16070      var markers2 = element.querySelectorAll('.hide-section-if-category-field-empty');
16071
16072      for (var j = 0; j < markers2.length; j++) {
16073        checkCollection(markers2[j]);
16074      }
16075    }, 1500);
16076  }
16077
16078  run();
16079
16080});
16081    };
16082</script>
16082
16083
16084<script>
16085    window.customWidgetsFunctions["8eedf98f346a42e0a55f9d6152653187~2"] = function (element, data, api) {
16086        window.dmAPI.runOnReady('dcbh2-h2-theme', () => {
16087
16088  const STYLE_ID = 'dcbh2-h2-theme-style';
16089  const TARGET_SELECTOR = '.widget-wrapper.DCBH2';
16090
16091  let updateTimer = null;
16092  let lastSignature = '';
16093
16094  /**
16095   * Find a usable H2 that is NOT inside the DCBH2 widget.
16096   */
16097  function findThemeH2() {
16098    const candidates = Array.from(
16099      document.querySelectorAll('#dm h2, h2')
16100    );
16101
16102    for (const el of candidates) {
16103
16104      // Do not use our own DCBH2 widget as the source
16105      if (el.closest(TARGET_SELECTOR)) {
16106        continue;
16107      }
16108
16109      // Ignore disconnected elements
16110      if (!el.isConnected) {
16111        continue;
16112      }
16113
16114      const computed = window.getComputedStyle(el);
16115
16116      // Ignore unavailable / hidden elements
16117      if (
16118        computed.display === 'none' ||
16119        computed.visibility === 'hidden'
16120      ) {
16121        continue;
16122      }
16123
16124      // Make sure useful typography has loaded
16125      if (!computed.fontFamily || !computed.fontSize) {
16126        continue;
16127      }
16128
16129      return el;
16130    }
16131
16132    return null;
16133  }
16134
16135
16136  /**
16137   * Read H2 typography and apply it to DCBH2 widgets.
16138   */
16139  function applyTheme() {
16140
16141    const h2Sample = findThemeH2();
16142
16143    if (!h2Sample) {
16144      return false;
16145    }
16146
16147    const computed = window.getComputedStyle(h2Sample);
16148
16149    const styles = {
16150      fontFamily: computed.fontFamily,
16151      fontSize: computed.fontSize,
16152      fontWeight: computed.fontWeight,
16153      lineHeight: computed.lineHeight,
16154      color: computed.color,
16155      letterSpacing: computed.letterSpacing,
16156      textTransform: computed.textTransform,
16157      fontStyle: computed.fontStyle,
16158      fontVariant: computed.fontVariant,
16159      fontStretch: computed.fontStretch
16160    };
16161
16162    /**
16163     * Only rewrite the stylesheet if something actually changed.
16164     */
16165    const signature = JSON.stringify(styles);
16166
16167    if (signature === lastSignature) {
16168      return true;
16169    }
16170
16171    lastSignature = signature;
16172
16173
16174    /**
16175     * Target Duda RTE elements, including nested spans.
16176     */
16177    const selectors = [
16178      '#dm .dmInner .widget-wrapper.DCBH2 .dmNewParagraph',
16179      '#dm .dmInner .widget-wrapper.DCBH2 .dmNewParagraph p',
16180      '#dm .dmInner .widget-wrapper.DCBH2 .dmNewParagraph span',
16181      '#dm .dmInner .widget-wrapper.DCBH2 .dmNewParagraph .rteBlock',
16182      '#dm .dmInner .widget-wrapper.DCBH2 .dmNewParagraph .rteBlock *',
16183
16184      '.dmInner .widget-wrapper.DCBH2 .dmNewParagraph',
16185      '.dmInner .widget-wrapper.DCBH2 .dmNewParagraph p',
16186      '.dmInner .widget-wrapper.DCBH2 .dmNewParagraph span',
16187      '.dmInner .widget-wrapper.DCBH2 .dmNewParagraph .rteBlock',
16188      '.dmInner .widget-wrapper.DCBH2 .dmNewParagraph .rteBlock *',
16189
16190      '.widget-wrapper.DCBH2 .dmNewParagraph',
16191      '.widget-wrapper.DCBH2 .dmNewParagraph p',
16192      '.widget-wrapper.DCBH2 .dmNewParagraph span',
16193      '.widget-wrapper.DCBH2 .dmNewParagraph .rteBlock',
16194      '.widget-wrapper.DCBH2 .dmNewParagraph .rteBlock *'
16195    ];
16196
16197    const css = `
16198${selectors.join(',\n')} {
16199  font-family: ${styles.fontFamily} !important;
16200  font-size: ${styles.fontSize} !important;
16201  font-weight: ${styles.fontWeight} !important;
16202  line-height: ${styles.lineHeight} !important;
16203  color: ${styles.color} !important;
16204  letter-spacing: ${styles.letterSpacing} !important;
16205  text-transform: ${styles.textTransform} !important;
16206  font-style: ${styles.fontStyle} !important;
16207  font-variant: ${styles.fontVariant} !important;
16208  font-stretch: ${styles.fontStretch} !important;
16209}
16210`;
16211
16212    let styleTag = document.getElementById(STYLE_ID);
16213
16214    if (!styleTag) {
16215      styleTag = document.createElement('style');
16216      styleTag.id = STYLE_ID;
16217      styleTag.setAttribute('data-dcbh2-h2-theme', 'true');
16218      document.head.appendChild(styleTag);
16219    }
16220
16221    styleTag.textContent = css;
16222
16223    return true;
16224  }
16225
16226
16227  /**
16228   * Debounce repeated Duda DOM changes.
16229   */
16230  function scheduleUpdate(delay = 100) {
16231
16232    clearTimeout(updateTimer);
16233
16234    updateTimer = setTimeout(() => {
16235      applyTheme();
16236    }, delay);
16237  }
16238
16239
16240  /**
16241   * Initial passes.
16242   *
16243   * Duda/theme/font rendering can continue after runOnReady.
16244   */
16245  applyTheme();
16246
16247  setTimeout(applyTheme, 250);
16248  setTimeout(applyTheme, 500);
16249  setTimeout(applyTheme, 1000);
16250  setTimeout(applyTheme, 2000);
16251  setTimeout(applyTheme, 4000);
16252
16253
16254  /**
16255   * Watch for Duda re-rendering or dynamic content changes.
16256   */
16257  const observer = new MutationObserver((mutations) => {
16258
16259    let relevantChange = false;
16260
16261    for (const mutation of mutations) {
16262
16263      // Ignore our own injected stylesheet
16264      if (
16265        mutation.target &&
16266        (
16267          mutation.target.id === STYLE_ID ||
16268          mutation.target.closest?.(`#${STYLE_ID}`)
16269        )
16270      ) {
16271        continue;
16272      }
16273
16274      relevantChange = true;
16275      break;
16276    }
16277
16278    if (relevantChange) {
16279      scheduleUpdate(150);
16280    }
16281
16282  });
16283
16284  observer.observe(document.body, {
16285    childList: true,
16286    subtree: true,
16287    attributes: true,
16288    attributeFilter: [
16289      'class',
16290      'style'
16291    ]
16292  });
16293
16294
16295  /**
16296   * Responsive breakpoint changes.
16297   */
16298  window.addEventListener('resize', () => {
16299    scheduleUpdate(150);
16300  });
16301
16302
16303  /**
16304   * Mobile orientation changes.
16305   */
16306  window.addEventListener('orientationchange', () => {
16307    scheduleUpdate(250);
16308  });
16309
16310
16311  /**
16312   * Reapply after web fonts finish loading.
16313   */
16314  if (document.fonts && document.fonts.ready) {
16315
16316    document.fonts.ready.then(() => {
16317
16318      lastSignature = '';
16319
16320      applyTheme();
16321
16322    }).catch(() => {
16323      // Nothing required
16324    });
16325
16326  }
16327
16328});
16329    };
16330</script>
16330
16331
16332<script>
16333    window.customWidgetsFunctions["403a957a34f84bc8a342137c59c93b31~3"] = function (element, data, api) {
16334        window.dmAPI.runOnReady('dcbp-p-theme', () => {
16335  'use strict';
16336
16337  const STYLE_ID = 'dcbp-p-theme-styles';
16338
16339  function getParagraphFontFamily() {
16340    const source =
16341      document.querySelector(
16342        '.widget-403a95 .cb2-theme-injector .dmNewParagraph p'
16343      ) ||
16344      document.querySelector(
16345        '.widget-403a95 .cb2-theme-injector p'
16346      ) ||
16347      document.querySelector(
16348        '.widget-403a95 .dmNewParagraph p'
16349      ) ||
16350      document.querySelector(
16351        '.widget-403a95 p'
16352      );
16353
16354    if (!source) {
16355      return null;
16356    }
16357
16358    const computed = window.getComputedStyle(source);
16359    const fontFamily = computed.fontFamily;
16360
16361    if (!fontFamily || fontFamily.trim() === '') {
16362      return null;
16363    }
16364
16365    return fontFamily;
16366  }
16367
16368  function applyTheme() {
16369    const dcbpElements = document.querySelectorAll(
16370      '#dm .DCBP, ' +
16371      '.DCBP, ' +
16372      '#dm .widget-wrapper.DCBP, ' +
16373      '.widget-wrapper.DCBP'
16374    );
16375
16376    if (!dcbpElements.length) {
16377      return false;
16378    }
16379
16380    const fontFamily = getParagraphFontFamily();
16381
16382    if (!fontFamily) {
16383      return false;
16384    }
16385
16386    const uSelectors = new Set();
16387
16388    const paragraphElements = document.querySelectorAll(
16389      '#dm .DCBP .dmNewParagraph, ' +
16390      '.DCBP .dmNewParagraph'
16391    );
16392
16393    paragraphElements.forEach(element => {
16394      element.classList.forEach(className => {
16395        if (!className.startsWith('u_')) {
16396          return;
16397        }
16398
16399        uSelectors.add(`div.${className}`);
16400        uSelectors.add(`div.${className} p`);
16401        uSelectors.add(`div.${className} li`);
16402        uSelectors.add(`div.${className} span`);
16403
16404        uSelectors.add(`#dm .dmInner div.${className}`);
16405        uSelectors.add(`#dm .dmInner div.${className} p`);
16406        uSelectors.add(`#dm .dmInner div.${className} li`);
16407        uSelectors.add(`#dm .dmInner div.${className} span`);
16408      });
16409    });
16410
16411    const baseSelectors = [
16412      '#dm .dmInner .widget-wrapper.DCBP .dmNewParagraph',
16413      '#dm .dmInner .widget-wrapper.DCBP .dmNewParagraph p',
16414      '#dm .dmInner .widget-wrapper.DCBP .dmNewParagraph li',
16415      '#dm .dmInner .widget-wrapper.DCBP .dmNewParagraph span',
16416      '#dm .dmInner .widget-wrapper.DCBP .dmNewParagraph .rteBlock',
16417
16418      '.dmInner .widget-wrapper.DCBP .dmNewParagraph',
16419      '.dmInner .widget-wrapper.DCBP .dmNewParagraph p',
16420      '.dmInner .widget-wrapper.DCBP .dmNewParagraph li',
16421      '.dmInner .widget-wrapper.DCBP .dmNewParagraph span',
16422      '.dmInner .widget-wrapper.DCBP .dmNewParagraph .rteBlock',
16423
16424      '.widget-wrapper.DCBP .dmNewParagraph',
16425      '.widget-wrapper.DCBP .dmNewParagraph p',
16426      '.widget-wrapper.DCBP .dmNewParagraph li',
16427      '.widget-wrapper.DCBP .dmNewParagraph span',
16428      '.widget-wrapper.DCBP .dmNewParagraph .rteBlock',
16429
16430      '#dm .DCBP .dmNewParagraph',
16431      '#dm .DCBP .dmNewParagraph p',
16432      '#dm .DCBP .dmNewParagraph li',
16433      '#dm .DCBP .dmNewParagraph span',
16434
16435      '.DCBP .dmNewParagraph',
16436      '.DCBP .dmNewParagraph p',
16437      '.DCBP .dmNewParagraph li',
16438      '.DCBP .dmNewParagraph span'
16439    ];
16440
16441    const allSelectors = [
16442      ...baseSelectors,
16443      ...uSelectors
16444    ].join(',\n');
16445
16446    const css = `
16447${allSelectors} {
16448  font-family: ${fontFamily} !important;
16449}
16450`;
16451
16452    let styleTag = document.getElementById(STYLE_ID);
16453
16454    if (!styleTag) {
16455      styleTag = document.createElement('style');
16456      styleTag.id = STYLE_ID;
16457      styleTag.setAttribute('data-dcbp-p-theme', 'true');
16458      document.head.appendChild(styleTag);
16459    }
16460
16461    styleTag.textContent = css;
16462
16463    return true;
16464  }
16465
16466  if (applyTheme()) {
16467    return;
16468  }
16469
16470  let attempts = 0;
16471  const maxAttempts = 30;
16472
16473  const interval = window.setInterval(() => {
16474    attempts += 1;
16475
16476    if (applyTheme() || attempts >= maxAttempts) {
16477      window.clearInterval(interval);
16478    }
16479  }, 300);
16480});
16481    };
16482</script>
16482
16483
16484<script>
16485    window.customWidgetsFunctions["328e6f1fbcec4101ac4f8d9184710a14~7"] = function (element, data, api) {
16486        $(document).ready(function(){
16487    $.getScript('https://widget.hibu.us/static/js/recent.js')
16488    .done(function(){
16489        dmAPI.runOnReady('hibu_recent_reviews',function(){
16490            //window.renderApp();
16491        });
16492        
16493    })
16494    .fail(function(){
16495        console.log("failed to load recent reviews js file")
16496    });
16497    
16498    element.children[0].removeAttribute(`rteblock"`)
16499    var merchantId = data.config.merchantId
16500    merchantId = merchantId.replace(/<p.*?>/g, '')
16501    merchantId = merchantId.replace(/&quot;/g, '"')
16502    merchantId = merchantId.replace(/\<\/p\>/g, '')
16503    
16504    element.children[0].setAttribute('data-merchant', merchantId)
16505})
16506    };
16507</script>
16507
16508
16509
16510<script type="text/javascript">
16511
16512    var d_version = "production_6839";
16513    var build = "2026-10-08T12_48_49";
16514    window['v' + 'ersion'] = d_version;
16515
16516    function buildEditorParent() {
16517        window.isMultiScreen = true;
16518        window.editorParent = {};
16519        window.previewParent = {};
16520        try {
16521            var _p = window.parent;
16522            if (_p && _p.document && _p.$ && _p.$.dmfw) {
16523                window.editorParent = _p;
16524            } else if (_p.isSitePreview) {
16525                window.previewParent = _p;
16526            }
16527        } catch (e) {
16528
16529        }
16530    }
16531
16532    buildEditorParent();
16533</script>
16533
16534
16535
16536
16537<!-- Injecting site-wide before scripts -->
16538
16539
16540<script src="https://app.termly.io/resource-blocker/649d75ef-7314-4906-b9db-9c4ce5eeae41?autoBlock=on" data-swh-uuid="3d35c6c8c8cd4788a6cc371876dcf71c">
16541</script>
16541
16542
16543
16544
16545
16546
16547
16548
16549
16550
16551
16552
16553
16554
16555
16556
16557
16558
16559
16560
16561<script type="text/javascript" id="d_track_campaign" data-entry-referrer data-utm-cookies></script>
16561
16562
16563<script type="text/javascript" >
16564    var _dm_gaq = {};
16565    var _gaq = _gaq || [];
16566    var _dm_insite = [];
16567</script>
16567
16568
16569
16570
16571
16572<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-TRHRTBKH" id="d-js-gtm"
16573    onload="window.dispatchEvent(new CustomEvent('gtmLoaded', { bubbles: true }))"></script>
16573
16574
16575<script type="text/javascript" id="d_track_ga">
16576    window.dataLayer = window.dataLayer || [];
16577    _dm_gaq.siteAlias = 'b4d59456c20346899f3ab9056d40b845';
16578    _dm_gaq.externalGaqID = 'non-empty';
16579
16580    function gtag() { dataLayer.push(arguments); }
16581
16582    function pushInsiteImpressions(id) {
16583        _dm_insite.forEach((rule) => {
16584            _dm_gaq.pushEvent(id, 'insite_impression', 'insite', rule.ruleType + '__' + rule.ruleId);
16585        });
16586    }
16587
16588    _dm_gaq.pushEvent = function(trackingId, event, category, label, value, additionalParams = {}) {
16589        const eventProps = {
16590            event_category: category,
16591            event_label: label,
16592            value,
16593            ...additionalParams
16594        };
16595        gtag('event', event, eventProps);
16596    }
16597
16598    gtag('js', new Date())
16599
16600    
16601gtag('set', 'developer_id.dMDU5Nj', true);
16602
16603    _dm_gaq.pushEvent('', 'page_view');
16604
16605    pushInsiteImpressions(_dm_gaq.externalGaqID);
16606</script>
16606
16607
16608
16609
16610
16611
16612
16613<!-- End Injecting site-wide to the head -->
16614
16615
16616
16617
16618
16619
16620<script>
16621    window.cookiesNotificationMarkupPreview = 'null';
16622</script>
16622
16623
16624
16625<script id='d-js-params'>
16626    window.INSITE = window.INSITE || {};
16627    window.INSITE.device = "desktop";
16628
16629    window.rtCommonProps = {};
16630    rtCommonProps["runtimecollector.url"] = 'https://rtc.hibuwebsites.com';
16631    rtCommonProps["performance.tabletPreview.removeScroll"] = 'false';
16632    rtCommonProps["inlineEditGrid.snap"] =true;
16633    rtCommonProps["popup.insite.cookie.ttl"] = '0.5';
16634    rtCommonProps["common.mapbox.token"] = 'pk.eyJ1IjoiZGFubnliMTIzIiwiYSI6ImNqMGljZ256dzAwMDAycXBkdWxwbDgzeXYifQ.Ck5P-0NKPVKAZ6SH98gxxw';
16635    rtCommonProps["common.mapbox.js.override"] =false;
16636    rtCommonProps["common.here.appId"] = 'iYvDjIQ2quyEu0rg0hLo';
16637    rtCommonProps["common.here.appCode"] = '1hcIxLJcbybmtBYTD9Z1UA';
16638    rtCommonProps["ecommerce.ecwid.script"] = 'https://app.multiscreenstore.com/script.js';
16639    rtCommonProps["common.build.dist.folder"] = 'production/6839';
16640    rtCommonProps["common.resources.cdn.host"] = 'https://static-res-cdn.websites.hibu.com';
16641    rtCommonProps["common.resources.folder"] = 'https://static-res-cdn.websites.hibu.com/mnlt/production/6839';
16642    rtCommonProps["common.fonts.api.v2.url"] = 'https://cdn.hibuwebsites.com/fonts/css2?family=';
16643    rtCommonProps["feature.flag.runtime.backgroundSlider.preload.slowly"] =true;
16644    rtCommonProps["feature.flag.runtime.newAnimation.jitAnimation.enabled"] =true;
16645    rtCommonProps["feature.flag.sites.google.analytics.gtag"] =true;
16646    rtCommonProps["feature.flag.runOnReadyNewTask"] =true;
16647    rtCommonProps["isAutomation.test"] =false;
16648    rtCommonProps["booking.cal.api.domain"] = 'api.cal.com';
16649
16650    
16651    rtCommonProps['common.mapsProvider'] = 'mapbox';
16652    
16653    rtCommonProps['common.mapsProvider.version'] = '0.52.0';
16654    rtCommonProps['common.geocodeProvider'] = 'mapbox';
16655    rtCommonProps['server.for.resources'] = '';
16656    rtCommonProps['feature.flag.lazy.widgets'] = true;
16657    rtCommonProps['feature.flag.single.wow'] = false;
16658    rtCommonProps['feature.flag.disallowPopupsInEditor'] = true;
16659    rtCommonProps['feature.flag.mark.anchors'] = true;
16660    rtCommonProps['captcha.public.key'] = '6LffcBsUAAAAAMU-MYacU-6QHY4iDtUEYv_Ppwlz';
16661    rtCommonProps['captcha.invisible.public.key'] = '6LeiWB8UAAAAAHYnVJM7_-7ap6bXCUNGiv7bBPME';
16662    rtCommonProps["images.sizes.small"] =160;
16663    rtCommonProps["images.sizes.mobile"] =640;
16664    rtCommonProps["images.sizes.tablet"] =1280;
16665    rtCommonProps["images.sizes.desktop"] =1920;
16666    rtCommonProps["import.images.storage.imageCDN"] = 'https://le-cdn.hibuwebsites.com/';
16667    rtCommonProps["feature.flag.performance.logs"] =false;
16668    rtCommonProps["site.widget.form.captcha.type"] = 'g_recaptcha';
16669    rtCommonProps["friendly.captcha.site.key"] = 'FCMGSQG9GVNMFS8K';
16670    rtCommonProps["cookiebot.mapbox.consent.category"] = 'marketing';
16671    rtCommonProps["termly.mapbox.consent.category"] = 'performance';
16672    // feature flags that's used out of runtime module (in  legacy files)
16673    rtCommonProps["platform.monolith.personalization.dateTimeCondition.popupMsgAction.moveToclient.enabled"] =true;
16674
16675    window.rtFlags = {};
16676    rtFlags["unsuspendEcwidStoreOnRuntime.enabled"] =false;
16677    rtFlags["scripts.widgetCount.enabled"] =true;
16678    rtFlags["fnb.animations.tracking.enabled"] =true;
16679    rtFlags["ecom.ecwidNewUrlStructure.enabled"] = false;
16680    rtFlags["ecom.ecwid.accountPage.emptyBaseUrl.enabled"] = true;
16681    rtFlags["ecom.ecwid.accountPage.repeatOrderRedirect.enabled"] = false;
16682    rtFlags["ecom.ecwid.oldStore.keepScrollOnLoad.enabled"] = true;
16683    rtFlags["ecom.ecwid.storefrontV3.enabled"] = false;
16684    rtFlags["ecom.monolith.legacy.js.api.disabled.for.new.sites"] = false;
16685    rtFlags["geocode.search.localize"] =false;
16686    rtFlags["feature.flag.runtime.newAnimation.asyncInit.setTimeout.enabled"] =false;
16687    rtFlags["twitter.heightLimit.enabled"] = true;
16688    rtFlags["streamline.monolith.personalization.supportMultipleConditions.enabled"] =false;
16689    rtFlags["flex.animation.design.panel.layout"] =true;
16690    rtFlags["runtime.cwv.report.cls.enabled"] =false;
16691    rtFlags["runtime.cwv.report.lcp.enabled"] =false;
16692    rtFlags["runtime.lcp.preload.selfheal.enabled"] =true;
16693    rtFlags["runtime.lcp.preload.bootstrap.enabled"] =true;
16694    rtFlags["runtime.lcp.preload.monitor.enabled"] =false;
16695    rtFlags["runtime.customwidget.heightpin.enabled"] =true;
16696    rtFlags["runtime.video.widget.lazy.enabled"] =true;
16697    rtFlags["contact.form.useActiveForm"] =true;
16698    rtFlags["contact.form.custom.errors.enabled"] =false;
16699    rtFlags["runtime.ssr.productStore.internal.observer"] =true;
16700    rtFlags["editor.widget.form.a11y"] =true;
16701</script>
vendor: 1 bytes, line 16701
16701
16702<script src="https://static-res-cdn.websites.hibu.com/mnlt/production/6839/_dm/s/rt/dist/scripts/d-js-runtime-flex-package.min.js"  id="d-js-core" defer></script>
16702
16703
16704
16705
16706
16707
16708
16709
16710
16711
16712
16713
16714
16715
16716
16717
16718
16719<script>
16720    document.addEventListener('DOMContentLoaded', () =>
16721        window.runtime.initLayout()
16722    );
16723</script>
16723
16724
16725
16726
16727
16728
16729
16730
16731
16732
16733
16734
16735
16736
16737
16738
16739
16740
16741
16742
16743<script type="text/javascript" id="d_track_sp">
16744    ;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
16745        p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
16746        };p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
16747        n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","//d32hwlnfiv2gyn.cloudfront.net/sp-2.0.0-dm-0.1.min.js","snowplow"));
16748    window.dmsnowplow  = window.snowplow;
16749
16750    dmsnowplow('newTracker', 'cf', 'd32hwlnfiv2gyn.cloudfront.net', { // Initialise a tracker
16751        appId: 'b4d59456c20346899f3ab9056d40b845'
16752    });
16753
16754    // snowplow queries element styles so we wait until CSS calculations are done.
16755    requestAnimationFrame(() => {
16756        dmsnowplow('trackPageView');
16757        _dm_insite.forEach((rule) => {
16758            // Specifically in popup only the client knows if it is shown or not so we don't always want to track its impression here
16759            // the tracking is in popup.js
16760            if (rule.actionName !== "popup") {
16761                dmsnowplow('trackStructEvent', 'insite', 'impression', rule.ruleType, rule.ruleId);
16762            }
16763            window?.waitForDeferred?.('dmAjax', () => {
16764                $.DM.events.trigger('event-ruleTriggered', {value: rule});
16765            });
16766        });
16767    });
16768</script>
16768
16769
16770
16771
16772<div style="display:none;" id="P6iryBW0Wu"></div>
16773
16774
16775
16776
16777
16778
16779
16780
16781
16782
16783
16784<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
16785
16786    
16787    <div class="pswp__bg"></div>
16788
16789    
16790    <div class="pswp__scroll-wrap">
16791
16792        
16793        <div class="pswp__container">
16794            <div class="pswp__item"></div>
16795            <div class="pswp__item"></div>
16796            <div class="pswp__item"></div>
16797        </div>
16798
16799        
16800        <div class="pswp__ui pswp__ui--hidden">
16801
16802            <div class="pswp__top-bar">
16803
16804                
16805
16806                <div class="pswp__counter"></div>
16807
16808                <button class="pswp__button pswp__button--close" data-auto="pswp-close" title="Close (Esc)"></button>
16809
16810                <button class="pswp__button pswp__button--share" data-auto="pswp-share" title="Share"></button>
16811
16812                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
16813
16814                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
16815
16816                
16817                
16818                <div class="pswp__preloader">
16819                    <div class="pswp__preloader__icn">
16820                      <div class="pswp__preloader__cut">
16821                        <div class="pswp__preloader__donut"></div>
16822                      </div>
16823                    </div>
16824                </div>
16825            </div>
16826
16827            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
16828                <div class="pswp__share-tooltip"></div> 
16829            </div>
16830
16831            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
16832            </button>
16833
16834            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
16835            </button>
16836
16837            <div class="pswp__caption">
16838                <div class="pswp__caption__center"></div>
16839            </div>
16840
16841        </div>
16842
16843    </div>
16844
16845</div>
16846<div id="fb-root"
16847        data-locale="en"></div>
16848<!-- Alias: b4d59456c20346899f3ab9056d40b845 -->
16849<script id="d_track_personalization">
16850	window?.waitForDeferred?.('dmAjax', () => {
16851		// Collects client data and updates cookies used by smart sites
16852		window.expireDays = 365;
16853		window.visitLength = 30 * 60000;
16854		$.setCookie("dm_timezone_offset", (new Date()).getTimezoneOffset(), window.expireDays);
16855			setSmartSiteCookiesInternal("dm_this_page_view","dm_last_page_view","dm_total_visits","dm_last_visit");
16856	});
16857</script>
vendor: 1 bytes, line 16857
16857
16858<script type="text/javascript">
16859    
16860    Parameters.NavigationAreaParams.MoreButtonText = 'MORE';
16861    
16862    Parameters.NavigationAreaParams.LessButtonText = 'LESS';
16863    </script>
vendor: 1 bytes, line 16863
16863
16864<script id="ssr-empty-script" type="text/javascript"></script>
16864<!--  Begin Body End Markup -->
16865<!-- Begin Body End Markup --><!-- Begin Name of Code --><!-- End Name of Code --><div id="placeholder_retargeting_pixel" style="display: none;" data-inject="placeholder_retargeting_pixel">
16866 
16866<script>
16867
16868
16869
16870
16871//<![CDATA[
16872(function(w,d,t,r,u){var f,n,i;w[u]=w[u]||[],f=function(){var o={ti:"97272220"};o.q=w[u],w[u]=new UET(o),w[u].push("pageLoad")},n=d.createElement(t),n.src=r,n.async=1,n.onload=n.onreadystatechange=function(){var s=this.readyState;s&&s!=="loaded"&&s!=="complete"||(f(),n.onload=n.onreadystatechange=null)},i=d.getElementsByTagName(t)[0],i.parentNode.insertBefore(n,i)})(window,document,"script","//bat.bing.com/bat.js","uetq");
16873//]]>
16874 
16875 
16876 
16877 
16878 </script>
16878
16879 <noscript>
16880  <img src="//bat.bing.com/action/0?ti=97272220&amp;Ver=2" height="0" width="0" style="display:none; visibility: hidden;"/>
16881 </noscript>
16882 <img src="//servedby.ipromote.com/ad/?src=pixel_cid&amp;nid=2185&amp;cid=RE2Q66JTC778&amp;cat=" width="1" height="1" border="0" alt=""/>
16883</div>
16884<div id="placeholder_dpni" style="display: none;" data-inject="placeholder_dpni">
16885 <!-- phone insertion script begins --> 
16885<script type="text/javascript" src="https://reports.hibu.com/analytics/js/ybDynamicPhoneInsertion.js">
16886 </script>
16886
16887 
16887<script>
16888//<![CDATA[
16889
16890
16891ybFindPhNums = ['19082594326', '19082594326', '19082594326', '19082594326', '19082594326'];
16892ybReplacePhNums = ['19082519114', '19082594066', '19082740252', '19082740839', '19082741149'];
16893
16894	document.addEventListener("YextPhoneChangeEvent", yextPhoneChangeEventHandler, false);
16895
16896	function yextPhoneChangeEventHandler(e) {
16897		e.preventDefault();
16898		ybFun_ReplaceText();
16899	}
16900
16901	if (typeof dmAPI != 'undefined') {
16902		dmAPI.runOnReady('dpni', function() {
16903			setTimeout(ybFun_ReplaceText, 500);
16904		});
16905		window.addEventListener('DOMContentLoaded', function() {
16906			dmAPI.subscribeEvent(dmAPI.EVENTS.SHOW_POPUP, 
16907				function(data) {
16908					setTimeout(ybFun_ReplaceText, 500);
16909				});
16910		});
16911	} else {
16912		window.onload = function() {
16913			setTimeout(ybFun_ReplaceText, 500);
16914		}
16915	}
16916
16917
16918//]]>
16919 </script>
16919
16920 <!-- phone insertion script ends -->
16921</div>
16922<div id="placeholder_analytics" style="display: none;" data-inject="placeholder_analytics">
16923 
16923<script>
16924
16925
16926
16927
16928
16929
16930
16931
16932
16933
16934
16935
16936
16937
16938
16939
16940
16941
16942
16943
16944
16945
16946
16947//<![CDATA[
16948
16949//yellAnalyticsConfig need to set before s_code.js
16950var yellAnalyticsConfig = {
16951"global" : {
16952 "trackDownloadLinks": true,
16953 "downloadFileTypes": "exe,zip,wav,mp3,mov,mpg,avi,wmv,pdf,doc,docx,xls,xlsx,ppt,pptx",
16954 "currency": "USD",
16955 "trackExternalLinks": true,
16956 "internalDomains": "javascript:",
16957 "charset": "UTF-8"
16958},
16959"omniture" : {
16960 "enabled": true,
16961 "server": "yellgroup.122.2o7.net",
16962 "serverSecure": "yellgroup.122.2o7.net",
16963 "namespace": "yellgroup",
16964 "account": "yelldudalive",
16965 "campaign": "CID|cam"
16966}}
16967var hibuWebsiteConfig={
16968 "prodSKU":"WBS-PRO-01",
16969 "assetID":"700152202",
16970 "customerID":"7004458252"
16971}
16972
16973//]]>
16974 
16975 
16976 
16977 
16978 
16979 
16980 
16981 
16982 
16983 
16984 
16985 
16986 
16987 
16988 
16989 
16990 
16991 
16992 
16993 
16994 
16995 
16996 
16997 </script>
16997
16998 
16998<script type="text/javascript" src="https://dh-static-files.s3.amazonaws.com/prod/AppMeasurement.js">
16999
17000
17001
17002
17003
17004
17005
17006
17007
17008
17009
17010
17011
17012
17013
17014
17015
17016
17017
17018
17019
17020
17021//<![CDATA[
17022
17023 
17024//]]>
17025 
17026 
17027 
17028 
17029 
17030 
17031 
17032 
17033 
17034 
17035 
17036 
17037 
17038 
17039 
17040 
17041 
17042 
17043 
17044 
17045 
17046 
17047 </script>
17047
17048 
17048<script type="text/javascript" src="https://dh-static-files.s3.amazonaws.com/prod/hibu-analytics.min.js" defer="">
17049
17050
17051
17052
17053
17054
17055
17056
17057
17058
17059
17060
17061
17062
17063
17064
17065
17066
17067
17068
17069
17070
17071//<![CDATA[
17072
17073 
17074//]]>
17075 
17076 
17077 
17078 
17079 
17080 
17081 
17082 
17083 
17084 
17085 
17086 
17087 
17088 
17089 
17090 
17091 
17092 
17093 
17094 
17095 
17096 
17097 </script>
17097
17098 
17098<script type="text/javascript" src="https://dh-static-files.s3.amazonaws.com/prod/omn_setting.js" defer="">
17099
17100
17101
17102
17103
17104
17105
17106
17107
17108
17109
17110
17111
17112
17113
17114
17115
17116
17117
17118
17119
17120
17121//<![CDATA[
17122
17123 
17124//]]>
17125 
17126 
17127 
17128 
17129 
17130 
17131 
17132 
17133 
17134 
17135 
17136 
17137 
17138 
17139 
17140 
17141 
17142 
17143 
17144 
17145 
17146 
17147 </script>
17147
17148</div>
17149<div id="placeholder_footer_reserve1" style="display: none;" data-inject="placeholder_footer_reserve1">
17150 <style>
17151  
17152 
17153 
17154 
17155 
17156 
17157 
17158 
17159 
17160 
17161 
17162 
17163 
17164 
17165 
17166 
17167 
17168 
17169 
17170 
17171 
17172 
17173 iframe[name="google_conversion_frame"] {height: 0 !important;width: 0 !important;line-height: 0 !important;font-size: 0 !important;margin-top: -13px;float: left;}
17174 
17175 
17176 
17177 
17178 
17179 
17180 
17181 
17182 
17183 
17184 
17185 
17186 
17187 
17188 
17189 
17190 
17191 
17192 
17193 
17194 
17195 
17196 </style>
17197</div>
17198<div id="placeholder_footer_reserve2" style="display: none;" data-inject="placeholder_footer_reserve2">
17199 <!-- Facebook Pixel Code --> 
17199<script nonce="5Vxn03p8">
17200
17201
17202
17203
17204
17205
17206
17207
17208
17209
17210
17211
17212
17213
17214
17215
17216
17217
17218
17219//<![CDATA[
vendor: 397 bytes, lines 17219-17227
17219
17220
17221!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
17222n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
17223n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
17224t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
17225document,'script','https://connect.facebook.net/en_US/fbevents.js');
17226
17227fbq('init', '
172271786867385827639
vendor: 30 bytes, lines 17227-17228
17227');
17228fbq('track', "PageView");
17229//]]>
17230 
17231 
17232 
17233 
17234 
17235 
17236 
17237 
17238 
17239 
17240 
17241 
17242 
17243 
17244 
17245 
17246 
17247 
17248 
17249 </script>
17249
17250 <noscript>
17251  <img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=1786867385827639&amp;ev=PageView&amp;noscript=1"/>
17252 </noscript>
17253 <!-- End Facebook Pixel Code -->
17254</div>
17255<div id="placeholder_footer_reserve3" style="display: none;" data-inject="placeholder_footer_reserve3">
17256 
17256<script>
17257
17258
17259
17260
17261
17262
17263
17264
17265
17266
17267
17268
17269
17270
17271
17272
17273
17274
17275
17276
17277
17278
17279
17280//<![CDATA[
17281
17282 (() => {
17283 // Hide any flex wrapper whose button text contains "do not use"
17284 function hideDoNotUse(root = document) {
17285 const wrappers = root.querySelectorAll('.flex-element.widget-wrapper');
17286 wrappers.forEach(wrap => {
17287 const btn = wrap.querySelector('a.dmButtonLink, a.flexButton, a[data-element-type="dButtonLinkId"]');
17288 if (!btn) return;
17289 const text = (btn.textContent || '').toLowerCase().trim();
17290 if (text.includes('do not use')) {
17291 wrap.style.display = 'none';
17292 wrap.setAttribute('data-hidden-reason', 'do-not-use');
17293 }
17294 });
17295 }
17296 // Run once on ready
17297 if (document.readyState === 'loading') {
17298 document.addEventListener('DOMContentLoaded', () => hideDoNotUse());
17299 } else {
17300 hideDoNotUse();
17301 }
17302 // Watch for dynamically inserted content or text changes
17303 const mo = new MutationObserver(mutations => {
17304 for (const m of mutations) {
17305 m.addedNodes.forEach(node => {
17306 if (node.nodeType !== 1) return;
17307 if (node.matches && node.matches('.flex-element.widget-wrapper')) {
17308 hideDoNotUse(node);
17309 } else {
17310 // Check if a new/changed node sits inside a wrapper
17311 const wrap = node.closest && node.closest('.flex-element.widget-wrapper');
17312 if (wrap) hideDoNotUse(wrap);
17313 else hideDoNotUse(node);
17314 }
17315 });
17316 if (m.type === 'characterData' || m.type === 'childList') {
17317 const wrap = m.target.closest && m.target.closest('.flex-element.widget-wrapper');
17318 if (wrap) hideDoNotUse(wrap);
17319 }
17320 }
17321 });
17322 mo.observe(document.documentElement, {
17323 childList: true,
17324 subtree: true,
17325 characterData: true
17326 });
17327})();
17328 
17329//]]>
17330 
17331 
17332 
17333 
17334 
17335 
17336 
17337 
17338 
17339 
17340 
17341 
17342 
17343 
17344 
17345 
17346 
17347 
17348 
17349 
17350 
17351 
17352 
17353 </script>
17353
17354</div>
17355<div id="placeholder_footer_reserve4" style="display: none;" data-inject="placeholder_footer_reserve4">
17356 
17357 
17358 
17359 
17360 
17361 
17362 
17363 
17364 
17365 
17366 
17367 
17368 
17369 
17370 
17371 
17372 
17373 
17374 
17375 
17376 
17377 
17378 
17379 
17380 {{placeholder_footer_reserve4}}
17381</div>
17382<div id="placeholder_footer_reserve5" style="display: none;" data-inject="placeholder_footer_reserve5">
17383 
17384 
17385 
17386 
17387 
17388 
17389 
17390 
17391 
17392 
17393 
17394 
17395 
17396 
17397 
17398 
17399 
17400 
17401 
17402 
17403 
17404 
17405 
17406 
17407 {{placeholder_footer_reserve5}}
17408</div>
17409<div id="placeholder_footer_reserve6" style="display: none;" data-inject="placeholder_footer_reserve6">
17410 
17411 
17412 
17413 
17414 
17415 
17416 
17417 
17418 
17419 
17420 
17421 
17422 
17423 
17424 
17425 
17426 
17427 
17428 
17429 
17430 
17431 
17432 
17433 
17434 {{placeholder_footer_reserve6}}
17435</div>
17436<div id="placeholder_footer_reserve7" style="display: none;" data-inject="placeholder_footer_reserve7">
17437 
17438 
17439 
17440 
17441 
17442 
17443 
17444 
17445 
17446 
17447 
17448 
17449 
17450 
17451 
17452 
17453 
17454 
17455 
17456 
17457 
17458 
17459 
17460 
17461 {{placeholder_footer_reserve7}}
17462</div>
17463<!-- End Body End Markup --><!-- form dropdown code begin -->
17463<script>
17464//<![CDATA[
17465function waitForDmAPI(callback) {
17466 if (typeof dmAPI !== 'undefined') {
17467 callback();
17468 } else {
17469 setTimeout(() => waitForDmAPI(callback), 100);
17470 }
17471}
17472
17473document.addEventListener("DOMContentLoaded", function () {
17474 waitForDmAPI(async function () {
17475 async function getServicesAndProducts() {
17476 try {
17477 var collection = await dmAPI.loadCollectionsAPI();
17478 var formData = await collection.data("form").get();
17479
17480 if (formData?.values?.length > 0) {
17481 let servicesHtml = formData.values[0].data["Services & Products"];
17482
17483 let tempDiv = document.createElement("div");
17484 tempDiv.innerHTML = servicesHtml;
17485
17486 return Array.from(tempDiv.querySelectorAll("p")).map(p =>
17487 p.textContent.trim()
17488 );
17489 }
17490 } catch (err) {
17491 console.error("Failed to load form data:", err);
17492 }
17493
17494 return [];
17495 }
17496
17497 async function replaceInputWithDropdown() {
17498 let servicesArray = await getServicesAndProducts();
17499 if (servicesArray.length === 0) return;
17500
17501 // Add "Services" to the beginning and "Other" to the end
17502 servicesArray = [...servicesArray, "Other"];
17503
17504 let labels = document.querySelectorAll("label");
17505 labels.forEach(label => {
17506 let labelText = label.textContent.trim();
17507 if (
17508 labelText.includes("Services") ||
17509 labelText.includes("Products") ||
17510 labelText.includes("Case Study")
17511 ) {
17512 let input = label.parentElement.querySelector("input[type='text']");
17513 if (input) {
17514 let select = document.createElement("select");
17515 select.name = input.name;
17516 select.id = input.id;
17517
17518 servicesArray.forEach(service => {
17519 let option = document.createElement("option");
17520 option.value = service;
17521 option.textContent = service;
17522 select.appendChild(option);
17523 });
17524
17525 input.replaceWith(select);
17526 }
17527 }
17528 });
17529 }
17530
17531 await replaceInputWithDropdown();
17532 });
17533});
17534//]]>
17535</script>
17535
17536<!-- form dropdown code end --><!-- form button code begin -->
17536<script>
17537//<![CDATA[
17538
17539 function waitForDmAPI(callback) {
17540 if (typeof dmAPI !== 'undefined') {
17541 callback();
17542 } else {
17543 setTimeout(() => waitForDmAPI(callback), 100);
17544 }
17545 }
17546
17547 async function getFormText() {
17548 var collection = await dmAPI.loadCollectionsAPI();
17549 var buttonsData = await collection.data("buttons").get();
17550
17551 if (buttonsData.values.length > 0) {
17552 let formHtml = buttonsData.values[0].data.Form_Button;
17553 let tempDiv = document.createElement("div");
17554 tempDiv.innerHTML = formHtml;
17555 return tempDiv.textContent || tempDiv.innerText;
17556 }
17557
17558 return null;
17559 }
17560
17561 async function updateSubmitButton() {
17562 try {
17563 let formText = await getFormText();
17564 if (!formText) {
17565 console.warn("No form text found.");
17566 return;
17567 }
17568
17569 let buttons = document.querySelectorAll('input[type="submit"][value="Form Button"]');
17570 buttons.forEach(button => {
17571 button.value = formText;
17572 });
17573
17574 console.log("Updated submit buttons with new value:", formText);
17575 } catch (error) {
17576 console.error("Error updating submit buttons:", error);
17577 }
17578 }
17579
17580 document.addEventListener("DOMContentLoaded", function () {
17581 waitForDmAPI(updateSubmitButton);
17582 });
17583
17584//]]>
17585</script>
17585
17586<!-- form button code end --><!--  End Body End Markup -->
17587<!--  Site Wide Html Markup -->
17588<!--  Site Wide Html Markup -->
17589<!--  Begin Product Custom HTML Markup -->
17590<div id="fb-root"></div> 
17591<script>(function(d, s, id) { 
17592var js, fjs = d.getElementsByTagName(s)[0]; 
17593if (d.getElementById(id)) return; 
17594js = d.createElement(s); js.id = id; 
17595js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.8&appId=1822295708016537"; 
17596fjs.parentNode.insertBefore(js, fjs); 
17597}(document, 'script', 'facebook-jssdk'));</script>
vendor: 2 bytes, line 17597
17597
17598<script>
17599//make sure video's on iOS load first screen of the video.
17600dmAPI.runOnReady('russTest',function() {
17601    var _hibu_vid = document.getElementsByTagName('video');
17602    for (var i = 0; i < _hibu_vid.length; i++) {
17603        _hibu_vid[i].load();
17604    }
17605})
17606</script>
vendor: 2 bytes, line 17606
17606
17607<script>
17608//simple addition by Russ to add consent banner link into footer - updated by Adrian to account for Termly installs.
17609(function () {
17610  var poweredByFirstAnchor = document.querySelector('.poweredBy a:first-child');
17611  if (!poweredByFirstAnchor) return;
17612
17613  if (window.Termly) {
17614    var termlySpan = document.createElement('span');
17615    termlySpan.className = 'termly-display-preferences';
17616    termlySpan.textContent = 'Manage Consent Preferences';
17617    poweredByFirstAnchor.insertAdjacentElement('afterend', termlySpan);
17618  } else {
17619    var spSpan = document.createElement('span');
17620    spSpan.id = 'spConsentWrapper';
17621    poweredByFirstAnchor.insertAdjacentElement('afterend', spSpan);
17622    window.localStorage.setItem('__springtime__repop__target', '#spConsentWrapper');
17623  }
17624})();
17625</script>
17625
17626<style>
17627  #spConsentWrapper:before,
17628  .poweredBy .termly-display-preferences:before { content: " | " }
17629  .poweredBy .termly-display-preferences { cursor: pointer; }
17630</style>
17631<script>
17632if (window.location.search.indexOf('preview') > 0) {
17633  window.localStorage.setItem('ae_user_consent_decision_has_been_made', true);
17634  var spEl = document.getElementById('spConsentWrapper');
17635  if (spEl) spEl.remove();
17636}
17637</script>
17637<!--  End Product Custom HTML Markup -->
17638</body>
17639</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.