1(() => { 2 "use strict"; 3 4 const openState = document.querySelector("[data-open-state]"); 5 const waitlistState = document.querySelector("[data-waitlist-state]"); 6 const membershipLabel = document.querySelector("[data-membership-label]"); 7 const checkoutButton = document.querySelector("[data-checkout-button]"); 8 const checkoutLabel = document.querySelector("[data-checkout-label]"); 9 const checkoutError = document.querySelector("[data-checkout-error]"); 10 const checkoutDialog = document.querySelector("[data-checkout-dialog]"); 11 const dialogTitle = checkoutDialog?.querySelector("[data-dialog-title]"); 12 const dialogMessage = checkoutDialog?.querySelector("[data-dialog-message]"); 13 const billingInputs = [ 14 ...document.querySelectorAll('input[name="billing-plan"]'), 15 ]; 16 const priceAmount = document.querySelector("[data-price-amount]"); 17 const priceDetail = document.querySelector("[data-price-detail]"); 18 const billingFact = document.querySelector("[data-billing-fact]"); 19 const renewalFact = document.querySelector("[data-renewal-fact]"); 20 const checkoutNote = document.querySelector("[data-checkout-note]"); 21 const waitlistForm = document.querySelector("[data-waitlist-form]"); 22 const waitlistMessage = document.querySelector("[data-waitlist-message]"); 23 const waitlistLabel = document.querySelector("[data-waitlist-label]"); 24 const portalLinks = [...document.querySelectorAll("[data-portal-link]")]; 25 const mobileActionBar = document.querySelector(".mobile-action-bar"); 26 const membershipOffer = document.querySelector("#membership-offer"); 27 const stateOverride = new URLSearchParams(window.location.search).get("state"); 28 const isStaticPreview = 29 ["localhost", "127.0.0.1"].includes(window.location.hostname) && 30 window.location.port === "4173"; 31 32 let checkoutReady = false; 33 let selectedBillingPlan = "annual"; 34 let turnstileWidgetId = null; 35 let turnstileSiteKey = ""; 36 37 const billingPlans = { 38 annual: { 39 amount: "500", 40 detail: 41 "billed once per year<br />plus applicable taxes<br />and processing fees", 42 billing: "One annual payment", 43 renewal: "Automatic annually", 44 note: 45 "Complete enrollment without leaving Humble Baron. Applicable taxes and the separate processing fee are shown before payment.", 46 }, 47 monthly: { 48 amount: "41.67", 49 detail: 50 "per month<br />12-month commitment<br />plus taxes and processing fees", 51 billing: "11 à $41.67 · final $41.63", 52 renewal: "New term offered after month 12", 53 note: 54 "The monthly plan totals exactly $500: 11 payments of $41.67 and a final payment of $41.63. Applicable taxes and the separate $5 processing fee are added to each payment.", 55 }, 56 }; 57 58 function setHidden(element, hidden) { 59 if (!element) return; 60 element.hidden = hidden; 61 } 62 63 function setMembershipState(isOpen) { 64 setHidden(openState, !isOpen); 65 setHidden(waitlistState, isOpen); 66 document.body.dataset.membershipState = isOpen ? "open" : "waitlist"; 67 } 68 69 function setPortalUrl(url) { 70 if (!url || !url.startsWith("https://")) return; 71 portalLinks.forEach((link) => { 72 link.href = url; 73 link.target = "_blank"; 74 link.rel = "noopener noreferrer"; 75 }); 76 } 77 78 function setBillingPlan(plan) { 79 if (!billingPlans[plan]) return; 80 selectedBillingPlan = plan; 81 const selected = billingPlans[plan]; 82 83 if (priceAmount) priceAmount.textContent = selected.amount; 84 if (priceDetail) priceDetail.innerHTML = selected.detail; 85 if (billingFact) billingFact.textContent = selected.billing; 86 if (renewalFact) renewalFact.textContent = selected.renewal; 87 if (checkoutNote) checkoutNote.textContent = selected.note; 88 clearCheckoutError(); 89 } 90 91 billingInputs.forEach((input) => { 92 input.addEventListener("change", () => { 93 if (input.checked) setBillingPlan(input.value); 94 }); 95 }); 96 97 function showDialog({ 98 title = "One final connection.", 99 message = "Stripe checkout is being connected for launch. The Club page is ready to preview, but no payment can be collected yet.", 100 }
100 = {}) { 101 if (!checkoutDialog) return; 102 if (dialogTitle) dialogTitle.textContent = title; 103 if (dialogMessage) dialogMessage.textContent = message; 104 105 if (typeof checkoutDialog.showModal === "function") { 106 checkoutDialog.showModal(); 107 } else { 108 checkoutDialog.setAttribute("open", ""); 109 } 110 } 111 112 function closeDialog() { 113 checkoutDialog?.close?.(); 114 checkoutDialog?.removeAttribute("open"); 115 } 116 117 checkoutDialog 118 ?.querySelector("[data-dialog-close]") 119 ?.addEventListener("click", closeDialog); 120 121 checkoutDialog?.addEventListener("click", (event) => { 122 if (event.target === checkoutDialog) closeDialog(); 123 }); 124 125 function showCheckoutError(message) { 126 if (!checkoutError) return; 127 checkoutError.textContent = message; 128 checkoutError.hidden = false; 129 } 130 131 function clearCheckoutError() { 132 if (!checkoutError) return; 133 checkoutError.textContent = ""; 134 checkoutError.hidden = true; 135 } 136 137 function setCheckoutLoading(isLoading) { 138 if (!checkoutButton) return; 139 checkoutButton.disabled = isLoading; 140 checkoutButton.classList.toggle("is-loading", isLoading); 141 checkoutButton.setAttribute("aria-busy", String(isLoading)); 142 if (checkoutLabel) { 143 checkoutLabel.textContent = isLoading 144 ? "Preparing enrollment" 145 : "Join the Membership"; 146 } 147 } 148 149 async function requestJson(url, options = {}) { 150 const controller = new AbortController(); 151 const timeout = window.setTimeout(() => controller.abort(), 12_000); 152 153 try { 154 const response = await fetch(url, { 155 ...options, 156 signal: controller.signal, 157 headers: { 158 "content-type": "application/json", 159 ...(options.headers || {}), 160 }, 161 }); 162 const payload = await response.json().catch(() => ({})); 163 return { response, payload }; 164 } finally { 165 window.clearTimeout(timeout); 166 } 167 } 168 169 function loadTurnstile(siteKey) { 170 const container = document.querySelector("[data-turnstile-container]"); 171 if (!container || !siteKey || turnstileWidgetId !== null) return; 172 173 turnstileSiteKey = siteKey; 174 const renderWidget = () => { 175 if (!window.turnstile || turnstileWidgetId !== null) return; 176 turnstileWidgetId = window.turnstile.render(container, { 177 sitekey: turnstileSiteKey, 178 theme: "dark", 179 }); 180 }; 181 182 if (window.turnstile) { 183 renderWidget(); 184 return; 185 } 186 187 const script = document.createElement("script"); 188 script.src = 189 "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"; 190 script.async = true; 191 script.defer = true; 192 script.addEventListener("load", renderWidget); 193 script.addEventListener("error", () => { 194 container.textContent = 195 "Spam protection could not load. Please refresh and try again."; 196 }); 197 document.head.appendChild(script); 198 } 199 200 async function loadMembershipStatus() { 201 if (!openState && !waitlistState && !portalLinks.length) return; 202 203 if (stateOverride === "waitlist") { 204 setMembershipState(false); 205 } else { 206 setMembershipState(true); 207 } 208 209 if (isStaticPreview) { 210 checkoutReady = false; 211 document.body.dataset.membershipPreview = "true"; 212 return; 213 } 214 215 try { 216 const { response, payload } = await requestJson( 217 "/api/membership/status", 218 { method: "GET", headers: {} }, 219 ); 220 if (!response.ok) throw new Error("Status unavailable"); 221 222 checkoutReady = Boolean(payload.checkoutReady); 223 const isOpen = 224 stateOverride === "waitlist" 225 ? false 226 : stateOverride === "open" 227 ? true 228 : Boolean(payload.membershipOpen); 229 230 setMembershipState(isOpen); 231 setPortalUrl(payload.portalUrl); 232 if (payload.turnstileSiteKey) loadTurnstile(payload.turnstileSiteKey); 233 234 if (membershipLabel) { 235 membershipLabel.textContent = isOpen 236 ? "Membership is open" 237 : "Membership is at capacity"; 238 } 239 } catch { 240 checkoutReady = false; 241 document.body.dataset.membershipPreview = "true"; 242 if (membershipLabel) { 243 membershipLabel.textContent = 244 "Enrollment status is temporarily unavailable"; 245 } 246 if (checkoutButton) { 247 checkoutButton.disabled = true; 248 checkoutButton.setAttribute("aria-disabled", "true"); 249 } 250 if (checkoutLabel) checkoutLabel.textContent = "Enrollment unavailable"; 251 showCheckoutError( 252 "We could not confirm membership availability. Please try again shortly or contact [email protected].", 253 ); 254 } 255 } 256 257 checkoutButton?.addEventListener("click", () => { 258 clearCheckoutError(); 259 260 if (!checkoutReady) { 261 showDialog(); 262 return; 263 } 264 265 setCheckoutLoading(true); 266 const query = new URLSearchParams({ plan: selectedBillingPlan }); 267 window.location.assign(`/barons-club/join/?${query.toString()}`); 268 }); 269 270 waitlistForm?.addEventListener("submit", async (event) => { 271 event.preventDefault(); 272 waitlistMessage.hidden = true; 273 waitlistMessage.classList.remove("is-error"); 274 275 if (!waitlistForm.reportValidity()) return; 276 277 const submitButton = waitlistForm.querySelector('button[type="submit"]'); 278 const formData = new FormData(waitlistForm); 279 const turnstileToken = 280 turnstileWidgetId !== null && window.turnstile 281 ? window.turnstile.getResponse(turnstileWidgetId) 282 : ""; 283 284 const payload = { 285 firstName: formData.get("firstName")?.toString().trim(), 286 lastName: formData.get("lastName")?.toString().trim(), 287 email: formData.get("email")?.toString().trim(), 288 phone: formData.get("phone")?.toString().trim(), 289 companyTitle: formData.get("companyTitle")?.toString().trim(), 290 website: formData.get("website")?.toString(), 291 consent: formData.get("consent") === "yes", 292 turnstileToken, 293 }; 294 295 submitButton.disabled = true; 296 submitButton.setAttribute("aria-busy", "true"); 297 if (waitlistLabel) waitlistLabel.textContent = "Saving your place"; 298 299 try { 300 const result = await requestJson("/api/membership/waitlist", { 301 method: "POST", 302 body: JSON.stringify(payload), 303 }); 304 305 if (!result.response.ok) { 306 throw new Error( 307 result.payload.message || 308 "Your waitlist request could not be saved. Please try again.", 309 ); 310 } 311 312 waitlistForm.reset(); 313 waitlistMessage.textContent = 314 result.payload.message || 315 "You are on the waitlist. Watch your inbox for Club updates."; 316 waitlistMessage.hidden = false;
317 if (turnstileWidgetId !== null && window.turnstile) { 318 window.turnstile.reset(turnstileWidgetId); 319 } 320 } catch (error) { 321 waitlistMessage.textContent = 322 error.name === "AbortError" 323 ? "The request took too long. Please try again." 324 : error.message || 325 "Your waitlist request could not be saved. Please try again."; 326 waitlistMessage.classList.add("is-error"); 327 waitlistMessage.hidden = false; 328 } finally { 329 submitButton.disabled = false; 330 submitButton.removeAttribute("aria-busy"); 331 if (waitlistLabel) waitlistLabel.textContent = "Join the Waitlist"; 332 } 333 }); 334 335 async function loadWelcomeSession() { 336 const welcomeMessage = document.querySelector("[data-welcome-message]"); 337 if (!welcomeMessage) return; 338 339 const memberName = document.querySelector("[data-member-name]"); 340 const welcomeKicker = document.querySelector("[data-welcome-kicker]"); 341 const sessionId = new URLSearchParams(window.location.search).get( 342 "session_id", 343 ); 344 345 if (!sessionId) { 346 if (welcomeKicker) welcomeKicker.textContent = "The Baronâs Club"; 347 return; 348 } 349 350 if (isStaticPreview) return; 351 352 try { 353 const { response, payload } = await requestJson( 354 `/api/membership/session?session_id=${encodeURIComponent(sessionId)}`, 355 { method: "GET", headers: {} }, 356 ); 357 if (!response.ok || !payload.confirmed) throw new Error("Not confirmed"); 358 359 if (payload.firstName && memberName) { 360 memberName.textContent = `Welcome, ${payload.firstName}.`; 361 memberName.hidden = false; 362 } 363 setPortalUrl(payload.portalUrl); 364 } catch { 365 if (welcomeKicker) welcomeKicker.textContent = "Confirmation received"; 366 } 367 } 368 369 document.querySelectorAll(".club-faq details").forEach((detail) => { 370 detail.addEventListener("toggle", () => { 371 if (!detail.open) return; 372 document.querySelectorAll(".club-faq details[open]").forEach((item) => { 373 if (item !== detail) item.open = false; 374 }); 375 }); 376 }); 377 378 if ( 379 mobileActionBar && 380 membershipOffer && 381 "IntersectionObserver" in window 382 ) { 383 const actionBarObserver = new IntersectionObserver( 384 ([entry]) => { 385 mobileActionBar.classList.toggle( 386 "is-context-hidden", 387 entry.isIntersecting, 388 ); 389 }, 390 { 391 rootMargin: "-6% 0px -12% 0px", 392 threshold: 0, 393 }, 394 ); 395 actionBarObserver.observe(membershipOffer); 396 } 397 398 loadMembershipStatus(); 399 loadWelcomeSession(); 400 setBillingPlan( 401 billingInputs.find((input) => input.checked)?.value || "annual", 402 ); 403})();
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.