    /* (1.1.11) ABA CAMPANHAS — saldo das listas                              */
    /* ---------------------------------------------------------------------- */
    .precs-sup-abas{ margin-bottom:var(--space-4); max-width:420px; }
    /* (1.1.100) O FILTRO DA ABA CAMPANHAS reusa `.precs-sup-filtros`/`.precs-sup-busca` do roster —
       mesma caixa, mesmo seletor, mesma contagem à direita. Só o que é dele fica aqui. */
    .precs-campt-filtro{ margin-bottom:var(--space-4); }
    .precs-campt-filtro .precs-sup-contagem{ color:var(--text-secondary); }
    html[data-tv] .precs-campt-filtro{ display:none; }
    /* O `hidden` do filtro precisa destas duas linhas: `.precs-campsaldo` é `display:flex` e o
       painel de seção tem `display` de autor — sem o par declarado, `card.hidden = true` não
       esconderia nada (a armadilha que já mordeu quatro vezes). */
    .precs-campsaldo[hidden]{ display:none; }
    .precs-campt-painel[hidden]{ display:none; }
    /* (1.1.100) RESPIRO ENTRE AS SEÇÕES. Cada seção é um `.panel` com moldura própria, e elas
       nasciam grudadas uma na outra — lia como um painel só com várias divisórias. Pedido do
       Pedro, 18/09/2026: "estão grudadas mas são cards, ficaria melhor com um espaçamento". */
    #sup-camp-todas > .precs-campt-painel{ margin-top:var(--space-5); }
    /* AS BOLHAS DAS SEÇÕES, na barra do filtro. Mesmo vocabulário das pílulas da aba: pastilha
       com borda, rótulo em `--font-acao` e a contagem em número tabular. `[hidden]` declarado
       porque o `display` é de autor. */
    .precs-campt-secoes{ display:inline-flex; flex-wrap:wrap; align-items:center; gap:var(--space-2); }
    .precs-campt-secoes[hidden]{ display:none; }
    .precs-campt-chip{
      display:inline-flex; align-items:center; gap:var(--space-2); min-height:28px;
      padding:var(--space-1) var(--space-3); border-radius:var(--radius-pill);
      border:1px solid var(--border-color); background:var(--bg-secondary); color:var(--text-secondary);
      font-family:var(--font-acao); font-size:var(--fs-3); font-weight:600; cursor:pointer; white-space:nowrap;
      transition:border-color var(--dur-1) ease, color var(--dur-1) ease, background var(--dur-1) ease;
    }
    .precs-campt-chip b{ font-family:var(--font-num); font-weight:700; color:var(--text-primary); }
    .precs-campt-chip.alerta b{ color:var(--accent-red-text); }
    .precs-campt-chip:hover{ color:var(--text-primary); border-color:var(--precs-gold);
      background:color-mix(in srgb, var(--precs-gold-forma) 8%, var(--bg-secondary)); }
    /* (1.1.166) AS BOLHAS GANHAM CORPO. "Os filtros de Ao vivo, Listas no fim, Follow, Propostas estagnadas estão sem
       destaque, quase imperceptíveis" (Pedro, 29/09/2026): fundo `bg-secondary` sobre a barra do mesmo tom, borda
       de 1px da cor da divisória e rótulo de `fs-3`. Agora o fundo sobe um degrau, a borda tem cor, o rótulo cresce
       e a contagem vira um selo dourado; "Listas no fim", que é a lista de AÇÃO, leva o contorno vermelho. */
    .precs-campt-chip{ min-height:32px; font-size:var(--fs-4); color:var(--text-primary); background:var(--bg-tertiary);
      border-color:color-mix(in srgb, var(--precs-gold-forma) 40%, var(--border-color)); }
    .precs-campt-chip b{ min-width:22px; padding:1px var(--space-2); border-radius:var(--radius-pill); text-align:center;
      background:var(--precs-gold-light); color:var(--precs-gold-title); }
    .precs-campt-chip.alerta{ border-color:color-mix(in srgb, var(--accent-red) 60%, var(--border-color)); }
    .precs-campt-chip.alerta b{ background:color-mix(in srgb, var(--accent-red) 16%, transparent); color:var(--accent-red-text); }
    .precs-campt-chip:hover{ border-color:var(--precs-gold); }
    .precs-campt-chip:focus-visible{
      outline:var(--focus-ring-largura) solid var(--focus-ring-cor); outline-offset:var(--focus-ring-folga); }
    /* O DESTINO ACENDE por um instante depois da rolagem: sem isto a tela para e o olho não sabe
       em qual das cinco molduras iguais ele deveria estar. `outline`, nunca `box-shadow` — a
       catraca de estilo conta `box-shadow: 0 0 0`. */
    .precs-campt-alvo, .precs-campsaldo-alvo{ animation:precsCamptAlvo 1.6s var(--ease-1) 1; }
    @keyframes precsCamptAlvo{
      0%   { outline:3px solid color-mix(in srgb, var(--precs-gold-forma) 85%, transparent); outline-offset:var(--space-1); }
      100% { outline:3px solid transparent; outline-offset:var(--space-3); }
    }
    @media (prefers-reduced-motion: reduce){
      .precs-campt-chip{ transition:none; }
      .precs-campt-alvo, .precs-campsaldo-alvo{ animation:none; }
    }
    /* (1.1.100) A PÍLULA DESLIZA. O `.active` do `.precs-seg` pinta a pastilha no botão; aqui ela
       vira um `::before` do trilho posicionado por duas variáveis que o JS mede
       (`precsSupAbasPilula`) — e a transição faz a pastilha ANDAR de uma aba para a outra em vez
       de sumir e reaparecer. Enquanto o JS não mediu (`data-pilula` ausente), o `.active` continua
       pintando o botão: nunca há um quadro sem indicação de aba. A aba WhatsApp mantém o fundo
       verde dela por cima (regra própria em 70-supwhatsapp.css, mais específica e carregada
       depois): a pílula chega embaixo, e o verde continua dizendo "outro canal". */
    .precs-sup-abas{ position:relative; }
    /* Sem `z-index`: o `::before` é o primeiro filho posicionado e os botões, também posicionados,
       pintam depois dele na ordem do DOM — a pílula fica embaixo por construção. */
    .precs-sup-abas > button{ position:relative; }
    .precs-sup-abas::before{
      content:''; position:absolute; top:var(--space-1); bottom:var(--space-1); left:0;
      width:var(--aba-w, 0px); transform:translateX(var(--aba-x, 0px));
      background:var(--bg-secondary); border-radius:calc(var(--radius-inner) - 1px);
      box-shadow:var(--shadow); opacity:0;
      transition:transform var(--dur-3) var(--ease-2), width var(--dur-3) var(--ease-2), opacity var(--dur-2);
    }
    .precs-sup-abas[data-pilula]::before{ opacity:1; }
    .precs-sup-abas[data-pilula] > button.active,
    .precs-sup-abas[data-pilula] > button.active:hover{ background:transparent; box-shadow:none; }
    @media (prefers-reduced-motion: reduce){ .precs-sup-abas::before{ transition:none; } }
    /* Selo de alerta na aba: é o que faz o aviso chegar a quem está na aba de Operadores.
       Vermelho de acento, não o dourado neutro dos outros selos — este pede ação. */
    .precs-sup-selo-alerta{
      background:color-mix(in srgb, var(--accent-red) 18%, transparent) !important;
      color:var(--accent-red-text) !important;
      border-color:color-mix(in srgb, var(--accent-red) 45%, transparent) !important;
    }
    /* (1.1.11) A GRADE USA O ESPAÇO QUE TEM.
       A primeira escrita usou `minmax(260px, 1fr)` com `auto-fill`, e o resultado em tela de
       1920 foi quatro cards apertados ocupando um quinto da largura, com o resto vazio — a
       tela de supervisão é a única do painel que roda em monitor grande, e ali `auto-fill`
       com mínimo pequeno espreme em vez de preencher.
       `auto-fit` + mínimo de 340px: com 4 campanhas eles dividem a largura inteira; com 12,
       quebram em duas linhas confortáveis. O `max-width` de 620px por card impede que UMA
       campanha sozinha vire um card de dois metros, que era o motivo de eu ter fugido do
       `auto-fit` — resolvido no lugar certo, sem espremer os outros casos. */
    .precs-campsaldo-grade{
      display:grid; gap:var(--space-4);
      grid-template-columns:repeat(auto-fit, minmax(340px, 1fr));
      padding:var(--space-4);
      /* `1fr` nas fileiras + `stretch` nos itens: todos os cards de uma fileira têm a altura do
         mais alto dela. Era `align-items:start`, e aí cada card tinha a altura do próprio
         conteúdo — a causa do "alguns cards têm tamanhos diferentes" de 20/08. */
      grid-auto-rows:1fr;
      align-items:stretch;
    }
    /* ======================================================================
       (1.1.15 · 8ª volta) FIXAR E ARRASTAR os cards de campanha.

       O topo do card ganhou duas affordances, e as duas são discretas até o hover do CARD: em
       repouso o supervisor está lendo números, e seis estrelas piscando na tela competiriam com o
       vermelho de "lista no fim", que é informação. Ficam visíveis quando o cursor entra no card —
       exceto a estrela JÁ FIXADA, que é estado e precisa ser vista de longe.
       ====================================================================== */
    .precs-campsaldo-topo{ position:relative; }
    .precs-campsaldo-acoes{
      display:inline-flex; align-items:center; gap:var(--space-1); margin-left:auto; flex:0 0 auto;
    }
    /* (Parte 4, 18/09/2026) VINTE E OITO, e não vinte e quatro, e a MESMA caixa para os três.
       A engrenagem tinha regra própria sem tamanho (só `padding`), então os três controles do
       mesmo nível saíam com três alvos diferentes. 28px é o que cabe na linha do nome sem
       empurrá-lo e ainda perdoa um clique meio torto — o card inteiro agora abre a modal, e um
       controle pequeno demais faz o clique na estrela virar abertura de modal. */
    /* (1.1.100, segunda volta) BOTÕES DE VERDADE, e não ícones que aparecem. O Pedro, com os
       prints da 3C ao lado: "ainda acho os botões de configuração, favoritar e arrastar muito
       sumidos, poderíamos dar mais vida a eles, ícones coloridos ou botões de verdade". Então cada
       um é uma pastilha: fundo de 6% da tinta, borda da moldura, ícone na tinta secundária — e o
       hover acende em ouro. A estrela FIXADA é uma pastilha acesa (fundo e borda em ouro de
       forma), que é o que se reconhece de longe. Nada de opacidade: a pastilha existe sempre. */
    .precs-campsaldo-fix, .precs-campsaldo-alca, .precs-campsaldo-cfg{
      display:inline-flex; align-items:center; justify-content:center;
      width:28px; height:28px; border-radius:var(--radius-sm);
      background:color-mix(in srgb, var(--text-primary) 6%, transparent);
      border:1px solid var(--border-color); color:var(--text-secondary);
      transition:background var(--dur-1) ease, color var(--dur-1) ease, border-color var(--dur-1) ease;
    }
    .precs-campsaldo-fix{ cursor:pointer; }
    /* A alça é `cursor:grab`, e é ela que diz "isto se pega". Sem o cursor, uma coluna de seis
       pontinhos não se anuncia como nada. */
    .precs-campsaldo-alca{ cursor:grab; }
    .precs-campsaldo-alca:active{ cursor:grabbing; }
    .precs-campsaldo-alca{ color:var(--text-muted); }
    .precs-campsaldo-fix:hover, .precs-campsaldo-alca:hover, .precs-campsaldo-cfg:hover{
      color:var(--precs-gold-title);
      background:color-mix(in srgb, var(--precs-gold-forma) 12%, transparent);
      border-color:color-mix(in srgb, var(--precs-gold-forma) 55%, transparent);
    }
    /* A ESTRELA QUE ACABOU DE ACENDER pisca uma vez: cresce e assenta, e o giro é o mesmo
       vocabulário da `precsEstrelaCalma` do estado vazio (quatro pontas, quarto de volta).
       É a classe que `precsSupCampFixar` põe no nó NOVO depois da repintura; sem ela o
       clique na estrela era a única ação do card que não respondia com nada além da cor. */
    .precs-campsaldo-fix-pisca svg{ animation:precsEstrelaFixa var(--dur-3) var(--ease-1) 1 both; }
    @keyframes precsEstrelaFixa{
      0%   { transform:scale(.6) rotate(-30deg); }
      60%  { transform:scale(1.28) rotate(8deg); }
      100% { transform:scale(1) rotate(0deg); }
    }
    @media (prefers-reduced-motion: reduce){ .precs-campsaldo-fix-pisca svg{ animation:none; } }
    /* FIXADA é ESTADO, não ação: a estrela cheia fica sempre visível, e em dourado — é o que
       permite reconhecer de longe que aquele card está na faixa do topo. */
    .precs-campsaldo-fix.on{
      color:var(--precs-gold-title);
      background:color-mix(in srgb, var(--precs-gold-forma) 16%, transparent);
      border-color:color-mix(in srgb, var(--precs-gold-forma) 60%, transparent);
    }
    @media (prefers-reduced-motion: reduce){
      .precs-campsaldo-fix, .precs-campsaldo-alca, .precs-campsaldo-cfg{ transition:none; }
    }

    /* A faixa das fixadas: mesma moldura das outras seções, com o cabeçalho em dourado para dizer
       "isto foi você que escolheu", e não "isto a 3C reportou". */
    .precs-campt-painel.fixadas .panel-header{
      color:var(--precs-gold-title, #D3B26B);
      border-bottom-color:color-mix(in srgb, var(--precs-gold, #B49552) 40%, var(--border-color));
    }
    /* O card fixado ganha um fio dourado à esquerda em QUALQUER seção onde apareça, porque ele
       aparece em duas (a faixa do topo e a de finalidade) e o fio é o que liga uma à outra. */
    .precs-campsaldo-fixada{
      box-shadow:inset 3px 0 0 0 var(--precs-gold, #B49552);
    }

    /* OS TRÊS ESTADOS DO ARRASTE, do SortableJS. Sem eles o card salta de posição sem nada dizer
       que está sendo movido, que é a diferença entre "fluido" e "quebrado":
         · fantasma — o buraco onde ele vai cair (o que o Sortable chama de placeholder);
         · pego     — o card de origem, enquanto o dedo está nele;
         · voando   — o clone que segue o cursor. */
    .precs-campsaldo-fantasma{
      opacity:.32;
      background:color-mix(in srgb, var(--precs-gold, #B49552) 12%, var(--bg-tertiary));
      border-style:dashed;
      border-color:color-mix(in srgb, var(--precs-gold, #B49552) 55%, var(--border-color));
    }
    .precs-campsaldo-pego{ cursor:grabbing; }
    .precs-campsaldo-voando{
      opacity:.95;
      box-shadow:0 18px 40px -12px rgba(0,0,0,.55), 0 0 0 1px var(--precs-gold, #B49552);
      transform:rotate(.6deg);   /* levíssimo: diz "na mão" sem virar brinquedo */
    }
    @media (prefers-reduced-motion: reduce){
      .precs-campsaldo-voando{ transform:none; }
    }

    /* ======================================================================
       (1.1.15 · 7ª volta) TODAS AS CAMPANHAS, EM SEÇÕES DE VERDADE.

       A 6ª volta fez isto em LINHA (`.precs-campt-linha`, um grid de quatro colunas com o número
       encostado na direita), e o argumento estava escrito aqui: "deliberadamente mais seca que o
       card da campanha ao vivo; aqui são dezenas de linhas e o que se faz com elas é varrer com o
       olho". O print de 19/08 com 38 campanhas mostrou o preço do argumento — o dado que decide
       ação (quanto falta discar, quantas listas morreram) virou 0,7rem no canto, e informação que
       não se lê é informação que não está na tela.

       Agora cada seção é um PAINEL, com a mesma moldura e o mesmo cabeçalho da seção ao vivo, e
       dentro dele o MESMO card (`.precs-campsaldo`). O que sobrou de específico aqui é pouco de
       propósito: se precisasse de muito, não seria "igual à seção do ao vivo".

       O `.precs-campt-linha` e os `.precs-campt-selo*` saíram junto com o markup deles. CSS órfão
       neste arquivo já custou meia hora nesta mesma volta: `.precs-esp-tempos` continuava valendo
       `display:grid` de quatro colunas e quebrou o bloco de tempos do espelho quando o nome foi
       reusado.
       ====================================================================== */
    /* Uma seção não repete a distância do painel de cima: elas são uma pilha de irmãos, e o
       espaçamento entre painéis já vem de `.precs-sup-panel`. */
    .precs-campt-painel .panel-header{ cursor:help; }
    /* A seção de alerta é a única colorida: ela é a que pede ação. Mesma regra da versão em
       linha, agora no cabeçalho do painel. */
    /* (1.1.43) O VERMELHO SAIU DO LITERAL E VIROU O TOKEN DE TEXTO.
       `#ef4444` cravado é o vermelho calibrado para fundo ESCURO, e este cabeçalho é texto: nos
       temas claros ele dava 3,0:1 sobre a superfície do card e 3,8:1 sobre o branco puro —
       reprovava AA desde sempre, e ninguém media porque nenhuma sonda cobria o cabeçalho de
       painel. A faixa da cabeça piorou para 2,8:1 no Bordô claro, e foi assim que apareceu.
       `--accent-red-text` existe exatamente para isto (#B42318 no claro, o próprio acento no
       escuro) — é a mesma regra do `--accent-orange-text` da 1.1.32: acento cru é FORMA, e como
       TEXTO sobre fundo claro usa-se a variante `-text`. O fio do divisor continua no acento
       cru, porque fio é forma. */
    .precs-campt-painel.alerta .panel-header{
      color:var(--accent-red-text, #ef4444);
      border-bottom-color:color-mix(in srgb, var(--accent-red, #ef4444) 45%, transparent);
    }
    /* O CARD DO INVENTÁRIO É MAIS BAIXO que o do ao vivo, e só nisso eles diferem: sem delta e
       sem linha de estados, o corpo tem duas linhas de texto em vez de quatro, e manter o padding
       de 20px deixaria o card com um vão vazio embaixo do número. */
    .precs-campt-painel .precs-campsaldo{ padding:var(--space-4); }
    /* `auto-FILL`, e não `auto-fit`, e a diferença aparece nas seções pequenas: com `auto-fit` as
       colunas vazias colapsam, então a seção com UMA campanha ganhava um card de 620px ao lado da
       seção com três, onde cada card tem 280px. Na pilha de cinco seções isso lia como se o card
       largo fosse mais importante. Com `auto-fill` a coluna vazia continua ocupando o lugar dela e
       todos os cards da aba têm a mesma largura.
       A seção ao vivo fica como está: ela tem uma linha só, e ali o card largo não compete com
       nada. */
    .precs-campt-painel .precs-campsaldo-grade{
      grid-template-columns:repeat(auto-fill, minmax(340px, 1fr));
    }
    /* Saldo não lido: o card fica visivelmente em espera, sem competir com quem tem número. Não é
       `opacity` no card inteiro, que apagaria também o nome da campanha — o que se apaga é o
       corpo, que é a parte que ainda não tem resposta. */
    .precs-campsaldo-sem-leitura .precs-campsaldo-corpo{ opacity:.62; }
    .precs-campsaldo-sem-leitura .precs-campsaldo-rosca-txt{ font-size:1.1rem; font-weight:400; }
    /* O rodapé da aba. Ele ficava DENTRO do painel de "Todas as campanhas" e por isso tinha
       `border-top` como separador; agora vem depois da pilha de painéis, então o que o separa é a
       distância, e a borda de cima seria uma linha solta no meio da página. */
    .precs-campt-rodape{
      display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap;
      padding:var(--space-2) var(--space-4) var(--space-4);
      font-size:var(--fs-3); color:var(--text-muted);
    }
    /* (1.1.32) Os dois botões subiram para o cabeçalho de "Campanhas ao vivo" e por isso
       cresceram: no rodapé eles eram tipo de .7rem no meio de um parágrafo cinza, e o Pedro os
       chamou de "botão minúsculo". Agora são ações de verdade — mesmo corpo do botão de
       Atualizar da aba de WhatsApp, com ícone. */
    .precs-campt-btn{
      display:inline-flex; align-items:center; gap:var(--space-2); min-height:28px;
      /* (Parte 4) `--font-acao` é a família de botão da marca, e o corpo desce ao degrau da
         escala (12px, era 12,16). A altura mínima é a mesma caixa dos controles do card: os
         seis botões da aba passam a ter um peso só. */
      font-family:var(--font-acao); font-size:var(--fs-4); font-weight:600;
      padding:var(--space-1) var(--space-3); border-radius:var(--radius-pill);
      background:var(--bg-primary); color:var(--text-secondary);
      border:1px solid var(--border-color); cursor:pointer;
      text-transform:none; letter-spacing:0; white-space:nowrap;
      transition:border-color var(--dur-2), color var(--dur-2), background var(--dur-2);
    }
    .precs-campt-btn svg{ display:block; }
    /* (1.1.97) O SELO DE IDADE DO VÍNCULO, dentro do próprio botão que o renova.
       Ele é LEITURA colada numa AÇÃO, então precisa ser visivelmente secundário ao rótulo,
       senão o botão passa a ter dois pesos iguais disputando o olho. Daí o corpo menor, a
       tinta apagada e a barra fina separando — o mesmo vocabulário do carimbo da parede.
       `--text-muted` sobre `--bg-primary` é o par que o teste-contraste já mede nas cinco
       paletas; nada de cor nova aqui para não abrir um caso novo de contraste por um texto
       de quatro palavras.
       O `[hidden]` vem declarado porque o `display` daqui é de AUTOR (é a pegadinha que já
       mordeu três vezes neste projeto): sem a linha, o selo vazio renderiza como uma barra
       solta no fim do botão antes da primeira leitura. */
    .precs-campt-idade{
      display:inline-flex; align-items:center;
      margin-left:var(--space-1); padding-left:var(--space-2);
      border-left:1px solid var(--border-color);
      font-size:var(--fs-2); font-weight:500; color:var(--text-muted);
    }
    .precs-campt-idade[hidden]{ display:none; }
    /* VELHO não é erro, é ritmo fora do normal: a varredura fecha a volta em ~7 minutos, então
       passar de doze quer dizer que ela não está dando conta. Âmbar, e não vermelho — nada
       quebrou, o dado só está mais velho do que deveria. */
    .precs-campt-idade.velho{ color:var(--st-manual); }
    .precs-campt-btn:hover:not(:disabled){ color:var(--text-primary); border-color:var(--precs-gold); }
    .precs-campt-btn:active:not(:disabled){ transform:translateY(1px); }
    .precs-campt-btn:disabled{ opacity:.55; cursor:default; }
    /* Os três não tinham anel: o Tab chegava neles e nada acendia. Token, como o resto. */
    .precs-campt-btn:focus-visible{
      outline:var(--focus-ring-largura) solid var(--focus-ring-cor); outline-offset:var(--focus-ring-folga);
    }
    @media (prefers-reduced-motion: reduce){
      .precs-campt-btn{ transition:none; }
      .precs-campt-btn:active:not(:disabled){ transform:none; }
    }
    /* A faixa de ações encosta na direita do cabeçalho, longe do selo de contagem que fica
       colado no título. `.panel-header` já é flex (ver estilo.css). */
    .precs-camp-acoes{ display:inline-flex; align-items:center; gap:var(--space-2);
      margin-left:auto; }
    /* (1.1.100) O DIVISOR entre as releituras e o modo TV: um fio, para o olho ler dois grupos. */
    .precs-campt-div{ width:1px; height:18px; background:var(--border-color); margin:0 var(--space-1); flex:0 0 auto; }
    /* Enquanto lê, o ícone gira (`aria-busy` vem do JS junto do `disabled`). `precsSpin` é o
       mesmo quadro do loader da marca em 00-base.css. */
    .precs-campt-btn[aria-busy="true"] svg{ animation:precsSpin .9s linear infinite; }
    /* (1.1.166) Botão com a seta própria da leitura (vínculos): fora da leitura só o ícone do botão; durante, só a seta,
       e é ela que gira. O `display` aqui é o único dono do par, sem `[hidden]` para disputar. */
    .precs-campt-btn .precs-campt-btn-gira{ display:none; }
    .precs-campt-btn[aria-busy="true"]:has(.precs-campt-btn-gira) svg:not(.precs-campt-btn-gira){ display:none; }
    .precs-campt-btn[aria-busy="true"] .precs-campt-btn-gira{ display:block; }
    @media (prefers-reduced-motion: reduce){ .precs-campt-btn[aria-busy="true"] svg{ animation:none; } }
    /* O modo TV é a ação de destaque: ela muda a tela inteira. Fundo com um lavado de ouro de
       FORMA a 10%, para ele não ser só mais uma pílula com a letra de outra cor. */
    .precs-campt-tv{ color:var(--precs-gold-title); border-color:var(--precs-gold);
      background:color-mix(in srgb, var(--precs-gold-forma) 8%, var(--bg-primary)); }
    /* O hover é um lavado OPACO (12% sobre `--bg-primary`), e não `--precs-gold-light`: aquele é
       translúcido, e composto sobre o repouso de 8% derrubava o contraste da letra a 4,2:1 nos
       temas claros — medido pelo teste-contraste-render, que compõe o hover sobre o repouso. */
    .precs-campt-tv:hover:not(:disabled){ background:color-mix(in srgb, var(--precs-gold-forma) 12%, var(--bg-primary)); }
    /* (1.1.15 · 9ª volta) ALTURA IGUAL DENTRO DA SEÇÃO. Pedido de 20/08: "alguns cards têm
       tamanhos diferentes, pode tentar deixar padrão?".

       A causa era conteúdo desigual, não CSS torto: quem tem operador ganha a linha de estados,
       quem está discando ganha a linha do delta, quem tem lista com peso 0 ganha o rodapé do
       mailing parado. Três linhas opcionais somam ~40px de diferença entre vizinhos, e numa grade
       de cards isso lia como se os altos fossem mais importantes.

       `height:100%` sobre `grid-auto-rows:1fr` (na grade) iguala a altura da FILEIRA, e o card
       virou coluna flex para o bloco das listas poder ir para o rodapé com `margin-top:auto` —
       sem isso a sobra de altura aparecia como um vão no meio do card, o que é pior que o
       desalinhamento que a mudança veio corrigir. */
    .precs-campsaldo{
      max-width:620px; height:100%;
      display:flex; flex-direction:column;
      background:var(--bg-tertiary); border:1px solid var(--border-color);
      border-radius:var(--radius-card); padding:var(--space-5);
      border-left:4px solid var(--st-offline);
      transition:border-color var(--dur-1) ease, box-shadow var(--dur-1) ease;
    }
    .precs-campsaldo:hover{ box-shadow:var(--shadow); }
    /* (1.1.100) E A BORDA ACENDE NO HOVER — só onde o card é clicável (`[tabindex]`, a aba). Sombra
       sozinha diz "elevado"; borda dourada diz "isto abre". Os três lados, e NÃO o esquerdo: o
       filete esquerdo é o nível de urgência (`--st-offline`, laranja, vermelho, ouro da pausada)
       e o `border-color` completo apagaria essa informação enquanto o mouse estivesse em cima. */
    .precs-campsaldo[tabindex]:hover{
      border-top-color:color-mix(in srgb, var(--precs-gold-forma) 55%, var(--border-color));
      border-right-color:color-mix(in srgb, var(--precs-gold-forma) 55%, var(--border-color));
      border-bottom-color:color-mix(in srgb, var(--precs-gold-forma) 55%, var(--border-color));
    }
    /* (1.1.99) O CARD PASSOU A SER CLICÁVEL, e o hover deixou de mentir. Desde a 1.1.85 ele
       subia com `--shadow` (32px deslocada 10px, o vocabulário de "elevado e clicável") e não
       fazia nada ao clique — relato do Pedro em 17/09/2026: "ao passar o mouse o card sobe
       fazendo parecer que é clicável mas não é". Agora abre a campanha ao vivo. O cursor e o
       anel de foco só na ABA: na parede não há mouse nem Tab, e o `html[data-tv]` abaixo devolve
       o que era. O anel sai de `outline`, nunca de box-shadow — a catraca de estilo conta
       `box-shadow: 0 0 0` e exige igualdade. */
    .precs-campsaldo[tabindex]{ cursor:pointer; }
    .precs-campsaldo[tabindex]:focus-visible{
      outline:var(--focus-ring-largura) solid var(--focus-ring-cor); outline-offset:var(--focus-ring-folga); }
    html[data-tv] .precs-campsaldo{ cursor:default; }
    /* Os três controles internos NÃO herdam o cursor do card, para o olho saber que ali a ação é
       outra: a estrela fixa, a engrenagem configura, a alça arrasta. */
    .precs-campsaldo .precs-campsaldo-acoes, .precs-campsaldo .precs-campsaldo-alca{ cursor:auto; }

    /* ======================================================================
       (1.1.99) A CAMPANHA AO VIVO NA MESA — o casco é o da ficha, o conteúdo é o da ampliada.
       As classes `.precs-tvamp-*` são dimensionadas em vh/vw para uma parede lida a quatro
       metros. Aqui elas são REDIMENSIONADAS por contexto, em vez de duplicadas: uma regra por
       peça, todas ancoradas em `.precs-campvivo-modal`, e a ampliada não muda um pixel. */
    .precs-campvivo-modal{ width:min(96vw, 1100px); }
    .precs-campvivo-body{ display:flex; flex-direction:column; gap:var(--space-4); }
    .precs-campvivo-vivo-selo{
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:600;
      letter-spacing:var(--ls-caps); text-transform:uppercase; color:var(--accent-green-text);
      border:1px solid var(--accent-green); border-radius:var(--radius-pill);
      padding:1px var(--space-2); margin-left:var(--space-2); }
    .precs-campvivo-cfg{ margin-left:auto; margin-right:var(--space-3); }
    .precs-campvivo-modal .precs-tvamp-topo{ gap:var(--space-5); padding:0 0 var(--space-3); }
    .precs-campvivo-modal .precs-tvamp-rosca{ width:132px; height:132px; }
    .precs-campvivo-modal .precs-tvamp-num b{ font-size:var(--fs-11); }
    .precs-campvivo-modal .precs-tvamp-num span,
    .precs-campvivo-modal .precs-tvamp-proj,
    .precs-campvivo-modal .precs-tvamp-ritmo-lbl{ font-size:var(--fs-4); }
    /* SETE CAIXAS EM 1.100px NÃO CABEM LADO A LADO (a parede as põe em sete colunas porque tem
       quatro metros de tela): aqui viram quatro por linha, e a nota da base de cálculo ganha a
       linha inteira — é a mesma resposta que a parede dá abaixo de 1500px, só que por CONTEXTO e
       não por largura de janela, porque a modal tem o mesmo tamanho em qualquer monitor. */
    .precs-campvivo-modal .precs-tvamp-kpis{ grid-template-columns:repeat(4, minmax(0, 1fr)); gap:var(--space-3); }
    /* A nota ocupa a célula que sobra na segunda linha (sete caixas em quatro colunas deixam
       uma vazia), encostada embaixo e à direita — numa linha só dela ela lia como palavra solta. */
    .precs-campvivo-modal .precs-tvamp-kpi-nota{ justify-content:flex-end; padding-bottom:var(--space-2); }
    .precs-campvivo-modal .precs-tvamp-kpi b{ font-size:var(--fs-10); }
    .precs-campvivo-modal .precs-tvamp-kpi-abs,
    .precs-campvivo-modal .precs-tvamp-kpi-rot,
    .precs-campvivo-modal .precs-tvamp-kpi-nota{ font-size:var(--fs-3); }
    .precs-campvivo-modal .precs-tvamp-bloco-tit{ font-size:var(--fs-5); }
    .precs-campvivo-modal .precs-tvamp-lista{ font-size:var(--fs-5); }
    /* O `break-all` da parede parte a palavra em qualquer letra (lá são duas linhas a quatro
       metros); aqui o nome quebra no `<wbr>` que o montador põe depois de cada `_`, e só parte a
       palavra se um segmento sozinho não couber. */
    .precs-campvivo-modal .precs-tvamp-lista-nome{ word-break:normal; overflow-wrap:anywhere; }
    /* O NOME TEM UM MÍNIMO. Na parede a coluna do nome é `minmax(0, 1fr)` e a barra de baldes
       leva 8vw; em 1.100px as duas colunas `auto` (estado com cronômetro, ligações) e a barra
       comiam tudo e o nome saía com duas letras. Medido na captura de 18/09/2026: "A", "Dav", "Br". */
    .precs-campvivo-modal .precs-tvamp-op{
      font-size:var(--fs-5); gap:var(--space-3);
      grid-template-columns:auto minmax(120px, 1fr) auto auto 70px; }
    /* AS DUAS COLUNAS QUASE IGUAIS. A parede dá 1.15fr às listas porque lá o nome do mailing é o
       que faz alguém levantar; aqui, com 1.100px, essa folga toda tirava da linha de gente o que
       ela precisa para caber em cinco colunas — e dar a folga inversa (1.2fr para a gente) fazia
       o nome da lista quebrar no meio da palavra. Medido nas duas capturas de 18/09/2026. */
    .precs-campvivo-modal .precs-tvamp-cols{ grid-template-columns:minmax(0, 1.05fr) minmax(0, 1fr); gap:var(--space-4); }
    .precs-campvivo-modal .precs-tvamp-lista-num{ font-size:var(--fs-8); }
    .precs-campvivo-modal .precs-tvamp-op-lig{ font-size:var(--fs-6); }
    .precs-campvivo-modal .precs-tvamp-cols{ max-height:none; }
    .precs-campvivo-modal .precs-tvamp-listas,
    .precs-campvivo-modal .precs-tvamp-ops{ max-height:320px; overflow:auto; }
    /* AS DUAS COLUNAS NOVAS DA LISTA — realizadas e conectadas —, só nesta superfície. Na
       parede o nó não existe (o montador só o emite quando pedido), então a contagem de
       filhos que o morfismo exige continua a mesma lá. */
    .precs-campvivo-modal .precs-tvamp-lista-desfecho{
      grid-column:1 / -1; display:flex; gap:var(--space-4); font-size:var(--fs-3);
      color:var(--text-secondary); }
    .precs-campvivo-modal .precs-tvamp-lista-desfecho b{ color:var(--text-primary); font-weight:600; }
    /* A SPARKLINE DE CADA INDICADOR, quando a sonda do `metrics/graphic` confirmar a forma. */
    .precs-campvivo-graf{ display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:var(--space-3); }
    .precs-campvivo-graf-item{ background:var(--bg-primary); border:1px solid var(--border-color);
      border-radius:var(--radius-inner); padding:var(--space-3); }
    /* `.precs-tv-ritmo` nasce `display:none` para a parede (só card alto de TV a mostra, pelo
       @container). Aqui ela é o motivo do bloco, então aparece sempre — como a ampliada já faz
       com a curva grande do topo. */
    .precs-campvivo-graf-item .precs-tv-ritmo{ display:block; width:100%; height:54px; }
    .precs-campvivo-graf-item .precs-tv-ritmo svg{ width:100%; height:100%; display:block; }
    .precs-campvivo-graf-rot{ font-family:var(--font-rotulo); font-size:var(--fs-2);
      text-transform:uppercase; letter-spacing:var(--ls-caps); color:var(--text-muted); margin-top:var(--space-1); }
    @media (prefers-reduced-motion: reduce){ .precs-campsaldo[tabindex]{ transition:none; } }
    /* Os dois níveis de urgência. Filete à esquerda e não fundo inteiro: o card precisa
       continuar legível, e fundo vermelho com texto por cima é o par que já custou
       contraste nesta versão. */
    .precs-campsaldo-atencao{ border-left-color:var(--accent-orange); }
    .precs-campsaldo-critico{ border-left-color:var(--accent-red); }
    /* (1.1.11) PAUSADA COM GENTE LOGADA. Filete dourado, e nao vermelho, de proposito: o
       vermelho deste card significa "a lista vai zerar", e sao problemas diferentes com
       acoes diferentes (despausar x subir lista). Duas causas na mesma cor obrigariam a ler
       o texto para saber o que fazer, que e exatamente o que o filete existe para evitar.
       O dourado tambem carrega o peso institucional certo: e o alerta mais forte do card. */
    .precs-campsaldo-parada{
      /* O filete e o tint são FORMA. O tint fica com o ouro de forma também: com o bronze ele
         puxava o card para um bege sujo nos temas claros. */
      border-left-color:var(--precs-gold-forma, #B49552);
      background:color-mix(in srgb, var(--precs-gold-forma, #B49552) 7%, var(--bg-tertiary));
    }
    /* (1.1.11) O PULSO, e SÓ aqui. Um painel que pulsa em vários lugares deixa de chamar
       atenção em algum: este card é a única coisa da aba que custa dinheiro AGORA (gente logada
       sem número para discar), e por isso é o único que se mexe. O anel cresce e desaparece, sem
       tocar no fundo — animar o fundo mudaria o contraste do texto a cada ciclo, que foi a lição
       do card tingido nesta mesma versão. */
    .precs-campsaldo-seca{ animation:precsSecaPulso 2.2s ease-out infinite; }
    @keyframes precsSecaPulso{
      0%   { box-shadow:0 0 0 0 color-mix(in srgb, var(--accent-red) 55%, transparent); }
      70%  { box-shadow:0 0 0 10px color-mix(in srgb, var(--accent-red) 0%, transparent); }
      100% { box-shadow:0 0 0 0 color-mix(in srgb, var(--accent-red) 0%, transparent); }
    }
    @media (prefers-reduced-motion: reduce){ .precs-campsaldo-seca{ animation:none; } }

    /* OS ESTADOS DOS OPERADORES da campanha, no cabeçalho do card. */
    .precs-campsaldo-estados{
      /* Mesma razão do `.precs-campsaldo-estado` logo abaixo: sem isto a linha de estados dos
         operadores esticava para a largura do card. */
      align-self:stretch;
      display:flex; flex-wrap:wrap; gap:var(--space-2); margin:calc(-1 * var(--space-2)) 0 var(--space-3);
    }
    .precs-campsaldo-est{
      display:inline-flex; align-items:center; gap:4px;
      font-size:var(--fs-2); font-weight:600; color:var(--text-muted);
      font-family:var(--font-acao);
    }
    .precs-campsaldo-est b{
      font-family:'JetBrains Mono',monospace; font-weight:700; color:var(--text-secondary);
    }
    .precs-campsaldo-est-bolha{
      width:6px; height:6px; flex:0 0 auto; aspect-ratio:1; border-radius:50%;
      background:var(--est-cor, var(--text-muted));
    }
    /* (1.1.35) A COR DA BOLINHA DE ESTADO VOLTOU, e ela tinha se perdido numa troca de mecanismo.
       RELATO DO PEDRO, 27/08/2026: "temos 1 falando 2 tpa 2 ocioso, tudo em bolinha cinza; poderíamos
       dar a cor à bolinha pra identificar sem ler, só de bater a cor e o número do lado".
       O QUE ACONTECEU: a regra acima pinta `background`, e foi escrita quando a bolinha era um
       `<span>`. Quando ela virou SVG (`precsBolinhaHTML`), a regra de `svg.precs-campsaldo-est-bolha`
       passou a zerar o fundo de propósito — o SVG pinta pelo `fill="currentColor"` do círculo. Aí a
       cor efetiva passou a ser a do TEXTO do chip, que é `--text-muted`: cinza, nos cinco estados.
       O conserto é dar a cor ao `color` do próprio SVG, que é de onde o `currentColor` sai. Continua
       saindo do token de estado (`--est-cor`), então os quatro temas seguem valendo. */
    svg.precs-campsaldo-est-bolha{ color: var(--est-cor, var(--text-muted)); }

    /* O TINT CUSTA CONTRASTE, e o --text-muted nao tinha margem para pagar. Ele foi calibrado
       em 4,83:1 contra --bg-tertiary no tema precs (ver o comentario dele no estilo.css: era
       4,41:1 e foi clareado o minimo para passar AA). Os 7% de ouro sobem o fundo de #232327
       para #2D2B2A e derrubam para 4,35:1 — medido, nao suposto.
       O texto sobe um nivel dentro deste card, e isso tambem e melhor desenho: aqui esta
       linha EXPLICA o alerta, entao ser o texto mais apagado da tela era errado de todo jeito. */
    /* REDEFINE O TOKEN NO ESCOPO DO CARD, em vez de listar as classes uma por uma. A primeira
       escrita enumerava tres seletores, e a rodada seguinte (a rosca) acrescentou um quarto
       texto apagado que reprovou de novo: enumerar so funciona ate alguem inserir um elemento.
       Redefinindo --text-muted aqui, todo descendente presente e futuro herda a versao que
       passa AA sobre o fundo tingido. */
    .precs-campsaldo-parada{ --text-muted: var(--text-secondary); }
    /* BARRA DE PROGRESSO DE CAMPANHA PARADA NAO PODE SER VERDE. O verde do preenchimento
       significa "esta consumindo", e num card que diz Pausada ou Fora do horario ele afirma o
       contrario do selo logo acima: a tela mostrando "83% consumido" em verde le como campanha
       saudavel. Vira cinza de borda, que e progresso sem movimento. */
    /* (1.1.85) As duas regras que pintavam `.precs-campsaldo-fill` de cinza sairam junto com a
       classe. A barra que existe hoje e `.precs-campsaldo-listas-fill`, e ela tem regra propria. */
    /* ======================================================================
       (1.1.83) A FICHA DA CAMPANHA

       A MOLDURA NAO E NOVA: o overlay e o modal reusam `.precs-listas-overlay` e
       `.precs-listas-modal` pelas classes, entao entrada, fundo escurecido, fechamento e a
       barra de titulo ja vem prontos. O que existe aqui e o que difere: largura (sao quatro
       abas de conteudo, e nao uma tabela), as sub-abas e a grade de campos.

       A GRADE E DE LEITURA, e o desenho tem que dizer isso sem precisar de aviso. Rotulo
       pequeno em caixa alta, valor em corpo maior logo abaixo: e a forma de uma FICHA. Se os
       campos tivessem moldura de caixa de texto, a tela prometeria edicao que esta leva nao
       entrega, e a primeira pessoa a clicar concluiria que o painel esta quebrado.
       ====================================================================== */
    /* ======================================================================
       (1.1.91) A FICHA GANHA LARGURA, E A ABA LISTAS RECUPERA O RESPIRO.

       860px apertava principalmente a aba Listas, que reusa a tabela de cinco colunas do
       modal. 1.100px cabe em 1366x768 com a escala de densidade ligada, que é a tela da
       operação — e o `min(96vw, …)` continua sendo quem manda em tela menor.
       ====================================================================== */
    .precs-campcfg-modal{ max-width:min(96vw, 1100px); }

    /* O nó do corpo carrega DUAS classes (`precs-listas-body precs-campcfg-body`), e o
       `display:block` daqui vencia o `display:flex` do arquivo do WhatsApp por ordem de carga:
       dentro da ficha a tabela perdia o `gap` que ela tem no modal. O modificador devolve o
       layout só na aba que é dela — ver o comentário em precsSupCfgPintar. */
    .precs-campcfg-body.na-listas{
      display:flex; flex-direction:column; gap:var(--space-3);
    }

    /* ======================================================================
       (1.1.91) A FAIXA AO VIVO, e por que ela fica na ficha.

       O card responde "como esta campanha está agora" e a ficha responde "como ela está
       configurada". Quem abre a ficha perdia a primeira de vista, e as duas perguntas se
       cruzam o tempo todo: "o horário está certo?" só faz sentido ao lado de "ela está no
       horário AGORA".

       CUSTO ZERO EM REQUISIÇÃO. Os três dados já estão em memória pelo ciclo de 15s que a
       1.1.85 montou: o estado em `PRECS_SUP_CAMP_ESTADO`, o saldo em `PRECS_SUP_CAMP.saldo`, e
       os operadores por estado em `precsSupCampOpsEEstados()`.

       ELA É PERSISTENTE ENTRE AS ABAS de propósito: fica fora do corpo que troca, então
       nenhuma das quatro precisa repeti-la, e a transição de sub-aba não a faz piscar.
       ====================================================================== */
    .precs-campcfg-vivo{
      display:flex; align-items:center; flex-wrap:wrap;
      gap:var(--space-2) var(--space-4);
      margin:0 var(--space-5) var(--space-3);
      padding-bottom:var(--space-3);
      border-bottom:1px solid var(--border-color);
    }
    /* SEM ISTO O `hidden` NÃO ESCONDE. `display:flex` de autor vence o `[hidden]` do
       navegador, e a faixa vazia continuaria ocupando a borda de baixo. É a mesma linha que o
       `.precs-seg[hidden]` tem no 50-modais, pelo mesmo motivo. */
    .precs-campcfg-vivo[hidden]{ display:none; }
    .precs-campcfg-vivo-item{
      display:inline-flex; align-items:baseline; gap:var(--space-1);
      font-family:var(--font-corpo); font-size:var(--fs-3); color:var(--text-secondary);
    }
    .precs-campcfg-vivo-item b{
      font-family:var(--font-num); font-size:var(--fs-6); font-weight:700;
      color:var(--text-primary);
    }
    /* O estado da campanha herda a MESMA tinta do selo do card. Duas telas dizendo a mesma
       coisa com cores diferentes é o defeito que o bloco de precsSupCampEstado já descreve. */
    /* A GEOMETRIA É DAQUI, A TINTA É DO CARD. O nó carrega também a
       `.precs-campsaldo-estado-<classe>`, que já define `color` e `border-color` por estado e
       é a fonte única daquelas três tintas desde a 1.1.11. Redeclará-las aqui criaria uma
       segunda verdade sobre a mesma coisa, que é o defeito que aquele bloco existe para não
       ter. Por isso a bolha é `currentColor`: ela segue a tinta que a outra classe decidir. */
    .precs-campcfg-vivo-estado{
      display:inline-flex; align-items:center; gap:var(--space-1);
      padding:var(--space-0) var(--space-2);
      border:1px solid var(--border-color);
      border-radius:var(--radius-pill);
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:700;
      letter-spacing:var(--ls-caps); text-transform:uppercase;
    }
    .precs-campcfg-vivo-bolha{
      width:6px; height:6px; flex:0 0 auto; border-radius:var(--radius-pill);
      background:currentColor;
    }
    /* Os operadores por estado, na mesma ordem de prioridade da tabela: quem está com alguém
       na linha primeiro, quem está parado por último. */
    .precs-campcfg-vivo-est{
      display:inline-flex; align-items:center; gap:var(--space-1);
      font-family:var(--font-rotulo); font-size:var(--fs-2); color:var(--text-secondary);
    }
    .precs-campcfg-vivo-est i{
      width:7px; height:7px; border-radius:var(--radius-pill);
      background:var(--est-cor, var(--text-muted));
    }
    .precs-campcfg-vivo-est b{ font-family:var(--font-num); color:var(--text-primary); }

    /* ======================================================================
       (1.1.91) A BARRA DE 24 HORAS, que responde de relance o que dois campos de texto
       respondiam por conta.

       "Início 09:00" e "Fim 18:00" exigem que quem lê faça a conta com o relógio na cabeça
       para saber se a campanha está no horário agora. A 3C já manda a resposta pronta
       (`is_on_active_time`), e o painel só a usava no selo do card.

       A BARRA É FORMA, ENTÃO ELA É OURO DE FORMA. Nos temas claros `--precs-gold` vira bronze
       porque ali ele é texto; contorno, trilho e marcador usam `--precs-gold-forma`, que é a
       divisão que o 40-temas estabelece.
       ====================================================================== */
    .precs-campcfg-horas{
      position:relative; height:10px; margin:var(--space-2) 0 var(--space-1);
      border-radius:var(--radius-pill);
      background:var(--bg-tertiary);
      border:1px solid var(--border-color);
      overflow:hidden;
    }
    .precs-campcfg-horas-faixa{
      position:absolute; top:0; bottom:0;
      background:color-mix(in srgb, var(--precs-gold-forma) 45%, transparent);
    }
    /* O AGORA é uma linha, e não um ponto: ponto de 2px some no trilho de 10px, e a pergunta
       "estamos dentro?" depende justamente de enxergar de que lado dele a faixa está. */
    .precs-campcfg-horas-agora{
      position:absolute; top:-2px; bottom:-2px; width:2px;
      background:var(--text-primary);
    }
    .precs-campcfg-horas-lbl{
      display:flex; justify-content:space-between;
      margin-bottom:var(--space-4);
      font-family:var(--font-rotulo); font-size:var(--fs-1); color:var(--text-muted);
      letter-spacing:var(--ls-caps);
    }

    /* ======================================================================
       (1.1.91) AS CHAVES PASSAM A TER TRÊS ESTADOS, e não dois.

       `.desliga` e `.ausente` não tinham UMA regra sequer: as duas caíam no estilo base e
       diferiam só pela palavra. Ou seja, "a 3C não mandou este campo" era visualmente idêntico
       a "está desligado" — e essas duas coisas pedem providências opostas.

       É a mesma regra que a ficha já segue nos CAMPOS desde a 1.1.83, onde ausente vira
       travessão apagado: a tela nunca afirma sobre o que ninguém mediu.
       ====================================================================== */
    .precs-campcfg-chave.desliga{ opacity:.72; }
    .precs-campcfg-chave.ausente{
      border-style:dashed;
      background:none;
    }
    .precs-campcfg-chave.ausente .precs-campcfg-chave-rot,
    .precs-campcfg-chave.ausente .precs-campcfg-chave-val{ color:var(--text-muted); }

    /* A chave que ACENDEU entre duas leituras pisca uma vez. `precsChipLiga` já existe desde o
       modo WhatsApp e nunca foi usada em lugar nenhum; é exatamente um chip acendendo. */
    .precs-campcfg-chave.acendeu{
      animation:precsChipLiga var(--dur-3) var(--ease-2);
    }
    @media (prefers-reduced-motion: reduce){
      .precs-campcfg-chave.acendeu{ animation:none; }
    }

    /* ======================================================================
       (1.1.91) OS GRUPOS DA ABA GERAL.

       Nove campos de peso idêntico numa grade não são uma ficha, são uma lista. O nome da
       campanha e a URL de integração não têm a mesma importância para quem abre isto no meio
       do turno, e o desenho não dizia nada sobre isso.

       O título de seção já existe (`.precs-campcfg-secao-tit`, usado pela Estratégia desde a
       1.1.83) e é reusado sem uma linha nova.
       ====================================================================== */
    .precs-campcfg-campo.forte .precs-campcfg-val{
      font-size:var(--fs-7); color:var(--precs-gold-title);
    }
    .precs-campcfg-abas{
      margin:0 var(--space-5) var(--space-3);
      flex:0 0 auto; align-self:flex-start;
    }
    .precs-campcfg-body{ display:block; }

    /* ======================================================================
       (1.1.89) A TROCA DE SUB-ABA PASSA A TER TEMPO, e é o último item da leva da aba
       Campanhas que tinha ficado de fora.

       As quatro seções da ficha trocam por `innerHTML`, e o conteúdo SALTAVA: num mesmo
       retângulo, nove campos viravam treze chaves sem nada dizer que a troca aconteceu.
       Quando o salto é instantâneo o olho não sabe se a tela respondeu ao clique ou se ela
       sempre esteve assim, e a pessoa clica de novo.

       Trezentos milissegundos e um deslocamento de 4px: o suficiente para a mudança ser
       percebida como CAUSADA pelo clique, e pouco o bastante para não atrasar quem já sabe
       o que quer. A curva é a `--ease-2`, a mesma das entradas do projeto.

       SÓ NA TROCA DELIBERADA. `precsSupCfgPintar` também roda quando a leitura da 3C chega,
       e animar ali faria a ficha piscar sozinha no meio da consulta — que é o mesmo defeito
       da animação de entrada das fotos que virou tique na 1.1.85.
       ====================================================================== */
    @keyframes precsCampcfgTroca{
      from{ opacity:0; transform:translateY(var(--space-1)); }
      to{ opacity:1; transform:none; }
    }
    .precs-campcfg-body.precs-campcfg-troca{
      animation:precsCampcfgTroca var(--dur-3) var(--ease-2);
    }
    @media (prefers-reduced-motion: reduce){
      .precs-campcfg-body.precs-campcfg-troca{ animation:none; }
    }

    /* O ANEL DE FOCO DAS SUB-ABAS, escopado à ficha. O `.precs-seg` do projeto inteiro não
       tem regra de `:focus-visible` própria, e mexer nele daqui alcançaria as abas da
       supervisão e o segmentado de Preferências, cuja largura é medida por outro teste. */
    .precs-campcfg-abas button:focus-visible{
      outline:var(--focus-ring-largura) solid var(--focus-ring-cor);
      outline-offset:var(--focus-ring-folga);
    }

    /* O CAMPO: rotulo em cima, valor embaixo. */
    .precs-campcfg-grade{
      display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));
      gap:var(--space-3) var(--space-5); margin-bottom:var(--space-5);
    }
    .precs-campcfg-campo{
      display:flex; flex-direction:column; gap:var(--space-0);
      padding-bottom:var(--space-2);
      border-bottom:1px solid var(--border-color);
      min-width:0;
    }
    .precs-campcfg-campo[data-tipv]{ cursor:help; }
    .precs-campcfg-rot{
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:600;
      letter-spacing:var(--ls-caps); text-transform:uppercase; color:var(--text-muted);
    }
    .precs-campcfg-val{
      font-family:var(--font-corpo); font-size:var(--fs-5); font-weight:600;
      color:var(--text-primary);
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    }
    /* CAMPO VAZIO NAO E CAMPO COM ZERO. O travessao fica apagado de proposito: ele diz "a 3C
       nao mandou isto", e nao "esta campanha vale nada aqui". */
    .precs-campcfg-campo.vazio .precs-campcfg-val{ color:var(--text-muted); font-weight:500; }

    .precs-campcfg-secao-tit{
      display:flex; align-items:center; gap:var(--space-2);
      margin:var(--space-4) 0 var(--space-3);
      font-family:var(--font-rotulo); font-size:var(--fs-2); font-weight:700;
      letter-spacing:var(--ls-caps); text-transform:uppercase; color:var(--precs-gold-title);
    }
    .precs-campcfg-secao-tit:first-child{ margin-top:0; }
    /* (1.1.91) O resumo vive DENTRO do título da seção, e não numa linha própria: ele é uma
       qualificação do título ("Comportamento: sete de treze"), e uma linha a mais empurraria a
       grade para baixo sem acrescentar nada. Peso e caixa vêm do título; só a tinta muda. */
    .precs-campcfg-resumo{
      margin-left:auto; text-transform:none; letter-spacing:normal;
      font-family:var(--font-corpo); font-size:var(--fs-3); font-weight:500;
      color:var(--text-secondary);
    }
    .precs-campcfg-resumo b{ font-family:var(--font-num); color:var(--text-primary); }
    .precs-campcfg-resumo i{ font-style:normal; color:var(--text-muted); }

    /* AS CHAVES DE COMPORTAMENTO, desenhadas como ESTADO e nao como interruptor. O ponto
       colorido a esquerda e a leitura de relance; a palavra ao lado e o que confirma, porque
       cor sozinha nao carrega informacao. */
    .precs-campcfg-chaves{
      display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr));
      gap:var(--space-2); margin-bottom:var(--space-5);
    }
    .precs-campcfg-chave{
      display:flex; align-items:center; gap:var(--space-2);
      padding:var(--space-2) var(--space-3);
      border:1px solid var(--border-color); border-radius:var(--radius-inner);
      background:var(--bg-secondary); min-width:0;
    }
    .precs-campcfg-chave[data-tipv]{ cursor:help; }
    .precs-campcfg-chave-pt{
      width:7px; height:7px; flex:0 0 auto; border-radius:var(--radius-pill);
      background:var(--text-muted);
    }
    .precs-campcfg-chave.liga .precs-campcfg-chave-pt{ background:var(--accent-green); }
    .precs-campcfg-chave-rot{
      flex:1 1 auto; min-width:0;
      font-family:var(--font-corpo); font-size:var(--fs-4); font-weight:500;
      color:var(--text-secondary);
      /* SEM `nowrap` DE PROPOSITO. Com ele, "Descartar improdutivas (AMD)" virava "Descartar
         improdutivas (AM…" e a reticencia comia justamente a sigla, que e o que identifica o
         recurso. Duas linhas custam altura; rotulo cortado custa o sentido. */
      overflow-wrap:anywhere;
    }
    .precs-campcfg-chave-val{
      flex:0 0 auto;
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:700;
      letter-spacing:var(--ls-caps); text-transform:uppercase; color:var(--text-muted);
    }
    .precs-campcfg-chave.liga .precs-campcfg-chave-val{ color:var(--accent-green-text); }

    /* AS PESSOAS, no tamanho em que o rosto se reconhece. Na faixa do card o teto de largura
       obrigou o anel a carregar o estado sozinho; aqui ha espaco, entao o nome vem inteiro,
       com ramal e a palavra do estado. */
    .precs-campcfg-pessoas{
      display:grid; grid-template-columns:repeat(auto-fill, minmax(210px, 1fr));
      gap:var(--space-2);
    }
    .precs-campcfg-pessoa{
      display:flex; align-items:center; gap:var(--space-2);
      padding:var(--space-2);
      border:1px solid var(--border-color); border-radius:var(--radius-inner);
      background:var(--bg-secondary); min-width:0;
    }
    .precs-campcfg-pessoa .precs-sup-av{ width:34px; height:34px; flex:0 0 auto; }
    /* O MESMO ANEL DA FAIXA, e pelo mesmo motivo de `outline` estar aqui em vez de
       `box-shadow`: o contador `anel-cru` da catraca de estilo exige igualdade. */
    .precs-campcfg-pessoa.live .precs-sup-av{
      outline:2px solid var(--vop-cor); outline-offset:0;
    }
    .precs-campcfg-pessoa:not(.live) .precs-sup-av{ opacity:.5; }
    .precs-campcfg-pessoa-txt{ display:flex; flex-direction:column; min-width:0; }
    .precs-campcfg-pessoa-txt b{
      font-family:var(--font-corpo); font-size:var(--fs-4); font-weight:600;
      color:var(--text-primary);
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    }
    .precs-campcfg-pessoa-txt i{
      font-style:normal;
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:500;
      color:var(--text-muted);
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    }
    /* As equipes da ficha sao os MESMOS chips do card, com a contagem ao lado: a 3C devolve
       `team_count` e nao os integrantes, entao o numero e tudo que da para afirmar sobre o
       tamanho da equipe sem inventar uma lista. */
    .precs-campcfg-equipes{
      display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2) var(--space-3);
    }
    .precs-campcfg-equipe-n{
      font-family:var(--font-rotulo); font-size:var(--fs-2); color:var(--text-muted);
      margin-inline-start:calc(-1 * var(--space-2));
    }
    .precs-campcfg-nada{
      font-family:var(--font-corpo); font-size:var(--fs-4); color:var(--text-muted);
      padding:var(--space-2) 0;
    }
    /* O RODAPE DIZ QUE A FICHA E LEITURA, no lugar onde a pessoa procuraria o botao Salvar. */
    .precs-campcfg-rodape{
      margin-top:var(--space-5); padding-top:var(--space-3);
      border-top:1px solid var(--border-color);
      font-family:var(--font-corpo); font-size:var(--fs-3); color:var(--text-muted);
    }
    .precs-campcfg-rodape b{ color:var(--text-secondary); font-weight:700; }

    /* A ENGRENAGEM DO CARD, irma da estrela de fixar: mesma caixa, mesma reacao ao hover. */
    /* A caixa (28px, borda transparente, cor apagada) vem da regra compartilhada com a estrela e a
       alça, no topo do arquivo; aqui fica só o que é dela: o giro no hover. */
    .precs-campsaldo-cfg{
      padding:0; cursor:pointer;
      transition:background var(--dur-1) ease, color var(--dur-1) var(--ease-1),
                 border-color var(--dur-1) ease, transform var(--dur-2) var(--ease-1);
    }
    .precs-campsaldo-cfg:hover{ color:var(--precs-gold-title); transform:rotate(45deg); }
    .precs-campsaldo-cfg:focus-visible{ color:var(--precs-gold-title); }
    @media (prefers-reduced-motion: reduce){
      .precs-campsaldo-cfg{ transition:none; }
      .precs-campsaldo-cfg:hover{ transform:none; }
    }

    /* ======================================================================
       (1.1.83) A FAIXA DE QUEM ESTA VINCULADO, e o selo de cadastro em risco.

       O QUE ELA RESPONDE. O resto do card responde "quanto falta" e "esta andando".
       Esta faixa responde "quem pode discar aqui", que e a pergunta que o cronograma da
       operacao refaz todo dia ao vincular e desvincular equipe.

       VINCULADO E LOGADO SE SEPARAM SEM DEPENDER DE COR. Quem esta na campanha agora vem
       opaco e com anel; quem esta vinculado e deslogado vem esmaecido e sem anel. Cor
       sozinha nao carrega informacao, entao opacidade e ordem carregam junto, e o balao de
       cada rosto escreve a palavra.

       O ANEL E `outline`, E NAO `box-shadow`, e isso e escolha medida e nao estilo. A
       parede usa `box-shadow: 0 0 0 2px` para o mesmo efeito (`.precs-tv-op .precs-sup-av`)
       e aquele uso ja esta contado na catraca de estilo (`anel-cru`, hoje em 68). Escrever
       mais um subiria o contador e reprovaria o commit inteiro. O `outline` com
       `outline-offset` desenha o mesmo anel, acompanha o `border-radius` do avatar,
       interpola cor na transicao igual, e nao entra em nenhum dos padroes contados.
       ====================================================================== */
    .precs-campsaldo-vinc{
      align-self:stretch;
      display:flex; align-items:center; flex-wrap:wrap;
      gap:var(--space-2); margin:0 0 var(--space-3);
    }
    /* SEM NINGUEM VINCULADO E UM ACHADO, nao um vazio. Ele diz em palavras o que o selo de
       risco diz em simbolo, para quem estiver lendo o card em vez de varrer. */
    .precs-campsaldo-vinc-vazia{
      font-family:var(--font-rotulo); font-size:var(--fs-2); font-weight:600;
      color:var(--text-muted); letter-spacing:var(--ls-caps); text-transform:uppercase;
      cursor:help;
    }
    .precs-campsaldo-vrostos{ display:inline-flex; align-items:center; }
    /* OS ROSTOS SE SOBREPOEM DE PROPOSITO. Oito avatares lado a lado consomem a largura do
       card inteiro; com a sobreposicao eles viram um grupo, que e como a informacao e lida
       ("estas pessoas"), e sobra espaco para os chips de equipe na mesma linha. */
    /* ======================================================================
       (1.1.83 · 3a volta) A ENTRADA ESCALONADA SAIU, e ela era um defeito e nao um enfeite.

       RELATO DO PEDRO, 16/09/2026: "na tela a foto dos operadores esta pulando/ticando".

       A CAUSA: a animacao estava na pastilha, e a pastilha e REMONTADA a cada repintura. O
       inventario repinta sempre que a assinatura muda, e a assinatura inclui o ESTADO de cada
       operador — que muda a cada ciclo de 5 segundos. Entao cada troca de estado de uma pessoa
       reanimava as fotos de todas as campanhas, e o painel ao vivo, que nem guarda de assinatura
       tem, reanimava a cada ciclo. O efeito que era para significar "o time entrou na campanha"
       virou um tique permanente.

       E ESTE ARQUIVO JA TINHA A LICAO ESCRITA, duas telas acima, sobre os cards de KPI que
       levaram tres versoes para parar de piscar: numa tela que fica aberta o dia inteiro,
       movimento sem evento e ruido. O que sobrou e a transicao de hover, que responde a uma
       acao da pessoa e por isso nunca dispara sozinha.
       ====================================================================== */
    .precs-campsaldo-vop{
      display:inline-flex; margin-inline-end:calc(-1 * var(--space-1)); cursor:help;
    }
    .precs-campsaldo-vop:last-of-type{ margin-inline-end:0; }
    /* O avatar sobe ao passar o mouse, e so ele: e o que deixa o rosto tapado pelo vizinho
       legivel sem tirar a sobreposicao. */
    .precs-campsaldo-vop .precs-sup-av{
      width:26px; height:26px; flex:0 0 auto;
      outline:2px solid var(--bg-tertiary); outline-offset:0;
      transition:outline-color var(--dur-2) var(--ease-1), transform var(--dur-1) var(--ease-1);
    }
    .precs-campsaldo-vop:hover .precs-sup-av{ transform:translateY(-2px); }
    /* NAO EXISTE MAIS ROSTO APAGADO NESTA FAIXA: desde a 3a volta, so quem esta na campanha
       agora ganha foto, e quem esta vinculado e fora virou contagem em texto. Ver o bloco em
       `precsSupCampVinculadosHTML`. */
    /* E O ANEL E O ESTADO de quem esta na campanha agora. A cor sai de `precsSupCor`, ou
       seja, dos mesmos tokens `--st-*` da tabela e da parede: um verde so significa uma
       coisa no painel inteiro. */
    .precs-campsaldo-vop.vop-live .precs-sup-av{ outline-color:var(--vop-cor); }
    @media (prefers-reduced-motion: reduce){
      .precs-campsaldo-vop{ animation:none; }
      .precs-campsaldo-vop .precs-sup-av{ transition:none; }
    }
    /* QUEM ESTA VINCULADO E FORA DA CAMPANHA E UM NUMERO, e nao uma fileira de rostos apagados.
       Ele responde "quantos podem entrar aqui" sem disputar a leitura com quem esta discando,
       que era exatamente a confusao do print de 16/09/2026: o titulo dizia "8 op." e a fileira
       mostrava dezoito pessoas. */
    .precs-campsaldo-vfora{
      display:inline-flex; align-items:center;
      margin-inline-start:var(--space-2);
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:600;
      letter-spacing:var(--ls-caps); text-transform:uppercase;
      color:var(--text-muted); white-space:nowrap; cursor:help;
    }
    .precs-campsaldo-vmais{
      display:inline-flex; align-items:center; justify-content:center;
      min-width:26px; height:26px; padding-inline:var(--space-1);
      margin-inline-start:var(--space-2);
      border-radius:var(--radius-pill); border:1px solid var(--border-color);
      background:var(--bg-tertiary); color:var(--text-secondary);
      font-family:var(--font-num); font-size:var(--fs-2); font-weight:700; cursor:help;
    }
    /* OS CHIPS DE EQUIPE. A equipe e a unidade que o cronograma move: vincular uma equipe
       inteira e um clique na 3C, e o card precisa deixar isso visivel sem abrir a ficha.
       Texto em token de TEXTO e nao em cor de marca: este chip aparece nos vinte e um temas,
       e o ouro da PRECS nao e legivel como texto sobre fundo claro. */
    /* ======================================================================
       (1.1.83 · 2a volta) A COR DO NIVEL DA EQUIPE.

       POR QUE ELA NAO SAI DA FAIXA, que era o caminho obvio: o Pedro descreveu a operacao real
       em 16/09/2026 e ela desmente a ideia. O Nivel 1 e o SDR; o Nivel 2 tem faixa branca (SDR
       que virou closer), azul e roxa; o Nivel 3 tem roxa e azul misturadas. Nivel e faixa sao
       eixos diferentes, entao pintar um com o outro daria duas equipes da mesma cor e uma
       equipe de duas cores.

       TRES TOKENS PROPRIOS, e nao os `--st-*`: aqueles carregam o estado do operador no painel
       inteiro (verde e "falando", vermelho e alerta), e repeti-los aqui faria o chip de equipe
       parecer um estado. O azul, o roxo e o ouro foram escolhidos por serem tres MATIZES
       distintos que sobrevivem aos fundos escuro e claro, e porque a escada azul -> roxo ->
       ouro le como progressao de senioridade sem precisar de legenda.

       A COR NUNCA E O TEXTO. Ela vive na borda, no ponto e num fundo de 14%; o texto fica em
       `--text-secondary` nos tres. O chip aparece nos vinte e um temas, e o ouro da marca nao e
       legivel como texto sobre fundo claro. Vale igual para a cor que vem da 3C
       (`--veq-cor`), que nao passou por contraste nenhum do nosso lado.
       ====================================================================== */
    /* (1.1.85) OS TRES TOKENS DE NIVEL MUDARAM PARA `40-temas.css`, que e o arquivo onde a cor
       da marca e as paletas moram. Aqui eles ficavam num `:root` no meio do arquivo do card e
       nao podiam ser recalibrados por tema junto do resto — cor solta longe das outras e cor
       que ninguem lembra de ajustar quando o tema muda. */
    .precs-campsaldo-veqs{ display:inline-flex; flex-wrap:wrap; gap:var(--space-1); }
    .precs-campsaldo-veq{
      display:inline-flex; align-items:center; gap:var(--space-1);
      padding:var(--space-0) var(--space-2);
      border-radius:var(--radius-pill);
      border:1px solid var(--veq-cor, var(--border-color));
      background:color-mix(in srgb, var(--veq-cor, var(--bg-secondary)) 14%, var(--bg-secondary));
      color:var(--text-secondary);
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:600;
      letter-spacing:var(--ls-caps); text-transform:uppercase;
      white-space:nowrap; cursor:help;
    }
    /* O PONTO E O QUE SE LE DE RELANCE, e ele so aparece quando ha cor: equipe sem nivel e sem
       cor cadastrada na 3C continua com o chip neutro de antes, em vez de ganhar um ponto cinza
       que nao significa nada. */
    .precs-campsaldo-veq-pt{ display:none; }
    .precs-campsaldo-veq[style] .precs-campsaldo-veq-pt,
    .precs-campsaldo-veq.veq-n1 .precs-campsaldo-veq-pt,
    .precs-campsaldo-veq.veq-n2 .precs-campsaldo-veq-pt,
    .precs-campsaldo-veq.veq-n3 .precs-campsaldo-veq-pt{
      display:inline-block; width:6px; height:6px; flex:0 0 auto;
      border-radius:var(--radius-pill); background:var(--veq-cor);
    }
    .precs-campsaldo-veq.veq-n1{ --veq-cor:var(--nivel-1); }
    .precs-campsaldo-veq.veq-n2{ --veq-cor:var(--nivel-2); }
    .precs-campsaldo-veq.veq-n3{ --veq-cor:var(--nivel-3); }
    /* ======================================================================
       (1.1.83) O SELO DE CADASTRO EM RISCO.

       ELE E CONFERENCIA, E NAO MEDICAO, e o desenho diz isso. Os alertas que ja existem no
       card (lista seca com gente logada, pausada com operador, minutos projetados) sao
       medicao da discagem e ficam com o vermelho e o pulso. Este aponta cadastro faltando na
       3C, e fica em ambar, discreto, numa linha propria.

       SE ELE GRITASSE MAIS ALTO que a lista seca, ensinaria o supervisor a olhar para o lado
       errado do card no unico momento em que o card importa.
       ====================================================================== */
    .precs-campsaldo-risco{
      align-self:stretch;
      display:inline-flex; align-items:center; gap:var(--space-1);
      margin:0 0 var(--space-2);
      color:var(--accent-orange); cursor:help;
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:700;
      letter-spacing:var(--ls-caps); text-transform:uppercase;
    }
    .precs-campsaldo-risco svg{ flex:0 0 auto; }

    /* ======================================================================
       (1.1.85) O BLOCO "QUEM ESTÁ AQUI", e por que ele passou a existir.

       Estados, selo, risco e rostos respondem à MESMA pergunta — quem está nesta campanha e
       como ela está — e viviam soltos entre o nome e a rosca, cada um com a margem que herdou
       da versão em que entrou. O resultado era uma pilha sem hierarquia: o olho não sabia onde
       terminava a identidade e começava a leitura.

       O bloco não desenha moldura. Ele dá respiro e um fio de separação, que é o suficiente
       para o card ler como três andares (quem · quanto · como) em vez de sete faixas.

       ELE SÓ EXISTE NA ABA: a parede recebe a mesma sequência de sempre, sem este nó, porque o
       card dela é reconciliado filho a filho e contagem de nós tem que ser constante.
       ====================================================================== */
    .precs-campsaldo-quem{
      display:flex; flex-direction:column; align-items:flex-start;
      gap:var(--space-2);
      align-self:stretch;
      margin-bottom:var(--space-3);
      padding-bottom:var(--space-3);
      border-bottom:1px solid var(--border-color);
    }
    /* As margens que cada pedaço trazia de fora somavam com o `gap` e faziam o bloco crescer
       sem motivo. Dentro dele, quem manda no espaço é o `gap`. */
    .precs-campsaldo-quem > *{ margin-top:0; margin-bottom:0; }

    /* ======================================================================
       (1.1.85) OS TRÊS CONTROLES DO CARD PASSAM A TER O MESMO PESO.

       A engrenagem entrou na 1.1.83 e ficou de fora da regra que esconde a estrela e a alça
       até o hover — resultado: um ícone sempre visível ao lado de dois invisíveis, dentro da
       mesma caixa. Isso lia como se a engrenagem fosse o controle principal do card, quando os
       três são ações do mesmo nível.

       REVELAM JUNTOS, e continuam alcançáveis pelo teclado a qualquer momento: `:focus-visible`
       traz qualquer um deles de volta, o que é o que importa para quem não usa mouse.
       ====================================================================== */
    /* (1.1.100) ESTRELA E ENGRENAGEM APARECEM EM REPOUSO, apagadas a 45%, e acendem no hover.
       Relato do Pedro em 18/09/2026, com a aba na frente: nenhum card mostrava ação nenhuma até
       o mouse chegar — só a estrela do card fixado. Quem nunca passou o mouse ali não sabia que
       existia configurar, fixar e arrastar. 45% anuncia sem competir com o número; a ALÇA continua
       só no hover, porque arrastar é raro e seis pontinhos em repouso são ruído. */
    /* (segunda volta, mesmo dia) Os 45% durAram uma tarde: viraram pastilhas sempre visíveis, ver o
       bloco dos controles no topo do arquivo. Fica a lição escrita: um controle que só aparece no
       hover é um controle que metade das pessoas nunca descobre. */
    /* O anel de foco sai do token, como o resto do painel: ele já é calibrado por família de
       tema (ouro de realce no escuro, bronze no claro) e medido a 3:1 pelo `teste-foco-anel`. */
    .precs-campsaldo-fix:focus-visible,
    .precs-campsaldo-cfg:focus-visible,
    .precs-camplista-btn:focus-visible,
    .precs-camplista-lupa:focus-visible{
      outline:var(--focus-ring-largura) solid var(--focus-ring-cor);
      outline-offset:var(--focus-ring-folga);
    }

    /* ======================================================================
       (1.1.85) O BOTÃO "ANALISAR" GANHA CSS.

       Ele existe desde a 1.1.17 e **não tinha uma única regra** em nenhuma das nove folhas:
       herdava o botão cru do navegador, com fundo cinza de sistema, do lado de um irmão
       (`.precs-camplista-btn`) inteiramente estilizado. Os dois dividem a mesma linha, e o
       comentário do CSS do irmão já falava do par — só que definia um.
       ====================================================================== */
    .precs-camplista-lupa{
      display:inline-flex; align-items:center; gap:var(--space-1);
      padding:var(--space-0) var(--space-2);
      border:1px solid transparent; background:none;
      border-radius:var(--radius-pill);
      color:var(--text-muted); cursor:pointer;
      font-family:var(--font-rotulo); font-size:var(--fs-1); font-weight:600;
      letter-spacing:var(--ls-caps); text-transform:uppercase;
      transition:color var(--dur-1) var(--ease-1), border-color var(--dur-1) var(--ease-1);
    }
    .precs-camplista-lupa:hover{
      color:var(--precs-gold-title);
      border-color:color-mix(in srgb, var(--precs-gold) 40%, transparent);
    }
    @media (prefers-reduced-motion: reduce){ .precs-camplista-lupa{ transition:none; } }

    /* (1.1.85) E O DELTA PARADO GANHA A DELE. `.desce` e `.esperando` tinham regra; `.parado`
       era emitido pelo card e caía no estilo base, então "nada consumido em 30s" e "−400 em 30s"
       tinham exatamente a mesma aparência — sendo que um é movimento e o outro é a ausência
       dele, que é justamente o que o supervisor está tentando ver. */
    .precs-campsaldo-delta.parado{
      color:var(--text-muted);
      border-color:var(--border-color);
    }

    .precs-campsaldo-topo{
      display:flex; align-items:baseline; justify-content:space-between;
      gap:var(--space-3); margin-bottom:var(--space-3);
    }
    /* O SELO DE ESTADO fica em linha propria, embaixo do nome: no topo ele competiria com a
       contagem de operadores por um espaco que, num card de 340px, o nome da campanha ja
       consome inteiro. */
    .precs-campsaldo-estado{
      /* (1.1.15 · 9ª volta) `align-self` porque o CARD virou coluna flex (para igualar alturas), e
         num container flex o `inline-flex` do filho não o impede de ESTICAR no eixo transversal:
         o selo "RODANDO" passou a ser uma pílula da largura do card inteiro. `inline-flex` decide
         como o filho arruma o conteúdo DELE; quem decide a largura dele aqui é o pai. */
      align-self:flex-start;
      display:inline-flex; align-items:center; gap:var(--space-2);
      margin:calc(-1 * var(--space-2)) 0 var(--space-3);
      font-size:.66rem; font-weight:700; text-transform:uppercase;
      letter-spacing:var(--ls-caps);
      font-family:var(--font-rotulo);
      padding:2px var(--space-2); border-radius:var(--radius-pill);
      border:1px solid var(--border-color); background:var(--bg-primary);
      color:var(--text-muted);
    }
    /* A bolha repete a cor em forma, e nao so em cor: quem nao distingue verde de vermelho
       ainda le o rotulo, e o `aspect-ratio` a mantem redonda em qualquer resolucao (foi o
       defeito do ponto de conectado nesta mesma versao). */
    .precs-campsaldo-bolha{
      width:6px; height:6px; flex:0 0 auto; aspect-ratio:1; border-radius:50%;
      background:currentColor;
    }
    .precs-campsaldo-estado-rodando{
      color:var(--accent-green-text); border-color:color-mix(in srgb, var(--accent-green) 40%, var(--border-color));
    }
    .precs-campsaldo-estado-pausada{
      color:var(--precs-gold, #B49552); border-color:color-mix(in srgb, var(--precs-gold, #B49552) 46%, var(--border-color));
    }
    .precs-campsaldo-estado-fora{
      color:var(--text-muted); border-color:var(--border-color);
    }
    /* Fora do horario o card inteiro recua: nao e problema, e nao pode disputar atencao com
       quem esta. Sem chegar a opacidade que prejudique leitura de contraste. */
    .precs-campsaldo:has(.precs-campsaldo-estado-fora) .precs-campsaldo-num{ color:var(--text-secondary); }
    /* O nome ganhou tamanho de título de card: com 340px de largura mínima ele cabe quase
       sempre, e quando não cabe o tooltip (data-tipv) entrega o resto. Antes truncava em
       "DISCAGEM MAN…" num card de 260px, que é onde o nome importa menos e cortava mais. */
    .precs-campsaldo-nome{
      font-weight:700; font-size:var(--fs-8); color:var(--text-primary);
      min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
      font-family:var(--font-corpo);
    }
    .precs-campsaldo-ops{
      flex:0 0 auto; font-size:var(--fs-3); font-weight:600; color:var(--text-muted);
      padding:2px var(--space-2); border-radius:var(--radius-pill);
      background:var(--bg-primary); border:1px solid var(--border-color);
      white-space:nowrap;
    }
    /* O SALDO É O PROTAGONISTA: é o número que o supervisor abre a aba para ver. */
    /* (Parte 4, 18/09/2026) O QUE FICOU EM `rem` CRAVADO, E POR QUÊ. As famílias viraram token
       (o valor computado é idêntico por construção) e os corpos a até 0,25px de um degrau da
       escala desceram para ele. Estes NÃO: 2.1rem (o saldo, sem degrau na escala), .66rem
       (selo e rótulo do saldo: 10,56px fica no meio entre 10 e 11), .78rem (a projeção, 12,5px
       entre 12 e 13) e 1.1rem (a rosca sem leitura, 17,6px entre 17 e 18). Foram calibrados a
       olho pelo Pedro em 2026-08, e mover meio pixel para caber num token é troca de fidelidade
       por pureza — não vale. Se a escala ganhar os degraus, eles entram. */
    .precs-campsaldo-num{
      font-family:var(--font-num); font-size:2.1rem; font-weight:700;
      color:var(--text-primary); line-height:1; display:flex; align-items:baseline;
      gap:var(--space-2); flex-wrap:wrap;
    }
    .precs-campsaldo-num span{
      font-family:var(--font-acao); font-size:.66rem; font-weight:600;
      text-transform:uppercase; letter-spacing:var(--ls-caps); color:var(--text-muted);
    }
    /* A projeção saiu do topo e virou linha própria embaixo do saldo: ali ela é a resposta
       para "e daí?", e no topo competia com o nome por atenção e por largura. */
    .precs-campsaldo-proj{
      margin-top:var(--space-2); font-size:.78rem; color:var(--text-secondary);
    }
    .precs-campsaldo-min{ font-family:var(--font-num); font-size:var(--fs-8); font-weight:700; }
    .precs-campsaldo-atencao .precs-campsaldo-proj,
    .precs-campsaldo-atencao .precs-campsaldo-min{ color:var(--accent-manual-text); }
    .precs-campsaldo-critico .precs-campsaldo-proj,
    .precs-campsaldo-critico .precs-campsaldo-min{ color:var(--accent-red-text); }
    .precs-campsaldo-semproj{ font-family:inherit; font-weight:500; color:var(--text-muted); font-size:var(--fs-4); }
    /* (1.1.85) SAIRAM DAQUI `.precs-campsaldo-trilha`, `-fill` e `-rod`: tres classes da PRIMEIRA
       escrita do card, sem um unico no correspondente desde que a barra de listas e o rodape
       foram refeitos. Ficavam como CSS que nunca pinta nada — e pior, `-fill` era referenciada
       por mais duas regras de alerta, o que fazia parecer que ela estava viva. CSS morto que
       parece vivo custa o tempo de quem for mexer aqui depois. */
    .precs-campsaldo-zerada{ color:var(--accent-red-text); font-weight:600; }
    @media (prefers-reduced-motion: reduce){ .precs-campsaldo{ transition:none; } }

    /* Breadcrumb "Funil → Etapa", igual ao que o Pipe mostra sob a fita. */
    .precs-fita-caminho{
      display:flex; align-items:center; gap:var(--space-2); flex-wrap:wrap;
      margin-bottom:var(--space-3); font-size:.78rem;
    }
    .precs-fita-funil{ color:var(--lead-muted); }
    .precs-fita-seta{ color:var(--lead-muted); opacity:.7; }
    .precs-fita-etapa{ font-weight:700; color:var(--lead-title); }
    .precs-fita-dias{ margin-left:auto; font-size:.72rem; color:var(--lead-muted); }

    /* ---- PRÓXIMA ATIVIDADE, na gramática do Pipedrive ----
       Ícone do TIPO à esquerda, título em negrito, e abaixo a pílula VENCIDO seguida da
       data por extenso. É o formato que o operador lê o dia inteiro na outra aba. */
    .precs-atv{
      display:flex; align-items:flex-start; gap:var(--space-3);
      margin-bottom:var(--space-3);
      padding:.6rem .8rem;
      background:var(--lead-surface-2); border:1px solid var(--lead-border-2);
      border-radius: var(--radius-inner);
    }
    .precs-atv-vencida{ border-color:var(--accent-red,#ef4444); }
    .precs-atv-ico{
      width:26px; height:26px; flex-shrink:0; border-radius:50%;
      display:inline-flex; align-items:center; justify-content:center;
      background:var(--lead-surface); border:1px solid var(--lead-border-2);
      color:var(--lead-muted);
    }
    .precs-atv-corpo{ min-width:0; }
    .precs-atv-tit{ font-size:.88rem; font-weight:700; color:var(--lead-text); line-height:1.3; }
    .precs-atv-meta{
      display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-2);
      margin-top:.25rem; font-size:.74rem; color:var(--lead-muted);
    }
    /* Vermelho cheio com texto branco, como o VENCIDO do Pipe. Contraste do par medido:
       #ef4444 com #fff dá 3,99:1, abaixo de AA para texto normal — por isso o texto é
       BOLD e o vermelho escurece um tom, o que leva o par para acima de 4,5:1. */
    .precs-atv-vencido{
      background:#C92A2A; color:#fff;
      font-size:.64rem; font-weight:700; letter-spacing:.4px;
      padding:.1rem .4rem; border-radius:4px; line-height:1.4;
    }
    .precs-atv-sep{ opacity:.6; }
    .precs-atv-pend{ color:var(--lead-muted); }

    /* Botão "Ver mais informações". Discreto de propósito: ele é uma saída para quem
       precisa dos campos, não um elemento que disputa atenção durante a ligação. */
    .precs-vermais{
      display:inline-flex; align-items:center; gap:var(--space-1);
      margin-top:var(--space-1);
      padding:.25rem .5rem .25rem .35rem;
      background:transparent; border:0; border-radius:var(--radius-pill);
      font-family:'Manrope','Poppins',sans-serif;
      font-size:.74rem; font-weight:600; color:var(--lead-muted);
      cursor:pointer;
    }
    .precs-vermais:hover{ color:var(--lead-title); }
    .precs-vermais:focus-visible{ outline:2px solid var(--accent-color,#B49552); outline-offset:2px; }
    .precs-vermais svg{ transition:transform var(--dur-1) ease; }
    .precs-vermais[aria-expanded="true"] svg{ transform:rotate(180deg); }
    @media (prefers-reduced-motion: reduce){ .precs-vermais svg{ transition:none; } }

    .precs-neg-grade{ display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:.6rem;
      margin-top:var(--space-2); }
    /* [hidden] precisa vencer o display:grid, senão o bloco recolhido continuaria na tela. */
    .precs-neg-grade[hidden]{ display:none; }
    /* Grade vazia acontece quando o mailing ja mostra tudo: some em vez de deixar um
       espaco morto embaixo dos blocos. */
    .precs-neg-grade:empty{ display:none; }
    /* (1.1.6) Nome da campanha no card do negócio, em LINHA PRÓPRIA (ver o comentário no
       precsRenderLeadCard). Encolhe até o conteúdo, com ellipsis só como último recurso: em
       linha própria ela tem a largura do card inteiro, então na prática o nome cabe. Discreta
       de propósito — é contexto, não o assunto do card. */
    .precs-pipe-camp-linha{ display:flex; margin:calc(-1 * var(--space-1)) 0 var(--space-3); }
    .precs-pipe-camp{
      min-width:0; max-width:100%;
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
      font-family:'Montserrat','Poppins',sans-serif; font-size:.62rem; font-weight:600;
      text-transform:uppercase; letter-spacing: var(--ls-caps); color:var(--lead-muted);
      padding:var(--space-0) var(--space-2); border-radius:var(--radius-sm);
      background:color-mix(in srgb, var(--lead-muted) 10%, transparent);
    }

    /* O card de anotações usa a MESMA moldura do card do negócio: a faixa verde
       encosta na borda, então a padding do .precs-col-card sai daqui e volta no
       corpo interno. */
    #precs-notas-panel{ width:100%; padding:0; overflow:hidden; }
    .precs-notas-corpo{ padding:var(--space-4) var(--space-5) var(--space-5); }

    /* UMA rolagem só para o histórico inteiro. O texto de cada anotação e a lista de
       carimbos não têm rolagem própria: três regiões roláveis empilhadas fazem o
       operador não saber onde a roda do mouse vai agir. O teto em vh garante que o
       card nunca passe da tela, mesmo num negócio com muita anotação.

       (1.1.44) A DIVISÃO POR `--precs-escala` FALTAVA AQUI, e este era o único `vh` do
       painel sem ela — o mailing (`estilo.css`) e o espelho já dividiam. O `zoom` do `main`
       multiplica o resultado do `calc`, mas `62vh` continua medindo a viewport inteira: o
       card renderizava a 62% × 0,85 = ~53% da tela no modo normal, e ~45% no compacto.
       O erro não estourava nada (ele encolhe, não cresce), e por isso sobreviveu: o efeito
       era DESPERDIÇAR altura justamente na coluna 3, que já é a mais vazia da tela — ela
       fica sem a qualificação durante a chamada inteira. Agora o teto é o que estava
       escrito. */
    .precs-notas-wrap{ max-height:calc(62vh / var(--precs-escala, 1)); overflow-y:auto; }
    /* Selo com a contagem de anotações ESCRITAS por pessoa (ver precsNotasContagem):
       responde "tem algo para eu ler aqui?" sem precisar percorrer a lista. */
    .precs-notas-cont:not(:empty){ font-size:.66rem; font-weight:700; line-height:1;
      margin-left:var(--space-0); padding:var(--space-0) var(--space-2); border-radius:4px;
      color:var(--precs-gold, #B49552);
      background:var(--lead-surface-2, var(--bg-primary));
      border:1px solid var(--precs-gold, #B49552); }

    /* ======================================================================
       (1.1.86) QUANTO FALTA PARA O TETO DE 100 ANOTAÇÕES DO PIPEDRIVE.

       O teto é rígido e por negócio, e até agora o operador só descobria que havia batido
       nele DEPOIS: a ligação encerrada, a qualificação já na 3C, e um toast dizendo que o
       Pipedrive recusou. O selo troca isso por um aviso ANTES, na hora em que ele abre a
       lead — e só aparece quando há o que avisar (ver precsNotasMedirFolga).

       A COR NÃO É TEXTO, e isso é deliberado. O número tem de ser legível nas cinco
       paletas, então ele fica em `--text-primary`, que é o único par de contraste já
       provado em todas elas; o estado vem da borda e de um tint do acento, que é o mesmo
       recurso que os toasts usam desde a 1.0. Pintar "94/100" de laranja resolveria o
       aviso e criaria um par de contraste novo para medir em cinco temas.
       ====================================================================== */
    .precs-notas-teto{ font-size:var(--fs-2); font-weight:700; line-height:1;
      margin-left:var(--space-0); padding:var(--space-0) var(--space-2);
      border-radius:var(--radius-sm);
      color:var(--text-primary);
      background:color-mix(in srgb, var(--accent-orange) 12%, transparent);
      border:1px solid color-mix(in srgb, var(--accent-orange) 55%, transparent); }
    /* Cheio é outro estado, não uma intensidade do mesmo: aqui o registro JÁ não está
       sendo gravado, e a diferença entre "falta pouco" e "parou" tem de saltar. */
    .precs-notas-teto.cheio{
      background:color-mix(in srgb, var(--accent-red) 18%, transparent);
      border-color:var(--accent-red); }

    /* Registros de ligação (anotação automática sem texto de pessoa): uma linha
       cada, atrás de um <details>. Ocupam o espaço de uma nota escrita inteira
       quando somados, e a informação de cada um cabe em "quando + qualificação". */
    /* "Status e campanhas do dia" no card Meu desempenho.
       PRECS (1.1.0) — era um <details> com seta, fechado por padrão, e as regras de
       summary/marcador que viviam aqui saíram junto. Hoje é só o bloco: as duas seções
       aparecem sempre, e o que restou é o filete de separação do conteúdo de cima. */
    .pm-detalhes{ margin-top:var(--space-4); border-top:1px solid var(--border-color); padding-top:var(--space-2); }
    /* A primeira seção do bloco não repete a linha de topo. */
    .pm-detalhes .pm-secao:first-of-type{ margin-top:var(--space-2); }

    .precs-notas-carimbos{ margin-top:var(--space-2); }
    .precs-notas-carimbos > summary{ cursor:pointer; list-style:none;
      font-family:'Manrope','Poppins',sans-serif; font-size:.72rem; font-weight:600;
      color:var(--lead-muted); padding:var(--space-1) var(--space-0); }
    .precs-notas-carimbos > summary::-webkit-details-marker{ display:none; }
    .precs-notas-carimbos > summary::before{ content:'▸ '; color:var(--precs-gold, #B49552); }
    .precs-notas-carimbos[open] > summary::before{ content:'▾ '; }
    .precs-notas-carimbos > summary:hover{ color:var(--lead-text); }
    .precs-notas-carimbos > summary:focus-visible{ outline:2px solid var(--precs-gold, #B49552); outline-offset:2px; }
    .precs-notas-carimbo{ display:flex; align-items:baseline; justify-content:space-between;
      gap:var(--space-2); padding:var(--space-1) var(--space-0);
      border-top:1px dashed var(--lead-border-2, var(--border-color)); }
    .precs-notas-carimbo-qual{ font-size:.76rem; color:var(--lead-text); min-width:0;
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:right; }

    /* PRECS — PREVIEW DE OPERADOR (uso interno). Controles dentro do modal de
       preferências, mais um selo fixo enquanto está ligado. O selo é deliberadamente
       chamativo: preview esquecido em produção altera a checklist do TPA, então ele
       tem que incomodar até ser desligado. */
    .precs-preview-grade{ display:flex; flex-direction:column; gap:var(--space-2); }
    .precs-preview-linha{ display:flex; align-items:center; gap:var(--space-2); }
    .precs-preview-linha > span{ flex:0 0 4.2rem; font-family:'Manrope','Poppins',sans-serif;
      font-size:.76rem; font-weight:600; color:var(--text-secondary); }
    .precs-preview-sel{ flex:1 1 auto; min-width:0; font-family:'Poppins',sans-serif;
      font-size:.78rem; padding:var(--space-1) var(--space-2); border-radius:7px; cursor:pointer;
      color:var(--text-primary); background:var(--bg-primary);
      border:1px solid var(--border-color); }
    .precs-preview-sel:focus-visible{ outline:2px solid var(--precs-gold, #B49552); outline-offset:1px; }
    .precs-preview-limpar{ margin-top:var(--space-2); width:100%; cursor:pointer;
      font-family:'Manrope','Poppins',sans-serif; font-size:.76rem; font-weight:600;
      padding:var(--space-2) var(--space-2); border-radius:7px; color:var(--accent-red);
      background:transparent; border:1px solid var(--accent-red);
      transition:background var(--dur-1); }
    .precs-preview-limpar:hover{ background:color-mix(in srgb, var(--accent-red) 12%, transparent); }
    .precs-preview-selo{ position:fixed; left:.6rem; bottom:2rem; z-index:9998; cursor:pointer;
      font-family:'JetBrains Mono','Manrope',monospace; font-size:.64rem; font-weight:700;
      letter-spacing: var(--ls-caps); text-transform:uppercase; padding:var(--space-1) var(--space-2); border-radius: var(--radius-sm);
      /* (1.1.42) O selo usava `--precs-gold-hi` (o token de REALCE, pensado para hover de
         texto) como fundo, com `#1a1a1a` cravado. No Zebrado claro o realce vale `#2E2E2E` e
         no Bordô claro `#993034`: preto sobre escuro nos dois. Passa a usar o par de
         superfície, como o grip e o avatar. */
      color:var(--precs-wa-tinta-ouro, #131315); background:var(--precs-gold-forma, #FFC95B);
      border:1px solid rgba(0,0,0,.25); box-shadow:var(--elev-1) rgba(0,0,0,.35); }
    .precs-preview-selo:hover{ filter:brightness(1.08); }
    /* (1.1.10) Era `#1a1a1a` cravado: preto sobre preto nos temas escuros, ou seja, anel de
       foco invisível justamente onde o fundo é grafite. */
    .precs-preview-selo:focus-visible{ outline:2px solid var(--precs-gold, var(--accent-3cplus)); outline-offset:2px; }
    @media (prefers-reduced-motion: reduce){
      .precs-preview-limpar{ transition:none; }
    }
    /* ANOTAÇÃO FECHADA: uma linha com data, autor, qualificação e prévia. Em produção
       as anotações são longas (relato inteiro do atendimento), e uma aberta enchia o
       card e escondia que existiam outras. Fechadas, todas as datas ficam à vista e o
       operador escolhe qual ler. */
    .precs-notas-item{ border-radius: var(--radius-inner);
      background:var(--lead-surface-2, var(--bg-primary));
      border:1px solid var(--lead-border-2, var(--border-color)); }
    .precs-notas-item + .precs-notas-item{ margin-top:var(--space-2); }
    .precs-notas-item.aberta{ border-color:var(--precs-gold, #B49552); }
    .precs-notas-topo{ display:block; width:100%; text-align:left; cursor:pointer;
      background:transparent; border:none; padding:var(--space-2) var(--space-3); border-radius: var(--radius-inner);
      font:inherit; color:inherit; }
    .precs-notas-topo:hover{ background:color-mix(in srgb, var(--precs-gold,#B49552) 8%, transparent); }
    .precs-notas-topo:focus-visible{ outline:2px solid var(--precs-gold, #B49552); outline-offset:-2px; }
    .precs-notas-meta{ display:flex; align-items:baseline; gap:var(--space-2); margin-bottom:var(--space-1); }
    .precs-notas-quando{ font-size:.68rem; font-weight:600; color:var(--precs-gold, #B49552); }
    .precs-notas-autor{ font-size:.68rem; color:var(--lead-muted); min-width:0;
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    /* Qualificação da ligação na linha fechada: é o "resultado" daquela conversa. */
    .precs-notas-qual{ font-size:.66rem; color:var(--lead-text); white-space:nowrap;
      padding:.08rem var(--space-1); border-radius:4px;
      background:color-mix(in srgb, var(--lead-text) 10%, transparent); }
    .precs-notas-seta{ margin-left:auto; flex:0 0 auto; font-size:.7rem;
      color:var(--precs-gold, #B49552); transition:transform var(--dur-2) ease; }
    .precs-notas-item.aberta .precs-notas-seta{ transform:rotate(90deg); }
    .precs-notas-previa{ display:block; font-size:.78rem; line-height:1.35;
      color:var(--lead-muted); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
    .precs-notas-item.aberta .precs-notas-previa{ display:none; }
    /* Mesma mecânica da checklist de qualificação: minmax(0,0fr) colapsa de verdade
       (0fr sozinho não colapsa, o mínimo automático ainda conta o conteúdo) e o
       min-height:0 no filho é o que deixa o grid comprimi-lo. */
    .precs-notas-detalhe{ display:grid; grid-template-rows:minmax(0, 0fr);
      opacity:0; overflow:hidden;
      transition:grid-template-rows .28s cubic-bezier(.16,1,.3,1), opacity .18s ease; }
    .precs-notas-detalhe > *{ min-height:0; }
    .precs-notas-item.aberta .precs-notas-detalhe{ grid-template-rows:minmax(0, 1fr); opacity:1; }
    .precs-notas-item.aberta .precs-notas-texto{ padding:0 var(--space-3) var(--space-2); }
    @media (prefers-reduced-motion: reduce){
      .precs-notas-detalhe{ transition:none; }
      .precs-notas-seta{ transition:none; }
    }
    /* pre-wrap porque precsNotaTexto entrega TEXTO com \n: as quebras da anotação
       original precisam sobreviver sem que a gente reintroduza tags. Sem rolagem
       própria: quem rola é o histórico inteiro (.precs-notas-wrap). */
    .precs-notas-texto{ font-size:.82rem; line-height:1.45; color:var(--lead-text);
      white-space:pre-wrap; overflow-wrap:anywhere; }
    .precs-notas-vazio{ font-size:.8rem; color:var(--lead-muted); line-height:1.4; }

    /* (1.1.5) O painel virou dois nós fixos (#precs-lead-info e #precs-lead-nota), então a
       animação de entrada desceu um nível: quem entra é o CARD dentro de cada nó, não o nó,
       que agora existe sempre. Sem descer, os dois nós animariam uma vez no boot e nunca
       mais, e cada card novo apareceria seco. */
    #precs-lead-info > *, #precs-lead-nota > * {
      animation: precsLeadIn 0.32s cubic-bezier(0.16, 1, 0.3, 1);
    }
    /* (1.1.7) Os dois viraram CARDS IRMÃOS na coluna, e a coluna tem `gap`. Um wrapper
       VAZIO tem altura zero mas continua contando como filho do flex, então cada um
       cobrava um respiro por um card que não está na tela. O pior caso são os dois vazios
       ao mesmo tempo (ocioso), somando dois espaços fantasma. `:has(> filho:empty)` casa
       exatamente com "o renderizador não escreveu nada aqui", que é o estado de "este
       card não existe agora". O espaçamento entre eles passa a ser o gap da coluna, e não
       uma margem própria da anotação. */
    #precs-lead-panel:has(> #precs-lead-info:empty),
    #precs-anotacao-panel:has(> #precs-lead-nota:empty) { display: none; }
    @keyframes precsLeadIn {
      from { opacity: 0; transform: translateY(8px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    @media (prefers-reduced-motion: reduce) {
      #precs-lead-info > *, #precs-lead-nota > * { animation: none; }
    }

    /* PRECS — Card "última lead trabalhada" no menu principal */
    .precs-ultima-lead {
      margin-top: 0;
    }
    .precs-ultima-lead-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--space-2);
      margin-bottom: var(--space-2);
    }
    .precs-ultima-lead-corpo .precs-hist-item:first-child { border-top: none !important; }
    .precs-ultima-lead-tag {
      font-size: 0.78rem;
      font-weight: 700;
      letter-spacing: var(--ls-caps);
      color: var(--text-secondary);
      text-transform: uppercase;
    }
    html[data-theme="precs"] .precs-ultima-lead-tag { color: var(--precs-gold); }
    .precs-ultima-lead-quando { font-size: 0.78rem; color: var(--text-muted); }
    .precs-ultima-lead-titulo { font-size: 1.05rem; font-weight: 700; margin-bottom: var(--space-2); }
    .precs-ultima-lead-info {
      display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-5);
      font-size: 0.85rem; color: var(--text-secondary);
      margin-bottom: var(--space-3);
    }
    .precs-ultima-lead-acoes { display: flex; flex-wrap: wrap; gap: var(--space-2); }
    .precs-ultima-lead-btn {
      width: auto; padding: var(--space-2) var(--space-4); font-size: 0.82rem;
      text-decoration: none; display: inline-flex; align-items: center;
    }

    /* PRECS — Aviso de ociosidade (3+ min parado): discreto, só informativo. */
    .precs-ocioso-faixa {
      display: flex;
      align-items: center;
      justify-content: center;
      padding: var(--space-2) var(--space-5);
      background: var(--bg-tertiary, #2a2a2e);
      color: var(--text-secondary);
      font-size: 0.8rem;
      font-weight: 500;
      letter-spacing: 0.2px;
    }
    /* Leve esmaecimento da faixa de estado do card (sutil, não o card inteiro). */
    .panel.precs-ocioso-cinza .precs-state-bar { background: #6b7280 !important; }

    /* PRECS — faixa de estado da Campanha Ativa.
       Mesma técnica da faixa verde do card Pipedrive: um bloco interno no topo
       do painel (que tem overflow:hidden), então os cantos arredondados são
       respeitados e não há retângulo flutuante. A cor vem de --precs-cor-estado. */
    .precs-state-bar {
      height: 6px;
      width: 100%;
      background: var(--precs-cor-estado, #9aa3b2);
      transition: background .4s ease;
    }
    /* PRECS — borda do painel de campanha acompanha a cor do estado. Antes ficava cinza e
       destoava do topo colorido no manual e em chamada. O color-mix mistura a cor do
       estado com a borda do tema, então funciona nos 3 temas.
       (O comentário citava que o id vencia a regra genérica de `body.manual-mode .panel`;
       aquela regra era código morto e saiu em 1.1.5.) */
    /* (1.1.42) A MISTURA PARTE DO FIO DO TEMA, e não mais do `--border-color` cru.
       `#precs-card-campanha` é seletor de ID (1,0,0) e vencia as regras `html[data-theme=…]
       .panel` (0,2,1) que dão ao painel o fio dourado, vinho ou preto de cada tema. Resultado:
       o MAIOR card da tela era o único que nunca mostrava a assinatura do tema — parte de "o
       bordô só parece que mudou algo na tela principal". A cor do estado continua dominante
       (50%), porque ela é o sinal; o que muda é com o que ela se mistura. */
    #precs-card-campanha {
      border-color: color-mix(in srgb, var(--precs-cor-estado, var(--panel-fio)) 50%, var(--panel-fio));
      box-shadow: 0 0 0 1px color-mix(in srgb, var(--precs-cor-estado, transparent) 22%, transparent), var(--shadow);
      transition: border-color .4s ease, box-shadow .4s ease;
    }
    /* Efeito de onda só quando discando/chamando: brilho que percorre a faixa */
    .precs-state-bar.precs-pulse {
      background-image: linear-gradient(90deg,
        var(--precs-cor-estado, #f59e0b) 0%,
        var(--precs-cor-estado, #f59e0b) 35%,
        rgba(255,255,255,0.85) 50%,
        var(--precs-cor-estado, #f59e0b) 65%,
        var(--precs-cor-estado, #f59e0b) 100%);
      background-size: 200% 100%;
      animation: precsOnda 1.8s linear infinite;
    }
    @keyframes precsOnda {
      0%   { background-position: 200% 0; }
      100% { background-position: -200% 0; }
    }
    @media (prefers-reduced-motion: reduce) {
      .precs-state-bar.precs-pulse { animation: none; }
    }

    /* ======================================================================
       (1.1.16) AS BOLINHAS VIRARAM SVG, E ESTE BLOCO É O QUE IMPEDE O QUADRADO.

       Relatado em 20/08/2026: "as bolinhas de status não estão redondas, a de conectado também —
       sendo que as dos cards do espelho estão certinhas". Os cards do espelho eram os únicos em
       SVG; ver o bloco de `precsBolinhaHTML` no app.js para o porquê.

       As regras antigas seguem valendo para quem ainda é `<span>`, e sobre um `<svg>` elas
       pintariam um QUADRADO de `background` atrás do círculo — o oposto do conserto. Aqui o
       seletor é `svg.classe`: um elemento mais uma classe vence a classe sozinha em
       especificidade, então isto neutraliza sem `!important` e sem tocar nas regras originais.

       `fill` NÃO é definido aqui de propósito: quem passa a cor é o call site, no atributo do
       próprio círculo, e é isso que preserva a cor dinâmica de cada lugar (cor do estado, da
       qualificação, do contraste da pílula).
       ====================================================================== */
    svg.dot, svg.precs-dot, svg.precs-sup-esc-dot,
    svg.precs-campsaldo-bolha, svg.precs-campsaldo-est-bolha{
      /* O raio FICA (ver precsBolinhaHTML): `box-shadow` de anel de pulso segue o raio da
         caixa, e com zero o anel vira um quadrado em volta da bolinha. */
      background:none;
      /* A largura e a altura vêm do atributo do SVG (que o helper escreve), e não do CSS: com
         `width` em px aqui, mudar o tamanho num call site não teria efeito. */
      width:auto; height:auto; min-width:0; min-height:0;
    }
    /* A pílula de status pinta a bolinha pela cor do texto dela e esmaecida — o `opacity` da
       regra original continua aplicável ao SVG, então só o fundo é neutralizado acima. */

    /* PRECS — garante bolinhas de cor perfeitamente redondas */
    .qualification-name { line-height: 1.2; }
    .precs-dot {
      width: 12px; height: 12px;
      min-width: 12px; min-height: 12px;
      aspect-ratio: 1 / 1;
      border-radius: 50%;
      flex: 0 0 auto;
      display: inline-block;
    }

    /* ==================================================================== */
    /* PRECS — MELHORIAS VISUAIS (tint de estado, cronômetro, termômetro,   */
    /* tema alto contraste, micro-animações). Adicionado sem alterar layout.*/
    /* ==================================================================== */

    /* PRECS (1.1.4) — PROPRIEDADES REGISTRADAS DO TINT.
     * Variável comum de CSS não interpola: o navegador troca o valor e recalcula o que
     * depende dela num único quadro. Era o que acontecia no tint, e é o motivo de a troca
     * de estado ser um corte seco. Registrando com @property, --precs-tint passa a ser uma
     * COR para o motor de animação e entra no `transition`; --precs-tint-vazio passa a ser
     * uma PORCENTAGEM e pode ser animada por keyframe.
     * O initial-value de --precs-tint-vazio é o mesmo 18% que estava escrito à mão no
     * gradiente, então sem animação nada muda de aparência.
     *
     * --precs-tint é `inherits: true` porque tem DOIS consumidores: a vinheta em
     * #precs-tint e o fundo do canvas em html[data-tint] (a calha da barra de rolagem, ver
     * o comentário do `html` no começo do arquivo). O app.js escreve a cor UMA vez, no
     * <html>, e ela desce para a vinheta por herança, em vez de dois lugares gravando a
     * mesma cor, que é como duas fontes de verdade nascem.
     * --precs-tint-vazio fica `inherits: false`: só a vinheta usa, e herdar espalharia
     * recálculo por toda a árvore sem nenhum consumidor. */
    @property --precs-tint {
      syntax: '<color>'; inherits: true; initial-value: transparent;
    }
    @property --precs-tint-vazio {
      syntax: '<percentage>'; inherits: false; initial-value: 18%;
    }

    /* (1) TINT DE ESTADO — vinheta fixa na viewport inteira, cor do estado.
     *
     * PRECS (1.1.3) — ELE SAIU DE DENTRO DA SEÇÃO, e este é o ponto todo da regra.
     *
     * Antes era `#campaign-active-section::after` com `position: fixed; inset: 0`, o
     * que PARECE cobrir a viewport e não cobria. `.section.active` roda a animação
     * de entrada `precsSecFade`, que anima `transform` — e um ancestral com transform
     * vira o BLOCO-CONTENEDOR de qualquer descendente `fixed`. Durante a animação o
     * `inset: 0` passava a resolver contra a caixa da SEÇÃO, não contra a tela.
     *
     * O sintoma era exatamente o relatado: ao entrar na campanha o tint aparecia num
     * retângulo no meio, com as bordas da tela pretas, e ao fim da animação (quando o
     * transform volta a `none`) ele saltava para a tela inteira. Pior ainda porque a
     * seção CRESCE enquanto os cards montam, então o retângulo ia inchando.
     * Medido antes de mexer, numa janela de 1600x900: a caixa do tint dava 1558x763
     * no confortável e 1838x921 no normal (aí o zoom de 0,85 distorce também).
     *
     * Agora o tint é um elemento próprio, PRIMEIRO filho do <body>: fora do <main>
     * (logo, fora do zoom) e fora da seção (logo, fora do transform). Nenhum ancestral
     * cria bloco-contenedor, então `inset: 0` é a viewport de verdade, sempre.
     * Cuidado ao mexer: header e main recebem `position: relative; z-index: 1` logo
     * abaixo justamente para continuarem por cima dele — sem isso o tint, que é
     * posicionado, pintaria sobre o conteúdo em fluxo. */
    #precs-tint {
      /* PRECS (1.1.4) — `width: 100vw` em vez de `right: 0`. Com `scrollbar-gutter:
         stable` no <html>, o bloco-contenedor do `fixed` é a tela MENOS a calha da barra
         de rolagem, então `inset: 0` deixava a faixa da direita sem cor de estado: quem
         passa atrás da máquina via a cor cortada antes da borda. `100vw` inclui a calha.
         Só funciona porque este elemento é filho direto do <body> e NÃO pega o `zoom` do
         header/main (dentro de elemento com zoom o Chrome não ajusta `vw`, ver o
         comentário do header). É `fixed`, então esticar além da tela não cria rolagem
         horizontal. */
      position: fixed; top: 0; bottom: 0; left: 0; width: 100vw;
      pointer-events: none; z-index: 0;
      /* elipse acompanha o formato da tela (menos "anel redondo") e a cor chega
         cheia na borda (100%) para o tint continuar VISÍVEL; rampa longa desde
         18% deixa a transição suave, sem quebra dura. */
      /* a intensidade (--precs-tint-forca) muda por TEMA: no claro precisa de
         mais opacidade para aparecer sobre o fundo claro; no escuro, menos. */
      background: radial-gradient(ellipse 125% 118% at 50% 50%, transparent var(--precs-tint-vazio, 18%),
        color-mix(in srgb, var(--precs-tint, transparent) var(--precs-tint-forca, 34%), transparent) 100%);
      opacity: 0;
      /* PRECS (1.1.4) — A COR AGORA TRANSICIONA, e a transição vive no <html> (é lá que a
         cor é escrita e de lá que ela é herdada). Sem registrar --precs-tint com
         @property, a troca de estado reescrevia a variável e o gradiente era recalculado
         no mesmo quadro: corte seco de cinza para laranja para verde. A opacidade tinha
         .6s e por isso SÓ a primeira aparição parecia suave. */
      transition: opacity .6s ease;
    }
    /* O conteúdo fica ACIMA do tint. Sem isto, um elemento posicionado (o tint) pinta
       na frente do conteúdo em fluxo, mesmo com z-index menor. */
    header, main { position: relative; z-index: 1; }
    /* tint FIXO (sem pulsar, a pedido) */
    #precs-tint.precs-tint-on { opacity: 1; }
    /* PRECS (1.1.4) — LAVAGEM DA COR NOVA, DA BORDA PARA O CENTRO.
       A cor sozinha já transiciona (ver o `transition` acima), mas trocar de cor no lugar
       é pouco para o uso real: o supervisor comercial passa atrás das máquinas e lê o
       estado pela cor de longe, sem olhar a tela de frente. Movimento é o que o olho
       periférico pega. Então, na troca, o vazio do centro do gradiente parte de 52% e
       fecha para os 18% de regime: a cor entra pelas bordas e avança para dentro.
       A curva é a mesma das entradas do painel (cubic-bezier(.16,1,.3,1)): rápida no
       começo e assentando no fim, que é o oposto de um pisca-pisca.
       A classe é posta e RETIRADA por precsCorEstado (busque `precs-tint-troca` no
       app.js), e só quando a cor muda de fato — a função é chamada várias vezes no mesmo
       estado, e reanimar a cada chamada seria a tela respirando sem motivo. */
    #precs-tint.precs-tint-troca { animation: precsTintLava .75s cubic-bezier(.16,1,.3,1); }
    @keyframes precsTintLava {
      from { --precs-tint-vazio: 52%; }
      to   { --precs-tint-vazio: 18%; }
    }
    /* força do tint por tema — intensidade reduzida.
       (1.1.4) Declarada no ELEMENTO DO TEMA e não mais em `... #precs-tint`: a vinheta
       recebe o valor por herança, igual a antes, e o fundo do canvas (html[data-tint],
       logo abaixo) passa a poder usar a mesma força. Sem isso a calha precisaria de uma
       segunda tabela de intensidades por tema, que divergiria da primeira. */
    html[data-luz="claro"] { --precs-tint-forca: 46%; } /* PRECS Claro */
    html[data-theme="precs"], html[data-theme="bordo"],
    html[data-theme="zebrado"], html[data-theme="saphira"],
    html[data-theme="topazio"], html[data-theme="ametista"],
    html[data-theme="rubi"], html[data-theme="leao"],
    html[data-theme="sakura"],
    html[data-theme="galaxia"] { --precs-tint-forca: 35%; }
    /* (1.1.42) OS DOIS CLAROS TEMÁTICOS BAIXARAM PARA 30%, e isto é a correção do
       "o bordô claro e o zebrado claro não mudou nada absolutamente nada EM CHAMADA"
       (Pedro, 31/08/2026).
       A vinheta cobre `100vw` por toda a altura e a cor dela é a do ESTADO, cravada no JS
       (`PRECS_ESTADO_CORES`), igual em todos os temas — como tem de ser. Com 46% de força,
       porém, ela recobria justamente a camada que distingue um tema claro do outro (as
       superfícies e o acento), e os três claros convergiam para a mesma imagem verde assim
       que a ligação conectava. Em 30% o verde continua verde, continua atravessando a tela e
       continua legível do corredor; o que volta a aparecer por baixo é o tema.
       O claro quente segue em 46% porque ele é o claro NEUTRO: não tem superfície própria
       para deixar aparecer. */
    html[data-theme="bordo-claro"], html[data-theme="zebrado-claro"],
    html[data-theme="saphira-claro"], html[data-theme="topazio-claro"],
    html[data-theme="ametista-claro"], html[data-theme="rubi-claro"],
    html[data-theme="galaxia-claro"] { --precs-tint-forca: 30%; }
    html[data-theme="contraste"] { --precs-tint-forca: 40%; }
    /* (1.1.45) O claro do par. Mais baixo que os 46% do grupo claro porque a lavagem de cor
       do estado atravessa um fundo neutro sem matiz nenhum: no #E8E8E8 ela aparece com menos
       força de tinta e mais de cor pura, e passar de 30% tingiria a tela inteira. */
    html[data-theme="contraste-claro"] { --precs-tint-forca: 30%; }

    /* PRECS (1.1.4) — A CALHA DA BARRA DE ROLAGEM TAMBÉM RECEBE A COR DO ESTADO.
       Aquela tira de ~10px na direita é a única parte da tela que conteúdo nenhum alcança
       (ver o comentário do `html` no topo do arquivo), e com a tela tingida ela ficava
       crua: de longe lia como se a página terminasse ali. Agora ela é o fundo do canvas
       tingido pela MESMA cor e pela MESMA força por tema da vinheta.
       O atributo data-tint é posto e retirado junto do .precs-tint-on da vinheta, para a
       calha voltar ao fundo neutro quando o operador sai da campanha.

       O 0,35 NÃO é chute: --precs-tint-forca é a força da cor no FIM do gradiente, e a
       elipse do gradiente é maior que a tela (125% x 118%), então na borda direita a
       vinheta está longe do fim dela. Medido pixel a pixel numa janela de 1400x700, tema
       claro, tint verde: a página encosta na borda com alfa efetivo 0,12 no meio da altura
       e 0,20 perto dos cantos, contra os 0,46 do token — ou seja, cerca de um terço.
       Usar o token cheio deixava a calha visivelmente mais saturada que a tela ao lado, o
       que troca uma tira crua por uma tira berrante. Com 0,35 a tira fica em 0,16, dentro
       da faixa que a tela tem ali. Se mexer na geometria do gradiente, refaça a medida. */
    html[data-tint] {
      background-color: color-mix(in srgb,
        var(--precs-tint) calc(var(--precs-tint-forca, 34%) * 0.35), var(--bg-primary));
    }

    /* PRECS (1.1.5) — O HEADER TAMBÉM RECEBE A COR DO ESTADO, COMO VÉU.
     *
     * POR QUE: na 3C Plus a barra do topo fica verde inteira em atendimento e azul em TPA,
     * e o supervisor comercial usa isso — ele passa ATRÁS das máquinas e lê o estado do
     * operador de longe, sem olhar a tela de frente. A barra do topo é o que ele vê
     * primeiro. Nossa vinheta cumpre parte disso, mas ela é mais forte nas bordas
     * laterais; o topo ficava neutro justamente onde o olho dele bate.
     *
     * POR QUE VÉU E NÃO COR SÓLIDA, que é o que a 3C faz: com o header sólido na cor do
     * estado, quase todo texto dele reprova AA nos 3 temas. Medido, com --st-call sólido:
     * --text-primary cai para 1,57 no dark e 1,72 no precs; --text-secondary reprova nos
     * 24 pares testados; o botão Sair (fundo transparente, texto vermelho) vai a 1,54; e o
     * pior, o badge ".status-badge.connected" — verde translúcido com texto verde — fica
     * em 1,00:1 sobre header verde, ou seja, DESAPARECE exatamente no estado que ele
     * anuncia. Cor sólida obrigaria a repintar badge, Sair, nome, empresa e a trocar a
     * logo (WebP dourado de fundo transparente, que sobre o amarelo do intervalo quase
     * não se vê). Nada disso é ganho de produto.
     *
     * O 14% NÃO É CHUTE. Pior caso de contraste do texto do header sobre o header tingido,
     * varrendo os 5 cenários de tema × os 6 estados, para --text-primary e --text-secondary:
     *     14% -> 5,63:1   (claro suave, qualificando, --text-secondary)
     *     16% -> 5,45:1
     *     20% -> 5,11:1
     * (1.1.10) A LISTA DE CENÁRIOS DESTA MEDIÇÃO era "claro, claro suave, dark, precs,
     * contraste" — a de quando ela foi feita, antes de a 1.1.6 remover o `dark` e o claro
     * clássico entrar no lugar. Não reescrevi a enumeração para a atual, porque isso faria o
     * comentário afirmar uma medição que ninguém rodou. O que mantém o número honesto hoje é
     * outra coisa, e ela é melhor: o teste-contraste.js varre o header tingido nos 5 estados
     * ATUAIS, no CI, a cada push. Se o 14% deixar de ter folga, ele reprova.
     * Ou seja, 14% tem folga de sobra e ainda é o TOPO da faixa que as sete regras mortas
     * de `body.manual-mode` usavam (4% a 14%) — a única parte delas que valia guardar.
     *
     * O QUE LIMITA DE VERDADE é o --text-muted, e por isso ele NÃO entra no header. Ele é
     * o token mais apertado do projeto (#918F86 no precs, que já reprovou AA uma vez).
     * Com ele dentro, o teto por tema cairia para 10% (claro), 14% (claro suave), 8%
     * (dark), 5% (precs) e 23% (contraste) — 5% é véu invisível, o que mata o motivo de
     * ele existir. O ramal que entrou no header nesta versão usa --text-secondary por
     * causa disso, não por estética. Quem for pôr algo em --text-muted aqui precisa
     * acrescentá-lo em teste-contraste.js e refazer esta escolha.
     *
     * A 86% de --bg-secondary, o badge translúcido e o Sair transparente ficam
     * praticamente como hoje, e é por isso que este item não precisou repintar nenhum dos
     * dois. Quem for AUMENTAR essa porcentagem herda os dois problemas de volta.
     *
     * O MIX É UM TOKEN e não um color-mix escrito dentro da regra do header, de propósito:
     * o teste-contraste.js lê tokens do documentElement e não alcança mix dentro de regra.
     * Como token, o CI consegue cruzar --header-bg-estado com os tokens de texto (ele faz
     * isso agora, num eixo tema × estado).
     *
     * Zero JS novo: precsCorEstado já escreve --precs-tint e data-tint no <html>, e
     * showSection já limpa os dois ao sair da campanha — então o header volta ao neutro
     * sozinho no login, no menu e na supervisão. --precs-tint é @property com
     * inherits:true justamente para ter mais de um consumidor; este é o terceiro.
     * Usa --precs-tint (hex fixo) e não --precs-cor-estado (token de tema) porque o véu do
     * header fica encostado na vinheta da tela: as duas precisam ler como a MESMA cor. */
    html:not([data-tint]) { --header-bg-estado: var(--bg-secondary); }
    html[data-tint] {
      --header-tint-forca: 14%;
      --header-bg-estado: color-mix(in srgb,
        var(--precs-tint) var(--header-tint-forca), var(--bg-secondary));
    }
    /* O filete: a borda de baixo do header assume a cor do estado e engorda para 2px. É o
       que dá leitura a DISTÂNCIA — o véu de 14% é sutil de perto e some de longe; uma linha
       de cor cheia atravessando a tela, não. 70% em vez de 100% para não virar um traço
       fluorescente no tema de alto contraste. */
    html[data-tint] header {
      border-bottom-width: 2px;
      border-bottom-color: color-mix(in srgb, var(--precs-tint) 70%, var(--border-color));
    }

    /* (3) CRONÔMETRO PROTAGONISTA — maior e com cor progressiva. */
    /* (1.1.10) OS TRÊS ESTADOS DO CRONÔMETRO usam a variante de TEXTO do acento.
       Ele é o número mais olhado da tela durante a ligação e media 2,35:1 no claro e 2,04:1
       no claro suave — reprovando até no limiar folgado de texto grande (3:1), apesar dos
       30px. O fallback literal saiu junto: `var(--accent-green, #2BD4A0)` afirmava que o
       verde padrão é o do tema escuro, e era isso que quem copiasse a linha reproduziria. */
    #call-duration-campaign {
      font-size: 1.9rem;
      font-weight: 700;
      letter-spacing: 1px;
      color: var(--accent-green-text);
      transition: color .5s ease;
    }
    #call-duration-campaign.precs-timer-warn   { color: var(--accent-manual-text); }
    #call-duration-campaign.precs-timer-danger { color: var(--accent-red-text); }

    /* (4) TERMÔMETRO DE SESSÃO — barra proporcional acima dos 4 cards. */
    .precs-thermo {
      display: flex;
      height: 24px;
      border-radius: var(--radius-inner);
      overflow: hidden;
      border: 1px solid var(--border-color);
      background: var(--bg-tertiary);
      margin-bottom: 12px;
    }
    .precs-thermo-seg {
      display: flex; align-items: center; justify-content: center;
      font-size: 10.5px; font-weight: 700; color: #0d0f13;
      white-space: nowrap; overflow: hidden; min-width: 0;
      transition: flex-basis .6s cubic-bezier(.4,0,.2,1);
    }
    /* 1ª pintura ao entrar na campanha: sem animação (nasce cheio) */
    .precs-thermo.precs-thermo-noanim .precs-thermo-seg { transition: none; }
    .precs-thermo-seg[data-balde="ocioso"]    { background: var(--st-idle); }
    .precs-thermo-seg[data-balde="manual"]    { background: var(--st-manual); }
    .precs-thermo-seg[data-balde="emChamada"] { background: var(--st-call); }
    .precs-thermo-seg[data-balde="tpa"]       { background: var(--st-tpa); color: #fff; }

    /* (5) MICRO-ANIMAÇÕES */
    /* card de dados do cliente desliza ao carregar */
    @keyframes precsSlideIn { from{opacity:0; transform:translateX(-14px)} to{opacity:1; transform:translateX(0)} }
    /* PRECS — mesma duração/curva da entrada do mailing (.precs-entra) para os
       dois cards da coluna esquerda surgirem em sincronia (crossfade coeso). */
    #client-data-section.precs-slide-in { animation: precsSlideIn .32s cubic-bezier(.16,1,.3,1); }
    @media (prefers-reduced-motion: reduce) {
      #client-data-section.precs-slide-in { animation: none !important; }
      /* (1.1.3) o tint saiu da seção. (1.1.4) a lavagem da cor também é movimento:
         sem animação, o gradiente nasce direto no vazio de regime. O `html` entra aqui
         junto porque é dele que saem as duas transições do tint: a cor (herdada pela
         vinheta) e o fundo do canvas, que pinta a calha da barra de rolagem.
         (1.1.5) o `header` entra pelo mesmo motivo: o véu de estado dele transiciona. */
      #precs-tint { transition: none; animation: none !important; }
      html, header { transition: none; }
    }

    /* (Tema) ALTO CONTRASTE — fundo puro, texto branco, acentos vivos. */
    html[data-theme="contraste"] {
      --bg-primary:#000000; --bg-secondary:#0A0A0A; --bg-tertiary:#141414;
      /* (1.1.45) 3:1 DE VERDADE, E SÓ NESTE TEMA. A WCAG 1.4.11 pede 3:1 para o contorno de
         componente, e a medição do teste-temas-render achou 25 pares abaixo disso nos nove
         temas. A decisão foi corrigir apenas aqui: este é o único tema cujo propósito
         declarado é acessibilidade, então é o único que TEM de cumprir a norma — nos outros a
         aparência foi calibrada e fica.
         Era #3A3A3A, que dava 1,85:1 sobre #000, 1,74:1 sobre #0A0A0A e 1,62:1 sobre #141414.
         #626262 é o piso exato para 3:1 contra a pior das três (3,02:1); #646464 dá 3,11:1 e
         não fica em cima da linha se alguma superfície for recalibrada depois.
         Um token resolve três pares de uma vez, porque `--panel-fio` deste tema é ele mesmo
         (ver o bloco de --panel-fio): a borda do campo de texto, a caixa do trilho de temas e
         o fio do card. */
      --border-color:#646464;
      --text-primary:#FFFFFF; --text-secondary:#D8D8D8; --text-muted:#A0A0A0;
      --precs-gold:#E8C46A; --precs-gold-soft:#F0D488; --precs-gold-light:rgba(232,196,106,0.20);
      --precs-gold-title:#F0D488; --precs-gold-hi:#FFD97A;
      --accent-3cplus:#E8C46A;
      /* (1.1.42) Faltava, e o efeito era pequeno e feio: `.btn-3cplus:hover` trocava o ouro
         claro do tema pelo âmbar 3C do `:root`, só no tema de acessibilidade. */
      --accent-3cplus-dark:#D9AE4E;
      --accent-blue:#7C9CFF; --accent-blue-hover:#93AEFF; --accent-blue-light:rgba(124,156,255,0.20);
      --accent-manual:#FFA94D; --accent-green:#3DF5B8; --accent-green-light:rgba(61,245,184,0.20);
      --accent-red:#FF7B7B; --accent-red-light:rgba(255,123,123,0.20);
      --accent-orange:#FFB84D; --accent-orange-light:rgba(255,184,77,0.20);
      --shadow:0 0 0 1px rgba(255,255,255,0.06), 0 8px 24px rgba(0,0,0,0.8);
      --shadow-lg:0 0 0 1px rgba(255,255,255,0.08), 0 12px 32px rgba(0,0,0,0.9);
      --lead-surface:#0A0A0A; --lead-border:#3A3A3A;
      /* superfície/borda dos CAMPOS internos do pipe: sem estas, os campos ficavam
         sem fundo/borda no contraste e "sumiam". */
      --lead-surface-2:#161616; --lead-border-2:#3A3A3A;
      --lead-title:#FFFFFF; --lead-text:#FFFFFF; --lead-label:#E8C46A; --lead-muted:#A0A0A0;
      --st-idle:#EDF1F7; --st-manual:#FFA94D; --st-call:#3DF5B8; --st-tpa:#7C9CFF; --st-mtpa:#C4A9FF; --st-interval:#FFD97A; --st-offline:#7A808A;
      --m-cpc:#7C9CFF;
      /* (1.1.10) Este tema clareia TODOS os acentos de propósito, para eles se separarem do
         preto puro do fundo. O efeito colateral é que branco em cima deles fica pior aqui do
         que em qualquer outro tema — o âmbar #FFA94D com texto branco media 1,90:1, o pior
         par do painel inteiro, justamente no tema cujo propósito é acessibilidade. Texto
         escuro em cima, então. Os tokens de TEXTO ficam sendo o próprio acento: sobre preto
         o verde dá 14,10:1. */
      --on-accent-blue: var(--on-accent-ink);
      --accent-green-text: var(--accent-green);
      --accent-red-text: var(--accent-red);
      --accent-manual-text: var(--accent-manual);
      --accent-orange-text: var(--accent-orange);
    }
    /* Os dois literais que estavam aqui (#3A3A3A e #000) repetiam à mão --border-color e
       --bg-primary, declarados 6 linhas acima no MESMO bloco. Duplicação de token no tema
       menos exercitado da casa é onde a divergência passa mais tempo sem ninguém ver. */
    html[data-theme="contraste"] .panel { border: 1px solid var(--panel-fio); }
    html[data-theme="contraste"] body { background: var(--bg-primary); }

    /* ==================================================================== */
    /* PRECS — MEU DESEMPENHO 2.0: cores por métrica + animações            */
    /* ==================================================================== */
    #precs-meudia-card { animation: pmEntra .45s cubic-bezier(.2,.7,.3,1); }
    @keyframes pmEntra { from{opacity:0; transform:translateY(10px)} to{opacity:1; transform:translateY(0)} }
    .pm-fade { animation: pmFade var(--dur-3) ease; }
    @keyframes pmFade { from{opacity:0; transform:translateY(4px)} to{opacity:1; transform:translateY(0)} }

    /* Cores semânticas das métricas (usadas nos cards E no gráfico, para
       ficarem sempre iguais). Para variar por tema, redefina estas variáveis
       dentro do bloco do tema, ex.: html[data-theme="precs"] { --m-cpc: #8AA0E6; }

       PRECS (1.1.2) — ESTES TOKENS SÃO COR DE TEXTO, e por isso valem AA.
       O que .pm-num pinta com --pm-cor é o NÚMERO da métrica, não um enfeite. Só
       que quatro deles nunca chegaram a ser usados: precsRenderStats e
       precsRenderMiniGrafico escreviam --pm-cor com hex à mão, os mesmos valores
       de tema escuro nos 3 temas. No tema claro o resultado era um verde
       #22C55E sobre --bg-tertiary (#F0F2F5) a 2,03:1, reprovação larga em cima do
       número que o operador mais olha no card.
       Agora o :root carrega os valores do tema CLARO (escuros, para contrastar com
       fundo claro) e cada tema escuro sobrescreve os seis. Contas contra o
       --bg-tertiary de cada tema, que é o fundo do .pm-stat:
         claro #F0F2F5 -> ligacoes 5,25 · atendidas 4,84 · taxa 6,33 · cpc 5,98 · falado 4,67 · duracao 5,03
         dark  #1E222B -> 7,38 · 6,99 · 5,85 · 6,26 · 7,85 · 6,26
         precs #232327 -> 7,26 · 6,87 · 5,75 · 6,15 · 7,72 · 6,16
         contr #141414 -> 8,54 · 8,08 · 6,77 · 7,07 · 9,08 · 7,24
       Os seis também são distinguíveis entre si em cada tema (distância RGB >= 60),
       porque ficam lado a lado numa fila de chips e a cor é o que os separa.
       O teste .github/scripts/teste-contraste.js trava as duas coisas: mexer aqui
       sem refazer a conta quebra o CI.

       No claro, --m-falado é o MESMO ouro do tema (--precs-gold) e --m-duracao é o mesmo
       cinza de --text-muted, de propósito: onde a paleta do tema já tinha um valor medido,
       ele é reusado em vez de inventar um sétimo tom de ouro. E --m-ligacoes ficou num
       laranja queimado (#A34B00) em vez do âmbar escurecido: âmbar escuro e ouro escuro
       colidiam (distância 54), e "Ligações" e "Falado" são chips vizinhos.

       (1.1.6) ESSE ACOPLAMENTO É A PEGADINHA DESTE BLOCO. Como dois dos seis valores são
       CÓPIAS de tokens do tema claro, retonalizar o claro sem vir aqui deixa dois chips do
       "Meu desempenho" na paleta velha, e a intenção acima passa a ser falsa em silêncio —
       ninguém vê um erro, só dois números num ouro e num cinza que não são mais os da tela.
       Com o PRECS Claro, os dois acompanharam: --m-falado virou o bronze #7A6320 (o novo
       --precs-gold) e --m-duracao virou #6B655C (o novo --text-muted). Se um dia o claro
       mudar de tom outra vez, estes dois vêm junto. O mesmo par já é repetido no bloco do
       brilho suave, por este mesmo motivo. */
    :root {
      --m-ligacoes: #A34B00;   /* laranja queimado — 5,03:1 */
      --m-atendidas: #0F7A3D;  /* verde — 4,63:1 */
      --m-taxa: #6D28D9;       /* violeta — taxa de atendimento — 6,07:1 */
      --m-cpc: #1D4ED8;        /* azul — 5,72:1 */
      --m-falado: #7A6320;     /* = --precs-gold do PRECS Claro — 4,94:1 */
      --m-duracao: #6B655C;    /* = --text-muted do PRECS Claro — 4,93:1 */
    }
    /* Temas escuros: os valores originais da família, que já contrastavam com fundo
       escuro. Os três compartilham a mesma regra porque os três fundos são escuros e
       o mesmo valor passa nos três (as contas acima confirmam).
       --m-cpc é a EXCEÇÃO e não está aqui: cada tema escuro tem o seu (#8AA0E6 no precs,
       #7C9CFF no contraste), e os dois continuam onde sempre estiveram, dentro do bloco do
       respectivo tema. Quem for mexer na família precisa saber disso: procure por
       "--m-cpc" antes de concluir que este é o único lugar. Unificar num valor só é
       possível, mas trocaria um azul afinado por tema por um genérico, o que é decisão de
       design e não de contraste.
       (1.1.6) Eram TRÊS temas escuros aqui; o `dark` saiu e levou o #60A5FA dele.
       (1.1.41) E o `bordo` entrou. Ele caía no `:root`, que são os valores do CLARO: cinco
       tokens de métrica em tom escuro sobre fundo vinho escuro, entre 2,3:1 e 3,0:1. Quem
       acusou foi o teste-contraste.js na primeira rodada do tema — que é exatamente o motivo
       de uma paleta liberada para poucas pessoas entrar na lista dele. */
    html[data-theme="precs"], html[data-theme="contraste"], html[data-theme="bordo"],
    html[data-theme="zebrado"], html[data-theme="saphira"],
    html[data-theme="topazio"], html[data-theme="ametista"],
    html[data-theme="rubi"], html[data-theme="leao"], html[data-theme="sakura"],
    html[data-theme="galaxia"] {
      --m-ligacoes: #E8A33D;   /* âmbar */
      --m-atendidas: #22C55E;  /* verde */
      --m-taxa: #A78BFA;       /* violeta */
      --m-falado: #D3B26B;     /* dourado suave */
      --m-duracao: #9AA3B2;    /* cinza neutro */
    }
    .pm-stats { display:flex; gap:var(--space-2); }
    .pm-stat {
      flex:1; text-align:center; padding:var(--space-2) var(--space-1);
      border-radius: var(--radius-inner); background:var(--bg-tertiary);
      border:1px solid var(--border-color);
      border-top:3px solid var(--pm-cor, var(--precs-gold, #B49552));
      transition:background .18s ease, box-shadow .18s ease;
    }
    /* informativo (não clicável): realce sutil no hover, sem levantar nem mudar tamanho */
    .pm-stat:hover {
      background:color-mix(in srgb, var(--pm-cor, var(--precs-gold, #B49552)) 12%, var(--bg-tertiary));
      box-shadow:0 0 0 1px color-mix(in srgb, var(--pm-cor, var(--precs-gold, #B49552)) 35%, transparent) inset;
    }
    /* PRECS (1.1.56) — O NÚMERO SOBE DE 1,2rem PARA 1,5rem, e é a primeira mudança DELIBERADA
       de tamanho desde que a escala existe.
       A leva anterior migrou 25 declarações desta tela para os degraus `--fs-*` sem mover nada:
       a tolerância do oráculo é 0,50px, então ela só podia dar NOME ao que havia. O `.pm-num`
       foi a única deixada de fora, de propósito — 1,2rem são 19,2px e o degrau mais próximo
       ficava a 1,2px, muito além da tolerância. Mover ali seria mudança visual disfarçada de
       migração; aqui ela é assumida.
       O motivo: estes quatro números são o ASSUNTO do card, e estavam a 19,2px contra rótulos
       de 11px. Perto demais para o olho separar "o dado" do "nome do dado". A 24px contra 10px
       a distância vira hierarquia. E o rótulo desce um degrau na mesma volta, porque hierarquia
       se faz afastando os DOIS lados, não só engordando um. */
    .pm-num {
      font-size:var(--fs-11); font-weight:700; line-height:1.2;
      font-family:'JetBrains Mono', monospace;
      color:var(--pm-cor, var(--text-primary));
    }
    .pm-lbl {
      font-size:var(--fs-2); text-transform:uppercase; letter-spacing: var(--ls-caps);
      color:var(--text-muted); margin-top:var(--space-0);
    }
    .pm-lbl-ic {
      width:9px; height:9px; vertical-align:-1px; margin-right:3px;
      color:var(--pm-cor, var(--precs-gold, #B49552));
    }

    .pm-secao {
      font-size:var(--fs-4); text-transform:uppercase; letter-spacing: var(--ls-caps);
      color:var(--text-muted); margin:var(--space-4) 0 var(--space-2);
    }
    /* PRECS — aviso "dia ainda não começou" no card Meu desempenho */
    .pm-empty-hoje {
      display:flex; gap:var(--space-3); align-items:center;
      padding:var(--space-4) var(--space-4); margin-bottom:var(--space-4);
      border:1px solid color-mix(in srgb, var(--precs-gold,#B49552) 42%, transparent);
      border-left:3px solid var(--precs-gold,#B49552);
      border-radius:var(--radius-card);
      background:linear-gradient(135deg,
        color-mix(in srgb, var(--precs-gold,#B49552) 14%, transparent),
        color-mix(in srgb, var(--precs-gold,#B49552) 4%, transparent));
    }
    .pm-empty-badge {
      flex:none; width:40px; height:40px; border-radius:50%;
      display:flex; align-items:center; justify-content:center;
      background:color-mix(in srgb, var(--precs-gold,#B49552) 16%, transparent);
      box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--precs-gold,#B49552) 30%, transparent);
    }
    .pm-empty-txt { min-width:0; }
    /* PRECS (1.1.57) — A ESTRELA ACALMOU, e a geometria explica as duas mudanças.
       O relato foi "não sei se está centralizada 100%". Medi: ESTÁ. O path tem simetria de
       ordem 4 em torno de (0,0), o `viewBox` é centrado e o badge de 40px deixa 9px de folga
       de cada lado. O que enganava era a ANIMAÇÃO.

       1) A ESCALA. O `precsStar` pulsa de `scale(.7)` a `scale(1)`. Em 0,7 a estrela ocupa
          13,9px dentro de um círculo de 40px — pequena demais para parecer assentada, e é daí
          que vem a sensação de estar solta. A faixa nova é 0,92 a 1: o desenho respira sem
          nunca deixar de preencher o círculo.

       2) O GIRO FECHA EM 90° E NÃO EM 360°, e isso é consequência direta da simetria de ordem
          4: girar a estrela 90° a leva EXATAMENTE sobre si mesma. Um laço de 360° é, para o
          olho, quatro voltas idênticas emendadas — três quartos de movimento que ninguém
          percebe como movimento. Com 90° o laço é contínuo do mesmo jeito (o quadro final é
          idêntico ao inicial, então não há salto), e o ciclo fica quatro vezes mais econômico
          para dizer a mesma coisa.

       ESCOPADO, e é obrigatório: o `precsStar` original serve também o `.pm-loader`
       (`40-temas.css`) e um indicador da supervisão, os dois a 1,2s. Mexer nele mudaria três
       lugares para consertar um. */
    .pm-empty-star { flex:none; animation:precsEstrelaCalma 2.4s ease-in-out infinite; }
    /* (1.1.57, terceira volta) O GIRO VOLTOU, e eu devo uma correção ao que escrevi antes.
       Eu tinha tirado a rotação com este argumento: a caixa da estrela cai para 71% da largura
       ao passar por 45°, então ela "encolheria 29%". A conta da CAIXA está certa e a CONCLUSÃO
       estava errada — o que encolhe é a caixa alinhada aos eixos, não a estrela. As quatro
       pontas ficam a 9 unidades do centro em QUALQUER ângulo: girando, elas percorrem a mesma
       circunferência, e o desenho ocupa sempre o mesmo círculo. Ninguém vê a caixa; vê a
       estrela. Eu inferi a percepção de um número que não media a percepção.
       O que de fato causava a sensação de "torta" era o pulso de escala de 0,7 a 1 do
       `precsStar` original — em 0,7 a estrela ocupava 13,9px num círculo de 40px. Isso foi
       corrigido na primeira volta e continua corrigido aqui: 0,94 a 1.
       O laço fecha em 90° porque a estrela é 4× simétrica: o quadro final é idêntico ao
       inicial, então o giro é contínuo e sem salto — e leva um quarto do tempo para dizer o
       mesmo que 360° diriam. */
    @keyframes precsEstrelaCalma {
      0%   { transform:rotate(0deg)  scale(.94); opacity:.8; }
      50%  { transform:rotate(45deg) scale(1);   opacity:1;  }
      100% { transform:rotate(90deg) scale(.94); opacity:.8; }
    }
    @media (prefers-reduced-motion: reduce){ .pm-empty-star { animation:none; } }
    .pm-empty-tit { font-weight:700; color:var(--text-primary); font-size:var(--fs-7); letter-spacing:.01em; }
    .pm-empty-label { font-size:var(--fs-2); text-transform:uppercase; letter-spacing: var(--ls-caps); font-weight:700;
      color:var(--precs-gold,#B49552); margin-bottom:var(--space-1); font-family:var(--font-acao); }
    .pm-empty-frase { font-weight:600; color:var(--text-primary); font-size:var(--fs-8); line-height:1.4;
      font-family:var(--font-corpo); }
    .pm-empty-sub { font-size:var(--fs-4); color:var(--text-secondary); margin-top:var(--space-1); line-height:1.4; }
    /* zeros do dia perdem destaque enquanto o dia não começou: a mensagem é o foco */
    .pm-stats-idle { opacity:.45; transition:opacity var(--dur-3) ease; }
    /* gráfico dos 7 dias: barras empilhadas em dois tons de dourado (total
       esmaecido, fração atendida em dourado cheio), altura proporcional.
       .pm-bars tem altura FIXA: não varia com a quantidade de campanhas. */
    /* PRECS (1.1.56) — A FOLGA DO CARD VIROU GRÁFICO. Era `height:130px; flex:none`, e toda a
       sobra entre a altura medida do card e o conteúdo real caía no `.pm-fill` logo abaixo —
       um espaçador VAZIO que, medido na tela do Kevin em 1920x1080, fica com cerca de 320px.
       É o maior espaço morto do menu, e ele existe desde sempre porque as barras não sabiam
       crescer.
       ISTO NÃO FURA a regra da altura independente do conteúdo (`00-base.css`, o bloco do
       `#precs-meudia-card`): quem define a altura do card continua sendo o JS, e ela continua
       sem depender do que há dentro. O que muda é para onde a folga escorre DEPOIS que a
       altura já foi decidida — antes, para o branco; agora, para o gráfico.
       O TETO DE 200px, E ELE JA FOI 300 NESTA MESMA LEVA. Sem teto nenhum, num card de
       ~820px as barras herdam ~500px; com 300 elas ficaram em 26px de largura por 300 de
       altura, 11:1, e na tela isso le como TERMOMETRO e nao como barra — foi preciso ver
       renderizado para perceber. A 200px, com a barra engordando para 34px (ver `.pm-bar`),
       a proporcao fica em 5,9:1, perto dos 5:1 que o grafico tinha em 130px.
       O QUE ISSO NAO RESOLVE, e vale dizer: em dia sem ligacoes o card ainda termina com
       ~190px de branco. Ele nao existe por falta de teto, existe porque a altura do card e
       o ESPACO DISPONIVEL e nao o conteudo — e isso e a regra que impede o menu de pular ao
       trocar o dia no grafico. Recuperar o resto exigiria abrir mao dela.
*/
    .pm-bars { display:flex; align-items:stretch; gap:var(--space-1); flex:1 1 auto;
               min-height:130px; max-height:200px; }
    /* espaçador que absorve a sobra no rodapé, mantendo os cards laterais na
       mesma altura sem esticar o gráfico */
    /* PRECS (1.1.56) — O ESPACADOR VIRA ZERO. Ele era `flex:1` para absorver a sobra do card,
       e com as barras tambem em `flex:1` os dois passaram a DIVIDIR a folga meio a meio —
       medido, o grafico ficou com 174px e o branco com 136px, quando a intencao era o grafico
       levar tudo ate o teto. Com `flex:0 0 0` quem cresce e so o grafico; o que passar do teto
       de 300px sobra no fim da coluna, que e visualmente o mesmo que este div mostrava.
       A classe fica porque o `app.js` a emite. */
    .pm-fill { flex:0 0 0; min-height:0; }
    .pm-dia {
      flex:1; display:flex; flex-direction:column; align-items:center;
      gap:var(--space-1); min-width:0; cursor:pointer;
      border-radius:var(--radius-sm); padding-bottom:var(--space-0);
      transition:background var(--dur-1) ease;
    }
    .pm-dia:hover { background:color-mix(in srgb, var(--precs-gold, #B49552) 10%, transparent); }
    .pm-dia-val { font-size:var(--fs-2); height:.85rem; line-height:.85rem; font-family:var(--font-num); color:var(--text-secondary); }
    /* trilha ocupa toda a altura disponível; a barra sobe a partir da base */
    /* PRECS (1.1.57) — A LINHA DE BASE. Sem eixo, as barras flutuavam: não havia nada dizendo
       onde é o zero, e num gráfico isso é a informação que sustenta todas as outras.
       É `box-shadow: inset` e não `border-bottom` de propósito — borda entra no fluxo e
       empurraria a barra 1px para cima, o que muda a altura de todas as sete por causa de uma
       linha decorativa. */
    .pm-track { flex:1; width:100%; display:flex; align-items:flex-end; justify-content:center;
                box-shadow: inset 0 -1px 0 color-mix(in srgb, var(--text-muted) 30%, transparent); }
    .pm-bar {
      /* (1.1.56) 26px → 34px de largura máxima. A barra tinha o teto de 26px calibrado para
         uma trilha de 130px de altura, onde a proporção fecha em 5:1. Com o gráfico passando
         a crescer, 26px por 200px dá 7,7:1 — e a 300px (o teto que esta leva chegou a tentar)
         dava 11:1, que na tela lê como TERMÔMETRO e não como barra. Engordar é o que mantém a
         forma enquanto a altura muda. Cabe: sete dias numa trilha de ~426px dão ~57px por dia. */
      width:72%; max-width:34px; height:0;   /* cresce até data-h (%) via JS */
      border-radius:6px 6px 3px 3px; overflow:hidden;
      display:flex; align-items:flex-end;
      background:color-mix(in srgb, var(--m-ligacoes, #E8A33D) 28%, transparent);
      transition:height .6s cubic-bezier(.2,.7,.3,1), filter var(--dur-2) ease;
    }
    /* fração atendida = verde de Atendidas (mesmas cores dos cards) */
    .pm-bar-atend {
      width:100%; height:0;
      background:linear-gradient(180deg,
        color-mix(in srgb, var(--m-atendidas, #22C55E) 88%, #fff),
        var(--m-atendidas, #22C55E));
      transition:height .6s cubic-bezier(.2,.7,.3,1) var(--dur-1);
    }
    /* PRECS (1.1.57) — O DIA COM ZERO LIGAÇÕES PASSA A EXISTIR NA TELA.
       Ele não desenhava nada: `hTotal` caía em 0 e o número virava string vazia, então sábado
       sem ligação ficava indistinguível de sábado sem dado. E a assimetria estava escrita no
       próprio código — o dia COM valor tem piso de 6% (`Math.max(6, …)`) justamente para nunca
       sumir, e o zero era o único caso sem tratamento equivalente.
       O toco é de 2px e usa o cinza apagado, NÃO a cor da métrica: ele diz "medi e deu zero",
       que é diferente de "tem pouco". Barra de 2px na cor do âmbar seria lida como um valor
       baixo — o oposto do que ela informa. */
    .pm-dia-zero .pm-bar {
      height: 2px;
      background: color-mix(in srgb, var(--text-muted) 55%, transparent);
      border-radius: var(--radius-sm);
    }
    .pm-dia-zero .pm-dia-val { opacity: .35; }
    /* PRECS (1.1.57) — A SELEÇÃO PASSA A MARCAR A COLUNA, e não só a barra.
       As três marcas antigas eram `outline` na barra, `brightness` no verde e o rótulo dourado.
       Com a barra em altura zero, as duas primeiras não tinham o que desenhar: num dia sem
       ligações a seleção sobrava só no rótulo — e o gráfico é clicável justamente para
       detalhar QUALQUER dia, inclusive os vazios. A coluna inteira sempre existe. */
    .pm-dia.sel { background: color-mix(in srgb, var(--precs-gold) 14%, transparent); }
    .pm-dia.sel .pm-bar { outline:2px solid var(--m-ligacoes, #E8A33D); outline-offset:1px; }
    .pm-dia.sel .pm-bar-atend { filter:brightness(1.08); }
    .pm-dia:hover .pm-bar { filter:brightness(1.12); }
    .pm-dia-rot { font-size:var(--fs-1); color:var(--text-muted); }
    .pm-dia.sel .pm-dia-rot { color:var(--precs-gold, #B49552); font-weight:700; }

    .pm-camp {
      padding:var(--space-2) var(--space-2) var(--space-2); border-radius:7px; margin-bottom:var(--space-1);
      background:var(--bg-tertiary); border:1px solid var(--border-color);
    }
    .pm-camp-topo { display:flex; justify-content:space-between; gap:var(--space-2); margin-bottom:var(--space-1); }
    .pm-camp-nome {
      font-size:var(--fs-4); color:var(--text-secondary);
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    }
    .pm-camp-qtd { font-size:var(--fs-4); font-weight:700; font-family:'JetBrains Mono',monospace; color:var(--text-primary); }
    .pm-camp-trilha { height:4px; border-radius:2px; background:color-mix(in srgb, var(--precs-gold, #B49552) 14%, transparent); overflow:hidden; }
    .pm-camp-fill { height:100%; width:0; border-radius:2px; background:var(--precs-gold, #B49552); transition:width .6s cubic-bezier(.2,.7,.3,1); }

    /* distribuição por status do dia (drill-down): pílulas com ponto colorido */
    .pm-status-wrap { display:flex; flex-wrap:wrap; gap:var(--space-1); margin-top:var(--space-0); }
    .pm-status {
      display:flex; align-items:center; gap:var(--space-2); font-size:var(--fs-3);
      padding:var(--space-1) var(--space-2); border-radius:var(--radius-pill);
      background:var(--bg-tertiary); border:1px solid var(--border-color);
      color:var(--text-secondary);
    }
    .pm-status-dot { width:7px; height:7px; border-radius:50%; background:var(--pm-sc, var(--text-muted)); flex:none; }
    .pm-status-qtd { font-weight:700; font-family:'JetBrains Mono',monospace; color:var(--text-primary); }

    @media (prefers-reduced-motion: reduce) {
      #precs-meudia-card, .pm-fade { animation:none; }
      .precs-qual-pop, .precs-confirm-modal, .precs-stats-modal, .precs-frases-modal { animation:none; }
      .pm-bar, .pm-bar-atend, .pm-camp-fill, .pm-stat { transition:none; }
    }

    /* ====================================================================
       PRECS (1.1.11) — OUVIR A GRAVAÇÃO DA LIGAÇÃO
       --------------------------------------------------------------------
       Duas ações no card de atendimento passaram a competir pela mesma
       linha ("Ouvir" e "Ver negócio"), e elas herdavam `margin-left:2.15rem`
       cada uma — o recuo que alinha o conteúdo com o avatar. Empilhadas,
       cada uma pagava o recuo de novo. O recuo subiu para o contêiner e as
       duas voltaram a ser botões comuns dentro dele.
       ==================================================================== */
    .precs-sup-atend-acoes {
      display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2);
      margin: var(--space-2) 0 0 2.15rem;
    }
    .precs-sup-atend-acoes > .precs-sup-atend-btn,
    .precs-sup-atend-acoes > .precs-sup-atend-semdeal { margin: 0; }
    .precs-sup-ouvir {
      display: inline-flex; align-items: center; gap: var(--space-1); flex: none;
      border-color: color-mix(in srgb, var(--precs-gold, #B49552) 46%, var(--border-color));
    }
    .precs-sup-ouvir svg { flex: 0 0 auto; }
    /* O triângulo de play é opticamente leve à esquerda; 1px de folga o
       centra dentro da pílula sem mexer no texto. */
    .precs-sup-ouvir svg polygon { transform: translateX(.5px); }

    .precs-audio-overlay {
      display: none; position: fixed; inset: 0; z-index: 10050;
      align-items: center; justify-content: center; padding: 5vh 5vw;
      background: color-mix(in srgb, #000 62%, transparent);
      backdrop-filter: blur(2px);
      opacity: 0; transition: opacity var(--dur-2, .2s) var(--ease-1, cubic-bezier(.2,.7,.3,1));
    }
    .precs-audio-overlay.open { display: flex; opacity: 1; }
    .precs-audio-overlay.closing { opacity: 0; }
    .precs-audio-modal {
      width: min(460px, 94vw); background: var(--bg-secondary);
      border: 1px solid var(--border-color); border-radius: var(--radius-card);
      box-shadow: var(--shadow-lg); padding: var(--space-5) var(--space-5) var(--space-4);
      animation: pmEntra .22s cubic-bezier(.2,.7,.3,1);
    }
    .precs-audio-tit {
      font-family: 'Montserrat','Poppins',sans-serif; font-size: .74rem; font-weight: 700;
      text-transform: uppercase; letter-spacing: var(--ls-caps, .04em);
      color: var(--precs-gold, #B49552); margin-bottom: var(--space-3);
    }
    .precs-audio-corpo { font-size: .8rem; color: var(--text-secondary); min-height: 54px;
      display: flex; align-items: center; }
    /* O player ocupa a largura toda: os controles nativos ficam apertados em
       caixa estreita, e arrastar a linha do tempo é o gesto principal aqui. */
    .precs-audio-corpo audio { width: 100%; }
    .precs-audio-erro { color: var(--accent-red); font-size: .78rem; line-height: 1.45; }

    @media (prefers-reduced-motion: reduce) {
      .precs-audio-overlay { transition: none; }
      .precs-audio-modal { animation: none; }
    }

    /* (1.1.11) "Ouvir" na linha do histórico do mergulho. Redondo e pequeno de propósito: um
       botão de texto aqui dobraria a altura de mil linhas.
       (1.1.15) Ele saiu da pilha da direita e virou a PRIMEIRA coluna da linha, com largura
       fixa: a ação fica sempre no mesmo lugar, em toda linha, e é o que o olho encontra
       primeiro. O `margin-top` de quando ele era o terceiro item de uma pilha saiu junto. */
    .st-hist-ouvir {
      width: 20px; height: 20px; flex: 0 0 auto; cursor: pointer;
      display: inline-flex; align-items: center; justify-content: center;
      border-radius: 50%; background: transparent;
      border: 1px solid color-mix(in srgb, var(--precs-gold, #B49552) 42%, var(--border-color));
      color: var(--precs-gold, #B49552);
      transition: background var(--dur-1, .15s) var(--ease-1, cubic-bezier(.2,.7,.3,1)),
                  border-color var(--dur-1, .15s) var(--ease-1, cubic-bezier(.2,.7,.3,1));
    }
    .st-hist-ouvir:hover {
      border-color: var(--precs-gold, #B49552);
      background: color-mix(in srgb, var(--precs-gold, #B49552) 14%, transparent);
    }
    .st-hist-ouvir svg { transform: translateX(.5px); }
    @media (prefers-reduced-motion: reduce) { .st-hist-ouvir { transition: none; } }

    /* (1.1.11) COLUNA DE DIA SEM DADO no grafico por dia. Acontece quando o agregado da 3C
       nao devolve o operador naquela data (ele pode nao aparecer quando esta fora). A coluna
       nao pode virar zero, que afirmaria que a pessoa nao trabalhou: fica tracejada, com "?"
       no lugar do numero, e sai da conta da media. */
    .st-bar-semdado .st-bar-track{
      border:1px dashed var(--border-color); border-radius:var(--radius-sm);
      background:transparent;
    }
    .st-bar-semdado .st-bar{ display:none; }
    .st-bar-semdado .st-bar-val{ color:var(--text-muted); font-weight:700; }
    .st-bar-semdado .st-bar-lbl{ color:var(--text-muted); }

    /* ====================================================================
       PRECS (1.1.11) — ESCUTA AO VIVO (SPY)
       ==================================================================== */
    /* (1.1.15 · 8ª volta) O BOTÃO DE ESCUTAR VIROU BOTÃO DE VERDADE.

       O que estava aqui antes: um chip de 0,62rem sem rótulo, na sub-linha do ramal, com a
       justificativa de que "entrar na linha de uma conversa é a ação mais invasiva desta tela,
       e um botão chamativo convida ao clique acidental".

       A INTENÇÃO ESTAVA CERTA E O REMÉDIO ERRADO, e o relato de 19/08 foi direto ao ponto
       ("pequeno e sem rótulo demais"). Discrição virou ilegibilidade: o chip ficava ao lado do
       selo "escutado", que tem o MESMO tamanho e a MESMA forma de pílula e não é clicável, e o
       supervisor tinha que caçar qual dos dois era o botão. O que protege de clique acidental é
       a CONFIRMAÇÃO, que já existe e continua lá — não o tamanho do alvo. Alvo pequeno não
       previne erro: ele produz erro, porque quem mira em 11px acerta o vizinho.

       Agora tem rótulo ("Escutar"), altura de botão e mora na coluna de Ações. Continua
       secundário na COR (contorno, sem preenchimento), que é onde a discrição cabe. */
    .precs-sup-spy, .precs-sup-sair{
      display:inline-flex; align-items:center; gap:5px; cursor:pointer; flex:0 0 auto;
      font-family:'Manrope','Poppins',sans-serif; font-size:.68rem; font-weight:700;
      white-space:nowrap;
      padding:var(--space-1) var(--space-3); border-radius:var(--radius-pill);
      background:transparent; color:var(--text-secondary);
      border:1px solid var(--border-color);
      transition:color var(--dur-1) var(--ease-1), border-color var(--dur-1) var(--ease-1),
                 background-color var(--dur-1) var(--ease-1), opacity var(--dur-1) var(--ease-1);
      /* (1.1.22 · 2ª volta) MODO FANTASMA: quinze pares de "Escutar/Deslogar" eram o elemento
         mais pesado da tabela, e quase nunca são o que o olho procura. Em repouso ficam
         esmaecidos; acendem no hover da linha e no foco por teclado. Quem NÃO esmaece: o botão
         da escuta ATIVA (ele é o estado "na linha", regra logo abaixo) e o `.espera`, que tem
         opacidade própria com significado ("já há escuta em outra linha"). */
      opacity:.55;
    }
    .precs-sup-linha:hover .precs-sup-spy:not(.espera),
    .precs-sup-linha:focus-within .precs-sup-spy:not(.espera),
    .precs-sup-linha:hover .precs-sup-sair:not(:disabled),
    .precs-sup-linha:focus-within .precs-sup-sair:not(:disabled){ opacity:1; }
    .precs-sup-spy:hover{
      color:var(--precs-gold, #B49552);
      border-color:color-mix(in srgb, var(--precs-gold, #B49552) 50%, var(--border-color));
    }
    .precs-sup-spy.ativo{
      color:var(--on-accent-red, #fff); background:var(--accent-red);
      border-color:var(--accent-red);
      /* Escuta ATIVA nunca esmaece: o botão vermelho é o estado "na linha", não uma ação. */
      opacity:1;
    }
    /* EM ESPERA: a supervisão já está em outra linha. Não usa `disabled` de propósito — o botão
       continua clicável para poder EXPLICAR o que fazer, e um `disabled` mudo obrigaria a
       descobrir o motivo por tentativa. Fica esmaecido e com o cursor de "não é agora". */
    .precs-sup-spy.espera{
      opacity:.42; cursor:help;
    }
    .precs-sup-spy.espera:hover{ opacity:.7; }

    /* DESLOGAR é vermelho só no hover: em repouso ele não pode competir com o alarme de ocioso
       nem com o selo de escuta, que são INFORMAÇÃO. A cor entra quando o cursor está nele, que é
       o instante em que a pessoa precisa saber que aquilo derruba alguém. */
    .precs-sup-sair:hover:not(:disabled){
      color:var(--accent-red, #ef4444);
      border-color:color-mix(in srgb, var(--accent-red, #ef4444) 55%, var(--border-color));
      background:color-mix(in srgb, var(--accent-red, #ef4444) 10%, transparent);
    }
    /* TRAVADO (em chamada ou TPA): `disabled` de verdade, porque aqui não há nada a explicar
       por clique — o balão explica no hover, e `cursor:help` diz que a explicação existe.
       Diferente do botão de escutar em "espera", que continua clicável de propósito: lá o clique
       ENSINA o caminho, aqui ele destruiria trabalho. */
    .precs-sup-sair:disabled{ opacity:.35; cursor:help; }

    /* A BARRA fica fixa no rodapé, fora dos cards. O card do operador é repintado a cada 5s
       pelo ciclo do roster, e a barra é o único elemento cujo estado o supervisor acompanha
       segundo a segundo: dentro do card ela piscaria junto com o resto. */
    .precs-spy-barra{
      position:fixed; left:50%; bottom:var(--space-4); z-index:10040;
      transform:translateX(-50%) translateY(8px); opacity:0;
      display:flex; align-items:center; gap:var(--space-3);
      max-width:min(720px, 94vw);
      padding:var(--space-3) var(--space-4);
      border-radius:var(--radius-pill);
      background:var(--bg-secondary); border:1px solid var(--border-color);
      box-shadow:var(--shadow-lg);
      transition:opacity var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-1);
    }
    .precs-spy-barra.open{ opacity:1; transform:translateX(-50%) translateY(0); }
    /* A barra entra deslizando de baixo, com uma curva que assenta: o fade seco anterior
       fazia ela simplesmente APARECER, e num elemento que anuncia "voce esta dentro de uma
       conversa" o movimento e o que chama o olho para ela. */
    .precs-spy-barra{ transform:translateX(-50%) translateY(14px) scale(.98); }
    .precs-spy-barra.open{ transform:translateX(-50%) translateY(0) scale(1);
      transition:opacity var(--dur-2) var(--ease-1), transform .26s cubic-bezier(.2,.9,.3,1); }
    /* O contorno segue o ESTADO do operador; o ponto vermelho segue o ÁUDIO. Duas informações
       diferentes em dois lugares, em vez de a barra ser sempre vermelha e a mudança de estado
       passar sem nada acontecer na tela. */
    .precs-spy-barra.escutando{
      /* (1.1.53) A MISTURA SUBIU DE 60% PARA 85%. A 60% o verde de "Falando" chegava à tela como
         rgb(43,145,115) — um verde-acinzentado que, sobre a barra escura, ninguém lê como verde
         (medido com sonda). A cor do estado existe para ser reconhecida de relance; 60% era
         discrição no lugar errado, e o Pedro apontou justamente isso. A borda esquerda de 4px já
         era cor cheia, e agora as duas concordam. */
      border-color:color-mix(in srgb, var(--spy-cor, var(--accent-red)) 85%, var(--border-color));
      border-left:4px solid var(--spy-cor, var(--accent-red));
      background-image:linear-gradient(to right,
        color-mix(in srgb, var(--spy-cor, transparent) 10%, transparent), transparent 45%);
    }
    .precs-spy-estado{
      font-family:'Montserrat','Poppins',sans-serif; font-size:.62rem; font-weight:700;
      text-transform:uppercase; letter-spacing:var(--ls-caps);
      color:var(--text-secondary);
    }
    /* ======================================================================
       (1.1.67) O PAR DE MODOS: "falar com ninguém" e "falar com o operador".

       A 3C oferece três na barra dela ("Só ouvir · Agente · Todos"); aqui são DOIS, e o terceiro
       não existe no código — ele é barge, a voz da supervisão entrando na conversa com o credor.

       A FORMA É DE SEGMENTO e não de par de botões soltos: os dois são exclusivos e um está
       sempre ligado, que é exatamente o que um segmento diz e um par de botões não.
       ====================================================================== */
    /* A FORMA DO BOTÃO É A QUE A BARRA JÁ TEM: os dois carregam `precs-spy-lead` junto, então
       fonte, padding, raio e borda vêm de lá. Escrever tamanho e espaçamento próprios aqui seria
       criar um terceiro botão nesta faixa e subir dois contadores da catraca por preguiça. */
    .precs-spy-modo{
      flex:0 0 auto; display:inline-flex; align-items:center; gap:var(--space-1);
      margin-left:auto;
      border-radius:var(--radius-pill);
      background:color-mix(in srgb, var(--bg-tertiary) 70%, transparent);
      border:1px solid var(--border-color);
    }
    /* O rótulo reusa a pastilha de estado da própria barra (`precs-spy-estado` no markup), então
       só a margem é dele. */
    .precs-spy-modo > i{ margin-left:var(--space-2); }
    .precs-spy-modo .precs-spy-lead{
      margin-left:0; min-width:0; border-color:transparent; color:var(--text-secondary);
      transition:background var(--dur-1) var(--ease-1), color var(--dur-1) var(--ease-1);
    }
    .precs-spy-modo .precs-spy-lead:hover{ color:var(--text-primary); }
    .precs-spy-modo .precs-spy-lead.ativo{
      background:var(--bg-secondary); color:var(--text-primary);
      border-color:var(--border-color);
    }
    /* O SEGUNDO MODO ACENDE EM LARANJA, e a cor não é decoração: laranja é o estado MANUAL no
       painel inteiro, ou seja "alguém está com a mão nisto agora". É o mesmo vocabulário, e é o
       que separa "estou ouvindo" de "estou falando" num relance. */
    .precs-spy-barra.falando .precs-spy-modo .precs-spy-lead.ativo{
      background:var(--st-manual); color:var(--bg-primary); border-color:var(--st-manual);
    }
    /* E A BARRA INTEIRA SE MARCA. Um modo que só aparece no botão aceso seria fácil de não ver
       numa faixa lida de relance, e este é o estado em que a pessoa do outro lado ouve a sala. */
    .precs-spy-barra.falando{
      border-color:var(--st-manual);
      border-left:4px solid var(--st-manual);
      background-image:linear-gradient(to right,
        color-mix(in srgb, var(--st-manual) 14%, transparent), transparent 55%);
    }
    /* No aparelho a barra já quebra em duas faixas: o segmento perde a âncora à direita e entra
       na linha de baixo junto dos outros botões. */
    html[data-aparelho] .precs-spy-modo{ margin-left:0; }

    .precs-spy-lead{
      flex:0 0 auto; cursor:pointer; margin-left:auto;
      font-family:'Manrope','Poppins',sans-serif; font-size:.72rem; font-weight:700;
      padding:var(--space-1) var(--space-3); border-radius:var(--radius-pill);
      background:transparent; color:var(--precs-gold, #B49552);
      border:1px solid var(--border-color);
      /* (1.1.15) LARGURA ESTÁVEL. O rótulo alterna entre "Ver a tela" e "Ocultar a tela", e sem
         piso a barra inteira crescia e encolhia a cada abertura do espelho — a barra é fixa na
         base da tela e o movimento chama atenção para o lugar errado (relatado em 19/08/2026).
         O piso é a largura do rótulo mais longo; centralizar o texto mantém os dois no lugar. */
      min-width:8.5rem; text-align:center;
    }
    .precs-spy-lead:hover{ border-color:var(--precs-gold, #B49552); }
    /* Com dois botões, só o primeiro empurra: o `margin-left:auto` do "Sair da linha" some. */
    .precs-spy-lead ~ .precs-spy-sair{ margin-left:0; }
    .precs-spy-barra.erro{
      border-color:color-mix(in srgb, var(--accent-red) 55%, var(--border-color));
    }
    /* O ponto PULSA só enquanto há áudio de verdade. Nos estados intermediários
       ("pedindo", "aguardando") ele fica parado: um pulso vermelho é lido como "ao vivo", e
       usá-lo antes de o socket confirmar seria a tela afirmando o que ainda não sabe. */
    .precs-spy-ponto{
      width:9px; height:9px; flex:0 0 auto; aspect-ratio:1; border-radius:50%;
      background:var(--text-muted);
    }
    .precs-spy-barra.escutando .precs-spy-ponto{
      background:var(--accent-red); animation:precsSpyPulso 1.4s ease-in-out infinite;
    }
    .precs-spy-barra.erro .precs-spy-ponto{ background:var(--accent-red); }
    @keyframes precsSpyPulso{
      0%,100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--accent-red) 60%, transparent); }
      50%    { box-shadow:0 0 0 6px color-mix(in srgb, var(--accent-red) 0%, transparent); }
    }
    .precs-spy-txt{
      display:flex; flex-direction:column; gap:2px; min-width:0;
      font-size:.8rem; color:var(--text-primary);
    }
    .precs-spy-txt b{ font-weight:700; }
    .precs-spy-erro{ font-size:.74rem; color:var(--accent-red-text); line-height:1.4; }
    /* O aviso de que a escuta não é anunciada fica NA BARRA, e não só no modal de
       confirmação: o modal aparece uma vez por sessão, e a barra fica na frente do
       supervisor durante toda a escuta. */
    .precs-spy-nota{ font-size:.68rem; color:var(--text-muted); }
    .precs-spy-sair{
      flex:0 0 auto; cursor:pointer; margin-left:auto;
      font-family:'Manrope','Poppins',sans-serif; font-size:.72rem; font-weight:700;
      padding:var(--space-1) var(--space-3); border-radius:var(--radius-pill);
      background:var(--accent-red); color:var(--on-accent-red, #fff);
      border:1px solid var(--accent-red);
    }
    .precs-spy-sair:hover{ filter:brightness(1.08); }
    @media (prefers-reduced-motion: reduce){
      .precs-spy-barra{ transition:none; }
      .precs-spy-barra.escutando .precs-spy-ponto{ animation:none; }
      .precs-sup-spy{ transition:none; }
    }

    /* ====================================================================
       PRECS (1.1.49) — A BARRA DA ESCUTA EM APARELHO: DUAS FAIXAS
       --------------------------------------------------------------------
       O print de 02/09/2026 mostrou o nome do operador saindo uma palavra por linha e
       "aguardando" transbordando por cima da borda arredondada. A conta que explica isso é
       exata, e vale a pena registrar porque ela é a régua desta correção — em 360px de tela:

           94vw ................  338px
           − padding lateral ...   32px  (var(--space-4) x2)
           − 3 gaps ............   36px  (var(--space-3))
           − o ponto ...........    9px  (flex:0 0 auto)
           − .precs-spy-lead ...  136px  (min-width:8.5rem, INEGOCIÁVEL na regra de cima)
           − .precs-spy-sair ...  ~99px  ("Sair da linha", flex:0 0 auto)
           = para o texto ......   26px

       26px. E `.precs-spy-txt` é o ÚNICO item encolhível da barra (é quem tem `min-width:0`),
       então toda a falta de espaço cai nele. Sem `flex-wrap`, sem `word-break` e sem
       `overflow:hidden`, o resultado é o do print.

       A correção NÃO é encolher mais o texto: é parar de disputar uma linha só. Em aparelho a
       barra vira duas faixas — o texto (nome, estado, cronômetro) em cima ocupando a largura
       inteira, os botões embaixo. O `min-width` de 136px do lead, que existe para a barra não
       pulsar de largura ao alternar "Ver a tela"/"Ocultar a tela" (1.1.15), perde a razão de
       ser quando os botões têm a faixa só para eles: aqui ele cede.

       `--space-2` no gap: com duas faixas os 12px de `--space-3` viravam altura, e altura é o
       que falta em paisagem de celular (390px). */
    html[data-aparelho] .precs-spy-barra{
      flex-wrap:wrap; gap:var(--space-2);
      /* A barra colada nas laterais: em 360px, cada 1% de margem é pixel que falta ao texto. */
      max-width:none; left:var(--space-2); right:var(--space-2);
      /* `--radius-card` e não a pílula: com duas faixas a barra deixou de ser uma faixa fina, e
         999px de raio num bloco de 90px de altura viraria um comprimido. */
      transform:translateY(8px); border-radius:var(--radius-card);
    }
    html[data-aparelho] .precs-spy-barra.open{ transform:translateY(0); }
    /* A faixa de cima: o ponto e o texto. `flex-basis:100%` no texto empurra os botões para a
       linha seguinte sem precisar de um `<div>` novo no HTML — a barra é montada por
       concatenação de string em `precsSpyPintar`, e mexer na estrutura ali é mais arriscado
       que resolver no CSS. */
    html[data-aparelho] .precs-spy-txt{ flex:1 1 100%; min-width:0; }
    html[data-aparelho] .precs-spy-txt b{
      /* O nome é o que estourava. `overflow-wrap:anywhere` só age quando não há mais onde
         quebrar, então nome normal continua quebrando por espaço. */
      overflow-wrap:anywhere;
    }
    /* A faixa de baixo: os botões dividem a largura, e o lead solta o piso. `margin-left:0`
       porque o `margin-left:auto` do lead (regra de cima) empurraria o par para a direita
       deixando um vazio à esquerda — com faixa própria, os dois começam na margem. */
    html[data-aparelho] .precs-spy-lead{
      min-width:0; flex:1 1 auto; margin-left:0;
    }
    html[data-aparelho] .precs-spy-sair{ flex:0 1 auto; margin-left:auto; }
    /* (1.1.49) A SUPERFÍCIE QUE CONTRASTA COM O VÉU, e só enquanto o espelho está aberto.
       O fundo branco da barra no print está CORRETO: `--bg-secondary` é #FFFFFF no PRECS
       Claro. Ele parece errado porque, com o espelho aberto, a barra flutua sobre o véu de
       `rgba(0,0,0,.6)` — branco puro sobre quase-preto, sem nada entre os dois. Fora do
       espelho a mesma barra fica sobre o fundo da supervisão e lê bem, então a mudança não
       pode ser na cor da barra: é neste estado, e volta sozinha quando o espelho fecha.
       A borda clareia junto com o fundo escurecendo: são os dois lados da mesma separação, e
       resolvem sem um anel de sombra novo — a catraca de estilo (`teste-estilo-tokens`) conta
       geometrias de `box-shadow` cruas e exige IGUALDADE, e ela está certa em exigir. */
    html[data-espelho="aberto"] .precs-spy-barra{
      background:color-mix(in srgb, var(--bg-secondary) 88%, #000);
    }
    /* (1.1.53) O CLAREAMENTO DA BORDA NÃO ALCANÇA A BARRA EM ESCUTA, e esta linha é o conserto de
       uma regressão que eu mesmo introduzi na 1.1.49.
       ------------------------------------------------------------------------------
       RELATO DO PEDRO, 02/09/2026: "a Julia estava em ligação mas a barra de baixo estava cinza
       em volta; se está em chamada ativa deveria ficar verde, né?"
       Ele está certo, e a causa era a regra acima: ela declarava `border-color` para contrastar
       com o véu do espelho, tem especificidade (0,2,1) e vem DEPOIS de
       `.precs-spy-barra.escutando` (0,2,0) — então vencia e pintava os QUATRO lados de cinza,
       inclusive a borda esquerda de 4px que carrega a cor do estado do operador desde a 1.1.11.
       Medido: `color(srgb 0.5035 0.5035 0.5129)` nos quatro lados, com o operador em Falando.
       E o pior é QUANDO isso acontecia: só com o espelho aberto — ou seja, exatamente durante a
       escuta, que é o único momento em que a cor do estado tem algo a dizer.
       O fundo escurecido já resolve o contraste com o véu, que era o objetivo. A borda clareia
       apenas quando NÃO há escuta em curso. */
    html[data-espelho="aberto"] .precs-spy-barra:not(.escutando){
      border-color:color-mix(in srgb, var(--border-color) 60%, #fff);
    }

    /* ====================================================================
       PRECS (1.1.11) — CARD DE CAMPANHA COM ROSCA, DELTA E LISTAS
       --------------------------------------------------------------------
       Estas regras vêm DEPOIS das do bloco original de .precs-campsaldo e
       sobrescrevem o que mudou de forma. Ficam separadas de propósito: o
       bloco de cima é a versão que nasceu do plano, esta é a que nasceu do
       dado real de 18/08/2026, e manter as duas legíveis lado a lado é o
       que deixa a próxima pessoa entender por que mudou.
       ==================================================================== */
    /* O card cresceu, então a coluna mínima cresce com ele: a rosca de 132px mais o número
       grande ao lado não cabem em 340px sem quebrar a linha do saldo. */
    /* `auto-fill` e não `auto-fit`, e a diferença aparece com POUCOS cards: `auto-fit` colapsa as
       trilhas vazias e a única campanha ao vivo esticava de ponta a ponta da tela — "não precisa
       ficar tudão", 24/08/2026. Com `auto-fill` as trilhas continuam lá, e um card sozinho fica do
       mesmo tamanho dos três da seção de baixo. Na parede isto não vale: lá a grade é outra, e
       preencher a tela É o requisito. */
    .precs-campsaldo-grade{
      grid-template-columns:repeat(auto-fill, minmax(420px, 1fr));
    }
    .precs-campsaldo{ max-width:none; }

    /* CORPO: rosca à esquerda, números à direita. Em card estreito a rosca vai para cima e o
       texto embaixo, em vez de as duas colunas se estrangularem. */
    .precs-campsaldo-corpo{
      display:flex; align-items:center; gap:var(--space-5); flex-wrap:wrap;
    }
    .precs-campsaldo-lado{ flex:1 1 180px; min-width:0; }

    .precs-campsaldo-rosca{
      position:relative; flex:0 0 auto;
      width:132px; height:132px;
      display:flex; align-items:center; justify-content:center;
    }
    .precs-campsaldo-rosca svg{
      position:absolute; inset:0; width:100%; height:100%;
      /* -90deg põe o começo do arco no topo, que é a convenção de qualquer gráfico de
         progresso circular. Sem isso ele começaria às 3 horas. */
      transform:rotate(-90deg);
    }
    .cs-trilha{
      fill:none; stroke:var(--border-color); stroke-width:9;
    }
    .cs-arco{
      fill:none; stroke-width:9; stroke-linecap:round;
      stroke:var(--accent-green);
      /* A transição é do dasharray: o arco CRESCE até o novo valor em vez de saltar, que é o
         que faz a leitura de 30 em 30 segundos parecer viva. */
      transition:stroke-dasharray var(--dur-3, .3s) cubic-bezier(.2,.7,.3,1), stroke var(--dur-2) ease;
    }
    .precs-campsaldo-atencao .cs-arco{ stroke:var(--accent-orange); }
    .precs-campsaldo-critico .cs-arco{ stroke:var(--accent-red); }
    /* Campanha parada: o arco perde a cor de "andando", igual à barra fez. */
    .precs-campsaldo-parada .cs-arco,
    .precs-campsaldo:has(.precs-campsaldo-estado-fora) .cs-arco{ stroke:var(--text-muted); }
    .precs-campsaldo-rosca-txt{
      position:relative; z-index:1; line-height:1;
      font-family:'JetBrains Mono',monospace; font-size:1.7rem; font-weight:700;
      color:var(--text-primary);
    }
    .precs-campsaldo-rosca-txt i{ font-style:normal; font-size:.9rem; opacity:.65; }
    /* `bottom` em PORCENTAGEM, e não os 34px de antes: eles acertavam a rosca de 132px e só ela.
       Na parede a mesma rosca vai a 260px, e o rótulo saía de dentro do círculo e caía em cima do
       arco. 26% é o mesmo lugar para qualquer diâmetro (34/132), então a tela do supervisor não
       muda um pixel. */
    .precs-campsaldo-rosca-lbl{
      position:absolute; z-index:1; bottom:26%; left:0; right:0; text-align:center;
      font-size:.56rem; font-weight:700; text-transform:uppercase;
      letter-spacing:var(--ls-caps); color:var(--text-muted);
    }
    .precs-campsaldo-rosca.vazia .precs-campsaldo-rosca-txt{ color:var(--text-muted); }

    /* O SALDO: continua o protagonista, agora ao lado da rosca. */
    .precs-campsaldo-num{
      display:flex; flex-direction:column; gap:2px; margin:0 0 var(--space-2);
    }
    .precs-campsaldo-num b{
      font-family:'JetBrains Mono',monospace; font-size:2rem; font-weight:700; line-height:1;
      color:var(--text-primary);
      transition:color var(--dur-2) ease;
    }
    .precs-campsaldo-num span{
      font-size:.6rem; font-weight:700; text-transform:uppercase;
      letter-spacing:var(--ls-caps); color:var(--text-muted);
    }
    /* A DIREÇÃO DA MUDANÇA, por um instante. Cair é o esperado (consumo); subir é lista nova
       entrando. Sem a cor, o supervisor vê o número mexer e não sabe para onde foi. */
    .precs-campsaldo.num-caiu .precs-campsaldo-num b{ color:var(--accent-green-text); }
    .precs-campsaldo.num-subiu .precs-campsaldo-num b{ color:var(--precs-gold, #B49552); }

    /* O DELTA: medição entre as duas últimas leituras, e é o que responde "está andando?". */
    .precs-campsaldo-delta{
      display:inline-flex; align-items:center; gap:var(--space-1);
      margin-top:var(--space-2);
      font-family:'JetBrains Mono',monospace; font-size:.7rem; font-weight:700;
      padding:2px var(--space-2); border-radius:var(--radius-pill);
      border:1px solid var(--border-color); background:var(--bg-primary);
      color:var(--text-muted);
    }
    .precs-campsaldo-delta.desce{
      color:var(--accent-green-text);
      border-color:color-mix(in srgb, var(--accent-green) 42%, var(--border-color));
    }
    .precs-campsaldo-delta.esperando{ font-style:italic; font-weight:500; }

    /* AS LISTAS saíram do rodapé em letra miúda. No dado real de 18/08/2026 uma campanha tinha
       653 das 906 listas zeradas — 72% do mailing morto ao lado de um saldo de 391.967. Essa
       proporção é o que pede ação, e estava em 0,66rem no canto de baixo. */
    .precs-campsaldo-listas{
      /* `auto` e não uma medida fixa: é ele que absorve a sobra de altura quando o card é mais
         curto que a fileira, mantendo a barra de listas colada no rodapé de todos. */
      margin-top:auto; padding-top:var(--space-3);
      border-top:1px solid var(--border-color);
    }
    /* O vão mínimo continua existindo quando o card JÁ é o mais alto da fileira. */
    .precs-campsaldo-corpo{ margin-bottom:var(--space-4); }
    .precs-campsaldo-listas-topo{
      display:flex; justify-content:space-between; align-items:baseline;
      gap:var(--space-2); margin-bottom:var(--space-2);
      font-size:.72rem; color:var(--text-secondary);
    }
    .precs-campsaldo-ok{ color:var(--accent-green-text); font-weight:600; }
    .precs-campsaldo-listas-barra{
      height:6px; border-radius:3px; overflow:hidden;
      background:color-mix(in srgb, var(--accent-green) 22%, var(--bg-primary));
    }
    .precs-campsaldo-listas-fill{
      height:100%; width:0; border-radius:3px; background:var(--accent-red);
      transition:width var(--dur-3, .3s) cubic-bezier(.2,.7,.3,1);
    }
    .precs-campsaldo-listas.vazio{
      font-size:.74rem; color:var(--text-muted);
      margin-top:var(--space-4); padding-top:var(--space-3);
      border-top:1px solid var(--border-color);
    }

    @media (prefers-reduced-motion: reduce){
      .cs-arco, .precs-campsaldo-listas-fill, .precs-campsaldo-num b{ transition:none; }
    }

    /* (1.1.11) A PILULA DE ESTADO. Na tela da propria 3C o estado e o elemento mais visivel da
       linha, e e ele que a supervisao varre procurando quem esta parado. Aqui era um ponto de
       7px com o texto em --text-secondary, a mesma cor do resto da linha: nao dava para
       escanear a tabela sem parar e ler. */
    .precs-sup-pill{
      display:inline-flex; align-items:center; gap:5px; flex:0 0 auto;
      /* (1.1.22 · 2ª volta) LARGURA PADRONIZADA, dimensionada pelo rótulo mais longo em uso
         ("Intervalo"): com as pílulas uniformes, os cronômetros "· 00:12" começam todos no
         mesmo x e a coluna de status vira uma régua — o olho varre os tempos de cima a baixo
         sem ziguezaguear atrás do fim de cada pílula. Em `em`, para acompanhar o zoom. */
      min-width:7.2em;
      padding:2px var(--space-2) 2px 6px; border-radius:var(--radius-pill);
      background:var(--pill-bg, var(--st-idle)); color:var(--pill-txt, #0F1115);
      /* (1.1.17) Com aspas, como as outras 40 declarações do projeto: era a única sem, e sem
         aspas um nome de família com espaço (o próximo que entrar na lista) não resolve. */
      font-family:'Manrope','Poppins',sans-serif; font-size:.68rem; font-weight:700;
      letter-spacing:.01em; white-space:nowrap; line-height:1.5;
      /* (1.1.22) `color` junto: com a linha reconciliada a pílula finalmente interpola (nó
         vivo tem valor de partida), e fundo animando com o texto saltando leria como defeito. */
      transition:background var(--dur-2) ease, color var(--dur-2) ease;
    }
    /* O PONTO CONTINUA, e nao e redundancia: ele e a unica pista que sobrevive a quem nao
       distingue as cores entre si — junto do texto, da a leitura por forma e por rotulo. */
    .precs-sup-pill .dot{
      width:6px; height:6px; flex:0 0 auto; aspect-ratio:1; border-radius:50%;
      background:currentColor; opacity:.55;
    }
    /* O CRONOMETRO FORA DA PILULA. precsSupTick reescreve ele a cada segundo, e dentro de um
       fundo colorido a pilula mudaria de largura a cada tique, empurrando a coluna vizinha. */
    /* ======================================================================
       (1.1.67) O TEMPO DO STATUS CRESCEU, E VIROU MONOESPAÇADO.

       PEDIDO DO SUPERVISOR, 12/09/2026: "o número de tempo que fica atualizando está bem pequeno,
       não dá para ver direito... temos muito espaço e os números estão tão pequenos".

       Ele estava em `.72rem` (11,5px) e em `--text-muted`, ou seja o menor e o mais apagado de
       toda a linha — sendo que é O ÚNICO NÚMERO QUE SE MOVE. A pílula ao lado tem `.68rem` e o
       total da coluna TEMPO tem `.74rem`: a região inteira estava calibrada para caber, e não
       para ser lida.

       MONOESPAÇADO NÃO É ESTÉTICA, É ESTABILIDADE: este nó é reescrito a cada segundo pelo tique.
       Em fonte proporcional, "11" e "00" têm larguras diferentes e a coluna respira a cada
       segundo. `--font-num` com `tabular-nums` é o que o resto dos números do painel já usa, e
       resolve os dois de uma vez.

       E ELE SUBIU DE HIERARQUIA junto com o tamanho: `--text-secondary` no lugar do `muted`. O
       estado (a pílula) diz O QUE está acontecendo; o tempo diz HÁ QUANTO, e é o segundo que
       decide se alguém levanta.
       ====================================================================== */
    .precs-sup-status .precs-sup-dur{
      color:var(--text-secondary); font-size:.95rem;
      font-family:var(--font-num); font-variant-numeric:tabular-nums;
    }
    /* AS TRÊS CORES DO MESMO ELEMENTO MORAM JUNTAS, e isto é conserto de um defeito que o teste
       pegou na mesma leva em que eu o criei.
       A regra de cima tem a MESMA especificidade de `.precs-sup-dur.passou` (duas classes) e vem
       DEPOIS no pacote — então ela passou a vencer o laranja do alerta em silêncio. Medido: o
       ocioso de 3 minutos e o de 1 minuto saíam os dois em #c1c6ce, e só o vermelho sobreviveu,
       porque `.passou.grave` tem três classes.
       Separar cor base e cor de alerta em dois arquivos é o tipo de coisa que funciona no dia em
       que se escreve e quebra no dia em que alguém mexe na ordem do pacote. */
    .precs-sup-status .precs-sup-dur.passou{ color:var(--accent-orange); }
    /* O SEGUNDO GRAU é a MESMA linguagem da tela do operador: aos 3 minutos de ocioso o
       cronômetro dele fica laranja, aos 4 fica vermelho. Quem recebe o print no grupo reconhece
       o vermelho de imediato — o argumento já está escrito em `precsCartaoDados`, e vale igual
       aqui. O pulso continua o mesmo: trocar a velocidade no segundo grau faria duas coisas
       mudarem ao mesmo tempo sem nenhuma delas ficar mais clara. */
    .precs-sup-status .precs-sup-dur.passou.grave{ color:var(--accent-red); }
    /* (1.1.21) A SETA DA MANUAL EM CHAMADA — a mesma convenção do painel da 3C ("Manual ↗"),
       que a supervisão já lê lá. Só tipografia: a cor vem do texto da pílula, que já é
       calculada para contraste, e a seta é a única diferença entre "discando" e "com credor
       na linha" dentro do MESMO estado Manual. */
    .precs-sup-pill .precs-sup-mch{ margin-left:2px; font-size:.8rem; line-height:1; }
    @media (prefers-reduced-motion: reduce){ .precs-sup-pill{ transition:none; } }

    /* ====================================================================
       PRECS (1.1.11) — FICHA AO VIVO DO OPERADOR
       --------------------------------------------------------------------
       Popover e nao modal: a supervisao esta VARRENDO a tabela, e um modal
       obrigaria a fechar antes de olhar a linha seguinte, virando navegacao
       o que deveria ser consulta rapida.
       ==================================================================== */
    /* (1.1.22) A COR DA FICHA É REGISTRADA PARA INTERPOLAR. Sem o @property, variável de CSS
       troca de valor num quadro (foi a lição do tint, ver --precs-tint); registrada, ela LAVA
       de um estado para o outro — e o nó .precs-ficha SOBREVIVE às repinturas (só o innerHTML
       interno troca), então filete e véu acompanham a troca de estado do operador de graça.
       Registrada uma DERIVADA escopada, e nunca a --precs-cor-estado global: initial-value
       obrigatório do registro mataria os fallbacks `var(--x, #hex)` do projeto inteiro. */
    @property --ficha-cor { syntax: '<color>'; inherits: false; initial-value: transparent; }
    .precs-ficha{
      position:fixed; z-index:10045; width:min(340px, 94vw);
      background:var(--bg-secondary);
      border:1px solid var(--border-color);
      /* O TINT DO ESTADO, no mesmo vocabulario que o painel do operador usa
         (--precs-cor-estado): a supervisao reconhece o estado pela cor da ficha antes de ler
         qualquer palavra. Filete grosso a esquerda e um sopro da cor no fundo — fundo inteiro
         colorido custaria o contraste de todo o texto de dentro, que foi a licao do card
         pausado nesta mesma versao. */
      --ficha-cor: var(--precs-cor-estado, var(--st-idle));
      border-left:4px solid var(--ficha-cor);
      background-image:linear-gradient(
        to bottom right,
        color-mix(in srgb, var(--ficha-cor) 9%, transparent),
        transparent 60%);
      border-radius:var(--radius-card);
      box-shadow:var(--shadow-lg);
      padding:var(--space-4);
      opacity:0; transform:translateY(-6px);
      transition:opacity var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-1),
        --ficha-cor .4s ease;
      pointer-events:none;
    }
        /* A ficha nasce um tico menor e sobe: da a impressao de ter saido da pilula que a
       abriu, em vez de aparecer solta em cima da tabela. */
    .precs-ficha{ transform:translateY(-8px) scale(.97); transform-origin:top left; }
    .precs-ficha.open{ opacity:1; transform:translateY(0) scale(1); pointer-events:auto;
      /* `--ficha-cor` VAI JUNTO: esta linha sobrescreve a transition da base, e é ABERTA que a
         ficha assiste o operador trocar de estado — sem repetir a propriedade aqui, a lavagem
         de cor só existiria na ficha fechada, que ninguém vê. */
      transition:opacity var(--dur-2) var(--ease-1), transform .22s cubic-bezier(.2,.9,.3,1),
        --ficha-cor .4s ease; }

    .precs-ficha-topo{
      display:flex; align-items:center; gap:var(--space-3); margin-bottom:var(--space-3);
    }
    .precs-ficha-quem{ display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto; }
    .precs-ficha-quem b{
      font-size:.9rem; font-weight:700; color:var(--text-primary);
      font-family:'Poppins','Manrope',sans-serif;
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
    }
    .precs-ficha-ramal{
      display:flex; align-items:center; gap:var(--space-2);
      font-size:.68rem; color:var(--text-muted); font-family:'JetBrains Mono',monospace;
    }
    /* (1.1.61) D1: A FAIXA DA FICHA TINHA 96x16px, e ninguem escolheu esse tamanho.
       `precsFaixinhaHTML` emite o SVG com `width="96" height="16"` no atributo, e as duas regras
       globais (`50-modais.css:1047-1048`) dao `display` e nenhuma medida. Quem contem a faixa e
       sempre o CONTEXTO: a tabela faz por `.precs-sup-fx svg` (40px), o trilho e o cabecalho de
       Conversas fazem por regra propria (52px e 58px). A ficha nao fazia, entao o SVG saia no
       tamanho nativo, com 96px de largura ao lado de um avatar de 30px: 2,4 vezes a faixa da
       tabela, na mesma tela.
       66px e o tamanho que o modo DENSO da tabela ja usa (`10-supervisao.css:360`), e o denso e
       o modo APERTADO. Se a faixa e legivel na coluna mais estreita do painel, ela e legivel
       aqui. `height:auto` deixa o viewBox de 96x16 preservar a proporcao de 6:1, o que da
       exatamente os 11px de altura do denso. */
    .precs-ficha-ramal .precs-faixinha svg{ width:66px; height:auto; }
    .precs-ficha-x{
      flex:0 0 auto; cursor:pointer; background:transparent; border:none;
      color:var(--text-muted); font-size:.9rem; line-height:1; padding:var(--space-1);
      border-radius:var(--radius-sm);
    }
    .precs-ficha-x:hover{ color:var(--text-primary); }

    /* O ESTADO em faixa larga, com a cor cheia: e a primeira coisa que a ficha comunica. */
    .precs-ficha-estado{
      display:flex; align-items:baseline; justify-content:space-between; gap:var(--space-2);
      padding:var(--space-2) var(--space-3); border-radius:var(--radius-inner);
      background:color-mix(in srgb, var(--precs-cor-estado, var(--st-idle)) 16%, var(--bg-primary));
      border:1px solid color-mix(in srgb, var(--precs-cor-estado, var(--border-color)) 40%, var(--border-color));
      margin-bottom:var(--space-3);
    }
    .precs-ficha-estado-rot{
      font-family:'Montserrat','Poppins',sans-serif; font-size:.74rem; font-weight:700;
      text-transform:uppercase; letter-spacing:var(--ls-caps); color:var(--text-primary);
    }
    .precs-ficha-estado-dur{
      font-family:'JetBrains Mono',monospace; font-size:.8rem; font-weight:700;
      color:var(--text-secondary);
    }

    .precs-ficha-corpo{ display:flex; flex-direction:column; gap:var(--space-2); }
    .precs-ficha-linha{ display:flex; align-items:baseline; gap:var(--space-3); font-size:.78rem; }
    .precs-ficha-rot{
      flex:0 0 74px; font-size:.62rem; font-weight:700; text-transform:uppercase;
      letter-spacing:var(--ls-caps); color:var(--text-muted);
    }
    .precs-ficha-val{ flex:1 1 auto; min-width:0; color:var(--text-secondary); word-break:break-word; }
    .precs-ficha-val b{ color:var(--text-primary); font-weight:700; }
    .precs-ficha-val i{ color:var(--text-muted); font-style:italic; }
    /* A LINHA PRINCIPAL (credor, ou o numero em manual) cresce: e o dado que a supervisao abriu
       a ficha para ver, e no meio de sete linhas de 0,78rem ela se perdia. */
    .precs-ficha-linha.destaque .precs-ficha-val{ font-size:.92rem; }
    .precs-ficha-tel{ font-family:'JetBrains Mono',monospace; }
    .precs-ficha-vazio{
      font-size:.78rem; color:var(--text-muted); line-height:1.5;
      padding:var(--space-2) 0;
    }
    .precs-ficha-vivo{ color:var(--accent-green-text); font-weight:600; }
    .precs-ficha-hist{ color:var(--text-muted); }

    /* O link do Pipe repete o padrao verde do resto do painel: e a MESMA acao em todo lugar. */
    /* (1.1.79) A MESMA CAIXA DO SELO DE NEGÓCIO (ver a canônica em 10-supervisao.css). Tinha
       .78rem e peso 700, a maior das nove medidas do mesmo botão. */
    .precs-ficha-pipe{
      display:inline-flex; align-items:center; justify-content:center; gap:var(--space-1);
      height:18px; font-family:var(--font-num); font-size:var(--fs-3); font-weight:600;
      font-variant-numeric:tabular-nums; color:#fff;
      background:linear-gradient(90deg,var(--pipe-verde,#017737),var(--pipe-verde-2,#0B8A4A));
      padding:0 var(--space-2); border-radius:var(--radius-pill);
      text-decoration:none;
    }
    .precs-ficha-pipe:hover{ filter:brightness(1.08); }
    .precs-ficha-pipe-p{ font-weight:800; opacity:.75; }

    .precs-ficha-nums{
      display:flex; gap:var(--space-4); margin-top:var(--space-3);
      padding-top:var(--space-3); border-top:1px solid var(--border-color);
    }
    .precs-ficha-nums span{
      display:flex; flex-direction:column; gap:1px;
      font-size:.6rem; font-weight:700; text-transform:uppercase;
      letter-spacing:var(--ls-caps); color:var(--text-muted);
    }
    .precs-ficha-nums b{
      font-family:'JetBrains Mono',monospace; font-size:1.15rem; font-weight:700;
      color:var(--text-primary); letter-spacing:0; text-transform:none;
    }

    .precs-ficha-acoes{
      display:flex; flex-wrap:wrap; gap:var(--space-2); margin-top:var(--space-3);
    }
    .precs-ficha-btn{
      flex:1 1 auto; cursor:pointer;
      font-family:'Manrope','Poppins',sans-serif; font-size:.72rem; font-weight:700;
      padding:var(--space-2) var(--space-3); border-radius:var(--radius-pill);
      background:transparent; color:var(--precs-gold, #B49552);
      border:1px solid var(--border-color);
      transition:border-color var(--dur-1) var(--ease-1);
    }
    .precs-ficha-btn:hover{ border-color:var(--precs-gold, #B49552); }

    /* A PILULA COMO GATILHO: precisa parecer clicavel sem virar mais um botao chamativo na
       linha. Cursor, um leve realce no hover, e anel quando a ficha esta aberta. */
    .precs-sup-ficha-btn{ cursor:pointer; border:none; font:inherit; }
    .precs-sup-ficha-btn:hover{ filter:brightness(1.1); }
    .precs-sup-ficha-btn.aberta{
      box-shadow:0 0 0 2px color-mix(in srgb, var(--pill-bg, var(--st-idle)) 45%, transparent);
    }
    @media (prefers-reduced-motion: reduce){
      .precs-ficha{ transition:none; }
      .precs-ficha-btn{ transition:none; }
    }

    /* (1.1.11) MAILING PARADO (peso 0 na 3C). Nao e lixo: e mailing comprado esperando peso, e
       a diferenca entre "acabou" e "esta esperando alguem dar peso" pede acoes opostas. Fica
       abaixo da barra de listas, discreto, porque e informacao de fundo e nao alarme. */
    .precs-campsaldo-parado{
      margin-top:var(--space-2); font-size:.68rem; color:var(--text-muted);
      font-family:'Manrope','Poppins',sans-serif;
    }
    .precs-campsaldo-parado b{
      font-family:'JetBrains Mono',monospace; font-weight:700; color:var(--text-secondary);
    }

    /* ====================================================================
       (1.1.17) O SUBMENU DAS LISTAS DA CAMPANHA.
       A barra de proporção acima responde "o mailing está saudável"; isto responde "QUAL lista",
       que é a pergunta seguinte e por isso nasce fechada. O dado sai da mesma leitura do saldo,
       então abrir não custa requisição nenhuma — só desenho.
       ==================================================================== */
    /* O botão de abrir e o alvo "analisar" dividem UMA linha: `width:100%` empurrava o segundo
       para baixo, e ali ele parecia um rótulo solto em vez de um segundo caminho para a mesma
       informação. `auto` deixa cada um ocupar o próprio texto. */
    .precs-camplista-btn{
      display:inline-flex; align-items:center; gap:var(--space-1); width:auto;
      margin-top:var(--space-2); padding:var(--space-1) 0;
      background:transparent; border:none; cursor:pointer;
      font-family:'Manrope','Poppins',sans-serif; font-size:.68rem; font-weight:600;
      color:var(--text-muted); text-align:left;
      transition:color var(--dur-1) ease;
    }
    .precs-camplista-btn:hover, .precs-camplista-btn.aberto{ color:var(--precs-gold,#B49552); }
    .precs-camplista-btn:focus-visible{ outline:2px solid var(--precs-gold,#B49552); outline-offset:2px; }
    .precs-camplista-seta{ font-size:.6rem; line-height:1; }
    /* Teto de altura porque uma campanha desta operação chegou a 906 listas: mesmo mostrando 40,
       quarenta linhas dentro de um card de inventário empurrariam os vizinhos para fora da tela. */
    .precs-camplistas{
      display:flex; flex-direction:column; gap:2px;
      max-height:calc(38vh / var(--precs-escala, 1)); overflow-y:auto; overscroll-behavior:contain;
      padding-right:2px; margin-top:var(--space-1);
    }
    .precs-camplista{
      display:grid; grid-template-columns:minmax(0,1fr) auto auto auto;
      align-items:center; gap:var(--space-2);
      padding:3px var(--space-2); border-radius:var(--radius-sm);
      background:var(--bg-primary); border:1px solid transparent;
      font-size:.66rem;
    }
    .precs-camplista:hover{ border-color:var(--border-color); }
    .precs-camplista-nome{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
      color:var(--text-secondary); font-weight:600; cursor:help; }
    .precs-camplista-num{ font-family:'JetBrains Mono',monospace; font-weight:700;
      color:var(--text-primary); cursor:help; }
    .precs-camplista-de{ font-family:'JetBrains Mono',monospace; color:var(--text-muted); cursor:help; }
    .precs-camplista-peso{ font-size:.6rem; text-transform:uppercase; letter-spacing:var(--ls-caps);
      padding:0 var(--space-1); border-radius:var(--radius-pill); cursor:help;
      color:var(--text-muted); border:1px solid var(--border-color); }
    /* Peso 0 e lista zerada leem diferente de propósito: uma espera alguém dar peso, a outra
       espera mailing novo. As duas pedem ações opostas, e o card acima já faz essa distinção. */
    .precs-camplista.parada{ opacity:.72; }
    /* (1.1.32) TEXTO usa `--accent-orange-text`; a BORDA segue no acento cru, que ali e
       forma. O laranja cru como texto dava cerca de 2,1:1 sobre branco no tema claro, e aqui
       o corpo e .6rem, o que e pior que o caso da aba de WhatsApp que motivou o token. */
    .precs-camplista.parada .precs-camplista-peso{
      color:var(--accent-orange-text);
      border-color:color-mix(in srgb, var(--accent-orange) 45%, transparent); }
    .precs-camplista.zerada .precs-camplista-num{ color:var(--text-muted); }
    /* O separador entre as ativas e as de peso 0. Fio pontilhado e não cheio: ele divide duas
       metades da MESMA lista, e não duas listas diferentes. */
    .precs-camplista-sep{
      margin-top:var(--space-1); padding:2px var(--space-2);
      border-top:1px dashed var(--border-color);
      font-size:.6rem; text-transform:uppercase; letter-spacing:var(--ls-caps);
      color:var(--accent-orange-text); cursor:help;
    }
    .precs-camplista-fora{ font-size:.64rem; color:var(--text-muted); padding:var(--space-1) var(--space-2); }

    /* ======================================================================
       (1.1.17) A PAREDE — a aba de Campanhas num monitor do operacional.
       Escopo em `html[data-tv]`, no mesmo espírito do `data-theme` e do `data-fonte`. Nenhuma cor
       nova: a escala de severidade é a mesma do card (atenção laranja, crítico vermelho, parada
       dourada, seca pulsando). O que muda é a ÁREA que a cor ocupa e o tamanho do que se lê.
       ====================================================================== */

    /* A PAREDE NÃO ROLA, E ISSO PASSA A SER ESTRUTURAL.
       Relato do Pedro em 24/08/2026: "o modo TV tá rolável, por ser tela cheia na teoria não é
       para ser". Estava certo, e a garantia era frágil: o layout CABIA por cálculo de densidade, e
       bastava uma campanha com 13 listas ativas para passar alguns pixels e a página ganhar
       rolagem — numa TV, o que rola é o que ninguém vê. Agora a janela é a moldura: `overflow
       hidden` no documento, e o mural se ajusta por dentro (o teto de listas passou a sair da
       ALTURA DA TELA, e o excedente continua declarado). O modo TV é a única tela do projeto que
       trava a rolagem, e é a única em que rolar não é um gesto que existe. */
    html[data-tv], html[data-tv] body{ height: 100%; overflow: hidden; }
    html[data-tv] main{ overflow: hidden; }

    /* (1.1.49) …E NUM APARELHO ELA ROLA, que é a exceção que a regra acima não previu porque
       quando ela foi escrita a parede só existia em TV.
       ------------------------------------------------------------------------------
       O argumento do bloco acima é bom e continua valendo onde nasceu: "numa TV, o que rola é o
       que ninguém vê" — ninguém atravessa a sala para rolar uma parede pendurada. Num telefone
       na mão a frase se inverte: rolar é o gesto que a pessoa já está fazendo, e o que NÃO rola
       é o que ninguém vê. Travar a rolagem ali obrigava a parede a racionar a informação para
       caber, e o racionamento tinha um custo medido: em retrato de celular a 1.1.48 mostrava UM
       card e a célula "+3 lista(s) não mostradas nesta tela" — e não havia gesto nenhum para
       chegar às três. A pergunta que se leva para o celular é "as campanhas estão acabando?", e
       essa pergunta é sobre todas elas.

       Então em aparelho o mural vira LISTA: as fileiras trocam `1fr` (que divide uma altura fixa)
       por altura de conteúdo, o documento volta a rolar, e cada card fica do tamanho que precisa.
       O laço de encaixe não roda (a guarda está em `precsSupTvCaber`, com o motivo) e o teto
       extra de cards por aparelho saiu. O clipe da parede vira `visible`: ele existe para o
       instante entre a pintura e o ajuste do laço, e sem laço não há esse instante.

       A cadeia de flex do bloco de baixo continua de pé e não atrapalha: o que ela garante é que
       o rodapé nunca seja empurrado para fora da parede, e com o documento rolando isso passa a
       significar "o relógio fica no fim da lista", que é onde ele deve estar. */
    html[data-tv][data-aparelho],
    html[data-tv][data-aparelho] body{ height: auto; min-height: 100%; overflow: visible; }
    html[data-tv][data-aparelho] main{ overflow: visible; }
    html[data-tv][data-aparelho] .precs-sup-tv-parede{
      /* A FILEIRA GANHA ALTURA DEFINIDA, E NÃO `auto`, e esta é a decisão técnica do bloco.
         `auto` era o caminho óbvio para "lista", e ele quebra o card: `.precs-campsaldo` é
         `container-type: size` (mais abaixo neste arquivo), o que implica `contain: size` — o
         tamanho do elemento passa a ser independente do conteúdo. Numa fileira `auto` isso
         colapsa o card para ZERO. E mesmo que não colapsasse, `container-type: size` exige
         altura definida para as consultas de altura valerem: com `auto`, as 18 medidas em `cqh`
         do card passariam a resolver contra a viewport e os SEIS degraus
         `@container card (max-height: …)` deixariam de casar de uma vez. Seria uma mudança de
         dezenas de valores por efeito colateral.
         Com altura declarada por fileira, o motor do card continua exatamente o que era — o que
         muda é só quantas fileiras existem e o fato de a soma delas poder passar da tela.
         `--tv-cel-h` fica aqui, num lugar só, e cada orientação escolhe o seu valor abaixo.
         As COLUNAS seguem vindo do pintor (`--tv-cols`): em paisagem, duas colunas de 400px são
         uma boa lista de duas em duas. */
      grid-template-rows: none;
      /* (1.1.51) `minmax(piso, auto)` NO LUGAR DE UMA ALTURA FIXA, e os dois lados do minmax
         têm função. O PISO impede o colapso: o card é `container-type: size`, e no primeiro
         quadro — antes de `precsSupTvCrescerAparelho` medir — ele não tem altura inline, então
         `auto` sozinho resolveria para ZERO (é o que `contain: size` faz). O `auto` deixa a
         fileira crescer com a altura que aquela função escreve em cada card, e só onde precisa:
         com 8 operadores a sonda mediu 573px de necessidade contra os 378 que ele tinha, e os
         rostos ficavam debaixo do card seguinte. */
      grid-auto-rows: minmax(var(--tv-cel-h, 380px), auto);
      align-items: stretch;
      overflow: visible; flex: 0 0 auto;
    }
    /* 380px, E O NÚMERO É MEDIDO. A sonda varreu 340, 380 e 400 nas duas orientações com o mural
       real montado, e 380 é o primeiro valor em que o card volta INTEIRO:

         cel=340 → caixa de 338px: rosca FORA, estados FORA, listas FORA
         cel=380 → caixa de 378px: rosca, estados e listas de volta, nada cortado
         cel=400 → igual a 380, só com 22px de ar

       A explicação é o degrau `@container card (max-height: 330px)`, que tira o bloco de listas
       e os estados. Com fileira de 340 a caixa do card fica em 338 — dois pixels do degrau — e
       ele dispara. Ou seja: 340 economizava 40px de rolagem em troca das três leituras que fazem
       o card valer a pena. Num mural de altura fixa esse era o trade-off certo, porque não havia
       para onde crescer; numa lista que rola, não é: os 40px são um gesto de dedo.
       Um valor para as duas orientações, e não um por orientação: em paisagem as duas colunas do
       pintor já mostram dois cards por tela, então a altura não precisa ceder ali. */
    html[data-tv][data-aparelho] .precs-sup-tv-parede{ --tv-cel-h: 380px; }
    /* (1.1.65) O ROSTO TEM VALOR PROPRIO NO APARELHO, e o motivo e que `cqh` MENTE aqui.
       Na parede a altura da celula e IMPOSTA pela grade (as linhas sao `1fr` numa caixa exata),
       entao `6.6cqh` no `--tv-av` le "quanto espaco esta sobrando", que e a pergunta certa. No
       aparelho a parede e lista que ROLA e a altura do card sai do CONTEUDO dele -- ali a leitura
       se inverte: card alto passa a significar "tem muita coisa dentro", e o token responderia
       dando rosto MAIOR justamente onde ha menos folga.
       Medido em 1.1.65: com o token solto, o card de 635px do aparelho pedia rosto de 41,8px
       (contra os 28px que a regra de densidade cravava antes) e o card passou a transbordar 7px.
       Quem reprovou foi o `teste-vista-bolso`, em `sobra <= 2`.
       28px e o valor que o aparelho ja entregava, e `html[data-aparelho]` e o mecanismo certo:
       ele e observavel de fora (`PRECS_APARELHO_LARG/ALT`), diferente de media query. E fica
       AQUI, junto do `--tv-cel-h`, e nao no bloco de degraus la embaixo -- a primeira escrita
       desta regra caiu dentro de um `@container card (max-height: 210px)` e nunca disparava, e o
       teste continuou reprovando com a regra ja escrita. */
    html[data-tv][data-aparelho] .precs-campsaldo > *{ --tv-av: 28px; }
    /* (1.1.165) A LOTAÇÃO VALE SÓ NO CELULAR. Na TV o rosto é um só por densidade, com o nome ao lado ("não faz sentido
       as fotos da ID 02 estarem com tamanho diferente das demais", Pedro, 29/09/2026). No celular a campanha cheia
       encolhe o rosto e esconde o nome, senão a fileira sai pelo fundo do card (`teste-vista-bolso`, sobra de 24px). */
    html[data-tv][data-aparelho] .precs-campsaldo[data-lot="medio"] > *{ --tv-av: 24px; }
    html[data-tv][data-aparelho] .precs-campsaldo[data-lot="muitos"] > *{ --tv-av: 22px; }
    html[data-tv][data-aparelho] .precs-campsaldo[data-lot="muitos"] .precs-tv-op b,
    html[data-tv][data-aparelho] .precs-campsaldo[data-lot="muitos"] .precs-tv-op i{ display: none; }

    /* A parede ignora a densidade da estação. Ela é lida a quatro metros, e o zoom de 0,72 que
       serve para caber tabela num notebook é o oposto do que ela precisa. Uma regra, sem mexer na
       preferência salva de ninguém — e `--precs-escala` acompanha, para as contas em vh. */
    /* A dupla de seletores não é redundância: a densidade é escrita como `html[data-fonte] main`,
       mesma especificidade desta regra, e vence por ordem de arquivo. Com `[data-tv][data-fonte]`
       a parede ganha por especificidade em vez de por sorte de ordem — e a primeira linha cobre a
       estação que ainda não gravou preferência nenhuma. */
    html[data-tv] header,
    html[data-tv] main,
    html[data-tv][data-fonte] header,
    html[data-tv][data-fonte] main { zoom: 1; }
    html[data-tv] { --precs-escala: 1; }

    /* ======================================================================
       (1.1.33) A CADEIA DE ALTURA, E POR QUE ELA TERMINA COM A FAIXA VAZIA.

       RELATO DO PEDRO, 26/08/2026, com print de tres campanhas: "ele nao aproveitou 100% a
       tela, ficou um espacao em baixo".

       A CAUSA. A parede declarava a propria altura (`min-height: calc(100vh - 210px)`) e o laco
       de encaixe, quando nao conseguia caber, aplicava `zoom` nela. O `min-height` e resolvido
       DENTRO da subarvore com zoom: em 1080p o piso vale 870px locais, e com `zoom: 0.85` isso
       pinta 739px de tela. A faixa vazia media `870 x (1 - zoom)` -- cerca de 130px a 0,85 e
       244px no piso de 0,72. No print o texto dos cards esta visivelmente menor que o do
       cabecalho, que e o sintoma: o cabecalho fica FORA da parede e nao e zoomado.

       O CONSERTO. A parede deixa de declarar altura e passa a RECEBER altura de quem tem. A
       cadeia ja existia quase inteira (`body` e flex-column, `main` e `flex:1; display:flex`,
       `html,body{height:100%}` logo acima); faltavam cinco elos no meio. Com o rodape sendo
       irmao `flex:0 0 auto` de uma parede `flex:1 1 auto`, fica ESTRUTURALMENTE impossivel a
       parede empurrar o relogio para fora da janela -- hoje isso era garantido por o laco de
       medicao convergir, e passa a ser garantido pela caixa.

       Medido depois da mudanca: caixa 870px, parede 870px, 100%, em n=1, 2, 3 e 4. O `210` que
       saiu daqui estava CERTO (o cromo real e exatamente 210px); o problema nunca foi o numero,
       foi a parede declarar altura propria e o zoom escalar por cima dela.

       `min-height: 0` em todos os elos porque item de flex tem `min-height: auto` por padrao, e
       isso o impede de encolher abaixo do conteudo -- e um so desses faltando reabre a rolagem
       que o `overflow:hidden` de `html[data-tv]` existe para negar.

       `:not([hidden])` na aba NAO e cosmetico: `display:flex` venceria o atributo `hidden` e a
       aba de Campanhas apareceria por baixo da de Operadores quando a TV entra pelo perfil.
       ====================================================================== */
    html[data-tv] main{ min-height: 0; }
    html[data-tv] #admin-section.section.active,
    html[data-tv] .precs-sup-wrap,
    html[data-tv] #sup-aba-campanhas:not([hidden]),
    html[data-tv] #sup-aba-campanhas:not([hidden]) > .panel,
    html[data-tv] #sup-camp-saldo{
      flex: 1 1 auto; display: flex; flex-direction: column; min-height: 0;
    }
    /* A parede fica com o que sobrou, e o rodape com o que ele mede. */
    /* (1.1.35) O CLIPE DA PAREDE GANHOU MARGEM, e é por causa do card que levita: o realce do
       cursor é um anel FORA da caixa (4px de traço a 5px de distância) mais uma sombra larga, e a
       parede clipa — sem folga, o card da ponta subia com o anel cortado no lado de fora.

       A PRIMEIRA TENTATIVA FOI `padding: 10px`, e ela custou caro: padding é LAYOUT, então as
       células (que são `1fr`) encolheram 20px de altura e o encaixe de 720p com quatro ou mais
       campanhas passou a estourar (medido: folgas de -2 a -11px). Vinte pixels de respiro não valem
       um card clipado.

       `overflow-clip-margin` resolve sem custo nenhum: o navegador passa a clipar 16px FORA da
       caixa, e o layout não muda um pixel. A rede continua de pé — o rodapé é irmão da parede com
       `margin-top` de 16px, então os 16px de folga caem na margem e nunca sobre o relógio, que é a
       única coisa da tela que prova que ela está viva. `overflow: clip` no lugar de `hidden` porque
       é ele que aceita a margem (e a parede nunca rolou, então não se perde nada).

       TRINTA E DOIS PIXELS, e o número é medido e não escolhido. RELATO DO PEDRO, 27/08/2026: "a
       animação de levitar ficou incrível, só que quando ela expande parece que corta o card no
       topo". Ele estava certo, e a conta explica por que o corte só aparecia com POUCAS campanhas:
       o estouro para cima é `subida + (escala-1) x altura/2 + folga do anel + traço do anel`. Com
       dois cards de 860px isso dá 10 + 8,6 + 5 + 4 = 27,6px; com quatro cards de 423px dá 21,1px.
       Os 16px da primeira escrita cobriam o segundo caso e cortavam o primeiro — que é justamente
       a densidade em que o card é maior e o corte mais visível. */
    html[data-tv] .precs-sup-tv-parede{ flex: 1 1 auto; min-height: 0;
      overflow: clip; overflow-clip-margin: 32px; }
    /* (1.1.33) O CARD CLIPA O PRÓPRIO ESTOURO, como rede de segurança e não como solução. Quem
       resolve é o laço de encaixe, que agora mede a folga DENTRO do card (ver `sobra()` no
       app.js) e corta lista, rosto ou escala até caber. O clipe existe para o instante entre a
       pintura e o ajuste, e para o caso patológico em que nem no piso da escala cabe: sem ele o
       conteúdo pinta por cima do rodapé e cobre o relógio, que é a única coisa da tela que prova
       que ela está viva. O teste afirma que nada é clipado de fato. */
    html[data-tv] .precs-campsaldo{ overflow: hidden; }
    html[data-tv] .precs-tv-rodape{ flex: 0 0 auto; }
    html[data-tv] .precs-tv-vazio{ flex: 1 1 auto; min-height: 0; }

    /* ======================================================================
       (1.1.66) B2 — O RODAPÉ ENTRA NO MOTOR DE TAMANHO.

       Ele era o ÚNICO bloco da parede fora dele: cinco `rem` soltos, nenhum `cqh`, nenhum
       `--tv-esc`. Medido, isso dava o mesmo tamanho em toda resolução — relógio 25,6px em
       1366x768, em 1920x1080 e em 2560x1440 — numa tela que cresce 33% em pixels e 16% em
       milímetro por pixel quando sai do monitor do gestor para a TV de 85".

       E ele não é mobília: o relógio é "a única coisa da tela que prova que ela está viva" (ver o
       comentário do clipe acima), e o carimbo é o que denuncia dado congelado.

       A UNIDADE É `vh`, E ISSO NÃO É EXCEÇÃO: o rodapé é filho direto do wrap e é dimensionado
       pela JANELA, não por um card — ele não tem `@container` para consultar. É o mesmo argumento
       já escrito na visão ampliada (30-whatsapp.css): "aqui a caixa é a janela, então `vh` já é a
       unidade da caixa". Container query aqui seria consultar uma caixa que não existe.

       OS PISOS SÃO OS VALORES DE HOJE, e é isso que torna a mudança segura no pior caso: em
       1366x768 e em 1920x720 todos os `clamp` batem no piso, então o rodapé mede o que media e o
       mural não perde um pixel. Quem cresce é a TV. O risco desta leva é justamente esse — rodapé
       maior é mural menor, e a altura do rodapé entra na conta da caixa dos cards.

       O QUE MUDA EM MINUTOS DE ARCO na 85" a 2560 e a 4 metros (0,4422' por px, pela tabela de
       acuidade acima): relógio 11,3' -> 19,1'; texto do rodapé 5,80' -> 8,26'; o aviso de sair
       4,95' -> 6,68'. O aviso de sair continua abaixo do reconhecimento confortável DE PROPÓSITO:
       ele é instrução de quem opera a TV, lida de perto uma vez por mês, e crescê-lo até 12'
       roubaria altura do mural todos os dias por causa de um texto que ninguém precisa ler de
       longe.
       ====================================================================== */
    html[data-tv]{
      --tv-pe-txt: clamp(13px, 1.3vh, 24px);
      --tv-pe-rel: clamp(25px, 3vh, 52px);
      --tv-pe-min: clamp(11px, 1.05vh, 20px);
      --tv-pe-av: clamp(26px, 2.3vh, 44px);
      --tv-pe-tit: clamp(24px, 3.2vh, 56px);
      --tv-pe-sub: clamp(15px, 1.7vh, 30px);
    }

    /* Fora de cena: o header do app (a parede não desloga ninguém), as abas, o título do painel e
       a moldura do card. A tela é o mural, e mais nada. */
    html[data-tv] header{ display: none; }
    html[data-tv] .precs-sup-abas{ display: none; }
    html[data-tv] #sup-aba-campanhas > .panel > .panel-header{ display: none; }
    html[data-tv] #sup-aba-campanhas > .panel{ border: none; background: transparent; box-shadow: none; }
    html[data-tv] #sup-camp-saldo{ padding: 0; }
    /* ======================================================================
       (1.1.65) O TETO DE LARGURA DO `main` TAMBEM SAI, e ele nunca tinha aparecido porque nada
       neste projeto rodou acima de 2.200px de CSS.

       `00-base.css` declara `main{ max-width: 2200px }` com o comentario "aproveita melhor
       monitores largos", e esta secao desfazia o teto so no `.precs-sup-wrap`, um nivel abaixo.
       Em 1920 a cadeia e 1910 / 1910 / 1878 / 1846 e o teto nunca entra em jogo. Em 2560, que e a
       resolucao da TV nova, ela e 2550 / 2200 / 2168 / 2136: a parede recebe 2.136px em vez de
       2.486, e os 350px que sobram viram BARRA PRETA. A 0,735mm por px de CSS sao 257mm de
       painel apagado, 13% da largura de uma TV de 85".

       E ESCOPO DE PAREDE, nao global, de proposito: `html[data-tv] main` deixa a tela de quem
       atende exatamente como estava em qualquer monitor. Mexer no `main` do 00-base alargaria a
       tela do operador acima de 2.200px e obrigaria a leva a declarar `APP_ALVO: 'todos'`.
       ====================================================================== */
    html[data-tv] main{ max-width: none; }
    html[data-tv] .precs-sup-wrap{ max-width: none;
      padding: var(--space-3) var(--space-4) var(--space-2); }
    /* O cabeçalho da parede diz quantas campanhas estão acabando (escrito no JS). O nome de quem
       está logado e o relógio do atendimento saem: o primeiro não informa nada numa TV e o segundo
       ficava em `--:--:--`, porque quem o atualiza é o ciclo que a parede não liga. O relógio de
       verdade está no rodapé, do lado do carimbo da leitura — juntos, que é como se confere idade. */
    html[data-tv] .precs-sup-titulo{ font-size: 1.6rem; }
    html[data-tv] .precs-sup-legenda{ font-size: .9rem; }
    html[data-tv] #sup-titulo-sep,
    html[data-tv] #sup-nome,
    html[data-tv] #sup-sub-sep,
    html[data-tv] #sup-relogio{ display: none; }

    /* AS LISTAS NA PAREDE. O botão sai (ninguém clica numa TV) e o bloco fica aberto por conta do
       JS nas faixas crítica e atenção: o nome da lista é a resposta acionável da tela — é por ele
       que o operacional sabe QUAL mailing repor. Tipo maior, porque é lido de longe. */
    html[data-tv] .precs-camplista-btn{ display: none; }
    html[data-tv] .precs-camplistas{ margin-top: var(--space-2); }
    html[data-tv] .tv-critica .precs-camplista,
    html[data-tv] .tv-critica .precs-camplista-nome{ font-weight: 600; }

    /* ======================================================================
       O MURAL, E A REGRA QUE MANDA: ELE PREENCHE A TELA.
       Uma campanha ao vivo é o caso comum (medido em produção em 22/08/2026), e o desenho de
       hierarquia — crítica grande, tranquila em linha — só faz sentido quando há disputa por
       espaço. Sem disputa, encolher o único card é jogar o monitor fora. Daí o `data-n` escrito
       pelo pintor: até quatro, cards INTEIROS ocupando a altura toda, com o tipo crescendo
       conforme sobra lugar; de cinco em diante, a hierarquia de três volta a valer.
       ====================================================================== */
    /* (1.1.33) SEM ALTURA PROPRIA. Ela vem da cadeia de flex declarada acima -- ver o comentario
       de lá para o porquê. O `min-height: calc(100vh - 210px)` que morava aqui era o que, sob
       `zoom`, deixava a parede pintar menor que a tela e abrir a faixa vazia. */
    html[data-tv] .precs-sup-tv-parede{
      display: grid; gap: var(--space-3);
      /* (1.1.33) COLUNAS E LINHAS DECLARADAS, as duas em `1fr`. Os dois números vêm do pintor
         (`--tv-cols` e `--tv-rows`, ver precsSupTvPintar): a densidade escolhe o arranjo, e não a
         largura da janela. Com linhas `1fr` numa caixa de altura exata, a soma das trilhas mais
         os gaps É a caixa — buraco entre fileiras deixa de ser possível. */
      grid-template-columns: repeat(var(--tv-cols, 1), minmax(0, 1fr));
      grid-template-rows: repeat(var(--tv-rows, 1), minmax(0, 1fr));
      grid-auto-rows: minmax(0, 1fr); align-items: stretch;
    }
    /* UM card: a tela inteira para ele — e AS DUAS COLUNAS DENTRO DELE.
       Empilhado, um card sozinho em 1920x1080 deixa dois terços da largura vazios à direita do
       número, com o rodapé de operadores lá embaixo: espaço sobrando ao lado da informação que
       falta. Então o card vira grade: nome em cima, rosca e número à esquerda, os nomes das listas
       à direita (é o que responde "qual mailing eu reponho"), operadores e ligações no pé. */

    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-campsaldo{
      /* A coluna da direita é maior porque é ela que enche: nome de lista real tem 40 caracteres
         em caixa alta, e a esquerda são três coisas (rosca, número, projeção). */
      display: grid; grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr);
      /* Quatro linhas, e a linha de estados tem a própria: sem declarar TODOS os filhos, o que
         sobra cai no auto-placement e embaralha as colunas dos outros. */
      /* Linhas AUTO e o bloco inteiro centrado na vertical. Com uma linha de `1fr` no meio, a
         sobra de altura virava um buraco entre as listas e a fileira de operadores — e buraco no
         meio de uma tela lida de longe parece conteúdo que faltou carregar. Centrado, a mesma
         sobra vira respiro simétrico em cima e embaixo. */
      grid-template-rows: auto auto auto auto;
      align-content: center; row-gap: var(--space-4);
      column-gap: var(--space-5); align-items: center; justify-items: stretch;
    }
    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-campsaldo-topo{
      grid-column: 1 / -1; grid-row: 1; align-self: start; }
    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-campsaldo-estados{
      grid-column: 1 / -1; grid-row: 2; align-self: start; }
    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-campsaldo-corpo{
      grid-column: 1; grid-row: 3; justify-content: flex-start; }
    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-campsaldo-listas{
      grid-column: 2; grid-row: 3; margin: 0; border-top: 0; align-self: center; }
    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-tv-extra{
      grid-column: 1 / -1; grid-row: 4; }
    /* DOIS lado a lado, e não um em cima do outro: em 1920 cada um fica com 950px de largura e a
       ALTURA INTEIRA, que é o que permite o número gigante. Empilhados, cada um teria 435px de
       altura e o tipo teria de encolher — pior nos dois eixos. */

    /* TRÊS: o mais urgente toma a linha de cima inteira, os outros dois dividem a de baixo. */

    /* QUATRO em 2x2. */

    /* Cinco ou mais: a hierarquia de três, com trilha larga o bastante para o nome caber.
       (1.1.32) `align-content: start`, E É ISTO QUE CONSERTA O MURAL CHEIO.
       Relato do Pedro em 26/08/2026, com print: com cinco e com seis campanhas a parede abria
       BURACOS de 300px entre as fileiras, e o mural virava três ilhas soltas numa tela preta.
       A causa: a grade tem `min-height: calc(100vh - 210px)` (a parede preenche a tela) e aqui as
       trilhas passam a ser `auto`. `align-content` numa grade vale `normal`, que para trilha
       automática se comporta como `stretch` — então toda a altura que sobrava era DIVIDIDA entre
       as fileiras, e cada divisão virava um vão. Com `start` a sobra fica inteira no fim, onde ela
       não parece conteúdo que faltou carregar.
       O `gap` continua sendo o único respiro entre fileiras, que é o que se queria desde sempre. */
    /* (1.1.33) `muitos` deixou de ser um layout à parte: colunas e linhas vêm de `--tv-cols` e
       `--tv-rows` como em toda densidade. Saíram daqui `auto-fill` (que fazia o número de colunas
       depender da largura da janela), `grid-auto-rows: auto`, `align-items: start` e o
       `align-content: start` — este último era o remendo de 26/08/2026 para esconder a sobra que
       as trilhas automáticas criavam, e com linhas `1fr` não há sobra para esconder. */
    /* A LINHA FINA TEM DE FICAR FINA, e para isso precisa desfazer o `height:100%` que o card
       carrega da tela do supervisor — lá ele existe para igualar a altura dos vizinhos numa grade
       (7ª volta), e aqui produzia o oposto: a tranquila esticava até a altura da crítica que está
       na mesma fileira, virando uma caixa de 380px com uma linha de texto dentro. */
    /* (1.1.33) A TRANQUILA VOLTA A SER CARD, e isto não é regressão de desenho.
       Ela era `height:auto; align-self:start` para virar uma linha fina de ~20% da altura. Com
       linhas de grade `1fr` numa caixa exata, um card que não preenche a própria linha é
       exatamente o buraco que a onda inteira existe para eliminar — e era ele que aparecia nos
       prints de cinco e seis campanhas. A hierarquia continua, por outro caminho: a tranquila
       mostra MENOS BLOCOS na mesma célula (ver o que sai de cena logo abaixo), e a crítica ganha
       o dobro de largura em n=5 e n=7. */

    /* ---- CARD INTEIRO (mural de até quatro): o conteúdo distribui na altura ----
       O `data-n="1"` fica FORA: lá o card é uma grade de duas colunas (regra acima), e as duas
       regras têm a mesma especificidade — quem vencia era a de baixo. O sintoma foi um card
       inteiro com tudo centralizado na horizontal, porque num flex-column `align-items:center`
       centra no eixo transversal, que ali é a largura. Excluir na FONTE em vez de contar ordem
       de arquivo: ordem é a coisa mais fácil de quebrar sem perceber ao mover um bloco. */
    html[data-tv] .precs-sup-tv-parede:not([data-n="muitos"]):not([data-n="1"]) .precs-campsaldo{
      display: flex; flex-direction: column; min-height: 0;
    }
    /* (1.1.33) QUEM CRESCE É O BLOCO DE LISTAS, E NÃO O CORPO. Esta inversão conserta um laço
       circular que o novo termômetro de encaixe expôs.
       O corpo (rosca + número + projeção) tinha `flex: 1 1 auto` para "ocupar o vão que sobra
       numa tela alta". O efeito colateral: o card ficava SEMPRE exatamente cheio, então
       `scrollHeight` era igual a `clientHeight` em qualquer situação, e cada linha de lista
       acrescentada virava transbordo. O laço de encaixe cortava lista para abrir espaço, o corpo
       crescia e engolia o espaço aberto, e o laço cortava de novo — medido num mural de quatro:
       teto 1 com 120px de ar no meio de cada card.
       Agora o corpo tem o tamanho dele e a sobra vai para as listas, que é o conteúdo de
       comprimento variável do card. Com isso a folga do card volta a significar algo, e o laço
       DEVOLVE linha até encher. O ar que sobrar fica no fim do bloco de listas, embaixo, em vez
       de virar um buraco no meio. */
    /* ======================================================================
       (1.1.33) NADA CRESCE DENTRO DO CARD, E ISSO É O QUE TORNA O ENCAIXE MEDÍVEL.

       Testei as três configurações antes de fechar nesta, e as outras duas têm o mesmo defeito
       de fundo: um bloco com `flex-grow` faz o card ficar SEMPRE exatamente cheio, então
       `scrollHeight` é igual a `clientHeight` em qualquer situação e o laço de encaixe perde o
       termômetro. Ele não consegue distinguir "cabe justo" de "sobra espaço".

        · corpo cresce (o desenho original): o laço encolhia o tipo até o piso (0,72) sem saber
          que já cabia, e o card ficava com uma lista e 120px de ar no meio.
        · listas crescem: o estouro passava a acontecer DENTRO do bloco de listas, que encolhia
          por flex em vez de empurrar o card — as últimas linhas pintavam por cima da fileira de
          operadores e o card reportava folga zero. Medir o bloco à parte resolvia o sintoma e
          reabria o laço circular no outro nível.
        · nada cresce (esta): o card tem a altura natural do conteúdo, a folga é a diferença real,
          e o laço enche de lista até o limite pela devolução por tentativa.

       O preço é o ar quando o conteúdo é curto. `justify-content: center` faz esse ar virar
       respiro simétrico em cima e embaixo, em vez de um buraco entre dois blocos — que é a mesma
       decisão já registrada no card de `data-n="1"`, com `align-content: center`. */
    html[data-tv] .precs-campsaldo-corpo{ flex: 0 0 auto; align-items: center; }
    html[data-tv] .precs-campsaldo-listas{ flex: 0 0 auto; min-height: 0; }
    /* `justify-content: center` foi TENTADO aqui e retirado: com o conteúdo centrado, o estouro
       sai pelos DOIS lados da caixa, e `scrollHeight` só conta o que passa por baixo. O laço de
       encaixe media metade do déficit, corrigia de menos, media de novo — e descia a escala até o
       piso sem nunca chegar. O ar do card curto fica embaixo, e isso é preço barato por um
       termômetro que não mente. */
    /* ======================================================================
       (1.1.33) O MOTOR DE TAMANHO: A CAIXA DO CARD MANDA, NAO A DENSIDADE.

       O QUE SAIU DAQUI. Cinco escadas paralelas de `font-size`/`width` por `data-n` (1, 2, 3,
       4, muitos), mais um bloco `@media (max-height: 900px)` de 68 linhas que reescrevia todas
       elas por extenso -- porque `@media` nao soma especificidade e a versao curta perdia para
       as regras de 1080p. Cerca de 130 linhas para dizer "o tipo acompanha o espaco".

       POR QUE SAIU. Tres razoes medidas, nao esteticas:

        1. A ESCADA TINHA BURACOS. Ao dar grade propria a n=5, 6 e 7 (nesta mesma versao), o
           numero do saldo despencou de 61px em n=4 para 32px em n=5, e o nome da lista SUBIU de
           15px para 17px -- porque nao existiam regras para essas densidades e tudo caia no
           tamanho base do card. Toda densidade nova exigia reescrever cinco escadas.

        2. A ESCADA NAO SABIA DO TAMANHO DA TELA. `@media (max-height:900px)` cobria 720p e mais
           nada. Num mini-PC saindo em 4K com escala do Windows em 100%, `100vh` vale 2160 e cada
           `rem` continua 16px: o tipo ficaria com METADE do tamanho fisico no mesmo painel, e
           nenhuma media query dispararia.

        3. O RELATO DO PEDRO SOBRE A ROSCA. A razao numero/rosca passeava de 0,41 (n=3) a 0,51
           (n=1), entao cada densidade parecia um desenho diferente. Fixar a razao e o que faz a
           parede parecer uma tela so.

       COMO FUNCIONA. Cada card e um `container-type: size`, e todo tamanho sai da caixa DELE em
       `cqh`/`cqi`, com piso e teto por `clamp()`. A celula do card ja tem altura definida (as
       linhas da grade sao `1fr` e o card e `height:100%`), que e o que `container-type: size`
       exige -- e e por isso que a grade explicita tinha de vir antes deste bloco.

       OS PISOS SAO DE LEGIBILIDADE, MEDIDOS, nao de gosto. A regra e a ALTURA DE MAIUSCULA em
       minutos de arco: o limite da acuidade 20/20 para reconhecer letra e ~5', e leitura corrida
       confortavel pede 12' a 16'.

       (1.1.65) A CONTA FICA ESCRITA, e o fator 0,7 e a parte que se perde ao refazer de cabeca:

         mm por px de CSS = largura fisica do painel / px de CSS na horizontal
         minutos de arco  = px * mm_por_px * 0,7 * 3437,75 / distancia_em_mm

       O 0,7 e a razao entre altura de MAIUSCULA e a caixa em `em`. Sem ele a conta sai 1,43x
       otimista, e foi assim que a primeira volta desta reescrita errou: dava 10,6' onde sao 7,4'.
       Confere contra as linhas de 1080p abaixo, que sao as originais de 1.1.33.

       Constantes, a 4 metros:
         55" a 1080p  ->  1.217,7mm / 1.920px = 0,634mm por px  ->  0,3815' por px
         85" a 2560   ->  1.881,9mm / 2.560px = 0,735mm por px  ->  0,4422' por px
         85" a 1920   ->  1.881,9mm / 1.920px = 0,980mm por px  ->  0,5896' por px
       Confortavel (12') pede 27,1px em 2560, e 20,4px em 1080p.

       O ALVO DE AGORA (decidido em 10/09/2026): Samsung de 85" a 2560x1440 px de CSS, a 4 metros.
       A resolucao nao e 4K por acidente: o mini-PC sai em 3840x2160 com a escala do Windows em
       150%, e e isso que entrega 2560x1440 de CSS (docs/parede-tv-minipc.md, secao 2). O alerta
       que aquela secao abre vale repetir: TV maior NAO da mais espaco de layout. Quem da espaco e
       resolucao dividida por escala.

       OS PISOS QUE O CODIGO TEM (e a tabela antiga listava errado: ela dizia "nome da lista a
       1,05rem", e 1,05rem e o piso do nome da CAMPANHA; o da lista e 0,9rem, e o 1,15rem que ela
       citava e o TETO de `--tv-quem`, nao piso de nada):

                             piso      px     85"/2560     55"/1080p
         --tv-lista          .9rem    14,4      6,37'         5,49'
         --tv-nome          1.05rem   16,8      7,43'         6,41'
         --tv-quem           .9rem    14,4      6,37'         5,49'
         --tv-proj          .85rem    13,6      6,01'         5,19'
         numero do saldo    1.75rem   28,0     12,38'        10,68'

       E OS VALORES MEDIDOS EM 2560x1440, que e o que a sala ve (parede de 2.136x1.222, com
       `--tv-esc` em 1 em todas as densidades):

         n=1 a n=3   nome 42,2px -> 18,7'   lista 20,0px -> 8,8'   numero 84,9 a 187,7px
         n=4 a n=7   nome 19,7px ->  8,7'   lista 14,4px -> 6,4'   numero  68,2px -> 30,2'
         muitos      nome 19,7px ->  8,7'   lista 14,4px -> 6,4'   numero  63,7px -> 28,2'

       A CONCLUSAO NOVA E MISTA, e a metade ruim e a que ninguem esperava.
       ------------------------------------------------------------------
       MELHORA muito: o numero do saldo sai de 10,7' para 28' a 83' e fica folgado em qualquer
       densidade, e o nome da campanha entra no confortavel (18,7') com tres campanhas ou menos.

       PIORA: na MESMA TV de 85", trocar 1920 por 2560 px de CSS ENCOLHE fisicamente o nome de
       lista, de 8,5' para 6,4'. Nas duas resolucoes ele esta preso ao piso de 0,9rem, e `rem` nao
       sabe da resolucao: 14,4px valem 0,98mm por px num caso e 0,735 no outro. A recomendacao de
       150% da secao 2 do doc compra espaco de verdade (4 linhas de lista contra 2, e o "+N" de
       rostos desaparece em n=5, 6 e 7) e paga com 25% do tamanho fisico do texto que faz alguem
       levantar e repor mailing. Aquela secao nao diz isso porque a conta nunca foi refeita para
       a 85".

       E o nome de lista NAO chega aos 12' em nenhuma densidade nesta tela: precisaria de 27,1px,
       quase o dobro do piso. Nao ha TV de 85" que resolva -- quem resolve e a DISTANCIA. A 2,12m
       os mesmos 14,4px dao 12,0'. Isso e decisao de operacao (onde as pessoas sentam), e o item
       do checklist do doc ("ler o nome de uma lista de onde as pessoas realmente sentam") passa a
       ter numero: alem de ~2,1m, nome de lista e reconhecimento e nao leitura corrida.

       E DAI SAI A MUDANCA DE ATENCAO QUE ESTE BLOCO REGISTRA: em 1440 varios TETOS passam a
       limitar antes da caixa. Medido em n=1 a n=3: `--tv-nome` sai a 42,2px, que e 2,2rem x 1,2
       exato, ou seja NO TETO; `--tv-lista` a 20,0px = 1,25rem, NO TETO; `--tv-proj` a 24,0px =
       1,5rem, NO TETO. Em 1080p nenhum dos tres encosta. Piso protege a tela pequena; teto
       calibra a grande, e a partir do alvo novo e o teto que precisa de olho. Quem for revisar
       tamanho aqui mede em 2560x1440 tambem -- e o teste-supervisao-tv.js afere nessa resolucao
       desde a 1.1.65 exatamente por isso.

       O que os pisos continuam garantindo e que a parede nunca desce ABAIXO do que 1.1.32
       entregava. Se um dia o piso nao puder ser cumprido, a tela tem de dizer isso em vez de
       encolher em silencio.

       A REGRA DE MANUTENCAO QUE ISTO ESTABELECE: regra de densidade escreve TOKEN (`--tv-*`),
       nunca propriedade de caixa. Foi um `padding: 2px 0` por densidade vencendo um
       `padding-bottom: 14px` por especificidade que colocou o traco dourado das listas em cima
       do texto -- ver o comentario da barra de consumo mais abaixo.
       ====================================================================== */
    html[data-tv] .precs-campsaldo{ container-type: size; container-name: card; }
    /* OS TOKENS VIVEM NOS FILHOS, E NÃO NO CARD. Esta linha é a diferença entre o motor funcionar
       e não funcionar, e ela me custou uma tarde: unidade de container (`cqh`, `cqi`) usada no
       PRÓPRIO elemento que é o container é INVÁLIDA — seria uma dependência circular, o tamanho
       dependendo de si mesmo. A declaração morre em silêncio no valor computado, e a propriedade
       cai no herdado. O sintoma foi a projeção do card saindo a 61px onde devia sair a 14, e o
       card estourando a célula por causa disso.
       Declarados nos filhos diretos, `cqh` e `cqi` consultam o card (que é o container mais
       próximo) e os tokens herdam daí para baixo — um bloco só, e correto. */
    html[data-tv] .precs-campsaldo > *{
      /* A ROSCA E A MEDIDA MESTRA: tudo se pendura nela. `26cqh` a mantem proporcional a altura
         da celula e `40cqi` impede que ela coma a largura num card estreito; o `min()` deixa o
         mais apertado dos dois mandar. */
      --tv-rosca: calc(clamp(76px, min(19cqh, 36cqi), 250px) * var(--tv-esc, 1));
      /* RAZAO FIXA em 0,62 entre a altura do numero e o diametro da rosca: e o valor em que a
         caixa alta do numero fica proxima do diametro INTERNO do anel, e os dois leem como um
         objeto so. O teto de `13cqi` e a trava de largura: em n=3 o card tem 607px, e um saldo de
         oito digitos a 145px atravessaria a coluna -- o defeito do print de 25/08/2026,
         reaparecendo numa densidade que a escada antiga nem cobria. */
      --tv-num: min(calc(var(--tv-rosca) * .62), 13cqi);
      --tv-nome: calc(clamp(1.05rem, 3.4cqh, 2.2rem) * var(--tv-esc, 1));
      --tv-proj: calc(clamp(.85rem, 2.1cqh, 1.5rem) * var(--tv-esc, 1));
      --tv-lista: calc(clamp(.9rem, 2.1cqh, 1.25rem) * var(--tv-esc, 1));
      /* O TETO DA FILEIRA DE ROSTOS É CALIBRADO PELO PIOR CASO REAL: 13 operadores numa
         campanha, que foi o número que o Pedro levantou e que o teste da parede fixa. Num card
         de 1846px, cada pastilha tem (avatar + primeiro nome + gap); com avatar de 48px e nome
         a 1,25rem isso dava ~150px por pessoa, 1.950px no total, e a fileira QUEBRAVA. Aí o laço
         de encaixe cortava rosto — exatamente o "+2" que 1.1.32 consertou, voltando por outro
         caminho. Com 40px e 1,15rem sobra folga para os treze numa linha só. */
      --tv-quem: calc(clamp(.9rem, 2.2cqh, 1.15rem) * var(--tv-esc, 1));
      /* (1.1.65) O ROSTO SAIU DO PIXEL CRAVADO, e este token passou a ser o unico dono dele.
         Ele convivia com TRES regras de densidade que cravavam caixa (34px de base, 44px em
         n=1/2, 28px em n=3/4/muitos), e as cravadas venciam por especificidade. O resultado,
         medido em 2560x1440: 44, 44, 28, 28, 32, 32, 32 de n=1 a n=7 -- ou seja o mural de TRES
         cards mostrava rosto MENOR que o de cinco. Escada invertida na peca que a tela existe
         para mostrar.
         E era o defeito que a regra de manutencao deste arquivo proibe em letra ("regra de
         densidade escreve TOKEN, nunca propriedade de caixa") e nao tinha oraculo. Agora tem: o
         teste-supervisao-tv.js trava a monotonia de `av` e de `quem` nas tres resolucoes.
         O COEFICIENTE FOI ESCOLHIDO MEDINDO, E TEM TETO DE CIMA E DE BAIXO.
         Por cima quem manda e o TREMOR, nao o gosto. Com `8.5cqh` (o valor que manteria os 44px
         de n=1 em 1366x768) o rosto vai a 44px tambem nas celulas densas de 1440, a fileira de
         pastilhas passa a quebrar, o laco de encaixe corta rosto, o corte muda a altura, a altura
         vira condicao de `@container` e a coisa OSCILA: medido, 35 mutacoes e 7 quedas para
         `innerHTML` em seis pinturas com dado identico, contra o zero que a parede exige.
         `6.6cqh` foi o maior valor que assenta. Por baixo, o piso de 24px e o que 1366x768 com
         quatro ou mais cards ja entregava.

         O QUE ISSO CUSTA, declarado em vez de escondido. Medido, de n=1 a n=7:
           2560x1440   44 44 44 38 38 38 38   (era 44 44 28 28 32 32 32)
           1920x1080   44 44 44 26 26 26 26   (era 44 44 28 28 24 24 24)
           1366x768    34 34 34 24 24 26 24   (era 44 44 28 28 24 24 24)
         Ou seja: a TV nova GANHA (n=3 sai de 28 para 44, e as densidades cheias de 28/32 para
         38), e o 1366x768 com uma ou duas campanhas PERDE, de 44 para 34.
         Aquela perda e coerente e nao acidental: em 1366 a celula de n=1 tem 550px de altura e em
         1440 tem 1222, e manter o mesmo rosto de 44px nas duas era justamente a crava dizendo
         "sobra tela" onde nao sobrava. A 34px o rosto ainda mede 30mm num painel de 55" a 1366
         (0,891mm por px), e reconhecer um rosto nao pede altura de maiuscula. */
      --tv-av: clamp(24px, 6.6cqh, 44px);
      /* `--dig-k` NO LUGAR DAS TRES ESCADAS DE `data-dig`. Elas existiam so para n=1 e n=2, e
         `data-dig` e escrito pelo pintor em toda densidade -- ou seja, de n=3 para cima um saldo
         de seis digitos nao tinha nada que cedesse. Agora o fator vale em todas. */
      --dig-k: 1;
    }
    /* (1.1.141) COM ATÉ QUATRO CAMPANHAS NA PAREDE os rostos crescem ("aumentar as fotos de tamanho", Pedro, 25/09/2026).
       Só até quatro: com sete cards em 1366x768 o orçamento de altura do card não aguenta, e o teste da parede
       reprovou (transbordo por dentro e bloco de listas sem linha). A escada continua monótona dentro de cada faixa. */
    /* (1.1.156) NOS FILHOS, e nao no card. A regra de 1.1.141 declarava `--tv-av` no proprio `.precs-campsaldo`, que e
       o container: unidade `cqh` no container morre em silencio (ver o bloco "OS TOKENS VIVEM NOS FILHOS" acima), e
       o rosto caia na regra generica de `6.6cqh` -- 44px com tres cards e 28px com quatro ("as fotos tao pequenas
       por nada, tem espaco", Pedro, 28/09/2026). Nos filhos a conta vale: 58px com tres, 54px com quatro em 1080p. */
    html[data-tv] .precs-sup-tv-parede[data-n="1"] .precs-campsaldo > *,
    html[data-tv] .precs-sup-tv-parede[data-n="2"] .precs-campsaldo > *,
    html[data-tv] .precs-sup-tv-parede[data-n="3"] .precs-campsaldo > *{
      --tv-av: clamp(30px, 8.4cqh, 58px);
    }
    /* (1.1.154) COM QUATRO a parede vira 2x2 e a célula tem metade da altura: os mesmos 8.4cqh davam 41px onde três
       cards davam 58 ("a partir de 4 cards as fotos são diminuídas drasticamente", Pedro, 28/09/2026). A fatia da
       célula sobe e o piso também, para o rosto de quatro campanhas ficar na mesma escala do de três. */
    /* (1.1.156) QUATRO CARDS: a mesma fatia de um a tres, mas SO quando a celula tem altura (container query). Em
       1080p a celula de quatro tem 421px e o orcamento do card ja esta no limite: medido no cenario 28 do teste da
       parede (quatro listas por card), qualquer rosto acima de 28px fazia o laco de encaixe cortar lista e oscilar
       (oito reescritas por seis pinturas), e em 1366x768 o card transbordava. A partir de 500px de celula -- a TV
       da sala, em 2560x1440, da 615px -- o rosto vai a 52px, que e o que o pedido de 28/09/2026 queria ver. */
    @container card (min-height: 500px){
      html[data-tv] .precs-sup-tv-parede[data-n="4"] .precs-campsaldo > *{
        --tv-av: clamp(30px, 8.4cqh, 58px);
      }
    }
    html[data-tv] .precs-campsaldo-num[data-dig="6"],
    html[data-tv] .precs-campsaldo-num[data-dig="7"]{ --dig-k: .78; }
    html[data-tv] .precs-campsaldo-num[data-dig="8"],
    html[data-tv] .precs-campsaldo-num[data-dig="9"],
    html[data-tv] .precs-campsaldo-num[data-dig="10"],
    html[data-tv] .precs-campsaldo-num[data-dig="11"]{ --dig-k: .62; }

    /* (1.1.33) O NOME DA CAMPANHA QUEBRA EM DUAS LINHAS EM VEZ DE TRUNCAR.
       Ele herda `white-space:nowrap` + `text-overflow:ellipsis` da tela do supervisor, onde
       truncar está certo: lá o card tem 620px de teto e o nome inteiro fica no balão, alcançável
       com o mouse. Numa parede não existe mouse, então "Campanha Automatica - I…" é uma campanha
       que ninguém identifica — e com o tipo de leitura desta versão (34px em n=3 contra 29px
       antes) o truncamento passou a acontecer em nome de tamanho normal, num card de 607px.
       Duas linhas e só depois reticências: `-webkit-line-clamp` é o único jeito de ter as duas
       coisas, e ele é suportado em toda parte que interessa aqui (Chrome no mini-PC). */
    html[data-tv] .precs-campsaldo-nome{
      font-size: var(--tv-nome);
      white-space: normal; overflow: hidden; text-overflow: ellipsis;
      display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
      line-height: 1.15;
    }
    html[data-tv] .precs-campsaldo-num b{ font-size: calc(var(--tv-num) * var(--dig-k, 1)); }
    html[data-tv] .precs-campsaldo-proj{ font-size: var(--tv-proj); }
    /* Os MINUTOS PROJETADOS ("~6 min restantes") saem do mesmo token da projeção, com um
       acréscimo: é o número que decide se alguém levanta agora. Antes eram dois tamanhos fixos
       por faixa (2rem na crítica, 1,5rem na atenção) — e era esse par que inflava o corpo do card
       crítico em ~30px, estourando a célula em 720p com quatro campanhas. */
    html[data-tv] .precs-campsaldo-min{ font-size: calc(var(--tv-proj) * 1.25); }
    html[data-tv] .precs-campsaldo-rosca{ width: var(--tv-rosca); height: var(--tv-rosca); }
    /* O texto e o rotulo da rosca em `em` da propria rosca, e nao em `rem`: eram `.9rem` e
       `.56rem` FIXOS, entao num anel de 300px o sinal de `%` tinha 14px ao lado de um numero de
       58px, e a palavra "consumido" tinha 9px em qualquer densidade. */
    html[data-tv] .precs-campsaldo-rosca-txt{ font-size: calc(var(--tv-rosca) * .21); }
    /* (1.1.63) O MESMO 0,42em SERVE OS DOIS SINAIS DE PERCENTUAL da parede: o da rosca (saldo de
       mailing) e o da célula do consumo da API. Era a mesma decisão escrita duas vezes com o mesmo
       número mágico, e duas cópias de um número divergem no dia em que alguém afina uma. */
    html[data-tv] .precs-campsaldo-rosca-txt i,
    html[data-tv] .precs-tv-api-num i{ font-size: .42em; }
    html[data-tv] .precs-campsaldo-rosca-lbl{ font-size: calc(var(--tv-rosca) * .062); }
    html[data-tv] .precs-camplista{ font-size: var(--tv-lista); }
    html[data-tv] .precs-camplista-nome{ font-size: 1em; }
    html[data-tv] .precs-tv-quem{ font-size: var(--tv-quem); }
    html[data-tv] .precs-tv-op .precs-sup-av{ width: var(--tv-av); height: var(--tv-av);
      font-size: calc(var(--tv-av) * .3); }
    html[data-tv] .precs-tv-lig b{ font-size: var(--tv-quem); }

    /* ======================================================================
       (1.1.35) O ANEL PERDEU O `non-scaling-stroke`, E ISSO É CONSERTO DE DEFEITO.

       RELATO DO PEDRO, 27/08/2026, com três prints: "o formato que aparece que o gráfico está
       sendo consumido ficou estranho, as cores ficaram em pontas separadas", e um print de 1% com
       TRÊS bolinhas espalhadas pelo anel.

       O QUE ESTAVA ERRADO. Em 1.1.33 o anel ganhou `vector-effect: non-scaling-stroke` para a
       espessura parar de variar 3,5x entre as densidades, e o comentário daquela volta afirmava
       que "o `stroke-dasharray` continua em unidades de usuário". Não continua. O atributo muda a
       unidade do TRAÇO para pixel de tela, e o dasharray vai junto — é a mesma propriedade de
       traço. O pintor calcula o arco no `viewBox` (perímetro 251,3 unidades), mas numa rosca de
       210px a circunferência de TELA tem 527px: o padrão "88 ligado, 251 desligado" cabe duas
       vezes e o navegador REPETE, porque dasharray é um padrão cíclico. Daí o arco em duas pontas
       a 35%, e três bolinhas a 1% (quanto menor a fatia, mais repetições cabem).

       Conferido no navegador com quatro variantes lado a lado, em 1, 12, 35, 68 e 99 por cento:
       com `non-scaling-stroke` todas as cinco saem partidas; sem ele, todas saem inteiras.
       `pathLength="100"` NÃO resolve (variante testada, continua partido). Calcular o dasharray em
       pixel de tela resolve, e foi recusado: obriga o pintor a saber o tamanho renderizado da
       rosca, que muda com a densidade, com o laço de encaixe e com a janela — acoplamento novo
       entre JS e layout para ganhar espessura constante.

       A ESPESSURA VOLTA A ESCALAR COM A CAIXA, e a variação fica em 1,4x (contra 3,5x da versão
       original) com dois degraus só: a rosca grande do card alto usa traço fino em unidades de
       usuário, e a pequena usa grosso. Os números saem das três larguras que a parede realmente
       produz (76, 97 e 210px, medidas pelo teste de densidade):
         · 76px x 10/100  = 7,6px de tela
         · 97px x 10/100  = 9,7px
         · 210px x 5/100  = 10,5px
       ====================================================================== */
    html[data-tv] .precs-campsaldo-rosca .cs-trilha,
    html[data-tv] .precs-campsaldo-rosca .cs-arco{
      stroke-width: 10;
    }
    /* ======================================================================
       (1.1.66) O DEGRAU DE CARD ALTO BAIXOU DE 700px PARA 580px.

       POR QUE. Relato do Pedro em 10/09/2026, olhando a parede na TV: card grande com pouco
       conteudo fica com um vazio enorme. Em 2560x1440 as celulas de quatro cards ou mais tem
       605px de altura -- fisicamente 44cm numa TV de 85" -- e caiam no bucket de card BAIXO,
       porque o degrau de card alto exigia 700px. Elas recebiam o tratamento calibrado para as
       celulas de 425px de 1080p: rosca pequena, nome pequeno e nenhum grafico de ritmo.

       O LIMIAR SAI DA MARGEM, e nao de um numero redondo -- E ELE E CONTRA A CONTENT BOX.
       Esta e a pegadinha que custou uma medicao: `@container` com `container-type: size` compara
       contra a CAIXA DE CONTEUDO, nao contra a caixa de borda. A celula de 1440 com quatro cards
       ou mais mede 605px de borda e **579px de conteudo**, porque o card tem 12px de padding em
       cima e embaixo mais as bordas. A primeira escrita deste degrau usou 580px, olhando os
       605 -- e ele nao disparava, errando por UM pixel. Medido, nao deduzido.
       As alturas de CONTEUDO que as tres resolucoes produzem:
         2560x1440   n=1 a n=3: 1180     n>=4: 579
         1920x1080   n=1 a n=3:  820     n>=4: 399
         1366x768    n=1 a n=3:  508     n>=4: 243
       O vao a atravessar e (508, 579]. O 545 fica 37px acima do maior caso de 1366x768 e 34px
       abaixo do caso de 1440, que e a folga mais equilibrada que o vao permite -- e ela importa
       porque o cromo da janela varia de navegador para navegador.

       E ISTO NAO E MEDIA QUERY DISFARCADA: o gatilho e a ALTURA DA CELULA, que e a grandeza de
       que este degrau sempre falou. A celula ser alta porque a tela e grande e consequencia, nao
       premissa -- em 1080p com uma campanha a celula tem 862px e ja recebia o mesmo tratamento.
       ====================================================================== */
    /* (1.1.66) E OS QUATRO DEGRAUS DE CARD ALTO NAO VALEM NO APARELHO, pelo mesmo motivo que o
       `--tv-av` proprio dele: la a parede e LISTA QUE ROLA e a altura do card sai do CONTEUDO,
       enquanto na parede ela e imposta pela grade. Com a altura vindo do conteudo, um degrau por
       altura e circular: o card passa de 545px, ganha rosca 1,35x, nome 1,2x e o grafico de
       ritmo, isso o deixa mais alto ainda, e ele estoura.
       Medido em 1.1.66, na primeira escrita deste degrau sem a guarda: o card do aparelho foi de
       470px para 716px e transbordou 20px, com a fileira de rostos terminando FORA da caixa.
       Quem reprovou foi o `teste-vista-bolso`. Com 700px isto nunca aparecia porque nenhum card
       de aparelho chegava la. */
    @container card (min-height: 545px){
      html[data-tv]:not([data-aparelho]) .precs-campsaldo-rosca .cs-trilha,
      html[data-tv]:not([data-aparelho]) .precs-campsaldo-rosca .cs-arco{ stroke-width: 5; }
    }

    /* E a coluna do numero pode ENCOLHER: sem `min-width:0` o conteudo largo trava a celula da
       grade no tamanho intrinseco e o transbordo pinta por cima da coluna vizinha. Valia so para
       n=1; agora vale para toda densidade, pelo mesmo motivo do `--dig-k`. */
    html[data-tv] .precs-campsaldo-corpo,
    html[data-tv] .precs-campsaldo-lado{ min-width: 0; }

    /* O QUE SAI DE CENA QUANDO HA TRES OU MAIS CARDS, e a escolha e por acao.
       A barra de proporcao de listas zeradas e as duas linhas de rotulo acima dela respondem "o
       mailing desta campanha esta saudavel?", que e pergunta de planejamento -- se resolve
       sentado, na aba, olhando a modal de listas. Numa parede lotada o que resta de espaco vai
       para o NOME DA LISTA e o quanto falta nela, que e o que faz alguem levantar e repor mailing
       agora. O numero de listas com peso 0 continua, porque ele tambem pede acao imediata.

       n=3 FICA DE FORA junto com 1 e 2: ali o card tem altura inteira (607x862), ha espaco para
       tudo, e dar padding menor a ele deixava a caixa de consulta MAIOR que a de n=2 -- a escada
       de tamanho invertia (rosca de 217px em n=3 contra 213px em n=2). */
    /* (1.1.33) A ESCADA DE CONTEÚDO PASSOU A SAIR DA ALTURA DO CARD.
       Amarrada a `data-n`, ela errava nas duas pontas: em 720p um mural de dois cards tem células
       de 548px (menos que as de três cards em 1080p, que têm 862) e mostrava tudo; um mural de
       quatro em 720p tem células de 267px e mostrava conteúdo para 390px, estourando 122 a 166px
       em TODOS os cards. Quem sabe o que cabe é o card. */
    /* (1.1.33) CARD ALTO CRESCE, e a pergunta é sobre a altura DELE.
       Com uma, duas ou três campanhas o card tem os ~862px inteiros da parede em 1080p, e a
       medida mestra saía em 156px — um anel pequeno no meio de meio metro de tela. Subir o
       coeficiente global não serve: ele é o mesmo que governa as células de 425px de um mural de
       quatro, onde 96px já estouram. `@container` separa os dois casos pela única coisa que
       importa, que é o espaço que o card tem.
       O `1.35` sai da medição: 156 x 1,35 = 211px, que é o ponto em que o anel e o número leem
       como um objeto só num card dessa altura sem invadir a coluna das listas. */
    @container card (min-height: 545px){
      html[data-tv]:not([data-aparelho]) .precs-campsaldo > *{
        --tv-rosca: calc(clamp(76px, min(19cqh, 36cqi), 250px) * var(--tv-esc, 1) * 1.35);
        --tv-nome: calc(clamp(1.05rem, 3.4cqh, 2.2rem) * var(--tv-esc, 1) * 1.2);
      }
      /* (1.1.66) E O NÚMERO DA API ACOMPANHA, com o MESMO 1,35. Sem esta regra a rosca do card
         cresceria em 1440 e o número da API ficaria onde estava, e a razão de 0,89 que o bloco
         de tokens dele acabou de fixar viraria 0,65 -- a mesma divergência de escalas que esta
         leva consertou, só ao contrário. */
      /* E O TETO DE LARGURA VEM JUNTO. Esquece-lo aqui foi o defeito medido: o teto entrou na
         declaracao base e NAO nesta, entao em 2560x1440 -- que e justamente onde o degrau
         dispara -- o numero do consumo saia em 78,9px contra os 75,1px do card mais urgente.
         Duas declaracoes do mesmo token so estao certas quando as duas carregam as duas regras. */
      html[data-tv]:not([data-aparelho]) .precs-tv-api > *{
        --tv-api-num: min(calc(clamp(76px, min(19cqh, 36cqi), 250px) * .55 * var(--tv-esc, 1) * 1.35),
                          11.53cqi);
      }
    }
    @container card (max-height: 640px){
      /* A barra de proporção de listas zeradas e os rótulos dela respondem "o mailing está
         saudável", que é pergunta de planejamento e se resolve sentado na aba. Sai primeiro. */
      html[data-tv] .precs-campsaldo-listas-topo,
      html[data-tv] .precs-campsaldo-listas-barra{ display: none; }
    }
    @container card (max-height: 330px){
      /* CÉLULA MUITO CURTA (720p com quatro campanhas ou mais): o bloco de listas inteiro sai.
         Não é perda de informação escondida — a contagem "N lista(s) com peso 0 · M números
         parados" fica, e o nome do mailing nessa altura sairia a 12px, que a 4 metros são 6
         minutos de arco: ilegível. Mostrar ilegível é pior que declarar a ausência. */
      html[data-tv] .precs-campsaldo-listas{ display: none; }
      /* E a linha de estados por campanha também: o selo "N op." do topo já diz quantos são. */
      html[data-tv] .precs-campsaldo-estados{ display: none; }
    }
    @container card (max-height: 300px){
      /* CÉLULA MUITO CURTA E ESTREITA (1366x768 com seis campanhas: 418x267): a pastilha do
         operador perde o NOME e fica só o rosto. Quatro pastilhas com nome nessa largura quebram
         em duas fileiras, e a segunda fileira custa ~55px de uma célula que tem 243 úteis — o
         laço de encaixe então esconde operador, que é justamente o que não pode acontecer.
         Rosto sem nome ainda responde "quem está nessa campanha": a foto é o que se reconhece a
         quatro metros, o nome a 12px não seria legível de qualquer forma, e o balão devolve o
         nome para quem chegar perto. */
      html[data-tv] .precs-tv-op b{ display: none; }
    }
    /* ======================================================================
       DENSIDADE AINDA MEXE EM CAIXA, E SÓ EM CAIXA — a lista completa, porque a versão anterior
       deste comentário dizia "são os ÚNICOS lugares" e estava desatualizada em sete regras.

       Comentário que mente é pior que px cru: foi o defeito que a leva 1.1.33 já pagou uma vez,
       quando um comentário falava de `8ch` e a declaração era `10ch`. Então aqui vai a lista
       inteira, e ela se mantém junto com as regras:

         · o respiro do bloco de listas e o padding do card (logo abaixo);
         · o `margin`/`padding` de 6px do bloco da TV a partir de quatro cards;
         · no mural cheio (`[data-n="muitos"]`): o padding do card, o padding da linha de lista,
           e as margens do topo, do corpo e do bloco da TV.

       TODAS ELAS ESCREVEM ESPAÇO, e nenhuma escreve tamanho. É essa a linha: espaço entre blocos
       é o que se corta para caber mais informação, e a caixa ali É a informação (quantas linhas
       de lista entram). Tamanho de tipo e de rosca saem dos tokens `--tv-*`, sempre — as três
       regras que violavam isso saíram na leva 1.1.66, e o bloco delas conta a medição.
       ====================================================================== */
    html[data-tv] .precs-sup-tv-parede:not([data-n="1"]):not([data-n="2"]):not([data-n="3"]) .precs-campsaldo-listas{
      margin-top: var(--space-2); padding-top: var(--space-2);
    }
    html[data-tv] .precs-sup-tv-parede:not([data-n="1"]):not([data-n="2"]):not([data-n="3"]) .precs-campsaldo{
      padding: var(--space-3);
    }
    html[data-tv] .precs-sup-tv-parede:not([data-n="1"]):not([data-n="2"]):not([data-n="3"]) .precs-tv-extra{
      margin-top: 6px; padding-top: 6px;
    }
    /* O estado escrito por operador sai a partir de tres cards: com a pastilha menor, a palavra
       nao cabe ao lado do nome sem quebrar a fileira. O anel colorido continua dizendo o estado,
       e o balao devolve a palavra para quem chegar perto. */
    html[data-tv] .precs-sup-tv-parede:not([data-n="1"]):not([data-n="2"]) .precs-tv-op i{
      display: none;
    }

    /* ---- A FAIXA CRÍTICA no mural cheio ----
       `1 / -1` para TODAS as críticas era o desenho da primeira volta, e ele não fecha na conta:
       três campanhas críticas ao vivo dariam três linhas de ~520px numa tela que tem 870 para o
       mural inteiro, e a terceira cairia abaixo da dobra — numa TV, isso é a mesma coisa que não
       existir. Então a largura dobrada fica com UMA: a primeira da ordem de urgência. As outras
       críticas continuam se distinguindo por tint de fundo, faixa lateral grossa e tipo grande.
       Com até quatro cards nada disso se aplica: lá a grade já é de largura inteira. */
    /* A HIERARQUIA VIRA ÁREA, e só onde sobra célula: com 5 cards em 3x2 e 7 em 4x2 fica uma
       célula vaga, e é o mais urgente que a toma. Em 2, 4, 6 e 8 todas as células são iguais, e
       esticar um deles abriria vão. */
    html[data-tv] .precs-sup-tv-parede[data-n="5"] .tv-primeiro,
    html[data-tv] .precs-sup-tv-parede[data-n="7"] .tv-primeiro{ grid-column: span 2; }
    html[data-tv] .precs-sup-tv-parede[data-n="muitos"] .tv-critica,
    html[data-tv] .precs-sup-tv-parede[data-n="muitos"] .tv-atencao{ grid-row: span 1; }
    /* ======================================================================
       (1.1.66) B4 — AQUI MORAVAM TRÊS REGRAS DE DENSIDADE QUE ESCREVIAM TAMANHO, e elas
       desligavam o motor de tamanho no arranjo mais cheio da parede.

       Elas cravavam `width: 100px; height: 100px` na rosca, `1.25rem` no texto dela e `.9rem` na
       linha de lista, sob `[data-n="muitos"]` (oito células ou mais). Medido no mural cheio, com
       as mesmas onze campanhas nas duas telas:

         1366x768   célula 314x269   rosca 100px   texto da rosca 20px   lista 14,4px
         2560x1440  célula 613x594   rosca 100px   texto da rosca 20px   lista 14,4px

       A célula mais que quadruplica de área e a rosca não se move. Ao lado dela, no mesmo card, o
       NÚMERO vai de 36,3px para 75,1px, porque ele sai do token. Ou seja: no arranjo que a TV de
       85" mais vai mostrar, o anel e o número eram governados por dois sistemas diferentes, e o
       anel estava congelado no valor de um monitor de 1080p.

       A REGRA DA CASA JÁ DIZIA ISSO, e está escrita no bloco de tokens deste arquivo: "regra de
       densidade escreve TOKEN (`--tv-*`), nunca propriedade de caixa". As três saem inteiras: o
       token `--tv-rosca` já encolhe sozinho em célula pequena (ele bate no piso de 76px em 1080p
       com quatro cards), então a intenção original ("com cinco ou mais ao vivo, cabe menos por
       card") continua atendida, agora pela medida certa.

       O `padding: 2px 0` da linha de lista FICA, e mudou de lugar para baixo, junto das outras
       regras de ESPAÇO: espaço entre linhas de lista muda quantas cabem, então ali a caixa é a
       informação. É a distinção que separa o que sai do que fica.
       ====================================================================== */
    html[data-tv] .precs-sup-tv-parede[data-n="muitos"] .precs-camplista{ padding: 2px 0; }
    html[data-tv] .precs-sup-tv-parede[data-n="muitos"] .precs-campsaldo{
      padding: var(--space-2) var(--space-3); }
    /* O RESPIRO ENTRE BLOCOS É O QUE SOBRA PARA CORTAR. Medido: com cinco campanhas ao vivo eram
       80px de margem por card, e três linhas de mural passavam da tela. Aqui o card fica apertado
       de propósito — o alternativo era jogar a terceira linha abaixo da dobra, e numa TV isso é o
       mesmo que apagar a informação. */
    html[data-tv] [data-n="muitos"] .precs-campsaldo-topo{ margin-bottom: 4px; }
    html[data-tv] [data-n="muitos"] .precs-campsaldo-corpo{ margin: 4px 0; }
    html[data-tv] [data-n="muitos"] .precs-tv-extra{ margin-top: 4px; padding-top: 4px; }
    /* "NÚMEROS A DISCAR" embaixo do número sai no mural cheio: com o rótulo da campanha logo
       acima e a rosca de consumo do lado, ninguém confunde aquele número com outra coisa. A
       tranquila já fazia isso desde a primeira volta, pelo mesmo motivo. */
    html[data-tv] [data-n="muitos"] .precs-campsaldo-num span{ display: none; }
    /* (1.1.22) A TROCA DE FAIXA ACOMPANHA O FLIP. A posição do card já desliza (FLIP), mas a
       forma (filete e fundo) trocava num quadro dentro do movimento. Borda e fundo interpolam;
       o FLIP escreve transition inline temporária e a restaura, então os dois convivem. */
    html[data-tv] .precs-campsaldo{
      transition: border-color var(--dur-1) ease, box-shadow var(--dur-1) ease,
        border-left-width var(--dur-3) ease, background var(--dur-3) ease;
    }
    /* Bloco SEPARADO e de corte fácil: o TIPO acompanhando a troca de faixa. Se a máquina da
       parede engasgar com reflow de texto animado, remova SÓ este bloco — filete e fundo acima
       já tiram o pior do corte. */
    html[data-tv] .precs-campsaldo-nome,
    html[data-tv] .precs-campsaldo-num b,
    html[data-tv] .precs-campsaldo-min{ transition: font-size var(--dur-3) ease; }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .precs-campsaldo{ transition: none; }
      html[data-tv] .precs-campsaldo-nome,
      html[data-tv] .precs-campsaldo-num b,
      html[data-tv] .precs-campsaldo-min{ transition: none; }
    }
    /* (1.1.33) A FAIXA MUDA COR E FILETE, NUNCA TAMANHO.
       Onze regras de `font-size` por faixa sobreviveram à troca do motor de tamanho, e uma delas
       era uma inversão de anos: em mural cheio, `tv-atencao` tinha número de 3,2rem contra 3rem
       da `tv-critica` — a campanha MENOS urgente com o número maior. Com o tamanho saindo só dos
       tokens `--tv-*`, essa classe de bug deixa de ser possível: não há onde escrever tamanho por
       faixa. O que a faixa diz, ela diz por cor, por filete e por área (o primeiro da urgência
       ganha duas colunas em n=5 e n=7). */
    html[data-tv] .tv-critica{ border-left-width: 10px; }
    /* (1.1.34) A CAMPANHA QUE PERDEU O PULSO PELA TRAVA. Acima de três acabando ao mesmo tempo só
       a primeira pulsa (ver `PRECS_SUP_TV_PULSO_MAX`), e esta classe é o que garante que as outras
       não fiquem MENOS visíveis por isso: elas ganham a borda esquerda no vermelho do alerta, sem
       animação nenhuma. O aviso fica; o movimento sai. */
    html[data-tv] .tv-acabando-mudo{
      border-left-color: var(--accent-red); border-left-width: 10px;
      /* (1.1.65) `animation: none` ENTROU AQUI PORQUE A TRAVA NAO ESTAVA TRAVANDO.
         O comentario acima promete "sem animacao nenhuma" desde a 1.1.34, e a regra nao cumpria:
         calar um card e tirar a classe `tv-acabando`, e tirar `tv-acabando` deixa a declaracao
         de `animation` cair na REGRA BASE do card. Uma campanha SECA carrega
         `.precs-campsaldo-seca` (escrita em `c.nivel`, ver app.js), e essa classe declara
         `animation: precsSecaPulso 2.2s infinite`.
         Ou seja: acima de tres acabando, uma seca silenciada voltava a pulsar -- a 2,2s, contra
         os 2,4s de quem legitimamente ficou com o pulso. Duas fases proximas batem a cada 26,4s,
         que e exatamente "cinco bordas pulsando em contrafase sao uma TELA piscando", o defeito
         que a trava existe para impedir, acontecendo por dentro dela.
         Nao aparecia no teste porque o cenario da trava usa `resta = 40` em todas, e `seca` exige
         `resta === 0`; e porque a assercao contava a classe no DOM, nao `animationName`. */
      animation: none;
    }
    html[data-tv] .tv-atencao{ border-left-width: 7px; }
    /* (1.1.33) AQUI MORAVA UM AGRUPAMENTO ERRADO, e ele era a causa raiz do card crítico
       estourando a célula.
       Três seletores sem relação nenhuma dividiam um `font-size: 3.8rem`: o número do primeiro
       card em mural cheio (que queria 3,8rem), o RÓTULO "números a discar" do card crítico e a
       PROJEÇÃO do card crítico. Os dois últimos herdaram 61px de um valor que não era para eles —
       medido: `.precs-campsaldo-proj` a 60,8px num card de 425px de altura, com o corpo inteiro
       a 238px. Era isso, e não a calibragem dos coeficientes, que estourava a célula em 720p com
       quatro campanhas e em 1080p com cinco.
       Agrupar seletores por valor igual em vez de por intenção igual é a armadilha: no dia em que
       um dos três precisa de outro tamanho, os outros dois vão junto sem ninguém notar. Todos
       passam a sair dos tokens `--tv-*`, e a ênfase da crítica volta pelo caminho certo abaixo. */
    /* A CRÍTICA GRITA OS MINUTOS, e é só isso que ela aumenta: o número que decide se alguém
       levanta agora. Sai do token da projeção, então acompanha a densidade em vez de ser um rem
       cravado. */
    html[data-tv] .tv-critica .precs-campsaldo-min{ font-size: calc(var(--tv-proj) * 1.6); }
    html[data-tv] .tv-atencao .precs-campsaldo-min{ font-size: calc(var(--tv-proj) * 1.3); }
    /* O tint de fundo na crítica: é a única do mural que ganha área de cor, e é o que a faz ser
       vista de longe antes de qualquer texto ser lido. */
    html[data-tv] .tv-critica{
      background: color-mix(in srgb, var(--accent-red) 9%, var(--bg-secondary));
    }
    html[data-tv] .tv-atencao{
      background: color-mix(in srgb, var(--accent-orange) 7%, var(--bg-secondary));
    }

    /* ---- A FAIXA TRANQUILA: uma linha, sem gráfico ----
       Some a rosca, a barra de listas, os estados e o delta. O que resta responde a única pergunta
       que uma campanha tranquila levanta numa parede: "quanto ainda tem?". É o inverso do que a 7ª
       volta fez ao promover a linha de 20px a card, e a razão é outra: lá a linha era a ÚNICA
       forma, aqui ela é o fundo de uma hierarquia de três. */
    /* ======================================================================
       (1.1.33) A HIERARQUIA DA TRANQUILA MUDOU DE EIXO: ALTURA -> CONTEÚDO.

       Ela era uma LINHA FINA. `height:auto; align-self:start`, sem rosca, sem listas, sem
       estados, com o nome à esquerda e o saldo à direita — 20% da altura de um card grande. O
       argumento era bom: cinco tranquilas ocupando card inteiro é meia parede gasta com o que
       não pede ação.

       O que derrubou o desenho: com as linhas da grade em `1fr` numa caixa de altura exata (a
       cadeia de flex desta versão), um card que NÃO preenche a própria linha é exatamente o
       buraco que os prints de cinco e seis campanhas de 26/08/2026 mostraram. Não dá para ter as
       duas coisas — ou a linha da grade se ajusta ao card (e sobra vão), ou o card preenche a
       linha.

       Então a tranquila ocupa a mesma célula e mostra MENOS BLOCOS. A partir de cinco cards ela
       perde a linha de estados e o delta: os dois são texto secundário, e o que responde "esta
       pode esperar" é o número com a projeção do lado, que fica. A rosca FICA, ao contrário do
       desenho antigo: ela é a leitura mais rápida do card e a célula de 610x429 tem espaço para
       ela.

       Abaixo de cinco não se esconde nada: sem crítica disputando espaço, enxugar metade do card
       é enxugar por nada — foi o que a tela de produção mostrou com uma campanha ao vivo.
       ====================================================================== */
    html[data-tv] [data-n="5"] .tv-calma .precs-campsaldo-estados,
    html[data-tv] [data-n="5"] .tv-calma .precs-campsaldo-delta,
    html[data-tv] [data-n="6"] .tv-calma .precs-campsaldo-estados,
    html[data-tv] [data-n="6"] .tv-calma .precs-campsaldo-delta,
    html[data-tv] [data-n="7"] .tv-calma .precs-campsaldo-estados,
    html[data-tv] [data-n="7"] .tv-calma .precs-campsaldo-delta,
    html[data-tv] [data-n="muitos"] .tv-calma .precs-campsaldo-estados,
    html[data-tv] [data-n="muitos"] .tv-calma .precs-campsaldo-delta{ display: none; }
    /* ======================================================================
       (1.1.66) A LISTA DA TRANQUILA SAIU DAQUI, e o motivo é que a premissa desta regra deixou
       de ser verdade.

       Ela dizia: "no mural de oito ou mais, onde a célula cai para ~455x300, ela perde também as
       listas". O número estava certo quando `muitos` significava OITO CAMPANHAS em 1080p. Hoje
       `muitos` conta CÉLULAS, e a célula do consumo da API é uma delas — então sete campanhas
       mais o bloco de consumo já entram aqui, com célula de 450x421 e 261px de folga por card.

       Medido no arranjo do print do Pedro de 11/09/2026 (sete campanhas tranquilas mais o bloco):

         1920x1080  card 421 (conteúdo 397)  folga 261px   custo de abrir uma linha: 44px
         2560x1440  card 594 (conteúdo 570)  folga 295px   custo de abrir uma linha: 44px

       E o efeito era duplo, porque esta regra também travava a passada que abre lista por
       medição: ela pergunta `bloco.offsetParent === null` para respeitar o degrau de célula
       curta, e um `display: none` escrito aqui é indistinguível daquele. Ou seja, a decisão do
       Pedro de 10/09 ("mostrar quando couber") não alcançava exatamente os cards em que havia
       espaço de sobra.

       QUEM RESPONDE ESTA PERGUNTA AGORA é o degrau `@container card (max-height: 330px)`, que
       já existe e diz a MESMA coisa pela medida certa: "célula muito curta, o bloco de listas
       inteiro sai, porque o nome do mailing nessa altura sairia a 12px". Num mural realmente
       cheio (oito campanhas em 1080p, célula ~455x300) ele dispara e o resultado é igual ao de
       antes; num mural de sete campanhas mais o bloco de consumo ele não dispara, e a lista
       aparece — que é o que a folga de 261px sempre disse que cabia.

       O BOTÃO E A LINHA DE LIGAÇÕES CONTINUAM SAINDO, sem mudança: ninguém clica numa TV, e a
       linha de ligações é o texto secundário que a tranquila cede primeiro.
       ====================================================================== */
    html[data-tv] [data-n="muitos"] .tv-calma .precs-camplista-btn,
    html[data-tv] [data-n="muitos"] .tv-calma .precs-tv-lig{ display: none; }
    /* O número e a projeção lado a lado: ler "8.340" e "~70 min" na mesma varredura do olho é o
       que responde "esta pode esperar". Vale em toda densidade. */
    html[data-tv] .tv-calma .precs-campsaldo-num{ display: flex; align-items: baseline;
      gap: var(--space-2); }

    /* ======================================================================
       (1.1.35) O CURSOR DA NAVEGAÇÃO: O CARD LEVITA.

       RELATO DO PEDRO, 27/08/2026, com print de duas campanhas: "ao selecionar uma campanha parece
       que as duas estão selecionadas, não ficou claro só com a bordinha preenchida; pode fazer o
       card levantar ou levitar ao selecionar?". Ele está certo, e a razão é mensurável: numa parede
       de dois cards, cada um já tem borda de 1px e filete lateral dourado por ser tranquilo, então
       um anel de 3px a quatro metros não é diferença de categoria, é diferença de grau.

       A VERSÃO ANTERIOR RECUSAVA TRANSFORM de propósito, porque a navegação lia
       `getBoundingClientRect` e transform contamina rect. Isso mudou junto: `precsSupTvNavGrade`
       passou a ler `offsetLeft`/`offsetTop`, que são layout e ignoram transform (ver o comentário
       longo lá). Com a medição imune, levitar é seguro — e o laço de encaixe nunca foi afetado,
       porque ele mede `offsetHeight` de filhos e `clientHeight - scrollHeight`, os dois imunes.

       TRÊS SINAIS SOMADOS, e é a soma que resolve a quatro metros: sobe 10px, cresce 2%, e ganha
       sombra projetada larga. A sombra é o que faz o olho ler ALTURA em vez de tamanho — sem ela,
       um card 2% maior lê como "card maior", não como "card na frente". O anel dourado fica e
       engorda para 4px.

       SEM PULSO. O cursor pode ficar 45 segundos na tela, e movimento perpétuo numa parede é
       desgaste (e é o pior padrão de burn-in que esta tela pode ter). A levitação é um estado, não
       uma animação: ela acontece na transição de entrada e para.
       ====================================================================== */
    /* (1.1.66) O SELETOR DEIXOU DE DIZER `.precs-campsaldo`, e passou a ser so `.tv-cursor` dentro
       da parede. O motivo e que o cursor deixou de pousar so em campanha: o bloco do consumo da
       API virou celula navegavel nesta leva, e sem isto a seta chegava nele e nada acendia -- o
       destaque existia no estado e nao na tela.
       Escrever `.tv-cursor` e seguro porque a classe so e escrita por `precsSupTvNavPor`, e so em
       celula da parede. E evita a alternativa, que seria listar `.precs-campsaldo.tv-cursor,
       .precs-tv-api.tv-cursor` em oito lugares e esquecer um no dia em que entrar a terceira. */
    html[data-tv] .precs-sup-tv-parede .tv-cursor{
      outline: 4px solid var(--precs-gold-hi, #FFC95B);
      outline-offset: 5px;
      opacity: 1;
      transform: translateY(-10px) scale(1.02);
      /* O CRESCIMENTO VAI PARA BAIXO, e isto é para o card da fileira de cima não invadir o
         cabeçalho. Com a origem no centro, os 2% se dividem entre topo e base e o card subia 18px
         no total; com a origem no TOPO, a borda de cima só se move pelos 10px da levitação e os 2%
         inteiros descem para dentro da parede. O rodapé é irmão com margem, então o excedente de
         baixo cai na margem e não sobre o relógio. */
      transform-origin: center top;
      /* A SOMBRA É O QUE VENDE A ALTURA: larga, escura e deslocada para baixo, mais um halo dourado
         curto que costura o anel ao card. */
      box-shadow: 0 22px 44px -8px rgba(0, 0, 0, .75),
                  0 0 0 1px color-mix(in srgb, var(--precs-gold-hi, #FFC95B) 35%, transparent);
      /* Acima dos vizinhos: sem isto o card de cima cobriria a beirada de quem levantou. */
      z-index: 4;
    }
    /* ======================================================================
       (1.1.35) A CÉLULA DO QUE FICOU FORA DA PAREDE.

       Acima de oito campanhas, a cauda tranquila sai do mural e vira esta célula. Ela é uma CÉLULA
       DA GRADE como as outras (ocupa um lugar, tem a mesma altura), mas de propósito ela não parece
       um card: sem moldura cheia, sem rosca, tipo menor. A diferença de forma é o que diz "isto não
       é uma campanha, é uma contagem" sem precisar de legenda.

       Ela também não é navegável e não é medida pelo laço de encaixe (não tem `data-camp`), então
       ela nunca decide o tamanho do tipo das outras. */
    /* ======================================================================
       (1.1.66) B3 — ELA VIROU CONTAINER, E ISSO MUDA COMPORTAMENTO.

       O defeito estava confessado em comentario desde a leva do consumo ("o `.precs-tv-resto` ao
       lado NAO e container"), e so agora foi medido. Sem `container-type`, os `cqh` dos filhos
       resolviam contra a JANELA, entao todos batiam no teto do proprio `clamp` em qualquer tela.
       Medido com onze campanhas, antes deste conserto:

         2560x1440  celula 613x594  numero do resumo 64px   numero do card 75,1px
         1920x1080  celula 453x421  numero do resumo 64px   numero do card 47,1px
         1366x768   celula 314x269  numero do resumo 64px   numero do card 36,3px
         1920x720   celula 453x245  numero do resumo 64px   numero do card 44,3px

       Duas coisas erradas de uma vez. A celula nao respondia ao proprio tamanho, e nas duas
       resolucoes pequenas UMA CONTAGEM ficava com o numero MAIOR que o das campanhas -- a mesma
       inversao de hierarquia que a leva anterior consertou no bloco do consumo, pela mesma causa
       estrutural: dois numeros governados por medidas diferentes, com as curvas se cruzando.

       E o `@container card (max-height: 300px)` logo abaixo, que existe para esconder a nota em
       celula baixa, nunca casava: medido, a nota estava VISIVEL na celula de 245px de 720p.

       O NUMERO PASSA A SE PENDURAR NA MESMA EXPRESSAO da rosca, como o do card (0,62) e o do
       consumo (0,55) ja fazem. O coeficiente 0,45 poe a contagem abaixo dos dois em toda
       densidade e em toda resolucao, POR CONSTRUCAO, e nao por calibragem que envelhece.
       ====================================================================== */
    html[data-tv] .precs-tv-resto b{
      font-family: 'JetBrains Mono', monospace; font-weight: 700; line-height: 1;
      /* O SEGUNDO TETO E DE LARGURA, e esquece-lo foi o defeito que o teste pegou: `--tv-num`
         do card e `min(calc(rosca * .62), 13cqi)`, e em celula ESTREITA quem manda e o `13cqi`.
         Medido com nove celulas em 1366x768: o numero do card caiu para 28,9px e este, sem o
         teto de largura, subiu para 32,1 -- a inversao voltando pela outra porta. Os dois tetos
         tem de ser espelhados pela MESMA razao: 13 x 0,45/0,62 = 9,44. */
      font-size: min(calc(clamp(76px, min(19cqh, 36cqi), 250px) * .45 * var(--tv-esc, 1)),
                     9.44cqi);
      color: var(--text-secondary);
    }
    html[data-tv] .precs-tv-resto-rot{
      font-size: clamp(.62rem, 3cqh, 1.3rem); font-weight: 700; text-transform: uppercase;
      letter-spacing: var(--ls-caps); color: var(--precs-gold-title, #D3B26B);
    }
    html[data-tv] .precs-tv-resto-num{
      font-family: 'JetBrains Mono', monospace;
      font-size: clamp(.7rem, 3.2cqh, 1.4rem); color: var(--text-primary);
    }
    html[data-tv] .precs-tv-resto-ops,
    html[data-tv] .precs-tv-resto-nota{
      font-size: clamp(.58rem, 2.4cqh, 1.1rem); color: var(--text-muted);
    }
    /* A nota sai nas células baixas: com quatro colunas e três fileiras ela seria a primeira coisa
       ilegível, e o rótulo acima já diz o essencial. */
    @container card (max-height: 300px){
      html[data-tv] .precs-tv-resto-nota{ display: none; }
    }

    /* ======================================================================
       (1.1.63) A CÉLULA DO CONSUMO DA API PIPEDRIVE.

       Ela era uma linha no rodapé (1.1.62). O Pedro pediu em 10/09/2026 um espaço reservado com
       número grande, para ler a metros na TV de 85" da sala do Operacional, e o preço está
       declarado no JS: a célula ocupa uma vaga do mural.

       MEDIDOR, E NÃO ROSCA. A decisão é do painel de onde o número vem e está escrita lá:
       "percentual contra um limite é medidor, nunca pizza". A rosca desta parede mede saldo
       restante de mailing, que é a outra pergunta — e ter as duas formas na mesma tela é o que
       impede confundir as duas.

       ELA É CONTAINER, e o `.precs-tv-resto` ao lado NÃO é (defeito registrado na leva: os `cqh`
       dos filhos dele resolvem contra o viewport, e o degrau de `max-height` logo acima nunca
       casa). Aqui o `container-type: size` está declarado, então o `@container` do fim desta
       região funciona de verdade.

       E OS TOKENS MORAM NOS FILHOS, nunca no próprio bloco: um container não pode consultar o
       próprio tamanho para decidir o próprio tamanho. É a mesma razão escrita no bloco de tokens
       do card, mais acima neste arquivo. */
    /* (1.1.64) A CÉLULA ENCHE, e antes ela flutuava. Relato do Pedro em 10/09/2026, comparando
       com a tela do painel de consumo: "dá pra melhorar e muito". Medido no print: a célula tinha
       425px de altura com cerca de 150px de conteúdo centrado, e o resto era vão.
       `justify-content: space-between` com `align-items: stretch` põe o rótulo no topo, o número
       no meio e a legenda no pé, que é a composição do cartão que ele elogiou. */
    /* ======================================================================
       (1.1.66) A CELULA PASSOU A TER FORMA DE CARD, COM FILETE DOURADO.

       RELATO DO PEDRO, 10/09/2026, olhando a parede na TV: "o card do API PIPEDRIVE se distingue
       muito dos cards de campanha... hoje so os - - - - parece que o card esta morto".

       O TRACEJADO NAO ESTAVA CUMPRINDO O PAPEL DELE, e isso foi medido antes de trocar. A ideia
       era que a borda dissesse "isto nao e uma campanha". A 4 metros, numa TV de 85":
         · o tracejado tem 1px, ou 0,63 minuto de arco, num contraste de 1,13:1 contra o fundo --
           e o PADRAO do tracejado, que e o que carrega o sentido, e menor ainda;
         · o fundo `--bg-secondary` contra o `--bg-tertiary` do card da 1,08:1.
       Ou seja: os dois sinais somavam nada na distancia de leitura, e o que ja fazia a celula ler
       como outra coisa era o CONTEUDO (rotulo dourado em caixa alta, numero grande, barra
       colorida, selo). O tracejado so entregava o ar de provisorio.

       O FILETE E O UNICO SINAL DE FORMA QUE SE VE A 4 METROS nesta parede: 4, 7 ou 10px, ou seja
       2,5 a 6,3 minutos de arco. Ele ja e a lingua da tela. Entao a celula passa a falar essa
       lingua: mesma borda, mesmo fundo e mesmo raio dos cards, com o filete no OURO DA MARCA.

       POR QUE OURO E NAO VERDE. Verde, laranja e vermelho sao a escala de URGENCIA das campanhas
       nesta tela, e desde 1.1.66 tambem a cor da barra de consumo de mailing. Um filete verde aqui
       entraria naquela escala e o bloco passaria a dizer "campanha tranquila", que e o que ele
       nao e. O ouro e a cor que a parede ja usa para o que nao e urgencia de mailing: o rotulo
       deste proprio bloco, o resumo "+N tranquilas", a barra de ligacoes. O filete de 4px e a
       espessura BASE do card, a mesma da campanha sem urgencia -- a celula entra na fileira sem
       fingir que pede acao.
       ====================================================================== */
    /* ======================================================================
       (1.1.66) B5 — A FORMA DA "CELULA QUE NAO E CARD", NUMA DECLARACAO SO.

       As duas celulas da grade que nao sao campanha (o resumo "+N tranquilas" e o consumo da API)
       compartilhavam a decisao de forma e divergiam em QUATRO coisas sem motivo escrito: padding
       (`--space-3` contra `--space-4`), alfa da borda (80% contra cheia), fundo (`bg-tertiary`
       a 45% contra opaco) e gap (4px contra `--space-2`). Divergencia por acidente, e a leva
       passada ja tinha medido que o tracejado e o fundo esmaecido NAO SE VEEM a 4 metros:
       1px a 1,13:1 de contraste, e 1,08:1 entre os dois fundos.

       ENTAO A FORMA VIRA UMA SO, e o que separa as duas passa a ser o que se ve de longe: o
       FILETE (cinza de estrutura no resumo, ouro da marca no consumo) e o CONTEUDO. As duas
       diferencas que sobrem estao declaradas logo abaixo, cada uma com o motivo.
       ====================================================================== */
    html[data-tv] .precs-tv-resto,
    html[data-tv] .precs-tv-api{
      container-type: size; container-name: card;
      display: flex; flex-direction: column;
      gap: var(--space-2); min-width: 0; min-height: 0; overflow: hidden;
      padding: var(--space-4);
      border: 1px solid var(--border-color); border-radius: var(--radius-card);
      border-left: 4px solid var(--border-color);
      background: var(--bg-tertiary);
    }
    /* O RESUMO CENTRA, e o motivo ja estava escrito: ele e uma contagem de quatro linhas curtas,
       e centrado ele le como placa. O consumo empilha do topo porque tem sete filhos e uma ordem
       de leitura (rotulo, numero, barra, legenda, selo). */
    html[data-tv] .precs-tv-resto{
      align-items: center; justify-content: center; text-align: center;
    }
    /* O CONSUMO FICA COM O FILETE DOURADO (ver o bloco acima), estica os filhos porque a barra
       de composicao precisa da largura toda, e mantem o `cursor: help` do titulo explicativo. */
    html[data-tv] .precs-tv-api{
      align-items: stretch; justify-content: flex-start;
      border-left-color: var(--precs-gold-forma);
      cursor: help;
    }
    /* (1.1.66) O `space-between` SAIU DAQUI, e o carimbo de idade e que passou a empurrar.
       Com sete filhos e `space-between` numa celula alta, a celula distribuia o vazio ENTRE os
       blocos: o numero, o absoluto, a barra, a legenda e o selo ficavam separados por buracos, e
       o selo `FOLGA` flutuava sozinho no meio do nada. Era o efeito que o Pedro leu como "card
       morto" tanto quanto o tracejado.
       Agora o conteudo se agrupa no topo, como no card de campanha, e so a IDADE vai para o pe --
       ela e meta-informacao sobre o dado, e o lugar dela e o rodape do bloco, do mesmo jeito que
       o carimbo "saldo lido as" mora no rodape da parede. */
    html[data-tv] .precs-tv-api-idade{ margin-top: auto; }
    /* O PISO DE 1,75rem É EMPRESTADO DO NÚMERO DO SALDO, e é o argumento mais forte disponível:
       aquele piso foi calibrado em minutos de arco para leitura a quatro metros (ver a tabela de
       acuidade mais acima). O número da API nunca desce abaixo do piso já aceito para o número
       que mais importa na tela.

       E O TETO DE 5rem É DE HIERARQUIA, não de legibilidade. As campanhas são o assunto da
       parede, então este número fica ABAIXO do número do card em toda densidade: 78px contra
       102px na célula de 922x870, 39px contra 50px na de 455x429, e o piso contra 47px no pior
       caso de 720p. `min(cqh, cqi)` com os dois termos pelo mesmo motivo da rosca: a altura
       mantém a proporção e a largura impede que o número atravesse uma coluna estreita. */
    html[data-tv] .precs-tv-api > *{
      /* ====================================================================
         (1.1.66) O NÚMERO DA API PERDEU A ESCALA PRÓPRIA E PASSOU A PEDIR EMPRESTADA A DO CARD.

         O QUE ESTAVA ERRADO, e o comentário de 1.1.64 (abaixo, preservado) afirmava o contrário:
         ele dizia "com 17cqh a célula de 610x429 dá 73px contra os 79px do número do card", e
         concluía que "o assunto da parede continua sendo a campanha". Medido na parede de
         produção em 10/09/2026, com quatro campanhas mais o bloco:

              1920x1080   API 66px  contra  card 47px    -> a API é 1,40x MAIOR
              2560x1440   API 97px  contra  card 92px    -> a API é 1,05x MAIOR

         A CAUSA E ESTRUTURAL, e é por isso que ajustar o coeficiente não resolveria. Os dois
         números eram governados por medidas DIFERENTES: o do card é 62% da ROSCA, e a rosca tem
         piso de 76px, então em 1080p com quatro cards ela bate no piso e o número fica preso em
         47px; o da API não se pendurava em nada e seguia subindo com a altura da célula. As duas
         curvas se cruzam, e onde elas cruzam a hierarquia inverte. Um coeficiente menor só moveria
         o ponto de cruzamento.

         O CONSERTO É PENDURAR OS DOIS NA MESMA MEDIDA. A expressão abaixo é a da rosca, byte a
         byte, multiplicada por 0,55 -- contra os 0,62 que o número do card usa. Assim a razão
         entre os dois é CONSTANTE por construção (0,55/0,62 = 0,89), em toda densidade e em toda
         resolução, e deixa de depender de onde duas curvas se cruzam.
         O piso vem de graça: o piso de 76px da rosca dá 41,8px de número, acima dos 28px que a
         régua de minutos de arco pede (ver a tabela de acuidade no topo deste arquivo).
         E o degrau de card alto vale para ele também, logo abaixo, senão em 1440 a rosca do card
         cresceria 1,35x e este número ficaria para trás -- que é a mesma divergência, ao contrário.

         O comentário de 1.1.64 fica registrado porque a MOTIVAÇÃO dele continua valendo: o número
         era pequeno demais (9cqh dava 8,5% da altura da célula) e a barra engrossou no mesmo
         movimento, deixando de ser um medidor quase vazio para ser a composição do consumo.
         ==================================================================== */
      /* (1.1.66) E O TETO DE LARGURA ENTROU AQUI TAMBEM, pelo mesmo motivo medido na celula do
         resumo: `--tv-num` do card e `min(calc(rosca * .62), 13cqi)`, e sem o segundo teto este
         numero passa o do card em celula estreita, que e exatamente a inversao que este bloco
         existe para impedir. Mesma razao: 13 x 0,55/0,62 = 11,53. */
      --tv-api-num:  min(calc(clamp(76px, min(19cqh, 36cqi), 250px) * .55 * var(--tv-esc, 1)),
                         11.53cqi);
      --tv-api-rot:  calc(clamp(.72rem, 3.2cqh, 1.45rem) * var(--tv-esc, 1));
      --tv-api-abs:  calc(clamp(.72rem, 2.6cqh, 1.25rem) * var(--tv-esc, 1));
      --tv-api-selo: calc(clamp(.62rem, 2.2cqh, 1rem)    * var(--tv-esc, 1));
      --tv-api-leg:  calc(clamp(.6rem, 2.1cqh, 1rem)     * var(--tv-esc, 1));
      --tv-api-med:  clamp(10px, 3.2cqh, 30px);
      /* A PROJECAO USA A ESCALA DA PROJECAO DO CARD, e nao uma propria: as duas respondem a
         mesma pergunta ("onde isto vai parar") em telas vizinhas, e tamanho diferente faria uma
         parecer mais importante que a outra sem ninguem ter decidido isso. */
      --tv-api-proj: calc(clamp(.7rem, 2.4cqh, 1.15rem) * var(--tv-esc, 1));
    }
    /* (1.1.66) A PROJECAO, logo abaixo do numero, no lugar em que o card de campanha poe a dele.
       O card diz "~230 min restantes"; este diz "neste ritmo fecha em 5,2%". */
    html[data-tv] .precs-tv-api-proj{
      font-family: var(--font-num); font-size: var(--tv-api-proj);
      color: var(--text-secondary); font-variant-numeric: tabular-nums;
      white-space: nowrap; text-align: left;
    }
    /* O RITMO E META-INFORMACAO, como a idade ao lado: mesma cor e mesmo corpo, e o `margin-top:
       auto` mudou para ele porque agora ele e o primeiro do grupo do pe. */
    html[data-tv] .precs-tv-api-ritmo{
      font-family: var(--font-num); font-size: var(--tv-api-leg);
      color: var(--text-muted); font-variant-numeric: tabular-nums;
      white-space: nowrap; text-align: left; margin-top: auto;
    }
    /* E com o ritmo no pe, a idade deixa de ser quem empurra: ela passa a se encostar nele. */
    html[data-tv] .precs-tv-api-ritmo + .precs-tv-api-idade{ margin-top: 0; }
    /* O RÓTULO É OBRIGATÓRIO, e não decoração: sem ele "20,4%" ao lado do saldo das campanhas lê
       como percentual de mailing. */
    html[data-tv] .precs-tv-api-rot{
      font-family: var(--font-rotulo); font-size: var(--tv-api-rot);
      text-transform: uppercase; letter-spacing: var(--ls-caps);
      color: var(--precs-gold); font-weight: 700; line-height: 1;
      white-space: nowrap; text-align: left;
    }
    /* (1.1.64) A SEGUNDA LINHA DO RÓTULO diz de que janela o número fala. Sem ela, "0,86%" numa
       parede cheia de percentuais de mailing não diz que é de um ciclo que vira às 04h. O cartão
       do painel tem a mesma linha, e é dela que a leitura "ciclo em curso" saiu. */
    html[data-tv] .precs-tv-api-rot i{
      display: block; font-style: normal; font-weight: 600;
      text-transform: none; letter-spacing: 0;
      color: var(--text-muted); font-size: var(--tv-api-leg);
      margin-top: var(--space-0);
    }
    html[data-tv] .precs-tv-api-num{
      font-family: var(--font-num); font-size: var(--tv-api-num);
      font-weight: 700; line-height: 1; color: var(--tv-api-cor, var(--text-primary));
      font-variant-numeric: tabular-nums; white-space: nowrap; text-align: left;
    }
    /* O SINAL DE PERCENTUAL SAI MENOR, como a rosca já faz com o dela: no mesmo corpo do dígito
       ele come a leitura do que importa, que são os algarismos. */
    /* O corpo do `%` vem da regra compartilhada, junto do da rosca. Aqui fica só o que é deste
       bloco: o `<i>` é semântico de ninguém, e sem isto ele sairia em itálico. */
    html[data-tv] .precs-tv-api-num i{ font-style: normal; }
    /* (1.1.64) A BARRA DE COMPOSIÇÃO. Ela ocupa a largura inteira SEMPRE, porque não mede
       fração de cota — mede a divisão do que foi consumido. Ver o pintor para a troca de
       pergunta. `display:flex` porque as fatias somam 100% entre si. */
    html[data-tv] .precs-tv-api-medidor{
      display: flex; width: 100%; height: var(--tv-api-med);
      border-radius: var(--radius-pill); overflow: hidden;
      background: var(--bg-tertiary); flex: 0 0 auto;
    }
    /* CADA FATIA TRANSICIONA, e é por isso que o pintor reconcilia por slot e escreve
       `style.width` no nó que sobrevive em vez de remontar a barra: com o nó vivo, a fatia
       desliza a cada leitura nova em vez de saltar. É a mesma lição medida no arco da rosca.
       O separador de 2px é `box-shadow` e não `margin`: margem entraria na soma das larguras e
       as fatias deixariam de fechar 100%. */
    /* O `min-width` FAZ A LEGENDA NAO MENTIR. Medido com o dado real: tres credenciais de 168
       tokens num consumo de 43.702 dão 0,38% cada, ou 2,2px na trilha de 573px — invisíveis. A
       legenda promete uma cor que o olho não acha na barra, e promessa que não se cumpre é pior
       que ausência. Cinco pixels distorcem a proporção em cerca de 1% e tornam a promessa
       verdadeira; o número exato de cada fatia está na legenda, que é onde se confere. */
    html[data-tv] .precs-tv-api-medidor > i{
      display: block; height: 100%; width: 0; min-width: 5px;
      background: var(--tv-api-slot, var(--text-muted));
      box-shadow: 1px 0 0 0 var(--bg-secondary) inset;
      transition: width var(--dur-3) var(--ease-1);
    }
    /* AS SEIS CORES DE DADO DO PROJETO, e não uma paleta nova. Elas moram em `:root` no arquivo
       da aba de WhatsApp, com a doutrina escrita ali: são cores de DADO, declaradas fora de tema
       porque "mudar o vocabulário de cor a cada tema faria a mesma operadora trocar de cor". É
       exatamente a propriedade que credencial precisa — a mesma pessoa, a mesma cor, nas duas
       telas e todos os dias. O slot vem pronto do outro projeto.
       O slot 0 é o resto (não atribuído), cinza nas duas telas. `un` é a barra de uma fatia só,
       que aparece quando o payload não traz `fatias`. */
    html[data-tv] .precs-tv-api-medidor > i[data-slot="1"]{ --tv-api-slot: var(--supwa-s1); }
    html[data-tv] .precs-tv-api-medidor > i[data-slot="2"]{ --tv-api-slot: var(--supwa-s2); }
    html[data-tv] .precs-tv-api-medidor > i[data-slot="3"]{ --tv-api-slot: var(--supwa-s3); }
    html[data-tv] .precs-tv-api-medidor > i[data-slot="4"]{ --tv-api-slot: var(--supwa-s4); }
    html[data-tv] .precs-tv-api-medidor > i[data-slot="5"]{ --tv-api-slot: var(--supwa-s5); }
    html[data-tv] .precs-tv-api-medidor > i[data-slot="6"]{ --tv-api-slot: var(--supwa-s6); }
    /* O RESTO É `--text-muted` E NÃO `--border-color`, e isto é conserto de um defeito medido:
       no tema PRECS a trilha é `#232327` e o `--border-color` é `#2C2C30`. A fatia do resto ficava
       invisível contra o próprio fundo, e a barra voltava a LER como medidor "30% cheio" — o
       contrário do que ela diz. Pior que feio: mentia, porque os 70% não atribuídos são uma fatia
       de verdade e a maior de todas. */
    html[data-tv] .precs-tv-api-medidor > i[data-slot="0"]{ --tv-api-slot: var(--text-muted); }
    html[data-tv] .precs-tv-api-medidor > i[data-slot="un"]{
      --tv-api-slot: var(--tv-api-cor, var(--precs-gold));
    }
    /* A DIVISÃO EM PALAVRAS, nas duas pontas da barra. Ela responde o que a barra desenha, e sem
       ela as cores viram enfeite: "atribuído 30%" é o que diz que 70% do consumo não tem dono. */
    html[data-tv] .precs-tv-api-divisao{
      display: flex; justify-content: space-between; gap: var(--space-2);
      font-family: var(--font-rotulo); font-size: var(--tv-api-leg);
      color: var(--text-muted); line-height: 1; white-space: nowrap;
    }
    /* A LEGENDA NOMEIA quem consumiu, por decisão do Pedro em 10/09/2026 ("nomear, como no
       painel"). Ela é o que separa "a cota está em 0,86%" de "e uma credencial responde por um
       terço disso". Em célula baixa ela é a primeira a ceder, junto do absoluto. */
    html[data-tv] .precs-tv-api-legenda{
      display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-3);
      font-family: var(--font-corpo); font-size: var(--tv-api-leg);
      color: var(--text-secondary); line-height: 1; min-width: 0; overflow: hidden;
    }
    html[data-tv] .precs-tv-api-legenda > span{
      display: inline-flex; align-items: center; gap: var(--space-1);
      min-width: 0; white-space: nowrap;
    }
    html[data-tv] .precs-tv-api-legenda i{
      width: .72em; height: .72em; border-radius: var(--radius-sm); flex: 0 0 auto;
      background: var(--tv-api-slot, var(--text-muted));
    }
    html[data-tv] .precs-tv-api-legenda b{
      font-family: var(--font-num); font-weight: 700; color: var(--text-primary);
    }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="1"]{ --tv-api-slot: var(--supwa-s1); }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="2"]{ --tv-api-slot: var(--supwa-s2); }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="3"]{ --tv-api-slot: var(--supwa-s3); }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="4"]{ --tv-api-slot: var(--supwa-s4); }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="5"]{ --tv-api-slot: var(--supwa-s5); }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="6"]{ --tv-api-slot: var(--supwa-s6); }
    html[data-tv] .precs-tv-api-legenda > span[data-slot="0"]{ --tv-api-slot: var(--text-muted); }
    html[data-tv] .precs-tv-api-abs{
      font-family: var(--font-num); font-size: var(--tv-api-abs);
      color: var(--text-secondary); line-height: 1; white-space: nowrap;
      text-align: left; overflow: hidden; text-overflow: ellipsis;
    }
    html[data-tv] .precs-tv-api-selo{
      font-family: var(--font-rotulo); font-size: var(--tv-api-selo);
      text-transform: uppercase; letter-spacing: var(--ls-caps); font-weight: 700;
      line-height: 1; padding: var(--space-1) var(--space-3);
      border-radius: var(--radius-pill);
      color: var(--tv-api-cor, var(--text-primary));
      border: 1px solid var(--tv-api-cor, var(--border-color));
      align-self: flex-start; flex: 0 0 auto;
    }
    /* A COR VEM DE `data-est` NA RAIZ, e o estado sai CALCULADO do outro projeto (folga até 50%,
       metade acima de 50%, atenção acima de 80%). Uma escrita governa o número, o preenchimento e
       o selo, então os três não podem divergir — e a parede não recalcula o corte, porque duas
       regras para a mesma coisa divergem no dia em que alguém mexer no orçamento da conta. */
    html[data-tv] .precs-tv-api[data-est="folga"]{   --tv-api-cor: var(--accent-green); }
    html[data-tv] .precs-tv-api[data-est="metade"]{  --tv-api-cor: var(--accent-orange); }
    html[data-tv] .precs-tv-api[data-est="atencao"]{ --tv-api-cor: var(--accent-red); }
    /* A IDADE DO DADO, com os dois degraus. Vazia enquanto fresco: quinze minutos são três coletas
       perdidas e pedem um olhar no log; quarenta são a coleta parada e pedem alguém de pé. Dado
       congelado tem de se denunciar sozinho, senão a parede repete o último número para sempre. */
    html[data-tv] .precs-tv-api-idade{
      font-family: var(--font-rotulo); font-size: var(--tv-api-selo);
      color: var(--text-muted); line-height: 1;
      transition: color var(--dur-3) ease;
    }
    html[data-tv] .precs-tv-api-idade.velho{ color: var(--accent-orange); font-weight: 700; }
    html[data-tv] .precs-tv-api-idade.morto{ color: var(--accent-red); font-weight: 700; }

    /* ======================================================================
       (1.1.158) A CÉLULA DO CONSUMO NO VOCABULÁRIO DA TELA CONSUMO PIPEDRIVE.

       "Agora que demos aquela polida na tela de consumo, o card do API Pipedrive no modo TV não
       acompanhou; deixar igual à tela, e com animações com o passar do tempo, igual ao hub quando
       está parado" (Pedro, 29/09/2026). O que entra: o fundo com o degradê do estado (o hero da
       tela), o anel com o percentual dentro (conic-gradient, sem SVG e sem nó a mais), os tiles
       com ícone, as barras por hora e a vida ambiente: um brilho atravessa o card de tempos em
       tempos (o mesmo compasso dos cartões do hub) e o halo do anel respira. Nada disto mexe no
       DOM: é CSS, e o teste de tremor continua contando zero mutação.

       TUDO CEDE POR CONTAINER QUERY, na ordem do que menos falta: barras por hora (pedem 520px),
       tiles (430px), e abaixo disso valem os degraus antigos da legenda, do absoluto e da divisão.
       ====================================================================== */
    @property --tv-api-pct{ syntax: '<number>'; inherits: true; initial-value: 0; }
    html[data-tv] .precs-tv-api{
      position: relative;
      /* `background-image`, e nao `background`: a cor de fundo continua a compartilhada com o resumo (o teste da
         forma compara `backgroundColor` das duas celulas), e o degrade do estado entra por cima, ate transparente. */
      background-image: linear-gradient(135deg, color-mix(in srgb, var(--tv-api-cor, var(--precs-gold)) 9%, transparent), transparent 62%);
    }
    /* O BRILHO QUE ATRAVESSA, do hub: um gradiente estreito, na cor do estado, que cruza o card a cada 18 s. */
    html[data-tv] .precs-tv-api::after{
      content: ''; position: absolute; inset: 0; pointer-events: none; border-radius: inherit;
      background: linear-gradient(115deg, transparent 42%, color-mix(in srgb, var(--tv-api-cor, var(--precs-gold)) 10%, transparent) 50%, transparent 58%);
      transform: translateX(-130%); animation: precsTvApiBrilho 18s var(--ease-1) infinite; animation-delay: 4s;
    }
    @keyframes precsTvApiBrilho{ 0%, 80%{ transform: translateX(-130%); } 93%, 100%{ transform: translateX(130%); } }
    html[data-tv] .precs-tv-api > *{
      --tv-api-tile: calc(clamp(.8rem, 3.1cqh, 1.5rem) * var(--tv-esc, 1));
      --tv-api-anel-esp: clamp(6px, 1.6cqh, 14px);
    }
    html[data-tv] .precs-tv-api-cab{
      display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-3);
    }
    html[data-tv] .precs-tv-api-hero{
      display: flex; align-items: center; gap: var(--space-4); min-width: 0;
    }
    /* O ANEL: o percentual mora dentro dele, como no hero da tela. O arco é um `conic-gradient` no
       `::before`, recortado em anel pela máscara, e desliza porque `--tv-api-pct` é propriedade
       registrada (sem `@property` um custom property não transiciona). O halo é o `::after`, e é ele
       que respira. */
    html[data-tv] .precs-tv-api-anel{
      --tv-api-pct: 0;
      position: relative; flex: 0 0 auto;
      /* (1.1.164) 3.35 e não 2.9: com "2,55%" o texto tem cerca de 2,6 vezes o corpo do dígito e encostava nos
         dois lados do anel ("o 2,55% está colado nas bordas", Pedro, 29/09/2026). A folga tem de caber o maior
         percentual que o ciclo produz com duas casas.
         O TETO POR ALTURA DA CÉLULA existe porque crescer 15% fez a célula transbordar num cenário do teste
         (payload de ciclo recém-virado, quatro campanhas em 1080p): o anel é o objeto mais alto do bloco, e
         sem teto ele empurra o resto para fora. `cqh` vale aqui porque quem declara é o FILHO, e não o
         container — a lição de 1.1.156. */
      width: min(calc(var(--tv-api-num) * 3.35), 33cqh);
      height: min(calc(var(--tv-api-num) * 3.35), 33cqh);
      display: flex; flex-direction: column; align-items: center; justify-content: center;
      gap: var(--space-0);
      transition: --tv-api-pct 1.2s var(--ease-2);
    }
    html[data-tv] .precs-tv-api-anel::before{
      content: ''; position: absolute; inset: 0; border-radius: 50%;
      background: conic-gradient(var(--tv-api-cor, var(--precs-gold)) calc(var(--tv-api-pct) * 1%), var(--bg-primary) 0);
      -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--tv-api-anel-esp)), var(--text-primary) calc(100% - var(--tv-api-anel-esp) + 1px));
      mask: radial-gradient(farthest-side, transparent calc(100% - var(--tv-api-anel-esp)), var(--text-primary) calc(100% - var(--tv-api-anel-esp) + 1px));
    }
    html[data-tv] .precs-tv-api-anel::after{
      content: ''; position: absolute; inset: -12%; border-radius: 50%; pointer-events: none;
      background: radial-gradient(circle, color-mix(in srgb, var(--tv-api-cor, var(--precs-gold)) 16%, transparent), transparent 68%);
      animation: precsTvApiRespira 6s ease-in-out infinite alternate;
    }
    @keyframes precsTvApiRespira{ from{ opacity: .35; } to{ opacity: 1; } }
    html[data-tv] .precs-tv-api-anel .precs-tv-api-num{ position: relative; text-align: center; }
    html[data-tv] .precs-tv-api-anel-rot{
      position: relative; font-family: var(--font-rotulo); font-size: var(--tv-api-selo);
      text-transform: uppercase; letter-spacing: var(--ls-caps); color: var(--text-muted); line-height: 1;
    }
    html[data-tv] .precs-tv-api-lado{
      display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; flex: 1 1 auto;
    }
    html[data-tv] .precs-tv-api-lado-sub{
      font-family: var(--font-rotulo); font-size: var(--tv-api-leg); color: var(--text-muted); line-height: 1;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    html[data-tv] .precs-tv-api-lado-sub b{ color: var(--text-secondary); font-family: var(--font-num); font-weight: 700; }
    /* OS TILES, os quatro da tela: ritmo, restante, pico e medição. Quatro colunas; duas quando a célula é
       estreita. Entram em cascata na primeira pintura, como na tela. */
    html[data-tv] .precs-tv-api-tiles{
      display: none; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-2);
    }
    html[data-tv] .precs-tv-api-tile{
      display: flex; align-items: center; gap: var(--space-2); min-width: 0;
      padding: var(--space-2) var(--space-3);
      border: 1px solid var(--border-color); border-radius: var(--radius-inner);
      background: color-mix(in srgb, var(--bg-primary) 70%, transparent);
    }
    /* (1.1.162) A cascata de entrada SO enquanto a celula carrega `nasce` (1,5 s a partir do nascimento, ver o
       pintor). Como regra fixa, qualquer reinicio de animacao a fazia correr de novo: era o "piscando" dos tiles. */
    html[data-tv] .precs-tv-api.nasce .precs-tv-api-tile{
      animation: precsConsumoEntra .5s var(--ease-2) both; animation-delay: calc(120ms + var(--i, 0) * 60ms);
    }
    html[data-tv] .precs-tv-api-tile-ico{
      flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
      width: calc(var(--tv-api-tile) * 1.9); height: calc(var(--tv-api-tile) * 1.9);
      border-radius: var(--radius-pill);
      background: color-mix(in srgb, var(--tv-api-cor, var(--precs-gold)) 16%, transparent);
      color: var(--tv-api-cor, var(--precs-gold-title));
    }
    html[data-tv] .precs-tv-api-tile-ico svg{ width: 55%; height: 55%; }
    html[data-tv] .precs-tv-api-tile-txt{ display: flex; flex-direction: column; gap: var(--space-0); min-width: 0; }
    html[data-tv] .precs-tv-api-tile b{
      font-family: var(--font-num); font-variant-numeric: tabular-nums; font-size: var(--tv-api-tile);
      font-weight: 700; color: var(--text-primary); line-height: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    html[data-tv] .precs-tv-api-tile b small{
      font-family: var(--font-rotulo); font-size: var(--tv-api-leg); font-weight: 600; color: var(--text-muted); margin-left: var(--space-0);
    }
    html[data-tv] .precs-tv-api-tile-txt > span{
      font-family: var(--font-rotulo); font-size: var(--tv-api-leg); color: var(--text-muted); line-height: 1;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    /* AS BARRAS POR HORA, a seção "Consumo por hora" da tela: 24 colunas desde a virada do ciclo, o pico na cor
       do estado e a hora de agora pulsando devagar. Só em célula alta. */
    html[data-tv] .precs-tv-api-horas{ display: none; flex-direction: column; gap: var(--space-1); min-width: 0; }
    html[data-tv] .precs-tv-api-horas-rot{
      font-family: var(--font-rotulo); font-size: var(--tv-api-leg); color: var(--text-muted); line-height: 1;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    html[data-tv] .precs-tv-api-horas-barras{
      display: flex; align-items: flex-end; gap: var(--space-0);
      height: clamp(24px, 7cqh, 84px);
    }
    html[data-tv] .precs-tv-api-horas-barras > i{
      flex: 1 1 0; min-width: 0; height: var(--h, 0%); min-height: 2px;
      border-radius: var(--radius-sm) var(--radius-sm) 0 0;
      background: color-mix(in srgb, var(--precs-gold) 55%, var(--bg-primary));
      transition: height var(--dur-3) var(--ease-1);
    }
    html[data-tv] .precs-tv-api-horas-barras > i[data-marca="pico"]{ background: var(--tv-api-cor, var(--precs-gold-title)); }
    html[data-tv] .precs-tv-api-horas-barras > i[data-marca="agora"]{
      background: var(--precs-gold-title); animation: precsTvApiVivo 2.4s ease-in-out infinite;
    }
    @keyframes precsTvApiVivo{ 0%, 100%{ opacity: 1; } 50%{ opacity: .55; } }
    /* O SELO EM ATENÇÃO PULSA, no compasso do alerta de telefonia do card de campanha. */
    html[data-tv] .precs-tv-api[data-est="atencao"] .precs-tv-api-selo{ animation: precsAgrPulsa 2.4s ease-in-out infinite; }
    /* Medido em 29/09/2026 com o payload real: em 1080p com quatro cards a celula tem 602x419 e sobrava um terco
       vazio. A container query mede a CAIXA DE CONTEUDO (419 - 32 de padding = 387): tiles a partir de 350px e
       barras a partir de 372px (com as barras em 7cqh) fecham em 401px de celula. */
    @container card (min-height: 350px){
      html[data-tv] .precs-tv-api-tiles{ display: grid; }
    }
    @container card (min-height: 372px){
      html[data-tv] .precs-tv-api-horas{ display: flex; }
    }
    @container card (max-width: 700px){
      html[data-tv] .precs-tv-api-tiles{ grid-template-columns: repeat(2, minmax(0, 1fr)); }
    }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .precs-tv-api::after,
      html[data-tv] .precs-tv-api-anel::after,
      html[data-tv] .precs-tv-api.nasce .precs-tv-api-tile,
      html[data-tv] .precs-tv-api-horas-barras > i[data-marca="agora"],
      html[data-tv] .precs-tv-api[data-est="atencao"] .precs-tv-api-selo{ animation: none; }
      html[data-tv] .precs-tv-api-anel,
      html[data-tv] .precs-tv-api-horas-barras > i{ transition: none; }
    }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .precs-tv-api-medidor > i,
      html[data-tv] .precs-tv-api-idade{ transition: none; }
    }
    /* O ABSOLUTO SAI EM CÉLULA BAIXA, e aqui isto funciona de verdade porque o bloco é container.
       O percentual responde "estamos apertados?"; o absoluto só serve para conferir contra o
       orçamento, e é o primeiro a ceder quando a altura aperta. */
    /* (1.1.64) A ORDEM EM QUE AS COISAS CEDEM, e ela é de informação e não de gosto: a legenda
       primeiro (é o detalhe), depois o absoluto (conferir contra o orçamento pede calma, e numa
       célula apertada ninguém confere), e por último a divisão em palavras. O número, a barra e o
       selo ficam até o fim — são a resposta. */
    @container card (max-height: 340px){
      html[data-tv] .precs-tv-api-legenda{ display: none; }
    }
    @container card (max-height: 300px){
      html[data-tv] .precs-tv-api-abs{ display: none; }
      html[data-tv] .precs-tv-api-rot i{ display: none; }
    }
    @container card (max-height: 240px){
      html[data-tv] .precs-tv-api-divisao{ display: none; }
    }

    /* ======================================================================
       (1.1.35) A ENTRADA DO REALCE TEM DIREÇÃO.

       PEDIDO DO PEDRO, 27/08/2026: "ao passar pra outra campanha poderia ter uma animação de passar
       entre as campanhas; não sei qual ficaria legal".

       O QUE FALTAVA não era animação — o card já subia animado, porque `transform` está na transição
       do card desde o holofote. Faltava DIREÇÃO: subindo no lugar, o realce parece aparecer; entrando
       pelo lado de onde a tecla veio, ele parece ter atravessado a parede.

       CADA DIREÇÃO TEM SEU `from`, e o `to` é implícito — o valor estático do `.tv-cursor`. Isso
       evita a duplicação da posição final em cinco lugares e garante que a animação sempre termine
       exatamente onde o estado manda, sem salto no último quadro.

       240ms E UMA CURVA COM PONTA (o `1.12` no fim do cubic-bezier passa um fio do destino e volta):
       é rápido o suficiente para não atrasar quem está varrendo a parede com a tecla, e o exagero
       mínimo é o que dá a sensação de peso. A saída fica com a transição lenta de sempre: entra
       rápido, sai devagar, que é como o olho espera que algo pesado se comporte. */
    @keyframes precsTvEntraDir{ from{ transform: translate(-28px, -10px) scale(1.02); } }
    @keyframes precsTvEntraEsq{ from{ transform: translate(28px, -10px) scale(1.02); } }
    @keyframes precsTvEntraBaixo{ from{ transform: translateY(-38px) scale(1.02); } }
    @keyframes precsTvEntraCima{ from{ transform: translateY(18px) scale(1.02); } }
    html[data-tv] .precs-sup-tv-parede .tv-cursor[data-tv-entra]{
      animation-duration: 240ms;
      animation-timing-function: cubic-bezier(.2, .85, .3, 1.12);
      animation-fill-mode: none;
    }
    html[data-tv] .precs-sup-tv-parede .tv-cursor[data-tv-entra="dir"]{ animation-name: precsTvEntraDir; }
    html[data-tv] .precs-sup-tv-parede .tv-cursor[data-tv-entra="esq"]{ animation-name: precsTvEntraEsq; }
    html[data-tv] .precs-sup-tv-parede .tv-cursor[data-tv-entra="baixo"]{ animation-name: precsTvEntraBaixo; }
    html[data-tv] .precs-sup-tv-parede .tv-cursor[data-tv-entra="cima"]{ animation-name: precsTvEntraCima; }
    /* A SUBIDA DO CARD SELECIONADO É RÁPIDA; a descida de quem perdeu o realce continua nos 600ms
       do holofote. Entra rápido, sai devagar. */
    html[data-tv] .precs-sup-tv-parede .tv-cursor{
      transition: transform 240ms cubic-bezier(.2, .85, .3, 1.12),
                  box-shadow 240ms ease,
                  opacity var(--dur-3, .3s) ease;
    }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .precs-sup-tv-parede .tv-cursor[data-tv-entra]{ animation: none; }
    }

    /* O esmaecimento das outras é REUSADO do holofote, e com o cursor ele APERTA: 0,55 servia para
       o rodízio, que só sugere onde olhar. Aqui alguém está dirigindo a tela, e o contraste entre
       quem está na frente e quem está atrás é a informação. */
    html[data-tv] .tv-holofote .tv-cursor{ opacity: 1; }
    /* (1.1.63) AS DUAS CÉLULAS QUE NÃO SÃO CAMPANHA ENTRAM NA LISTA DO QUE ESMAECE. Sem isto, com
       o cursor aceso a parede escurece as campanhas para 0,38 e o bloco do consumo da API fica o
       objeto MAIS BRILHANTE da tela — o contrário exato do que o holofote quer dizer. O resumo
       (`.precs-tv-resto`) já tinha a mesma lacuna e entra junto. */
    html[data-tv] .precs-sup-tv-parede.tv-holofote:has(.tv-cursor) .precs-campsaldo,
    html[data-tv] .precs-sup-tv-parede.tv-holofote:has(.tv-cursor) .precs-tv-resto,
    html[data-tv] .precs-sup-tv-parede.tv-holofote:has(.tv-cursor) .precs-tv-api{ opacity: .38; }
    html[data-tv] .precs-sup-tv-parede.tv-holofote:has(.tv-cursor) .tv-cursor{
      opacity: 1;
    }
    @media (prefers-reduced-motion: reduce){
      /* No regime reduzido a levitação FICA (ela é estado, não animação) e só a transição sai. O
         anel de 4px continua identificando sozinho, que é o mesmo argumento da versão anterior. */
      html[data-tv] .precs-sup-tv-parede .tv-cursor{ transition: none; }
    }
    /* A BATIDA DE BORDA: o cursor não circula, então "não aconteceu nada" precisa de resposta —
       numa parede, silêncio lê como tela travada. */
    @keyframes precsTvBateu{
      0%{ transform: translateX(0); } 30%{ transform: translateX(6px); }
      60%{ transform: translateX(-3px); } 100%{ transform: translateX(0); }
    }
    html[data-tv] .precs-campsaldo.tv-bateu{ animation: precsTvBateu 200ms ease; }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .precs-campsaldo.tv-bateu{ animation: none; }
    }

    /* Nada é clicável na parede: cursor de mão em tela sem mouse é promessa falsa. */
    html[data-tv] .precs-campsaldo *{ cursor: default; }

    /* ---- (1.1.34) O GRÁFICO DE RITMO ----
       Números por minuto nos últimos vinte minutos. Ele responde o que o delta de 30 segundos
       não responde: a campanha está acelerando, estável ou morrendo.
       QUEM DECIDE SE ELE APARECE É A ALTURA DO CARD, não a densidade — o JS monta sempre (para a
       estrutura do bloco não mudar e o morfismo continuar andando por índice) e o CSS esconde.
       Só card alto tem espaço: numa célula de 425px, 54px de gráfico saem de onde as listas
       moram, e nome de mailing é o que faz alguém levantar. */
    .precs-tv-ritmo{ display: none; }
    @container card (min-height: 545px){
      /* LINHA PRÓPRIA, e não uma terceira coluna. Disputando largura com a barra de ligações e
         com a fileira de rostos, o gráfico tirava espaço de quem já era apertado: medido, treze
         operadores caíam de doze rostos visíveis para sete. Vertical o card alto tem de sobra —
         é justamente por isso que o gráfico só aparece nele. */
      html[data-tv]:not([data-aparelho]) .precs-tv-ritmo{
        display: block; grid-column: 1 / -1;
        width: 100%; height: clamp(38px, 6cqh, 72px);
        margin-bottom: var(--space-2);
      }
    }
    html[data-tv] .precs-tv-ritmo svg{ width: 100%; height: 100%; display: block; }
    /* A ÁREA usa o dourado por forma a 22%: ela dá volume à curva sem competir com o número
       grande, que é quem manda no card. */
    .precs-tv-ritmo-area{ fill: color-mix(in srgb, var(--precs-gold-forma, #B49552) 22%, transparent);
      stroke: none; }
    .precs-tv-ritmo-linha{ fill: none; stroke: var(--precs-gold-forma, #B49552);
      stroke-width: 2; stroke-linejoin: round; stroke-linecap: round;
      vector-effect: non-scaling-stroke; }
    /* A MÉDIA como referência tracejada: sem ela, "está acelerando" é comparação com o nada. */
    .precs-tv-ritmo-media{ stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 4;
      vector-effect: non-scaling-stroke; opacity: .7; }
    .precs-tv-ritmo-fim{ fill: var(--precs-gold-title, #D3B26B); }
    /* Sem dois pontos não há curva, e a tela DIZ isso em vez de desenhar uma reta que parece
       ritmo constante — mesma regra da projeção, que devolve nada em vez de inventar minuto. */
    .precs-tv-ritmo-vazio{ display: none; }
    @container card (min-height: 545px){
      html[data-tv]:not([data-aparelho]) .precs-tv-ritmo-vazio{
        display: flex; grid-column: 1 / -1; align-items: center; justify-content: center;
        font-family: 'JetBrains Mono', monospace; font-size: .72rem; color: var(--text-muted);
      }
    }
    @media (prefers-reduced-motion: reduce){
      .precs-tv-ritmo-linha, .precs-tv-ritmo-area{ transition: none; }
    }

    /* ---- O BLOCO DA PAREDE: ligações do dia e quem está na campanha ---- */
    /* (1.1.32) DUAS FAIXAS RESERVADAS, e não uma fileira onde o maior empurra o menor.
       Relato do Pedro em 26/08/2026: "dependendo da quantidade de ligações na tela serem muitas,
       tipo a ID 01, ela tira espaço de foto dos operadores". Era exatamente isso: a linha era um
       flex com `.precs-tv-lig` em `flex: 1 1 220px` (cresce) e `.precs-tv-quem` sem declaração
       nenhuma (`0 1 auto`, encolhe). Sempre que a barra tinha o que mostrar, ela tomava a largura
       e os rostos se espremiam até virar "+3" — e rosto é a coisa que a parede é lida para ver.
       Agora são duas trilhas de grade: a barra tem um TETO (nunca passa de 340px, nem que a tela
       cresça) e os rostos ficam com o resto, quebrando em duas fileiras se precisar. Numa carta
       estreita a grade vira uma coluna e os dois empilham, que é o comportamento que o
       `flex-wrap` dava e a gente quer manter. */
    .precs-tv-extra{ display: grid; align-items: center;
      grid-template-columns: minmax(0, 1fr);
      gap: var(--space-2) var(--space-4); margin-top: var(--space-2);
      padding-top: var(--space-2); border-top: 1px solid var(--border-color); }
    /* DUAS TRILHAS SÓ ONDE O CARD É LARGO, e a pergunta passou a ser sobre a LARGURA DO CARD e
       não sobre a densidade.
       Reservar 340px para a barra de ligações empurra os rostos para uma segunda fileira quando
       o card é estreito; o card estoura a altura da célula e o laço de encaixe começa a cortar
       rosto para caber. Amarrar isso a `data-n="1"` e `="2"` funcionava em 1080p, onde dois
       cards têm 917px cada — e quebrava em 720p, onde os mesmos dois cards têm 660px: medido, a
       fileira de rostos ia a 209px (quatro fileiras) e o card estourava 50px.
       `@container` resolve na fonte: quem sabe se cabe é o card, não a contagem de campanhas. */
    @container card (min-width: 820px){
      html[data-tv] .precs-tv-extra{
        grid-template-columns: minmax(160px, 340px) minmax(0, 1fr); }
    }
    /* Sem barra (a tranquila do mural cheio esconde a dela), a trilha da esquerda sumiria vazia. */
    html[data-tv] .precs-tv-extra:not(:has(> .precs-tv-lig)){
      grid-template-columns: minmax(0, 1fr); }
    .precs-tv-lig{ display: inline-flex; align-items: center; gap: var(--space-2);
      min-width: 0; }
    .precs-tv-lig-trilha{ flex: 1 1 auto; height: 8px; min-width: 60px;
      border-radius: var(--radius-pill); background: var(--bg-tertiary); overflow: hidden; }
    .precs-tv-lig-fill{ height: 100%; border-radius: var(--radius-pill);
      background: var(--precs-gold-forma, var(--precs-gold));
      transition: width var(--dur-3, .3s) cubic-bezier(.2,.7,.3,1); }
    .precs-tv-lig b{ font-family: 'JetBrains Mono', monospace; font-weight: 700;
      font-size: 1rem; color: var(--text-primary); }
    .precs-tv-lig span{ font-size: .62rem; font-weight: 700; text-transform: uppercase;
      letter-spacing: var(--ls-caps); color: var(--text-muted); }
    /* ---- QUEM ESTÁ NA CAMPANHA: rosto, primeiro nome e o estado no anel ----
       Dimensionado para o caso real que o Pedro levantou: campanhas com 13 operadores ao mesmo
       tempo. Com nome inteiro e estado escrito por pessoa, treze viravam três linhas e o card
       saía da tela. Aqui cada pessoa ocupa uma pastilha de ~120px, e treze cabem numa linha só
       num monitor de 1920 — sobrando a quebra automática para telas menores. */
    /* `justify-content: flex-end` alinha os rostos à direita do card: com a barra à esquerda,
       encostar as duas coisas nas bordas opostas separa as duas leituras sem precisar de régua. */
    .precs-tv-quem{ position: relative;   /* (1.1.162) a pastilha que se despede fica posicionada nela */
      display: flex; flex-wrap: wrap; align-items: center;
      justify-content: flex-end; gap: var(--space-2) var(--space-3); font-size: .78rem; }
    /* Alinhar à direita só vale quando a barra está do lado esquerdo NA MESMA fileira. Nos
       murais de três ou mais os dois empilham, e aí os rostos voltam a começar da esquerda —
       texto centrado ou jogado na direita numa linha própria é o que faz a fileira parecer
       desalinhada com o resto do card. */
    html[data-tv] .precs-tv-quem{ justify-content: flex-start; }
    /* Alinhar à direita só vale quando a barra está do lado esquerdo NA MESMA fileira, ou seja
       na mesma condição de largura da regra acima. */
    @container card (min-width: 820px){
      html[data-tv] .precs-tv-extra:has(> .precs-tv-lig) .precs-tv-quem{
        justify-content: flex-end; }
    }
    /* (1.1.33) O PRIMEIRO NOME TEM LARGURA MÁXIMA, e isso é o que dá à pastilha um tamanho
       PREVISÍVEL. Sem teto, "Gabrielle" ocupa 50% mais que "Ana", e treze pastilhas de largura
       imprevisível quebram de linha sem aviso — o card estoura e o laço de encaixe começa a
       esconder gente. O balão devolve o nome inteiro.

       (1.1.44) O NÚMERO DESTE COMENTÁRIO ESTAVA DESALINHADO DO CÓDIGO: ele dizia "com 8ch"
       e a declaração abaixo é `10ch`. A conta de "treze numa linha só num card de 1846px"
       foi medida na era do 8ch e NÃO foi refeita quando o teto subiu — ou seja, a promessa
       de treze rostos numa linha está apoiada num número que não é mais o do código.
       O que segura o comportamento hoje não é este comentário, é a asserção `!rostoAToa` do
       teste-supervisao-tv.js, que reprova se o laço esconder rosto havendo espaço. Quem for
       recalibrar o teto: mede primeiro com o roster real, e atualiza os DOIS. */
    .precs-tv-op b{ max-width: 10ch; overflow: hidden; text-overflow: ellipsis;
      white-space: nowrap; }
    .precs-tv-op{ display: inline-flex; align-items: center; gap: 7px; min-width: 0;
      color: var(--text-secondary); font-weight: 600; cursor: help; }
    /* O ANEL É O ESTADO. Cor sozinha não carrega informação, então a palavra continua ao lado
       (e no balão): o anel é o que se lê de longe, o texto é o que confirma de perto. */
    /* (1.1.22) O ANEL TRANSICIONA: com a pastilha reconciliada por chave (o nó sobrevive, ver
       precsSupTvExtraReconciliar), a troca ocioso→falando→tpa deixou de remontar o avatar — e
       box-shadow interpola cor, então o anel LAVA para o estado novo em vez de cortar seco.
       Era o pedido do print de 25/08/2026. */
    /* (1.1.65) `width`/`height` saem daqui: quem dimensiona e `--tv-av`, aplicado na regra
       `html[data-tv] .precs-tv-op .precs-sup-av` mais acima. Esta regra ficou com o que e dela:
       nao encolher no flex, e o anel de estado que transiciona. */
    .precs-tv-op .precs-sup-av{ flex: 0 0 auto;
      box-shadow: 0 0 0 2px var(--bg-tertiary), 0 0 0 4px var(--tv-op-cor, var(--border-color));
      transition: box-shadow .6s ease; }
    @media (prefers-reduced-motion: reduce){
      .precs-tv-op .precs-sup-av{ transition: none; } }
    /* (1.1.162) O GESTO DA TROCA DE ESTADO: o anel pulsa uma vez e a palavra nova entra de baixo, enquanto a cor
       lava pela transição acima. Quem SAI da campanha se despede: encolhe e apaga em 0,36 s antes de a fileira
       se reorganizar (ver precsSupTvExtraReconciliar). */
    @keyframes precsTvOpTroca{ 0%{ transform: scale(1); } 40%{ transform: scale(1.14); } 100%{ transform: scale(1); } }
    @keyframes precsTvOpPalavra{ from{ opacity: 0; transform: translateY(4px); } to{ opacity: 1; transform: none; } }
    .precs-tv-op.op-troca .precs-sup-av{ animation: precsTvOpTroca .7s var(--ease-2); }
    .precs-tv-op.op-troca i{ animation: precsTvOpPalavra .5s var(--ease-2) both; }
    @keyframes precsTvOpSai{ to{ opacity: 0; transform: scale(.6); } }
    .precs-tv-op.op-sai{ position: absolute; animation: precsTvOpSai .36s var(--ease-3) forwards; pointer-events: none; }
    @media (prefers-reduced-motion: reduce){
      .precs-tv-op.op-troca .precs-sup-av, .precs-tv-op.op-troca i, .precs-tv-op.op-sai{ animation: none; } }
    /* ======================================================================
       (1.1.35) OCIOSO HÁ MAIS DE QUATRO MINUTOS: O ANEL FICA VERMELHO E RESPIRA.

       PEDIDO DO PEDRO, 27/08/2026: "o operador ficasse com a borda vermelha rodando um fade de
       ocioso por mais de 4 min; daí quando vai pra falando, para o vermelho de pulsar e volta ao
       verde normal de falando".

       FADE E NÃO PISCA, e a diferença importa numa tela de 24 horas: 2,6 segundos de ida e volta em
       `ease-in-out` lê como respiração e chama o olho sem cansar. O pulso curto do alerta de
       campanha acabando (2,4s com anel de sombra crescendo) é para UMA coisa por vez na parede;
       aqui podem ser vários operadores ao mesmo tempo, e vários piscando seria a tela piscando.

       O ANEL VOLTA AO VERDE SOZINHO: a classe depende do estado ser `Ocioso`, então quando a pessoa
       atende ela cai, a animação para, e a cor volta pela transição de 0,45s que a pastilha já tem.
       Nenhuma regra de "parar" é necessária.
       ====================================================================== */
    @keyframes precsTvOpParado{
      0%, 100%{ box-shadow: 0 0 0 2px var(--bg-tertiary),
                            0 0 0 4px var(--accent-red, #f26d6d); }
      50%{ box-shadow: 0 0 0 2px var(--bg-tertiary),
                       0 0 0 4px color-mix(in srgb, var(--accent-red, #f26d6d) 30%, transparent); }
    }
    .precs-tv-op.tv-op-parado .precs-sup-av{
      animation: precsTvOpParado 2.6s ease-in-out infinite;
    }
    .precs-tv-op.tv-op-parado b{ color: var(--accent-red, #f26d6d); }
    .precs-tv-op.tv-op-parado i{ color: var(--accent-red, #f26d6d); font-weight: 700; }
    @media (prefers-reduced-motion: reduce){
      /* O vermelho FICA (é o aviso), e só a respiração sai. */
      .precs-tv-op.tv-op-parado .precs-sup-av{
        animation: none;
        box-shadow: 0 0 0 2px var(--bg-tertiary), 0 0 0 4px var(--accent-red, #f26d6d);
      }
    }
    .precs-tv-op b{ font-weight: 700; color: var(--text-primary); white-space: nowrap; }
    .precs-tv-op i{ font-style: normal; font-weight: 500; color: var(--text-muted);
      font-size: .86em; white-space: nowrap; }
    /* (1.1.65) O CHIP "+N" SE PENDURA NO MESMO TOKEN DO ROSTO. Ele era `34px` cravado para
       casar com o avatar de `34px` que também era cravado, e quando um dos dois mudasse o outro
       ficaria para trás — que é o que aconteceria agora, com o rosto crescendo em 1440 e o chip
       parado. Ele fica na fileira ao lado dos rostos, então o que ele tem de casar é a altura
       deles. */
    .precs-tv-op-mais{ display: inline-flex; align-items: center; justify-content: center;
      min-width: var(--tv-av); height: var(--tv-av); padding: 0 8px;
      border-radius: var(--radius-pill);
      background: var(--bg-tertiary); border: 1px solid var(--border-color);
      color: var(--text-muted); font-weight: 700; font-size: .82rem; cursor: help; }
    /* (1.1.22) A pastilha nova entra pela curva canônica do projeto (a mesma dos cards de lead
       e do mural), em vez de aparecer de estalo. Só quem NASCE anima: a reconciliação garante
       que pastilha existente nunca passa por aqui. */
    .precs-tv-op.op-nasce{ animation: precsLeadIn .32s cubic-bezier(.16,1,.3,1) both; }
    @media (prefers-reduced-motion: reduce){ .precs-tv-op.op-nasce{ animation: none; } }
    /* (1.1.22) A CONVERSÃO, DISCRETA (decisão do Pedro em 25/08/2026): um anel dourado pulsa
       UMA vez na pastilha de quem converteu e a tela volta ao normal sozinha. Sem som. O anel
       do avatar fica dourado durante o pulso e depois LAVA de volta para a cor do estado pela
       transição que já existe acima. Começa e termina no estado normal, para não tremer. */
    @keyframes precsTvConverteu{
      0%{ box-shadow: 0 0 0 0 color-mix(in srgb, var(--precs-gold-hi, #FFC95B) 55%, transparent); }
      35%{ box-shadow: 0 0 0 9px color-mix(in srgb, var(--precs-gold-hi, #FFC95B) 26%, transparent); }
      100%{ box-shadow: 0 0 0 14px transparent; }
    }
    .precs-tv-op.tv-converteu{ border-radius: var(--radius-pill);
      animation: precsTvConverteu 1.8s ease-out; }
    .precs-tv-op.tv-converteu .precs-sup-av{
      box-shadow: 0 0 0 2px var(--bg-tertiary), 0 0 0 4px var(--precs-gold-hi, #FFC95B); }
    @media (prefers-reduced-motion: reduce){
      .precs-tv-op.tv-converteu{ animation: none; } }
    /* (1.1.65) AQUI MORAVAM AS DUAS CRAVAS DE n=1/n=2, e as duas eram inversao.
       "Mural de um ou dois: sobra tela, então o rosto cresce junto com o resto" era a intenção, e
       ela está certa — só que quem faz isso agora é `--tv-av`, que sai da altura da célula e
       portanto cresce sozinho quando sobra tela. A crava de 44px fazia o contrário do que dizia
       em 1440: n=1 e n=2 ficavam com 44 e n=3 caía para 28, porque a outra crava (28px, logo
       abaixo) pegava n=3.
       A do `--tv-quem` era pior, porque era MENOR que o teto do token: `1.05rem` são 16,8px, e o
       teto de `--tv-quem` é `1.15rem` = 18,4px. Então a campanha SOZINHA mostrava o nome do
       operador menor que um mural de três, que é o oposto de "sobra tela". */
    /* De três cards em diante o estado escrito sai e fica só o anel: com oito pastilhas por card
       a palavra dobra a largura de cada uma e o bloco quebra em duas fileiras — 62px a mais de
       altura por card, que num mural de três é o que joga a última fileira para fora da tela. A
       palavra continua no balão de cada avatar. */
    html[data-tv] [data-n="3"] .precs-tv-op i,
    html[data-tv] [data-n="4"] .precs-tv-op i,
    html[data-tv] [data-n="muitos"] .precs-tv-op i{ display: none; }
    /* (1.1.65) E aqui morava a crava de 28px de n=3/4/muitos, o outro lado da mesma inversão.
       A regra que sobrevive acima é a que esconde a PALAVRA do estado, que é decisão de conteúdo
       e não de caixa: com oito pastilhas por card a palavra dobra a largura de cada uma e o bloco
       quebra em duas fileiras. Essa continua. O tamanho do rosto sai do token. */

    /* ======================================================================
       (1.1.66) O MERGULHO DO CONSUMO DA API: a barra e os pontos de cor.

       O VOCABULARIO DE COR E O MESMO DA LEGENDA DO CARD, e por construcao: `data-slot` no no
       declara `--tv-api-slot`, e o quadradinho dentro dele so consome. Se um dia a paleta mudar,
       muda nos dois lugares de uma vez -- que e o ponto de a cor vir do outro projeto, para a
       mesma credencial ter a mesma cor em toda tela da casa.
       ====================================================================== */
    /* O `html[data-tv]` AQUI NAO E ENFEITE: o no da barra carrega TAMBEM `.precs-tv-api-medidor`,
       para ser pintado pela mesma funcao do card, e a regra daquela classe vem prefixada com
       `html[data-tv]`. Sem o prefixo aqui ela vencia por especificidade e pedia
       `height: var(--tv-api-med)` -- um token que so e declarado dentro da celula da parede.
       Fora dela o valor e invalido, a altura caia para `auto`, os filhos tinham `height: 100%` de
       nada, e a barra ficava com 1.262px de largura e ZERO de altura. Medido. */
    html[data-tv] .precs-tvapi-barra{
      display: flex; width: 100%; height: 16px; overflow: hidden;
      border-radius: var(--radius-pill);
      background: color-mix(in srgb, var(--text-muted) 70%, var(--bg-tertiary));
      margin-bottom: var(--space-3);
    }
    html[data-tv] .precs-tvapi-barra > i{ display: block; height: 100%; flex: 0 0 auto; }
    /* A PONTA DO ARCO E RETA NO MERGULHO DO CONSUMO, e nao redonda como na rosca da campanha.
       O percentual do ciclo e pequeno por natureza (0,3% a 5% num dia comum), e com `linecap:
       round` um arco dessa fracao vira um PONTO SOLTO no topo do anel -- a mesma sujeira que o
       card evita nao desenhando arco nenhum abaixo de meio pixel. Aqui nao da para simplesmente
       nao desenhar: o arco e a unica coisa que mostra que o ciclo comecou. Com a ponta reta ele
       vira um risco fino e honesto. */
    html[data-tv] .precs-tvamp-rosca.precs-tvapi-rosca .cs-arco{ stroke-linecap: butt; }
    .precs-tvapi-linha .precs-tvamp-lista-nome{ display: flex; align-items: center;
      gap: var(--space-2); }
    .precs-tvapi-linha .precs-tvamp-lista-nome > i{
      width: .8em; height: .8em; border-radius: var(--radius-sm); flex: 0 0 auto;
      background: var(--tv-api-slot, var(--text-muted));
    }
    .precs-tvapi-linha[data-slot="1"]{ --tv-api-slot: var(--supwa-s1); }
    .precs-tvapi-linha[data-slot="2"]{ --tv-api-slot: var(--supwa-s2); }
    .precs-tvapi-linha[data-slot="3"]{ --tv-api-slot: var(--supwa-s3); }
    .precs-tvapi-linha[data-slot="4"]{ --tv-api-slot: var(--supwa-s4); }
    .precs-tvapi-linha[data-slot="5"]{ --tv-api-slot: var(--supwa-s5); }
    .precs-tvapi-linha[data-slot="6"]{ --tv-api-slot: var(--supwa-s6); }
    /* O slot 0 e o resto nao atribuido, e a cor dele e a mesma do card: cinza de texto, e nao a
       borda -- ver o defeito medido no bloco da barra do card ("pior que feio: mentia"). */
    .precs-tvapi-linha[data-slot="0"]{ --tv-api-slot: var(--text-muted); }

    /* ---- AS LISTAS NA PAREDE ----
       O nome real é longo e todo em caixa alta com underscore
       (`CAMPANHA_ESPECIAL_SABADO_INSS_2027_PROSPECCAO_ID3`), então ele manda no layout: fica com
       a largura que sobra e QUEBRA em duas linhas em vez de virar reticências — numa parede, nome
       cortado é nome que não identifica o mailing. Os números vão para a direita, alinhados, em
       largura fixa, para o olho descer a coluna. */
    /* As colunas continuam as quatro da regra base (nome, falta, de/%, peso): redeclarar três
       aqui jogava o selo de peso numa coluna implícita e desalinhava a tabela inteira. */
    /* CINCO COLUNAS: nome, quanto falta, "de N / X%", a BARRA e o selo de peso. A barra entra
       antes do selo para as barras de todas as linhas ficarem numa coluna só — é descendo essa
       coluna com o olho que se vê qual lista está secando, que é a pergunta pela qual a parede
       existe. `clamp` na largura para ela não sumir num card estreito nem dominar num largo. */
    html[data-tv] .precs-camplista{ align-items: center;
      grid-template-columns: minmax(0, 1fr) auto auto clamp(40px, 9cqi, 96px) auto;
      border-bottom: 1px solid var(--border-color); }
    html[data-tv] .precs-camplista:last-child{ border-bottom: 0; }
    /* (1.1.33) O NOME DA LISTA QUEBRA EM DUAS LINHAS, E NÃO EM TRÊS.
       Ele quebrava livre (`overflow: visible`), e com o tipo de leitura desta versão um nome real
       de 46 caracteres virava TRÊS linhas num card de 607px — cada lista comendo a altura de três,
       o que derruba o teto de listas do card de cinco para duas. Duas linhas e reticências:
       identifica o mailing (o prefixo é o que distingue) sem gastar a altura das outras. */
    html[data-tv] .precs-camplista-nome{
      white-space: normal; overflow: hidden; text-overflow: ellipsis;
      display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
      line-height: 1.2; word-break: break-word; }
    html[data-tv] .precs-camplista-num{ font-variant-numeric: tabular-nums; }
    /* "de 372 · 8%" e o selo de peso encolhem: eles são contexto, e o que decide é o que FALTA. */
    html[data-tv] .precs-camplista-de{ font-size: .78em; color: var(--text-muted); }
    html[data-tv] .precs-camplista-peso{ font-size: .7em; }
    /* Sem peso 0 no mural (o resumo acima conta quantas são), então o selo verde de peso vira
       ruído repetido linha a linha. */
    html[data-tv] .precs-camplista-peso.ok{ display: none; }
    /* (1.1.33) A BARRA DE CONSUMO EM CÉLULA PRÓPRIA. Ver o comentário longo no `app.js`, em
       `precsSupCampListasHTML`, para o porquê — em uma frase: em `::after` absoluto contra a
       linha ela virava sublinhado da primeira palavra do nome, e a reserva de `padding-bottom`
       que abria espaço para ela perdia por especificidade em quase toda densidade.
       Altura em `em` do tamanho da linha, então ela cresce e encolhe com a escada de densidade
       SEM nenhuma regra por `data-n`. Nenhuma regra de densidade menciona esta classe, e é isso
       que impede o conflito de voltar: ele deixa de ser possível em vez de ser vencido. */
    /* (1.1.65) A TRILHA PRECISOU DE COR PROPRIA, E ISSO E CONSERTO DE DEFEITO MEDIDO.
       Ela era `var(--bg-tertiary)`, que e EXATAMENTE o fundo do card (`.precs-campsaldo`, mais
       acima neste arquivo). Razao de contraste 1,00:1 na tranquila, 1,02:1 na critica (onde o
       tint de vermelho a 9% muda o fundo) e 1,03:1 na atencao. Ou seja: a barra nao era uma
       PROPORCAO, era um traco de comprimento arbitrario sem o 100% visivel do lado.
       Enquanto o preenchimento era dourado institucional isso era feio. Com o preenchimento na
       cor do nivel passa a MENTIR, e no sentido pior: uma barra vermelha de 85% sem trilha
       visivel le como 100%, num card que ja esta gritando.
       E o mesmo defeito que a celula da API registra e conserta na fatia do resto (ver o bloco
       `data-slot="0"` mais abaixo: "pior que feio: mentia"), morando num bloco vizinho.
       O VALOR e o menor que passa 3:1 usando so o que existe: 3,09:1 contra o card tranquilo e
       3,02:1 contra o critico. `--text-muted` e o mesmo token que a celula da API escolheu para
       o mesmo problema, e 3:1 e o limiar de WCAG 1.4.11 para elemento nao textual, que e o que
       uma barra e. */
    html[data-tv] .precs-camplista-barra{
      display: inline-block; width: 100%; height: .42em; min-width: 40px;
      border-radius: var(--radius-pill); overflow: hidden;
      background: color-mix(in srgb, var(--text-muted) 70%, var(--bg-tertiary));
    }
    /* O DOURADO CONTINUA SENDO O VALOR BASE, e agora com motivo escrito: as tres regras de nivel
       logo abaixo casam com a raiz do CARD, e a visao ampliada reusa esta mesma classe num bloco
       de listas que NAO esta dentro do `.precs-tvamp-topo` (que e quem carrega a faixa la). Sem
       este valor base as barras da ampliada ficariam sem preenchimento.
       O `#B49552` saiu do `var()`: `--precs-gold-forma` e declarado em `:root`, entao aquele
       fallback nunca era alcancado -- era codigo morto, e a catraca de estilo o contava. */
    html[data-tv] .precs-camplista-barra i{
      display: block; height: 100%; border-radius: var(--radius-pill);
      background: var(--precs-gold-forma);
      transition: width var(--dur-3, .3s) cubic-bezier(.2,.7,.3,1);
    }
    /* ======================================================================
       (1.1.65) A BARRA DE CONSUMO PEGA A COR DO NIVEL DO CARD.

       DECISAO DO PEDRO, 10/09/2026, fechando o item que `docs/parede-tv-minipc.md` listava como
       decisao aberta desde a estreia da parede: dourado institucional para todas, ou a cor do
       nivel.

       ACENTO E NAO SUPERFICIE, e isto foi MEDIDO antes de aceitar, porque a regra da casa e
       "cor como acento, nunca como superficie" (o tint de fundo por estado foi recusado em
       25/08/2026 com medo nomeado de arco-iris, ver o comentario da tabela de supervisao no
       app.js). Na celula de referencia da TV de 85" (mural de 6, celula de ~823x609):
         a coluna da barra e `clamp(40px, 9cqi, 96px)` = 74,1px, e a altura `.42em` de
         `--tv-lista` = 6,05px. A 85% consumido isso da 381px de tinta por linha.
         QUATRO listas ativas (o N real: `precsSupCampListasHTML` filtra `peso > 0`, e a
         campanha de 359 listas do parque tem 2 ativas) dao 0,30% da area do card, 25% da tinta
         do filete critico e 3,4% da do tint. No teto de doze listas ainda fica abaixo do filete.
       O arco-iris tambem nao acontece por CONSTRUCAO: a cor vem do NIVEL DO CARD e nao da linha,
       entao todas as barras de um card tem UMA cor, e a parede mostra no maximo tres. E a escala
       de severidade nao e nova: e a mesma do filete, do tint e do arco.

       E CONSERTA UMA INCOERENCIA EM VEZ DE CRIAR UMA: `.cs-arco` ja pinta a rosca de
       `--accent-green` em toda campanha tranquila, e e o maior objeto colorido do card depois do
       tint. Ate agora a barra era o UNICO elemento do card que discordava do arco sobre o nivel.

       CONTRASTE, contra a trilha nova: verde 8,03:1, laranja 7,88:1, vermelho 5,31:1 (o dourado
       de antes dava 5,43:1). Os tres ficam no mesmo patamar ou acima do que havia, e muito acima
       do piso de 3:1. O vermelho contra o fundo tingido do card critico da 5,18:1.
       ====================================================================== */
    html[data-tv] .tv-calma .precs-camplista-barra i{ background: var(--accent-green); }
    html[data-tv] .tv-atencao .precs-camplista-barra i{ background: var(--accent-orange); }
    html[data-tv] .tv-critica .precs-camplista-barra i{ background: var(--accent-red); }
    /* CAMPANHA PARADA NAO GANHA COR DE NIVEL, e sem esta regra a decisao acima criaria justamente
       a discordancia que ela conserta: `precsSupTvFaixa` manda `parada` para `atencao`, entao uma
       campanha pausada com gente logada sairia com barra LARANJA, filete DOURADO (o dourado de
       "despausar", que e outro problema e outra acao) e arco CINZA. Tres cores sobre o mesmo card.
       A barra acompanha o arco, com os mesmos seletores e a mesma razao escrita la em cima: "o
       arco perde a cor de andando". */
    html[data-tv] .precs-campsaldo-parada .precs-camplista-barra i,
    html[data-tv] .precs-campsaldo:has(.precs-campsaldo-estado-fora) .precs-camplista-barra i{
      background: var(--text-muted);
    }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .precs-camplista-barra i{ transition: none; }
    }

    /* A parede não rola: o bloco de listas nunca ganha barra própria. O que não cabe é declarado
       na linha de "+N não mostradas". */
    html[data-tv] .precs-camplistas{ max-height: none; overflow: visible; }

    /* Parede sem campanha ao vivo. Centralizado e grande, porque a quatro metros a diferença
       entre "não tem nada acontecendo" e "a tela travou" precisa ser óbvia.
       O MURAL VAZIO PERDE A ALTURA MÍNIMA: ela existe para o card preencher a tela, e com zero
       cards ela virava 870px de nada empurrando o aviso e o relógio para FORA da janela — uma tela
       que diz "nenhuma campanha" com o rodapé abaixo da dobra é exatamente a que parece travada. */
    /* (1.1.33) `min-height: 0` daqui saiu junto com o `min-height` da parede: com a altura
       vindo do flex, o aviso de mural vazio nao tem mais o que desfazer. Ele fica `flex:0 0 auto`
       para nao esticar, e o espaco sobrando fica embaixo dele. */
    html[data-tv] .precs-sup-tv-parede[data-n="0"]{ flex: 0 0 auto; }
    /* (1.1.33) O `min-height: calc(100vh - 260px)` saiu: era a TERCEIRA copia da constante de
       cromo (as outras duas eram 210, em `.precs-sup-tv-parede` e no `alturaUtil` do app.js), e
       com tres numeros para o mesmo conceito eles nao andavam juntos. A altura agora vem do
       `flex: 1 1 auto` que a regra da cadeia da a este no. */
    .precs-tv-vazio{ display: flex; flex-direction: column; align-items: center;
      justify-content: center; gap: var(--space-2);
      padding: 2rem; text-align: center; }
    /* Os `var(..., valor de hoje)` daqui para baixo: o token só existe dentro de `html[data-tv]`
       (ver o bloco B2 acima), e o segundo argumento é exatamente o que a regra dizia antes. Fora
       da parede, nada muda. */
    .precs-tv-vazio b{ font-size: var(--tv-pe-tit, 1.5rem); color: var(--text-primary); }
    .precs-tv-vazio span{ font-size: var(--tv-pe-sub, .95rem); color: var(--text-muted);
      max-width: 46ch; line-height: 1.5; }

    /* ---- O RODAPÉ: relógio, carimbo, e as duas contas que ficam fora do mural ---- */
    .precs-tv-rodape{ display: flex; flex-wrap: wrap; align-items: center;
      gap: var(--space-2) var(--space-4); margin-top: var(--space-4);
      padding-top: var(--space-3); border-top: 1px solid var(--border-color);
      font-size: var(--tv-pe-txt, .82rem); color: var(--text-muted); }
    .precs-tv-relogio{ font-family: 'JetBrains Mono', monospace; font-weight: 700;
      font-size: var(--tv-pe-rel, 1.6rem); color: var(--text-primary); }
    /* O carimbo em destaque quando envelhece: uma TV sem isso é uma TV em que ninguém confia, e
       dado congelado precisa se denunciar sozinho. */
    /* (1.1.22) O carimbo LAVA para o laranja em vez de saltar: a classe é trocada pelo tique
       num nó que agora sobrevive (a comparação do rodapé neutraliza relógio e carimbo). */
    /* (1.1.66) A VERSÃO NO RODAPÉ, e o selo flutuante desligado aqui dentro.
       Ele é `position: fixed` com estilo inline (ver `precsSeloVersao`), mas o inline não declara
       `display` -- então esta regra vence sem precisar de `!important`, que é o que se quer: um
       `!important` aqui seria um martelo que alguém teria de desfazer para diagnosticar. */
    html[data-tv] #precs-selo-versao{ display: none; }
    .precs-tv-versao{
      /* `--font-num` e o token da fonte monoespacada da casa: escrever a familia a mao aqui
         subiria o contador `familia-a-mao` da catraca, e ela exige IGUALDADE. */
      font-family: var(--font-num); font-size: var(--tv-pe-min, .7rem);
      color: var(--text-muted); opacity: .75; cursor: help;
    }
    /* ======================================================================
       (1.1.68) O BOTÃO QUE TIRA E DEVOLVE O CARD DO CONSUMO DA API.

       Pedido do Pedro em 12/09/2026: em dia de campanha especial, a célula do consumo tira altura
       das campanhas, e a mesma tela precisa dos dois arranjos com uma decisão de um clique.

       ELE MORA NO RODAPÉ porque é ali que fica o que a tela diz sobre SI MESMA (relógio, carimbo,
       versão), e porque é a única faixa da parede que não disputa altura com card nenhum.

       DISCRETO POR PADRÃO, E NÃO ESCONDIDO: `--text-muted` como os vizinhos, e só ganha contorno e
       cor no hover e no foco. Numa TV de 85" que a sala inteira olha, um botão com aparência de
       botão o tempo todo é mobília piscando; mas um controle que some de vez é caminho sem volta,
       e aqui não há menu nenhum para procurar.

       O ALVO DE TOQUE segue os 32px que o resto do painel usa, com o `padding` fazendo a área —
       o rodapé tem texto de `--tv-pe-txt` e um alvo do tamanho do texto seria impossível de
       acertar em pé, na frente da TV, que é como esta tela é usada. */
    .precs-tv-alternar{
      display: inline-flex; align-items: center; gap: var(--space-2);
      min-height: 32px; padding: var(--space-1) var(--space-3);
      border: 1px solid transparent; border-radius: var(--radius-pill);
      background: transparent; color: var(--text-muted);
      font-family: inherit; font-size: var(--tv-pe-min, .7rem);
      cursor: pointer; transition: color var(--dur-1) var(--ease-1),
        border-color var(--dur-1) var(--ease-1), background var(--dur-1) var(--ease-1);
    }
    .precs-tv-alternar:hover{ color: var(--text-primary); border-color: var(--border-color);
      background: var(--bg-tertiary); }
    .precs-tv-alternar:focus-visible{ color: var(--text-primary);
      outline: 2px solid var(--precs-gold-hi); outline-offset: 2px; }
    /* ESCONDIDO É UM ESTADO, e ele se anuncia: sem isto, quem chega na sala não tem como saber que
       existe um card fora da tela, e o botão lê como "mostrar" sem dizer que já foi decidido. */
    .precs-tv-alternar[aria-pressed="true"]{ color: var(--st-manual); }
    @media (prefers-reduced-motion: reduce){ .precs-tv-alternar{ transition: none; } }
    .precs-tv-carimbo{ transition: color .4s ease; }
    .precs-tv-carimbo.velho{ color: var(--accent-orange); font-weight: 700; }
    @media (prefers-reduced-motion: reduce){ .precs-tv-carimbo{ transition: none; } }
    .precs-tv-fora{ cursor: help; }

    /* ======================================================================
       (1.1.35) QUEM ESTÁ EM DISCAGEM MANUAL, COM ROSTO, NO RODAPÉ.

       PEDIDO DO PEDRO, 27/08/2026: "aparece 3 operadores em discagem manual e não sei quem são os
       3, poderia aparecer a foto deles lá embaixo em laranja".

       A campanha manual continua FORA do mural (ela não tem lista para acabar, e um card grande
       dizendo "—" numa parede de urgência é ruído). O que muda é a qualidade do que o rodapé diz:
       "3 operador(es)" é contagem, e a pergunta que ela levanta é "quem".

       LARANJA É A COR DO ESTADO MANUAL no painel inteiro (`--st-manual`), então o anel do avatar
       aqui usa exatamente ela. Nenhum token novo, e quem já conhece a cor na aba de Operadores
       reconhece na parede sem aprender nada. */
    .precs-tv-manual{ display: inline-flex; align-items: center; gap: var(--space-2);
      flex-wrap: wrap; }
    .precs-tv-manual > i{ font-style: normal; font-weight: 700; text-transform: uppercase;
      letter-spacing: var(--ls-caps); font-size: .8em;
      color: var(--st-manual, #f59e0b); }
    .precs-tv-manual-op{ display: inline-flex; align-items: center; gap: 5px; cursor: help; }
    /* O ANEL SEGUE O MOLDE DA PASTILHA DO CARD: dois box-shadow, o primeiro na cor do fundo para
       abrir um vão e o segundo na cor do estado. É o que faz o anel ler como anel e não como borda
       grudada na foto. */
    .precs-tv-manual-op .precs-sup-av{
      width: var(--tv-pe-av, 26px); height: var(--tv-pe-av, 26px); flex: 0 0 auto;
      /* A inicial acompanha a foto: 0,38 do lado é o que reproduz os .62rem de hoje nos 26px de
         hoje, então o piso do token devolve exatamente o tamanho antigo. */
      font-size: calc(var(--tv-pe-av, 26px) * .38);
      box-shadow: 0 0 0 2px var(--bg-primary, #131315),
                  0 0 0 4px var(--st-manual, #f59e0b);
    }
    .precs-tv-manual-op b{ font-weight: 700; color: var(--text-secondary); white-space: nowrap; }
    .precs-tv-manual-mais{ font-weight: 700; color: var(--st-manual, #f59e0b);
      font-family: 'JetBrains Mono', monospace; }
    .precs-tv-sair{ margin-left: auto; background: transparent;
      border: 1px solid var(--border-color); border-radius: var(--radius-pill);
      color: var(--text-muted); font-family: 'Manrope', 'Poppins', sans-serif;
      font-size: var(--tv-pe-min, .7rem); padding: var(--space-1) var(--space-3);
      cursor: pointer; }
    .precs-tv-sair:hover{ border-color: var(--precs-gold, #B49552); color: var(--precs-gold, #B49552); }


    /* ======================================================================
       ACABANDO AGORA (`tv-acabando`) e O HOLOFOTE (`tv-foco`).
       As duas únicas coisas da parede que se movem sozinhas, e as duas existem pelo mesmo motivo:
       ser vistas por quem NÃO está olhando a tela. Sem som, por decisão registrada — alerta
       sonoro em sala de operação é desligado no primeiro dia, e aí o alerta deixa de existir sem
       ninguém perceber.
       Nenhuma cor nova: é a mesma escala de severidade do card, com a área e o brilho mudando.
       ====================================================================== */
    @keyframes precsTvAcabando{
      0%, 100%{ box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent-red) 55%, transparent); }
      50%{ box-shadow: 0 0 0 10px color-mix(in srgb, var(--accent-red) 0%, transparent); }
    }
    html[data-tv] .tv-acabando{
      border-color: color-mix(in srgb, var(--accent-red) 55%, var(--border-color));
      animation: precsTvAcabando 2.4s ease-out infinite;
    }
    /* O número em destaque: numa parede, o que decide a ação é "quantos faltam", e é ele que a
       pessoa do outro lado da sala precisa conseguir ler antes do nome da campanha. */
    html[data-tv] .tv-acabando .precs-campsaldo-num b{ color: var(--accent-red); }
    html[data-tv] .tv-acabando .precs-campsaldo-proj{ font-weight: 700; }

    /* O HOLOFOTE. As outras baixam o brilho, e não somem: quem estava lendo a terceira campanha
       continua conseguindo ler. É a diferença entre destacar e esconder — e foi por esconder que
       o planejamento tinha decidido contra rotação. */
    html[data-tv] .precs-sup-tv-parede.tv-holofote .precs-campsaldo,
    html[data-tv] .precs-sup-tv-parede.tv-holofote .precs-tv-resto,
    html[data-tv] .precs-sup-tv-parede.tv-holofote .precs-tv-api{
      opacity: .55; transition: opacity var(--dur-3, .3s) ease, transform var(--dur-3, .3s) ease;
    }
    html[data-tv] .precs-sup-tv-parede.tv-holofote .precs-campsaldo.tv-foco{
      opacity: 1; transform: scale(1.015);
      box-shadow: 0 0 0 2px color-mix(in srgb, var(--precs-gold, #B49552) 60%, transparent),
                  var(--shadow-lg);
    }
    /* Sem holofote, ninguém fica com opacidade parcial pendurada. `box-shadow` entrou na lista por
       causa da levitação do cursor (1.1.35): sem ele a sombra aparecia de estalo enquanto o card
       subia animado, e o efeito lia como falha de render. */
    html[data-tv] .precs-sup-tv-parede .precs-campsaldo{
      transition: opacity var(--dur-3, .3s) ease, transform var(--dur-3, .3s) ease,
                  box-shadow var(--dur-3, .3s) ease;
    }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .tv-acabando{ animation: none;
        border-color: color-mix(in srgb, var(--accent-red) 55%, var(--border-color)); }
      html[data-tv] .precs-sup-tv-parede .precs-campsaldo{ transition: none; }
      html[data-tv] .precs-sup-tv-parede.tv-holofote .precs-campsaldo{ opacity: 1; }
      html[data-tv] .precs-sup-tv-parede.tv-holofote .precs-campsaldo.tv-foco{ transform: none; }
    }

    /* ---- MOVIMENTO ----
       Entrada de card novo e saída de card que deixou o mural. O reordenamento é FLIP, escrito no
       JS (transform inline), e não tem regra aqui de propósito: a duração viaja com a chamada. */
    @keyframes precsTvNasce{
      from{ opacity: 0; transform: translateY(10px) scale(.985); }
      to{ opacity: 1; transform: none; }
    }
    html[data-tv] .tv-nasce{ animation: precsTvNasce 240ms cubic-bezier(.2,.7,.3,1); }
    html[data-tv] .tv-saindo{ opacity: 0; transform: scale(.98);
      transition: opacity 200ms ease, transform 200ms ease; pointer-events: none; }
    @media (prefers-reduced-motion: reduce){
      html[data-tv] .tv-nasce{ animation: none; }
      html[data-tv] .tv-saindo{ transition: none; }
      .precs-tv-lig-fill{ transition: none; }
    }
    @media (prefers-reduced-motion: reduce){ .precs-camplista-btn{ transition:none; } }

    /* (1.1.11) A situacao da ligacao na ficha: em curso, encerrada, ou ultima registrada. As
       tres sao afirmacoes diferentes e a versao anterior chamava todas de "a ligacao dele". */
    .precs-ficha-fim{ color:var(--accent-manual-text); font-weight:600; }


    /* ====================================================================== */
    /* ======================================================================
       (1.1.46) A VISTA DE BOLSO DA ABA CAMPANHAS
    ----------------------------------------------------------------------
       PARA QUE ELA EXISTE. O pedido era "ver a aba de campanha no celular", e o caminho
       tentado primeiro foi o modo TV — que nao vai, por sete impedimentos estruturais
       (o numero de colunas sai da QUANTIDADE de campanhas e nao conhece a largura; o laco
       de encaixe mede so a altura, e um celular em retrato tem altura sobrando e largura
       faltando, entao ele conclui que cabe; os pisos de fonte foram calibrados para leitura
       a quatro metros; a rolagem esta proibida por construcao; e a navegacao e 100%
       teclado, sem um unico handler de toque). Esta vista responde a mesma pergunta sem
       nada disso: o saldo das campanhas, fora da mesa.

       POR QUE E SO CSS, e isso importa. A ordem em que os cards saem ja e a ordem certa
       para leitura de urgencia: `precsSupCampComparador` poe a campanha SECA com gente
       parada primeiro, depois a pausada com gente, e ordena o resto por minutos ate
       esgotar. Nada de JS precisou mudar, o que significa risco zero para a tela grande.

       O DEFEITO CONCRETO QUE ELA CONSERTA: a grade e
       `repeat(auto-fill, minmax(420px, 1fr))`, e uma trilha de 420px num aparelho de 390px
       estoura a viewport — a pagina inteira rola na horizontal, que e o unico jeito de
       perder o rumo numa tela de leitura.

       O QUE SAI DE CENA, e por acao e nao por espaco:
        • a alca de arraste e a estrela de fixar. Arrastar card por toque nao existe (o
          SortableJS e ligado com handle de mouse), e reordenar e coisa de quem esta na
          mesa. Sem elas o topo do card fica so com nome e contagem de operadores.
        • a rosca de consumo encolhe em vez de sair: ela e a unica leitura que se faz sem
          ler numero, e num aparelho na mao isso vale mais, nao menos.

       O breakpoint e 620px de proposito: pega retrato de qualquer celular e fica bem abaixo
       do menor da supervisao (760px), entao nao disputa com nenhum deles. O escopo
       `html:not([data-tv])` garante que a parede nunca cai aqui — ela tem grade propria e o
       proprio motor de escala. */
    /* (1.1.49) ESTE BLOCO ERA UM @media (max-width: 620px). Ver o comentario de
       precsAplicarAparelho no index.html: o criterio virou o atributo html[data-aparelho],
       que inclui a ALTURA (paisagem de celular tem 844px de largura e passava por estacao) e
       que um teste consegue LER — media query nao e observavel de fora. O limiar vive num
       lugar so, em PRECS_APARELHO_LARG/ALT. */

      /* O `zoom` DA ESTACAO SAI DE CENA AQUI, e este foi o achado da medicao: o painel roda com
         `zoom: 0.85` no modo normal (index.html), e ele vale para o documento todo — inclusive
         no celular. Medido em 390px: a rosca declarada com 84px chegava na tela com 71, e o
         nome da campanha com 16px de fonte virava 13,6px reais. O zoom existe para CABER MAIS
         numa estacao de 1366px; num aparelho na mao ele faz o oposto do que se quer.
         A parede ja resolve isso do mesmo jeito (`html[data-tv][data-aparelho][data-fonte] main{ zoom:1 }`), e a
         conta de escala do CSS acompanha pelo `--precs-escala`.

         O `[data-fonte]` no seletor NAO E ENFEITE: a regra do zoom vive no `<style>` critico do
         <head>, que fica DEPOIS dos <link> de propósito, e `html[data-fonte="normal"] main` tem
         especificidade (0,1,2). Sem o segundo atributo aqui daria empate, e quem vem depois
         ganharia — foi exatamente o que aconteceu na primeira escrita desta regra: a rosca
         continuou chegando na tela com 71px em vez de 84. E o mesmo motivo pelo qual a parede
         usa dois atributos. */
      html[data-fonte][data-aparelho]:not([data-tv]) header,
      html[data-fonte][data-aparelho]:not([data-tv]) main{ zoom:1; }
      /* (1.1.49) A VARIAVEL VAI NA RAIZ, E NAO NO `main`. A 1.1.47 declarou `--precs-escala:1`
         junto do `zoom` ali em cima, e isso deixou a variavel MENTINDO: medido em 360px, ela
         valia 1 no `<main>` e continuava 0,85 no `<html>`. Quem le do `:root` — ou de qualquer
         elemento FORA do main — pegava 0,85 com o zoom valendo 1.
         Nao e detalhe: treze contas dividem por ela, entre elas a largura do modal do espelho
         (`10-supervisao.css:782`), a altura do mailing (`00-base.css:1422`) e a telemetria
         (`app.js:1153`). E o espelho e justamente um dos que vivem fora do `<main>` (o overlay
         esta em index.html:994; o main abre em :1055), o que explica por que ele nao recebeu o
         reset de escala e chegou ao celular com zoom 0,85 e altura fixa em vh. */
      html[data-fonte][data-aparelho]:not([data-tv]){ --precs-escala:1; }
      /* (1.1.49) E O ESPELHO ENTRA NO RESET, senao a linha de cima passa a MENTIR do outro lado.
         Medido com a sonda em 390x844, com a variavel ja corrigida na raiz: `--precs-escala`
         valia 1 e o modal continuava com `zoom: 0.85` (40-temas.css:1129). O modal dimensiona
         com `height: min(900px/escala, 78vh/escala)` — com escala 1 ele pediu 658px e a tela
         entregou 559px, porque o zoom encolheu depois da conta. Ou seja: consertar a variavel
         sem consertar o zoom deixa o modal 15% menor do que ele mesmo pediu.
         O zoom do modal existe para o espelho renderizar IGUAL a tela do operador (o comentario
         em 40-temas.css:1121 explica), e num aparelho a tela do operador nao esta em jogo: o que
         esta em jogo e ler no celular. Ele acompanha o `main`. */
      html[data-fonte][data-aparelho]:not([data-tv]) .precs-espelho-modal{ zoom:1; }
      /* E O HEADER PRECISA CABER, senao desligar o zoom troca um problema por outro pior.
         Medido: `#header-right` (nome, razao social, Sair, "Conectado") tem 248px, e com os
         32px de padding lateral de cada lado a borda direita dele chegava a 410px numa
         viewport de 390 — a pagina inteira rolando na horizontal. Com o zoom de 0,85 isso
         caberia por acidente; ao desliga-lo, o header foi o primeiro a estourar.
         Tres ajustes, e nenhum esconde estado: o padding lateral encolhe, as duas metades
         passam a poder quebrar, e a RAZAO SOCIAL sai — ela e a linha mais longa e a menos
         acionavel das duas (o nome e o ramal ficam). O chip "Conectado" continua com texto,
         de proposito: reduzi-lo ao ponto colorido usaria cor como sinal unico, contra a
         propria regra do projeto.
         Isto vale para o painel INTEIRO em tela estreita, e nao so para a supervisao: um
         header que estoura a viewport nao e melhor em nenhuma tela. */
      /* (1.1.47) O padding lateral caiu de --space-3 para --space-2, e sao 8px que decidem uma
         LINHA inteira de header. Medido: a logo (92px) mais o cluster do usuario (240px) somam
         332px. Em 390px havia folga; em 360px sobravam 328 uteis com o padding de 12px de cada
         lado, e o header quebrava em duas faixas — 105px em vez de 73. Com 8px de cada lado
         sao 344 uteis, e ele cabe nos dois tamanhos.
         O `flex-wrap` FICA: ele e a rede de seguranca para o aparelho que for mais estreito
         ainda, e sem ele o header volta a estourar a viewport em vez de quebrar. */
      html[data-aparelho]:not([data-tv]) header{ padding-left:var(--space-2); padding-right:var(--space-2);
        flex-wrap:wrap; gap:var(--space-2) var(--space-1); }
      html[data-aparelho]:not([data-tv]) .header-right{ flex-wrap:wrap; gap:var(--space-2) var(--space-3); }
      html[data-aparelho]:not([data-tv]) .user-company{ display:none; }
      html[data-aparelho]:not([data-tv]) .precs-sup-wrap{ padding-left:var(--space-2); padding-right:var(--space-2); }
      /* O SUBTITULO FICA SO COM O RELOGIO. "Acompanhamento ao vivo da operacao" ocupa duas
         linhas em 390px, e o " · " mais o relogio ficam pendurados no fim da segunda — foi o
         que a primeira captura mostrou. O rotulo e redundante com o titulo logo acima
         ("Supervisao · <nome>"); o relogio nao e, porque e ele que diz que a tela esta viva. */
      html[data-aparelho]:not([data-tv]) #sup-sub-rotulo,
      html[data-aparelho]:not([data-tv]) #sup-sub-sep{ display:none; }
      /* O TRILHO DE ABAS TEM DE CABER INTEIRO, e ele estourava por pouco. Os botoes sao
         `flex:1` sem `min-width:0`, entao respeitam o min-content do proprio texto: somados,
         "Operadores", "Campanhas" e o "WhatsApp" com icone pedem ~345px, e em 360px sobram 344
         depois do padding. O resultado media `rolaHorizontal: false` (a pagina nao rolava) e
         mesmo assim a aba WhatsApp ficava CORTADA na borda — ou seja, o supervisor perdia o
         acesso a ela sem nada indicar que havia mais coisa ali.
         Escopado ao trilho da supervisao de proposito: `.precs-seg` e compartilhado com as
         Preferencias e o WhatsApp, e nenhum dos dois tem este aperto.
         E o seletor e `.precs-sup-abas.precs-seg`, SEM espaco: as duas classes vivem no MESMO
         elemento (`<div class="precs-sup-abas precs-seg" role="tablist">`). A primeira escrita
         usou o descendente e nao casou com nada — a aba continuou cortada na captura. */
      html[data-aparelho]:not([data-tv]) .precs-sup-abas{ max-width:none; }
      html[data-aparelho]:not([data-tv]) .precs-sup-abas.precs-seg{ gap:var(--space-1); }
      html[data-aparelho]:not([data-tv]) .precs-sup-abas.precs-seg button{
        min-width:0; font-size:.78rem;
        padding-left:var(--space-1); padding-right:var(--space-1);
      }
      /* (1.1.60) A FAIXA DE SUB-ABAS DO WHATSAPP ENTRA NAS MESMAS TRES REGRAS, e a razao de ela
         estar de fora era uma letra: as regras acima miram `.precs-sup-abas` e aquela faixa e
         `.precs-supwa-abas` (com `wa`). Nenhuma casava, entao os botoes ficavam com o padding e
         o corpo de tela grande num aparelho — e desde 09/09/2026 sao QUATRO botoes, com
         "Conversas" no fim, o que aperta ainda mais. E o mesmo tipo de erro que o comentario
         logo acima documenta ("a primeira escrita usou o descendente e nao casou com nada").
         O corpo sai de `--fs-4` (12px) e nao do `.78rem` da regra irma logo acima: os dois
         valem 12,48px contra 12px, dentro da tolerancia de 0,50px do `teste-tipografia`, e a
         irma migra quando a faixa dela migrar. Escrever cru aqui so para "combinar" faria a
         catraca de estilo SUBIR, e ela reprova aumento. */
      html[data-aparelho]:not([data-tv]) .precs-supwa-abas{ max-width:none; }
      html[data-aparelho]:not([data-tv]) .precs-supwa-abas.precs-seg{ gap:var(--space-1); }
      html[data-aparelho]:not([data-tv]) .precs-supwa-abas.precs-seg button{
        min-width:0; font-size:var(--fs-4);
        padding-left:var(--space-1); padding-right:var(--space-1);
      }
      /* Uma campanha por linha, e o minimo da trilha e ZERO — nao `auto`.
         `1fr` sozinho significa `minmax(auto, 1fr)`, e `auto` respeita o MIN-CONTENT do item.
         Medido em 360px: a grade ficava com 260px e o card com 345, ou seja, o item estourava o
         proprio grid e levava a pagina a rolar na horizontal. `minmax(0, 1fr)` deixa a trilha
         encolher abaixo do min-content e devolve a responsabilidade a quem sabe lidar com ela:
         os filhos do card, que ja tem `min-width:0` com `text-overflow:ellipsis`. */
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-grade{
        grid-template-columns:minmax(0, 1fr);
        gap:var(--space-3);
        padding:var(--space-3) 0;
        /* `1fr` nas fileiras existe para igualar alturas numa fileira de varios cards. Com um
           card por linha ele so obriga todos a ter a altura do MAIS ALTO, o que enche a tela
           de vazio na campanha de discagem manual (que nao tem lista). */
        grid-auto-rows:auto;
      }
      html[data-aparelho]:not([data-tv]) .precs-campsaldo{ padding:var(--space-4); }
      /* A ROSCA ENCOLHE, E O `flex-wrap` FICA COMO ESTA. A primeira escrita desta regra punha
         `nowrap` aqui, para manter rosca e saldo lado a lado. Estava errado, e a medicao disse
         por que: com `nowrap` o corpo passa a ter largura minima intrinseca (rosca + os 180px
         de base do `-lado`), o card travou em 345px e a PAGINA INTEIRA passou a rolar na
         horizontal — 20px de excesso em 390, 50px em 360. Rolagem horizontal numa tela que
         existe para ser lida de relance e pior que o saldo cair uma linha.
         O CSS base ja tinha decidido isto, e o comentario dele esta duas mil linhas acima: "em
         card estreito a rosca vai para cima e o texto embaixo, em vez de as duas colunas se
         estrangularem". Com a rosca em 84px os dois cabem lado a lado em 390px, e abaixo disso
         a quebra acontece sozinha — que e o comportamento certo. */
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-corpo{ gap:var(--space-4); }
      /* 68px e o `flex-basis` de 120 no lado: e o par que faz rosca e saldo caberem NA MESMA
         LINHA num card de 290px. Com a rosca em 84 e a base de 180 do CSS grande, o corpo
         quebrava e cada card passava a ocupar mais de meia tela de altura — numa lista que
         existe para ser percorrida, altura e o recurso escasso, e nao largura.
         O `flex-basis` menor nao encolhe o numero: `.precs-campsaldo-num` cresce com `flex:1`
         e o saldo continua em 1,7rem. */
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-rosca{ width:68px; height:68px; }
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-lado{ flex:1 1 120px; }
      /* DENTRO DA ROSCA PEQUENA CABE UM DADO, e nao dois. O rotulo "consumido" e
         `position:absolute; bottom:26%`, uma porcentagem calibrada para a rosca de 132px (e
         que a parede reaproveita a 260px). Em 68px os 26% caem em cima do numero, e a captura
         mostrou "CONSUMIDO" atravessando o "73%".
         Sai o rotulo, fica a porcentagem: o `%` ao lado do numero ja diz o que ele e, e o
         circulo preenchido diz o resto sem palavra nenhuma. O numero tambem desce de 1,7rem
         para 1,15rem, que e o tamanho que respira dentro de 68px. */
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-rosca-lbl{ display:none; }
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-rosca-txt{ font-size:1.15rem; }
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-rosca-txt i{ font-size:.7rem; }
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-num{ font-size:1.7rem; }
      /* O NOME QUEBRA EM DUAS LINHAS em vez de ser cortado. Na tela grande o `nowrap` com
         ellipsis e a escolha certa: o card tem 420px e o nome cabe. Num card de 300px,
         "Campanha Automatica - ID 02" viraria "Campanha Automa…", e o que distingue duas
         campanhas costuma estar justamente no fim do nome (o ID). Duas linhas com clamp e o
         mesmo padrao que a parede de TV ja usa, e pelo mesmo motivo. */
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-nome{
        white-space:normal; line-height:1.2;
        display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2;
      }
      /* Arraste e fixar sao gestos de mesa. Ver o bloco acima. */
      html[data-aparelho]:not([data-tv]) .precs-campsaldo-acoes{ display:none; }
    

    /* ======================================================================
       (1.1.47) A SUPERVISAO NO CELULAR: INVERTER CROMO E DADO
    ----------------------------------------------------------------------
       MEDIDO em producao, 390x844, com a aba Operadores aberta e os cinco cards de estado:

         header ....... 115px
         titulo ....... 113px   (o nome do supervisor quebra em duas linhas)
         abas .........  44px
         cards ........ 170px   (grade 2x3; dois dos cinco costumam estar em zero)
         filtros ...... 120px   (tres controles, um por linha)
                        -----
         cromo ........ 562px, e a TABELA comeca em 668px

       Sobravam 176px de 844 para a tabela de agentes — cabia uma linha e meia. Ou seja: 79%
       da tela era moldura e 21% era o dado que a tela existe para mostrar. Nos prints do teste
       de 02/09/2026 a tabela nao aparecia em nenhum.

       Nenhum destes blocos e supérfluo na ESTACAO: o titulo grande, os cards em grade e os
       filtros em linha propria sao o que faz a tela funcionar num monitor. O que muda no
       celular e a proporcao — e por isso tudo aqui vive na media query, e a metade do
       teste-vista-bolso que mede 1366px existe para garantir que nada disto vaze.

       A ORDEM DAS ESCOLHAS, do que rende mais para o que rende menos:
        • os CARDS viram faixa horizontal rolavel (170 -> ~56). Cinco chips numa tira que rola
          com o dedo, em vez de uma grade que empilha. E o maior ganho isolado, e o gesto e o
          que a pessoa ja espera de uma fila de filtros no telefone.
        • o TITULO desce de 1,5rem (113 -> ~44). O nome do supervisor cabe numa linha.
        • o HEADER encolhe a logo (115 -> ~64).
        • os FILTROS passam a duas linhas em vez de tres (120 -> ~88): a busca ocupa a largura
          e os dois seletores dividem a linha de baixo. Ficaria melhor atras de um botao
          "Filtros", e isso pede HTML e JS novos — fica para quando alguem pedir. */
    /* (1.1.49) ESTE BLOCO ERA UM @media (max-width: 620px). Ver o comentario de
       precsAplicarAparelho no index.html: o criterio virou o atributo html[data-aparelho],
       que inclui a ALTURA (paisagem de celular tem 844px de largura e passava por estacao) e
       que um teste consegue LER — media query nao e observavel de fora. O limiar vive num
       lugar so, em PRECS_APARELHO_LARG/ALT. */

      /* OS CARDS DE ESTADO VIRAM UMA TIRA. `overflow-x:auto` com `flex:0 0 auto` nos itens: a
         fila rola no dedo e nenhum card e cortado ao meio. O `scroll-snap` faz a tira parar
         com um card inteiro na borda, em vez de no meio de um numero. */
      html[data-aparelho]:not([data-tv]) .precs-sup-cards{
        display:flex; gap:var(--space-2); overflow-x:auto; overflow-y:hidden;
        margin-bottom:var(--space-3); padding-bottom:var(--space-1);
        scroll-snap-type:x proximity;
        /* A tira sangra ate a borda da tela e volta com padding proprio: cortar um card na
           borda e o que anuncia "tem mais coisa para o lado". */
        scrollbar-width:none;
      }
      html[data-aparelho]:not([data-tv]) .precs-sup-cards::-webkit-scrollbar{ display:none; }
      /* Um respiro no fim da tira: sem ele o ultimo card encosta na borda e, ao parar ali, o
         numero fica cortado ao meio — o que parece defeito em vez de "acabou a fila". */
      html[data-aparelho]:not([data-tv]) .precs-sup-cards::after{
        content:''; flex:0 0 var(--space-2);
      }
      html[data-aparelho]:not([data-tv]) .precs-sup-card{
        flex:0 0 auto; width:auto; min-width:96px; scroll-snap-align:start;
        padding:var(--space-2) var(--space-3);
        /* Numero e rotulo em COLUNA continuam, mas colados: o que economiza altura aqui e o
           padding e o tipo, nao o arranjo — trocar para linha faria o numero competir com o
           rotulo por largura numa tira que ja rola. */
      }
      html[data-aparelho]:not([data-tv]) .precs-sup-num{ font-size:1.25rem; }
      html[data-aparelho]:not([data-tv]) .precs-sup-lbl{ font-size:.62rem; margin-top:0; }
      /* A barra de fatia sai: numa tira de 96px ela e um fio de 60px que nao se le, e a
         informacao dela (a proporcao do quadro) esta no proprio numero ao lado dos outros. */
      html[data-aparelho]:not([data-tv]) .precs-sup-card-fatia{ display:none; }

      /* O TITULO EM UMA LINHA. 1,5rem de Sora com "Supervisao · Pedro Augusto" quebra em duas
         em 390px, e as duas linhas de titulo custam mais que o titulo inteiro informa. */
      html[data-aparelho]:not([data-tv]) .precs-sup-titulo{ font-size:1.15rem; line-height:1.2; }

      /* A LOGO ENCOLHE PARA O HEADER CABER EM UMA LINHA, e o numero e o que decide.
         O header e `flex-wrap` desde a 1.1.46 (sem isso ele estourava a viewport). Medido em
         390px: a logo ocupa 130px e o cluster do usuario 240px — 370 contra 358 disponiveis,
         entao ela quebrava em duas linhas de 40px e o header ficava com 115. Com a logo em
         24px de altura (~92px de largura), a soma cai para 332 e o header vira UMA linha.
         Trocar 43px de altura por uma marca menor num aparelho na mao e barganha boa: a
         identidade continua na tela, e o que se ganha e linha de tabela.
         O `.precs-brand-logo` e a logo do header; o `.logo-circle` e outro elemento (o avatar
         do card de chamada), e nao tem nada a ver com esta conta.

         22px E NAO 24, E A FOLGA E O PONTO. Com 24px a logo fica em 92px, e em 360px a conta
         dava 92 + 12 de gap + 240 do cluster = 344, contra 344 uteis — EMPATE EXATO, e o
         navegador quebra a linha no empate. Foram duas medicoes para achar isso. Com 22px
         (~77px) e o gap horizontal em 4px, sobram 23px de folga, que e o que faz a regra valer
         para o aparelho de 360 e nao so para o de 390. */
      html[data-aparelho]:not([data-tv]) .precs-brand-logo{ height:22px; width:auto; }
      /* O NOME SAI DO HEADER, e o que o justifica e REDUNDANCIA, nao falta de espaco: a tela
         de supervisao escreve "Supervisao · <nome>" no titulo, e a do operador escreve
         "Boa tarde, <nome>" na faixa de boas-vindas. O nome aparecia duas vezes na mesma
         dobra, e era ele que impedia o cluster de caber numa linha.
         Medido em 390px: engrenagem 40 + WhatsApp 40 + nome ~120 + avatar 40 + Sair 60 +
         "Conectado" 130 = 430px, contra 358 disponiveis. Sem o nome, 310 — cabe.
         O AVATAR FICA: e ele que identifica quem esta logado numa estacao compartilhada, e
         custa 40px contra os 120 do nome. */
      html[data-aparelho]:not([data-tv]) .precs-user-nome{ display:none; }

      /* OS FILTROS EM DUAS LINHAS: a busca ocupa a largura, os dois seletores dividem a de
         baixo. O `min-width:0` E O QUE FAZ ISSO FUNCIONAR, e sem ele a primeira escrita desta
         regra nao mudou nada: `flex-basis: calc(50% - 8px)` da 162px, mas um item flex nao
         encolhe abaixo do min-content do proprio conteudo, e "Ordenar: ligacoes do dia" e mais
         largo que isso. Os dois continuavam pegando uma linha cada, e o bloco passou de 120
         para 128px — o contrario do pretendido.
         O texto do seletor passa a truncar, e e uma troca deliberada: quem escolheu o filtro
         sabe o que escolheu, e a lista inteira continua legivel quando ele abre. */
      html[data-aparelho]:not([data-tv]) .precs-sup-busca{ flex:1 1 100%; max-width:none; }
      html[data-aparelho]:not([data-tv]) .precs-sup-filtros select{
        flex:1 1 calc(50% - var(--space-2)); max-width:none; min-width:0;
        font-size:.74rem; padding:var(--space-1) var(--space-2);
      }
    

    /* ======================================================================
       (1.1.47) A TABELA DE AGENTES VIRA CARTAO NO CELULAR
    ----------------------------------------------------------------------
       A linha e `grid-template-columns: 2.1fr 1.5fr 1fr .45fr .45fr 1.15fr 206px` — sete
       colunas, e a ultima com largura FIXA. Em 390px isso nao cabe de jeito nenhum: sete
       trilhas para 358px uteis dariam 51px cada, e uma delas ja pede 206.

       O projeto ja resolveu este mesmo problema uma vez, e a solucao esta a mil linhas daqui:
       `estilo-componentes.css` faz as tabelas da aba WhatsApp virarem cartao abaixo de 1100px,
       com o comentario "Estreito: as linhas viram cartões". Aqui e o mesmo padrao.

       O ARRANJO, em TRES faixas, na ordem em que o olho procura:
         nome do agente ............................. estado
         campanha (largura inteira)
         ligacoes  conversoes  tempo ......... acoes

       A primeira escrita usava QUATRO faixas, com as acoes na largura inteira embaixo, e a
       medicao reprovou: 152px por cartao, dois na tela. Numa lista que pode ter cinquenta
       operadores, densidade e o requisito — as acoes voltaram para a linha dos numeros e os
       botoes ficaram compactos.

       O QUE NAO FOI FEITO, e por que: reduzir as acoes a icones sem rotulo caberia em duas
       faixas, e contraria uma decisao de 1.1.15 registrada no proprio app.js — o botao de
       escutar JA foi "um chip de 11px pendurado na sub-linha do ramal", e o retorno foi
       "pequeno e sem rotulo demais". Rotulo curto com alvo de toque de verdade vale a faixa.

       POR QUE `nth-child` E NAO CLASSES: as sete celulas nao tem classe propria — a de
       campanha sai de `th('Campanha', '')`, sem nome. A posicao e o unico seletor disponivel,
       e ela E estavel por construcao: o cabecalho (`precs-sup-th`) e a linha de agente
       compartilham a MESMA grade, entao acrescentar coluna quebraria as duas juntas e de forma
       visivel. O teste-vista-bolso conta as celulas para que uma coluna nova nao entre em
       silencio e embaralhe o cartao.

       O CABECALHO SAI DE CENA: sete rotulos de coluna sobre um layout que nao tem colunas
       seria um cartao de titulos sem tabela embaixo. Os cartoes se explicam pelo proprio
       arranjo, e e assim que a aba WhatsApp tambem faz. */
    /* (1.1.49) ESTE BLOCO ERA UM @media (max-width: 620px). Ver o comentario de
       precsAplicarAparelho no index.html: o criterio virou o atributo html[data-aparelho],
       que inclui a ALTURA (paisagem de celular tem 844px de largura e passava por estacao) e
       que um teste consegue LER — media query nao e observavel de fora. O limiar vive num
       lugar so, em PRECS_APARELHO_LARG/ALT. */

      html[data-aparelho]:not([data-tv]) .precs-sup-th{ display:none; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha{
        grid-template-columns:1fr auto auto auto;
        grid-template-areas:
          "nome  nome  nome   st"
          "camp  camp  camp   camp"
          "lig   conv  tempo  acoes";
        row-gap:var(--space-1);
        padding:var(--space-2) var(--space-3);
        align-items:start;
      }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(1){ grid-area:nome; min-width:0; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(2){ grid-area:camp; min-width:0; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(3){ grid-area:st;   justify-self:end; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(4){ grid-area:lig; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(5){ grid-area:conv; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(6){ grid-area:tempo; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(7){
        grid-area:acoes; justify-self:end; display:flex; gap:var(--space-1);
      }
      /* Botoes compactos, e o piso e o ALVO DE TOQUE: 32px de altura com o rotulo em .68rem.
         Abaixo disso o dedo erra, e errar em "Deslogar" custa a sessao de um operador. */
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(7) button,
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(7) .btn{
        padding:var(--space-1) var(--space-2); font-size:.68rem; min-height:32px;
        white-space:nowrap;
      }
      /* Os tres numeros ganham rotulo, porque sem o cabecalho eles ficariam tres numeros soltos
         na mesma linha. `::before` com o rotulo em caixa alta e o mesmo recurso que a aba
         WhatsApp usa nos cartoes dela. */
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(4)::before{ content:'lig '; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(5)::before{ content:'conv '; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(6)::before{ content:'tempo '; }
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(4)::before,
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(5)::before,
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(6)::before{
        font-size:.6rem; text-transform:uppercase; letter-spacing:var(--ls-caps);
        color:var(--text-muted); font-weight:600;
      }
      /* `nowrap` NOS TRES, e a captura mostrou por que: com "28min" no tempo (mais largo que
         "3min"), a coluna das ligacoes comprimiu e o numero caiu para a linha de baixo,
         separado do proprio rotulo — "LIG" em cima e "23" embaixo. Rotulo e numero sao uma
         unidade de leitura; quebrar entre eles inventa um dado que nao existe. */
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(4),
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(5),
      html[data-aparelho]:not([data-tv]) .precs-sup-linha > *:nth-child(6){ white-space:nowrap; }
      /* O filete colorido do estado ganha corpo: no cartao ele e a borda esquerda inteira, e e
         o que deixa varrer a lista pela cor sem ler nome nenhum. */
      html[data-aparelho]:not([data-tv]) .precs-sup-linha{ border-left-width:4px; }

    /* ======================================================================
       (1.1.49) EM PAISAGEM A TABELA VOLTA A SER TABELA
    ----------------------------------------------------------------------
       O cartão de três faixas acima é a resposta certa para RETRATO, onde há 360px de largura e
       800 de altura: empilhar é a única saída. Em paisagem o aparelho tem 844px de largura — mais
       do que muitos notebooks tinham quando esta tabela foi desenhada — e 390px de ALTURA. Ali o
       cartão custa o recurso errado: cada operador passa a ocupar três linhas de altura, e a
       altura é justamente o que não existe. Com o cartão, quatro operadores enchem a tela; como
       linha, entram nove.

       Girar o telefone é um gesto deliberado, e o que ele pede é "quero ver mais de uma vez".
       Esta é a orientação em que ver o quadro inteiro é o ganho, e a linha é o que entrega isso.

       A TRILHA DE AÇÕES CONTINUA EM PIXELS, e isso é de propósito: o plano desta leva dizia
       trocá-la por `auto`, e trocar seria reintroduzir um defeito que já foi pago. O comentário
       em 10-supervisao.css:288 registra o mecanismo — cada linha é um grid próprio, então uma
       trilha `auto` mede o conteúdo DAQUELA linha, e a linha do operador Offline (que não tem
       botão) media zero e saía de fila com as vizinhas. O que muda é o TAMANHO da trilha: os
       206px foram dimensionados para o botão em tamanho de estação, e aqui os botões já estão
       compactos pela regra de aparelho (.68rem, 32px de altura). 150px é a medida do par
       compacto mais largo ("Sair da linha" + "Deslogar"), e devolve 56px para as colunas de
       leitura. */
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-th{ display:grid; }
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha{
        grid-template-columns:2fr 1.2fr 1.1fr .45fr .45fr 150px;
        grid-template-areas:none;
        row-gap:0; align-items:center;
        padding:var(--space-2) var(--space-3);
      }
      /* AS ÁREAS NOMEADAS SAEM UMA POR UMA, E COM `nth-child`, e a primeira escrita desta regra
         errou aqui — vale registrar porque é a mesma armadilha de especificidade que já cobrou
         pedágio duas vezes nesta leva. `grid-template-areas:none` no pai não apaga o `grid-area`
         que cada célula recebeu, e uma célula com `grid-area:nome` num grid sem essa área
         nomeada cai numa trilha IMPLÍCITA. Medido: a linha ficou com 8 colunas (as 6 minhas mais
         duas implícitas de 0px e 207px) e 2 fileiras, em vez de 6 e 1.
         Um `> *{ grid-area:auto }` não resolve: as regras do cartão são
         `... .precs-sup-linha > *:nth-child(N)`, com especificidade (0,3,2), e `> *` é (0,3,1) —
         perde. Cada célula precisa ser desfeita pelo MESMO tipo de seletor que a fez. */
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(1),
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(2),
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(3),
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(4),
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(5),
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(6){
        grid-area:auto; justify-self:auto;
      }
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(7){
        grid-area:auto; justify-self:end;
      }
      /* Os rótulos `::before` ("LIG", "CONV", "TEMPO") saem: eles existem porque o cartão não tem
         cabeçalho, e a linha tem. Repetidos, seriam o rótulo duas vezes em cada célula. */
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(4)::before,
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(5)::before,
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-linha > *:nth-child(6)::before{
        content:none;
      }
      /* A coluna TEMPO fica fora, como já fica em qualquer tela abaixo de 1200px
         (10-supervisao.css:444): são seis trilhas para seis células. */
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-tempo,
      html[data-aparelho="paisagem"]:not([data-tv]) .precs-sup-th > *:nth-child(6){
        display:none;
      }

    /* ======================================================================
       (1.1.49) O ESPELHO NUM APARELHO: ALTURA DE CONTEÚDO E UMA ROLAGEM SÓ
    ----------------------------------------------------------------------
       O espelho é a superfície mais quebrada dos prints de 02/09/2026, e a causa não é o
       cabeçalho cortado que eu achei ter medido (aquilo era erro de unidade no meu teste; está
       registrado em 10-supervisao.css, na regra que NÃO deve ser escrita). A causa é a caixa, e
       a sonda deu o número em 390x844, com as três colunas empilhadas:

         card de 201px — 52 de cabeçalho e 148 de corpo — TRÊS deles, cada um com a própria
         barra de rolagem. Depois: card de 477px, uma rolagem só.

       O mecanismo: `.precs-espelho-corpo` tem ALTURA FIXA (`min(900px, 78vh)/escala`, em
       10-supervisao.css) e o container query de 771px empilha as três colunas. Sem
       `grid-template-rows`, cada fileira fica com um terço daquela altura fixa — 200px — e
       dentro de 200px o card recebe 148px de corpo. Ler o mailing de um credor em 148px, com o
       dedo rolando uma caixa de 148px que está dentro de uma tela que não rola, é o "quebrado"
       do print: três tocos, três barrinhas, e a informação toda fora de vista.

       A altura fixa foi uma decisão deliberada da 1.1.17, e o comentário de lá explica bem:
       "o modal expande depois diminui por causa das anotações, e isso é ruim" (21/08/2026), e a
       cura foi congelar a caixa e rolar dentro de cada coluna. Isso continua certo na ESTAÇÃO,
       onde há três colunas LADO A LADO e cada uma tem a altura inteira do quadro para rolar. Com
       as três empilhadas a mesma regra divide aquela altura por três, e três áreas de rolagem
       numa pilha não são três colunas: são uma coluna partida em três. A pilha quer uma rolagem.

       Então em aparelho o corpo troca altura fixa por `flex:1 1 auto` dentro do modal (que já é
       flex em coluna e já tem o freio de 88vh), e é ELE quem rola. As colunas e os cards passam
       a ter altura de conteúdo, e as três listas internas devolvem a rolagem própria: rolagem
       dentro de rolagem, no toque, é o gesto sendo capturado pela caixa errada.

       O `max-height` do modal também muda de régua: 88vh ignora a reserva que o overlay faz no
       pé para a barra da escuta, e as duas somadas passavam da tela. `100%` resolve contra a
       caixa de conteúdo do overlay, que já é a viewport MENOS a reserva. */
      html[data-aparelho]:not([data-tv]) .precs-espelho-modal{
        max-height:100%;
      }
      html[data-aparelho]:not([data-tv]) .precs-espelho-corpo{
        height:auto; flex:1 1 auto; min-height:0;
        overflow-y:auto; overscroll-behavior:contain;
        padding:var(--space-3);
      }
      /* Coluna e card com altura de CONTEÚDO: sem isto o `flex:1 1 auto` do card (a regra em
         10-supervisao.css:904) continuaria disputando a altura de um pai que agora não tem
         altura definida, e o resultado é o card colapsando para o mínimo. */
      html[data-aparelho]:not([data-tv]) .precs-esp-col{
        overflow:visible; min-height:0;
      }
      html[data-aparelho]:not([data-tv]) .precs-esp-col > .precs-esp-cresce{
        flex:0 0 auto;
      }
      html[data-aparelho]:not([data-tv]) .precs-esp-col > .precs-esp-cresce > .panel-body{
        overflow:visible;
      }
      /* As três listas devolvem a rolagem e o teto de altura: quem rola é o corpo. */
      html[data-aparelho]:not([data-tv]) .precs-esp-mailing,
      html[data-aparelho]:not([data-tv]) .precs-esp-quals,
      html[data-aparelho]:not([data-tv]) .precs-esp-notas{
        overflow-y:visible; max-height:none;
      }
      /* O OVERLAY PARA DE CENTRALIZAR. `align-items:center` com conteúdo mais alto que a caixa
         corta em CIMA e embaixo ao mesmo tempo — e o topo é onde está o nome de quem está sendo
         espelhado. Ancorado no topo, o que sobra vai para a rolagem, e não para fora da tela. */
      html[data-aparelho]:not([data-tv]) .precs-espelho-overlay{
        align-items:flex-start; padding:var(--space-2) var(--space-2) var(--precs-spy-reserva, 7rem);
      }


    /* ======================================================================
       (1.1.47) A PAREDE NUM APARELHO NA MAO, e o toast que a cobria
    ----------------------------------------------------------------------
       O teste no celular de 02/09/2026 rendeu dois achados na parede, e os dois estao aqui.
       A conta de COLUNAS foi para o app.js, onde ela e tomada; o que sobra e CSS.

       1) O LEMBRETE DE F11 SAI, porque a tecla nao existe. Ele e honesto na TV: quem entra pela
          conta com `perfilSup: "tv"` nao passa por gesto nenhum, o navegador recusa tela cheia
          por politica, e dizer qual e a tecla e a unica saida. Num celular ele e uma instrucao
          impossivel — e instrucao impossivel gasta a confianca do resto da tela.

       2) O TOAST DEIXA DE COBRIR O CONTEUDO. No print do teste, "Erro ao conectar: websocket
          error" apareceu como uma faixa vermelha atravessando o TITULO da parede. Numa tela
          feita para ser lida de longe isso e o pior lugar possivel, e por um motivo que nao e
          estetico: ninguem vai ate a TV fechar um toast. Ele fica ali, sobre o dado, ate o
          timer correr.
          A correcao NAO esconde o erro — esconder erro e o que a 1.1.40 proibiu no WhatsApp,
          com o motivo escrito. Ele vai para o rodape, estreito e junto do relogio e do
          carimbo, que e onde a parede ja poe o que e meta-informacao. Continua legivel de
          perto, para quem for conferir, e para de tapar a resposta. */
    /* (1.1.49) ESTE BLOCO ERA UM @media (max-width: 620px). Ver o comentario de
       precsAplicarAparelho no index.html: o criterio virou o atributo html[data-aparelho],
       que inclui a ALTURA (paisagem de celular tem 844px de largura e passava por estacao) e
       que um teste consegue LER — media query nao e observavel de fora. O limiar vive num
       lugar so, em PRECS_APARELHO_LARG/ALT. */

      html[data-tv][data-aparelho] .precs-tv-f11{ display:none; }
    
    html[data-tv] .toast-container{
      top:auto; bottom:var(--space-2); right:var(--space-2); left:var(--space-2);
      max-width:none; align-items:flex-end;
    }
    html[data-tv] .toast{
      padding:var(--space-1) var(--space-3); font-size:.72rem; max-width:min(560px, 92vw);
      box-shadow:none;
    }

    /* ======================================================================
       (1.1.47) OS DOIS DEGRAUS QUE FALTAVAM NO CARD BAIXO DA PAREDE
    ----------------------------------------------------------------------
       A parede ja tinha degraus por altura de card: as listas somem em `max-height: 640px`,
       o bloco de listas e os estados em `330px`, o nome do operador em `300px`. Eles foram
       calibrados para a TV, onde o card mais apertado tem ~292px.

       Num aparelho o card desce muito mais. Medido em 02/09/2026, depois de o teto por altura
       entrar: 278px no retrato e 141px na paisagem. E a ROSCA, nessa faixa, faz o oposto do que
       se espera — ela CRESCE: o `clamp(76px, min(19cqh, 36cqi), 250px)` passa a ser governado
       pelo `36cqi` (a largura do card), e num card largo e baixo isso deu 102px, que era mais
       do que ela tinha na TV. Um card de 278px com 102px de rosca nao fecha.

       Os dois degraus abaixo resolvem por SUBTRACAO, que e a gramatica que a parede ja usa:

        • abaixo de 320px a ROSCA SAI, e com ela os ROSTOS dos operadores. A rosca e leitura de
          relance, e leitura de relance precisa de area; sem area ela deixa de informar e passa
          a ocupar. Os rostos custam 28px numa faixa que tem 141.
        • abaixo de 210px sai o bloco `.precs-tv-extra` inteiro (as ligacoes do dia e os
          rostos) e a projecao. Sobram nome e numero, que e o minimo com que o card ainda diz
          alguma coisa. Abaixo disso a parede nao tem resposta, e a saida honesta ja existe: o
          teto por altura manda a campanha para a celula de resumo em vez de desenhar um card
          ilegivel.

       E UMA NOTA SOBRE COMO ISTO FOI ESCRITO ERRADO NA PRIMEIRA VEZ: o degrau de 210px
       escondia `.precs-campsaldo-rodape`, uma classe que NAO EXISTE no painel — eu a tinha
       inventado ao montar um card de mentira para medir, e depois escrevi o CSS contra o meu
       proprio card em vez de contra o do produto. O bloco real chama-se `.precs-tv-extra`, e
       quem mostrou isso foi a medicao dos filhos do card: `precs-tv-extra h=63` num card de
       141px. Seletor que nao casa com nada nao da erro nenhum — so nao funciona.

       E O `@media` EM VOLTA E OBRIGATORIO, nao enfeite. Sem ele estes degraus valiam para
       QUALQUER tela em que o card ficasse baixo — inclusive 1366x768, que e a estacao mais
       comum da operacao e tambem resolucao de mini-PC de parede. Lá a parede mostra ate sete
       cards de proposito, e ela sabe caber: tem a escada `--tv-esc` ate 0,72 exatamente para
       isso. Com os degraus soltos, a rosca sumia em 720p e o teste-supervisao-tv derrubou com
       `getTotalLength` num elemento nao renderizado. A pergunta certa nao e "o card esta
       baixo?", e "isto e um aparelho?" — e essa so a viewport responde. */
    /* (1.1.49) ESTE BLOCO ERA UM @media (max-width: 620px). Ver o comentario de
       precsAplicarAparelho no index.html: o criterio virou o atributo html[data-aparelho],
       que inclui a ALTURA (paisagem de celular tem 844px de largura e passava por estacao) e
       que um teste consegue LER — media query nao e observavel de fora. O limiar vive num
       lugar so, em PRECS_APARELHO_LARG/ALT. */

    @container card (max-height: 320px){
      html[data-tv][data-aparelho] .precs-campsaldo-rosca{ display:none; }
      html[data-tv][data-aparelho] .precs-campsaldo-corpo{ gap:var(--space-2); }
      html[data-tv][data-aparelho] .precs-tv-quem{ display:none; }
    }
    @container card (max-height: 210px){
      html[data-tv][data-aparelho] .precs-campsaldo-proj,
      html[data-tv][data-aparelho] .precs-tv-extra{ display:none; }
      /* E o nome desce para UMA linha. Na parede ele tem duas (clamp de 2, para nome de
         campanha longo caber inteiro), e duas linhas de `--tv-nome` custam ~50px de 141 — mais
         de um terco do card. Com uma linha o nome trunca, e truncar o nome de uma campanha num
         card que ja e o resumo de uma tela sem espaco e menos ruim que cortar o NUMERO, que e
         a resposta. */
      html[data-tv][data-aparelho] .precs-campsaldo-nome{ -webkit-line-clamp:1; }
      /* Os ultimos 4px. Depois de tirar `.precs-tv-extra` o card passou de 72px de corte para
         4 — o arredondamento de um gap. Nesta faixa o padding de 12px em cima e embaixo e o
         unico lugar de onde tirar sem mexer em conteudo. */
      html[data-tv][data-aparelho] .precs-campsaldo{ padding-top:var(--space-2); padding-bottom:var(--space-2); }
    }
    

    /* (1.1.154) Os chips da campanha ampliada: operadores livres, discador parado, ligações por operador. */
    .precs-tvamp-chips{ display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
    .precs-tvamp-chip{ font-family: var(--font-rotulo); font-size: var(--fs-4); font-weight: 600; padding: var(--space-1) var(--space-3);
      border-radius: var(--radius-pill); background: var(--bg-tertiary); color: var(--text-secondary); white-space: nowrap; }
    .precs-tvamp-chip.livre{ color: var(--accent-green); }
    .precs-tvamp-chip.parado{ color: var(--accent-orange); }
    .precs-tvamp-chip.alerta{ color: var(--accent-orange); }   /* (1.1.157) o sinal de telefonia, o mesmo do card da parede */
    .precs-tvamp-chip.discando{ color: var(--accent-green); }
    html[data-tv] .precs-tvamp-chip{ font-size: var(--fs-6); }

    /* (1.1.155) A linha da agressividade, da telefonia e do discador parado no card de campanha. Na parede ela
       so aparece quando o card tem altura (container query), como a curva de ritmo; na aba, sempre. */
    .precs-campsaldo-agr{ display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
    .precs-campsaldo-agr-chip{ font-family: var(--font-rotulo); font-size: var(--fs-4); font-weight: 600; padding: var(--space-1) var(--space-3);
      border-radius: var(--radius-pill); background: var(--bg-tertiary); color: var(--text-secondary); white-space: nowrap; }
    .precs-campsaldo-agr-chip b{ color: var(--text-primary); font-family: var(--font-num); }
    /* (1.1.166) O ALERTA CABE NO CARD. "O texto está para fora: discando, mas as ligações falham · 55 falhas em 2 min ·
       ver telefonia" (Pedro, 29/09/2026): o chip é `nowrap` e esse é o único texto longo da linha, então ele
       atravessava a borda do card. Chip nunca passa da largura da linha, e o alerta quebra em duas linhas quando
       precisa, com o raio do cartão em vez da pílula (pílula de duas linhas vira elipse). */
    .precs-campsaldo-agr-chip, .precs-tvamp-chip{ max-width: 100%; }
    .precs-campsaldo-agr-chip.alerta, .precs-tvamp-chip.alerta{ white-space: normal; border-radius: var(--radius-inner); }
    .precs-campsaldo-agr-chip.espera{ color: var(--text-muted); }
    .precs-campsaldo-agr-chip.discando{ color: var(--accent-green); }   /* (1.1.157) o socket viu ligação no último minuto: o saldo parado é rediscagem, e não discador morto */
    .precs-campsaldo-agr-chip.alerta{ color: var(--accent-orange); background: color-mix(in srgb, var(--accent-orange) 14%, transparent); animation: precsAgrPulsa 2.4s ease-in-out infinite; }
    @keyframes precsAgrPulsa{ 0%, 100%{ opacity: 1; } 50%{ opacity: .6; } }
    /* (1.1.164) A AGRESSIVIDADE E A TELEFONIA APARECEM SEMPRE. Elas nasceram atrás de um container query
       ("card alto mostra"), e com quatro células o card encolhe e a linha sumia — "quando vamos para 4 cards
       não aparece mais" (Pedro, 29/09/2026). A pergunta que essa linha responde (o discador está aberto em
       quantas por operador, e por qual rota) é a mesma em qualquer densidade, e é ela que diz se o operador
       ocioso é telefonia. Quem cede altura quando aperta continua sendo a LISTA, pelo laço de encaixe. */
    html[data-tv]:not([data-aparelho]) .precs-campsaldo-agr{ display: flex; }
    html[data-tv][data-aparelho] .precs-campsaldo-agr{ display: none; }
    /* E ela é a ÚLTIMA a ceder, não a primeira. Abaixo de 290px de conteúdo a lista já sumiu (o degrau dela é
       640px) e o laço de encaixe fica sem alavanca barata: medido em 1366x768 com quatro células, o card
       transbordava. O corte fica nesse fundo de poço, e não em "card não é alto o bastante" — que era o degrau
       de 420px de antes e escondia a linha na parede de 1080p com quatro células, que é o caso do Pedro. */
    @container card (max-height: 290px){
      /* A especificidade acompanha a regra que ela vence (`:not([data-aparelho])` conta), senão a linha de cima
         ganha por peso e o degrau não faz nada — foi o que a medição mostrou na primeira escrita. */
      html[data-tv]:not([data-aparelho]) .precs-campsaldo-agr{ display: none; }
    }
    html[data-tv] .precs-campsaldo-agr-chip{ font-size: var(--fs-6); }
    /* (1.1.165) "Discando, mas as ligações falham" não vai à parede (pedido do Pedro, 29/09/2026); na aba de campanhas fica. */
    html[data-tv] .precs-campsaldo-agr-chip.falham, html[data-tv] .precs-tvamp-chip.falham{ display: none; }
    /* (1.1.155) As marcas da curva do mergulho: verde quando um operador entrou em ligacao, cinza quando ficou livre. */
    .precs-tv-ritmo-marca{ stroke-width: 1; stroke-dasharray: 2 3; opacity: .85; vector-effect: non-scaling-stroke; }
    .precs-tv-ritmo-marca.m-falando{ stroke: var(--st-call); }
    .precs-tv-ritmo-marca.m-ocioso{ stroke: var(--st-idle); }
    @media (prefers-reduced-motion: reduce){ .precs-campsaldo-agr-chip.alerta{ animation: none; } }

/* ===================================================================== */
/* (05/10/2026) A MODAL DA CAMPANHA AO VIVO REPAGINADA, e o peso da lista   */
/* editável (piloto na campanha `teste`). Tudo ancorado em                 */
/* `.precs-campvivo-modal`: a ampliada da parede não muda um pixel.        */
/* ===================================================================== */
    /* O casco: borda que puxa para o ouro, cabeçalho com o degradê da casa e o título na fonte de título */
    .precs-campvivo-modal{ border-color:color-mix(in srgb, var(--precs-gold) 32%, var(--border-color)); overflow:hidden; }
    .precs-campvivo-modal .precs-listas-head{
      background:linear-gradient(180deg, color-mix(in srgb, var(--precs-gold) 9%, var(--bg-secondary)), var(--bg-secondary));
      font-family:var(--font-titulo); font-size:var(--fs-7); }
    .precs-campvivo-modal .precs-listas-titulo > svg{ color:var(--precs-gold-title); }
    /* O selo "ao vivo" com o ponto que pulsa: é a única coisa da janela que diz "isto está andando" */
    .precs-campvivo-vivo-selo{ display:inline-flex; align-items:center; gap:var(--space-1); }
    .precs-campvivo-vivo-selo::before{ content:''; width:6px; height:6px; border-radius:50%; background:var(--accent-green); animation:precsCampVivoPulso 1.8s var(--ease-1) infinite; }
    @keyframes precsCampVivoPulso{ 0%, 100%{ opacity:1; outline:0 solid color-mix(in srgb, var(--accent-green) 50%, transparent); } 50%{ opacity:.6; outline:4px solid color-mix(in srgb, var(--accent-green) 0%, transparent); } }
    /* A faixa do topo e os blocos: fundo um degrau abaixo do casco, e o bloco acende a borda sob o mouse */
    .precs-campvivo-modal .precs-tvamp-topo{ background:linear-gradient(100deg, color-mix(in srgb, var(--precs-gold) 7%, var(--bg-primary)), var(--bg-primary) 60%); }
    .precs-campvivo-modal .precs-tvamp-bloco{ background:var(--bg-primary); transition:border-color var(--dur-2) var(--ease-1); }
    .precs-campvivo-modal .precs-tvamp-bloco:hover{ border-color:color-mix(in srgb, var(--precs-gold) 40%, var(--border-color)); }
    .precs-campvivo-modal .precs-tvamp-bloco-tit{ background:color-mix(in srgb, var(--precs-gold) 5%, transparent); }
    .precs-campvivo-modal .precs-tvamp-kpi{ transition:border-color var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-2); }
    .precs-campvivo-modal .precs-tvamp-kpi:hover{ border-color:color-mix(in srgb, var(--precs-gold) 45%, var(--border-color)); transform:translateY(-1px); }
    .precs-campvivo-graf-item{ transition:border-color var(--dur-2) var(--ease-1); }
    .precs-campvivo-graf-item:hover{ border-color:color-mix(in srgb, var(--precs-gold) 40%, var(--border-color)); }
    /* A linha da lista responde ao mouse, com o fundo saindo até a borda do bloco */
    .precs-campvivo-modal .precs-tvamp-lista{ margin:0 calc(-1 * var(--space-2)); padding-left:var(--space-2); padding-right:var(--space-2); border-radius:var(--radius-inner); transition:background-color var(--dur-1) var(--ease-1); }
    .precs-campvivo-modal .precs-tvamp-lista:hover{ background:color-mix(in srgb, var(--precs-gold) 6%, transparent); }
    /* A entrada em cascata: o topo, as taxas, as curvas e as duas colunas chegam um atrás do outro */
    .precs-campvivo-body > *{ animation:precsCampVivoEntra var(--dur-3) var(--ease-2) both; }
    .precs-campvivo-body > :nth-child(2){ animation-delay:40ms; }
    .precs-campvivo-body > :nth-child(3){ animation-delay:80ms; }
    .precs-campvivo-body > :nth-child(n+4){ animation-delay:120ms; }
    @keyframes precsCampVivoEntra{ from{ opacity:0; translate:0 8px; } to{ opacity:1; translate:0 0; } }
    /* O PESO EDITÁVEL: menos, o número com o percentual, mais; a lista parada (peso 0) apagada no fim */
    .precs-peso{ display:inline-flex; align-items:center; gap:var(--space-1); color:var(--text-primary); }
    .precs-peso b{ font-family:var(--font-num); font-size:var(--fs-5); font-weight:700; min-width:1.4em; text-align:center; }
    .precs-peso small{ font-size:var(--fs-2); color:var(--text-muted); font-family:var(--font-num); }
    .precs-peso-btn{ display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; border-radius:50%; cursor:pointer;
      border:1px solid var(--border-color); background:var(--bg-tertiary); color:var(--text-secondary);
      transition:border-color var(--dur-1) var(--ease-1), color var(--dur-1) var(--ease-1), background-color var(--dur-1) var(--ease-1), transform var(--dur-1) var(--ease-2); }
    .precs-peso-btn:hover:not(:disabled){ border-color:var(--precs-gold); color:var(--precs-gold-title); background:color-mix(in srgb, var(--precs-gold) 12%, transparent); transform:scale(1.08); }
    .precs-peso-btn:active:not(:disabled){ transform:scale(.94); }
    .precs-peso-btn:disabled{ opacity:.35; cursor:not-allowed; }
    .precs-peso-btn:focus-visible{ outline:2px solid var(--precs-gold); outline-offset:2px; }
    .precs-peso.mexendo b{ color:var(--precs-gold-title); }
    .precs-peso-giro{ width:12px; height:12px; border-radius:50%; border:2px solid color-mix(in srgb, var(--precs-gold) 30%, transparent); border-top-color:var(--precs-gold); animation:precsSpin .8s linear infinite; }
    .precs-peso-piloto{ color:var(--precs-gold-title); }
    .precs-campvivo-modal .precs-tvamp-lista.parada{ opacity:.55; }
    .precs-campvivo-modal .precs-tvamp-lista.parada:hover{ opacity:.85; }
    .precs-campvivo-modal .precs-tvamp-lista.parada .precs-tvamp-lista-num{ color:var(--text-muted); }
    @media (prefers-reduced-motion: reduce){
      .precs-campvivo-vivo-selo::before, .precs-campvivo-body > *, .precs-peso-giro{ animation:none; }
      .precs-campvivo-modal .precs-tvamp-bloco, .precs-campvivo-modal .precs-tvamp-kpi, .precs-campvivo-graf-item, .precs-campvivo-modal .precs-tvamp-lista, .precs-peso-btn{ transition:none; }
      .precs-campvivo-modal .precs-tvamp-kpi:hover, .precs-peso-btn:hover:not(:disabled){ transform:none; }
    }

/* ===================================================================== */
/* (05/10/2026) A FICHA DA CAMPANHA REPAGINADA. O casco ganha ALTURA     */
/* própria (cada aba ditava o tamanho e a janela pulava a cada troca);     */
/* abas com ícone e pílula deslizante; troca com direção; faixa ao vivo   */
/* em chips; seções com fio; chaves e pessoas que respondem ao mouse.     */
/* Só a ficha: a modal ao vivo tem o próprio bloco acima.                 */
/* ===================================================================== */
    .precs-campcfg-modal:not(.precs-campvivo-modal){ height:min(92vh, 760px); overflow:hidden;
      border-color:color-mix(in srgb, var(--precs-gold) 32%, var(--border-color)); }
    .precs-campcfg-modal:not(.precs-campvivo-modal) .precs-listas-head{
      background:linear-gradient(180deg, color-mix(in srgb, var(--precs-gold) 9%, var(--bg-secondary)), var(--bg-secondary));
      font-family:var(--font-titulo); font-size:var(--fs-7); }
    .precs-campcfg-modal:not(.precs-campvivo-modal) .precs-listas-titulo > svg{ color:var(--precs-gold-title); }
    /* O corpo é quem rola; o casco fica parado */
    .precs-campcfg-body{ flex:1 1 auto; min-height:0; overflow-y:auto; }
    /* As abas: ícone, e a pílula que desliza (a mesma das Estratégias, `.precs-seg-pilula`) */
    .precs-campcfg-abas button{ display:inline-flex; align-items:center; gap:var(--space-1); }
    .precs-campcfg-abas button svg{ flex:0 0 auto; opacity:.7; transition:opacity var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-2); }
    .precs-campcfg-abas button.active svg{ opacity:1; transform:scale(1.08); }
    /* A troca entra do lado da aba nova (as curvas moram no 70-supwhatsapp.css, com as sub-abas das Estratégias) */
    .precs-campcfg-body.precs-campcfg-troca.entra-dir{ animation-name:precsEstEntraDir; }
    .precs-campcfg-body.precs-campcfg-troca.entra-esq{ animation-name:precsEstEntraEsq; }
    /* A faixa ao vivo vira um painel de chips */
    .precs-campcfg-vivo{ padding:var(--space-2) var(--space-3); border:1px solid var(--border-color); border-radius:var(--radius-inner);
      background:color-mix(in srgb, var(--precs-gold) 5%, var(--bg-primary)); }
    .precs-campcfg-vivo-item{ padding:var(--space-0) var(--space-2); border-radius:var(--radius-pill); background:var(--bg-tertiary); }
    .precs-campcfg-vivo-est{ padding:var(--space-0) var(--space-2); border-radius:var(--radius-pill); border:1px solid color-mix(in srgb, var(--est-cor, var(--text-muted)) 40%, transparent); }
    /* O título de seção ganha o fio até a borda; o resumo fica depois do fio */
    .precs-campcfg-secao-tit::after{ content:''; order:1; flex:1 1 auto; height:1px; background:color-mix(in srgb, var(--precs-gold) 22%, var(--border-color)); }
    .precs-campcfg-resumo{ order:2; margin-left:0; }
    .precs-campcfg-secao-tit .precs-sup-selo{ order:0; }
    /* O campo acende o fio sob o mouse */
    .precs-campcfg-campo{ transition:border-color var(--dur-2) var(--ease-1); }
    .precs-campcfg-campo:hover{ border-bottom-color:color-mix(in srgb, var(--precs-gold) 50%, var(--border-color)); }
    /* A barra de 24 h: a faixa em degradê de ouro */
    .precs-campcfg-horas-faixa{ background:linear-gradient(90deg, color-mix(in srgb, var(--precs-gold-forma) 35%, transparent), color-mix(in srgb, var(--precs-gold-forma) 60%, transparent)); }
    /* As chaves: a ligada tem fundo tingido; todas respondem ao mouse */
    .precs-campcfg-chave{ transition:border-color var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-2), background-color var(--dur-2) var(--ease-1); }
    .precs-campcfg-chave.liga{ background:color-mix(in srgb, var(--accent-green) 6%, var(--bg-secondary)); border-color:color-mix(in srgb, var(--accent-green) 30%, var(--border-color)); }
    .precs-campcfg-chave:hover{ border-color:color-mix(in srgb, var(--precs-gold) 45%, var(--border-color)); transform:translateY(-1px); }
    .precs-campcfg-chave.liga .precs-campcfg-chave-pt{ outline:3px solid color-mix(in srgb, var(--accent-green) 25%, transparent); }
    /* As pessoas: quem está na campanha agora tem a borda na cor do estado; todas acendem sob o mouse */
    .precs-campcfg-pessoa{ transition:border-color var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-2); }
    .precs-campcfg-pessoa.live{ border-color:color-mix(in srgb, var(--vop-cor) 45%, var(--border-color)); }
    .precs-campcfg-pessoa:hover{ border-color:color-mix(in srgb, var(--precs-gold) 45%, var(--border-color)); transform:translateY(-1px); }
    /* O rodapé: um filete de ouro em vez do fio de cima a cima */
    .precs-campcfg-rodape{ border-top:0; padding:var(--space-2) var(--space-3); border-left:3px solid color-mix(in srgb, var(--precs-gold) 45%, var(--border-color));
      background:color-mix(in srgb, var(--precs-gold) 4%, transparent); border-radius:var(--radius-sm); }
    /* As seções entram em cascata na troca de aba */
    .precs-campcfg-body.precs-campcfg-troca > *{ animation:precsCampVivoEntra var(--dur-3) var(--ease-2) both; }
    .precs-campcfg-body.precs-campcfg-troca > :nth-child(2){ animation-delay:30ms; }
    .precs-campcfg-body.precs-campcfg-troca > :nth-child(3){ animation-delay:60ms; }
    .precs-campcfg-body.precs-campcfg-troca > :nth-child(n+4){ animation-delay:90ms; }
    @media (prefers-reduced-motion: reduce){
      .precs-campcfg-abas button svg, .precs-campcfg-campo, .precs-campcfg-chave, .precs-campcfg-pessoa{ transition:none; }
      .precs-campcfg-chave:hover, .precs-campcfg-pessoa:hover, .precs-campcfg-abas button.active svg{ transform:none; }
      .precs-campcfg-body.precs-campcfg-troca > *{ animation:none; }
    }

/* (05/10/2026, tarde) A LISTA CONCLUÍDA e o peso travado: a ordem da modal passou a ser por peso, as concluídas
   descem apagadas (a 3C também não deixa mexer no peso delas) e o controle travado diz por quê no balão. */
    .precs-campvivo-modal .precs-tvamp-lista.concluida{ opacity:.6; }
    .precs-campvivo-modal .precs-tvamp-lista.concluida:hover{ opacity:.85; }
    .precs-campvivo-modal .precs-tvamp-lista.concluida .precs-tvamp-lista-num{ color:var(--text-muted); }
    .precs-campvivo-modal .precs-tvamp-lista-de b{ color:var(--text-secondary); font-weight:600; }
    .precs-peso.travado b{ color:var(--text-muted); }
    .precs-listas-tab tbody tr.concluida:not(.parada) td{ color:var(--text-muted); }
    .precs-listas-tab tbody tr.concluida:not(.parada) td.nome span{ color:var(--text-secondary); }
    .precs-est-sonda-corpo{ max-height:40vh; overflow:auto; margin:0; padding:var(--space-3); border-radius:var(--radius-inner); background:var(--bg-primary);
      border:1px solid var(--border-color); font-family:var(--font-num); font-size:var(--fs-3); color:var(--text-secondary); white-space:pre-wrap; }

/* ===================================================================== */
/* (05/10/2026) O ARRASTO COM CARA, OS SELETORES DA CASA E A IDENTIDADE   */
/* DAS CAMPANHAS na aba Campanhas e nos painéis de Operadores.            */
/* ===================================================================== */
    /* O fantasma que segue o cursor (SortableJS com `forceFallback`): levanta, inclina e ganha a sombra da casa,
       como o das Estratégias. O lugar vago fica tracejado e apagado; o escolhido acende a borda. */
    .precs-arrasto-voando{ opacity:.96; transform:rotate(1.2deg) scale(1.02); box-shadow:var(--shadow-lg); border-color:var(--precs-gold); pointer-events:none;
      transition:none; cursor:grabbing; }
    .precs-sup-painel-fantasma, .precs-campsaldo-fantasma{ opacity:.35; outline:2px dashed color-mix(in srgb, var(--precs-gold) 65%, transparent); outline-offset:-2px;
      background:color-mix(in srgb, var(--precs-gold) 6%, var(--bg-secondary)); }
    .precs-sup-painel-fantasma > *, .precs-campsaldo-fantasma > *{ visibility:hidden; }
    .precs-sup-painel-escolhido, .precs-campsaldo-pego{ border-color:var(--precs-gold); }
    .precs-sup-painel-alca, .precs-campsaldo-alca{ transition:color var(--dur-1) var(--ease-1), background-color var(--dur-1) var(--ease-1), transform var(--dur-2) var(--ease-2); }
    .precs-sup-painel-alca:hover, .precs-campsaldo-alca:hover{ transform:scale(1.08); }
    /* Os seletores da casa nas barras de filtro: o mesmo botão do seletor de operador, com a identidade da campanha */
    .precs-sup-pick-host{ display:inline-flex; min-width:0; }
    .precs-sup-pick{ min-width:230px; max-width:340px; }
    .precs-sup-pick .precs-est-ident{ min-width:34px; height:30px; font-size:var(--fs-3); }
    .precs-sup-pick-sit{ position:relative; background:var(--bg-tertiary); }
    .precs-sup-pick-sit::after{ content:''; width:10px; height:10px; border-radius:50%; background:var(--sit, var(--text-muted)); outline:3px solid color-mix(in srgb, var(--sit, var(--text-muted)) 25%, transparent); }
    .precs-sup-pick-sit[data-sit="operador"], .precs-sup-pick-sit[data-sit="rodando"]{ --sit:var(--accent-green); }
    .precs-sup-pick-sit[data-sit="sem"]{ --sit:var(--st-offline); }
    .precs-sup-pick-sit[data-sit="pausada"]{ --sit:var(--accent-orange); }
    .precs-sup-pick-sit[data-sit="fora-horario"]{ --sit:var(--precs-paleta-9); }
    .precs-sup-pick-sit[data-sit="fixadas"]{ --sit:var(--precs-gold-hi); }
    /* A IDENTIDADE NO CARD: o filete no topo na cor da família, o selo antes do nome e, na Follow, a foto do dono em
       marca-d'água à direita, por baixo de tudo (`::before` pinta antes do conteúdo). Só na aba (a parede não recebe
       a classe). O filete esquerdo continua sendo o nível de urgência. */
    .precs-campsaldo-fam{ position:relative; }
    /* O filete da família é um pseudo no topo, e não o `border-top`: a borda continua sendo o sinal de hover (três lados) */
    /* (2ª volta) O filete vai de borda a borda, colado ao casco e com o raio do card: antes era uma barra curta que crescia
       no hover e "parecia que não pertencia ao card" (Pedro). O hover agora tinge o fundo de leve na cor da família. */
    /* (3ª volta) O filete É a borda de cima do card, com o raio do card: o pseudo passava das quinas arredondadas. */
    .precs-campsaldo-fam{ border-top:3px solid var(--id); }
    .precs-campsaldo-fam[tabindex]:hover{ border-top-color:var(--id); background:linear-gradient(180deg, color-mix(in srgb, var(--id) 9%, var(--bg-tertiary)), var(--bg-tertiary) 55%); }
    /* O selo vem DEPOIS do nome no DOM (o texto do card continua começando pelo nome) e ANTES na tela */
    .precs-campsaldo-ident{ flex:0 0 auto; order:-1; margin-right:var(--space-1); }
    .precs-campsaldo-fam.com-foto{ overflow:hidden; }
    .precs-campsaldo-fam.com-foto::before{ content:''; position:absolute; inset:0; pointer-events:none; opacity:.14;
      background:var(--foto) right 40% / auto 115% no-repeat;
      -webkit-mask:linear-gradient(90deg, transparent 30%, black 75%); mask:linear-gradient(90deg, transparent 30%, black 75%);
      transition:opacity var(--dur-2) var(--ease-1); }
    .precs-campsaldo-fam.com-foto:hover::before{ opacity:.24; }
    .precs-campsaldo-fam.com-foto > *{ position:relative; }
    /* As bolhas das seções na cor da família, com o ponto; "Listas no fim" fica vermelha como já era */
    .precs-campt-chip[data-secao="follow"]{ --id:var(--precs-paleta-7); }
    .precs-campt-chip[data-secao="estagnadas"]{ --id:var(--precs-paleta-2); }
    .precs-campt-chip[data-secao="prospeccao"]{ --id:var(--precs-paleta-8); }
    .precs-campt-chip[data-secao="outras"]{ --id:var(--precs-paleta-9); }
    .precs-campt-chip[data-secao="fixadas"]{ --id:var(--precs-gold-hi); }
    .precs-campt-chip[data-secao="aovivo"]{ --id:var(--accent-green); }
    .precs-campt-chip[data-secao]:not(.alerta){ border-color:color-mix(in srgb, var(--id, var(--precs-gold-forma)) 45%, var(--border-color)); }
    .precs-campt-chip[data-secao]:not(.alerta)::before{ content:''; width:8px; height:8px; border-radius:50%; flex:0 0 auto; background:var(--id, var(--precs-gold-forma)); }
    .precs-campt-chip[data-secao]:not(.alerta) b{ background:color-mix(in srgb, var(--id, var(--precs-gold-forma)) 18%, transparent); color:var(--text-primary); }
    .precs-campt-chip[data-secao]:not(.alerta):hover{ border-color:var(--id, var(--precs-gold)); }
    @media (prefers-reduced-motion: reduce){
      .precs-arrasto-voando{ transform:none; }
      .precs-sup-painel-alca, .precs-campsaldo-alca, .precs-campsaldo-fam.com-foto::before{ transition:none; }
      .precs-sup-painel-alca:hover, .precs-campsaldo-alca:hover{ transform:none; }
    }

/* (05/10/2026, 2ª volta) A FOLLOW É A PESSOA: o rosto do dono no lugar do selo, com o anel do estado; o card puxa para a
   cor da família e a foto fica em marca-d'água mais leve, no canto. E a modal sem leitura. */
    .precs-campsaldo-fam-fu{ background:linear-gradient(165deg, color-mix(in srgb, var(--id) 12%, var(--bg-tertiary)), var(--bg-tertiary) 60%); }
    .precs-campsaldo-fam-fu[tabindex]:hover{ background:linear-gradient(165deg, color-mix(in srgb, var(--id) 18%, var(--bg-tertiary)), var(--bg-tertiary) 60%); }
    .precs-campsaldo-dono{ flex:0 0 auto; order:-1; display:inline-flex; margin-right:var(--space-1); }
    .precs-campsaldo-dono .precs-sup-av{ width:40px; height:40px; outline:2px solid color-mix(in srgb, var(--id) 60%, transparent); outline-offset:2px; opacity:.75;
      transition:outline-color var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-2); }
    .precs-campsaldo-dono.live .precs-sup-av{ outline-color:var(--vop-cor); opacity:1; }
    .precs-campsaldo[tabindex]:hover .precs-campsaldo-dono .precs-sup-av{ transform:scale(1.06); }
    .precs-campsaldo-fam-fu.com-foto::before{ opacity:.10; background-position:right -10% bottom -20%; background-size:auto 95%; }
    .precs-campsaldo-fam-fu.com-foto:hover::before{ opacity:.16; }
    .precs-campsaldo-fam-fu .precs-campsaldo-topo{ align-items:center; }
    /* O nome da Follow em até duas linhas: com o rosto grande e os três controles, "Follow-Up Ana" virava "Follow-Up…" */
    .precs-campsaldo-fam-fu .precs-campsaldo-nome{ white-space:normal; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; font-size:var(--fs-7); }
    .precs-campvivo-semleitura{ display:flex; flex-direction:column; align-items:center; gap:var(--space-3); }
    .precs-campvivo-semleitura .precs-est-cal-link{ margin:0; }
    @media (prefers-reduced-motion: reduce){ .precs-campsaldo-dono .precs-sup-av{ transition:none; } .precs-campsaldo[tabindex]:hover .precs-campsaldo-dono .precs-sup-av{ transform:none; } }

/* (05/10/2026, 3ª volta) A FICHA QUE EDITA: a chave vira interruptor, o limite vira caixa de número, a rota vira o
   seletor da casa; enquanto a 3C responde, a ficha fica esmaecida e sem clique. */
    .precs-campcfg-chave.editavel{ cursor:pointer; }
    .precs-campcfg-chave.editavel .precs-est-switch{ margin-left:auto; flex:0 0 auto; }
    .precs-campcfg-chave.editavel:hover{ border-color:var(--precs-gold); }
    .precs-campcfg-val.editavel{ display:flex; align-items:center; gap:var(--space-2); white-space:normal; overflow:visible; }
    .precs-campcfg-val.editavel small{ font-size:var(--fs-3); color:var(--text-muted); }
    .precs-campcfg-val.editavel .precs-sup-pick{ min-width:0; max-width:100%; }
    .precs-campcfg-num{ width:6em; font:inherit; font-family:var(--font-num); font-size:var(--fs-5); font-weight:600; color:var(--text-primary);
      background:var(--bg-primary); border:1px solid var(--border-color); border-radius:var(--radius-sm); padding:var(--space-0) var(--space-2);
      transition:border-color var(--dur-1) var(--ease-1); }
    .precs-campcfg-num:hover{ border-color:color-mix(in srgb, var(--precs-gold) 50%, var(--border-color)); }
    .precs-campcfg-num:focus{ outline:none; border-color:var(--precs-gold); }
    .precs-campcfg-body.mexendo{ opacity:.6; pointer-events:none; }
    .precs-campcfg-rodape.editando{ border-left-color:var(--accent-green); background:color-mix(in srgb, var(--accent-green) 6%, transparent); }
    @media (prefers-reduced-motion: reduce){ .precs-campcfg-num{ transition:none; } }

/* (05/10/2026, 4ª volta) A SOMBRA FICA: "esse shadow quando selecionamos é muito bonito, podia ficar assim para sempre"
   (Pedro). O card clicável nasce elevado; o hover passa a ser o LEVANTAR: sobe 2 px, a sombra cresce e o filete da família
   ganha brilho. Mesmo tratamento para os cards do quadro das Estratégias. */
    .precs-campsaldo[tabindex]{ box-shadow:var(--shadow); transition:border-color var(--dur-2) var(--ease-1), box-shadow var(--dur-2) var(--ease-1), transform var(--dur-2) var(--ease-2), background-color var(--dur-2) var(--ease-1); }
    .precs-campsaldo[tabindex]:hover{ box-shadow:var(--shadow-lg); transform:translateY(-2px); }
    .precs-campsaldo[tabindex]:active{ transform:translateY(0); }
    .precs-campsaldo-fam[tabindex]:hover{ border-top-color:color-mix(in srgb, var(--id) 70%, white); }
    .precs-est-camp{ box-shadow:var(--shadow); }
    .precs-est-camp:hover{ box-shadow:var(--shadow-lg); transform:translateY(-2px); }
    .precs-est-palco.arrastando .precs-est-camp:hover, .precs-est-palco.movendo .precs-est-camp:hover{ transform:none; }
    @media (prefers-reduced-motion: reduce){
      .precs-campsaldo[tabindex]:hover, .precs-est-camp:hover{ transform:none; }
    }

/* (05/10/2026, 5ª volta) A COR DA FAMÍLIA É PERMANENTE: o que o Pedro gostou na Follow (o card tingido, com o brilho sem
   mouse) vale para toda família; o hover só intensifica e levanta. O mesmo tingido vai para os cards do quadro das
   Estratégias, que já carregam `--id`. */
    .precs-campsaldo-fam{ background:linear-gradient(165deg, color-mix(in srgb, var(--id) 11%, var(--bg-tertiary)), var(--bg-tertiary) 62%); }
    .precs-campsaldo-fam[tabindex]:hover{ background:linear-gradient(165deg, color-mix(in srgb, var(--id) 18%, var(--bg-tertiary)), var(--bg-tertiary) 62%); }
    .precs-campsaldo-fam-fu{ background:linear-gradient(165deg, color-mix(in srgb, var(--id) 12%, var(--bg-tertiary)), var(--bg-tertiary) 60%); }
    .precs-est-camp:not(.vazia){ background:linear-gradient(165deg, color-mix(in srgb, var(--id, var(--border-color)) 10%, var(--bg-secondary)), var(--bg-secondary) 62%); }
    .precs-est-camp:not(.vazia):hover{ background:linear-gradient(165deg, color-mix(in srgb, var(--id, var(--border-color)) 16%, var(--bg-secondary)), var(--bg-secondary) 62%); }

/* (05/10/2026, 6ª volta) O HOVER DA ABA CAMPANHAS, enxuto: o foco de luz que seguia o mouse saiu ("parece uma lanterna",
   Pedro). Fica o que ele aprovou: a borda acesa na cor da campanha (três lados; o filete esquerdo de urgência não muda)
   e o levantar, agora mais lento e com saída suave. */
    .precs-campsaldo[tabindex]{ --luz:var(--id, var(--precs-gold-forma));
      transition:border-color var(--dur-3) var(--ease-1), box-shadow var(--dur-3) var(--ease-1), transform var(--dur-3) var(--ease-1), background-color var(--dur-2) var(--ease-1); }
    .precs-campsaldo[tabindex]:hover{
      border-top-color:color-mix(in srgb, var(--luz) 60%, var(--border-color));
      border-right-color:color-mix(in srgb, var(--luz) 60%, var(--border-color));
      border-bottom-color:color-mix(in srgb, var(--luz) 60%, var(--border-color));
    }
    .precs-campsaldo-fam[tabindex]:hover{ border-top-color:color-mix(in srgb, var(--id) 70%, white); }

/* (05/10/2026) MODO TV LEVE: a parede para o navegador da própria TV (Tizen, pouco processador). Nada de animação,
   transição, sombra, filtro nem desfoque: cada um desses obriga a TV a recompor a tela a cada quadro, e era isso que
   travava. O que informa (cor, borda, texto, número) fica igual; o alerta que pulsava vira borda fixa. */
    html[data-tv-leve] *, html[data-tv-leve] *::before, html[data-tv-leve] *::after{
      animation:none !important; transition:none !important;
      box-shadow:none !important; text-shadow:none !important;
      filter:none !important; backdrop-filter:none !important; -webkit-backdrop-filter:none !important;
      will-change:auto !important; }

/* (05/10/2026) ROSTO GRANDE NO CARD COM FOLGA. Na TV da sala (1080p, quatro células de 421px) o rosto ficava em 28px:
   "as fotos na TV estão minúsculas" (Pedro). O teto de 28px vinha do card CHEIO (quatro listas), que o teste da parede
   mede; o card com até duas listas e quatro operadores (`data-folga`, escrito pelo pintor) tem altura sobrando. Só com
   quatro células e célula de 400px ou mais (a TV em 1080p): com seis ou sete cards, e em 1366x768, o teste da parede
   mostrou card transbordando e rosto escondido em "+N". */
    @container card (min-height: 400px){
      html[data-tv] .precs-sup-tv-parede[data-n="4"] .precs-campsaldo[data-folga] > *{ --tv-av: clamp(32px, 9cqh, 46px); }
    }

/* (06/10/2026) A FICHA COMO A 3C: "Ações do gestor", "Ações do agente" e as outras chaves do cadastro (só leitura). E o AMD
   em destaque, com moldura vermelha: é a chave que mais muda o resultado da discagem. */
    .precs-campcfg-grupo-tit{ margin:var(--space-3) 0 var(--space-1); font-size:var(--fs-1); font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--text-secondary); }
    .precs-campcfg-grupo-tit small{ margin-left:var(--space-1); font-weight:500; letter-spacing:0; text-transform:none; color:var(--text-muted); }
    .precs-campcfg-chave.destaque{ grid-column:span 2; border:1.5px solid color-mix(in srgb, var(--accent-red) 75%, var(--border-color));
      background:color-mix(in srgb, var(--accent-red) 9%, var(--bg-tertiary)); outline:1px solid color-mix(in srgb, var(--accent-red) 25%, transparent); outline-offset:2px; }
    .precs-campcfg-chave.destaque .precs-campcfg-chave-rot{ font-weight:700; color:var(--text-primary); }
    .precs-campcfg-chave.destaque .precs-campcfg-chave-pt{ background:var(--accent-red); }
    .precs-campcfg-chave.destaque.liga .precs-campcfg-chave-pt{ background:var(--accent-green); }
    .precs-campcfg-outra{ display:flex; align-items:center; gap:var(--space-2); padding:var(--space-1) var(--space-2); border-radius:var(--radius-inner);
      border:1px dashed var(--border-color); color:var(--text-secondary); font-size:var(--fs-2); }
    .precs-campcfg-outra .precs-campcfg-chave-rot{ flex:1 1 auto; min-width:0; }
    .precs-campcfg-outra.liga .precs-campcfg-chave-pt{ background:var(--accent-green); }
