I am developing the online web shop, and I am making the filter and sorting function for the product and seller.
The filter can be set the categories and I have the layout design for the categories selection that come from other section for the reference.
Here are the reference:
JavaScript:
(() => {
  // 參數設定-可由後端設定傳入
  const config = {
    minProductSelection: 2,
    maxProductSelection: 5,
    minSellerSelection: 0,
    maxSellerSelection: 3
  };

  // 使用者已選的子分類與商家ID
  let selectedProductChildIds = new Set([]);
  let selectedSellerIds = new Set([]);

  // 展開狀態鍵值對，key = 父類 id，value = 是否展開 (true/false)
  const expandedParents = new Map();

  // 商品分類資料與商家分類資料 --- 初值為空，將用Ajax取得
  let productCategories = [];
  let sellerCategories = [];

  // DOM 元素
  document.addEventListener('DOMContentLoaded', () => {
    const modalOverlay = document.getElementById('favoriteModalOverlay');
    const modalTitle = document.getElementById('categoryModalTitle');
    const productCategoryList = document.getElementById('productCategoryList');
    const productCategorySearch = document.getElementById('productCategorySearch');
    const productValidationMsg = document.getElementById('productValidationMsg');
    

    const sellerList = document.getElementById('sellerList');
    const sellerSearchInput = document.getElementById('sellerSearchInput');
    const sellerValidationMsg = document.getElementById('sellerValidationMsg');

    const selectedProductChildTags = document.getElementById('selectedProductChildTags');
    const selectedSellerTags = document.getElementById('selectedSellerTags');

    
    const closeCatBtn = document.getElementById('closeCatBtn');
    const clearAllBtn = document.getElementById('clearAllBtn');
    const saveBtn = document.getElementById('saveFavoritesBtn');
    const collapseAllBtn = document.getElementById('collapseAllBtn');

    // 工具：字串包含（忽略大小寫與空白）
    function includesIgnoreCase(str, query) {
      return str.toLowerCase().trim().includes(query.toLowerCase().trim());
    }

    // 從伺服器Ajax取得商品分類 (雙層結構)
    async function fetchProductCategories(search = '') {
      try {
        const res = await fetch(base_url + `category/get_product_categories?search=${encodeURIComponent(search)}`, { credentials: 'same-origin' });
        if (!res.ok) throw new Error('伺服器錯誤');
        const data = await res.json();
        return data;
      } catch (e) {
        alert('獲取商品分類失敗，請稍後再試');
        return [];
      }
    }

    // 從伺服器Ajax取得商家分類
    async function fetchSellerCategories(search = '') {
      try {
        const res = await fetch(base_url + `category/get_seller_categories?search=${encodeURIComponent(search)}`, { credentials: 'same-origin' });
        if (!res.ok) throw new Error('伺服器錯誤');
        const data = await res.json();
        return data;
      } catch (e) {
        alert('獲取商家分類失敗，請稍後再試');
        return [];
      }
    }

    // 各渲染函式 (請沿用您原本的函式，這裡示範bind了新變量)
    
    function renderProductCategories(){
      productCategoryList.innerHTML = "";
      const keyword = productCategorySearch.value.trim();
      const filteredParents = productCategories.filter(parent => {
        if (!keyword) return true;
        if (includesIgnoreCase(parent.name, keyword)) return true;
        return parent.children.some(child => includesIgnoreCase(child.name, keyword));
      });
      if(filteredParents.length === 0){
        const li = document.createElement('li');
        li.textContent = "無符合搜尋結果";
        li.style.padding = "0.5rem 1rem";
        productCategoryList.appendChild(li);
        return;
      }
      filteredParents.forEach(parent => {
        const parentLi = document.createElement('li');
        parentLi.className = 'parent-category';
        parentLi.id = `parent-${parent.id}`;
        parentLi.tabIndex = 0;
        parentLi.setAttribute('role', 'treeitem');
        const isExpanded = expandedParents.get(parent.id) || false;
        parentLi.setAttribute('aria-expanded', isExpanded.toString());

        const arrow = document.createElement('span');
        arrow.textContent = isExpanded ? '▼' : '▶';
        arrow.style.userSelect = 'none';
        arrow.style.marginRight = '0.5rem';
        arrow.style.fontSize = '0.9rem';
        arrow.style.flexShrink = 0;

        const labelSpan = document.createElement('span');
        labelSpan.textContent = parent.name;

        const countSpan = document.createElement('span');
        countSpan.className = 'child-count';
        countSpan.textContent = `(${parent.children.length})`;

        const selectedCount = parent.children.reduce((acc, c) => acc + (selectedProductChildIds.has(c.id) ? 1 : 0), 0);
        const selectedMark = document.createElement('span');
        selectedMark.className = 'selected-mark';
        selectedMark.style.flexShrink = 0;
        selectedMark.style.marginLeft = '8px';
        selectedMark.textContent = selectedCount > 0 ? `已選 ${selectedCount}` : '';

        const textWrapper = document.createElement('div');
        textWrapper.style.display = 'flex';
        textWrapper.style.alignItems = 'center';
        textWrapper.style.flexGrow = 1;
        textWrapper.appendChild(arrow);
        textWrapper.appendChild(labelSpan);
        textWrapper.appendChild(countSpan);
        textWrapper.appendChild(selectedMark);

        parentLi.appendChild(textWrapper);

        parentLi.addEventListener('click', e => {
          if(e.target === parentLi || e.target === textWrapper || e.target === labelSpan || e.target === arrow){
            const currExp = expandedParents.get(parent.id) || false;
            expandedParents.set(parent.id, !currExp);
            renderProductCategories();
          }
        });
        parentLi.addEventListener('keydown', e => {
          if(e.key === ' ' || e.key === 'Enter'){
            e.preventDefault();
            const currExp = expandedParents.get(parent.id) || false;
            expandedParents.set(parent.id, !currExp);
            renderProductCategories();
          }
        });

        productCategoryList.appendChild(parentLi);

        if(isExpanded){
          const childUl = document.createElement('ul');
          childUl.className = 'child-list';
          childUl.setAttribute('role', 'group');

          const filteredChildren = parent.children.filter(child => {
            if(!keyword) return true;
            return includesIgnoreCase(child.name, keyword) || includesIgnoreCase(parent.name, keyword);
          });

          if(filteredChildren.length === 0){
            const noResLi = document.createElement('li');
            noResLi.textContent = '無符合搜尋結果';
            noResLi.style.color = '#666';
            noResLi.style.fontStyle = 'italic';
            childUl.appendChild(noResLi);
          }
          filteredChildren.forEach(child => {
            const childLi = document.createElement('li');
            childLi.setAttribute('role', 'treeitem');

            const label = document.createElement('label');
            label.style.cursor = 'pointer';
            label.htmlFor = `childchk-${child.id}`;
            label.style.userSelect = 'none';

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = `childchk-${child.id}`;
            checkbox.value = child.id;
            checkbox.checked = selectedProductChildIds.has(child.id);
            checkbox.setAttribute('aria-checked', checkbox.checked ? 'true' : 'false');

            checkbox.addEventListener('change', e => {
              if(e.target.checked){
                if(selectedProductChildIds.size >= config.maxProductSelection){
                  e.target.checked = false;
                  alert(`最多可選擇 ${config.maxProductSelection} 個商品分類`);
                  return;
                }
                selectedProductChildIds.add(child.id);
              } else {
                selectedProductChildIds.delete(child.id);
              }
              validateAndRenderAll();
            });

            label.appendChild(checkbox);
            const spanTxt = document.createElement('span');
            spanTxt.textContent = child.name;
            label.appendChild(spanTxt);
            childLi.appendChild(label);
            childUl.appendChild(childLi);
          });

          productCategoryList.appendChild(childUl);
        }
      });
    }

    function renderSellerList(){
      sellerList.innerHTML = '';
      const keyword = sellerSearchInput.value.trim();
      const filtered = sellerCategories.filter(s => !keyword || includesIgnoreCase(s.name, keyword));
      if(filtered.length === 0){
        const spanNone = document.createElement('span');
        spanNone.textContent = '無符合搜尋結果';
        spanNone.style.color = '#666';
        spanNone.style.fontStyle = 'italic';
        sellerList.appendChild(spanNone);
        return;
      }
      filtered.forEach(seller => {
        const div = document.createElement('div');
        div.className = 'seller-item';
        div.tabIndex = 0;
        div.setAttribute('role', 'option');
        div.setAttribute('aria-selected', selectedSellerIds.has(seller.id) ? 'true' : 'false');
        div.dataset.id = seller.id;
        div.textContent = seller.name;
        if(selectedSellerIds.has(seller.id)) div.classList.add('selected');

        div.addEventListener('click', () => toggleSellerSelection(seller.id));
        div.addEventListener('keydown', e => {
          if(e.key === 'Enter' || e.key === ' '){
            e.preventDefault();
            toggleSellerSelection(seller.id);
          }
        });
        sellerList.appendChild(div);
      });
    }

    function toggleSellerSelection(id){
      if(selectedSellerIds.has(id)){
        selectedSellerIds.delete(id);
      } else {
        if(selectedSellerIds.size >= config.maxSellerSelection){
          alert(`最多可選擇 ${config.maxSellerSelection} 個商家分類`);
          return;
        }
        selectedSellerIds.add(id);
      }
      validateAndRenderAll();
    }

    function renderSelectedProductChildTags(){
      selectedProductChildTags.innerHTML = '';
      if(selectedProductChildIds.size === 0){
        selectedProductChildTags.textContent = '尚無選擇子分類';
        return;
      }
      const tags = [];
      selectedProductChildIds.forEach(id => {
        for(const parent of productCategories){
          const child = parent.children.find(c => c.id === id);
          if(child){
            tags.push({id, name: child.name, parentName: parent.name});
            break;
          }
        }
      });
      tags.forEach(tag => {
        const span = document.createElement('span');
        span.className = 'tag product-child';
        span.textContent = `${tag.parentName} > ${tag.name}`;
        const btn = document.createElement('button');
        btn.setAttribute('aria-label', `移除商品分類 ${tag.parentName} 的 ${tag.name}`);
        btn.type = 'button';
        btn.textContent = '×';
        btn.addEventListener('click', () => {
          selectedProductChildIds.delete(tag.id);
          validateAndRenderAll();
        });
        span.appendChild(btn);
        selectedProductChildTags.appendChild(span);
      });
    }

    function renderSelectedSellerTags(){
      selectedSellerTags.innerHTML = '';
      if(selectedSellerIds.size === 0){
        selectedSellerTags.textContent = '尚無選擇商家分類';
        return;
      }
      selectedSellerIds.forEach(id => {
        const seller = sellerCategories.find(s => s.id === id);
        if(!seller) return;
        const span = document.createElement('span');
        span.className = 'tag seller';
        span.textContent = seller.name;
        const btn = document.createElement('button');
        btn.setAttribute('aria-label', `移除商家分類 ${seller.name}`);
        btn.type = 'button';
        btn.textContent = '×';
        btn.addEventListener('click', () => {
          selectedSellerIds.delete(id);
          validateAndRenderAll();
        });
        span.appendChild(btn);
        selectedSellerTags.appendChild(span);
      });
    }

    function validateSelections(){
      let productValid = true;
      let sellerValid = true;
      if(selectedProductChildIds.size < config.minProductSelection){
        productValidationMsg.textContent = `請至少選擇 ${config.minProductSelection} 個商品分類。`;
        productValid = false;
      } else if(selectedProductChildIds.size > config.maxProductSelection){
        productValidationMsg.textContent = `最多只能選擇 ${config.maxProductSelection} 個商品分類。`;
        customPopup.open({
            type: 'warning',
            message: `最多只能選擇 ${config.maxProductSelection} 個商品分類。`,
            showConfirm: true,
            autoClose: 3000,
            confirmText: '確定',
            onConfirm: () => closeModal()
        });
        productValid = false;
      } else {
        productValidationMsg.textContent = '';
      }
      /*
      if(selectedSellerIds.size < config.minSellerSelection){
        sellerValidationMsg.textContent = `請至少選擇 ${config.minSellerSelection} 個商家分類。`;
        sellerValid = false;
      } else if(selectedSellerIds.size > config.maxSellerSelection){
        sellerValidationMsg.textContent = `最多只能選擇 ${config.maxSellerSelection} 個商家分類。`;
        customPopup.open({
            type: 'warning',
            message: `最多只能選擇 ${config.maxSellerSelection} 個商家分類。`,
            showConfirm: true,
            autoClose: 3000,
            confirmText: '確定',
            onConfirm: () => closeModal()
        });
        sellerValid = false;
      } else {
        sellerValidationMsg.textContent = '';
      }
      */
      saveBtn.disabled = !(productValid && sellerValid);
      return productValid && sellerValid;
    }

    function validateAndRenderAll(){
      renderProductCategories();
      renderSelectedProductChildTags();
      //renderSellerList();
      //renderSelectedSellerTags();
      validateSelections();
    }

    function clearAllSelections(){
        customPopup.open({
            type: 'warning',
            message: '確定要清空所有選擇嗎？',
            confirmCancel: true,
            confirmText: 'Yes',
            cancelText: 'No',
            onConfirm: () => {
                selectedProductChildIds.clear();
                selectedSellerIds.clear();
                validateAndRenderAll();
            },
            onCancel: () => {
                customPopup.close();
                return;
            }
        });
      
    }

    async function saveSelections(){
      if(!validateSelections()) return;
      // 送到後端API，格式為JSON字串
      const payload = {
        productChildIds: Array.from(selectedProductChildIds),
        //sellerIds: Array.from(selectedSellerIds),
      };
      try {
        const res = await fetch(base_url + 'category/update_favorite_categories', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify(payload),
        });
        if (res.status == 'failed') throw new Error('更新失敗');

        customPopup.open({
            type: 'success',
            message: '我們已收到並更新您的喜好',
            showConfirm: true,
            autoClose: 3000,
            confirmText: '確定',
            onConfirm: () => closeModal(),
            onAutoClose: () => closeModal()
        });
        
      } catch(e){
        customPopup.open({
            type: 'error',
            message: '系統發生未知錯誤，請重試',
            autoClose: 3000,
            showConfirm: false,
        });
      }
    }

    function openCategoryModal(isUpdate){
      modalOverlay.style.display = 'flex';
      if(isUpdate){
        modalTitle.innerHTML = `重新選擇我的喜好`;
        closeCatBtn.style.display = "block";
        closeCatBtn.addEventListener('click', closeModal);
      } else {
        modalTitle.innerHTML = `您好${user_name}，歡迎登入YYMart宜優<br>請先讓我們了解你的喜好`;
        closeCatBtn.style.display = "none";
        closeCatBtn.removeEventListener('click', closeModal);
      }
      document.body.style.overflow = 'hidden';
      productCategorySearch.focus();
    }
    function closeModal(){
      modalOverlay.style.display = 'none';
      document.body.style.overflow = '';
    }

    modalOverlay.addEventListener('click', e => {
      if(e.target === modalOverlay){
        e.preventDefault();
        e.stopPropagation();
      }
    });
    document.addEventListener('keydown', e => {
      if(modalOverlay.style.display === 'flex'){
        if(e.key === 'Escape'){ e.preventDefault(); }
        else if(e.key === 'Tab'){
          const focusables = Array.from(modalOverlay.querySelectorAll('input,button,[tabindex]:not([tabindex="-1"])')).filter(el => el.offsetParent !== null);
          if(focusables.length === 0) return;
          const firstEl = focusables[0], lastEl = focusables[focusables.length-1];
          if(!modalOverlay.contains(document.activeElement)){
            firstEl.focus();
            e.preventDefault();
            return;
          }
          if(e.shiftKey){
            if(document.activeElement === firstEl){
              lastEl.focus();
              e.preventDefault();
            }
          } else {
            if(document.activeElement === lastEl){
              firstEl.focus();
              e.preventDefault();
            }
          }
        }
      }
    });

    const collapseAllParents = () => {
      for(const key of expandedParents.keys()){
        expandedParents.set(key, false);
      }
      renderProductCategories();
    };

    // 綁定事件
    productCategorySearch.addEventListener('input', async () => {
      productCategories = await fetchProductCategories(productCategorySearch.value.trim());
      expandedParents.clear();
      productCategories.forEach(c => expandedParents.set(c.id, false));
      renderProductCategories();
    });
    /*
    sellerSearchInput.addEventListener('input', async () => {
      sellerCategories = await fetchSellerCategories(sellerSearchInput.value.trim());
      renderSellerList();
    });
    */
    clearAllBtn.addEventListener('click', clearAllSelections);
    saveBtn.addEventListener('click', saveSelections);
    collapseAllBtn.addEventListener('click', collapseAllParents);

    window.categoriesModal = {
      open: openCategoryModal,
      close: closeModal,
    };

    

    // 初始化時取得分類資料
    (async () => {
      productCategories = await fetchProductCategories('');
      productCategories.forEach(c => expandedParents.set(c.id, false));
      //sellerCategories = await fetchSellerCategories('');
      validateAndRenderAll();

      if(is_logged_in == "1") {
        if(cat_favorites == "-1")
          categoriesModal.open(false);
      }
    })();

  });
})();

CSS:
  /* Modal 遮罩 & 容器 */
  #favoriteModalOverlay {
    position: fixed; top:0; left:0; width:100%; height:100%;
    background: rgba(0,0,0,0.5);
    z-index: 1001;
    display: flex; justify-content: center; align-items: center;
  }
  #favoriteModal {
    background: #fff;
    border-radius: 8px;
    width: 95%;
    max-width: 750px;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 5px 15px rgba(0,0,0,0.3);
    display: flex;
    flex-direction: column;
  }
  #favoriteModal header {
    font-weight: 700;
    font-size: 1.3rem;
    padding: 1rem 1.5rem;
    border-bottom: 1px solid #ddd;
  }
  #favoriteModal .modal-body {
    padding: 1rem 1.5rem;
    flex-grow: 1;
    overflow-y: auto;
  }
  #favoriteModal footer {
    padding: 1rem 1.5rem;
    border-top: 1px solid #ddd;
    background: #f9f9f9;
    text-align: right;
  }
  /* 按鈕 */
  #clearAllBtn, #saveFavoritesBtn, #closeCatBtn {
    border-radius: 5px;
    border: none;
    font-size: 1rem;
    padding: 0.5rem 1.2rem;
    cursor: pointer;
    transition: background-color 0.3s ease;
  }
  #closeCatBtn{
    background: #f09124;
    color: white;
    margin-right: 1rem;
  }
  #clearAllBtn {
    background: #6c757d;
    color: white;
    margin-right: 1rem;
  }
  #clearAllBtn:hover:not(:disabled) {
    background: #5a6268;
  }
  #saveFavoritesBtn {
    background: #007bff;
    color: white;
  }
  #saveFavoritesBtn:disabled {
    background: #a0c0ff;
    cursor: not-allowed;
  }
  #saveFavoritesBtn:hover:not(:disabled) {
    background: #0056b3;
  }

  /* 搜尋框 */
  .category-search-box {
    margin-bottom: 0.75rem;
  }
  .category-search-box input[type="search"] {
    width: 100%;
    padding: 0.4rem 0.6rem;
    font-size: 1rem;
    border: 1px solid #bbb;
    border-radius: 5px;
  }
  .category-search-box input[type="search"]:focus {
    outline: none;
    border-color: #007bff;
    box-shadow: 0 0 5px #007bff88;
  }
  
  /* 調整商品搜尋容器，方便放按鈕 */
  #productCategorySearchWrapper {
    display: flex;
    gap: 0.5rem;
    align-items: center;
  }
  #collapseAllBtn {
    flex-shrink: 0;
    background: #6c757d;
    color: white;
    border: none;
    border-radius: 5px;
    padding: 0.35rem 0.8rem;
    font-size: 0.9rem;
    cursor: pointer;
    user-select: none;
    transition: background-color 0.2s ease-in-out;
  }
  #collapseAllBtn:hover:not(:disabled){
    background: #5a6268;
  }
  #collapseAllBtn:focus {
    outline: 2px solid #343a40;
    outline-offset: 2px;
  }

  /* 商品分類清單（雙層） */
  #productCategoryList {
    list-style: none;
    padding: 0;
    margin: 0 0 1.8rem 0;
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid #ddd;
    border-radius: 6px;
  }
  .parent-category {
    cursor: pointer;
    padding: 0.5rem 1rem;
    border-bottom: 1px solid #eee;
    user-select: none;
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #f8f9fa;
    font-weight: 600;
  }
  .parent-category:hover,
  .parent-category:focus {
    background-color: #e2e6ea;
    outline:none;
  }
  .parent-category[aria-expanded="true"] {
    background-color: #d1ecf1;
  }
  .child-list {
    list-style: none;
    margin: 0;
    padding-left: 1.8rem;
    max-height: 160px;
    overflow-y: auto;
    background:#fafafa;
  }
  .child-list li {
    user-select: none;
    margin: 0.2rem 0;
  }
  .child-list li label {
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-weight: 400;
    font-size: 0.95rem;
    color: #333;
  }
  .child-list li label input[type="checkbox"] {
    cursor: pointer;
    width: 1rem;
    height: 1rem;
  }

  /* 標示父分類子分類數量 */
  .child-count {
    font-size: 0.9rem;
    color: #666;
    margin-left: 0.5rem;
  }

  /* 被選擇的父分類與子分類標示 */
  .parent-category .selected-mark,
  .child-list li.selected > label {
    font-weight: 700;
    color: #007bff;
  }
  .parent-category .selected-mark {
    margin-left: 0.5rem;
    color: #007bff;
    font-size: 0.9rem;
    user-select:none;
  }

  /* 賣家分類 */
  #sellerSection {
    margin-bottom: 1.8rem;
  }
  #sellerSection .section-title {
    font-weight: 600;
    font-size: 1.1rem;
    margin-bottom: 0.5rem;
  }
  #sellerSearchBox {
    margin-bottom: 0.75rem;
  }
  #sellerSearchBox input[type=search] {
    width: 100%;
    padding: 0.4rem 0.6rem;
    font-size: 1rem;
    border-radius: 5px;
    border: 1px solid #bbb;
  }
  #sellerSearchBox input[type=search]:focus {
    outline:none;
    border-color: #dc3545;
    box-shadow: 0 0 5px #dc354588;
  }
  #sellerList {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    min-height: 40px;
    max-height: 140px;
    overflow-y: auto;
    border: 1px solid #ddd;
    border-radius: 6px;
    padding: 0.25rem 0.6rem;
    background: #fafafa;
  }
  .seller-item {
    user-select: none;
    cursor: pointer;
    background: #eee;
    border-radius: 20px;
    padding: 0.4rem 0.75rem;
    font-size: 0.9rem;
    color: #333;
    border: 2px solid transparent;
    transition: background-color 0.3s, border-color 0.3s;
    white-space: nowrap;
  }
  .seller-item.selected {
    background: #dc3545;
    color: white;
    border-color: #a31b2a;
  }
  .seller-item:hover:not(.selected) {
    background-color: #f5c3c8;
  }
  .seller-item:focus {
    outline: 2px solid #a31b2a;
    outline-offset: 2px;
  }

  /* 已選標籤區 */
  #selectedPreviewSection {
    margin-bottom: 1.5rem;
  }
  #selectedPreviewSection h4 {
    margin-bottom: 0.5rem;
    font-weight: 600;
    font-size: 1.1rem;
  }
  .tag-list {
    display: flex;
    flex-wrap: wrap;
    gap: 0.45rem;
  }
  .tag {
    background: #007bff;
    color: white;
    border-radius: 20px;
    padding: 0.3rem 0.65rem;
    font-size: 0.9rem;
    display: inline-flex;
    align-items: center;
    user-select: none;
  }
  .tag.product-child {
    background: #28a745;
  }
  .tag.seller {
    background: #dc3545;
  }
  .tag button {
    border: none;
    background: none;
    color: white;
    font-weight: 700;
    font-size: 1rem;
    line-height: 1;
    padding: 0;
    margin-left: 0.4rem;
    cursor: pointer;
    user-select: none;
  }
  .tag button:focus {
    outline: 1px solid white;
    outline-offset: 2px;
  }

  /* 驗證訊息 */
  .validation-msg {
    font-size: 0.9rem;
    color: #d93025;
    min-height: 1.2em;
    margin-top: 0.2rem;
    margin-bottom: 0.8rem;
  }

  /* 響應 */
  @media (max-width: 600px) {
    #favoriteModal {
      max-width: 100vw;
      width: 100vw;
      height: 90vh;
      border-radius: 0;
      max-height: 90vh;
    }
  }

HTML(The Popup Modal):
<!-- Favorite Modal -->
    <div id="favoriteModalOverlay" aria-modal="true" role="dialog" aria-labelledby="modalTitle" style="display:none;">
        <div id="favoriteModal" role="document" tabindex="-1">
            <header id="categoryModalTitle"></header>
            <div class="modal-body">

            <!-- 已選標籤 -->
            <section id="selectedPreviewSection" aria-label="已選擇標籤預覽區">
                <h4>已選商品分類</h4>
                <div id="selectedProductChildTags" class="tag-list" aria-live="polite" aria-relevant="additions removals"></div>
                <h4 style="margin-top:1.25rem; display:none;">已選商家分類</h4>
                <div id="selectedSellerTags" class="tag-list" style="display:none;" aria-live="polite" aria-relevant="additions removals"></div>
            </section>

            <!-- 商品分類搜尋 -->
            <section id="productCategorySection" aria-label="商品類別選擇區">
                <div class="section-title">產品分類喜好</div>
                <div id="productCategorySearchWrapper" class="category-search-box">
                    <input type="search" id="productCategorySearch" placeholder="搜尋商品父類別或子類別..." aria-label="搜尋商品類別" autocomplete="off" />
                    <button type="button" id="collapseAllBtn" aria-label="全部收合商品分類清單">全部收合</button>
                </div>
                <ul id="productCategoryList" role="tree" tabindex="0" aria-multiselectable="true" aria-label="商品分類列表">
                    <!-- 動態項目 -->
                </ul>
                <div id="productValidationMsg" class="validation-msg" aria-live="assertive" role="alert"></div>
            </section>

            <!-- 商家分類 -->
            
            <section id="sellerSection" aria-label="商家類別選擇區" style="display:none;">
                <div class="section-title">商家分類喜好</div>
                <div id="sellerSearchBox" class="category-search-box">
                <input type="search" id="sellerSearchInput" placeholder="搜尋商家類別..." aria-label="搜尋商家類別" autocomplete="off" />
                </div>
                <div id="sellerList" role="listbox" aria-label="商家列表" tabindex="0">
                </div>
                <div id="sellerValidationMsg" class="validation-msg" aria-live="assertive" role="alert"></div>
            </section>
            

            </div>

            <footer style="display: flex;justify-content: end;">
            <button type="button" id="closeCatBtn">關閉</button>
            <button type="button" id="clearAllBtn">清空</button>
            <button type="button" id="saveFavoritesBtn" disabled>儲存</button>
            </footer>
        </div>
    </div>

The filter and sorting will be place on the following two pages (product list and seller list):
product list:
<?php 
global $hkdNBF;
$categories = $this->category_model->get_categories('');

// Your full products array:
$all_products = $products['product']; 

// Chunk size (number of products per batch):
$batch_size = 6;

// Chunk products into batches:
$product_batches = array_chunk($all_products, $batch_size);

// Encode batches as JSON for JS:
$product_batches_json = json_encode($product_batches);

?>
<!-- ========================= Breadcrumb Start =============================== -->
<!--
<div class="breadcrumb mb-0 py-26 bg-main-two-50">
    <div class="container container-lg">
        <div class="breadcrumb-wrapper flex-between flex-wrap gap-16">
            <h6 class="mb-0">產品列表</h6>
            <ul class="flex-align gap-8 flex-wrap">
                <li class="text-sm">
                    <a href="<?=base_url()?>" class="text-gray-900 flex-align gap-8 hover-text-main-600">
                        <i class="ph ph-house"></i>
                        主頁
                    </a>
                </li>
                <li class="flex-align">
                    <i class="ph ph-caret-right"></i>
                </li>
                <li class="text-sm text-main-600">產品列表</li>
            </ul>
        </div>
    </div>
</div>
-->
<!-- ========================= Breadcrumb End =============================== -->

<section class="shop py-20">
    <div class="container container-lg">
        <div class="row">
            <!-- Sidebar Start -->
            <div class="col-lg-3">
                <div class="shop-sidebar">
                    <?php if (isset($categories)) { ?>
                        <div class="shop-sidebar__box border border-gray-100 rounded-8 p-32 mb-32">
                            <h6 class="text-xl border-bottom border-gray-100 pb-12">產品分類</h6>
                            <div id="product-page-cat-filter" class="responsive-dropdown style-two common-dropdown nav-submenu p-0 submenus-submenu-wrapper shadow-none border border-gray-100 position-relative border-top-0" style="border: 0px !important;width:100%;height:100%;">
                                <ul class="responsive-dropdown__list scroll-sm overflow-y-auto" style="margin-top: 12px !important;">
                                    <?php $_cnt = 0; foreach($categories as $cat) { ?>
                                        <li class="has-submenus-submenu">
                                            <a href="javascript:void(0)" class="text-gray-500 text-15 py-12 px-16 flex-align gap-8 rounded-0" style="color: black !important;">
                                                <span><?=$cat['name']?></span>
                                                <span class="icon text-md d-flex ms-auto"></span>
                                            </a>
                                            <div class="submenus-submenu py-16" style="top:<?=$_cnt*48?>px !important">
                                                <h6 class="text-lg px-16 submenus-submenu__title"><?=$cat['name']?></h6>
                                                <ul class="submenus-submenu__list overflow-y-auto scroll-sm">
                                                    <?php foreach($cat['children'] as $subCat) { ?>
                                                        <li>
                                                            <a href="<?=base_url("products/category/".$subCat['slug'])?>"><?=$subCat['name']?></a>
                                                        </li>
                                                    <?php } ?>
                                                </ul>
                                            </div>
                                        </li>
                                    <?php $_cnt++; } ?>
                                </ul>
                            </div>
                        </div>
                    <?php } ?>

                    <!-- Brand Filter -->
                    <div class="shop-sidebar__box border border-gray-100 rounded-8 p-32 mb-32">
                        <h6 class="text-xl border-bottom border-gray-100 pb-24 mb-24">品牌</h6>
                        <ul class="max-h-540 overflow-y-auto scroll-sm">
                            <li class="mb-24">
                                <div class="form-check common-check common-radio">
                                    <input class="form-check-input" type="radio" name="brand" id="brand1">
                                    <label class="form-check-label" for="brand1">Apple</label>
                                </div>
                            </li>
                            <li class="mb-24">
                                <div class="form-check common-check common-radio">
                                    <input class="form-check-input" type="radio" name="brand" id="brand2">
                                    <label class="form-check-label" for="brand2">Samsung</label>
                                </div>
                            </li>
                            <li class="mb-24">
                                <div class="form-check common-check common-radio">
                                    <input class="form-check-input" type="radio" name="brand" id="brand3">
                                    <label class="form-check-label" for="brand3">Microsoft</label>
                                </div>
                            </li>
                            <li class="mb-24">
                                <div class="form-check common-check common-radio">
                                    <input class="form-check-input" type="radio" name="brand" id="brand4">
                                    <label class="form-check-label" for="brand4">HP</label>
                                </div>
                            </li>
                            <li class="mb-0">
                                <div class="form-check common-check common-radio">
                                    <input class="form-check-input" type="radio" name="brand" id="DELL">
                                    <label class="form-check-label" for="DELL">DELL</label>
                                </div>
                            </li>
                        </ul>
                    </div>
                </div>
            </div>
            <!-- Sidebar End -->

            <!-- Content Start -->
            <div class="col-lg-9">
                <div class="flex-between gap-16 flex-wrap mb-10 ">
                    <div class="position-relative flex-align gap-16 flex-wrap">
                        <div class="list-grid-btns flex-align gap-16">
                            <button type="button" class="w-44 h-44 flex-center border border-gray-100 rounded-6 text-2xl list-btn">
                                <i class="ph-bold ph-list-dashes"></i>
                            </button>
                            <button type="button" class="w-44 h-44 flex-center border border-main-600 text-white bg-main-600 rounded-6 text-2xl grid-btn">
                                <i class="ph ph-squares-four"></i>
                            </button>
                        </div>  
                        <div class="position-relative text-gray-500 flex-align gap-4 text-14">
                            <label for="product_sort_by" class="text-inherit flex-shrink-0">排序方式: </label>
                            <select class="form-control common-input px-14 py-14 text-inherit rounded-6 w-auto" id="product_sort_by">
                                <option value="" selected>預設排序</option>
                                <option value="top-rated" <?= ($this->input->get('sort') == "top-rated") ? 'selected' : '' ?>>最熱門</option>
                                <option value="date-desc" <?= ($this->input->get('sort') == "date-desc") ? 'selected' : '' ?>>最新</option>
                                <option value="price-asc" <?= ($this->input->get('sort') == "price-asc") ? 'selected' : '' ?>>價格: 低至高</option>
                                <option value="price-desc" <?= ($this->input->get('sort') == "price-desc") ? 'selected' : '' ?>>價格: 高至低</option>
                            </select>
                        </div>
                        <button type="button" class="w-44 h-44 d-lg-none d-flex flex-center border border-gray-100 rounded-6 text-2xl sidebar-btn"><i class="ph-bold ph-funnel"></i></button>
                    </div>

                </div>

                <div class="tab-content" id="pills-tabContent">
                    <div class="tab-pane fade show active" id="pills-sale" role="tabpanel" aria-labelledby="pills-sale-tab" tabindex="0">
                        <div id="new-product-list-container" class="new-product-list-container">
                            <div id="new-product-list-grid" class="new-product-list-grid" role="list" aria-label="產品列表"></div>
                            <div id="new-product-list-loading" class="new-product-list-loading" style="display:none;">載入中...</div>
                        </div>
                    </div>
                </div>
            </div>
            <!-- Content End -->
        </div>
    </div>
</section>


<script>
(() => {
  // Use the server-rendered JSON injected by PHP  
  const productBatches = <?= isset($product_batches_json) ? $product_batches_json : '[]' ?> || [];
  const batchSize = <?= isset($batch_size) ? intval($batch_size) : 6 ?>;
  let currentBatchIndex = 0;
  const productGrid = document.getElementById("new-product-list-grid");
  const loadingElem = document.getElementById("new-product-list-loading");

  function escapeHtml(text) {
    if (!text) return "";
    return String(text).replace(/[&<>"']/g, function (m) {
      return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#039;" }[m];
    });
  }

  function formatPrice(product) {
    const type = product.type;
    const minMax = product.min_max_price || {};
    let finalPriceStr = "";
    let originalPriceStr = "";
    if (type === "variable_product") {
      const minPrice = parseFloat(minMax.min_price) || 0;
      const specialPrice = parseFloat(minMax.special_price) || 0;
      let priceToUse = minPrice;
      if (specialPrice > 0 && specialPrice < minPrice) {
        priceToUse = specialPrice;
      }
      finalPriceStr = `HK$ ${priceToUse.toFixed(1)} 起`;
    } else {
      const variants = product.variants || [];
      if (variants.length > 0) {
        const sp = parseFloat(variants[0].special_price);
        const pr = parseFloat(variants[0].price);
        let finalP = sp > 0 && sp < pr ? sp : pr;
        finalPriceStr = `HK$ ${finalP.toFixed(1)}`;
        if (sp > 0 && sp !== pr) {
          originalPriceStr = `HK$ ${pr.toFixed(1)}`;
        }
      }
    }
    return { finalPriceStr, originalPriceStr };
  }

  function createStarRating(rating) {
    const maxStars = 5;
    const percent = Math.min(Math.max(rating / maxStars, 0), 1) * 100;
    return `
      <div style="display:flex; align-items:center; gap:6px; user-select:none;">
        <div class="new-product-list-stars" aria-hidden="true">
          <div class="new-product-list-stars-fill" style="width: ${percent}%;"></div>
        </div>
        <span style="font-weight:600; color:#555; font-size:12px;">${rating.toFixed(1)}</span>
      </div>
    `;
  }

  function buildProductHTML(product) {
    const nameEsc = escapeHtml(product.name);
    const slugEsc = escapeHtml(product.slug);
    const categoryEsc = escapeHtml(product.category_name);
    const sellerSlugEsc = escapeHtml(product.seller_slug);
    const storeNameEsc = escapeHtml(product.store_name);
    const imageUrlEsc = escapeHtml(product.image_sm || product.image);

    const rating = parseFloat(product.rating) || 0;
    const noOfRatings = parseInt(product.no_of_ratings) || 0;

    const prices = formatPrice(product);
    const isVariable = product.type === "variable_product";

    return `
      <article class="new-product-list-product-card" role="listitem">
        <div class="new-product-list-image-wrapper">
          <a href="${base_url}products/details/${slugEsc}" aria-label="查看商品: ${nameEsc}">
            <img src="${imageUrlEsc}" alt="${nameEsc}" loading="lazy" />
          </a>
        </div>
        <div class="new-product-list-content">
          <div class="new-product-list-tags-row">
            <div class="new-product-list-tags-wrapper" aria-label="產品標籤">
              <div class="new-product-list-tags"></div>
              <span class="new-product-list-tag-expand" role="button" tabindex="0" aria-expanded="false" title="顯示更多標籤" style="display:none;">+0</span>
            </div>
          </div>
          <div class="new-product-list-tags-extended" aria-label="更多標籤"></div>

          <h3 class="new-product-list-name">
            <a href="${base_url}products/details/${slugEsc}" title="${nameEsc}">${nameEsc}</a>
          </h3>

          <div class="new-product-list-rating" aria-label="商戶評分及評價數" tabindex="0">
            ${createStarRating(rating)}
            <span>(${noOfRatings})</span>
          </div>

          <div class="new-product-list-category" aria-label="分類">
            <a href="${base_url}products/category/${encodeURIComponent(categoryEsc)}">${categoryEsc}</a>
          </div>

          <div class="new-product-list-store" aria-label="商店">
            <a href="${base_url}sellers/seller_details/${encodeURIComponent(sellerSlugEsc)}">由${storeNameEsc}提供</a>
          </div>

          <div class="new-product-list-price-wrapper" aria-label="價格">
            ${
              !isVariable && prices.originalPriceStr
                ? `<span class="new-product-list-price-original">${prices.originalPriceStr}</span>`
                : ""
            }
            <span class="new-product-list-price-final">${prices.finalPriceStr}</span>
            ${
              product.discount_amount && parseFloat(product.discount_amount) !== 0
                ? `<div class="new-product-list-discount-tag" title="折扣百分比">最高 -${parseFloat(product.discount_percentage)}%</div>`
                : ""
            }
          </div>
        </div>
      </article>
    `;
  }

  function setupTagsOverflow(card, product) {
    const tagsRow = card.querySelector(".new-product-list-tags-row");
    const tagsContainer = card.querySelector(".new-product-list-tags");
    const extendedTagsContainer = card.querySelector(".new-product-list-tags-extended");
    const expandBtn = card.querySelector(".new-product-list-tag-expand");
    const tagsWrapper = card.querySelector(".new-product-list-tags-wrapper");

    tagsContainer.innerHTML = "";
    extendedTagsContainer.innerHTML = "";
    expandBtn.style.display = "none";
    expandBtn.setAttribute("aria-expanded", "false");
    expandBtn.textContent = "+0";

    if (!product.tags || product.tags.length === 0) {
        extendedTagsContainer.remove();
        tagsRow.remove();
        return; // no tags
    }

    // Create tag elements
    const allTagEls = product.tags.map((tag) => {
      const span = document.createElement("span");
      span.className = "new-product-list-tag";

      const a = document.createElement("a");
      a.href = base_url + "products/tags/" + encodeURIComponent(tag);
      a.title = tag;
      a.textContent = tag;

      span.appendChild(a);
      return span;
    });

    allTagEls.forEach((el) => tagsContainer.appendChild(el));

    // Use requestAnimationFrame to get correct widths after render
    requestAnimationFrame(() => {
      const containerWidth = tagsWrapper.clientWidth || 0;
      let usedWidth = 0;
      let lastFitIndex = -1;

      // Estimate space for "+N" button (~50px)
      const plusNWidthEstimate = 50;

      for (let i = 0; i < allTagEls.length; i++) {
        const tagEl = allTagEls[i];
        const style = getComputedStyle(tagEl);
        const marginLeft = parseFloat(style.marginLeft) || 0;
        const marginRight = parseFloat(style.marginRight) || 0;
        // total width includes margins
        const totalWidth = tagEl.offsetWidth + marginLeft + marginRight;
        if (usedWidth + totalWidth + plusNWidthEstimate <= containerWidth) {
          usedWidth += totalWidth;
          lastFitIndex = i;
        } else {
          break;
        }
      }

      if (lastFitIndex < allTagEls.length - 1) {
        expandBtn.style.display = "inline-flex";

        const hiddenCount = allTagEls.length - (lastFitIndex + 1);
        let isExpanded = false;

        const visibleTags = allTagEls.slice(0, lastFitIndex + 1);
        const overflowTags = allTagEls.slice(lastFitIndex + 1);

        tagsContainer.innerHTML = ""; // clear all then append visible
        visibleTags.forEach((el) => tagsContainer.appendChild(el));

        extendedTagsContainer.innerHTML = ""; // clear and append overflow tags
        overflowTags.forEach((el) => extendedTagsContainer.appendChild(el));

        expandBtn.textContent = "+" + hiddenCount;
        expandBtn.setAttribute("aria-expanded", "false");

        expandBtn.onclick = () => {
          isExpanded = !isExpanded;
          if (isExpanded) {
            extendedTagsContainer.classList.add("new-product-list-tags-expanded-visible");
            expandBtn.setAttribute("aria-expanded", "true");
            expandBtn.textContent = "隱藏";
          } else {
            extendedTagsContainer.classList.remove("new-product-list-tags-expanded-visible");
            expandBtn.setAttribute("aria-expanded", "false");
            expandBtn.textContent = "+" + hiddenCount;
          }
        };

        expandBtn.onkeydown = (e) => {
          if (e.key === "Enter" || e.key === " ") {
            e.preventDefault();
            expandBtn.onclick();
          }
        };
      } else {
        expandBtn.style.display = "none";
        extendedTagsContainer.innerHTML = "";
      }
    });
  }

  function renderBatch(index) {
    if (index >= productBatches.length) {
      currentBatchIndex = 0; // Loop back to start
      index = 0;
    }
    const batch = productBatches[index];
    if (!batch) return;

    loadingElem.style.display = 'block';

    // Build HTML
    let html = "";
    batch.forEach((product) => {
      html += buildProductHTML(product);
    });
    productGrid.insertAdjacentHTML("beforeend", html);

    // Setup tag overflow for each newly added product card
    // Newly added elements are last batchSize children
    const newCards = Array.from(productGrid.children).slice(-batch.length);
    newCards.forEach((card, i) => {
      setupTagsOverflow(card, batch[i]);
    });

    loadingElem.style.display = 'none';

    // Optional: Limit max products chunks in DOM for performance
    const maxBatchesInDOM = 5; // e.g., max 5 batches * batchSize products
    if (productGrid.children.length > maxBatchesInDOM * batchSize) {
      for (let i = 0; i < batchSize; i++) {
        if (productGrid.firstChild) productGrid.removeChild(productGrid.firstChild);
      }
    }
  }

  function onScroll() {
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 400) {
      // debounce trigger by resetting listener temporarily
      window.removeEventListener('scroll', onScroll);
      currentBatchIndex++;
      renderBatch(currentBatchIndex);
      // rebind after short delay
      setTimeout(() => {
        window.addEventListener('scroll', onScroll);
      }, 200);
    }
  }

  // Initial render
  renderBatch(currentBatchIndex);
  window.addEventListener("scroll", onScroll);
})();
</script>

seller list:
<?php 
function renderStars(float $rating, int $maxStars = 5) {
    $fullStars = floor($rating);
    $halfStar = ($rating - $fullStars) >= 0.5 ? true : false;
    $emptyStars = $maxStars - $fullStars - ($halfStar ? 1 : 0);
    $percent = floor(($rating / $maxStars) * 100);
    ob_start();
    echo '<div style="display:flex; align-items:center; gap:6px; user-select:none;">';
    echo    '<div class="new-product-list-stars" aria-hidden="true">';
    echo      '<div class="new-product-list-stars-fill" style="width: ' . $percent . '"></div>';
    echo    '</div>';
    echo    '<span style="font-weight:600; color:#555; font-size:12px;">'. number_format($rating, 2) . '</span>';
    echo '</div>';
    /*
    echo '<div class="stars" aria-label="商家評分: '.number_format($rating, 2).'顆星">';
    for ($i=0; $i<$fullStars; $i++) echo '<i class="ph-fill ph-star" aria-hidden="true"></i>';
    if ($halfStar) echo '<i class="ph ph-star-half" aria-hidden="true"></i>';
    for ($i=0; $i<$emptyStars; $i++) echo '<i class="ph ph-star" aria-hidden="true"></i>';
    echo "<span class='text-xs fw-medium text-white'>&nbsp;" . number_format($rating, 2) . '</span>';
    echo '</div>';
    */
    return ob_get_clean();
}
?>
<section class="vendor-two py-20">
    <div class="container container-lg">

        <?php if (isset($sellers) && !empty($sellers)) { ?>
        <div class="row">
            
            <div class="">
                <!-- Vendors Start -->
                <div class="list-grid-wrapper vendors-two-item-wrapper grid-cols-3">
                    <?php foreach ($sellers as $row) {
            
                    ?>
                    <div class="vendors-two-item rounded-12 overflow-hidden bg-color-three border border-neutral-50 hover-border-main-two-600 transition-2">
                        <div class="vendors-two-item__top bg-overlay style-two position-relative">
                            <div class="vendors-two-item__thumbs h-210">
                                
                                <a href="<?= base_url('sellers/seller_details/' . $row['slug']) ?>" style="width: 100%; height:100%"> 
                                    <img src="<?= $row['seller_banner'] ?>" alt="" class="cover-img"> 
                                </a>
                                
                            </div>
                            <div class="position-absolute top-0 inset-inline-start-0 w-100 h-100 p-24 z-1 d-flex flex-column justify-content-between">
                                <div class="d-flex align-items-center justify-content-between">
                                    <span class="w-80 h-80 flex-center bg-white rounded-8">
                                        <img style="width:100%;height:100%;object-fit: contain;" src="<?= $row['seller_profile'] ?>" alt="">
                                    </span>
                                    <!--<button type="button" class="text-uppercase border border-white px-16 py-8 rounded-pill text-white text-sm hover-bg-main-two-600 hover-text-white hover-border-main-two-600 transition-2">FOLLOW</button>-->
                                </div>
                                <div class="mt-8">
                                    <h6 class="text-white fw-semibold mb-8">
                                        <a href="<?= base_url('sellers/seller_details/' . $row['slug']) ?>" class=""><?= short_description_word_limit(output_escaping(str_replace('\r\n', '&#13;&#10;', strip_tags($row['store_name'])))) ?></a>
                                    </h6>
                                    
                                    <div class="flex-align gap-6">
                                        <?= renderStars($row['seller_rating']) ?>
                                    </div>
                                    
                                </div>
                                <div class="">
                                    <span class="text-xs fw-medium text-white">(<?= number_format($row['no_of_ratings']) ?> 評價)</span>
                                </div>
                            </div>
                        </div>
                        <div class="vendors-two-item__content p-12 flex-grow-1">
                            <!--
                            <div class="d-flex flex-column gap-14">
                                <div class="flex-align gap-8">
                                    <span class="flex-center text-main-two-600 text-2xl flex-shrink-0">
                                        <i class="ph ph-map-pin-line"></i>
                                    </span>
                                    <p class="text-md text-gray-900">6391 Elgin St. Celina, Delaware 10299</p>
                                </div>
                                <div class="flex-align gap-8">
                                    <span class="flex-center text-main-two-600 text-2xl flex-shrink-0">
                                        <i class="ph ph-envelope-simple"></i>
                                    </span>
                                    <a href="mailto:info@watch.com" class="text-md text-gray-900 hover-text-main-60">info@watch.com</a>
                                </div>
                                <div class="flex-align gap-8">
                                    <span class="flex-center text-main-two-600 text-2xl flex-shrink-0">
                                        <i class="ph ph-phone"></i>
                                    </span>
                                    <a href="tel:<PRESIDIO_ANONYMIZED_PHONE_NUMBER>" class="text-md text-gray-900 hover-text-main-60">083 308 1888</a>
                                </div>
                            </div>
                            -->
                            <a href="<?= base_url('sellers/seller_details/' . $row['slug']) ?>" class="btn bg-neutral-600 hover-bg-neutral-700 text-white py-12 px-24 rounded-8 flex-center gap-8 fw-medium">
                                瀏覽商家詳情
                                <span class="text-xl d-flex text-main-two-600"> <i class="ph ph-storefront"></i></span>
                            </a>
                        </div>
                    </div>

                    <?php } ?>
                </div>
                <!-- Vendors End -->
            </div>
        </div>

        <?php } else { ?>
            <div class="col-12 text-center">
                <h1 style="padding-bottom: 10px;font-size:18px;"><?= !empty($this->lang->line('no_sellers_found')) ? $this->lang->line('no_sellers_found') : '沒有相符的商家' ?></h1>
                <a style="margin-bottom: 10px;" href="<?= base_url('sellers') ?>" class="btn btn-main"><?= "前往商家總覽"?></a>
                <a style="margin-bottom: 10px;" href="<?= base_url('products') ?>" class="btn btn-main"><?= "前往產品總覽" ?></a>
            </div>
        <?php } ?>
    </div>
 </section>

For Product List, the following controller function will be called to fetch the data:
public function index()
    {
        $web_doctor_brown = get_settings('web_doctor_brown', true);
        $system_settings = get_settings('system_settings', true);

        if ((!isset($web_doctor_brown) || empty($web_doctor_brown))) {
            /* redirect him to the page where he can enter the purchase code */
            redirect(base_url("admin/purchase-code"));
        }
        if ((isset($system_settings['is_web_under_maintenance']) && $system_settings['is_web_under_maintenance'] == 1)) {
            /* redirect him to the page where he can enter the purchase code */
            redirect(base_url("maintenance"));
        }
        $this->form_validation->set_data($this->input->get(null, true));
        $this->form_validation->set_rules('category', 'Category', 'trim|xss_clean');
        $this->form_validation->set_rules('per-page', 'Per Page', 'trim|numeric|xss_clean');
        $this->form_validation->set_rules('sort', 'Sort', 'trim|xss_clean');
        $this->form_validation->set_rules('min-price', 'Min Price', 'trim|xss_clean');
        $this->form_validation->set_rules('max-price', 'Max Price', 'trim|xss_clean');

        if (!empty($_GET) && !$this->form_validation->run()) {
            redirect(base_url('products'));
        }


        $attribute_values = '';
        $attribute_names = '';
        $cat_slugs = null;
        foreach ($this->input->get(null, true) as $key => $value) {
            if (strpos($key, 'filter-') !== false) {
                if (!empty($attribute_values)) {
                    $attribute_values .= "|" . $this->input->get($key, true);
                } else {
                    $attribute_values = $this->input->get($key, true);
                }

                $key = str_replace('filter-', '', $key);
                if (!empty($attribute_names)) {
                    $attribute_names .= "|" . $key;
                } else {
                    $attribute_names = $key;
                }
            }
        }

        //get attributes ids
        $cat_slugs = explode('|', $attribute_values);
        $attribute_values = explode('|', $attribute_values);
        $attribute_names = explode('|', $attribute_names);
        
        $filter['categories'] = get_category_ids_by_slugs($cat_slugs);
        $filter['categories'] = implode(',', $filter['categories']);
        $filter['attribute_value_ids'] = get_attribute_ids_by_value($attribute_values, $attribute_names);
        $filter['attribute_value_ids'] = implode(',', $filter['attribute_value_ids']);
        
        if(empty($cat_slugs)){
            $cat_slugs = null;
        }
        
        //print_r($filter['categories']);

        $category_id = ($this->input->get('category')) ? $this->input->get('category') : null;
        
        //print_r($category_id);
        //die;
        $limit = ($this->input->get('per-page')) ? $this->input->get('per-page', true) : 12;
        $sort_by = ($this->input->get('sort')) ? $this->input->get('sort', true) : '';
        $seller_slug = (isset($_GET['seller']) && !empty($_GET['seller']) && $_GET['seller'] != "") ? $this->input->get('seller', true) : '';
        $seller_id = $seller = "";
        if (!empty($seller_slug)) {
            $seller = fetch_details("seller_data", ['slug' => $seller_slug], "user_id,store_name");
            $seller_id = (!empty($seller) && isset($seller[0]['user_id'])) ? $seller[0]['user_id'] : "";
        }

        $brand_slug = (isset($_GET['brand']) && !empty($_GET['brand']) && $_GET['brand'] != "") ? $this->input->get('brand', true) : '';
        if (!empty($brand_slug)) {
            $brand = fetch_details('brands', ['slug' => $brand_slug]);
            $filter['brand'] =  $brand[0]['name'];
        }

        $category_slug = (isset($_GET['category']) && !empty($_GET['category']) && $_GET['category'] != "") ? $this->input->get('category', true) : '';
        
        // print_r($category_slug);
        // die;


        $offset =  0;
        $sort = 'row_order';
        $order =  'ASC';
        $brands = $this->brand_model->get_brands('', $limit = NULL, $offset, $sort, $order, 'false');

        $products = fetch_product(null, $filter, null, $category_slug, $limit = NULL, $offset, $sort, $order);
        //print_r($products);
        // echo "<pre>";
        // print_r($brands);
        // die;


        // $limit = ($this->input->get('per-page')) ? $this->input->get('per-page', true) : 12;
        // $brands = array();
        // for ($i = 0; $i < count($products['product']); $i++) {
        //     if (!empty($products['product'][$i]['brand'])) {
        //         // echo "<pre>";
        //         $brand = explode(',', $products['product'][$i]['brand']);
        //         $brands = array_values(array_unique(array_merge($brands, $brand)));
        //     }
        // }

        $category = $this->category_model->get_categories(
            '',
            null,
            $offset = 0,
            $sort = null,
            $order = null,
            $has_child_or_item = 'true'
        );

        if (!empty($category_id)) {
            $category_id = explode('|', $category_id);
        }
        $user_id = NULL;
        if ($this->data['is_logged_in']) {
            $user_id = $this->data['user']->id;
        }

        //Product Sorting
        $sort = '';
        $order = '';
        $filter['search'] =  null;
        if ($sort_by == "top-rated") {
            $filter['product_type'] = "top_rated_product_including_all_products";
        } elseif ($sort_by == "date-desc") {
            $sort = 'pv.date_added';
            $order = 'desc';
        } elseif ($sort_by == "date-asc") {
            $sort = 'pv.date_added';
            $order = 'asc';
        } elseif ($sort_by == "price-asc") {
            $sort = 'pv.price';
            $order = 'ASC';
        } elseif ($sort_by == "price-desc") {
            $sort = 'pv.price';
            $order = 'DESC';
        }
        $total_rows = fetch_product(null, $filter, null, $category_id, null, null, null, null, TRUE, NULL, $seller_id);

        $theme = fetch_details('themes', ['status' => 1], 'name');
        // print_r($filter);
        // die;
        $limit = 6;//($this->input->get('per-page')) ? $this->input->get('per-page', true) : 12;
        $config['base_url'] = base_url('products');
        $config['total_rows'] = $total_rows;
        $config['per_page'] = $limit;
        $config['num_links'] = 3;
        $config['use_page_numbers'] = TRUE;
        $config['reuse_query_string'] = TRUE;
        $config['page_query_string'] = FALSE;

        $config['attributes'] = array('class' => 'page-link');
        $config['full_tag_open'] = '<ul class="pagination justify-content-center">';
        $config['full_tag_close'] = '</ul>';

        if (isset($theme[0]['name']) && strtolower($theme[0]['name']) == 'modern') {

            $config['prev_tag_open'] = '<li class="page-item">';
            $config['prev_link'] = '<i class="uil uil-arrow-left"></i>';
            $config['prev_tag_close'] = '</li>';

            $config['next_tag_open'] = '<li class="page-item">';
            $config['next_link'] = '<i class="uil uil-arrow-right"></i>';
            $config['next_tag_close'] = '</li>';
        } else {
            $config['first_tag_open'] = '<li class="page-item">';
            $config['first_link'] = '第一頁';
            $config['first_tag_close'] = '</li>';

            $config['last_tag_open'] = '<li class="page-item">';
            $config['last_link'] = '最後一頁';
            $config['last_tag_close'] = '</li>';

            $config['prev_tag_open'] = '<li class="page-item">';
            $config['prev_link'] = '<i class="fa fa-arrow-left"></i>';
            $config['prev_tag_close'] = '</li>';

            $config['next_tag_open'] = '<li class="page-item">';
            $config['next_link'] = '<i class="fa fa-arrow-right"></i>';
            $config['next_tag_close'] = '</li>';
        }

        $config['cur_tag_open'] = '<li class="page-item active disabled"><a class="page-link">';
        $config['cur_tag_close'] = '</a></li>';

        $config['num_tag_open'] = '<li class="page-item">';
        $config['num_tag_close'] = '</li>';

        $page_no = (empty($this->uri->segment(2))) ? 1 : $this->uri->segment(2);
        if (!is_numeric($page_no)) {
            redirect(base_url('products'));
        }
        $offset = ($page_no - 1) * $limit;
        // print_r($offset);
        $this->pagination->initialize($config);
        $this->data['links'] =  $this->pagination->create_links();
        $this->data['main_page'] = 'product-listing';
        $this->data['title'] = '產品列表 | ' . $this->data['web_settings']['site_title'];
        $this->data['seller'] = $seller;
        $this->data['brands'] = (isset($brands)) ? (json_encode($brands)) : "";
        $this->data['categories'] = (isset($category)) ? (json_encode($category)) : "";
        $this->data['sub_categories'] = (isset($sub_category)) ? ($sub_category) : [];
        $this->data['keywords'] = '產品列表, ' . $this->data['web_settings']['meta_keywords'];
        $this->data['description'] = '產品列表 | ' . $this->data['web_settings']['meta_description'];
        $this->data['products'] = fetch_product($user_id, $filter, null, $category_id, null, null, $sort, $order, NULL, NULL, $seller_id);


        $this->data['filters'] = (isset($this->data['products']['filters'])) ? json_encode($this->data['products']['filters']) : "";
        // echo "<pre>";
        //         print_r($brands);
        //         die;
        $this->data['filters_key'] = 'all_products_listing';
        $this->data['page_main_bread_crumb'] = "Product Listing";
        $this->load->view('front-end/' . THEME . '/template', $this->data);
    }

and for seller list:
    public function index()
    {
        $web_doctor_brown = get_settings('web_doctor_brown', true);
        $system_settings = get_settings('system_settings', true);

        if ((!isset($web_doctor_brown) || empty($web_doctor_brown))) {
            /* redirect him to the page where he can enter the purchase code */
            redirect(base_url("admin/purchase-code"));
        }
        if ((isset($system_settings['is_web_under_maintenance']) && $system_settings['is_web_under_maintenance'] == 1)) {
            /* redirect him to the page where he can enter the purchase code */
            redirect(base_url("maintenance"));
        }
        $this->form_validation->set_data($this->input->get(null, true));
        $this->form_validation->set_rules('per-page', 'Per Page', 'trim|numeric|xss_clean');

        if (!empty($_GET) && !$this->form_validation->run()) {
            redirect(base_url('sellers'));
        }
        $sellers = $this->Seller_model->get_sellers();
        $limit = ($this->input->get('per-page')) ? $this->input->get('per-page', true) : 12;
        $sort_by = ($this->input->get('sort')) ? $this->input->get('sort', true) : '';
        $seller_search = ($this->input->get('search')) ? $this->input->get('search', true) : '';

        $category = $this->category_model->get_categories(
            '',
            null,
            $offset = 0,
            $sort = null,
            $order = null,
            $has_child_or_item = 'true'
        );

        if (!empty($category_id)) {
            $category_id = explode('|', $category_id);
        }
        

        //Seller Sorting
        $sort = $order = '';
        if ($sort_by == "top-rated") {
            $sort = 'rating';
            $order = 'DESC';
        } elseif ($sort_by == "date-desc") {
            $sort = 'u.id';
            $order = 'desc';
        } elseif ($sort_by == "date-asc") {
            $sort = 'u.id';
            $order = 'asc';
        }

        $config['base_url'] = base_url('sellers');
        $config['total_rows'] = $sellers['total'];
        $config['per_page'] = $limit;
        $config['num_links'] = 7;
        $config['use_page_numbers'] = TRUE;
        $config['reuse_query_string'] = TRUE;
        $config['page_query_string'] = FALSE;

        $config['attributes'] = array('class' => 'page-link');
        $config['full_tag_open'] = '<ul class="pagination justify-content-center">';
        $config['full_tag_close'] = '</ul>';

        $config['first_tag_open'] = '<li class="page-item">';
        $config['first_link'] = 'First';
        $config['first_tag_close'] = '</li>';

        $config['last_tag_open'] = '<li class="page-item">';
        $config['last_link'] = 'Last';
        $config['last_tag_close'] = '</li>';

        $config['prev_tag_open'] = '<li class="page-item">';
        $config['prev_link'] = '<i class="fa fa-arrow-left"></i>';
        $config['prev_tag_close'] = '</li>';

        $config['next_tag_open'] = '<li class="page-item">';
        $config['next_link'] = '<i class="fa fa-arrow-right"></i>';
        $config['next_tag_close'] = '</li>';

        $config['cur_tag_open'] = '<li class="page-item active"><a class="page-link">';
        $config['cur_tag_close'] = '</a></li>';

        $config['num_tag_open'] = '<li class="page-item">';
        $config['num_tag_close'] = '</li>';
        $page_no = (empty($this->uri->segment(2))) ? 1 : $this->uri->segment(2);
        if (!is_numeric($page_no)) {
            redirect(base_url('sellers'));
        }
        $offset = ($page_no - 1) * $limit;
        
        $this->pagination->initialize($config);
        $this->data['links'] =  $this->pagination->create_links();
        

        $this->data['main_page'] = 'seller-listing';
        $this->data['title'] = '商家列表 | ' . $this->data['web_settings']['site_title'];
        $this->data['keywords'] = '商家列表, ' . $this->data['web_settings']['meta_keywords'];
        $this->data['description'] = '商家列表 | ' . $this->data['web_settings']['meta_description'];
        $this->data['seller_search'] = $seller_search;
        $sellers = $this->Seller_model->get_sellers_for_search("", null, null, $sort, $order, $seller_search);
        $this->data['sellers'] = $sellers['data'];
        $this->data['categories'] = (isset($category)) ? (json_encode($category)) : "";
        $this->data['page_main_bread_crumb'] = "Seller Listing";
        $this->load->view('front-end/' . THEME . '/template', $this->data);
    }

Please help to modify the code from reference to make the filter and sorting for product list and seller list.