Spaces:
Running
Running
Product titles: MODEL CODE | Malloca | Name. Catalogue search UI ready.
Browse files- index.html +76 -5
index.html
CHANGED
|
@@ -378,7 +378,12 @@ textarea.form-input{height:120px;resize:vertical}
|
|
| 378 |
<div class="page-section" id="page-catalogue">
|
| 379 |
<div class="section-header" style="text-align:center;margin-bottom:10px">
|
| 380 |
<h2 style="font-size:1.8rem;font-weight:800;color:var(--d)"><i class="fas fa-book-open" style="color:var(--a);margin-right:10px"></i>Catalogue <span style="color:var(--a)">Malloca</span></h2>
|
| 381 |
-
<p style="color:var(--g);font-size:.9rem;max-width:550px;margin:10px auto 0">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 382 |
</div>
|
| 383 |
<!-- Tab selector -->
|
| 384 |
<div id="catTabs" style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-bottom:20px"></div>
|
|
@@ -475,7 +480,7 @@ const PRICES=[
|
|
| 475 |
{l:'15-30 triệu',min:15e6,max:3e7},{l:'30-50 triệu',min:3e7,max:5e7},{l:'Trên 50 triệu',min:5e7,max:1e15}
|
| 476 |
];
|
| 477 |
|
| 478 |
-
fetch('products.json').then(r=>r.json()).then(d=>{D=d.map(p=>({name:p.n,link:p.l,image:p.i,price:p.p,priceNum:p.pn,cat:p.c,catSlug:p.cs,catIcon:p.ci,images:p.imgs||[],summary:p.sum||'',desc:p.desc||'',specs:p.specs||{},feats:p.feats||[],sku:p.sku||'',video:p.vid||''}));init()});
|
| 479 |
|
| 480 |
function init(){buildCats();buildPrices();render()}
|
| 481 |
|
|
@@ -494,7 +499,7 @@ document.getElementById('pr').innerHTML=h;document.getElementById('prm').innerHT
|
|
| 494 |
function getF(){
|
| 495 |
let r=D;
|
| 496 |
if(S.cat!=='all')r=r.filter(p=>p.catSlug===S.cat);
|
| 497 |
-
if(S.q){let q=S.q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'');r=r.filter(p=>{let n=(p.name+' '+p.cat+' '+p.sku).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'');return n.includes(q)})}
|
| 498 |
if(S.pmin>0||S.pmax<1e15)r=r.filter(p=>p.priceNum>=S.pmin&&p.priceNum<=S.pmax);
|
| 499 |
if(S.sort==='pa')r.sort((a,b)=>a.priceNum-b.priceNum);
|
| 500 |
else if(S.sort==='pd')r.sort((a,b)=>b.priceNum-a.priceNum);
|
|
@@ -511,7 +516,7 @@ let g=document.getElementById('grid');
|
|
| 511 |
g.className='pg'+(S.view==='l'?' lv':'');
|
| 512 |
if(!items.length){g.innerHTML=`<div class="empty" style="grid-column:1/-1"><i class="fas fa-search"></i><h3>Không tìm thấy</h3><p>Thử thay đổi bộ lọc hoặc từ khóa</p><button class="reset-btn" onclick="resetAll()">Xóa bộ lọc</button></div>`;
|
| 513 |
}else{
|
| 514 |
-
g.innerHTML=items.map((p,i)=>{let idx=D.indexOf(p);return`<div class="pc fade" onclick="showDetail(${idx})" style="transition-delay:${Math.min(i*25,400)}ms"><div class="pi"><img src="${p.image}" alt="${p.name}" loading="lazy" onerror="this.style.display='none'"><span class="pi-badge"><i class="fas ${p.catIcon}"></i> ${p.cat}</span></div><div class="pb"><div class="pn">${p.name}</div><div class="pf"><span class="pp">${p.price||'Liên hệ'}</span><i class="fas fa-chevron-right"></i></div></div></div>`}).join('');
|
| 515 |
}
|
| 516 |
requestAnimationFrame(()=>document.querySelectorAll('.fade').forEach(e=>e.classList.add('vis')));
|
| 517 |
renderPag(tp);
|
|
@@ -586,9 +591,10 @@ dv.innerHTML=`
|
|
| 586 |
${allImgs.length>1?`<div class="gallery-thumbs">${thumbs}</div>`:''}
|
| 587 |
</div>
|
| 588 |
<div class="detail-info">
|
| 589 |
-
${p.sku?`<div class="detail-sku">SKU: ${p.sku}</div>`:''}
|
| 590 |
<div class="detail-cat"><i class="fas ${p.catIcon}"></i> ${p.cat}</div>
|
| 591 |
<h1 class="detail-name">${p.name}</h1>
|
|
|
|
| 592 |
<div class="detail-price">${p.price||'Liên hệ'}</div>
|
| 593 |
${p.summary?`<div class="detail-summary">${p.summary}</div>`:''}
|
| 594 |
<div class="detail-badges">
|
|
@@ -726,6 +732,71 @@ document.body.appendChild(overlay);
|
|
| 726 |
}
|
| 727 |
|
| 728 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
// ===== NAVIGATION =====
|
| 730 |
let currentPage='products';
|
| 731 |
|
|
|
|
| 378 |
<div class="page-section" id="page-catalogue">
|
| 379 |
<div class="section-header" style="text-align:center;margin-bottom:10px">
|
| 380 |
<h2 style="font-size:1.8rem;font-weight:800;color:var(--d)"><i class="fas fa-book-open" style="color:var(--a);margin-right:10px"></i>Catalogue <span style="color:var(--a)">Malloca</span></h2>
|
| 381 |
+
<p style="color:var(--g);font-size:.9rem;max-width:550px;margin:10px auto 0">Tất cả 607 trang catalogue hiển thị trực tiếp — tìm kiếm theo văn bản trong ảnh</p>
|
| 382 |
+
<div style="max-width:480px;margin:16px auto 0;position:relative">
|
| 383 |
+
<i class="fas fa-search" style="position:absolute;left:14px;top:50%;transform:translateY(-50%);color:var(--g);font-size:.85rem"></i>
|
| 384 |
+
<input type="text" id="catSearch" placeholder="Tìm trong catalogue... (tên SP, mã SP, thông số...)" oninput="searchCatalogue()" style="width:100%;padding:10px 14px 10px 40px;border:2px solid var(--gl);border-radius:12px;font-size:.85rem;font-family:inherit;outline:none">
|
| 385 |
+
<span id="catSearchCount" style="position:absolute;right:12px;top:50%;transform:translateY(-50%);font-size:.68rem;color:var(--g);background:var(--l);padding:2px 7px;border-radius:6px"></span>
|
| 386 |
+
</div>
|
| 387 |
</div>
|
| 388 |
<!-- Tab selector -->
|
| 389 |
<div id="catTabs" style="display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin-bottom:20px"></div>
|
|
|
|
| 480 |
{l:'15-30 triệu',min:15e6,max:3e7},{l:'30-50 triệu',min:3e7,max:5e7},{l:'Trên 50 triệu',min:5e7,max:1e15}
|
| 481 |
];
|
| 482 |
|
| 483 |
+
fetch('products.json').then(r=>r.json()).then(d=>{D=d.map(p=>({name:p.n,link:p.l,image:p.i,price:p.p,priceNum:p.pn,cat:p.c,catSlug:p.cs,catIcon:p.ci,images:p.imgs||[],summary:p.sum||'',desc:p.desc||'',specs:p.specs||{},feats:p.feats||[],sku:p.sku||'',video:p.vid||'',model:p.mod||''}));init()});
|
| 484 |
|
| 485 |
function init(){buildCats();buildPrices();render()}
|
| 486 |
|
|
|
|
| 499 |
function getF(){
|
| 500 |
let r=D;
|
| 501 |
if(S.cat!=='all')r=r.filter(p=>p.catSlug===S.cat);
|
| 502 |
+
if(S.q){let q=S.q.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'');r=r.filter(p=>{let n=(p.name+' '+p.cat+' '+p.sku+' '+p.model+' malloca').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g,'');return n.includes(q)})}
|
| 503 |
if(S.pmin>0||S.pmax<1e15)r=r.filter(p=>p.priceNum>=S.pmin&&p.priceNum<=S.pmax);
|
| 504 |
if(S.sort==='pa')r.sort((a,b)=>a.priceNum-b.priceNum);
|
| 505 |
else if(S.sort==='pd')r.sort((a,b)=>b.priceNum-a.priceNum);
|
|
|
|
| 516 |
g.className='pg'+(S.view==='l'?' lv':'');
|
| 517 |
if(!items.length){g.innerHTML=`<div class="empty" style="grid-column:1/-1"><i class="fas fa-search"></i><h3>Không tìm thấy</h3><p>Thử thay đổi bộ lọc hoặc từ khóa</p><button class="reset-btn" onclick="resetAll()">Xóa bộ lọc</button></div>`;
|
| 518 |
}else{
|
| 519 |
+
g.innerHTML=items.map((p,i)=>{let idx=D.indexOf(p);return`<div class="pc fade" onclick="showDetail(${idx})" style="transition-delay:${Math.min(i*25,400)}ms"><div class="pi"><img src="${p.image}" alt="${p.name}" loading="lazy" onerror="this.style.display='none'"><span class="pi-badge"><i class="fas ${p.catIcon}"></i> ${p.cat}</span></div><div class="pb"><div style="font-size:.65rem;font-weight:700;color:var(--a);letter-spacing:.5px;margin-bottom:4px">${p.model}</div><div class="pn">${p.name}</div><div class="pf"><span class="pp">${p.price||'Liên hệ'}</span><i class="fas fa-chevron-right"></i></div></div></div>`}).join('');
|
| 520 |
}
|
| 521 |
requestAnimationFrame(()=>document.querySelectorAll('.fade').forEach(e=>e.classList.add('vis')));
|
| 522 |
renderPag(tp);
|
|
|
|
| 591 |
${allImgs.length>1?`<div class="gallery-thumbs">${thumbs}</div>`:''}
|
| 592 |
</div>
|
| 593 |
<div class="detail-info">
|
| 594 |
+
${p.sku?`<div class="detail-sku">SKU: ${p.sku} • Model: ${p.model}</div>`:''}
|
| 595 |
<div class="detail-cat"><i class="fas ${p.catIcon}"></i> ${p.cat}</div>
|
| 596 |
<h1 class="detail-name">${p.name}</h1>
|
| 597 |
+
<div style="font-size:.95rem;font-weight:800;color:var(--a);letter-spacing:1px;margin-bottom:12px">${p.model} | Malloca</div>
|
| 598 |
<div class="detail-price">${p.price||'Liên hệ'}</div>
|
| 599 |
${p.summary?`<div class="detail-summary">${p.summary}</div>`:''}
|
| 600 |
<div class="detail-badges">
|
|
|
|
| 732 |
}
|
| 733 |
|
| 734 |
|
| 735 |
+
// ===== CATALOGUE SEARCH (OCR text index) =====
|
| 736 |
+
let catOcrData=null;
|
| 737 |
+
let catSearchMode=false;
|
| 738 |
+
|
| 739 |
+
function loadCatOcr(){
|
| 740 |
+
if(catOcrData)return Promise.resolve();
|
| 741 |
+
return fetch('catalogue_ocr.json').then(r=>r.ok?r.json():null).then(d=>{catOcrData=d}).catch(()=>{catOcrData=null});
|
| 742 |
+
}
|
| 743 |
+
|
| 744 |
+
function searchCatalogue(){
|
| 745 |
+
let q=document.getElementById('catSearch').value.trim().toLowerCase();
|
| 746 |
+
let countEl=document.getElementById('catSearchCount');
|
| 747 |
+
if(!q){
|
| 748 |
+
catSearchMode=false;
|
| 749 |
+
countEl.textContent='';
|
| 750 |
+
renderCatPages();
|
| 751 |
+
return;
|
| 752 |
+
}
|
| 753 |
+
catSearchMode=true;
|
| 754 |
+
if(!catOcrData){
|
| 755 |
+
loadCatOcr().then(()=>searchCatalogue());
|
| 756 |
+
countEl.textContent='Đang tải...';
|
| 757 |
+
return;
|
| 758 |
+
}
|
| 759 |
+
// Search through OCR texts
|
| 760 |
+
let results=[];
|
| 761 |
+
for(let ci=0;ci<CAT_LIST.length;ci++){
|
| 762 |
+
let ocr=catOcrData&&catOcrData[ci]?catOcrData[ci].texts:[];
|
| 763 |
+
let pages=CAT_LIST[ci].pages;
|
| 764 |
+
for(let pi=0;pi<pages.length;pi++){
|
| 765 |
+
let txt=(ocr[pi]||'').toLowerCase();
|
| 766 |
+
if(txt.includes(q)){
|
| 767 |
+
results.push({catIdx:ci,catName:CAT_LIST[ci].name,pageIdx:pi,pageNum:pi+1,img:pages[pi],text:ocr[pi]||''});
|
| 768 |
+
}
|
| 769 |
+
}
|
| 770 |
+
}
|
| 771 |
+
countEl.textContent=results.length?results.length+' trang':'Không tìm thấy';
|
| 772 |
+
renderCatSearchResults(results,q);
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
function renderCatSearchResults(results,q){
|
| 776 |
+
let container=document.getElementById('catContent');
|
| 777 |
+
if(!results.length){
|
| 778 |
+
container.innerHTML=`<div style="text-align:center;padding:50px;color:var(--g)"><i class="fas fa-search" style="font-size:2.5rem;opacity:.3;display:block;margin-bottom:14px"></i><h3 style="font-size:1rem;font-weight:700;color:var(--d);margin-bottom:6px">Không tìm thấy "${q}"</h3><p style="font-size:.85rem">Thử từ khóa khác hoặc xóa bộ lọc</p></div>`;
|
| 779 |
+
return;
|
| 780 |
+
}
|
| 781 |
+
// Hide tabs selection when showing search results
|
| 782 |
+
container.innerHTML=`
|
| 783 |
+
<div style="background:#fff;border-radius:20px;padding:24px;box-shadow:0 2px 16px rgba(0,0,0,.06)">
|
| 784 |
+
<div style="margin-bottom:16px"><h3 style="font-size:1.1rem;font-weight:700;color:var(--d)"><i class="fas fa-search" style="color:var(--a);margin-right:8px"></i>${results.length} trang chứa "${q}"</h3></div>
|
| 785 |
+
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px">
|
| 786 |
+
${results.map(r=>`<div style="position:relative;border-radius:12px;overflow:hidden;border:2px solid var(--gl);cursor:pointer;transition:all .2s" onmouseover="this.style.borderColor='var(--p)';this.style.transform='translateY(-2px)'" onmouseout="this.style.borderColor='var(--gl)';this.style.transform=''" onclick="openCatImage('${r.img.replace(/'/g,"\\'")}')">
|
| 787 |
+
<img src="${r.img}" alt="" loading="lazy" style="width:100%;display:block">
|
| 788 |
+
<div style="position:absolute;top:8px;left:8px;background:var(--p);color:#fff;padding:3px 10px;border-radius:6px;font-size:.68rem;font-weight:700">${r.catName} — Trang ${r.pageNum}</div>
|
| 789 |
+
<div style="position:absolute;bottom:0;left:0;right:0;background:linear-gradient(transparent,rgba(0,0,0,.7));padding:10px 12px 8px;color:#fff;font-size:.72rem;line-height:1.4">${highlightText(r.text.substring(0,120),q)}...</div>
|
| 790 |
+
</div>`).join('')}
|
| 791 |
+
</div>
|
| 792 |
+
</div>`;
|
| 793 |
+
}
|
| 794 |
+
|
| 795 |
+
function highlightText(text,q){
|
| 796 |
+
let re=new RegExp('('+q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+')','gi');
|
| 797 |
+
return text.replace(re,'<mark style="background:var(--al);color:var(--d);padding:1px 2px;border-radius:2px">$1</mark>');
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
// ===== NAVIGATION =====
|
| 801 |
let currentPage='products';
|
| 802 |
|