Sản phẩm
Video sản phẩm
Đang tải...
Đã bán 0 | Chia sẻ | Truy xuất nguồn gốc
' + '' + escapedValue + '' + ''; }); html += ''; // Cột phải html += '
'; rightColumn.forEach(function (spec) { const label = spec.label?.vi_VN || spec.label || 'N/A'; const value = spec.value?.vi_VN || spec.value || 'N/A'; const escapedLabel = escapeHtml(String(label || '').trim()); const escapedValue = escapeHtml(String(value || '').trim()); html += '
' + '' + escapedLabel + ':' + '' + escapedValue + '' + '
'; }); html += '
'; html += ''; container.innerHTML = html; if (container.classList) { container.classList.remove('hidden'); } } // Xử lý lỗi function showSpecificationsError() { // Ẩn toàn bộ section khi có lỗi const sectionEl = document.getElementById('product-specifications-section'); if (sectionEl && sectionEl.classList) { sectionEl.classList.add('hidden'); } } // Khởi tạo async function initProductSpecifications() { if (!Utils) { console.warn('CommerceUtils not available for specifications'); showSpecificationsError(); return; } // Ensure API hydration (avoid reading DOM data-product-id placeholder) try { if (typeof Utils.getApiProduct === 'function') { await Utils.getApiProduct(); } } catch (e) { } const productInfo = Utils.getProductInfo(); if (!productInfo || !productInfo.productId) { console.warn('Cannot get productId for specifications'); showSpecificationsError(); return; } try { const specifications = await fetchProductSpecifications(productInfo.productId); renderSpecifications(specifications); } catch (error) { console.error('Error loading product specifications:', error); showSpecificationsError(); } } // Chạy khi DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initProductSpecifications); } else { initProductSpecifications(); } // Hỗ trợ Liferay events if (typeof Liferay !== 'undefined' && Liferay.on) { Liferay.on('allPortletsReady', function () { setTimeout(initProductSpecifications, 500); }); } })(); '; }; } // Tạo QR code function generateQRCode(data) { const container = document.getElementById('qrcode-container'); if (!container) return; // Xóa QR code cũ nếu có container.innerHTML = ''; // Hiển thị loading container.innerHTML = '
'; // Đợi QRCode library load xong (giảm thời gian đợi xuống 3 giây) waitForQRCode(function (QRCodeLib) { if (!QRCodeLib) { // Fallback: Sử dụng API online generateQRCodeWithAPI(data); return; } // Xóa loading container.innerHTML = ''; // Tạo canvas element const canvas = document.createElement('canvas'); container.appendChild(canvas); // Tạo QR code mới try { QRCodeLib.toCanvas(canvas, data.traceUrl, { width: 300, margin: 2, color: { dark: '#000000', light: '#FFFFFF' } }, function (error) { if (error) { console.error('Error generating QR code:', error); // Fallback: Sử dụng API online generateQRCodeWithAPI(data); } }); } catch (e) { console.error('Exception generating QR code:', e); // Fallback: Sử dụng API online generateQRCodeWithAPI(data); } }, 30); // Giảm từ 50 xuống 30 (3 giây) } // Hiển thị modal QR code function showQRCodeModal() { const modal = document.getElementById('qr-code-modal'); if (!modal) return; const traceData = getProductTraceData(); if (!traceData) { showToastSPNB('Không thể lấy thông tin sản phẩm để tạo mã QR', true); return; } generateQRCode(traceData); modal.classList.remove('hidden'); } // Đóng modal QR code function closeQRCodeModal() { const modal = document.getElementById('qr-code-modal'); if (modal) { modal.classList.add('hidden'); } } // Hiển thị modal quét QR function showScanQRModal() { const modal = document.getElementById('scan-qr-modal'); if (!modal) return; closeQRCodeModal(); modal.classList.remove('hidden'); // Khởi tạo HTML5 QR Code Scanner const readerContainer = document.getElementById('qr-reader'); if (readerContainer && !html5QrCode) { html5QrCode = new Html5Qrcode("qr-reader"); html5QrCode.start( { facingMode: "environment" }, // Sử dụng camera sau { fps: 10, qrbox: { width: 250, height: 250 } }, function (decodedText, decodedResult) { // Khi quét được QR code handleScannedQR(decodedText); }, function (errorMessage) { // Lỗi khi quét (không hiển thị lỗi thường xuyên) } ).catch(function (err) { console.error('Error starting QR scanner:', err); showToastSPNB('Không thể khởi động camera. Vui lòng kiểm tra quyền truy cập camera.', true); }); } } // Đóng modal quét QR function closeScanQRModal() { const modal = document.getElementById('scan-qr-modal'); if (modal) { modal.classList.add('hidden'); } // Dừng scanner if (html5QrCode) { html5QrCode.stop().then(function () { html5QrCode.clear(); html5QrCode = null; }).catch(function (err) { console.error('Error stopping QR scanner:', err); }); } } // Trích productId từ nội dung QR (URL đủ/không scheme, hoặc chỉ số) function extractProductIdFromQrPayload(qrData) { const raw = (qrData || '').toString().trim(); if (!raw) return null; if (/^\d+$/.test(raw)) return raw; function idFromSearch(str) { try { const u = new URL(str); const p = u.searchParams.get('productId'); if (p) { const digits = String(p).replace(/[^\d]/g, ''); return digits || String(p); } } catch (e) { } return null; } let id = idFromSearch(raw); if (id) return id; // Nhiều app quét QR trả về URL không có scheme → new URL() lỗi if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) { id = idFromSearch('https://' + raw.replace(/^\/+/, '')); if (id) return id; } const m1 = raw.match(/[?&#]productId=(\d+)/i); if (m1) return m1[1]; const m2 = raw.match(/productId[=:]\s*(\d+)/i); if (m2) return m2[1]; return null; } // Xử lý khi quét được QR code function handleScannedQR(qrData) { closeScanQRModal(); const productId = extractProductIdFromQrPayload(qrData); if (!productId) { showToastSPNB('Mã QR không hợp lệ. Vui lòng thử lại.', true); return; } showTraceInfoModal(productId); } // Lấy thông tin sản phẩm: ưu tiên API storefront by-id (cùng nguồn với trang chi tiết), // vì headless-commerce-admin-catalog thường yêu cầu quyền admin — khách quét QR sẽ bị lỗi. async function fetchProductTraceInfo(productId) { const idStr = String(productId || '').trim(); if (!idStr) { throw new Error('Thiếu mã sản phẩm'); } const csrf = (typeof Liferay !== 'undefined' && Liferay.authToken) ? Liferay.authToken : ''; const headers = { 'Accept': 'application/json' }; if (csrf) { headers['x-csrf-token'] = csrf; } const fetchOpts = { method: 'GET', headers: headers, credentials: 'include' }; let productData = null; try { const byIdUrl = '/o/gocgac-commerce/v1.0/products/by-id?id=' + encodeURIComponent(idStr); const byIdRes = await fetch(byIdUrl, fetchOpts); if (byIdRes.ok) { const responseText = await byIdRes.text().catch(function () { return ''; }); const trimmed = (responseText || '').trim(); if (trimmed) { try { productData = JSON.parse(trimmed); } catch (e) { console.warn('fetchProductTraceInfo: by-id JSON parse failed', e); } } } } catch (e) { console.error('fetchProductTraceInfo: by-id request error', e); } if (!productData) { try { const adminUrl = APP_CONFIG.API_BASE_ADMIN + '/products/' + encodeURIComponent(idStr); const response = await fetch(adminUrl, fetchOpts); if (response.ok) { productData = (window.safeResponseJson ? await window.safeResponseJson(response) : null); } } catch (e) { console.error('fetchProductTraceInfo: admin product request error', e); } } if (!productData) { throw new Error('Không thể tải thông tin sản phẩm (kiểm tra mạng hoặc đăng nhập).'); } const product = Object.assign({}, productData, { productId: productData.productId != null ? productData.productId : idStr, id: productData.id != null ? productData.id : (productData.productId || idStr) }); let catalogData = null; let accountData = null; const catalogId = product.catalogId || productData.catalogId; if (catalogId) { try { const catalogUrl = APP_CONFIG.API_BASE_ADMIN + '/catalog/' + catalogId; const catalogRes = await fetch(catalogUrl, fetchOpts); if (catalogRes.ok) { catalogData = (window.safeResponseJson ? await window.safeResponseJson(catalogRes) : null); if (catalogData && catalogData.accountId) { try { const accountUrl = APP_CONFIG.API_BASE_ACCOUNT + '/accounts/' + catalogData.accountId; const accountRes = await fetch(accountUrl, fetchOpts); if (accountRes.ok) { accountData = (window.safeResponseJson ? await window.safeResponseJson(accountRes) : null); } } catch (e) { console.error('Error fetching account info:', e); } } } } catch (e) { console.error('Error fetching catalog/account info:', e); } } return { product: product, catalog: catalogData, account: accountData }; } // Hiển thị modal thông tin truy xuất nguồn gốc async function showTraceInfoModal(productId) { const modal = document.getElementById('trace-info-modal'); const content = document.getElementById('trace-info-content'); if (!modal || !content) return; modal.classList.remove('hidden'); // Hiển thị loading content.innerHTML = `

Đang tải thông tin...

`; try { const traceInfo = await fetchProductTraceInfo(productId); renderTraceInfo(traceInfo); } catch (error) { const errorMsg = error.message || 'Vui lòng thử lại sau'; content.innerHTML = '
' + '' + '' + '' + '' + '' + '

Không thể tải thông tin sản phẩm

' + '

' + escapeHtml(errorMsg) + '

' + '
'; } } // Render thông tin truy xuất nguồn gốc function renderTraceInfo(traceInfo) { const content = document.getElementById('trace-info-content'); if (!content) return; const { product, catalog, account } = traceInfo; // Xác định nguồn gốc sản phẩm từ một số field phổ biến (nếu có) let originText = ''; try { const productCf = product?.customFields || {}; const accountCf = account?.customFields || {}; originText = productCf.origin || productCf.originName || productCf.province || productCf.xuatXu || // trường hay dùng cho "xuất xứ" product?.address || accountCf.origin || accountCf.originName || accountCf.address || ''; } catch (e) { originText = ''; } let html = '
'; // Thông tin sản phẩm html += '
'; html += '

'; html += ''; html += ''; html += ''; html += ''; html += ''; html += 'Thông tin sản phẩm'; html += '

'; html += '
'; html += '
Tên sản phẩm:' + escapeHtml(product.name?.vi_VN || product.name || 'N/A') + '
'; html += '
Mã sản phẩm:' + escapeHtml(product.productId || product.id || 'N/A') + '
'; if (product.sku) { html += '
SKU:' + escapeHtml(product.sku) + '
'; } html += '
'; html += '
'; // Thông tin nhà sản xuất/cửa hàng if (account) { html += '
'; html += '

'; html += ''; html += ''; html += ''; html += ''; html += 'Thông tin nhà sản xuất'; html += '

'; html += '
'; html += '
Tên tổ chức:' + escapeHtml(account.name || 'N/A') + '
'; if (account.customFields?.address) { html += '
Địa chỉ:' + escapeHtml(account.customFields.address) + '
'; } if (account.customFields?.phone) { html += '
Điện thoại:' + escapeHtml(account.customFields.phone) + '
'; } html += '
'; html += '
'; } // Thông tin catalog if (catalog) { html += '
'; html += '

'; html += ''; html += ''; html += ''; html += ''; html += ''; html += 'Thông tin danh mục'; html += '

'; html += '
'; html += '
Tên danh mục:' + escapeHtml(catalog.name?.vi_VN || catalog.name || 'N/A') + '
'; html += '
'; html += '
'; } // Thông tin truy xuất html += '
'; html += '

'; html += ''; html += ''; html += ''; html += ''; html += 'Thông tin truy xuất'; html += '

'; html += '
'; const traceDate = new Date().toLocaleString('vi-VN'); html += '
Thời gian truy xuất:' + escapeHtml(traceDate) + '
'; // Hiển thị nguồn gốc (nếu không có dữ liệu thì ghi "Đang cập nhật") const originDisplay = originText ? escapeHtml(String(originText)) : 'Đang cập nhật'; html += '
Nguồn gốc:' + originDisplay + '
'; html += '
Trạng thái:✓ Đã xác thực
'; html += '
'; html += '
'; html += '
'; content.innerHTML = html; } // Đóng modal thông tin truy xuất function closeTraceInfoModal() { const modal = document.getElementById('trace-info-modal'); if (modal) { modal.classList.add('hidden'); } } // Khởi tạo event listeners (chỉ gắn một lần — tránh double fire khi Liferay allPortletsReady) function initTraceOrigin() { if (!window.__newDetailTraceOriginUiBound) { window.__newDetailTraceOriginUiBound = true; const traceLink = document.getElementById('trace-origin-link'); if (traceLink) { traceLink.addEventListener('click', function (e) { e.preventDefault(); showQRCodeModal(); }); } const closeQRBtns = [ document.getElementById('close-qr-modal'), document.getElementById('close-qr-modal-btn') ]; closeQRBtns.forEach(function (btn) { if (btn) { btn.addEventListener('click', closeQRCodeModal); } }); const scanQRBtn = document.getElementById('scan-qr-btn'); if (scanQRBtn) { scanQRBtn.addEventListener('click', showScanQRModal); } const closeScanBtns = [ document.getElementById('close-scan-modal'), document.getElementById('close-scan-modal-btn') ]; closeScanBtns.forEach(function (btn) { if (btn) { btn.addEventListener('click', closeScanQRModal); } }); const closeTraceInfoBtns = [ document.getElementById('close-trace-info-modal'), document.getElementById('close-trace-info-modal-btn') ]; closeTraceInfoBtns.forEach(function (btn) { if (btn) { btn.addEventListener('click', closeTraceInfoModal); } }); const modals = [ document.getElementById('qr-code-modal'), document.getElementById('scan-qr-modal'), document.getElementById('trace-info-modal') ]; modals.forEach(function (modal) { if (modal) { modal.addEventListener('click', function (e) { if (e.target === modal) { if (modal.id === 'qr-code-modal') { closeQRCodeModal(); } else if (modal.id === 'scan-qr-modal') { closeScanQRModal(); } else if (modal.id === 'trace-info-modal') { closeTraceInfoModal(); } } }); } }); } try { const params = new URLSearchParams(window.location.search || ''); if (params.get('traceOrigin') === '1' && !window.__newDetailTraceOriginAutoShown) { let pid = params.get('productId'); if (pid) { pid = String(pid).replace(/[^\d]/g, '') || pid; } if (!pid && Utils && typeof Utils.getProductInfo === 'function') { if (Utils.apiProduct && Utils.apiProduct.productId) { pid = String(Utils.apiProduct.productId); } else { const info = Utils.getProductInfo(); if (info && info.productId) { pid = String(info.productId); } } } if (pid) { window.__newDetailTraceOriginAutoShown = true; showTraceInfoModal(pid); } } } catch (e) { } } // Khởi tạo khi DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(initTraceOrigin, 100); }); } else { setTimeout(initTraceOrigin, 100); } // Hỗ trợ Liferay events if (typeof Liferay !== 'undefined' && Liferay.on) { Liferay.on('allPortletsReady', function () { setTimeout(initTraceOrigin, 500); }); } })(); (function () { 'use strict'; async function updateStaticViewProductsAddToCart() { var STATIC_PRODUCT_ID = null; try { var Utils = window.CommerceUtils; if (Utils && typeof Utils.getApiProduct === 'function') { var api = await Utils.getApiProduct(); if (api && api.catalogEntryId) { STATIC_PRODUCT_ID = String(api.catalogEntryId); } } } catch (e) { } if (!STATIC_PRODUCT_ID) { var el = document.querySelector('[data-product-id]'); if (el) { STATIC_PRODUCT_ID = (el.getAttribute('data-product-id') || '').toString().replace(/[^\d]/g, ''); } } if (!STATIC_PRODUCT_ID || STATIC_PRODUCT_ID === '0') return; var API_BASE = window.location.origin + '/o/c/staticviewproducts'; var csrf = (typeof Liferay !== 'undefined' && Liferay.authToken) ? Liferay.authToken : ''; // GET theo filter var filter = "r_staticProductId_CProductId eq '" + STATIC_PRODUCT_ID + "'"; var getUrl = API_BASE + '/?filter=' + encodeURIComponent(filter); var getRes = await fetch(getUrl, { method: 'GET', headers: { 'accept': 'application/json', 'x-csrf-token': csrf }, credentials: 'include' }); if (!getRes.ok) { throw new Error('GET staticviewproducts failed: ' + getRes.status); } var data = await getRes.json(); var item = (data && data.items && data.items.length > 0) ? data.items[0] : null; // Nếu có record => PUT clickNumber giữ nguyên, chỉ tăng addToCartNumber if (item) { var erc = item.externalReferenceCode; if (!erc) throw new Error('Missing externalReferenceCode on existing item'); var nextAddToCart = (Number(item.addToCartNumber) || 0) + 1; var clickNumber = String(item.clickNumber ?? 0); var putUrl = API_BASE + '/by-external-reference-code/' + encodeURIComponent(erc); var putBody = { addToCartNumber: String(nextAddToCart), clickNumber: clickNumber, r_staticProductId_CProductId: String(STATIC_PRODUCT_ID) }; var putRes = await fetch(putUrl, { method: 'PUT', headers: { 'accept': 'application/json', 'content-type': 'application/json', 'x-csrf-token': csrf }, credentials: 'include', body: JSON.stringify(putBody) }); if (!putRes.ok) { var text = await putRes.text().catch(function () { return ''; }); throw new Error('PUT update failed: ' + putRes.status + ' ' + text); } return; } // Nếu chưa có record => POST tạo mới (clickNumber = 0, addToCartNumber = 1) var postBody = { addToCartNumber: '1', clickNumber: '0', r_staticProductId_CProductId: String(STATIC_PRODUCT_ID) }; var postRes = await fetch(API_BASE + '/', { method: 'POST', headers: { 'accept': 'application/json', 'content-type': 'application/json', 'x-csrf-token': csrf }, credentials: 'include', body: JSON.stringify(postBody) }); if (!postRes.ok) { var text2 = await postRes.text().catch(function () { return ''; }); throw new Error('POST create failed: ' + postRes.status + ' ' + text2); } } // expose để gọi từ addToCart() window.updateStaticViewProductsAddToCart = updateStaticViewProductsAddToCart; })(); sản phẩm' : 'Hết hàng'; } else { stockInfo.removeAttribute('data-max-quantity'); stockInfo.innerHTML = 'Đang cập nhật tồn kho theo phân loại'; } } btnIds.forEach(function (id) { var btn = document.getElementById(id); if (btn) btn.disabled = !canPurchase; }); if (window.CommerceUtils && window.CommerceUtils.apiProduct) { var rawUomList = sku.skuUnitOfMeasures || []; var primaryUom = rawUomList.find(function (u) { return u.primary === true; }) || rawUomList[0]; window.CommerceUtils.apiProduct.skuId = sku.id || null; window.CommerceUtils.apiProduct.skuUnitOfMeasureKey = sku.skuUnitOfMeasureKey || (primaryUom ? primaryUom.key : ''); window.CommerceUtils.apiProduct.skuUnitOfMeasure = normalizeUOM(primaryUom); window.CommerceUtils.apiProduct.stockLevel = stock; window.CommerceUtils.apiProduct.price = listPrice; window.CommerceUtils.apiProduct.promoPrice = promoPrice; window.CommerceUtils.apiProduct.finalPrice = finalPrice; } var quantityInput = document.getElementById('quantity-input'); var quantityInputMobile = document.getElementById('quantity-input-mobile'); if (quantityInput) { // Khi đổi SKU thì reset về 1 để tính giá/tồn theo biến thể mới quantityInput.value = '1'; if (quantityInputMobile) quantityInputMobile.value = '1'; quantityInput.dispatchEvent(new Event('change', { bubbles: true })); } } function renderSkuOptions(skus) { var section = document.getElementById('sku-variant-section'); var container = document.getElementById('sku-variant-options'); if (!section || !container) return; if (!Array.isArray(skus) || skus.length === 0) { syncNoVariantBadge(false); section.classList.remove('hidden'); container.innerHTML = 'Chưa có dữ liệu phân loại.'; return; } if (skus.length === 1) { syncNoVariantBadge(false); section.classList.add('hidden'); return; } syncNoVariantBadge(true); container.innerHTML = ''; section.classList.remove('hidden'); var currentSkuId = window.CommerceUtils && window.CommerceUtils.apiProduct ? parseInt(window.CommerceUtils.apiProduct.skuId, 10) : null; var selectedIdx = 0; if (currentSkuId) { var found = skus.findIndex(function (s) { return parseInt(s.id, 10) === currentSkuId; }); if (found >= 0) selectedIdx = found; } skus.forEach(function (sku, idx) { var btn = document.createElement('button'); btn.type = 'button'; btn.className = 'px-3 py-1.5 rounded border text-sm transition-colors ' + (idx === selectedIdx ? 'border-emerald-600 text-emerald-700 bg-emerald-50' : 'border-gray-300 text-gray-700 hover:border-emerald-400'); btn.textContent = getSkuLabel(sku, idx); btn.addEventListener('click', function () { Array.prototype.forEach.call(container.children, function (el) { el.className = 'px-3 py-1.5 rounded border text-sm transition-colors border-gray-300 text-gray-700 hover:border-emerald-400'; }); btn.className = 'px-3 py-1.5 rounded border text-sm transition-colors border-emerald-600 text-emerald-700 bg-emerald-50'; applySkuToUi(sku); }); container.appendChild(btn); }); applySkuToUi(skus[selectedIdx]); } /** Đồng bộ: map từ apiProduct (by-id) — không gọi API SKU / Admin. */ function buildVariantSkusFromApiProduct(ap) { if (!ap) { return { skus: [], resolvedProductId: null }; } try { if (ap.skuStockLevels && ap.skuStockLevels.length > 0) { return { skus: ap.skuStockLevels.map(function (row) { // Giá từng SKU độc lập: 0 = chưa niêm yết, không gộp giá cấp sản phẩm (tránh SIZE S hiện giá SIZE M) var rp = row.price; var rpp = row.promoPrice; var rowList = (rp !== undefined && rp !== null && !isNaN(Number(rp))) ? Number(rp) : NaN; var rowPromo = (rpp !== undefined && rpp !== null && !isNaN(Number(rpp))) ? Number(rpp) : NaN; return { id: row.skuId, sku: row.sku || ('SKU ' + row.skuId), price: !isNaN(rowList) ? rowList : (Number(ap.listPrice) || Number(ap.price) || 0), promoPrice: !isNaN(rowPromo) ? rowPromo : (Number(ap.promoPrice) || 0), stockLevel: row.stockLevel, skuUnitOfMeasureKey: row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey, skuUnitOfMeasures: (row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey) ? [{ key: row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey, name: row.unitOfMeasure || row.skuUnitOfMeasureKey || ap.skuUnitOfMeasureKey, primary: true }] : (ap.skuUnitOfMeasure ? [ap.skuUnitOfMeasure] : []) }; }), resolvedProductId: ap.productId || null }; } if (ap.skuId) { return { skus: [{ id: ap.skuId, sku: 'SKU ' + ap.skuId, price: ap.price, promoPrice: ap.promoPrice, stockLevel: ap.stockLevel, skuUnitOfMeasureKey: ap.skuUnitOfMeasureKey, skuUnitOfMeasures: ap.skuUnitOfMeasure ? [ap.skuUnitOfMeasure] : [] }], resolvedProductId: ap.productId || null }; } } catch (e) { } return { skus: [], resolvedProductId: null }; } async function initVariantSelector() { try { if (!window.CommerceUtils || typeof window.CommerceUtils.getApiProduct !== 'function') return; var apiProduct = await window.CommerceUtils.getApiProduct(); if (!apiProduct || !apiProduct.productId) return; var result = buildVariantSkusFromApiProduct(apiProduct); var enrichedSkus = enrichSkuInventoryLevels(result.skus || []); renderSkuOptions(enrichedSkus); } catch (e) { console.warn('initVariantSelector error:', e); var section = document.getElementById('sku-variant-section'); var container = document.getElementById('sku-variant-options'); if (section && container) { section.classList.remove('hidden'); container.innerHTML = 'Không tải được phân loại.'; } } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initVariantSelector); } else { initVariantSelector(); } })(); <!-- THÔNG TIN DỊCH VỤ --> <div class="mt-3 lg:mt-0"> <div class="grid grid-cols-3 gap-2 lg:gap-3 text-center"> <!-- Đảm bảo chất lượng --> <div class="flex flex-col items-center border border-gray-200 py-3 px-2"> <div class="w-10 h-10 lg:w-8 lg:h-8 mb-2 flex items-center justify-center"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-600 lg:w-5 lg:h-5"> <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path> <path d="m9 12 2 2 4-4"></path> </svg> </div> <p class="font-medium text-gray-900 text-xs lg:text-xs">Đảm bảo chất lượng</p> </div> <!-- Giao hàng nhanh --> <div class="flex flex-col items-center border border-gray-200 py-3 px-2"> <div class="w-10 h-10 lg:w-8 lg:h-8 mb-2 flex items-center justify-center"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-600 lg:w-5 lg:h-5"> <rect x="1" y="3" width="15" height="13"></rect> <polygon points="16 8 20 8 23 11 23 16 16 16 16 8"></polygon> <circle cx="5.5" cy="18.5" r="2.5"></circle> <circle cx="18.5" cy="18.5" r="2.5"></circle> </svg> </div> <p class="font-medium text-gray-900 text-xs lg:text-xs">Giao hàng nhanh</p> </div> <!-- Đóng gói cẩn thận --> <div class="flex flex-col items-center border border-gray-200 py-3 px-2"> <div class="w-10 h-10 lg:w-8 lg:h-8 mb-2 flex items-center justify-center"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-600 lg:w-5 lg:h-5"> <path d="M16.5 9.4 7.55 4.24"></path> <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"> </path> <polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline> <line x1="12" y1="22.08" x2="12" y2="12"></line> </svg> </div> <p class="font-medium text-gray-900 text-xs lg:text-xs">Đóng gói cẩn thận</p> </div> </div> </div> </div> </div> <!-- ==================== THÔNG SỐ KỸ THUẬT / CHI TIẾT SẢN PHẨM ==================== --> <div class="w-full mx-auto mb-12 px-[18px] lg:px-0" id="product-specifications-section"> <div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden"> <!-- Header --> <div class="bg-gradient-to-r from-emerald-50 to-green-50 px-4 lg:px-6 py-4 border-b border-gray-200"> <h2 class="text-2xl font-bold text-gray-900 flex items-center gap-2"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-600"> <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> <polyline points="14 2 14 8 20 8"></polyline> <line x1="16" y1="13" x2="8" y2="13"></line> <line x1="16" y1="17" x2="8" y2="17"></line> <polyline points="10 9 9 9 8 9"></polyline> </svg> Thông số kỹ thuật </h2> <p class="text-sm text-gray-600 mt-1">Thông tin chi tiết về sản phẩm</p> </div> <!-- Content - Container để render dữ liệu từ API --> <div class="p-4 lg:p-6" id="product-specifications-container"> <!-- Loading state --> <div id="specifications-loading" class="text-center py-8"> <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div> <p class="text-gray-600 mt-2">Đang tải thông số kỹ thuật...</p> </div> <!-- Error state --> <div id="specifications-error" class="hidden text-center py-8"> <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-400 mx-auto"> <circle cx="12" cy="12" r="10"></circle> <line x1="12" y1="8" x2="12" y2="12"></line> <line x1="12" y1="16" x2="12.01" y2="16"></line> </svg> <p class="text-gray-600 mt-2">Không thể tải thông số kỹ thuật</p> </div> <!-- Specifications content (sẽ được render bởi JavaScript) --> <div id="specifications-content" class="hidden"> <!-- Sẽ được render động --> </div> </div> </div> </div> <!-- ==================== CARD THÔNG TIN TỔ CHỨC/CỬA HÀNG ==================== --> <div class="w-full mx-auto mb-0 xl:mb-12 px-[18px] lg:px-0"> <div class="bg-white shadow-sm border border-gray-200"> <div class="p-4 lg:p-5"> <div class="flex gap-4"> <!-- Logo --> <div class="w-12 h-12 rounded-full bg-emerald-100 flex items-center justify-center flex-shrink-0 overflow-hidden" id="organization-logo-container"> <span class="text-2xl" id="organization-logo-placeholder">🏢</span> </div> <!-- Thông tin chính --> <div class="flex-1 min-w-0"> <h3 class="font-bold text-base text-gray-900 truncate" id="organization-name">Đang tải...</h3> <!-- Địa chỉ và đánh giá ở dòng riêng bên dưới --> <div class="flex items-center gap-3 text-sm text-gray-600 mt-2"> <div class="flex items-center gap-1 flex-shrink-0"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="fill-yellow-400 text-yellow-400"> <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"> </polygon> </svg> <span class="font-medium" id="organization-rating">—</span> </div> </div> </div> <!-- Nút Chat ngay: mở chat với HTX của sản phẩm (gocgac-chat-widget), kèm thẻ sản phẩm --> <div class="flex flex-col gap-2 flex-shrink-0 self-start"> <button type="button" id="organization-chat-btn" onclick="(function(){var pid=new URLSearchParams(window.location.search).get('productId');if(!(window.Liferay&&Liferay.ThemeDisplay&&Liferay.ThemeDisplay.isSignedIn())){window.location.href='/c/portal/login?redirect='+encodeURIComponent(window.location.pathname+window.location.search);return;}window.dispatchEvent(new CustomEvent('gocgac-start-chat',{detail:{productId:pid}}));})()" class="inline-flex items-center justify-center gap-1 px-3 py-1.5 text-sm font-medium text-white bg-emerald-600 hover:bg-emerald-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition"> 💬 Chat ngay </button> <a href="#" id="organization-url-link" class="inline-flex items-center justify-center px-3 py-1.5 border border-gray-300 text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-emerald-500 transition flex-shrink-0 self-start"> Xem cửa hàng </a> </div> </div> <div class="flex items-center gap-1 mt-3"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path d="M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"> </path> <circle cx="12" cy="10" r="3"></circle> </svg> <span id="organization-location" class="truncate">Đang tải...</span> </div> </div> </div> </div> <!-- Dialog Điều kiện voucher (cửa hàng) --> <div id="voucher-conditions-dialog-overlay" class="fixed inset-0 bg-black/40 z-[9998] hidden items-center justify-center p-4" style="display: none;"> <div class="bg-white rounded-xl shadow-xl max-w-md w-full p-6 relative border border-purple-100"> <div class="flex items-center justify-between mb-4"> <h3 class="text-lg font-bold text-gray-900">Điều kiện áp dụng</h3> <button type="button" id="voucher-conditions-dialog-close" class="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button> </div> <div id="voucher-conditions-dialog-body" class="text-sm text-gray-600 space-y-2"></div> <p id="voucher-conditions-dialog-empty" class="text-sm text-gray-500 hidden">Không có điều kiện.</p> <div class="mt-4 flex justify-end"> <button type="button" id="voucher-conditions-dialog-ok" class="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white font-medium rounded-lg transition-colors">Đóng</button> </div> </div> </div> <!-- ==================== VOUCHER SLIDER (discounts theo channel HTX) ==================== --> <div class="w-full mx-auto mb-6 xl:mb-12 px-[18px] lg:px-0 mt-6" id="voucher-section"> <div class="bg-white shadow-sm border border-gray-200 rounded-lg overflow-hidden"> <div class="px-4 py-3 border-b border-gray-200 bg-gray-50"> <h3 class="font-bold text-gray-900">Voucher của cửa hàng</h3> <p class="text-sm text-gray-500 mt-0.5">Chọn voucher và nhấn Lưu để dùng khi thanh toán</p> </div> <div class="p-4"> <div id="voucher-loading" class="flex justify-center py-8"> <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div> </div> <div id="voucher-error" class="hidden text-center py-6 text-gray-500 text-sm"></div> <div id="voucher-slider-wrap" class="hidden relative"> <div id="voucher-slider" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <!-- Voucher cards render here --> </div> <button type="button" id="voucher-slider-prev" class="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-1 w-10 h-10 rounded-full bg-white border border-gray-300 shadow-md flex items-center justify-center text-gray-600 hover:bg-gray-50 z-10 hidden" aria-label="Trước"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M15 18l-6-6 6-6" /> </svg> </button> <button type="button" id="voucher-slider-next" class="absolute right-0 top-1/2 -translate-y-1/2 translate-x-1 w-10 h-10 rounded-full bg-white border border-gray-300 shadow-md flex items-center justify-center text-gray-600 hover:bg-gray-50 z-10 hidden" aria-label="Sau"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <path d="M9 18l6-6-6-6" /> </svg> </button> </div> <div id="voucher-empty" class="hidden text-center py-6 text-gray-500 text-sm">Chưa có voucher nào cho cửa hàng này.</div> </div> </div> </div> <!-- ==================== MODAL QR CODE TRUY XUẤT NGUỒN GỐC ==================== --> <div id="qr-code-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 p-6"> <div class="flex justify-between items-center mb-4"> <h3 class="text-xl font-bold text-gray-900">Mã QR Truy xuất nguồn gốc</h3> <button id="close-qr-modal" class="text-gray-400 hover:text-gray-600 transition-colors"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div class="text-center"> <div id="qrcode-container" class="flex justify-center mb-4 p-4 bg-white rounded-lg border-2 border-gray-200"> <!-- QR Code sẽ được render ở đây --> </div> <p class="text-sm text-gray-600 mb-4">Quét mã QR để xem thông tin truy xuất nguồn gốc sản phẩm</p> <div class="flex gap-3"> <button id="scan-qr-btn" class="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white font-medium py-2 px-4 rounded-lg transition-colors"> Quét mã QR </button> <button id="close-qr-modal-btn" class="flex-1 border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg hover:bg-gray-50 transition-colors"> Đóng </button> </div> </div> </div> </div> <!-- ==================== MODAL QUÉT QR CODE ==================== --> <div id="scan-qr-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg shadow-xl max-w-md w-full mx-4 p-6"> <div class="flex justify-between items-center mb-4"> <h3 class="text-xl font-bold text-gray-900">Quét mã QR</h3> <button id="close-scan-modal" class="text-gray-400 hover:text-gray-600 transition-colors"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div class="text-center"> <div id="qr-reader" class="mb-4" style="width: 100%; max-width: 400px; margin: 0 auto;"> <!-- QR Scanner sẽ được render ở đây --> </div> <p class="text-sm text-gray-600 mb-4">Đưa mã QR vào khung hình để quét</p> <div class="flex gap-3"> <button id="close-scan-modal-btn" class="flex-1 border border-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg hover:bg-gray-50 transition-colors"> Đóng </button> </div> </div> </div> </div> <!-- ==================== MODAL THÔNG TIN TRUY XUẤT NGUỒN GỐC ==================== --> <div id="trace-info-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg shadow-xl max-w-2xl w-full mx-4 p-6 max-h-[90vh] overflow-y-auto"> <div class="flex justify-between items-center mb-4"> <h3 class="text-xl font-bold text-gray-900">Thông tin truy xuất nguồn gốc</h3> <button id="close-trace-info-modal" class="text-gray-400 hover:text-gray-600 transition-colors"> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div id="trace-info-content" class="space-y-4"> <!-- Thông tin sẽ được render động ở đây --> <div class="text-center py-8"> <div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-emerald-600"></div> <p class="text-gray-600 mt-2">Đang tải thông tin...</p> </div> </div> <div class="mt-6 flex justify-end"> <button id="close-trace-info-modal-btn" class="px-6 py-2 border border-gray-300 text-gray-700 font-medium rounded-lg hover:bg-gray-50 transition-colors"> Đóng </button> </div> </div> </div> <!-- ==================== MODAL CHIA SẺ SẢN PHẨM ==================== --> <div id="share-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg shadow-xl max-w-sm w-full mx-4"> <div class="flex items-center justify-between px-4 py-3 border-b border-gray-200"> <h3 class="text-base font-semibold text-gray-900">Chia sẻ sản phẩm</h3> <button id="share-modal-close" class="text-gray-400 hover:text-gray-600 transition-colors"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div class="px-4 pt-4 pb-3"> <p class="text-sm text-gray-600 mb-4"> Chọn nền tảng để chia sẻ liên kết sản phẩm: </p> <div class="grid grid-cols-2 gap-3"> <!-- Facebook --> <button type="button" class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-blue-600 hover:bg-blue-50 transition-colors" data-platform="facebook"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-blue-600 text-white"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <path d="M13.5 9H15V6.75A15.64 15.64 0 0 0 12.93 6C11.17 6 9.9 7.24 9.9 9.52V12H8v3h1.9v7h3.1v-7h2.03L15 12h-2v-2.26C13 9.29 13.13 9 13.5 9Z"> </path> </svg> </span> <span class="text-sm font-medium text-gray-900">Facebook</span> </button> <!-- Zalo --> <button type="button" class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-sky-500 hover:bg-sky-50 transition-colors" data-platform="zalo"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-sky-500 text-white"> <span class="text-xs font-bold tracking-tight">ZL</span> </span> <span class="text-sm font-medium text-gray-900">Zalo</span> </button> <!-- Twitter --> <button type="button" class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-sky-400 hover:bg-sky-50 transition-colors" data-platform="twitter"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-sky-400 text-white"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <path d="M22 5.92a8.38 8.38 0 0 1-2.36.65 4.12 4.12 0 0 0 1.8-2.27 8.27 8.27 0 0 1-2.61.98A4.14 4.14 0 0 0 16.11 4a4.18 4.18 0 0 0-4.18 4.18c0 .33 0 .65.07.95A11.78 11.78 0 0 1 3.15 4.6a4.18 4.18 0 0 0 1.29 5.58 4.1 4.1 0 0 1-1.89-.52v.05A4.18 4.18 0 0 0 6.1 13.3a4.23 4.23 0 0 1-1.89.07 4.19 4.19 0 0 0 3.9 2.9A8.33 8.33 0 0 1 2 18.13a11.75 11.75 0 0 0 6.29 1.84c7.55 0 11.68-6.26 11.68-11.68 0-.18 0-.36-.01-.54A8.18 8.18 0 0 0 22 5.92Z"> </path> </svg> </span> <span class="text-sm font-medium text-gray-900">Twitter</span> </button> <!-- LinkedIn --> <button type="button" class="flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-200 hover:border-sky-700 hover:bg-sky-50 transition-colors" data-platform="linkedin"> <span class="inline-flex items-center justify-center w-8 h-8 rounded-full bg-sky-700 text-white"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor"> <path d="M6.94 6.5A2.44 2.44 0 1 1 4.5 4.06 2.44 2.44 0 0 1 6.94 6.5ZM4.75 8.75h3.5v10.5h-3.5ZM10.38 8.75h3.36v1.44h.05a3.69 3.69 0 0 1 3.32-1.83c3.55 0 4.21 2.34 4.21 5.38v5.51h-3.5v-4.88c0-1.16 0-2.65-1.62-2.65s-1.87 1.27-1.87 2.57v4.96h-3.5Z"> </path> </svg> </span> <span class="text-sm font-medium text-gray-900">LinkedIn</span> </button> </div> </div> <div class="px-4 pb-4 flex justify-end"> <button id="share-modal-cancel" type="button" class="px-4 py-2 text-sm font-medium text-gray-700 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors"> Đóng </button> </div> </div> </div> <!-- ==================== MODAL VIDEO SẢN PHẨM (media.attachments) ==================== --> <div id="product-video-modal" class="product-video-modal fixed inset-0 z-[100] hidden flex items-center justify-center p-3 sm:p-6 bg-black/65 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="product-video-modal-title"> <div class="product-video-modal__panel relative w-full max-w-[min(100%,56rem)] max-h-[min(90vh,900px)] flex flex-col bg-white rounded-2xl shadow-[0_25px_50px_-12px_rgba(0,0,0,0.45)] ring-1 ring-black/10 overflow-hidden"> <div class="flex items-center justify-between gap-3 px-4 py-3 sm:px-5 sm:py-3.5 border-b border-gray-200 bg-white shrink-0"> <span id="product-video-modal-title" class="text-base sm:text-lg font-semibold text-gray-900 truncate pr-2">Video sản phẩm</span> <button type="button" id="product-video-modal-close" class="product-video-modal__close shrink-0 w-10 h-10 rounded-full bg-gray-100 hover:bg-gray-200 text-gray-700 flex items-center justify-center transition-colors shadow-sm border border-gray-200/80" aria-label="Đóng"> <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line> </svg> </button> </div> <div class="relative w-full bg-black aspect-video max-h-[calc(min(90vh,900px)-4.25rem)] min-h-[200px]" id="product-video-modal-stage"> <div id="product-video-modal-loading" class="absolute inset-0 z-20 hidden flex-col items-center justify-center gap-3 bg-black/80 text-white text-sm"> <svg class="h-10 w-10 animate-spin text-emerald-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"> </path> </svg> <span id="product-video-modal-loading-msg">Đang tải video…</span> </div> <video id="product-video-modal-player" class="absolute inset-0 z-[1] w-full h-full object-contain hidden [&::-webkit-media-controls-panel]:bg-black/40" controls playsinline preload="none"></video> <iframe id="product-video-modal-iframe" class="absolute inset-0 z-[1] w-full h-full hidden border-0 bg-black" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </div> </div> </div> <style> /* Modal video: đảm bảo hiển thị đẹp kể cả khi theme Liferay override Tailwind */ #product-video-modal.product-video-modal { box-sizing: border-box; } #product-video-modal .product-video-modal__panel { animation: productVideoModalIn 0.2s ease-out; } @keyframes productVideoModalIn { from { opacity: 0; transform: scale(0.96) translateY(8px); } to { opacity: 1; transform: scale(1) translateY(0); } } #product-video-modal #product-video-modal-player { background: #0a0a0a; } @media (max-width: 640px) { #product-video-modal .product-video-modal__panel { max-height: 88vh; } } </style> <!-- ==================== STICKY FOOTER CHO MOBILE ==================== --> <div id="mobile-action-footer" class="lg:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 shadow-lg z-50 safe-area-inset-bottom"> <div class="flex items-stretch"> <!-- Quantity Selector - Mobile --> <div class="flex items-center justify-center px-3 bg-white"> <!-- Quantity Controls --> <div class="flex items-center border border-gray-300 rounded-lg overflow-hidden bg-white"> <!-- Nút giảm --> <button type="button" id="btn-decrease-mobile" class="w-10 h-10 flex items-center justify-center border-r border-gray-300 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-600"> <line x1="5" y1="12" x2="19" y2="12"></line> </svg> </button> <!-- Input số lượng --> <input id="quantity-input-mobile" name="quantity-mobile" type="text" value="1" class="w-12 h-10 text-center border-0 border-x border-gray-300 font-medium text-gray-700 focus:ring-0 focus:outline-none" readonly /> <!-- Nút tăng --> <button type="button" id="btn-increase-mobile" class="w-10 h-10 flex items-center justify-center border-l border-gray-300 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-gray-600"> <line x1="12" y1="5" x2="12" y2="19"></line> <line x1="5" y1="12" x2="19" y2="12"></line> </svg> </button> </div> <!-- Label Số lượng --> <!-- <label class="text-xs text-gray-500 mt-1">Số lượng</label> --> </div> <!-- Button Thêm vào giỏ hàng - Mobile (icon trên, label dưới) --> <button id="btn-add-to-cart-mobile" type="button" class="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white font-medium text-sm py-2.5 px-3 flex flex-col items-center justify-center gap-1 transition-all disabled:opacity-50 disabled:cursor-not-allowed" disabled> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="8" cy="21" r="1"></circle> <circle cx="19" cy="21" r="1"></circle> <path d="M2.05 2.05h2l2.66 12.42a2 2 0 0 0 2 1.58h9.78a2 2 0 0 0 1.95-1.57l1.65-7.43H5.12"></path> </svg> <span id="add-to-cart-text-mobile" class="text-xs leading-tight text-center">Thêm vào giỏ hàng</span> </button> <!-- Button Mua ngay --> <button id="btn-buy-now-mobile" type="button" class="flex-1 bg-orange-500 hover:bg-orange-600 text-white font-medium text-base py-3 px-4 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" disabled> <span id="buy-now-text-mobile">Mua ngay</span> </button> </div> </div> </div> <!-- ==================== CUSTOM STYLES ==================== --> <style> /* Padding bottom cho content để không bị che bởi sticky footer trên mobile */ @media (max-width: 1023px) { body { padding-bottom: 90px; } /* Đảm bảo các section có padding bottom đủ */ #product-specifications-section, .w-full.mx-auto.mb-12 { margin-bottom: 4rem !important; padding-bottom: 1rem; } /* Đảm bảo container chính có padding bottom */ .grid.grid-cols-1.lg\\:grid-cols-2 { padding-bottom: 1rem; } } /* Safe area inset cho iPhone với notch */ .safe-area-inset-bottom { padding-bottom: env(safe-area-inset-bottom, 12px); } /* Sticky footer styling */ #mobile-action-footer { box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); } /* Quantity selector mobile styling */ #quantity-input-mobile { appearance: none; -webkit-appearance: none; -moz-appearance: textfield; } #quantity-input-mobile::-webkit-outer-spin-button, #quantity-input-mobile::-webkit-inner-spin-button { appearance: none; -webkit-appearance: none; margin: 0; } /* Tối ưu spacing cho mobile */ @media (max-width: 1023px) { .space-y-6>*+* { margin-top: 1rem; } .space-y-4>*+* { margin-top: 0.75rem; } } /* Product Description Styling */ .product-description { line-height: 1.8; font-size: 0.875rem; color: #7a818b; } /* Collapsed state - limit to 5 lines */ .product-description.product-description-collapsed { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 5; line-clamp: 5; max-height: none; overflow: hidden; position: relative; } /* Gradient fade effect when collapsed */ .product-description.product-description-collapsed::after { content: ''; position: absolute; bottom: 0; left: 0; right: 0; height: 40px; background: linear-gradient(to bottom, transparent, rgba(255, 255, 255, 0.95)); pointer-events: none; } /* Expanded state */ .product-description.product-description-expanded { max-height: none; overflow: visible; } /* Style for paragraphs within description */ .product-description p { margin-bottom: 0.75rem; line-height: 1.8; } .product-description p:last-child { margin-bottom: 0; } /* Style for bold text (headings, labels) */ .product-description strong, .product-description b { font-weight: 600; color: #1f2937; } /* Style for lists */ .product-description ul, .product-description ol { margin: 0.75rem 0; padding-left: 1.5rem; } .product-description li { margin-bottom: 0.5rem; line-height: 1.7; } /* Enhanced spacing */ .product-description br { line-height: 1.8; } /* Toggle button styling */ .product-description-toggle { transition: color 0.2s ease; display: inline-flex; align-items: center; gap: 0.25rem; } .product-description-toggle:hover { text-decoration: none; } .hide-scrollbar::-webkit-scrollbar { display: none; } #voucher-slider .voucher-card { min-width: 0; } #voucher-slider .voucher-card { position: relative; } .voucher-conditions-btn { position: absolute; top: 0.5rem; right: 0.5rem; font-size: 0.75rem; color: #6d28d9; font-weight: 500; cursor: pointer; background: none; border: none; padding: 0; } .voucher-conditions-btn:hover { color: #5b21b6; } #voucher-conditions-dialog-overlay.show { display: flex !important; } </style> <!-- ==================== LOAD CSS FRAMEWORK ==================== --> <script src="https://cdn.tailwindcss.com">

Đánh giá sản phẩm

0

Dựa trên 0 đánh giá

Gợi ý sản phẩm

Sản phẩm tương tự