feat: redesign the Montagem page, with thumbnails, check chips and a DPI price scale
All checks were successful
Build and deploy / Validate source (push) Successful in 6s
Build and deploy / Integration suite on a real stack (push) Successful in 2m31s
Build and deploy / Secret scan and release gate (push) Successful in 6s
Build and deploy / Publish images (push) Successful in 1m0s

The product page is rebuilt around mockup 1. The two ways to send are
cards under the title; the PNG/CDR route choice is gone (it never changed
the price, and the picker now takes every allowed format). Each file card
has a thumbnail, its size, check chips with a plain sentence for any
problem, a "Ver detalhes da conferência" link and a copies stepper; a CDR
file says "Conferência manual · preço cheio" and asks for the length. The
buy box shows the price scale in DPI with the customer's step, the metre
and 10 cm length, the total and what the resolution saved, and "Sai em N
partes" only above 20 m. Folha já montada gets "Ampliar" instead of zoom.
Pricing, grade and checks are unchanged.

Fixes on top: the card thumbnail is a small image made once, not the full
preview re-parsed on every repaint; sizes from 1 GB up read in GB; a failed
upload is shown with "Tentar de novo" instead of retrying silently on every
cart change. The browser suites wait for the product and the reloaded page
instead of racing them, and the artwork suite delays imagemDaArte, which
the packing now uses, fixing the CI failure of f4aacb5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Cauê Faleiros
2026-09-29 16:36:41 -03:00
parent f4aacb5776
commit 8dddf0fa25
13 changed files with 335 additions and 240 deletions

View File

@@ -112,12 +112,21 @@
return window.dtfUpload(file,{api,scope:session.cart_scope,progress:()=>{},
onBytes:n=>{e.sent=n;pintaEnvio();}});
})();
e.promise.then(()=>{e.done=true;pintaEnvio();},
error=>{e.failed=error;envios.delete(k);pintaEnvio();});
e.promise.then(()=>{e.done=true;falhas.delete(k);pintaEnvio();},
error=>{envios.delete(k);falhas.set(k,{file,error});pintaEnvio();});
envios.set(k,e);
}
return e.promise;
}
// A failed upload is shown and waits for the customer, never retried behind
// their back: a file the check refused would fail again on every change.
const falhas=new Map();
function tentaDeNovo() {
const arquivos=[...falhas.values()].map(f=>f.file);
falhas.clear();
arquivos.forEach(f=>enviar(f).catch(()=>{}));
pintaEnvio();
}
const arquivosDoCarrinho=()=>[...pedido,...(busy&&itemAtual?[itemAtual]:[])].flatMap(it=>it.localFiles||[]);
const gb=n=>(n/1073741824).toLocaleString('pt-BR',{maximumFractionDigits:1})+' GB';
const mb=n=>n>=1073741824 ? gb(n) : Math.round(n/1048576)+' MB';
@@ -149,6 +158,20 @@
function pintaEnvio() {
const e=estadoEnvio(), texto=textoEnvio(e);
const el=document.getElementById('envioArq');
const falhou=arquivosDoCarrinho().map(f=>falhas.get(chaveArquivo(f))).filter(Boolean);
const topo=document.getElementById('cartEnvio');
if (falhou.length) {
if (el) {
el.replaceChildren(document.createTextNode('Não foi possível enviar '+falhou[0].file.name+': '+
(falhou[0].error?.message||'erro no envio').replace(/\.$/,'')+(falhou.length>1?' (e mais '+(falhou.length-1)+')':'')+'. '));
const b=document.createElement('button'); b.type='button'; b.className='envioDeNovo'; b.textContent='Tentar de novo';
b.onclick=tentaDeNovo; el.append(b);
el.classList.remove('ok'); el.classList.add('erro');
}
if (topo) topo.textContent='· falha no envio';
return;
}
if (el) el.classList.remove('erro');
if (el) {
el.replaceChildren();
if (e && e.fase!=='pronto') {
@@ -160,7 +183,6 @@
el.classList.toggle('ok', e?.fase==='pronto');
}
// Visible on every page while it runs.
const topo=document.getElementById('cartEnvio');
if (topo) topo.textContent = !e || e.fase==='pronto' ? '' : e.fase==='verificando' ? '· verificando' : '· enviando '+e.pct+'%';
if (busy && texto) {
message(texto);
@@ -173,7 +195,10 @@
if (e && e.fase==='enviando') { event.preventDefault(); event.returnValue=''; }
});
// Only what is in the cart: the item on the product page may still change.
window.addEventListener('dtf-cart-changed',()=>{ arquivosDoCarrinho().forEach(f=>{ enviar(f).catch(()=>{}); }); pintaEnvio(); });
window.addEventListener('dtf-cart-changed',()=>{
arquivosDoCarrinho().forEach(f=>{ if(!falhas.has(chaveArquivo(f))) enviar(f).catch(()=>{}); });
pintaEnvio();
});
async function upload(file) {
return enviar(file);
}

View File

@@ -234,8 +234,6 @@ h1 em{font-style:normal;color:var(--laranja)}
font-family:"Inter",sans-serif;font-weight:700}
/* upload */
/* o cliente escolhe o caminho antes de subir · a declaração orienta,
mas quem decide o preço é o arquivo que chegar */
.caminhos{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-bottom:13px}
@media(max-width:620px){.caminhos{grid-template-columns:1fr}}
.caminhos>*{min-width:0}
@@ -254,7 +252,6 @@ h1 em{font-style:normal;color:var(--laranja)}
.cam span{display:block;font-size:10.5px;color:var(--fraco);line-height:1.45}
.cam em{display:block;margin-top:6px;font-style:normal;font-family:"Inter",sans-serif;
font-weight:700;font-size:12px;color:var(--verde-ml);line-height:1.3}
.cam em.cheio{color:var(--vermelho)}
.trocouCam{margin-top:-4px;margin-bottom:11px;padding:10px 12px;border-radius:9px;
background:#FFF6E8;border:1px solid #F0D9AE;font-size:11.5px;color:#7A5A16;line-height:1.5}
.trocouCam b{color:#B07A18;font-family:"Inter",sans-serif;font-weight:700}
@@ -301,12 +298,6 @@ h1 em{font-style:normal;color:var(--laranja)}
/* repetição do FILE */
.rep{background:var(--cinza);border-radius:9px;padding:12px 14px;margin-top:8px}
.somaF{margin-top:10px;padding:11px 13px;border-radius:9px;background:#F1FBF5;
border:1px solid #BFE6CE;display:flex;flex-wrap:wrap;align-items:baseline;gap:8px}
.somaF span{font-size:11.5px;color:#2A6B45}
.somaF b{font-family:"Inter",sans-serif;font-weight:700;font-size:15px;color:#1F5C3A;
margin-left:auto}
.somaF em{flex-basis:100%;font-style:normal;font-size:10.5px;color:#4A7A5E}
.rep .l1{display:flex;justify-content:space-between;gap:10px;align-items:center;margin-bottom:9px}
.rep .l1 b{font-weight:500;font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rep .l1 button{border:0;background:none;color:var(--fraco);cursor:pointer;font-size:15px;flex:none}
@@ -317,33 +308,10 @@ h1 em{font-style:normal;color:var(--laranja)}
.progF{margin:9px 0 4px;padding:9px 11px;border-radius:7px;background:#FFF6E8;
border:1px solid #F0D9AE;font-size:11px;color:#7A5A16;line-height:1.5}
.progF b{font-family:"Inter",sans-serif;font-weight:700;color:#B07A18}
.rep .cps{display:grid;grid-template-columns:120px 1fr;gap:10px;align-items:end}
.rep label{display:block;font-size:10px;color:var(--fraco);margin-bottom:3px}
.rep input{width:100%;padding:7px 9px;border:1px solid var(--linha);border-radius:6px;
font-family:inherit;font-size:13px;background:#fff;color:var(--tx)}
.rep input:focus{outline:none;border-color:var(--laranja)}
.rep .conta{font-size:11.5px;color:var(--fraco);line-height:1.4}
.rep .conta b{color:var(--tx);font-family:"Inter",sans-serif}
.med{background:#fff;border:1px solid var(--linha);border-radius:8px;padding:10px 12px;margin-top:9px}
.med b{display:block;font-family:"Inter",sans-serif;font-size:15px;font-weight:700;color:var(--tx)}
.med span{display:block;font-size:11px;color:var(--fraco);margin-top:2px}
.med em{display:block;font-style:normal;font-size:11.5px;color:#B07A18;margin-top:7px;line-height:1.45}
.med.alerta{border-color:#E8A796;background:#FFF3F0}
.med.alerta b{color:#8C3B22}
.med.alerta em{color:#8C3B22}
.med.pede{border-color:var(--laranja);background:#FFFAF2}
.med.pede b{font-size:12.5px;color:#B07A18}
.conf{margin-top:9px;padding:9px 11px;border-radius:7px;background:#F1FBF5;
border:1px solid #BFE6CE;line-height:1.5}
.conf b{display:block;font-family:"Inter",sans-serif;font-size:11.5px;color:#1F5C3A;
margin-bottom:3px}
.conf span{display:block;font-size:10.5px;color:#2A6B45}
.med .troca{margin-top:9px;padding:9px 11px;border-radius:7px;background:#F1FBF5;
border:1px solid #BFE6CE;font-size:11.5px;color:#1F5C3A;line-height:1.5}
.med .troca b{display:inline;font-size:11.5px;color:#1F5C3A;font-weight:700}
.med input{width:100%;margin-top:8px;padding:8px 10px;border:1px solid var(--linha);
border-radius:6px;font-family:inherit;font-size:13px;background:#fff;color:var(--tx)}
.med input:focus{outline:none;border-color:var(--laranja)}
/* fila de artes ao lado da montagem */
.foco2{display:grid;grid-template-columns:minmax(340px,1fr) 320px;gap:20px;
@@ -929,55 +897,39 @@ footer a:hover{color:var(--laranja2)}
<div class="cat" id="catFoco">
<div class="cabfoco" style="padding:20px 22px 14px;margin:0">
<div><h3 id="fTit">—</h3><span class="lg" id="fLg" style="display:block"></span></div>
<span class="pr2" id="fPreco"></span>
<p id="fSub"></p>
<div class="caminhos tipoEnvio" id="tipoEnvio" hidden>
<button type="button" class="cam on" data-tipo="folha">
<span class="mk2"></span>
<div><b>Folha já montada</b>
<span>a imagem ocupa a largura inteira do filme de
<span id="larguraFolhaPronta">57</span> cm</span>
<em id="tipoFolhaPreco">—</em></div>
<div><b>Folha já montada</b><em id="tipoFolhaPreco">—</em>
<span>Você manda a folha pronta, com
<span id="larguraFolhaPronta">57</span> cm de largura</span></div>
</button>
<button type="button" class="cam" data-tipo="avulsa">
<span class="mk2"></span>
<div><b>Artes separadas</b>
<span>você informa a largura de cada uma e nós montamos a folha</span>
<em id="tipoAvulsaPreco">—</em></div>
<div><b>Artes separadas</b><em id="tipoAvulsaPreco">—</em>
<span>Você manda cada arte e nós montamos a folha</span></div>
</button>
</div>
</div>
<div class="in" style="padding:0 22px 22px">
<div class="foco2" id="foco2">
<div class="fila">
<div class="trocouCam" id="trocouCam" style="display:none"></div>
<div class="trocouCam" id="avisoTipo" style="display:none"></div>
<div class="caminhos" id="caminhos">
<button class="cam on" data-cam="auto">
<span class="mk2"></span>
<div><b>PNG, JPG ou PDF</b>
<span>Conferimos sozinhos e a nota desconta no metro</span>
<em id="camA">—</em></div>
</button>
<button class="cam" data-cam="tabela">
<span class="mk2"></span>
<div><b>CDR, AI, PSD ou TIFF</b>
<span>Ninguém consegue conferir · alguém abre na mão</span>
<em id="camB" class="cheio">—</em></div>
</button>
</div>
<div class="recusa" id="recusa" style="display:none"></div>
<div id="lista"></div>
<div class="zona" id="zona" tabindex="0" role="button">
<div class="ico">↑</div>
<b id="zTit">Arraste aqui</b><span id="zSub"></span>
<span id="zLimite">Até 5 GB por arquivo.</span>
<span class="bt">ou escolher no computador</span>
<span class="zOutra"><b id="zOutra">Adicionar outra folha</b> ou arraste aqui</span>
<span class="zManual" id="zManual">Tem <b>CDR, AI, PSD ou TIFF</b>? Pode mandar também.
Alguém abre na mão e o metro sai pelo preço cheio.</span>
</div>
<div class="recusa" id="recusa" style="display:none"></div>
<div id="lista"></div>
<div class="resumoA" id="rA" style="display:none"></div>
<input type="file" id="inp" hidden>
<aside class="req" id="req">
<h3 class="reqcab">Dicas sobre a arte</h3>
<h3 class="reqcab">Dúvidas:</h3>
<details>
<summary>Como pagar o menor metro</summary>
@@ -1047,17 +999,22 @@ footer a:hover{color:var(--laranja2)}
<div class="zoomB"><button id="zMenos" title="Diminuir">−</button>
<span id="zTxt">100%</span>
<button id="zMais" title="Aumentar">+</button></div>
<button type="button" class="ampliar" id="bAmpliar" hidden>Ampliar</button>
<span id="vMt">—</span></div>
<div id="vArea"></div>
<div id="vUso"></div>
<div class="compra" id="compra">
<div class="aoVivo" id="aoVivo"></div>
<div id="compraVals" hidden>
<div class="cNota"><span>Nota da arte</span><i><i id="cNotaBar"></i></i><b id="cNota">—</b></div>
<div class="cLin"><span>Preço do metro</span><span><s id="cTabela"></s><b id="cMetro">—</b></span></div>
<div class="cLin"><span>Metragem cobrada</span><b id="cMetragem">—</b></div>
<div class="cEscada">
<span>Preço do metro pela resolução</span>
<ol id="cEscada"></ol>
</div>
<div class="cLin"><span id="cMetroLbl">Metro</span><b id="cMetro">—</b></div>
<div class="cLin"><span>Metragem <small>(a cada 10 cm)</small></span><b id="cMetragem">—</b></div>
<p class="cObs" id="cObs"></p>
<div class="cTot"><span>Total</span><b id="cTotal">—</b></div>
<p class="cEco" id="cEco"></p>
<div id="qmsg" style="display:none"></div>
<div class="ciente" id="ciente">
<h4 id="cienteTit">Ressalva de resolução</h4>
@@ -1067,10 +1024,15 @@ footer a:hover{color:var(--laranja2)}
</div>
</div>
<button type="button" class="cAdd" id="bAdd" disabled>Adicionar ao carrinho</button>
<p class="cPe">Revisão de 5 pontos e reencaixe grátis</p>
<p class="cPe">Revisamos e reencaixamos antes de imprimir, sem custo.<span id="cPartes"></span></p>
</div>
</aside>
<dialog class="ampliaDlg" id="ampliaDlg" aria-labelledby="ampliaTit">
<div class="ampliaCab"><b id="ampliaTit">Sua folha</b>
<button type="button" id="ampliaFecha" aria-label="Fechar">×</button></div>
<div id="ampliaArea"></div>
</dialog>
</div>
</div>
</div>

View File

@@ -120,7 +120,7 @@ function carrinho(){
previa();
if(nota<90){
const piso=pisoEscada(), ganho=(base-piso)*cob;
if(ganho>0.5) qm('<b>Dá para chegar ao melhor preço.</b> Corrigindo os pontos e chegando a 90, '+
if(ganho>0.5) qm('<b>Dá para pagar menos.</b> Com a arte em 300 DPI, '+
'o metro cai para '+rs(piso)+' — são '+rs(ganho)+' a menos neste pedido.','var(--verde)');
}
}

View File

@@ -1,4 +1,4 @@
/* Site DTF — the product page's buy box: grade, price per metre, billed length,
/* Site DTF — the product page's buy box: price ladder, price per metre, billed length,
total and the add-to-cart button, beside the live sheet. It shows the item
the other site-*.js files already priced (itemAtual) and never computes a
price of its own, so the box and the cart always agree. */
@@ -10,18 +10,29 @@
add.disabled=!it || !itemPodeEnviar(it);
if(!it) return;
const semNota=!it.nota;
$('cNota').textContent = semNota ? 'sem nota' : it.nota;
$('cNotaBar').style.width=(semNota?0:it.nota)+'%';
$('cNotaBar').className = semNota ? '' : it.nota>=85 ? 'ok' : it.nota>=50 ? 'av' : 'er';
$('cTabela').textContent = TABELA[it.modo]-it.unit>0.001 ? rs(TABELA[it.modo]) : '';
// The price ladder in resolution: nota = DPI ÷ 3, so 90 is 270 DPI.
const faixas=FAIXAS[it.modo], ativa=faixas.findIndex(([min])=>it.nota>=min);
$('cEscada').innerHTML=faixas.map(([min,v],i)=>
'<li'+(i===ativa?' class="on" aria-current="true"':'')+'><span>'+
(min? min*3+'+'+(i? '' : ' DPI') : semNota? 'cheio' : 'menos')+'</span>'+
'<b>'+rs(v).slice(3)+'</b></li>').join('');
const analisadas=ehFolha(it.modo) && folhas.length && folhas.every(x=>x.an);
$('cMetroLbl').textContent = semNota ? 'Metro · preço cheio'
: analisadas && folhas.every(x=>x.an.vetor) ? 'Metro · arte vetorial'
: analisadas ? 'Metro · '+Math.min(...folhas.map(x=>x.an.dpi))+' DPI'
: 'Metro';
$('cMetro').textContent=rs(it.unit);
$('cMetragem').textContent=fmtM(it.cob)+' m';
const economia=(TABELA[it.modo]-it.unit)*it.cob;
$('cEco').textContent = economia>0.005 ? rs(economia)+' a menos pela resolução da arte' : '';
const partes=ehFolha(it.modo) ? Math.ceil(it.metros/20) : 1;
$('cPartes').textContent = partes>1 ? ' Sai em '+partes+' partes de até 20 m.' : '';
const sobraCm=Math.round((it.cob-it.metros)*100);
$('cObs').textContent =
it.cob===MINIMO_M && it.metros<MINIMO_M
? (ehFolha(it.modo) ? 'Pedido mínimo de 1 m.'
: 'Pedido mínimo de 1 m: ainda cabem '+sobraCm+' cm de artes sem custo.')
: sobraCm>0 ? fmtM(it.metros)+' m de folha, arredondado a cada 10 cm.' : '';
: '';
$('cTotal').textContent=rs(it.total);
}
add.addEventListener('click',()=>adicionaAoCarrinho());

View File

@@ -5,28 +5,19 @@
const $=id=>document.getElementById(id);
const rs=v=>'R$ '+v.toFixed(2).replace('.',',');
const fmt=b=>b<1048576?(b/1024).toFixed(0)+' KB':(b/1048576).toFixed(1).replace('.',',')+' MB';
const fmt=b=>b<1048576?(b/1024).toFixed(0)+' KB'
:b<1073741824?(b/1048576).toFixed(1).replace('.',',')+' MB':(b/1073741824).toFixed(1).replace('.',',')+' GB';
// ── modos
const MODOS={
file: {tit:'Arquivo por metro', lg:'DTF têxtil · 57 cm', larg:57,
preco:'R$ 14,90 a R$ 19,90 /m conforme a nota', multi:true, folha:true,
sub:'Sua folha montada. Revisamos, corrigimos e reencaixamos antes de imprimir.',
zt:'Arraste suas folhas montadas',
zs:'uma ou várias · PNG, JPG, TIFF ou PDF · PSD, AI e CDR passam por tratamento'},
avulsa:{tit:'Artes avulsas', lg:'DTF têxtil · 57 cm', larg:57,
preco:'R$ 24,90 a R$ 29,90 /m conforme a nota', multi:true,
sub:'Cada arte num arquivo. Montamos a folha com o melhor encaixe.',
file: {tit:'Arquivo por metro', lg:'DTF têxtil · 57 cm', larg:57, multi:true, folha:true,
zt:'Arraste sua folha montada aqui', zs:'PNG, JPG ou PDF · 57 cm de largura'},
avulsa:{tit:'Artes avulsas', lg:'DTF têxtil · 57 cm', larg:57, multi:true,
zt:'Arraste suas artes aqui',
zs:'PNG ou JPG com fundo transparente · informe a largura de cada uma'},
uvfile:{tit:'Arquivo por metro · UV', lg:'Adesivo UV · 28,5 cm', larg:28.5,
preco:'R$ 69,90 a R$ 85,90 /m conforme a nota', multi:true, folha:true,
sub:'Sua folha de UV montada. Revisamos, corrigimos e reencaixamos antes de imprimir.',
zt:'Arraste suas folhas de UV',
zs:'uma ou várias · PNG, JPG, TIFF ou PDF · PSD, AI e CDR passam por tratamento'},
uv: {tit:'Artes avulsas · UV', lg:'Adesivo UV · 28,5 cm', larg:28.5,
preco:'R$ 83,90 a R$ 99,90 /m conforme a nota', multi:true,
sub:'Cada arte de UV num arquivo. Montamos a folha com o melhor encaixe.',
uvfile:{tit:'Arquivo por metro · UV', lg:'Adesivo UV · 28,5 cm', larg:28.5, multi:true, folha:true,
zt:'Arraste sua folha de UV aqui', zs:'PNG, JPG ou PDF · 28,5 cm de largura'},
uv: {tit:'Artes avulsas · UV', lg:'Adesivo UV · 28,5 cm', larg:28.5, multi:true,
zt:'Arraste suas artes de UV',
zs:'PNG ou JPG com fundo transparente · informe a largura de cada arte'}
};
@@ -77,7 +68,6 @@ let pedido=[]; // itens já fechados · o pagamento
// ao que a expedição já recebe hoje, sem criar processo novo.
let entrega={tipo:'retira', cep:'', valor:0, cotado:true, end:{}};
let cliente={cnpj:'', zap:'', mail:''};
let caminho='auto'; // o que o cliente diz que vai mandar
// CNPJ com os dois dígitos verificadores · máscara sem conta não vale nada
function cnpjOk(v){

View File

@@ -1,4 +1,4 @@
/* Site DTF — Carousel, path selector, delivery choice and input masks.
/* Site DTF — Carousel, delivery choice and input masks.
Extracted verbatim from the single inline script in web/index.html.
Loaded as classic scripts in the order listed there: they share one global
scope and run top to bottom, exactly as the original did. */
@@ -73,31 +73,6 @@ $('pVolta').addEventListener('click',()=>{
$('cPro').addEventListener('click',()=>ir(atual()+1));
})();
// A declaração de formato só orienta: filtra o seletor e mostra o preço esperado.
// Se o arquivo que chegar contrariar, o arquivo ganha e o site avisa.
const AUTO=/[.](png|jpe?g|webp|pdf)$/i;
function pintaCaminhos(){
if(!$('caminhos')) return;
const ehF=modo&&ehFolha();
$('caminhos').style.display = ehF? '' : 'none';
if(!ehF) return;
document.querySelectorAll('#caminhos .cam').forEach(b=>b.classList.toggle('on', b.dataset.cam===caminho));
$('camA').textContent='a partir de '+rs(pisoEscada());
$('camB').textContent=rs(TABELA[modo])+' fixo';
$('inp').accept = caminho==='auto' ? '.png,.jpg,.jpeg,.pdf'
: '.tif,.tiff,.psd,.psb,.ai,.cdr';
$('zSub').textContent = caminho==='auto'
? 'PNG, JPG ou PDF · conferência automática, nota na tela'
: 'CDR, AI, PSD ou TIFF · sem conferência, metro pela tabela';
}
document.querySelectorAll('#caminhos .cam').forEach(b=>b.addEventListener('click',()=>{
caminho=b.dataset.cam; avisoCam(''); pintaCaminhos();
}));
function avisoCam(txt){
const el=$('trocouCam'); if(!el) return;
el.innerHTML=txt; el.style.display = txt? '' : 'none';
}
function pintaEntrega(){
document.querySelectorAll('.opE').forEach(b=>
b.classList.toggle('on', b.dataset.ent===entrega.tipo));
@@ -150,6 +125,14 @@ $('zMenos').addEventListener('click',()=>{
if(zoomI>0){ zoomI--; pintaZoom(); previaAoVivo(); }
});
pintaZoom();
// A ready sheet has nothing to zoom: "Ampliar" shows the same preview bigger.
$('bAmpliar').addEventListener('click',()=>{
const previas=$('vArea').querySelector('.folhaPrevias'); if(!previas) return;
$('ampliaArea').replaceChildren(previas.cloneNode(true));
$('ampliaDlg').showModal();
});
$('ampliaFecha').addEventListener('click',()=>$('ampliaDlg').close());
$('ampliaDlg').addEventListener('click',e=>{ if(e.target===$('ampliaDlg')) $('ampliaDlg').close(); });
document.querySelectorAll('.opE').forEach(b=>b.addEventListener('click',()=>{
entrega.tipo=b.dataset.ent;

View File

@@ -15,7 +15,7 @@ function abrir(m){
avisoTipo('');
pintaModo();
$('lista').innerHTML=''; $('rA').style.display='none'; recusa([]);
recemChegada=null; caminho='auto'; avisoCam(''); pintaCaminhos();
recemChegada=null;
$('vMt').textContent='—'; previaAoVivo(); agendaAvaliacao();
limpaPaineis(); // o carrinho só some se o pedido estiver vazio
vaiPara(PAGINAS[m]);
@@ -25,10 +25,13 @@ function pintaModo(){
const c=MODOS[m];
$('cards').style.display='none'; $('foco').classList.add('on');
$('fTit').textContent=c.tit; $('fLg').textContent=c.lg;
$('fPreco').textContent=c.preco; $('fSub').textContent=c.sub;
$('zTit').textContent=c.zt; $('zSub').textContent=c.zs;
$('zManual').hidden=!ehFolha(m);
$('zOutra').textContent = ehFolha(m) ? 'Adicionar outra folha' : 'Adicionar mais artes';
$('zona').classList.remove('compacta');
$('inp').multiple=c.multi;
$('inp').accept = ehFolha(m) ? '.png,.jpg,.jpeg,.pdf'
// The file decides the price: PNG, JPG and PDF are checked here, the others by hand.
$('inp').accept = ehFolha(m) ? '.png,.jpg,.jpeg,.pdf,.tif,.tiff,.psd,.psb,.ai,.cdr'
: '.png,.jpg,.jpeg,.webp';
$('catFoco').className='cat '+(ehFolha(m)?'file':'av');
$('larguraFolhaPronta').textContent=String(c.larg).replace('.',',');
@@ -46,8 +49,8 @@ function pintaTipoEnvio(){
if(el.hidden) return;
const folha = ehFolha() ? modo : PAR[modo];
const avulsa = ehFolha() ? PAR[modo] : modo;
$('tipoFolhaPreco').textContent='a partir de '+rs(pisoEscada(folha));
$('tipoAvulsaPreco').textContent='a partir de '+rs(pisoEscada(avulsa))+' · montagem inclusa';
$('tipoFolhaPreco').textContent=rs(pisoEscada(folha))+' a '+rs(TABELA[folha]).slice(3)+' /m';
$('tipoAvulsaPreco').textContent='+ '+rs(MONTAGEM[avulsa])+' /m';
$('larguraFolhaPronta').textContent=String(MODOS[folha].larg).replace('.',',');
const atual = ehFolha() ? 'folha' : 'avulsa';
el.querySelectorAll('.cam').forEach(b=>{
@@ -89,8 +92,7 @@ function trocaTipo(destino){
modo=destino;
history.replaceState(history.state,'',PAGINAS[modo]); document.title=tituloPagina();
$('lista').innerHTML=''; $('rA').style.display='none'; recusa([]);
caminho='auto'; avisoCam('');
pintaModo(); pintaCaminhos(); limpaPaineis(); agendaAvaliacao(); previaAoVivo();
pintaModo(); limpaPaineis(); agendaAvaliacao(); previaAoVivo();
}
$('bVoltar').addEventListener('click',()=>{
itemAtual=null; modo=null; $('atual').style.display='none'; $('carrLin').innerHTML=''; pintaPedido();

View File

@@ -286,6 +286,7 @@ function previaAoVivo(){
clearTimeout(tMont);
montagemLayout=null;
$('vArea').montagemVersao=($('vArea').montagemVersao||0)+1;
if(!ehFolha()) $('bAmpliar').hidden=true;
if(ehFolha()){
$('vUso').innerHTML='';
const tot=folhaTotalM();
@@ -319,7 +320,9 @@ function previaAoVivo(){
figura.append(pilha,legenda); painel.append(figura);
});
$('vArea').replaceChildren(painel);
$('bAmpliar').hidden=false;
}else{
$('bAmpliar').hidden=true;
$('vArea').innerHTML = medidas.length
? '<div class="semmont"><b>'+(medidas.length>1
? medidas.length+' folhas · '+fmtM(tot)+' m'

View File

@@ -356,6 +356,15 @@ function textoProgresso(x){
if(x.med && x.med.grande) return 'gerando a prévia e conferindo a arte… '+x.pct+'%';
return x.pct<50?'lendo o arquivo…':x.pct<70?'medindo a folha…':'conferindo a arte…';
}
// The file card's thumbnail (52 x 96 px, drawn at twice that for sharp screens).
function miniatura(fonte){
const w=fonte.naturalWidth||fonte.width, h=fonte.naturalHeight||fonte.height;
if(!(w>0 && h>0)) return null;
const s=Math.min(1,104/w,192/h), cv=document.createElement('canvas');
cv.width=Math.max(1,Math.round(w*s)); cv.height=Math.max(1,Math.round(h*s));
cv.getContext('2d').drawImage(fonte,0,0,cv.width,cv.height);
return cv.toDataURL('image/webp',0.8);
}
// Moves one sheet's progress bar in place: repainting the whole list and the
// preview on every step made "Sua folha" flicker while a large file was read.
function pintaProgressoFolha(x){
@@ -364,66 +373,79 @@ function pintaProgressoFolha(x){
if(!barra){ pintaFolha(); return; }
barra.style.width=x.pct+'%'; if(texto) texto.textContent=textoProgresso(x);
}
// What the check found, as chips: ok (verde), av (atenção), er (resolva você),
// fix (arrumamos de graça), nt (a equipe confere). Details stay one click away.
function chipsDaFolha(a){
const chip=(k,t)=>'<li class="chip '+k+'">'+t+'</li>';
const dpi = a.vetor ? chip('ok','Arte vetorial')
: chip(a.dpi>=DPI_AVISA?'ok':a.dpi>=DPI_RECUSA?'av':'er', a.dpi+' DPI');
if(a.grande) return dpi+chip('nt','Peças, resíduos e fundo: a equipe confere');
return dpi+
(a.fundoChapado? chip('er','Fundo chapado') : chip('ok','Fundo transparente'))+
(a.residuos? chip('er',a.residuos+' resíduo'+(a.residuos>1?'s':'')) : chip('ok','Sem resíduos'))+
(a.encostadas? chip('fix',a.encostadas+' peças coladas · separamos grátis') : chip('ok','Peças separadas'));
}
function avisosDaFolha(a){
if(a.grande) return '';
return (a.fundoChapado? '<p class="avisoF"><b>O fundo não é transparente.</b> '+
'Branco parecendo transparente sai branco no filme. Exporte com fundo transparente.</p>' : '')+
(a.residuos? '<p class="avisoF"><b>'+a.residuos+' pontinho'+(a.residuos>1?'s soltos serão impressos':' solto será impresso')+
'.</b> A impressora imprime qualquer resíduo de recorte. Apague no arquivo e envie de novo.</p>' : '');
}
function detalhesDaFolha(a,i,aberto){
const linhas = a.grande
? ['Resolução '+a.dpi+' DPI, lida do arquivo',
'Arquivo grande: peças, resíduos e fundo são conferidos pela equipe']
: [a.artes+' peça'+(a.artes===1?'':'s')+' · '+a.aproveitamento+'% da folha com arte',
'Resolução '+a.dpi+' DPI · '+(a.fonteDpi==='imagens'? 'medida imagem por imagem dentro do PDF'
: a.fonteDpi==='vetor'? 'arte vetorial, sem imagem embutida' : 'lida do arquivo'),
'Conferido numa grade de '+n1(a.grade)+' mm'];
return '<details class="confDet" data-det="'+i+'"'+(aberto?' open':'')+'>'+
'<summary>Ver detalhes da conferência</summary>'+
linhas.map(l=>'<span>'+l+'</span>').join('')+'</details>';
}
function pintaFolha(){
invalidaItemAtual();
const L=larguraFilme();
pintaTipoEnvio(); // trava o seletor enquanto houver folha
$('zona').classList.toggle('compacta', folhas.length>0);
if(!folhas.length){ $('lista').innerHTML=''; agendaAvaliacao(); previaAoVivo(); return; }
$('lista').innerHTML = folhas.map((x,i)=>{
const tot=folhaTotalM();
$('lista').innerHTML = '<div class="listaCab"><h4>Seus arquivos</h4><span>'+
folhas.length+' folha'+(folhas.length>1?'s':'')+(tot>0? ' · '+fmtM(tot)+' m' : '')+'</span></div>'+
folhas.map((x,i)=>{
const lido=!!x.med, larga=lido && x.med.larg>L, suspeito=lido && x.med.foraDoPadrao;
const sub=(x.m||0)*(x.rep||1);
let medida='';
const sub=(x.m||0)*(x.rep||1), a=lido && x.an;
let medida='', manual='';
if(lido){
const a=x.an;
const conferido = a && a.grande
? '<div class="conf"><b>Arquivo grande · '+a.dpi+' DPI</b>'+
'<span>nota pela resolução · peças, resíduos e fundo conferidos pela equipe</span></div>'
: a
? '<div class="conf"><b>Conferido de verdade</b>'+
'<span>'+a.artes+' peça'+(a.artes===1?'':'s')+' · '+a.aproveitamento+
'% da folha virou arte · '+
(a.residuos? a.residuos+' resíduo'+(a.residuos>1?'s':'') : 'sem resíduo')+' · '+
(a.fundoChapado? 'fundo chapado' : 'fundo transparente')+'</span>'+
'<span>resolução '+a.dpi+' DPI · '+
(a.fonteDpi==='imagens'? 'medida imagem por imagem dentro do PDF'
: a.fonteDpi==='vetor'? 'arte vetorial, sem imagem embutida'
: 'lida do arquivo')+
' · grade de '+n1(a.grade)+' mm</span></div>'
: '';
medida='<div class="med'+(larga||suspeito?' alerta':'')+'">'+
'<b>'+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm</b>'+
'<span>'+x.med.fonte+' · '+fmtM(x.m)+' m de folha</span>'+
conferido+
(suspeito? '<em><b>Este PDF passa do tamanho que o formato comporta.</b> '+
medida='<div class="repMed"><b>'+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm</b>'+
'<span>'+escapeHTML(extDe(x.f.name))+' · '+fmt(x.f.size)+'</span></div>'+
(a? '<ul class="chips">'+chipsDaFolha(a)+'</ul>'+avisosDaFolha(a)+detalhesDaFolha(a,i,x.detAberto) : '')+
(suspeito? '<p class="avisoF"><b>Este PDF passa do tamanho que o formato comporta.</b> '+
'A especificação para em 508 cm e o seu tem '+n1(x.med.alt)+' cm. '+
'Ele abre certo só em quem o gerou — aqui vai precisar de tratamento. '+
'Manda em <b>PNG ou TIFF</b>, ou divide em partes de até 5 m, que sai na hora.</em>':'')+
(larga? '<em><b>Esta folha não roda neste filme.</b> Ela tem '+n1(x.med.larg)+
'Manda em <b>PNG ou TIFF</b>, ou divide em partes de até 5 m, que sai na hora.</p>':'')+
(larga? '<p class="avisoF"><b>Esta folha não roda neste filme.</b> Ela tem '+n1(x.med.larg)+
' cm de largura e o filme aqui tem '+n1(L)+' cm. Não dá para reduzir: '+
'encolher a folha encolheria cada arte junto.'+
(x.med.larg<=57.5
? ' Ela cabe no <b>DTF têxtil de 57 cm</b> — troque de produto ali em cima.'
: ' Remonte a folha em '+n1(L)+' cm e suba de novo.')+'</em>':'')+
'</div>';
: ' Remonte a folha em '+n1(L)+' cm e suba de novo.')+'</p>':'');
}else if(x.measurementError){
medida='<div class="med alerta"><b>Este PDF não pode ser orçado</b><span>'+
escapeHTML(x.measurementError)+'</span></div>';
medida='<span class="repPeso">'+escapeHTML(extDe(x.f.name))+' · '+fmt(x.f.size)+'</span>'+
'<p class="avisoF"><b>Este PDF não pode ser orçado.</b> '+escapeHTML(x.measurementError)+'</p>';
}else{
const teto=TABELA[modo], piso=pisoEscada();
const dif=(x.m>0)? (teto-piso)*cobrar(x.m*(x.rep||1)) : 0;
medida='<div class="med pede">'+
'<b>'+escapeHTML(extDe(x.f.name))+' não abre no navegador</b>'+
'<span>Sem conseguir conferir o arquivo, não temos como dar nota — e sem nota '+
'o metro sai pela tabela, <b>'+rs(teto)+'</b>. Alguém vai abrir esse arquivo na mão '+
'antes de imprimir.</span>'+
'<div class="troca">Exportando a mesma folha em <b>PNG ou PDF</b>, a conferência é '+
'automática e o metro pode cair até <b>'+rs(piso)+'</b>'+
(dif>0.5? ' — <b>'+rs(dif)+' a menos</b> só nesta folha' : '')+'.</div>'+
'<span style="margin-top:9px">Informe o comprimento para seguir:</span>'+
'<input type="number" min="0.5" max="60" step="0.01" data-comp="'+i+'" '+
'placeholder="metros" value="'+(x.m? x.m.toFixed(2):'')+'">'+
'</div>';
medida='<span class="chipSo">Conferência manual · preço cheio</span>'+
'<p class="repTxt">O navegador não abre '+escapeHTML(extDe(x.f.name))+', então alguém da equipe '+
'confere antes de imprimir e o metro sai pela tabela, <b>'+rs(teto)+'</b>. '+
'Exportando em <b>PNG ou PDF</b>, o metro pode cair até <b>'+rs(piso)+'</b>'+
(dif>0.5? ' — <b>'+rs(dif)+' a menos</b> só nesta folha' : '')+'.</p>';
manual='<div class="campo"><label for="comp'+i+'">Comprimento</label>'+
'<span class="un"><input type="number" id="comp'+i+'" min="0.5" max="60" step="0.01" data-comp="'+i+'" '+
'placeholder="0,00" value="'+(x.m? x.m.toFixed(2):'')+'"><i>m</i></span></div>';
}
const barra = x.pct!=null
? '<div class="prog"><i style="width:'+x.pct+'%"></i></div>'+
@@ -433,36 +455,41 @@ function pintaFolha(){
'. Seguimos com o preço de tabela — se quiser a nota e o desconto, exporte a mesma '+
'folha em <b>PNG</b>, que a conferência é bem mais leve.</div>'
: '';
return '<div class="rep" data-folha="'+i+'"><div class="l1"><b>'+
(folhas.length>1? (i+1)+'. ':'')+escapeHTML(x.f.name)+'</b>'+
'<span style="color:var(--fraco);font-size:10.5px">'+fmt(x.f.size)+'</span>'+
'<button data-rmf="'+i+'">×</button></div>'+
barra+medida+
(x.m>0 ?
'<div class="cps" style="margin-top:10px">'+
'<div><label>quantas vezes repetir</label>'+
'<input type="number" min="1" max="200" data-repf="'+i+'" value="'+x.rep+'"></div>'+
'<div class="conta"><b>'+fmtM(sub)+' m</b>'+
(x.rep>1? ' · '+x.rep+' × '+fmtM(x.m)+' m':'')+'</div>'+
'</div>' : '')+
// A small thumbnail made once: the list is rebuilt on every change, and the
// full preview of a file up to 150 MB would be re-parsed each time.
const mini = x.miniSrc
? '<img src="'+escapeHTML(x.miniSrc)+'" alt="">'
: '<span>'+escapeHTML(extDe(x.f.name))+'</span>';
const copias = x.m>0 || manual ?
'<div class="cps">'+manual+
(x.m>0 ?
'<div class="campo"><label for="repf'+i+'">Cópias</label>'+
'<span class="passo"><button type="button" data-passo="-1" data-i="'+i+'" aria-label="Menos uma cópia">−</button>'+
'<input type="number" id="repf'+i+'" min="1" max="200" data-repf="'+i+'" value="'+x.rep+'">'+
'<button type="button" data-passo="1" data-i="'+i+'" aria-label="Mais uma cópia">+</button></span></div>'+
'<div class="conta">'+(x.rep>1? x.rep+' × '+fmtM(x.m)+' m = ' : '')+'<b>'+fmtM(sub)+' m</b></div>' : '')+
'</div>' : '';
return '<div class="rep" data-folha="'+i+'">'+
'<div class="repTopo"><div class="miniF">'+mini+'</div><div class="repInfo">'+
'<div class="l1"><b>'+(folhas.length>1? (i+1)+'. ':'')+escapeHTML(x.f.name)+'</b>'+
'<button data-rmf="'+i+'" aria-label="Remover '+escapeHTML(x.f.name)+'">×</button></div>'+
(lido || x.measurementError ? '' : '<span class="repPeso">'+escapeHTML(extDe(x.f.name))+' · '+fmt(x.f.size)+'</span>')+
barra+medida+
'</div></div>'+copias+
'</div>';
}).join('');
// total do pedido, quando há mais de uma folha
const tot=folhaTotalM(), partes=Math.ceil(tot/20);
if(folhas.length>1 && tot>0){
$('lista').innerHTML += '<div class="somaF">'+
'<span>'+folhas.length+' folhas neste pedido</span>'+
'<b>'+fmtM(tot)+' m no total</b>'+
(partes>1? '<em>vai em '+partes+' partes de até 20 m</em>':'')+'</div>';
}else if(partes>1 && tot>0){
$('lista').innerHTML += '<div class="somaF"><span>folha longa</span>'+
'<b>'+fmtM(tot)+' m</b><em>vai em '+partes+' partes de até 20 m</em></div>';
}
$('lista').querySelectorAll('[data-rmf]').forEach(b=>b.addEventListener('click',()=>{
folhas.splice(+b.dataset.rmf,1); pintaFolha();
}));
$('lista').querySelectorAll('[data-det]').forEach(d=>d.addEventListener('toggle',()=>{
folhas[+d.dataset.det].detAberto=d.open;
}));
$('lista').querySelectorAll('[data-passo]').forEach(b=>b.addEventListener('click',()=>{
const el=$('repf'+b.dataset.i);
el.value=Math.max(1,Math.min(200,(+el.value||1)+(+b.dataset.passo)));
el.dispatchEvent(new Event('change'));
}));
$('lista').querySelectorAll('[data-repf]').forEach(el=>el.addEventListener('change',e=>{
folhas[+el.dataset.repf].rep=Math.max(1,Math.min(200,+e.target.value||1)); pintaFolha();
}));
@@ -501,6 +528,7 @@ function pintaProgressoArte(a){
}
function pintaArtes(){
invalidaItemAtual();
$('zona').classList.toggle('compacta', artes.length>0);
$('lista').innerHTML=artes.map((a,i)=>{
let barra='', dica='', calc='<span>informe a largura para ver a qualidade</span>';
if(a.lendo!=null){

View File

@@ -48,21 +48,6 @@ function sel(fs){
}
if(!fs.length) return;
if(ehFolha()){
const auto=fs.filter(f=>AUTO.test(f.name)).length;
const manual=fs.length-auto;
if(caminho==='auto' && manual){
caminho='tabela';
avisoCam('<b>Trocamos o caminho para você.</b> Você marcou conferência automática, '+
'mas '+(manual>1? manual+' arquivos vieram':'o arquivo veio')+' em formato que não '+
'abrimos. O metro vai pela tabela, '+rs(TABELA[modo])+'.');
pintaCaminhos();
}else if(caminho==='tabela' && auto){
caminho='auto';
avisoCam('<b>Boa notícia.</b> Você marcou o caminho sem conferência, mas '+
(auto>1? 'os arquivos vieram':'o arquivo veio')+' em formato que a gente confere. '+
'Vamos avaliar e o metro pode cair até '+rs(pisoEscada())+'.');
pintaCaminhos();
}
const novas=fs.map(f=>({f, med:null, rep:1, m:0, semAnalise:null}));
folhas=folhas.concat(novas); // soma, não substitui
pintaFolha();
@@ -87,7 +72,7 @@ function sel(fs){
if(pct-mostrado>=1){ mostrado=pct; x.pct=pct; pintaProgressoFolha(x); }
}).then(cv=>{
if(cv && folhas.includes(x)){
x.previewSrc=cv.toDataURL('image/webp',0.85);
x.previewSrc=cv.toDataURL('image/webp',0.85); x.miniSrc=miniatura(cv);
try{
const an=analisarFolha(cv, md.larg);
x.an={...an, dpi:md.dpiFolha, fonteDpi:'arquivo', grande:false};
@@ -96,7 +81,7 @@ function sel(fs){
}).catch(()=>{}).finally(()=>{ x.pct=null; pintaFolha(); });
}else if(md && RENDERIZA.test(x.f.name)){
carregarImagem(x.f).then(img=>{
if(img){ x.previewSrc=img.src; try{ x.an=analisarFolha(img, md.larg); if(x.an) x.an.fonteDpi='arquivo'; }
if(img){ x.previewSrc=img.src; x.miniSrc=miniatura(img); try{ x.an=analisarFolha(img, md.larg); if(x.an) x.an.fonteDpi='arquivo'; }
catch(e){} }
x.pct=null; pintaFolha();
});
@@ -112,6 +97,7 @@ function sel(fs){
: '');
}
if(r && r.tela){
x.miniSrc=miniatura(r.tela);
try{
x.an=analisarFolha(r.tela, md.larg);
// vetor puro não tem resolução · nota máxima, e está certo

View File

@@ -104,35 +104,48 @@ section+section{border-top:0}
.voltar:hover{color:var(--laranja-escuro)}
.focoGrid{grid-template-columns:minmax(0,1fr);gap:20px}
.cat,.cat.file{border:0;background:transparent;box-shadow:none;overflow:visible}
.cabfoco,.cat.file .cabfoco{padding:0 0 20px!important;background:transparent;border:0;display:grid;
grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"t e" "s e" "p e";column-gap:24px;align-items:end}
.cabfoco>div:first-child{grid-area:t;display:flex;flex-direction:column-reverse;gap:4px}
.cabfoco .lg{font-size:12px;color:#707070;letter-spacing:.05em}
.cabfoco h3{font-size:32px;font-weight:800;letter-spacing:-.02em;line-height:1.1}
.cabfoco p{grid-area:s;margin-top:6px;font-size:15px;color:var(--texto2)}
.cabfoco .pr2{grid-area:p;margin-top:4px;font-size:14px;font-weight:600;color:var(--fraco);white-space:normal}
.tipoEnvio{grid-area:e;margin:0}
.tipoEnvio:not([hidden]){display:inline-flex;gap:4px;padding:4px;border-radius:10px;background:#EFEFEF}
.tipoEnvio .cam,.tipoEnvio .cam.on{border:0;box-shadow:none;background:transparent;padding:0 14px;min-height:40px;border-radius:8px;align-items:center}
.tipoEnvio .cam.on{background:#fff;box-shadow:0 1px 2px rgba(3,6,11,.14)}
.tipoEnvio .cam .mk2,.tipoEnvio .cam span,.tipoEnvio .cam em{display:none}
.tipoEnvio .cam b{margin:0;font-size:13.5px;font-weight:600;white-space:nowrap}
.tipoEnvio .cam.on b{font-weight:800}
.cabfoco,.cat.file .cabfoco{padding:0 0 24px!important;background:transparent;border:0;display:flex;flex-direction:column;gap:20px}
.cabfoco>div:first-child{display:flex;flex-direction:column-reverse;gap:4px}
.cabfoco .lg{font-size:12.5px;font-weight:800;color:var(--laranja-escuro);letter-spacing:.06em;text-transform:uppercase}
.cabfoco h3{font-size:36px;font-weight:800;letter-spacing:-.02em;line-height:1.1}
.tipoEnvio{margin:0}
.tipoEnvio:not([hidden]){display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;max-width:calc(100% - 452px)}
.tipoEnvio .cam,.tipoEnvio .cam.on{padding:14px 16px;min-height:0;border-radius:12px;border:1px solid var(--linha);background:#F9F9F9;box-shadow:none;align-items:flex-start}
.tipoEnvio .cam.on{padding:13px 15px;border:2px solid var(--preto);background:#fff}
.tipoEnvio .cam .mk2{display:none}
.tipoEnvio .cam div{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:12px;row-gap:4px;align-items:baseline}
.tipoEnvio .cam b{margin:0;font-size:15px;font-weight:700;color:var(--texto2)}
.tipoEnvio .cam.on b{color:var(--preto);font-weight:800}
.tipoEnvio .cam em{margin:0;font-size:13px;font-weight:500;color:var(--fraco);white-space:nowrap}
.tipoEnvio .cam.on em{font-weight:600;color:var(--preto)}
.tipoEnvio .cam div>span{grid-column:1/-1;font-size:13px;color:var(--fraco);line-height:1.4}
.tipoEnvio .cam.on div>span{color:var(--texto2)}
.tipoEnvio .cam:disabled{cursor:default}
.tipoEnvio .cam:disabled:not(.on){opacity:.5}
.tipoEnvio .cam:disabled:not(.on){opacity:.55}
@media(max-width:1100px){.tipoEnvio:not([hidden]){max-width:none}}
.cat .in{padding:0!important}
.foco2{grid-template-columns:minmax(0,1fr) 420px;gap:32px}
.fila{display:flex;flex-direction:column;gap:12px}
.fila>*{margin:0}
.cam{border-radius:var(--raio2)}
.cam.on{border-color:var(--laranja);background:var(--aviso-fundo)}
#larguraFolhaPronta{display:inline}
#larguraFolhaPronta{display:inline;font-size:inherit;color:inherit}
.zona{display:grid;grid-template-columns:44px minmax(0,1fr);column-gap:16px;row-gap:2px;align-items:center;text-align:left;padding:16px 20px;margin:0;border:2px dashed #F2B25A;background:var(--aviso-fundo);border-radius:var(--raio)}
.zona .ico{grid-row:1/4;display:flex;align-items:center;justify-content:center;width:44px;height:44px;line-height:1;margin:0;border-radius:10px;background:var(--laranja);color:var(--preto);font-size:20px;box-shadow:none}
.zona b{font-size:15px;margin:0}
.zona span{max-width:none;margin:0;font-size:13px;color:var(--fraco)}
.zona .bt{display:none}
.zona:hover,.zona.sobre{border-color:var(--laranja2);box-shadow:none}
.zona .zManual{grid-column:1/-1;margin-top:12px;padding-top:12px;border-top:1px solid var(--aviso-linha);color:var(--texto2);line-height:1.5}
.zona .zManual[hidden]{display:none}
.zona .zManual b{display:inline;font-size:13px}
.zona .zOutra{display:none}
.zona.compacta{display:flex;justify-content:center;align-items:center;min-height:56px;padding:0 20px;border:1.5px dashed #D0D0D0;background:#fff;cursor:pointer}
.zona.compacta>*{display:none}
.zona.compacta .zOutra{display:inline;font-size:14px;color:var(--fraco)}
.zona.compacta .zOutra b{display:inline;font-size:14px;font-weight:600;color:var(--preto)}
.zona.compacta .zOutra b::before{content:"+";margin-right:8px;font-weight:700}
.zona.compacta:hover,.zona.compacta.sobre{border-color:var(--laranja2)}
#lista{display:flex;flex-direction:column;gap:10px}
#lista:empty{display:none}
.art{margin:0;padding:14px 16px;border-radius:12px;background:#fff;border:1px solid var(--linha)}
@@ -141,14 +154,69 @@ section+section{border-top:0}
.art label{font-size:12px}
.art input{height:42px;font-size:15px;font-weight:600;border-radius:8px}
.art .cps{grid-template-columns:repeat(2,minmax(0,160px))}
.rep{margin:0;padding:14px 16px;border-radius:12px;background:#fff;border:1px solid var(--linha)}
.listaCab{display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.listaCab h4{margin:0;font-size:18px;font-weight:700}
.listaCab span{font-size:13px;color:var(--fraco)}
.rep{margin:0;padding:20px;border-radius:14px;background:#fff;border:1px solid var(--linha);display:flex;flex-direction:column;gap:16px}
.repTopo{display:flex;gap:16px;align-items:flex-start}
.miniF{flex:none;width:52px;height:96px;border-radius:6px;border:1px solid #D6D6D6;background:#F4F4F2;display:flex;align-items:center;justify-content:center;overflow:hidden}
.miniF img{width:100%;height:100%;object-fit:contain;display:block}
.miniF span{font-size:11px;font-weight:800;color:var(--texto2)}
.repInfo{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
.rep .l1{margin:0}
.rep .l1 b{font-size:14px;font-weight:600;color:var(--preto)}
.rep .l1 button{width:32px;height:32px;margin:-6px -6px -6px 0;border-radius:8px;font-size:18px}
.rep .l1 button:hover{background:#F1F1F1;color:var(--preto)}
.repMed{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.repMed b{font-family:"Inter",sans-serif;font-size:22px;font-weight:700;letter-spacing:-.01em}
.repMed span,.repPeso{font-size:13px;color:var(--fraco)}
.chips{list-style:none;margin:4px 0 0;padding:0;display:flex;flex-wrap:wrap;gap:8px}
.chip{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;font-size:13px;font-weight:600;line-height:1.2}
.chip::before{font-weight:800}
.chip.ok{background:var(--verde-fundo);color:var(--verde)}.chip.ok::before{content:"✓"}
.chip.av{background:#FFF1DB;color:#8A4B00}.chip.av::before{content:"!"}
.chip.er{background:#FDE8E6;color:#B3261E}.chip.er::before{content:"×"}
.chip.fix{background:#E8F0FB;color:#1F4E8C}.chip.fix::before{content:"✓"}
.chip.nt{background:#F1F1F1;color:var(--texto2)}
.chipSo{align-self:flex-start;padding:5px 10px;border-radius:999px;background:#F1F1F1;color:var(--texto2);font-size:13px;font-weight:600}
.avisoF{margin:4px 0 0;padding:10px 12px;border-radius:10px;background:#FDE8E6;font-size:13px;line-height:1.5;color:var(--preto)}
.avisoF b{color:#B3261E}
.repTxt{margin:2px 0 0;font-size:13.5px;line-height:1.5;color:var(--texto2)}
.repTxt b{color:var(--preto)}
.confDet{font-size:13px}
.confDet summary{display:inline;cursor:pointer;list-style:none;color:var(--laranja-escuro);font-weight:500}
.confDet summary::-webkit-details-marker{display:none}
.confDet summary:hover{text-decoration:underline}
.confDet[open] summary{display:inline-block;margin-bottom:6px}
.confDet span{display:block;color:var(--texto2);line-height:1.6}
.rep .cps{display:flex;align-items:center;gap:16px 24px;flex-wrap:wrap;margin:0;padding-top:16px;border-top:1px solid #EFEFEF}
.rep .campo{display:flex;align-items:center;gap:12px}
.rep .campo label{margin:0;font-size:14px;font-weight:600;color:var(--preto)}
.passo{display:inline-flex;border:1px solid #D6D6D6;border-radius:10px;overflow:hidden}
.passo button{width:44px;height:44px;border:0;background:#F9F9F9;font-size:18px;color:var(--preto);cursor:pointer}
.passo button:hover{background:#EFEFEF}
.rep .passo input{width:56px;height:44px;padding:0;border:0;border-radius:0;text-align:center;font-size:16px;font-weight:700;-moz-appearance:textfield;appearance:textfield}
.passo input::-webkit-inner-spin-button,.passo input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.rep .un{display:inline-flex;align-items:center;gap:6px;height:44px;padding:0 12px;border:1px solid #D6D6D6;border-radius:10px}
.rep .un input{width:64px;padding:0;border:0;font-size:16px;font-weight:700;text-align:right}
.rep .un i{font-style:normal;font-size:14px;color:var(--fraco)}
.rep .conta{margin-left:auto;font-size:14px;color:var(--texto2)}
.rep .conta b{font-family:"Inter",sans-serif;font-size:16px;color:var(--preto)}
.calc{font-size:13px;margin-top:8px}
.dica{display:flex;align-items:center;gap:12px;margin-top:10px;padding:10px 12px;border:0;border-radius:8px;background:var(--verde-fundo);font-size:13px;color:#14532D}
.dica.av{background:var(--aviso-fundo);color:#5A3A00}
.dica button{display:inline-flex;width:auto;margin:0 0 0 auto;flex:none;height:34px;align-items:center;padding:0 12px;border-radius:7px;border:1px solid var(--verde);background:#fff;color:var(--verde);box-shadow:none;font-size:13px}
.dica button:hover{background:var(--verde);color:#fff;box-shadow:none}
.req{position:static;padding:16px 20px 6px;border-radius:12px;box-shadow:none}
.reqcab{font-size:15px;margin-bottom:6px}
.req{position:static;padding:8px 0 0;border:0;border-radius:0;background:none;box-shadow:none;display:flex;flex-wrap:wrap;align-items:baseline;gap:8px 20px}
.reqcab{margin:0;font-size:13px;font-weight:400;color:var(--fraco)}
.req details,.req details:first-of-type{border:0}
.req summary{display:inline;padding:0;font-size:13px;font-weight:500;color:var(--laranja-escuro)}
.req summary::before{display:none}
.req summary:hover{text-decoration:underline}
.req details[open]{order:1;flex-basis:100%;padding:14px 16px 4px;border-radius:12px;background:var(--cinza)}
.req details[open] summary{display:block;margin-bottom:6px;font-size:14px;font-weight:700;color:var(--preto);text-decoration:none}
.req li{font-size:13px}
.req .obs2{font-size:12px}
/* The buy box */
.mont{position:sticky;top:var(--topo-passos,128px);padding:20px;border-radius:16px;background:#fff;box-shadow:none;display:flex;flex-direction:column;gap:12px}
@@ -160,12 +228,36 @@ section+section{border-top:0}
.compra .aoVivo.pronto{display:none}
#compraVals{display:flex;flex-direction:column;gap:10px;font-size:15px}
#compraVals[hidden]{display:none}
.cNota{display:flex;align-items:center;gap:12px}
.cNota span,.cLin>span:first-child{color:var(--texto2)}
.cNota>i{flex:1;height:6px;border-radius:999px;background:#EFEFEF;overflow:hidden}
.cNota>i i{display:block;height:100%;border-radius:999px;background:#C9CCD1}
.cNota>i i.ok{background:var(--verde)}.cNota>i i.av{background:var(--laranja)}.cNota>i i.er{background:var(--vermelho)}
.cNota b{font-family:"Inter",sans-serif;font-weight:800}
.cat.file .zoomB{display:none}
.ampliar{margin-left:auto;height:32px;padding:0 12px;border-radius:8px;border:1px solid #D6D6D6;background:#fff;font-size:12.5px;font-weight:600;color:var(--preto);cursor:pointer}
.ampliar:hover{border-color:var(--preto)}
.ampliar[hidden]{display:none}
.cat.file #vArea{max-height:340px}
.cEscada{display:flex;flex-direction:column;gap:8px;margin-bottom:4px}
.cEscada>span{font-size:13px;font-weight:600}
.cEscada ol{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:4px}
.cEscada li{padding:8px 2px;border-radius:8px;background:#F4F4F2;border:1.5px solid transparent;display:flex;flex-direction:column;align-items:center;gap:2px}
.cEscada li span{font-size:11px;color:var(--fraco);white-space:nowrap}
.cEscada li b{font-size:13px;font-weight:500;color:var(--texto2)}
.cEscada li.on{background:var(--verde-fundo);border-color:var(--verde)}
.cEscada li.on span,.cEscada li.on b{font-weight:700;color:var(--verde)}
.cEscada li.on:last-child{background:#F1F1F1;border-color:var(--preto)}
.cEscada li.on:last-child span,.cEscada li.on:last-child b{color:var(--preto)}
.cLin>span:first-child{color:var(--texto2)}
.cLin small{font-size:12px;color:var(--fraco)}
.cEco{margin:-6px 0 0;text-align:right;font-size:13px;font-weight:600;color:var(--verde)}
.cEco:empty{display:none}
#cPartes{display:block}
.ampliaDlg{width:min(960px,calc(100vw - 32px));max-height:calc(100vh - 48px);padding:0;border:0;border-radius:16px;box-shadow:0 20px 60px rgba(3,6,11,.3)}
.ampliaDlg::backdrop{background:rgba(3,6,11,.55)}
.ampliaCab{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--linha);background:#fff}
.ampliaCab b{font-size:16px;font-weight:800}
.ampliaCab button{width:40px;height:40px;border:0;border-radius:8px;background:#F1F1F1;font-size:20px;cursor:pointer}
#ampliaArea .folhaPrevias{display:grid;gap:16px;padding:16px;background:#F8F9FA}
#ampliaArea .folhaPrevia{margin:0;background:#fff;border:1px solid var(--linha);border-radius:8px;overflow:hidden}
#ampliaArea .folhaPilha{display:flex;flex-direction:column;align-items:center;gap:4px}
#ampliaArea .folhaPilha img{display:block;width:auto;max-width:100%;height:auto;max-height:calc(100vh - 180px)!important}
#ampliaArea figcaption{padding:8px 12px;font-size:13px;color:var(--texto2);border-top:1px solid var(--linha)}
.cLin{display:flex;align-items:baseline;gap:8px}
.cLin>span:last-child,.cLin>b{margin-left:auto;display:flex;gap:8px;align-items:baseline}
.cLin s{font-size:13px;color:#707070}
@@ -184,8 +276,14 @@ section+section{border-top:0}
.cPe{margin:0;font-size:13px;color:var(--fraco);text-align:center}
@media(max-width:1100px){.foco2{grid-template-columns:1fr}.mont{position:static}}
@media(max-width:620px){.montcab b{font-size:15px}
.cabfoco,.cat.file .cabfoco{grid-template-columns:1fr;grid-template-areas:"t" "s" "p" "e"}
.tipoEnvio:not([hidden]){display:grid;grid-template-columns:1fr 1fr;margin-top:14px}}
.cabfoco h3{font-size:28px}
.tipoEnvio .cam div{grid-template-columns:1fr}
.tipoEnvio .cam div>span{display:none}
.rep{padding:16px}
.repTopo{gap:12px}
.miniF{width:40px;height:80px}
.repMed b{font-size:20px}
.rep .conta{margin-left:0}}
/* The quality, preview, cart and checkout panels sit directly in #envio,
outside .w; they share the page column so nothing runs edge to edge. */
@@ -231,6 +329,8 @@ html:is([data-rota="pagamento"],[data-rota="pix"]) .checkout{max-width:1040px;ma
html:is([data-rota="pagamento"],[data-rota="pix"]) .pagGrid{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:32px;align-items:start}
.envioArq{font-size:13px;color:var(--texto2);margin-top:14px;line-height:1.5}
.envioArq.ok{color:var(--verde)}
.envioArq.erro{color:#B42318}
.envioDeNovo{margin-left:4px;padding:0;border:0;background:none;color:inherit;font:inherit;font-weight:700;text-decoration:underline;cursor:pointer}
.envioBarra{height:6px;border-radius:999px;background:#EDEDED;overflow:hidden;margin-bottom:6px}
.envioBarra i{display:block;height:100%;background:var(--laranja);border-radius:999px;transition:width .4s}
.cartEnvio{font-size:13px;font-weight:700;color:var(--laranja-escuro);margin-left:6px}