1(function () { 2 const API_ORIGIN = "https://contact.haiphen.io"; 3 const ENDPOINT = `${API_ORIGIN}/api/contact`; 4 5 const LOG_PREFIX = "[contact]"; 6 const WIRE_TIMEOUT_MS = 15000; 7 8 function qsIn(root, id) { 9 return (root || document).querySelector(`#${CSS.escape(id)}`); 10 } 11 12 function setStatus(root, msg) { 13 const el = qsIn(root, "contactStatus"); 14 if (el) el.textContent = msg || ""; 15 } 16 17 function disableForm(root, disabled) { 18 const btn = qsIn(root, "contactSubmit"); 19 if (btn) btn.disabled = !!disabled; 20 21 const form = qsIn(root, "contactForm"); 22 if (!form) return; 23 24 [...form.elements].forEach((e) => { 25 if (e && e.tagName && e.tagName !== "BUTTON") e.disabled = !!disabled; 26 }); 27 } 28 29 async function postJson(url, body) { 30 console.debug(LOG_PREFIX, "POST", url, body); 31 const resp = await fetch(url, { 32 method: "POST", 33 headers: { "Content-Type": "application/json" }, 34 body: JSON.stringify(body), 35 }); 36 37 const text = await resp.text(); 38 let data = null; 39 try { data = text ? JSON.parse(text) : null; } catch (_) {} 40 41 if (!resp.ok) { 42 const msg = data?.error || `Request failed (${resp.status})`; 43 throw new Error(msg); 44 } 45 return data; 46 } 47 48 function showReceipt(root, data) { 49 const form = qsIn(root, "contactForm"); 50 const receipt = qsIn(root, "contactReceipt"); 51 if (!form || !receipt) return; 52 53 form.hidden = true; 54 receipt.hidden = false; 55 56 qsIn(root, "ticketId").textContent = data.ticketId || "â"; 57 qsIn(root, "ticketEmail").textContent = data.email || "â"; 58 qsIn(root, "ticketMeta").textContent = 59 `Queue position: ${data.queuePosition ?? "â"} ⢠Received: ${data.receivedAt || "â"}`; 60 } 61 62 function showForm(root) { 63 const form = qsIn(root, "contactForm"); 64 const receipt = qsIn(root, "contactReceipt"); 65 if (!form || !receipt) return; 66 67 form.hidden = false; 68 receipt.hidden = true; 69 form.reset(); 70 setStatus(root, ""); 71 } 72 73 function attachOnce(root) { 74 const form = qsIn(root, "contactForm"); 75 if (!form) return false; 76 77 // Prevent double-wiring even if you re-inject 78 if (form.dataset.hpWired === "1") return true; 79 form.dataset.hpWired = "1"; 80 81 console.info(LOG_PREFIX, "wiring submit handler"); 82 83 form.addEventListener("submit", async (e) => { 84 e.preventDefault(); 85 setStatus(root, ""); 86 87 const fd = new FormData(form); 88 const payload = { 89 name: String(fd.get("name") || "").trim(), 90 email: String(fd.get("email") || "").trim(), 91 phone: String(fd.get("phone") || "").trim(), 92 message: String(fd.get("message") || "").trim(), 93 website: String(fd.get("website") || "").trim(), // honeypot 94 token: "", // optional 95 pageUrl: window.location.href, 96 userAgent: navigator.userAgent, 97 }; 98 99 if (!payload.name || !payload.email || payload.message.length < 10) { 100 setStatus(root, "Please fill name, email, and a longer message."); 101 return; 102 } 103 104 disableForm(root, true); 105 setStatus(root, "Submittingâ¦"); 106 107 try { 108 const data = await postJson(ENDPOINT, payload); 109 setStatus(root, ""); 110 showReceipt(root, data); 111 } catch (err) { 112 console.warn(LOG_PREFIX, "submit failed", err); 113 setStatus(root, err?.message || "Submit failed. Try again."); 114 } finally { 115 disableForm(root, false); 116 } 117 }); 118 119 const another = qsIn(root, "contactAnother"); 120 if (another && another.dataset.hpWired !== "1") { 121 another.dataset.hpWired = "1"; 122 another.addEventListener("click", () => showForm(root)); 123 } 124 125 return true; 126 } 127 128 function applyPrefill(root) { 129 const prefill = window.HAIPHEN?.prefillContact; 130 if (!prefill?.message) return; 131 const form = qsIn(root, "contactForm"); 132 if (!form) return; 133 const msgEl = form.querySelector('[name="message"]'); 134 if (msgEl) { 135 msgEl.value = prefill.message; 136 delete window.HAIPHEN.prefillContact; 137 } 138 } 139 140 function wireReveal(root) { 141 var revealEls = (root || document).querySelectorAll('.hp-contact-reveal'); 142 if (!revealEls.length) return; 143 var io = new IntersectionObserver(function(entries) {
144 entries.forEach(function(e) { 145 if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); } 146 }); 147 }, { threshold: 0.15 }); 148 revealEls.forEach(function(el) { io.observe(el); }); 149 } 150 151 // â PUBLIC INIT (no auto observer unless you call it) 152 function init(rootEl) { 153 const root = rootEl || document; 154 155 // Fast-path: if the form exists now, wire immediately. 156 if (attachOnce(root)) { 157 applyPrefill(root); 158 wireReveal(root); 159 return true; 160 } 161 162 // Optional: if you *do* want a wait mode, do it only when Contact is being opened. 163 const startedAt = Date.now(); 164 const obs = new MutationObserver(() => { 165 if (attachOnce(root)) { 166 obs.disconnect(); 167 applyPrefill(root); 168 wireReveal(root); 169 } else if (Date.now() - startedAt > WIRE_TIMEOUT_MS) { 170 obs.disconnect(); 171 console.error(LOG_PREFIX, "timed out waiting for #contactForm to appear"); 172 } 173 }); 174 175 obs.observe(root === document ? document.documentElement : root, { 176 childList: true, 177 subtree: true, 178 }); 179 180 return false; 181 } 182 183 window.HAIPHEN = window.HAIPHEN || {}; 184 window.HAIPHEN.ContactForm = { init }; 185})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.