1(function ($) { 2 3/* =============================== 4 GLOBAL HELPER 5=============================== */ 6 7function getQueryParam(param) { 8 const urlParams = new URLSearchParams(window.location.search); 9 let val = urlParams.get(param); 10 11 if (!val) return null; 12 13 val = val.toLowerCase().trim(); 14 15 if (val === 'all') return null; 16 17 return val; 18} 19 20/* =============================== 21 SLIDER MODULE 22=============================== */ 23 24function initSliders(scope = document) { 25 26 scope.querySelectorAll('.slider-track').forEach(track => { 27 28 if (track.dataset.init === '1') return; 29 track.dataset.init = '1'; 30 31 const wrapper = track.closest('.slider-container'); 32 const nextBtn = wrapper?.querySelector('.next'); 33 const prevBtn = wrapper?.querySelector('.prev'); 34 const dotsContainer = wrapper?.querySelector('.dots-container'); 35 const controlsWrap = wrapper?.querySelector('.slider-dots'); 36 37 let currentIndex = 0; 38 let slidesPerView = window.innerWidth <= 768 ? 1 : 3; 39 40 function getSlides() { 41 return Array.from(track.children).filter(el => $(el).is(':visible')); 42 } 43 44 function setupLayout() { 45 const slides = getSlides(); 46 if (!slides.length) return; 47 48 const container = wrapper.querySelector('.hm-grid'); 49 const containerWidth = container.clientWidth; 50 51 const gap = parseInt(window.getComputedStyle(track).gap) || 20; 52 const totalGap = gap * (slidesPerView - 1); 53 54 const slideWidth = (containerWidth - totalGap) / slidesPerView; 55
56 slides.forEach(slide => { 57 slide.style.flex = `0 0 ${slideWidth}px`; 58 slide.style.maxWidth = `${slideWidth}px`; 59 }); 60 } 61 62 function createDots() { 63 64 if (!dotsContainer) return; 65 66 const slides = getSlides(); 67 const totalSlides = slides.length; 68 const totalDots = Math.ceil(totalSlides / slidesPerView); 69 70 if (totalSlides <= slidesPerView) { 71 dotsContainer.innerHTML = ''; 72 controlsWrap && (controlsWrap.style.display = 'none'); 73 return; 74 } else { 75 controlsWrap && (controlsWrap.style.display = 'flex'); 76 } 77 78 dotsContainer.innerHTML = ''; 79 80 for (let i = 0; i < totalDots; i++) { 81 const dot = document.createElement('span'); 82 dot.className = 'dot' + (i === 0 ? ' active' : ''); 83 84 dot.addEventListener('click', () => { 85 currentIndex = i; 86 move(); 87 }); 88 89 dotsContainer.appendChild(dot); 90 } 91 } 92 93 function move() { 94 const slides = getSlides(); 95 if (!slides.length) return; 96 97 const slide = slides[0]; 98 const slideWidth = slide.offsetWidth; 99 const gap = parseInt(window.getComputedStyle(track).gap) || 0; 100 101 const moveX = currentIndex * (slideWidth + gap) * slidesPerView; 102 103 track.style.transform = `translateX(-${moveX}px)`; 104 105 if (dotsContainer) { 106 [...dotsContainer.children].forEach(d => d.classList.remove('active')); 107 dotsContainer.children[currentIndex]?.classList.add('active'); 108 } 109 } 110 111 nextBtn?.addEventListener('click', () => { 112 const totalDots = Math.ceil(getSlides().length / slidesPerView); 113 if (currentIndex < totalDots - 1) { 114 currentIndex++; 115 move(); 116 } 117 }); 118 119 prevBtn?.addEventListener('click', () => { 120 if (currentIndex > 0) { 121 currentIndex--; 122 move(); 123 } 124 }); 125 126 setTimeout(() => { 127 setupLayout(); 128 createDots(); 129 move(); 130 }, 80); 131 132 window.addEventListener('resize', () => { 133 slidesPerView = window.innerWidth <= 768 ? 1 : 3; 134 currentIndex = 0; 135 136 setupLayout(); 137 createDots(); 138 move(); 139 }); 140 141 }); 142} 143 144/* =============================== 145 FILTER + SORT (SPONSORSHIP) 146=============================== */ 147 148function sortByMeta($grid) { 149 150 let items = $grid.find('.resource-item:visible').get(); 151 152 items.sort((a, b) => { 153 let oA = +a.dataset.order || 999999; 154 let oB = +b.dataset.order || 999999; 155 if (oA !== oB) return oA - oB; 156 157 let mA = +a.dataset.menuOrder || 0; 158 let mB = +b.dataset.menuOrder || 0; 159 if (mA !== mB) return mA - mB; 160 161 return (+b.dataset.date || 0) - (+a.dataset.date || 0); 162 }); 163 164 $.each(items, (_, el) => $grid.append(el)); 165} 166 167function applyFiltersAndSorting() { 168 169 $('.hm-sponsorship-wrapper').each(function () { 170 171 const $parent = $(this); 172 173 const category = ($parent.find('.sponsorship-hm-filter-dropdown').val() || 'all') 174 .toLowerCase().trim(); 175 176 const $grid = $parent.find('.hm-grid'); 177 const $items = $grid.find('.resource-item'); 178 179 $items.each(function () { 180 181 const $card = $(this).find('.hm-card'); 182 183 const terms = ($card.attr('data-terms') || '').toLowerCase(); 184 185 const cats = ($card.attr('data-categories') || '') 186 .split(',') 187 .map(c => c.trim()) 188 .filter(Boolean); 189 190 const show = (category === 'all') ? true : terms.includes('|' + category + '|'); 191 192 $(this).toggle(show); 193 194 let display = cats[0] || ''; 195 196 if (category !== 'all') { 197 const match = cats.find(c => c.toLowerCase() === category); 198 if (match) { 199 display = match; 200 } 201 } 202 203 $card.find('.hm-cat').text(display); 204 205 206 207 }); 208 209 sortByMeta($grid); 210 211 const track = $grid[0]; 212 213 if (track) {
214 track.style.transition = 'none'; 215 track.style.transform = 'translateX(0)'; 216 delete track.dataset.init; 217 track.offsetHeight; 218 track.style.transition = 'transform 0.3s ease'; 219 } 220 221 setTimeout(() => { 222 initSliders($parent[0]); 223 }, 100); 224 225 }); 226} 227 228/* =============================== 229 EVENTS (SPONSORSHIP) 230=============================== */ 231 232$(document).on('change', '.sponsorship-hm-filter-dropdown', function () { 233 234 const val = $(this).val(); 235 sessionStorage.setItem('hm_sponsorship_category', val); 236 applyFiltersAndSorting(); 237}); 238 239$(document).ready(function () { 240 241 let urlCategory = getQueryParam('category'); 242 let savedCategory = sessionStorage.getItem('hm_sponsorship_category'); 243 244 $('.hm-sponsorship-wrapper').each(function () { 245 246 const $dropdown = $(this).find('.sponsorship-hm-filter-dropdown'); 247 let finalCategory = urlCategory || savedCategory; 248 249 if (finalCategory && $dropdown.find(`option[value="${finalCategory}"]`).length) { 250 $dropdown.val(finalCategory); 251 } 252 }); 253 254 initSliders(); 255 256 setTimeout(() => { 257 applyFiltersAndSorting(); 258 if (urlCategory && $('.hm-resources').length) { 259 $('html, body').animate({ 260 scrollTop: $('.hm-resources').offset().top 261 }, 800); 262 } 263 }, 50); 264}); 265 266})(jQuery); 267 268/* =============================== 269 AJAX PRODUCTS 270=============================== */ 271 272jQuery(function ($) { 273 274 let isLoading = false; 275 let $wrapper = $('.hm-resources'); 276 277 let defaultType = $wrapper.data('default-type') || 'free'; 278 let spanishCategory = $wrapper.data('spanish-category') || null; 279 let mergeFreePremium = $wrapper.data('merge-free-premium') == 1; 280 let switchedToPremiumForMerge = false; 281 282 let type = defaultType; 283 let category = 'all'; 284 let offset = 0; 285 let limit = 9; 286 let total = 0; 287 288 let urlCategory = (function () { 289 const urlParams = new URLSearchParams(window.location.search); 290 let val = urlParams.get('category'); 291 return val ? val.toLowerCase().trim() : null; 292 })(); 293 294 const $tabsContainer = $('.hm-tabs'); 295 const $freeTab = $tabsContainer.find('[data-type="free"]'); 296 const $premiumTab = $tabsContainer.find('[data-type="premium"]'); 297 const $memberTab = $tabsContainer.find('[data-type="member"]'); 298 299 if (defaultType === 'premium') { 300 $premiumTab.prependTo($tabsContainer); 301 $freeTab.appendTo($tabsContainer); 302 $memberTab.appendTo($tabsContainer); 303 } else { 304 $freeTab.prependTo($tabsContainer); 305 $premiumTab.appendTo($tabsContainer); 306 $memberTab.appendTo($tabsContainer); 307 } 308 309 $('.hm-tab-btn').removeClass('active'); 310 $(`.hm-tab-btn[data-type="${type}"]`).addClass('active'); 311 312 function loadCategories() { 313 314 $('#hm-category').html('<option>Loading...</option>'); 315 316 $.post(hm_ajax_obj.ajax_url, { 317 action: 'hm_get_categories', 318 type: type 319 }, function (res) { 320 321 let html = '<option value="all">All Categories</option>'; 322 323 res.forEach(function (cat) { 324 html += `<option value="${cat.toLowerCase()}">${cat}</option>`; 325 }); 326 327 $('#hm-category').html(html); 328 329 if (urlCategory && $('#hm-category option[value="' + urlCategory + '"]').length) { 330 category = urlCategory; 331 $('#hm-category').val(urlCategory); 332 }else if (spanishCategory && $('#hm-category option[value="' + spanishCategory.toLowerCase() + '"]').length) { 333 category = spanishCategory.toLowerCase(); 334 $('#hm-category').val(spanishCategory.toLowerCase()); 335 } 336 337 loadProducts(true); 338 }); 339 } 340 341 function loadProducts(reset = false) { 342 343 if (isLoading) return; 344 345 isLoading = true; 346 347 if (reset) { 348 349 offset = 0; 350 351 $('#hm-products').html(` 352 <div class="hm-loader"> 353 <span></span><span></span><span></span> 354 </div> 355 `); 356 357 } 358 359 $('#hm-load-more').hide(); 360 $('#hm-scroll-loader').show(); 361 362 $.post(hm_ajax_obj.ajax_url, { 363 364 action: 'hm_get_products', 365 type: type, 366 category: category, 367 offset: offset, 368 is_spanish: spanishCategory ? 1 : 0 369 370 }, function (res) { 371 372 if (reset) { 373 374 $('#hm-products').html(res.html); 375 376 } else { 377 378 $('#hm-products').append(res.html); 379 380 } 381 382 total = res.total; 383 384 offset += limit; 385 386 isLoading = false;
387 $('#hm-scroll-loader').hide(); 388 389 }); 390 391} 392 393 /* INIT */ 394 loadCategories(); 395 396 /* EVENTS */ 397 398 $(document).on('click', '.hm-tab-btn', function () { 399 400 $('.hm-tab-btn').removeClass('active'); 401 $(this).addClass('active'); 402 403 type = $(this).data('type'); 404 405 $('#hm-mobile-tab-select').val(type); 406 407 category = 'all'; 408 switchedToPremiumForMerge = false; 409 410 loadCategories(); 411}); 412 413$(document).on('change', '#hm-mobile-tab-select', function () { 414 415 let selectedType = $(this).val(); 416 417 $('.hm-tab-btn').removeClass('active'); 418 419 let $tab = $('.hm-tab-btn[data-type="' + selectedType + '"]'); 420 421 $tab.addClass('active'); 422 423 type = selectedType; 424 category = 'all'; 425 switchedToPremiumForMerge = false; 426 427 loadCategories(); 428}); 429 $(document).on('change', '#hm-category', function () { 430 category = $(this).val(); 431 loadProducts(true); 432 }); 433 434 $(window).on('scroll', function () { 435 436 // already loading 437 if (isLoading) return; 438 439 let scrollTop = $(window).scrollTop(); 440 let windowHeight = $(window).height(); 441 let documentHeight = $(document).height(); 442 443 // near bottom 444 let nearBottom = (scrollTop + windowHeight >= documentHeight - 600); 445 446 if (!nearBottom) return; 447 448 // all loaded for current type 449 if (offset >= total) { 450 451 // Spanish page special case: once free products are exhausted, 452 // keep scrolling in the same (free) tab but start loading premium products 453 // (still restricted to the "en español" category). 454 if (mergeFreePremium && type === 'free' && !switchedToPremiumForMerge) { 455 456 switchedToPremiumForMerge = true; 457 type = 'premium'; 458 category = spanishCategory ? spanishCategory.toLowerCase() : 'all'; 459 offset = 0; 460 461 let premiumLabel = $('.hm-tab-btn[data-type="premium"]').first().text().trim(); 462 if (premiumLabel) { 463 $('#hm-products').append( 464 $('<h2>', { class: 'hm-section-heading hm-merge-premium-heading', text: premiumLabel }) 465 ); 466 } 467 468 loadProducts(false); 469 } 470 471 return; 472 } 473 474 loadProducts(false); 475 476}); 477 478 479});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.