feat(ecommerce): release Ari Shopping Frontend v2.1 real catalog
Build and Push Frontend / build (push) Successful in 2m30s

This commit is contained in:
devops committed 2026-07-19 13:41:37 -05:00
1 parent 6700166821
commit 1c74220c88
33 files changed
+547 -261

No files matched your search

+1 -5
View File
@@ -1,5 +1 @@
:root{--ink:#241b2d;--muted:#746a7a;--pink:#ff3f7f;--purple:#7c3aed;--soft:#f8f4f8;--line:#e9e1eb;--shadow:0 20px 55px rgba(47,28,55,.12)}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;color:var(--ink);font-family:Inter,system-ui,-apple-system,Segoe UI,sans-serif}body.lock{overflow:hidden}a{text-decoration:none;color:inherit}button,input,select{font:inherit}.shell{width:min(calc(100% - 32px),1240px);margin:auto}.announcement{padding:9px;text-align:center;color:#fff;background:linear-gradient(90deg,var(--pink),var(--purple));font-size:13px}header{position:sticky;top:0;z-index:20;background:#ffffffed;border-bottom:1px solid var(--line);backdrop-filter:blur(16px)}.head{min-height:84px;display:grid;grid-template-columns:190px 1fr auto auto;gap:18px;align-items:center}.head img{width:175px}.search{display:flex;align-items:center;gap:9px;padding:0 16px;min-height:48px;border:1px solid var(--line);border-radius:999px;background:var(--soft)}.search input{width:100%;border:0;outline:0;background:transparent}.ig,.cart{padding:12px 16px;border:1px solid var(--line);border-radius:999px;background:#fff}.cart{cursor:pointer}.cart b{display:inline-grid;place-items:center;min-width:23px;height:23px;margin-left:6px;border-radius:50%;color:#fff;background:var(--ink)}.hero{min-height:600px;display:grid;grid-template-columns:1.05fr .95fr;gap:48px;align-items:center;padding:70px 0}.eyebrow{color:#d91f60;font-size:12px;font-weight:850;letter-spacing:.12em;text-transform:uppercase}.hero h1{margin:15px 0 20px;font-size:clamp(48px,6.4vw,82px);line-height:.98;letter-spacing:-.055em}.hero p{max-width:650px;color:var(--muted);font-size:18px;line-height:1.7}.heroActions{display:flex;gap:12px;flex-wrap:wrap;margin-top:28px}.btn{display:inline-flex;align-items:center;justify-content:center;min-height:47px;padding:0 20px;border:0;border-radius:999px;font-weight:800;cursor:pointer}.primary{color:#fff;background:linear-gradient(90deg,var(--pink),var(--purple));box-shadow:0 12px 27px #ff3f7f38}.ghost{background:var(--soft);border:1px solid var(--line)}.light{background:#fff}.art{min-height:480px;display:grid;place-items:center;border-radius:42px;background:radial-gradient(circle at 75% 18%,#fff,transparent 25%),linear-gradient(135deg,#ffd8e7,#e8ddff 55%,#cef6ed);box-shadow:var(--shadow)}.art>div{width:70%;padding:38px;border:1px solid #ffffffb0;border-radius:32px;background:#ffffffb5;transform:rotate(-6deg);box-shadow:var(--shadow);backdrop-filter:blur(12px)}.art small,.art span{display:block;color:var(--muted)}.art strong{display:block;margin:12px 0 28px;font-size:42px;line-height:1.02}.benefits{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:100px}.benefits article{display:flex;gap:14px;padding:24px;border:1px solid var(--line);border-radius:22px}.benefits b{width:38px;height:38px;display:grid;place-items:center;border-radius:50%;color:#fff;background:var(--ink)}.benefits span strong,.benefits span small{display:block}.benefits span small{margin-top:5px;color:var(--muted)}.catalog{padding-bottom:110px}.sectionTitle{display:flex;justify-content:space-between;align-items:end;gap:24px}.sectionTitle h2,.instagram h2{margin:8px 0;font-size:clamp(34px,4vw,52px);letter-spacing:-.04em}.sectionTitle p{color:var(--muted)}.sectionTitle select{min-height:46px;padding:0 14px;border:1px solid var(--line);border-radius:12px;background:#fff}.categories{display:flex;gap:9px;overflow:auto;padding:18px 0 24px}.chip{flex:0 0 auto;padding:10px 16px;border:1px solid var(--line);border-radius:999px;background:#fff;cursor:pointer;font-weight:750}.chip.active{color:#fff;background:var(--ink)}.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}.card{overflow:hidden;border:1px solid var(--line);border-radius:22px;background:#fff;transition:.25s}.card:hover{transform:translateY(-5px);box-shadow:var(--shadow)}.media{position:relative;aspect-ratio:1;background:var(--soft);overflow:hidden}.media img{width:100%;height:100%;object-fit:cover}.badges{position:absolute;z-index:2;top:12px;left:12px;display:flex;gap:5px}.badge{padding:6px 9px;border-radius:999px;color:#fff;background:var(--purple);font-size:10px;font-weight:850}.badge.sale{background:var(--pink)}.wish{position:absolute;z-index:2;right:12px;top:12px;width:40px;height:40px;border:1px solid #fff;border-radius:50%;background:#ffffffd9;font-size:23px;cursor:pointer}.wish.active{color:var(--pink)}.body{padding:18px}.body>.category{color:var(--muted);font-weight:700}.body h3{margin:8px 0;font-size:17px}.body p{min-height:42px;color:var(--muted);font-size:13px;line-height:1.55}.price{display:flex;align-items:baseline;gap:8px}.price strong{font-size:20px}.price del{color:#9e929f;font-size:13px}.body .add{width:100%;margin-top:15px}.empty{padding:60px;text-align:center;border:1px dashed var(--line);border-radius:22px;background:var(--soft)}.instagram{padding:80px 0;color:#fff;background:linear-gradient(110deg,#24152b,#6d1a55,#4a2b85)}.instagram>.shell{display:flex;Line truncated
@media(max-width:1000px){.grid{grid-template-columns:repeat(3,1fr)}.hero{grid-template-columns:1fr 1fr}.head{grid-template-columns:160px 1fr auto}.ig{display:none}.benefits{grid-template-columns:1fr}}
@media(max-width:760px){.head{grid-template-columns:1fr auto;padding:10px 0}.head img{width:150px}.search{grid-column:1/-1;grid-row:2}.hero{grid-template-columns:1fr;padding:50px 0}.art{min-height:360px}.grid{grid-template-columns:repeat(2,1fr)}.sectionTitle{align-items:stretch;flex-direction:column}.instagram>.shell,.foot{align-items:flex-start;flex-direction:column}}
@media(max-width:480px){.shell{width:min(calc(100% - 22px),1240px)}.hero h1{font-size:48px}.art>div{width:80%;padding:26px}.art strong{font-size:34px}.grid{gap:10px}.body{padding:13px}.body h3{font-size:14px}.body p{display:none}.price strong{font-size:16px}.foot img{width:150px}}
:root{--ink:#17111c;--muted:#756d78;--pink:#ff3f86;--pink-dark:#e32270;--purple:#9c39ef;--soft:#faf7fb;--line:#ece5ee;--white:#fff;--shadow:0 18px 55px rgba(40,24,48,.10);--shell:1240px}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;color:var(--ink);background:#fff;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}body.locked{overflow:hidden}button,input,select{font:inherit}button{color:inherit}a{color:inherit;text-decoration:none}img{display:block;max-width:100%}.shell{width:min(calc(100% - 36px),var(--shell));margin-inline:auto}.skip-link{position:fixed;z-index:999;left:10px;top:10px;padding:10px 15px;color:#fff;background:#111;transform:translateY(-150%)}.skip-link:focus{transform:none}.announcement{height:34px;display:flex;align-items:center;justify-content:center;gap:34px;padding:0 16px;color:#fff;background:linear-gradient(90deg,var(--pink),var(--purple));font-size:12px}.announcement strong{text-transform:uppercase;letter-spacing:.06em}.site-header{position:sticky;z-index:80;top:0;background:rgba(255,255,255,.95);border-bottom:1px solid var(--line);backdrop-filter:blur(18px)}.header-shell{min-height:82px;display:grid;grid-template-columns:205px 1fr auto;align-items:center;gap:28px}.brand img{width:190px}.main-nav{display:flex;justify-content:center;gap:27px}.main-nav button{border:0;padding:10px 0;background:transparent;color:#6f6672;cursor:pointer;font-size:13px}.main-nav button:hover{color:var(--pink-dark)}.header-actions{display:flex;align-items:center;gap:9px}.search-open{width:43px;height:43px;display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;background:#fff;cursor:pointer}.search-open svg{width:20px;fill:none;stroke:currentColor;stroke-width:1.8}.cart-button{height:43px;display:flex;align-items:center;gap:9px;padding:0 14px;border:0;border-radius:999px;color:#fff;background:linear-gradient(90deg,var(--pink),var(--purple));cursor:pointer;font-weight:800}.cart-button strong{width:23px;height:23px;display:grid;place-items:center;border-radius:50%;background:#fff;color:var(--ink);font-size:11px}.search-panel{padding:0 0 18px}.search-panel label{display:block;margin-bottom:7px;font-size:12px;font-weight:800;color:var(--pink-dark);text-transform:uppercase;letter-spacing:.08em}.search-panel>div{display:flex;gap:8px}.search-panel input{width:100%;height:48px;padding:0 16px;border:1px solid var(--line);border-radius:999px;outline:0;background:var(--soft)}.search-panel button{padding:0 18px;border:0;border-radius:999px;background:var(--ink);color:#fff;cursor:pointer}.hero{min-height:650px;display:grid;grid-template-columns:.9fr 1.1fr;align-items:center;gap:65px;padding-block:70px}.eyebrow{display:inline-block;color:var(--pink-dark);font-size:11px;font-weight:900;letter-spacing:.12em;text-transform:uppercase}.hero h1{max-width:620px;margin:16px 0 22px;font-size:clamp(48px,6vw,82px);line-height:.98;letter-spacing:-.055em}.hero h1 em{display:block;color:var(--pink);font-family:Georgia,serif;font-weight:400}.hero-copy>p{max-width:580px;margin:0;color:var(--muted);font-size:17px;line-height:1.7}.hero-actions{display:flex;align-items:center;gap:20px;margin-top:30px}.button{min-height:47px;display:inline-flex;align-items:center;justify-content:center;padding:0 22px;border:0;border-radius:999px;cursor:pointer;font-weight:850}.button.primary{color:#fff;background:linear-gradient(90deg,var(--pink),var(--purple));box-shadow:0 12px 30px rgba(255,63,134,.22)}.button.text{padding:0;background:transparent}.button.white{background:#fff}.button.full{width:100%}.hero-image{position:relative}.hero-image img{width:100%;border-radius:34px}.hero-note{position:absolute;right:4%;bottom:7%;padding:12px 16px;border-radius:14px;background:rgba(255,255,255,.9);box-shadow:var(--shadow);font-size:12px;font-weight:800}.service-strip{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;padding-bottom:100px}.service-strip article{display:flex;gap:14px;align-items:center;padding:20px 0;border-top:1px solid var(--line)}.service-strip i{width:32px;height:32px;display:grid;place-items:center;border-radius:50%;color:var(--pink);background:#fff0f6;font-style:normal;font-weight:900}.service-strip strong,.service-strip span{display:block}.service-strip strong{font-size:13px}.service-strip span{margin-top:4px;color:var(--muted);font-size:12px}.category-section,.products-section{padding-bottom:110px}.section-heading{display:flex;align-items:end;justify-content:space-between;gap:25px;margin-bottom:28px}.section-heading h2{margin:8px 0;font-size:clamp(34px,4.4vw,58px);line-height:1;letter-spacing:-.045em}.section-heading p{margin:0;color:var(--muted)}.section-heading>a{color:var(--pink-dark);font-size:13px;font-weight:800}.category-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:18px}.category-card{position:relative;min-height:390px;overflow:hidden;display:flex;flex-direction:column;justify-content:end;align-items:start;padding:25px;border:Line truncated
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

@@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 100"><defs><linearGradient id="g"><stop stop-color="#ff3f7f"/><stop offset="1" stop-color="#7c3aed"/></linearGradient></defs><rect width="360" height="100" rx="24" fill="#fff"/><circle cx="50" cy="50" r="34" fill="url(#g)"/><path d="M31 67 47 30h8l17 37H59l-3-8H44l-3 8Zm17-18h5l-2.5-7Z" fill="#fff"/><text x="95" y="48" font-family="Arial" font-size="30" font-weight="800" fill="#201a2b">Ari</text><text x="95" y="76" font-family="Arial" font-size="25" font-weight="700" fill="#ff3f7f">Shopping</text></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 520 116" role="img" aria-labelledby="title">
<title id="title">Ari Shopping</title>
<defs><linearGradient id="g" x1="0" x2="1"><stop stop-color="#ff3f86"/><stop offset="1" stop-color="#a139ef"/></linearGradient></defs>
<circle cx="55" cy="58" r="44" fill="#17111c"/>
<text x="55" y="68" text-anchor="middle" font-family="Georgia,serif" font-size="39" font-style="italic" font-weight="700" fill="#fff">ARI</text>
<text x="116" y="56" font-family="Arial,sans-serif" font-size="34" font-weight="850" fill="#211927">Ari Shopping</text>
<text x="117" y="83" font-family="Arial,sans-serif" font-size="15" font-weight="700" letter-spacing="2.6" fill="url(#g)">DETALLES PARA DISFRUTAR</text>
</svg>

Before

Width:  |  Height:  |  Size: 565 B

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

+1 -1
View File
@@ -1 +1 @@
(()=>{"use strict";const $=(s,r=document)=>r.querySelector(s),$$=(s,r=document)=>[...r.querySelectorAll(s)];const S={products:[],store:null,category:"Todos",query:"",sort:"featured",cart:JSON.parse(localStorage.getItem("ari-cart-v2")||"{}"),wish:JSON.parse(localStorage.getItem("ari-wish-v2")||"[]")};const E={grid:$("#grid"),cat:$("#categories"),search:$("#search"),sort:$("#sort"),summary:$("#summary"),empty:$("#empty"),drawer:$("#drawer"),overlay:$("#overlay"),items:$("#cartItems"),count:$("#cartCount"),total:$("#cartTotal"),toast:$("#toast"),card:$("#card")};const money=v=>new Intl.NumberFormat(S.store?.locale||"es-CO",{style:"currency",currency:S.store?.currency||"COP",maximumFractionDigits:0}).format(v);const norm=v=>String(v||"").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g,"");const p=id=>S.products.find(x=>x.id===id);function save(){localStorage.setItem("ari-cart-v2",JSON.stringify(S.cart));localStorage.setItem("ari-wish-v2",JSON.stringify(S.wish))}function toast(m){E.toast.textContent=m;E.toast.hidden=false;setTimeout(()=>E.toast.hidden=true,2600)}function categories(){E.cat.innerHTML=["Todos",...new Set(S.products.map(x=>x.category))].map(x=>`<button class="chip ${S.category===x?"active":""}" data-c="${x}">${x}</button>`).join("")}function list(){let q=norm(S.query),a=S.products.filter(x=>x.available!==false&&(S.category==="Todos"||x.category===S.category)&&(!q||norm([x.name,x.category,x.description,...(x.tags||[])].join(" ")).includes(q)));a.sort((x,y)=>S.sort==="asc"?x.price-y.price:S.sort==="desc"?y.price-x.price:S.sort==="new"?Number(y.new)-Number(x.new):Number(y.featured)-Number(x.featured));return a}function render(){let a=list();E.grid.innerHTML="";E.summary.textContent=`${a.length} producto${a.length===1?"":"s"} disponible${a.length===1?"":"s"}`;E.empty.hidden=!!a.length;a.forEach(x=>{let f=E.card.content.cloneNode(true),c=$(".card",f),img=$("img",f),w=$(".wish",f);img.src=x.images?.[0]||"assets/img/brand/favicon.svg";img.alt=x.name;$(".category",f).textContent=x.category;$("h3",f).textContent=x.name;$(".price strong",f).textContent=money(x.price);let d=$(".price del",f);x.compareAtPrice>x.price?d.textContent=money(x.compareAtPrice):d.remove();$(".body p",f).textContent=x.description;if(x.new)$(".badges",f).insertAdjacentHTML("beforeend",'<span class="badge">Nuevo</span>');if(x.compareAtPrice>x.price)$(".badges",f).insertAdjacentHTML("beforeend",`<span class="badge sale">-${Math.round((1-x.price/x.compareAtPrice)*100)}%</span>`);w.classList.toggle("active",S.wish.includes(x.id));w.textContent=S.wish.includes(x.id)?"♥":"♡";w.onclick=()=>{S.wish=S.wish.includes(x.id)?S.wish.filter(i=>i!==x.id):[...S.wish,x.id];save();render();toast(S.wish.includes(x.id)?"Guardado en favoritos":"Eliminado de favoritos")};$(".add",f).onclick=()=>add(x.id);E.grid.append(f)});cart()}function add(id){S.cart[id]=(S.cart[id]||0)+1;save();cart();toast(`${p(id).name} agregado`)}function cart(){let a=Object.entries(S.cart).map(([id,q])=>({x:p(id),q})).filter(i=>i.x),n=a.reduce((s,i)=>s+i.q,0),t=a.reduce((s,i)=>s+i.x.price*i.q,0);E.count.textContent=n;E.total.textContent=money(t);E.items.innerHTML=a.length?a.map(i=>`<article class="line" data-id="${i.x.id}"><img src="${i.x.images[0]}" alt=""><div><strong>${i.x.name}</strong><small>${money(i.x.price)}</small><div class="qty"><button data-d="-1">−</button><span>${i.q}</span><button data-d="1">+</button></div></div><button class="remove">×</button></article>`).join(""):'<div class="drawerEmpty">Tu carrito está vacío.</div>';$$('[data-d]',E.items).forEach(b=>b.onclick=()=>{let id=b.closest('[data-id]').dataset.id;S.cart[id]+=Number(b.dataset.d);if(S.cart[id]<=0)delete S.cart[id];save();cart()});$$('.remove',E.items).forEach(b=>b.onclick=()=>{delete S.cart[b.closest('[data-id]').dataset.id];save();cart()})}function open(){E.drawer.classList.add("open");E.overlay.hidden=false;document.body.classList.add("lock")}function close(){E.drawer.classList.remove("open");E.overlay.hidden=true;document.body.classList.remove("lock")}function order(){let a=Object.entries(S.cart).map(([id,q])=>({x:p(id),q})).filter(i=>i.x);if(!a.length)return;let total=a.reduce((s,i)=>s+i.x.price*i.q,0),m=[`Hola ${S.store.brand} 👋`,`Quiero confirmar disponibilidad:`,"",...a.map(i=>`• ${i.q} x ${i.x.name} — ${money(i.x.price*i.q)}`),"",`Total estimado: ${money(total)}`,"","Nombre:","Ciudad:"].join("\n"),num=String(S.store.whatsappNumber||"").replace(/\D/g,"");if(num)window.open(`https://wa.me/${num}?text=${encodeURIComponent(m)}`,"_blank");else navigator.clipboard.writeText(m).then(()=>toast("Pedido copiado. Configura WhatsApp en data/store.json"))}async function init(){let [st,pr]=await Promise.all([fetch("data/store.json",{cache:"no-store"}).then(r=>r.json()),fetch("data/products.json",{cache:"no-store"}).then(r=>r.json())]);S.store=st;S.products=pr.products||[];categories();render()}E.search.oninput=e=>{S.query=e.target.value;render()};E.sort.onchange=e=>{S.sort=e.target.valuLine truncated
(()=>{"use strict";const KEY="ari-shopping-cart-v2-1";const state={store:null,products:[],category:"Todos",collection:"",query:"",sort:"featured",cart:read()};const $=(s,r=document)=>r.querySelector(s),$$=(s,r=document)=>[...r.querySelectorAll(s)];const e={grid:$("#productGrid"),tpl:$("#productTemplate"),chips:$("#filterChips"),sort:$("#sortSelect"),summary:$("#catalogSummary"),empty:$("#emptyState"),reset:$("#resetButton"),searchOpen:$("#searchOpen"),searchPanel:$("#searchPanel"),searchInput:$("#searchInput"),searchClose:$("#searchClose"),cartBtn:$("#cartButton"),drawer:$("#cartDrawer"),cartClose:$("#cartClose"),cartItems:$("#cartItems"),cartCount:$("#cartCount"),checkout:$("#checkoutButton"),clear:$("#clearCart"),overlay:$("#overlay"),dialog:$("#productDialog"),dialogClose:$("#dialogClose"),dialogContent:$("#dialogContent"),toast:$("#toastRegion"),availability:$("#availabilityLink")};function read(){try{return JSON.parse(localStorage.getItem(KEY))||{}}catch{return{}}}function save(){localStorage.setItem(KEY,JSON.stringify(state.cart))}function norm(v){return String(v||"").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g,"").trim()}function esc(v){return String(v??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#039;")}async function load(){try{state.store=await(await fetch("data/store.json",{cache:"no-store"})).json();state.products=(await(await fetch(state.store.catalogUrl,{cache:"no-store"})).json()).products||[];e.availability.href=state.store.whatsappUrl;renderFilters();renderAll()}catch(err){console.error(err);e.summary.textContent="No se pudo cargar el catálogo.";e.empty.hidden=false}}function cats(){return["Todos",...new Set(state.products.map(p=>p.category))]}function renderFilters(){e.chips.innerHTML=cats().map(c=>`<button class="filter-chip ${state.category===c&&!state.collection?"active":""}" type="button" data-filter-category="${esc(c)}">${esc(c)}</button>`).join("")}function filtered(){const q=norm(state.query);return state.products.filter(p=>{if(state.category!=="Todos"&&p.category!==state.category)return false;if(state.collection&&p.collection!==state.collection)return false;if(!q)return true;return norm([p.name,p.category,p.collection,p.description,p.dimensions].join(" ")).includes(q)}).sort((a,b)=>state.sort==="name"?a.name.localeCompare(b.name,"es"):state.sort==="newest"?Number(!!b.new)-Number(!!a.new):Number(!!b.featured)-Number(!!a.featured)||Number(!!b.new)-Number(!!a.new)||a.name.localeCompare(b.name,"es"))}function renderProducts(){const list=filtered();e.grid.innerHTML="";e.empty.hidden=!!list.length;e.summary.textContent=`${list.length} producto${list.length===1?"":"s"} reales de Ari Shopping`;list.forEach(p=>{const f=e.tpl.content.cloneNode(true),card=$(".product-card",f);card.dataset.productId=p.id;const img=$("img",f);img.src=p.images[0];img.alt=p.name;$(".product-category",f).textContent=p.category;$(".product-collection",f).textContent=p.collection;$(".product-name",f).textContent=p.name;$(".product-description",f).textContent=p.description;$$(".product-open",f).forEach(b=>b.addEventListener("click",()=>openProduct(p.id)));$(".add-button",f).addEventListener("click",()=>add(p.id));e.grid.appendChild(f)})}function byId(id){return state.products.find(p=>p.id===id)}function add(id){const p=byId(id);if(!p)return;state.cart[id]=Number(state.cart[id]||0)+1;save();renderCart();toast(`${p.name} agregado al pedido.`)}function update(id,q){if(q<=0)delete state.cart[id];else state.cart[id]=q;save();renderCart()}function entries(){return Object.entries(state.cart).map(([id,q])=>({product:byId(id),quantity:Number(q)})).filter(x=>x.product&&x.quantity>0)}function renderCart(){const list=entries();e.cartCount.textContent=String(list.reduce((s,x)=>s+x.quantity,0));if(!list.length){e.cartItems.innerHTML='<div class="cart-empty"><strong>Tu selección está vacía</strong><p>Agrega productos para preparar el pedido.</p></div>';e.checkout.disabled=true;return}e.checkout.disabled=false;e.cartItems.innerHTML=list.map(({product:p,quantity:q})=>`<article class="cart-line" data-product-id="${esc(p.id)}"><img src="${esc(p.images[0])}" alt=""><div><strong>${esc(p.name)}</strong><small>${esc(p.collection)} · Consultar precio</small><div class="qty"><button type="button" data-change="-1">−</button><span>${q}</span><button type="button" data-change="1">+</button></div></div><button type="button" data-remove>×</button></article>`).join("");$$("[data-change]",e.cartItems).forEach(b=>b.addEventListener("click",()=>{const id=b.closest("[data-product-id]").dataset.productId;update(id,Number(state.cart[id])+Number(b.dataset.change))}));$$("[data-remove]",e.cartItems).forEach(b=>b.addEventListener("click",()=>update(b.closest("[data-product-id]").dataset.productId,0)))}function orderText(list){return["Hola Ari Shopping 👋","Quiero consultar disponibilidad para:","",...list.map(({product:p,quantity:q})=>`• ${q} x ${p.name}`),"","Nombre:"Line truncated