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>
121 lines
7.1 KiB
JavaScript
121 lines
7.1 KiB
JavaScript
/* Site DTF — Product tables, commercial ladders, shared state and field validation.
|
|
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. */
|
|
|
|
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';
|
|
|
|
// ── 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.',
|
|
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.',
|
|
zt:'Arraste suas artes de UV',
|
|
zs:'PNG ou JPG com fundo transparente · informe a largura de cada arte'}
|
|
};
|
|
// Quatro escadas. A de artes avulsas e a da folha pronta mais a montagem:
|
|
// R$ 10,00 no têxtil, R$ 14,00 no UV, constante em todas as faixas.
|
|
const FAIXAS={
|
|
file: [[90,14.90],[75,16.20],[60,17.50],[40,18.70],[0,19.90]],
|
|
avulsa:[[90,24.90],[75,26.20],[60,27.50],[40,28.70],[0,29.90]],
|
|
uvfile:[[90,69.90],[75,73.90],[60,77.90],[40,81.90],[0,85.90]],
|
|
uv: [[90,83.90],[75,87.90],[60,91.90],[40,95.90],[0,99.90]]
|
|
};
|
|
const TABELA={file:19.90, avulsa:29.90, uvfile:85.90, uv:99.90}; // teto de cada escada
|
|
const MONTAGEM={avulsa:10.00, uv:14.00}; // o que a montagem soma sobre a folha pronta
|
|
const ehFolha=m=>!!MODOS[m||modo].folha;
|
|
const pisoEscada=m=>FAIXAS[m||modo][0][1];
|
|
const MINIMO_M=1, GAP_CM=0.5, RODAPE_CM=5;
|
|
const RENDERIZA=/[.](png|jpe?g|webp|gif|bmp)$/i;
|
|
// Folha pronta: dá para medir PNG, JPG e PDF. PSD, AI, TIFF e CDR entram, mas
|
|
// o navegador não abre — o cliente informa o comprimento e o arquivo passa por
|
|
// tratamento antes de imprimir.
|
|
const ACEITA={
|
|
folha: /[.](png|jpe?g|tiff?|pdf|psd|psb|ai|cdr)$/i,
|
|
avulsa: /[.](png|jpe?g|webp)$/i
|
|
};
|
|
const MEDE=/[.](png|jpe?g|webp|gif|bmp|pdf)$/i;
|
|
const DPI_RECUSA=96, DPI_AVISA=300; // régua dos designers · 02/09/2026
|
|
// A especificação do PDF limita a página a 14.400 unidades — 200 pol, 508 cm.
|
|
// Passar disso só é válido com UserUnit (PDF 1.6+), que a maioria dos programas
|
|
// não escreve: o arquivo sai fora do padrão e só o gerador lê direito.
|
|
const PDF_MAX_PT=14400, PT_CM=2.54/72;
|
|
const extDe=n=>(n.split('.').pop()||'').toUpperCase();
|
|
let CLIENTE='SEU NOME AQUI', PEDIDO='000000';
|
|
|
|
let modo=null, artes=[], recemChegada=null, metros=0, nota=0, reencaixado=false;
|
|
let folhas=[]; // [{f, med, rep, m}] · o cliente pode subir várias
|
|
const folhaTotalM=()=>folhas.reduce((t,x)=>t+(x.m||0)*(x.rep||1),0);
|
|
// O cliente declara o que está mandando escolhendo o produto, em vez de o site
|
|
// adivinhar pelo formato do arquivo. A declaração de folha montada é conferida na
|
|
// medição: uma imagem sem pixel para ocupar a largura do filme não é uma folha.
|
|
// Cada modo por metro tem o seu par de artes avulsas, e o seletor anda nos dois sentidos.
|
|
const PAR={file:'avulsa', avulsa:'file', uvfile:'uv', uv:'uvfile'};
|
|
let montagemCm=0; // altura da folha montada pelo motor, em cm
|
|
let montagemLayout=null; // posições aprovadas de cada cópia, em cm
|
|
const ZOOMS=[1,1.5,2,3,4]; let zoomI=0; // ampliação da montagem ao vivo
|
|
let pedido=[]; // itens já fechados · o pagamento é um só
|
|
// A escolha de entrega vale para o pedido inteiro, não por item. No Tiny, retirada
|
|
// grava forma_envio X, transportador DropStar e frete por conta do remetente — igual
|
|
// 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){
|
|
const d=(v||'').replace(/\D/g,'');
|
|
if(d.length!==14 || /^(\d)\1{13}$/.test(d)) return false;
|
|
const calc=(base,pesos)=>{
|
|
const soma=base.split('').reduce((t,n,i)=>t+ +n*pesos[i],0);
|
|
const r=soma%11; return r<2? 0 : 11-r;
|
|
};
|
|
const p1=[5,4,3,2,9,8,7,6,5,4,3,2], p2=[6,...p1];
|
|
return calc(d.slice(0,12),p1)===+d[12] && calc(d.slice(0,13),p2)===+d[13];
|
|
}
|
|
const zapOk=v=>{ const d=(v||'').replace(/\D/g,''); return d.length===10||d.length===11; };
|
|
const mailOk=v=>/^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test((v||'').trim());
|
|
const clienteOk=()=>cnpjOk(cliente.cnpj)&&zapOk(cliente.zap)&&mailOk(cliente.mail);
|
|
// Frete cotado pelo CEP não entrega nada sozinho: a transportadora e o Tiny
|
|
// precisam do endereço completo antes do pagamento.
|
|
const UFS='AC AL AP AM BA CE DF ES GO MA MT MS MG PA PB PR PE PI RJ RN RS RO RR SC SP SE TO'.split(' ');
|
|
const enderecoOk=()=>{
|
|
if(entrega.tipo!=='frete') return true;
|
|
const e=entrega.end||{}, t=v=>(v||'').trim();
|
|
return t(e.nome).length>=2 && t(e.rua).length>=2 && t(e.num).length>=1 &&
|
|
t(e.bairro).length>=2 && t(e.cidade).length>=2 && UFS.includes(t(e.uf).toUpperCase());
|
|
};
|
|
const destinoApi=()=>entrega.tipo!=='frete' ? null : {
|
|
recipient:entrega.end.nome.trim(), street:entrega.end.rua.trim(), number:entrega.end.num.trim(),
|
|
complement:(entrega.end.comp||'').trim(), district:entrega.end.bairro.trim(),
|
|
city:entrega.end.cidade.trim(), state:entrega.end.uf.trim().toUpperCase(), postal_code:entrega.cep};
|
|
const larguraFilme=()=>MODOS[modo].larg;
|
|
// cobrança proporcional: arredonda para cima em 10 cm · 2,75 m vira 2,80 m
|
|
const cobrar=m=>Math.max(MINIMO_M, Math.ceil(m*10)/10);
|
|
const fmtM=m=>m.toFixed(2).replace('.',',');
|
|
const precoBase=n=>
|
|
(FAIXAS[modo].find(f=>n>=f[0])||FAIXAS[modo][FAIXAS[modo].length-1])[1];
|
|
const descontoMetragem=()=>0; // sem progressão por quantidade
|
|
const cls=n=>n>=85?'ok':n>=50?'av':'er';
|
|
const dpiDe=a=>a.cm>0? (a.giro?a.py:a.px)/(a.cm/2.54) : 0;
|
|
// girar 90° inverte a proporção · espelhar não muda medida, só o desenho
|
|
const propDe=a=>{ const p=a.prop||1; return a.giro? 1/p : p; };
|
|
const TAMANHOS={57:[10,15,20,25,28.2], 28.5:[5,8,10,14,28.2]};
|