Merch Story

Selected: 0
${decodeHTML(p.name)}
${inr(p.unit)}
Added for PDF: ${currentQty||0}
`; }function ensureState(catId){ if(!PRODUCTS_BY_CAT.has(catId)) PRODUCTS_BY_CAT.set(catId,{items:[],page:0,hasMore:true}); return PRODUCTS_BY_CAT.get(catId); } function sectionShell(cat){ return `

${cat.name}

`; } async function mountSections(){ $('#sections').innerHTML = CATS.map(sectionShell).join(''); const io=new IntersectionObserver(async (entries)=>{ for(const e of entries){ if(e.isIntersecting){ io.unobserve(e.target); const id=+e.target.id.replace('sec-',''); await loadNext(id); } } },{rootMargin:'200px'}); CATS.forEach(c=> io.observe(document.getElementById(`sec-${c.id}`))); CATS.forEach(c=> document.getElementById(`more-${c.id}`).onclick=()=>loadNext(c.id)); } async function loadNext(catId){ const st=ensureState(catId); if(!st.hasMore) return; const next=st.page+1; const items=await fetchProductsForCategory(catId,next,9); st.items = st.items.concat(items); st.page=next; if(items.length<9) st.hasMore=false; const grid=document.getElementById(`cards-${catId}`); grid.insertAdjacentHTML('beforeend', items.map(cardHtml).join('')); bindGrid(grid); if(!st.hasMore) document.getElementById(`more-${catId}`).style.display='none'; }function bindGrid(root){ root.querySelectorAll('[data-add]').forEach(b=> b.onclick=()=>{ const id=b.getAttribute('data-add'); const p=findProduct(id); if(!p) return; const qtySel=document.querySelector(`.qtySel[data-qty="${id}"]`); const qty=Math.max(1, parseInt(qtySel?.value||'1',10)); SELECTIONS[id]={qty:(SELECTIONS[id]?.qty||0)+qty, name:decodeHTML(p.name), unit:p.unit, image:p.image, desc:p.desc}; updateSelCount(); const card=document.getElementById(`card-${id}`); if(card){ card.classList.add('added'); const note=card.querySelector('.addedNote'); if(note){ note.innerHTML=`Added for PDF: ${SELECTIONS[id].qty}`; } } }); }function findProduct(id){ for(const st of PRODUCTS_BY_CAT.values()){ const f=st.items.find(x=>x.id===id); if(f) return f; } return null; }/* ====== PDF helpers ====== */ async function loadPoppinsFonts(doc){ try{ const urls={ regular:'https://raw.githubusercontent.com/google/fonts/main/ofl/poppins/Poppins-Regular.ttf', semibold:'https://raw.githubusercontent.com/google/fonts/main/ofl/poppins/Poppins-SemiBold.ttf', bold:'https://raw.githubusercontent.com/google/fonts/main/ofl/poppins/Poppins-Bold.ttf' }; const toB64 = async (url)=>{ const res=await fetch(url); const buf=await res.arrayBuffer(); let binary=''; const bytes=new Uint8Array(buf); const chunk=0x8000; for(let i=0;i{ const fr=new FileReader(); fr.onload=()=>ok(fr.result); fr.onerror=err; fr.readAsDataURL(blob); }); }catch(e){ return null; } }function calcTotals(){ let qty=0, sub=0; Object.values(SELECTIONS).forEach(it=>{ qty+=it.qty; sub+=it.unit*it.qty; }); let rate=0; for(const t of DISCOUNT_TIERS){ if(qty>=t.minQty){ rate=t.rate; break; } } const discount=sub*rate, grand=sub-discount, ratePct=Math.round(rate*100); return {qty, sub, discount, grand, ratePct}; }function splitIntoTwoColumns(items, colWidth, doc, xLeft, xRight, startY) { const gutter = 18; const xCols = [xLeft, xLeft + colWidth + gutter]; const maxY = 800; let col = 0, y = startY;for (const it of items) { if (!it?.desc) continue;// estimate height doc.setFont('Poppins','normal'); doc.setFontSize(10.5); const wrap = doc.splitTextToSize(it.desc, colWidth - 16); const needed = 14 + (wrap.length * 12) + 12;if (y + needed > maxY) { if (col === 0) { col = 1; y = startY; } else { doc.addPage(); col = 0; y = 72; } }// name (semibold, muted) doc.setFont('Poppins','semibold'); doc.setFontSize(11); doc.setTextColor(80, 89, 102); // slate-600 doc.text(it.name || 'Item', xCols[col], y); y += 14;// bullet + hanging paragraph (muted) doc.setFont('Poppins','normal'); doc.setFontSize(10.5); doc.setTextColor(107,114,128); // slate-500 const bullet = '• '; doc.text(bullet, xCols[col], y); const hangX = xCols[col] + doc.getTextWidth(bullet) + 6; for (const ln of wrap) { doc.text(ln, hangX, y); y += 12; } y += 12; } return y; }async function makePDF({download=true}={}){ const { jsPDF }=window.jspdf; const t=calcTotals(); const doc=new jsPDF({unit:'pt',format:'a4'}); // 595x842 const gotPoppins=await loadPoppinsFonts(doc);// margins & columns const xL=44, xR=551; const col={ item:xL, unit:xL+360, qty:xR }; const nameWidth=(col.unit-xL)-12; let y=72;// Header doc.setFont('helvetica','bold'); doc.setFontSize(18); doc.text('Merch Story — Quote', xL, y); y+=22; doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.text(new Date().toLocaleString(), xL, y); y+=18;// Table header doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.text('Item', col.item, y); doc.text('Unit (₹)', col.unit, y); doc.text('Qty', col.qty, y, {align:'right'}); y+=10; doc.setDrawColor(215); doc.setLineWidth(.9); doc.line(xL, y, xR, y); y+=16;// Rows let rowIndex=0; for(const it of Object.values(SELECTIONS)){ if(!it || !it.qty) continue; const rowH=60; if(y+rowH>772){ doc.addPage(); y=72; doc.setFont('helvetica','bold'); doc.setFontSize(12); doc.text('Item', col.item, y); doc.text('Unit (₹)', col.unit, y); doc.text('Qty', col.qty, y, {align:'right'}); y+=10; doc.setDrawColor(215); doc.setLineWidth(.9); doc.line(xL, y, xR, y); y+=16; }// zebra bg if(rowIndex%2===0){ doc.setFillColor(248,250,252); doc.rect(xL, y-8, xR-xL, rowH, 'F'); }// thumbnail const imgX=xL+4, imgY=y-4, imgW=44, imgH=44; if(it.image){ const d=await urlToDataUrl(it.image); if(d){ try{ doc.addImage(d,'JPEG',imgX,imgY,imgW,imgH,undefined,'MEDIUM'); }catch(e){} } }// item name doc.setFont('helvetica','normal'); doc.setFontSize(11); doc.setTextColor(0,0,0); const nameX=imgX+imgW+8; const lines=doc.splitTextToSize((it.name||'Item'), nameWidth-imgW-8); lines.slice(0,3).forEach((L,i)=> doc.text(L, nameX, y + i*12));// numbers (monospace) doc.setFont('courier','normal'); doc.setFontSize(12); doc.setTextColor(0,0,0); doc.text(inr(it.unit), col.unit, y); doc.text(String(it.qty), col.qty, y, {align:'right'});y+=rowH; rowIndex++; }// Totals box (Poppins) if(y>700){ doc.addPage(); y=90; } const boxX=xL, boxW=xR-xL, lineH=22, boxY=y+6, boxH=lineH*3+28; doc.setDrawColor(210); doc.setLineWidth(0.8); doc.rect(boxX,boxY,boxW,boxH); let ty=boxY+20; doc.setFont(gotPoppins?'Poppins':'helvetica','normal'); doc.setFontSize(12); doc.text(`Subtotal: ${inr(t.sub)}`, boxX+12, ty); ty+=lineH; doc.text(`Expected Discount (${t.ratePct}%): - ${inr(t.discount)}`, boxX+12, ty); ty+=lineH+6; doc.setFont(gotPoppins?'Poppins':'helvetica','bold'); doc.setFontSize(13); doc.text(`Grand Total: ${inr(t.grand)}`, boxX+12, ty);// Disclaimer ty=boxY+boxH+24; doc.setFont(gotPoppins?'Poppins':'helvetica','normal'); doc.setFontSize(10); doc.setTextColor(71,85,105); doc.text('Prices are indicative and in INR. Actual pricing may differ based on brand options, print method, and availability.', xL, ty); doc.setTextColor(0,0,0);// Product Descriptions (AFTER totals) — light, two-column, no heavy blacks const itemsWithDesc = Object.values(SELECTIONS).filter(it => it?.desc && it.desc.trim()); if (itemsWithDesc.length){ ty += 18; doc.setFont(gotPoppins?'Poppins':'helvetica', gotPoppins?'semibold':'bold'); doc.setFontSize(12); doc.setTextColor(71,85,105); doc.text('Product Descriptions', xL, ty); ty += 12; doc.setDrawColor(229); doc.setLineWidth(0.6); doc.line(xL, ty, xR, ty); ty += 14;// Body text style (muted) doc.setFont(gotPoppins?'Poppins':'helvetica','normal'); doc.setFontSize(10.5); doc.setTextColor(107,114,128); const colWidth = ((xR - xL) - 18) / 2; ty = splitIntoTwoColumns(itemsWithDesc, colWidth, doc, xL, xR - colWidth - 18, ty); doc.setTextColor(0,0,0); }if(download){ doc.save('MerchStory-Quote.pdf'); return null; }else{ // return Blob for sharing return doc.output('blob'); } }/* ====== ACTIONS ====== */ $('#reset').onclick=()=>{ SELECTIONS={}; updateSelCount(); $$('.added').forEach(c=>{c.classList.remove('added'); const n=c.querySelector('.addedNote'); if(n) n.innerHTML='';}); localStorage.removeItem('ms_pdf_selections'); }; $('#pdfBtn').onclick=()=>makePDF({download:true}); $('#pdfBtnM').onclick=()=>makePDF({download:true});// Share to WhatsApp: try Web Share with file; fallback to wa.me text $('#waBtn').onclick=async ()=>{ try{ const blob=await makePDF({download:false}); if(blob && navigator.canShare && navigator.canShare({files:[new File([blob],'MerchStory-Quote.pdf',{type:'application/pdf'})]})){ const file = new File([blob],'MerchStory-Quote.pdf',{type:'application/pdf'}); await navigator.share({files:[file], title:'Merch Story Quote', text:'Instant quote from Merch Story'}); }else{ const msg = encodeURIComponent('Hi, please check this instant quote from Merch Story.'); window.open(`https://wa.me/${WA_NUMBER}?text=${msg}`,'_blank'); } }catch(e){ const msg = encodeURIComponent('Hi, please check this instant quote from Merch Story.'); window.open(`https://wa.me/${WA_NUMBER}?text=${msg}`,'_blank'); } };/* ====== BOOT ====== */ async function start(){ updateSelCount(); WC_BASE=await resolveBase(); if(!WC_BASE){ // Demo if Woo Store API not available CATS=[{id:1,name:'Bags',count:3}]; PRODUCTS_BY_CAT.set(1,{items:[ {id:'demo1',name:'Leather Laptop Messenger Bag in Tan Brown',categoryId:1,category:'Bags',unit:2399,image:'',desc:'Premium faux leather with padded laptop sleeve.'}, {id:'demo2',name:'Crocodile Leather Laptop Messenger Bag in Bottle Green',categoryId:1,category:'Bags',unit:2750,image:'',desc:'Croc pattern finish with metal hardware.'}, {id:'demo3',name:'Executive Laptop Messenger Bag in Brown',categoryId:1,category:'Bags',unit:2599,image:'',desc:'Executive series, multiple organizers inside.'} ],page:1,hasMore:false}); renderTabs(); renderSide(); $('#sections').innerHTML=`

Bags

`; $('#cards-1').innerHTML=PRODUCTS_BY_CAT.get(1).items.map(cardHtml).join(''); bindGrid($('#sections')); return; } await fetchCategories(); renderTabs(); renderSide(); await mountSections(); } start();