Your Safari
Start Date
Start Date
November 2025
Mon
Tue
Wed
Thu
Fri
Sat
Sun
Travelers
Travelers
Age at the end of the tour
Tour Length
½ day
21+ days
Rates in USD $
Per person, excl. international flights
$100
$10,000+
Comfort Level
Tour Type
Safari Type
Other Tour Features
Filters
African Safari - 55 Tours
1-10 of 55 results
// Ensure customBookingAjax is defined
if (typeof customBookingAjax === 'undefined') {
var customBookingAjax = {
ajaxurl: 'https://ottosafariguide.com/wp-admin/admin-ajax.php',
nonce: 'd8049c4c5d'
};
}
document.addEventListener('DOMContentLoaded', function() {
// Mobile Filters Modal functionality
const mobileFiltersModal = document.getElementById('mobile-filters-modal');
const mobileFiltersModalBody = document.getElementById('mobile-filters-modal-body');
const mobileFiltersModalClose = document.getElementById('mobile-filters-modal-close');
const filtersContainer = document.getElementById('filters-container');
const mobileFiltersBtn = document.getElementById('mobile-filters-btn');
const mobileWhereToBtn = document.getElementById('mobile-where-to-btn');
const mobileTourLengthBtn = document.getElementById('mobile-tour-length-btn');
const mobileFiltersApply = document.querySelector('.mobile-filters-apply');
const mobileFiltersClear = document.querySelector('.mobile-filters-clear');
// Copy filters content to modal
function syncFiltersToModal() {
if (filtersContainer && mobileFiltersModalBody) {
mobileFiltersModalBody.innerHTML = filtersContainer.innerHTML;
// Re-initialize all handlers for modal content
initializeModalFilters();
}
}
// Initialize modal filter handlers
function initializeModalFilters() {
// Re-initialize destination dropdown in modal
const modalDestinationSelect = mobileFiltersModalBody.querySelector('#destination-select-tours');
const modalDestinationClearBtn = mobileFiltersModalBody.querySelector('.destination-clear-btn');
if (modalDestinationSelect) {
modalDestinationSelect.addEventListener('change', function() {
// Sync with sidebar
if (destinationSelect) {
destinationSelect.value = this.value;
}
if (modalDestinationClearBtn) {
if (this.value && this.value !== '') {
modalDestinationClearBtn.style.display = '';
} else {
modalDestinationClearBtn.style.display = 'none';
}
}
if (destinationClearBtn) {
if (this.value && this.value !== '') {
destinationClearBtn.style.display = '';
} else {
destinationClearBtn.style.display = 'none';
}
}
debounceFilter();
});
}
if (modalDestinationClearBtn) {
modalDestinationClearBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
if (modalDestinationSelect) {
modalDestinationSelect.value = '';
this.style.display = 'none';
}
if (destinationSelect) {
destinationSelect.value = '';
}
if (destinationClearBtn) {
destinationClearBtn.style.display = 'none';
}
debounceFilter();
});
}
// Re-initialize range sliders in modal
const modalTourLengthMin = mobileFiltersModalBody.querySelector('#tour-length-min');
const modalTourLengthMax = mobileFiltersModalBody.querySelector('#tour-length-max');
const modalRatesMin = mobileFiltersModalBody.querySelector('#rates-min');
const modalRatesMax = mobileFiltersModalBody.querySelector('#rates-max');
if (modalTourLengthMin && modalTourLengthMax) {
modalTourLengthMin.addEventListener('input', function() {
// Sync with sidebar
const sidebarMin = document.querySelector('.sidebar-filters #tour-length-min');
if (sidebarMin) sidebarMin.value = this.value;
updateTourLengthSlider();
debounceFilter();
});
modalTourLengthMax.addEventListener('input', function() {
const sidebarMax = document.querySelector('.sidebar-filters #tour-length-max');
if (sidebarMax) sidebarMax.value = this.value;
updateTourLengthSlider();
debounceFilter();
});
}
if (modalRatesMin && modalRatesMax) {
modalRatesMin.addEventListener('input', function() {
const sidebarMin = document.querySelector('.sidebar-filters #rates-min');
if (sidebarMin) sidebarMin.value = this.value;
updateRatesSlider();
debounceFilter();
});
modalRatesMax.addEventListener('input', function() {
const sidebarMax = document.querySelector('.sidebar-filters #rates-max');
if (sidebarMax) sidebarMax.value = this.value;
updateRatesSlider();
debounceFilter();
});
}
// Sync checkboxes
const modalCheckboxes = mobileFiltersModalBody.querySelectorAll('input[type="checkbox"]');
modalCheckboxes.forEach(modalCb => {
modalCb.addEventListener('change', function() {
const sidebarCb = document.querySelector('.sidebar-filters input[name="' + this.name + '"][value="' + this.value + '"]');
if (sidebarCb) sidebarCb.checked = this.checked;
debounceFilter();
});
});
// Re-initialize Travelers panel in modal
const modalTravelersField = mobileFiltersModalBody.querySelector('.travelers-field');
const modalTravelersPanel = mobileFiltersModalBody.querySelector('.travelers-panel');
const modalTravelersFieldContent = mobileFiltersModalBody.querySelector('.travelers-field-content');
const modalTravelersCloseBtn = mobileFiltersModalBody.querySelector('.travelers-close-btn');
const modalTravelersDoneBtn = mobileFiltersModalBody.querySelector('.travelers-done-btn');
const modalTravelersClearBtn = mobileFiltersModalBody.querySelector('.travelers-clear-btn');
const modalAdultsCountInput = mobileFiltersModalBody.querySelector('#adults-count');
const modalChildrenCountInput = mobileFiltersModalBody.querySelector('#children-count');
const modalChildrenAgesContainer = mobileFiltersModalBody.querySelector('#children-ages');
const modalCounterButtons = mobileFiltersModalBody.querySelectorAll('.counter-btn');
// Function to update children ages in both sidebar and modal
function updateChildrenAgesBoth() {
// Update sidebar
if (childrenAgesContainer) {
const previousValues = Array.from(childrenAgesContainer.querySelectorAll('select.age-select')).map(sel => sel.value || '');
childrenAgesContainer.innerHTML = '';
for (let i = 1; i <= childrenCount; i++) {
const ageGroup = document.createElement('div');
ageGroup.className = 'child-age-group';
const prevVal = previousValues[i - 1] || childAgesSelected[i - 1] || '';
ageGroup.innerHTML = `
<label class="child-label">Child ${i}:</label>
<select class="age-select" data-child="${i}">
<option value="">- Age -</option>
${Array.from({length: 18}, (_, j) => {
const val = j.toString();
const selected = prevVal === val ? 'selected' : '';
return `<option value="${val}" ${selected}>${j}</option>`;
}).join('')}
</select>
`;
childrenAgesContainer.appendChild(ageGroup);
}
attachAgeSelectListeners(childrenAgesContainer);
}
// Update modal
if (modalChildrenAgesContainer) {
const previousValues = Array.from(modalChildrenAgesContainer.querySelectorAll('select.age-select')).map(sel => sel.value || '');
modalChildrenAgesContainer.innerHTML = '';
for (let i = 1; i <= childrenCount; i++) {
const ageGroup = document.createElement('div');
ageGroup.className = 'child-age-group';
const prevVal = previousValues[i - 1] || childAgesSelected[i - 1] || '';
ageGroup.innerHTML = `
<label class="child-label">Child ${i}:</label>
<select class="age-select" data-child="${i}">
<option value="">- Age -</option>
${Array.from({length: 18}, (_, j) => {
const val = j.toString();
const selected = prevVal === val ? 'selected' : '';
return `<option value="${val}" ${selected}>${j}</option>`;
}).join('')}
</select>
`;
modalChildrenAgesContainer.appendChild(ageGroup);
}
attachAgeSelectListeners(modalChildrenAgesContainer);
}
}
if (modalTravelersField && modalTravelersPanel) {
function openModalTravelersPanel() {
closeAllPanels();
if (modalTravelersField) modalTravelersField.classList.add('active');
}
function closeModalTravelersPanel() {
if (modalTravelersField) modalTravelersField.classList.remove('active');
}
if (modalTravelersFieldContent) {
modalTravelersFieldContent.addEventListener('click', function(e) {
e.stopPropagation();
openModalTravelersPanel();
});
}
modalTravelersField.addEventListener('click', function(e) {
if (e.target.closest('.travelers-clear-btn')) return;
openModalTravelersPanel();
});
if (modalTravelersCloseBtn) {
modalTravelersCloseBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeModalTravelersPanel();
});
}
modalCounterButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const type = this.getAttribute('data-type');
const isIncrease = this.classList.contains('counter-increase');
if (type === 'adults') {
if (isIncrease) {
adultsCount++;
} else {
if (adultsCount > 0) adultsCount--;
}
if (modalAdultsCountInput) modalAdultsCountInput.value = adultsCount;
if (adultsCountInput) adultsCountInput.value = adultsCount;
} else if (type === 'children') {
if (isIncrease) {
childrenCount++;
} else {
if (childrenCount > 0) childrenCount--;
}
if (modalChildrenCountInput) modalChildrenCountInput.value = childrenCount;
if (childrenCountInput) childrenCountInput.value = childrenCount;
updateChildrenAgesBoth();
}
updateTravelersDisplay();
const decreaseBtn = this.parentElement.querySelector('.counter-decrease');
if (type === 'adults') {
decreaseBtn.disabled = adultsCount <= 1;
} else {
decreaseBtn.disabled = childrenCount <= 0;
}
});
});
if (modalTravelersDoneBtn) {
modalTravelersDoneBtn.addEventListener('click', function(e) {
e.stopPropagation();
updateTravelersDisplay();
closeModalTravelersPanel();
setTimeout(debounceFilter, 100);
});
}
if (modalTravelersClearBtn) {
modalTravelersClearBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
adultsCount = 0;
childrenCount = 0;
if (modalAdultsCountInput) modalAdultsCountInput.value = 0;
if (modalChildrenCountInput) modalChildrenCountInput.value = 0;
if (adultsCountInput) adultsCountInput.value = 0;
if (childrenCountInput) childrenCountInput.value = 0;
updateChildrenAgesBoth();
updateTravelersDisplay();
closeModalTravelersPanel();
});
}
}
// Re-initialize Calendar panel in modal
const modalDateField = mobileFiltersModalBody.querySelector('.date-field');
const modalCalendarPanel = mobileFiltersModalBody.querySelector('.calendar-panel');
const modalDateFieldContent = mobileFiltersModalBody.querySelector('.date-field-content');
const modalCalendarCloseBtn = mobileFiltersModalBody.querySelector('.calendar-close-btn');
const modalCalendarDays = mobileFiltersModalBody.querySelector('.calendar-days');
const modalCalendarMonthYear = mobileFiltersModalBody.querySelector('.calendar-month-year');
const modalPrevMonthBtn = mobileFiltersModalBody.querySelector('.prev-month');
const modalNextMonthBtn = mobileFiltersModalBody.querySelector('.next-month');
const modalDateClearBtn = mobileFiltersModalBody.querySelector('.date-clear-btn');
const modalDateDisplay = mobileFiltersModalBody.querySelector('.date-display');
// Function to render calendar in modal
function renderModalCalendar() {
if (!modalCalendarDays || !modalCalendarMonthYear) return;
// Ensure calendar variables are initialized
if (typeof currentYear === 'undefined' || typeof currentMonth === 'undefined') {
const now = new Date();
if (typeof currentYear === 'undefined') currentYear = now.getFullYear();
if (typeof currentMonth === 'undefined') currentMonth = now.getMonth();
}
const firstDay = new Date(currentYear, currentMonth, 1);
const lastDay = new Date(currentYear, currentMonth + 1, 0);
const daysInMonth = lastDay.getDate();
const startingDayOfWeek = (firstDay.getDay() + 6) % 7;
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
modalCalendarMonthYear.textContent = `${monthNames[currentMonth]} ${currentYear}`;
modalCalendarDays.innerHTML = '';
for (let i = 0; i < startingDayOfWeek; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'calendar-day empty';
modalCalendarDays.appendChild(emptyDay);
}
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let day = 1; day <= daysInMonth; day++) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day';
dayElement.textContent = day;
const cellDate = new Date(currentYear, currentMonth, day);
cellDate.setHours(0, 0, 0, 0);
if (cellDate < today) {
dayElement.classList.add('past');
} else {
dayElement.classList.add('available');
if (selectedDate && cellDate.getTime() === selectedDate.getTime()) {
dayElement.classList.add('selected');
}
dayElement.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
selectedDate = new Date(currentYear, currentMonth, day);
const formattedDate = formatDate(selectedDate);
if (modalDateDisplay) modalDateDisplay.textContent = formattedDate;
if (dateDisplay) dateDisplay.textContent = formattedDate;
updateDateClearButton();
closeModalCalendar();
setTimeout(debounceFilter, 100);
});
}
modalCalendarDays.appendChild(dayElement);
}
}
if (modalDateField && modalCalendarPanel) {
function openModalCalendar() {
closeAllPanels();
currentMonth = currentDate.getMonth();
currentYear = currentDate.getFullYear();
renderModalCalendar();
if (modalDateField) modalDateField.classList.add('active');
}
function closeModalCalendar() {
if (modalDateField) modalDateField.classList.remove('active');
}
if (modalDateFieldContent) {
modalDateFieldContent.addEventListener('click', function(e) {
e.stopPropagation();
openModalCalendar();
});
}
modalDateField.addEventListener('click', function(e) {
if (e.target.closest('.date-clear-btn')) return;
openModalCalendar();
});
if (modalCalendarCloseBtn) {
modalCalendarCloseBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeModalCalendar();
});
}
if (modalPrevMonthBtn) {
modalPrevMonthBtn.addEventListener('click', function(e) {
e.stopPropagation();
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderModalCalendar();
// Also update sidebar calendar
renderCalendar();
});
}
if (modalNextMonthBtn) {
modalNextMonthBtn.addEventListener('click', function(e) {
e.stopPropagation();
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderModalCalendar();
// Also update sidebar calendar
renderCalendar();
});
}
if (modalDateClearBtn) {
modalDateClearBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
selectedDate = null;
if (modalDateDisplay) modalDateDisplay.textContent = '';
if (dateDisplay) dateDisplay.textContent = '';
updateDateClearButton();
closeModalCalendar();
});
}
// Initial render - defer to ensure variables are initialized
setTimeout(function() {
if (typeof currentYear !== 'undefined' && typeof currentMonth !== 'undefined') {
renderModalCalendar();
}
}, 0);
}
}
// Calendar functionality - declare early so modal can use them
// (These need to be declared before syncFiltersToModal() calls initializeModalFilters())
let currentDate = new Date();
let selectedDate = null;
let currentMonth = currentDate.getMonth();
let currentYear = currentDate.getFullYear();
// Initial sync
syncFiltersToModal();
// Mobile description read more functionality
function initializeDescriptionReadMore() {
const descriptionContainer = document.getElementById('destination-description-container');
const descriptionText = document.getElementById('destination-description-text');
if (!descriptionContainer || !descriptionText) return;
const textElement = descriptionText;
const isMobile = window.innerWidth <= 991;
if (isMobile) {
// Remove any existing read more link first
const existingLink = descriptionContainer.querySelector('.read-more-link');
if (existingLink) {
existingLink.remove();
}
// Force apply truncation styles immediately
textElement.style.display = '-webkit-box';
textElement.style.webkitLineClamp = '1';
textElement.style.webkitBoxOrient = 'vertical';
textElement.style.overflow = 'hidden';
textElement.style.textOverflow = 'ellipsis';
textElement.classList.add('destination-description-truncated');
// Check if text actually needs truncation by comparing heights
const lineHeight = parseFloat(window.getComputedStyle(textElement).lineHeight);
const maxHeight = lineHeight * 1.2; // Slightly more than 1 line for safety
// Temporarily remove truncation to measure full height
textElement.style.display = 'block';
textElement.style.webkitLineClamp = 'unset';
textElement.style.overflow = 'visible';
const fullHeight = textElement.scrollHeight;
// Re-apply truncation
textElement.style.display = '-webkit-box';
textElement.style.webkitLineClamp = '1';
textElement.style.webkitBoxOrient = 'vertical';
textElement.style.overflow = 'hidden';
textElement.style.textOverflow = 'ellipsis';
textElement.classList.add('destination-description-truncated');
// Only show read more if text is actually truncated
if (fullHeight > maxHeight) {
// Create read more link
const readMoreLink = document.createElement('a');
readMoreLink.className = 'read-more-link';
readMoreLink.textContent = 'read more';
readMoreLink.href = '#';
readMoreLink.style.display = 'inline';
let isExpanded = false;
readMoreLink.addEventListener('click', function(e) {
e.preventDefault();
if (!isExpanded) {
// Show full
textElement.style.display = 'block';
textElement.style.webkitLineClamp = 'unset';
textElement.style.overflow = 'visible';
textElement.classList.remove('destination-description-truncated');
readMoreLink.textContent = 'read less';
isExpanded = true;
} else {
// Show truncated
textElement.style.display = '-webkit-box';
textElement.style.webkitLineClamp = '1';
textElement.style.webkitBoxOrient = 'vertical';
textElement.style.overflow = 'hidden';
textElement.style.textOverflow = 'ellipsis';
textElement.classList.add('destination-description-truncated');
readMoreLink.textContent = 'read more';
isExpanded = false;
}
});
descriptionContainer.appendChild(readMoreLink);
}
} else {
// Desktop - show full, remove truncation
textElement.style.display = 'block';
textElement.style.webkitLineClamp = 'unset';
textElement.style.overflow = 'visible';
textElement.classList.remove('destination-description-truncated');
const readMoreLink = descriptionContainer.querySelector('.read-more-link');
if (readMoreLink) {
readMoreLink.remove();
}
}
}
// Initialize on load - wait for content to be ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setTimeout(initializeDescriptionReadMore, 300);
});
} else {
setTimeout(initializeDescriptionReadMore, 300);
}
// Re-initialize on resize
let resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
initializeDescriptionReadMore();
}, 250);
});
// Open modal function
function openMobileFiltersModal() {
if (mobileFiltersModal) {
// Sync before opening
syncFiltersToModal();
mobileFiltersModal.classList.add('active');
document.body.style.overflow = 'hidden';
}
}
// Close modal function
function closeMobileFiltersModal() {
if (mobileFiltersModal) {
// Scroll modal body back to top before closing
const modalBody = document.getElementById('mobile-filters-modal-body');
if (modalBody) {
modalBody.scrollTop = 0;
}
mobileFiltersModal.classList.remove('active');
document.body.style.overflow = '';
}
}
// Open modal on button clicks
if (mobileFiltersBtn) {
mobileFiltersBtn.addEventListener('click', function() {
openMobileFiltersModal();
});
}
if (mobileWhereToBtn) {
mobileWhereToBtn.addEventListener('click', function() {
openMobileFiltersModal();
});
}
if (mobileTourLengthBtn) {
mobileTourLengthBtn.addEventListener('click', function() {
openMobileFiltersModal();
// Scroll to Tour Length section after modal opens
setTimeout(function() {
const modalBody = document.getElementById('mobile-filters-modal-body');
if (modalBody) {
const tourLengthSection = modalBody.querySelector('#tour-length-filter-section');
if (tourLengthSection) {
tourLengthSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
}, 350);
});
}
// Close modal
if (mobileFiltersModalClose) {
mobileFiltersModalClose.addEventListener('click', closeMobileFiltersModal);
}
if (mobileFiltersModal) {
mobileFiltersModal.addEventListener('click', function(e) {
if (e.target.classList.contains('mobile-filters-modal-overlay')) {
closeMobileFiltersModal();
}
});
}
// Apply filters
if (mobileFiltersApply) {
mobileFiltersApply.addEventListener('click', function() {
closeMobileFiltersModal();
if (typeof debounceFilter === 'function') {
debounceFilter();
}
});
}
// Clear filters
if (mobileFiltersClear) {
mobileFiltersClear.addEventListener('click', function() {
// Clear all filters in modal
const modalCheckboxes = mobileFiltersModalBody.querySelectorAll('input[type="checkbox"]');
modalCheckboxes.forEach(cb => cb.checked = false);
const modalInputs = mobileFiltersModalBody.querySelectorAll('input[type="text"], input[type="range"]');
modalInputs.forEach(input => {
if (input.type === 'text') {
input.value = '';
} else if (input.id === 'tour-length-min') {
input.value = '0.5';
} else if (input.id === 'tour-length-max') {
input.value = '21';
} else if (input.id === 'rates-min') {
input.value = '100';
} else if (input.id === 'rates-max') {
input.value = '10000';
}
});
// Also clear in original sidebar (for sync)
if (filtersContainer) {
const sidebarCheckboxes = filtersContainer.querySelectorAll('input[type="checkbox"]');
sidebarCheckboxes.forEach(cb => cb.checked = false);
}
// Update displayed values
if (typeof updateTourLengthSlider === 'function') {
updateTourLengthSlider();
}
if (typeof updateRatesSlider === 'function') {
updateRatesSlider();
}
});
}
// Get URL parameters
const urlParams = new URLSearchParams(window.location.search);
const destinationParam = urlParams.get('destination') || '';
const startDateParam = urlParams.get('start_date') || '';
const adultsParam = urlParams.get('adults') ? parseInt(urlParams.get('adults')) : 0;
const childrenParam = urlParams.get('children') ? parseInt(urlParams.get('children')) : 0;
const childrenAgesParam = urlParams.get('children_ages') ? urlParams.get('children_ages').split(',') : [];
// Calendar functionality (variables already declared above before syncFiltersToModal)
// Get destination dropdown element
const destinationSelect = document.getElementById('destination-select-tours');
const destinationClearBtn = document.querySelector('.destination-clear-btn');
// Pre-fill destination if URL parameter exists (and it's not empty)
if (destinationSelect && destinationParam && destinationParam.trim() !== '') {
destinationSelect.value = destinationParam;
if (destinationClearBtn) {
destinationClearBtn.style.display = '';
}
} else {
// Auto-select "All Safari Destinations" by default when no destination is specified
if (destinationSelect) {
const allSafariDestinations = 'All Safari Destinations';
destinationSelect.value = allSafariDestinations;
if (destinationClearBtn) {
destinationClearBtn.style.display = '';
}
// Trigger filter to apply the selection
setTimeout(function() {
if (typeof debounceFilter === 'function') {
debounceFilter();
}
}, 100);
}
}
// Calendar elements
const dateField = document.querySelector('.date-field');
const dateDisplay = document.querySelector('.date-display');
const calendarPanel = document.querySelector('.calendar-panel');
const calendarCloseBtn = document.querySelector('.calendar-close-btn');
const calendarDays = document.querySelector('.calendar-days');
const calendarMonthYear = document.querySelector('.calendar-month-year');
const prevMonthBtn = document.querySelector('.prev-month');
const nextMonthBtn = document.querySelector('.next-month');
const dateClearBtn = document.querySelector('.date-clear-btn');
const dateFieldContent = document.querySelector('.date-field-content');
// Travelers elements
const travelersField = document.querySelector('.travelers-field');
const travelersPanel = document.querySelector('.travelers-panel');
const travelersCloseBtn = document.querySelector('.travelers-close-btn');
const travelersDoneBtn = document.querySelector('.travelers-done-btn');
const travelersDisplay = document.querySelector('.travelers-display');
const travelersClearBtn = document.querySelector('.travelers-clear-btn');
const travelersFieldContent = document.querySelector('.travelers-field-content');
const adultsCountInput = document.getElementById('adults-count');
const childrenCountInput = document.getElementById('children-count');
const childrenAgesContainer = document.getElementById('children-ages');
const counterButtons = document.querySelectorAll('.counter-btn');
// Function to close all panels
function closeAllPanels() {
// Close calendar panel
if (dateField) {
dateField.classList.remove('active');
}
// Close travelers panel
if (travelersField) {
travelersField.classList.remove('active');
}
}
// Handle destination dropdown change
if (destinationSelect) {
destinationSelect.addEventListener('change', function() {
const selectedValue = this.value;
if (destinationClearBtn) {
if (selectedValue && selectedValue !== '') {
destinationClearBtn.style.display = '';
} else {
destinationClearBtn.style.display = 'none';
}
}
// Trigger filter update
if (typeof debounceFilter === 'function') {
debounceFilter();
}
});
}
// Handle destination clear button
if (destinationClearBtn && destinationSelect) {
destinationClearBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
destinationSelect.value = '';
this.style.display = 'none';
// Trigger filter update
if (typeof debounceFilter === 'function') {
debounceFilter();
}
});
}
// Calendar functionality (variables already declared above)
function formatDate(date) {
if (!date) return '';
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
return `${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
}
// Function to parse date from URL parameter
function parseDateFromURL(dateString) {
if (!dateString) return null;
// Decode URL encoding
dateString = decodeURIComponent(dateString);
// Try to parse various date formats
// Format: "Dec 5, 2025" or "December 5, 2025" or "12/5/2025"
let date = new Date(dateString);
// If parsing failed, try alternative formats
if (isNaN(date.getTime())) {
// Try replacing + with space
dateString = dateString.replace(/\+/g, ' ');
date = new Date(dateString);
}
if (isNaN(date.getTime())) {
return null;
}
return date;
}
// Pre-fill date from URL if provided (will be called after updateDateClearButton is defined)
function prefillDateFromURL() {
if (startDateParam && dateDisplay) {
const parsedDate = parseDateFromURL(startDateParam);
if (parsedDate && !isNaN(parsedDate.getTime())) {
selectedDate = parsedDate;
const formattedDate = formatDate(selectedDate);
dateDisplay.textContent = formattedDate;
// Set calendar to show the selected date's month
currentMonth = parsedDate.getMonth();
currentYear = parsedDate.getFullYear();
// Re-render calendar to show selected date
if (dateField && calendarPanel) {
renderCalendar();
}
}
}
}
function renderCalendar() {
const firstDay = new Date(currentYear, currentMonth, 1);
const lastDay = new Date(currentYear, currentMonth + 1, 0);
const daysInMonth = lastDay.getDate();
const startingDayOfWeek = (firstDay.getDay() + 6) % 7;
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
if (calendarMonthYear) calendarMonthYear.textContent = `${monthNames[currentMonth]} ${currentYear}`;
if (calendarDays) calendarDays.innerHTML = '';
if (calendarDays) {
for (let i = 0; i < startingDayOfWeek; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'calendar-day empty';
calendarDays.appendChild(emptyDay);
}
}
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let day = 1; day <= daysInMonth; day++) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day';
dayElement.textContent = day;
const cellDate = new Date(currentYear, currentMonth, day);
cellDate.setHours(0, 0, 0, 0);
if (cellDate < today) {
dayElement.classList.add('past');
} else {
dayElement.classList.add('available');
if (selectedDate && cellDate.getTime() === selectedDate.getTime()) {
dayElement.classList.add('selected');
}
dayElement.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
selectedDate = new Date(currentYear, currentMonth, day);
const formattedDate = formatDate(selectedDate);
if (dateDisplay) {
dateDisplay.textContent = formattedDate;
}
updateDateClearButton();
closeCalendar();
// Trigger filter after date selection
if (typeof debounceFilter === 'function') {
setTimeout(debounceFilter, 100);
}
});
}
if (calendarDays) {
calendarDays.appendChild(dayElement);
}
}
}
function openCalendar() {
closeAllPanels(); // Close other panels first
// Reset to current month when opening
currentMonth = currentDate.getMonth();
currentYear = currentDate.getFullYear();
renderCalendar();
if (dateField) dateField.classList.add('active');
}
function closeCalendar() {
if (dateField) dateField.classList.remove('active');
}
if (dateField && calendarPanel) {
// Use current month and year
currentMonth = currentDate.getMonth();
currentYear = currentDate.getFullYear();
renderCalendar();
if (dateFieldContent) {
dateFieldContent.addEventListener('click', function(e) {
e.stopPropagation();
openCalendar();
});
}
dateField.addEventListener('click', function(e) {
if (e.target.closest('.date-clear-btn')) return;
openCalendar();
});
if (calendarCloseBtn) {
calendarCloseBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeCalendar();
});
}
if (prevMonthBtn) {
prevMonthBtn.addEventListener('click', function(e) {
e.stopPropagation();
currentMonth--;
if (currentMonth < 0) {
currentMonth = 11;
currentYear--;
}
renderCalendar();
});
}
if (nextMonthBtn) {
nextMonthBtn.addEventListener('click', function(e) {
e.stopPropagation();
currentMonth++;
if (currentMonth > 11) {
currentMonth = 0;
currentYear++;
}
renderCalendar();
});
}
if (dateClearBtn) {
// Initially hide the clear button
dateClearBtn.style.display = 'none';
dateClearBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
selectedDate = null;
if (dateDisplay) dateDisplay.textContent = '';
updateDateClearButton();
closeCalendar();
});
}
// Pre-fill date from URL and check on page load if date is already set
prefillDateFromURL();
updateDateClearButton();
document.addEventListener('click', function(e) {
if (dateField && !dateField.contains(e.target)) {
closeCalendar();
}
});
}
// Travelers functionality - initialize from URL parameters
let adultsCount = adultsParam || 0;
let childrenCount = childrenParam || 0;
function updateTravelersDisplay() {
let displayText = '';
if (adultsCount > 0) {
displayText = `${adultsCount} Adult${adultsCount > 1 ? 's' : ''}`;
}
if (childrenCount > 0) {
if (displayText) displayText += ', ';
displayText += `${childrenCount} Child${childrenCount > 1 ? 'ren' : ''}`;
}
// Update sidebar travelers display
if (travelersDisplay) travelersDisplay.textContent = displayText;
// Update modal travelers display if it exists
const modalTravelersDisplay = mobileFiltersModalBody ? mobileFiltersModalBody.querySelector('.travelers-display') : null;
if (modalTravelersDisplay) {
modalTravelersDisplay.textContent = displayText;
}
// Show/hide travelers clear button in sidebar
if (travelersClearBtn) {
if (adultsCount > 0 || childrenCount > 0) {
travelersClearBtn.style.display = 'block';
} else {
travelersClearBtn.style.display = 'none';
}
}
// Show/hide travelers clear button in modal
const modalTravelersClearBtn = mobileFiltersModalBody ? mobileFiltersModalBody.querySelector('.travelers-clear-btn') : null;
if (modalTravelersClearBtn) {
if (adultsCount > 0 || childrenCount > 0) {
modalTravelersClearBtn.style.display = 'block';
} else {
modalTravelersClearBtn.style.display = 'none';
}
}
}
// Function to show/hide date clear button
function updateDateClearButton() {
if (dateClearBtn && dateDisplay) {
if (dateDisplay.textContent && dateDisplay.textContent.trim() !== '') {
dateClearBtn.style.display = 'block';
} else {
dateClearBtn.style.display = 'none';
}
}
}
const childAgesSelected = [];
function attachAgeSelectListeners(container) {
if (!container) return;
container.querySelectorAll('select.age-select').forEach((sel, idx) => {
sel.addEventListener('change', () => {
childAgesSelected[idx] = sel.value || '';
});
});
}
function updateChildrenAges() {
// Update sidebar
if (childrenAgesContainer) {
const previousValues = Array.from(childrenAgesContainer.querySelectorAll('select.age-select')).map(sel => sel.value || '');
childrenAgesContainer.innerHTML = '';
for (let i = 1; i <= childrenCount; i++) {
const ageGroup = document.createElement('div');
ageGroup.className = 'child-age-group';
const prevVal = previousValues[i - 1] || childAgesSelected[i - 1] || '';
ageGroup.innerHTML = `
<label class="child-label">Child ${i}:</label>
<select class="age-select" data-child="${i}">
<option value="">- Age -</option>
${Array.from({length: 18}, (_, j) => {
const val = j.toString();
const selected = prevVal === val ? 'selected' : '';
return `<option value="${val}" ${selected}>${j}</option>`;
}).join('')}
</select>
`;
childrenAgesContainer.appendChild(ageGroup);
}
attachAgeSelectListeners(childrenAgesContainer);
}
// Update modal if it exists
const modalChildrenAgesContainer = mobileFiltersModalBody ? mobileFiltersModalBody.querySelector('#children-ages') : null;
if (modalChildrenAgesContainer) {
const previousValues = Array.from(modalChildrenAgesContainer.querySelectorAll('select.age-select')).map(sel => sel.value || '');
modalChildrenAgesContainer.innerHTML = '';
for (let i = 1; i <= childrenCount; i++) {
const ageGroup = document.createElement('div');
ageGroup.className = 'child-age-group';
const prevVal = previousValues[i - 1] || childAgesSelected[i - 1] || '';
ageGroup.innerHTML = `
<label class="child-label">Child ${i}:</label>
<select class="age-select" data-child="${i}">
<option value="">- Age -</option>
${Array.from({length: 18}, (_, j) => {
const val = j.toString();
const selected = prevVal === val ? 'selected' : '';
return `<option value="${val}" ${selected}>${j}</option>`;
}).join('')}
</select>
`;
modalChildrenAgesContainer.appendChild(ageGroup);
}
attachAgeSelectListeners(modalChildrenAgesContainer);
}
}
function openTravelersPanel() {
closeAllPanels(); // Close other panels first
if (travelersField) travelersField.classList.add('active');
}
function closeTravelersPanel() {
if (travelersField) travelersField.classList.remove('active');
}
if (travelersField && travelersPanel) {
// Initially hide the clear button
if (travelersClearBtn) {
travelersClearBtn.style.display = 'none';
}
// Pre-fill adults and children counts from URL
if (adultsCountInput) {
adultsCountInput.value = adultsCount;
}
if (childrenCountInput) {
childrenCountInput.value = childrenCount;
}
updateTravelersDisplay();
updateChildrenAges();
// Pre-fill children ages from URL if provided
if (childrenAgesParam.length > 0 && childrenAgesContainer) {
setTimeout(function() {
const ageSelects = childrenAgesContainer.querySelectorAll('select.age-select');
ageSelects.forEach((select, index) => {
if (childrenAgesParam[index] !== undefined && childrenAgesParam[index] !== '') {
select.value = childrenAgesParam[index];
}
});
}, 100);
}
if (travelersFieldContent) {
travelersFieldContent.addEventListener('click', function(e) {
e.stopPropagation();
openTravelersPanel();
});
}
travelersField.addEventListener('click', function(e) {
if (e.target.closest('.travelers-clear-btn')) return;
openTravelersPanel();
});
if (travelersCloseBtn) {
travelersCloseBtn.addEventListener('click', function(e) {
e.stopPropagation();
closeTravelersPanel();
});
}
counterButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const type = this.getAttribute('data-type');
const isIncrease = this.classList.contains('counter-increase');
if (type === 'adults') {
if (isIncrease) {
adultsCount++;
} else {
if (adultsCount > 0) adultsCount--;
}
if (adultsCountInput) adultsCountInput.value = adultsCount;
} else if (type === 'children') {
if (isIncrease) {
childrenCount++;
} else {
if (childrenCount > 0) childrenCount--;
}
if (childrenCountInput) childrenCountInput.value = childrenCount;
updateChildrenAges();
}
// Update travelers display and clear button visibility
updateTravelersDisplay();
const decreaseBtn = this.parentElement.querySelector('.counter-decrease');
if (type === 'adults') {
decreaseBtn.disabled = adultsCount <= 1;
} else {
decreaseBtn.disabled = childrenCount <= 0;
}
});
});
document.querySelectorAll('.counter-decrease').forEach(btn => {
const type = btn.getAttribute('data-type');
if (type === 'adults') {
btn.disabled = adultsCount <= 1;
} else {
btn.disabled = childrenCount <= 0;
}
});
if (travelersDoneBtn) {
travelersDoneBtn.addEventListener('click', function(e) {
e.stopPropagation();
updateTravelersDisplay();
closeTravelersPanel();
// Trigger filter after updating display
setTimeout(debounceFilter, 100);
});
}
if (travelersClearBtn) {
travelersClearBtn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
adultsCount = 0;
childrenCount = 0;
if (adultsCountInput) adultsCountInput.value = 0;
if (childrenCountInput) childrenCountInput.value = 0;
updateChildrenAges();
updateTravelersDisplay();
closeTravelersPanel();
});
}
document.addEventListener('click', function(e) {
if (travelersField && !travelersField.contains(e.target)) {
closeTravelersPanel();
}
});
}
// Heart icon toggle functionality
const heartButtons = document.querySelectorAll('.btn-heart');
heartButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
e.preventDefault();
const icon = this.querySelector('i');
if (this.classList.contains('favorited')) {
this.classList.remove('favorited');
icon.classList.remove('fa-solid');
icon.classList.add('fa-regular');
} else {
this.classList.add('favorited');
icon.classList.remove('fa-regular');
icon.classList.add('fa-solid');
}
});
});
// Show more/less functionality for filter sections
function initializeFilterSections() {
const filterSections = document.querySelectorAll('.filter-section');
filterSections.forEach(section => {
const checkboxGroup = section.querySelector('.checkbox-group');
if (checkboxGroup) {
const allItems = checkboxGroup.querySelectorAll('.checkbox-item');
if (allItems.length > 5) {
// Hide items after the 5th
allItems.forEach((item, index) => {
if (index >= 5) {
item.classList.add('filter-item-hidden');
}
});
// Add show more link if it doesn't exist
if (!section.querySelector('.show-more-link')) {
const showMoreLink = document.createElement('a');
showMoreLink.href = '#';
showMoreLink.className = 'show-more-link';
showMoreLink.textContent = '+ Show more';
showMoreLink.setAttribute('data-action', 'show');
checkboxGroup.parentNode.insertBefore(showMoreLink, checkboxGroup.nextSibling);
}
}
}
});
}
// Initialize filter sections
initializeFilterSections();
// Show more/less functionality
document.addEventListener('click', function(e) {
if (e.target.classList.contains('show-more-link')) {
e.preventDefault();
const link = e.target;
const filterSection = link.closest('.filter-section');
const checkboxGroup = filterSection.querySelector('.checkbox-group');
const allItems = checkboxGroup.querySelectorAll('.checkbox-item');
const isShowingMore = link.textContent.includes('Show more');
if (isShowingMore) {
// Show all items
allItems.forEach(item => {
item.classList.remove('filter-item-hidden');
});
link.textContent = '- Show less';
link.setAttribute('data-action', 'hide');
} else {
// Hide items after 5th (show only first 5)
allItems.forEach((item, index) => {
if (index >= 5) {
item.classList.add('filter-item-hidden');
}
});
link.textContent = '+ Show more';
link.setAttribute('data-action', 'show');
}
}
});
// AJAX Tour Filtering - moved inside DOMContentLoaded
let filterTimeout;
let currentPage = 1;
function collectFilters() {
const filters = {
destination: destinationSelect ? destinationSelect.value.trim() : '',
start_date: dateDisplay ? dateDisplay.textContent.trim() : '',
adults: adultsCount || 0,
children: childrenCount || 0,
children_ages: '',
comfort_level: [],
tour_type: [],
safari_type: [],
specialized: [],
tour_features: [],
tour_length_min: document.getElementById('tour-length-min') ? parseFloat(document.getElementById('tour-length-min').value) : 0.5,
tour_length_max: document.getElementById('tour-length-max') ? parseFloat(document.getElementById('tour-length-max').value) : 21,
rates_min: document.getElementById('rates-min') ? parseInt(document.getElementById('rates-min').value) : 100,
rates_max: document.getElementById('rates-max') ? parseInt(document.getElementById('rates-max').value) : 10000,
paged: currentPage
};
// Get children ages
if (childrenAgesContainer) {
const ageSelects = childrenAgesContainer.querySelectorAll('select.age-select');
const ages = [];
ageSelects.forEach(select => {
if (select.value) {
ages.push(select.value);
}
});
filters.children_ages = ages.join(',');
}
// Get checked checkboxes
document.querySelectorAll('input[name="comfort_level[]"]:checked').forEach(cb => {
filters.comfort_level.push(parseInt(cb.value));
});
document.querySelectorAll('input[name="tour_type[]"]:checked').forEach(cb => {
filters.tour_type.push(parseInt(cb.value));
});
document.querySelectorAll('input[name="safari_type[]"]:checked').forEach(cb => {
filters.safari_type.push(parseInt(cb.value));
});
document.querySelectorAll('input[name="specialized[]"]:checked').forEach(cb => {
filters.specialized.push(parseInt(cb.value));
});
document.querySelectorAll('input[name="tour_features[]"]:checked').forEach(cb => {
filters.tour_features.push(parseInt(cb.value));
});
return filters;
}
// Range Slider Functions
function formatTourLength(value) {
if (value === 0.5) return '½ day';
if (value >= 21) return '21+ days';
return value + ' day' + (value > 1 ? 's' : '');
}
function formatPrice(value) {
if (value >= 10000) return '$10,000+';
return '$' + value.toLocaleString();
}
function updateTourLengthSlider() {
const minSlider = document.getElementById('tour-length-min');
const maxSlider = document.getElementById('tour-length-max');
const minValue = document.getElementById('tour-length-min-value');
const maxValue = document.getElementById('tour-length-max-value');
if (minSlider && maxSlider && minValue && maxValue) {
let min = parseFloat(minSlider.value);
let max = parseFloat(maxSlider.value);
if (min > max) {
min = max;
minSlider.value = min;
}
minValue.textContent = formatTourLength(min);
maxValue.textContent = formatTourLength(max);
}
}
function updateRatesSlider() {
const minSlider = document.getElementById('rates-min');
const maxSlider = document.getElementById('rates-max');
const minValue = document.getElementById('rates-min-value');
const maxValue = document.getElementById('rates-max-value');
if (minSlider && maxSlider && minValue && maxValue) {
let min = parseInt(minSlider.value);
let max = parseInt(maxSlider.value);
if (min > max) {
min = max;
minSlider.value = min;
}
minValue.textContent = formatPrice(min);
maxValue.textContent = formatPrice(max);
}
}
// Initialize range sliders
const tourLengthMin = document.getElementById('tour-length-min');
const tourLengthMax = document.getElementById('tour-length-max');
const ratesMin = document.getElementById('rates-min');
const ratesMax = document.getElementById('rates-max');
if (tourLengthMin && tourLengthMax) {
tourLengthMin.addEventListener('input', function() {
updateTourLengthSlider();
debounceFilter();
});
tourLengthMax.addEventListener('input', function() {
updateTourLengthSlider();
debounceFilter();
});
updateTourLengthSlider();
}
if (ratesMin && ratesMax) {
ratesMin.addEventListener('input', function() {
updateRatesSlider();
debounceFilter();
});
ratesMax.addEventListener('input', function() {
updateRatesSlider();
debounceFilter();
});
updateRatesSlider();
}
// Generate histogram (placeholder - you can enhance this with real data)
function generateHistogram(containerId, min, max, steps) {
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = '';
for (let i = 0; i < steps; i++) {
const bar = document.createElement('div');
bar.className = 'histogram-bar';
// Random height for demo - replace with actual data
const height = Math.random() * 100;
bar.style.height = height + '%';
container.appendChild(bar);
}
}
// Generate histograms on page load
setTimeout(function() {
generateHistogram('tour-length-histogram', 0.5, 21, 20);
generateHistogram('rates-histogram', 100, 10000, 20);
}, 100);
function filterTours() {
if (typeof customBookingAjax === 'undefined') {
console.error('customBookingAjax is not defined');
return;
}
const filters = collectFilters();
const safariListings = document.querySelector('.safari-listings .row');
const paginationContainer = document.querySelector('.pagination-container');
const resultsCount = document.querySelector('.results-count');
if (!safariListings) return;
// Show loading state
safariListings.style.opacity = '0.5';
safariListings.style.pointerEvents = 'none';
// Make AJAX request
const formData = new FormData();
formData.append('action', 'filter_tours');
formData.append('nonce', customBookingAjax.nonce);
Object.keys(filters).forEach(key => {
if (Array.isArray(filters[key])) {
filters[key].forEach(val => {
formData.append(key + '[]', val);
});
} else {
formData.append(key, filters[key]);
}
});
fetch(customBookingAjax.ajaxurl, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update tour listings
safariListings.innerHTML = data.data.html;
// Update pagination
if (paginationContainer) {
if (data.data.pagination) {
paginationContainer.innerHTML = data.data.pagination;
} else {
paginationContainer.innerHTML = '';
}
}
// Update results count
if (resultsCount) {
resultsCount.innerHTML = '<strong>' + data.data.showing_start + '</strong>-<strong>' + data.data.showing_end + '</strong> of <strong>' + data.data.total + '</strong> results';
}
// Update destination header and filters if provided
const contentHeader = document.querySelector('.content-header');
if (contentHeader && data.data.destination_header) {
const existingHeader = contentHeader.querySelector('.destination-header');
if (existingHeader) {
existingHeader.outerHTML = data.data.destination_header;
} else {
// Insert at the beginning of content-header
contentHeader.insertAdjacentHTML('afterbegin', data.data.destination_header);
}
}
if (contentHeader && data.data.selected_filters) {
const existingFilters = contentHeader.querySelector('.selected-filters-section');
if (existingFilters) {
existingFilters.outerHTML = data.data.selected_filters;
} else if (data.data.selected_filters.trim() !== '') {
const resultsCountEl = contentHeader.querySelector('.results-count');
if (resultsCountEl) {
resultsCountEl.insertAdjacentHTML('beforebegin', data.data.selected_filters);
} else {
// Insert before safari-listings if results-count doesn't exist
const safariListings = document.querySelector('.safari-listings');
if (safariListings) {
safariListings.insertAdjacentHTML('beforebegin', data.data.selected_filters);
}
}
}
} else if (contentHeader) {
// Remove filters section if no filters
const existingFilters = contentHeader.querySelector('.selected-filters-section');
if (existingFilters) {
existingFilters.remove();
}
}
// Re-initialize filter chip removal handlers
initializeFilterChips();
// Re-initialize heart buttons
const heartButtons = document.querySelectorAll('.btn-heart');
heartButtons.forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const icon = this.querySelector('i');
if (this.classList.contains('favorited')) {
this.classList.remove('favorited');
icon.classList.remove('fa-solid');
icon.classList.add('fa-regular');
} else {
this.classList.add('favorited');
icon.classList.remove('fa-regular');
icon.classList.add('fa-solid');
}
});
});
// Update URL without page reload
updateURL(filters);
}
// Remove loading state
safariListings.style.opacity = '1';
safariListings.style.pointerEvents = 'auto';
})
.catch(error => {
console.error('Error filtering tours:', error);
safariListings.style.opacity = '1';
safariListings.style.pointerEvents = 'auto';
});
}
function updateURL(filters) {
const params = new URLSearchParams();
if (filters.destination) params.append('destination', filters.destination);
if (filters.start_date) params.append('start_date', filters.start_date);
if (filters.adults > 0) params.append('adults', filters.adults);
if (filters.children > 0) params.append('children', filters.children);
if (filters.children_ages) params.append('children_ages', filters.children_ages);
// Add range filters
if (filters.tour_length_min !== undefined && filters.tour_length_min !== 0.5) {
params.append('tour_length_min', filters.tour_length_min);
}
if (filters.tour_length_max !== undefined && filters.tour_length_max !== 21) {
params.append('tour_length_max', filters.tour_length_max);
}
if (filters.rates_min !== undefined && filters.rates_min !== 100) {
params.append('rates_min', filters.rates_min);
}
if (filters.rates_max !== undefined && filters.rates_max !== 10000) {
params.append('rates_max', filters.rates_max);
}
// Add taxonomy filters
if (filters.comfort_level.length > 0) {
filters.comfort_level.forEach(id => params.append('comfort_level[]', id));
}
if (filters.tour_type.length > 0) {
filters.tour_type.forEach(id => params.append('tour_type[]', id));
}
if (filters.safari_type.length > 0) {
filters.safari_type.forEach(id => params.append('safari_type[]', id));
}
if (filters.specialized.length > 0) {
filters.specialized.forEach(id => params.append('specialized[]', id));
}
if (filters.tour_features.length > 0) {
filters.tour_features.forEach(id => params.append('tour_features[]', id));
}
// Update URL without page reload
const newURL = window.location.pathname + (params.toString() ? '?' + params.toString() : '');
window.history.pushState({}, '', newURL);
}
function debounceFilter() {
clearTimeout(filterTimeout);
filterTimeout = setTimeout(filterTours, 300);
}
// Listen for filter changes
if (destinationSelect) {
destinationSelect.addEventListener('change', debounceFilter);
}
// Listen for date changes
if (dateDisplay) {
const dateObserver = new MutationObserver(debounceFilter);
dateObserver.observe(dateDisplay, { childList: true, characterData: true, subtree: true });
}
// Listen for travelers changes
if (travelersDisplay) {
const travelersObserver = new MutationObserver(debounceFilter);
travelersObserver.observe(travelersDisplay, { childList: true, characterData: true, subtree: true });
}
// Listen for checkbox changes
document.querySelectorAll('input[type="checkbox"][name*="[]"]').forEach(checkbox => {
checkbox.addEventListener('change', debounceFilter);
});
// Listen for pagination clicks (WordPress paginate_links outputs .page-numbers, not .pagination)
document.addEventListener('click', function(e) {
var paginationLink = e.target.closest('.pagination-container a.page-numbers');
if (paginationLink && !paginationLink.classList.contains('current')) {
var href = paginationLink.getAttribute('href');
if (href && href !== '#') {
e.preventDefault();
var match = href.match(/paged=(\d+)/);
if (match) {
currentPage = parseInt(match[1], 10);
filterTours();
} else {
// Prev to page 1 or Next to last page - follow link as fallback
window.location.href = href;
}
}
}
});
// Function to initialize filter chip removal handlers
function initializeFilterChips() {
// Remove individual filter chips
document.querySelectorAll('.filter-chip:not(.clear-all-filters)').forEach(chip => {
chip.addEventListener('click', function(e) {
e.preventDefault();
const filterType = this.getAttribute('data-filter-type');
const filterValue = this.getAttribute('data-filter-value');
if (filterType === 'destination') {
// Clear destination select
if (destinationSelect) {
destinationSelect.value = '';
if (destinationClearBtn) {
destinationClearBtn.style.display = 'none';
}
}
} else if (filterType === 'tour_length') {
// Reset tour length sliders to default
const tourLengthMinSlider = document.getElementById('tour-length-min');
const tourLengthMaxSlider = document.getElementById('tour-length-max');
if (tourLengthMinSlider) {
tourLengthMinSlider.value = 0.5;
}
if (tourLengthMaxSlider) {
tourLengthMaxSlider.value = 21;
}
updateTourLengthSlider();
} else if (filterType === 'rates') {
// Reset rates sliders to default
const ratesMinSlider = document.getElementById('rates-min');
const ratesMaxSlider = document.getElementById('rates-max');
if (ratesMinSlider) {
ratesMinSlider.value = 100;
}
if (ratesMaxSlider) {
ratesMaxSlider.value = 10000;
}
updateRatesSlider();
} else {
// Uncheck corresponding checkbox
const checkbox = document.querySelector(`input[name="${filterType}[]"][value="${filterValue}"]`);
if (checkbox) {
checkbox.checked = false;
}
}
// Trigger filter
debounceFilter();
});
});
// Clear all filters
document.querySelectorAll('.clear-all-filters').forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
// Clear destination
if (destinationSelect) {
destinationSelect.value = '';
if (destinationClearBtn) {
destinationClearBtn.style.display = 'none';
}
}
// Clear all checkboxes
document.querySelectorAll('input[type="checkbox"][name*="[]"]').forEach(cb => {
cb.checked = false;
});
// Clear date
if (dateDisplay) {
dateDisplay.textContent = '';
}
selectedDate = null;
// Clear travelers
adultsCount = 0;
childrenCount = 0;
if (adultsCountInput) adultsCountInput.value = 0;
if (childrenCountInput) childrenCountInput.value = 0;
updateTravelersDisplay();
updateChildrenAges();
// Reset tour length sliders
const tourLengthMinSlider = document.getElementById('tour-length-min');
const tourLengthMaxSlider = document.getElementById('tour-length-max');
if (tourLengthMinSlider) {
tourLengthMinSlider.value = 0.5;
}
if (tourLengthMaxSlider) {
tourLengthMaxSlider.value = 21;
}
updateTourLengthSlider();
// Reset rates sliders
const ratesMinSlider = document.getElementById('rates-min');
const ratesMaxSlider = document.getElementById('rates-max');
if (ratesMinSlider) {
ratesMinSlider.value = 100;
}
if (ratesMaxSlider) {
ratesMaxSlider.value = 10000;
}
updateRatesSlider();
// Trigger filter
debounceFilter();
});
});
}
// Initialize filter chips on page load
initializeFilterChips();
});
// Debug: Log tour URLs to console
(function() {
console.log('%c=== Tour URL Debug ===', 'color: #1eb054; font-weight: bold; font-size: 14px;');
var tourLinks = document.querySelectorAll('.safari-card-link[data-tour-id]');
console.log('Found ' + tourLinks.length + ' tour links');
var fixedCount = 0;
var notFixedCount = 0;
tourLinks.forEach(function(link, index) {
var tourId = link.getAttribute('data-tour-id');
var tourUrl = link.getAttribute('data-tour-url');
var debugData = link.getAttribute('data-debug');
try {
var debug = JSON.parse(debugData);
var urlParts = tourUrl.split('/').filter(function(part) { return part; });
var hasRegionInUrl = urlParts.length >= 3 && urlParts[urlParts.length - 3] !== 'tour-detail';
var isFixed = hasRegionInUrl || (!debug.has_region && !debug.has_park);
if (isFixed) fixedCount++;
else notFixedCount++;
var statusColor = isFixed ? '#1eb054' : '#ff6b6b';
var statusText = isFixed ? '✓ FIXED' : '✗ NOT FIXED';
console.log('%cTour #' + (index + 1) + ' - ' + statusText, 'color: ' + statusColor + '; font-weight: bold;');
console.log(' ID: ' + tourId);
console.log(' URL: ' + tourUrl);
console.log(' Has Region: ' + (debug.has_region ? 'YES (' + (debug.region_slug || 'N/A') + ')' : 'NO'));
console.log(' Has Park: ' + (debug.has_park ? 'YES (' + (debug.park_slug || 'N/A') + ')' : 'NO'));
console.log(' URL includes region/park: ' + (hasRegionInUrl ? 'YES' : 'NO'));
console.log(' URL structure: ' + urlParts.slice(-4).join(' / '));
if (!isFixed && debug.has_region) {
console.warn(' ⚠️ Tour has region but URL doesn\'t include it!');
}
} catch(e) {
console.error('Tour #' + (index + 1) + ' (ID: ' + tourId + ') - Error parsing debug data:', e);
console.log(' URL: ' + tourUrl);
}
});
console.log('%c=== Summary ===', 'color: #1eb054; font-weight: bold;');
console.log('Fixed: ' + fixedCount + ' | Not Fixed: ' + notFixedCount);
console.log('%c=== End Tour URL Debug ===', 'color: #1eb054; font-weight: bold; font-size: 14px;');
})();
// Mobile menu toggle
document.addEventListener('DOMContentLoaded', function() {
const navbarToggle = document.getElementById('navbarToggle');
const navbarMenu = document.querySelector('.navbar-menu');
if (navbarToggle && navbarMenu) {
navbarToggle.addEventListener('click', function() {
navbarMenu.classList.toggle('active');
const icon = this.querySelector('i');
if (navbarMenu.classList.contains('active')) {
icon.classList.remove('fa-bars');
icon.classList.add('fa-times');
} else {
icon.classList.remove('fa-times');
icon.classList.add('fa-bars');
}
});
}
});
// Force Font Awesome 6 on icons (fix for Elementor Font Awesome 4 conflict)
jQuery(document).ready(function($) {
function forceFontAwesome6() {
$('[class*="fa-"], [class^="fa-"]').each(function() {
var $icon = $(this);
var classes = $icon.attr('class') || '';
var hasFA6 = classes.indexOf('fa-solid') !== -1 || classes.indexOf('fa-regular') !== -1 || classes.indexOf('fa-brands') !== -1;
if (hasFA6) {
var existingStyle = $icon.attr('style') || '';
if (existingStyle.indexOf('font-family') === -1) {
$icon.attr('style', existingStyle + '; font-family: "Font Awesome 6 Free" !important; font-weight: 900 !important; font-style: normal !important;');
}
}
});
}
// Apply immediately and after delay
forceFontAwesome6();
setTimeout(forceFontAwesome6, 500);
setTimeout(forceFontAwesome6, 1500);
});
const lazyloadRunObserver = () => {
const lazyloadBackgrounds = document.querySelectorAll( `.e-con.e-parent:not(.e-lazyloaded)` );
const lazyloadBackgroundObserver = new IntersectionObserver( ( entries ) => {
entries.forEach( ( entry ) => {
if ( entry.isIntersecting ) {
let lazyloadBackground = entry.target;
if( lazyloadBackground ) {
lazyloadBackground.classList.add( 'e-lazyloaded' );
}
lazyloadBackgroundObserver.unobserve( entry.target );
}
});
}, { rootMargin: '200px 0px 200px 0px' } );
lazyloadBackgrounds.forEach( ( lazyloadBackground ) => {
lazyloadBackgroundObserver.observe( lazyloadBackground );
} );
};
const events = [
'DOMContentLoaded',
'elementor/lazyload/observe',
];
events.forEach( ( event ) => {
document.addEventListener( event, lazyloadRunObserver );
} );
https://ottosafariguide.com/wp-content/plugins/business-reviews-wp/assets/js/app.min.js
https://ottosafariguide.com/wp-content/plugins/elementskit-lite/widgets/init/assets/js/widget-scripts.js
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/assets/lib/owl-carousel-2.3.4/owl.carousel.js
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/assets/lib/dropzone/dropzone.min.js
https://ottosafariguide.com/wp-content/plugins/wte-elementor-widgets/dist/js/wte-offcanvas.js
https://ottosafariguide.com/wp-content/plugins/wte-elementor-widgets/dist/js/wpte-animation.js
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/assets/lib/flatpickr-4.6.9/fpickr.js
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/assets/lib/flatpickr-4.6.9/l10n/en.js
https://ottosafariguide.com/wp-includes/js/dist/hooks.min.js
https://ottosafariguide.com/wp-includes/js/dist/i18n.min.js
wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ 'ltr' ] } );
//# sourceURL=wp-i18n-js-after
;(function(){
var wte = window[wte] || {};
if(! window.wte){
window.wte = {"personFormat":"\/person","bookNow":"Book Now","totaltxt":"Total:","currency":{"code":"USD","symbol":"$"},"payments":[],"single_showtabs":false,"pax_labels":[],"booking_cutoff":{"enable":false,"cutoff":0,"unit":"days"}};
}
})();
;(function(){
var wte_account_page = window[wte_account_page] || {};
if(! window.wte_account_page){
window.wte_account_page = {"ajax_url":"https:\/\/ottosafariguide.com\/wp-admin\/admin-ajax.php","change_user_profile_msg":"Click here or Drop new image to update your profile picture"};
}
})();
;(function(){
var rtl = window[rtl] || {};
if(! window.rtl){
window.rtl = [];
}
})();
;(function(){
var wtePreFetch = window[wtePreFetch] || {};
if(! window.wtePreFetch){
window.wtePreFetch = {"tripID":12956,"wpapi":{"root":"https:\/\/ottosafariguide.com\/wp-json\/","nonce":"e0cd1d7640","versionString":"wp\/v2\/"},"iframe_url":""};
}
})();
;(function(){
var WTEAjaxData = window[WTEAjaxData] || {};
if(! window.WTEAjaxData){
window.WTEAjaxData = {"ajaxurl":"https:\/\/ottosafariguide.com\/wp-admin\/admin-ajax.php","nonce":"e0cd1d7640"};
}
})();
;(function(){
var wteL10n = window[wteL10n] || {};
if(! window.wteL10n){
window.wteL10n = {"version":"6.8.1","baseCurrency":"USD","baseCurrencySymbol":"$","currency":"USD","currencySymbol":"$","home_url":"https:\/\/ottosafariguide.com","_nonces":{"addtocart":"b65ebdd8aa","downloadSystemInfo":"78d11735b9"},"wpapi":{"root":"https:\/\/ottosafariguide.com\/wp-json\/","nonce":"e0cd1d7640","versionString":"wp\/v2\/"},"wpxhr":{"root":"https:\/\/ottosafariguide.com\/wp-admin\/admin-ajax.php","nonce":"b16f20b735"},"format":{"number":{"decimal":"default","decimalSeparator":".","thousandSeparator":","},"price":"%CURRENCY_SYMBOL%%FORMATED_AMOUNT%","date":"F j, Y","time":"g:i a","datetime":{"date":"F j, Y","time":"g:i a","GMTOffset":"+00:00","timezone":""},"enableRound":false},"extensions":[],"locale":"en_US","l10n":{"invalidCartTraveler":"No. of Travellers' should be at least %s","availableSeatsExceed":"The number of pax can not exceed more than %s","invalidCartExtraReq":"%s selection is essential. Please specify a number.","invalidCartExtra":"Extra Services marked with * is essential. Please specify a number.","extraServicesTitle":"","checkout.submitButtonText":"Book Now","checkout.bookingSummary":"Booking Summary","checkout.totalPayable":"Total Payable Now"},"layout":{"showFeaturedTripsOnTop":true,"showoptionfilter":true},"rtl":false};
}
})();
//# sourceURL=wp-travel-engine-js-before
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/dist/public/wte-public.js
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/dist/public/trip-search/widgets-dropdown.js
https://ottosafariguide.com/wp-content/plugins/wp-travel-engine/dist/public/trip-search/widgets-slider.js
https://ottosafariguide.com/wp-content/plugins/contact-form-7/includes/swv/js/index.js
var wpcf7 = {
"api": {
"root": "https:\/\/ottosafariguide.com\/wp-json\/",
"namespace": "contact-form-7\/v1"
},
"cached": 1
};
//# sourceURL=contact-form-7-js-before
https://ottosafariguide.com/wp-content/plugins/contact-form-7/includes/js/index.js
https://ottosafariguide.com/wp-content/plugins/travel-booking-toolkit/public/js/travel-booking-toolkit-public.min.js
https://ottosafariguide.com/wp-content/plugins/travel-booking-toolkit/public/js/fontawesome/all.min.js
https://ottosafariguide.com/wp-content/plugins/travel-booking-toolkit/public/js/fontawesome/v4-shims.min.js
window.nfdPerformance = window.nfdPerformance || {};
window.nfdPerformance.imageOptimization = window.nfdPerformance.imageOptimization || {};
window.nfdPerformance.imageOptimization.lazyLoading = {"classes":["nfd-performance-not-lazy","a3-notlazy","disable-lazyload","no-lazy","no-lazyload","skip-lazy"],"attributes":["data-lazy-src","data-crazy-lazy=\"exclude\"","data-no-lazy","data-no-lazy=\"1\""]};
//# sourceURL=nfd-performance-lazy-loader-js-before
https://ottosafariguide.com/wp-content/plugins/mojo-marketplace-wp-plugin//vendor/newfold-labs/wp-module-performance/build/image-lazy-loader.min.js
var travel_monster_custom = {"url":"https://ottosafariguide.com/wp-admin/admin-ajax.php","rtl":"","sticky_widget":"1","ed_sticky_header":""};
//# sourceURL=travel-monster-custom-js-extra
https://ottosafariguide.com/wp-content/themes/travel-monster/js/custom.min.js
https://ottosafariguide.com/wp-content/plugins/elementor/assets/js/webpack.runtime.min.js
https://ottosafariguide.com/wp-content/plugins/elementor/assets/js/frontend-modules.min.js
https://ottosafariguide.com/wp-includes/js/jquery/ui/core.min.js
var elementorFrontendConfig = {"environmentMode":{"edit":false,"wpPreview":false,"isScriptDebug":false},"i18n":{"shareOnFacebook":"Share on Facebook","shareOnTwitter":"Share on Twitter","pinIt":"Pin it","download":"Download","downloadImage":"Download image","fullscreen":"Fullscreen","zoom":"Zoom","share":"Share","playVideo":"Play Video","previous":"Previous","next":"Next","close":"Close","a11yCarouselPrevSlideMessage":"Previous slide","a11yCarouselNextSlideMessage":"Next slide","a11yCarouselFirstSlideMessage":"This is the first slide","a11yCarouselLastSlideMessage":"This is the last slide","a11yCarouselPaginationBulletMessage":"Go to slide"},"is_rtl":false,"breakpoints":{"xs":0,"sm":480,"md":768,"lg":1025,"xl":1440,"xxl":1600},"responsive":{"breakpoints":{"mobile":{"label":"Mobile Portrait","value":767,"default_value":767,"direction":"max","is_enabled":true},"mobile_extra":{"label":"Mobile Landscape","value":880,"default_value":880,"direction":"max","is_enabled":false},"tablet":{"label":"Tablet Portrait","value":1024,"default_value":1024,"direction":"max","is_enabled":true},"tablet_extra":{"label":"Tablet Landscape","value":1200,"default_value":1200,"direction":"max","is_enabled":false},"laptop":{"label":"Laptop","value":1366,"default_value":1366,"direction":"max","is_enabled":true},"widescreen":{"label":"Widescreen","value":2400,"default_value":2400,"direction":"min","is_enabled":false}},"hasCustomBreakpoints":true},"version":"4.1.3","is_static":false,"experimentalFeatures":{"e_font_icon_svg":true,"additional_custom_breakpoints":true,"container":true,"landing-pages":true,"nested-elements":true,"global_classes_should_enforce_capabilities":true,"e_variables":true,"e_opt_in_v4_page":true,"e_components":true,"e_interactions":true,"e_widget_creation":true,"import-export-customization":true},"urls":{"assets":"https:\/\/ottosafariguide.com\/wp-content\/plugins\/elementor\/assets\/","ajaxurl":"https:\/\/ottosafariguide.com\/wp-admin\/admin-ajax.php","uploadUrl":"https:\/\/ottosafariguide.com\/wp-content\/uploads"},"nonces":{"floatingButtonsClickTracking":"ed52241227","atomicFormsSendForm":"7f285bf722"},"swiperClass":"swiper","settings":{"page":[],"editorPreferences":[]},"kit":{"active_breakpoints":["viewport_mobile","viewport_tablet","viewport_laptop"],"lightbox_enable_counter":"yes","lightbox_enable_fullscreen":"yes","lightbox_enable_zoom":"yes","lightbox_enable_share":"yes","lightbox_title_src":"title","lightbox_description_src":"description"},"post":{"id":12956,"title":"All%20Safari%20Destinations%20-%2055%20Tours%20%7C%20Otto%20Safari%20Guide%20Tanzania","excerpt":"","featuredImage":false}};
//# sourceURL=elementor-frontend-js-before
https://ottosafariguide.com/wp-content/plugins/elementor/assets/js/frontend.min.js
