
(function () {
    'use strict';

    /* Evaluated once per embed tag. The whole widget lives in this closure so a
       second evaluation (the builder canvas re-mounts it after every save, and
       a page may paste the snippet twice) cannot collide on a top-level name.
       currentScript is only set while the script runs synchronously, so it is
       captured before anything else happens.

       NOTE: AppController::embed strips every literal script open/close tag from
       this file and injects style.css into the HTML template below, so neither
       tag may be written out in this file and the template needs no backticks. */
    var currentScript = document.currentScript;

    
    var CONFIG = {
        uid: "7ea5b1580e16e",
        title: "How much is your home worth right now?",
        subtitle: "Get a free, instant estimate in seconds.",
        buttonLabel: "Get my value",
        design: "pill",
        placeholder: "Enter your home address",
        sampleMode: false,
        collectsPhone: true,
        contactEmail: "tania@itstheperfectspot.com",
        styleOverride: "",
        urls: {
            valuation: "https:\/\/idxapps.com\/api\/plunkvaluation\/7ea5b1580e16e",
            sendGhl: "https:\/\/idxapps.com\/api\/plunk-send-ghl\/7ea5b1580e16e",
            marketInsights: "https:\/\/idxapps.com\/api\/plunkvaluation\/send\/7ea5b1580e16e"        }
    };

    var MAPS_SRC = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyB5aKUxlSqIedNX9UV_LQmF0j0BHHvfxbQ&libraries=places';

    /* ---- Registry: window.IDXHomeValuation[uid].destroy() --------------------
       destroy() tears down every instance mounted for this uid. Each wrapper also
       carries wrapper.idxHomeValuation = { destroy } for DOM-local callers. */
    var registry = (window.IDXHomeValuation = window.IDXHomeValuation || {});
    var entry = registry[CONFIG.uid];
    if (!entry || !Array.isArray(entry.instances)) {
        entry = registry[CONFIG.uid] = { instances: [] };
        entry.destroy = function () {
            entry.instances.slice().forEach(function (instance) { instance.destroy(); });
        };
    }

    var destroyed = false;
    var mounted = null;

    /* ---- Google Maps Places: loaded at most once per page --------------------
       Shared through a window-level state object so a remount, a second embed or
       a host page that already loads Maps never produces a second Maps tag. */
    function mapsReady() {
        return !!(window.google && window.google.maps && window.google.maps.places
            && window.google.maps.places.Autocomplete);
    }

    function loadMaps(done) {
        if (mapsReady()) { done(true); return; }

        var state = window.__idxHvMaps;
        if (state && state.waiters) { state.waiters.push(done); return; }

        state = window.__idxHvMaps = { waiters: [done], poll: null };

        function finish(ok) {
            if (!ok) window.__idxHvMaps = null; // allow a later retry
            var waiters = state.waiters || [];
            state.waiters = null;
            window.clearInterval(state.poll);
            waiters.forEach(function (waiter) { waiter(ok); });
        }

        function addOwnTag() {
            var tag = document.createElement('script');
            tag.src = MAPS_SRC;
            tag.async = true;
            tag.onload = function () { finish(mapsReady()); };
            tag.onerror = function () {
                if (tag.parentNode) tag.parentNode.removeChild(tag);
                finish(false);
            };
            document.head.appendChild(tag);
        }

        var existing = document.querySelector('script[src*="maps.googleapis.com/maps/api/js"]');
        if (existing) {
            // Someone else owns the tag, so poll for the API rather than adding a
            // second copy (which Google reports as a duplicate-load error). Host
            // pages often load Maps WITHOUT places: once Maps itself is up, ask
            // it for places via importLibrary, and if that is unavailable or the
            // host tag never delivers, fall back to our own tag as the widget
            // always did before.
            var waited = 0;
            var importing = false;
            state.poll = window.setInterval(function () {
                waited += 100;
                if (mapsReady()) { finish(true); return; }
                var maps = window.google && window.google.maps;
                if (maps && typeof maps.importLibrary === 'function' && !importing) {
                    importing = true;
                    maps.importLibrary('places').then(function () {
                        if (mapsReady()) finish(true);
                    }, function () { /* keep polling; the timeout falls back */ });
                }
                if (waited >= 5000) {
                    window.clearInterval(state.poll);
                    addOwnTag();
                }
            }, 100);
            return;
        }

        addOwnTag();
    }

    function destroy() {
        destroyed = true;
        if (mounted) mounted.destroy();
    }

    loadMaps(function (ok) {
        if (destroyed) return;
        if (!ok) {
            console.error('HomeValuation: Google Maps Places did not load');
            return;
        }
        // The embed tag was removed from the page while Maps was loading (the
        // builder canvas was re-mounted): this instance is stale, do not paint.
        if (currentScript && !currentScript.isConnected) return;

        mounted = mount();
        entry.instances.push(mounted);
        // The builder unlocks its canvas only when THIS says sample mode is on,
        // so a live script that slipped in (expired session, a cache that
        // ignores the query string) stays display-only there.
        mounted.wrapper.dispatchEvent(new CustomEvent('idxhv:ready', {
            bubbles: true,
            composed: true,
            detail: { sampleMode: CONFIG.sampleMode === true }
        }));
    });

    function mount() {
        var HTML = `<style>/* NOTE: never use a backtick or a dollar-brace in this file. AppController::embed
   injects it into a JS template literal via addslashes(), which escapes quotes
   but NOT backticks -- one would terminate the string and break the whole widget.

   The widget carries no reset of its own and was quietly relying on the host page
   providing one: box-sizing is not inherited, so only the two elements that opt in
   below were ever border-box. Inside the IDXApps dashboard Tailwind supplies a
   universal border-box rule and everything lines up; on a customer site without a
   reset a fixed-width padded button grew past its width and overran the input.

   The two modals are injected as SIBLINGS of the wrapper, not inside it, so they
   have to be listed separately. */
#plunk-valuation-wrapper,
#plunk-valuation-wrapper *,
#plunk-valuation-wrapper *::before,
#plunk-valuation-wrapper *::after,
#pv-lead-modal,
#pv-lead-modal *,
#pv-valuation,
#pv-valuation * {
    box-sizing: border-box;
}

#plunk-valuation-wrapper {
    --pv-color-text: #0B1F3A;
    --pv-bg-btn: #1D5FD8;
    --pv-color-btn: #ffffff;
    --pv-bg-input: #ffffff;
    --pv-border-color: #D5DEEA;
    --pv-input-color: #0B1F3A;

    /* Stated, not left to \"auto\": container-type gives the wrapper no intrinsic
       width, so in a flex or grid parent an auto width collapses it to its
       padding. 100% fills the slot up to max-width, as a block parent did. */
    width: 100%;
    max-width: 680px;
    padding: 24px 20px;
    margin: 0px auto;
    background-color: white;
    /* Lets the stacked layout below key off the widget\'s OWN width, so a
       narrow sidebar column on a wide page stacks too. */
    container-type: inline-size;
    /* Inherit the host page\'s font; the widget ships no font of its own. */
    font-family: inherit;
    color: var(--pv-color-text);
    box-sizing: border-box;
    border-radius: 16px;
    line-height: normal !important;
    letter-spacing: normal !important;
}

#plunk-valuation-wrapper .pv-heading-container {
    text-align: center;
    margin-top: 0;
    margin-bottom: 32px;
    color: var(--pv-color-text);
}

#plunk-valuation-wrapper .pv-title-text {
    display: block;
    margin: 0;
    padding: 0;
    font-size: 34px;
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -0.02em;
    /* Hosts with a wider font wrap this; balance avoids a one-word last line. */
    text-wrap: balance;
    text-transform: none;
    color: var(--pv-color-text);
}

/* Fixed grey (7.5:1 on white) -- deliberately not a customer variable. The
   explicit margin and padding beat host-page paragraph rules. */
#plunk-valuation-wrapper .pv-subtitle-text {
    margin: 14px 0 0;
    padding: 0;
    font-size: 17px;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: normal;
    text-transform: none;
    color: #4B5563;
}

/* THE PILL. A flex row that owns border, radius, background, shadow and the
   focus ring. The button is a real flex item (not absolutely positioned), so
   its width comes from its own padding and the input simply takes the rest.

   position:relative is required: Google Places appends its .pac-container
   inside this form. Never add overflow:hidden here or the dropdown is clipped. */
#plunk-valuation-wrapper .pv-input-container {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    width: 100%;
    max-width: 600px;
    min-width: 0;
    height: 72px;
    margin: 0 auto;
    padding: 0;
    padding-inline-end: 8px;
    background: var(--pv-bg-input);
    border: 1px solid var(--pv-border-color);
    border-radius: 999px;
    box-shadow: 0 10px 30px rgba(15, 35, 70, 0.08);
    transition: border-color 0.15s ease-out, box-shadow 0.15s ease-out;
}

#plunk-valuation-wrapper .pv-input-container:focus-within {
    /* Transparent outline: invisible normally, but forced-colours mode drops
       box-shadow and paints outlines, so the field keeps a focus indicator. */
    outline: 2px solid transparent;
    outline-offset: 2px;
    border-color: var(--pv-bg-btn);
    box-shadow: 0 0 0 2px var(--pv-bg-btn), 0 10px 30px rgba(15, 35, 70, 0.08);
}

/* The pin and the input live in the label. It is a flex ROW, with the icon as a
   real sibling column of the input rather than an overlay, so no padding value
   (or host-page override) can ever put text under the icon.

   Scoped to the direct child of the form -- .pv-label is reused by the four
   lead-capture fields, which must stay column-flow. */
#plunk-valuation-wrapper .pv-input-container > .pv-label {
    display: flex;
    flex-direction: row;
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
    align-items: center;
    gap: 14px;
    margin: 0;
    padding: 0;
    padding-inline-start: 28px;
    height: 100%;
    background: transparent;
    border: none;
    cursor: text;
}

#plunk-valuation-wrapper .pv-input-container > .pv-label > .bi {
    display: block;
    flex: 0 0 20px;
    width: 20px !important;
    height: 20px !important;
    margin: 0;
    /* Derived from the input colour, not a fixed grey: a customer\'s dark
       input background comes with a light input text. */
    color: var(--pv-input-color);
    opacity: 0.8;
    /* Both icons are outlines. A host rule like \"svg { fill: red }\" beats the
       fill=\"none\" attribute, so the paint is restated here. */
    fill: none;
    stroke: currentColor;
}

/* min-width:0 is load-bearing: a flex item\'s default min-width is auto, so a
   long address would otherwise force the input wider than the pill instead of
   scrolling inside it. Hosts often style inputs aggressively, hence the reset
   of every property that could change the box. */
#plunk-valuation-wrapper .pv-input {
    box-sizing: border-box;
    flex: 1 1 auto;
    min-width: 0;
    width: 100% !important;
    height: auto !important;
    min-height: 0;
    margin: 0;
    padding: 0;
    font: inherit;
    font-size: 18px;
    font-weight: 400;
    line-height: 24px !important;
    letter-spacing: normal;
    text-transform: none;
    color: var(--pv-input-color);
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background: transparent;
    background-image: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
    outline: none;
}

/* The input colour at 65% -- about 5:1 on the default white, and it follows a
   customer\'s own input colours. Firefox dims placeholders by default, so the
   opacity is stated. */
#plunk-valuation-wrapper .pv-input::placeholder {
    color: var(--pv-input-color);
    opacity: 0.65;
}

#plunk-valuation-wrapper .pv-input:focus-visible,
#plunk-valuation-wrapper .pv-input:focus {
    outline: none;
    border: none;
    box-shadow: none;
}

.pv-lead-input {
    box-sizing: border-box;
    color: var(--pv-input-color);
    font-size: 13px;
    width: 100% !important;
    padding: 8px 13px;
    appearance: none;
    outline-offset: 0.5px;
    height: 46px !important;
    border: 1px solid var(--pv-border-color);
    border-radius: 0.375rem;
    /* 6px */
    z-index: 10;

}

#pv-tel {
    height: 50px;
    font-weight: bold;
    color: #003960;
    font-size: 16px;
}

#pv-tel::placeholder {
    color: #809CAF;
}

#pv-tel:focus-visible {
    outline: none;
    border: 1px solid #2F82FF;
    box-shadow: 0 0 0 1px #2F82FF;
}

#plunk-valuation-wrapper .pv-button {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: 8px;
    flex: 0 0 auto;
    width: auto;
    max-width: 55%;
    min-width: 0;
    height: 56px;
    min-height: 0;
    margin: 0;
    padding: 0 22px;
    border: none;
    border-radius: 999px;
    box-shadow: none;
    background-color: var(--pv-bg-btn);
    background-image: none;
    color: var(--pv-color-btn) !important;
    font: inherit;
    font-size: 17px;
    font-weight: 700;
    line-height: 1 !important;
    letter-spacing: normal;
    text-transform: none;
    text-decoration: none;
    white-space: nowrap;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    pointer-events: all;
    cursor: pointer;
    transition: filter 0.15s ease-out, transform 0.15s ease-out;
}

/* The label text is customer-editable: a long one truncates inside the pill. */
#plunk-valuation-wrapper .pv-button .pv-button-label {
    min-width: 0;
    letter-spacing: normal;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#plunk-valuation-wrapper .pv-button svg {
    display: block;
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    margin: 0;
    color: inherit;
    fill: none;
    stroke: currentColor;
}

/* Colours are restated on every state: host themes commonly ship
   \"button:hover { background }\" rules that would otherwise repaint it. */
#plunk-valuation-wrapper .pv-button:hover,
#plunk-valuation-wrapper .pv-button:focus,
#plunk-valuation-wrapper .pv-button:active {
    background-color: var(--pv-bg-btn);
    background-image: none;
    color: var(--pv-color-btn) !important;
    border: none;
    box-shadow: none;
}

#plunk-valuation-wrapper .pv-button:hover {
    filter: brightness(0.92);
}

#plunk-valuation-wrapper .pv-button:active {
    filter: brightness(0.85);
    transform: translateY(1px);
}

/* Text colour, not the button colour: a customer\'s light button would make a
   same-coloured ring disappear against the white card. */
#plunk-valuation-wrapper .pv-button:focus-visible {
    outline: 3px solid var(--pv-color-text);
    outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
    #plunk-valuation-wrapper .pv-input-container,
    #plunk-valuation-wrapper .pv-button {
        transition: none;
    }

    #plunk-valuation-wrapper .pv-button:active {
        transform: none;
    }
}

/* Narrow layout: the pill becomes a rounded card. The input row sits on top and
   the button drops full-width underneath, so nothing overflows down to 320px.

   Written twice on purpose. The @container copy keys off the widget\'s own
   width (a 300px sidebar column on a desktop page); the @media copy is the
   fallback for browsers without container queries and also tightens the
   wrapper\'s own padding, which a container cannot do for itself. */
@media (max-width: 520px) {
    #plunk-valuation-wrapper {
        padding: 16px;
    }

    #plunk-valuation-wrapper .pv-heading-container {
        margin-bottom: 24px;
    }

    #plunk-valuation-wrapper .pv-title-text {
        font-size: 26px;
    }

    #plunk-valuation-wrapper .pv-subtitle-text {
        font-size: 16px;
    }

    #plunk-valuation-wrapper .pv-input-container {
        flex-wrap: wrap;
        height: auto;
        padding: 8px;
        border-radius: 20px;
    }

    #plunk-valuation-wrapper .pv-input-container > .pv-label {
        flex: 1 1 100%;
        height: 52px;
        padding: 0;
        padding-inline: 12px 8px;
        gap: 12px;
    }

    /* 16px stops iOS Safari zooming the page on focus. */
    #plunk-valuation-wrapper .pv-input {
        font-size: 16px;
    }

    /* Full width here, so a long label can wrap instead of being cut off. */
    #plunk-valuation-wrapper .pv-button {
        flex: 1 1 100%;
        width: 100%;
        max-width: 100%;
        height: auto;
        min-height: 56px;
        margin-top: 4px;
        padding-top: 10px;
        padding-bottom: 10px;
        line-height: 1.2 !important;
        white-space: normal;
    }

    #plunk-valuation-wrapper .pv-button .pv-button-label {
        white-space: normal;
    }
}

@container (max-width: 520px) {
    #plunk-valuation-wrapper .pv-heading-container {
        margin-bottom: 24px;
    }

    #plunk-valuation-wrapper .pv-title-text {
        font-size: 26px;
    }

    #plunk-valuation-wrapper .pv-subtitle-text {
        font-size: 16px;
    }

    #plunk-valuation-wrapper .pv-input-container {
        flex-wrap: wrap;
        height: auto;
        padding: 8px;
        border-radius: 20px;
    }

    #plunk-valuation-wrapper .pv-input-container > .pv-label {
        flex: 1 1 100%;
        height: 52px;
        padding: 0;
        padding-inline: 12px 8px;
        gap: 12px;
    }

    /* 16px stops iOS Safari zooming the page on focus. */
    #plunk-valuation-wrapper .pv-input {
        font-size: 16px;
    }

    /* Full width here, so a long label can wrap instead of being cut off. */
    #plunk-valuation-wrapper .pv-button {
        flex: 1 1 100%;
        width: 100%;
        max-width: 100%;
        height: auto;
        min-height: 56px;
        margin-top: 4px;
        padding-top: 10px;
        padding-bottom: 10px;
        line-height: 1.2 !important;
        white-space: normal;
    }

    #plunk-valuation-wrapper .pv-button .pv-button-label {
        white-space: normal;
    }
}

/* Shown by the Go handler when no address has been resolved. Sits just under the
   pill, aligned with the input text (28px inset + 20px icon + 14px gap + 1px
   border). Selected through the wrapper so the shared .pv-error rule further
   down (margin-top: 0) cannot take the gap back. */
#plunk-valuation-wrapper .pv-address-error {
    margin: 10px auto 0;
    max-width: 600px;
    padding: 0;
    padding-inline-start: 63px;
    text-align: start;
}

@media (max-width: 520px) {
    #plunk-valuation-wrapper .pv-address-error {
        padding-inline-start: 21px;
    }
}

@container (max-width: 520px) {
    #plunk-valuation-wrapper .pv-address-error {
        padding-inline-start: 21px;
    }
}

#pv-cta-button.is-loading {
    pointer-events: none;
    opacity: .50;
}

.pv-powered-container {
    display: flex;
    justify-content: center;
}

.pv-powered-container img {
    width: 250px;
}

/* The search bar\'s own \"Powered by\" row. Scoped to the wrapper: the valuation
   modal reuses .pv-powered-container and keeps the rules above. */
#plunk-valuation-wrapper .pv-powered-container {
    align-items: center;
    gap: 8px;
    margin-top: 28px;
}

#plunk-valuation-wrapper .pv-powered-text {
    margin: 0;
    font-size: 13px;
    line-height: 1;
    letter-spacing: normal;
    text-transform: none;
    color: #4B5563;
}

#plunk-valuation-wrapper .pv-powered-container img {
    display: block;
    width: 140px;
    max-width: 100%;
    height: auto;
    margin: 0;
    padding: 0;
    border: 0;
    box-shadow: none;
}

.pv-modal {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, 0.7);
    opacity: 0;
    visibility: hidden;
    transform: scale(1.1);
    transition: visibility 0s linear 0.25s, opacity 0.25s 0s, transform 0.25s;
    font-family: \"Open Sans\", \"Helvetica Neue\", Helvetica, sans-serif;
    z-index: 1000;
}

.pv-modal-content {
    position: absolute;
    color: var(--neutral-3);
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: white;
    padding: 24px;
    border-radius: 8px;
    width: 100%;
    max-width: 100vw;
    max-height: 100vh;
    overflow-y: auto;
    transition: max-height 0.15s ease-out;
}

@media (min-width: 480px){
    .pv-modal-content {
        max-width: 540px;
    }
}

.pv-label {
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 100%;
    margin-bottom: 14px;
}

.pv-radio-hidden {
    display: none;
}

.pv-tabs-interest {
    display: flex;
    justify-content: center;
    width: 100%;
}

.pv-tab-interest {
    width: 100%;
    text-align: center;
    margin-top: 10px;
    padding: 20px 20px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 16px;
    background-color: #f1f5f9;
    color: #003960;
    transition: background-color 0.15s ease-out 0s, color 0.15s ease-out 0s;
    border: 1px solid #e2e8f0;
}

.pv-tab-interest:first-child {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
}

.pv-tab-interest:last-child {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
}

.pv-tab-interest:hover {
    background-color: #e2e8f0;
}

.pv-tab-interest:has(input[type=\"radio\"]:checked) {
    border-color: #2569CC;
    border-width: 2px;
}

.pv-lead-input:focus {
    border-color: green;
    /* Cambia el color del borde cuando el campo está enfocado y es inválido */
}

input:invalid:focus {
    border-color: red;
    /* Cambia el color del borde cuando el campo está enfocado y es inválido */
}



.close-button {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    width: 1.5rem;
    line-height: 1.5rem;
    text-align: center;
    cursor: pointer;
    border-radius: 0.25rem;
    font-size: 20px;
    background-color: #f1f5f9;
}

.close-button:hover {
    background-color: #e2e8f0;
}

.show-modal {
    opacity: 1;
    visibility: visible;
    transform: scale(1.0);
    transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s;
}

.pv-full-name {
    display: flex;
    gap: 8px;
    width: 100%;
}

.pv-address-wrapper {
    width: 100%;
    display: flex;
    flex-direction: column;
}

.pv-address-title {
    font-size: 24px;
    font-weight: bold;
    color: #003960 !important;
    margin: 0 !important;
}

.pv-address-subtitle {
    margin-top: 4px;
    margin-bottom: 0;
}

.pv-address-subtitle #pv-city,
.pv-address-subtitle #pv-state {
    font-size: 16px;
    margin: 0 !important;
}

.pv-content {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    width: 100%;
}

.pv-subtitle {
    position: relative;
    text-align: center;
    font-size: 21px;
    text-wrap: balance;
    padding-bottom: 6px;
    margin-bottom: 16px;
    font-weight: bold;
}

.pv-input-group {
    position: relative;
}

.pv-input-group svg {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    right: 10px;
    fill: #003960;
    width: 16px;
    height: 16px;
}

.pv-error {
    display: none;
    margin-top: 0;
    color: red;
    font-size: 12px;
}

.pv-submit {
    background-color: #2569CC;
    color: white;
    border: none;
    padding: 15px 30px;
    border-radius: 8px;
    cursor: pointer;
    font-size: 16px;
    margin-top: 20px;
    float: right;
}

.pv-submit.center {
    align-self: center;

}

.plunk-component {
    display: flex;
    justify-content: center;
    gap: 5px;
    margin-top: 40px;
    margin-bottom: 50px;
    width: 100%;
}

.pv-price-symbol {
    margin-right: 10px;
    font-size: 42px;
    font-weight: bold;
    color: #003960;
    margin: 0;
}

.pv-valuation-price {
    font-size: 72px;
    text-align: center;
    font-weight: bold;
    color: #003960;
    margin: 0;
    line-height: 0.9;
}

/* Market insights */
#market-insights-container {
    padding-top: 30px;
    width: 100%;
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.15s ease-out;
}

#market-insights-container.market-insights-expanded {
    grid-template-rows: 1fr;
    border-top: solid 1px #E6EBEF;
}

.pv-callToAction-wrapper {
    overflow: hidden;
}

.pv-callToAction {
    margin-bottom: 15px;
    background: #1488CC;
    /* fallback for old browsers */
    background: -webkit-linear-gradient(to bottom, #2B32B2, #1488CC);
    /* Chrome 10-25, Safari 5.1-6 */
    background: linear-gradient(to bottom, #2B32B2, #1488CC);
    /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
    border-radius: 13px;
    color: white;
    padding: 19px;
    font-size: 14px;
    overflow: hidden;
}

.pv-cta-form {
    box-sizing: inherit;
    position: relative;
    min-width: 168px;
    display: flex;
    justify-content: center;
    margin: 0;
    margin: 15px 0;
}

.pv-callToAction h2 {
    font-size: 22px;
    margin-bottom: 18px;
    font-weight: bold;
    color: white;
}

.pv-callToAction p {
    font-size: 13px;
}

@media (min-width: 480px) {

    .pv-callToAction h2 {
        font-size: 24px;
    }

    .pv-callToAction p {
        font-size: 16px;
    }
}

.pv-email {
    border-radius: 8px;
    padding: 6px 9px;
}

.css-1s4q5ra {
    width: 100%;
}

/* Google sizes the suggestions to the input\'s own width and positions them
   against the page. Inside our form they hang from the pill instead: full pill
   width, just below it. Scoped to the form so another Places field on the host
   page is left alone. */
#pv-form .pac-container {
    top: 80px !important;
    left: 0px !important;
    width: 100% !important;
}

/* Stacked layout: the input row ends 61px into the card (8px padding + 52px
   row + 1px border), so open the suggestions right under it, over the button. */
@media (max-width: 520px) {
    #pv-form .pac-container {
        top: 64px !important;
    }
}

@container (max-width: 520px) {
    #pv-form .pac-container {
        top: 64px !important;
    }
}

/* Skeleton loader Animations */
.pv-skeleton-wrapper {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.pv-skeleton-animation {
    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}

.pv-ticker-big-skeleton {
    margin-top: 35px;
    width: 268px;
    height: 60px;
    background-color: #BFCDD7;
    border-radius: 4px;
}

.pv-ticker-small-skeleton {
    margin-top: 5px;
    width: 124px;
    height: 37px;
    background-color: #E6EBEF;
    border-radius: 4px;
}

@keyframes pulse {

    0%,
    100% {
        opacity: 1;
    }

    50% {
        opacity: .5;
    }
}

.plunk-home-no-value-home-value {
    font-family: inherit;
    letter-spacing: -1px;
    font-size: 24px;
    margin-top: 0.3rem;
    font-weight: 700;
    color: #003960;
    text-align: center;
}

.plunk-home-no-value-suffix {
    margin-left: auto;
    margin-right: auto;
    width: 77%;
    text-align: center;
    font-family: inherit;
    color: #406A88;
    font-weight: 500;
    font-size: 18px;
    margin-top: 0.6rem;
}

.pv-alert {
    padding: 12px 16px;
    border-radius: 6px;
    font-size: 14px;
    line-height: 1.5;
    margin: 10px 0;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.pv-alert.show {
    opacity: 1;
}

.pv-alert-danger {
    background: #fef2f2;
    color: #991b1b;
    border: 1px solid #fecaca;
}

.pv-alert-warning {
    background: #fffbeb;
    color: #92400e;
    border: 1px solid #fde68a;
}

/* TODO remove */
.site-header {
    z-index: unset;
}

/* ==========================================================================
   SPLIT DESIGN  --  #plunk-valuation-wrapper[data-pv-design=\"split\"]

   Two columns: headline, address field and a full-width button on the left; a
   decorative sample card on the right. Everything here is scoped to the
   attribute, and every selector carries it, so it outranks the pill rules above
   (including their narrow-width blocks) without editing them: the pill design
   must keep rendering exactly as it did.

   The columns are a wrapping flex row, not a grid. The wrapper is the query
   container and a container cannot restyle itself, so the switch to one column
   has to happen without a query: the two flex bases (340px + 260px + the gap)
   simply stop fitting below about 645px of content width and the aside wraps.
   ========================================================================== */
#plunk-valuation-wrapper:not([data-pv-design=\"split\"]) .pv-preview-card {
    display: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 28px 44px;
    max-width: 960px;
    /* A percentage padding resolves against the PARENT\'s width, which is the
       one thing the wrapper can measure about its slot without a query on
       itself: 40px in a wide slot, down to 16px in a 300px sidebar column. */
    padding: clamp(16px, 5.2%, 40px);
    background-color: #F5F7FB;
    text-align: start;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-main {
    flex: 1 1 340px;
    min-width: 0;
    margin: 0;
    padding: 0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-aside {
    flex: 1 1 260px;
    min-width: 0;
    margin: 0;
    padding: 0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-heading-container {
    margin-bottom: 28px;
    text-align: start;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-title-text {
    font-size: 40px;
    line-height: 1.12;
    /* Left-aligned in a narrow column, balanced lines look arbitrary. */
    text-wrap: wrap;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-subtitle-text {
    margin: 16px 0 0;
    font-size: 17px;
    line-height: 1.5;
    text-align: start;
}

/* The form is no longer the pill: it is a plain column, and the LABEL carries
   the field\'s box. Every property the pill rules (wide and narrow) put on the
   form is restated so none of it shows through. */
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input-container {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    max-width: none;
    height: auto;
    margin: 0;
    padding: 0;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input-container:focus-within {
    outline: none;
    border: 0;
    box-shadow: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input-container > .pv-label {
    flex: 0 0 auto;
    width: 100%;
    height: 56px;
    gap: 12px;
    padding: 0;
    padding-inline: 20px 16px;
    background: var(--pv-bg-input);
    border: 1px solid var(--pv-border-color);
    border-radius: 12px;
    /* The pale border alone barely separates a white field from the pale
       background; the pill has its drop shadow for that. */
    box-shadow: 0 1px 2px rgba(15, 35, 70, 0.08), 0 4px 12px rgba(15, 35, 70, 0.05);
    transition: border-color 0.15s ease-out, box-shadow 0.15s ease-out;
}

/* Same ring as the pill, on the box that now owns the border. The transparent
   outline keeps a focus indicator in forced-colours mode. */
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input-container > .pv-label:focus-within {
    outline: 2px solid transparent;
    outline-offset: 2px;
    border-color: var(--pv-bg-btn);
    box-shadow: 0 0 0 2px var(--pv-bg-btn);
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input {
    font-size: 17px;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-button {
    flex: 0 0 auto;
    width: 100%;
    max-width: 100%;
    height: auto;
    min-height: 52px;
    margin: 12px 0 0;
    padding: 10px 22px;
    border-radius: 10px;
    font-size: 16px;
    line-height: 1.2 !important;
    white-space: normal;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-button .pv-button-label {
    white-space: normal;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-address-error {
    max-width: none;
    margin: 8px 0 0;
    padding: 0;
    text-align: start;
}

/* The field is the first row of the form at every width, 56px tall. */
#plunk-valuation-wrapper[data-pv-design=\"split\"] #pv-form .pac-container {
    top: 60px !important;
}

/* ---- The card. A decorative, aria-hidden placeholder until a valuation has
   been run: fixed whites and greys, with the accent taken from the customer\'s
   button colour. Its figures are skeleton blocks on purpose -- a made-up dollar
   amount beside a real address field would read as a real estimate. Once the
   visitor has a result it is filled with it (see the data-pv-filled rules at
   the end of this section). ---- */
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card {
    display: block;
    margin: 0;
    padding: 22px;
    background: #ffffff;
    border: 1px solid #E3E8F0;
    border-radius: 20px;
    box-shadow: 0 12px 32px rgba(15, 35, 70, 0.08);
    color: #4B5563;
    text-align: start;
    letter-spacing: normal;
    text-transform: none;
    pointer-events: none;
    -webkit-user-select: none;
    user-select: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-head {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 12px;
    margin: 0;
    padding: 0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-icon {
    display: flex;
    flex: 0 0 40px;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    margin: 0;
    padding: 0;
    border-radius: 10px;
    background: #E8EFFC;
    color: var(--pv-bg-btn);
}

/* Behind @supports: a declaration containing var() is always accepted at parse
   time, so without the guard a browser lacking color-mix would drop the tile\'s
   background entirely instead of falling back to the plain colour above. */
@supports (background: color-mix(in srgb, red 10%, white)) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-icon {
        background: color-mix(in srgb, var(--pv-bg-btn) 12%, #ffffff);
    }
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-icon svg {
    display: block;
    width: 20px !important;
    height: 20px !important;
    max-width: none;
    margin: 0;
    fill: none;
    stroke: currentColor;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-lines {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
    margin: 0;
    padding: 0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-bar {
    display: block;
    width: 62%;
    height: 10px;
    margin: 0;
    border-radius: 5px;
    background: #E5E9F0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-bar-short {
    width: 40%;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-label {
    margin: 18px 0 0;
    padding: 0;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #4B5563;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-value {
    display: block;
    width: 70%;
    height: 34px;
    margin: 8px 0 0;
    padding: 0;
    border-radius: 8px;
    background: var(--pv-color-text);
    opacity: 0.12;
}

/* Hosts commonly ship \"svg { max-width: 100%; height: auto }\"; without the
   pinned size the chart collapses to its viewBox ratio or to nothing. */
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-chart {
    display: block;
    width: 100% !important;
    max-width: none;
    height: 64px !important;
    margin: 16px 0 0;
    padding: 0;
    overflow: visible;
    fill: none;
    stroke: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-area {
    fill: var(--pv-bg-btn);
    fill-opacity: 0.1;
    stroke: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-line {
    fill: none;
    stroke: var(--pv-bg-btn);
    stroke-width: 2.5px;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-range {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin: 12px 0 0;
    padding: 0;
    font-size: 13px;
    line-height: 1.2;
    color: #4B5563;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-low,
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-high {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    gap: 6px;
    margin: 0;
    padding: 0;
    letter-spacing: normal;
    text-transform: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-chip {
    display: block;
    width: 44px;
    height: 10px;
    margin: 0;
    border-radius: 5px;
    background: #E5E9F0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-powered-container {
    margin-top: 16px;
}

@media (prefers-reduced-motion: reduce) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input-container > .pv-label {
        transition: none;
    }
}

/* The card is drawn with backgrounds only. Forced-colours mode and print both
   drop backgrounds, which would leave three labels beside blanks. */
@media (forced-colors: active) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card {
        display: none;
    }
}

@media print {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card {
        display: none;
    }
}

/* ---- Narrow. Written twice like the pill blocks above: the container copy
   follows the widget\'s own width, the media copy is the fallback. Neither the
   one-column switch nor the wrapper padding needs a query (see the notes at
   the top of this section). ---- */
@media (max-width: 520px) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-title-text {
        font-size: 30px;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-subtitle-text {
        font-size: 16px;
    }

    /* 16px stops iOS Safari zooming the page on focus. */
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input {
        font-size: 16px;
    }
}

@container (max-width: 520px) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-title-text {
        font-size: 30px;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-subtitle-text {
        font-size: 16px;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-input {
        font-size: 16px;
    }
}

/* Very narrow: the sample card would push the powered-by row a full screen
   down for little benefit, so it goes. */
@media (max-width: 380px) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-title-text {
        font-size: 26px;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card {
        display: none;
    }
}

@container (max-width: 380px) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-title-text {
        font-size: 26px;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card {
        display: none;
    }
}

/* ---- Filled sample card. After the visitor closes the valuation popup the
   card shows their real result: street address, \"City, ST\" and the figure.
   Placeholder rules above are untouched; everything below needs the
   data-pv-filled attribute on the card, except the new elements\' base rules. ---- */
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-aside {
    /* Anchors the visually hidden announcer so its 1px box cannot create
       scroll overflow on the page. No layout effect. */
    position: relative;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-address,
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-place {
    display: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-announce {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
    /* The placeholder is inert decoration; a filled card is the visitor\'s own
       result, so they can select and copy it. */
    pointer-events: auto;
    -webkit-user-select: text;
    user-select: text;
    border-color: #C7D2E6;
    box-shadow: 0 14px 36px rgba(15, 35, 70, 0.14);
    transition: box-shadow 0.2s ease-out, border-color 0.2s ease-out;
}

@supports (border-color: color-mix(in srgb, red 35%, white)) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
        border-color: color-mix(in srgb, var(--pv-bg-btn) 35%, #ffffff);
    }
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-bar {
    display: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-address,
#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-place {
    display: block;
    min-width: 0;
    margin: 0;
    padding: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    letter-spacing: normal;
    text-transform: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-address {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--pv-color-text);
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-place {
    margin-top: 2px;
    font-size: 13px;
    font-weight: 400;
    line-height: 1.3;
    color: #4B5563;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-place:empty {
    display: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-value {
    width: auto;
    max-width: 100%;
    height: auto;
    margin: 6px 0 0;
    padding: 0;
    border-radius: 0;
    background: transparent;
    opacity: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 30px;
    font-weight: 800;
    line-height: 1.15;
    letter-spacing: -0.01em;
    text-transform: none;
    font-variant-numeric: tabular-nums;
    color: var(--pv-color-text);
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-range {
    display: none;
}

#plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-chart {
    margin: 12px 0 4px;
}

@media (prefers-reduced-motion: reduce) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
        transition: none;
    }
}

/* A filled card is content: it stays visible where the placeholder is hidden.
   These follow the hiding rules above and carry the attribute for specificity. */
@media (max-width: 380px) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
        display: block;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-value {
        font-size: 26px;
    }
}

@container (max-width: 380px) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
        display: block;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-value {
        font-size: 26px;
    }
}

/* Backgrounds and the chart colours are dropped in forced-colours mode: give
   the card a system border and drop the decorative chart. */
@media (forced-colors: active) {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
        display: block;
        border: 1px solid CanvasText;
    }

    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] .pv-preview-chart {
        display: none;
    }
}

@media print {
    #plunk-valuation-wrapper[data-pv-design=\"split\"] .pv-preview-card[data-pv-filled] {
        display: block;
    }
}
</style>
            <div id="plunk-valuation-wrapper">
                <div class="pv-main">
                <div class="pv-heading-container">
                    <span class="pv-title-text"></span>
                    <p class="pv-subtitle-text"></p>
                </div>
                <form id="pv-form" class="pv-input-container">
                    <label class="pv-label">
                        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="bi" aria-hidden="true"><path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
                        <input id="pv-address" name="address" class="pv-input" autocomplete="off" aria-label="Home address">
                    </label>
                    <button type="submit" class="pv-button">
                        <span class="pv-button-label"></span>
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M5 12h14"></path><path d="M13 6l6 6-6 6"></path></svg>
                    </button>
                </form>
                <p class="pv-error pv-address-error" id="pv-address-error" role="alert">Please choose an address from the suggestions.</p>
                </div>
                <div class="pv-aside">
                    <div class="pv-preview-card" aria-hidden="true">
                        <div class="pv-preview-head">
                            <span class="pv-preview-icon"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l9-8 9 8"></path><path d="M5 10v10h14V10"></path><path d="M10 20v-6h4v6"></path></svg></span>
                            <span class="pv-preview-lines"><span class="pv-preview-bar"></span><span class="pv-preview-bar pv-preview-bar-short"></span><span class="pv-preview-address"></span><span class="pv-preview-place"></span></span>
                        </div>
                        <div class="pv-preview-label">Estimated value</div>
                        <div class="pv-preview-value"></div>
                        <svg class="pv-preview-chart" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 64" preserveAspectRatio="none"><path class="pv-preview-area" d="M0 52 L40 44 L80 47 L120 36 L160 39 L200 26 L240 18 L240 64 L0 64 Z"></path><polyline class="pv-preview-line" points="0,52 40,44 80,47 120,36 160,39 200,26 240,18"></polyline></svg>
                        <div class="pv-preview-range">
                            <span class="pv-preview-low">Low <span class="pv-preview-chip"></span></span>
                            <span class="pv-preview-high">High <span class="pv-preview-chip"></span></span>
                        </div>
                    </div>
                <div class="pv-powered-container">
                    <span class="pv-powered-text">Powered by</span>
                    <img src="https://images.leadconnectorhq.com/image/f_webp/q_80/r_1200/u_https://assets.cdn.filesafe.space/LB6pV83yCjjuXJZqjSMq/media/67d07e47de31f45f561eacd6.png" alt="HomeValuation.ai" />
                </div>
                <p class="pv-preview-announce" aria-live="polite"></p>
                </div>
            </div>
            <div class="pv-modal" id="pv-lead-modal">
            <form class="pv-modal-content" id="lead-form">
                <h2 class="pv-subtitle">One step closer to getting your free valuation!</h2>
                <span class="close-button">&times;</span>
            <div class="pv-full-name">
                <label class="pv-label"> First name * <div class="pv-input-group"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome  - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/></svg><input type="text" placeholder="John" name="first_name" class="pv-lead-input" required></div></label>
                <label class="pv-label"> Last name * <div class="pv-input-group"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome  - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/></svg><input type="text" placeholder="Doe" name="last_name" class="pv-lead-input" required></div></label>
                </div>
                <label class="pv-label"> Email * <div class="pv-input-group"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome  - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M48 64C21.5 64 0 85.5 0 112c0 15.1 7.1 29.3 19.2 38.4L236.8 313.6c11.4 8.5 27 8.5 38.4 0L492.8 150.4c12.1-9.1 19.2-23.3 19.2-38.4c0-26.5-21.5-48-48-48H48zM0 176V384c0 35.3 28.7 64 64 64H448c35.3 0 64-28.7 64-64V176L294.4 339.2c-22.8 17.1-54 17.1-76.8 0L0 176z"/></svg><input type="text" placeholder="example@example.com" name="email" class="pv-lead-input"></div><p class="pv-error">Please submit a valid email</p>
                </label>
                <label class="pv-label">Phone * <div class="pv-input-group"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome  - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M164.9 24.6c-7.7-18.6-28-28.5-47.4-23.2l-88 24C12.1 30.2 0 46 0 64C0 311.4 200.6 512 448 512c18 0 33.8-12.1 38.6-29.5l24-88c5.3-19.4-4.6-39.7-23.2-47.4l-96-40c-16.3-6.8-35.2-2.1-46.3 11.6L304.7 368C234.3 334.7 177.3 277.7 144 207.3L193.3 167c13.7-11.2 18.4-30 11.6-46.3l-40-96z"/></svg><input type="tel" placeholder="(555) 123-4567" name="phone" class="pv-lead-input" maxlength="16">
                    </div>
                    <p class="pv-error">Please submit a valid phone number</p>
                </label>
                <h3 style="margin-bottom: 0;">Select Your Purpose</h3>
                <div class="pv-tabs-interest">
                    <label class="pv-tab-interest">Buying<input type="radio" name="interest" value="buying" class="pv-radio-hidden" required/></label>
                    <label class="pv-tab-interest">Selling<input type="radio" name="interest" value="selling" checked class="pv-radio-hidden"/></label>
                </div>
                <button type="submit" aria-label="search" class="pv-submit">Submit</button>
                </form>
            </div>
            <div class="pv-modal" id="pv-valuation">
            <div class="pv-modal-content">
                <span class="close-button">&times;</span>

                <div class="pv-content">

                <div class="pv-address-wrapper">     
                    <h2 class="pv-address-title" id="pv-address"></h2>
                    <p class="pv-address-subtitle"><span id="pv-city"></span><span id="pv-state"></span></p>
                </div>

                <div class="pv-skeleton-wrapper">
                    <div class="pv-ticker-big-skeleton pv-skeleton-animation">
                    </div>
                    <div class="pv-ticker-small-skeleton pv-skeleton-animation">
                    </div>
                </div>

                <div class="plunk-component" id="rc_home_value" data-component-id="rc_home_value">
                    <p class="pv-price-symbol">$</p>
                    <div id="valuationPrice" class="pv-valuation-price">

                    </div>
                </div>

                <div id="pv-no-results" style="display: none; flex-direction: column;">
                    <div>
                        <div style="display: flex; justify-content: center">
                            <div style="margin-top:20px;">
                                <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path fill-rule="evenodd" clip-rule="evenodd" d="M18 4.8C10.7098 4.8 4.8 10.7098 4.8 18C4.8 25.2902 10.7098 31.2 18 31.2C25.2902 31.2 31.2 25.2902 31.2 18C31.2 10.7098 25.2902 4.8 18 4.8ZM18 1.5C27.1127 1.5 34.5 8.8873 34.5 18C34.5 27.1127 27.1127 34.5 18 34.5C8.8873 34.5 1.5 27.1127 1.5 18C1.5 8.8873 8.8873 1.5 18 1.5ZM16.35 19.65V9.75H19.65V19.65H16.35ZM18 22.95C18.9113 22.95 19.65 23.6887 19.65 24.6C19.65 25.5113 18.9113 26.25 18 26.25C17.0887 26.25 16.35 25.5113 16.35 24.6C16.35 23.6887 17.0887 22.95 18 22.95Z" fill="#406A88"></path>
                                </svg>
                            </div>
                        </div>

                        <div style="display: flex; justify-content: center">
                            <div class="plunk-home-no-value-home-value">
                                Valuation information for <span class="plunk-home-no-value-home-value" id="address-no-value"></span>
                                is currently missing.
                            </div>
                        </div>
                        
                        <div class="plunk-home-value-appreciation-container-no-data">
                            <div class="plunk-home-no-value-suffix">Sorry, HomeValuation.ai does not have enough information to determine an accurate home value</div>
                        </div>
                    </div>   
                </div>

                <div id="pv-service-error" style="display: none; flex-direction: column;">
                    <div>
                        <div style="display: flex; justify-content: center">
                            <div style="margin-top:20px;">
                                <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
                                <path fill-rule="evenodd" clip-rule="evenodd" d="M18 4.8C10.7098 4.8 4.8 10.7098 4.8 18C4.8 25.2902 10.7098 31.2 18 31.2C25.2902 31.2 31.2 25.2902 31.2 18C31.2 10.7098 25.2902 4.8 18 4.8ZM18 1.5C27.1127 1.5 34.5 8.8873 34.5 18C34.5 27.1127 27.1127 34.5 18 34.5C8.8873 34.5 1.5 27.1127 1.5 18C1.5 8.8873 8.8873 1.5 18 1.5ZM16.35 19.65V9.75H19.65V19.65H16.35ZM18 22.95C18.9113 22.95 19.65 23.6887 19.65 24.6C19.65 25.5113 18.9113 26.25 18 26.25C17.0887 26.25 16.35 25.5113 16.35 24.6C16.35 23.6887 17.0887 22.95 18 22.95Z" fill="#406A88"></path>
                                </svg>
                            </div>
                        </div>

                        <div style="display: flex; justify-content: center">
                            <div class="plunk-home-no-value-home-value">
                                Something went wrong on our end.
                            </div>
                        </div>

                        <div class="plunk-home-value-appreciation-container-no-data">
                            <div class="plunk-home-no-value-suffix">We could not complete your valuation just now. Please try again in a moment.</div>
                        </div>
                    </div>
                </div>

                <div id="pv-limit" style="display: none; flex-direction: column; align-items: center; text-align: center; padding: 20px;">
                    <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="margin-bottom: 12px; color: var(--pv-color-text, #003960);">
                        <path d="M3 12a9 9 0 1 0 18 0 9 9 0 0 0-18 0z"/>
                        <path d="M12 8v4l2 2"/>
                    </svg>
                    <p style="margin-bottom: 8px; font-weight: 800; font-size: 22px; color: var(--pv-color-text, #003960);">We'll be back soon!</p>
                    <p style="margin: 0 0 16px 0; color: #456E8B; font-size: 16px; line-height: 1.5; max-width: 320px;">Our online home valuation tool is temporarily unavailable. Please reach out to your agent directly for a personalized home valuation.</p>
                    <a class="pv-contact-agent" href="#" style="display: inline-block; padding: 10px 24px; background-color: var(--pv-color-text, #003960); color: #ffffff; text-decoration: none; border-radius: 8px; font-size: 15px; font-weight: 600;">Contact Agent</a>
                </div>
                
                <div id="sms_results" style="width:100%;overflow-y: hidden;"></div>
                
                <div id="market-insights-container">
                    <div class="pv-callToAction-wrapper">            
                    <div class="pv-callToAction">
                        <h2>Unlock your market insights now</h2>
                        <p style="color: white;">You are just one step away from getting the Market Insights for your home at <span id="pv-address-2"></span>.</p>
                        <p style="color: white;">Please press the confirm button bellow and we will shoot that right over to you. </p>
                        <form id="pv-cta-form" class="pv-cta-form">
                            <button id="pv-cta-button" class="pv-submit center" type="submit" aria-label="search" style="margin-top:0; background: white; color: #2569CC;">Confirm</button>
                        </form>
                        <p style="margin-top: 8px; font-size: 10px; color: white;">Your personal information is strictly confidential and will not be shared with any outside organization. By submitting this form with your telephone number, you authorize us and our authorized representatives to contact you even if your name is on the federal "Do Not Call" list.</p>
                    </div>
                    </div>
                </div>

                <div id="pv-thank-you" style="display: none; flex-direction: column;">
                    <img src="https://idxapps.sfo3.digitaloceanspaces.com/PlunkHomeValuation/mail_wpwyse.png" width="100" style="margin: 0 auto; max-width: 100%; height: auto;">
                    <h3 style="font-size: 25px; color: var(--pv-color-text, #003960); font-weight: bold; text-align: center; margin: 10px auto 8px auto;">Thank You</h3>
                    <p class="pv-thankyou-intro"></p>
                    <p id="pv-thankyou-address"></p>
                </div>

                <div class="pv-powered-container">
                    <img src="https://images.leadconnectorhq.com/image/f_webp/q_80/r_1200/u_https://assets.cdn.filesafe.space/LB6pV83yCjjuXJZqjSMq/media/67d07e47de31f45f561eacd6.png" alt="Home Valuation Logo" />
                </div>

                </div>
            </div>
            </div>`;

        var template = document.createElement('template');
        template.innerHTML = HTML;
        var nodes = Array.prototype.slice.call(template.content.childNodes);

        var wrapper = null;
        var leadModal = null;
        var valuationModal = null;
        nodes.forEach(function (node) {
            if (node.nodeType !== 1) return;
            if (node.id === 'plunk-valuation-wrapper') wrapper = node;
            else if (node.id === 'pv-lead-modal') leadModal = node;
            else if (node.id === 'pv-valuation') valuationModal = node;
        });

        // Per-instance colour override, appended after the stylesheet so it wins
        // ties. Built as a node so a value can never be parsed as markup.
        if (CONFIG.styleOverride) {
            var overrideStyle = document.createElement('style');
            overrideStyle.textContent = CONFIG.styleOverride;
            template.content.insertBefore(overrideStyle, wrapper);
            nodes.push(overrideStyle);
        }

        // The design is an attribute the stylesheet keys off. The sample
        // estimate card belongs to the split design only; for the pill it is
        // taken out of the document entirely rather than hidden.
        var design = CONFIG.design === 'split' ? 'split' : 'pill';
        wrapper.setAttribute('data-pv-design', design);
        if (design !== 'split') {
            var previewCard = wrapper.querySelector('.pv-preview-card');
            if (previewCard) previewCard.parentNode.removeChild(previewCard);
            var previewAnnounce = wrapper.querySelector('.pv-preview-announce');
            if (previewAnnounce) previewAnnounce.parentNode.removeChild(previewAnnounce);
        }
        wrapper.querySelector('#pv-address').placeholder = CONFIG.placeholder || 'Enter your home address';

        // Dynamic text goes in as text, never as markup.
        wrapper.querySelector('.pv-title-text').textContent = CONFIG.title;
        var subtitleEl = wrapper.querySelector('.pv-subtitle-text');
        if (subtitleEl) {
            if (String(CONFIG.subtitle).trim() === '') {
                subtitleEl.parentNode.removeChild(subtitleEl);
            } else {
                subtitleEl.textContent = CONFIG.subtitle;
            }
        }
        wrapper.querySelector('.pv-button-label').textContent = CONFIG.buttonLabel;
        valuationModal.querySelector('.pv-thankyou-intro').textContent =
            'We just sent you ' + (CONFIG.collectsPhone ? 'a text message' : 'an email')
            + ' with more information about';
        valuationModal.querySelector('.pv-contact-agent').setAttribute('href', 'mailto:' + CONFIG.contactEmail);

        if (currentScript && currentScript.parentNode) {
            currentScript.parentNode.insertBefore(template.content, currentScript.nextSibling);
        } else {
            document.body.appendChild(template.content);
        }

        var modal = valuationModal;
        var cleanups = [];

        // Using all caps with the address string values will help with validation
        var addressObj = {};
        // The input value at the moment a suggestion was accepted, so typing over it
        // can be told apart from Google writing it.
        var selectedAddressText = '';
        var home_valuation_id = null;
        var home_valuation_token = null;
        // True from a valid lead submit until the valuation render settles.
        var leadSubmitting = false;
        // The last successful valuation, held until the valuation popup closes
        // and the split design's card can show it.
        var pendingPreview = null;
        // Counts searches, so a response that arrives after a newer search
        // started can be recognised and ignored.
        var searchSeq = 0;

        var searchForm = wrapper.querySelector('#pv-form');
        searchForm.addEventListener('submit', onSubmit);

        // The wrapper holds the search input (#pv-address); the valuation modal
        // reuses that id for its title, which is why every lookup is scoped.
        var addressInput = wrapper.querySelector('#pv-address');
        if (addressInput) {
            addressInput.addEventListener('input', function () {
                setAddressError(false);

                // Editing the text after picking a suggestion invalidates it --
                // otherwise you could select one address, type over it, and get
                // a valuation for the one you replaced.
                //
                // Compared against the value Google itself wrote, because
                // assigning input.value programmatically does not fire 'input';
                // only the user's own typing reaches this.
                if (addressInput.value !== selectedAddressText) {
                    addressObj = {};
                }
            });
        }

        var leadForm = leadModal.querySelector('#lead-form');
        leadForm.addEventListener('submit', getLeadData);
        var marketForm = valuationModal.querySelector('#pv-cta-form');
        marketForm.addEventListener('submit', onMarketInsightsSubmit);

        var phoneField = leadForm.querySelector('[name="phone"]');
        if (phoneField) {
            phoneField.addEventListener('input', function () {
                phoneField.value = phoneFormat(phoneField.value);
            });
        }

        [leadModal, valuationModal].forEach(function (host) {
            host.querySelectorAll('.close-button').forEach(function (button) {
                button.addEventListener('click', toggleModal);
            });
        });

        function openModal() {
            return [leadModal, valuationModal].filter(function (candidate) {
                return candidate.classList.contains('show-modal');
            })[0] || null;
        }

        function toggleModal() {
            var current = openModal();
            if (current) {
                current.classList.remove('show-modal');
                afterModalToggled(current);
            }
        }

        // The split card fills when the valuation popup goes from open to closed.
        // windowOnClick reaches this through toggleModal.
        function afterModalToggled(target) {
            if (target === valuationModal && !valuationModal.classList.contains('show-modal') && pendingPreview) {
                fillPreviewCard(pendingPreview);
                pendingPreview = null;
            }
        }

        function setPreviewText(card, selector, text) {
            var el = card.querySelector(selector);
            if (el) el.textContent = text;
        }

        function fillPreviewCard(result) {
            var card = wrapper.querySelector('.pv-preview-card');
            if (!card || !result) return;

            var address = String(result.addressLineOne || '');
            var place = [result.city, result.state].filter(function (part) { return !!part; }).join(', ');
            var value = '$' + result.valuation.toLocaleString('en-US', {
                minimumFractionDigits: 0,
                maximumFractionDigits: 0
            });

            setPreviewText(card, '.pv-preview-address', address);
            setPreviewText(card, '.pv-preview-place', place);
            setPreviewText(card, '.pv-preview-value', value);
            card.setAttribute('data-pv-filled', '');
            card.removeAttribute('aria-hidden');
            card.setAttribute('role', 'group');
            card.setAttribute('aria-label', 'Your estimated home value');

            // In the builder the figure is made up, and the note saying so is
            // inside the popup that has just closed. Keep it honest here too.
            var label = CONFIG.sampleMode ? 'Sample estimate' : 'Estimated value';
            setPreviewText(card, '.pv-preview-label', label);

            var announce = wrapper.querySelector('.pv-preview-announce');
            if (announce) announce.textContent = label + ' for ' + address + ': ' + value;
        }

        function resetPreviewCard() {
            var card = wrapper.querySelector('.pv-preview-card');
            if (!card) return;

            setPreviewText(card, '.pv-preview-address', '');
            setPreviewText(card, '.pv-preview-place', '');
            setPreviewText(card, '.pv-preview-value', '');
            setPreviewText(card, '.pv-preview-label', 'Estimated value');
            card.removeAttribute('data-pv-filled');
            card.removeAttribute('role');
            card.removeAttribute('aria-label');
            card.setAttribute('aria-hidden', 'true');

            var announce = wrapper.querySelector('.pv-preview-announce');
            if (announce) announce.textContent = '';
        }

        function windowOnClick(event) {
            var current = openModal();
            if (current && event.target === current) {
                toggleModal();
            }
        }
        window.addEventListener('click', windowOnClick);
        cleanups.push(function () { window.removeEventListener('click', windowOnClick); });

        initializeSearchBox();

        if (CONFIG.sampleMode) {
            var sampleAddress = SAMPLE.pickAddress();
            addressObj = sampleAddress;
            selectedAddressText = [sampleAddress.addressLineOne, sampleAddress.city,
                sampleAddress.state + ' ' + sampleAddress.zipcode].join(', ');
            if (addressInput) addressInput.value = selectedAddressText;

            // Typed text is good enough here; an empty field still shows the
            // normal "choose an address" error.
            if (addressInput) {
                addressInput.addEventListener('input', function () {
                    var typed = addressInput.value.trim();
                    addressObj = typed === '' ? {} : {
                        addressLineOne: typed,
                        aptNum: '',
                        city: sampleAddress.city,
                        state: sampleAddress.state,
                        zipcode: sampleAddress.zipcode
                    };
                });
            }

            // The live copy says "We just sent you a text message"; here nothing
            // was sent, and the note above it says so.
            var thankYouIntro = valuationModal.querySelector('.pv-thankyou-intro');
            if (thankYouIntro) {
                thankYouIntro.textContent = 'Your visitor would now get '
                    + (CONFIG.collectsPhone ? 'a text message' : 'an email')
                    + ' with more information about';
            }

            [leadModal, valuationModal].forEach(function (host) {
                var box = host.querySelector('.pv-modal-content');
                if (!box) return;
                var note = document.createElement('p');
                note.className = 'pv-sample-note';
                note.setAttribute('role', 'note');
                note.textContent = SAMPLE.noteText;
                // Right margin clears the absolutely positioned close button.
                note.style.cssText = 'margin:0 2.25rem 16px 0;padding:8px 12px;border-radius:8px;background:#FEF3C7;border:1px solid #FCD34D;color:#78350F;font-size:12px;line-height:1.4;font-weight:600;text-align:center;letter-spacing:normal;text-transform:none;';
                box.insertBefore(note, box.firstChild);
            });
        }

        function phoneFormat(input) { // returns (###) ###-####
            input = input.replace(/\D/g, '');
            var size = input.length;
            if (size > 0) {
                input = '(' + input;
            }
            if (size > 3) {
                input = input.slice(0, 4) + ') ' + input.slice(4, 11);
            }
            if (size > 6) {
                input = input.slice(0, 9) + '-' + input.slice(9);
            }
            return input;
        }

        // Fetch real home value data from the API using the address in addressObj.
        // The server enforces the monthly limit and saves the lead itself; this
        // only reports what came back and hands the stored lead on to HighLevel.
        async function getPlunkHomeValue(addressObj) {
            if (CONFIG.sampleMode) return SAMPLE.valuation();

            // A new search must never reuse the previous search's record.
            home_valuation_id = null;
            home_valuation_token = null;

            // renderHomeValue has already numbered this search. If a newer one
            // starts while this request is out, its record must not be adopted:
            // Confirm would then send insights for the previous address.
            var requestSeq = searchSeq;

            var params = {
                address: addressObj.addressLineOne,
                cityName: addressObj.city,
                stateAbrv: addressObj.state,
                zipcode: addressObj.zipcode,
                first_name: addressObj.first_name,
                last_name: addressObj.last_name,
                email: addressObj.email,
                phone: addressObj.phone,
                interest: addressObj.interest
            };
            var query = Object.keys(params).map(function (key) {
                return key + '=' + encodeURIComponent(params[key]);
            }).join('&');
            var url = CONFIG.urls.valuation + '?' + query;

            // The server waits up to 20 s on its vendor, so give up a little after.
            var controller = typeof AbortController === 'function' ? new AbortController() : null;
            var abortTimer = controller ? window.setTimeout(function () { controller.abort(); }, 30000) : null;

            var apiRes;
            var homeValueData = null;
            var bodyReadable = true;

            try {
                var options = {
                    method: 'GET',
                    headers: {
                        // Accept matters: without it Laravel renders an HTML error page
                        // for a 500, which .json() below cannot parse.
                        "Accept": "application/json",
                        'Content-Type': 'application/json'
                    }
                };
                if (controller) options.signal = controller.signal;
                apiRes = await fetch(url, options);

                // Read inside the same timeout: a stalled body must not hang either.
                try {
                    homeValueData = await apiRes.json();
                } catch (err) {
                    bodyReadable = false;
                    if (apiRes.ok) {
                        console.error('HomeValuation: valuation returned an unreadable body', err);
                    }
                }
            } catch (err) {
                console.error('HomeValuation: valuation request failed', err);
                return { success: false, reason: 'error' };
            } finally {
                window.clearTimeout(abortTimer);
            }

            // Remember the stored lead whenever the server returned it, whatever
            // the outcome: market insights and the HighLevel hand-off both need it.
            var record = bodyReadable && homeValueData && homeValueData.homeValuation;
            if (record && record.home_valuation_id && record.token) {
                if (requestSeq === searchSeq) {
                    home_valuation_id = record.home_valuation_id;
                    home_valuation_token = record.token;
                }

                // Handed off even when superseded: that lead was saved, and the
                // agent should still get it.
                sendToGHL(record.home_valuation_id, record.token);
            }

            // A server fault is not the same as "we have no data for this address",
            // and telling the visitor their address is unknown when the service is
            // down sends them chasing the wrong problem.
            if (!apiRes.ok) {
                console.error('HomeValuation: valuation responded ' + apiRes.status);
                return { success: false, reason: 'error' };
            }

            if (!bodyReadable || !homeValueData) {
                return { success: false, reason: 'error' };
            }

            if (homeValueData.reason === 'limit' || homeValueData.error === 'limit_reached') {
                // Painted by renderHomeValue, after its superseded-search check.
                return { success: false, reason: 'limit' };
            }

            if (homeValueData.success === false) {
                homeValueData.reason = 'no_data';
            }

            return homeValueData;
        }

        // Fire-and-forget: the server reads the stored lead, so only the record
        // id and its token go.
        async function sendToGHL(valuationId, token) {
            if (CONFIG.sampleMode) return { success: true, message: 'sample' };
            if (!valuationId || !token) return undefined;

            try {
                var apiRes = await fetch(CONFIG.urls.sendGhl, {
                    method: 'POST',
                    headers: { "Accept": "application/json", 'Content-Type': 'application/json' },
                    body: JSON.stringify({ home_valuation_id: valuationId, token: token })
                });
                var responseData = await apiRes.json();

                return responseData.success
                    ? { success: true, message: responseData.message }
                    : { success: false, message: responseData.message };
            } catch (error) {
                console.error('HomeValuation: GHL hand-off failed', error);
                return { success: false, error: error.message };
            }
        }

        async function renderHomeValue(addressObj) {

            // A new search starts: whatever the card showed belongs to the
            // previous one. Cleared here, not when the result arrives, so the
            // old estimate is never on the page beside the new address.
            var seq = ++searchSeq;
            pendingPreview = null;
            resetPreviewCard();

            // Get real Home Value data from Plunk's API
            modal.querySelector('#rc_home_value').style.display = 'none';

            var homeValueData = await getPlunkHomeValue(addressObj);

            // Superseded: a newer search has started since this one was sent.
            // Painting it would put this result in a popup, and later on the
            // card, that now belong to another address.
            if (seq !== searchSeq) {
                return undefined;
            }

            // Check BEFORE dereferencing. This used to read .homeValuation and then
            // .home_valuation_id off it two lines before testing success, so any
            // failure threw a TypeError, showNotFoundForm() was unreachable, and the
            // modal sat on the loading skeleton forever.
            if (!homeValueData || homeValueData.success === false) {
                if (homeValueData && homeValueData.reason === 'error') {
                    showServiceErrorForm();
                } else if (homeValueData && homeValueData.reason === 'limit') {
                    // Painted here, after the superseded-search check above.
                    limitReachedMessage();
                } else {
                    showNotFoundForm();
                }

                return undefined;
            }

            var homeValuationRecord = homeValueData.homeValuation;

            if (!homeValuationRecord) {
                showNotFoundForm();
                return undefined;
            }

            // The column is a string and the server only rejects an empty one, so
            // "N/A" or "512,000" can arrive marked successful. That used to
            // paint "NaN" as the price; it is no estimate, so say so.
            var priceNumber = Number(homeValuationRecord.valuation); // Convert to number

            if (!Number.isFinite(priceNumber) || priceNumber <= 0) {
                showNotFoundForm();
                return undefined;
            }

            home_valuation_id = homeValuationRecord.home_valuation_id ?? null;

            modal.querySelector('#rc_home_value').style.display = 'flex';

            // Home Valuation AI home value component
            var priceDiv = modal.querySelector('.plunk-component #valuationPrice');

            priceDiv.textContent = priceNumber.toLocaleString('en-US', {
                currency: 'USD',
                minimumFractionDigits: 0,
                maximumFractionDigits: 0
            });

            plunkFillData();

            // A snapshot, not a reference: addressObj is replaced by the next
            // search. Held until the valuation popup closes (afterModalToggled).
            pendingPreview = {
                valuation: priceNumber,
                addressLineOne: addressObj.addressLineOne,
                city: addressObj.city,
                state: addressObj.state
            };

            // The visitor closed the popup while this was still loading, so no
            // close is coming to hand the result over: show it on the card now.
            if (!valuationModal.classList.contains('show-modal')) {
                fillPreviewCard(pendingPreview);
                pendingPreview = null;
            }

            modal.querySelector('.pv-skeleton-wrapper').style.display = 'none';

            modal.querySelector('#market-insights-container').classList.add('market-insights-expanded');
        }

        function plunkFillData() {
            modal.querySelector('#pv-address').textContent = addressObj.addressLineOne;
            modal.querySelector('#pv-address-2').textContent = addressObj.addressLineOne;
            modal.querySelector('#pv-city').textContent = addressObj.city;
            modal.querySelector('#pv-state').textContent = ', ' + addressObj.state;
        }

        function initializeSearchBox() {
            var input = addressInput;
            var searchBox = new google.maps.places.Autocomplete(input, { types: ['address'] });

            // Google appends one .pac-container to the body per Autocomplete. Note
            // the ones that exist now so the new one can be told apart later.
            var knownContainers = Array.prototype.slice.call(document.querySelectorAll('.pac-container'));

            searchBox.setComponentRestrictions({ country: 'us' });
            searchBox.setFields(['address_components', 'name']); // Important
            searchBox.addListener('place_changed', function () {
                // Nothing in here may throw. Google dispatches this listener itself
                // and swallows exceptions, so a single bad read used to leave
                // addressObj empty and make the Go button silently inert.
                try {
                    var place = searchBox.getPlace();

                    // Submitting without picking a suggestion still fires this, but
                    // getPlace() then returns a stub carrying only the typed text.
                    var address = (place && place.address_components) || [];

                    // Not every address has a locality. New York boroughs come back
                    // as sublocality, unincorporated areas as a county-level
                    // component -- and previously any of those threw on .long_name.
                    var city = componentValue(address, ['locality', 'sublocality', 'sublocality_level_1',
                        'administrative_area_level_3', 'neighborhood'
                    ]);
                    var state = componentValue(address, ['administrative_area_level_1'], 'short_name');
                    var zipcode = componentValue(address, ['postal_code']);

                    var streetNumber = componentValue(address, ['street_number']);
                    var route = componentValue(address, ['route']);
                    var line = [streetNumber, route].filter(Boolean).join(' ');

                    // A street with no house number ("Santana Row, San Jose, CA")
                    // comes back with no postal_code, so require a state plus
                    // either a city or a zip rather than demanding all three.
                    if (line && state && (city || zipcode)) {
                        addressObj = {
                            addressLineOne: line,
                            aptNum: '',
                            city: city,
                            state: state,
                            zipcode: zipcode
                        };
                        selectedAddressText = input ? input.value : '';
                        setAddressError(false);
                    } else {
                        // Deliberately left empty rather than half-populated: the
                        // valuation lookup needs a real address, and a partial one
                        // would fail further in with a worse message.
                        addressObj = {};
                    }
                } catch (err) {
                    addressObj = {};
                    console.error('HomeValuation: could not read the selected place', err);
                }
            });

            var moveTimer = window.setTimeout(function () {
                var pacContainer = Array.prototype.slice.call(document.querySelectorAll('.pac-container'))
                    .filter(function (candidate) { return knownContainers.indexOf(candidate) === -1; })[0];

                // append(null) would insert a literal "null" text node into the
                // form -- an in-flow child that shifts the absolutely positioned
                // search icon off the input.
                var inputContainer = searchForm;
                if (inputContainer && pacContainer) {
                    inputContainer.append(pacContainer);
                }
            }, 1000);

            cleanups.push(function () {
                window.clearTimeout(moveTimer);
                if (window.google && window.google.maps && window.google.maps.event) {
                    window.google.maps.event.clearInstanceListeners(searchBox);
                }
                // The container lives inside the form once moved; if it never was,
                // it is still the one Google made for this input.
                var stray = Array.prototype.slice.call(document.querySelectorAll('.pac-container'))
                    .filter(function (candidate) { return knownContainers.indexOf(candidate) === -1; });
                stray.forEach(function (candidate) {
                    if (candidate.parentNode) candidate.parentNode.removeChild(candidate);
                });
            });
        }

        // First matching component, or '' -- never undefined, so callers can't blow
        // up reading a property off a miss.
        function componentValue(components, types, key) {
            key = key || 'long_name';

            for (var i = 0; i < types.length; i++) {
                var match = getComponent(components || [], types[i]);

                if (match && match[key]) {
                    return match[key];
                }
            }

            return '';
        }

        function getComponent(array, search) {
            return array.find(function (component) {
                return component.types.includes(search);
            });
        }

        function setAddressError(show) {
            var error = wrapper.querySelector('#pv-address-error');

            if (error) {
                error.style.display = show ? 'block' : 'none';
            }
        }

        function validatePhone(number) {
            return /^\(\d{3}\) \d{3}-\d{4}$/.test(number);
        }

        function validateEmail(email) {
            return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
        }

        function getLeadData(e) {
            e.preventDefault();
            var formData = new FormData(leadForm);
            var entries = Array.from(formData.entries());
            for (var i = 0; i < entries.length; i++) {
                var pair = entries[i];

                if (pair[0] === 'phone') {
                    var phoneInput = leadForm.querySelector("[name='phone']");
                    if (!validatePhone(pair[1])) {
                        phoneInput.parentElement.parentElement.querySelector('p').style.display = 'block';
                        phoneInput.style.border = '1px solid red';
                        return;
                    }
                    phoneInput.parentElement.parentElement.querySelector('p').style.display = 'none';
                    phoneInput.style.border = '';
                }
                if (pair[0] === 'email') {
                    var emailInput = leadForm.querySelector("[name='email']");
                    if (!validateEmail(pair[1])) {
                        emailInput.parentElement.parentElement.querySelector('p').style.display = 'block';
                        emailInput.style.border = '1px solid red';
                        return;
                    }
                    emailInput.parentElement.parentElement.querySelector('p').style.display = 'none';
                    emailInput.style.border = '';
                }
                addressObj[pair[0]] = pair[1];
            }
            // A second click on Submit, while the first is still being handled.
            if (leadSubmitting) return;
            leadSubmitting = true;

            var submitButton = leadForm.querySelector('.pv-submit');
            if (submitButton) submitButton.disabled = true;

            leadModal.classList.remove('show-modal');
            valuationModal.classList.add('show-modal');

            var settle = function () {
                leadSubmitting = false;
                if (submitButton) submitButton.disabled = false;
            };

            renderHomeValue(addressObj).catch(function (err) {
                console.error('HomeValuation: rendering the valuation failed', err);
                showServiceErrorForm();
            }).then(settle, settle);
        }

        function onSubmit(event) {
            event.preventDefault();

            // Pressing Enter makes Places fire place_changed with a stub, which
            // empties addressObj after the typed text was accepted. In sample
            // mode whatever is in the field is good enough, so rebuild from it.
            if (CONFIG.sampleMode && addressInput && addressInput.value.trim() !== ''
                && (!addressObj || !addressObj.addressLineOne)) {
                var sampleBase = SAMPLE.pickAddress();
                addressObj = {
                    addressLineOne: addressInput.value.trim(),
                    aptNum: '',
                    city: sampleBase.city,
                    state: sampleBase.state,
                    zipcode: sampleBase.zipcode
                };
            }

            // A popup is already open: a second submit must do nothing.
            if (openModal()) return;

            // Used to be a bare `return`. With no error element and no `required`
            // on the input, pressing Go with an unresolved address produced nothing
            // at all -- no modal, no message, no native validation bubble.
            if (Object.keys(addressObj).length === 0) {
                setAddressError(true);

                if (addressInput) addressInput.focus();

                return;
            }

            setAddressError(false);

            // Reset all the values
            modal.querySelector('#pv-address').textContent = addressObj.addressLineOne;
            modal.querySelector('#pv-city').textContent = addressObj.city;
            modal.querySelector('#pv-state').textContent = ' ' + addressObj.state;
            modal.querySelector('#rc_home_value').style.display = 'block';
            modal.querySelector('.pv-skeleton-wrapper').style.display = 'flex';
            modal.querySelector('#market-insights-container').classList.remove('market-insights-expanded');
            modal.querySelector('.pv-callToAction-wrapper').style.display = '';
            modal.querySelector('#pv-no-results').style.display = 'none';
            modal.querySelector('#pv-service-error').style.display = 'none';
            modal.querySelector('#pv-limit').style.display = 'none';
            modal.querySelector('#pv-thank-you').style.display = 'none';

            var tel = wrapper.querySelector('#pv-tel');
            if (tel != null) {
                tel.value = ''; // clear phone
            }
            var mail = wrapper.querySelector('#pv-email');
            if (mail != null) {
                mail.value = ''; // clear email
            }

            if (CONFIG.sampleMode) {
                var sampleLead = SAMPLE.lead();
                Object.keys(sampleLead).forEach(function (name) {
                    var field = leadForm.querySelector('[name="' + name + '"]');
                    if (field && field.value === '') field.value = sampleLead[name];
                });
            }

            // Show modal at the end
            // A fresh lead form for a fresh search. The guard exists to stop one
            // opening of the form being submitted twice; left set, it would
            // make this form's Submit do nothing while an earlier, abandoned
            // request (up to 30 s) is still settling.
            leadSubmitting = false;
            var freshSubmit = leadForm.querySelector('.pv-submit');
            if (freshSubmit) freshSubmit.disabled = false;

            leadModal.classList.add('show-modal');
        }

        async function onMarketInsightsSubmit(event) {
            event.preventDefault();

            var ctaButton = modal.querySelector('#pv-cta-button');
            // Enter can re-submit while the first request is still in flight.
            if (ctaButton.classList.contains('is-loading')) return;
            ctaButton.classList.add('is-loading');

            // The server reads the stored lead; only the record and its token go.
            var marketInsightData = {
                home_valuation_id: home_valuation_id,
                token: home_valuation_token
            };

            var output = '';
            var data = null;

            if (CONFIG.sampleMode) {
                data = await SAMPLE.marketInsights();
            } else {
                // Unchecked before: a 500 returns an HTML page, .json() rejects, and the
                // Confirm button stays stuck in its loading state with nothing shown.
                try {
                    var response = await fetch(CONFIG.urls.marketInsights, {
                        method: 'POST',
                        headers: { "Accept": "application/json", 'Content-Type': 'application/json' },
                        body: JSON.stringify(marketInsightData)
                    });

                    if (response.ok) {
                        data = await response.json();
                    } else {
                        console.error('HomeValuation: market insights responded ' + response.status);
                    }
                } catch (err) {
                    console.error('HomeValuation: market insights request failed', err);
                }
            }

            if (!data || data.type == 'error') {
                ctaButton.classList.remove('is-loading');
                output = '<div class="pv-alert pv-alert-danger">Sorry, we could not send that just now. Please try again in a moment.</div>';
            } else {
                modal.querySelector('#pv-thank-you').style.display = 'flex';
                ctaButton.classList.remove('is-loading');
                modal.querySelector('#market-insights-container').classList.remove('market-insights-expanded');
                modal.querySelector('#rc_home_value').style.display = 'none';
                modal.querySelector('.pv-skeleton-wrapper').style.display = 'none';
                modal.querySelector('#pv-address').textContent = '';
                modal.querySelector('#pv-city').textContent = '';
                modal.querySelector('#pv-state').textContent = '';
                modal.querySelector('#pv-thankyou-address').textContent = addressObj.addressLineOne;

                if (data.delivery_failed && data.market_insight_url) {
                    // The URL is server-issued but still goes through the DOM as a
                    // property, not markup.
                    output = '<div class="pv-alert pv-alert-warning show">'
                        + 'We couldn\'t deliver your market insights right now. '
                        + '<a target="_blank" rel="noopener" style="color: inherit; font-weight: bold; text-decoration: underline;">Click here to view them.</a>'
                        + '</div>';
                }
            }

            var alertDiv = modal.querySelector('#sms_results');
            alertDiv.innerHTML = output;

            var link = alertDiv.querySelector('a');
            if (link && /^https?:/i.test(String(data.market_insight_url))) link.setAttribute('href', data.market_insight_url);

            // firstChild is null whenever output stayed empty (the success path),
            // which threw and skipped everything after this.
            if (alertDiv.firstChild && alertDiv.firstChild.classList) {
                alertDiv.firstChild.classList.add('show');
            }

            ctaButton.classList.remove('is-loading');

            if (alertDiv.firstChild && !alertDiv.firstChild.classList.contains('pv-alert-warning')) {
                window.setTimeout(function () {
                    if (alertDiv.firstChild && alertDiv.firstChild.classList) {
                        alertDiv.firstChild.classList.remove('show');
                    }
                }, 3000);
            }
        }

        function clearAddressTexts() {
            modal.querySelector('#pv-address').textContent = '';
            modal.querySelector('#pv-city').textContent = '';
            modal.querySelector('#pv-state').textContent = '';
        }

        function showNotFoundForm() {
            pendingPreview = null;
            resetPreviewCard();
            // Was `await this.getMarketInsights()` -- `this` is not the object that
            // holds it, and the result was only logged. It also delayed the panel by
            // a whole round trip, so the skeleton kept spinning for no reason.
            modal.querySelector('#pv-service-error').style.display = 'none';
            modal.querySelector('#pv-no-results').style.display = 'flex';
            modal.querySelector('.pv-callToAction-wrapper').style.display = 'none';
            modal.querySelector('#rc_home_value').style.display = 'none';
            modal.querySelector('.pv-skeleton-wrapper').style.display = 'none';
            clearAddressTexts();

            modal.querySelector('#address-no-value').textContent = String(addressObj.addressLineOne);
        }

        // The valuation could not be completed for a reason that is ours, not the
        // address's. Kept distinct from #pv-no-results so an outage does not tell
        // the visitor their home does not exist.
        function showServiceErrorForm() {
            pendingPreview = null;
            resetPreviewCard();
            modal.querySelector('#pv-service-error').style.display = 'flex';
            modal.querySelector('#pv-no-results').style.display = 'none';
            modal.querySelector('.pv-callToAction-wrapper').style.display = 'none';
            modal.querySelector('#rc_home_value').style.display = 'none';
            modal.querySelector('.pv-skeleton-wrapper').style.display = 'none';
            clearAddressTexts();
        }

        function limitReachedMessage() {
            pendingPreview = null;
            resetPreviewCard();
            modal.querySelector('#pv-service-error').style.display = 'none';
            modal.querySelector('#pv-limit').style.display = 'flex';
            modal.querySelector('#pv-no-results').style.display = 'none';
            modal.querySelector('.pv-callToAction-wrapper').style.display = 'none';
            modal.querySelector('#rc_home_value').style.display = 'none';
            modal.querySelector('.pv-skeleton-wrapper').style.display = 'none';
            clearAddressTexts();
        }

        /* ---- Teardown: everything this mount added, and nothing else ---------- */
        var instance = {
            wrapper: wrapper,
            destroy: function () {
                cleanups.splice(0).forEach(function (cleanup) {
                    try { cleanup(); } catch (_) { /* best effort */ }
                });
                nodes.forEach(function (node) {
                    if (node.parentNode) node.parentNode.removeChild(node);
                });
                var at = entry.instances.indexOf(instance);
                if (at !== -1) entry.instances.splice(at, 1);
                delete wrapper.idxHomeValuation;
            }
        };

        wrapper.idxHomeValuation = { destroy: destroy };

        return instance;
    }
})();

