Files
dtf-system/dtf-site.html
Cauê Faleiros 67aa6c970c fix: make the by-metre product reachable and its declaration verified
Navigation links carried data-modo-cta and opened loose artwork directly, so
every entry point except the price card bypassed "Arquivo por metro". Dropping
a PNG or JPG from the by-metre editor then switched the order to loose artwork
and repriced it, while the same screen advertised PNG/JPG in its drop zone and
marked that path as the cheaper one.

Replace the guessing with a declaration: the product is the choice. #tipoEnvio
shows ready sheet and loose artwork side by side with both prices, in all four
modes, reversible until a file is attached and locked afterwards. sel() no
longer reassigns modo, so a file can never change product or price on its own.

Verify the declaration instead of trusting it. medirFolha returns dpiFolha, and
an image without the pixels to span the film width at DPI_RECUSA is refused as
a sheet, with the numbers shown and one click to send it as loose artwork.
Previously a small image declared as a sheet was billed by the metre.

Scope pintaCaminhos to #caminhos .cam: its global selector was clearing the new
control. Move the checkout status out of #carr, which the success path hides,
so a paid order still confirms itself to the customer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 18:32:38 -03:00

2954 lines
152 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DTF Dropstar · mande sua arte e veja a qualidade</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root{
/* tokens do site institucional */
--font-family-base:"Roboto",sans-serif; --font-family-title:"Inter",sans-serif;
--font-color-base:#03060b; --font-color-title:#03060b;
--font-weight-base:400; --font-weight-title:700; --font-weight-subtitle:600; --font-weight-button:600;
--font-size-title:3rem; --font-size-subtitle:2rem; --font-size-button:1.4rem;
--font-size-small:1.2rem; --font-size-base:1.4rem; --font-size-medium:1.6rem; --font-size-big:1.8rem;
--line-height:1.5;
--white:#fff; --black:#03060b;
--gray-1:#f9f9f9; --gray-2:#c3c3c3; --gray-3:#707070; --gray-4:#363636;
--primary-color:#ffa81a; --secondary-color:#ff7f0a;
--white-transparency:#ffffffb3; --transition:all .4s ease;
/* apelidos usados nesta pagina, mapeados nos tokens acima */
--marinho:var(--black); --papel:var(--white); --cinza:var(--gray-1);
--tx:var(--font-color-base); --fraco:var(--gray-3); --linha:#e6e6e6;
--laranja:var(--primary-color); --laranja2:var(--secondary-color);
--vermelho:#D94A28; --verde:#2E9E5B; --verde-ml:#00A650; --verde-claro:#57D68A;
--sombra:0 1px 2px rgba(3,6,11,.05), 0 4px 14px rgba(3,6,11,.05);
--sombra-alta:0 2px 6px rgba(3,6,11,.07), 0 10px 30px rgba(3,6,11,.11)}
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;font-size:62.5%}
body{background:var(--white);color:var(--font-color-base);font-family:var(--font-family-base);
font-size:var(--font-size-base);font-weight:var(--font-weight-base);line-height:var(--line-height);
-webkit-font-smoothing:antialiased}
h1,h2,h3{font-family:var(--font-family-title);color:var(--font-color-title);
font-weight:var(--font-weight-title);letter-spacing:normal;line-height:1.15}
.w{max-width:1020px;margin:0 auto;padding:0 22px}
section{padding:clamp(34px,5vw,56px) 0}
section+section{border-top:1px solid var(--linha)}
.prova+section{border-top:0}
/* tarja rotativa, como no site */
.tarja{background:var(--laranja);color:#111;text-align:center;padding:8px 16px;
font-family:"Inter",sans-serif;font-weight:700;font-size:12px;letter-spacing:.02em}
.tarja a{color:#111;text-decoration:none}
/* topo */
.topo{background:#fff;border-bottom:1px solid var(--linha);position:sticky;top:0;z-index:60}
.topo .w{display:flex;align-items:center;gap:22px;padding-top:15px;padding-bottom:15px}
.logo{display:flex;align-items:center;text-decoration:none;white-space:nowrap;flex:none}
.logo img{height:26px;width:auto;display:block}
.logotexto{font-family:"Inter",sans-serif;font-weight:700;font-size:19px;color:var(--black);
letter-spacing:-.03em}
.logotexto i{font-style:normal;color:var(--laranja)}
.busca{flex:1;max-width:420px;position:relative}
.busca input{width:100%;padding:9px 14px 9px 36px;border:1px solid var(--linha);border-radius:22px;
font-family:inherit;font-size:13px;background:var(--cinza);color:var(--tx)}
.busca input:focus{outline:none;border-color:var(--laranja);background:#fff}
.busca .lupa{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--fraco);font-size:13px}
.acts{margin-left:auto;display:flex;gap:18px;align-items:center;font-size:12.5px}
.acts a{color:var(--tx);text-decoration:none;display:flex;align-items:center;gap:6px}
.acts a:hover{color:var(--laranja)}
/* menu */
.menu{background:#fff;border-bottom:1px solid var(--linha)}
.menu .w{display:flex;gap:0;flex-wrap:wrap}
.mi{position:relative}
.mi>a{display:block;padding:13px 16px;font-family:"Inter",sans-serif;font-weight:600;
font-size:12.5px;color:var(--tx);text-decoration:none;white-space:nowrap}
.mi:hover>a{color:var(--laranja)}
.mi.on>a{color:var(--laranja);box-shadow:inset 0 -2px 0 var(--laranja)}
.sub{display:none;position:absolute;top:100%;left:0;background:#fff;border:1px solid var(--linha);
border-radius:0 0 9px 9px;min-width:224px;padding:7px 0;z-index:70;
box-shadow:0 10px 26px rgba(0,0,0,.09)}
.mi:hover .sub,.mi:focus-within .sub{display:block}
.sub a{display:block;padding:8px 17px;font-size:12.5px;color:var(--fraco);text-decoration:none}
.sub a:hover{color:var(--laranja);background:var(--cinza)}
@media(max-width:820px){
.menu .w{overflow-x:auto;flex-wrap:nowrap}
.sub{display:none!important}
.busca{display:none}
}
.hero{background:#000;color:#fff;position:relative;overflow:hidden}
.hero .fundo{position:absolute;inset:0;pointer-events:none}
.hero .fundo svg{width:100%;height:100%;display:block;opacity:.22}
/* escurece do lado do texto, para o título não competir com o fundo */
.hero .fundo::after{content:"";position:absolute;inset:0;
background:linear-gradient(100deg,#000 0%,#000 32%,rgba(0,0,0,.85) 52%,rgba(0,0,0,.55) 100%)}
.hero .w{position:relative;z-index:2}
.hgrid{display:grid;grid-template-columns:1.15fr auto;gap:38px;align-items:center;
padding:clamp(26px,3.6vw,40px) 0}
@media(max-width:940px){.hgrid{grid-template-columns:1fr;gap:22px}}
h1{font-size:clamp(24px,3.6vw,38px);font-weight:700;max-width:20ch;line-height:1.08}
h1 em{font-style:normal;color:var(--laranja)}
.hero .sub2{color:#B7C1D2;font-size:14px;max-width:56ch;margin-top:10px}
.hero .sub2 b{color:#fff}
.hcta{display:flex;gap:12px;margin-top:16px;flex-wrap:wrap;align-items:center}
.btn1{background:var(--laranja);color:#241703;border:0;border-radius:9px;padding:12px 24px;
font-family:"Inter",sans-serif;font-weight:700;font-size:14.5px;cursor:pointer;text-decoration:none}
.btn1:hover{background:var(--laranja2)}
.mini{font-size:12px;color:#8D9AB0}
/* preços lado a lado, em faixa */
.pfaixa{display:flex;gap:10px;flex-wrap:wrap}
@media(max-width:940px){.pfaixa{flex-wrap:wrap}}
.pcard{flex:1 1 44%;min-width:128px;background:rgba(255,255,255,.055);
border:1px solid rgba(255,255,255,.13);border-radius:11px;padding:13px 15px;
text-decoration:none;position:relative;transition:.15s}
.pcard:hover{background:rgba(255,168,26,.12);border-color:rgba(255,168,26,.45)}
.pcard.dest{border-color:rgba(255,168,26,.5);background:rgba(255,168,26,.09)}
.pcard .marc{position:absolute;top:-8px;left:13px;background:var(--verde);color:#fff;
font-family:"Inter",sans-serif;font-size:8.5px;font-weight:700;letter-spacing:.09em;
text-transform:uppercase;padding:2px 8px;border-radius:4px;white-space:nowrap}
.pcard .apartir{display:block;font-size:9.5px;color:var(--verde-claro);letter-spacing:.09em;
text-transform:uppercase;font-family:"Inter",sans-serif;font-weight:700;margin-bottom:2px}
.pcard .vl{font-family:"Inter",sans-serif;font-size:20px;font-weight:700;color:var(--laranja);
line-height:1;display:block;margin-bottom:3px}
.pcard .vl small{font-size:11px;font-weight:600;color:#8D9AB0}
.pcard .nm{font-family:"Inter",sans-serif;font-size:12.5px;font-weight:700;color:#fff;display:block}
.pcard .dsc{font-size:10.5px;color:#8D9AB0;display:block;margin-top:1px}
/* selos e barra */
.selos1{display:flex;gap:7px;flex-wrap:wrap;align-items:center;margin-bottom:11px}
.sel{font-family:"Inter",sans-serif;font-size:9.5px;font-weight:700;letter-spacing:.09em;
text-transform:uppercase;padding:3px 9px;border-radius:5px}
.sel.top{background:var(--laranja);color:#241703}
.sel.novo{background:rgba(46,158,91,.18);color:#7FD4A0;border:1px solid rgba(46,158,91,.4)}
.sel.pos{background:rgba(255,255,255,.09);color:#B7C1D2;border:1px solid rgba(255,255,255,.16)}
.barrah{border-top:1px solid rgba(255,255,255,.1);padding:11px 0 14px;
display:flex;gap:24px;flex-wrap:wrap;font-size:12px;color:#B7C1D2}
.barrah span{display:flex;align-items:center;gap:7px}
.barrah b{color:#fff;font-family:"Inter",sans-serif}
.barrah i{color:var(--laranja);font-style:normal;font-size:9px}
/* aviso do topo do envio */
/* escolha em 3 cards */
.escolha{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:24px;
align-items:stretch}
.escolha .ec{display:flex;flex-direction:column}
.escolha .ec .in{flex:1}
@media(max-width:1000px){.escolha{grid-template-columns:repeat(2,1fr)}}
@media(max-width:600px){.escolha{grid-template-columns:1fr}}
.ec{border:1px solid var(--linha);border-radius:14px;background:#fff;padding:0;
cursor:pointer;text-align:left;font-family:inherit;overflow:hidden;
box-shadow:var(--sombra);transition:var(--transition);
display:block;width:100%;position:relative}
/* marca d'agua dos cards · mesma da foto de produto: curva de nivel + sacola Dropstar
(a sacola e uma reconstrucao; trocar pelo arquivo oficial do designer) */
.ec::before{content:"";position:absolute;inset:0;pointer-events:none;z-index:0;
background-repeat:no-repeat;background-position:right -60px center;background-size:auto 150%;
background-image:url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 300'>\
<g fill='none' stroke='%23000000' stroke-opacity='.055' stroke-width='1.6'>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.86) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.72) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.58) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.44) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.30) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(1.16) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(1.34) translate(-200 -150)'/>\
</g></svg>")}
.ec::after{content:"";position:absolute;right:16px;bottom:14px;width:104px;height:26px;
opacity:.16;pointer-events:none;z-index:0;background-repeat:no-repeat;
background-position:right bottom;background-size:contain;
background-image:url("https://cdn.vnda.com.br/dropstar/2025/12/17/logo-header-15125700-16129080.png?v=1766009286")}
.ec .top,.ec .in{position:relative;z-index:1}
.ec:hover{border-color:var(--laranja);box-shadow:var(--sombra-alta);transform:translateY(-2px)}
.ec .marc{display:block;background:var(--verde-ml);color:#fff;text-align:center;
font-family:"Inter",sans-serif;font-size:9px;font-weight:700;letter-spacing:.14em;
text-transform:uppercase;padding:5px 8px;position:relative;z-index:2}
.ec.f{border-color:var(--verde-ml)}
.ec.f:hover{border-color:var(--verde-ml)}
/* anéis verdes por trás do card destacado, mesma família dos selos */
.ec.f::before{background-image:url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 300'>\
<g fill='none' stroke='%2300A650' stroke-opacity='.10' stroke-width='1.6'>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.82) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.64) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.46) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(.28) translate(-200 -150)'/>\
<path d='M200 46c66 0 138 40 138 104s-72 104-138 104S62 214 62 150 134 46 200 46z' transform='translate(200 150) scale(1.2) translate(-200 -150)'/>\
</g></svg>")}
.ec .top{padding:17px 20px;display:flex;justify-content:space-between;align-items:flex-start;gap:10px}
.ec.f .top{background:#FFF4E3} .ec.a .top{background:var(--cinza)} .ec.u .top{background:#EEF4FB}
.ec .top h3{font-family:"Inter",sans-serif;font-size:17px;font-weight:700;color:var(--tx)}
.ec .top .p{font-family:"Inter",sans-serif;font-weight:700;font-size:15px;white-space:nowrap;
color:var(--tx);text-align:right;line-height:1.15}
.ec .top .p i{display:block;font-style:normal;font-family:"Roboto",sans-serif;font-size:10px;
font-weight:500;color:var(--fraco);margin-bottom:1px;letter-spacing:0;
text-transform:uppercase;letter-spacing:.1em}
.chama2{background:#F1FBF5;border-top:1px solid #BFE6CE;border-bottom:1px solid #BFE6CE;
padding:9px 20px;font-size:11.5px;color:#1F5C3A;line-height:1.45;position:relative;z-index:1}
.chama2 b{font-family:"Inter",sans-serif;font-weight:700;color:var(--verde-ml);font-size:12.5px}
.ec .top .p small{font-size:11px;font-weight:600;color:var(--fraco)}
.ec .in{padding:18px 20px 20px}
.ec .in p{font-size:13px;color:var(--fraco);margin-bottom:13px}
.ec ul{list-style:none;font-size:12.5px;margin-bottom:15px}
.ec li{padding:4px 0 4px 19px;position:relative;color:var(--fraco)}
.ec li::before{content:"✓";position:absolute;left:2px;color:var(--verde);font-size:11px}
.ec .go{font-family:"Inter",sans-serif;font-weight:700;font-size:13.5px;color:var(--laranja);display:block}
.ec .lg{display:block;font-size:10.5px;color:var(--fraco);letter-spacing:.1em;
text-transform:uppercase;font-family:"Inter",sans-serif;font-weight:700;margin-bottom:5px}
/* painel focado */
.foco{display:none}
.foco.on{display:block}
.focoGrid{display:grid;grid-template-columns:minmax(0,1fr) 264px;gap:16px;align-items:start}
.focoGrid>*{min-width:0}
@media(max-width:820px){.focoGrid{grid-template-columns:1fr}}
.voltar{display:inline-flex;align-items:center;gap:7px;background:#fff;
border:1px solid var(--linha);border-radius:8px;padding:8px 14px 8px 11px;
font-family:"Inter",sans-serif;font-weight:600;font-size:12.5px;color:var(--tx);
cursor:pointer;margin-bottom:14px;box-shadow:var(--sombra);transition:var(--transition)}
.voltar span{font-size:15px;line-height:1;color:var(--fraco)}
.voltar:hover{border-color:var(--laranja);color:var(--laranja)}
.voltar:hover span{color:var(--laranja)}
.cat{border:1px solid var(--linha);border-radius:14px;background:#fff;overflow:hidden;
box-shadow:var(--sombra)}
.cat.file{border-color:var(--laranja);border-width:2px}
.cabfoco{display:flex;justify-content:space-between;align-items:baseline;gap:14px;
flex-wrap:wrap;border-bottom:1px solid var(--linha);background:var(--cinza);padding:18px 22px}
.cat.file .cabfoco{background:#FFF4E3}
.cabfoco h3{font-family:"Inter",sans-serif;font-size:20px;font-weight:700}
.cabfoco .pr2{font-family:"Inter",sans-serif;font-weight:700;font-size:17px;white-space:nowrap}
.cabfoco p{font-size:12.5px;color:var(--fraco);width:100%;margin-top:4px}
.cabfoco .lg{font-size:10.5px;color:var(--fraco);letter-spacing:.1em;text-transform:uppercase;
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}
.cam{display:flex;gap:9px;align-items:flex-start;text-align:left;background:#fff;
border:1px solid var(--linha);border-radius:10px;padding:11px 12px;cursor:pointer;
font-family:inherit;color:var(--tx);transition:var(--transition);
min-width:0;overflow:hidden}
.cam:hover{border-color:var(--gray-3)}
.cam.on{border-color:var(--verde-ml);background:#F4FCF7;box-shadow:0 0 0 1px var(--verde-ml)}
.cam .mk2{flex:none;width:15px;height:15px;border-radius:50%;margin-top:2px;
border:2px solid var(--gray-2);background:#fff}
.cam.on .mk2{border-color:var(--verde-ml);
background:radial-gradient(circle,var(--verde-ml) 0 3px,#fff 4px)}
.cam div{flex:1;min-width:0;overflow-wrap:anywhere}
.cam b{display:block;font-family:"Inter",sans-serif;font-size:12.5px;margin-bottom:2px}
.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}
.trocarModo{margin:-4px 0 11px;padding:10px 12px;border-radius:9px;background:#F1FBF5;
border:1px solid #BFE6CE;font-size:11.5px;color:#1F5C3A;line-height:1.5}
.trocarModo b{font-family:"Inter",sans-serif;font-weight:700}
.trocarModo label{display:block;margin-top:7px;cursor:pointer}
.zona{border:2px dashed var(--laranja);border-radius:13px;padding:44px 20px;text-align:center;
cursor:pointer;margin-bottom:14px;background:#FFF6E8;transition:var(--transition)}
.zona:hover,.zona.sobre{border-color:var(--laranja2);background:#FFEFD6;
box-shadow:0 6px 22px rgba(255,168,26,.22)}
.zona .ico{width:46px;height:46px;margin:0 auto 12px;border-radius:50%;
background:var(--laranja);color:#fff;font-size:22px;line-height:46px;font-weight:700;
box-shadow:0 0 0 7px rgba(255,168,26,.18)}
.zona:hover .ico,.zona.sobre .ico{background:var(--laranja2)}
.zona b{display:block;font-family:"Inter",sans-serif;font-size:18px;font-weight:700;
margin-bottom:5px;color:var(--tx);line-height:1.2}
.zona span{font-size:12px;color:#8A6A2E;display:block;max-width:30ch;margin:0 auto;line-height:1.5}
.zona .bt{display:inline-block;margin-top:13px;background:var(--laranja);color:#241703;
font-family:"Inter",sans-serif;font-weight:700;font-size:13px;border-radius:8px;
padding:9px 18px}
.zona:hover .bt{background:var(--laranja2)}
.recusa{margin-top:11px;padding:11px 13px;border-radius:9px;background:#FFF3F0;
border:1px solid #F0C4B8;font-size:11.5px;color:#8C3B22;line-height:1.5}
.recusa b{font-family:"Inter",sans-serif;font-weight:700;color:#8C3B22}
.aoVivo{margin-top:11px;padding:11px 13px;border-radius:9px;background:var(--cinza);
border:1px solid var(--linha);font-size:12px;color:var(--fraco);line-height:1.45;
display:flex;align-items:center;gap:9px}
.aoVivo b{font-family:"Inter",sans-serif;font-weight:700;color:var(--tx);font-size:12.5px}
.aoVivo .pt2{flex:none;width:8px;height:8px;border-radius:50%;background:var(--gray-2)}
.aoVivo.pronto{background:#F1FBF5;border-color:#BFE6CE;color:#2A6B45}
.aoVivo.pronto .pt2{background:var(--verde-ml)}
.aoVivo.pronto b{color:#1F5C3A}
.b2{width:100%;padding:13px;border-radius:9px;border:1px solid var(--marinho);background:#fff;
color:var(--tx);font-family:"Inter",sans-serif;font-weight:600;font-size:14.5px;cursor:pointer}
.cat.file .b2{background:var(--laranja);border-color:var(--laranja);color:#241703}
.b2:hover{background:#111;color:#fff}
.cat.file .b2:hover{background:var(--laranja2);color:#241703}
.it{display:flex;justify-content:space-between;gap:9px;padding:10px 13px;background:var(--cinza);
border-radius:8px;margin-bottom:7px;align-items:center;font-size:12.5px}
.it b{font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.it span{color:var(--fraco);font-size:11px;flex:none}
.it button{border:0;background:none;color:var(--fraco);cursor:pointer;font-size:15px}
/* 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}
.prog{height:5px;border-radius:4px;background:#E4E7EC;overflow:hidden;margin:9px 0 5px}
.prog i{display:block;height:100%;background:var(--laranja);border-radius:4px;
transition:width .35s ease}
.progT{display:block;font-size:10.5px;color:var(--fraco)}
.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;
align-items:start;margin-top:2px}
.foco2>*{min-width:0}
.foco2.sofila{grid-template-columns:1fr}
.foco2.sofila .mont{display:none}
@media(max-width:1360px){.foco2{grid-template-columns:1fr}}
.mont{position:sticky;top:104px;border:1px solid var(--linha);border-radius:11px;
background:var(--cinza);padding:12px;box-shadow:var(--sombra)}
.montcab{display:flex;justify-content:space-between;align-items:baseline;gap:10px;margin-bottom:9px}
.montcab b{font-family:"Inter",sans-serif;font-size:12.5px;font-weight:700}
.zoomB{display:flex;align-items:center;gap:4px;margin-left:auto}
.zoomB button{width:22px;height:22px;border-radius:6px;border:1px solid var(--linha);
background:#fff;color:var(--tx);cursor:pointer;font-size:14px;line-height:1;padding:0}
.zoomB button:hover{border-color:var(--laranja);color:var(--laranja)}
.zoomB button:disabled{opacity:.35;cursor:not-allowed}
.zoomB span{font-family:"Inter",sans-serif;font-size:10.5px;font-weight:700;
color:var(--fraco);min-width:34px;text-align:center}
#vArea.ampliado{overflow:auto}
.montcab span{font-family:"Inter",sans-serif;font-weight:700;font-size:16px;color:var(--tx)}
#vArea{max-height:min(56vh,560px);min-height:200px;overflow-y:auto;overscroll-behavior:contain;
border:1px solid var(--linha);border-radius:8px;background:#fff}
#vArea::-webkit-scrollbar{width:8px}
#vArea::-webkit-scrollbar-thumb{background:var(--gray-2);border-radius:6px;
border:2px solid #fff}
#vArea::-webkit-scrollbar-track{background:transparent}
#vArea canvas{max-width:100%;height:auto;display:block;background:#fff}
#vArea.ampliado canvas{max-width:none}
#vArea .folhaPrevias{min-height:200px;padding:10px;display:grid;gap:12px;background:#F8F9FA}
#vArea .folhaPrevia{margin:0;background:#fff;border:1px solid var(--linha);border-radius:6px;overflow:hidden}
#vArea .folhaPrevia img{width:100%;height:auto;max-height:520px;object-fit:contain;display:block;background:#fff}
#vArea .folhaPrevia figcaption{padding:7px 9px;font-size:10.5px;color:var(--fraco);border-top:1px solid var(--linha)}
#vArea .semmont{border:0;min-height:200px;display:flex;flex-direction:column;
align-items:center;justify-content:center}
#vUso:empty{display:none}
.semmont{border:1px dashed var(--linha);border-radius:8px;background:#fff;
padding:30px 14px;text-align:center}
.semmont b{display:block;font-family:"Inter",sans-serif;font-size:13px;margin-bottom:4px}
.semmont span{font-size:11.5px;color:var(--fraco);line-height:1.4;display:block}
.montpe{font-size:10.5px;color:var(--fraco);margin-top:9px;line-height:1.45}
/* caixa de requisitos · o que a máquina aceita */
.req{position:sticky;top:104px;background:#fff;border:1px solid var(--linha);
border-radius:14px;box-shadow:var(--sombra);padding:16px 16px 6px}
@media(max-width:820px){.req{position:static}}
.reqcab{font-family:"Inter",sans-serif;font-size:18px;font-weight:700;line-height:1.15;
letter-spacing:normal;color:var(--tx);margin-bottom:12px}
.req details{border-top:1px solid var(--linha)}
.req details:first-of-type{border-top:0}
.req summary{cursor:pointer;list-style:none;padding:9px 0;font-family:"Inter",sans-serif;
font-size:13px;font-weight:700;color:var(--tx);display:flex;align-items:center;gap:8px}
.req summary::-webkit-details-marker{display:none}
.req summary::before{content:"+";flex:none;width:16px;height:16px;border-radius:50%;
border:1px solid rgba(0,166,80,.45);background:#fff;color:var(--verde-ml);font-size:11px;
line-height:14px;text-align:center;font-weight:700}
.req summary:hover::before{background:#F1FBF5}
.req details[open] summary::before{content:"−";background:var(--verde-ml);
border-color:var(--verde-ml);color:#fff}
.req ul{list-style:none;padding:0 0 9px;margin:0}
.req li{position:relative;padding:4px 0 4px 17px;font-size:11.5px;color:var(--fraco);
line-height:1.5}
.req li b{color:var(--tx);font-weight:700;font-family:"Inter",sans-serif}
.req li::before{position:absolute;left:0;top:4px;font-size:11px;font-weight:700}
.req li.s::before{content:"✓";color:var(--verde-ml)}
.req li.a::before{content:"!";color:var(--laranja)}
.req li.n::before{content:"×";color:var(--vermelho)}
.req .obs2{font-size:10.5px;color:var(--fraco);padding:0 0 9px;line-height:1.45;
font-style:italic}
.mont .usoBom,.mont .usoRuim{margin-top:8px;font-size:11px;line-height:1.4}
/* artes avulsas */
.art{background:var(--cinza);border-radius:9px;padding:11px 12px;margin-bottom:7px;
transition:box-shadow .3s,background .3s}
.art.nova{background:#FFF6E8;box-shadow:inset 0 0 0 1px rgba(255,168,26,.55)}
.art.nova .l1 b::after{content:"nova";font-family:"Inter",sans-serif;font-size:9px;
font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#B07A18;
background:#FFE9C4;border-radius:3px;padding:1px 5px;margin-left:7px;vertical-align:1px}
.art .l1{display:flex;justify-content:space-between;gap:8px;align-items:center;margin-bottom:8px}
.art .l1 b{font-weight:500;font-size:12.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.art .l1 button{border:0;background:none;color:var(--fraco);cursor:pointer;font-size:15px;flex:none}
.art .cps{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.atalhos{display:flex;flex-wrap:wrap;gap:5px;align-items:center;margin-top:9px}
.atalhos .tm,.atalhos .gr{border:1px solid var(--linha);background:#fff;border-radius:6px;
padding:5px 9px;font-family:"Inter",sans-serif;font-size:11px;font-weight:600;
color:var(--tx);cursor:pointer;transition:var(--transition)}
.atalhos .tm:hover,.atalhos .gr:hover{border-color:var(--laranja);color:var(--laranja)}
.atalhos .tm.on{background:var(--verde-ml);border-color:var(--verde-ml);color:#fff}
.atalhos .gr{font-size:14px;line-height:1;padding:4px 9px}
.atalhos .gr.on{background:var(--laranja);border-color:var(--laranja);color:#241703}
.atalhos .sep{flex:1;min-width:4px}
.art label{display:block;font-size:10px;color:var(--fraco);margin-bottom:3px}
.art 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)}
.art input:focus{outline:none;border-color:var(--laranja)}
.qb2{height:7px;border-radius:5px;background:#E4E7EC;overflow:hidden;margin-top:9px}
.qb2 i{display:block;height:100%;border-radius:5px}
.qb2 i.ok{background:var(--verde)}.qb2 i.av{background:var(--laranja)}.qb2 i.er{background:var(--vermelho)}
.calc{font-size:10.5px;margin-top:6px;display:flex;justify-content:space-between;gap:8px;color:var(--fraco)}
.calc .ok{color:var(--verde)}.calc .av{color:var(--laranja)}.calc .er{color:var(--vermelho)}
.dica{margin-top:9px;background:#FFF8EE;border:1px solid #F0D9AE;border-radius:8px;
padding:10px 12px;font-size:11.5px;color:#7A5A16;line-height:1.45}
.dica b{display:block;font-family:"Inter",sans-serif;color:#B07A18;font-size:12px;margin-bottom:3px}
.dica button{display:block;width:100%;margin-top:10px;padding:10px 14px;border-radius:7px;
border:1px solid var(--verde-ml);background:var(--verde-ml);color:#fff;
font-family:"Inter",sans-serif;font-weight:700;font-size:13px;letter-spacing:.01em;
cursor:pointer;box-shadow:0 2px 8px rgba(0,166,80,.28);transition:var(--transition)}
.dica button:hover{background:#00934A;border-color:#00934A;box-shadow:0 4px 14px rgba(0,166,80,.38)}
.dicaok{margin-top:8px;font-size:11px;color:var(--verde)}
/* cota em escala da peça dentro da largura do filme */
.viz{margin-top:10px;max-width:100%}
.vizL{display:flex;align-items:center;gap:7px;margin-bottom:5px}
.vizL i{flex:1;height:1px;background:var(--gray-2);position:relative}
.vizL i::before{content:"";position:absolute;top:-3px;width:1px;height:7px;background:var(--gray-2)}
.vizL i:first-child::before{left:0} .vizL i:last-child::before{right:0}
.vizL b{font-family:"Inter",sans-serif;font-size:9.5px;font-weight:700;letter-spacing:.08em;
text-transform:uppercase;color:var(--fraco)}
.vizRow{display:flex;gap:7px;align-items:flex-end}
.vizTira{flex:none;max-width:100%}
.vizH{flex:none;display:flex;align-items:center;justify-content:center;padding-left:4px;
border-left:1px solid var(--gray-2);min-height:22px}
.vizH span{writing-mode:vertical-rl;font-family:"Inter",sans-serif;font-size:9.5px;
font-weight:700;color:var(--fraco);white-space:nowrap}
.tira{display:flex;background:#fff;border:1px solid var(--linha);border-radius:4px;
padding:2px;overflow:hidden;min-height:22px;width:100%}
.pc{flex:none;overflow:hidden;display:flex;align-items:center;justify-content:center;
background:#F2F2F2;border-radius:2px}
.pc img{width:100%;height:100%;object-fit:contain;display:block}
.pc span{font-family:"Inter",sans-serif;font-size:9px;font-weight:700;color:var(--fraco)}
.folga{flex:1;min-width:0;display:flex;align-items:center;justify-content:center;border-radius:2px;
background:repeating-linear-gradient(45deg,#FBFBFB,#FBFBFB 4px,#F0F0F0 4px,#F0F0F0 8px)}
.folga span{font-size:8.5px;color:var(--fraco);white-space:nowrap;overflow:hidden}
.vizPe{font-size:10.5px;color:var(--fraco);margin-top:6px;line-height:1.4}
.vizPe b{color:var(--tx);font-family:"Inter",sans-serif;font-weight:700}
.resumoA{background:#fff;border:1px solid var(--linha);border-radius:9px;padding:11px 13px;
font-size:12px;margin-bottom:12px;display:none}
.resumoA.on{display:block}
.resumoA .r{display:flex;justify-content:space-between;padding:2px 0}
.resumoA .r b{font-family:"Inter",sans-serif}
/* nota de qualidade */
.qual{background:#fff;border:1px solid var(--linha);border-radius:13px;padding:22px;margin-top:16px;display:none}
.qual.on{display:block}
.qh{display:flex;justify-content:space-between;align-items:flex-end;gap:14px;flex-wrap:wrap;margin-bottom:14px}
.qh h3{font-family:"Inter",sans-serif;font-size:17px;font-weight:700}
.qh .pct{font-family:"Inter",sans-serif;font-weight:700;font-size:38px;line-height:1;color:var(--verde)}
.qh .pct small{font-size:14px;color:var(--fraco)}
.qbar{height:13px;border-radius:8px;background:var(--cinza);overflow:hidden}
.qbar i{display:block;height:100%;border-radius:8px;background:var(--verde)}
.qitens{display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:7px;margin-top:13px}
.qi{display:flex;gap:8px;align-items:center;border:1px solid var(--linha);border-radius:8px;
padding:7px 10px;font-size:11.5px}
.qi .c{width:15px;height:15px;border-radius:50%;display:grid;place-items:center;font-size:8.5px;
flex:none;color:#fff;font-family:"Inter",sans-serif;font-weight:700}
.qi b{display:block;font-family:"Inter",sans-serif;font-size:11.5px}
.qi span{color:var(--fraco);font-size:10.5px}
.qi.ok .c{background:var(--verde)}.qi.av .c{background:var(--laranja);color:#241703}
.qi.er .c{background:var(--vermelho)}
.qi.fix{border-color:#BBD9E8;background:#F2F9FC}
.qi.fix .c{background:#2F7FA8}
.ciente{background:#FFF8EE;border:1px solid #F0D9AE;border-radius:10px;padding:14px 17px;
margin-top:14px;display:none}
.ciente.on{display:block}
.ciente.recusa{background:#FFF3F0;border-color:#E8A796}
.ciente.recusa h4{color:#8C3B22}
.ciente.recusa label{display:none}
.ciente.recusa .lista{color:#8C3B22}
.ciente h4{font-family:"Inter",sans-serif;font-size:14px;font-weight:700;color:#B07A18;margin-bottom:5px}
.ciente p{font-size:12.5px;color:#7A5A16;line-height:1.5}
.ciente .lista{margin:10px 0 0;font-size:12px;color:#7A5A16}
.ciente .lista div{padding:3px 0 3px 15px;position:relative}
.ciente .lista div::before{content:"·";position:absolute;left:4px;font-weight:700}
.ciente label{display:flex;gap:9px;align-items:flex-start;margin-top:12px;padding-top:11px;
border-top:1px solid #F0D9AE;cursor:pointer;font-size:12.5px;color:#5E4512}
.ciente input{margin-top:2px;width:16px;height:16px;flex:none;accent-color:#B07A18}
@keyframes pulsa{0%,100%{transform:scale(1);box-shadow:0 0 0 0 rgba(217,74,40,.55)}
50%{transform:scale(1.04);box-shadow:0 0 0 12px rgba(217,74,40,0)}}
.qbts{display:flex;gap:9px;flex-wrap:wrap;margin-top:16px}
.qbts .qb{flex:1;min-width:220px}
.qb{padding:11px 17px;border-radius:8px;border:1px solid var(--linha);background:#fff;
font-family:"Inter",sans-serif;font-weight:600;font-size:13.5px;cursor:pointer;color:var(--tx)}
.qb.p{background:var(--laranja);border-color:var(--laranja);color:#241703}
.qb.chama{background:var(--vermelho);border-color:var(--vermelho);color:#fff;
animation:pulsa 1.6s ease-in-out infinite}
.qb.chama:hover{background:#C1421F;animation:none}
.qb:disabled{opacity:.45;cursor:not-allowed}
@media(prefers-reduced-motion:reduce){.qb.chama{animation:none}}
/* prévia */
.prev{background:#fff;border:1px solid var(--linha);border-radius:13px;padding:22px;margin-top:16px;display:none}
.prev.on{display:block}
.prev .ph{display:flex;justify-content:space-between;align-items:flex-end;gap:14px;flex-wrap:wrap;margin-bottom:14px}
.prev .ph h3{font-family:"Inter",sans-serif;font-size:17px;font-weight:700}
.prev .ph p{font-size:12.5px;color:var(--fraco)}
.prev .ph .mt{font-family:"Inter",sans-serif;font-weight:700;font-size:22px;white-space:nowrap}
.prev .ph .mt small{font-size:12px;color:var(--fraco);font-weight:600}
.pgrid{display:grid;grid-template-columns:300px 1fr;gap:18px;align-items:start}
@media(max-width:760px){.pgrid{grid-template-columns:1fr}}
.pfolha{background:var(--cinza);border:1px solid var(--linha);border-radius:10px;padding:12px}
.pfolha canvas,.pfolha img{width:100%;height:auto;display:block;border-radius:6px;background:#fff}
.pfolha .semprev,.pfolha .carregando{padding:40px 16px;text-align:center;color:var(--fraco);
font-size:12px;background:#fff;border-radius:6px}
.pfolha .semprev b{display:block;font-family:"Inter",sans-serif;color:var(--tx);font-size:13px;margin-bottom:4px}
.usoBom,.usoRuim{margin-top:10px;border-radius:8px;padding:9px 12px;font-size:11.5px;line-height:1.45}
.usoBom{background:#F1F8F3;border:1px solid #CDE6D6;color:#2A6B45}
.usoRuim{background:#FFF8EE;border:1px solid #F0D9AE;color:#7A5A16}
.usoBom b,.usoRuim b{display:block;font-family:"Inter",sans-serif;font-size:12.5px;margin-bottom:2px}
.pinfo .li{display:flex;gap:10px;align-items:flex-start;padding:7px 0;font-size:12.5px;
border-bottom:1px solid var(--linha)}
.pinfo .li:last-child{border:0}
.pinfo .li .c{width:19px;height:19px;border-radius:50%;display:grid;place-items:center;flex:none;
margin-top:1px;font-size:10px;color:#fff;font-family:"Inter",sans-serif;font-weight:700}
.pinfo .li b{display:block;font-family:"Inter",sans-serif;font-size:12.5px}
.pinfo .li span{color:var(--fraco);font-size:11.5px}
.pcta{display:flex;gap:9px;flex-wrap:wrap;margin-top:16px;padding-top:15px;border-top:1px solid var(--linha)}
.pb{padding:11px 18px;border-radius:8px;border:1px solid var(--linha);background:#fff;
font-family:"Inter",sans-serif;font-weight:600;font-size:13.5px;cursor:pointer;color:var(--tx)}
.pb.p{background:var(--laranja);border-color:var(--laranja);color:#241703}
.pb:hover{border-color:#111}
/* carrinho */
/* faixa de prova · cinco motivos logo abaixo do topo */
.prova{padding:clamp(22px,3vw,34px) 0}
.prova .w{display:grid;grid-template-columns:repeat(auto-fit,minmax(178px,1fr));gap:14px}
.p1{border-left:2px solid var(--laranja);padding:2px 0 2px 13px}
.p1 b{display:block;font-family:"Inter",sans-serif;font-size:14px;font-weight:700;
line-height:1.25;color:var(--tx);margin-bottom:5px}
.p1 b em{font-style:normal;color:var(--laranja)}
.p1 span{display:block;font-size:11.5px;color:var(--fraco);line-height:1.5}
/* ── Carrinho no padrão de checkout: entrega à esquerda, resumo fixo à direita ── */
.carr{display:none;margin-top:18px}
.checkout{margin-top:14px}
.checkout #checkoutStatus:empty{display:none}
.carr.on{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:18px;align-items:start}
@media(max-width:900px){.carr.on{grid-template-columns:1fr}}
.carr h3{font-family:"Inter",sans-serif;font-size:19px;font-weight:700;margin-bottom:12px}
.cxE{background:#fff;border:1px solid var(--linha);border-radius:12px;
box-shadow:var(--sombra);padding:17px 18px;margin-bottom:14px}
.cxE h4{font-family:"Inter",sans-serif;font-size:15px;font-weight:700;margin-bottom:11px}
/* itens do pedido */
.item{display:flex;gap:11px;align-items:flex-start;padding:11px 0;border-top:1px solid var(--linha)}
.item:first-child{border-top:0;padding-top:2px}
.item .nm2{flex:1;min-width:0}
.item .nm2 b{display:block;font-family:"Inter",sans-serif;font-size:13.5px;color:var(--tx)}
.item .nm2 span{font-size:11.5px;color:var(--fraco)}
.item .vl2{font-family:"Inter",sans-serif;font-weight:700;font-size:14px;color:var(--tx);
white-space:nowrap}
.item .x{border:0;background:none;color:var(--gray-2);cursor:pointer;font-size:17px;
line-height:1;padding:0 0 0 4px}
.item .x:hover{color:var(--vermelho)}
.rot{font-family:"Inter",sans-serif;font-size:10px;font-weight:700;letter-spacing:.12em;
text-transform:uppercase;color:var(--fraco);margin:0 0 7px}
.carr .l{display:flex;justify-content:space-between;font-size:12.5px;padding:5px 0;
color:var(--fraco);gap:14px}
.carr .l b{color:var(--tx);font-family:"Inter",sans-serif;font-weight:700;white-space:nowrap}
/* identificação do cliente */
.subC{font-size:11.5px;color:var(--fraco);line-height:1.5;margin:-4px 0 12px}
.campos{display:grid;grid-template-columns:1fr 1fr;gap:11px}
@media(max-width:620px){.campos{grid-template-columns:1fr}}
.cp.larga{grid-column:1/-1}
.cp label{display:block;font-size:10.5px;color:var(--fraco);margin-bottom:5px}
.cp input{width:100%;padding:11px 12px;border-radius:8px;border:1px solid var(--linha);
background:#fff;font-family:inherit;font-size:13.5px;color:var(--tx)}
.cp input:focus{outline:none;border-color:var(--laranja)}
.cp input.ruim{border-color:var(--vermelho);background:#FFF6F4}
.cp input.bom{border-color:var(--verde-ml)}
.cp .err{display:block;font-size:10.5px;color:var(--vermelho);margin-top:4px;min-height:13px}
/* forma de entrega, como no checkout do marketplace */
.entrega{display:grid;gap:9px}
.opE{display:flex;gap:11px;align-items:flex-start;text-align:left;width:100%;
background:#fff;border:1px solid var(--linha);border-radius:10px;padding:13px 14px;
cursor:pointer;font-family:inherit;color:var(--tx);transition:var(--transition)}
.opE:hover{border-color:var(--gray-3)}
.opE.on{border-color:var(--verde-ml);background:#F4FCF7;box-shadow:0 0 0 1px var(--verde-ml)}
.opE .mk{flex:none;width:17px;height:17px;border-radius:50%;margin-top:2px;
border:2px solid var(--gray-2);background:#fff}
.opE.on .mk{border-color:var(--verde-ml);
background:radial-gradient(circle,var(--verde-ml) 0 4px,#fff 5px)}
.opE div{flex:1;min-width:0}
.opE b{display:block;font-family:"Inter",sans-serif;font-size:13.5px;margin-bottom:2px}
.opE span{font-size:11.5px;color:var(--fraco);line-height:1.5}
.opE em{flex:none;font-style:normal;font-family:"Inter",sans-serif;font-weight:700;
font-size:13px;color:var(--verde-ml);white-space:nowrap}
.cep{display:none;margin-top:10px;padding:12px;background:var(--cinza);border-radius:9px}
.cep.on{display:block}
.cep label{display:block;font-size:10.5px;color:var(--fraco);margin-bottom:6px}
.cepL{display:flex;gap:8px}
.cepL input{flex:1;min-width:0;padding:10px 12px;border-radius:8px;background:#fff;
border:1px solid var(--linha);font-family:inherit;font-size:13px;color:var(--tx)}
.cepL input:focus{outline:none;border-color:var(--laranja)}
.cepL button{flex:none;width:auto;padding:10px 18px;border-radius:8px;
border:1px solid var(--linha);cursor:pointer;background:#fff;color:var(--tx);
font-family:"Inter",sans-serif;font-weight:600;font-size:12.5px}
.cepL button:hover{border-color:var(--laranja);color:var(--laranja)}
.cep p{font-size:11.5px;color:var(--fraco);margin-top:9px;line-height:1.5}
.avisoE:empty{display:none}
.avisoE{font-size:11.5px;color:#1F5C3A;background:#F1FBF5;border:1px solid #BFE6CE;
border-radius:8px;padding:10px 12px;line-height:1.5;margin-top:9px}
/* resumo da compra */
.resumo{position:sticky;top:104px;background:#fff;border:1px solid var(--linha);
border-radius:12px;box-shadow:var(--sombra);padding:17px 18px}
@media(max-width:900px){.resumo{position:static}}
.resumo h4{font-family:"Inter",sans-serif;font-size:16px;font-weight:700;margin-bottom:12px}
.resumo .l{padding:6px 0}
.carr .tot{display:flex;justify-content:space-between;align-items:baseline;gap:12px;
font-family:"Inter",sans-serif;font-size:15px;font-weight:700;padding-top:12px;margin-top:8px;
border-top:1px solid var(--linha);color:var(--tx)}
.carr .tot b{font-size:23px;color:var(--tx)}
.cbts{display:grid;gap:9px;margin-top:14px}
.cbts button{width:100%;padding:13px;border-radius:9px;border:0;background:var(--laranja);
color:#241703;font-family:"Inter",sans-serif;font-weight:700;font-size:14.5px;cursor:pointer;
transition:var(--transition)}
.cbts button:hover{background:var(--laranja2)}
.cbts button:disabled{opacity:.4;cursor:not-allowed}
.cbts .sec{background:#fff;color:var(--tx);border:1px solid var(--linha);font-weight:600}
.cbts .sec:hover{background:#fff;border-color:var(--laranja);color:var(--laranja)}
.carr .obs{font-size:11px;color:var(--fraco);margin-top:11px;line-height:1.5}
.carro{position:relative;margin-top:22px}
.trilho{display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x mandatory;
scroll-behavior:smooth;padding-bottom:6px;-webkit-overflow-scrolling:touch}
.trilho::-webkit-scrollbar{height:0}
.slide{flex:0 0 clamp(244px,76%,316px);scroll-snap-align:start;
background:var(--cinza);border:1px solid var(--linha);border-radius:13px;padding:17px 18px}
.slide .et2{font-family:"Inter",sans-serif;font-size:9.5px;font-weight:700;letter-spacing:.14em;
text-transform:uppercase;color:var(--laranja);margin-bottom:8px;display:block}
.slide h3{font-family:"Inter",sans-serif;font-size:16px;font-weight:700;margin-bottom:6px;line-height:1.18}
.slide p{font-size:12.5px;color:var(--fraco);line-height:1.5}
/* selo circular · o benefício em número, com os anéis da marca em volta */
.slide{position:relative;overflow:hidden}
.stop{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
.sinfo{min-width:0;flex:1}
.selo{flex:none;position:relative;width:74px;height:74px;border-radius:50%;
display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;
background:#fff;border:2px solid var(--verde-ml);margin:2px 4px 0 0}
.selo i{position:absolute;inset:-6px;border-radius:50%;border:1px solid rgba(0,166,80,.34)}
.selo::before,.selo::after{content:"";position:absolute;border-radius:50%;pointer-events:none}
.selo::before{inset:-13px;border:1px solid rgba(0,166,80,.18)}
.selo::after{inset:-21px;border:1px solid rgba(0,166,80,.09)}
.selo b{font-family:"Inter",sans-serif;font-weight:700;font-size:19px;line-height:1;
color:var(--verde-ml);letter-spacing:-.01em}
.selo b em{font-style:normal;font-size:11px;margin-left:1px}
.selo span{font-family:"Inter",sans-serif;font-weight:700;font-size:8px;line-height:1.2;
letter-spacing:.07em;text-transform:uppercase;color:var(--fraco);margin-top:4px;padding:0 5px}
.slide .num{font-family:"Inter",sans-serif;font-weight:700;font-size:26px;
color:var(--laranja);line-height:1;margin-bottom:6px}
.slide ul{list-style:none;font-size:12px;margin-top:9px}
.slide li{padding:4px 0 4px 19px;position:relative;color:var(--fraco)}
.slide li::before{content:"✓";position:absolute;left:2px;color:var(--verde);font-size:11px}
.slide li.no::before{content:"!";color:var(--laranja);font-weight:700}
.slide table{width:100%;font-size:12px;margin-top:9px;border-collapse:collapse}
.slide td{padding:5px 0;border-bottom:1px solid #E3E3E3;color:var(--fraco)}
.slide td+td{text-align:right;font-family:"Inter",sans-serif;font-weight:700;color:var(--tx)}
.slide td.cab{font-family:"Inter",sans-serif;font-size:9.5px;font-weight:700;letter-spacing:.1em;
text-transform:uppercase;color:var(--fraco)}
.slide tr:last-child td{border:0}
.slide .dest{background:#fff;border-radius:8px;padding:9px 11px;margin-top:9px;
font-size:11.5px;color:var(--tx)}
.navc{display:flex;gap:8px;align-items:center;margin-top:14px}
.navc button{width:32px;height:32px;border-radius:50%;border:1px solid var(--linha);
background:#fff;color:var(--tx);cursor:pointer;font-size:15px;line-height:1}
.navc button:hover{border-color:var(--laranja);color:var(--laranja)}
.pontos{display:flex;gap:5px;margin-left:auto}
.pt{width:20px;height:3px;border-radius:2px;background:var(--linha);border:0;padding:0;cursor:pointer}
.pt.on{background:var(--laranja)}
@media(prefers-reduced-motion:reduce){.trilho{scroll-behavior:auto}}
h2{font-size:clamp(23px,3.6vw,34px);font-weight:700;max-width:20ch}
h2 em{font-style:normal;color:var(--laranja)}
.et{font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--laranja);
font-family:"Inter",sans-serif;font-weight:700;margin-bottom:10px}
.lead{color:var(--fraco);font-size:15px;max-width:56ch;margin-top:12px}
.dor{display:grid;grid-template-columns:repeat(auto-fit,minmax(215px,1fr));gap:13px;margin-top:24px}
.dc{background:#fff;border:1px solid var(--linha);border-radius:12px;padding:18px 19px}
.dc .ic{width:30px;height:30px;border-radius:9px;display:grid;place-items:center;
font-family:"Inter",sans-serif;font-weight:700;font-size:14px;margin-bottom:11px;
background:#FBEEEA;color:var(--vermelho)}
.dc h3{font-size:15px;font-weight:700;margin-bottom:4px}
.dc p{font-size:12.5px;color:var(--fraco)}
.nota{background:var(--cinza);border-radius:11px;padding:14px 18px;margin-top:15px;font-size:13px;color:var(--fraco)}
.nota b{color:var(--tx);font-family:"Inter",sans-serif}
footer{background:#000;color:#8D9AB0;padding:26px 0 20px;font-size:12.5px}
.frow{display:flex;gap:26px;align-items:flex-start;flex-wrap:wrap;padding-bottom:18px}
.fmarca{display:flex;align-items:center;gap:9px;font-family:"Inter",sans-serif;
font-weight:700;font-size:13px;color:var(--laranja);letter-spacing:.06em;white-space:nowrap}
.fmarca img{height:22px;width:auto;display:block;filter:brightness(0) invert(1)}
footer a{color:#8D9AB0;text-decoration:none}
footer a:hover{color:var(--laranja2)}
.flinks{display:flex;gap:18px;flex-wrap:wrap;font-size:12.5px}
.fcont{margin-left:auto;text-align:right;font-size:12.5px;line-height:1.7}
@media(max-width:720px){.fcont{margin-left:0;text-align:left}}
.fbase{border-top:1px solid rgba(255,255,255,.1);padding-top:14px;display:flex;
justify-content:space-between;gap:12px;flex-wrap:wrap;font-size:11px;color:#5A6570}
@media(prefers-reduced-motion:reduce){*{transition:none!important;scroll-behavior:auto}}
</style>
</head>
<body>
<div class="tarja">
ENVIE SEU ARQUIVO DTF PELO SITE · DTF TÊXTIL COM O MENOR PREÇO DO METRO EM FRANCA-SP
</div>
<header class="topo"><div class="w">
<a class="logo" href="https://www.dropstaratacado.com.br" aria-label="Dropstar Atacado"><img src="https://cdn.vnda.com.br/dropstar/2025/12/17/logo-header-15125700-16129080.png?v=1766009286" alt="Dropstar"></a>
<div class="busca">
<span class="lupa">⌕</span>
<input type="search" placeholder="Buscar" aria-label="Buscar">
</div>
<div class="acts">
<a href="/portal.html">Minha conta</a>
<a href="#carr" id="cartLink">Carrinho (0)</a>
</div>
</div></header>
<nav class="menu"><div class="w">
<div class="mi">
<a href="https://www.dropstaratacado.com.br/camiseta" target="_blank" rel="noopener">Vestuários</a>
<div class="sub">
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blcs" target="_blank" rel="noopener">Camiseta Premium</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blos" target="_blank" rel="noopener">Camiseta Oversized</a>
<a href="https://www.dropstaratacado.com.br/moletom-canguru/modelo-blmc" target="_blank" rel="noopener">Moletom Canguru</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blmp" target="_blank" rel="noopener">Camiseta Pima</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blcp" target="_blank" rel="noopener">Camiseta Poliamida</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blcd" target="_blank" rel="noopener">Camiseta Dry Fit</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blci" target="_blank" rel="noopener">Camiseta Infantil</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blrm" target="_blank" rel="noopener">Regata Machão</a>
<a href="https://www.dropstaratacado.com.br/camiseta/modelo-blgp" target="_blank" rel="noopener">Camisa Polo</a>
<a href="https://www.dropstaratacado.com.br/modelo-blmi" target="_blank" rel="noopener">Moletom Canguru Infantil</a>
</div>
</div>
<div class="mi on">
<a href="#envio">Impressão DTF</a>
<div class="sub">
<a href="#envio">DTF Têxtil · 57 cm</a>
<a href="#envio">DTF UV · 28,5 cm</a>
</div>
</div>
<div class="mi">
<a href="https://www.dropstaratacado.com.br/modelo-emb" target="_blank" rel="noopener">Embalagens</a>
<div class="sub">
<a href="https://www.dropstaratacado.com.br/modelo-emb/modelo-sacseg" target="_blank" rel="noopener">Saco de Segurança</a>
<a href="https://www.dropstaratacado.com.br/modelo-emb/fit-adesiva" target="_blank" rel="noopener">Fita Adesiva</a>
<a href="https://www.dropstaratacado.com.br/modelo-emb/modelo-etiqtermica" target="_blank" rel="noopener">Etiqueta Térmica</a>
</div>
</div>
<div class="mi">
<a href="https://www.dropstaratacado.com.br/modelo-acessorios" target="_blank" rel="noopener">Acessórios</a>
<div class="sub">
<a href="https://www.dropstaratacado.com.br/modelo-acessorios/modelo-bon" target="_blank" rel="noopener">Boné Trucker</a>
<a href="https://www.dropstaratacado.com.br/chinelo-tradicional-unissex" target="_blank" rel="noopener">Chinelo Tradicional Unissex</a>
</div>
</div>
<div class="mi">
<a href="https://www.dropstaratacado.com.br/insumos" target="_blank" rel="noopener">Insumos</a>
<div class="sub">
<a href="https://www.dropstaratacado.com.br/filme-dtf" target="_blank" rel="noopener">Filme DTF</a>
<a href="https://www.dropstaratacado.com.br/poliamida-dtf" target="_blank" rel="noopener">Poliamida DTF</a>
<a href="https://www.dropstaratacado.com.br/tinta-dtf" target="_blank" rel="noopener">Tinta DTF</a>
<a href="https://www.dropstaratacado.com.br/fluido-limpeza" target="_blank" rel="noopener">Fluído Limpeza</a>
<a href="https://www.dropstaratacado.com.br/primer-limpeza" target="_blank" rel="noopener">Primer Limpeza</a>
</div>
</div>
<div class="mi">
<a href="https://www.dropstaratacado.com.br/produto/personalizacao-premium-sua-estampa-aqui-envie-a-estampa-16-99230-2546-59" target="_blank" rel="noopener">Personalizados</a>
</div>
</div></nav>
<header class="hero">
<div class="fundo" aria-hidden="true">
<svg viewBox="0 0 1400 300" preserveAspectRatio="xMidYMid slice">
<g fill="#fff">
<rect x="20" y="16" width="118" height="140" rx="5"/>
<rect x="150" y="16" width="118" height="140" rx="5"/>
<rect x="280" y="16" width="76" height="66" rx="5"/>
<rect x="368" y="16" width="76" height="66" rx="5"/>
<rect x="280" y="94" width="164" height="62" rx="5"/>
<rect x="456" y="16" width="96" height="96" rx="5"/>
<rect x="564" y="16" width="96" height="96" rx="5"/>
<rect x="456" y="124" width="204" height="32" rx="5"/>
<rect x="672" y="16" width="118" height="140" rx="5"/>
<rect x="802" y="16" width="112" height="66" rx="5"/>
<rect x="802" y="94" width="112" height="62" rx="5"/>
<rect x="926" y="16" width="150" height="140" rx="5"/>
<rect x="1088" y="16" width="86" height="86" rx="5"/>
<rect x="1186" y="16" width="86" height="86" rx="5"/>
<rect x="1088" y="114" width="184" height="42" rx="5"/>
<rect x="1284" y="16" width="96" height="140" rx="5"/>
<rect x="20" y="168" width="76" height="76" rx="5"/>
<rect x="108" y="168" width="76" height="76" rx="5"/>
<rect x="196" y="168" width="76" height="76" rx="5"/>
<rect x="284" y="168" width="160" height="76" rx="5"/>
<rect x="456" y="168" width="104" height="118" rx="5"/>
<rect x="572" y="168" width="104" height="118" rx="5"/>
<rect x="688" y="168" width="226" height="54" rx="5"/>
<rect x="688" y="234" width="108" height="52" rx="5"/>
<rect x="808" y="234" width="106" height="52" rx="5"/>
<rect x="926" y="168" width="150" height="118" rx="5"/>
<rect x="1088" y="168" width="184" height="56" rx="5"/>
<rect x="1088" y="236" width="86" height="50" rx="5"/>
<rect x="1186" y="236" width="86" height="50" rx="5"/>
<rect x="1284" y="168" width="96" height="118" rx="5"/>
<rect x="20" y="256" width="424" height="30" rx="5"/>
</g>
</svg>
</div>
<div class="w">
<div class="hgrid">
<div>
<div class="selos1">
<span class="sel top">Metro a partir de R$ 14,90</span>
<span class="sel novo">Revisão inclusa</span>
<span class="sel pos">Franca · SP</span>
</div>
<h1>A gente encaixa sua folha<br><em>e você paga menos metro</em></h1>
<p class="sub2">Mande a arte, veja a nota em segundos e a folha montada antes de fechar.
<b>Revisão e remanejamento por nossa conta.</b></p>
<div class="hcta">
<a href="#envio" class="btn1">Enviar minha arte</a>
<span class="mini">sem cadastro · resposta em segundos</span>
</div>
</div>
<div class="pfaixa">
<a class="pcard dest" href="#envio">
<span class="marc">mais vendido</span>
<span class="apartir">com arte boa</span>
<span class="vl">R$ 14,90<small> /m</small></span>
<span class="nm">Arquivo por metro</span>
<span class="dsc">têxtil · 57 cm</span>
</a>
<a class="pcard" href="#envio">
<span class="apartir">com arte boa</span>
<span class="vl">R$ 24,90<small> /m</small></span>
<span class="nm">Artes avulsas</span>
<span class="dsc">têxtil · montamos a folha</span>
</a>
<a class="pcard" href="#envio">
<span class="apartir">com arte boa</span>
<span class="vl">R$ 69,90<small> /m</small></span>
<span class="nm">Arquivo UV</span>
<span class="dsc">rígido · 28,5 cm</span>
</a>
<a class="pcard" href="#envio">
<span class="apartir">com arte boa</span>
<span class="vl">R$ 83,90<small> /m</small></span>
<span class="nm">Artes avulsas UV</span>
<span class="dsc">rígido · montamos a folha</span>
</a>
</div>
</div>
<div class="barrah">
<span><i>◆</i> Revisão de <b>5 pontos</b> sem custo</span>
<span><i>◆</i> Cobrança por <b>proporção</b>, a cada 10 cm</span>
<span><i>◆</i> Filme de <b>57 cm</b>, o dobro do padrão</span>
<span><i>◆</i> Prévia da folha <b>antes de pagar</b></span>
</div>
</div>
</header>
<!-- PROVA -->
<section class="prova"><div class="w">
<div class="p1"><b>Quanto melhor sua arte,<br><em>menor o preço do metro</em></b>
<span>Têxtil de R$ 19,90 a R$ 14,90 · UV de R$ 99,90 a R$ 69,90.
A nota aparece na tela antes de você pagar.</span></div>
<div class="p1"><b>Filme de 57 cm,<br><em>o dobro do padrão</em></b>
<span>Cabe o dobro de arte por metro. E reencaixamos sua folha sem cobrar.</span></div>
<div class="p1"><b>Você paga só<br><em>o que usa</em></b>
<span>Cobrança a cada 10 cm. Folha de 2,75 m custa 2,80 — não 3.</span></div>
<div class="p1"><b>Erro pego<br><em>antes de imprimir</em></b>
<span>5 pontos conferidos e a nota na tela. Sem custo.</span></div>
<div class="p1"><b>Envio em<br><em>até 24 horas</em></b>
<span>E avisamos por WhatsApp a cada etapa do pedido.</span></div>
</div></section>
<!-- ENVIO -->
<section id="envio"><div class="w">
<!-- 3 cards -->
<div id="cards">
<div class="et">Comece por aqui</div>
<h2>Qual é o seu <em>caso</em>?</h2>
<div class="escolha">
<button class="ec f" data-modo="file">
<span class="marc">Mais vendido</span>
<div class="top"><h3>Arquivo por metro</h3>
<span class="p"><i>tabela</i>R$ 19,90<small>/m</small></span></div>
<div class="chama2">Quer pagar <b>R$ 14,90</b>? Arte em 300 DPI, em arquivo que a gente confere.</div>
<div class="in">
<span class="lg">DTF têxtil · 57 cm</span>
<p>Você monta a folha e manda pronta. Revisamos, corrigimos e reencaixamos.</p>
<ul><li>Remanejamos e sua folha encolhe</li><li>Arquivo bom paga o mínimo</li>
<li>Você continua no controle do layout</li></ul>
<span class="go">Enviar minha folha ›</span>
</div>
</button>
<button class="ec a" data-modo="avulsa">
<div class="top"><h3>Artes avulsas</h3><span class="p"><i>tabela</i>R$ 29,90<small>/m</small></span></div>
<div class="chama2">Quer pagar <b>R$ 24,90</b>? Arte em 300 DPI, em arquivo que a gente confere.</div>
<div class="in">
<span class="lg">DTF têxtil · 57 cm</span>
<p>Cada arte num arquivo. Montamos a folha com o melhor encaixe.</p>
<ul><li>Sugerimos a largura que rende mais</li><li>Sobreposição não acontece</li>
<li>Montagem inclusa · R$ 10,00 no metro</li></ul>
<span class="go">Enviar minhas artes ›</span>
</div>
</button>
<button class="ec u" data-modo="uvfile">
<div class="top"><h3>Arquivo por metro · UV</h3><span class="p"><i>tabela</i>R$ 85,90<small>/m</small></span></div>
<div class="chama2">Quer pagar <b>R$ 69,90</b>? Arte em 300 DPI, em arquivo que a gente confere.</div>
<div class="in">
<span class="lg">Adesivo UV · 28,5 cm</span>
<p>Sua folha de UV montada. Revisamos, corrigimos e reencaixamos.</p>
<ul><li>Arquivo bom paga o mínimo</li><li>Remanejamos e sua folha encolhe</li>
<li>Rígido: caneca, garrafa, case</li></ul>
<span class="go">Enviar minha folha de UV ›</span>
</div>
</button>
<button class="ec u" data-modo="uv">
<div class="top"><h3>Artes avulsas · UV</h3><span class="p"><i>tabela</i>R$ 99,90<small>/m</small></span></div>
<div class="chama2">Quer pagar <b>R$ 83,90</b>? Arte em 300 DPI, em arquivo que a gente confere.</div>
<div class="in">
<span class="lg">Adesivo UV · 28,5 cm</span>
<p>Cada arte num arquivo. Montamos a folha de UV com o melhor encaixe.</p>
<ul><li>Sugerimos a largura que rende mais</li><li>Montagem inclusa</li>
<li>Não precisa de prensa</li></ul>
<span class="go">Enviar minhas artes de UV ›</span>
</div>
</button>
</div>
</div>
<!-- painel focado -->
<div class="foco" id="foco">
<button class="voltar" id="bVoltar"><span>‹</span>Trocar de produto</button>
<div class="focoGrid">
<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>
<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="caminhos" id="tipoEnvio" hidden>
<button type="button" class="cam on" data-tipo="folha">
<span class="mk2"></span>
<div><b>Já é uma folha montada</b>
<span>a imagem ocupa a largura inteira do filme de
<span id="larguraFolhaPronta">57</span> cm</span>
<em id="tipoFolhaPreco">—</em></div>
</button>
<button type="button" class="cam" data-tipo="avulsa">
<span class="mk2"></span>
<div><b>São artes separadas</b>
<span>você informa a largura de cada uma e nós montamos a folha</span>
<em id="tipoAvulsaPreco">—</em></div>
</button>
</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="zona" id="zona" tabindex="0" role="button">
<div class="ico">↑</div>
<b id="zTit">Arraste aqui</b><span id="zSub"></span>
<span class="bt">ou escolher no computador</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>
<div class="aoVivo" id="aoVivo"></div>
</div>
<aside class="mont" id="mont">
<div class="montcab"><b id="montcabTit">Montagem ao vivo</b>
<div class="zoomB"><button id="zMenos" title="Diminuir">−</button>
<span id="zTxt">100%</span>
<button id="zMais" title="Aumentar">+</button></div>
<span id="vMt">—</span></div>
<div id="vArea"></div>
<div id="vUso"></div>
<p class="montpe">Prévia aproximada. No servidor o encaixe é refeito do zero —
o resultado sai igual ou melhor, nunca pior.</p>
</aside>
</div>
</div>
</div>
</div>
<aside class="req" id="req">
<h3 class="reqcab">O que devo saber<br>sobre a arte</h3>
<details open>
<summary>Como pagar o menor metro</summary>
<ul>
<li class="s"><b>300 DPI no tamanho que vai imprimir.</b> É a nota máxima
e o menor preço da tabela</li>
<li class="s"><b>O tamanho manda mais que o arquivo.</b> A mesma arte passa
em 20 cm e não passa em 40. Reduzir um pouco costuma pular uma faixa
de preço inteira</li>
<li class="s"><b>Larguras parecidas rendem mais.</b> Se as suas artes têm
medidas próximas, cabem mais por fileira e a folha encolhe</li>
</ul>
<p class="obs2">Resolução é a única coisa que mexe no preço — porque é a
única que a gente não consegue melhorar por você.</p>
</details>
<details>
<summary>O que arrumamos sem cobrar</summary>
<ul>
<li class="s"><b>Artes coladas.</b> Separamos com 5 mm, sem você refazer nada</li>
<li class="s"><b>Encaixe mal aproveitado.</b> Remanejamos e a sua folha
encolhe — você paga menos metro do que enviou</li>
<li class="s"><b>Peça passando da largura.</b> Remanejamos em vez de cortar</li>
<li class="s"><b>Margem.</b> Não precisa deixar. Só respeitar a largura do filme</li>
</ul>
</details>
<details>
<summary>Manda em PNG e sai na hora</summary>
<ul>
<li class="s"><b>PNG ou JPG</b> com fundo transparente entram direto, sem passar
por ninguém, e não têm limite de comprimento. Só que a gente lê a resolução
<b>do arquivo</b>: se a arte foi ampliada antes de exportar, isso não aparece
para nós — e sai serrilhado no filme do mesmo jeito</li>
<li class="s"><b>PDF é o que conferimos melhor.</b> Nele medimos a resolução de
cada imagem dentro da folha, uma por uma. Se a sua arte é boa, é onde a nota
aparece com mais precisão</li>
<li class="a"><b>PDF só até 5 m.</b> O limite é do próprio formato: a página do PDF
para em 508 cm. Acima disso o arquivo sai fora do padrão e só abre certo em quem
o gerou — divida em partes ou mande PNG</li>
<li class="a"><b>TIFF, PSD, AI e CDR</b> a gente aceita, mas ninguém consegue
conferir sozinho: alguém abre na mão, sai mais devagar e o metro vai pela
tabela, sem desconto de nota</li>
<li class="a">Em <b>artes avulsas</b> só PNG e JPG: a montagem precisa
enxergar a sua arte para encaixar pela forma</li>
</ul>
</details>
<details>
<summary>O que só você resolve</summary>
<ul>
<li class="n"><b>Fundo branco ou colorido.</b> Precisa ser transparente de
verdade — branco parecendo transparente sai branco no filme</li>
<li class="n"><b>Pontinho de recorte.</b> A impressora enxerga qualquer
resíduo e imprime. Não existe pequeno demais</li>
<li class="n"><b>Cópias sobrepostas.</b> Duas iguais com 1 mm de diferença
saem duplicadas</li>
<li class="n"><b>Menos de 96 DPI.</b> Abaixo disso não imprimimos: sai
serrilhado a ponto de não valer o filme</li>
<li class="n"><b>Degradê de transparência</b>, comum em arte de IA. O branco
por baixo sai irregular e sai assim mesmo</li>
</ul>
</details>
</aside>
</div>
</div>
<!-- nota -->
<div class="qual" id="qual">
<div class="qh">
<div><h3 id="qNome">sua folha</h3>
<span style="font-size:12px;color:var(--fraco)" id="qSub"></span></div>
<div style="text-align:right"><div class="pct" id="pct">—</div>
<span style="font-size:11px;color:var(--fraco)">qualidade</span></div>
</div>
<div class="qbar"><i id="qbi" style="width:0%"></i></div>
<div class="qitens" id="qitens"></div>
<div class="ciente" id="ciente">
<h4 id="cienteTit">Ressalva de resolução</h4>
<div id="cienteTxt"></div>
<label><input type="checkbox" id="cienteOk">
<span>Li e entendi. Quero imprimir assim mesmo.</span></label>
</div>
<div class="qbts">
<button class="qb chama" id="qOk">Seguir para o pedido</button>
</div>
<div id="qmsg" style="margin-top:12px;font-size:12.5px;display:none"></div>
</div>
<!-- prévia da montagem -->
<div class="prev" id="prev">
<div class="ph">
<div><h3>Como sua folha vai sair</h3>
<p id="pSub">confira antes de fechar o pedido</p></div>
<div style="text-align:right">
<div class="mt" id="pMt">—</div>
<span style="font-size:11px;color:var(--fraco)">cobrado por proporção</span></div>
</div>
<div class="pgrid">
<div class="pfolha"><div id="pArea"></div></div>
<div class="pinfo" id="pInfo"></div>
</div>
<div class="pcta">
<button class="pb p" id="pOk">Está certo, fechar o pedido</button>
<button class="pb" id="pMais">Adicionar mais artes</button>
<button class="pb" id="pVolta">Trocar o arquivo</button>
</div>
</div>
<!-- carrinho -->
<div class="carr" id="carr">
<div>
<h3>Seu pedido</h3>
<div class="cxE">
<h4>Itens</h4>
<div id="itens"></div>
<div id="atual">
<div class="rot" style="margin-top:12px">Item que você está montando</div>
<div id="carrLin"></div>
</div>
</div>
<div class="cxE">
<h4>Seus dados</h4>
<p class="subC">A conta nasce aqui, no pagamento. O CNPJ é o mesmo da nota fiscal,
e é por ele que suas artes ficam guardadas para o próximo pedido.</p>
<div class="campos">
<div class="cp"><label>CNPJ</label>
<input id="fCnpj" inputmode="numeric" maxlength="18" placeholder="00.000.000/0000-00">
<span class="err" id="eCnpj"></span></div>
<div class="cp"><label>WhatsApp</label>
<input id="fZap" inputmode="numeric" maxlength="15" placeholder="(16) 99999-9999">
<span class="err" id="eZap"></span></div>
<div class="cp larga"><label>E-mail para a nota</label>
<input id="fMail" type="email" placeholder="voce@empresa.com.br">
<span class="err" id="eMail"></span></div>
</div>
</div>
<div class="cxE">
<h4>Forma de entrega</h4>
<div class="entrega" id="entrega">
<button class="opE on" data-ent="retira">
<span class="mk"></span>
<div>
<b>Retirar em Franca</b>
<span>Av. Major Elias Motta, 1281 · Jardim São Luís<br>
Segunda a sexta, das 7h às 17h</span>
</div>
<em>Grátis</em>
</button>
<button class="opE" data-ent="frete">
<span class="mk"></span>
<div>
<b>Receber em casa</b>
<span>Cotamos com a transportadora pelo seu CEP</span>
</div>
<em id="vFrete">—</em>
</button>
<div class="cep" id="cep">
<label>CEP de entrega</label>
<div class="cepL">
<input id="cepIn" inputmode="numeric" maxlength="9" placeholder="00000-000">
<button id="bCep">Calcular</button>
</div>
<p id="cepMsg"></p>
</div>
<p class="avisoE" id="avisoE"></p>
</div>
</div>
</div>
<aside class="resumo">
<h4>Resumo da compra</h4>
<div id="resLin"></div>
<div class="tot"><span>Total</span><b id="carrTot">R$ 0,00</b></div>
<div class="cbts">
<button id="bPagar">Ir para o pagamento</button>
<button id="bMais" class="sec">Adicionar outro produto</button>
</div>
<p class="obs">A nota fiscal sai no CNPJ informado. O arquivo fica guardado por 90 dias e o histórico
do pedido por 12 meses, para você repetir sem subir de novo.</p>
</aside>
</div>
<!-- Fora de #carr de propósito: o pedido pago esvazia o carrinho, e a
confirmação não pode sumir junto com o painel. -->
<div class="checkout">
<p id="checkoutStatus" class="avisoE" role="status" aria-live="polite"></p>
<div id="checkoutActions"></div>
</div>
</div></section>
<!-- INFORMAÇÃO EM CARROSSEL -->
<section id="info"><div class="w">
<div class="et">Como funciona</div>
<h2>Tudo o que você precisa saber, <em>em cinco cartões</em></h2>
<div class="carro">
<div class="trilho" id="trilho">
<div class="slide">
<div class="stop">
<div class="sinfo"><span class="et2">Preço</span><h3>Arte melhor,<br>metro mais barato</h3></div>
<div class="selo"><i></i><b>−25<em>%</em></b><span>no metro</span></div>
</div>
<p>Vale nos quatro produtos. Preço da folha que você monta:</p>
<table>
<tr><td class="cab">nota</td><td class="cab">têxtil</td><td class="cab">UV</td></tr>
<tr><td>90–100</td><td>R$ 14,90</td><td>R$ 69,90</td></tr>
<tr><td>75–89</td><td>R$ 16,20</td><td>R$ 73,90</td></tr>
<tr><td>60–74</td><td>R$ 17,50</td><td>R$ 77,90</td></tr>
<tr><td>40–59</td><td>R$ 18,70</td><td>R$ 81,90</td></tr>
<tr><td>abaixo de 40</td><td>R$ 19,90</td><td>R$ 85,90</td></tr>
</table>
<div class="dest">Se a montagem for nossa, some <b>R$ 10,00</b> no têxtil e
<b>R$ 14,00</b> no UV — mesma escada, em qualquer faixa</div>
</div>
<div class="slide">
<div class="stop">
<div class="sinfo"><span class="et2">Largura</span><h3>O dobro do<br>filme padrão</h3></div>
<div class="selo"><i></i><b>57<em>cm</em></b><span>de filme</span></div>
</div>
<p>A maioria trabalha com 28,5 cm. Na nossa largura cabe o dobro de arte
por metro corrido — e o metro sai mais barato por peça.</p>
<ul>
<li>Duas artes de 28 cm lado a lado</li>
<li>Menos emenda, menos corte</li>
<li>DTF UV também disponível, em 28,5 cm</li>
</ul>
</div>
<div class="slide">
<div class="stop">
<div class="sinfo"><span class="et2">Encaixe</span><h3>Reencaixamos<br>sua folha de graça</h3></div>
<div class="selo"><i></i><b>grátis</b><span>reencaixe</span></div>
</div>
<p>Se a sua montagem deixa espaço sobrando, refazemos o encaixe com o mesmo
motor que usamos nas nossas montagens.</p>
<ul>
<li>Artes que se tocam, separadas com 5 mm</li>
<li>Giramos as peças para aproveitar a largura</li>
<li>A folha encolhe e a sua conta diminui</li>
</ul>
<div class="dest">Refazemos o encaixe <b>antes de imprimir</b>, sem cobrar nada</div>
</div>
<div class="slide">
<div class="stop">
<div class="sinfo"><span class="et2">Cobrança</span><h3>Você paga<br>o que usa</h3></div>
<div class="selo"><i></i><b>10<em>cm</em></b><span>de cada vez</span></div>
</div>
<p>Arredondamos a cada 10 cm. Uma folha de 2,75 m custa 2,80 m — quem cobra
metro cheio cobraria 3.</p>
<ul>
<li>Pedido mínimo de 1 metro</li>
<li>A identificação do pedido é por nossa conta</li>
<li>Sem taxa de arquivo, sem taxa de revisão</li>
</ul>
</div>
<div class="slide">
<div class="stop">
<div class="sinfo"><span class="et2">Sem burocracia</span><h3>Conta só na<br>hora de pagar</h3></div>
<div class="selo"><i></i><b>0</b><span>conta e senha</span></div>
</div>
<p>Sobe a arte e vê o preço sem conta nenhuma. A conta nasce no pagamento, junto com o CNPJ da nota — e o próximo pedido já vem com suas artes anteriores. A revisão de 5 pontos vem junto —
há quem cobre R$ 19,90 por pedido só por isso.</p>
<ul>
<li>Sem cadastro para subir e ver o preço</li>
<li>Envio em até 24 horas</li>
<li>WhatsApp a cada etapa do pedido</li>
<li>Arquivo guardado 90 dias · histórico 12 meses</li>
</ul>
</div>
</div>
<div class="navc">
<button id="cAnt" aria-label="Anterior">‹</button>
<button id="cPro" aria-label="Próximo">›</button>
<div class="pontos" id="cPontos"></div>
</div>
</div>
</div></section>
<footer><div class="w">
<div class="frow">
<span class="fmarca"><img src="https://cdn.vnda.com.br/dropstar/2025/12/17/logo-header-15125700-16129080.png?v=1766009286" alt="Dropstar">DTF</span>
<div class="flinks">
<a href="https://www.dropstaratacado.com.br" target="_blank" rel="noopener">Loja de atacado</a>
<a href="https://www.dropstaratacado.com.br/p/termos" target="_blank" rel="noopener">Trocas e privacidade</a>
<a href="https://www.dropstaratacado.com.br/p/atendimento" target="_blank" rel="noopener">Fale conosco</a>
<a href="https://www.instagram.com/dropstar.ofc/" target="_blank" rel="noopener">Instagram</a>
</div>
<div class="fcont">
<a href="https://wa.me/message/NJ76CLIOUGXLB1" target="_blank" rel="noopener">16 99428-0056</a>
· arquivos 16 99230-2546<br>
<a href="mailto:atendimentodropstar@gmail.com">atendimentodropstar@gmail.com</a><br>
Segunda a sexta, 07h às 17h
</div>
</div>
<div class="fbase">
<span>© Dropstar Atacado · CNPJ 55.786.635/0001-58 · Franca-SP</span>
<span>Visa · Master · Elo · Amex · Pix · Boleto</span>
<span>têxtil 57 cm · UV 28,5 cm</span>
</div>
</div></footer>
<script>
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)));
// ── escolha
document.querySelectorAll('.ec').forEach(b=>b.addEventListener('click',()=>abrir(b.dataset.modo)));
function abrir(m){
modo=m; artes=[]; folhas=[]; metros=0; nota=0; reencaixado=false;
montagemCm=0;
avisoTipo('');
pintaModo();
$('lista').innerHTML=''; $('rA').style.display='none'; recusa([]);
recemChegada=null; caminho='auto'; avisoCam(''); pintaCaminhos();
$('vMt').textContent='—'; previaAoVivo(); agendaAvaliacao();
limpaPaineis(); // o carrinho só some se o pedido estiver vazio
$('foco').scrollIntoView({behavior:'smooth',block:'start'});
}
function pintaModo(){
const m=modo;
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;
$('inp').multiple=c.multi;
$('inp').accept = ehFolha(m) ? '.png,.jpg,.jpeg,.pdf'
: '.png,.jpg,.jpeg,.webp';
$('catFoco').className='cat '+(ehFolha(m)?'file':'av');
$('larguraFolhaPronta').textContent=String(c.larg).replace('.',',');
$('foco2').classList.remove('sofila'); // a caixa de requisitos vale nos 4 modos
$('montcabTit').textContent = ehFolha(m) ? 'Sua folha' : 'Montagem ao vivo';
pintaTipoEnvio();
}
// O par folha montada / artes separadas fica visível e com preço nos dois lados,
// nos dois modos: nenhuma troca acontece sem o cliente ver o que muda, e sempre
// dá para voltar atrás sem recomeçar o pedido.
function pintaTipoEnvio(){
const el=$('tipoEnvio'); if(!el) return;
el.hidden=!modo;
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';
$('larguraFolhaPronta').textContent=String(MODOS[folha].larg).replace('.',',');
const atual = ehFolha() ? 'folha' : 'avulsa';
el.querySelectorAll('.cam').forEach(b=>{
b.classList.toggle('on', b.dataset.tipo===atual);
b.disabled=folhas.length>0 || artes.length>0; // já tem arquivo: a escolha está feita
});
}
function avisoTipo(txt){
const el=$('avisoTipo'); if(!el) return;
el.innerHTML=txt; el.style.display = txt? '' : 'none';
}
// A imagem foi declarada folha montada mas não tem resolução para isso. Devolve o
// arquivo com o motivo e o caminho certo — sem trocar preço por conta própria.
function recusaFolha(x, md){
const arquivo=x.f, destino = modo==='uvfile' ? 'uv' : 'avulsa';
folhas=folhas.filter(y=>y!==x);
pintaFolha();
avisoTipo('<b>'+escapeHTML(arquivo.name)+' não é uma folha montada.</b> '+
'Para ocupar os '+n1(larguraFilme())+' cm do filme ela teria só '+md.dpiFolha+
' DPI, e o mínimo para imprimir é '+DPI_RECUSA+'. '+
'Se for uma arte para montarmos, use <b>'+MODOS[destino].tit+'</b>, '+
'a partir de '+rs(pisoEscada(destino))+'/m. '+
'<button type="button" id="usarAvulsa">Enviar como '+MODOS[destino].tit.toLowerCase()+'</button>');
const botao=$('usarAvulsa');
if(botao) botao.addEventListener('click',()=>{
avisoTipo(''); trocaTipo(PAR[modo]); sel([arquivo]);
});
}
document.querySelectorAll('#tipoEnvio .cam').forEach(b=>b.addEventListener('click',()=>{
if(b.disabled) return;
const querFolha=b.dataset.tipo==='folha';
if(querFolha===ehFolha()) return;
avisoTipo(''); trocaTipo(PAR[modo]); // escolha explícita do cliente
}));
// Troca declarada, nunca silenciosa: o cliente já viu os dois preços no seletor.
function trocaTipo(destino){
if(!destino || modo===destino) return;
folhas=[]; artes=[]; recemChegada=null; metros=0; nota=0; montagemCm=0;
modo=destino;
$('lista').innerHTML=''; $('rA').style.display='none'; recusa([]);
caminho='auto'; avisoCam('');
pintaModo(); pintaCaminhos(); limpaPaineis(); agendaAvaliacao(); previaAoVivo();
}
$('bVoltar').addEventListener('click',()=>{
itemAtual=null; $('atual').style.display='none'; $('carrLin').innerHTML=''; pintaPedido();
$('foco').classList.remove('on'); $('cards').style.display='';
['qual','prev'].forEach(id=>$(id).classList.remove('on'));
if(!pedido.length) $('carr').classList.remove('on');
document.getElementById('envio').scrollIntoView({behavior:'smooth'});
});
// ── upload
// Untrusted filenames and recovered cart text must never become HTML.
function escapeHTML(value){
return String(value).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
function recusa(lista){
const el=$('recusa'); if(!el) return;
if(!lista.length){ el.style.display='none'; el.innerHTML=''; return; }
const exts=escapeHTML([...new Set(lista.map(f=>extDe(f.name)))].join(', '));
el.style.display='block';
el.innerHTML = ehFolha()
? '<b>'+exts+' não dá para enviar aqui.</b> A folha pronta aceita PNG, JPG, TIFF, '+
'PDF, PSD, AI e CDR.'
: '<b>'+exts+' não dá para enviar aqui.</b> Artes avulsas precisam de PNG ou JPG com '+
'fundo transparente — é o que a montagem enxerga para encaixar. '+
'Se o seu arquivo é CDR, AI ou PSD, exporte em PNG ou use <b>Arquivo por metro</b>.';
}
const Z=$('zona'), I=$('inp');
Z.addEventListener('click',()=>I.click());
Z.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){I.click();e.preventDefault();}});
['dragover','dragenter'].forEach(ev=>Z.addEventListener(ev,e=>{e.preventDefault();Z.classList.add('sobre')}));
['dragleave','drop'].forEach(ev=>Z.addEventListener(ev,e=>{
e.preventDefault(); Z.classList.remove('sobre');
if(ev==='drop') sel([...e.dataTransfer.files]);
}));
I.addEventListener('change',()=>sel([...I.files]));
function sel(fs){
if(!fs.length) return;
// Pixels cannot tell a loose artwork from a finished sheet, so the customer
// declares it by choosing the product in the selector above. The declaration is
// verified in medirFolha; the file never changes product or price on its own.
const regra = ehFolha() ? ACEITA.folha : ACEITA.avulsa;
const fora = fs.filter(f=>!regra.test(f.name));
fs = fs.filter(f=>regra.test(f.name));
recusa(fora);
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();
novas.forEach(x=>{
x.pct=5; pintaFolha();
medirFolha(x.f).then(md=>{
if(md && md.dpiFolha!=null && md.dpiFolha<DPI_RECUSA) return recusaFolha(x, md);
x.med=md; if(md) x.m=md.alt/100; x.pct=55; pintaFolha();
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'; }
catch(e){} }
x.pct=null; pintaFolha();
});
}else if(md && /\.pdf$/i.test(x.f.name)){
x.pct=70; pintaFolha();
rasterizarPdf(x.f, md.larg, md.alt).then(r=>{
if(r && r.erro){
x.semAnalise = r.erro + (r.semWorker
? ' · este navegador não deixa o leitor rodar em segundo plano'
: '');
}
if(r && r.tela){
try{
x.an=analisarFolha(r.tela, md.larg);
// vetor puro não tem resolução · nota máxima, e está certo
x.an.dpi = r.dpi==null ? DPI_AVISA : r.dpi;
x.an.vetor = r.dpi==null;
x.an.fonteDpi = r.dpi==null ? 'vetor' : 'imagens';
x.an.res = r.res || null;
}catch(e){}
}
x.pct=null; pintaFolha();
});
}else{ x.pct=null; pintaFolha(); }
});
});
return;
}
const novos=fs.map(f=>({f,px:px(f),cm:0,q:1,prop:1,giro:0,esp:false}));
artes=novos.concat(artes); // a mais recente fica no topo da fila
recemChegada=novos[0];
pintaArtes();
$('lista').scrollIntoView({behavior:'smooth',block:'nearest'});
Promise.all(novos.map(medir)).then(pintaArtes);
}
function medir(a){
return carregarImagem(a.f).then(img=>{
if(img){ a.prop=img.height/img.width; a.px=img.width; a.src=img.src; } else a.prop=a.prop||1;
return a;
});
}
// A metragem sai do arquivo, nunca do peso em bytes.
// imagem → proporção da imagem aplicada à largura do filme
// PDF → MediaBox da primeira página, em pontos (1 pt = 1/72 pol)
// TIFF, PSD, AI, CDR → o navegador não abre; o cliente informa o comprimento
// ── Análise real da folha: lê o canal alfa, separa cada ilha de arte e mede.
// Grade de 1 mm. Só roda em arquivo que o navegador abre; PDF precisa do servidor.
const MM=0.1; // grade fina: 1 mm
const CELULAS_MAX=1200000; // teto de custo da análise
// Folha grande vira grade mais grossa. Sem isso, 57 x 480 cm dá 2,7 milhões de
// células e o navegador congela antes de terminar.
function passoDe(larguraCm, alturaCm){
const fino=(larguraCm/MM)*(alturaCm/MM);
return fino<=CELULAS_MAX ? MM : MM*Math.sqrt(fino/CELULAS_MAX);
}
// Dilata a máscara em r células · usada para agrupar por distância real, não por caixa
function inchar(bits,cw,ch,r){
const a=new Uint8Array(cw*ch), b=new Uint8Array(cw*ch);
for(let y=0;y<ch;y++){ const o=y*cw;
for(let x=0;x<cw;x++){ if(!bits[o+x]) continue;
const x0=Math.max(0,x-r), x1=Math.min(cw-1,x+r);
for(let k=x0;k<=x1;k++) a[o+k]=1; } }
for(let x=0;x<cw;x++)
for(let y=0;y<ch;y++){ if(!a[y*cw+x]) continue;
const y0=Math.max(0,y-r), y1=Math.min(ch-1,y+r);
for(let k=y0;k<=y1;k++) b[k*cw+x]=1; }
return b;
}
// Componentes conexos de 8 vizinhos · devolve o mapa de rótulos e quantos grupos
function grupos(bits,cw,ch){
const marca=new Int32Array(cw*ch).fill(-1), fila=new Int32Array(cw*ch);
let n=0;
for(let p0=0;p0<bits.length;p0++){
if(!bits[p0]||marca[p0]>=0) continue;
const id=n++; let ini=0,fim=0; fila[fim++]=p0; marca[p0]=id;
while(ini<fim){
const p=fila[ini++], x=p%cw, y=(p/cw)|0;
for(let dy=-1;dy<=1;dy++) for(let dx=-1;dx<=1;dx++){
const nx=x+dx, ny=y+dy;
if(nx<0||ny<0||nx>=cw||ny>=ch) continue;
const q=ny*cw+nx;
if(bits[q]&&marca[q]<0){ marca[q]=id; fila[fim++]=q; }
}
}
}
return {marca,n};
}
// ── O que a folha permite medir, e só isso.
// Peça = pixels a menos de 2 mm entre si · letras de uma palavra ficam juntas.
// Encostadas = peças que só se unem quando a folga chega a 6 mm.
const R_PECA=1, R_PERTO=2; // peça: 2 mm · aviso de encostadas: 4 mm
function analisarFolha(img, larguraCm){
const alturaCmEst=larguraCm*img.height/img.width;
const passo=passoDe(larguraCm, alturaCmEst);
const cw=Math.max(8,Math.round(larguraCm/passo));
const ch=Math.max(8,Math.round(cw*img.height/img.width));
const cv=document.createElement('canvas'); cv.width=cw; cv.height=ch;
const ctx=cv.getContext('2d',{willReadFrequently:true});
ctx.clearRect(0,0,cw,ch); ctx.drawImage(img,0,0,cw,ch);
const d=ctx.getImageData(0,0,cw,ch).data;
const bits=new Uint8Array(cw*ch);
let opacos=0;
for(let i=0,j=3;i<bits.length;i++,j+=4) if(d[j]>10){ bits[i]=1; opacos++; }
// peças: agrupa pela distância real entre pixels
const gp=grupos(inchar(bits,cw,ch,R_PECA), cw, ch);
const caixas=[];
for(let p=0;p<bits.length;p++){
if(!bits[p]) continue;
const id=gp.marca[p]; if(id<0) continue;
const x=p%cw, y=(p/cw)|0;
const c=caixas[id] || (caixas[id]={x0:x,x1:x,y0:y,y1:y,area:0,id});
if(x<c.x0)c.x0=x; if(x>c.x1)c.x1=x; if(y<c.y0)c.y0=y; if(y>c.y1)c.y1=y;
c.area++;
}
const pecasTodas=caixas.filter(Boolean);
const limRes=Math.max(1, Math.round(0.2/passo)); // 2 mm na grade em uso
const residuos=pecasTodas.filter(o=>(o.x1-o.x0)<limRes && (o.y1-o.y0)<limRes);
const artes=pecasTodas.filter(o=>!((o.x1-o.x0)<limRes && (o.y1-o.y0)<limRes));
// encostadas: peças diferentes que se unem quando a folga chega a 6 mm
const gper=grupos(inchar(bits,cw,ch,R_PERTO), cw, ch);
const porPerto=new Map();
for(let p=0;p<bits.length;p++){
if(!bits[p]) continue;
const a=gp.marca[p], b=gper.marca[p];
if(a<0||b<0) continue;
if(!porPerto.has(b)) porPerto.set(b,new Set());
porPerto.get(b).add(a);
}
let encostadas=0;
porPerto.forEach(set=>{ if(set.size>1) encostadas+=set.size; });
return {
dpi: Math.round(img.width/(larguraCm/2.54)),
artes: artes.length,
residuos: residuos.length,
encostadas,
fundoChapado: opacos/(cw*ch) > 0.985,
aproveitamento: Math.max(1,Math.min(100,Math.round(opacos/(cw*ch)*100))),
alturaCm: ch*passo,
grade: +(passo*10).toFixed(1) // mm por célula analisada
};
}
// ── PDF também é conferido: rasteriza a página e mede o DPI das imagens de dentro.
// Arte vetorial não tem resolução — nesses casos a nota é máxima, e isso é correto.
const PDFJS_URL='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js';
const PDFJS_WORKER='https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
let pdfLibP=null, temWorker=null;
// Alguns navegadores e iframes bloqueiam Worker de blob. Descobrimos antes de
// tentar, porque sem worker o PDF é lido na thread principal e precisa de outra
// estratégia: grade mais grossa e mais tempo.
function testaWorker(url){
try{ const w=new Worker(url); w.terminate(); return true; }catch(e){ return false; }
}
function carregarPdfJs(){
if(typeof window==='undefined') return Promise.resolve(null);
if(window.pdfjsLib) return Promise.resolve(window.pdfjsLib);
if(pdfLibP) return pdfLibP;
pdfLibP=new Promise(res=>{
const sc=document.createElement('script');
sc.src=PDFJS_URL;
sc.onload=()=>{
try{
const codigo='importScripts("'+PDFJS_WORKER+'");';
const url=URL.createObjectURL(new Blob([codigo],{type:'application/javascript'}));
temWorker=testaWorker(url);
window.pdfjsLib.GlobalWorkerOptions.workerSrc = temWorker? url : PDFJS_WORKER;
}catch(e){
temWorker=false;
try{ window.pdfjsLib.GlobalWorkerOptions.workerSrc=PDFJS_WORKER; }catch(e2){}
}
res(window.pdfjsLib||null);
};
sc.onerror=()=>res(null);
document.head.appendChild(sc);
});
return pdfLibP;
}
// DPI real de cada imagem embutida: acompanha a matriz de transformação e compara
// o tamanho em que a imagem foi colocada com quantos pixels ela tem.
function dpiDasImagens(page, ops){
const mul=(a,b)=>[a[0]*b[0]+a[2]*b[1], a[1]*b[0]+a[3]*b[1],
a[0]*b[2]+a[2]*b[3], a[1]*b[2]+a[3]*b[3],
a[0]*b[4]+a[2]*b[5]+a[4], a[1]*b[4]+a[3]*b[5]+a[5]];
const O=window.pdfjsLib.OPS;
let ctm=[1,0,0,1,0,0]; const pilha=[]; const dpis=[];
for(let i=0;i<ops.fnArray.length;i++){
const fn=ops.fnArray[i], ar=ops.argsArray[i];
if(fn===O.save) pilha.push(ctm.slice());
else if(fn===O.restore) ctm=pilha.pop()||[1,0,0,1,0,0];
else if(fn===O.transform) ctm=mul(ctm,ar);
else if(fn===O.paintImageXObject||fn===O.paintJpegXObject||fn===O.paintImageXObjectRepeat){
let im=null;
try{ im=page.objs.get(ar[0]); }catch(e){}
if(!im||!im.width) continue;
const larguraPt=Math.hypot(ctm[0],ctm[1]);
if(larguraPt<1) continue;
const areaPt=Math.abs(ctm[0]*ctm[3]-ctm[1]*ctm[2]);
dpis.push({dpi: im.width/(larguraPt/72), area: areaPt});
}
}
return dpis;
}
// A régua de 96 DPI vale por arte, não pela folha inteira. Numa folha de 250 peças,
// um selo pequeno em 65 DPI não pode barrar o pedido: o que decide é quanto da
// área impressa está abaixo do limite.
function resumoDpi(lista){
if(!lista.length) return null;
const total=lista.reduce((t,x)=>t+x.area,0) || 1;
const abaixoPiso=lista.filter(x=>x.dpi<DPI_RECUSA);
const abaixoAviso=lista.filter(x=>x.dpi<DPI_AVISA);
const pond=lista.reduce((t,x)=>t+x.dpi*x.area,0)/total;
return {
pior: Math.round(Math.min(...lista.map(x=>x.dpi))),
ponderado: Math.round(pond),
fracBaixa: abaixoPiso.reduce((t,x)=>t+x.area,0)/total,
fracAviso: abaixoAviso.reduce((t,x)=>t+x.area,0)/total,
quantasBaixas: abaixoPiso.length,
quantasAviso: abaixoAviso.length,
total: lista.length
};
}
// Rasteriza a primeira página a 1 mm por pixel e devolve a tela para a análise
function rasterizarPdf(file, larguraCm, alturaCm){
let motivo=null;
const trabalho=carregarPdfJs().then(lib=>{
if(!lib){ motivo='não foi possível carregar o leitor de PDF'; return null; }
// sem worker tudo roda na thread principal · grade mais grossa para caber
const folga = temWorker===false ? 2.2 : 1;
return file.arrayBuffer().then(buf=>lib.getDocument({
data:buf, disableFontFace:true, isEvalSupported:false, useSystemFonts:false,
verbosity:0 // sem worker é fallback, não erro
}).promise).then(doc=>
doc.getPage(1).then(page=>{
const vp1=page.getViewport({scale:1});
const passo=passoDe(larguraCm, alturaCm||larguraCm)*folga;
const alvo=Math.max(8, Math.round(larguraCm/passo));
const esc=alvo/vp1.width;
const vp=page.getViewport({scale:esc});
const cv=document.createElement('canvas');
cv.width=Math.max(8,Math.round(vp.width)); cv.height=Math.max(8,Math.round(vp.height));
const ctx=cv.getContext('2d',{willReadFrequently:true});
return page.render({canvasContext:ctx, viewport:vp, background:'rgba(0,0,0,0)'})
.promise.then(()=>page.getOperatorList()).then(ops=>{
let dpis=[];
try{ dpis=dpiDasImagens(page,ops); }catch(e){}
const r=resumoDpi(dpis);
return {tela:cv, dpi: r? r.ponderado : null, res:r};
});
})
);
}).catch(e=>{ motivo=(e&&e.message)? e.message : 'erro ao abrir o PDF'; return null; });
// sem worker o processo é lento: damos mais tempo antes de desistir
const espera = temWorker===false ? 90000 : 30000;
const tarde=new Promise(r=>setTimeout(()=>{ motivo='demorou demais neste navegador'; r('tempo'); }, espera));
return Promise.race([trabalho, tarde]).then(r=>{
const saida = r==='tempo'? null : r;
if(!saida) return {erro: motivo||'não deu para conferir', semWorker: temWorker===false};
return saida;
});
}
function medirFolha(file){
return new Promise(res=>{
const nome=(file.name||'').toLowerCase();
if(RENDERIZA.test(nome)){
carregarImagem(file).then(img=>{
if(!img || !img.width) return res(null);
const L=larguraFilme();
// Uma imagem só é folha montada se tiver pixel para ocupar a largura do
// filme. Sem esta conferência, qualquer arte pequena vira "folha" e leva
// o preço por metro sem nunca ter sido montada.
res({larg:L, alt:+(img.height/img.width*L).toFixed(1), fonte:'proporção da imagem',
dpiFolha:Math.round(img.width/(L/2.54)), px:img.width});
});
return;
}
if(/\.pdf$/.test(nome)){
const pedaco=(inicio,fim)=>new Promise(r=>{
const fr=new FileReader();
fr.onload=()=>r(new TextDecoder('latin1').decode(new Uint8Array(fr.result)));
fr.onerror=()=>r('');
fr.readAsArrayBuffer(file.slice(inicio,fim));
});
const M=4*1048576;
Promise.all([pedaco(0,M), pedaco(Math.max(0,file.size-M), file.size)]).then(([a,b])=>{
const txt=a+b;
const re=/\/MediaBox\s*\[\s*(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)\s+(-?[\d.]+)/;
const m=txt.match(re);
if(!m) return res(null);
// UserUnit escala a página inteira · sem ele, 1 unidade = 1/72 pol
const uu=txt.match(/\/UserUnit\s+([\d.]+)/);
const esc=uu? Math.max(1,Math.min(75000,+uu[1])) : 1;
const ptW=Math.abs(+m[3]-+m[1]), ptH=Math.abs(+m[4]-+m[2]);
if(!(ptW>0&&ptH>0)) return res(null);
const w=ptW*PT_CM*esc, h=ptH*PT_CM*esc;
// acima de 14.400 unidades sem UserUnit o arquivo está fora da especificação
const foraDoPadrao = !uu && (ptW>PDF_MAX_PT || ptH>PDF_MAX_PT);
res({larg:+w.toFixed(1), alt:+h.toFixed(1),
fonte:'página do PDF'+(uu? ' · UserUnit '+esc : ''), foraDoPadrao});
});
return;
}
res(null);
});
}
function pintaFolha(){
const L=larguraFilme();
pintaTipoEnvio(); // trava o seletor enquanto houver folha
if(!folhas.length){ $('lista').innerHTML=''; agendaAvaliacao(); previaAoVivo(); return; }
$('lista').innerHTML = folhas.map((x,i)=>{
const lido=!!x.med, larga=lido && x.med.larg>L+0.5, suspeito=lido && x.med.foraDoPadrao;
const sub=(x.m||0)*(x.rep||1);
let medida='';
if(lido){
const a=x.an;
const conferido = 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> '+
'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)+
' 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>';
}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>';
}
const barra = x.pct!=null
? '<div class="prog"><i style="width:'+x.pct+'%"></i></div>'+
'<span class="progT">'+(x.pct<50?'lendo o arquivo…':
x.pct<70?'medindo a folha…':'conferindo a arte…')+'</span>'
: x.semAnalise
? '<div class="progF"><b>Não conseguimos conferir esta folha.</b> '+x.semAnalise+
'. 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"><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>' : '')+
'</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-repf]').forEach(el=>el.addEventListener('change',e=>{
folhas[+el.dataset.repf].rep=Math.max(1,Math.min(200,+e.target.value||1)); pintaFolha();
}));
$('lista').querySelectorAll('[data-comp]').forEach(el=>el.addEventListener('change',e=>{
folhas[+el.dataset.comp].m=Math.max(0,Math.min(60,+e.target.value||0)); pintaFolha();
}));
agendaAvaliacao(); previaAoVivo();
}
// sugestão de largura
const cabemNaLinha=w=>Math.max(1,Math.floor((larguraFilme()+GAP_CM)/(w+GAP_CM)));
const larguraPara=n=>(larguraFilme()-(n-1)*GAP_CM)/n;
// Só sugere se a folha realmente encolher. Caber mais um por fileira não adianta
// nada se a quantidade pedida continuar ocupando o mesmo número de fileiras.
function sugestao(a){
const w=a.cm, q=Math.max(1,a.q||1), prop=propDe(a);
const n=cabemNaLinha(w), alvo=Math.floor(larguraPara(n+1)*10)/10;
if(!(alvo>0) || alvo>=w) return null;
if((w-alvo)/w>0.12) return null; // não corta mais que 12% da peça
const fa=Math.ceil(q/n), fd=Math.ceil(q/(n+1));
if(fd>=fa) return null; // mesmo número de fileiras: não muda nada
const economiaCm=fa*w*prop - fd*alvo*prop;
if(economiaCm<0.5) return null; // menos de meio centímetro não é notícia
return {alvo, de:n, para:n+1, fileiras:fa-fd, cm:economiaCm};
}
// A cota em escala: o cliente ve a peca dentro da largura do filme, quantas cabem
// lado a lado e quanto sobra. Numero em texto ninguem le; retangulo todo mundo entende.
function vizPeca(a){
const BASE=300, L=larguraFilme();
const e0=BASE/L, alt0=a.cm*propDe(a)*e0;
const k = alt0>132 ? 132/alt0 : 1; // tira nunca fica alta demais
const e = e0*k;
const n=cabemNaLinha(a.cm), pw=a.cm*e, ph=a.cm*propDe(a)*e, gp=GAP_CM*e;
const sobra=L-(n*a.cm+(n-1)*GAP_CM);
const ext=escapeHTML(a.f.name.split('.').pop().toUpperCase());
let pecas='';
for(let i=0;i<n;i++) pecas+='<div class="pc" style="width:'+pw.toFixed(1)+'px;height:'+
ph.toFixed(1)+'px">'+(a.src?'<img src="'+a.src+'" alt="" style="transform:'+
(a.giro?'rotate(90deg) ':'')+(a.esp?'scaleX(-1)':'')+'">':'<span>'+ext+'</span>')+'</div>';
const Wt=(L*e+6).toFixed(1); // +6 = borda e respiro da tira
return '<div class="viz">'+
'<div class="vizRow">'+
'<div class="vizH" style="height:'+ph.toFixed(1)+'px"><span>'+n1(a.cm*propDe(a))+' cm</span></div>'+
'<div class="vizTira" style="width:'+Wt+'px">'+
'<div class="vizL"><i></i><b>'+n1(L)+' cm de filme</b><i></i></div>'+
'<div class="tira" style="height:'+ph.toFixed(1)+'px;gap:'+gp.toFixed(1)+'px">'+pecas+
(sobra>0.25?'<div class="folga"><span>sobra '+n1(sobra)+' cm</span></div>':'')+
'</div>'+
'</div>'+
'</div>'+
'<div class="vizPe"><b>'+n1(a.cm)+' cm</b> de largura · <b>'+n+
(n>1?' peças':' peça')+'</b> por fileira'+
(sobra>0.25?' · sobram <b>'+n1(sobra)+' cm</b>':' · largura toda aproveitada')+'</div>'+
'</div>';
}
const n1=v=>v.toFixed(1).replace('.',',');
function pintaArtes(){
$('lista').innerHTML=artes.map((a,i)=>{
let barra='', dica='', calc='<span>informe a largura para ver a qualidade</span>';
if(a.cm>0){
const dpi=Math.round(a.px/(a.cm/2.54)), n=Math.max(6,Math.min(100,Math.round(dpi/300*100)));
barra='<div class="qb2"><i class="'+cls(n)+'" style="width:'+n+'%"></i></div>';
calc='<span class="'+cls(n)+'"><b>'+n+'%</b> · '+dpi+' DPI</span><span>'+
a.q+' × '+n1(a.cm)+' × '+n1(a.cm*propDe(a))+' cm</span>';
const sug=sugestao(a);
dica = sug
? '<div class="dica"><b>Reduzindo para '+n1(sug.alvo)+' cm você economiza '+
n1(sug.cm)+' cm de folha</b>Passam de '+sug.de+' para '+sug.para+
' por fileira, e as suas '+a.q+' peças ocupam '+sug.fileiras+
' fileira'+(sug.fileiras>1?'s':'')+' a menos.<button data-usar="'+i+'" data-cm="'+
sug.alvo+'">Usar sugerido</button></div>'
: '';
dica = vizPeca(a) + dica;
}
return '<div class="art'+(a===recemChegada?' nova':'')+'"><div class="l1"><b>'+escapeHTML(a.f.name)+'</b>'+
'<span style="color:var(--fraco);font-size:10.5px">'+a.px+' px</span>'+
'<button data-rm="'+i+'">×</button></div><div class="cps">'+
'<div><label>largura na peça</label><input type="number" min="1" max="'+larguraFilme()+
'" placeholder="cm" data-cm="'+i+'" value="'+(a.cm||'')+'"></div>'+
'<div><label>quantas vezes</label><input type="number" min="1" placeholder="1" data-q="'+i+
'" value="'+a.q+'"></div></div>'+
'<div class="atalhos">'+
(TAMANHOS[larguraFilme()]||[]).map(t=>'<button class="tm'+
(Math.abs(a.cm-t)<0.05?' on':'')+'" data-tam="'+i+'" data-t="'+t+'">'+
n1(t)+' cm</button>').join('')+
'<span class="sep"></span>'+
'<button class="gr'+(a.giro?' on':'')+'" data-giro="'+i+'" title="Girar 90°">⟳</button>'+
'<button class="gr'+(a.esp?' on':'')+'" data-esp="'+i+'" title="Espelhar">⇋</button>'+
'</div>'+barra+'<div class="calc">'+calc+'</div>'+dica+'</div>';
}).join('');
$('lista').querySelectorAll('[data-rm]').forEach(b=>b.addEventListener('click',()=>{artes.splice(+b.dataset.rm,1);pintaArtes();}));
$('lista').querySelectorAll('[data-cm]').forEach(i=>i.addEventListener('change',()=>{artes[+i.dataset.cm].cm=+i.value||0;pintaArtes();}));
$('lista').querySelectorAll('[data-q]').forEach(i=>i.addEventListener('change',()=>{artes[+i.dataset.q].q=Math.max(1,Math.floor(+i.value||1));pintaArtes();}));
$('lista').querySelectorAll('[data-cm], [data-q]').forEach(i=>i.addEventListener('input',()=>{
const largura=i.hasAttribute('data-cm');
const a=artes[+(largura?i.dataset.cm:i.dataset.q)];
a[largura?'cm':'q']=largura?Math.max(0,+i.value||0):Math.max(1,Math.floor(+i.value||1));
previaAoVivo();
}));
$('lista').querySelectorAll('[data-usar]').forEach(b=>b.addEventListener('click',()=>{
artes[+b.dataset.usar].cm=+b.dataset.cm; pintaArtes();
}));
$('lista').querySelectorAll('[data-tam]').forEach(b=>b.addEventListener('click',()=>{
artes[+b.dataset.tam].cm=+b.dataset.t; pintaArtes();
}));
$('lista').querySelectorAll('[data-giro]').forEach(b=>b.addEventListener('click',()=>{
const a=artes[+b.dataset.giro]; a.giro=a.giro?0:90; pintaArtes();
}));
$('lista').querySelectorAll('[data-esp]').forEach(b=>b.addEventListener('click',()=>{
const a=artes[+b.dataset.esp]; a.esp=!a.esp; pintaArtes();
}));
resumoArtes();
previaAoVivo();
}
function resumoArtes(){
const r=$('rA'), prontas=artes.filter(a=>a.cm>0);
if(!prontas.length){ r.style.display='none'; return; }
const m=montagemCm/100; // a mesma folha que aparece ao lado
const notas=prontas.map(a=>Math.max(6,Math.min(100,Math.round((a.px/(a.cm/2.54))/300*100))));
const media=Math.round(notas.reduce((s,n)=>s+n,0)/notas.length), pior=Math.min(...notas);
const falta=artes.length-prontas.length;
r.style.display='block'; r.className='resumoA on';
r.innerHTML='<div class="r"><span>artes com tamanho</span><b>'+prontas.length+' de '+artes.length+'</b></div>'+
'<div class="r"><span>qualidade média</span><b style="color:'+
(media>=85?'var(--verde)':media>=50?'var(--laranja)':'var(--vermelho)')+'">'+media+'%</b></div>'+
(pior<media?'<div class="r"><span>a pior arte</span><b>'+pior+'%</b></div>':'')+
(m>0?'<div class="r"><span>folha montada</span><b>'+fmtM(m)+' m</b></div>'+
(cobrar(m)>m+0.001?'<div class="r"><span>cobrança</span><b>'+fmtM(cobrar(m))+
' m'+(cobrar(m)===MINIMO_M?' · mínimo':'')+'</b></div>':''):'')+
(falta?'<div class="r" style="color:var(--laranja)"><span>faltam informar</span><b>'+falta+'</b></div>':'');
}
// ── enviar
// Avalia sozinho conforme o cliente sobe e preenche. Devolve o que ainda falta,
// para a fila dizer em texto em vez de travar num botão.
function avaliar(){
if(ehFolha()){
if(!folhas.length) return {pronto:false, falta:'Arraste sua folha montada para começar.'};
const larga=folhas.find(x=>x.med && x.med.larg > larguraFilme()+0.5);
if(larga) return {pronto:false, falta:'A folha '+larga.f.name+' tem '+n1(larga.med.larg)+
' cm e não cabe no filme de '+n1(larguraFilme())+' cm.'};
const semMedida=folhas.filter(x=>!(x.m>0)).length;
if(semMedida) return {pronto:false, falta:'Informe o comprimento de '+semMedida+
' folha'+(semMedida>1?'s':'')+'.'};
metros=folhaTotalM();
reencaixado=false;
const ans=folhas.map(x=>x.an).filter(Boolean);
if(!ans.length){
// sem análise possível (PDF, CDR, PSD): não inventa nota
const exts=[...new Set(folhas.map(f=>extDe(f.f.name)))].join(', ');
mostraNota([
['av','Sem nota · '+exts+' não abre no navegador',
'não temos como conferir a arte antes de imprimir'],
['er','Metro pela tabela · '+rs(TABELA[modo]),
'exportando em PNG ou PDF, cai até '+rs(pisoEscada())],
['fix','Alguém abre seu arquivo','a conferência é feita na mão, na sala']
], 0, folhas.length>1? folhas.length+' folhas' : folhas[0].f.name,
larguraFilme()+' cm · '+fmtM(metros)+' m · preço de tabela');
return {pronto:true};
}
const dpi=Math.min(...ans.map(a=>a.dpi));
const n=Math.max(6,Math.min(100,Math.round(dpi/DPI_AVISA*100)));
const soma=(k)=>ans.reduce((t,a)=>t+a[k],0);
const itens=[
ans.every(a=>a.vetor)
? ['ok','Arte vetorial','sem imagem embutida · imprime em qualquer tamanho']
: [dpi>=DPI_AVISA?'ok':dpi>=DPI_RECUSA?'av':'er','Resolução '+dpi+' DPI',
ans.some(a=>a.fonteDpi==='imagens')
? 'média por área das imagens dentro do PDF · pior em '+
Math.min(...ans.map(a=>a.res? a.res.pior : a.dpi))+' DPI'
: 'resolução do arquivo · arte ampliada antes de exportar não aparece aqui'],
['ok', soma('artes')+' peças na folha',
Math.round(soma('aproveitamento')/ans.length)+'% da folha virou arte'],
soma('residuos') ? ['er','Resíduo de recorte',
soma('residuos')+' ponto'+(soma('residuos')>1?'s':'')+' abaixo de 2 mm · a impressora imprime']
: ['ok','Sem resíduo de recorte','nada solto na folha'],
soma('encostadas') ? ['fix', soma('encostadas')+' peças a menos de 5 mm',
'se forem artes diferentes, separamos com 5 mm sem custo']
: ['ok','Espaço entre peças','nenhuma abaixo de 6 mm'],
ans.some(a=>a.fundoChapado) ? ['er','Fundo chapado','a folha está sem transparência']
: ['ok','Fundo transparente','canal alfa conferido'],
null
].filter(Boolean);
mostraNota(itens, n, folhas.length>1? folhas.length+' folhas' : folhas[0].f.name,
larguraFilme()+' cm · '+fmtM(metros)+' m');
return {pronto:true};
}
if(!artes.length) return {pronto:false, falta:'Arraste suas artes para começar.'};
const sem=artes.filter(a=>!a.cm).length;
if(sem) return {pronto:false, falta:'Informe a largura de '+sem+' arte'+(sem>1?'s':'')+
' para ver a nota e o preço.'};
// metragem = altura da folha depois de montada, com os 5 mm entre peças
metros=+(montagemCm/100).toFixed(4);
if(!(metros>0)) return {pronto:false, falta:'Montando a folha…'};
const notas=artes.map(a=>Math.max(6,Math.min(100,Math.round((a.px/(a.cm/2.54))/300*100))));
const media=Math.round(notas.reduce((s,n)=>s+n,0)/notas.length), pior=Math.min(...notas);
mostraNota([
['ok','Montagem por nossa conta','5 mm garantidos'],
[cls(pior),'Pior arte em '+pior+'%',pior>=85?'todas em boa resolução':'reenviar maior sai mais barato']
],media,artes.length+' arte'+(artes.length>1?'s':''),
'folha montada por nós · '+fmtM(metros)+' m');
return {pronto:true};
}
// redesenha 220 ms depois da última mexida, para não avaliar a cada tecla
let tAval=null;
function agendaAvaliacao(){
clearTimeout(tAval);
tAval=setTimeout(()=>{
const r=avaliar();
const el=$('aoVivo');
if(!el) return;
if(r.pronto){
el.className='aoVivo pronto';
el.innerHTML='<span class="pt2"></span><span><b>Nota e preço calculados.</b> '+
'Confira abaixo e feche o pedido.</span>';
}else{
el.className='aoVivo';
el.innerHTML='<span class="pt2"></span><span>'+escapeHTML(r.falta)+'</span>';
limpaPaineis(); // o carrinho só some se o pedido estiver vazio
}
},220);
}
function mostraNota(itens,n,titulo,sub){
nota=n;
$('qual').classList.add('on');
$('qNome').textContent=titulo; $('qSub').textContent=sub;
const semNota = n===0;
$('pct').innerHTML = semNota? '—<small>/100</small>' : n+'<small>/100</small>';
const c = semNota? 'var(--fraco)' : n>=85?'var(--verde)':n>=50?'var(--laranja)':'var(--vermelho)';
$('pct').style.color=c;
$('qbi').style.width=(semNota?0:n)+'%'; $('qbi').style.background=c;
$('qitens').innerHTML=itens.map(i=>
'<div class="qi '+i[0]+'"><span class="c">'+
(i[0]==='ok'?'✓':i[0]==='av'?'!':i[0]==='fix'?'↻':'×')+'</span>'+
'<div><b>'+escapeHTML(i[1])+'</b><span>'+escapeHTML(i[2])+'</span></div></div>').join('');
$('qmsg').style.display='none';
ressalvaResolucao();
carrinho();
}
// A resolução do arquivo não é a das artes dentro dele: um PDF em 300 DPI
// pode ter uma imagem inserida em 180, e é essa que sai serrilhada.
function ressalvaResolucao(){
const box=$('ciente'), chk=$('cienteOk');
let baixas=[], geral=300, recusadas=[];
if(ehFolha()){
// Uma régua só: o número que precifica é o número que bloqueia.
// Recusa quando a média por área fica abaixo de 96 DPI — a folha inteira não
// vale o filme. Se a média passa mas há peças ruins, é ressalva com aceite.
folhas.forEach((x,k)=>{
if(!x.an) return;
const r=x.an.res, cm=x.med? x.med.larg : larguraFilme();
const nome=(folhas.length>1? (k+1)+'. ':'')+x.f.name;
const extra = r ? {pior:r.pior, de:r.total,
quantas:r.quantasBaixas, parte:Math.round(r.fracBaixa*100),
quantasAviso:r.quantasAviso, parteAviso:Math.round(r.fracAviso*100)} : {};
if(x.an.dpi<DPI_RECUSA) recusadas.push(Object.assign({nome, dpi:x.an.dpi, cm}, extra));
else if(x.an.dpi<DPI_AVISA || (r&&r.quantasAviso))
baixas.push(Object.assign({nome, dpi:x.an.dpi, cm}, extra));
});
geral = folhas.some(x=>x.an) ? Math.max(...folhas.filter(x=>x.an).map(x=>x.an.dpi)) : 300;
}else{
const comTamanho=artes.filter(a=>a.cm>0);
recusadas=comTamanho.filter(a=>dpiDe(a)<DPI_RECUSA)
.map(a=>({nome:a.f.name, dpi:Math.round(dpiDe(a)), cm:a.cm}));
baixas=comTamanho.filter(a=>dpiDe(a)>=DPI_RECUSA && dpiDe(a)<DPI_AVISA)
.map(a=>({nome:a.f.name, dpi:Math.round(dpiDe(a)), cm:a.cm}));
const todas=comTamanho.map(a=>dpiDe(a));
geral = todas.length? Math.round(Math.max(...todas)) : 300;
}
// Abaixo de 96 DPI a sala não imprime. Não é desconto, é recusa —
// e não existe aceite que destrave.
if(recusadas.length){
$('cienteTxt').innerHTML=
'<p><b>A média da folha ficou abaixo de '+DPI_RECUSA+' DPI.</b> Nesse ponto a peça '+
'sai serrilhada a ponto de não valer o filme. Reenvie maior ou reduza a largura.</p>'+
'<div class="lista">'+recusadas.map(b=>'<div><b>'+escapeHTML(b.nome)+'</b> — '+
(b.quantas
? 'média de '+b.dpi+' DPI · pior imagem em '+b.pior+' DPI · '+b.quantas+
' de '+b.de+' imagens abaixo de '+DPI_RECUSA+' DPI, cobrindo '+b.parte+'% da arte'
: b.dpi+' DPI em '+n1(b.cm)+' cm · precisa de '+
Math.ceil(DPI_AVISA*b.cm/2.54)+' px de largura')+'</div>')
.join('')+'</div>';
$('cienteTit').textContent='Resolução abaixo do que imprimimos';
box.className='ciente on recusa'; chk.checked=false; $('qOk').disabled=true;
return;
}
$('cienteTit').textContent='Ressalva de resolução';
box.className='ciente';
if(!baixas.length){
box.classList.remove('on'); chk.checked=false; $('qOk').disabled=false; return;
}
const menor=baixas.reduce((p,b)=>b.dpi<p.dpi?b:p);
const media=Math.round(baixas.reduce((s,b)=>s+b.dpi,0)/baixas.length);
$('cienteTxt').innerHTML=
'<p><b>Parte da arte está abaixo de '+DPI_AVISA+' DPI.</b> Essas peças podem sair '+
'com traço serrilhado, e isso não tem conserto depois de impresso. '+
'O resto da folha sai normal.</p>'+
'<div class="lista">'+baixas.map(b=>'<div><b>'+escapeHTML(b.nome)+'</b> — '+
(b.quantasAviso
? 'média de '+b.dpi+' DPI · '+b.quantasAviso+' de '+b.de+' imagens abaixo de '+
DPI_AVISA+' DPI, cobrindo '+b.parteAviso+'% da arte'+
(b.quantas? ' · dessas, '+b.quantas+' abaixo de '+DPI_RECUSA+' DPI ('+
b.parte+'% da arte)' : '')
: b.dpi+' DPI'+(b.cm? ' em '+n1(b.cm)+' cm':''))+
'</div>').join('')+'</div>';
box.className='ciente on'; chk.checked=false; $('qOk').disabled=true;
}
$('cienteOk').addEventListener('change',e=>{
if($('ciente').classList.contains('recusa')) return; // recusa não se aceita
$('qOk').disabled=!e.target.checked;
});
// ── imagens e prévia
function carregarImagem(file){
return new Promise(res=>{
if(!RENDERIZA.test(file.name)) return res(null);
const fr=new FileReader();
fr.onload=()=>{ const im=new Image(); im.onload=()=>res(im); im.onerror=()=>res(null); im.src=fr.result; };
fr.onerror=()=>res(null);
fr.readAsDataURL(file);
});
}
// A folha precisa parecer uma folha: borda, largura marcada e régua de metros.
// Sem isso o cliente não tem noção de escala e acha que está mal encaixado.
function molduraFolha(ctx,W,H,larguraCm,metrosReais){
const REG=34; // faixa da régua, à direita
ctx.save();
// fundo da régua
ctx.fillStyle='#FAFAF8'; ctx.fillRect(W,0,REG,H);
ctx.strokeStyle='#D5DAE3'; ctx.lineWidth=1;
ctx.beginPath(); ctx.moveTo(W+.5,0); ctx.lineTo(W+.5,H); ctx.stroke();
// marcas a cada 10 cm, número a cada metro
const pxPorM = H/Math.max(metrosReais,0.01);
ctx.font='9px "Roboto", sans-serif'; ctx.textAlign='left';
for(let cm=0; cm<=metrosReais*100; cm+=10){
const y=cm/100*pxPorM;
if(y>H) break;
const metro = cm%100===0;
ctx.strokeStyle = metro? '#B4BAC5' : '#E1E5EC';
ctx.beginPath(); ctx.moveTo(W+1,y+.5); ctx.lineTo(W+(metro?12:6),y+.5); ctx.stroke();
if(metro && cm>0){
ctx.fillStyle='#8A8F98';
ctx.fillText((cm/100)+' m', W+14, y+3);
}
}
// borda da folha
ctx.strokeStyle='#B4BAC5'; ctx.lineWidth=1;
ctx.strokeRect(.5,.5,W-1,H-1);
// cota da largura, no topo
ctx.fillStyle='#FFFFFFDD'; ctx.fillRect(0,0,W,18);
ctx.strokeStyle='#8A8F98'; ctx.lineWidth=1;
ctx.beginPath();
ctx.moveTo(8,9); ctx.lineTo(W-8,9);
ctx.moveTo(8,5); ctx.lineTo(8,13);
ctx.moveTo(W-8,5); ctx.lineTo(W-8,13);
ctx.stroke();
const t=String(larguraCm).replace('.',',')+' cm';
ctx.font='600 10px "Roboto", sans-serif'; ctx.textAlign='center';
const tw=ctx.measureText(t).width;
ctx.fillStyle='#FAFAF8'; ctx.fillRect(W/2-tw/2-6,2,tw+12,14);
ctx.fillStyle='#5A6570'; ctx.fillText(t,W/2,13);
ctx.restore();
}
const LOGO_URL='https://cdn.vnda.com.br/dropstar/2025/12/17/logo-header-15125700-16129080.png?v=1766009286';
const logoImg=new Image(); logoImg.crossOrigin='anonymous'; logoImg.src=LOGO_URL;
function marcaDagua(ctx,w,h){
ctx.save(); ctx.globalAlpha=.14;
const passo=Math.max(90,w/2.1);
ctx.translate(w/2,h/2); ctx.rotate(-Math.PI/7); ctx.translate(-w/2,-h/2);
if(logoImg.complete && logoImg.naturalWidth){
const lw=Math.max(70,w/3.4), lh=lw*(logoImg.naturalHeight/logoImg.naturalWidth);
for(let y=-h;y<h*2;y+=passo) for(let x=-w;x<w*2;x+=passo*1.7)
ctx.drawImage(logoImg,x,y,lw,lh);
}else{
ctx.fillStyle='#111';
ctx.font='700 '+Math.max(13,Math.round(w/13))+'px Inter, sans-serif'; ctx.textAlign='center';
for(let y=-h;y<h*2;y+=passo) for(let x=-w;x<w*2;x+=passo*1.5) ctx.fillText('PRÉVIA',x,y);
}
ctx.restore();
}
const CEL=GAP_CM; // célula da máscara, em cm = a folga de 5 mm
// Dilata 1 célula para a direita e para baixo. Como toda peça é dilatada igual,
// duas vizinhas ficam separadas por exatamente 1 célula — os 5 mm da regra.
function dilatar(bruto,cw,ch,cheias){
const cw2=cw+1, ch2=ch+1;
const out=new Uint8Array(cw2*ch2);
for(let y=0;y<ch;y++) for(let x=0;x<cw;x++){
if(!bruto[y*cw+x]) continue;
out[y*cw2+x]=1; out[y*cw2+x+1]=1; out[(y+1)*cw2+x]=1; out[(y+1)*cw2+x+1]=1;
}
return {cw:cw2,ch:ch2,bits:out,cheias}; // cheias = área de arte, sem a folga
}
function mascara(img,w,h,rot,esp=false){
const cw=Math.max(1,Math.ceil(w/CEL)), ch=Math.max(1,Math.ceil(h/CEL));
const cv=document.createElement('canvas');
cv.width=cw; cv.height=ch;
const ctx=cv.getContext('2d',{willReadFrequently:true});
let bruto, cheias;
if(!img){ // sem imagem: caixa cheia
bruto=new Uint8Array(cw*ch).fill(1); cheias=cw*ch;
return dilatar(bruto,cw,ch,cheias);
}
ctx.save();
ctx.translate(cw/2,ch/2);
ctx.rotate(rot*Math.PI/180);
if(esp) ctx.scale(-1,1);
const dw=rot%180===90?ch:cw, dh=rot%180===90?cw:ch;
ctx.drawImage(img,-dw/2,-dh/2,dw,dh);
ctx.restore();
const d=ctx.getImageData(0,0,cw,ch).data;
bruto=new Uint8Array(cw*ch); cheias=0;
for(let i=0,j=3;i<bruto.length;i++,j+=4){ bruto[i] = d[j]>16 ? 1 : 0; cheias+=bruto[i]; }
return dilatar(bruto,cw,ch,cheias);
}
// Encaixe pela forma, como o motor do servidor: mapa de ocupação real,
// máscara por arte e candidatos numa grade ao longo da largura.
// pecas em centímetros: {w,h,img}. W é a largura do filme em cm.
function encaixar(pecas,W){
const cols=Math.ceil(W/CEL)+1; // +1 = a coluna de folga da dilatação
let linhas=0; const mapa=[];
function garantir(ate){ while(linhas<ate){ mapa.push(new Uint8Array(cols)); linhas++; } }
function colide(m,cx,cy){
if(cx<0||cy<0||cx+m.cw>cols) return true;
for(let y=0;y<m.ch;y++){
const gy=cy+y;
if(gy>=linhas) continue; // vazio abaixo do que já existe
const L=mapa[gy], off=y*m.cw;
for(let x=0;x<m.cw;x++)
if(m.bits[off+x] && L[cx+x]) return true;
}
return false;
}
function ocupar(m,cx,cy){
garantir(cy+m.ch);
for(let y=0;y<m.ch;y++){
const L=mapa[cy+y], off=y*m.cw;
for(let x=0;x<m.cw;x++) if(m.bits[off+x]) L[cx+x]=1;
}
}
const postas=[];
const ordem=[...pecas].sort((a,b)=>(b.w*b.h)-(a.w*a.h));
ordem.forEach(p=>{
const oris=[{w:p.w,h:p.h,rot:0}];
if(!p.mPronta && !p.travado && Math.abs(p.w-p.h)>1 && p.h<=W) oris.push({w:p.h,h:p.w,rot:90});
let melhor=null;
oris.forEach(o=>{
if(o.w>W) return;
// a máscara segue a medida da orientação escolhida · girada, largura e altura trocam
const m = (p.mPronta && o.rot===0) ? p.mPronta : mascara(p.img, o.w, o.h,
o.rot+(p.a?.giro||0), p.a?.esp);
const xs=new Set([0]), ys=new Set([0]);
postas.forEach(q=>{ xs.add(q.cx+q.m.cw); xs.add(q.cx); ys.add(q.cy+q.m.ch); ys.add(q.cy); });
const passoX=Math.max(1,Math.round(cols/34));
for(let x=0;x+m.cw<=cols;x+=passoX) xs.add(x);
xs.add(cols-m.cw);
const passoY=Math.max(1,Math.round(Math.max(linhas,1)/50));
for(let y=0;y<=linhas;y+=passoY) ys.add(y);
const XS=[...xs].filter(x=>x>=0 && x+m.cw<=cols && x*CEL+o.w<=W+0.01).sort((a,b)=>a-b);
const YS=[...ys].filter(y=>y>=0).sort((a,b)=>a-b);
for(const cy of YS){
if(melhor && cy*CEL > melhor.y + CEL) break;
for(const cx of XS){
if(cx*CEL + o.w > W + 0.01) continue; // não deixa a peça sair do filme
if(colide(m,cx,cy)) continue;
const cand={cx,cy,m,x:cx*CEL,y:cy*CEL,w:o.w,h:o.h,rot:o.rot,ref:p};
// menor y, depois menor x · em empate, mantém a orientação original.
// Desempatar por largura gira tudo, e texto deitado na prévia parece erro.
const ganha = !melhor || cand.y<melhor.y ||
(cand.y===melhor.y && cand.x<melhor.x) ||
(cand.y===melhor.y && cand.x===melhor.x && cand.rot===0 && melhor.rot!==0);
if(ganha) melhor=cand;
break;
}
}
});
if(!melhor){
const k=Math.min(1, W/p.w); // cabe na largura, nem que seja reduzindo
const w=p.w*k, h=p.h*k;
const m=mascara(p.img,w,h,p.a?.giro||0,p.a?.esp);
melhor={cx:0,cy:linhas,m,x:0,y:linhas*CEL,w,h,rot:0,ref:p};
}
// a folga só existe entre peças, não é folha cobrada
ocupar(melhor.m,melhor.cx,melhor.cy);
postas.push(melhor);
});
const altura=Math.max(...postas.map(p=>p.y+p.h),1);
return {pos:postas, altura};
}
function desenhaMontagem(area,lista,mts,W,fora,aoTerminar){
const versao=area.montagemVersao=(area.montagemVersao||0)+1;
const filme=larguraFilme(), PX_CM=W/filme;
const itens=lista.map(a=>({...a}));
if(fora) fora.innerHTML='';
if(!itens.length){
area.innerHTML='<div class="semmont"><b>A montagem aparece aqui</b>'+
'<span>informe a largura de cada arte e a folha se desenha sozinha</span></div>';
if(aoTerminar) aoTerminar({alturaCm:0, uso:0, girou:0});
return;
}
Promise.all(itens.map(a=>carregarImagem(a.f).then(img=>({a,img})))).then(imagens=>{
if(area.montagemVersao!==versao) return;
const cargas=imagens.flatMap(c=>Array.from({length:c.a.q||1},()=>({...c})));
cargas.forEach(c=>{
c.w = c.a.cm; // o motor trabalha em centímetros
c.h = c.w * (c.img ? (c.a.giro? c.img.width/c.img.height : c.img.height/c.img.width) : 1);
c.travado = !!c.a.giro; // giro do cliente manda no encaixe
});
const enc=encaixar(cargas,filme);
const alturaCm=enc.altura;
const pos=enc.pos.map(p=>({...p, x:p.x*PX_CM, y:p.y*PX_CM, w:p.w*PX_CM, h:p.h*PX_CM}));
const altura=alturaCm*PX_CM;
const usado=Math.max(altura,10);
const REG=34;
const cv=document.createElement('canvas');
cv.width=W+REG; cv.height=Math.round(usado);
const ctx=cv.getContext('2d');
ctx.fillStyle='#fff'; ctx.fillRect(0,0,cv.width,cv.height);
const pal=['#C3CEDD','#B7A9C8','#A9B7C8','#A8BFA9','#C8C0A0','#CBB6B6'];
let girou=0;
pos.forEach((p,j)=>{
const c=p.ref;
if(p.rot===90) girou++;
ctx.save();
const giro = (p.rot===90?90:0) + (c.a && c.a.giro? 90:0);
ctx.translate(p.x+p.w/2, p.y+p.h/2);
ctx.rotate(giro*Math.PI/180);
if(c.a && c.a.esp) ctx.scale(-1,1);
const dw = giro%180===90 ? p.h : p.w, dh = giro%180===90 ? p.w : p.h;
if(c.img) ctx.drawImage(c.img,-dw/2,-dh/2,dw,dh);
else{ ctx.fillStyle=pal[j%pal.length]; ctx.fillRect(-dw/2,-dh/2,dw,dh); }
ctx.restore();
if(!c.img){
ctx.fillStyle='#5A6570'; ctx.font='11px "Roboto", sans-serif'; ctx.textAlign='center';
ctx.fillText(c.a.f.name.split('.').pop().toUpperCase(),p.x+p.w/2,p.y+p.h/2+4);
}
});
molduraFolha(ctx,W,usado,filme,alturaCm/100);
marcaDagua(ctx,W,cv.height);
area.innerHTML=''; area.appendChild(cv);
// O que virou arte é a área pintada, não a soma das caixas: com encaixe por forma
// as caixas se sobrepõem de propósito e a soma delas passa de 100%.
const areaArteCm=enc.pos.reduce((s,p)=>
s + (p.m && p.m.cheias!=null ? p.m.cheias*CEL*CEL : p.w*p.h), 0);
const uso=Math.max(1,Math.min(100,
Math.round(areaArteCm/(filme*Math.max(alturaCm,0.1))*100)));
const el=document.createElement('div');
el.className = uso>=70? 'usoBom' : 'usoRuim';
el.innerHTML = (uso>=70
? '<b>'+uso+'% da folha virou arte</b> bom aproveitamento'
: '<b>'+uso+'% da folha virou arte</b> dá para melhorar: artes de larguras parecidas '+
'se encaixam melhor lado a lado')
+ (girou? ' · <b style="display:inline">'+girou+
(girou>1?' artes giradas':' arte girada')+' 90°</b> para caber melhor':'');
(fora||area).appendChild(el);
if(aoTerminar) aoTerminar({alturaCm, uso, girou});
});
}
function previaArtes(){ desenhaMontagem($('pArea'), artes, metros, 560); }
// montagem ao vivo, ao lado da fila · redesenha 140 ms depois da ultima mexida
let tMont=null;
function previaAoVivo(){
clearTimeout(tMont);
$('vArea').montagemVersao=($('vArea').montagemVersao||0)+1;
if(ehFolha()){
$('vUso').innerHTML='';
const tot=folhaTotalM();
$('vMt').textContent = tot>0 ? fmtM(cobrar(tot))+' m' : '—';
const medidas=folhas.filter(x=>x.med);
const previews=folhas.filter(x=>x.previewSrc);
if(previews.length){
const painel=document.createElement('div'); painel.className='folhaPrevias';
previews.forEach((x,index)=>{
const figura=document.createElement('figure'); figura.className='folhaPrevia';
const imagem=new Image(); imagem.src=x.previewSrc; imagem.alt=x.f.name;
const legenda=document.createElement('figcaption');
legenda.textContent=(previews.length>1 ? (index+1)+'. ' : '')+x.f.name+
(x.med ? ' · '+n1(x.med.larg)+' × '+n1(x.med.alt)+' cm' : '');
figura.append(imagem,legenda); painel.append(figura);
});
$('vArea').replaceChildren(painel);
}else{
$('vArea').innerHTML = medidas.length
? '<div class="semmont"><b>'+(medidas.length>1
? medidas.length+' folhas · '+fmtM(tot)+' m'
: n1(medidas[0].med.larg)+' × '+n1(medidas[0].med.alt)+' cm')+'</b>'+
'<span>'+(medidas.length>1? 'medidas lidas de cada arquivo' : medidas[0].med.fonte)+
' · vão para a máquina do jeito que você montou</span></div>'
: '<div class="semmont"><b>Sua folha aparece aqui</b>'+
'<span>a medida é lida do próprio arquivo, não do peso dele</span></div>';
}
return;
}
tMont=setTimeout(()=>{
const prontas=artes.filter(a=>a.cm>0);
const W=Math.round(300*ZOOMS[zoomI]);
$('vArea').classList.toggle('ampliado', zoomI>0);
desenhaMontagem($('vArea'), prontas, 0, W, $('vUso'), r=>{
montagemCm = r.alturaCm; // altura real da folha montada
$('vMt').textContent = r.alturaCm? fmtM(cobrar(r.alturaCm/100))+' m' : '—';
resumoArtes(); // o resumo lê a mesma folha
agendaAvaliacao(); // a nota só sai com a metragem pronta
});
$('vArea').scrollTop=0;
},140);
}
function motivoNota(){
if(!nota) return 'sem nota ainda · a conferência roda na sala antes de imprimir';
if(!ehFolha()){
const ruins=artes.filter(a=>a.cm>0 && (a.px/(a.cm/2.54))<300);
if(!ruins.length) return 'nota '+nota+' · todas as artes em 300 DPI ou mais';
const pior=ruins.reduce((p,a)=>(a.px/(a.cm/2.54))<(p.px/(p.cm/2.54))?a:p);
return 'nota '+nota+' · '+ruins.length+(ruins.length>1?' artes abaixo':' arte abaixo')+
' de 300 DPI · a menor é "'+pior.f.name+'" com '+
Math.round(pior.px/(pior.cm/2.54))+' DPI em '+pior.cm.toFixed(1).replace('.',',')+' cm';
}
return nota>=90 ? 'nota '+nota+' · resolução ótima em toda a folha'
: 'nota '+nota+' · a resolução é o único ponto — o resto corrigimos por você';
}
function previa(){
// Na folha pronta o painel não tem o que mostrar: a folha vem fechada, ninguém
// olhou dentro dela. Largura e metragem — o que de fato medimos — já aparecem
// na coluna ao lado e no carrinho.
if(ehFolha()){ $('prev').classList.remove('on'); return; }
const cob=cobrar(metros), sobra=+(cob-metros).toFixed(2);
$('pMt').innerHTML=fmtM(cob)+'<small> m</small>';
$('pSub').textContent='folha montada por nós com as artes que você enviou';
const linhas=[
['var(--verde)','✓','Largura',larguraFilme()+' cm · dentro do padrão da máquina'],
['var(--verde)','✓','Espaço entre artes','5 mm garantidos'],
[nota>=75?'var(--verde)':'var(--laranja)', nota>=75?'✓':'!','Qualidade da folha',motivoNota()],
['var(--verde)','✓','Metragem proporcional',
'sua folha tem '+fmtM(metros)+' m e você paga '+fmtM(cob)+
' m · arredondamos só até os 10 cm mais próximos'],
];
linhas.push(['var(--verde)','+','Identificação do pedido',
'aplicamos seu nome e o número do pedido no fim da folha · por nossa conta']);
$('pInfo').innerHTML=linhas.map(l=>
'<div class="li"><span class="c" style="background:'+l[0]+
(l[0]==='var(--laranja)'?';color:#241703':'')+'">'+l[1]+'</span>'+
'<div><b>'+escapeHTML(l[2])+'</b><span>'+escapeHTML(l[3])+'</span></div></div>').join('');
$('prev').classList.add('on');
$('pArea').innerHTML='<div class="carregando">montando a prévia…</div>';
try{ previaArtes(); }
catch(e){ $('pArea').innerHTML='<div class="semprev"><b>Prévia indisponível</b>'+
'O arquivo será processado normalmente.</div>'; }
}
// ── carrinho
let itemAtual=null;
function limpaPaineis(){
['qual','prev'].forEach(id=>$(id).classList.remove('on'));
if(!pedido.length){ $('carr').classList.remove('on'); }
}
// O carrinho junta itens de produtos diferentes e cobra uma vez só.
function pintaPedido(){
$('itens').innerHTML = pedido.map((it,i)=>
'<div class="item"><div class="nm2"><b>'+escapeHTML(it.tit)+'</b><span>'+escapeHTML(it.desc)+'</span></div>'+
'<div class="vl2">'+rs(it.total)+'</div>'+
'<button class="x" data-rmi="'+i+'" aria-label="Remover">×</button></div>').join('');
$('itens').querySelectorAll('[data-rmi]').forEach(b=>b.addEventListener('click',()=>{
pedido.splice(+b.dataset.rmi,1); pintaPedido();
}));
const itens = pedido.reduce((t,it)=>t+it.total,0) + (itemAtual? itemAtual.total : 0);
const frete = entrega.tipo==='frete' && entrega.cotado ? entrega.valor : 0;
const soma = itens + frete;
const n = pedido.length + (itemAtual?1:0);
const eco = [...pedido, ...(itemAtual?[itemAtual]:[])]
.reduce((t,it)=>t+(TABELA[it.modo]-it.unit)*it.cob, 0);
$('resLin').innerHTML =
'<div class="l"><span>'+n+(n===1?' item':' itens')+'</span><b>'+rs(itens+eco)+'</b></div>'+
(eco>0.01? '<div class="l" style="color:var(--verde-ml)"><span>Desconto pela nota</span>'+
'<b style="color:var(--verde-ml)">− '+rs(eco)+'</b></div>' : '')+
'<div class="l"><span>Frete</span><b>'+
(entrega.tipo==='retira' ? '<span style="color:var(--verde-ml)">Grátis</span>'
: entrega.cotado ? rs(frete) : 'a cotar')+'</b></div>'+
'<div class="l"><span>Revisão e reencaixe</span>'+
'<b><span style="color:var(--verde-ml)">Grátis</span></b></div>';
$('carrTot').textContent=rs(soma);
$('bPagar').textContent = (pedido.length + (itemAtual?1:0))>1
? 'Pagar os '+(pedido.length+(itemAtual?1:0))+' itens de uma vez'
: 'Ir para o pagamento';
if(pedido.length || itemAtual) $('carr').classList.add('on');
if($('cartLink')) $('cartLink').textContent='Carrinho ('+n+')';
window.dispatchEvent(new Event('dtf-cart-changed'));
}
function carrinho(){
const base=precoBase(nota), cob=cobrar(metros);
const unit=base, tot=cob*unit, sobra=+(cob-metros).toFixed(2);
$('carr').classList.add('on');
let l='<div class="l"><span>'+MODOS[modo].tit+'</span><b>'+rs(base)+'/m</b></div>';
{
const teto=TABELA[modo], eco=(teto-base)*cob;
l = '<div class="l"><span>'+MODOS[modo].tit+' · tabela</span><b>'+rs(teto)+'/m</b></div>';
l += eco>0.01
? '<div class="l" style="color:#7FD4A0"><span>Desconto pela nota '+nota+'</span><b>−'+rs(eco)+'</b></div>'
: '<div class="l" style="color:#8D9AB0"><span>Nota '+nota+' · sem desconto</span><b>—</b></div>';
}
if(!ehFolha()) l += '<div class="l" style="color:#8D9AB0"><span>Montagem da folha</span>'+
'<b>inclusa · '+rs(MONTAGEM[modo])+'/m</b></div>';
l += '<div class="l"><span>Metragem'+(sobra>0.001?' · arredondada para 10 cm':'')+
'</span><b>'+fmtM(cob)+' m</b></div>';
l += '<div class="l"><span>Preço final do metro</span><b>'+rs(unit)+'</b></div>'+
'<div class="l" style="color:var(--laranja2)"><span>Revisão e reencaixe</span><b>grátis</b></div>';
$('carrLin').innerHTML=l;
$('atual').style.display='';
itemAtual={modo, tit:MODOS[modo].tit, nota, metros, cob, unit, total:tot,
localFiles:(ehFolha()?folhas:artes).map(x=>x.f),
desc:fmtM(cob)+' m · '+rs(unit)+'/m · nota '+nota};
pintaPedido();
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, '+
'o metro cai para '+rs(piso)+' — são '+rs(ganho)+' a menos neste pedido.','var(--verde)');
}
}
function qm(t,c){ const m=$('qmsg'); m.style.display='block'; m.style.color=c||'var(--verde)'; m.innerHTML=t; }
// ── decisões
// Remanejar conserta encaixe, não resolução — e a nota mede só resolução.
// Por isso a nota NÃO muda aqui. E a metragem nova só existe depois que o motor
// roda na folha de verdade, no servidor: o navegador recebe a folha fechada,
// não as artes separadas, então não há como recalcular aqui sem inventar número.
$('qOk').addEventListener('click',()=>{
if($('qOk').disabled) return;
const ok=$('ciente').classList.contains('on');
qm(ok? '<b>Registrado.</b> Você confirmou que viu a ressalva de resolução.'
: '<b>Certo.</b> Seu pedido está montado abaixo.','var(--tx)');
$('carr').scrollIntoView({behavior:'smooth',block:'nearest'});
});
$('pOk').addEventListener('click',()=>$('carr').scrollIntoView({behavior:'smooth'}));
$('pMais').addEventListener('click',()=>$('foco').scrollIntoView({behavior:'smooth'}));
$('pVolta').addEventListener('click',()=>{
folhas=[]; artes=[]; reencaixado=false; pintaFolha(); pintaArtes();
limpaPaineis(); // o carrinho só some se o pedido estiver vazio
$('foco').scrollIntoView({behavior:'smooth'});
});
// carrossel
(function(){
const tr=$('trilho'); if(!tr) return;
const slides=[...tr.children], pontos=$('cPontos');
slides.forEach((_,i)=>{
const b=document.createElement('button');
b.className='pt'+(i?'':' on'); b.setAttribute('aria-label','Cartão '+(i+1));
b.addEventListener('click',()=>ir(i)); pontos.appendChild(b);
});
function ir(i){
i=Math.max(0,Math.min(slides.length-1,i));
tr.scrollTo({left:slides[i].offsetLeft-tr.offsetLeft,behavior:'smooth'});
}
function atual(){
const meio=tr.scrollLeft+tr.clientWidth/2;
let a=0,d=Infinity;
slides.forEach((s,i)=>{
const c=s.offsetLeft-tr.offsetLeft+s.offsetWidth/2;
if(Math.abs(c-meio)<d){ d=Math.abs(c-meio); a=i; }
});
return a;
}
tr.addEventListener('scroll',()=>{
const a=atual();
[...pontos.children].forEach((p,i)=>p.classList.toggle('on',i===a));
},{passive:true});
$('cAnt').addEventListener('click',()=>ir(atual()-1));
$('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));
$('cep').classList.toggle('on', entrega.tipo==='frete');
$('vFrete').textContent = entrega.tipo!=='frete' ? '—'
: entrega.cotado ? rs(entrega.valor) : 'a cotar';
$('avisoE').innerHTML = entrega.tipo==='retira'
? 'DTF é impresso depois que você paga. Avisamos no WhatsApp quando estiver '+
'<b>pronto para retirar</b> — não venha antes do aviso.'
: '';
$('bPagar').disabled = !entrega.cotado || !clienteOk();
pintaPedido();
}
// máscaras e conferência dos campos do cliente
function liga(id,fmt,val,chave,erro,msg){
const el=$(id), er=$(erro);
el.addEventListener('input',e=>{
e.target.value=fmt(e.target.value);
cliente[chave]=e.target.value;
el.classList.remove('ruim','bom'); er.textContent='';
if(val(e.target.value)) el.classList.add('bom');
pintaEntrega();
});
el.addEventListener('blur',()=>{
if(!el.value){ el.classList.remove('ruim','bom'); er.textContent=''; return; }
const ok=val(el.value);
el.classList.toggle('ruim',!ok); el.classList.toggle('bom',ok);
er.textContent = ok? '' : msg;
});
}
liga('fCnpj', v=>{
const d=v.replace(/\D/g,'').slice(0,14);
return d.replace(/^(\d{2})(\d)/,'$1.$2').replace(/^(\d{2})\.(\d{3})(\d)/,'$1.$2.$3')
.replace(/\.(\d{3})(\d)/,'.$1/$2').replace(/(\d{4})(\d)/,'$1-$2');
}, cnpjOk, 'cnpj', 'eCnpj', 'CNPJ inválido · confira os números');
liga('fZap', v=>{
const d=v.replace(/\D/g,'').slice(0,11);
return d.length>6 ? '('+d.slice(0,2)+') '+d.slice(2,d.length-4)+'-'+d.slice(-4)
: d.length>2 ? '('+d.slice(0,2)+') '+d.slice(2) : d;
}, zapOk, 'zap', 'eZap', 'Faltam dígitos no número');
liga('fMail', v=>v.trim(), mailOk, 'mail', 'eMail', 'E-mail inválido');
function pintaZoom(){
$('zTxt').textContent=Math.round(ZOOMS[zoomI]*100)+'%';
$('zMenos').disabled = zoomI===0;
$('zMais').disabled = zoomI===ZOOMS.length-1;
}
$('zMais').addEventListener('click',()=>{
if(zoomI<ZOOMS.length-1){ zoomI++; pintaZoom(); previaAoVivo(); }
});
$('zMenos').addEventListener('click',()=>{
if(zoomI>0){ zoomI--; pintaZoom(); previaAoVivo(); }
});
pintaZoom();
document.querySelectorAll('.opE').forEach(b=>b.addEventListener('click',()=>{
entrega.tipo=b.dataset.ent;
entrega.cotado = entrega.tipo==='retira';
entrega.valor=0;
if(entrega.tipo==='frete') $('cepMsg').innerHTML='';
pintaEntrega();
}));
$('cepIn').addEventListener('input',e=>{
const v=e.target.value.replace(/\D/g,'').slice(0,8);
e.target.value = v.length>5 ? v.slice(0,5)+'-'+v.slice(5) : v;
entrega.cep=v; entrega.cotado=false; entrega.valor=0; pintaEntrega();
});
$('bCep').addEventListener('click',async()=>{
if(entrega.cep.length!==8){ $('cepMsg').innerHTML='CEP incompleto.'; return; }
if(window.dtfFreight){ await window.dtfFreight(); return; }
// A cotação vem da transportadora. Enquanto a integração não existir, o pedido não
// fecha por frete: o valor precisa estar fechado antes do Mercado Pago.
$('cepMsg').innerHTML='<b style="color:#FFB63D">Cotação de frete ainda não integrada.</b><br>'+
'O valor precisa vir da transportadora antes do pagamento — senão você pagaria um '+
'total que não é o final. Por enquanto, retire em Franca ou fale conosco no WhatsApp.';
pintaEntrega();
});
$('bMais').addEventListener('click',()=>{
if(itemAtual){ pedido.push(itemAtual); itemAtual=null; }
$('atual').style.display='none'; $('carrLin').innerHTML='';
$('foco').classList.remove('on'); $('cards').style.display='';
['qual','prev'].forEach(id=>$(id).classList.remove('on'));
modo=null; artes=[]; folhas=[]; metros=0; nota=0; montagemCm=0;
pintaPedido();
document.getElementById('envio').scrollIntoView({behavior:'smooth'});
});
$('bPagar').addEventListener('click',()=>{
if(window.dtfCheckout) window.dtfCheckout();
else alert('O pedido online está indisponível no momento. Tente novamente em instantes.');
});
pintaEntrega();
</script>
<script src="/privacy.js"></script>
<script src="/upload.js"></script>
<script src="/checkout.js"></script>
<script src="/cart.js"></script>
</body>
</html>