/* ===========================================================================
   05 · UTILITÁRIOS
   As classes que recebem os padrões que hoje vivem dentro de `style=`.

   O QUE FOI MEDIDO (08/10/2026): 1.004 atributos `style=` nas views, com
   2.042 declarações dentro deles. Elas não são 2.042 ideias diferentes: as 20
   primeiras cobrem a maior parte, e as 5 campeãs são
     133x  color: var(--text-muted)
      92x  display: flex
      80x  margin-right: 8px      (todas em <i> de ícone antes de um texto)
      52x  text-align: right      (sempre em th/td de coluna de valor)
      48x  margin-bottom: 24px

   NENHUMA CLASSE DAQUI TEM USUÁRIO HOJE, e isso é de propósito: esta fase não
   pode mudar a tela. A conversão é a fase seguinte, que mexe em view. Uma
   classe nova sem usuário custa bytes e não muda pixel; declarar a linguagem
   antes de falar nela é justamente o ponto.

   REGRA DE QUEM CONVERTER: utilitário é para o padrão ANÔNIMO e repetido
   (um respiro, um alinhamento). Quando o padrão tem nome no domínio (a linha
   do painel de cobrança, o cartão da carteira), ele vira componente em
   04-componentes.css, não utilitário aqui.
   =========================================================================== */

/* ── Texto ─────────────────────────────────────────────────────────────── */
/* `color: var(--text-muted)`, 133 ocorrências: é de longe o padrão mais
   repetido do sistema. */
.txt-fraco   { color: var(--text-muted); }
.txt-gold    { color: var(--gold); }
.txt-gold-claro { color: var(--gold-light); }
.txt-forte   { color: white; }
.txt-ok      { color: var(--secondary); }
.txt-alerta  { color: var(--warning); }
.txt-erro    { color: var(--danger); }
/* Branco a 80%: a legenda de apoio DENTRO de um cartão colorido, onde
   `--text-muted` (#888) não tem contraste. 9 ocorrências, todas em <small>. */
.txt-suave   { color: var(--branco-a80); }
/* Preto puro, não `--dark` (#0A0A0A): é o texto sobre o fundo dourado sólido
   do badge, e trocar por #0A0A0A mudaria pixel. */
.txt-preto   { color: black; }

/* Dinheiro em tabela que precisa alinhar dígito com dígito. */
.mono        { font-family: monospace; }

.t-nota      { font-size: var(--txt-nota); }
.t-mini      { font-size: var(--txt-mini); }
.t-rotulo    { font-size: var(--txt-rotulo); }
.t-corpo     { font-size: var(--txt-corpo); }
.t-destaque  { font-size: var(--txt-destaque); }
.t-titulo    { font-size: var(--txt-titulo); }
.t-titulo-g  { font-size: var(--txt-titulo-g); }
.t-pagina    { font-size: var(--txt-pagina); }

.peso-medio  { font-weight: var(--peso-medio); }
.peso-forte  { font-weight: var(--peso-forte); }
/* 400 não é token: a escala do projeto começa em 500 (`--peso-normal`). Esta
   classe existe para DESFAZER o negrito herdado (o <small> dentro de um <th>,
   o rótulo dentro de um <strong>), e é por isso que ela tem o valor cru. */
.peso-leve   { font-weight: 400; }

/* Texto que pega a cor do pai: o telefone dentro de uma célula já cinza, onde
   o <a> traria o azul do navegador. */
.txt-herda   { color: inherit; }

.centro      { text-align: center; }
.direita     { text-align: right; }
/* `text-align: right` em th/td de coluna de valor, 52 ocorrências. A classe
   tem nome próprio porque o motivo é semântico (é número, e número alinha
   pela direita) e não um alinhamento qualquer: quem converter sabe que a
   mesma classe vale para o <th> e para os <td> daquela coluna. */
.col-num     { text-align: right; }

.sem-quebra  { white-space: nowrap; }
.sem-sublinhado { text-decoration: none; }
/* `display: inline` explícito em <div>/<span> que o JS mostra e esconde, e em
   rótulo que precisa fluir dentro do texto. */
.em-linha    { display: inline; }
/* Valor em reais longo não pode esticar o card: ver a regra de
   responsividade no CLAUDE.md. */
.quebra-tudo { overflow-wrap: anywhere; }

/* ── Ícone colado num texto ────────────────────────────────────────────────
   `margin-right: 8px`, 80 ocorrências, TODAS em <i> antes de um rótulo. */
.icone { margin-right: var(--esp-2); }

/* A MARGEM ACIMA SÓ VALE ONDE O PAI NÃO SEPARA SOZINHO.
   Ela nasceu para substituir os 80 `style="margin-right: 8px"` dos antigos <i>,
   mas `.icone` é a classe base de TODO ícone do sprite, então caía também
   dentro de contêiner com `gap` e a separação dobrava. Medido:
     .btn       gap  8 + margem 8 = 16px entre ícone e texto
     .nav-item  gap 12 + margem 8 = 20px na sidebar
   e, no botão só de ícone, ela inflava a caixa: 44x24 onde deveria ser 36x24,
   com 12px de folga à esquerda contra 20px à direita, 4px fora do centro, em 84
   botões de /loans, 40 de /clients e 150 de /blacklist.
   Em /elite-cred/loans, dos 149 ícones visíveis 126 têm pai com gap (a margem é
   duplicação) e 23 não têm (ali ela é legítima e continua valendo).

   A lista é só de contêiner que REALMENTE tem gap, conferida com sonda no
   navegador. `.g-1`…`.g-6` entram porque são elas que carregam o gap das caixas
   da fase 5: `.linha`, `.em-fila`, `.coluna` e `.grade` não declaram gap
   nenhum, quem declara é a escada. Zerar também em `.coluna`/`.grade` é
   correto por outro motivo: ali `margin-right` não separa de item nenhum.
   Especificidade (0,2,0) contra os (0,1,0) da regra acima. */
:is(.btn, .nav-item, .back-link, .card-header, .quick-actions, .cobranca-acoes,
    .barra-filtros, .logo, .logo-icon, .header-logo-icon, .alert, .grade, .coluna,
    .barra-inf-item, .stat-icon, .empty-state, .menu-toggle, .mobile-menu-btn,
    .g-1, .g-2, .g-3, .g-4, .g-5, .g-6) > .icone {
    margin-right: 0;
}

/* A TERCEIRA FURADA foi o `.menu-toggle`, achada pelo dono num print do celular:
   botao de 44x44 com o hamburguer 4px a esquerda do centro, em 5 telas do
   portal. O botao ja declarava `justify-content: center`; quem o deslocava era
   a margem, que ali nao separa de nada. `.mobile-menu-btn` (o irmao dele nas
   telas publicas) entrou junto, embora passe hoje: ele so passa porque a
   landing nao carrega esta folha, e no dia em que carregar o defeito nasce
   pronto.

   A varredura que o achou procurou pela DEFINICAO do defeito, nao pelo nome:
   icone com margem direita e sem texto depois, em 22 telas e 2 larguras. Ela
   tambem rendeu um FALSO POSITIVO que vale guardar, porque e o espelho exato
   da armadilha descrita abaixo: na tela de Recuperacao o icone do WhatsApp e
   filho unico de um `<a>`, mas o telefone vem logo depois do `</a>`. O texto
   existe, so esta FORA do pai, e ali a margem e o unico espaco entre os dois
   links. Zerar coloria um no outro. Ou seja: nem "ha texto dentro do pai" nem
   "e filho unico" descrevem a regra; o que descreve e "ha algo visivel do lado
   direito", e isso o CSS nao pergunta.

   POR QUE ESTA LISTA NAO VIRA UMA REGRA ESTRUTURAL, ja que ela ja furou tres
   vezes (a barra inferior nasceu depois dela, o `.logo-icon` ficou de fora
   porque o seletor e filho direto e la o SVG mora um nivel mais fundo, e o
   `.menu-toggle` acima):
   tentou-se `.icone:only-child`, com o argumento de que icone sozinho nao tem
   de que se separar. Nao funciona, e o erro e sutil: `:only-child` conta
   ELEMENTOS, e no corado de texto nao e elemento. Num
   `<h2 class="card-title"><svg class="icone"/>Filtros</h2>` o SVG e o unico
   elemento filho, a regra casa, e a margem que ali e o UNICO espacamento some,
   colando o icone na palavra. Aconteceu em todo `.card-title` do sistema.
   CSS nao tem como perguntar "ha texto depois de mim", entao a lista fica.
   Quem criar componente novo com icone sozinho acrescenta a classe aqui. */


/* ── Caixa flexível ────────────────────────────────────────────────────────
   `display:flex` (92) quase sempre vem com `align-items:center` (49) e um
   `gap` (8px em 32, 12px em 30, 16px em 18). A classe junta as três coisas
   que andam sempre juntas; o gap continua escolhido à parte. */
.linha  { display: flex; align-items: center; }
/* Flex SEM alinhamento vertical. Existe separada de `.linha` porque trocar o
   `align-items: stretch` do padrão por `center` muda a altura dos filhos: numa
   fila de botões com um <form> no meio, o <form> deixa de esticar. */
.em-fila { display: flex; }
.coluna { display: flex; flex-direction: column; }
.entre  { justify-content: space-between; }
.ao-fim { justify-content: flex-end; }
.fim    { align-items: flex-end; }
.quebra { flex-wrap: wrap; }

/* DENTRO DE CELULA DE TABELA A FILA DE BOTOES NUNCA QUEBRA.
   Mapeado em todas as telas do painel, a 1500 e a 390px: 10 celulas empilhavam,
   e elas usam TRES contêineres diferentes, com causas diferentes:
     .quick-actions ... 12 usos, quebra por `flex-wrap: wrap` quando a coluna
                        aperta. A 390px levava a linha a 169px, e a 221px na
                        tela de Clientes, que tem 4 botoes;
     form ............. 4 usos, quebra porque <form> e `display: block` e cada
                        um ocupa a linha inteira sozinho;
     div + form ....... 2 usos, os dois problemas juntos.
   A coluna pode crescer sem custo nenhum: o `.table-container` ja rola na
   horizontal, e linha baixa mostra mais registros, que e para o que a tabela
   existe. Corrigir caso a caso nao funcionou: cada tela usa um contêiner. */
.table-container td :is(.em-fila, .quick-actions, .cobranca-acoes, form) {
    display: inline-flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--esp-2);
    vertical-align: middle;
}

/* ...MENOS O FORM QUE SO ENVOLVE UM BOTAO, que e o caso dos 4 usos citados
   acima. Com `inline-flex` ele vira caixa com gap proprio e mede 49px para um
   botao de 40: a sobra de 9px cai DEPOIS do botao e soma ao gap do grupo, e a
   fila sai com 8px entre os primeiros e 14px antes do ultimo. So aparece no
   celular, porque e la que a regra de toque da `min-width: 40px` ao botao e a
   caixa passa a sobrar.

   `contents` tira a caixa do form do LAYOUT, nao o elemento: `action`,
   `method`, o input hidden e o submit seguem iguais, e o botao vira item
   direto do grupo, com o mesmo gap dos vizinhos.

   A especificidade precisa passar os (0,2,1) da regra acima: esta da (0,2,2). */
.table-container td :is(.em-fila, .quick-actions, .cobranca-acoes) > form {
    display: contents;
}

/* Grade que se acomoda sozinha. É a forma que o CLAUDE.md manda usar, e já
   aparece 18 vezes escrita à mão dentro de `style=`, com o mesmo mínimo de
   240px. Coluna fixa (`repeat(4, 1fr)`, `1fr 1fr`) está proibida: `1fr` tem
   mínimo `auto` e estoura. */
.grade { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); gap: var(--esp-4); }
/* Mesma grade com coluna mais larga, onde o card tem tabela dentro. O
   modificador fica AQUI, logo depois da `.grade`, e não em 04-componentes:
   lá ele vem antes e perde a disputa de especificidade em silêncio. */
.grade.larga { grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); }

/* BLOCO DE FORMULARIO: coluna de 340px.
   Nao e numero escolhido no olho. A 768px o conteudo util e ~736, e duas
   colunas com o gap de 24 ficam com 356 cada: com 340 de minimo elas cabem
   ali e NAO cabem a 600 (util ~568), que e onde precisam empilhar.
   Os blocos de formulario traziam `minmax(min(240px, 100%), 1fr)` escrito
   inline, e a 597px isso dava duas colunas de 275 com os campos cortados. */
.grade.formulario { grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr)); }

/* MESMO CONTEUDO EM DOIS LUGARES DA TELA, um por largura.
   Existe porque CSS nao move um elemento entre dois conteineres: quando o
   celular pede uma peca em outro lugar da pagina, ela e escrita nos dois e so
   um aparece. O corte e 768px, o mesmo em que a barra do topo empilha os
   botoes.
   Use com parcimonia, e NUNCA com texto calculado em cada copia: o valor vem
   de uma variavel so, senao as duas versoes divergem e ninguem percebe,
   porque nunca estao na tela ao mesmo tempo. */
.so-no-celular { display: none; }
@media (max-width: 768px) {
    .so-no-celular { display: inline-flex; }
    .so-no-desktop { display: none; }
}

/* A escada de `gap` vem DEPOIS da `.grade` de propósito. As duas têm a mesma
   especificidade (uma classe), então quem ganha é quem aparece por último no
   arquivo. Declarada antes, `.grade .g-6` deixava o gap em 16px e as 4 grades
   de 24px das views encolhiam 8px sem erro nenhum. */
.g-1 { gap: var(--esp-1); }
.g-2 { gap: var(--esp-2); }
.g-3 { gap: var(--esp-3); }
.g-4 { gap: var(--esp-4); }
.g-5 { gap: var(--esp-5); }
.g-6 { gap: var(--esp-6); }

/* Item de flex que ocupa a linha inteira e força quebra (rodapé de um grupo de
   filtros, aviso que não divide espaço com os vizinhos). */
.linha-inteira { flex: 1 1 100%; }

/* `min-width: 0` em 16 lugares: é o que deixa um filho de grid/flex encolher
   abaixo do conteúdo. Sem ele a página inteira estica. */
.encolhe { min-width: 0; }

/* ── Respiro ───────────────────────────────────────────────────────────────
   Só os degraus que a medição mostrou valendo a pena. Margem lateral não
   entrou: são 3 ocorrências no sistema todo. */
.mt-0 { margin-top: 0; }
.mt-1 { margin-top: var(--esp-1); }
.mt-2 { margin-top: var(--esp-2); }
.mt-3 { margin-top: var(--esp-3); }
.mt-4 { margin-top: var(--esp-4); }
.mt-6 { margin-top: var(--esp-6); }

.mb-0 { margin-bottom: 0; }
.mb-1 { margin-bottom: var(--esp-1); }
.mb-2 { margin-bottom: var(--esp-2); }
.mb-3 { margin-bottom: var(--esp-3); }
.mb-4 { margin-bottom: var(--esp-4); }
.mb-5 { margin-bottom: var(--esp-5); }
.mb-6 { margin-bottom: var(--esp-6); }

/* Margem lateral: só o degrau de 8px, que é o respiro entre um valor e o
   badge que vem colado nele, e entre dois botões da barra do topo. */
.ml-2 { margin-left: var(--esp-2); }
.mr-2 { margin-right: var(--esp-2); }

.p-3 { padding: var(--esp-3); }
.p-4 { padding: var(--esp-4); }
.p-6 { padding: var(--esp-6); }

/* ── Caixa ─────────────────────────────────────────────────────────────── */
.clicavel { cursor: pointer; }

/* ── Visibilidade ─────────────────────────────────────────────────────────
   CUIDADO AO CONVERTER: dos 15 `style="display: none"` das views, vários são
   o estado INICIAL de um elemento que o JavaScript liga depois com
   `elemento.style.display = 'block'` (busca de CEP em clients-form.ejs,
   gráfico em loans-index.ejs). Trocar por `.oculto` continua funcionando,
   porque estilo inline vence classe, MAS só enquanto o JavaScript escrever em
   `style.display`. Se algum dia ele passar a LER `style.display` para decidir
   o que fazer, a leitura volta vazia e a lógica inverte sem erro nenhum.
   Converter esses casos exige abrir o script junto. */
.oculto  { display: none; }
.invisivel { visibility: hidden; }

/* Item de flex que cresce a partir de uma base. Os três valores são os que
   existem hoje nos formulários: o campo de busca e os dois campos de motivo. */
.cresce-220 { flex: 1 1 220px; }
.cresce-260 { flex: 1 1 260px; }
.cresce-320 { flex: 1 1 320px; }

.ml-1 { margin-left: var(--esp-1); }

.p-8 { padding: var(--esp-8); }
.bloco { display: block; }
.sublinhado { text-decoration: underline; }
/* O contador "de N na carteira" ao lado do total: fica meio apagado porque é
   contexto, não o número que a linha anuncia. */
.opaco-75 { opacity: .75; }


/* ── A marca ───────────────────────────────────────────────────────────────
   Traco BRANCO e ocupando mais da caixa, por decisao do dono em 08/10/2026.
   O tamanho vai em PORCENTAGEM, nao em px, porque a caixa do logo tem medidas
   diferentes em cada contexto: 40px na sidebar do painel, 44px na do portal e
   outra nas telas publicas. Em px, o icone ficaria proporcionalmente menor
   justamente na caixa maior.
   A COR NAO E DECLARADA de proposito: o simbolo usa currentColor e os quatro
   conteineres de logo ja definem o escuro. Chegou a ser branco por um dia, e
   voltou: branco sobre o dourado da 2,1:1, abaixo dos 3:1 que a WCAG pede para
   elemento grafico, enquanto o escuro da 9,42:1. */
:is(.logo-icon, .header-logo-icon) > .icone {
    width: 65%;
    height: 65%;
}
