diff --git a/tests/artwork_browser_test.mjs b/tests/artwork_browser_test.mjs
index dbcfc55..7dd952f 100644
--- a/tests/artwork_browser_test.mjs
+++ b/tests/artwork_browser_test.mjs
@@ -115,7 +115,7 @@ try{
await call('Runtime.enable');await call('Page.enable');
await call('Emulation.setDeviceMetricsOverride',{width:1440,height:1100,deviceScaleFactor:1,mobile:false});
await call('Page.navigate',{url:`http://127.0.0.1:${server.address().port}/`});
- await waitFor(()=>evaluate(`typeof sel==='function' && typeof AUTO!=='undefined'`),'Site scripts');
+ await waitFor(()=>evaluate(`typeof sel==='function' && typeof pintaEntrega==='function'`),'Site scripts');
// Observe the actual engine, without replacing its placement or rendering.
await evaluate(`(()=>{
const original=encaixar;
@@ -149,6 +149,9 @@ try{
// Navigation links must reach the chooser, never preselect a product.
assert.equal(await evaluate(`document.querySelectorAll('[data-modo-cta]').length`),0);
await click('[data-modo="file"]');
+ // Opening a product paints on the next frame; on a busy runner the check
+ // must wait for it rather than race it.
+ await waitFor(()=>evaluate(`!$('tipoEnvio').hidden`),'by-metre product open');
// By-metre opens declaring a mounted sheet; switching is explicit and priced.
assert.deepEqual(await evaluate(`({shown:!$('tipoEnvio').hidden,
on:[...document.querySelectorAll('#tipoEnvio .cam.on')].map(b=>b.dataset.tipo)})`),
@@ -293,13 +296,14 @@ try{
await fill('[data-cm]',10);await fill('[data-q]',4);await packed(4);
}
// An older image load must not overwrite a newer edit, even before debounce.
- await evaluate(`(()=>{window.realLoad=carregarImagem;window.delayed=[];
- carregarImagem=f=>new Promise(resolve=>delayed.push(()=>realLoad(f).then(resolve)));
+ // The packing loads each artwork's image through imagemDaArte.
+ await evaluate(`(()=>{window.realLoad=imagemDaArte;window.delayed=[];
+ imagemDaArte=a=>new Promise(resolve=>delayed.push(()=>realLoad(a).then(resolve)));
})()`);
await fill('[data-q]',5);
await waitFor(()=>evaluate('delayed.length===1'),'delayed preview');
await fill('[data-q]',7);
- await evaluate(`carregarImagem=realLoad;delayed[0]()`);
+ await evaluate(`imagemDaArte=realLoad;delayed[0]()`);
await packed(7);
const timeoutCleanup=await evaluate(`(async()=>{
const realLoader=carregarPdfJs, realTimer=setTimeout, realWorker=temWorker;
diff --git a/tests/browser_test.mjs b/tests/browser_test.mjs
index 94df84a..5ea3f5e 100644
--- a/tests/browser_test.mjs
+++ b/tests/browser_test.mjs
@@ -62,12 +62,13 @@ try{
await site.call('Page.setBypassCSP',{enabled:false});
await site.call('Page.reload');
await waitFor(()=>site.eval('typeof window.dtfCheckout === "function"'),'reload after security probe');
+ // The check above can still pass on the page being replaced; wait for the new one's cards.
+ await waitFor(()=>site.eval(`!!document.querySelector('[data-modo="file"]') && document.readyState==='complete'`),'product cards after reload');
await site.click('[data-modo="file"]');
// Each product has its own page; the home's parts are not on it.
assert.deepEqual(await site.eval(`({path:location.pathname,page:document.documentElement.dataset.rota,
cards:getComputedStyle($('cards')).display,cart:getComputedStyle($('carr')).display})`),
{path:'/arquivo-por-metro',page:'produto',cards:'none',cart:'none'});
- await site.click('[data-cam="tabela"]');
const root=await site.call('DOM.getDocument');
const input=await site.call('DOM.querySelector',{nodeId:root.root.nodeId,selector:'#inp'});
await site.call('DOM.setFileInputFiles',{nodeId:input.nodeId,files:[resolve('tests/fixtures/local-test.cdr')]});
diff --git a/web/checkout.js b/web/checkout.js
index 011c10b..eb80270 100644
--- a/web/checkout.js
+++ b/web/checkout.js
@@ -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);
}
diff --git a/web/index.html b/web/index.html
index 51c37bb..b2b76bc 100644
--- a/web/index.html
+++ b/web/index.html
@@ -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)}
—
-
-
-
-
-
-
-
+
+
↑
Arraste aquiAté 5 GB por arquivo.ou escolher no computador
+ Adicionar outra folha ou arraste aqui
+ Tem CDR, AI, PSD ou TIFF? Pode mandar também.
+ Alguém abre na mão e o metro sai pelo preço cheio.
Revisamos e reencaixamos antes de imprimir, sem custo.
+
diff --git a/web/site-cart.js b/web/site-cart.js
index accb66a..bdef20a 100644
--- a/web/site-cart.js
+++ b/web/site-cart.js
@@ -120,7 +120,7 @@ function carrinho(){
previa();
if(nota<90){
const piso=pisoEscada(), ganho=(base-piso)*cob;
- if(ganho>0.5) qm('Dá para chegar ao melhor preço. Corrigindo os pontos e chegando a 90, '+
+ if(ganho>0.5) qm('Dá para pagar menos. Com a arte em 300 DPI, '+
'o metro cai para '+rs(piso)+' — são '+rs(ganho)+' a menos neste pedido.','var(--verde)');
}
}
diff --git a/web/site-compra.js b/web/site-compra.js
index fd2154a..c82857f 100644
--- a/web/site-compra.js
+++ b/web/site-compra.js
@@ -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)=>
+ '
').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.metros0 ? fmtM(it.metros)+' m de folha, arredondado a cada 10 cm.' : '';
+ : '';
$('cTotal').textContent=rs(it.total);
}
add.addEventListener('click',()=>adicionaAoCarrinho());
diff --git a/web/site-config.js b/web/site-config.js
index 815b6ae..b1f5d21 100644
--- a/web/site-config.js
+++ b/web/site-config.js
@@ -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){
diff --git a/web/site-flow.js b/web/site-flow.js
index 6140cb1..532468a 100644
--- a/web/site-flow.js
+++ b/web/site-flow.js
@@ -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;
diff --git a/web/site-modes.js b/web/site-modes.js
index 4173f42..7eca571 100644
--- a/web/site-modes.js
+++ b/web/site-modes.js
@@ -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();
diff --git a/web/site-packing.js b/web/site-packing.js
index 05c6881..9edfdcf 100644
--- a/web/site-packing.js
+++ b/web/site-packing.js
@@ -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
? '
'+(medidas.length>1
? medidas.length+' folhas · '+fmtM(tot)+' m'
diff --git a/web/site-pdf.js b/web/site-pdf.js
index 929ad87..26a1b01 100644
--- a/web/site-pdf.js
+++ b/web/site-pdf.js
@@ -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)=>'
O fundo não é transparente. '+
+ 'Branco parecendo transparente sai branco no filme. Exporte com fundo transparente.
' : '')+
+ (a.residuos? '
'+a.residuos+' pontinho'+(a.residuos>1?'s soltos serão impressos':' solto será impresso')+
+ '. A impressora imprime qualquer resíduo de recorte. Apague no arquivo e envie de novo.
' : '');
+}
+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 ''+
+ 'Ver detalhes da conferência'+
+ linhas.map(l=>''+l+'').join('')+'';
+}
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 = '
Arquivo grande · '+a.dpi+' DPI'+
- 'nota pela resolução · peças, resíduos e fundo conferidos pela equipe
'
- : a
- ? '
Conferido de verdade'+
- ''+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')+''+
- '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
'
- : '';
- medida='
'+
- ''+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm'+
- ''+x.med.fonte+' · '+fmtM(x.m)+' m de folha'+
- conferido+
- (suspeito? 'Este PDF passa do tamanho que o formato comporta. '+
+ medida='
Este PDF passa do tamanho que o formato comporta. '+
'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 PNG ou TIFF, ou divide em partes de até 5 m, que sai na hora.
':'')+
- (larga? 'Esta folha não roda neste filme. Ela tem '+n1(x.med.larg)+
+ 'Manda em PNG ou TIFF, ou divide em partes de até 5 m, que sai na hora.':'')+
+ (larga? '
Esta folha não roda neste filme. 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 DTF têxtil de 57 cm — troque de produto ali em cima.'
- : ' Remonte a folha em '+n1(L)+' cm e suba de novo.')+'
':'')+
- '
';
+ : ' Remonte a folha em '+n1(L)+' cm e suba de novo.')+'':'');
}else if(x.measurementError){
- medida='
Este PDF não pode ser orçado'+
- escapeHTML(x.measurementError)+'
'+
- ''+escapeHTML(extDe(x.f.name))+' não abre no navegador'+
- 'Sem conseguir conferir o arquivo, não temos como dar nota — e sem nota '+
- 'o metro sai pela tabela, '+rs(teto)+'. Alguém vai abrir esse arquivo na mão '+
- 'antes de imprimir.'+
- '
Exportando a mesma folha em PNG ou PDF, a conferência é '+
- 'automática e o metro pode cair até '+rs(piso)+''+
- (dif>0.5? ' — '+rs(dif)+' a menos só nesta folha' : '')+'.
'+
- 'Informe o comprimento para seguir:'+
- ''+
- '
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, '+rs(teto)+'. '+
+ 'Exportando em PNG ou PDF, o metro pode cair até '+rs(piso)+''+
+ (dif>0.5? ' — '+rs(dif)+' a menos só nesta folha' : '')+'.
';
+ manual='
'+
+ 'm
';
}
const barra = x.pct!=null
? '
'+
@@ -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 PNG, que a conferência é bem mais leve.
' : '')+
+ // 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
+ ? ''
+ : ''+escapeHTML(extDe(x.f.name))+'';
+ const copias = x.m>0 || manual ?
+ '
'+manual+
+ (x.m>0 ?
+ '
'+
+ ''+
+ ''+
+ '
'+
+ '
'+(x.rep>1? x.rep+' × '+fmtM(x.m)+' m = ' : '')+''+fmtM(sub)+' m
';
}).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 += '
'+
- ''+folhas.length+' folhas neste pedido'+
- ''+fmtM(tot)+' m no total'+
- (partes>1? 'vai em '+partes+' partes de até 20 m':'')+'