Our offices will be closed for the holiday season from December 25, 2025, to January 11, 2026. For urgent matters, please contact support@pecb.com.

Our offices will be closed for the holiday season from December 25, 2025, to January 11, 2026. For urgent matters, please contact support@pecb.com.

Our offices will be closed for the holiday season from December 25, 2025, to January 11, 2026. For urgent matters, please contact support@pecb.com.

jQuery(document).ready(function ($) { let page = 1; let searchTimeout = null; const $container = $('#pecb-events-container'); const $loadMore = $('#pecb-events-load-more'); const $filterForm = $('#pecb-events-filter'); const $searchInput = $('#pecb-events-search'); // Initialize delivery format from URL parameter or default to Classroom const urlParams = new URLSearchParams(window.location.search); let currentDeliveryFormat = urlParams.get('deliveryFormat') || 'Classroom'; let currentScheme = urlParams.get('scheme') || ''; let partnersData = {}; // Store partner data: { id: name } let coursesData = {}; // Cache course data let eventsCache = {}; // Cache events by filter key let lastFilters = null; // Track filter changes // Dynamic sticky filter position based on scroll direction let lastScrollTop = 0; const $filter = $('.events-api-container .filter'); $(window).on('scroll', function() { const scrollTop = $(this).scrollTop(); // Prevent negative scrollTop values if (scrollTop <= 0) { lastScrollTop = scrollTop; return; } if (scrollTop > lastScrollTop) { // Scrolling down $filter.css('top', '20px'); } else if (scrollTop < lastScrollTop) { // Scrolling up $filter.css('top', '130px'); } lastScrollTop = scrollTop; }); // Filter toggle for mobile (max-width 981px) const $filterToggle = $('.events-api-container .filter-toggle'); const $filterContent = $('.events-api-container .filter'); $filterToggle.on('click', function() { $(this).toggleClass('open'); $filterContent.toggleClass('open'); }); // SKELETON (CLEAN + MODERN) - Define early const showSkeleton = (count = 6) => Array(count).fill(`
`).join(''); // Helper function to normalize website URLs const normalizeWebsiteUrl = (url) => { if (!url) return ''; url = url.trim(); if (url.startsWith('http://') || url.startsWith('https://')) { return url; } return 'https://' + url; }; // Country list for contact form const countryOptions = ` `; // Equalize heights of event card sections const equalizeCardHeights = () => { // Reset heights first $('.pecb-event-card .title').css('height', 'auto'); $('.pecb-event-card .other > div:first-of-type').css('height', 'auto'); // Find max height for title sections let maxTitleHeight = 0; $('.pecb-event-card .title').each(function() { const h = $(this).outerHeight(); if (h > maxTitleHeight) maxTitleHeight = h; }); // Find max height for other first div sections let maxOtherHeight = 0; $('.pecb-event-card .other > div:first-of-type').each(function() { const h = $(this).outerHeight(); if (h > maxOtherHeight) maxOtherHeight = h; }); // Apply max heights if (maxTitleHeight > 0) { $('.pecb-event-card .title').css('height', maxTitleHeight + 'px'); } if (maxOtherHeight > 0) { $('.pecb-event-card .other > div:first-of-type').css('height', maxOtherHeight + 'px'); } }; // Check if PHP rendered any cards const hasPhpCards = $container.find('.pecb-event-card').length > 0; // If no PHP cards, show skeleton while loading if (!hasPhpCards) { $container.html(showSkeleton(6)); } // Initialize flatpickr for single range input if present const $eventDate = $('#event-date'); let eventDatePicker = null; if ($eventDate.length && typeof flatpickr !== 'undefined') { eventDatePicker = $eventDate.flatpickr({ mode: 'range', dateFormat: 'Y-m-d', allowInput: true }); } // Initialize Select2 across form selects const initSelect2 = () => { if (typeof $.fn.select2 === 'undefined') { return; } $filterForm.find('.pecb-select').each(function () { const $select = $(this); $select.select2({ width: '100%', placeholder: $select.data('placeholder') || '', allowClear: true, }); // Apply URL parameter value immediately after Select2 init const urlValue = $select.data('url-value'); if (urlValue) { $select.val(urlValue).trigger('change.select2'); } }); }; initSelect2(); // Refresh Select2 display helper const refreshSelect2 = ($element) => { if ($element && $element.length && $element.hasClass('pecb-select') && typeof $.fn.select2 !== 'undefined') { $element.trigger('change.select2'); } }; // UPDATE URL with current filters and modal ID function updateURL(modalId = null) { const params = new URLSearchParams(); // Get all form fields including partnerId $filterForm.serializeArray().forEach(item => { if (item.value) { params.set(item.name, item.value); } }); // Add date range const val = $eventDate.val(); if (val) { if (val.includes(' to ')) { const [start, end] = val.split(' to ').map(s => s.trim()); if (start && end) { params.set('startDate', start); params.set('endDate', end); } } else { params.set('startDate', val); params.set('endDate', val); } } // Add delivery format if selected if (currentDeliveryFormat) { params.set('deliveryFormat', currentDeliveryFormat); } // Add search/scheme if entered if (currentScheme) { params.set('scheme', currentScheme); } // Add modal ID if provided if (modalId) { params.set('modal', modalId); } // Update URL without page reload const newUrl = params.toString() ? `${window.location.pathname}?${params.toString()}` : window.location.pathname; window.history.pushState({}, '', newUrl); } // OPEN MODAL by ID (for direct links) function openModalById(eventId) { // Try to get event data from DOM first const $card = $(`.pecb-event-card[data-event-id="${eventId}"]`); const cardData = $card.length ? $card.data('event') : null; $modal.addClass('active'); // If we have data from DOM, show overview immediately then load full details if (cardData) { renderModalContent(cardData, true); // true = partial data (no description/partner details) $modalContent.show(); $modalLoading.hide(); // Fetch full event details in background for description and partner info loadEventDetails(eventId); } else { // Fetch full event data if not in DOM (e.g., direct URL access) $modalContent.hide(); $modalLoading.show(); $.post(pecbEvents.ajax_url, { action: 'pecb_get_event', nonce: pecbEvents.nonce, event_id: eventId }, function (resp) { if (resp && resp.success && resp.data) { renderModalContent(resp.data, false); $modalLoading.hide(); $modalContent.show(); } else { $modalLoading.html('

Event not found.

'); } }).fail(function () { $modalLoading.html('

Failed to load event with ID: ' + eventId + '

'); }); } } // Load event details (description and partner info) and update modal function loadEventDetails(eventId) { $.post(pecbEvents.ajax_url, { action: 'pecb_get_event', nonce: pecbEvents.nonce, event_id: eventId }, function (resp) { if (resp && resp.success && resp.data) { updateModalWithFullDetails(resp.data); } }); } // Update modal with description and partner details from API function updateModalWithFullDetails(eventData) { const event = eventData || {}; const partnerDetails = event.partnerDetails || {}; const locationDetails = event.eventLocationDetails || {}; // Update contact button with partner email $('.contact-btn').data('partner-email', partnerDetails.email || ''); $('.contact-btn').data('partner-name', partnerDetails.companyName || event.partnerCompanyName || ''); // Format description helper const formatDescription = (text) => { if (!text || typeof text !== 'string') return ''; let result = text; const anchors = new Map(); let id = 0; result = result.replace(/]*>[\s\S]*?<\/a>/gi, (match) => { const placeholder = `__EXISTING_LINK_${id++}__`; anchors.set(placeholder, match); return placeholder; }); result = result.replace( /(^|\s)(https?:\/\/[^\s<>"']+)|(^|\s)(www\.[^\s<>"']+\.[^\s<>"']+)/gi, (match, space1, httpUrl, space2, wwwUrl) => { const url = httpUrl || wwwUrl; const prefix = space1 || space2 || ''; let cleanUrl = url.replace(/[)"'\u00bb"]*$/, ''); let trailing = url.slice(cleanUrl.length); const finalUrl = cleanUrl.startsWith('www.') ? 'https://' + cleanUrl : cleanUrl; return `${prefix}${cleanUrl}${trailing}`; } ); result = result.replace(/\r\n|\r|\n/g, '
'); for (const [placeholder, original] of anchors) { result = result.split(placeholder).join(original); } return result; }; // Update event description section const $descriptionSection = $('.event-description'); if (event.eventDescription) { if ($descriptionSection.length) { $descriptionSection.find('.description-content').html(formatDescription(event.eventDescription)); } else { // Insert description section before partner information const descHtml = `

Event Description

${formatDescription(event.eventDescription)}
`; $('.partner-information').before(descHtml); } } else { // Remove loading text if no description $descriptionSection.find('.loading-text').text('No description available.'); } // Update partner information section const $partnerSection = $('.partner-information .partner-details'); let partnerHtml = `
Partner ${partnerDetails.companyName || event.partnerCompanyName || 'N/A'}
`; if (partnerDetails.address) { partnerHtml += `
Address ${partnerDetails.address}
`; } if (partnerDetails.postalZipCode || partnerDetails.postalCode) { partnerHtml += `
Zip Code ${partnerDetails.postalZipCode || partnerDetails.postalCode}
`; } if (partnerDetails.city) { partnerHtml += `
City ${partnerDetails.city}
`; } if (locationDetails.country || partnerDetails.country) { partnerHtml += `
Event Country ${locationDetails.country || partnerDetails.country}
`; } if (partnerDetails.website) { partnerHtml += ` `; } $partnerSection.html(partnerHtml); } // GET FILTERS - combines form filters, delivery format, and search function getFilters() { const filters = {}; // Get all form filters $filterForm.serializeArray().forEach(item => { if (!item.value || item.name === 'eventDate') return; if (item.name === 'partnerId') { // Send partnerId directly - backend will convert to partner name filters.partnerId = parseInt(item.value); } else if (item.name === 'course' || item.name === 'trainingEventStatusId') { filters[item.name] = parseInt(item.value); } else { filters[item.name] = item.value; } }); // Read range from flatpickr value (or raw input fallback) const val = $eventDate.val(); if (val) { if (val.includes(' to ')) { const [start, end] = val.split(' to ').map(s => s.trim()); if (start && end) { filters.startDate = start; filters.endDate = end; } } else { filters.startDate = filters.endDate = val; // Single date } } // Add delivery format and search if present if (currentDeliveryFormat) filters.deliveryFormat = currentDeliveryFormat; if (currentScheme) filters.scheme = currentScheme; return filters; } function formatDate(dateStr) { if (!dateStr) return ''; // Parse as UTC by appending 'Z' or using UTC format const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr.replace(' ', 'T') + 'Z'; const dateObj = new Date(utcDateStr); const day = dateObj.toLocaleDateString('en-US', { weekday: 'short', timeZone: 'UTC' }); const number = dateObj.toLocaleDateString('en-US', { day: '2-digit', timeZone: 'UTC' }); return `${day}${number}`; } // Format date string to UTC date and time (YYYY-MM-DD HH:mm UTC) function formatDateUTC(dateStr) { if (!dateStr) return 'N/A'; // Parse as UTC by appending 'Z' or using UTC format const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr.replace(' ', 'T') + 'Z'; const dateObj = new Date(utcDateStr); const date = dateObj.toLocaleDateString('en-CA', { timeZone: 'UTC' }); // YYYY-MM-DD format const time = dateObj.toLocaleTimeString('en-GB', { timeZone: 'UTC', hour: '2-digit', minute: '2-digit', hour12: false }); return `${date} ${time} UTC`; } // Render event card HTML const renderEventCard = (ev) => { // Escape JSON for HTML attribute const eventJson = JSON.stringify(ev).replace(/'/g, ''').replace(/"/g, '"'); return `
${formatDate(ev.startDate)}

${ev.courseTitle?.toLowerCase() || 'N/A'}


Partner: ${ev.partnerCompanyName || 'N/A'}

${ ( ev.eventLocation) ? `

Location: ${ev.eventLocation}

` : '' }

Event Status ${ev?.eventStatusName === 'ActiveConfirmed' ? 'Active/Confirmed' : ev?.eventStatusName || 'N/A'}

Event Dates ${formatDateUTC(ev.startDate)} - ${formatDateUTC(ev.endDate)}

Enroll Now

Delivery Format${ev.eventType || 'N/A'}

Course Language${ev.courseLanguage || 'N/A'}

Language of Instruction${ev.languageOfInstruction || 'N/A'}

Partner Level${ev.partnerLevelName || 'N/A'}

`; }; // Helper function to compare filter objects function filtersEqual(filters1, filters2) { if (!filters1 || !filters2) return false; const keys1 = Object.keys(filters1).sort(); const keys2 = Object.keys(filters2).sort(); if (keys1.length !== keys2.length) return false; return keys1.every(key => filters1[key] === filters2[key]); } // Generate cache key for filters function getCacheKey(filters, page) { return JSON.stringify({ ...filters, page }); } // LOAD EVENTS - Optimized with caching function loadEvents(pageNum = 1, replace = true) { page = pageNum; const filters = getFilters(); const cacheKey = getCacheKey(filters, page); // Check cache first for exact match if (eventsCache[cacheKey] && !replace) { const cachedData = eventsCache[cacheKey]; $container.append(cachedData.html); const hasMore = cachedData.hasMore; hasMore ? $loadMore.show() : $loadMore.hide(); $loadMore.text('Load More').prop('disabled', false); return; } // Skip API call if same filters and page 1 if (replace && lastFilters && filtersEqual(filters, lastFilters) && pageNum === 1) { return; } if (!replace) { $container.append(showSkeleton(6)); $loadMore.text('Loading...').prop('disabled', true); } else { $container.html(showSkeleton(6)); lastFilters = { ...filters }; // Store current filters } $.post(pecbEvents.ajax_url, { action: 'pecb_load_events', nonce: pecbEvents.nonce, page: page, filters: filters }, function (resp) { if (!replace) $container.find('.skeleton-card').remove(); // Cache the response eventsCache[cacheKey] = { html: resp.success && resp.data ? (resp.data.results || []) : [], hasMore: resp.data?.has_more !== false, timestamp: Date.now() }; // Limit cache size (keep last 15 entries) const cacheKeys = Object.keys(eventsCache); if (cacheKeys.length > 15) { const oldestKey = cacheKeys.sort((a, b) => eventsCache[a].timestamp - eventsCache[b].timestamp )[0]; delete eventsCache[oldestKey]; } // Handle normalized response structure const results = resp.success && resp.data ? (resp.data.results || []) : []; if (results.length > 0) { const html = results.map(ev => renderEventCard(ev)).join(''); replace ? $container.html(html) : $container.append(html); // Equalize card heights after rendering setTimeout(equalizeCardHeights, 50); // Show/hide load more based on has_more flag or result count const hasMore = resp.data?.has_more !== false && results.length >= 20; hasMore ? $loadMore.show() : $loadMore.hide(); } else { if (replace) { $container.html('

No events found matching your criteria.

'); } $loadMore.hide(); } $loadMore.text('Load More').prop('disabled', false); }).fail(function (xhr, status, error) { $container.find('.skeleton-card').remove(); if (replace) { $container.html('

Error loading events. Please try again later.

'); } else { $container.append('

Error loading more events.

'); } $loadMore.text('Load More').prop('disabled', false); }); } // Helper to populate dropdowns const populateDropdown = ($select, items, cache = null) => { const options = ['', ...items.map(item => { if (cache) cache[item.id] = item.name; return ``; })]; $select.html(options.join('')); const urlValue = $select.data('url-value'); if (urlValue) $select.val(urlValue); refreshSelect2($select); }; // LOAD COURSES AND PARTNERS IN PARALLEL - Optimized for performance Promise.all([ $.post(pecbEvents.ajax_url, { action: 'pecb_get_courses', nonce: pecbEvents.nonce }), $.post(pecbEvents.ajax_url, { action: 'pecb_get_partners', nonce: pecbEvents.nonce }) ]).then(([coursesResp, partnersResp]) => { // Populate courses dropdown if (coursesResp.success && coursesResp.data?.length) { populateDropdown($('#course-select'), coursesResp.data, coursesData); } // Populate partners dropdown if (partnersResp.success && partnersResp.data?.length) { populateDropdown($('#partner-select'), partnersResp.data, partnersData); } // Load events via AJAX if no PHP cards if (!hasPhpCards) { checkAndLoadInitialEvents(); } }).catch((error) => { if (!hasPhpCards) { checkAndLoadInitialEvents(); } }); function checkAndLoadInitialEvents() { // Apply URL filters if present if (typeof pecbInitialFilters !== 'undefined' && Object.keys(pecbInitialFilters).length) { const $partnerSelect = $('#partner-select'); // Handle partnerId from URL - set select value directly if (pecbInitialFilters.partnerId) { $partnerSelect.val(pecbInitialFilters.partnerId); refreshSelect2($partnerSelect); } // Also handle partner name from URL (fallback) else if (pecbInitialFilters.partner) { const partnerId = Object.entries(partnersData).find(([, name]) => name === pecbInitialFilters.partner)?.[0]; if (partnerId) { $partnerSelect.val(partnerId); refreshSelect2($partnerSelect); } } // Apply delivery format if present in URL if (pecbInitialFilters.deliveryFormat) { currentDeliveryFormat = pecbInitialFilters.deliveryFormat; $('.format-btn').removeClass('active'); $(`.format-btn[data-format="${pecbInitialFilters.deliveryFormat}"]`).addClass('active'); } if (pecbInitialFilters.scheme) { currentScheme = pecbInitialFilters.scheme; $searchInput.val(currentScheme); } } loadEvents(1, true); } // Check URL for modal parameter and open if present const modalId = urlParams.get('modal'); if (modalId) { setTimeout(function () { openModalById(modalId); }, 1000); // Wait for page to fully load } // DELIVERY FORMAT SELECTOR $('.format-btn').on('click', function () { $('.format-btn').removeClass('active'); $(this).addClass('active'); currentDeliveryFormat = $(this).data('format') || 'Classroom'; updateURL(); // Update URL with new filter loadEvents(1, true); checkFiltersState(); }); // SEARCH INPUT - debounced $searchInput.on('input', function () { clearTimeout(searchTimeout); const searchValue = $(this).val().trim(); searchTimeout = setTimeout(function () { currentScheme = searchValue; updateURL(); // Update URL with new search loadEvents(1, true); checkFiltersState(); }, 500); // 500ms debounce }); // EVENTS $filterForm.on('submit', e => { e.preventDefault(); updateURL(); // Update URL with form filters loadEvents(1, true); scrollToTop(); }); $loadMore.on('click', () => loadEvents(page + 1, false)); // Scroll to top of events container const scrollToTop = () => { const $eventsHeader = $('.events-api-container'); if ($eventsHeader.length) { $('html, body').animate({ scrollTop: $eventsHeader.offset().top -120 }, 50); } }; // Check if filters are filled and show/hide clear button let checkFiltersTimeout; const checkFiltersState = () => { clearTimeout(checkFiltersTimeout); checkFiltersTimeout = setTimeout(() => { const hasFilters = $filterForm.serializeArray().some(item => item.value && item.name !== 'eventDate') || $eventDate.val() || (currentDeliveryFormat && currentDeliveryFormat !== 'Classroom') || currentScheme; const $clearBtn = $('#clear-filters-btn'); if (hasFilters) { $clearBtn.show(); } else { $clearBtn.hide(); } }, 100); }; // Clear all filters $('#clear-filters-btn').on('click', function() { const $clearBtn = $(this); // Immediately hide button to prevent blinking $clearBtn.hide(); // Temporarily remove filter monitoring $filterForm.off('change', 'select, input', checkFiltersState); $eventDate.off('change', checkFiltersState); // Clear form inputs $filterForm.find('select').val('').trigger('change'); $filterForm.find('input[type="text"]').val(''); // Clear date picker if (eventDatePicker) { eventDatePicker.clear(); } // Reset delivery format currentDeliveryFormat = 'Classroom'; $('.format-btn').removeClass('active'); $('.format-btn[data-format="Classroom"]').addClass('active'); // Reset search currentScheme = ''; $searchInput.val(''); // Clear URL params window.history.pushState({}, '', window.location.pathname); // Reload events loadEvents(1, true); scrollToTop(); // Re-enable filter monitoring after clearing is complete setTimeout(() => { $filterForm.on('change', 'select, input', checkFiltersState); $eventDate.on('change', checkFiltersState); }, 200); }); // Monitor filter changes $filterForm.on('change', 'select, input', checkFiltersState); $eventDate.on('change', checkFiltersState); // Check initial state checkFiltersState(); // MODAL HANDLERS const $modal = $('#pecb-event-modal'); const $modalContent = $modal.find('.modal-content'); const $modalLoading = $modal.find('.modal-loading'); // Open modal on View More click - uses card data for instant overview $(document).on('click', '.view-more', function () { const $card = $(this).closest('.pecb-event-card'); const eventId = $(this).data('event-id'); const cardData = $card.data('event'); if (!eventId) { return; } // Update URL with modal ID updateURL(eventId); $modal.addClass('active'); // If we have data from card, show overview immediately if (cardData) { renderModalContent(cardData, true); // true = partial data $modalContent.show(); $modalLoading.hide(); // Load full details (description + partner info) in background loadEventDetails(eventId); } else { // Fallback to full API load $modalLoading.show(); $modalContent.hide(); $.post(pecbEvents.ajax_url, { action: 'pecb_get_event', nonce: pecbEvents.nonce, event_id: eventId }, function (resp) { $modalLoading.hide(); if (resp?.success && resp.data) { renderModalContent(resp.data, false); } else { $modalContent.html('

Failed to load event details.

'); } $modalContent.show(); }).fail(function () { $modalLoading.hide(); $modalContent.html('

Error loading event details. Please try again.

'); $modalContent.show(); }); } }); // Close modal handler const closeModal = () => { $modal.removeClass('active'); updateURL(); // Remove modal parameter from URL }; $modal.find('.pecb-modal-close, .pecb-modal-overlay').on('click', closeModal); // $(document).on('keydown', (e) => e.key === 'Escape' && $modal.hasClass('active') && closeModal()); $(document).on('keydown', function (e) { if (e.which === 27 && $modal.hasClass('active')) { // 27 = Escape closeModal(); } }); // Contact form toggle $(document).on('click', '.contact-btn', function () { const $contactForm = $('.contact-form'); const $button = $(this); if ($contactForm.is(':visible')) { $contactForm.slideUp(300); $button.text('Contact'); } else { $contactForm.slideDown(300); $button.text('Close Form'); } }); // Contact form submission $(document).on('submit', '.contact-form-fields', function (e) { e.preventDefault(); const $form = $(this); const $submitBtn = $form.find('.send-message-btn'); const $contactBtn = $('.contact-btn'); // Get partner details const partnerEmail = $contactBtn.data('partner-email'); const partnerName = $contactBtn.data('partner-name'); // Collect form data const formData = { name: $form.find('#contact-name').val(), email: $form.find('#contact-email').val(), phone: $form.find('#contact-phone').val(), country: $form.find('#contact-country').val(), message: $form.find('#contact-message').val(), terms: $form.find('#contact-terms').is(':checked'), partner_email: partnerEmail || '', partner_name: partnerName || '' }; // Validate required fields if (!formData.name || !formData.email || !formData.terms) { alert('Please fill in all required fields and accept the terms.'); return; } // Show loading state $submitBtn.prop('disabled', true).text('Sending...'); // Send message via AJAX $.post(pecbEvents.ajax_url, { action: 'pecb_send_contact_message', nonce: pecbEvents.nonce, form_data: formData }, function (response) { if (response.success) { alert('Message sent successfully! The partner will receive your message within 48 hours.'); $form[0].reset(); // Clear form $('.contact-form').slideUp(300); $('.contact-btn').text('Contact'); } else { alert('Error sending message: ' + (response.data?.message || 'Unknown error')); } }).fail(function () { alert('Error sending message. Please try again later.'); }).always(function () { $submitBtn.prop('disabled', false).text('Send message'); }); }); // Render modal content // isPartialData = true means we only have list data (no description/partner details yet) function renderModalContent(eventData, isPartialData = false) { const event = eventData || {}; const partnerDetails = event.partnerDetails || {}; const locationDetails = event.eventLocationDetails || {}; // Convert \r\n to
for proper line breaks in HTML // const formatDescription = (text) => text ? text.replace(/\r\n|\n/g, '
') : 'N/A'; const formatDescription = (text) => { if (!text || typeof text !== 'string') return ''; let result = text; // ── 1. Protect existing tags (if any) ───────────────────── const anchors = new Map(); let id = 0; result = result.replace(/]*>[\s\S]*?<\/a>/gi, (match) => { const placeholder = `__EXISTING_LINK_${id++}__`; anchors.set(placeholder, match); return placeholder; }); // ── 2. Replace plain URLs (http, https, www.) with tags ───── // This regex is carefully crafted to handle your exact URL + trailing space/quote result = result.replace( /(^|\s)(https?:\/\/[^\s<>"']+)|(^|\s)(www\.[^\s<>"']+\.[^\s<>"']+)/gi, (match, space1, httpUrl, space2, wwwUrl) => { const url = httpUrl || wwwUrl; const prefix = space1 || space2 || ''; // Clean trailing punctuation/quotes that are NOT part of the URL let cleanUrl = url.replace(/[)"'»”]*$/, ''); let trailing = url.slice(cleanUrl.length); // keep the punctuation outside const finalUrl = cleanUrl.startsWith('www.') ? 'https://' + cleanUrl : cleanUrl; return `${prefix}${cleanUrl}${trailing}`; } ); // ── 3. Convert line breaks to
───────────────────────────── result = result.replace(/\r\n|\r|\n/g, '
'); // ── 4. Restore any original tags ─────────────────────────── for (const [placeholder, original] of anchors) { result = result.split(placeholder).join(original); } return result; }; let html = ` `; $modalContent.html(html); } // Re-equalize on window resize (debounced) let resizeTimeout; $(window).on('resize', function() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(equalizeCardHeights, 150); }); // Equalize heights for PHP-rendered cards on initial load if ($container.find('.pecb-event-card').length > 0) { setTimeout(equalizeCardHeights, 100); } });