feat: redesign the Site order flow and fix the cart layout
All checks were successful
Build and deploy / Validate source (push) Successful in 5s
Build and deploy / Integration suite on a real stack (push) Successful in 2m23s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m48s

New landing (hero with a sheet preview, four steps, product cards priced
from the checkout table, price table and benefits), a progress bar that
follows the order through Montagem, Dados e entrega and Pagamento, the
live sheet beside the artworks, and a running total bar on phones.

The cart's saved-in-browser note no longer takes a grid column, which had
pushed the order into a narrow strip and the summary below it. The
previous look is kept in tag ui-v1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-24 16:33:06 -03:00
parent 2e03b0362b
commit 177882e77b
5 changed files with 356 additions and 237 deletions

72
web/site-steps.js Normal file
View File

@@ -0,0 +1,72 @@
/* Site DTF — the order progress bar, the product card prices and the phone
total bar. This file only reads the page's state (which panels are open,
the cart total, the checkout status); it never changes an order, a price or
a file, so the flow in the other site-*.js files behaves exactly as before. */
(function(){
// Card prices come from the same table the checkout uses, so they cannot drift.
document.querySelectorAll('[data-de]').forEach(el=>{ el.textContent=rs(pisoEscada(el.dataset.de)); });
document.querySelectorAll('[data-ate]').forEach(el=>{ el.textContent=rs(TABELA[el.dataset.ate]); });
const passos=$('passos'), barra=$('barraM');
// The store header is sticky; the progress bar and the side panels sit below it.
function alturas(){
const topo=document.querySelector('.topo')?.offsetHeight||0;
document.documentElement.style.setProperty('--topo',topo+'px');
document.documentElement.style.setProperty('--topo-passos',(topo+(passos.hidden?0:passos.offsetHeight)+16)+'px');
}
const foco=$('foco'), carr=$('carr'), status=$('checkoutStatus'), acoes=$('checkoutActions');
function naTela(el){
const r=el.getBoundingClientRect();
return r.top < innerHeight*0.55 && r.bottom > innerHeight*0.2;
}
function etapa(){
// Once the cart is sent, the quote, the payment and the order live in the
// checkout status: that is the payment step.
if((status.textContent||'').trim() || acoes.children.length) return 3;
if(carr.classList.contains('on') && (naTela(carr) || !foco.classList.contains('on'))) return 2;
return 1;
}
let pendente=false;
function pinta(){
pendente=false;
const ativo=foco.classList.contains('on') || carr.classList.contains('on') || (status.textContent||'').trim();
passos.hidden=!ativo; alturas();
if(ativo){
const atual=etapa();
passos.querySelectorAll('[data-passo]').forEach(a=>{
const n=Number(a.dataset.passo);
a.classList.toggle('atual',n===atual); a.classList.toggle('feito',n<atual);
if(n===atual) a.setAttribute('aria-current','step'); else a.removeAttribute('aria-current');
});
}
// Phone bar: the running total while the cart itself is off screen.
const temPedido=carr.classList.contains('on');
const mostrar=temPedido && !naTela(carr) && !(status.textContent||'').trim();
barra.hidden=!mostrar;
if(mostrar){
$('barraMTot').textContent=$('carrTot').textContent;
$('barraMSub').textContent=foco.classList.contains('on')?'Total até agora':'Seu pedido';
}
}
function agenda(){ if(!pendente){ pendente=true; requestAnimationFrame(pinta); } }
const obs=new MutationObserver(agenda);
obs.observe(foco,{attributes:true,attributeFilter:['class']});
obs.observe(carr,{attributes:true,attributeFilter:['class']});
obs.observe(status,{childList:true,characterData:true,subtree:true});
obs.observe(acoes,{childList:true});
obs.observe($('carrTot'),{childList:true,characterData:true,subtree:true});
addEventListener('scroll',agenda,{passive:true});
addEventListener('resize',agenda);
addEventListener('dtf-cart-changed',agenda);
// Steps are links; step 3 only makes sense once there is something to pay.
passos.querySelectorAll('a').forEach(a=>a.addEventListener('click',e=>{
const destino=document.querySelector(a.getAttribute('href'));
if(!destino || destino.offsetParent===null){ e.preventDefault(); return; }
e.preventDefault(); destino.scrollIntoView({behavior:'smooth',block:'start'});
}));
$('barraMBtn').addEventListener('click',e=>{ e.preventDefault(); carr.scrollIntoView({behavior:'smooth',block:'start'}); });
pinta();
})();