I am developing the online web shop with codeigniter and design to make the product grid display to show the list of products in home page.
Here are the requirements:
1. Responsive Layout for pc, tablet and mobile.
2. each grid of product should show the name, image, category_name, rating with no of rating, tags, original price, special price (aka discounted price) of product and store name
3. just a suggestion, display 2 column each row for mobile, 3 for tablet and 6 for pc (prefer that adjustable based on the device width automatically)
4. tags of product maybe too long and no space to show all, so there should hide the exceeded tag by default and allow user to read more by hover or click on the + sign
5. name of product maybe also too long and split to two or more line to show, so limit the display length of name, maximun two row only.
6. for the final price display of variant product, show the min_price or special_price (Whichever is lower) with postfix "起", example of display: HK$ 120.0 起
7. for the final price display of single product, show the special_price without postfix, example of display: HK$ 120.0
8. format the price with prefix "HK$" and show to 1 decimal digit, example of display: HK$ 100.0
9. display the discount_percentage like a tag with "最高-" at the price below (if discount_amount not = 0), example of display: 最高 -5%
10. user can click on any tag, category and store name to browse the related page:
10.1: for tag: products/tags/<tag_name>
10.2: for category: products/category/<category_name>
10.3: for seller: sellers/seller_details/<seller_slug>

There are two type of product in shop, one is single product (the product without any variant/spec), another one is variant product (the product with spec selection)
Example of Data of Single Product from Server:
{
            "total": "1",
            "sales": "1",
            "stock_type": "",
            "is_prices_inclusive_tax": "0",
            "type": "simple_product",
            "attr_value_ids": "",
            "seller_rating": "0.0",
            "seller_slug": "閃互-flash-mutual",
            "seller_no_of_ratings": "0",
            "seller_profile": "https://yymart.com.hk/uploads/seller/flash-mutual-logo-uzdG3sf7.webp",
            "store_name": "閃互 Flash Mutual",
            "store_description": "閃互科技（國際）是一間專注於通過科技改善老年人生活質量的投資服務型公司。我們的核心目標是搭建一個多層次的安全守護網絡，通過科技與社會資源的結合，希望為長者創造一個更安全、更溫暖的生活環境，守護他們的幸福晚年。",
            "seller_id": "75",
            "seller_name": null,
            "id": "32",
            "stock": "",
            "name": "閃互「銀齡守護卡」：長者智慧守護卡",
            "category_id": "89",
            "short_description": "閃互「銀齡守護卡」溶合騰訊科技，配合GPS+北斗雙模定位系統，能在1米範圍內精確定位使用者位置。當緊急情況發生時，簡單的一個按鈕就能連通24小時待命的支援團隊能在平均20秒內作出回應。在香港，澳門及中國的中移動覆蓋的網路支援下，無論您的家人在茶餐廳閑坐還是在內地探親，守護從不間斷。",
            "slug": "閃互銀齡守護卡長者智慧守護卡-1",
            "description": "Example",
            "extra_description": "",
            "total_allowed_quantity": "",
            "status": "1",
            "deliverable_type": "1",
            "is_attachment_required": "0",
            "deliverable_zipcodes": "",
            "minimum_order_quantity": "1",
            "sku": "",
            "quantity_step_size": "1",
            "cod_allowed": "0",
            "row_order": "0",
            "rating": "0.00",
            "no_of_ratings": "0",
            "image": "https://yymart.com.hk/uploads/media/2025/銀齡守護卡_YY_Mart_Product_Thumbnail.webp",
            "is_returnable": "0",
            "is_cancelable": "0",
            "cancelable_till": "",
            "indicator": "0",
            "other_images": [],
            "video_type": "youtube",
            "video": "https://youtu.be/Do3IZvCvscw?si=1DL4Zosl9sXZmqzm",
            "tags": [
                "銀髮",
                "銀齡守護卡"
            ],
            "warranty_period": "",
            "guarantee_period": "",
            "made_in": "",
            "hsn_code": "",
            "download_allowed": "",
            "download_type": "",
            "download_link": "",
            "pickup_location": "",
            "brand": "",
            "availability": "",
            "product_slug": "閃互銀齡守護卡長者智慧守護卡-1",
            "brand_slug": null,
            "category_name": "輔助器材",
            "tax_percentage": "0",
            "tax_id": "0",
            "review_images": [],
            "attributes": [],
            "variants": [
                {
                    "id": "40",
                    "product_id": "32",
                    "attribute_value_ids": "",
                    "attribute_set": "",
                    "price": "3312",
                    "special_price": "0",
                    "sku": "",
                    "stock": "",
                    "weight": "0.01",
                    "height": "0",
                    "breadth": "0",
                    "length": "0",
                    "images": [],
                    "availability": "",
                    "exredeem_id": "",
                    "status": "1",
                    "date_added": "2025-07-02 23:14:40",
                    "variant_ids": "",
                    "attr_name": "",
                    "variant_values": "",
                    "swatche_type": "0",
                    "swatche_value": "0",
                    "images_md": [],
                    "images_sm": [],
                    "variant_relative_path": [],
                    "cart_count": "0"
                }
            ],
            "total_stock": "",
            "min_max_price": {
                "min_price": 3312,
                "max_price": 3312,
                "special_price": 0,
                "max_special_price": 0,
                "discount_in_percentage": 100
            },
            "relative_path": "uploads/media/2025/銀齡守護卡_YY_Mart_Product_Thumbnail.webp",
            "other_images_relative_path": [],
            "video_relative_path": "https://youtu.be/Do3IZvCvscw?si=1DL4Zosl9sXZmqzm",
            "total_product": "1",
            "deliverable_zipcodes_ids": "",
            "is_deliverable": true,
            "is_purchased": false,
            "discount_amount": 0,
            "discount_percentage": 0,
            "is_favorite": "0",
            "image_md": "https://yymart.com.hk/uploads/media/2025/銀齡守護卡_YY_Mart_Product_Thumbnail.webp",
            "image_sm": "https://yymart.com.hk/uploads/media/2025/銀齡守護卡_YY_Mart_Product_Thumbnail.webp",
            "other_images_sm": [],
            "other_images_md": [],
            "variant_attributes": []
        }
Example of the Data of Variant Product from Server:
{
            "total": "1",
            "sales": "4",
            "stock_type": "2",
            "is_prices_inclusive_tax": "0",
            "type": "variable_product",
            "attr_value_ids": "1,4,8,10",
            "seller_rating": "4.0",
            "seller_slug": "網上商店",
            "seller_no_of_ratings": "1",
            "seller_profile": "https://yymart.com.hk/uploads/seller/YYMART_Square_Black.webp",
            "store_name": "網上商店",
            "store_description": "",
            "seller_id": "2",
            "seller_name": "網上商店",
            "id": "14",
            "stock": "",
            "name": "Variant Product",
            "category_id": "12",
            "short_description": "For Test only",
            "slug": "variant-product-1",
            "description": "<p>Testing</p>",
            "extra_description": "",
            "total_allowed_quantity": "",
            "status": "1",
            "deliverable_type": "1",
            "is_attachment_required": "0",
            "deliverable_zipcodes": "",
            "minimum_order_quantity": "1",
            "sku": "",
            "quantity_step_size": "1",
            "cod_allowed": "0",
            "row_order": "0",
            "rating": "0.00",
            "no_of_ratings": "0",
            "image": "https://yymart.com.hk/uploads/media/2025/temp_product.png",
            "is_returnable": "0",
            "is_cancelable": "0",
            "cancelable_till": "",
            "indicator": "0",
            "other_images": [],
            "video_type": "",
            "video": "",
            "tags": [],
            "warranty_period": "",
            "guarantee_period": "",
            "made_in": "",
            "hsn_code": "",
            "download_allowed": "",
            "download_type": "",
            "download_link": "",
            "pickup_location": "",
            "brand": "APPLE",
            "availability": 1,
            "product_slug": "variant-product-1",
            "brand_slug": "apple",
            "category_name": "運動手環",
            "tax_percentage": "0",
            "tax_id": "0",
            "review_images": [],
            "attributes": [
                {
                    "ids": "8,10",
                    "value": " 256GB, 1TB",
                    "attr_name": "容量",
                    "name": "容量",
                    "swatche_type": "0,0",
                    "swatche_value": "0,0"
                },
                {
                    "ids": "1,4",
                    "value": " 100, 400",
                    "attr_name": "面額",
                    "name": "面額",
                    "swatche_type": "0,0",
                    "swatche_value": "0,0"
                }
            ],
            "variants": [
                {
                    "id": "19",
                    "product_id": "14",
                    "attribute_value_ids": "1,8",
                    "attribute_set": "",
                    "price": "3000",
                    "special_price": "2800",
                    "sku": "T001",
                    "stock": "5",
                    "weight": "0",
                    "height": "0",
                    "breadth": "0",
                    "length": "0",
                    "images": [],
                    "availability": "1",
                    "exredeem_id": "",
                    "status": "1",
                    "date_added": "2025-06-25 03:23:55",
                    "variant_ids": "1,8",
                    "attr_name": " 面額, 容量",
                    "variant_values": "100,256GB",
                    "swatche_type": "0,0",
                    "swatche_value": "0,0",
                    "images_md": [],
                    "images_sm": [],
                    "variant_relative_path": [],
                    "cart_count": "0"
                },
                {
                    "id": "20",
                    "product_id": "14",
                    "attribute_value_ids": "1,10",
                    "attribute_set": "",
                    "price": "8500",
                    "special_price": "7000",
                    "sku": "T002",
                    "stock": "5",
                    "weight": "0",
                    "height": "0",
                    "breadth": "0",
                    "length": "0",
                    "images": [],
                    "availability": "1",
                    "exredeem_id": "",
                    "status": "1",
                    "date_added": "2025-06-25 03:23:55",
                    "variant_ids": "1,10",
                    "attr_name": " 面額, 容量",
                    "variant_values": "100,1TB",
                    "swatche_type": "0,0",
                    "swatche_value": "0,0",
                    "images_md": [],
                    "images_sm": [],
                    "variant_relative_path": [],
                    "cart_count": "0"
                },
                {
                    "id": "21",
                    "product_id": "14",
                    "attribute_value_ids": "4,8",
                    "attribute_set": "",
                    "price": "6000",
                    "special_price": "5500",
                    "sku": "T003",
                    "stock": "5",
                    "weight": "0",
                    "height": "0",
                    "breadth": "0",
                    "length": "0",
                    "images": [],
                    "availability": "1",
                    "exredeem_id": "",
                    "status": "1",
                    "date_added": "2025-06-25 03:23:55",
                    "variant_ids": "4,8",
                    "attr_name": " 面額, 容量",
                    "variant_values": "400,256GB",
                    "swatche_type": "0,0",
                    "swatche_value": "0,0",
                    "images_md": [],
                    "images_sm": [],
                    "variant_relative_path": [],
                    "cart_count": "0"
                },
                {
                    "id": "22",
                    "product_id": "14",
                    "attribute_value_ids": "4,10",
                    "attribute_set": "",
                    "price": "13000",
                    "special_price": "10000",
                    "sku": "T004",
                    "stock": "5",
                    "weight": "0",
                    "height": "0",
                    "breadth": "0",
                    "length": "0",
                    "images": [],
                    "availability": "1",
                    "exredeem_id": "",
                    "status": "1",
                    "date_added": "2025-06-25 03:23:55",
                    "variant_ids": "4,10",
                    "attr_name": " 面額, 容量",
                    "variant_values": "400,1TB",
                    "swatche_type": "0,0",
                    "swatche_value": "0,0",
                    "images_md": [],
                    "images_sm": [],
                    "variant_relative_path": [],
                    "cart_count": "0"
                }
            ],
            "total_stock": 20,
            "min_max_price": {
                "min_price": 3000,
                "max_price": 13000,
                "special_price": 2800,
                "max_special_price": 10000,
                "discount_in_percentage": 6
            },
            "relative_path": "uploads/media/2025/temp_product.png",
            "other_images_relative_path": [],
            "video_relative_path": "",
            "total_product": "12",
            "deliverable_zipcodes_ids": "",
            "is_deliverable": true,
            "is_purchased": false,
            "discount_amount": 3000,
            "discount_percentage": 23,
            "is_favorite": "0",
            "image_md": "https://yymart.com.hk/uploads/media/2025/thumb-md/temp_product.png",
            "image_sm": "https://yymart.com.hk/uploads/media/2025/thumb-sm/temp_product.png",
            "other_images_sm": [],
            "other_images_md": [],
            "variant_attributes": []
        }


For reference, I have following javascript for the infinite load more function:
<script>
const productBatches = <?= $product_batches_json ?>; // array of arrays of products
const batchSize = <?= $batch_size ?>;
let currentBatchIndex = 0;

console.log(productBatches);

const productListElem = document.getElementById('product-list');
const loadingElem = document.getElementById('loading');
loadingElem.style.display = 'none';

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

function formatCurrency(value) {
    return 'HKD ' + Number(value).toFixed(2);
}

function buildProductHTML(product_row) {
    const variant_price = (product_row.variants[0].special_price > 0 && product_row.variants[0].special_price !== '') 
        ? product_row.variants[0].special_price 
        : product_row.variants[0].price;
    const special_price = product_row.variants[0].special_price;
    const price = product_row.variants[0].price;

    const name = escapeHtml(product_row.name);
    const category_name = escapeHtml(product_row.category_name);
    const seller_slug = escapeHtml(product_row.seller_slug);
    const store_name = escapeHtml(product_row.store_name);
    const slug = escapeHtml(product_row.slug);
    const image_sm = escapeHtml(product_row.image_sm);

    const tags_html = product_row.tags.map(tag => {
        const t = escapeHtml(tag);
        return `<span class="py-2 px-8 text-xs rounded-pill text-main-two-600 bg-main-two-50"><a href="<?= base_url('products/tags/') ?>${t}">${t}</a></span>`;
    }).join(' ');

    return `
    <div class="col-6 col-md-3 col-lg-2 product-grid" data-aos="fade-up" data-aos-duration="200">
        <div class="product-card h-100 rounded-16 position-relative transition-2" style="padding: 0px !important;">
            <div class="product-card__thumb rounded-8 position-relative">
                <a href="<?= base_url() ?>products/details/${slug}" class="w-100 h-100 flex-center"> 
                    <img src="${image_sm}" alt="${name}" class="w-auto rounded-8">
                </a>
            </div>
            <div class="product-card__content w-100">
                <div class="product-card-tags">
                    ${tags_html}
                </div>
                <h6 class="title text-lg fw-semibold" style="margin-bottom: 0px !important;">
                    <a href="<?= base_url() ?>products/details/${slug}" class="link text-line-2" tabindex="0">${name}</a>
                </h6>

                <span class="py-2 px-8 text-xs rounded-pill text-main-two-600 bg-main-two-50 mt-4">
                    <a href="<?= base_url() ?>products/category/${category_name}">${category_name}</a>
                </span>

                <span class="py-2 px-8 text-xs rounded-pill text-main-two-600 bg-main-two-50 mt-4">
                    <a href="<?= base_url() ?>sellers/seller_details/${seller_slug}">由${store_name}提供</a>
                </span>

                <div class="product-card__price mt-4 mb-4">
                    ${special_price !== '' && special_price > 0 && special_price !== price ? `<span class="text-gray-400 text-md fw-semibold text-decoration-line-through">${formatCurrency(price)}</span>` : ''}
                    <span class="text-heading text-md fw-semibold">${formatCurrency(variant_price)}</span>
                </div>
            </div>
        </div>
    </div>
    `;
}

function renderBatch(index) {
    if (index >= productBatches.length) {
        currentBatchIndex = 0; // Loop back to first batch
        index = 0;
    }
    const batch = productBatches[index];
    let html = '';
    batch.forEach(item => {
        html += buildProductHTML(item);
    });
    productListElem.insertAdjacentHTML('beforeend', html);

    // Optional: limit max products chunks to avoid too many nodes for performance
    const maxBatchesInDOM = 5; // e.g. max 5 batches ~ 30 products
    if(productListElem.children.length > maxBatchesInDOM * batchSize) {
        // Remove oldest batch:
        for(let i = 0; i < batchSize; i++) {
            if(productListElem.firstChild) productListElem.removeChild(productListElem.firstChild);
        }
    }
}

function onScroll() {
    if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 400) {
        currentBatchIndex++;
        renderBatch(currentBatchIndex);
    }
}

// Initial render first batch
renderBatch(currentBatchIndex);
window.addEventListener('scroll', onScroll);
</script>

and the products data from server will be handled by following code to split the products with the batch size for load more function:
<?php
// Your full product array
$all_products = $this->data['products']['product'];

// Chunk size
$batch_size = 6;
$product_batches = array_chunk($all_products, $batch_size);

// Convert batches to JSON and safely pass to JS
$product_batches_json = json_encode($product_batches);
?>

And the following html template is the updated design of product grid and need to replace the current one:
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Product Grid Demo - Decimal Stars + Overflow Tags</title>
<style>
  /* Container */
  .new-product-list-container {
    width: 100%;
    padding: 10px;
    box-sizing: border-box;
    background: #f9f9f9;
    min-height: 100vh;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
      Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
  }

  /* Grid */
  .new-product-list-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 12px 10px;
    align-items: stretch;
  }

  @media (min-width: 600px) {
    .new-product-list-grid {
      grid-template-columns: repeat(3, 1fr);
    }
  }

  @media (min-width: 992px) {
    .new-product-list-grid {
      grid-template-columns: repeat(6, 1fr);
    }
  }

  /* Product Card */
  .new-product-list-product-card {
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 0 6px rgba(0,0,0,0.05);
    display: flex;
    flex-direction: column;
    min-height: 320px;
    transition: box-shadow 0.25s ease;
    overflow: hidden;
  }
  .new-product-list-product-card:hover {
    box-shadow: 0 6px 15px rgba(0,0,0,0.12);
  }

  /* Image wrapper */
  .new-product-list-image-wrapper {
    position: relative;
    width: 100%;
    padding-top: 100%; /* square */
    overflow: hidden;
    border-top-left-radius: 12px;
    border-top-right-radius: 12px;
    flex-shrink: 0;
  }
  .new-product-list-image-wrapper img {
    position: absolute;
    top: 50%;
    left: 50%;
    max-width: 90%;
    max-height: 90%;
    transform: translate(-50%, -50%);
    object-fit: contain;
    border-radius: 12px;
  }

  /* Content */
  .new-product-list-content {
    padding: 8px 12px 12px;
    display: flex;
    flex-direction: column;
    flex-grow: 1;
    min-height: 0;
    overflow: visible;
  }

  /* Tags Container +N Flex + scrollable tags wrapper */
  .new-product-list-tags-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 6px;
  }

  .new-product-list-tags-wrapper {
    overflow-x: auto;
    flex-grow: 1;
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none;  /* IE 10+ */
    scroll-behavior: smooth;
  }
  .new-product-list-tags-wrapper::-webkit-scrollbar {
    display: none;
  }
  .new-product-list-tags {
    display: inline-flex;
    gap: 6px;
    white-space: nowrap;
  }

  /* Visible tags - same style */
  .new-product-list-tag {
    font-size: 10px;
    padding: 4px 9px;
    border-radius: 6px;
    background-color: #e3f3ff;
    color: #1a73e8;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    max-width: 100px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    line-height: 1;
    transition: background-color 0.2s ease;
  }
  .new-product-list-tag:hover,
  .new-product-list-tag a:hover {
    background-color: #c2dafb;
  }
  .new-product-list-tag a {
    color: inherit;
    text-decoration: none;
    display: block;
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* +N tag */
  .new-product-list-tag-expand {
    font-size: 10px;
    padding: 4px 9px;
    border-radius: 6px;
    background-color: #e3f3ff;
    color: #1a73e8;
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
    max-width: 100px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    line-height: 1;
    transition: background-color 0.2s ease;
  }


  /* Extended tags container */
  .new-product-list-tags-extended {
    margin-top: 6px;
    display: none;
    flex-wrap: wrap;
    gap: 6px;
    overflow: visible;
  }
  .new-product-list-tags-extended.new-product-list-tags-expanded-visible {
    display: flex !important;
  }
  .new-product-list-tags-extended .new-product-list-tag {
    max-width: none;
    white-space: normal;
    text-overflow: clip;
    padding: 4px 9px;
    background-color: #e3f3ff;
    border-radius: 6px;
    color: #1a73e8;
    cursor: pointer;
    user-select: none;
  }
  .new-product-list-tags-extended .new-product-list-tag a {
    white-space: normal;
    overflow: visible;
  }

  /* Product name */
  .new-product-list-name {
    font-weight: 600;
    font-size: 14px;
    line-height: 1.3;
    margin: 6px 0 8px;
    color: #222;
    overflow: hidden;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    word-break: break-word;
    text-overflow: ellipsis;
    cursor: pointer;
  }
  .new-product-list-name a {
    color: inherit;
    text-decoration: none;
  }
  .new-product-list-name a:hover {
    text-decoration: underline;
  }

  /* Category and Store with 6px radius */
  .new-product-list-category,
  .new-product-list-store,
  .new-product-list-discount-tag {
    font-size: 11px;
    margin: 2px 0 4px;
    padding: 5px 12px;
    border-radius: 6px;
    background-color: #f0f7ff;
    color: #2979ff;
    max-width: fit-content;
    cursor: pointer;
    white-space: nowrap;
  }
  .new-product-list-category a,
  .new-product-list-store a {
    color: inherit;
    text-decoration: none;
  }
  .new-product-list-category a:hover,
  .new-product-list-store a:hover {
    text-decoration: underline;
  }

  /* Discount tag */
  .new-product-list-discount-tag {
    background-color: #ffe6e6;
    color: #d32f2f;
    font-weight: 700;
    font-size: 11px;
    padding: 2px 8px;
    margin-top: 4px;
    user-select: none;
    white-space: nowrap;
  }

  /* Rating */
  .new-product-list-rating {
    font-size: 12px;
    color: #f5a623;
    margin: 6px 0 8px;
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    user-select: none;
  }

  /* Stars container with fixed width */
  .new-product-list-stars {
    position: relative;
    display: inline-block;
    width: 70px;  /* allow 5 stars * (14px + gap) */
    height: 14px;
    background: url('data:image/svg+xml;utf8,<svg fill="%23ddd" height="14" viewBox="0 0 20 20" width="14" xmlns="http://www.w3.org/2000/svg"><path d="M10 15l-5.878 3.09 1.123-6.545L.49 6.91l6.572-.955L10 0l2.938 5.955 6.572.955-4.755 4.635 1.123 6.545z"/></svg>') repeat-x 0 0 / 14px 14px;
  }
  .new-product-list-stars-fill {
    position: absolute;
    top: 0;
    left: 0;
    height: 14px;
    background: url('data:image/svg+xml;utf8,<svg fill="%23f5a623" height="14" viewBox="0 0 20 20" width="14" xmlns="http://www.w3.org/2000/svg"><path d="M10 15l-5.878 3.09 1.123-6.545L.49 6.91l6.572-.955L10 0l2.938 5.955 6.572.955-4.755 4.635 1.123 6.545z"/></svg>') repeat-x 0 0 / 14px 14px;
    pointer-events:none;
  }

  /* Price wrapper */
  .new-product-list-price-wrapper {
    margin-top: auto;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  /* Original price smaller */
  .new-product-list-price-original {
    font-size: 12px;
    color: #aaa;
    text-decoration: line-through;
    line-height: 1;
  }

  /* Final price - bold red */
  .new-product-list-price-final {
    font-size: 18px;
    font-weight: 800;
    color: #d9534f;
    line-height: 1.2;
  }

  /* Loading */
  .new-product-list-loading {
    padding: 16px;
    font-size: 16px;
    font-weight: 600;
    text-align: center;
    color: #666;
    user-select: none;
  }

  /* Hide scrollbar for tags scroll on Webkit */
  .new-product-list-tags-wrapper::-webkit-scrollbar {
    display: none;
  }
</style>
</head>
<body>
<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>

<script>
(function () {
  const baseUrl = "https://example.com/"; // Update your base URL here

  const productBatches = [
    [
      {
        total: "1",
        type: "simple_product",
        seller_rating: "4.3",
        seller_slug: "seller-one",
        seller_no_of_ratings: "12",
        store_name: "賣家一號",
        id: "1",
        name: "簡單商品 - 長名稱測試用，看看截斷是否正常運作",
        category_name: "類別A",
        slug: "simple-product-1",
        image_sm: "https://via.placeholder.com/150?text=Prod1",
        rating: "4.3",
        no_of_ratings: "12",
        tags: [
          "標籤一",
          "非常長的標籤名稱二",
          "標籤三",
          "標籤四",
          "標籤五",
          "超出標籤六",
          "更多標籤七"
        ],
        discount_amount: 120,
        discount_percentage: 20,
        variants: [{ special_price: "80", price: "100" }],
        min_max_price: {
          min_price: 100,
          max_price: 100,
          special_price: 80,
          max_special_price: 80,
          discount_in_percentage: 20,
        },
      },
      {
        total: "1",
        type: "variable_product",
        seller_rating: "3.7",
        seller_slug: "seller-two",
        seller_no_of_ratings: "20",
        store_name: "賣家二號",
        id: "2",
        name: "多變體產品範例",
        category_name: "類別B",
        slug: "variable-product-1",
        image_sm: "https://via.placeholder.com/150?text=Prod2",
        rating: "3.7",
        no_of_ratings: "20",
        tags: ["熱門", "新上市", "促銷", "熱門"],
        discount_amount: 50,
        discount_percentage: 10,
        variants: [
          { special_price: "90", price: "100" },
          { special_price: "85", price: "95" },
        ],
        min_max_price: {
          min_price: 85,
          max_price: 100,
          special_price: 85,
          max_special_price: 90,
          discount_in_percentage: 10,
        },
      },
    ],
  ];

  const batchSize = 6;
  let currentBatchIndex = 0;
  const productGrid = document.getElementById("new-product-list-grid");

  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 tags = product.tags || [];
    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="${baseUrl}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="${baseUrl}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="${baseUrl}products/category/${encodeURIComponent(categoryEsc)}">${categoryEsc}</a>
          </div>
          <div class="new-product-list-store" aria-label="商店">
            <a href="${baseUrl}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 renderBatch(index) {
    if (index >= productBatches.length) {
      currentBatchIndex = 0;
      index = 0;
    }
    const batch = productBatches[index];
    if (!batch) return;

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

    Array.from(productGrid.children).forEach((card, i) => {
      const prod = batch[i];
      if (!prod) return;

      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";

      const allTagEls = prod.tags.map((tag) => {
        const span = document.createElement("span");
        span.className = "new-product-list-tag";
        const a = document.createElement("a");
        a.href = baseUrl + "products/tags/" + encodeURIComponent(tag);
        a.title = tag;
        a.textContent = tag;
        span.appendChild(a);
        return span;
      });

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

      requestAnimationFrame(() => {
        const containerWidth = tagsWrapper.clientWidth || 0;
        let usedWidth = 0;
        let lastFitIndex = -1;

        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;
          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 = "";
          visibleTags.forEach((el) => tagsContainer.appendChild(el));
          extendedTagsContainer.innerHTML = "";
          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 = "";
        }
      });
    });
  }

  renderBatch(currentBatchIndex);
})();
</script>
</body>
</html>

Please integrate all script with the updated layout and without the dummy data (the data should come from server by PHP, and already done).