/* ═══════════════════════════════════════════════════════════════════
   NEXUM — COMPONENTES COMPARTILHADOS
   ───────────────────────────────────────────────────────────────────
   O que mora aqui: tudo usado por MAIS DE UMA tela — botão, campo,
   badge, card, avatar, chip, cartão de número, skeleton, estado vazio,
   toast, tabela.

   ⚠️ Está escrevendo uma classe no CSS de um módulo e ela pode servir a
      outra tela? Ela nasceu no arquivo errado. CSS de módulo só carrega
      na rota que o declara — usada em outra tela, renderiza SEM ESTILO
      NENHUM, e o defeito é silencioso.
   ═══════════════════════════════════════════════════════════════════ */

/* ── BASE ── */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--fonte-base);
  font-size: var(--txt-base);
  line-height: var(--linha-normal);
  color: var(--texto-normal);
  background-color: var(--bg-app);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--esp-3);
  font-family: var(--fonte-titulo);
  font-weight: var(--peso-semi);
  line-height: var(--linha-apertada);
  color: var(--texto-forte);
}

h1 { font-size: var(--txt-2xl); }
h2 { font-size: var(--txt-xl); }
h3 { font-size: var(--txt-lg); }
h4, h5, h6 { font-size: var(--txt-base); }

p { margin: 0 0 var(--esp-4); }

a {
  color: var(--marca-texto);
  text-decoration: none;
  transition: color var(--transicao-rapida);
}
a:hover { color: var(--cor-primaria-hover); text-decoration: underline; }

img, svg, video { max-width: 100%; height: auto; }

/* ⚠️ Foco de teclado SEMPRE visível. `outline: none` sem substituto
   torna o sistema inoperável por teclado. */
:focus-visible {
  outline: 2px solid var(--cor-primaria);
  outline-offset: 2px;
  border-radius: var(--raio-sm);
}

::selection { background: var(--marca-200); color: var(--cinza-900); }

/* ── UTILITÁRIOS ── */
.nx-oculto { display: none !important; }

/* Esconde visualmente mas mantém para leitor de tela. */
.nx-so-leitor {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── ÍCONE DEPENDENTE DO TEMA ──
   Sol no tema escuro, lua no claro — o ícone mostra o que o clique FAZ.
   Mora aqui, e não em `auth.css`, porque login e painel usam o mesmo par
   de ícones em botões diferentes: no arquivo de uma tela só, a outra
   renderizaria os dois ícones ao mesmo tempo. */
.nx-icone-claro,
.nx-icone-escuro { display: inline-flex; }

.nx-icone-escuro { display: none; }
[data-tema="escuro"] .nx-icone-claro  { display: none; }
[data-tema="escuro"] .nx-icone-escuro { display: inline-flex; }

@media (prefers-color-scheme: dark) {
  :root:not([data-tema="claro"]) .nx-icone-claro  { display: none; }
  :root:not([data-tema="claro"]) .nx-icone-escuro { display: inline-flex; }
}

/* ── PULAR PARA O CONTEÚDO ──
   ⚠️ NÃO usar `.nx-so-leitor` aqui: ela esconde MESMO com foco, e o link
      de pular existe justamente para quem navega por teclado. Fica fora
      da tela até receber foco, e então aparece. */
.nx-pular {
  position: absolute;
  top: var(--esp-2);
  left: var(--esp-2);
  z-index: var(--z-toast);
  padding: var(--esp-2) var(--esp-4);
  background: var(--bg-elevado);
  color: var(--texto-forte);
  border: 1px solid var(--borda);
  border-radius: var(--raio-md);
  box-shadow: var(--sombra-media);
  font-size: var(--txt-sm);
  font-weight: var(--peso-medio);
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform var(--transicao-rapida);
}
.nx-pular:focus { transform: translateY(0); text-decoration: none; }

.nx-texto-fraco  { color: var(--texto-fraco); }
.nx-texto-forte  { color: var(--texto-forte); }
.nx-texto-perigo { color: var(--cor-perigo); }
.nx-texto-sucesso{ color: var(--cor-sucesso); }
.nx-mono         { font-family: var(--fonte-mono); font-variant-numeric: tabular-nums; }

/* Dinheiro e quantidade alinham pela vírgula em listagem. */
.nx-numero {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

/* ═══════════════════════════════════════════════════════════════════
   BOTÕES
   ═══════════════════════════════════════════════════════════════════ */

/* ⚠️ `--controle-altura` (44px no toque, 36px no desktop), NÃO
      `--toque-min` fixo: o alvo de 44px é exigência do DEDO, e aplicá-lo
      onde se aponta com o mouse deixava o painel com cara de aplicativo
      de celular esticado. O token troca sozinho em 992px (tokens.css). */
.nx-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--esp-2);
  min-height: var(--controle-altura);
  padding: var(--esp-2) var(--esp-4);
  font-family: inherit;
  font-size: var(--txt-sm);
  font-weight: var(--peso-medio);
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  border: 1px solid transparent;
  border-radius: var(--raio-md);
  cursor: pointer;
  transition: background-color var(--transicao-rapida),
              border-color var(--transicao-rapida),
              color var(--transicao-rapida),
              box-shadow var(--transicao-rapida),
              transform var(--transicao-rapida);
  user-select: none;
}

.nx-btn:hover { text-decoration: none; }
.nx-btn:active { transform: translateY(1px); }

.nx-btn:disabled,
.nx-btn[aria-disabled="true"] {
  opacity: .55;
  cursor: not-allowed;
  transform: none;
}

/* Primário — a ação da tela. Um por tela, no máximo dois. */
.nx-btn--primario {
  background-color: var(--cor-primaria);
  border-color: var(--cor-primaria);
  color: var(--sobre-primaria);
}
.nx-btn--primario:hover:not(:disabled) {
  background-color: var(--cor-primaria-hover);
  border-color: var(--cor-primaria-hover);
  color: var(--sobre-primaria);
}

.nx-btn--secundario {
  background-color: var(--bg-superficie);
  border-color: var(--borda);
  color: var(--texto-forte);
}
.nx-btn--secundario:hover:not(:disabled) {
  background-color: var(--bg-sutil);
  border-color: var(--borda-forte);
  color: var(--texto-forte);
}

/* Fantasma — a ação de linha (Editar, Excluir, Movimentar) e a ação
   discreta de barra.

   ⚠️ NÃO é transparente. Ele nasceu sem fundo E sem borda, e o resultado
      era que a coluna de ações parecia uma lista de LINKS: só ao passar o
      mouse a pessoa descobria que aquilo era clicável — e no tablet, que é
      o balcão da loja, não existe "passar o mouse". Quem opera de dedo via
      texto solto e não sabia onde tocar.

      Agora ele tem superfície e borda próprias: lê-se como botão parado,
      sem competir com o primário, que continua sendo o único preenchido
      com a cor da marca. A hierarquia vem do PESO (cheio × contornado),
      não da ausência de forma. */
.nx-btn--fantasma {
  background-color: var(--bg-superficie);
  border-color: var(--borda);
  color: var(--texto-normal);
}
.nx-btn--fantasma:hover:not(:disabled) {
  background-color: var(--bg-sutil);
  border-color: var(--borda-forte);
  color: var(--texto-forte);
}

/* ⚠️ A ação destrutiva de linha é `.nx-btn--fantasma.nx-texto-perigo`.
      Com a borda neutra herdada acima, ela ficava idêntica a "Editar"
      exceto pela cor da letra — e cor sozinha não é sinal acessível.
      A borda acompanha o texto para o vermelho, em intensidade baixa:
      identifica sem virar o `--perigo` cheio, que é reservado ao botão de
      confirmar a exclusão dentro do modal.

   ⚠️ Sem `color-mix()`: quando ele não é entendido, a declaração inteira é
      DESCARTADA e a borda volta a não existir — exatamente o defeito que
      esta regra conserta, de volta e em silêncio. `--cor-perigo-suave` já
      existe nos dois temas e não tem esse risco. */
.nx-btn--fantasma.nx-texto-perigo {
  border-color: var(--cor-perigo-suave);
}
.nx-btn--fantasma.nx-texto-perigo:hover:not(:disabled) {
  background-color: var(--cor-perigo-suave);
  border-color: var(--cor-perigo);
  color: var(--cor-perigo);
}

/* Perigo — NUNCA é o botão padrão, e nunca ocupa a posição de
   "continuar". Quem aperta Enter por reflexo não pode cancelar uma
   venda. */
.nx-btn--perigo {
  background-color: var(--cor-perigo);
  border-color: var(--cor-perigo);
  color: #FFFFFF;
}
.nx-btn--perigo:hover:not(:disabled) { filter: brightness(.92); color: #FFFFFF; }

/* ⚠️ As variantes acompanham `--controle-altura` em vez de fixar px, para
      a diferença entre elas sobreviver à troca de tamanho de tela. Com
      altura fixa, `--sm` (36px) e o botão comum ficariam IDÊNTICOS no
      desktop, e a hierarquia visual entre ação principal e ação de linha
      desapareceria justo onde ela mais se usa. */
.nx-btn--sm {
  min-height: calc(var(--controle-altura) - 8px);
  padding: var(--esp-1) var(--esp-3);
  font-size: var(--txt-xs);
}
.nx-btn--lg {
  min-height: calc(var(--controle-altura) + 8px);
  padding: var(--esp-3) var(--esp-6);
  font-size: var(--txt-base);
}
.nx-btn--bloco { display: flex; width: 100%; }

/* Botão só de ícone — precisa de aria-label, sem exceção.
   ⚠️ Quadrado: a largura segue a altura, senão ele vira retângulo no
      desktop e o ícone desencontra do centro. */
.nx-btn--icone {
  width: var(--controle-altura);
  min-width: var(--controle-altura);
  padding: 0;
}

/* ⚠️ Estado de carregamento: desabilita E troca o texto. Sem isto, o
   duplo clique numa rede lenta finaliza a mesma venda duas vezes. */
.nx-btn[data-carregando="true"] {
  pointer-events: none;
  opacity: .8;
}
.nx-btn[data-carregando="true"]::before {
  content: "";
  width: 1em; height: 1em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: nx-girar .7s linear infinite;
}

@keyframes nx-girar { to { transform: rotate(360deg); } }

/* ═══════════════════════════════════════════════════════════════════
   FORMULÁRIO
   ═══════════════════════════════════════════════════════════════════ */

.nx-campo { margin-bottom: var(--esp-4); }

/* ⚠️ <label> real, sempre. Placeholder não substitui rótulo: ele some
   quando a pessoa digita, e ela esquece o que o campo era. */
.nx-rotulo {
  display: block;
  margin-bottom: var(--esp-1);
  font-size: var(--txt-sm);
  font-weight: var(--peso-medio);
  color: var(--texto-forte);
}

/* Rótulo compacto, para campo dentro de uma linha de lista — a pedra da
   joia, a opção de um atributo personalizado.

   ⚠️ O rótulo CONTINUA EXISTINDO, apenas menor. A tentação ali é usar só
      o `placeholder`: ele some quando a pessoa digita, e quem volta para
      conferir a ficha vê seis campos com valores e nenhum dizendo qual é
      a cor e qual é a pureza. Em pedra preciosa, "F" e "VS1" no campo
      trocado muda o valor da peça. */
.nx-rotulo--sm {
  font-size: var(--txt-xs);
  font-weight: var(--peso-normal);
  color: var(--texto-fraco);
}

.nx-rotulo__obrigatorio {
  color: var(--cor-perigo);
  margin-left: 2px;
}

/* ⚠️ O CAMPO ACOMPANHA O BOTÃO. Encolher só o botão deixaria os dois
      desalinhados na barra de listagem, onde a busca fica ao lado dos
      filtros e do alternador de visão — e o desalinhamento de 8px numa
      linha horizontal é exatamente o tipo de descuido que derruba a
      percepção de acabamento. */
.nx-input,
.nx-select,
.nx-textarea {
  display: block;
  width: 100%;
  /* ⚠️ `width: 100%` NÃO faz o campo encolher — define a largura preferida,
        não a mínima. O `<input>` carrega uma largura mínima intrínseca do
        atributo `size` (~20 caracteres, perto de 396px nesta fonte), e num
        contêiner flex ou grid é ELA que vence: o campo se recusa a descer
        de 396px, empurra a barra de listagem, o card, a coluna da grade e
        por fim o documento inteiro — a página passa a medir 470px num
        celular de 360px e a lateral inteira fica cortada.

        Custou a investigação de 24/09/2026: o sintoma aparece como "a tela
        está cortada à direita", e o culpado é o campo de busca, que é o
        último lugar onde se olha. `min-width: 0` devolve ao navegador o
        direito de encolher o campo até onde o contêiner mandar.

     ⚠️ `.nx-input-grupo .nx-input` já trazia esta linha desde sempre — era
        a mesma correção, aplicada num contexto só. Aqui ela vale para
        todos os campos, que é onde deveria estar desde o início. */
  min-width: 0;
  min-height: var(--controle-altura);
  padding: var(--esp-2) var(--esp-3);
  font-family: inherit;
  font-size: var(--txt-base);
  line-height: var(--linha-normal);
  color: var(--texto-forte);
  background-color: var(--bg-superficie);
  border: 1px solid var(--borda);
  border-radius: var(--raio-md);
  transition: border-color var(--transicao-rapida),
              box-shadow var(--transicao-rapida);
  appearance: none;
}

.nx-textarea { min-height: 96px; resize: vertical; }

.nx-select {
  padding-right: var(--esp-7);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%237A7772' stroke-width='1.6'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--esp-3) center;
  background-size: 16px;
}

.nx-input:focus,
.nx-select:focus,
.nx-textarea:focus {
  outline: none;
  border-color: var(--cor-primaria);
  box-shadow: var(--sombra-foco);
}

.nx-input::placeholder,
.nx-textarea::placeholder {
  color: var(--texto-fraco);
  opacity: 1;
}

.nx-input:disabled,
.nx-select:disabled,
.nx-textarea:disabled {
  background-color: var(--bg-sutil);
  color: var(--texto-fraco);
  cursor: not-allowed;
}

/* Texto de ajuda: visível abaixo do campo, não escondido em tooltip. */
.nx-ajuda {
  display: block;
  margin-top: var(--esp-1);
  font-size: var(--txt-xs);
  line-height: var(--linha-normal);
  color: var(--texto-fraco);
}

/* Erro no campo, dizendo como corrigir. */
.nx-campo--erro .nx-input,
.nx-campo--erro .nx-select,
.nx-campo--erro .nx-textarea {
  border-color: var(--cor-perigo);
}
.nx-campo--erro .nx-input:focus,
.nx-campo--erro .nx-select:focus,
.nx-campo--erro .nx-textarea:focus {
  box-shadow: 0 0 0 3px rgba(179, 38, 30, .25);
}

.nx-erro-campo {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-1);
  margin-top: var(--esp-1);
  font-size: var(--txt-xs);
  color: var(--cor-perigo);
}

/* Campo com botão ao lado (senha, busca com leitor de código). */
.nx-input-grupo { display: flex; gap: var(--esp-2); align-items: stretch; }
.nx-input-grupo .nx-input { flex: 1 1 auto; min-width: 0; }

/* Senha com olho — reduz erro de digitação, sobretudo no mobile. */
.nx-senha { position: relative; }
.nx-senha .nx-input { padding-right: calc(var(--controle-altura) + var(--esp-1)); }
/* ⚠️ O olho precisa caber DENTRO do campo: com 44px fixos num campo de
      36px no desktop, ele transbordaria por cima da borda. */
.nx-senha__olho {
  position: absolute;
  top: 50%; right: var(--esp-1);
  transform: translateY(-50%);
  width: var(--controle-altura); height: var(--controle-altura);
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: var(--raio-sm);
  color: var(--texto-fraco);
  cursor: pointer;
}
.nx-senha__olho:hover { color: var(--texto-forte); background: var(--bg-sutil); }

.nx-check {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-2);
  cursor: pointer;
  font-size: var(--txt-sm);
}
.nx-check input[type="checkbox"],
.nx-check input[type="radio"] {
  width: 18px; height: 18px;
  margin-top: 2px;
  accent-color: var(--cor-primaria);
  cursor: pointer;
  flex-shrink: 0;
}

/* Sufixo colado ao campo: %, g, un. — a unidade fica visível enquanto se
   digita, em vez de escondida no texto de ajuda embaixo. */
.nx-input-grupo__sufixo {
  display: flex;
  align-items: center;
  padding: 0 var(--esp-3);
  border: 1px solid var(--borda);
  border-radius: var(--raio-md);
  background-color: var(--bg-sutil);
  color: var(--texto-fraco);
  font-size: var(--txt-sm);
  white-space: nowrap;
}

/* Campo que agrupa vários controles (um fieldset de checkboxes). Zera o
   que o navegador põe em <fieldset> por padrão — borda e recuo — para
   que ele se pareça com os outros campos do formulário. */
.nx-campo--grupo {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}

/* Lista de opções marcáveis com rolagem própria.
   ⚠️ O teto de altura é o que impede uma empresa com 30 filiais de fazer
      o modal crescer até o rodapé sair da tela — o botão de salvar some e
      o formulário fica sem saída. */
.nx-lista-opcoes {
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
  max-height: 220px;
  overflow-y: auto;
  padding: var(--esp-3);
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-md);
  background-color: var(--bg-sutil);
}

/* Linha de item editável dentro de `.nx-lista-opcoes` — subcategoria,
   pedra da joia, opção de atributo personalizado.

   ⚠️ NASCE EM app.css, não no CSS de categorias: são três módulos da Fase
      4 com a mesma estrutura (campo + botão de remover numa linha). Posta
      no CSS de um módulo, ela renderizaria SEM ESTILO nas outras telas, e
      o defeito seria silencioso (03-FRONTEND-PADROES.md §2.1.1).

   ⚠️ `align-items: flex-start` porque a linha pode ter um texto de ajuda
      embaixo do campo ("3 produtos usam esta subcategoria") — centralizar
      deixaria o botão de remover flutuando no meio. */
.nx-sub-linha {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-2);
}
.nx-sub-linha__campo { flex: 1 1 auto; min-width: 0; }
.nx-sub-linha__campo .nx-ajuda { margin-top: var(--esp-1); }

/* Campo compacto, para uso dentro de uma lista de linhas.
   ⚠️ Segue `--controle-altura` pelo mesmo motivo de `.nx-btn--sm`: com
      36px fixos ele ficaria IDÊNTICO ao campo normal no desktop, e a
      lista de subcategorias perderia a densidade que a distingue do
      formulário em volta. */
.nx-input--sm {
  min-height: calc(var(--controle-altura) - 8px);
  padding: var(--esp-1) var(--esp-2);
  font-size: var(--txt-sm);
}

/* Valor CALCULADO, mostrado na forma de um campo — a margem do produto, o
   total de um orçamento, o saldo de um caixa.

   ⚠️ NÃO é um <input readonly>: um campo desabilitado ainda parece
      editável e convida ao clique, e o leitor de tela o anuncia como
      campo de formulário que não aceita nada. Aqui é um <p> com a cara de
      campo, marcado com `aria-live` por quem o usa — o valor muda
      enquanto a pessoa digita em OUTRO campo, e sem isso a mudança passa
      despercebida por quem não vê a tela.

   ⚠️ E é o que impede o segundo lugar onde o preço é decidido: margem
      digitável seria um número que discorda do motor de precificação. */
.nx-input--leitura {
  display: flex;
  align-items: center;
  margin: 0;
  background: var(--bg-sutil);
  border-style: dashed;
  color: var(--texto-normal);
  cursor: default;
}

/* Bloco recolhido de opções avançadas.
   ⚠️ O marcador padrão do <summary> é substituído para o triângulo do
      navegador não conflitar com o alinhamento dos rótulos. */
.nx-detalhes__resumo {
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  list-style: none;
  user-select: none;
}
.nx-detalhes__resumo::-webkit-details-marker { display: none; }
.nx-detalhes__resumo::before {
  content: "";
  width: 6px; height: 6px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--transicao-rapida);
}
details[open] > .nx-detalhes__resumo::before { transform: rotate(45deg); }

/* Campos lado a lado dentro de um formulário.

   ⚠️ NASCE EM app.css, e não no CSS de produtos, pelo mesmo motivo de
      `.nx-sub-linha`: cliente, fornecedor, encomenda e configurações vão
      pedir a mesma coisa nas próximas fases. Batizada com o nome de um
      módulo, ela seria copiada — e nasceria a segunda definição.

   ⚠️ `auto-fit` + `minmax`, não `repeat(2, 1fr)`: é o que faz a grade
      virar UMA coluna sozinha no celular, sem media query e sem o campo
      de 150px espremido que o PDV já sofreu em projeto anterior. */
.nx-campos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 0 var(--esp-4);
}

/* Campo que ocupa a linha inteira mesmo dentro da grade — nome de
   produto, descrição, observações. */
.nx-campos__largo { grid-column: 1 / -1; }

/* Camada do cadastro em etapas (08-PRODUTOS-E-JOIAS.md §2).

   ⚠️ A PRIMEIRA CAMADA NÃO É UM <details>: ela está sempre aberta, porque
      é o que basta para vender. As outras enriquecem, e recolhê-las é o
      que impede o formulário de 40 campos que faz o lojista desistir e
      voltar para a planilha. */
.nx-camada {
  padding-top: var(--esp-4);
  border-top: 1px solid var(--borda-sutil);
  margin-top: var(--esp-4);
}

.nx-camada__titulo {
  margin: 0;
  font-size: var(--txt-sm);
  font-weight: var(--peso-semi);
  color: var(--texto-forte);
}

/* A frase que diz o que a camada resolve, ao lado do título — quem lê
   "Características da joia" não sabe se precisa abrir. */
.nx-camada__dica {
  margin-left: auto;
  font-size: var(--txt-xs);
  font-weight: var(--peso-normal);
  color: var(--texto-fraco);
}

/* Resumo do que já foi preenchido dentro da camada FECHADA.
   ⚠️ Sem ele, quem reabre a edição não tem como saber que a peça já tem
      3 pedras cadastradas sem abrir as seis camadas uma a uma. */
.nx-camada__preenchida {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  color: var(--cor-sucesso);
}

/* ═══════════════════════════════════════════════════════════════════
   GALERIA — fotos de um registro
   ───────────────────────────────────────────────────────────────────
   Foto de produto hoje; foto de manutenção (Fase 17) e do catálogo
   digital (Fase 21) depois. ⚠️ Por isso nasce em app.css e com nome
   genérico: batizada `.nx-produto-fotos`, seria copiada no próximo
   módulo e nasceria a segunda definição da mesma coisa.
   ═══════════════════════════════════════════════════════════════════ */

/* ⚠️ `auto-fill` com minmax, não um número fixo de colunas: a grade se
      ajusta do celular ao desktop sem media query, e a última linha não
      estica as fotos que sobraram (o que `auto-fit` faria). */
.nx-galeria {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: var(--esp-3);
  padding: var(--esp-3);
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-md);
  background-color: var(--bg-sutil);
}

/* A frase do estado vazio ocupa a grade inteira. */
.nx-galeria > p { grid-column: 1 / -1; margin: 0; }

.nx-galeria__item {
  position: relative;
  border: 2px solid transparent;
  border-radius: var(--raio-md);
  overflow: hidden;
  background: var(--bg-superficie);
}

/* ⚠️ `aspect-ratio` + `object-fit: cover` em vez de altura livre: fotos
      de proporções diferentes fariam a grade virar um mosaico irregular,
      e as ações de cada card cairiam em alturas diferentes. A miniatura
      já chega quadrada do servidor; isto protege o caso do arquivo antigo
      ou restaurado de backup. */
.nx-galeria__foto {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  background: var(--cinza-100);
}

[data-tema="escuro"] .nx-galeria__foto { background: var(--cinza-800); }

/* A foto principal é marcada pela borda, não só pelo selo: o selo some
   quando a grade fica estreita, a borda não. */
.nx-galeria__item--principal { border-color: var(--marca-500); }

.nx-galeria__selo {
  position: absolute;
  top: var(--esp-1);
  left: var(--esp-1);
  padding: 2px var(--esp-2);
  border-radius: var(--raio-full);
  background: var(--marca-500);
  color: #FFFFFF;
  font-size: var(--txt-xs);
  font-weight: var(--peso-semi);
}

/* ⚠️ As ações ficam SEMPRE visíveis, nunca só no `:hover`. No celular não
      existe hover: um botão de excluir que só aparece ao passar o mouse
      é um botão que não existe em metade dos aparelhos. */
.nx-galeria__acoes {
  display: flex;
  gap: var(--esp-1);
  padding: var(--esp-1);
  background: var(--bg-superficie);
  border-top: 1px solid var(--borda-sutil);
}

.nx-galeria__acoes .nx-btn {
  flex: 1 1 auto;
  min-width: 0;
  padding-inline: var(--esp-1);
  font-size: var(--txt-xs);
}

/* Foto sendo enviada: o lugar dela já aparece na grade, esmaecido, para
   a grade não "pular" quando o upload terminar. */
.nx-galeria__item--enviando { opacity: 0.55; }

/* ═══════════════════════════════════════════════════════════════════
   GRADE DE PERMISSÕES — módulo × ação
   ───────────────────────────────────────────────────────────────────
   ⚠️ Cada ação leva o rótulo E a explicação embaixo, não só o nome.
      "Ver custo" não diz nada a quem configura; "vê quanto a loja pagou
      na peça" diz. Quem monta um nível decide o que um funcionário vai
      enxergar do negócio, e precisa entender a consequência ANTES de
      salvar (15-UX-DIDATICA.md).
   ═══════════════════════════════════════════════════════════════════ */

.nx-permissoes__grupo { margin-bottom: var(--esp-5); }

.nx-permissoes__titulo {
  padding: 0;
  margin-bottom: var(--esp-3);
  font-size: var(--txt-xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--texto-fraco);
}

.nx-permissoes__modulo {
  padding: var(--esp-4);
  margin-bottom: var(--esp-3);
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-md);
}
.nx-permissoes__modulo:last-child { margin-bottom: 0; }

.nx-permissoes__cabecalho { margin-bottom: var(--esp-3); }

.nx-permissoes__nome {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  font-size: var(--txt-sm);
  color: var(--texto-forte);
}

/* As ações em grade: cabem 2 por linha no desktop, 1 no celular. */
.nx-permissoes__acoes {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--esp-3);
}

.nx-permissoes__acao { align-items: flex-start; }

/* ⚠️ A ação sensível (ver custo, ver financeiro, autorizar desconto) é
   destacada por uma BARRA LATERAL, não por cor de texto: cor sozinha não
   é indicador de estado, e o daltônico veria uma opção idêntica às
   outras (03-FRONTEND-PADROES.md). */
.nx-permissoes__acao--sensivel {
  padding-left: var(--esp-3);
  border-left: 3px solid var(--cor-aviso);
}

/* ═══════════════════════════════════════════════════════════════════
   CARTÃO
   ═══════════════════════════════════════════════════════════════════ */

.nx-card {
  background-color: var(--bg-superficie);
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-lg);
  box-shadow: var(--sombra-sutil);
}

.nx-card__cabecalho {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-3);
  padding: var(--esp-4) var(--esp-5);
  border-bottom: 1px solid var(--borda-sutil);
}

.nx-card__titulo {
  margin: 0;
  font-size: var(--txt-lg);
  font-weight: var(--peso-semi);
  color: var(--texto-forte);
}

.nx-card__corpo   { padding: var(--esp-5); }
.nx-card__rodape  {
  padding: var(--esp-4) var(--esp-5);
  border-top: 1px solid var(--borda-sutil);
  background-color: var(--bg-sutil);
  border-radius: 0 0 var(--raio-lg) var(--raio-lg);
}

/* Card clicável precisa de hover, foco e ativo. */
.nx-card--clicavel {
  cursor: pointer;
  transition: box-shadow var(--transicao-rapida),
              border-color var(--transicao-rapida),
              transform var(--transicao-rapida);
}
.nx-card--clicavel:hover {
  box-shadow: var(--sombra-media);
  border-color: var(--borda);
  transform: translateY(-1px);
}

/* ═══════════════════════════════════════════════════════════════════
   CARTÃO DE NÚMERO (indicador do dashboard)
   ───────────────────────────────────────────────────────────────────
   Faturamento, margem e ticket médio são o que o dono da joalheria olha
   primeiro: número grande e legível, rótulo pequeno acima.
   ═══════════════════════════════════════════════════════════════════ */

.nx-indicador {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
  padding: var(--esp-5);
  background-color: var(--bg-superficie);
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-lg);
  box-shadow: var(--sombra-sutil);
}

.nx-indicador__rotulo {
  display: flex;
  align-items: center;
  gap: var(--esp-1);
  font-size: var(--txt-xs);
  font-weight: var(--peso-medio);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--texto-fraco);
}

.nx-indicador__valor {
  font-size: var(--txt-3xl);
  font-weight: var(--peso-semi);
  line-height: 1.1;
  color: var(--texto-forte);
  font-variant-numeric: tabular-nums;
}

.nx-indicador__comparativo {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  font-size: var(--txt-xs);
  font-weight: var(--peso-medio);
}
.nx-indicador__comparativo--alta  { color: var(--cor-sucesso); }
.nx-indicador__comparativo--baixa { color: var(--cor-perigo); }

/* ═══════════════════════════════════════════════════════════════════
   BADGE E CHIP
   ───────────────────────────────────────────────────────────────────
   ⚠️ Cor nunca é o único indicador de estado — o badge sempre tem texto.
   ═══════════════════════════════════════════════════════════════════ */

.nx-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-1);
  padding: 2px var(--esp-2);
  font-size: var(--txt-xs);
  font-weight: var(--peso-medio);
  line-height: 1.5;
  border-radius: var(--raio-full);
  white-space: nowrap;
}

.nx-badge--neutro  { background: var(--cinza-200); color: var(--cinza-700); }
.nx-badge--sucesso { background: var(--cor-sucesso-suave); color: var(--cor-sucesso); }
.nx-badge--aviso   { background: var(--cor-aviso-suave);   color: var(--cor-aviso); }
.nx-badge--perigo  { background: var(--cor-perigo-suave);  color: var(--cor-perigo); }
.nx-badge--info    { background: var(--cor-info-suave);    color: var(--cor-info); }
.nx-badge--marca   { background: var(--marca-suave-hover); color: var(--marca-texto); }

[data-tema="escuro"] .nx-badge--neutro { background: var(--cinza-800); color: var(--cinza-300); }

/* ═══════════════════════════════════════════════════════════════════
   AVATAR
   ═══════════════════════════════════════════════════════════════════ */

.nx-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px; height: 40px;
  flex-shrink: 0;
  font-size: var(--txt-sm);
  font-weight: var(--peso-semi);
  color: var(--marca-texto);
  background-color: var(--marca-suave-hover);
  border-radius: var(--raio-full);
  overflow: hidden;
  object-fit: cover;
}
/* Serve tanto como <img> quanto como <span> com a foto dentro — o
   segundo caso é o do menu do usuário, que cai nas iniciais sem foto. */
.nx-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

.nx-avatar--sm { width: 32px; height: 32px; font-size: var(--txt-xs); }
.nx-avatar--lg { width: 56px; height: 56px; font-size: var(--txt-lg); }

/* ═══════════════════════════════════════════════════════════════════
   MINIATURA DE PRODUTO
   ───────────────────────────────────────────────────────────────────
   É uma joalheria: a peça é o produto, e miniatura de 40px não vende
   nada. Fundo próprio para a joia recortada em branco não sumir no
   tema escuro.
   ═══════════════════════════════════════════════════════════════════ */

.nx-miniatura {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px; height: 56px;
  flex-shrink: 0;
  background-color: #FFFFFF;   /* fixo de propósito: ver comentário */
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-md);
  overflow: hidden;
}
.nx-miniatura img { width: 100%; height: 100%; object-fit: cover; }
.nx-miniatura--sem-foto { color: var(--cinza-400); background-color: var(--cinza-100); }
.nx-miniatura--lg { width: 88px; height: 88px; }

[data-tema="escuro"] .nx-miniatura--sem-foto { background-color: var(--cinza-800); }

/* Primeira célula de uma listagem: miniatura à esquerda, texto à direita.

   ⚠️ `min-width: 0` no bloco de texto — sem ele, um nome de 92 caracteres
      sem espaços não encolhe (o mínimo implícito de um item flex é o
      tamanho do conteúdo) e empurra a tabela inteira para fora da tela.
      É o caso que a peça de demonstração com nome longo existe para pegar.

   ⚠️ `align-items: flex-start` porque o bloco de texto tem três linhas
      (nome, referência, selos) e a miniatura, 56px: centralizar faria a
      imagem flutuar no meio de uma célula alta. */
.nx-celula-com-miniatura {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
}

.nx-celula-com-miniatura__texto { min-width: 0; }

/* ═══════════════════════════════════════════════════════════════════
   OS CINCO ESTADOS
   ═══════════════════════════════════════════════════════════════════ */

/* 1. CARREGANDO — skeleton com a FORMA do conteúdo real, não um spinner
      solto no meio da tela. */
.nx-skeleton {
  position: relative;
  overflow: hidden;
  background-color: var(--cinza-200);
  border-radius: var(--raio-sm);
}
.nx-skeleton::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(90deg,
    transparent, rgba(255, 255, 255, .45), transparent);
  animation: nx-brilho 1.4s ease-in-out infinite;
}
[data-tema="escuro"] .nx-skeleton { background-color: var(--cinza-800); }
[data-tema="escuro"] .nx-skeleton::after {
  background: linear-gradient(90deg,
    transparent, rgba(255, 255, 255, .06), transparent);
}

@keyframes nx-brilho { to { transform: translateX(100%); } }

.nx-skeleton--texto  { height: 1em; margin-bottom: var(--esp-2); }
.nx-skeleton--titulo { height: 1.6em; width: 45%; margin-bottom: var(--esp-3); }
.nx-skeleton--linha  { height: 56px; margin-bottom: var(--esp-2); border-radius: var(--raio-md); }

@media (prefers-reduced-motion: reduce) {
  .nx-skeleton::after { animation: none; }
}

/* 2. VAZIO — explica E oferece o próximo passo. */
.nx-vazio {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--esp-8) var(--esp-5);
  max-width: 480px;
  margin-inline: auto;
}
.nx-vazio__icone {
  margin-bottom: var(--esp-4);
  color: var(--marca-300);
}
.nx-vazio__titulo {
  margin: 0 0 var(--esp-2);
  font-size: var(--txt-lg);
  font-weight: var(--peso-semi);
  color: var(--texto-forte);
}
.nx-vazio__texto {
  margin: 0 0 var(--esp-5);
  font-size: var(--txt-sm);
  color: var(--texto-fraco);
  line-height: var(--linha-solta);
}

/* 4. ERRO — diz o que houve E como corrigir. Nunca "Erro ao processar". */
.nx-alerta {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
  padding: var(--esp-4);
  border: 1px solid transparent;
  border-radius: var(--raio-md);
  font-size: var(--txt-sm);
  line-height: var(--linha-normal);
}
/**
 * ⚠️⚠️ `display: flex` VENCE O `hidden` NATIVO — e isso já produziu um
 *    defeito real: um alerta escondido por `hidden` (o aviso de
 *    divergência do recebimento) aparecia como uma CAIXA AMARELA VAZIA,
 *    só com o ícone, porque o `flex` acima sobrepõe o `display: none` que
 *    o navegador dá a `[hidden]`.
 *
 *    O sintoma é discreto e enganoso: parece um erro de conteúdo ("por que
 *    o texto sumiu?") quando é o elemento inteiro que não deveria estar
 *    ali. Custou uma investigação em 24/09/2026.
 *
 *    ⚠️ Toda classe que define `display` e pode receber `hidden` precisa
 *       desta linha — `.nx-sugestoes`, `.nx-painel-saldo` e `.nx-modal` já
 *       a têm, cada uma no seu bloco.
 */
.nx-alerta[hidden] { display: none; }

.nx-alerta__icone { flex-shrink: 0; margin-top: 1px; }
.nx-alerta__conteudo { flex: 1 1 auto; min-width: 0; }
.nx-alerta__titulo {
  margin: 0 0 var(--esp-1);
  font-size: var(--txt-sm);
  font-weight: var(--peso-semi);
}
.nx-alerta p:last-child { margin-bottom: 0; }

.nx-alerta--erro {
  background: var(--cor-perigo-suave);
  border-color: var(--cor-perigo);
  color: var(--cor-perigo);
}
.nx-alerta--aviso {
  background: var(--cor-aviso-suave);
  border-color: var(--cor-aviso);
  color: var(--cor-aviso);
}
.nx-alerta--sucesso {
  background: var(--cor-sucesso-suave);
  border-color: var(--cor-sucesso);
  color: var(--cor-sucesso);
}
.nx-alerta--info {
  background: var(--cor-info-suave);
  border-color: var(--cor-info);
  color: var(--cor-info);
}

/* 5. SEM PERMISSÃO — explica e diz a quem pedir; não some em silêncio. */
.nx-sem-permissao {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--esp-8) var(--esp-5);
  max-width: 460px;
  margin-inline: auto;
  color: var(--texto-fraco);
}
.nx-sem-permissao__icone { margin-bottom: var(--esp-4); color: var(--texto-fraco); }

/* ═══════════════════════════════════════════════════════════════════
   TOAST — confirmação de sucesso. Nunca modal bloqueante para "salvo".
   ═══════════════════════════════════════════════════════════════════ */

.nx-toasts {
  position: fixed;
  right: var(--esp-4);
  bottom: var(--esp-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
  max-width: min(380px, calc(100vw - var(--esp-8)));
  pointer-events: none;
}

.nx-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-4);
  background: var(--bg-elevado);
  border: 1px solid var(--borda);
  border-left: 3px solid var(--cor-info);
  border-radius: var(--raio-md);
  box-shadow: var(--sombra-elevada);
  font-size: var(--txt-sm);
  color: var(--texto-forte);
  pointer-events: auto;
  animation: nx-toast-entra var(--transicao-media);
}
.nx-toast--sucesso { border-left-color: var(--cor-sucesso); }
.nx-toast--erro    { border-left-color: var(--cor-perigo); }
.nx-toast--aviso   { border-left-color: var(--cor-aviso); }
.nx-toast--saindo  { animation: nx-toast-sai var(--transicao-media) forwards; }

.nx-toast__icone   { flex-shrink: 0; margin-top: 1px; }
.nx-toast__corpo   { flex: 1 1 auto; min-width: 0; }
.nx-toast__fechar  {
  flex-shrink: 0;
  background: none; border: 0;
  color: var(--texto-fraco);
  cursor: pointer;
  padding: 0 var(--esp-1);
  line-height: 1;
}

@keyframes nx-toast-entra {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes nx-toast-sai {
  to { opacity: 0; transform: translateX(16px); }
}

@media (max-width: 767.98px) {
  .nx-toasts {
    left: var(--esp-3);
    right: var(--esp-3);
    bottom: calc(var(--tabbar-altura) + var(--esp-3));
    max-width: none;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   TABELA
   ───────────────────────────────────────────────────────────────────
   ⚠️ Tabela larga rola DENTRO do próprio contêiner. A página nunca tem
      scroll horizontal.
   ═══════════════════════════════════════════════════════════════════ */

.nx-tabela-rolagem { width: 100%; overflow-x: auto; }

.nx-tabela {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--txt-sm);
}

.nx-tabela th {
  padding: var(--esp-3) var(--esp-4);
  text-align: left;
  font-size: var(--txt-xs);
  font-weight: var(--peso-semi);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--texto-fraco);
  background: var(--bg-sutil);
  border-bottom: 1px solid var(--borda);
  white-space: nowrap;
}

.nx-tabela td {
  padding: var(--esp-3) var(--esp-4);
  border-bottom: 1px solid var(--borda-sutil);
  color: var(--texto-normal);
  vertical-align: middle;
}

.nx-tabela tbody tr { transition: background-color var(--transicao-rapida); }
.nx-tabela tbody tr:hover { background: var(--bg-sutil); }
.nx-tabela tbody tr:last-child td { border-bottom: 0; }

.nx-tabela--clicavel tbody tr { cursor: pointer; }

/* Dinheiro e quantidade à direita, alinhados pela vírgula. */
.nx-tabela .nx-col-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.nx-tabela .nx-col-acoes { text-align: right; white-space: nowrap; width: 1%; }

/* ═══════════════════════════════════════════════════════════════════
   VISÃO EM CARD (só no celular)
   ───────────────────────────────────────────────────────────────────
   A MESMA <table> vira uma lista de cards. Não existe um segundo HTML.

   ⚠️ POR QUE ASSIM, e não um `<div class="card">` montado pelo JS:
      cada módulo teria de desenhar as linhas DUAS vezes — uma em <tr>,
      outra em card — e as duas divergiriam no terceiro mês. Pior: a
      coluna nova entraria só numa delas, e o defeito só apareceria no
      celular de um cliente.

      Aqui o JS de cada módulo só declara `data-label` em cada <td>, que
      é a mesma informação que já está no <th>. O CSS faz o resto.

   ⚠️ A TABELA CONTINUA UMA TABELA para o leitor de tela: `display: block`
      nos elementos de tabela remove a semântica de grade, então cada
      célula carrega o próprio rótulo visível — que é justamente o que o
      leitor de tela perderia.
   ═══════════════════════════════════════════════════════════════════ */

@media (max-width: 767.98px) {

  /* O cabeçalho da tabela some: cada célula passa a levar o próprio
     rótulo, e uma linha de títulos sobre cards não teria a que se
     referir. */
  .nx-tabela--cards thead { display: none; }

  .nx-tabela--cards,
  .nx-tabela--cards tbody,
  .nx-tabela--cards tr,
  .nx-tabela--cards td { display: block; width: 100%; }

  /* ⚠️ A rolagem horizontal do contêiner precisa morrer junto: com ela
     ativa, o card herda a largura da tabela e o conteúdo sai da tela.

     A classe é posta pelo JS e o `:has()` cobre o caso de ela faltar —
     não o contrário. Depender só do `:has()` deixaria o Safari 15.3 e
     anteriores com os cards cortados, e é justamente no celular que
     este modo existe. */
  .nx-tabela-rolagem--cards,
  .nx-tabela-rolagem:has(.nx-tabela--cards) { overflow-x: visible; }

  .nx-tabela--cards tr {
    padding: var(--esp-4);
    margin-bottom: var(--esp-3);
    border: 1px solid var(--borda-sutil);
    border-radius: var(--raio-md);
    background: var(--bg-superficie);
  }
  .nx-tabela--cards tr:last-child { margin-bottom: 0; }
  .nx-tabela--cards tbody tr:hover { background: var(--bg-superficie); }

  /* ⚠️ RÓTULO À ESQUERDA, VALOR À DIREITA, NA MESMA LINHA.
     Empilhar o rótulo acima do valor dobra a altura do card: com 5
     colunas ele passa de 5 para 10 linhas, e a lista deixa de caber na
     tela. Lado a lado, o olho desce pela coluna de rótulos e encontra o
     valor na horizontal — que é como se lê uma ficha. */
  .nx-tabela--cards td {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    /* ⚠️ `wrap` é o que permite o subtexto cair para a linha de baixo
       (ver `.nx-ajuda` adiante) e o que salva o par rótulo:valor quando
       os dois não cabem lado a lado numa tela estreita. */
    flex-wrap: wrap;
    gap: 0 var(--esp-4);
    padding: var(--esp-3) 0;
    border-bottom: 1px solid var(--borda-sutil);
    text-align: right;
  }

  .nx-tabela--cards td[data-label]::before {
    content: attr(data-label);
    /* ⚠️ `flex-shrink: 0` sozinho basta para o rótulo; o `min-width: 0`
       no lado do valor é que evita o estouro. Sem ele, um e-mail longo
       sem espaços não quebra e empurra o card para fora da tela. */
    flex-shrink: 0;
    font-size: var(--txt-xs);
    font-weight: var(--peso-medio);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--texto-fraco);
    text-align: left;
  }

  /* O valor ocupa o resto e quebra quando precisa. */
  .nx-tabela--cards td[data-label] > * { min-width: 0; overflow-wrap: anywhere; }

  /* ⚠️ O SUBTEXTO PRECISA FICAR SOB O VALOR, não ao lado dele.
     As células compostas ("Ativo" + "Entrou em 16/09", perfil + nível)
     têm dois elementos soltos, e num contêiner flex os dois viram itens
     lado a lado — o card mostraria "Ativo   Entrou em 16/09" numa linha
     só, espremido. `flex-basis: 100%` devolve cada um à sua linha,
     mantendo o rótulo à esquerda. */
  .nx-tabela--cards td[data-label] > .nx-ajuda { flex-basis: 100%; text-align: right; }

  /* ⚠️ A PRIMEIRA CÉLULA É O TÍTULO DO CARD, e não leva rótulo: "Filial:
     Matriz" é ruído — o nome já se identifica sozinho, e é o que a
     pessoa procura ao percorrer a lista. Ocupa a linha inteira, alinhada
     à esquerda. */
  .nx-tabela--cards td:first-child {
    display: block;
    padding-top: 0;
    text-align: left;
    font-size: var(--txt-base);
  }
  .nx-tabela--cards td:first-child::before { content: none; }

  /* Número segue o valor, à direita como os demais: dentro do card ele é
     um par rótulo:valor, não uma coluna a comparar entre linhas. */
  .nx-tabela--cards .nx-col-num { text-align: right; }

  /* ⚠️ As ações fecham o card: sem borda embaixo (é a última linha) e
     encostadas à direita, onde o polegar alcança. */
  .nx-tabela--cards .nx-col-acoes {
    width: auto;
    white-space: normal;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--esp-2);
    padding-bottom: 0;
    border-bottom: 0;
  }
  .nx-tabela--cards .nx-col-acoes::before { content: none; }
  .nx-tabela--cards .nx-col-acoes .nx-btn { min-height: var(--toque-min); }

  /* A última célula nunca leva borda, seja ela ações ou não. */
  .nx-tabela--cards td:last-child { border-bottom: 0; padding-bottom: 0; }

  /* Célula vazia não vira rótulo órfão. */
  .nx-tabela--cards td:empty { display: none; }

  /* ⚠️ A célula de pessoa (avatar + nome) fica à ESQUERDA mesmo quando
     não é a primeira: um avatar empurrado para a direita se desalinha do
     resto e parece defeito. */
  .nx-tabela--cards td > .nx-pessoa { justify-content: flex-start; }
}

/* ── O ALTERNADOR card/lista ──
   ⚠️ Só existe no celular: no desktop a tabela é sempre melhor (compara
      linhas lado a lado), e um botão que não muda nada visível ali seria
      ruído. `display: none` fora do mobile, não `hidden` no HTML — a SPA
      recria o miolo e o estado se perderia. */
.nx-visao { display: none; }

@media (max-width: 767.98px) {
  .nx-visao {
    display: inline-flex;
    gap: 2px;
    padding: 2px;
    background: var(--bg-sutil);
    border: 1px solid var(--borda-sutil);
    border-radius: var(--raio-md);
  }

  .nx-visao__botao {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--esp-2);
    min-height: 34px;
    padding: 0 var(--esp-3);
    background: transparent;
    border: 0;
    border-radius: calc(var(--raio-md) - 3px);
    font-size: var(--txt-xs);
    font-weight: var(--peso-semi);
    color: var(--texto-fraco);
    cursor: pointer;
    transition: background-color var(--transicao-rapida), color var(--transicao-rapida);
  }

  /* ⚠️ O estado ativo NÃO se distingue só pela cor: ganha o fundo
     elevado e `aria-pressed`, que é o que o leitor de tela anuncia. */
  .nx-visao__botao[aria-pressed="true"] {
    background: var(--bg-superficie);
    color: var(--texto-forte);
    box-shadow: var(--sombra-sutil);
  }
}

/* ═══════════════════════════════════════════════════════════════════
   TOOLTIP / AJUDA CONTEXTUAL
   ───────────────────────────────────────────────────────────────────
   ⚠️ Tooltip só de hover NÃO EXISTE no celular. O `nx-dica` é um
      <button>, para funcionar por toque e por teclado.
   ═══════════════════════════════════════════════════════════════════ */

.nx-dica {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  padding: 0;
  font-size: 11px;
  font-weight: var(--peso-forte);
  color: var(--texto-fraco);
  background: transparent;
  border: 1px solid var(--borda-forte);
  border-radius: var(--raio-full);
  cursor: help;
  vertical-align: middle;
}
.nx-dica:hover { color: var(--texto-forte); border-color: var(--texto-forte); }

/* ═══════════════════════════════════════════════════════════════════
   PESSOA — avatar + nome numa célula
   ───────────────────────────────────────────────────────────────────
   Listagem de usuários, vendedor da venda, responsável pela encomenda:
   o mesmo par aparece em muitas telas.
   ═══════════════════════════════════════════════════════════════════ */

.nx-pessoa {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  min-width: 0;
}
.nx-pessoa > span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ═══════════════════════════════════════════════════════════════════
   PASSOS — lista de configuração/onboarding
   ───────────────────────────────────────────────────────────────────
   ⚠️ O passo concluído NÃO se distingue só pela cor: ganha a marca de
      confirmação e o texto "(concluído)" para leitor de tela. Cor
      sozinha não é indicador de estado.
   ═══════════════════════════════════════════════════════════════════ */

.nx-passos {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--esp-4);
}

.nx-passo {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
}

.nx-passo__marca {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  margin-top: 2px;
  border: 2px solid var(--borda);
  border-radius: var(--raio-full);
  color: transparent;
}

.nx-passo--feito .nx-passo__marca {
  background: var(--cor-sucesso);
  border-color: var(--cor-sucesso);
  color: #FFFFFF;
}

.nx-passo__corpo { min-width: 0; }

.nx-passo__titulo {
  font-size: var(--txt-sm);
  font-weight: var(--peso-semi);
  color: var(--texto-forte);
}
.nx-passo--feito .nx-passo__titulo { color: var(--texto-fraco); }

.nx-passo__texto {
  margin: 2px 0 0;
  font-size: var(--txt-xs);
  color: var(--texto-fraco);
}

/* ═══════════════════════════════════════════════════════════════════
   ABAS DENTRO DE UM CARD
   ───────────────────────────────────────────────────────────────────
   Para duas listas irmãs que não merecem dois itens de menu — marcas e
   coleções são o primeiro caso, e configurações terá vários.

   ⚠️ NÃO CONFUNDIR COM `.nx-filtros`. A pílula filtra a MESMA lista; a
      aba troca a lista inteira, e a diferença precisa se ver: por isso
      uma é arredondada e a outra é sublinhada, e não duas pílulas.
      Iguais, o lojista leria "Marcas / Coleções / Todas / Ativas" como
      quatro filtros do mesmo grupo.

   ⚠️ A aba ativa NÃO se distingue só pela cor: o sublinhado e o peso da
      fonte mudam junto, e `aria-selected` conta a mesma coisa ao leitor
      de tela.
   ═══════════════════════════════════════════════════════════════════ */

.nx-abas {
  display: flex;
  gap: var(--esp-1);
  padding: 0 var(--esp-5);
  border-bottom: 1px solid var(--borda);
  overflow-x: auto;
  /* Duas ou três abas cabem em qualquer tela; a rolagem é a garantia
     para quando forem seis, sem nunca rolar a PÁGINA na horizontal. */
  scrollbar-width: none;
}

.nx-abas::-webkit-scrollbar { display: none; }

.nx-aba {
  position: relative;
  /* Alvo de toque de 44px: a aba é o primeiro controle da tela no
     celular, e errar nela troca a lista inteira. */
  min-height: 44px;
  padding: var(--esp-3) var(--esp-3);
  background: none;
  border: 0;
  /* O sublinhado ativo nasce transparente para a aba não "pular" 2px ao
     ser selecionada. */
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  font-family: inherit;
  font-size: var(--txt-sm);
  font-weight: var(--peso-medio);
  color: var(--texto-fraco);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--transicao-rapida),
              border-color var(--transicao-rapida);
}

.nx-aba:hover { color: var(--texto-forte); }

.nx-aba--ativa {
  border-bottom-color: var(--cor-primaria);
  color: var(--marca-texto);
  font-weight: var(--peso-semi);
}

/* Contador na aba: quantos itens esperam ação ali dentro (transferências
   chegando, encomendas atrasadas).
   ⚠️ Mora aqui, e não no CSS de um módulo, porque a aba é compartilhada —
      classe de componente comum no `app.css` é regra do checklist visual.
   ⚠️ Ele não é só enfeite: é o que faz a pessoa CLICAR na aba que tem
      trabalho pendente. Por isso usa a cor de aviso, e não a neutra. */
.nx-aba__contador {
  display: inline-block;
  min-width: 20px;
  margin-left: var(--esp-1);
  padding: 0 6px;
  font-size: var(--txt-xs);
  font-weight: var(--peso-semi);
  line-height: 18px;
  text-align: center;
  color: var(--cor-aviso);
  background: var(--cor-aviso-suave);
  border-radius: var(--raio-full);
}

@media (max-width: 767px) {
  .nx-abas { padding: 0 var(--esp-4); }
}

/* ═══════════════════════════════════════════════════════════════════
   FILTROS EM PÍLULA
   ───────────────────────────────────────────────────────────────────
   Filtrar por situação aparece em empresas, vendas, encomendas, contas…
   Por isso mora aqui, e não no CSS de um módulo.
   ═══════════════════════════════════════════════════════════════════ */

.nx-filtros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
}

/* ⚠️ Segue `--controle-altura` como o botão e o campo: os três dividem a
      `.nx-listagem__barra`, e alturas diferentes ali viram uma linha
      irregular. Antes era 36px fixo — igual ao desktop por coincidência,
      e 8px mais baixo que os vizinhos no celular. */
.nx-filtro {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  min-height: var(--controle-altura);
  padding: var(--esp-1) var(--esp-3);
  background: var(--bg-sutil);
  border: 1px solid var(--borda);
  border-radius: var(--raio-full);
  font-family: inherit;
  font-size: var(--txt-xs);
  font-weight: var(--peso-medio);
  color: var(--texto-normal);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--transicao-rapida),
              border-color var(--transicao-rapida),
              color var(--transicao-rapida);
}
.nx-filtro:hover { border-color: var(--borda-forte); color: var(--texto-forte); }

/* ⚠️ O filtro ativo NÃO se distingue só pela cor: `aria-pressed` conta a
   mesma coisa ao leitor de tela, e o peso da fonte muda junto. */
.nx-filtro--ativo {
  background: var(--marca-suave);
  border-color: var(--marca-suave-borda);
  color: var(--marca-texto);
  font-weight: var(--peso-semi);
}

.nx-filtro__contador {
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: var(--texto-fraco);
}
.nx-filtro--ativo .nx-filtro__contador { color: inherit; }

/* ═══════════════════════════════════════════════════════════════════
   PAGINAÇÃO
   ═══════════════════════════════════════════════════════════════════ */

.nx-paginacao {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  flex-shrink: 0;
}

/* ── Barra de busca + filtros de uma listagem ──
   Nome genérico de propósito: serve a produtos, vendas, clientes e
   empresas. Classe batizada com o nome de um módulo acaba copiada para o
   próximo, e nasce a segunda definição da mesma coisa. */
.nx-listagem__barra {
  flex-wrap: wrap;
  gap: var(--esp-3);
}

.nx-listagem__busca {
  flex: 1 1 260px;
  min-width: 0;
  margin-bottom: 0;   /* dentro do cabeçalho, não empilha */
}

/* Select de filtro na mesma barra — categoria em produtos, vendedor em
   vendas, filial em estoque.

   ⚠️ NÃO cresce como a busca (`flex: 0 1 auto`): um select com 40
      categorias e um nome de 54 caracteres esticaria a barra inteira e
      empurraria os `.nx-filtro` para a linha de baixo já no desktop.
      A largura máxima corta o nome com reticências, e o `title` do
      <option> continua legível ao abrir a lista. */
.nx-listagem__filtro {
  flex: 0 1 auto;
  max-width: 15rem;
  min-width: 0;
  margin-bottom: 0;   /* dentro do cabeçalho, não empilha */
}

.nx-listagem__rodape {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-3);
  flex-wrap: wrap;
}

@media (max-width: 767.98px) {
  .nx-listagem__rodape { flex-direction: column; align-items: stretch; }
  .nx-paginacao { justify-content: space-between; }

  /* No celular o select ocupa a linha: com `max-width` ele ficaria
     espremido ao lado da busca, e o alvo do dedo cairia abaixo dos 44px. */
  .nx-listagem__filtro { flex: 1 1 100%; max-width: none; }
}

/* ── FAIXA DE RESUMO ──
   Os poucos números que respondem "como está isto?" antes de a pessoa ler
   a lista: valor em estoque, itens, unidades. Nasce em estoque e serve a
   vendas, caixa e financeiro nas fases seguintes — por isso o nome é
   genérico, e não `.nx-estoque__resumo`.

   ⚠️ Grade com `auto-fit`, não colunas fixas: os módulos seguintes têm 2,
      3 ou 5 números, e uma grade fixa obrigaria cada um a redefinir a
      classe — que é como nasce a segunda definição da mesma coisa. */
.nx-resumo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--esp-3);
  margin-bottom: var(--esp-4);
}

.nx-resumo__item {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
  padding: var(--esp-4);
  background: var(--bg-superficie);
  border: 1px solid var(--borda-sutil);
  border-radius: var(--raio-md);
}

.nx-resumo__rotulo {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  font-size: var(--txt-sm);
  color: var(--texto-fraco);
}

/* ⚠️ `tabular-nums` importa aqui: sem ele, "R$ 1.480,00" e "R$ 128.450,00"
      alinham com larguras de dígito diferentes, e a coluna de números
      dança a cada recarga da tela. */
.nx-resumo__valor {
  font-size: var(--txt-xl);
  font-weight: 600;
  color: var(--texto-forte);
  font-variant-numeric: tabular-nums;
}

.nx-resumo__nota {
  font-size: var(--txt-xs);
  color: var(--texto-fraco);
}

/* ── LISTA DE SUGESTÕES (autocompletar) ──
   A lista que cai sob um campo de busca. Nasce no modal de estoque e será
   a mesma do PDV, do orçamento e da encomenda — por isso o nome é
   genérico.

   ⚠️ `position: absolute` exige que o `.nx-campo` que a contém seja
      `position: relative`, senão ela ancora no modal e aparece no canto.

   ⚠️ `z-index` acima do corpo do modal: sem ele a lista fica ATRÁS do
      campo seguinte, e a pessoa vê meia sugestão sem entender por quê. */
.nx-sugestoes {
  position: absolute;
  z-index: 20;
  left: 0;
  right: 0;
  margin: 0;
  padding: var(--esp-1);
  list-style: none;
  max-height: 16rem;
  overflow-y: auto;
  background: var(--bg-superficie);
  border: 1px solid var(--borda);
  border-radius: var(--raio-md);
  box-shadow: var(--sombra-elevada);
}

.nx-sugestoes[hidden] { display: none; }

/* ⚠️ O campo que contém sugestões precisa ser o berço do
      `position: absolute` delas. Sem isto a lista ancora no modal e
      aparece no canto da tela — e só em quem tem sugestão, o que faz o
      bug parecer aleatório. Marcado por classe, não em `.nx-campo`
      inteiro: `position: relative` em todo campo mudaria o empilhamento
      de telas que já estão certas. */
.nx-campo--com-sugestoes { position: relative; }

.nx-sugestoes li {
  padding: var(--esp-2) var(--esp-3);
  border-radius: var(--raio-sm);
  cursor: pointer;
  /* ⚠️ 44px de alvo de toque: no celular, uma lista de sugestões com
        linhas de 32px faz o dedo escolher a de cima. */
  min-height: 44px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
}

.nx-sugestoes li:hover,
.nx-sugestoes li[aria-selected="true"] {
  background: var(--bg-sutil);
}

.nx-sugestoes__nome { font-weight: 500; color: var(--texto-forte); }
.nx-sugestoes__meta { font-size: var(--txt-xs); color: var(--texto-fraco); }

/* ── PAINEL DE SALDO ──
   O bloco de "como está agora", mostrado antes de a pessoa digitar
   qualquer número. É o que transforma o ajuste de adivinhação em
   conferência. */
.nx-painel-saldo {
  padding: var(--esp-3) var(--esp-4);
  margin-bottom: var(--esp-4);
  background: var(--bg-sutil);
  border-radius: var(--raio-md);
}

.nx-painel-saldo[hidden] { display: none; }

.nx-painel-saldo__linha {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-3);
  padding: var(--esp-1) 0;
  font-size: var(--txt-sm);
  color: var(--texto-fraco);
}

.nx-painel-saldo__linha[hidden] { display: none; }

.nx-painel-saldo__linha strong {
  color: var(--texto-forte);
  font-variant-numeric: tabular-nums;
}

/* ── LINHA DESFEITA ──
   O movimento que foi estornado. Ele NÃO some do extrato (regra 6: nada
   se apaga) — fica visível e riscado, ao lado do estorno que o desfez.

   ⚠️ Riscado E esmaecido, não só um dos dois. O risco sozinho some numa
      tela pequena; a opacidade sozinha parece linha desabilitada. Juntos,
      o lojista lê "isto aconteceu e foi desfeito" de relance — que é a
      informação que ele procura quando desconfia de um furo. */
.nx-linha-desfeita td {
  text-decoration: line-through;
  opacity: .6;
}

/* ⚠️ Os botões da linha desfeita não são riscados: um botão riscado
      parece desabilitado, e o de "Histórico" continua servindo. */
.nx-linha-desfeita .nx-btn { text-decoration: none; }

/* ── SALDO NEGATIVO ──
   ⚠️ NUNCA DISFARÇADO (06-MOTORES.md §1.4). Saldo negativo significa que
      a loja vendeu o que não tinha registrado — é um furo, e um furo que
      se parece com um número normal dura meses.

   ⚠️ A cor não vai sozinha: o peso da fonte acompanha, porque cerca de 8%
      dos homens não distinguem vermelho de cinza. Cor como ÚNICO sinal é
      informação perdida para eles. */
.nx-saldo-negativo {
  color: var(--cor-perigo);
  font-weight: 600;
}

/* ── DIVISOR ── */
.nx-divisor {
  height: 1px;
  margin: var(--esp-5) 0;
  background: var(--borda-sutil);
  border: 0;
}

/* ── IMPRESSÃO ── nada de painel no papel. */
@media print {
  .nx-nao-imprimir,
  .nx-toasts { display: none !important; }
  body { background: #FFFFFF; color: #000000; }
}
