'); paymentWindow.document.close(); } } catch (e) { console.warn('Failed to open payment window:', e); }bbShowLoading('Reserving slots with ranger...');const tourTypeMap = { 'private-town': 'private_town_tour', 'living-lake': 'living_at_the_lake', 'twilight': 'twilight_tour', 'large-group': 'large_group_tour', 'mines': 'mines_mills_tour' };const payload = { date: bbState.selectedDate, time: bbState.selectedTime, party_size: partySize, tour_type: tourTypeMap[tourKey] || 'large_group_tour', vehicle_acknowledgment: tourKey === 'mines' ? (document.getElementById('mines-vehicle-ack') ? document.getElementById('mines-vehicle-ack').checked : false) : false, guest: { name: name, email: email, phone: phone } };try { let csrfToken = ''; let recaptchaSiteKey = '';try { const csrfResponse = await fetchWithTimeout(BOOKING_API, { method: 'GET', credentials: 'include', timeout: 10000 });if (csrfResponse.ok) { const csrfData = await csrfResponse.json(); csrfToken = csrfData.csrf_token; recaptchaSiteKey = csrfData.recaptcha_site_key; } } catch (csrfErr) { console.warn('Could not retrieve CSRF token or site key:', csrfErr); }let recaptchaToken = '';if (recaptchaSiteKey) { try { await Promise.race([ loadRecaptchaScript(recaptchaSiteKey), new Promise(function(_, reject) { setTimeout(function() { reject(new Error('reCAPTCHA script load timeout')); }, 5000); }) ]);if (typeof grecaptcha !== 'undefined') { recaptchaToken = await Promise.race([ grecaptcha.execute(recaptchaSiteKey, { action: 'submit_booking' }), new Promise(function(_, reject) { setTimeout(function() { reject(new Error('reCAPTCHA execution timeout')); }, 4000); }) ]); } } catch (err) { console.warn('reCAPTCHA execution failed, proceeding without token:', err); } }payload.recaptcha_token = recaptchaToken;const response = await fetchWithTimeout(BOOKING_API, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken }, credentials: 'include', body: JSON.stringify(payload), timeout: 10000 });let result = {};try { result = await response.json(); } catch (jsonErr) { console.warn('Could not parse response JSON:', jsonErr); }if (response.ok && result.status === 'success') { const totalPrice = tour ? tour.calcPrice(partySize) : partySize * 25.00;document.getElementById('bb-amount-display').textContent = '$' + totalPrice.toFixed(2) + ' DUE'; document.getElementById('bb-confirm-id').textContent = result.booking_id || '--'; document.getElementById('bb-confirm-party').textContent = partySize + ' Guest(s)';const displayDate = new Date(bbState.selectedDate + 'T12:00:00').toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });document.getElementById('bb-confirm-datetime').textContent = tourName + ' on ' + displayDate + ' @ ' + bbState.selectedTime;const invoiceLink = document.getElementById('bb-invoice-link'); const countdownEl = document.getElementById('bb-redirect-countdown');if (invoiceLink && result.payment_link && isTrustedRedirectUrl(result.payment_link)) { invoiceLink.href = result.payment_link; invoiceLink.style.display = 'inline-flex'; } else if (invoiceLink) { invoiceLink.style.display = 'none'; }const cancelLink = document.getElementById('bb-cancel-link');if (cancelLink && result.booking_id && result.token) { cancelLink.href = 'https://us-west2-bodie-tours-prod.cloudfunctions.net/cancel-tour?booking_id=' + encodeURIComponent(result.booking_id) + '&token=' + encodeURIComponent(result.token); }let openedInNewTab = false;if (paymentWindow) { try { if (!paymentWindow.closed) { if (result.payment_link && isTrustedRedirectUrl(result.payment_link)) { paymentWindow.location.href = result.payment_link; openedInNewTab = true; } else { paymentWindow.close(); } } } catch (e) { console.warn('Failed to set payment window location:', e); } }if (countdownEl) { if (openedInNewTab) { countdownEl.innerHTML = 'Your invoice has been opened in a new tab. If it did not open, click Pay Your Invoice Now above.'; } else { countdownEl.innerHTML = 'Please click the Pay Your Invoice Now button above to complete your secure payment.'; } }bbGoTo(4); } else { if (paymentWindow) { try { paymentWindow.close(); } catch (e) {} }if (response.status === 400) { errMsg.textContent = result.message || 'Validation error. Please check your inputs.'; } else if (response.status === 409) { errMsg.textContent = result.message || 'The requested slot is already booked. Please choose another slot.'; } else if (response.status === 429) { errMsg.textContent = 'Too many booking attempts. Please wait a moment and try again.'; } else if (response.status === 500) { errMsg.textContent = 'A temporary server error occurred. Please try again shortly.'; } else { errMsg.textContent = result.message || 'The slot is no longer available. Please select another time.'; }errBox.style.display = 'flex'; } } catch (err) { if (paymentWindow) { try { paymentWindow.close(); } catch (e) {} }console.error('Booking failed:', err); errMsg.textContent = 'Network or connection error. Please try again.'; errBox.style.display = 'flex'; } finally { bbState.isSubmitting = false;if (confirmBtn) { confirmBtn.disabled = false; }bbHideLoading(); } }function bbAnnounce(message) { const announcer = document.getElementById('bb-announcer');if (announcer) { announcer.textContent = '';setTimeout(function() { announcer.textContent = message; }, 50); } }function bbGoTo(step) { if (step < 4) { bbClearTimers(); }document.querySelectorAll('.bb-step').forEach(function(pane) { pane.classList.remove('active'); });document.getElementById('step-pane-' + step).classList.add('active');document.getElementById('bb-progress-bar').style.width = (step / 4) * 100 + '%';const progressContainer = document.getElementById('bb-progress-container');if (progressContainer) { progressContainer.setAttribute('aria-valuenow', step + 1); }document.querySelectorAll('.bb-progress-step').forEach(function(dot, index) { if (index < step) { dot.className = 'bb-progress-step completed'; dot.removeAttribute('aria-current'); } else if (index === step) { dot.className = 'bb-progress-step active'; dot.setAttribute('aria-current', 'step'); } else { dot.className = 'bb-progress-step'; dot.removeAttribute('aria-current'); } });if (step === 1) { bbFetchMonthAvailability(bbState.currentYear, bbState.currentMonth).then(function() { bbRenderCalendar(); }); }if (step === 2) { bbPopulateSlots(); }if (step === 3) { bbUpdateStep3Fields(); }const heading = document.getElementById('bb-step-heading-' + step);if (heading) { heading.focus({ preventScroll: true }); bbAnnounce('Step ' + (step + 1) + ' of 5: ' + heading.textContent.trim()); }const widget = document.getElementById('bodie-booking-widget');if (widget) { window.scrollTo({ top: widget.offsetTop - 30, behavior: 'smooth' }); } }function bbSelectTour(tourKey) { bbState.selectedTour = tourKey;document.querySelectorAll('.bb-tour-card').forEach(function(card) { const isSelected = card.getAttribute('data-tour') === tourKey;if (isSelected) { card.classList.add('selected'); card.setAttribute('aria-checked', 'true'); card.setAttribute('aria-pressed', 'true'); card.setAttribute('tabindex', '0'); } else { card.classList.remove('selected'); card.setAttribute('aria-checked', 'false'); card.setAttribute('aria-pressed', 'false'); card.setAttribute('tabindex', '-1'); } });const tourName = TOURS[tourKey] ? TOURS[tourKey].name : 'Tour';bbAnnounce('Selected tour: ' + tourName);document.getElementById('bb-to-step-1').disabled = false;bbState.selectedDate = null; bbState.selectedTime = null;document.getElementById('bb-to-step-2').disabled = true; document.getElementById('bb-to-step-3').disabled = true; }function bbUpdateStep3Fields() { const tourKey = bbState.selectedTour; const tour = TOURS[tourKey];const ackGroup = document.getElementById('mines-vehicle-ack-group'); const ackInput = document.getElementById('mines-vehicle-ack'); const partyInput = document.getElementById('guest-party'); const partyLabel = document.querySelector('label[for="guest-party"]');if (tourKey === 'mines') { ackGroup.style.display = 'block'; ackInput.required = true; } else { ackGroup.style.display = 'none'; ackInput.required = false; ackInput.checked = false; }const maxParty = tour ? tour.maxParty : 20;partyInput.max = maxParty; partyInput.placeholder = 'e.g., 4 (maximum ' + maxParty + ')'; partyLabel.textContent = 'Number of Guests (Required, max ' + maxParty + ')'; }async function bbRetryCalendarFetch() { await bbFetchMonthAvailability(bbState.currentYear, bbState.currentMonth); bbRenderCalendar(); }function bbStartOver() { bbClearTimers();bbState = { selectedTour: null, currentYear: new Date().getFullYear(), currentMonth: new Date().getMonth(), selectedDate: null, selectedTime: null, availableDates: {}, redirectTimeout: null, redirectInterval: null, calendarFetchFailed: false, isSubmitting: false };const form = document.getElementById('bb-booking-form');if (form) { form.reset(); }const errBox = document.getElementById('bb-error-box');if (errBox) { errBox.style.display = 'none'; }document.getElementById('bb-to-step-1').disabled = true; document.getElementById('bb-to-step-2').disabled = true; document.getElementById('bb-to-step-3').disabled = true;document.querySelectorAll('.bb-tour-card').forEach(function(card) { card.classList.remove('selected'); card.setAttribute('aria-checked', 'false'); card.setAttribute('aria-pressed', 'false'); });bbGoTo(0); }function bbShowLoading(text) { const loading = document.getElementById('bb-loading'); const loadingText = document.getElementById('bb-loading-text');if (loadingText) { loadingText.textContent = text; }if (loading) { loading.setAttribute('aria-hidden', 'false'); loading.classList.add('active'); } }function bbHideLoading() { const loading = document.getElementById('bb-loading');if (loading) { loading.setAttribute('aria-hidden', 'true'); loading.classList.remove('active'); } }window.bbSelectTour = bbSelectTour; window.bbRetryCalendarFetch = bbRetryCalendarFetch; window.bbStartOver = bbStartOver; window.bbChangeMonth = bbChangeMonth; window.bbGoTo = bbGoTo; window.bbSubmitBooking = bbSubmitBooking; window.bbInit = bbInit;function bbSafeInit() { if (!document.getElementById('bodie-booking-widget')) { return; }bbInit(); }if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', bbSafeInit); } else { bbSafeInit(); } })();

Ghost Walks are ON

After having to cancel June’s Ghost Walk this year due to Covid-19, we have now reformatted the walks to ensure we are meeting safety guidelines for both the State of California as well as Mono County.

The July Ghost Walk is already full, and if you’ve already signed up keep an eye out for an email from me detailing the changes to the walk and the schedule. Feel free to send me an email with questions after that.

PLEASE NOTE THAT THIS YEAR THE GENERAL PUBLIC IS NOT ALLOWED IN THE PARK AFTER HOURS DURING GHOST WALKS. ONLY THOSE ATTENDING GHOST WALKS MAY BE IN THE PARK AFTER 6PM.

Thank you to everyone for your patience while we navigate our way through changing times.

-Jessica Schneider
Bodie Foundation, Executive Director

Join our mailing list

Love Bodie State Historic Park? Stay connected for information on Bodie and the Foundation’s work.