/* Site DTF — Product tables, commercial ladders, shared state and field validation. Extracted verbatim from the single inline script in dtf-site.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:'tabela R$ 19,90/m · até R$ 14,90', 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:'tabela R$ 29,90/m · até R$ 24,90', 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:'tabela R$ 85,90/m · até R$ 69,90', 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:'tabela R$ 99,90/m · até R$ 83,90', 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 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}; 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); 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.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]}; const px=f=>Math.round(Math.min(6000,Math.max(600,Math.sqrt(f.size/1024)*95)));