41
Luxury Meets Untouched Nature in 9-Acre Private and Tranquil Haven in Ojochal Costa Rica
Luxury Meets Untouched Nature in 9-Acre Private and Tranquil Haven in Ojochal Costa Rica
  • $1,100,000

Description

Discover your sanctuary in the heart of Costa Ballena de Osa, where luxury meets the untouched beauty of nature. This exquisite property, nestled atop a secluded mountain and enveloped by lush secondary rainforest, offers unparalleled privacy and tranquility, making it the perfect refuge for those seeking solace away from the hustle and bustle.

This beautiful home stands as a testament to exclusivity, with its protected gated entrance and sprawling 9.6 acres of pristine land, ensuring a haven of peace. The property comprises two elegantly designed homes, each a masterpiece of indoor/outdoor living, allowing you to immerse yourself in the stunning green valley, mountain, and ocean vistas from various serene spots.

Read More

Property Documents

Luxury Meets Untouched Nature in 9-Acre Private and Tranquil Haven in Ojochal Costa Rica

Details

  • Property ID 24189
  • Price $1,100,000
  • House Size 121 m²
  • Land Size 38938 m²
  • Bedrooms 2
  • Bathrooms 2
  • Property Type Home
  • Property Status Sold
  • Pool Yes - private
  • View Ocean - Full, Mountains
  • Interior Features Tile Floors
  • Building Type Concrete Block with Stucco
  • Outdoor Space Features Balcony, Patio/Terraza
  • Lot/Garden/Land Features Landscaped, Gated entry
  • Location Features 2-Wheel Drive Access
  • Drinking Water AyA or ASADA - Legal Source
  • Septic/Sewer Septic System
  • Internet Fiber/Cable, Cellular/Mobile
  • Roof Metal
  • Air Conditioning Yes - Whole House
  • Electrical Service Utility Grid Connected- ICE
  • Appliances Refrigerator, Ceiling Fans, Clothes Washer, Clothes Dryer
  • Agent MLS # otp-tk-140000
  • Financing Available No - No Financing Available
  • City/Town: Ojochal
  • Province: Puntarenas

Contact Information

Inquire About This Property

Compare listings

Compare
- by the time it is submitted there is no data-hz-id // ancestor left to walk to, the same disconnect the WhatsApp fix above // works around. The one moment the card is still reachable is the click // that OPENED the modal, so that is captured here and handed to // captureLead() by the modal's own id when it submits. var pendingModalCtx = {}; document.addEventListener("click", function(e){ var t = e.target && e.target.closest ? e.target.closest("[data-model-id]") : null; if (!t) return; var modalId = t.dataset.modelId; if (!modalId) return; var lid = "", el = t, depth = 0, card = null; while (el && depth < 20) { if (el.dataset && el.dataset.hzId) { card = el; lid = el.dataset.hzId; break; } el = el.parentElement; depth++; } var url = cbpCardUrl(card); if (lid || url) pendingModalCtx[modalId] = { lid: lid, url: url }; }, true); var sentEmails = {}; function fieldVal(form, sel){ try { var el = form.querySelector(sel); return el && el.value ? String(el.value).trim() : ""; } catch (e) { return ""; } } function looksEmail(s){ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s); } function captureLead(form){ if (!form || typeof form.querySelectorAll !== "function") return; var inputs = form.querySelectorAll("input, textarea"), i, el, val; // Email: prefer a real email field; fall back to scanning values (Green // Popups/lepopup uses opaque id-based field names, so type/name won't match). var email = fieldVal(form, 'input[type=email]') || fieldVal(form, 'input[name*="email" i]'); if (!looksEmail(email)) { email = ""; for (i = 0; i < inputs.length; i++) { val = (inputs[i].value || "").trim(); if (looksEmail(val)) { email = val; break; } } } if (!email) return; // no email → not a lead form if (sentEmails[email]) return; // dedupe per page life sentEmails[email] = 1; // Name: prefer a name-ish field; else first short text value that isn't the // email, a phone, or a long message. var name = fieldVal(form, 'input[name*="name" i]:not([name*="user" i])'); if (!name) { for (i = 0; i < inputs.length; i++) { el = inputs[i]; val = (el.value || "").trim(); var t = (el.type || "").toLowerCase(); if (!val || looksEmail(val)) continue; if (t === "email" || t === "tel" || t === "number" || t === "hidden" || t === "checkbox" || t === "radio" || t === "submit" || t === "button") continue; var digits = val.replace(/\D/g, ""); if (digits.length >= 7 && digits.length >= val.length - 4) continue; // phone-like if (val.length > 60 || !/[A-Za-z]/.test(val)) continue; // message / non-name name = val; break; } } // v3.54.0: L (CBP_VE_LISTING) is only ever set on the listing's OWN page. // A card's Email button submitted from a search-results list has no L to // fall back on - use the context stashed when that card's button was // clicked (pendingModalCtx, above) instead of leaving this lead // unattributed. var modalEl = form.closest ? form.closest(".modal") : null; var modalCtx = modalEl && modalEl.id ? pendingModalCtx[modalEl.id] : null; var leadExtra = { event_type: "form_submit", email: email, name: name, listing_id: (modalCtx && modalCtx.lid) || (L ? L.listing_id : "") }; if (modalCtx && modalCtx.url) leadExtra.page_url = modalCtx.url; var data = body(leadExtra); try { if (navigator.sendBeacon) { navigator.sendBeacon(ENDPOINT, new Blob([data], { type: "application/x-www-form-urlencoded" })); } else { fetch(ENDPOINT, { method: "POST", credentials: "include", keepalive: true, headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: data }).catch(function(){}); } } catch (e) {} } document.addEventListener("submit", function(e){ captureLead(e.target); }, true); // Green Popups (lepopup) forms have NO
tag — the submit control is an // , so no submit event fires. Capture on button // click instead: find the popup container (nearest ancestor holding a // lepopup field) and scan it. captureLead is container-agnostic. function lepopupRoot(el){ var n = el; while (n && n !== document.body) { if (n.querySelector && n.querySelector('input[name^="lepopup-"]')) return n; n = n.parentElement; } return null; } document.addEventListener("click", function(e){ var t = e.target; var btn = t && t.closest ? t.closest(".lepopup-button") : null; if (!btn) return; var root = lepopupRoot(btn); if (root) captureLead(root); }, true); // ── Favourites (v2.44.0). Read from the live custom.min.js (houzez.Favorites), // 2026-09-06: the heart is . // A member's saved heart is re-classed to .remove_fav; a guest's (cookie // houzez_favorite_listings) keeps .add-favorite-js and gains .remove-favorite. // Both Houzez handlers are delegated on document in the BUBBLE phase, so // this capture-phase observer sees every click first and must not stop it: // Houzez still saves / un-saves / shows its login modal exactly as before. // The click itself is the signal -- even when Houzez saves nothing for a // guest, the person just told us which listing they want. document.addEventListener("click", function(e){ var btn = e.target && e.target.closest ? e.target.closest(".add-favorite-js, .remove_fav") : null; if (!btn) return; var id = parseInt(btn.getAttribute("data-listid") || "", 10); if (!id) return; var removing = btn.classList.contains("remove_fav") || btn.classList.contains("remove-favorite"); var onPage = L && String(L.listing_id) === String(id); var data = body({ event_type: removing ? "favorite_remove" : "favorite_add", listing_id: id, listing_price: onPage ? L.price : "", listing_type: onPage ? L.primary_type : "" }); // The answer to this one beacon is read, unlike every other: the portal // counts the saves and says when to offer to keep them (v2.98.0). The // rule lives there, not here, so it cannot drift from the rest of the // site. keepalive is dropped because we are waiting for the body. try { fetch(ENDPOINT, { method: "POST", credentials: "include", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: data }) .then(function (r) { return r.json(); }) .then(function (j) { if (!j || !j.success || !j.data || !j.data.save_ask) return; if (window.CBPVE && window.CBPVE.saveCard) window.CBPVE.saveCard(j.data.save_ask, j.data.saves); }) .catch(function(){}); } catch (e2) {} }, true); // WhatsApp click (v3.53.0). Every "message the agent" button on the site — // Houzez's own per-card and per-listing contact icons, plus our co-broke // banner's Ask/Share-location buttons — is a plain or // , target=_blank, so the tab this // script runs in never navigates away: no sendBeacon needed, a normal // keepalive fetch has time to land. Chris, 2026-09-22: wants every one of // these logged so a listing+time-range search (or a site-wide search) can // find who reached for WhatsApp. listing_id comes from the nearest // Houzez v9 card wrapper (data-hz-id, grid/related cards) or, failing // that, the current single-listing page's own id — never both guessed at // once, so a related-listing card is never mis-attributed to the page // it happens to sit on. document.addEventListener("click", function(e){ var a = e.target && e.target.closest ? e.target.closest('a[href*="wa.me"],a[href*="api.whatsapp.com"]') : null; if (!a) return; var lid = "", el = a, depth = 0, card = null; while (el && depth < 20) { if (el.dataset && el.dataset.hzId) { card = el; lid = el.dataset.hzId; break; } el = el.parentElement; depth++; } if (!lid && L) lid = L.listing_id; // v3.54.0: on a search-results list the click's own page (location.href, // the "common" default) is the LIST, not the card's listing - read the // card's real URL instead so the VE timeline links to the property that // was actually asked about. var linkUrl = cbpWaUrl(a) || cbpCardUrl(card); var whatsappExtra = { event_type: "whatsapp_click", listing_id: lid }; if (linkUrl) whatsappExtra.page_url = linkUrl; var data = body(whatsappExtra); try { fetch(ENDPOINT, { method: "POST", credentials: "include", keepalive: true, headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: data }).catch(function(){}); } catch (e3) {} // Ad-platform conversion feed (v3.53.1). Chris, 2026-09-22: wants this // counted as a GA4/Google Ads/Bing conversion too. Checked in GTM // (container GTM-KDT3465R) with Tag Assistant: the site's existing // "WhatsApp - Link Click" / "WhatsApp 2 - Link Click" triggers (Just // Links, matching this exact URL) have three tags built and ready // (BingAds/GA4/GAds Conv. - WhatsApp - Click) but NONE of them have // fired on a real click — confirmed live, zero dataLayer activity. // Pushing our own named event here is a second, independent path into // the same three tags once GTM is repointed at a Custom Event trigger // for it, one that does not depend on GTM's own link-click listener // ever starting to catch this click. try { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: "whatsapp_click", whatsapp_listing_id: lid || undefined }); } catch (e4) {} }, true); // Guest Save-Search (v2.40.0) — see the PHP comment above where // $guest_save_endpoint is set. Empty for a logged-in visitor: the whole // block below is inert (the early-return on the capture listener never // finds a target worth intercepting, since GSS_ENDPOINT is falsy) and // Houzez's own button keeps working exactly as it always has. var GSS_ENDPOINT = "https://portal.costaballenaproperty.com/wp-admin/admin-ajax.php?action=cbp_ve_guest_save_search"; if (GSS_ENDPOINT) { var gssStyleTag = document.createElement("style"); gssStyleTag.textContent = "#cbpss-ov{position:fixed;inset:0;background:rgba(20,20,30,.55);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif}" + "#cbpss-ov .cbpss-card{background:#fff;border-radius:14px;max-width:380px;width:100%;padding:22px 22px 18px;box-shadow:0 20px 50px rgba(0,0,0,.25);box-sizing:border-box}" + "#cbpss-ov .cbpss-h{font-size:18px;font-weight:700;color:#16213d;margin-bottom:6px}" + "#cbpss-ov .cbpss-sum{font-size:13px;color:#5a6478;line-height:1.5;margin-bottom:14px}" + "#cbpss-ov .cbpss-name,#cbpss-ov .cbpss-email{width:100%;box-sizing:border-box;border:1px solid #cfd6e3;border-radius:10px;padding:12px 14px;font-size:16px;margin-bottom:6px}" + "#cbpss-ov .cbpss-name:focus,#cbpss-ov .cbpss-email:focus{outline:none;border-color:#0b5ed7;box-shadow:0 0 0 3px rgba(11,94,215,.15)}" + "#cbpss-ov .cbpss-err{color:#c0392b;font-size:13px;margin:4px 0}" + "#cbpss-ov .cbpss-row{display:flex;gap:10px;margin-top:14px}" + "#cbpss-ov button{border-radius:10px;padding:12px 16px;font-size:15px;font-weight:600;cursor:pointer;min-height:44px;border:1px solid #cfd6e3;background:#fff;color:#3f4a66;flex:0 0 auto}" + "#cbpss-ov .cbpss-save{background:#0b5ed7;border-color:#0b5ed7;color:#fff;flex:1 1 auto}" + "#cbpss-ov .cbpss-save:disabled{opacity:.6;cursor:default}"; document.head.appendChild(gssStyleTag); function gssOpen() { if (document.getElementById("cbpss-ov")) return; var S = window.CBP_VE_SEARCH; var summary = (S && S.summary) || "your current search"; var ov = document.createElement("div"); ov.id = "cbpss-ov"; var esc = function (s) { return String(s).replace(/&/g, "&").replace(/Save this search' + '
' + esc(summary) + '
' + '' + '' + '' + '
' + '' + '' + '
' + ''; document.body.appendChild(ov); var nameEl = ov.querySelector(".cbpss-name"); var emailEl = ov.querySelector(".cbpss-email"); var errEl = ov.querySelector(".cbpss-err"); var saveBtn = ov.querySelector(".cbpss-save"); setTimeout(function () { nameEl.focus(); }, 0); ov.addEventListener("click", function (e) { if (e.target === ov) ov.remove(); }); ov.querySelector(".cbpss-cancel").onclick = function () { ov.remove(); }; nameEl.addEventListener("keydown", function (e) { if (e.key === "Enter") emailEl.focus(); }); emailEl.addEventListener("keydown", function (e) { if (e.key === "Enter") saveBtn.click(); }); saveBtn.onclick = function () { var n = nameEl.value.trim(); var v = emailEl.value.trim(); if (!n) { errEl.textContent = "Enter your name."; errEl.style.display = "block"; return; } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)) { errEl.textContent = "Enter a valid email address."; errEl.style.display = "block"; return; } errEl.style.display = "none"; saveBtn.textContent = "Saving…"; saveBtn.disabled = true; // The QUERY STRING, exactly as Houzez's own save-search sends it (its // hidden field is search_URI). This is the search; search_params is a // parsed rendering of it kept for display and editing. Storing only // the parsed form is what made the portal rebuild a URL on every send, // and the rebuilt one did not return the same listings. var qs = ""; try { qs = (window.location.search || "").replace(/^\?/, ""); } catch (e) { qs = ""; } var data = "name=" + encodeURIComponent(n) + "&email=" + encodeURIComponent(v) + "&search_uri=" + encodeURIComponent(qs) + "&search_params=" + encodeURIComponent(JSON.stringify((S && S.params) || [])) + "&summary=" + encodeURIComponent(summary); fetch(GSS_ENDPOINT, { method: "POST", credentials: "include", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: data }).then(function (r) { return r.json(); }).then(function (j) { if (j && j.success) { ov.querySelector(".cbpss-card").innerHTML = '
✓ Saved
' + '
We\'ll email you when something new matches.
' + '
'; ov.querySelector(".cbpss-cancel").onclick = function () { ov.remove(); }; } else { errEl.textContent = (j && j.data && j.data.message) || "Something went wrong — try again."; errEl.style.display = "block"; saveBtn.textContent = "Save & get email alerts"; saveBtn.disabled = false; } }).catch(function () { errEl.textContent = "Network error — try again."; errEl.style.display = "block"; saveBtn.textContent = "Save & get email alerts"; saveBtn.disabled = false; }); }; } // Capture phase on document, attached as early as possible: this runs // BEFORE Houzez's own bubble-phase jQuery .on('click', ...) handler can // fire, regardless of attachment order. stopPropagation() here keeps the // event from ever reaching that handler (or the login modal it would // open) at all -- this is the only reason the fix works without editing // custom.js, which a Houzez update would overwrite wholesale anyway. document.addEventListener("click", function (e) { var btn = e.target && e.target.closest ? e.target.closest(".save_search_click") : null; if (!btn) return; e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); gssOpen(); }, true); } // "Email me the flyer PDF" (v3.50.3) — see the PHP comment above where // $flyer_req_endpoint is set. Houzez's print button is
  • , d-none d-md-block (desktop only) in the live theme — // verified by reading the actual DOM, same rule as never editing custom.js. // The d-none/d-md-class removal (mobile-friendly, Chris's requirement) only // ever runs once FLYER_REQ_ENABLED is confirmed true (the fetch().then() // above) — Chris: unchecked, "use the default Houzez version," meaning // untouched, not just non-functional. Houzez's own button is left exactly // alone, visually and behaviourally, until the setting says otherwise. var PDFREQ_ENDPOINT = "https://portal.costaballenaproperty.com/wp-admin/admin-ajax.php?action=cbp_ve_flyer_request"; function cbpprUpgradeButtons() { if (!FLYER_REQ_ENABLED) return; var btns = document.querySelectorAll(".houzez-print"); for (var i = 0; i < btns.length; i++) { var li = btns[i]; if (li.getAttribute("data-cbppr")) continue; li.setAttribute("data-cbppr", "1"); li.classList.remove("d-none", "d-md-block"); var span = li.querySelector("[role=button]") || li; span.setAttribute("title", "Email me this listing's PDF"); } } window.cbpprUpgradeButtons = cbpprUpgradeButtons; document.addEventListener("DOMContentLoaded", cbpprUpgradeButtons); var cbpprStyleTag = document.createElement("style"); cbpprStyleTag.textContent = "#cbppr-ov{position:fixed;inset:0;background:rgba(20,20,30,.55);z-index:99999;display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Arial,sans-serif}" + "#cbppr-ov .cbppr-card{background:#fff;border-radius:14px;max-width:380px;width:100%;padding:22px 22px 18px;box-shadow:0 20px 50px rgba(0,0,0,.25);box-sizing:border-box}" + "#cbppr-ov .cbppr-h{font-size:18px;font-weight:700;color:#16213d;margin-bottom:6px}" + "#cbppr-ov .cbppr-sum{font-size:13px;color:#5a6478;line-height:1.5;margin-bottom:14px}" + "#cbppr-ov .cbppr-name,#cbppr-ov .cbppr-email,#cbppr-ov .cbppr-fname,#cbppr-ov .cbppr-femail{width:100%;box-sizing:border-box;border:1px solid #cfd6e3;border-radius:10px;padding:12px 14px;font-size:16px;margin-bottom:6px}" + "#cbppr-ov .cbppr-name:focus,#cbppr-ov .cbppr-email:focus,#cbppr-ov .cbppr-fname:focus,#cbppr-ov .cbppr-femail:focus{outline:none;border-color:#0b5ed7;box-shadow:0 0 0 3px rgba(11,94,215,.15)}" + // Language — a small flip switch, not a form control the same size as // name/email. Chris, 2026-09-17: "much smaller... not such a big deal." // Unchecked = English (the default), checked = Spanish. "#cbppr-ov .cbppr-langrow{display:flex;align-items:center;gap:6px;font-size:12px;color:#8a93a6;margin:0 0 10px}" + "#cbppr-ov .cbppr-switch{position:relative;display:inline-block;width:32px;height:18px;flex:0 0 auto}" + "#cbppr-ov .cbppr-switch input{position:absolute;inset:0;opacity:0;margin:0;cursor:pointer;z-index:1}" + "#cbppr-ov .cbppr-switch-track{position:absolute;inset:0;background:#cfd6e3;border-radius:999px;transition:background .15s}" + "#cbppr-ov .cbppr-switch-thumb{position:absolute;top:2px;left:2px;width:14px;height:14px;background:#fff;border-radius:50%;transition:transform .15s;box-shadow:0 1px 2px rgba(0,0,0,.3)}" + "#cbppr-ov .cbppr-switch input:checked ~ .cbppr-switch-track{background:#0b5ed7}" + "#cbppr-ov .cbppr-switch input:checked ~ .cbppr-switch-thumb{transform:translateX(14px)}" + "#cbppr-ov .cbppr-friend-toggle{display:flex;align-items:center;gap:8px;font-size:14px;color:#3f4a66;margin:4px 0 4px;cursor:pointer;min-height:44px}" + "#cbppr-ov .cbppr-friend-toggle input{width:18px;height:18px}" + "#cbppr-ov .cbppr-friend-block{border-top:1px solid #eef1f8;margin-top:4px;padding-top:10px}" + "#cbppr-ov .cbppr-err{color:#c0392b;font-size:13px;margin:4px 0}" + "#cbppr-ov .cbppr-row{display:flex;gap:10px;margin-top:14px}" + "#cbppr-ov button{border-radius:10px;padding:12px 16px;font-size:15px;font-weight:600;cursor:pointer;min-height:44px;border:1px solid #cfd6e3;background:#fff;color:#3f4a66;flex:0 0 auto}" + "#cbppr-ov .cbppr-save{background:#0b5ed7;border-color:#0b5ed7;color:#fff;flex:1 1 auto}" + "#cbppr-ov .cbppr-save:disabled{opacity:.6;cursor:default}"; document.head.appendChild(cbpprStyleTag); // A little flip switch, not a dropdown — unchecked (default) = English, // checked = Spanish. $LABEL is what the switch turns ON, since English is // already the assumed default and doesn't need its own labelled state. function cbpprLangSwitch(cls, label) { return ''; } function cbpprOpen(pid) { if (document.getElementById("cbppr-ov")) return; var ov = document.createElement("div"); ov.id = "cbppr-ov"; ov.innerHTML = '
    ' + '
    Get the PDF
    ' + '
    Tell us where to send it and we\'ll email you the listing PDF right away.
    ' + '' + '' + cbpprLangSwitch("cbppr-lang", "Send it in Spanish") + '' + '' + '' + '
    ' + '' + '' + '
    ' + '
    '; document.body.appendChild(ov); var nameEl = ov.querySelector(".cbppr-name"); var emailEl = ov.querySelector(".cbppr-email"); var langEl = ov.querySelector(".cbppr-lang"); var friendCheck = ov.querySelector(".cbppr-friend-check"); var friendBlock = ov.querySelector(".cbppr-friend-block"); var fnameEl = ov.querySelector(".cbppr-fname"); var femailEl = ov.querySelector(".cbppr-femail"); var flangEl = ov.querySelector(".cbppr-flang"); var errEl = ov.querySelector(".cbppr-err"); var saveBtn = ov.querySelector(".cbppr-save"); setTimeout(function () { nameEl.focus(); }, 0); ov.addEventListener("click", function (e) { if (e.target === ov) ov.remove(); }); ov.querySelector(".cbppr-cancel").onclick = function () { ov.remove(); }; nameEl.addEventListener("keydown", function (e) { if (e.key === "Enter") emailEl.focus(); }); emailEl.addEventListener("keydown", function (e) { if (e.key === "Enter") saveBtn.click(); }); friendCheck.addEventListener("change", function () { friendBlock.style.display = friendCheck.checked ? "block" : "none"; if (friendCheck.checked) fnameEl.focus(); }); var emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; saveBtn.onclick = function () { var n = nameEl.value.trim(); var v = emailEl.value.trim(); if (!n) { errEl.textContent = "Enter your name."; errEl.style.display = "block"; return; } if (!emailRe.test(v)) { errEl.textContent = "Enter a valid email address."; errEl.style.display = "block"; return; } var sendFriend = friendCheck.checked; var fn = sendFriend ? fnameEl.value.trim() : ""; var fv = sendFriend ? femailEl.value.trim() : ""; if (sendFriend && (!fn || !emailRe.test(fv))) { errEl.textContent = "Enter your friend's name and a valid email address, or uncheck \"send to a friend\"."; errEl.style.display = "block"; return; } errEl.style.display = "none"; saveBtn.textContent = "Sending…"; saveBtn.disabled = true; var data = "pid=" + encodeURIComponent(pid) + "&name=" + encodeURIComponent(n) + "&email=" + encodeURIComponent(v) + "&lang=" + ( langEl.checked ? "es" : "en" ) + (sendFriend ? "&friend_name=" + encodeURIComponent(fn) + "&friend_email=" + encodeURIComponent(fv) + "&friend_lang=" + ( flangEl.checked ? "es" : "en" ) : ""); fetch(PDFREQ_ENDPOINT, { method: "POST", credentials: "include", headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: data }).then(function (r) { return r.json(); }).then(function (j) { if (j && j.success) { // Same beacon as every other signal (favourites, saved searches): // logged here, not inside the mail-sending endpoint, which only // owns identity + delivery, never the audit trail (cbp_events). fetch(ENDPOINT, { method: "POST", credentials: "include", keepalive: true, headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" }, body: body({ event_type: "pdf_request", listing_id: pid }) }).catch(function(){}); var sumMsg = (j.data && j.data.friend_sent) ? "Check your email in a minute or two — and your friend's." : "Check your email in a minute or two."; ov.querySelector(".cbppr-card").innerHTML = '
    ✓ On its way
    ' + '
    ' + sumMsg + '
    ' + '
    '; ov.querySelector(".cbppr-cancel").onclick = function () { ov.remove(); }; } else { errEl.textContent = (j && j.data && j.data.message) || "Something went wrong — try again."; errEl.style.display = "block"; saveBtn.textContent = "Email me the PDF"; saveBtn.disabled = false; } }).catch(function () { errEl.textContent = "Network error — try again."; errEl.style.display = "block"; saveBtn.textContent = "Email me the PDF"; saveBtn.disabled = false; }); }; } // Capture phase, same race-winning technique as guest-save-search above — // Houzez's own bubble-phase handler (window.print()) must never fire. // Gated on FLYER_REQ_ENABLED: unchecked in Settings, this listener still // exists but does nothing — the click falls through untouched and Houzez's // own print handler runs exactly as if this feature were never installed. document.addEventListener("click", function (e) { if (!FLYER_REQ_ENABLED) return; var btn = e.target && e.target.closest ? e.target.closest(".houzez-print") : null; if (!btn) return; e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); var pid = parseInt(btn.getAttribute("data-propid") || (L ? L.listing_id : 0), 10); if (!pid) return; cbpprOpen(pid); }, true); })();