/* Raiz · sistema de design v2
   Hierarquia por tom (quatro camadas), quase nenhuma linha. Fraunces para títulos e números; Instrument Sans no corpo.
   Um acento (lima→água) que só aparece onde a IA está trabalhando. Cores restantes carregam significado, não decoração. */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght,SOFT@9..144,300..700,50&family=Instrument+Sans:wght@400;500;600;700&display=swap');
@property --spin { syntax: '<angle>'; inherits: false; initial-value: 0deg; }

:root {
  --l0: #151614; --l1: #1C1D1B; --l2: #232522; --l3: #2C2E2A; --l4: #363934;
  --ink: #F2F0EA; --ink2: #CBC9C1; --mute: #8D8C85; --faint: #5E5E58;
  --acc1: #C6F26B; --acc2: #5FD7C3; --acc: var(--acc1);
  --ai: linear-gradient(110deg, var(--acc1), var(--acc2));
  --leaf: #9BD98A; --honey: #E8B65A; --berry: #F2807A;
  /* derivados: quem troca --leaf na tela de tema troca o fundo suave junto, sem digitar duas vezes */
  --leafSoft: color-mix(in srgb, var(--leaf) 14%, transparent);
  --honeySoft: color-mix(in srgb, var(--honey) 14%, transparent);
  --berrySoft: color-mix(in srgb, var(--berry) 14%, transparent);
  --r: 22px; --r2: 14px; --pill: 999px;
  /* medidas que a tela de tema ajusta */
  --pad: clamp(20px, 2.4vw, 60px);      /* margem lateral da página */
  --gap: 14px;                          /* espaço entre cartões do painel */
  --colmin: 300px;                      /* largura mínima da coluna do painel; menor = mais colunas */
  --size: 14px;                         /* tamanho base do texto */
  --lh: 1.5;                            /* altura da linha do corpo */
  --track: 0em;                         /* espaço entre letras do corpo */
  --bw: 1.5px;                          /* largura da moldura de cartão, botão e linha selecionada */
  --spinDur: 5s;                        /* uma volta do anel animado */
  --ease: cubic-bezier(.2,.7,.2,1);
  --display: "Fraunces", Georgia, serif; --body: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
  /* Peso e estilo em variável porque a tela de Aparência mexe neles. Os valores são os que o Raiz
     sempre usou: título em 500 (o Fraunces é largo; 700 fecha os contornos) e corpo em 400. */
  --dispWeight: 500; --dispStyle: normal; --bodyWeight: 400; --bodyStyle: normal;
  /* Animação da padronagem do fundo. `none` é nome de animação válido e é o padrão: quem não escolheu
     desenho nem movimento não paga nem um quadro de renderização. Ver os @keyframes pat* abaixo. */
  --bgAnim: none; --bgDur: 60s; --bgDir: normal;
  /* ── tokens de contraste ──
     Existem porque o tema claro precisa invertê-los. Antes eram rgba() e hex fixos espalhados
     pelo arquivo: no fundo claro a zebra da tabela sumia e o texto sobre o acento ficava ilegível.
     Regra: nenhuma cor literal fora deste bloco. */
  --edge: rgba(255,255,255,.03);        /* fio de luz no topo do cartão */
  --zebra: rgba(255,255,255,.018);      /* linha ímpar da tabela e coluna do gráfico sob o mouse */
  --inkHi: #fff;                        /* botão sólido sob o mouse (mais claro que --ink) */
  --onAcc: #10140A;                     /* texto sobre o gradiente de acento; o acento é claro nos dois temas */
  --onSemantic: #1A0D10;                /* texto sobre leaf/honey/berry cheios */
  --accSoft: color-mix(in srgb, var(--acc1) 18%, transparent); /* seleção, brilho do herói, item novo no PDV */
  --kbdBg: rgba(0,0,0,.25);             /* fundo da tecla */
  --scrim: rgba(0,0,0,.55);             /* fundo atrás de gaveta e modal */
  --drop: 0 20px 50px -20px rgba(0,0,0,.8);
  --halo: .35;                          /* intensidade do brilho no hover; 0 desliga */
  --haloSize: 14px;                     /* quanto o brilho escapa para fora do cartão */
  --haloBlur: 22px;                     /* borrão do brilho; pequeno vira contorno, grande vira névoa */
  /* ── opacidade, uma por camada ──
     Antes havia um alfa só (--cardAlpha) e ele alcançava três lugares derivados. O resultado é o que
     se via em Notas e em Entrada/Saída Manual: o cartão ficava translúcido e os CAMPOS dentro dele
     — que são --l3 — continuavam sólidos, mais acesos que o cartão que os contém. O cartão parecia
     não obedecer justamente nas telas em que ele é quase todo feito de campo.
     Agora cada camada tem o seu alfa e cada uma vira uma superfície pronta: `--sN` é a camada `--lN`
     já com a opacidade `--lNa`.
     A REGRA, e ela é curta: **quem pinta fundo usa --sN; quem desenha borda, sombra, traço de gráfico
     ou anel usa --lN cru.** Borda translúcida não revela nada de útil, some contra o próprio fundo, e
     um anel de foco que desaparece é um problema de acessibilidade, não um enfeite.
     Todos nascem em 100%: ninguém vê diferença até mexer no slider da Aparência. */
  --l0a: 100%; --l1a: 100%; --l2a: 100%; --l3a: 100%; --l4a: 100%;
  --s0: color-mix(in srgb, var(--l0) var(--l0a), transparent);
  --s1: color-mix(in srgb, var(--l1) var(--l1a), transparent);
  --s2: color-mix(in srgb, var(--l2) var(--l2a), transparent);
  --s3: color-mix(in srgb, var(--l3) var(--l3a), transparent);
  --s4: color-mix(in srgb, var(--l4) var(--l4a), transparent);
  /* ── padronagem ──
     O ladrilho em si é montado pelo theme.js (um SVG em data-URI) e chega por estas variáveis.
     `none` é o padrão e é um valor válido de camada de fundo: quem não subiu desenho nenhum paga
     zero, porque `none 0 0/auto repeat` simplesmente não pinta nada.
     São duas famílias independentes de propósito — o dono pode querer uma figura discreta atrás da
     página e outra, ou nenhuma, dentro dos cartões. `--pat*Bg` existe para as regras lá embaixo
     citarem a camada inteira numa palavra só: fundo é lista, e repetir os quatro pedaços em cada
     regra é onde se esquece um. */
  /* `*W`/`*H` são a mesma medida de `*Size` separada por eixo. Existem só para as keyframes do fundo
     poderem empurrar o desenho por um ladrilho num eixo e deixar o outro parado — coisa que não se
     escreve a partir de "96px 72px" numa palavra só. */
  --patSite: none; --patSiteSize: auto; --patSiteRepeat: repeat; --patSitePos: 0 0; --patSiteW: 0px; --patSiteH: 0px;
  --patCard: none; --patCardSize: auto; --patCardRepeat: repeat; --patCardPos: 0 0; --patCardW: 0px; --patCardH: 0px;
  --patSiteBg: var(--patSite) var(--patSitePos)/var(--patSiteSize) var(--patSiteRepeat);
  --patCardBg: var(--patCard) var(--patCardPos)/var(--patCardSize) var(--patCardRepeat);
  /* piso opaco das superfícies que se apoiam na página: é o que o slider de opacidade revela.
     Cor crua de propósito — o piso existe justamente para não deixar passar nada. */
  --under: linear-gradient(var(--l0), var(--l0));
  /* Aliases usados pelas páginas. Os que nomeiam SUPERFÍCIE apontam para `--sN` e por isso obedecem
     ao slider; `--line`/`--line2` nomeiam TRAÇO e continuam na cor crua, senão a divisória some. */
  --bg: var(--s0); --bg2: var(--s1); --surface: var(--s1); --surface2: var(--s2); --surface3: var(--s3); --line: var(--l3); --line2: var(--l4); --paper: var(--s0); --soft: var(--s2); --mint: var(--acc2); --brand: var(--ai);
}
/* tema claro: as camadas sobem de tom em vez de descer, e os tokens de contraste invertem.
   Um só atributo em <html> troca a interface inteira — nenhuma regra abaixo sabe qual tema está ativo. */
:root[data-theme="light"] {
  --l0: #F7F6F1; --l1: #FFFFFF; --l2: #F0EEE7; --l3: #E3E0D6; --l4: #D2CEC1;
  --ink: #1A1B18; --ink2: #40423C; --mute: #74766D; --faint: #A3A49B;
  --acc1: #8FD11F; --acc2: #17B49B;
  --leaf: #3E9B31; --honey: #B07914; --berry: #C7443C;
  /* leafSoft/honeySoft/berrySoft/accSoft não aparecem aqui: são color-mix sobre as cores acima e
     acompanham a troca sozinhos. */
  --edge: rgba(0,0,0,.04); --zebra: rgba(0,0,0,.022); --inkHi: #000;
  --onAcc: #10140A; --onSemantic: #FFFFFF;
  --kbdBg: rgba(0,0,0,.07); --scrim: rgba(30,32,28,.38); --drop: 0 18px 44px -22px rgba(0,0,0,.28);
}
* { box-sizing: border-box }
html, body { margin: 0; height: 100% }
/* A animação anda com a camada da padronagem e deixa o degradê de baixo onde está — daí os dois
   valores em cada keyframe, um por camada do `background`. Andar exatamente um ladrilho é o que faz
   a volta fechar sem emenda: no fim do ciclo o desenho está de novo sobre si mesmo. */
@keyframes patX { to { background-position: var(--patSiteW) 0, 0 0 } }
@keyframes patY { to { background-position: 0 calc(-1 * var(--patSiteH)), 0 0 } }
@keyframes patXY { to { background-position: var(--patSiteW) calc(-1 * var(--patSiteH)), 0 0 } }
body { font-family: var(--body); font-size: var(--size); font-weight: var(--bodyWeight); font-style: var(--bodyStyle); letter-spacing: var(--track); line-height: var(--lh); color: var(--ink); background: var(--patSiteBg), linear-gradient(var(--l0), var(--l0)); font-feature-settings: "tnum"; -webkit-font-smoothing: antialiased;
  animation: var(--bgAnim) var(--bgDur) linear infinite var(--bgDir) }
button, input, select, textarea { font: inherit; color: inherit }
a { color: var(--ink); text-decoration: none }
::selection { background: color-mix(in srgb, var(--acc1) 25%, transparent) }
:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px }
::-webkit-scrollbar { width: 10px; height: 10px } ::-webkit-scrollbar-thumb { background: var(--s3); border-radius: 99px; border: 3px solid var(--l0) }

/* ── tipografia ── */
h1 { font-family: var(--display); font-variation-settings: "opsz" 72, "SOFT" 50; font-weight: 500; font-size: 30px; letter-spacing: -.01em; line-height: 1.1; margin: 0 }
h2 { font-family: var(--display); font-variation-settings: "opsz" 36, "SOFT" 50; font-weight: 500; font-size: 20px; margin: 0 }
.m { color: var(--mute) } .b { font-weight: 600 } .r { text-align: right } .sp { flex: 1 } .row { display: flex; justify-content: space-between; align-items: baseline }
.grad { background: var(--ai); -webkit-background-clip: text; background-clip: text; color: transparent }

/* ── camadas ── */
.card, .tile, .modal, .drawer, .box, .msg.a { background: var(--s1); border: 0; border-radius: var(--r) }
/* Janela, gaveta e balão de conversa também leem `--s1`: quem quiser uma gaveta translúcida pode ter.
   Eles existem para tapar o que está atrás, então texto sobre texto é o risco real de baixar o alfa
   dessa camada — mas é uma escolha de quem configura, não uma trava. O slider nasce em 100%. */
/* A camada de padronagem vem antes da cor porque em `background` a primeira camada fica por cima.
   A cor entra como gradiente de uma cor só: numa lista de camadas, `background-color` só pode ser
   a última e não aceita a sintaxe `posição/tamanho` — escrever tudo como imagem deixa as regras
   iguais entre si e evita a pegadinha de a cor apagar a figura.
   `--under` é a ÚLTIMA camada, ou seja, a de baixo: um piso opaco na cor da página. O slider de
   opacidade continua valendo — o cartão a 80% é a mistura da cor dele com a da página, que é o que
   se quer ver —, mas o cartão deixa de ser um buraco. Sem esse piso, tudo o que mora atrás dele
   subia à face: o halo de hover (uma cunha cônica do tamanho do cartão) e a padronagem do site.
   Quem quiser figura DENTRO do cartão tem a padronagem dos cartões, que é um ajuste separado.
   Só entram aqui as superfícies que se apoiam na página. Cartão dentro de cartão não leva piso: ali
   o que está atrás é o cartão de fora, e revelá-lo é justamente o efeito. */
.card, .tile, .box { background: var(--patCardBg), linear-gradient(var(--s1), var(--s1)), var(--under) }
.card { box-shadow: inset 0 1px 0 var(--edge) }
.card .card, .modal .card { background: var(--patCardBg), linear-gradient(var(--s2), var(--s2)) }
/* dentro de um painel com fundo próprio, o piso muda de assunto: o que está atrás não é mais a
   página, é o painel. Vale para o `.tile` (que é a pastilha de produto do PDV e o ícone do
   launcher), e é `> *` e não o próprio seletor porque o painel também usa `--under` no fundo dele —
   declarar na mesma caixa faria a superfície se apoiar em si mesma e o slider dela pararia de valer. */
:is(.card, .modal, .drawer, .left) > * { --under: linear-gradient(var(--l1), var(--l1)) }
/* o brilho do herói é fundo, não pseudo-elemento: com ::after ele precisava de `overflow: hidden`,
   e o overflow recortava o halo de hover lá embaixo. */
.hero { background: var(--patCardBg), radial-gradient(closest-side at 108% 132%, var(--accSoft), transparent 70%), linear-gradient(var(--s2), var(--s2)), var(--under); position: relative }

/* anel animado: só onde a IA trabalha. Fundo duplo, sem máscara. */
.glow { position: relative; border: var(--bw) solid transparent !important; border-radius: var(--r);
  background: linear-gradient(var(--s2), var(--s2)) padding-box, conic-gradient(from var(--spin), var(--l4) 0 55%, var(--acc1) 72%, var(--acc2) 84%, var(--l4) 96% 100%) border-box !important;
  animation: spin var(--spinDur) linear infinite }
.glow::after { content: ""; position: absolute; inset: calc(-1 * var(--haloSize)); z-index: -1; border-radius: inherit; pointer-events: none; opacity: var(--halo); filter: blur(var(--haloBlur));
  background: conic-gradient(from var(--spin), transparent 0 55%, var(--acc1) 72%, var(--acc2) 84%, transparent 96% 100%); animation: spin var(--spinDur) linear infinite }
@keyframes spin { to { --spin: 360deg } }
/* estado "pensando": brilho corre pela superfície */
.thinking { background-image: linear-gradient(100deg, transparent 30%, var(--accSoft) 50%, transparent 70%); background-size: 240% 100%; animation: sweep 1.6s linear infinite }
@keyframes sweep { from { background-position: 120% 0 } to { background-position: -120% 0 } }

/* ── controles ── */
.chip { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: var(--pill); font-size: 12px; font-weight: 600; background: var(--s3); color: var(--ink2) }
.ok { background: var(--leafSoft); color: var(--leaf) } .warn { background: var(--honeySoft); color: var(--honey) } .bad { background: var(--berrySoft); color: var(--berry) }
/* Chip que se escolhe (motivo do ajuste manual de estoque). A cor do chip é a do RISCO — quebra é
   vinho, doação é folha — e ela NÃO pode virar a cor da seleção: se o escolhido ficasse verde, o
   lojista leria "deu certo" onde está escrito "perdi mercadoria". Então o escolhido guarda o próprio
   tom e ganha um anel em `currentColor`; os outros desbotam. O anel é `box-shadow` e não `border`
   para não empurrar o texto meio pixel quando a seleção troca. */
.chip.pick { border: 0; font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; opacity: .5; transition: opacity .16s, box-shadow .16s }
.chip.pick:hover { opacity: .8 }
.chip.pick.on { opacity: 1; box-shadow: 0 0 0 2px currentColor inset }
/* selo com ✕ embutido, só no cadastro do produto: no caixa o mesmo selo é .chip puro, sem botão. */
.chip.rm { gap: 6px; padding-right: 5px } .chip.rm button { border: 0; padding: 0 3px; background: none; color: inherit; font: inherit; line-height: 1; cursor: pointer; opacity: .55; transition: opacity .12s } .chip.rm button:hover { opacity: 1 }
/* interruptor: alvo grande de propósito. A caixinha nativa de 13 px fazia o clique escapar. */
.sw { display: inline-flex; align-items: center; gap: 9px; padding: 7px 14px 7px 9px; border: 0; border-radius: var(--pill); background: var(--s3); color: var(--mute); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; transition: background .18s, color .18s }
.sw > i { width: 34px; height: 20px; border-radius: 99px; background: var(--s4); position: relative; transition: background .18s }
.sw > i::after { content: ""; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; border-radius: 99px; background: var(--ink); transition: transform .18s var(--ease) }
.sw.on { background: var(--leafSoft); color: var(--leaf) }
.sw.on > i { background: var(--leaf) } .sw.on > i::after { transform: translateX(14px) }
.kbd { display: inline-block; font-size: 10.5px; font-weight: 600; padding: 1px 6px; border-radius: 6px; background: var(--kbdBg); color: var(--mute); line-height: 1.6 }
.empty { padding: 48px 20px; text-align: center; color: var(--mute) }
.field { width: 100%; padding: 12px 16px; border: 0; border-radius: var(--r2); background: var(--s2); color: var(--ink); transition: background .15s, box-shadow .15s }
.card .field, .modal .field, .drawer .field { background: var(--s3) }
.field::placeholder { color: var(--mute) } .field:hover { background: var(--s3) }
.field:focus { outline: none; background: var(--s3); box-shadow: 0 0 0 2px var(--l4) }
select.field { appearance: none; padding-right: 36px; background-image: linear-gradient(45deg, transparent 50%, var(--mute) 50%), linear-gradient(135deg, var(--mute) 50%, transparent 50%); background-position: calc(100% - 18px) 50%, calc(100% - 13px) 50%; background-size: 5px 5px; background-repeat: no-repeat }
select.field option { background: var(--s2) } input[type=date].field::-webkit-calendar-picker-indicator { filter: invert(.6) }
textarea.field { resize: vertical }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 18px; border-radius: var(--pill); font-weight: 600; font-size: 14px; border: 0; cursor: pointer; white-space: nowrap;
  background: var(--ink); color: var(--l0); transition: transform .1s var(--ease), background .15s, opacity .15s }
.btn:hover:not(:disabled) { background: var(--inkHi) } .btn:active:not(:disabled) { transform: scale(.97) } .btn:disabled { opacity: .35; cursor: not-allowed }
.btn.ghost { background: var(--s3); color: var(--ink) } .btn.ghost:hover:not(:disabled) { background: var(--s4) }
.btn.danger { background: var(--berrySoft); color: var(--berry) }
.btn.primary-grad, .btn.ai { background: var(--ai); color: var(--onAcc) } .btn.ai:hover:not(:disabled), .btn.primary-grad:hover:not(:disabled) { background: var(--ai); filter: brightness(1.08) }
.btn .kbd { background: var(--kbdBg); color: inherit } .btn.ghost .kbd { background: var(--kbdBg); color: var(--mute) }
.icon { background: none; border: 0; padding: 6px; cursor: pointer; color: var(--mute); border-radius: 10px; display: inline-flex; transition: background .12s, color .12s }
.icon:hover { background: var(--s3); color: var(--ink) } .icon.del:hover { color: var(--berry); background: var(--berrySoft) }
.tab { padding: 8px 14px; border-radius: var(--pill); font-size: 13px; font-weight: 600; color: var(--ink2); cursor: pointer; border: 0; background: transparent; white-space: nowrap; transition: background .12s, color .12s }
.tab:hover { background: var(--s2); color: var(--ink) } .tab.on { background: var(--s3); color: var(--ink) }

/* ── tabelas: sem linhas, zebra tonal ── */
table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 14px }
th { text-align: left; font-weight: 500; color: var(--mute); font-size: 12px; padding: 12px 16px }
td { padding: 13px 16px; vertical-align: middle; border: 0 }
tbody tr:nth-child(odd) td { background: var(--zebra) }
tbody tr td:first-child { border-radius: 12px 0 0 12px } tbody tr td:last-child { border-radius: 0 12px 12px 0 }
tbody tr:hover td { background: var(--s2) }
/* cabeçalho que ordena: a seta aparece no hover para 6 colunas nao virarem 6 setinhas cinzas
   competindo com o dado; na coluna ordenada ela fica fixa, porque e o unico sinal de por que a
   lista esta nessa ordem. `inherit` na fonte e na cor: o botao nao pode parecer botao. */
th .thsort { display: inline-flex; align-items: center; gap: 5px; border: 0; background: none; padding: 0; margin: 0; font: inherit; color: inherit; cursor: pointer; text-align: inherit; transition: color .12s }
th .thsort:hover { color: var(--ink) }
th .thsort .thar { font-size: 9px; line-height: 1; opacity: 0; transition: opacity .14s }
th .thsort:hover .thar { opacity: .5 }
th .thsort.on { color: var(--ink); font-weight: 600 }
th .thsort.on .thar { opacity: 1; color: var(--acc) }
/* coluna a direita: a seta vai para a esquerda do rotulo, senao rotulo+seta desalinham da coluna
   de numeros que esta embaixo. */
th.r .thsort { flex-direction: row-reverse }

/* ── layout do painel: trilho de ícones ── */
/* ── casca: barra fina em cima, tela cheia embaixo. Navegar é papel do launcher, não de um trilho fixo. ── */
.app { display: grid; grid-template-rows: 56px 1fr; height: 100vh }
/* ".top" e não ".bar": .bar já é o gráfico de barras lá embaixo — a casca herdava height:160px dele. */
.top { display: flex; align-items: center; gap: 14px; padding: 0 18px 0 12px; background: var(--s0) }
.launch { display: flex; align-items: center; gap: 9px; padding: 6px 12px 6px 6px; border: 0; border-radius: var(--pill); background: none; color: var(--ink); font: inherit; font-family: var(--display); font-weight: 600; font-size: 15px; cursor: pointer; transition: background .14s }
.launch:hover { background: var(--s2) }
.launch > i { width: 30px; height: 30px; border-radius: 10px; background: var(--ai); display: grid; place-items: center; color: var(--onAcc); flex-shrink: 0 }
.launch > i svg { width: 17px; height: 17px }
.launch > span + span { color: var(--mute) } .launch > span + span svg { width: 15px; height: 15px }
.where { margin: 0; font-family: var(--body); font-size: 13px; font-weight: 600; color: var(--mute); padding-left: 14px; border-left: 1px solid var(--l3); letter-spacing: .01em }
.lnk { color: var(--mute); font-size: 12.5px; font-weight: 500; text-decoration: none; padding: 6px 10px; border-radius: var(--pill); transition: background .14s, color .14s } .lnk:hover { background: var(--s2); color: var(--ink) }
.me { width: 30px; height: 30px; border-radius: 99px; border: 0; background: var(--s3); color: var(--ink); font: inherit; font-weight: 700; font-size: 11px; cursor: pointer; flex-shrink: 0 } .me:hover { background: var(--s4) }
.live { display: grid; place-items: center; padding: 0 2px } .live i { width: 7px; height: 7px; border-radius: 99px; background: var(--leaf) } .live.off i { background: var(--berry) }

/* ── launcher: a grade É a navegação. Ícone grande, nome embaixo, busca em cima. ── */
/* minmax(0,1fr) nas colunas: sem isso a coluna implícita cresce até o max-content da grade de ícones
   (auto-fill mede todos numa linha só) e o launcher rola de lado com metade dos aplicativos cortada. */
/* As duas linhas são `auto`, e não `auto 1fr`: com `1fr` a faixa da grade já ocupava toda a altura
   sobrando, então centralizar não teria efeito nenhum — os cartões apenas encostavam no topo de uma
   faixa do tamanho da tela. Com as duas em `auto` o bloco mede o que ele é e o `align-content` passa
   a mandar. O `safe` é o que impede o remédio de virar defeito: centralizar conteúdo que não cabe
   corta o começo dele, e a busca sumiria acima da borda sem rolagem que a alcance — com `safe`, se
   transbordar o navegador volta a alinhar pelo topo sozinho. Efeito de lado bem-vindo: o vazio em
   volta agora pertence ao scrim, e não à grade, então clicar longe dos cartões fecha a gaveta. */
.lscrim { position: fixed; inset: 0; z-index: 60; display: grid; grid-template-rows: auto auto; grid-template-columns: minmax(0, 1fr); align-content: safe center; gap: 26px; padding: 40px; overflow: auto; background: color-mix(in srgb, var(--s0) 82%, transparent); backdrop-filter: blur(26px) saturate(1.3); animation: lin .22s var(--ease) both }
@keyframes lin { from { opacity: 0; backdrop-filter: blur(0) } }
.lhead { display: flex; align-items: center; gap: 12px; justify-content: center }
.lsearch { width: min(440px, 70vw); padding: 12px 20px; border: 0; border-radius: var(--pill); background: var(--s2); color: var(--ink); font: inherit; font-size: 15px; text-align: center; outline: 0 }
.lsearch::placeholder { color: var(--mute) } .lsearch:focus { background: var(--s3) }
/* Flex, e não grade, porque o que se pede aqui é o contrário do que a grade faz bem. Com colunas
   `1fr` os cartões esticavam para ocupar a largura toda, então o bloco nunca estava centralizado:
   ele era a linha inteira. E a última fileira, sempre incompleta, encostava à esquerda. Em flex com
   largura fixa por cartão, cada fileira mede o que tem e o `justify-content` centraliza — inclusive
   a última. `flex-grow: 0` é a peça essencial: com grow, um cartão sozinho (busca filtrando até
   sobrar um) inflaria para 1600 px de largura. */
.lpad { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; max-width: 1600px; width: 100%; min-width: 0; margin: 0 auto }
.lpad .empty { flex: 1 1 100% }
/* ── a gaveta dividida por assunto ──
   Uma `.lpad` por grupo, empilhadas. O título é discreto de propósito: ele serve para o olho pular de
   assunto em assunto, não para competir com o nome dos aplicativos — se pesar tanto quanto um cartão,
   a gaveta passa a ter trinta e nove coisas para ler em vez de trinta e duas. Com a busca preenchida
   não sai nenhum `.lgroup`: fica uma `.lpad` só, e o espaçamento volta a ser o de antes. */
.lbody { display: grid; gap: 20px; max-width: 1600px; width: 100%; min-width: 0; margin: 0 auto }
.lgroup { display: grid; gap: 10px; min-width: 0 }
.lgroup > h3 { margin: 0; font: inherit; font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--mute); text-align: center }
/* Cartão, não ícone. O `.tile` que o botão também carrega já traz fundo, raio, anel de hover e o
   afundar no clique — daqui sai só o que é do launcher: o selo em cima e o nome legível embaixo.
   Reusar o cartão do sistema é o que faz o launcher acompanhar sozinho o tema, o alfa e a padronagem.
   O seletor é `.tile.appicon` (duas classes) porque `.tile` está mais abaixo neste arquivo: com uma
   classe só, o empate de especificidade cai para a ordem e a altura do `.tile` venceria a daqui. */
/* `backwards`, não `both`: com `both` o preenchimento do fim da animação continua mandando no
   `transform` depois que ela acaba e o afundar do `.tile:active` nunca aparece. `backwards` só
   segura o estado inicial durante o atraso escalonado e depois devolve o controle ao CSS normal. */
.tile.appicon { flex: 0 0 212px; border: 0; color: var(--ink); text-decoration: none; font: inherit; font-size: 15px; font-weight: 600; line-height: 1.2; justify-content: space-between; min-height: 132px; animation: pop .3s var(--ease) backwards; animation-delay: calc(var(--n) * 14ms) }
@keyframes pop { from { opacity: 0; transform: scale(.9) translateY(8px) } }
.appicon > i { width: 46px; height: 46px; border-radius: 15px; background: var(--s3); display: grid; place-items: center; color: var(--ink); position: relative; flex-shrink: 0; transition: background .16s }
.appicon > i svg { width: 22px; height: 22px }
.appicon.on > i { background: var(--ai); color: var(--onAcc) }
.appicon .badge { position: absolute; top: -3px; right: -3px; margin: 0; background: var(--honey); color: var(--onSemantic); border-radius: 99px; padding: 0 6px; font-size: 10px; font-weight: 700 }
/* isolation: isolate é o que faz o brilho dos cards existir. O halo é um ::before com z-index:-1, e um
   filho negativo é pintado ANTES do fundo de um bloco ancestral — ou seja, o fundo opaco do <main>
   passava por cima e engolia o borrão, sobrando só a linha animada da borda. Virando contexto de
   empilhamento próprio, o main pinta o fundo dele primeiro e o halo por cima. */
main { overflow: auto; min-width: 0; background: var(--patSiteBg), linear-gradient(var(--l0), var(--l0)); isolation: isolate }
/* sem teto de largura: a tela é do dono, não de uma coluna de 1180 px. O respiro vem do padding,
   que cresce junto com o monitor. Só o texto corrido continua limitado, senão a linha fica ilegível. */
/* `backwards` e NÃO `both`: com `both` a animação continua aplicada depois de terminar, então o
   transform do keyframe nunca volta a `none` — fica na matriz identidade, que ainda assim é um
   transform e cria contexto de empilhamento. Aí o ::before do halo (z-index:-1) passa a valer dentro
   do próprio cartão e é pintado DEPOIS do fundo dele, ou seja, por cima: o brilho lavava a face nas
   telas de formulário alto (Notas, Entrada/Saída Manual). `backwards` mantém o efeito do atraso —
   que é o único motivo do fill aqui, senão os filhos 2 e 3 piscam visíveis antes de começar — e
   solta o elemento ao terminar, devolvendo transform: none. */
.page { padding: 34px var(--pad) 60px; animation: reveal .5s var(--ease) backwards }
.page > * { animation: reveal .5s var(--ease) backwards } .page > *:nth-child(2) { animation-delay: .06s } .page > *:nth-child(3) { animation-delay: .12s }
@keyframes reveal { from { opacity: 0; transform: translateY(6px) } }
.head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 26px; flex-wrap: wrap } .head p { margin: 6px 0 0; color: var(--mute); max-width: 74ch }
.search { position: relative; max-width: 460px; margin-bottom: 16px } .search .field { padding-left: 42px; border-radius: var(--pill) } .search svg { position: absolute; left: 15px; top: 13px; color: var(--mute) }
/* barra de filtro das listas (o `filtro()` do app.js monta; toda tabela e grade da casa passa por ela).
   O `.search` da casa nasceu para ficar sozinho acima da lista e traz `margin-bottom` — dentro da
   barra ele é um item de linha, então a margem é zerada aqui em vez de removida de lá.
   `.flclear` esconde-se com `visibility`, não com `display`: sumindo da caixa ele levaria junto a
   largura, e a contagem à direita andaria para o lado a cada vez que a primeira letra é digitada. */
.flbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 14px }
.flbar .search { margin: 0; flex: 1 1 240px; max-width: 420px }
.flbar > select.field { width: auto; max-width: 220px; border-radius: var(--pill) }
.flcount { margin-left: auto; color: var(--mute); font-size: 12.5px; white-space: nowrap; font-variant-numeric: tabular-nums }
.flclear { visibility: hidden; padding: 9px 15px }
.flclear.on { visibility: visible }
@media (max-width: 560px) { .flbar .search { flex-basis: 100%; max-width: none } .flcount { margin-left: 0 } }
.grid { display: grid; gap: 14px } .g3 { grid-template-columns: 1.6fr 1fr }
.stat { padding: 26px } .stat .l { color: var(--mute); font-size: 13px } .stat .v { font-family: var(--display); font-variation-settings: "opsz" 96, "SOFT" 50; font-weight: 500; font-size: 44px; letter-spacing: -.02em; line-height: 1.05; margin: 4px 0 }
.stat .b { font-family: var(--display); font-variation-settings: "opsz" 36, "SOFT" 50; font-weight: 500; font-size: 17px }

/* gaveta */
.scrim { position: fixed; inset: 0; background: var(--scrim); display: flex; justify-content: flex-end; z-index: 40; animation: fade .2s } @keyframes fade { from { opacity: 0 } }
.drawer { width: 100%; max-width: 480px; height: 100%; border-radius: 28px 0 0 28px; background: var(--s1); display: flex; flex-direction: column; animation: slide .3s var(--ease) } @keyframes slide { from { transform: translateX(40px); opacity: 0 } }
.drawer header { display: flex; align-items: center; justify-content: space-between; padding: 24px 28px 12px } .drawer .body { padding: 12px 28px 24px; overflow: auto; flex: 1; display: flex; flex-direction: column; gap: 14px } .drawer footer { padding: 16px 28px 24px; display: flex; justify-content: flex-end; gap: 8px }
label.f { display: block } label.f span { display: block; font-weight: 500; color: var(--mute); margin-bottom: 6px; font-size: 12.5px }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px }
/* Dobras da gaveta. O cadastro de produto tem mais de vinte campos e ninguém preenche os vinte na
   mesma sessão: quem repõe estoque mexe em custo e validade, quem fecha o mês mexe em NCM. Cada
   seção é um <details>, e as que só um perfil usa nascem fechadas. O fundo é `--l0` de propósito —
   os campos são `--l2`, então a dobra precisa ser mais funda que a gaveta para eles flutuarem nela. */
.fold { background: var(--s0); border-radius: var(--r2); padding: 0 14px }
.fold > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px; padding: 13px 2px; font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--mute); user-select: none; transition: color .15s }
.fold > summary::-webkit-details-marker { display: none }
.fold > summary:hover { color: var(--ink) }
.fold > summary > i { font: inherit; font-weight: 500; text-transform: none; letter-spacing: 0; opacity: .8 }
.fold > summary::after { content: ""; width: 7px; height: 7px; margin-left: auto; border-right: 2px solid currentColor; border-bottom: 2px solid currentColor; transform: rotate(-45deg); transition: transform .18s var(--ease) }
.fold[open] > summary::after { transform: rotate(45deg) }
.fold > .row2 { padding-bottom: 14px }
/* Campo de leitura: continua legível, mas perde o fundo de campo para não convidar ao clique.
   Estoque se move por nota e por venda — digitar aqui seria inventar um número sem lastro. */
label.f.ro .field { background: none; box-shadow: inset 0 0 0 1px var(--l3); color: var(--ink2); cursor: default }
/* Bloco calculado: o número que o sistema deduziu, com a frase que explica de onde ele saiu. Sem
   o acento lima→água, que nesta casa é reservado a onde a IA atua — isto aqui é aritmética. */
.calc { background: var(--s2); border-radius: var(--r2); padding: 12px 14px; display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px }
.calc .t { width: 100%; font-size: 12.5px; font-weight: 500; color: var(--mute) }
.calc b { font-family: var(--display); font-variation-settings: "opsz" 32; font-size: 26px; font-weight: 500; line-height: 1.1 }
.calc small { flex: 1; min-width: 140px; font-size: 12px; line-height: 1.35; color: var(--mute) }
.calc .btn { padding: 6px 12px; font-size: 12px; margin-left: auto }
/* Slot da foto no topo da gaveta: é por ela que se reconhece o item antes de mexer no cadastro.
   Vazio ele tem de parecer campo — a borda tracejada é o que faz a área ler como "solte aqui", e sem
   ela o retângulo liso vira espaçador aos olhos de quem não sabe que ali cabe alguma coisa.
   `.on` (já tem foto) troca a moldura pela própria imagem; `.off` (item ainda não salvo) desliga o
   convite inteiro, porque prometer clique onde o clique não funciona é pior do que não convidar. */
.shotpick { width: 100%; min-height: 168px; border-radius: 16px; background: var(--s2) center/cover no-repeat; border: 1.5px dashed color-mix(in srgb, var(--mute) 45%, transparent); display: grid; place-items: center; align-content: center; gap: 6px; text-align: center; padding: 20px; color: var(--mute); font-size: 13.5px; cursor: pointer; transition: background .15s, border-color .15s, color .15s }
.shotpick .ph { display: inline-flex; color: var(--mute); transition: color .15s } .shotpick .ph svg { width: 30px; height: 30px; stroke-width: 1.6 }
.shotpick b { color: var(--ink); font-weight: 650 } .shotpick .m { font-size: 12px }
.shotpick:hover { background-color: var(--s3); border-color: var(--acc1); color: var(--ink2) } .shotpick:hover .ph { color: var(--acc1) }
.shotpick.off { cursor: default; border-style: solid; border-color: transparent } .shotpick.off:hover { background-color: var(--s2); color: var(--mute) } .shotpick.off:hover .ph { color: var(--mute) } .shotpick.off b { color: var(--mute); font-weight: 600 }
/* com foto: a moldura sai de cena e o convite só aparece por cima quando o mouse chega */
.shotpick.on { border-color: transparent; padding: 0; align-content: end; justify-items: stretch }
.shotpick.on .over { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 10px; border-radius: 0 0 15px 15px; background: color-mix(in srgb, var(--s0) 78%, transparent); backdrop-filter: blur(8px); color: var(--ink); font-weight: 600; opacity: 0; transition: opacity .15s }
.shotpick.on:hover .over { opacity: 1 }
.node { display: flex; align-items: center; gap: 4px; padding: 8px 10px; border-radius: 12px } .node:hover { background: var(--s2) } .node .acts { opacity: 0; margin-left: auto; display: flex } .node:hover .acts { opacity: 1 }
.toast { position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%); background: var(--ink); color: var(--l0); padding: 10px 18px; border-radius: var(--pill); font-weight: 600; z-index: 60; animation: slideup .25s var(--ease) }
.toast.err { background: var(--berry); color: var(--onSemantic) } @keyframes slideup { from { transform: translate(-50%, 10px); opacity: 0 } }

/* OSCAR */
.chat { display: flex; flex-direction: column; height: 100vh }
.msgs { flex: 1; overflow: auto; padding: 40px; display: flex; flex-direction: column; gap: 14px; max-width: 880px; width: 100%; margin: 0 auto }
.msg { max-width: 82%; padding: 14px 18px; border-radius: 20px; white-space: pre-wrap; animation: reveal .3s var(--ease); line-height: 1.55 }
.msg.u { align-self: flex-end; background: var(--s3); color: var(--ink); border-bottom-right-radius: 6px } .msg.a { align-self: flex-start; border-bottom-left-radius: 6px }
.msg .acts { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--l3); font-size: 12px; color: var(--mute); font-family: ui-monospace, monospace }
.compose { padding: 12px 40px 28px } .compose .in { display: flex; gap: 8px; max-width: 880px; margin: 0 auto; padding: 8px 8px 8px 20px; border-radius: 28px; background: var(--s2) }
.compose .in .field { background: transparent; box-shadow: none; padding: 8px 0 } .compose .in .field:focus, .compose .in .field:hover { background: transparent }
.sugg { display: flex; gap: 6px; flex-wrap: wrap; max-width: 880px; margin: 0 auto 12px } .sugg button { background: transparent; border: 1px solid var(--l3); border-radius: var(--pill); padding: 6px 12px; cursor: pointer; font-size: 12.5px; font-weight: 500; color: var(--ink2) } .sugg button:hover { background: var(--s2); color: var(--ink) }

/* Agente GPT flutuante: composição limpa, familiar e integrada ao sistema de design. */
.assistant-dock { position: fixed; right: 22px; bottom: 22px; z-index: 70; display: flex; flex-direction: column; align-items: flex-end; gap: 12px }
.assistant-launch { width: 58px; height: 58px; border: 0; border-radius: 50%; padding: 4px; display: grid; place-items: center; cursor: pointer; background: var(--s1); box-shadow: 0 18px 48px -16px var(--scrim), 0 0 22px var(--accSoft); transition: transform .2s var(--ease), box-shadow .2s }
.assistant-launch:hover { transform: translateY(-2px); box-shadow: 0 22px 54px -16px var(--scrim), 0 0 32px var(--accSoft) }
.assistant-launch .assistant-orb { width: 50px; height: 50px }
.assistant-orb { display: block; flex: none; border-radius: 50%; position: relative; overflow: hidden; background: radial-gradient(circle at 28% 20%, var(--inkHi) 0 3%, transparent 21%), radial-gradient(circle at 72% 70%, var(--acc2) 0 8%, transparent 44%), radial-gradient(circle at 30% 72%, var(--berry) 0 5%, transparent 42%), conic-gradient(from var(--spin), var(--acc1), var(--acc2), var(--berry), var(--honey), var(--acc1)); animation: assistant-orbit 7s linear infinite, assistant-breathe 3.4s ease-in-out infinite; box-shadow: inset 0 0 18px color-mix(in srgb, var(--inkHi) 24%, transparent), 0 0 24px var(--accSoft) }
.assistant-orb::before, .assistant-orb::after { content: ""; position: absolute; border-radius: inherit; pointer-events: none }
.assistant-orb::before { inset: 8%; background: radial-gradient(circle at 66% 30%, color-mix(in srgb, var(--inkHi) 65%, transparent), transparent 30%), linear-gradient(140deg, transparent 25%, color-mix(in srgb, var(--acc2) 60%, transparent), transparent 74%); filter: blur(5px); mix-blend-mode: screen }
.assistant-orb::after { inset: 22%; border: 1px solid color-mix(in srgb, var(--inkHi) 42%, transparent); box-shadow: 0 0 16px color-mix(in srgb, var(--inkHi) 25%, transparent) }
.assistant-orb i { position: absolute; width: 38%; height: 38%; left: 31%; top: 31%; border-radius: 50%; background: color-mix(in srgb, var(--s1) 36%, transparent); filter: blur(7px) }
@keyframes assistant-orbit { to { --spin: 360deg } }
@keyframes assistant-breathe { 50% { transform: scale(.93); filter: saturate(1.2) brightness(1.06) } }
.assistant-panel { width: min(430px, calc(100vw - 28px)); height: min(660px, calc(100dvh - 98px)); display: none; grid-template-rows: auto 1fr auto; overflow: hidden; border: 1px solid var(--l3); border-radius: 24px; background: var(--s1); box-shadow: 0 30px 90px -22px var(--scrim); transform-origin: bottom right }
.assistant-panel.open { display: grid; animation: rise .22s var(--ease) }
.assistant-panel.dragging-files { outline: 2px dashed var(--acc1); outline-offset: -8px }
.assistant-panel > header { min-height: 64px; padding: 12px 14px 12px 16px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--l3); background: var(--s1) }
.assistant-panel > header .m { margin-top: 2px; font-size: 11px }
.assistant-identity, .assistant-head-actions { display: flex; align-items: center; gap: 9px }
.assistant-mini-orb { width: 28px; height: 28px; border-radius: 50%; background: var(--ai); box-shadow: 0 0 15px var(--accSoft); animation: assistant-breathe 3.4s ease-in-out infinite }
.assistant-voice-toggle, .assistant-panel > header .icon { width: 36px; max-width: 36px; height: 36px; overflow: hidden; flex: none; padding: 0; border: 0; border-radius: 50%; display: grid; place-items: center; cursor: pointer; background: transparent; color: var(--mute); transition: background .16s, color .16s }
.assistant-voice-toggle:hover, .assistant-panel > header .icon:hover { background: var(--s3); color: var(--ink) }
.assistant-voice-toggle.on { color: var(--ink); background: var(--accSoft) }
.assistant-voice-label { display: none }
.assistant-messages { overflow: auto; padding: 12px 18px 18px; display: flex; flex-direction: column; gap: 16px }
.assistant-messages .msg { max-width: 88%; padding: 0; font-size: 13px; line-height: 1.55; border-radius: 18px }
.assistant-messages .msg.u { align-self: flex-end; padding: 10px 13px; background: var(--s3); color: var(--ink) }
.assistant-messages .msg.a { align-self: flex-start; position: relative; padding-right: 34px; color: var(--ink2) }
.assistant-messages .acts { margin-top: 8px; padding: 8px 10px; border-radius: 10px; background: var(--s2); color: var(--mute); font-size: 11px; white-space: pre-line }
.assistant-sent-files { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px }
.assistant-sent-files span { max-width: 100%; display: inline-flex; align-items: center; gap: 4px; padding: 4px 7px; border-radius: var(--pill); background: var(--s2); font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.assistant-sent-files svg { width: 11px; height: 11px; flex: none }
.assistant-listen { position: absolute; right: 0; top: -6px; width: 30px; height: 30px; border: 0; border-radius: 50%; display: grid; place-items: center; cursor: pointer; background: transparent; color: var(--mute) }
.assistant-listen:hover { color: var(--ink); background: var(--s3) }
.assistant-listen svg { width: 14px; height: 14px }
.assistant-compose { padding: 10px 12px 12px; background: var(--s1) }
.assistant-composer-shell { border: 1px solid var(--l3); border-radius: 20px; padding: 10px; background: var(--s2); box-shadow: 0 7px 24px -20px var(--scrim) }
.assistant-compose textarea { width: 100%; min-height: 52px; max-height: 130px; resize: none; border: 0; padding: 2px 3px 8px; background: transparent; box-shadow: none }
.assistant-compose textarea:focus { outline: 0; box-shadow: none }
.assistant-composer-tools, .assistant-compose-left, .assistant-compose-right { display: flex; align-items: center }
.assistant-composer-tools { justify-content: space-between; gap: 10px }
.assistant-compose-left, .assistant-compose-right { gap: 7px }
.assistant-compose-hint { color: var(--mute); font-size: 10px }
.assistant-attachments { display: flex; gap: 6px; overflow-x: auto; padding: 0 2px 8px }
.assistant-attachments[hidden] { display: none }
.assistant-file { min-width: 0; max-width: 180px; display: inline-flex; align-items: center; gap: 5px; background: var(--s2); padding: 6px 8px; border-radius: var(--pill); color: var(--ink2); font-size: 10px }
.assistant-file > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.assistant-file svg { width: 12px; height: 12px; flex: none }
.assistant-file button { border: 0; background: none; color: var(--mute); cursor: pointer; padding: 0; font-size: 16px; line-height: 1 }
.assistant-attach, .assistant-mic, .assistant-send { width: 34px; height: 34px; border: 0; border-radius: 50%; display: grid; place-items: center; cursor: pointer; overflow: hidden; flex: none; transition: transform .15s, background .15s, color .15s }
.assistant-head-actions button > span, .assistant-composer-tools button > span { width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center }
.assistant-head-actions svg, .assistant-composer-tools svg { width: 16px; height: 16px; display: block }
.assistant-attach, .assistant-mic { background: transparent; color: var(--ink2) }
.assistant-attach:hover, .assistant-mic:hover { background: var(--s3); transform: translateY(-1px) }
.assistant-send { background: var(--ink); color: var(--s1) }
.assistant-send:hover { transform: translateY(-1px) scale(1.03) }
.assistant-mic.recording { background: var(--berry); color: var(--onSemantic); animation: assistant-pulse 1.1s infinite }
.assistant-panel[data-state="listening"] .assistant-orb, .assistant-launch[data-state="listening"] .assistant-orb { animation-duration: 1.1s, .75s; filter: saturate(1.3) }
.assistant-panel[data-state="thinking"] .assistant-orb, .assistant-launch[data-state="thinking"] .assistant-orb { animation-duration: .8s, 1.4s }
.assistant-panel[data-state="speaking"] .assistant-orb, .assistant-launch[data-state="speaking"] .assistant-orb { animation: assistant-orbit 2.2s linear infinite, assistant-wave .65s ease-in-out infinite alternate }
.assistant-panel[data-state="error"] .assistant-orb, .assistant-launch[data-state="error"] .assistant-orb { filter: saturate(.35) }
/* "Ao vivo" é pílula com rótulo, e não mais um ícone redondo igual aos vizinhos: abrir o microfone e
   deixá-lo aberto é a ação mais cara desta janela e precisa ser a mais legível. Ligada, fica vermelha
   e pulsa — é o único aviso de que a sala está sendo ouvida. */
.assistant-live { display: inline-flex; align-items: center; gap: 6px; height: 36px; flex: none; padding: 0 12px; border: 0; border-radius: 999px; cursor: pointer; background: var(--s3); color: var(--ink2); font: inherit; font-size: 12px; font-weight: 600; transition: background .16s, color .16s }
.assistant-live:hover { background: var(--accSoft); color: var(--ink) }
/* os outros botões deste cabeçalho só têm ícone, então há uma regra acima fixando 16×16 em `button > span`.
   Este é o único com rótulo: sem desfazer aquilo, "Ao vivo" quebra em duas linhas dentro da pílula. */
.assistant-head-actions .assistant-live > span { width: auto; height: auto; white-space: nowrap }
.assistant-live.on { background: var(--berry); color: var(--onSemantic); animation: assistant-pulse 1.6s infinite }
/* enquanto a conversa está aberta, escrever e gravar pela via antiga só atropelaria o áudio.
   O nome da classe é `em-chamada` porque `.live` já existe lá em cima como o pontinho de status,
   e ela traz `display: grid; place-items: center` junto — no painel isso encolhe a janela inteira
   até caber no conteúdo. Classe de estado curta em folha global é armadilha; namespace sempre. */
.assistant-panel.em-chamada .assistant-mic, .assistant-panel.em-chamada .assistant-send { opacity: .4; pointer-events: none }
@keyframes assistant-pulse { 50% { box-shadow: 0 0 0 7px var(--berrySoft) } }
@keyframes assistant-wave { from { transform: scale(.88) } to { transform: scale(1.06) } }
/* ── a agente abre pela voz ──
   Duas classes que já foram uma só, e a separação é o ponto: `voz` é a **experiência** (a janela abre
   no palco falado, o texto vira desvio) e vale em qualquer aparelho; `mobile` é só a **geometria** de
   tela cheia, que continua presa a dedo E tela pequena — não é `@media`, porque uma janela estreita no
   computador tem teclado e mouse e continua melhor servida pelo cartão. Juntar as duas de novo faria o
   computador herdar `position: fixed` e o painel engolir o admin inteiro. */
/* por padrão entra no palco de voz; `.texto` é o desvio, não o contrário */
.assistant-panel.voz:not(.texto) { grid-template-rows: 1fr }
.assistant-panel.voz:not(.texto) > header, .assistant-panel.voz:not(.texto) .assistant-messages, .assistant-panel.voz:not(.texto) .assistant-compose { display: none }
.assistant-panel > header .icon.assistant-back-voice { display: none }
/* preenchido, e não só tingido: ao lado dele mora o alternador de voz, que também é um ícone de som
   num círculo claro — dois botões iguais aqui viram adivinhação sobre qual volta para o palco */
.assistant-panel.voz.texto > header .icon.assistant-back-voice { display: grid; background: var(--ink); color: var(--s1) }
.assistant-stage { display: none }
.assistant-panel.voz:not(.texto) .assistant-stage { display: grid }
/* ── celular: além da voz, a agente ocupa a tela toda ──
   O tamanho vem do `inset: 0`, e as medidas da regra do cartão são desfeitas de propósito. `100dvh`
   encolhe quando a barra do endereço reaparece e quando o navegador abre a faixa de permissão do
   microfone — ou seja, a tela inteira virava tela quase inteira no instante em que a pessoa toca a
   esfera, e o cartão parecia se redimensionar sozinho. Com `auto`, o painel fica preso ao retângulo
   e o navegador pode mexer na barra dele à vontade. */
.assistant-panel.mobile { position: fixed; inset: 0; width: auto; height: auto; max-width: none; max-height: none; border: 0; border-radius: 0; box-shadow: none; overscroll-behavior: none }
.assistant-panel.mobile.open { animation: fade .18s var(--ease) }
/* a esfera de abrir fica no canto de baixo à direita e cairia em cima do seletor de modo */
.assistant-panel.mobile.open ~ .assistant-launch { display: none }
/* no modo escrito do celular a pílula "Ao vivo" sai do cabeçalho: com ela, os botões somam mais que a
   largura de um celular e transbordam sem barra de rolagem. No cartão do computador ela cabe e fica. */
.assistant-panel.mobile.texto .assistant-live { display: none }
.assistant-stage { grid-template-rows: auto 1fr auto auto; min-height: 0; background: var(--s1); padding: max(14px, env(safe-area-inset-top)) 16px max(18px, env(safe-area-inset-bottom)) }
.assistant-stage-top { display: flex; align-items: center; justify-content: space-between; gap: 10px }
.assistant-stage-top b { font-size: 14px }
.assistant-stage-top > span, .assistant-stage-top .icon { width: 40px; height: 40px; flex: none }
.assistant-stage-top .icon { padding: 0; border: 0; border-radius: 50%; display: grid; place-items: center; background: var(--s3); color: var(--ink2); cursor: pointer }
.assistant-stage-top .icon svg { width: 18px; height: 18px }
.assistant-stage-core { position: relative; display: grid; place-items: center; min-height: 0 }
/* o anel é decoração que reage ao som: fica atrás da esfera e não intercepta o toque dela */
.assistant-spectrum { position: absolute; inset: 0; width: 100%; height: 100%; color: var(--acc1); pointer-events: none }
.assistant-stage-orb { position: relative; padding: 0; border: 0; background: none; cursor: pointer; display: grid; place-items: center; -webkit-tap-highlight-color: transparent }
.assistant-stage-orb .assistant-orb { width: min(42vw, 190px); height: min(42vw, 190px) }
.assistant-stage-orb:active .assistant-orb { transform: scale(.96) }
/* no palco não existe a pílula vermelha do cabeçalho; o aro em volta da esfera é o único aviso
   de que o microfone está aberto, e precisa existir para a pessoa saber que a sala está sendo ouvida */
.assistant-panel.em-chamada .assistant-stage-orb .assistant-orb { box-shadow: inset 0 0 18px color-mix(in srgb, var(--inkHi) 24%, transparent), 0 0 0 4px var(--berrySoft), 0 0 34px var(--accSoft) }
/* Altura travada: "pronto", "conectando…" e "ao vivo · pode falar" não têm o mesmo tamanho, e a dica
   quebra em duas linhas em tela estreita. Se este bloco crescer, ele rouba altura do núcleo — que é
   `1fr` — e a esfera pula de lugar a cada mudança de estado. Reservado o pior caso, nada se mexe. */
.assistant-stage-copy { display: grid; gap: 4px; align-content: center; text-align: center; height: 78px; box-sizing: border-box; padding: 6px 0 16px }
.assistant-stage-status { font-size: 15px; line-height: 20px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
.assistant-stage-hint { font-size: 12px; line-height: 16px; color: var(--mute); display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden }
.assistant-modes { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; padding: 4px; border-radius: var(--pill); background: var(--s3) }
.assistant-modes button { height: 44px; padding: 0 8px; border: 0; border-radius: var(--pill); background: transparent; color: var(--mute); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; transition: background .16s, color .16s }
.assistant-modes button.on { background: var(--s1); color: var(--ink); box-shadow: 0 2px 10px -4px var(--scrim) }
/* Os dois nunca aparecem juntos e têm a mesma altura: o palco continua com quatro linhas e a esfera
   não muda de lugar quando a chamada começa ou termina. */
.assistant-stage-end { display: none; height: 52px; padding: 0 12px; border: 0; border-radius: var(--pill); background: var(--berry); color: var(--onSemantic); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer }
.assistant-panel.em-chamada .assistant-stage-end { display: block }
.assistant-panel.em-chamada .assistant-modes { display: none }

/* Controles do cartão Assistente GPT. */
.assistant-settings-card { padding: 22px; display: grid; gap: 12px; align-content: start }
.voice-control { display: grid; gap: 6px; color: var(--ink2); font-size: 12px }
.voice-speed { display: grid; gap: 8px; padding: 12px 14px; border-radius: 14px; background: var(--s2) }
.voice-speed-head, .voice-speed-scale, .voice-test-row { display: flex; align-items: center; justify-content: space-between; gap: 8px }
.voice-speed-head { color: var(--ink2); font-size: 12px }
.voice-speed-value { min-width: 48px; padding: 3px 8px; border-radius: var(--pill); background: var(--s3); color: var(--ink); font: inherit; font-weight: 700; text-align: center }
.voice-speed input[type="range"] { width: 100%; accent-color: var(--acc1) }
.voice-speed-scale { color: var(--mute) }
.voice-test-row { justify-content: flex-start; flex-wrap: wrap }
.agent-permission { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 14px; padding: 13px 14px; border-radius: 14px; background: var(--s2); cursor: pointer }
.agent-permission-copy { display: grid; gap: 3px }
.agent-permission-copy b { font-size: 13px }.agent-permission-copy small { color: var(--mute); font-size: 11px }
.agent-permission input { position: absolute; opacity: 0; pointer-events: none }
.agent-switch { width: 44px; height: 25px; padding: 3px; border-radius: var(--pill); background: var(--s4); transition: background .18s }
.agent-switch::after { content: ""; display: block; width: 19px; height: 19px; border-radius: 50%; background: var(--s1); box-shadow: 0 2px 8px var(--scrim); transition: transform .18s var(--ease) }
.agent-permission input:checked + .agent-switch { background: var(--leaf) }
.agent-permission input:checked + .agent-switch::after { transform: translateX(19px) }
.agent-permission input:focus-visible + .agent-switch { outline: 2px solid var(--acc1); outline-offset: 2px }

/* gráficos */
.chart { width: 100%; height: 190px; display: block; overflow: visible }
.chart .area { fill: url(#areaGrad); opacity: 0; animation: fade-in .9s .6s var(--ease) forwards }
.chart .line { fill: none; stroke: var(--ink); stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 2000; stroke-dashoffset: 2000; animation: draw 1.4s var(--ease) forwards }
.chart .dot { fill: var(--ink); stroke: var(--l2); stroke-width: 3; opacity: 0; transition: opacity .15s } .chart .col:hover .dot { opacity: 1 }
.chart .lbl { fill: var(--mute); font-size: 10px; font-family: var(--body) } .chart .tip { fill: var(--ink); font-size: 11px; font-weight: 600; opacity: 0; transition: opacity .15s; font-family: var(--body) } .chart .col:hover .tip { opacity: 1 }
.chart .col rect { fill: transparent } .chart .col:hover rect { fill: var(--zebra) }
@keyframes draw { to { stroke-dashoffset: 0 } } @keyframes fade-in { to { opacity: 1 } }
.bar { display: flex; align-items: flex-end; gap: 6px; height: 160px; padding: 8px 0 }
.bar div { flex: 1; background: var(--s4); border-radius: 6px 6px 2px 2px; min-height: 2px; position: relative; transform-origin: bottom; animation: grow .7s var(--ease) both; animation-delay: calc(.03s * var(--i, 0)) }
.bar div:hover { background: var(--ink) } @keyframes grow { from { transform: scaleY(0) } }
.bar div b { position: absolute; bottom: 100%; left: 0; right: 0; text-align: center; font-size: 10px; color: var(--mute); font-weight: 500; margin-bottom: 3px }
.bar div s { position: absolute; top: 100%; left: 0; right: 0; text-align: center; font-size: 10px; color: var(--mute); text-decoration: none; margin-top: 5px; white-space: nowrap; overflow: hidden }

/* ── marca: o mesmo bloco (desenho + nome) na entrada, no caixa e na loja ── */
/* Uma linha só, com o desenho e o nome centrados: um no outro e no espaço que sobrar. O <i> precisa
   de caixa própria (grid) — enquanto era inline, o logo ficava pendurado na linha de base do texto,
   torto ao lado do nome, e o tamanho em porcentagem da tela de Aparência não tinha onde pegar. */
.brand { display: flex; align-items: center; justify-content: center; gap: 10px; font-family: var(--display); font-weight: 600; font-size: 16px; color: var(--ink); text-decoration: none }
.brand > i { width: 30px; height: 30px; border-radius: 10px; background: var(--ai); color: var(--onAcc); display: grid; place-items: center; flex-shrink: 0 }
.brand > i svg { width: 17px; height: 17px }

/* ── PDV ── */
header.top { display: flex; align-items: center; gap: 12px; padding: 0 18px; height: 60px; flex-shrink: 0 }
header.top .brand { padding: 0 } header.top .brand > i { width: 32px; height: 32px; border-radius: 11px; transition: transform .12s var(--ease) }
/* no caixa o logo é a porta de volta para o painel de aplicativos, então precisa parecer clicável —
   sem isso o caixa não descobre que dá para clicar ali. */
header.top a.brand:hover > i { transform: scale(1.08) } header.top a.brand:active > i { transform: scale(.94) }
/* três colunas: grade 1/4, ficha do produto 2/4, venda 1/4 — a ficha é a coluna grande, porque é ela
   que o cliente do outro lado do balcão lê (foto do produto, nome em corpo 56, preço, tabela
   nutricional); a grade é só a mão de quem opera. O `minmax(330px, 1fr)` da venda continua porque
   proporção pura espremia a coluna do dinheiro a nada em tela de 13"; os outros dois abrem em
   `minmax(0, …)` para que 1:2 seja 1:2 de verdade, sem o piso de conteúdo do `1fr` cru puxando a
   grade para mais largura. Abaixo de 1100px a ficha some (ver o @media lá embaixo). */
.pos { flex: 1; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr) minmax(330px, 1fr); min-height: 0; gap: 12px; padding: 0 12px 12px }
/* mesmo motivo do isolate do <main> (linha acima): qualquer painel opaco ENTRE o cartão e o contexto de
   empilhamento engole o halo, porque o ::before de z-index:-1 vai parar atrás do fundo do painel. Aqui o
   contexto era o <main>, então o brilho das pastilhas de produto era pintado debaixo do fundo do .left e
   só sobrava a borda animada. Denunciava o problema o item esgotado, o único que acendia: .tile.dim tem
   opacity < 1, e opacity < 1 já cria contexto de empilhamento sozinho. */
.left { display: flex; flex-direction: column; min-height: 0; background: var(--s1); border-radius: var(--r); isolation: isolate }
.left .search { margin: 14px 16px 6px; max-width: none } .left .search .field { background: var(--s2); font-size: 16px; padding: 13px 44px 13px 44px } .left .search svg { top: 15px } .left .search .kbd { position: absolute; right: 14px; top: 15px }
.tabs { display: flex; gap: 4px; padding: 4px 16px 10px; overflow: auto }
.pgrid { flex: 1; overflow: auto; padding: 0 16px 16px; display: grid; grid-template-columns: repeat(auto-fill, minmax(172px, 1fr)); gap: 10px; align-content: start }
.tile { padding: 16px; text-align: left; cursor: pointer; border-radius: 18px; background: var(--patCardBg), linear-gradient(var(--s2), var(--s2)), var(--under); transition: background .15s, transform .1s var(--ease); display: flex; flex-direction: column; gap: 10px; min-height: 108px }
/* o hover repete a camada de padronagem porque `background` é atalho: escrever só a cor aqui apagaria
   o `background-image` e a padronagem sumiria debaixo do mouse. */
.tile:hover { background: var(--patCardBg), linear-gradient(var(--s3), var(--s3)), var(--under) } .tile:active { transform: scale(.97) } .tile.dim { opacity: .35; cursor: not-allowed }
.tile .n { font-weight: 500; line-height: 1.25; color: var(--ink2) }
/* o fornecedor mora dentro do `.n`, colado no nome, e não como irmão dele: o `.tile` é uma coluna
   flex com `gap: 10px`, então irmão nasceria a dez pixels de distância — longe do nome que ele
   qualifica. Reticências porque são duas palavras de razão social ("PURAVIDA-NEO VIDA") numa
   pastilha de 172px. */
.tile .forn { margin-top: 3px; font-size: 11px; font-weight: 500; color: var(--mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis } .tile .p { margin-top: auto; display: flex; justify-content: space-between; align-items: baseline; font-family: var(--display); font-variation-settings: "opsz" 36, "SOFT" 50; font-weight: 500; font-size: 19px } .tile .p small { font-family: var(--body); font-weight: 500; font-size: 12px; color: var(--mute) }
/* A pastilha do caixa é o cartão da bancada beta, **por decisão do dono (2026-09-17)**: a foto tem
   BLOCO PRÓPRIO (`.ph`, 108px de altura) em cima, e debaixo dela o nome, o fornecedor, o estoque e o
   preço, cada um no seu lugar. Antes a foto era o FUNDO do cartão e o texto era escrito por cima
   dela, o que custava duas coisas que agora deixam de existir: o texto só era legível debaixo de um
   véu de gradiente (o `--tileVeu`, que saiu junto daqui e do pdv.html), e a foto era recortada
   `cover` pelo formato do cartão — a fruta fotografada de frente chegava ao caixa como uma faixa do
   meio dela. Com bloco próprio a foto tem um formato só e o texto não disputa espaço com ela.

   Por isso a fileira é `max-content`, e não um comprimento: a altura do cartão é a soma do que está
   dentro dele, e o nome pode quebrar em duas linhas. Cravar altura aqui é o defeito que esta grade
   já teve duas vezes — em 2026-09-15 com fileira de 180px e a pastilha se desenhando com 230
   (topos medidos em 180/310/441), e em 2026-09-17 com a fileira em metade da coluna, onde 71px
   esmagaram o bloco da foto a 2px. Nas duas, como `.tile` não tem `overflow: hidden`, o preço de uma
   pastilha foi escrito por cima do nome da outra. E `max-content`, não `auto`: `auto` colapsa a
   fileira a 25px, porque `.tile` é um <button> (tem tamanho mínimo automático próprio, que não é o
   `min-height: auto` de um <div>) e o `min-height: 0` daqui zera o piso que sobraria. Medido na
   tela: `auto` 25px e `fit-content(400px)` 25px contra 229px de conteúdo real; `min-content` e
   `max-content`, os 229px honestos.

   Quem depende disso é o `cabem()` do pdv.html: ele lia o lado da pastilha em `grid-auto-rows`, e
   com `max-content` não há mais número para ler ali — passou a medir a fileira DESENHADA.

   A largura da coluna continua CALCULADA (`--col`), nunca `1fr`, por um motivo que sobreviveu à
   mudança: `1fr` divide a sobra em fração e devolve coluna quebrada (214,656px), e meio pixel de
   diferença encolhia o vão vertical para 9,34px contra 10px do horizontal — o respiro desigual que
   era para sumir. A conta faz à mão o que o `1fr` faria: quantas colunas de `--min` cabem, e a sobra
   dividida entre elas, com `round(down, …, 1px)` cortando a fração. `100cqw` e não `100%` porque
   unidade de container é comprimento de verdade; o `container-type` vive no `.left`, e não aqui,
   porque um elemento não pode medir a si mesmo.

   O escopo `.left` é obrigatório: `.tile` também veste ícone de app, pastilha de tema e cartão de
   seção, e nenhum desses tem foto em cima. `.left` só existe no caixa (pdv.html e a bancada
   pdv-beta) — e as regras do cartão moram aqui, e não no `<style>` do beta onde nasceram, porque
   agora as duas telas mostram o MESMO cartão: deixar cópia no beta seria duas verdades para a
   próxima pessoa que mexer numa delas. */
.left { container-type: inline-size }
.left .pgrid {
  --gap: 10px; --min: 172px; --avail: calc(100cqw - 32px);
  --cols: round(down, calc((var(--avail) + var(--gap)) / (var(--min) + var(--gap))), 1);
  --col: round(down, calc((var(--avail) - (var(--cols) - 1) * var(--gap)) / var(--cols)), 1px);
  gap: var(--gap); grid-template-columns: repeat(var(--cols), var(--col)); grid-auto-rows: max-content; justify-content: start;
}
.left .pgrid .tile { min-height: 0; padding: 10px 10px 12px; gap: 5px }
/* `flex-shrink: 0` na foto não é zelo: bloco de altura fixa dentro de coluna flex continua sendo item
   flexível, e foi assim que ela virou uma tira de 2px no dia em que a fileira apertou. */
.left .pgrid .tile .ph { height: 108px; flex-shrink: 0; border-radius: 13px; background: var(--s3) center/cover no-repeat; display: grid; place-items: center; color: var(--mute); font-size: 11px; margin-bottom: 3px }
/* `--ink` e não o `--ink2` do `.tile` genérico: ali o nome era escrito sobre foto e o tom mais fraco
   ajudava; aqui ele está sobre a camada do cartão e é a primeira coisa que o operador procura. */
.left .pgrid .tile .n { color: var(--ink) }
.left .pgrid .tile .st { font-size: 11.5px; color: var(--mute) }
/* "a caminho": o que já foi pedido ao fornecedor e ainda não chegou. Mora colado no estoque porque a
   pergunta do balcão é uma só ("tem tomate?") e a resposta tem duas partes. `--honey` e não `--berry`:
   esgotado-com-pedido-em-aberto não é a mesma notícia que esgotado-e-nada-vindo, e o tom é o que
   distingue as duas sem obrigar a ler.
   `.wait` existe porque `.tile.dim` apaga a pastilha a 35% — legível para "não venda isto", ilegível
   para a única informação que a atendente precisa passar ao cliente. A 60% ela continua recuada (não
   dá para vender) e volta a dar para ler. */
.left .pgrid .tile .st .vem { color: var(--honey); margin-left: 6px }
.left .pgrid .tile.dim.wait { opacity: .6 }
.left .pgrid .tile .p { font-size: 17px; align-items: center }
/* o "+" NÃO é botão: `.tile` já é um <button>, e botão dentro de botão é HTML inválido (o navegador
   desmonta a árvore). É o sinal de que o cartão inteiro adiciona — por isso apaga junto no `.dim`. */
.left .pgrid .tile .add { width: 30px; height: 30px; border-radius: 99px; background: var(--ink); color: var(--l0); display: grid; place-items: center; font-family: var(--body); font-size: 17px; font-weight: 600; line-height: 1 }
.left .pgrid .tile.dim .add { background: var(--s3); color: var(--mute) }
@media (max-width: 560px) { .left .pgrid { --min: 140px } }
/* ficha do produto sob o mouse (ou da linha escolhida na venda). Só leitura: não é .tile de propósito,
   senão herdaria o cursor de clique e o anel de hover das pastilhas e pareceria botão. */
/* `--recuo` é o respiro lateral desta coluna, e vale para TODO filho dela (2026-09-17, pedido do
   dono: "todo conteúdo da coluna do meio deve obedecer a mesma margem, que deve ser a que está na
   tabela nutricional"). Os 3% nasceram na moldura da tabela nutricional — colada nas bordas da
   coluna ela parecia parte dela, e 8% pareceu exagero — mas o resultado era uma ficha com duas
   margens: foto, nome, preço e selos a 16px da borda, a tabela a 35,6px. O olho lia como
   desalinhamento, porque é.
   Os 3% são do `.peek` e não mais da `.nfacts` para haver um número só; e como margem em % conta
   sobre a caixa de conteúdo do pai, o recuo da tabela não mudou nem um pixel ao mudar de lugar.
   O `padding` do `.peek` foi a zero e os 16px dele passaram para dentro do recuo de cada filho
   (`calc(var(--recuo) + 16px)`), porque a foto tem de encostar na borda do cartão: `overflow`
   corta no limite do padding, então com padding no pai margem negativa nenhuma levaria a foto
   até lá. Sem padding lateral o recuo em % passou a contar sobre a largura CHEIA do cartão — ~1px
   mais de respiro do que antes, invisível, e vale por a conta ter um lugar só. O respiro de cima e
   de baixo virou margem do primeiro e do último filho, com a `.pkTop` de fora: é ela que leva a
   foto às duas pontas. */
.peek { display: flex; flex-direction: column; gap: 12px; min-height: 0; overflow: auto; padding: 0; background: var(--s1); border-radius: var(--r); --recuo: 3% }
.peek > * { margin-inline: calc(var(--recuo) + 16px) }
/* `:not(.pkTop)` porque a dupla foto+leitura é a única que vai de ponta a ponta; e a
   especificidade extra do `:not()` é o que faz esta regra ganhar de `.peek h3 { margin-block: 0 }`,
   que senão apagaria o respiro de cima na tela da cesta — empate de especificidade, e o `h3` vem
   depois no arquivo. */
.peek > *:first-child:not(.pkTop) { margin-top: 16px }
.peek > *:last-child:not(.pkTop) { margin-bottom: 16px }
/* A ficha abre em duas colunas (2026-09-17, pedido do dono): foto à esquerda, tudo o que se lê à
   direita, e a tabela nutricional dentro da coluna da direita. A medida da foto mudou SETE vezes
   no mesmo dia (1:1, 2:1, 9:16, altura cheia com 9:16 de piso, metade a metade com 3% de avanço,
   colada em três bordas do cartão e agora 3/4) — e cada uma fazia sentido com o
   layout de quando foi pedida: em coluna única a foto deitada era a única que deixava o nome e o
   preço acima da dobra; em duas colunas quem manda na altura é o cartão, e a foto acompanha.
   Não crave altura nem largura aqui: a largura vem da faixa do grid e a altura vem do cartão —
   foi `width: 100%` que vazou quando a coluna ganhou `--recuo`, porque 100% mede a caixa de
   conteúdo inteira e a margem soma por fora.
   A coluna da foto tem **3/4** e a da leitura **1/4**, e a da direita avança 3% sobre a da foto
   (pedido do dono, 17/09). A foto não paga recuo nenhum e a coluna de texto paga o recuo cheio
   (`3% + 16px`) só do lado direito, porque ela é conteúdo e conteúdo mantém a margem. Então o par
   ocupa 100% − 16px e o 3 para 1 reparte isso: **foto = 75% − 12px** e **texto = 25% − 4px** —
   cada uma cede a SUA parte dos 16px (três quartos e um quarto), que é o único jeito de a
   proporção sair exata; reparte em partes iguais e o 3 para 1 sai torto.
   Na prática: faixas `3fr 1fr` com `gap: 0`, +12px no fim da foto, −3cqi − 12px no começo do
   texto (o avanço de 3% mais os 12px que a foto já recuou da divisa) e 3cqi + 16px no fim dele.
   Em 1440 dá 510px para a foto e 170px para o texto, com 20,9px de avanço.
   O texto pisa na faixa da foto exatamente onde o véu do `::after` já é a cor cheia do cartão, então
   ele cai em fundo limpo — as duas regras se sustentam, e mexer numa pede olhar a outra.
   A tabela nutricional mora DENTRO da coluna da direita, debaixo da descrição — começou atravessando
   as duas e o dono corrigiu no mesmo dia. Por isso a coluna da direita é a alta, e a foto acompanha:
   ver `align-self` na regra da `.shot`. O que não couber na primeira tela rola (`overflow: auto`).
   `container-type: inline-size` existe aqui pelas contas que precisam da largura DESTA caixa e não
   da faixa de cada coluna: como a `.pkTop` vai de ponta a ponta, `1cqi` é 1% do cartão — o mesmo
   1% que o `--recuo` mede — e por isso o recuo e o avanço do texto saem em `cqi`. Em porcentagem
   não sairia: margem em % num item de grid conta sobre a FAIXA dele, que agora é um quarto de um
   lado e três quartos do outro — as duas contas sairiam erradas, e cada uma de um jeito. */
/* `flex: 1 0 auto` faz a dupla de colunas ocupar a altura que SOBRA do cartão, e não só a altura do
   próprio conteúdo: sem isso a foto terminava onde a tabela nutricional terminava e ficava um vão
   morto embaixo ("até o fundo... tem espaço sobrando", 2026-09-17). Grow 1 para crescer, shrink 0
   para NÃO encolher — quando o conteúdo passa da tela quem rola é o `.peek`, e deixar encolher aqui
   espremeria a ficha em vez de rolar. */
.peek .pkTop { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 1fr); gap: 0; align-items: stretch; container-type: inline-size; flex: 1 0 auto; margin: 0 }
/* A coluna da direita é conteúdo, então mantém a margem inteira em três lados: 16px em cima e
   embaixo e `3cqi + 16px` na direita, o mesmo recuo dos outros blocos da ficha. Só o começo dela
   é negativo, e são duas coisas somadas: o avanço de 3% sobre a foto e os 12px que a foto recuou
   da divisa das faixas por causa da conta do 3 para 1.
   `z-index: 1` não é enfeite: o véu do `.shot::after` é posicionado, e elemento posicionado pinta
   por cima do texto de irmão não posicionado mesmo vindo antes no HTML — sem isso a cor do cartão
   cobriria a primeira letra de cada linha desta coluna. */
.peek .pkInfo { display: flex; flex-direction: column; gap: 8px; min-width: 0; margin: 16px calc(3cqi + 16px) 16px calc(-3cqi - 12px); position: relative; z-index: 1 }
/* A foto é a única coisa da ficha sem margem: encosta na borda de cima, na de baixo e na da
   esquerda do cartão (pedido do dono, 17/09 — "colada nas extremidades do card... o conteúdo
   permanece mantendo a margem"). E chega lá sem margem negativa nenhuma: quem vai de ponta a
   ponta é a `.pkTop` (`margin: 0`, e de fora do respiro de primeiro/último filho), e o `padding`
   do `.peek` é zero — com padding no pai o `overflow` cortaria a foto no limite dele.
   Na vertical quem estica é o `align-self: stretch`, numa linha que o `flex: 1 0 auto` mandou
   ocupar a altura que sobra do cartão; na horizontal a largura é a da faixa menos os 12px da
   conta do 3 para 1. `aspect-ratio` saiu daqui de propósito: com altura e largura definidas ele
   não tem o que decidir, e regra morta engana quem for ler depois.
   O piso de 9:16 que morava aqui (`min-height`) saiu em 17/09, quando a foto foi para 3/4 da
   largura: 9:16 de 510px dá 907px, mais alto que o cartão (828px), e `min-height` num item de
   grid empurra a altura da LINHA — a ficha inteira passaria a rolar, que é justamente o que o
   dono proibiu ("nenhuma coluna deve ficar pra fora do card em hipótese alguma"). O piso existia
   para a foto não virar um quadrado em produto sem tabela nutricional, e isso hoje quem resolve
   é o `flex: 1 0 auto` da `.pkTop`: a dupla já ocupa a altura toda do cartão, tenha o texto ao
   lado 258px ou 800px. Se um dia a foto voltar a ser estreita, o piso volta a fazer sentido.
   `border-radius: 0` porque quem arredonda os cantos dela agora é o próprio cartão, que corta o
   que passa da borda — raio próprio aqui abriria um bico entre a foto e o canto do cartão.
   Consequência aceita: com `cover` a faixa de 510×828 corta em cima e embaixo o que for mais
   estreito que ela, e nas laterais o que for deitado. É o preço de a foto encostar nas pontas. */
.peek .shot { position: relative; align-self: stretch; margin-inline-end: 12px; border-radius: 0; background: var(--s3) center/cover no-repeat; display: grid; place-items: center; color: var(--mute); font-size: 12px }
/* A foto não termina numa borda: este véu derrama a cor do cartão sobre o lado direito dela, para
   a imagem mesclar com o fundo em vez de cortar em linha reta (pedido do dono, 2026-09-17).
   É pseudo-elemento e não uma camada de `background` porque a foto chega em `style=` inline, no
   HTML, e `background-image` inline ganha de qualquer `background-image` escrito aqui.
   `var(--s1)` é a cor do próprio `.peek`, então o degradê acaba exatamente na cor que está atrás —
   tema que troque o fundo do cartão por padrão ou imagem quebraria a ilusão.
   Começa em 50% para o miolo da foto ficar intacto, e fecha em 90% e não em 100% por causa do
   avanço da coluna de texto: os últimos 4,1% da foto ficam POR BAIXO do texto e têm de ser cor
   cheia, senão a imagem aparece por trás das letras e por dentro da moldura da tabela nutricional,
   que é vazada. `border-radius: inherit` para o véu não vazar dos cantos e `pointer-events: none`
   para ele não comer clique nenhum. */
.peek .shot::after { content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; background: linear-gradient(to right, transparent 50%, var(--s1) 90%) }
/* 56px é tamanho de cartaz, não de rótulo: o caixa lê o nome do item de longe, sem se debruçar na tela.
   Nesse corpo a `opsz` sobe para 96 (a fonte afina os traços em tamanho grande, senão engorda) e o
   `line-height` fecha para 1.02, porque entrelinha de texto corrido em duas palavras grandes abre um
   buraco. `anywhere` é a rede de segurança: nome comprido quebra dentro da palavra em vez de vazar
   da coluna — feio é melhor que ilegível.
   `margin-block: 0` e não `margin: 0`: o zero das quatro bordas também apagava o `--recuo` que o
   `.peek > *` dá de lado (seletor mais específico ganha), e o nome ficava 19px à esquerda de todo
   o resto da ficha — justamente o desalinhamento que o `--recuo` existe para acabar. */
.peek h3 { font-family: var(--display); font-variation-settings: "opsz" 96, "SOFT" 50; font-weight: 500; font-size: 56px; line-height: 1.02; letter-spacing: -.02em; margin-block: 0; overflow-wrap: anywhere }
.peek .price { font-family: var(--display); font-variation-settings: "opsz" 96, "SOFT" 50; font-weight: 500; font-size: 32px; letter-spacing: -.02em; margin-top: -4px }
.peek .price small { font-family: var(--body); font-size: 13px; font-weight: 500; color: var(--mute) }
.peek .badges { display: flex; flex-wrap: wrap; gap: 6px }
/* o fornecedor aqui sai com o nome INTEIRO, e na pastilha da grade sai com duas palavras: na
   pastilha ele divide 150px com o nome do produto, aqui tem coluna própria e quem lê a ficha está
   perguntando de quem é aquilo. */
.peek .forn { font-size: 13px; color: var(--mute) }
/* `pre-wrap` porque a descrição é digitada em `<textarea>`: parágrafo que o lojista separou com
   Enter tem de continuar separado na tela, senão o texto dele chega colado num bloco só. */
.peek .desc { font-size: 13.5px; line-height: 1.5; color: var(--ink2); white-space: pre-wrap; margin: 0 }
.peek .nuthead { font-weight: 600; font-size: 12.5px; color: var(--mute); margin-bottom: 4px }
/* só respiro em cima e embaixo: o de lado agora é o `--recuo` do `.peek`, e os 6px que havia aqui
   somavam por dentro — a frase começava 6px à direita da foto e da tabela. */
.peek .empty { padding: 30px 0; font-size: 13px; line-height: 1.5 }
/* Rótulo nutricional no desenho clássico de embalagem (o "Nutrition Facts" do FDA): moldura grossa,
   título pesado, três espessuras de divisória (grossa 8px / média 4px / fio), valor colado no nome,
   %VD em coluna própria à direita e subitens indentados. Só a ESTRUTURA vem do modelo — cor é sempre
   do tema (--ink para barras e texto, --line para fios), e a língua é a nossa. Os cantos são retos de
   propósito: é a reprodução de um rótulo, não um cartão da interface. */
/* o respiro lateral começou aqui e mudou de casa em 2026-09-17: virou o `--recuo` do `.peek`, porque
   o dono quis a mesma margem para a coluna toda. Não repetir aqui — somaria duas vezes. */
.nfacts { border: 2px solid var(--ink); padding: 8px 10px; font-size: 12.5px; line-height: 1.3; color: var(--ink) }
.nfacts .nfTitle { font-weight: 800; font-size: 25px; line-height: 1.02; letter-spacing: -.015em; margin-bottom: 5px }
.nfacts .nfServing { font-weight: 700; font-size: 13.5px; padding: 4px 0; border-top: 1px solid var(--line) }
.nfacts .nfBar { height: 8px; background: var(--ink); margin: 3px 0 }
.nfacts .nfBar.mid { height: 4px }
.nfacts .nfAmt { font-weight: 700; font-size: 10.5px; padding-top: 2px }
.nfacts .nfKcal { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-weight: 800; padding-bottom: 2px }
.nfacts .nfKcal span { font-size: 17px }
.nfacts .nfKcal b { font-size: 27px; line-height: 1; font-variant-numeric: tabular-nums; white-space: nowrap }
.nfacts .nfKcal b small { font-size: 13px }
.nfacts .nfVdHead { text-align: right; font-weight: 700; font-size: 10.5px; padding: 3px 0 }
.nfacts .nfRow { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 3.5px 0; border-top: 1px solid var(--line) }
/* linha logo depois de uma barra grossa não leva fio — no modelo a barra já é a divisória */
.nfacts .nfBar + .nfRow { border-top: 0 }
.nfacts .nfRow .n { min-width: 0; overflow-wrap: anywhere }
.nfacts .nfRow .n b { font-weight: 700 }
.nfacts .nfRow.sub { padding-left: 16px }
.nfacts .nfRow.sub2 { padding-left: 32px }
.nfacts .nfRow .vd { font-weight: 700; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums }
.nfacts .nfFoot { font-size: 10.5px; line-height: 1.4; color: var(--ink); padding-top: 4px }
/* Comparação NOVA. Duas barras na MESMA escala de 0 a 100% e nunca normalizadas pela maior: a barra
   é o argumento, e esticar a menor até encostar na maior transformaria 0,3% em "quase igual". Barra
   curta é a notícia. O acento lima marca a linha da loja e a média nacional fica em cinza — não é
   verde-bom/vermelho-ruim, é "este é você / este é o país", que é o que o dado diz. */
.nova { display: flex; flex-direction: column; gap: 6px }
.nova .lin { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; font-size: 13px }
.nova .lin b { font-weight: 600; font-variant-numeric: tabular-nums }
.nova .lin.ref, .nova .lin.ref b { color: var(--mute); font-weight: 500 }
.nova .bar { height: 6px; border-radius: 3px; background: var(--s3); overflow: hidden }
.nova .bar > i { display: block; height: 100%; border-radius: 3px; background: var(--acc); min-width: 2px }
.nova .bar.ref > i { background: var(--faint) }
.nova .bar.zero > i { min-width: 0 }
aside.cart { display: flex; flex-direction: column; min-height: 0; background: var(--s1); border-radius: var(--r) }
.cust { display: flex; align-items: center; gap: 8px; padding: 12px 12px 8px }
.lines { flex: 1; overflow: auto; padding: 0 8px }
.line { display: flex; align-items: center; gap: 8px; padding: 12px 10px; border-radius: 14px; cursor: default; transition: background .15s }
.line.sel { border: var(--bw) solid transparent; background: linear-gradient(var(--s3), var(--s3)) padding-box, conic-gradient(from var(--spin), var(--l4) 0 55%, var(--acc1) 72%, var(--acc2) 84%, var(--l4) 96% 100%) border-box; animation: spin var(--spinDur) linear infinite }
.line:not(.sel) { border: var(--bw) solid transparent } .line.new { animation: pop .5s } @keyframes pop { from { background: var(--accSoft) } }
.line .t { flex: 1; min-width: 0 } .line .t b { display: block; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap } .line .t small { color: var(--mute) }
.qty { display: flex; align-items: center; background: var(--s3); border-radius: var(--pill) } .qty button { background: none; border: 0; padding: 5px 10px; cursor: pointer; color: var(--ink2) } .qty span { width: 26px; text-align: center; font-weight: 600 }
.sum { padding: 14px 16px 16px; background: var(--s2); border-radius: 0 0 var(--r) var(--r); display: flex; flex-direction: column; gap: 10px }
.tot { display: flex; justify-content: space-between; align-items: baseline } .tot b { font-family: var(--display); font-variation-settings: "opsz" 96, "SOFT" 50; font-weight: 500; font-size: 38px; letter-spacing: -.02em }
.modalscrim { position: fixed; inset: 0; background: var(--scrim); display: grid; place-items: center; padding: 16px; z-index: 50; animation: fade .2s } .modalscrim.solid { background: var(--s0) }
.modal { width: 100%; max-width: 480px; overflow: hidden; animation: rise .25s var(--ease); max-height: 92vh; display: flex; flex-direction: column; border-radius: 28px }
@keyframes rise { from { opacity: 0; transform: translateY(12px) scale(.985) } }
.modal header { padding: 22px 24px 8px; display: flex; align-items: center; gap: 8px } .modal .body { padding: 12px 24px 24px; overflow: auto }
.big { font-family: var(--display); font-variation-settings: "opsz" 96, "SOFT" 50; font-weight: 500; font-size: 40px; text-align: center; letter-spacing: -.02em }
.pay { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-bottom: 14px } .pay button { padding: 14px 6px; cursor: pointer; font-weight: 600; font-size: 13px; border: 0; border-radius: 16px; background: var(--s3); color: var(--ink2); transition: background .12s } .pay button.on { background: var(--ink); color: var(--l0) }

/* ── login ── */
.box { width: min(400px, 92vw); padding: 34px; animation: rise .35s var(--ease); border-radius: 30px }
.box .brand { padding: 0 0 6px; font-size: 18px }
.dots { display: flex; gap: 12px; justify-content: center; margin: 22px 0 16px; height: 12px } .dots i { width: 12px; height: 12px; border-radius: 99px; background: var(--s3); transition: background .12s } .dots i.on { background: var(--ink) }
.pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px } .pad button { padding: 16px; border-radius: 16px; border: 0; background: var(--s2); font-family: var(--display); font-size: 22px; font-weight: 500; cursor: pointer; transition: background .1s } .pad button:hover { background: var(--s3) } .pad button:active { background: var(--s4) } .pad .soft { font-family: var(--body); font-size: 14px; color: var(--mute) }
.err { color: var(--berry); font-weight: 600; min-height: 20px; margin-top: 10px; text-align: center }

/* ── loja ── */
.shop { max-width: 1140px; margin: 0 auto; padding: 0 28px 100px }
.shop-head { display: flex; align-items: center; gap: 14px; padding: 22px 0; position: sticky; top: 0; z-index: 10; background: linear-gradient(var(--s0) 75%, transparent) }
.shop-head .brand { padding: 0; font-size: 18px } .shop-head .brand > i { width: 34px; height: 34px; border-radius: 12px }
.hero-shop { padding: 64px 0 34px } .hero-shop h1 { font-size: clamp(44px, 7vw, 84px); font-variation-settings: "opsz" 144, "SOFT" 80; font-weight: 400; letter-spacing: -.02em; line-height: .98; max-width: 720px } .hero-shop h1 em { font-style: italic; font-weight: 300 }
.hero-shop p { color: var(--ink2); font-size: 18px; max-width: 520px; margin: 22px 0 0; line-height: 1.5 }
.cats { display: flex; gap: 6px; overflow: auto; padding: 8px 0 20px }
.pgrid.shop-grid { padding: 0; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; overflow: visible }
.ptile { padding: 20px; display: flex; flex-direction: column; gap: 8px; min-height: 200px; position: relative; overflow: hidden; border-radius: 24px; background: var(--s1); animation: reveal .5s var(--ease) both; animation-delay: calc(var(--i, 0) * 25ms); transition: background .15s }
.ptile:hover { background: var(--s2) }
.ptile .swatch { height: 78px; border-radius: 14px; background: radial-gradient(120% 100% at 20% 0%, var(--c1), var(--c2)); opacity: .8 }
.ptile .n { font-family: var(--display); font-variation-settings: "opsz" 36, "SOFT" 50; font-weight: 500; font-size: 18px; line-height: 1.15 } .ptile .p { margin-top: auto; display: flex; align-items: center; justify-content: space-between } .ptile .p b { font-size: 18px; font-weight: 600 }
.ptile .out { position: absolute; inset: 0; background: color-mix(in srgb, var(--s0) 78%, transparent); display: grid; place-items: center; color: var(--mute); font-weight: 600 }
.cartbar { position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 16px; padding: 8px 8px 8px 22px; border-radius: var(--pill); z-index: 20; animation: slideup .3s var(--ease); background: var(--s2); box-shadow: var(--drop) }
.sheet { position: fixed; inset: 0; z-index: 30; display: flex; justify-content: flex-end; background: var(--scrim); animation: fade .2s }
.sheet .panel { width: 100%; max-width: 500px; height: 100%; background: var(--s1); border-radius: 28px 0 0 28px; display: flex; flex-direction: column; animation: slide .3s var(--ease) }
.steps { display: flex; gap: 6px; padding: 0 28px 8px } .steps i { flex: 1; height: 3px; border-radius: 99px; background: var(--s3) } .steps i.on { background: var(--ink) }
.check { width: 68px; height: 68px; border-radius: 99px; background: var(--leafSoft); display: grid; place-items: center; color: var(--leaf); margin: 0 auto 14px; font-size: 26px; animation: rise .4s var(--ease) }

/* ── painel: grade de 12 colunas com widgets pequenos ──
   Um cartão = uma pergunta respondida. Título em caixa alta pequena para não competir
   com o número; o número é que carrega a hierarquia.
   A grade não tem número fixo de colunas: `auto-fill` cria quantas couberem a partir de 300 px, então
   o mesmo painel mostra 2 cartões por linha num notebook e 6 num monitor grande, sem breakpoint novo.
   Larguras relativas: o normal ocupa 1 coluna, .w6/.w8 ocupam 2, .w12 a linha inteira. */
.dash { display: grid; grid-template-columns: repeat(auto-fill, minmax(var(--colmin), 1fr)); gap: var(--gap) }
.dash > * { grid-column: span 1 }
.dash > .w6, .dash > .w8 { grid-column: span 2 } .dash > .w12 { grid-column: 1 / -1 }
/* o cartão de hoje acompanha a altura da coluna de números ao lado; o gráfico desce até o pé */
.dash > .stat.hero { display: flex; flex-direction: column } .dash > .stat.hero > *:last-child { margin-top: auto }
.wid { padding: 18px 20px; display: grid; gap: 10px; align-content: start }
.wid > h3 { margin: 0; font-family: var(--body); font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase; color: var(--mute) }
.wid .n { font-family: var(--display); font-variation-settings: "opsz" 72, "SOFT" 50; font-weight: 500; font-size: 30px; letter-spacing: -.02em; line-height: 1 }
.sub { color: var(--mute); font-size: 12.5px; line-height: 1.45 }
.delta { font-size: 12px; font-weight: 600 } .delta.up { color: var(--leaf) } .delta.down { color: var(--berry) }
.donut { width: 118px; height: 118px; flex-shrink: 0 }
/* listinha genérica de widget: rótulo à esquerda, número à direita, separador quase invisível.

   `grid-template-columns: minmax(0, 1fr)` parece redundante num grid de uma coluna, e não é. Sem ele
   a única faixa é implícita e `auto`, e faixa `auto` se dimensiona pelo min-content da linha — a
   linha é um flex `nowrap`, então o min-content dela é o nome inteiro sem quebra. A faixa crescia
   junto (medimos 342px dentro de um cartão de 311px) e o número da direita era pintado 53px FORA do
   cartão, porque `.wid` não tem `overflow: hidden`. O `min-width: 0` do `.t` nunca entrava em ação:
   não faltava espaço para o texto, faltava teto para a faixa. `minmax(0, 1fr)` dá o teto.

   O desbotado é `mask-image` e não um gradiente pintado por cima: o cartão tem a camada de padronagem
   da tela de Aparência atrás, e um gradiente com cor de fundo chapada apagaria a padronagem naquela
   faixa. A máscara para em 28% de opacidade em vez de zero para as reticências do `text-overflow`
   continuarem visíveis dentro do trecho desbotado — desbotar até o transparente as apagaria, e a
   pista de que há texto escondido é justamente o que convida o mouse.

   `:not(:has(*))` protege o `rankRow` do app.js, que aninha um `.t` dentro de outro `.t`: mascarar o
   de fora desbotaria também o valor e o fim da barrinha do `.meter`, que não têm texto para esconder.
   Só o `.t` folha (o que contém apenas texto) ganha o efeito.

   No hover a linha volta a `white-space: normal`: o nome completo quebra em mais linhas DENTRO do
   cartão, que foi o pedido — revelar sem empurrar nada para fora. */
.lst { display: grid; grid-template-columns: minmax(0, 1fr) }
.lst > * { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 7px 0; border-top: 1px solid var(--l2) }
.lst > *:first-child { border-top: 0; padding-top: 0 }
.lst .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1 1 auto }
.lst .t:not(:has(*)) { mask-image: linear-gradient(to right, #000 calc(100% - 2.4em), rgba(0, 0, 0, .28)) }
.lst > *:hover .t:not(:has(*)) { white-space: normal; text-overflow: clip; mask-image: none }
.meter { height: 3px; border-radius: 99px; background: var(--s3); overflow: hidden; margin-top: 6px }
.meter i { display: block; height: 100%; border-radius: 99px; animation: wipe .8s var(--ease) both } @keyframes wipe { from { transform: scaleX(0); transform-origin: left } }

/* ── painel com trilha de ícones à esquerda ────────────────────────────────────────────────────────
   A trilha gruda no topo porque quem rola é o <main>, não a janela: num painel comprido ela acompanha
   a leitura em vez de sumir na primeira rolagem. Em tela estreita vira uma fita horizontal — nove
   ícones empilhados comeriam metade da altura de um celular antes do primeiro número aparecer. */
.dashwrap { display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 20px; align-items: start }
.dashbody { min-width: 0 }
.drail { position: sticky; top: 0; display: grid; gap: 2px; padding: 6px; border-radius: var(--r); background: var(--s1); box-shadow: inset 0 1px 0 var(--edge); z-index: 4 }
.drail button { position: relative; display: grid; place-items: center; width: 44px; height: 44px; border: 0; border-radius: 13px; background: none; color: var(--mute); cursor: pointer; transition: background .14s, color .14s }
.drail button:hover, .drail button.on { background: var(--s3); color: var(--ink) }
.drail button.on::before { content: ""; position: absolute; left: -6px; top: 13px; bottom: 13px; width: 3px; border-radius: 99px; background: var(--acc1) }
.drail svg { width: 19px; height: 19px }
/* o nome só aparece ao passar o mouse: ícone sozinho é adivinhação, mas rótulo fixo ao lado
   transformaria a trilha numa segunda barra de navegação concorrendo com a de cima. */
.drail button > s { position: absolute; left: 50px; padding: 6px 10px; border-radius: 9px; background: var(--ink); color: var(--l0); font-size: 12px; font-weight: 600; text-decoration: none; white-space: nowrap; opacity: 0; pointer-events: none; transform: translateX(-4px); transition: opacity .12s, transform .12s }
.drail button:hover > s, .drail button:focus-visible > s { opacity: 1; transform: none }
/* seletor de janela (7/30/90 dias): o mesmo gesto em todos os painéis, ao lado do título */
.dwin { display: flex; gap: 2px; padding: 3px; border-radius: var(--pill); background: var(--s1) }
@media (max-width: 860px) {
  .dashwrap { grid-template-columns: 1fr }
  .drail { position: static; grid-auto-flow: column; grid-auto-columns: 44px; justify-content: start; overflow: auto }
  .drail button > s { display: none }
}

/* ── faixa de alertas: o que precisa de alguém hoje, antes de qualquer número ── */
.alerts { display: grid; grid-template-columns: repeat(auto-fill, minmax(272px, 1fr)); gap: 8px; margin-bottom: 18px }
.al { display: flex; align-items: center; gap: 11px; padding: 11px 15px; border: 0; border-radius: 15px; background: var(--s1); text-align: left; font: inherit; color: inherit; transition: background .15s }
button.al { cursor: pointer } button.al:hover { background: var(--s3) }
.al > i { width: 8px; height: 8px; border-radius: 99px; flex-shrink: 0; background: var(--mute) }
.al.bad > i { background: var(--berry) } .al.warn > i { background: var(--honey) } .al.ok > i { background: var(--leaf) }
.al .t { flex: 1; min-width: 0 } .al .t b { display: block; font-weight: 600; font-size: 13px }
.al .t small { display: block; color: var(--mute); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }

/* ── anel animado no hover: mesmo desenho do .glow, porém efêmero, em cartões e botões ──
   A borda transparente é permanente (com box-sizing: border-box não desloca nada); no hover ela
   ganha o gradiente cônico. --fill é a camada interna do BOTÃO e precisa bater com o fundo dele,
   senão o gradiente vaza por baixo; o cartão não usa --fill (ver o anel recortado abaixo).
   Vem depois das regras de .btn de propósito. */
.card, .tile, .box, .btn { border: var(--bw) solid transparent }
.btn { --fill: linear-gradient(var(--inkHi), var(--inkHi)) }
.btn.ghost { --fill: linear-gradient(var(--s4), var(--s4)) }
.btn.danger { --fill: linear-gradient(var(--berrySoft), var(--berrySoft)), linear-gradient(var(--s1), var(--s1)) }
.btn.ai, .btn.primary-grad { --fill: var(--ai) }
.btn.btn:hover:not(:disabled) {
  background: var(--fill) padding-box, conic-gradient(from var(--spin), var(--l4) 0 55%, var(--acc1) 72%, var(--acc2) 84%, var(--l4) 96% 100%) border-box;
  animation: spin var(--spinDur) linear infinite }
/* O cartão não usa o truque de duas camadas acima, e sim um anel recortado, porque a opacidade do
   cartão é regulável: o truque só esconde o cônico enquanto --fill é opaco, e com o cartão
   translúcido o gradiente aparecia inteiro por baixo do conteúdo — o cartão lavava de roxo no
   hover. Aqui o cônico é pintado num ::after e a máscara vaza só a moldura de --bw, então nada
   depende do fundo ser sólido. O `inset` negativo é porque o pseudo-elemento se posiciona contra a
   caixa de padding e precisa crescer para alcançar a borda.
   Como o anel não depende mais do preenchimento, o cartão não declara --fill nenhum: o hover só
   liga a opacidade do ::after e deixa a cor de fundo de cada elemento em paz (é assim que o
   `.tile:hover` consegue clarear e manter a padronagem ao mesmo tempo). */
:is(.card, .tile, .box):not(.glow)::after {
  content: ""; position: absolute; inset: calc(-1 * var(--bw)); border-radius: inherit; pointer-events: none;
  padding: var(--bw); opacity: 0; transition: opacity .2s var(--ease);
  background: conic-gradient(from var(--spin), var(--l4) 0 55%, var(--acc1) 72%, var(--acc2) 84%, var(--l4) 96% 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0) }
/* o :has() evita que o cartão de fora acenda junto quando o mouse está num filho que já acende */
:is(.card, .tile, .box):not(.glow):hover:not(:has(:is(.card, .tile, .box, .btn):hover))::after {
  opacity: 1; animation: spin var(--spinDur) linear infinite }

/* ── halo no hover: o mesmo brilho difuso do .glow, agora em todo cartão ──
   Antes só o anel da borda acendia; o brilho que escapa para fora era exclusividade do .glow
   permanente, então parecia que o efeito só existia em dois ou três lugares.
   ::before e não ::after porque o ::after do cartão já é o anel da moldura (regra acima).
   z-index:-1 põe o borrão atrás do cartão — só aparece o que transborda dos --haloSize, e aparece
   difuso, porque o desfoque cai inteiro para fora sem nenhum corte no caminho. Quem garante o
   "atrás" é o piso opaco de --under lá em cima (ver o comentário das camadas): sem ele o halo
   atravessava a face do cartão translúcido e a cunha do cônico, que é medida sobre a caixa do
   elemento, cobria meia tela num cartão grande. Recortar o borrão pela área do cartão também
   resolveria a mancha, mas o desfoque é aplicado ANTES da máscara: o corte cai no meio do
   degradê e o que sobra vira um contorno duro, não uma aura. Piso opaco preserva o cônico inteiro.
   O outro lado da mesma moeda está no `reveal` lá em cima: o piso só protege se o halo estiver mesmo
   atrás, e para isso o cartão não pode virar contexto de empilhamento (ver o comentário do .page).
   Consequência (inalterada): cartão dentro de cartão não mostra halo (o fundo do pai cobre), então
   o aninhado ganha um box-shadow equivalente logo abaixo. */
:is(.card, .tile, .box) { position: relative }
:is(.card, .tile, .box):not(.glow)::before {
  content: ""; position: absolute; inset: calc(-1 * var(--haloSize)); z-index: -1; border-radius: inherit; pointer-events: none;
  opacity: 0; filter: blur(var(--haloBlur)); transition: opacity .28s var(--ease);
  background: conic-gradient(from var(--spin), transparent 0 55%, var(--acc1) 72%, var(--acc2) 84%, transparent 96% 100%) }
:is(.card, .tile, .box):not(.glow):hover:not(:has(:is(.card, .tile, .box, .btn):hover))::before { opacity: var(--halo); animation: spin var(--spinDur) linear infinite }
/* ── peso e itálico dos títulos ──
   Uma regra só, e no fim do arquivo de propósito: cada bloco de título já declara `font-weight: 500`
   com a mesma especificidade desta lista, e quem empata perde para quem vem depois. Trocar as treze
   declarações uma a uma daria no mesmo e espalharia por 400 linhas um controle que é um só na tela
   de Aparência. Ficam de fora os títulos que NÃO estão em 500 — total do caixa (700), soma do
   orçamento (800), nome da marca (600): eles carregam ênfase própria, e igualar tudo aqui apagaria
   justamente a hierarquia que o dono não pediu para mexer. */
h1, h2, .stat .v, .stat .b, .calc b, .tile .p, .peek h3, .peek .price, .tot b, .big, .pad button, .ptile .n, .wid .n {
  font-weight: var(--dispWeight); font-style: var(--dispStyle) }
/* aninhado: sem pseudo-elemento, porque ele ficaria escondido atrás do cartão de fora */
:is(.card, .modal, .drawer) :is(.card, .tile, .box):not(.glow):hover:not(:has(:is(.card, .tile, .box, .btn):hover)) {
  box-shadow: 0 0 var(--haloBlur) -4px color-mix(in srgb, var(--acc1) calc(var(--halo) * 60%), transparent) }

@media print { body * { visibility: hidden } .receipt, .receipt * { visibility: visible } .receipt { position: fixed; inset: 0; width: 80mm; margin: auto; font-family: monospace; color: #000; background: var(--inkHi) } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important } .chart .line { stroke-dashoffset: 0 } .chart .area { opacity: 1 } }
/* o antigo breakpoint de 1240 px sumiu: com auto-fill a contagem de colunas se resolve sozinha.
   Sobra só o piso — abaixo de ~640 px cabe uma coluna só, e aí `span 2` transbordaria a tela. */
@media (max-width: 640px) { .dash > *, .dash > .w6, .dash > .w8 { grid-column: 1 / -1 } }
/* a ficha do produto é conforto, não ferramenta: em tela estreita ela roubaria espaço da grade e da
   venda, que são o que o caixa precisa para trabalhar. Some antes de apertar as outras duas. */
@media (max-width: 1100px) { .pos { grid-template-columns: 1fr minmax(340px, 420px) } .peek { display: none } }
@media (max-width: 820px) {
  .where, .lnk { display: none } .lscrim { padding: 28px 16px; gap: 20px }
  /* dois cartões por linha no celular: menos que isso vira lista, mais que isso volta a ser o ícone
     miúdo que o dono reclamou. Aqui a largura é proporcional e não fixa — os 212 px do desktop
     deixariam um cartão só por linha numa tela de 375 px, com folga sobrando dos dois lados. */
  .lpad { gap: 10px } .lbody { gap: 16px } .lgroup { gap: 8px } .tile.appicon { flex-basis: calc(50% - 5px); min-height: 110px; font-size: 14px }
  .g3 { grid-template-columns: 1fr } .pos { grid-template-columns: 1fr } aside.cart { position: fixed; inset: auto 12px 12px 12px; max-height: 52vh }
  .assistant-dock { right: 10px; bottom: 10px } .assistant-panel { width: calc(100vw - 20px); height: calc(100dvh - 82px); border-radius: 20px } .assistant-messages { padding-inline: 14px } .assistant-compose-hint { display: none }
  .assistant-settings-card { padding: 18px } .agent-permission { grid-template-columns: minmax(0, 1fr) auto }
  /* No celular o resumo precisa ocupar a largura útil inteira para continuar visível durante a
     rolagem. As margens negativas cobrem o padding de `main`; sem elas a tabela aparece nas laterais
     e dá a impressão de que o cabeçalho deixou de ser fixo. */
  .stickhead { top: 0; margin-inline: -14px; padding: 12px 14px 10px; background: var(--l0); box-shadow: 0 8px 14px -14px var(--ink) }
  .stickhead > .grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: 7px }
  .stickhead > .grid .card { padding: 11px !important; min-height: 76px }
  .stickhead > .grid .card > div:last-child { font-size: 18px !important }
  .stickhead > .fieldrow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px }
  .stickhead > .fieldrow .btn { width: 100%; min-width: 0; padding-inline: 8px; white-space: normal; line-height: 1.2 }
  .note-edit-head { display: none }
  .note-edit-line { grid-template-columns: minmax(150px,1fr) 76px; padding: 10px; border: 1px solid var(--line); border-radius: var(--r2) }
  .note-edit-line > :nth-child(3), .note-edit-line > :nth-child(4) { min-width: 0 }
  .period-filter { align-items: stretch }
  .period-filter .f { flex: 1 1 130px }
  .period-count { width: 100%; margin-left: 0 }
  /* A esfera ocupa o canto do rodapé da gaveta e cobria justamente o botão principal. Durante um
     diálogo ela some; ao fechar, volta no mesmo lugar e estado. */
  body:has(.scrim) .assistant-dock { display: none }
}

/* ── tela de Aparência ──
   O campo de token é texto + paleta nativa lado a lado, com um controle deslizante embaixo quando
   o valor é uma medida. O texto é a fonte da verdade: a paleta do sistema só entende #rrggbb, e
   quem precisa de rgba() ou color-mix() digita. */
.tk .tkrow { display: flex; gap: 8px; align-items: center }
.tk .tkrow .field { flex: 1; min-width: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px }
.tk span i { font-style: normal; font-weight: 400 }
input.tsw { width: 38px; height: 38px; flex: none; padding: 0; border: 0; border-radius: var(--r2); background: var(--s3); cursor: pointer }
input.tsw::-webkit-color-swatch-wrapper { padding: 4px } input.tsw::-webkit-color-swatch { border: 0; border-radius: 9px }
input.trg { width: 100%; margin: 8px 0 0; accent-color: var(--acc1); cursor: pointer }
/* prévia de gradiente e de sombra: mesma pastilha do seletor de cor, mas só para olhar — não existe
   paleta do sistema para "linear-gradient(…)", então quem manda é o texto ao lado. */
.tprev { width: 38px; height: 38px; flex: none; border-radius: var(--r2); background: var(--s3); box-shadow: inset 0 0 0 4px var(--l1) }
.tile.preset { min-height: 0; gap: 5px; padding: 12px }
.tile.preset .swat { height: 26px; border-radius: 9px; display: block }
/* Prévia do logo. O <i> de dentro repete a placa da barra do topo: é ele que some no "sem fundo" e é
   a caixa dele que a porcentagem estica, então a moldura precisa de folga para o logo a 200% caber. */
.logoprev { width: 124px; height: 124px; border-radius: var(--r2); background: var(--s2); display: grid; place-items: center; color: var(--ink) }
.logoprev > i { width: 50px; height: 50px; border-radius: 16px; background: var(--ai); display: grid; place-items: center; color: var(--onAcc) }
.logoprev > i svg { width: 28px; height: 28px }
/* Prévia da padronagem. O ladrilho entra por `style` inline (é o mesmo que o theme.js gera para a
   tela), então aqui só mora a moldura. Cor de fundo é a camada base do painel para a opacidade do
   desenho ser lida com o mesmo contraste que vai ter de verdade. */
.patprev { height: 132px; border-radius: var(--r2); background-color: var(--s2); border: 1px solid var(--l3) }
/* "×" de apagar tema salvo: botão dentro de botão não existe, então ele é irmão do bloco e só aparece
   quando o ponteiro está por perto — a ação normal é aplicar o tema, apagar é a exceção. */
.mypreset { position: relative } .mypreset > .tile { width: 100% }
.tilex { position: absolute; top: 5px; right: 5px; width: 22px; height: 22px; border: 0; border-radius: 8px; background: var(--s3); color: var(--mute); font: inherit; font-size: 15px; line-height: 1; cursor: pointer; opacity: 0; transition: opacity .15s, background .15s, color .15s }
.mypreset:hover .tilex, .galit:hover .tilex, .tilex:focus-visible { opacity: 1 } .tilex:hover { background: var(--berrySoft); color: var(--berry) }

/* ── galeria de desenhos ──
   Carrossel e não grade: a galeria fica DENTRO do editor de logo e do de padronagem, que já são
   cartões cheios. Uma grade cresceria para baixo e empurraria os controles para fora da tela toda
   vez que o dono guardasse um desenho; a faixa que rola de lado tem altura fixa, então o cartão
   não muda de tamanho entre um desenho e quarenta.
   `scroll-snap` porque o passo do carrossel é uma miniatura, não um punhado de pixels: as setas
   rolam e o navegador encaixa. A barra de rolagem some (o dedo e as setas bastam) mas a rolagem
   continua existindo — é ela que faz o carrossel funcionar em tela de toque sem código nenhum. */
.galwrap { display: grid; grid-template-columns: auto 1fr auto; gap: 6px; align-items: center }
.gal { display: flex; gap: 8px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; padding: 2px }
.gal::-webkit-scrollbar { display: none }
.galit { position: relative; flex: 0 0 76px; scroll-snap-align: start }
.galit > button.use { width: 76px; height: 76px; padding: 6px; border: var(--bw) solid transparent; border-radius: var(--r2); background: var(--s2); color: var(--ink); cursor: pointer; display: grid; place-items: center; transition: background .15s }
.galit > button.use:hover { background: var(--s3) } .galit > button.use svg { max-width: 100%; max-height: 100%; width: auto; height: auto }
.galit.on > button.use { border-color: var(--acc1) }
.galit .cap { display: block; margin-top: 3px; font-size: 10.5px; line-height: 1.2; color: var(--mute); text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.galnav { width: 28px; height: 28px; flex: none; border: 0; border-radius: 9px; background: var(--s3); color: var(--ink2); font: inherit; cursor: pointer; display: grid; place-items: center }
.galnav:hover { background: var(--s4) } .galnav:disabled { opacity: .35; cursor: default }
/* Campo colado no botão que age — o nome do desenho no logo, nas duas padronagens, na galeria e nos
   temas; e também a categoria nova, o número da nota de entrada e o cupom do cliente, que antes eram
   `prompt()`. O campo encolhe (`min-width: 0`) e o botão não (`flex: none`): quem some primeiro numa
   gaveta estreita tem de ser a largura do campo, nunca o rótulo do botão, que é onde está escrito o
   que o clique vai fazer. Abaixo de 420 px viram duas linhas, o botão ocupando tudo. */
.fieldrow { display: flex; gap: 8px; align-items: center; flex-wrap: wrap }
.fieldrow > .field { flex: 1 1 150px; min-width: 0 }
.fieldrow > .btn { flex: none }
@media (max-width: 420px) { .fieldrow > .field { flex-basis: 100% } .fieldrow > .btn { width: 100%; justify-content: center } }
/* "voltar ao padrão" de um token só. Nasce apagado porque a resposta certa quase sempre é não mexer;
   acende junto com o campo, como o "×" do tema salvo. */
.tkdef { border: 0; background: none; color: var(--faint); font: inherit; font-size: 11.5px; cursor: pointer; padding: 0 2px; opacity: 0; transition: opacity .15s, color .15s }
.tk:hover .tkdef, .tkdef:focus-visible { opacity: 1 } .tkdef:hover { color: var(--ink2) }

/* ── lançador de assuntos da Aparência ──
   Um cartão pequeno por assunto, na mesma grade do lançador de aplicativos: a página inteira cabe
   numa tela e o dono escolhe o que quer mexer antes de ver campo nenhum. `auto-fill` e não uma
   contagem fixa de colunas — a página tem quinze cartões e a barra lateral do painel muda de
   largura entre o notebook e o monitor da loja. */
.tsecs { display: grid; grid-template-columns: repeat(auto-fill, minmax(238px, 1fr)); gap: 12px }
/* ── quadro das Configurações ──
   Os mesmos cartões da grade acima, só que empilhados em colunas por etapa do negócio: vender,
   divulgar, medir, manter. A grade solta ordenava por acaso — "Mercado Pago" caía ao lado de
   "Backup" — e com quinze assuntos isso vira uma parede onde nada tem vizinho com sentido. Em
   coluna, o dono que veio configurar anúncio lê um título e já sabe onde não precisa olhar.
   `auto-fit` com mínimo de 250px, e não quatro colunas fixas: no notebook cabem duas, no monitor
   da loja cabem as quatro, e no celular vira uma lista — sem nenhuma consulta de mídia.
   `align-items: start` para que uma coluna de dois cartões não estique os dois até a altura da
   coluna de quatro. */
.tkan { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 14px; align-items: start }
.tkancol { display: flex; flex-direction: column; gap: 10px; min-width: 0 }
/* O título fica sobre a coluna, sem fundo: é uma legenda, não um cartão, e dar-lhe caixa própria
   faria o olho contar cinco elementos onde há quatro mais um rótulo. */
.tkanh { display: flex; align-items: center; gap: 7px; padding: 0 3px 2px; font-size: 11.5px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--mute) }
/* A legenda segue o título em vez de ir para a borda direita da coluna: empurrada para o fim, ela
   encostava no título da coluna seguinte e o olho a lia como subtítulo da vizinha errada. Corta com
   reticências quando não couber — coluna estreita não pode quebrar a legenda em duas linhas e
   desalinhar o topo dos cartões. */
.tkanh small { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--faint) }
.tkancol > .tile.tsec { width: 100% }
/* `backwards` e não `both`, pelo mesmo motivo do lançador: com `both` o cartão fica preso na
   transformação final da animação e o afundar do clique nunca acontece. */
.tile.tsec { text-align: left; color: var(--ink); font: inherit; min-height: 0; padding: 15px; gap: 12px; animation: pop .3s var(--ease) backwards; animation-delay: calc(var(--n) * 12ms) }
.tshead { display: flex; gap: 11px; align-items: flex-start }
.tshead > i { width: 34px; height: 34px; flex: none; border-radius: 11px; background: var(--s3); color: var(--ink); display: grid; place-items: center; transition: background .16s, color .16s }
.tile.tsec:hover .tshead > i { background: var(--ai); color: var(--onAcc) }
.tshead b { display: block; font-size: 14.5px; font-weight: 650; line-height: 1.25 }
.tshead span { display: block; margin-top: 2px; font-size: 11.5px; line-height: 1.35 }
/* A tira de prévia. Quase todas são feitas só de `var(--…)`: como salvar não é o que repinta a tela
   (o `T.apply()` reescreve o `:root` a cada tecla), a tira acompanha a digitação sem uma linha de
   JavaScript. Só as que dependem de dado — logo, temas guardados, galeria, modo aceso — têm função
   de redesenho do outro lado. `margin-top:auto` cola a tira no rodapé para os cartões de descrição
   curta e longa terminarem alinhados. */
.tsprev { height: 46px; margin-top: auto; padding: 8px; border-radius: 12px; background: var(--s3); box-shadow: inset 0 0 0 1px var(--l4); display: flex; align-items: center; gap: 6px; overflow: hidden }
.tslayers i { flex: 1; align-self: stretch; border-radius: 7px }
.tsink b { font-family: var(--display); font-size: 19px; line-height: 1 }
.tsbar { flex: 1; align-self: stretch; border-radius: 8px }
.tsdots i { width: 18px; height: 18px; border-radius: 99px }
.tsshape i { width: 30px; height: 30px; flex: none; border: var(--bw) solid var(--acc1); background: var(--s4) }
.tstype { flex-direction: column; align-items: flex-start; justify-content: center; gap: 1px }
.tstype b { font-family: var(--display); font-weight: var(--dispWeight); font-style: var(--dispStyle); font-size: 17px; line-height: 1.1 }
.tstype span { font-family: var(--body); font-weight: var(--bodyWeight); font-style: var(--bodyStyle); font-size: 11px; line-height: 1.1; color: var(--mute) }
/* A tira de estado das Configurações. Diferente das tiras da Aparência, esta não mostra a cor: ela
   responde de olho batido "como está esse assunto agora?" — 10% sem aprovação, Token guardado,
   Homologação. Duas linhas dentro dos mesmos 46px, então tudo aqui é apertado de propósito e o
   texto corta com reticências: o nome da loja e a razão social não têm tamanho previsível, e sem
   o corte a tira estouraria o cartão. */
.tsstat { flex-direction: column; align-items: flex-start; justify-content: center; gap: 3px }
.tsstat > * { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.tsstat b { font-family: var(--display); font-size: 17px; line-height: 1.1 }
.tsstat > .m { font-size: 11.5px; line-height: 1.1 }
.tsstat .chip { padding: 2px 9px; font-size: 11px }
/* o mesmo ladrilho e a mesma animação do fundo do site, em miniatura: é a única prévia honesta de
   um desenho que anda sozinho. As `@keyframes` do fundo movem duas camadas porque o `body` tem
   duas; aqui só existe uma e o valor sobrando é ignorado sem estrago. */
.tsmove { background-image: var(--patSite); background-size: var(--patSiteSize); background-repeat: var(--patSiteRepeat); animation: var(--bgAnim) var(--bgDur) linear infinite var(--bgDir) }
.tsglow i { width: 26px; height: 26px; border-radius: 9px; background: var(--ai); box-shadow: 0 0 var(--haloBlur) color-mix(in srgb, var(--acc1) calc(var(--halo) * 90%), transparent) }
.tspat-site { background-image: var(--patSite); background-size: var(--patSiteSize); background-repeat: var(--patSiteRepeat) }
.tspat-card { background-image: var(--patCard); background-size: var(--patCardSize); background-repeat: var(--patCardRepeat) }
.tsbrand > i { width: 30px; height: 30px; flex: none; border-radius: 10px; background: var(--ai); color: var(--onAcc); display: grid; place-items: center; overflow: hidden }
.tsbrand b { font-family: var(--display); font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.tsgal i svg, .tsbrand > i svg { width: 26px; height: 26px }
.tsgal b { font-size: 12px; color: var(--mute); font-weight: 600 }
.tsthemes b { font-size: 12px; color: var(--ink2); font-weight: 600; white-space: nowrap }
.tspill { display: inline-flex; align-items: center; gap: 5px; padding: 5px 10px; border-radius: var(--pill); background: var(--s3); color: var(--mute); font-size: 11.5px; font-weight: 600 }
.tspill.on { background: var(--ai); color: var(--onAcc) }
.tspill svg { width: 13px; height: 13px }
/* Única prévia do lançador que **não** usa as cores da tela: o `doc.html` não carrega o `ui.css`
   porque papel é branco mesmo com a loja no tema escuro. Mostrar o orçamento no tom do painel
   prometeria um documento preto. Só o acento é herdado, que é o que o documento herda de verdade. */
.tsdoc { background: #fff; flex-direction: column; align-items: stretch; justify-content: center; gap: 4px; padding: 9px 12px }
.tsdoc i { height: 4px; border-radius: 2px; background: #d9dcd5 }
.tsdoc i:first-child { width: 38%; height: 6px; background: var(--acc1) }
.tsdoc i:last-child { width: 62% }

/* A gaveta da Aparência é a mesma do cadastro de produto, com uma diferença de propósito: o
   escurecido de trás fica quase transparente. Em toda outra tela o escuro serve para tirar a
   página de cena; aqui a página atrás É a prévia, e cobri-la enquanto se escolhe a cor do cartão
   seria pedir para o dono editar às cegas.
   Não chamar de `.peek`: esse nome já é a ficha do produto no PDV, que é `flex-direction: column`
   com padding — o escurecido herdava as duas coisas e a gaveta ia parar no canto esquerdo. */
.scrim.thru { background: color-mix(in srgb, var(--l0) 18%, transparent) }
.drawer header p { max-width: 62ch }

/* A busca não filtra a página, leva até o campo: abre a gaveta certa e pisca a linha. Sem o piscar,
   "abriu a gaveta de Forma" não responde quem digitou "borda" — são seis campos parecidos lá. */
.tfindwrap { max-width: 560px }
.tfind { display: none; position: absolute; left: 0; right: 0; top: calc(100% + 6px); z-index: 6; max-height: 340px; overflow: auto; padding: 5px; border-radius: var(--r2); background: var(--s1); box-shadow: var(--drop) }
.tfind.on { display: grid; gap: 2px }
/* `.search svg` posiciona a lupa em absoluto, e a regra alcança **todo** svg dentro da busca — os
   ícones das linhas do resultado saíam do fluxo e se empilhavam em cima da lupa. */
.tfind svg { position: static; flex: none; width: 15px; height: 15px; color: var(--mute) }
/* `justify-content` explícito: a regra geral de `button` centraliza o conteúdo, e resultado de
   busca centrado obriga a ler cada linha inteira para comparar duas. */
.tfrow { display: flex; align-items: center; justify-content: flex-start; gap: 9px; width: 100%; padding: 8px 10px; border: 0; border-radius: 10px; background: none; color: var(--ink); font: inherit; font-size: 13px; text-align: left; cursor: pointer }
.tfrow:hover, .tfrow:focus-visible { background: var(--s3) }
/* `.m` e não `span`: o ícone da linha também vem embrulhado num span, e o `margin-left:auto` caía
   nos dois — o primeiro empurrava a linha inteira para o meio. */
.tfrow b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
.tfrow > .m { margin-left: auto; font-size: 11.5px; white-space: nowrap }
/* `picker()`: a mesma caixa de resultados servindo de <select> com busca. Herda `.search`, que foi
   escrita para a barra de filtro no topo de uma tela e por isso vem com largura máxima e margem
   embaixo — dentro de uma `.fieldrow` as duas coisas atrapalham, e é só isto que estas regras
   desfazem. Cresce mais que um campo comum porque carrega nome de fornecedor por extenso. */
.fieldrow > .pick { flex: 2 1 240px; min-width: 0 }
.pick .search { max-width: none; margin: 0 }

/* Cabeçalho que não rola junto: os totais e os botões de ação ficam à vista enquanto a lista longa
   passa por baixo. Quem rola é `main`, então o `top: 0` é o topo da área de conteúdo.
   O fundo é sólido e sobe um pouco além do bloco (`padding-top` com `margin-top` negativo de mesmo
   valor): sem essa faixa, a primeira linha da tabela aparecia POR CIMA do espaço entre os cartões
   ao deslizar, e o cabeçalho parecia transparente em vez de fixo. `z-index` abaixo do 6 da lista de
   busca, para o resultado do `picker()` continuar caindo na frente dos cartões. */
.stickhead { position: sticky; top: 0; z-index: 5; padding-top: 14px; margin-top: -14px; background: var(--l0) }

/* Editor auditável da nota: produto ganha o espaço elástico; números permanecem alinhados em colunas
   para comparar a versão digitada com o espelho logo acima. */
.note-edit-head, .note-edit-line { display: grid; grid-template-columns: minmax(210px,1fr) 88px 108px 96px 92px 36px; gap: 6px; align-items: center }
.note-edit-head { padding: 12px 0 5px; color: var(--muted); font-size: 11.5px }
.note-edit-line { margin-bottom: 7px }
.period-filter { padding: 12px; margin-bottom: 12px; display: flex; gap: 8px; align-items: end; flex-wrap: wrap }
.period-filter .f { margin: 0; min-width: 150px }
.period-count { margin-left: auto; align-self: center; font-size: 12px }
@media (max-width: 820px) {
  .note-edit-head { display: none }
  .note-edit-line { grid-template-columns: minmax(150px,1fr) 76px; padding: 10px; border: 1px solid var(--line); border-radius: var(--r2) }
  .period-filter { align-items: stretch }
  .period-filter .f { flex: 1 1 130px }
  .period-count { width: 100%; margin-left: 0 }
}

/* Texto de célula que abre alguma coisa. É `<button>` e não `<a>` porque não navega para lugar
   nenhum — abre uma gaveta ali mesmo —, e um link com `href="#"` mentiria para o teclado e para o
   leitor de tela. O sublinhado pontilhado é o que diz "isto clica" numa tabela onde todo o resto é
   texto: sem ele o número do pedido parece só mais uma coluna e ninguém descobre a porta. */
.linkish { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); cursor: pointer;
  text-decoration: underline dotted; text-underline-offset: 3px }
.linkish:hover { color: var(--leaf) }
/* Realce por `box-shadow` e não por `padding` + fundo: a linha é um `<label>` no meio da coluna da
   gaveta, e ganhar padding por um segundo empurraria todas as outras para baixo — o campo que a
   busca acabou de trazer para o centro sairia de lugar justamente enquanto pisca. */
.flash { animation: flash 1.2s var(--ease); border-radius: 10px }
@keyframes flash { 0%, 100% { box-shadow: 0 0 0 9px transparent } 22% { box-shadow: 0 0 0 9px var(--accSoft) } }

/* Barra de salvar. A tela já mostra o tema novo enquanto se mexe, então sem um aviso explícito o
   dono fecha a página convencido de ter gravado. Ela só existe quando há mexida pendente — botão
   de salvar sempre visível e sempre inerte ensina a ignorar botão de salvar.
   `.page > *` anima tudo que entra na página; aqui isso brigaria com a subida da própria barra. */
.savebar { position: fixed; left: 50%; bottom: 22px; z-index: 30; display: flex; align-items: center; gap: 10px; padding: 9px 9px 9px 18px; border-radius: var(--pill); background: var(--s1); box-shadow: var(--drop); font-size: 13px; transform: translate(-50%, 90px); opacity: 0; pointer-events: none; transition: transform .28s var(--ease), opacity .28s var(--ease) }
.savebar.on { transform: translate(-50%, 0); opacity: 1; pointer-events: auto }
/* Some enquanto a gaveta está aberta: ali o rodapé já tem "Salvar aparência", e a barra, que é
   centrada na tela, ficava metade escondida atrás da gaveta. Vem depois de `.on` de propósito —
   as duas têm a mesma especificidade e quem decide o empate é a ordem do arquivo. */
.savebar.hid { transform: translate(-50%, 90px); opacity: 0; pointer-events: none }
.page > .savebar { animation: none }
/* No celular a frase inteira não cabe ao lado dos dois botões e a barra transbordaria a tela. O
   aviso vira a própria presença da barra — some o texto, ficam "Descartar" e "Salvar". */
@media (max-width: 560px) { .savebar { left: 12px; right: 12px; transform: translate(0, 90px) } .savebar.on { transform: translate(0, 0) } .savebar > span { display: none } .savebar .btn { flex: 1; justify-content: center } }

/* ── orçamentos e faturas ──
   O editor de documento é largo porque cada linha tem produto, quantidade, preço e desconto: no
   `.drawer` padrão de 480px os campos ficariam do tamanho de um selo. */
.drawer.wide { max-width: 760px }
/* A conferência do recebimento é mais larga que o editor de documento porque tem **seis** colunas, e
   quem manda na largura delas não são os campos e sim os rótulos: "Preço por volume" e "Caixa/fardo"
   não encolhem, e são eles que ensinam a diferença entre volume e unidade — encurtar para caber seria
   economizar 40px vendendo a única explicação que a tela dá. Em 760px a tabela passava de 730 e nascia
   com barra horizontal: o nome do produto descia uma palavra por linha e o "Total CIF" ficava fora da
   vista, que é justamente o número que se confere contra o papel na mão. */
.drawer.conf { max-width: 900px }
.qline { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 10px; border-radius: var(--r2); background: var(--s2); margin-bottom: 8px }
.qline .grow { flex: 1 1 220px; min-width: 0 }
.qline .num { width: 78px; flex: none; text-align: right }
.qline .sum { margin-left: auto; font-weight: 700; font-family: var(--display); white-space: nowrap }
.qline .lbl { font-size: 11.5px; color: var(--mute) }
.totbox { display: grid; gap: 4px; padding: 16px; border-radius: var(--r2); background: var(--s2) }
.totbox div { display: flex; justify-content: space-between; font-size: 13px; color: var(--ink2) }
.totbox div.big { font-size: 22px; font-weight: 800; font-family: var(--display); color: var(--ink); margin-top: 6px }

/* ── WhatsApp: a conversa acontecendo ──
   Não é tabela nem relatório: é um fio que continua enquanto a pessoa olha. Por isso a altura é
   fechada e quem rola é o miolo — a lista à esquerda e o fio à direita rolam por dentro, e a barra
   de escrever fica parada onde a mão espera encontrá-la. No telefone as duas colunas viram uma:
   a lista ocupa a tela inteira e a conversa entra por cima quando alguém toca num contato. */
.wa { display: grid; grid-template-columns: minmax(230px, 320px) 1fr; gap: var(--gap); height: clamp(440px, calc(100dvh - 250px), 880px) }
.wacol, .wathread { display: flex; flex-direction: column; overflow: hidden; min-height: 0 }
.wacolhead { padding: 12px; border-bottom: 1px solid var(--l3) } .wacolhead .field { border-radius: var(--pill) }
.walista { flex: 1; overflow: auto; padding: 8px; display: flex; flex-direction: column; gap: 2px }
.waitem { display: flex; align-items: center; gap: 10px; width: 100%; padding: 9px 10px; border: 0; border-radius: 14px; background: transparent; color: var(--ink2); font: inherit; text-align: left; cursor: pointer; transition: background .14s }
.waitem:hover { background: var(--s2) } .waitem.on { background: var(--s3); color: var(--ink) }
.waav { flex: none; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; background: var(--s3); color: var(--ink); font-family: var(--display); font-weight: 600; font-size: 13px }
.waitem.on .waav { background: var(--accSoft) }
.wameio { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px }
.walinha { display: flex; align-items: center; gap: 6px; min-width: 0 }
.walinha b { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
.walinha small { flex: 1; min-width: 0; font-size: 11.5px; color: var(--mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis }
.walinha time { flex: none; font-size: 11px; color: var(--mute) }
.walinha .chip { flex: none; font-size: 10px; padding: 2px 8px }
/* o ponto vale mais que um número: a pergunta em aberto é o que faz alguém abrir esta tela */
.wapt { flex: none; display: block; width: 8px; height: 8px; border-radius: 50%; background: var(--acc1) }
.wahead { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 12px 16px; border-bottom: 1px solid var(--l3) }
.wawho { flex: 1; min-width: 130px; display: flex; flex-direction: column; line-height: 1.3 }
.wawho small { font-size: 11.5px; color: var(--mute) }
.wahead .icon.on { color: var(--berry) }
.wavolta { display: none }
.wafeed { flex: 1; overflow: auto; padding: 18px; display: flex; flex-direction: column; gap: 8px; scroll-behavior: smooth }
.wamsg { max-width: min(78%, 520px); padding: 10px 14px; border-radius: 16px; font-size: 13.5px; display: flex; flex-direction: column; gap: 3px }
.wamsg .wat { white-space: pre-wrap }
/* o balão de quem chega lê `--s1` desde o OSCAR, onde o fundo atrás dele é `--s0` e o contraste
   existe. Aqui ele vive dentro de um cartão, que também é `--s1`: o que o cliente disse saía como
   texto solto, sem balão, enquanto a resposta da loja tinha o dela. Metade do diálogo sem forma. */
.wafeed .msg.a { background: var(--s2) }
.wameta { align-self: flex-end; font-size: 10.5px; color: var(--mute) }
.msg.u .wameta { color: color-mix(in srgb, var(--ink) 52%, transparent) }
.wadia { align-self: center; margin: 10px 0 2px }
.wadia span { padding: 3px 12px; border-radius: var(--pill); background: var(--s2); color: var(--mute); font-size: 11px; font-weight: 600 }
.wabarra { display: flex; align-items: flex-end; gap: 8px; padding: 12px; border-top: 1px solid var(--l3) }
.wacampo { flex: 1; max-height: 132px; resize: none; border-radius: 20px; padding: 11px 16px; line-height: 1.45; font: inherit; font-size: 13.5px }
.waenviar { flex: none; width: 42px; height: 42px; padding: 0; border-radius: 50% }
.waoff { flex: 1; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12.5px; color: var(--mute) }
.waoff span { flex: 1; min-width: 160px }
@media (max-width: 900px) {
  /* uma coluna: a lista é a tela, e a conversa entra no lugar dela. O botão de voltar só existe
     aqui — no computador as duas ficam à vista e não há para onde voltar. */
  .wa { grid-template-columns: 1fr; height: calc(100dvh - 190px) }
  .wa > .wathread { display: none } .wa.aberta > .wacol { display: none } .wa.aberta > .wathread { display: flex }
  .wavolta { display: inline-flex } .wahead { padding: 10px 12px } .wafeed { padding: 14px }
  .wamsg { max-width: 86% }
}

/* ══════════════ O CAIXA NO CELULAR ══════════════
   Duas faces sobre o MESMO carrinho. Nada aqui refaz venda, desconto, cupom, fila offline ou
   pagamento: as duas telas só chamam `pick()`, a mesma porta que a grade do computador usa. Caixa
   com duas contas paralelas é exatamente como se perde dinheiro sem ninguém perceber.
   O `.cart` também não é duplicado — ele é MOVIDO para cá e vira gaveta de rodapé. Assim
   `drawCart()` segue funcionando sem saber que a tela mudou. */
.mob { display: none; flex: 1; min-height: 0; position: relative; overflow: hidden }
body.mob-on .pos { display: none }
body.mob-on .mob { display: flex; flex-direction: column }

/* ── face LEITOR: a câmera ocupa a tela inteira e tudo mais flutua sobre ela ── */
.mobcam { position: absolute; inset: 0; background: #000 }
.mobcam video { width: 100%; height: 100%; object-fit: cover; display: block }
/* A mira não lê nada: quem lê é o BarcodeDetector, e ele varre o quadro inteiro. Ela existe só para
   a pessoa saber onde encostar o código — e é isso que faz a leitura ser rápida na prática. */
.mobmira { position: absolute; left: 50%; top: 24%; transform: translate(-50%, -50%); width: min(72vw, 290px); aspect-ratio: 1.55; border-radius: 16px; border: 2px solid rgba(255,255,255,.92); box-shadow: 0 0 0 100vmax rgba(0,0,0,.45); pointer-events: none }
.mobmira.hit { border-color: var(--acc1); animation: mobhit .45s var(--ease) }
@keyframes mobhit { 0% { transform: translate(-50%,-50%) scale(1) } 45% { transform: translate(-50%,-50%) scale(1.07) } 100% { transform: translate(-50%,-50%) scale(1) } }
.mobdica { position: absolute; left: 50%; top: calc(24% + 92px); transform: translateX(-50%); z-index: 2; padding: 7px 14px; border-radius: var(--pill); background: rgba(0,0,0,.6); color: #fff; font-size: 12.5px; white-space: nowrap; pointer-events: none }
/* A gaveta da venda: do rodapé até o meio da tela, por cima da câmera. A classe mora no próprio
   `.cart` porque as duas faces a usam — no leitor fica sempre aberta, no T9 abre a pedido. */
.cart.mobgaveta { position: absolute; left: 0; right: 0; bottom: 0; top: 47%; z-index: 3; border-radius: 22px 22px 0 0; box-shadow: 0 -16px 46px rgba(0,0,0,.55) }
.cart.mobgaveta.fechada { display: none }

/* ── face T9: busca por teclado, sem teclado do sistema ── */
.mobt9 { position: absolute; inset: 0; display: flex; flex-direction: column; background: var(--s0) }
.mobtopo { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--l3) }
.mobtopo .btn { flex: 1; justify-content: flex-start; min-width: 0 }
/* O que foi digitado. Não é <input>: encostar nele abriria o teclado do sistema por cima do T9,
   que é justamente o que esta tela veio evitar. */
.mobecho { display: flex; align-items: center; gap: 8px; padding: 12px 16px; min-height: 52px; border-bottom: 1px solid var(--l3) }
.mobecho b { font-family: var(--display); font-size: 21px; letter-spacing: 3px; color: var(--ink) }
.mobecho .m { font-size: 12.5px }
.mobhits { flex: 1; overflow: auto; padding: 6px; display: flex; flex-direction: column; gap: 2px }
.mobhit { display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px; border: 0; border-radius: 14px; background: transparent; color: var(--ink2); font: inherit; text-align: left; cursor: pointer }
.mobhit:active { background: var(--s2) }
.mobhit .mobfoto { flex: none; width: 42px; height: 42px; border-radius: 11px; background: var(--s3) center/cover no-repeat }
.mobhit .mobnome { flex: 1; min-width: 0; line-height: 1.3 }
.mobhit .mobnome u { text-decoration: none; color: var(--acc1); font-weight: 700 }
.mobhit .mobpreco { font-family: var(--display); font-size: 16px; white-space: nowrap }
/* O teclado. `aspect-ratio` aqui é seguro porque a coluna é `1fr` de um grid de largura conhecida e
   a tecla não tem conteúdo que possa estourar — ao contrário da pastilha da grade do caixa. */
.mobpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; padding: 8px 10px calc(8px + env(safe-area-inset-bottom, 0px)); background: var(--s1); border-top: 1px solid var(--l3) }
.mobkey { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px; aspect-ratio: 1.75; border: 0; border-radius: 15px; background: var(--s2); color: var(--ink); font: inherit; cursor: pointer; transition: background .1s, opacity .12s }
.mobkey:active { background: var(--s3) }
.mobkey b { font-family: var(--display); font-size: 19px; font-weight: 600; line-height: 1 }
.mobkey span { font-size: 10px; letter-spacing: 1.4px; color: var(--mute) }
/* Tecla apagada é tecla que não leva a produto nenhum do catálogo. Ela continua ocupando o lugar de
   propósito: o dedo já sabe onde fica cada letra, e mover as teclas a cada toque destruiria isso. */
.mobkey:disabled { opacity: .2; cursor: default }
.mobkey.fim { background: var(--s3); color: var(--mute) }
/* Barra do total: some quando não há nada na venda, para não roubar altura do teclado. */
.mobbarra { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-top: 1px solid var(--l3); background: var(--s1) }
.mobbarra b { font-family: var(--display); font-size: 19px }
.mobsoma { display: flex; flex-direction: column; line-height: 1.15; margin-right: auto }
.mobsoma .m { font-size: 11.5px; white-space: nowrap }
.mobbarra .btn { flex: none; white-space: nowrap }

/* O cabeçalho do balcão não cabe no celular: foi desenhado para uma barra larga e, em 375px, quebra
   em quatro linhas e come um terço da tela útil. Some com o que não se usa em pé no salão —
   relatório da gaveta, pedidos, estacionadas, impressora e painel são trabalho de quem está sentado,
   e quem precisar deles troca de volta para a tela do computador. Ficam marca, conexão, gaveta,
   troca de tela e sair. */
body.mob-on .top { padding: 6px 10px; gap: 6px }
body.mob-on .top #who, body.mob-on .top #today, body.mob-on .top #bOrders,
body.mob-on .top #bHeld, body.mob-on .top #bAdmin, body.mob-on .top #bPrint { display: none }
/* Esconder seis botões não bastou: os cinco que sobravam ainda somavam 519px numa tela de 375, e o
   `.top` é `display:flex` sem quebra — então "Celular" e "Sair" ficavam simplesmente fora da tela,
   sem barra de rolagem que denunciasse. Foi por isso que o dono precisou afastar a página com os
   dedos para alcançar o botão. Medido em 2026-09-14: marca 132 + conexão 63 + gaveta 114 +
   celular 98 + sair 73. O corte abaixo derruba o total para perto de 260px, que cabe com folga.

   O nome ao lado do símbolo sai por `font-size: 0` porque ele é um nó de texto solto dentro do `<a>`,
   sem elemento próprio para receber `display: none`; o `<i>` recupera o tamanho para o desenho não
   encolher junto. O atalho `F9` some porque não há teclado aqui. E "Celular" some porque no celular
   é o botão que não leva a lugar nenhum novo: cada face já traz o seu "Trocar tela". */
body.mob-on .top .brand { font-size: 0; gap: 0 }
body.mob-on .top .brand > i { font-size: 16px }
body.mob-on .top #bMob { display: none }
body.mob-on .top .btn .kbd { display: none }
body.mob-on .top .btn { padding: 7px 11px; font-size: 13px }
body.mob-on .top #net { font-size: 11px; padding: 3px 9px }

/* Escolha da face. Não depende de largura de tela: às vezes o celular está deitado, às vezes é um
   tablet no balcão. Quem escolhe é a pessoa, não a media query. */
.mobpick { display: grid; gap: 10px }
.mobpick button { display: flex; align-items: center; gap: 12px; padding: 14px; border: 1.5px solid var(--l3); border-radius: 16px; background: var(--s2); color: var(--ink); font: inherit; text-align: left; cursor: pointer }
.mobpick button:hover { border-color: var(--acc1) }
.mobpick button i { flex: none; width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center; background: var(--s3); font-size: 19px; font-style: normal }
.mobpick button small { display: block; color: var(--mute); font-size: 12px; line-height: 1.4; margin-top: 2px }

/* ── o celular não é um computador pequeno ──────────────────────────────────────────────────────
   Três coisas que no computador são conforto e no caixa de mão são defeito.

   1. Zoom. `touch-action: manipulation` desliga o toque duplo que dá zoom, sem desligar o rolar nem
      o toque simples. Anda junto com o `user-scalable=no` do pdv.html: um trata o pinçar, o outro o
      toque duplo, e é preciso os dois. Sem isso a tela saía do lugar no meio de uma venda — foi o
      que aconteceu no teste do dono, que precisou afastar e reaproximar a página com os dedos.
   2. Barra de rolagem. No celular o navegador já mostra a barra só enquanto o dedo arrasta; o que
      aparecia era a barra do computador, desenhada por cima do conteúdo das listas.
   3. Elástico. `overscroll-behavior: none` impede que puxar a lista até o fim arraste a PÁGINA
      inteira junto — no leitor isso descolava a câmera da borda e aparecia uma faixa preta. Também
      desliga o "puxar para recarregar" do Chrome, que num caixa com venda aberta apagaria o carrinho.
   Tudo preso a `body.mob-on`: no computador a barra de rolagem é informação útil e fica como está. */
body.mob-on { touch-action: manipulation; overscroll-behavior: none }
body.mob-on * { -webkit-tap-highlight-color: transparent }
body.mob-on :is(.mobhits, .mob, .cart, .lst, .drawer .body, .pgrid) { scrollbar-width: none; overscroll-behavior: contain }
body.mob-on ::-webkit-scrollbar { width: 0; height: 0 }
/* A gaveta lateral nasceu com largura fixa em px (o `openModal` manda 400 ou 520). Num aparelho de
   375px ela passava da borda e o botão de fechar ficava fora da tela. `max-width` vence a largura
   fixa sem precisar de `!important` e sem tocar em quem chama. */
@media (max-width: 560px) { .drawer { max-width: 100vw } }
/* Compras: resumo persistente, mantendo a paleta do tema ativo. */
.purchase-sticky { position:sticky; top:0; z-index:4; background:var(--bg); padding:8px 0; }
.purchase-lines .card > div { max-height:65vh; overflow:auto !important; }
.purchase-lines thead th { position:sticky; top:0; z-index:2; background:var(--s1); }
@media(max-width:700px) { .purchase-sticky { position:static; } }
