/* Sapucaia NanoGalv — folha da aplicação.
   Intenção de design da plataforma: `docs/design/intent.md` (Espresso/Frappe como
   gramática, voz Sapucaia por cima, três zonas). Valores de marca:
   `docs/sgi/design-system/sapucaia-core.css`.

   Fontes: RESOLVIDO em 06/09/2026. Inter + JetBrains Mono estão vendorizadas em
   `/static/vendor/fontes/*.woff2` e declaradas em `/static/base/tokens.css`.
   (O texto que estava aqui pedia para "colocar Archivo em /static/fonts/" — pedido
   que ficou no arquivo por todo o projeto sem ninguém cumprir, enquanto a
   plataforma inteira rodava em system-ui sem que isso aparecesse em lugar nenhum.
   A LAN da fábrica segue offline: nada de @import externo, e há teste provando.) */

:root {
  --red-500: #EE1C25; --red-600: #D01820; --red-wash: #FEECEE;
  --ink-800: #231F20; --ink-600: #5A5458; --ink-500: #5A6870; --ink-100: #E6EAED;
  /* --ink-500 era #6B7B84: 4,05:1 sobre o fundo dos tiles, abaixo do minimo
     de 4,5 do WCAG AA — e ele pinta justamente os rotulos de 11px do painel
     do operador, lidos de longe e com luva. Agora 5,2:1. */
  --page: #F5F6F7; --surface: #FFFFFF; --divider: #E6EAED; --divider-soft: #F0F2F4;
  --success: #1E7A3E; --success-600:#186531; --warning: #B85A0C;
  /* 🔴 Inter desde 06/09/2026, e ANTES DISSO ISTO ERA UMA PROMESSA VAZIA: dizia
     'Archivo' e não havia Archivo nenhuma para carregar — nem `@font-face`, nem
     pasta de fontes — então toda tela da plataforma caía no `system-ui`. A fonte
     agora existe de verdade, vendorizada em `vendor/fontes/` e declarada em
     `base/tokens.css`. Decisão e porquê: `docs/design/intent.md` §D1. */
  --font-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  /* U9/ISA-101 — "OCUPADO" é ESTADO NORMAL e por isso NÃO leva a cor da marca.
     Vermelho é reservado ao anormal/acionável. Este token é o cinza-azulado de
     "presente/em processo" usado no mímico das Linhas e no do Painel. */
  --ocupado:#5A6675; --ocupado-tinta:#FFFFFF;
  /* Cores de TEXTO de estado. Existem separadas das cores de FUNDO (--success,
     --red-500) porque as duas precisam de coisas opostas no tema escuro: o
     verde da marca sobre fundo escuro dá 2,5:1 e some, mas clarear o --success
     estragaria o branco em cima de `button.primary`. Medido em WCAG AA. */
  --ok-txt:#1E7A3E; --aviso-txt:#B8860B; --alarme-txt:#C0161D; --semcomm-txt:#6B7B84;
  /* FUNDO de aviso (faixa de problemas de cadastro, alerta do editor). Precisa
     de par próprio pelo mesmo motivo das cores de texto acima: no tema escuro
     um creme claro apaga o texto. */
  --aviso-bg:#FEF3C7;
  /* RAIO alinhado ao canvas "Design System Sapucaia" (export de 26/08/2026, em
     docs/sgi/design-system/sapucaia-core.css). Era 8/12/16 aqui e 14/18/24 lá —
     as telas novas de SGR/SGP/SGX/Flow já nasceram seguindo o canvas, então o
     descompasso estava DENTRO do produto, não entre produto e maquete.
     Alinhado em 27/08/2026; `--r-xs`/`--r-sm` não existiam e vêm do canvas.
     Isto move 36 superfícies (35 usos de var(--r-*) aqui + 25 nos módulos).
     ⚠ Os 27 `border-radius:` literais em px deste arquivo NÃO foram convertidos
     para token de carona — varrer a tela do operador por estética é o refactor
     que o §5 proíbe. Convertem-se quando o bloco for mexido por outro motivo. */
  --r-xs: 6px; --r-sm: 10px;
  --r-md: 14px; --r-lg: 18px; --r-xl: 24px; --pill: 999px;
  --shadow-sm: 0 1px 3px rgba(35,31,32,.08), 0 1px 2px rgba(35,31,32,.04);
  --shadow-md: 0 4px 12px rgba(35,31,32,.08);
  /* 🔴 A BORDA DO CARD FICA, e o canvas não manda tirar daqui. No canvas o card
     é só sombra; nesta folha, 26 superfícies usam `border:1px solid var(--divider)`
     — e na tela do operador, lida a 1–2 m sob luz de galpão, a borda é o que
     separa dois tiles adjacentes. `sapucaia-core.css` desliga TODA sombra em
     `prefers-contrast`: num tablete nessa configuração, card sem borda e sem
     sombra é card sem divisão nenhuma. Quem quer o card do canvas tira a borda
     na PRÓPRIA folha (shell.css, modulos/*), onde a superfície tem sombra viva
     e a decisão é local — não aqui, onde vale para o painel da linha também. */
}
* { box-sizing: border-box; }
body {
  margin: 0; font-family: var(--font-sans); color: var(--ink-800);
  background: var(--page); font-size: 16px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
.hidden { display: none !important; }
/* 🔴 O `[hidden]` do NAVEGADOR é `display: none` na folha do agente de usuário,
   e QUALQUER `display` numa classe do projeto o sobrepõe — sem erro, sem aviso.
   Medido em 28/08/2026: seis elementos nasciam `hidden` e apareciam mesmo
   assim, entre eles a engrenagem de configuração do Flow (`.fl-engrenagem`,
   `display: inline-flex`), que o comentário do HTML jurava só acender quando o
   SERVIDOR devolvesse `pode_editar`. Ela acendia para todo mundo — e ainda
   custava 54px do cabeçalho de quem não é admin.
   Não é furo de acesso (quem barra são as rotas, que conferem de novo); é uma
   promessa escrita que o código não cumpria, e uma segunda verdade sobre o que
   está na tela. A regra vive aqui, na folha base que toda tela carrega, para
   valer também para o próximo elemento que nascer oculto. */
[hidden] { display: none !important; }

/* Modais (motivo de pausa, fim de batch — NanoGalv) */
.modal-backdrop { position: fixed; inset: 0; background: rgba(35,31,32,.55);
  z-index: 60; display: flex; align-items: flex-start; justify-content: center;
  padding: 40px 16px; overflow: auto; }
.modal-backdrop .modal { width: 100%; max-width: 520px; margin: auto; }

/* Sub-navegação (Configurações, Histórico) — pílulas de grupo */
.subnav { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.subnav button { background: var(--surface); color: var(--ink-500);
  box-shadow: none; border: 1px solid var(--divider); padding: 9px 16px; }
.subnav button.active { color: var(--red-500); background: var(--red-wash);
  border-color: #FAD1D3; }

/* Botões — pílula (identidade do site Sapucaia) */
button {
  font: inherit; font-weight: 700; letter-spacing: -.005em;
  border: 1.5px solid transparent; border-radius: var(--pill);
  padding: 11px 22px; cursor: pointer; background: var(--surface); color: var(--ink-800);
  box-shadow: var(--shadow-sm);
  transition: transform .2s cubic-bezier(.2,.7,.3,1), background .2s, box-shadow .2s, color .2s, border-color .2s;
  /* U4: sem isto o Android/iOS pinta um retângulo cinza no toque, por cima da
     pílula. O retorno visual do toque é dado pelo `:active` abaixo. */
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;   /* mata o atraso de 300 ms do duplo-toque */
}
button.primary { background: var(--success); color: #fff; border-color: transparent; box-shadow: 0 12px 26px -12px rgba(30,122,62,.6); }
button.danger { background: var(--red-500); color: #fff; box-shadow: 0 12px 26px -12px rgba(238,28,37,.6); }
button.ghost { background: var(--surface); color: var(--ink-800); border-color: rgba(35,31,32,.12); }
button:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }

/* U4 — TOQUE: o feedback é AFUNDAR, e vale em qualquer dispositivo.
   No toque o `:active` dura o aperto e sai sozinho; era o `:hover` que ficava
   GRUDADO depois do toque (o botão seguia "levantado" até se tocar em outro
   lugar, e o operador não sabia se havia apertado). */
button:not(:disabled):active { transform: translateY(1px) scale(.985);
  box-shadow: var(--shadow-sm); filter: brightness(.96); }
tr.click:active { background: var(--divider-soft); }

/* U4 — HOVER só onde existe mouse de verdade. Antes estes efeitos valiam para
   todo mundo: em tela de toque o navegador emula `:hover` e ele não sai mais.
   MEDIDO na auditoria: ZERO ocorrências de (hover:hover)/(pointer:coarse) no
   CSS inteiro. Tudo que é decoração de mouse mora aqui. */
@media (hover: hover) and (pointer: fine) {
  button:not(:disabled):hover { transform: translateY(-2px); }
  button.primary:not(:disabled):hover { background: var(--success-600); }
  button.danger:not(:disabled):hover { background: var(--red-600); }
  .subnav button:hover { color: var(--ink-800); transform: none; }
  #topbar .tab:hover { color: var(--ink-800); transform: none; }
  tr.click:hover { background: var(--divider-soft); }
  #statusbar .sb-kbtn:hover { color: #fff; border-color: #3A4656; transform: none; }
}

input, select, textarea {
  font: inherit; background: var(--surface); color: var(--ink-800);
  border: 1px solid var(--ink-100); border-radius: var(--r-md); padding: 9px 11px; width: 100%;
  /* U3: controle NUNCA passa do container. Sem isto o `<select>` de densidade do
     cabeçalho do Painel (que usa width:auto) fazia a PÁGINA rolar de lado em
     tablet retrato — medido: scrollWidth − innerWidth = 21 px a 768×1024.
     Rolagem horizontal numa tela de supervisão é inaceitável.
     ⚠ `max-width` sozinho NÃO resolve: como item flex, o controle tem
     `min-width:auto` = tamanho do conteúdo, e mínimo vence máximo no CSS. Por
     isso o `min-width:0` — foi ele que matou os 21 px. */
  max-width: 100%; min-width: 0;
}
input:focus, select:focus { outline: none; border-color: var(--red-500); }
/* Caixa de marcar não é campo de texto: não herda o `width:100%` acima. Estava
   sendo desfeito com `style="width:auto"` INLINE em cada uma — e estilo inline
   vence folha de estilo, o que impedia qualquer regra de tamanho de alvo de
   pegar nelas (ver o bloco de toque). Regra aqui, inline nenhum. */
input[type="checkbox"], input[type="radio"] { width: auto; }
label { display: block; font-size: 12px; font-weight: 600; color: var(--ink-500); margin: 10px 0 4px;
  text-transform: uppercase; letter-spacing: .04em; }
/* unidade dentro de rótulo/cabeçalho MAIÚSCULO: mantém minúsculo p/ o μ de micron
   não virar Μ (Mu grego, visualmente = "M") — evita ler "µm" como "MM". */
.u { text-transform: none; }

/* Topbar */
#topbar {
  display: flex; align-items: center; gap: 6px; padding: 10px 16px;
  background: var(--surface); border-bottom: 1px solid var(--divider); flex-wrap: wrap;
  box-shadow: var(--shadow-sm); position: sticky; top: 0; z-index: 10;
}
#topbar .brand { display: flex; align-items: center; gap: 10px; font-weight: 800; margin-right: auto; letter-spacing: -.01em; }
#topbar .brand img { height: 26px; width: auto; }
#topbar .tab { background: transparent; box-shadow: none; color: var(--ink-500); padding: 8px 14px; }
#topbar .tab.active { color: var(--red-500); background: var(--red-wash); }
#topbar .who { color: var(--ink-500); font-size: 14px; }

/* 100% da largura também no estreito (ver a regra no bloco do trilho) */
/* 🔴 --pad-tela: o padding lateral do <main> E, com sinal trocado, a SANGRIA do
   #statusbar — a barra é o único elemento que vaza para fora desse padding, para
   encostar nas duas bordas. Os dois números TÊM de ser o mesmo, e já desandaram:
   146f786 baixou o padding de 26 -> 14 px e deixou a sangria em -26, então a
   barra passava 12 px da borda direita e a PÁGINA INTEIRA rolava de lado
   (medido a 1280: scrollWidth 1277 contra clientWidth 1265). Num tablet de 8,7"
   um estouro desses desliza a tela de lado quando o operador tenta rolar na
   vertical com luva — contraria "zero scroll horizontal no corpo da página"
   (CLAUDE.md §3): tabela e gráfico rolam DENTRO do contêiner, o corpo não.
   Agora quem mexer no padding mexe aqui e a barra acompanha sozinha; há teste
   que prende isso (test_css_tablet_e_isa101). */
:root { --pad-tela: 12px; }
main { padding: var(--pad-tela); max-width: none; margin: 0; }
.card { background: var(--surface); border: 1px solid var(--divider); border-radius: var(--r-xl);
  padding: 18px; margin-bottom: 16px; box-shadow: var(--shadow-sm); }
.card h3 { margin: 0 0 12px; font-size: 20px; font-weight: 700; }
.row { display: flex; gap: 12px; flex-wrap: wrap; }
.row > * { flex: 1; min-width: 130px; }
/* Tela inicial (cadastro de usuário): campo do tamanho de um CAMPO, não do
   tamanho da linha. Sem isto o `flex:1` do `.row > *` esticava o <select>
   sozinho por ~700 px, e a mesma linha mudava de largura quando o campo "Linha
   desta tela" aparecia — o formulário pulava debaixo do dedo de quem cadastra.
   `0 1 min(320px, 100%)`: não cresce, e encolhe até caber no tablete. */
.us-tela-campo { flex: 0 1 min(320px, 100%); }
/* Rótulo que EMBRULHA o próprio controle ("Densidade [select]") é barra de
   ferramentas, não campo de formulário: não deve esticar nem ser espremido pelo
   `flex:1 / min-width:130px` acima. Toma o tamanho natural e, se não couber, o
   `flex-wrap` do `.row` desce ele para a linha seguinte — que é o certo. Antes
   ele era comprimido a 130 px e o `<select>` de dentro vazava para fora da
   página (os 21 px de rolagem horizontal do U3). */
.row > label:has(> select), .row > label:has(> input), .row > label:has(> button) {
  flex: 0 0 auto; min-width: 0;
}

#login { max-width: 380px; margin: 12vh auto; }
#login h2 { margin: 0 0 4px; }
#login .sub { color: var(--ink-500); font-size: 14px; margin-bottom: 8px; }

/* Leituras ao vivo.
   U2: era `repeat(3, 1fr)` FIXO. MEDIDO a 1920: 3 tiles de 379 px cada — tile
   gigante e espaço vazio em vez de mais informação. Agora o número de colunas
   sai da largura disponível, como o Painel já fazia. Isto também substitui o
   antigo `@media (max-width:560px)` de 2 colunas: a 560 px o auto-fit já dá 2. */
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.tile { background: var(--page); border: 1px solid var(--divider); border-radius: var(--r-lg); padding: 12px; }
/* RN-3: dado velho tem de PARECER velho. Tratamento de "quality overlay":
   esmaece o valor e listra o fundo — dá para ver de longe que aquele tile
   parou de atualizar, mesmo com o resto da tela viva. */
.tile.stale-tag { opacity: .62; border-style: dashed;
  background: repeating-linear-gradient(135deg, var(--page), var(--page) 8px,
    color-mix(in srgb, var(--ink-500) 8%, var(--page)) 8px,
    color-mix(in srgb, var(--ink-500) 8%, var(--page)) 16px); }
.tile.stale-tag .v { text-decoration: line-through; text-decoration-thickness: 1px; }
.tile .k { font-size: 11px; font-weight: 700; color: var(--ink-500); text-transform: uppercase; letter-spacing: .08em; }
.tile .v { font-family: var(--font-mono); font-size: 26px; font-weight: 600; margin-top: 4px; }
.tile.destaque { background: var(--red-wash); border-color: #FAD1D3; }

/* `.big` = "este comando é diferente dos outros". É o ESCAPE da régua de 40 px
   (ver o bloco TOQUE): o piso desceu para todo mundo em 08/09/2026, e quem
   precisa de alvo maior pede por aqui, explicitamente.

   🔴 44 px, não os 60 de antes — e isso encolhe `PARAR / EMERGÊNCIA` e
   `DESLIGAR TODOS OS BANHOS`, que são os dois usos críticos da classe.
   **Decisão do Juan em 08/09/2026, tomada depois de eu recomendar o
   contrário** citando a regra inviolável 7. Fica escrito para a próxima sessão
   não "consertar" isto achando que foi descuido: foi escolha, com o risco
   apontado e assumido.

   Continua ACIMA do piso (44 > 40) de propósito. Se `.big` valesse o mesmo que
   um botão comum, a classe deixaria de significar coisa alguma e os 32 lugares
   que a usam perderiam a hierarquia sem ninguém decidir isso. */
.big { min-height: 44px; font-size: 17px; width: 100%; }
.banner { padding: 13px 16px; border-radius: var(--r-lg); margin-bottom: 14px; font-weight: 700; }
.banner.crit { background: var(--red-500); color: #fff; }
.banner.warn { background: var(--warning); color: #fff; }
.banner.stale { background: var(--ink-600); color: #fff; }
/* Acompanhamento de fora da fábrica (28/09/2026): cinza, sem cor de alarme —
   não é falha, é o modo. ISA-101: o normal não grita. */
.banner.acomp { background: var(--ink-100); color: var(--ink-800); border: 1px solid var(--divider);
  font-weight: 600; font-size: .85em; padding: 6px 12px; margin-bottom: 10px; }
/* Dado congelado (sem conexao com o servidor): NUNCA parecer ao vivo */
main.stale .tile .v, main.stale canvas { opacity: .45; }

/* Alarme piscante de elemento monitorado (norma SCADA: chama o olho) */
@keyframes ligaBlink { 0%,100% { background: var(--red-wash); } 50% { background: var(--red-500); color: #fff; } }
.tile.blink { animation: ligaBlink 1s infinite; border-color: var(--red-500); }
.tile.blink .v, .tile.blink .k { color: inherit; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--divider); }
th { font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--ink-500); }
tr.click { cursor: pointer; }
/* U8: envelope que rola de lado. Em tablet retrato sobram ~733 px úteis e o card
   "Ciclos de tambor" tem 13 colunas — sem isto a PÁGINA inteira rola de lado.
   Aplicado a todas as tabelas no boot (ver app.js), não tabela por tabela. */
.tabela-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* U7: altura proporcional à tela, não 150 px fixos num monitor de 1080. */
#log { height: clamp(150px, 26vh, 420px); overflow: auto; background: var(--page); border: 1px solid var(--divider);
  border-radius: var(--r-lg); padding: 10px; font-family: var(--font-mono); font-size: 12.5px; white-space: pre-wrap; }

/* Tile com texto (estado decodificado) em vez de numero grande */
.tile .v.texto { font-family: var(--font-sans); font-size: 14px; font-weight: 600; line-height: 1.45; }

/* ---------- Menu lateral (mobile) + hamburguer ---------- */
#menu-btn { display: none; padding: 8px 14px; font-size: 20px; line-height: 1; box-shadow: none; }
#drawer-backdrop { position: fixed; inset: 0; background: rgba(35,31,32,.45); z-index: 40; }
#drawer {
  position: fixed; top: 0; left: 0; bottom: 0; width: 250px; z-index: 41;
  background: var(--surface); box-shadow: var(--shadow-md); padding: 14px;
  display: flex; flex-direction: column; gap: 6px;
}
#drawer .dr-head { display: flex; align-items: center; gap: 10px; font-weight: 800; padding: 4px 6px 14px; border-bottom: 1px solid var(--divider); margin-bottom: 8px; }
#drawer .dr-head img { height: 26px; }
#drawer > button { text-align: left; background: transparent; box-shadow: none; border-radius: var(--r-md); padding: 12px 14px; }
#drawer > button.active { color: var(--red-500); background: var(--red-wash); }
#drawer .dr-foot { margin-top: auto; border-top: 1px solid var(--divider); padding-top: 10px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }

/* ---------- Mobile / tablet: operacao limpa ---------- */
@media (max-width: 820px) {
  #menu-btn { display: inline-block; }
  #topbar .tab, #topbar #who, #topbar #btn-sair { display: none; } /* menu vai para o drawer */
  #topbar { padding: 8px 12px; gap: 8px; }
  #topbar .brand { font-size: 15px; }
  :root { --pad-tela: 10px; }
  .card { padding: 14px; border-radius: var(--r-lg); }
  /* no estreito o formulário empilha: o campo da tela inicial ocupa a linha
     inteira como os outros, em vez de ficar curto num canto */
  .us-tela-campo { flex: 1 1 auto; }
  .tile .v { font-size: 22px; }
  button.big { min-height: 48px; }  /* 08/09/2026: era 64 ("dedo com luva") */
  canvas { height: 180px; }
}
.pill { display: inline-block; padding: 3px 10px; border-radius: var(--pill); font-size: 12px; font-weight: 700; background: var(--divider); }
.pill.on { background: var(--success); color: #fff; }
/* `off` = falha de verdade (sem comunicação, leitura irreal) — vermelho é certo. */
.pill.off { background: var(--red-500); color: #fff; }
/* U9/ISA-101: PAUSADO é decisão do usuário, não defeito. Estava caindo no
   `.pill.off` e saía vermelho — o operador pausa um elemento de propósito e o
   sistema o repreende como se fosse alarme. Neutro. */
.pill.pausado { background: var(--ink-100); color: var(--ink-800); }
/* Atenção (não alarme): fim de turno chegando. Âmbar, não vermelho — turno
   acabando é o curso normal das coisas, só precisa ser VISTO a tempo. */
.pill.aviso { background: var(--warning); color: #fff; }
/* U7: alturas proporcionais (clamp com vh) em vez de px cravado. */
canvas { width: 100%; height: clamp(170px, 24vh, 330px); background: var(--page); border: 1px solid var(--divider); border-radius: var(--r-lg); }
/* #gr-chart = container do ECharts (a lib gerencia o canvas interno).
   touch-action pan-y: o dedo lê o gráfico E ainda rola a página (issue
   ECharts #12879 — gráfico em tela cheia trava o scroll no celular). */
#gr-chart { width: 100%; height: clamp(320px, 48vh, 640px); touch-action: pan-y; }

/* ---- mimico do transportador (aba Linhas) ---- */
.lm-strip { position: relative; display: flex; gap: 4px; margin-top: 34px; }
.lm-pos { flex: 1; min-width: 0; background: var(--page); border: 1px solid var(--divider);
  border-radius: 0 0 8px 8px; padding: 8px 4px 10px; text-align: center; }
.lm-pos .lm-num { display: block; font-weight: 700; font-size: 13px; }
.lm-pos .lm-nome { display: block; font-size: 10px; color: var(--ink-2, #6B7B84);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* U9/ISA-101: o carrinho AQUI é "onde a atenção está", não anormal — usa a cor
   viva (azul), a mesma que o mímico do Painel já usa para posição atual. */
.lm-pos.ativo { outline: 2px solid var(--live); background: #fff; }
/* tambor ocupando a estação: nº do tambor (ou a OS) + residência ao vivo.
   Duas linhas para o rótulo e o tempo não se cortarem na célula estreita.
   U9/ISA-101: era o VERMELHO DA MARCA. "Tem tambor na estação" é o estado mais
   normal que existe — com a linha operando bem o mímico inteiro ficava
   vermelho, e cor que aparece em tudo deixa de ser sinal. Vermelho agora é
   exclusivo de alarme. O contraste do branco segue acima de 4.5:1. */
.lm-pos .lm-tb { display: block; font-size: 10px; line-height: 1.35;
  background: var(--ocupado); color: var(--ocupado-tinta);
  border-radius: 6px; margin: -4px -2px 4px; padding: 1px 3px; }
.lm-pos .lm-tb b, .lm-pos .lm-tb i { display: block; font-style: normal;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lm-pos .lm-tb b { font-weight: 700; }
.lm-pos .lm-tb i { font-weight: 600; font-variant-numeric: tabular-nums;
  opacity: .92; }
.lm-pos.ocupada { border-color: var(--ocupado);           /* U9: era a marca */
  background: color-mix(in srgb, var(--ocupado) 8%, var(--page)); }
/* tambor presente/ausente: TODA posição mostra o slot do tambor — cheio
   (marca) quando o RastreadorTambor vê tambor na estação, tracejado quando
   vazia. Assim dá para varrer a linha inteira num relance (pedido do usuário,
   2026-07-30). Vale para todas as linhas rastreadas (R1/R2/R3/Parado).
   O tambor é o OCTÓGONO da foto do carrinho, visto de topo, com o eixo
   atravessando. O eixo existe por um motivo: octógono tem simetria de 45° e,
   girando sozinho, parece parado — é o eixo que denuncia o movimento. */
.lm-pos .lm-drum, .lm-cart .lm-drum { display: block; width: 22px; height: 22px;
  margin: 2px auto 4px; overflow: visible; }
.lm-drum .d-corpo { fill: none; stroke: var(--divider); stroke-width: 2;
  stroke-dasharray: 3 3; opacity: .75; }
.lm-drum .d-eixo, .lm-drum .d-hub { display: none; }
.lm-pos.ocupada .lm-drum .d-corpo { fill: var(--ocupado); stroke: var(--ocupado);
  stroke-dasharray: none; opacity: 1; }
.lm-pos.ocupada .lm-drum .d-eixo { display: block; stroke: var(--ocupado-tinta);
  stroke-width: 1.7; stroke-linecap: round; opacity: .95; }
.lm-pos.ocupada .lm-drum .d-hub { display: block; fill: var(--ocupado-tinta); }
/* 5 rpm = 12 s por volta (pedido do usuário: velocidade realista, para o
   operador reconhecer o movimento da máquina e conferir se o programa está
   depositando nas posições certas). Só gira DENTRO do tanque. */
@keyframes lm-girar { to { transform: rotate(360deg); } }
.lm-pos.ocupada.girando .lm-drum { animation: lm-girar 12s linear infinite;
  transform-origin: 50% 50%; }
/* tambor pendurado no carrinho: existe, mas PARADO (em trânsito não gira) */
.lm-cart .lm-drum { display: none; width: 18px; height: 18px; margin: -2px auto 0; }
.lm-cart.carregando .lm-drum { display: block; }
.lm-cart.carregando .lm-drum .d-corpo { fill: var(--ocupado); stroke: var(--ocupado);
  stroke-dasharray: none; opacity: 1; }
.lm-cart.carregando .lm-drum .d-eixo { display: block; stroke: var(--ocupado-tinta);
  stroke-width: 1.7; stroke-linecap: round; }
.lm-cart.carregando .lm-drum .d-hub { display: block; fill: var(--ocupado-tinta); }
/* U4: quem pediu menos movimento não leva animação nenhuma — o estado
   (cheio/vazio) continua legível pelo preenchimento, que não depende do giro. */
@media (prefers-reduced-motion: reduce) {
  .lm-pos.ocupada.girando .lm-drum { animation: none; }
}
/* ≈ = estação ESTIMADA pelo POS: o cronômetro é real, mas pode estar contando
   no tanque errado. Sem isto o operador lê o tempo deduzido igual ao medido. */
.lm-pos.estimada .lm-tb { background: repeating-linear-gradient(135deg,
    var(--ocupado) 0 6px, color-mix(in srgb, var(--ocupado) 70%, #000) 6px 12px); }
/* amperagem do banho DENTRO da célula (era o card "Retificadores por
   posição", que duplicava o mapa do carrinho — unificado em 2026-08-04) */
.lm-pos .lm-amp { display: block; font-size: 11px; font-weight: 800;
  font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden;
  text-overflow: ellipsis; }
#opf-faixa .lm-pos .lm-amp { font-size: 14px; }
.lm-cart { position: absolute; top: -32px; left: 0; width: 28px; font-size: 22px;
  text-align: center; transition: left .6s ease, top .4s ease; line-height: 1; }
.lm-cart.baixo { top: -14px; }
.lm-timer { font-weight: 700; font-size: 15px; color: var(--ink-800); }  /* U9 */
@media (max-width: 700px) {
  .lm-pos .lm-nome { display: none; }
  /* célula fica com ~24px: cabe o nº do tambor, não o tempo (fica no title) */
  .lm-pos .lm-tb i { display: none; }
  .lm-pos .lm-tb { padding: 1px 1px; }
}

/* ============================================================
   v38 — SHELL SCADA: trilho lateral (desktop) + tema híbrido.
   Supervisão (Painel/Linhas/Gráficos/Alarmes) = tema ESCURO,
   full-bleed (parede). Operação/cadastros/config = tema CLARO,
   coluna legível. O tema é aplicado em body[data-theme] pelo
   app.js na troca de aba; cards/tiles/tabelas usam tokens, então
   herdam sem mudança de markup.
   ============================================================ */
:root { --rail-w: 96px; }

/* --- tokens do tema escuro (só trocam as cores; layout é o mesmo) ---
   ISA-101/ASM: fundo CINZA MÉDIO, não quase-preto e não branco. O `#0D1117`
   anterior cansa a vista no turno e achata a discriminação de cor — sobre preto
   qualquer cor "salta", então nenhuma se destaca de verdade, que é o oposto do
   que uma tela de supervisão precisa. Os tons de tinta subiram junto para o
   texto secundário não perder contraste com o fundo mais claro (conferido: ver
   as razões WCAG medidas na auditoria, §6/U9). */
body[data-theme="dark"] {
  --page:#2A2F36; --surface:#343A43; --divider:#4A525D; --divider-soft:#31373F;
  --ink-800:#F2F5F8; --ink-600:#C7D0DA; --ink-500:#AAB4C0; --ink-100:#4A525D;
  --red-wash:rgba(255,99,105,.18);
  --ocupado:#8E9AA8; --ocupado-tinta:#1B1F24;
  /* variantes claras das cores de texto de estado (ver a nota no :root) */
  --ok-txt:#5BD97F; --aviso-txt:#FFB020; --alarme-txt:#FF8A8F; --semcomm-txt:#AAB4C0;
  --aviso-bg:rgba(255,176,32,.16);
  --shadow-sm:0 1px 3px rgba(0,0,0,.35); --shadow-md:0 10px 30px rgba(0,0,0,.45);
  background:var(--page);
}
body[data-theme="dark"] input,
body[data-theme="dark"] select,
body[data-theme="dark"] textarea { background:#242930; border-color:#4A525D; color:var(--ink-800); }
body[data-theme="dark"] .tile { background:#242930; }
body[data-theme="dark"] button.ghost { background:#3D444E; color:var(--ink-800); border-color:#4A525D; }
body[data-theme="dark"] .subnav button { background:#343A43; border-color:#4A525D; color:var(--ink-500); }
body[data-theme="dark"] #log { background:#242930; }

/* --- desktop: a topbar vira TRILHO lateral fixo (chrome sempre escuro) --- */
@media (min-width: 821px) {
  #topbar {
    position:fixed; left:0; top:0; bottom:0; width:var(--rail-w); z-index:20;
    flex-direction:column; align-items:stretch; flex-wrap:nowrap; gap:2px;
    padding:14px 10px; border-bottom:0; border-right:1px solid #3A4149;
    background:#1E232A; box-shadow:none; overflow-y:auto;
  }
  #topbar .brand { flex-direction:column; gap:6px; margin:0 0 14px; text-align:center;
    font-size:11px; line-height:1.15; color:#E8EEF4; }
  #topbar .brand img { height:34px; }
  #topbar .brand .who { display:none; }
  #topbar .tab { color:#AAB4C0; background:transparent; padding:10px 6px; border-radius:10px;
    text-align:center; font-size:11.5px; white-space:normal; line-height:1.2; }
  #topbar .tab.active { color:#fff; background:#2E353E; box-shadow:inset 3px 0 0 var(--red-500); }
  #topbar #who { margin-top:auto; color:#AAB4C0; font-size:11px; text-align:center;
    padding:10px 0 6px; border-top:1px solid #3A4149; }
  #topbar #btn-sair { background:transparent; color:#AAB4C0; box-shadow:none; padding:8px; font-size:12px; }
  #menu-btn { display:none !important; }

  :root { --pad-tela: 14px; }
  main { margin-left:var(--rail-w); padding:var(--pad-tela); max-width:none; }
  /* 🔴 REGRA DO SISTEMA: a tela usa 100% da largura. Sem cap, sem centralizar.
     Terceira vez que o Juan pede: primeiro o cap era 1200 px CRAVADO (a 1920,
     Operar/Histórico/Config usavam 1200 e desperdiçavam 720 px), depois virou
     1600 px "para a linha de texto não cansar" — e continuou sobrando borda dos
     dois lados na tela dele. Em supervisório o espaço vale mais que a métrica
     tipográfica de texto corrido: o mímico do carrinho precisa de largura para
     caber o nome escrito DENTRO de cada posição, e o card de linha precisa dela
     para os tiles não truncarem.
     Se algum dia um formulário ficar largo demais para ler, a trava é NELE
     (`max-width` no bloco de texto), nunca na seção inteira.
     ⚠ E o cap estava amarrado ao TEMA (`body[data-theme="light"]`): cor
     decidindo layout. No escuro era full-bleed, no claro era capado — a mesma
     tela mudava de largura ao trocar de tema. Isso sai junto.
     Há um teste que prende esta regra: test_css_tablet_e_isa101. */
}

/* ---- Barra de status (chrome escuro, fixa no topo) ---- */
/* U3: a barra NÃO pode empurrar a página de lado. Os itens têm `nowrap` e o
   número deles VARIA (um por gateway Modbus), então em tablet retrato a soma
   estourava: medido 32 px de rolagem horizontal a 768 px, com o relógio (o
   último item) terminando em 800. Agora ela quebra em duas linhas em vez de
   transbordar — nada fica escondido, que seria o defeito de rolar por dentro. */
#statusbar { display:flex; align-items:center; flex-wrap:wrap; align-content:center;
  gap:8px 18px; min-height:52px; flex:0 0 auto;
  position:sticky; top:0; z-index:15; color:#E8EEF4; font-size:13px;
  background:linear-gradient(180deg,#262C34,#1E232A); border-bottom:1px solid #3A4149;
  margin:calc(-1 * var(--pad-tela)) calc(-1 * var(--pad-tela)) 14px;
  padding:0 var(--pad-tela); }
#statusbar .sb-brand { font-weight:800; letter-spacing:-.01em; font-size:15px; }
#statusbar .sb-brand b { color:var(--red-500); }
#statusbar .sb-sub { color:#AAB4C0; font-weight:500; font-size:12px; }
#statusbar .sb-sp { flex:1; }
#statusbar .sb-item { color:#AAB4C0; display:flex; align-items:center; gap:6px; white-space:nowrap; }
#statusbar .sb-item b { color:#E8EEF4; font-weight:700; }
#statusbar .sb-dot { width:9px; height:9px; border-radius:50%; background:#AAB4C0; flex:0 0 auto; }
#statusbar .sb-dot.ok { background:var(--success); box-shadow:0 0 8px var(--success); }
#statusbar .sb-dot.bad { background:#FF4249; box-shadow:0 0 8px #FF4249; }
#statusbar .sb-badge { background:rgba(255,66,73,.14); border:1px solid rgba(255,66,73,.45);
  /* Tom clareado junto com o fundo cinza médio: o badge tem fundo
     SEMITRANSPARENTE, então a cor efetiva dele acompanha a barra. Com a barra
     antiga (quase preta) o `#FF5A60` dava 4,98:1; sobre a barra nova caía para
     4,03:1 — abaixo do mínimo AA, e no elemento que mais importa ler. 5,44:1. */
  color:#FF8A8F; padding:5px 11px; border-radius:8px; font-weight:700; cursor:pointer; white-space:nowrap; }
/* aviso de relógio: âmbar, não vermelho — o dado segue chegando, o que está
   suspeito é o CARIMBO DE TEMPO dele (ver web/relogio.py). */
#statusbar .sb-badge.hora { background:rgba(255,176,32,.14); border-color:rgba(255,176,32,.45);
  color:#FFB020; }
#statusbar .sb-clock { font-family:var(--font-mono); font-size:15px; font-weight:600;
  font-variant-numeric:tabular-nums; }
#statusbar .sb-clock.suspeito { color:#FFB020; text-decoration:underline dotted; }
#statusbar .sb-kbtn { background:transparent; border:1px solid #4A525D; color:#AAB4C0;
  padding:5px 10px; border-radius:8px; cursor:pointer; font-size:15px; line-height:1; box-shadow:none; }

/* U4 — hover do trilho lateral (só com mouse; ver o bloco geral de hover) */
@media (min-width:821px) and (hover:hover) and (pointer:fine) {
  #topbar .tab:hover { color:#fff; background:#2E353E; transform:none; }
  #topbar #btn-sair:hover { color:#fff; transform:none; }
}

/* ---- modo kiosk / tela cheia (posto de operação) ----
   U12: crescia SÓ a aba Operar. A Planta é justamente a tela de PAREDE,
   e ficava no tamanho normal em kiosk. */
body.kiosk #topbar { display:none !important; }
body.kiosk main { margin-left:0 !important; }
body.kiosk #tab-operar .tile .v { font-size:30px; }
body.kiosk #tab-operar button.big { min-height:78px; font-size:22px; }
body.kiosk #tab-operar .card h3 { font-size:24px; }
body.kiosk .tile .v { font-size:30px; }
body.kiosk .card h3 { font-size:23px; }
body.kiosk .lc-m .mv { font-size:1.35em; }
body.kiosk .pl-met b { font-size:var(--t-4xl); }
body.kiosk .oee-v { font-size:23px; } body.kiosk .oee-v b { font-size:30px; }
/* rótulos minúsculos a distância de parede */
body.kiosk .lc-m .mk, body.kiosk .oee-k, body.kiosk .pl-met small { font-size:12px; }
body.kiosk .lm-pos .lm-num { font-size:16px; }
body.kiosk .lm-pos .lm-nome { font-size:12.5px; }
@media (min-width:821px) {
  #statusbar { margin-bottom:18px; }
  /* trilho fica só com o símbolo; o nome da marca vai p/ a barra de status */
  #topbar .brand #brand-nome, #topbar .brand #brand-cliente { display:none; }
}
@media (max-width:820px) {
  #statusbar { margin-bottom:12px; gap:11px; }
  #statusbar .sb-brand { display:none; }
}

/* ---- ícones no trilho (desktop) e no drawer (mobile) ---- */
@media (min-width:821px) {
  #topbar .tab { display:flex; flex-direction:column; align-items:center; gap:3px; padding:9px 4px; }
  #topbar .tab svg { width:22px; height:22px; flex:0 0 auto; }
  #topbar .tab .lbl { font-size:9.5px; font-weight:700; letter-spacing:.02em; }
}
#drawer button[data-tab] { display:flex; align-items:center; gap:11px; }
#drawer button[data-tab] svg { width:20px; height:20px; flex:0 0 auto; }

/* ============================================================
   O PAINEL SAIU em 02/10/2026: a tela Planta (`static/planta.css`) ficou no
   lugar dele e das Linhas. Daqui fica só o que a tela de OEE do Histórico
   ainda desenha (`#oee-totais`, a barra e a timeline do turno).
   ============================================================ */
:root { --live:#2563EB; }
body[data-theme="dark"] { --live:#4CA6FF; }

/* ---- OEE (Histórico → OEE): tambores × meta do turno + timeline do dia ---- */
.oee { margin-top:8px; }
.oee-k, .oee-k2 { font-size:9.5px; font-weight:700; letter-spacing:.07em;
  text-transform:uppercase; color:var(--ink-500); }
.oee-k2 { margin-top:3px; text-transform:none; letter-spacing:0; font-weight:500; }
.oee-v { font-size:20px; font-weight:800; line-height:1.1; }
.oee-v b { font-size:26px; }
.oee-bar { position:relative; height:8px; border-radius:4px; overflow:hidden;
  background:var(--page); border:1px solid var(--divider); margin-top:3px; }
.oee-bar i { position:absolute; left:0; top:0; bottom:0; border-radius:4px; }
.oee-bar i.ok { background:var(--live, #22c55e); }
.oee-bar i.atras { background:#F59E0B; }
/* marcador do pró-rata: onde a meta "deveria estar" agora */
.oee-bar s { position:absolute; top:-2px; bottom:-2px; width:2px;
  background:var(--ink-500); opacity:.8; }
/* timeline do dia (00–24h): faixa = fora de automático; u = agora.
   U9/ISA-101: era vermelho. Não serve: à noite e no fim de semana a linha está
   LEGITIMAMENTE fora de automático, então a barra passava o dia vermelha — de
   novo cor de alarme em estado normal. É um histórico informativo, não um
   alarme; quem decide se aquilo foi parada indevida é o relatório de turno. */
.oee-tl { position:relative; height:10px; border-radius:3px; margin-top:6px;
  background:var(--page); border:1px solid var(--divider); overflow:hidden; }
.oee-tl i { position:absolute; top:0; bottom:0; background:var(--ink-500); opacity:.75; }
.oee-tl u { position:absolute; top:0; bottom:0; width:2px;
  background:var(--live, #22c55e); }

.lc-m { display:flex; gap:16px; flex-wrap:wrap; margin-top:auto; }
.lc-m .mk { font-size:9.5px; font-weight:700; letter-spacing:.07em; text-transform:uppercase; color:var(--ink-500); }
.lc-m .mv { font-family:var(--font-mono); font-size:19px; font-weight:600; font-variant-numeric:tabular-nums; line-height:1.2; }
.lc-m .mv u { font-size:11px; color:var(--ink-500); text-decoration:none; font-family:var(--font-sans); }
/* temperatura de banho da linha: fora da faixa e sem sinal têm de se ler no
   próprio número, não só pela tarja lateral do card */
.lc-m .mv.alarme { color:var(--alarme-txt); }
.lc-m .mv.mudo { color:var(--ink-500); }

/* ============================================================
   TOQUE — U5/U6/U14 da auditoria. A regra vale por PONTEIRO, não por
   largura: o breakpoint único de 820 px classificava o iPad retrato (768)
   como CELULAR e o iPad paisagem (1024) como DESKTOP. Resultado medido:
   em retrato o operador perdia o NOME das estações do mímico; em paisagem
   ganhava um trilho com rótulos de 9,5 px — texto de 9,5 px a distância de
   braço, com luva, no chão de fábrica.
   🔴 A RÉGUA MUDOU EM 08/09/2026, POR DECISÃO DO JUAN: **40 px, não 56.**
   Palavras dele: *"esquecer essa regra dos 56 px — tem botões muito grandes
   dentro do sistema todo, colocar isso para tudo; é melhor eu pedir botões
   grandes se eu precisar em algum lugar."*

   O que a régua velha assumia e a fábrica desmentiu: **o operador NÃO opera de
   luva.** Os 56 px vinham da norma de HMI industrial (~15 mm) para dedo
   enluvado; sem luva eles só gastam altura. E o preço era concreto — no
   tablete de 8,7" do posto do R1 (~400 px úteis em paisagem) um cartão de
   banho ficava com 342 px e cabia UM dos seis.

   Quem precisa de grande **pede**: a classe `.big` continua existindo e
   continua sendo maior que o piso. É o escape explícito que a decisão prevê,
   e é onde os comandos de exceção devem viver.

   MEDIDO antes desta mudança (fica como referência do que a régua velha
   corrigia): select/input 38–43 px, setpoint dos Kraft 42×70–90 px,
   checkbox 13×13 px, linha de tabela ~34 px. O piso de 40 px continua acima
   de todos eles — a régua não foi embora, só deixou de ser de luva.
   ============================================================ */
@media (pointer: coarse) {
  /* --- alvos de toque --- */
  button, input, select, textarea { min-height: 40px; }
  input[type="checkbox"], input[type="radio"] { width: 26px; height: 26px; min-height: 0; }
  /* A caixa em si nunca chega a tamanho de dedo sem ficar grotesca — mas ela
     SEMPRE vive dentro de um <label>, e o rótulo inteiro alterna o valor.
     Então o alvo é o RÓTULO: é ele que precisa ter tamanho de dedo. */
  label:has(> input[type="checkbox"]), label:has(> input[type="radio"]) {
    min-height: 40px; display: flex; align-items: center; }
  /* campo numérico: largura suficiente para o valor E para o dedo. 90 px ainda
     comporta "500.0" com folga; os 120 px vinham da mesma régua de luva. */
  input[type="number"] { min-width: 90px; }
  /* linha de tabela clicável — o padding vinha dos 15 mm de luva */
  th, td { padding: 10px 10px; }
  /* espaçamento MÍNIMO entre alvos — encostados, o dedo erra. Este NÃO cai
     junto: com alvos menores, a folga entre eles é o que sobra para o dedo
     não pegar o vizinho. */
  .row { gap: 14px; } .subnav { gap: 12px; }
  .subnav button { padding: 10px 18px; }
  /* a barra de status é chrome, e o botão de kiosk é dedo: piso + folga.
     Era 68 px por causa do botão de 56 que morava dentro dela. */
  #statusbar { min-height: 48px; }
  #statusbar .sb-kbtn { min-height: 40px; min-width: 40px; padding: 8px 14px; }
  /* Organizar o Painel entra na mesma régua do resto. Com 40 px o chrome do
     card volta a caber em UMA linha no tablete — era a régua de luva que o
     quebrava em duas. */
  .pnl-chrome button, .pnl-chrome select { min-height: 40px; min-width: 40px; padding: 0 12px; }
  .pnl-chrome { gap: 8px; }
  .pnl-chip { min-height: 40px; padding: 8px 16px; }
  .pnl-chips { gap: 10px; }

  /* --- legibilidade a distância de braço (U6) --- */
  #topbar .tab .lbl { font-size: 12.5px; }
  #topbar .tab svg { width: 24px; height: 24px; }
  label { font-size: 13px; }
  table { font-size: 15px; }

}

/* O NOME da estação volta no mímico a partir de tablet: é como o operador sabe
   que banho é aquele, e escondê-lo a 768 px era efeito colateral do breakpoint
   de celular. Cabe por elipse na célula estreita e o `title` traz o completo.
   Em CELULAR fica escondido de propósito: a 375 px a célula tem ~28 px. */
@media (pointer: coarse) and (min-width: 700px) {
  .lm-pos .lm-nome { display: block; font-size: 12px; }
  .lm-pos .lm-num { font-size: 15px; }
  .lm-pos .lm-tb { font-size: 11.5px; }
}

/* Trilho mais largo onde há toque E espaço (iPad paisagem): o rótulo de
   12,5 px não cabe nos 96 px pensados para 9,5 px. */
@media (pointer: coarse) and (min-width: 821px) {
  :root { --rail-w: 122px; }
}

/* U14: `dvh` desconta a barra de endereço do navegador do tablet, que
   `vh` ignora — em retrato o topo ficava apertado sob o #statusbar sticky. */
@supports (height: 100dvh) {
  #login { margin: 10dvh auto; }
  #gr-chart { height: clamp(320px, 48dvh, 640px); }
  canvas { height: clamp(170px, 24dvh, 330px); }
  #log { height: clamp(150px, 26dvh, 420px); }
  .pnl-alist { max-height: clamp(240px, 32dvh, 560px); }
}

/* ---- Gráficos (ECharts): controles compactos, cards e detalhe do tambor ---- */
.gr-ctl { display:flex; flex-wrap:wrap; align-items:center; gap:8px 10px; margin-bottom:8px; }
/* A aba Escala (28/09/2026): o quadro de Resultados embutido, ocupando a altura
   útil da tela; rola dentro de si, nunca o corpo. */
.escala-quadro { display:block; width:100%; height:calc(100dvh - 140px); min-height:24rem;
  border:1px solid var(--divider); border-radius:var(--r-sm); background:var(--page); }
.gr-pills { display:inline-flex; gap:4px; background:var(--page); border:1px solid var(--divider);
  border-radius:var(--pill); padding:3px; }
.gr-pills button { border:none; background:transparent; color:var(--ink-500); font-weight:600;
  font-size:13px; padding:6px 12px; border-radius:var(--pill); cursor:pointer; }
.gr-pills button.active { background:var(--surface); color:var(--ink-800); box-shadow:var(--shadow-sm); }
/* `flex-wrap` obrigatorio: no telefone (375 px) com alvo de toque de 44 px os
   quatro controles do dia nao cabem numa linha e o "Hoje" ficava CORTADO na
   borda, inalcancavel. Quebrar e melhor que esconder. */
.gr-dia { display:inline-flex; flex-wrap:wrap; align-items:center; gap:4px; }
.gr-dia input[type="date"] { width:auto; }
.gr-esc { display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap; }
.gr-esc label { margin:0; }
.gr-esc input { width:76px; text-align:center; font-variant-numeric:tabular-nums; }
.gr-esc input:disabled { opacity:.65; }
/* o par fundo–topo nunca se separa na quebra (no telefone o "–" sobrava solto) */
.gr-esc-par { display:inline-flex; align-items:center; gap:6px; flex-wrap:nowrap; }
/* NAVEGACAO NO TEMPO: passos nomeados, campo de data/hora sempre a vista. A
   barra deslizante saiu (mapeava semanas em ~300 px: 1 px valia ~40 min, e
   "andar 1 hora" era impossivel de acertar). `flex-wrap` porque no telefone os
   sete alvos de 44 px nao cabem numa linha, e quebrar e melhor que encolher. */
.gr-nav { display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-bottom:6px; }
.gr-nav input[type="datetime-local"] { width:auto; flex:0 1 auto;
  font-variant-numeric:tabular-nums; }
.gr-nav-sep { flex:1 1 0; min-width:0; }
.gr-leitura { display:flex; flex-wrap:wrap; gap:4px 18px; margin-top:8px; font-size:13px;
  font-variant-numeric:tabular-nums; }
.gr-leitura b { font-family:var(--font-mono); }
.gr-cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:10px; margin-bottom:10px; }
.gr-card { background:var(--surface); border:1px solid var(--divider); border-radius:var(--r-lg);
  padding:10px 14px; }
.gr-card .gc-rot { font-size:11px; text-transform:uppercase; letter-spacing:.4px; color:var(--ink-500); }
.gr-card .gc-val { font-family:var(--font-mono); font-size:24px; font-weight:600;
  font-variant-numeric:tabular-nums; line-height:1.25; }
.gr-card .gc-val u { font-size:12px; color:var(--ink-500); text-decoration:none; font-family:var(--font-sans); }
.gr-card .gc-sub { font-size:12px; color:var(--ink-500); }
/* pendencia de CONFIGURACAO (falta o corte de corrente): nao e alarme de planta,
   entao nao usa o vermelho de alarme — usa a borda ambar de "decidir algo". */
.gr-card.falta { border-left:4px solid #b45309; }
body[data-theme="dark"] .gr-card.falta { border-left-color:#fbbf24; }
.gr-card.falta .gc-val { color:var(--ink-500); }
/* UM GRAFICO POR SERIE (modo "Separados"). Seis curvas no mesmo eixo dizem se a
   LINHA trabalhou; separadas dizem QUAL banho parou. minmax(min(100%,340px),1fr)
   e a forma que nunca estoura a largura: no telefone vira 1 coluna, no tablete
   de 8,7" duas, no desktop tres ou quatro — sem largura fixa em px (CLAUDE.md 3). */
.gr-multi { display:grid; gap:10px;
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 340px), 1fr)); }
.gr-um { background:var(--surface); border:1px solid var(--divider);
  border-radius:var(--r-lg); padding:8px 10px 4px; min-width:0; }
.gr-um-top { display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 10px;
  margin-bottom:2px; }
.gr-um-nome { font-size:13px; font-weight:600; display:inline-flex;
  align-items:center; gap:6px; min-width:0; }
.gr-um-nome i { width:10px; height:10px; border-radius:3px; flex:0 0 auto; }
.gr-um-top .who b { font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
/* altura por dvh: a barra do navegador do tablete muda a altura viva (CLAUDE.md 3) */
.gr-um-chart { height:200px; touch-action:pan-y; }
@supports (height: 100dvh) { .gr-um-chart { height:clamp(160px, 26dvh, 260px); } }
.gr-tambor { background:var(--surface); border:1px solid var(--divider); border-left:4px solid var(--red-500);
  border-radius:var(--r-lg); padding:10px 14px; margin-top:8px; font-size:14px; }
.gr-tambor h4 { margin:0 0 4px; font-size:15px; }
.gr-tambor .gt-linha { display:flex; flex-wrap:wrap; gap:4px 18px; }
.gr-tambor .gt-linha span b { font-family:var(--font-mono); font-variant-numeric:tabular-nums; }
body[data-theme="dark"] .gr-pills { background:#242930; border-color:#4A525D; }
body[data-theme="dark"] .gr-pills button.active { background:#3D444E; }
@media (max-width: 820px) {
  .gr-card .gc-val { font-size:20px; }
  /* no telefone o separador nao empurra nada: os botoes ja quebram em linhas */
  .gr-nav-sep { display:none; }
}
@media (pointer: coarse) {
  .gr-pills button { padding:10px 14px; min-height:44px; }
  /* alvo de toque: 44 px de altura tambem nos passos de tempo, que sao os
     botoes mais apertados da tela (sete numa faixa) */
  .gr-nav .ghost { min-width:52px; min-height:44px; }
  .gr-esc button { min-height:44px; }
}
/* cursores Início/Fim (vista Estufas): overlay DOM sobre o ECharts — arrastar
   não re-renderiza o gráfico (60 fps até em celular fraco); o chart só é a
   régua de conversão tempo↔pixel (convertToPixel/convertFromPixel). */
#gr-chart { position:relative; }
.gr-cursor { position:absolute; top:10px; bottom:28px; width:0;
  border-left:2px dashed var(--ink-800); cursor:ew-resize; touch-action:none; z-index:5; }
.gr-cursor::before { content:""; position:absolute; left:-16px; right:-16px; top:0; bottom:0; }
.gr-cursor span { position:absolute; top:-8px; left:50%; transform:translateX(-50%);
  background:var(--ink-800); color:var(--surface); font-size:11px; font-weight:700;
  padding:2px 8px; border-radius:var(--pill); user-select:none; white-space:nowrap; }

/* ============================================================
   TELA DO OPERADOR (tablet 11", paisagem) — UMA tela fixa, sem rolagem.
   Grade de 4 linhas: topo (linha/turno/hora) · mímico (cresce) · leituras
   (A/V por banho + temperatura) · rodapé (lote + paradas + carga). O painel
   de carga é modal e abre sozinho no depósito na posição de carga. */
#opf { position: fixed; inset: 0; z-index: 55; background: var(--page);
  display: grid; grid-template-rows: auto 1fr auto auto; gap: 10px;
  padding: 10px 14px; height: 100dvh; overflow: hidden; }
body.opf-on { overflow: hidden; }
#opf-topo { display: flex; align-items: center; gap: 10px; min-width: 0; }
#opf-linha { font-size: 20px; white-space: nowrap; }
#opf-turno { font-weight: 600; color: var(--ink-500); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
#opf-hora { font-family: var(--font-mono); font-weight: 700; font-size: 20px; }
#opf-topo button { padding: 8px 16px; flex: 0 0 auto; }
#opf-mimico { background: var(--surface); border: 1px solid var(--divider);
  border-radius: var(--r-lg); padding: 12px 14px 10px; display: flex;
  align-items: center; min-height: 0; overflow: hidden; }
#opf-faixa { width: 100%; }
#opf-faixa .lm-strip { margin-top: 58px; }
#opf-faixa .lm-pos .lm-num { font-size: 17px; }
#opf-faixa .lm-pos .lm-nome { font-size: 12px; display: block; }
#opf-faixa .lm-pos .lm-tb { font-size: 12px; }
#opf-faixa .lm-cart { font-size: 30px; top: -48px; width: 34px; }
#opf-faixa .lm-cart.baixo { top: -22px; }
/* quem está no carrinho: cliente + OS em pílula presa ao ícone do carrinho */
#opf-cart-tag { position: absolute; left: 50%; transform: translateX(-50%);
  bottom: 100%; margin-bottom: 2px; background: var(--ocupado);
  color: var(--ocupado-tinta); font-size: 13px; font-weight: 700;
  padding: 2px 10px; border-radius: var(--pill); white-space: nowrap; }
#opf-leituras { display: flex; gap: 8px; overflow-x: auto; }
.opf-tile { flex: 1; min-width: 118px; background: var(--surface);
  border: 1px solid var(--divider); border-top: 4px solid var(--divider);
  border-radius: var(--r-md); padding: 8px 10px; text-align: center; }
.opf-tile .k { font-size: 12px; font-weight: 600; color: var(--ink-500);
  text-transform: uppercase; letter-spacing: .03em; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.opf-tile .v { font-size: 30px; font-weight: 800; line-height: 1.15;
  font-variant-numeric: tabular-nums; }
.opf-tile .v u { text-decoration: none; font-size: 14px; font-weight: 600; margin-left: 2px; }
.opf-tile .s { font-size: 12px; color: var(--ink-500); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
.opf-tile.temp { border-top-color: var(--warning); }
#opf-rodape { display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--divider);
  border-radius: var(--r-lg); padding: 10px 14px; min-height: 76px; }
#opf-lote { font-size: 17px; min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
/* ⚠ `width: auto` é obrigatório: o `.big` global tem `width: 100%` (ele nasceu
   para botão em coluna), e aqui cada botão do rodapé pedia a LARGURA INTEIRA —
   o texto do lote colapsava em 0 px e os botões empilhavam. Estava assim antes
   de existir o "Fim de turno"; com três botões ficou impossível de ignorar. */
#opf-rodape button.big { min-height: 44px; flex: 0 0 auto; width: auto; }
#opf-paradas { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
#opf-paradas select { width: auto; min-width: 170px; }
/* PAINEL DA POSIÇÃO DE CARGA: alvo de dedo com luva (>=64px por peça).
   O que ENTRA vem em corpo grande porque é a decisão do operador e ele lê de
   longe, em pé, com luva — o pedido foi "MUITO CLARO qual peça ele está
   carregando". O que SAIU é informativo e fica menor, atrás de uma divisória. */
#opf-carga .modal, #opf-fila .modal { max-width: 880px; }
.opfc-bloco { border-top: 1.5px solid var(--divider); padding-top: 10px; margin-top: 10px; }
.opfc-bloco:first-child { border-top: 0; margin-top: 0; padding-top: 0; }
.opfc-rot { font-size: 12px; font-weight: 700; letter-spacing: .08em;
  color: var(--ink-500); text-transform: uppercase; margin-bottom: 4px; }
.opfc-grande { font-size: 30px; font-weight: 800; line-height: 1.15; }
.opfc-med { font-size: 20px; font-weight: 700; margin-top: 2px; }
#opfc-saiu .opfc-grande { font-size: 17px; font-weight: 600; }
#opfc-acoes { gap: 10px; margin-top: 12px; }
#opfc-acoes button.big { flex: 1 1 0; min-width: 0; }
/* opções da fila em botões grandes — substituiu o prompt() com lista numerada,
   que era o pior controle da tela (escolher carga digitando um número) */
#opfc-opcoes { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px; max-height: 40dvh; overflow: auto; }
.opfc-opcao { display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  text-align: left; border-radius: var(--r-md); border: 1.5px solid var(--divider);
  background: var(--surface); box-shadow: none; padding: 10px 12px; min-height: 48px; }
.opfc-opcao b { font-size: 17px; }
.opfc-opcao span { font-weight: 600; font-size: 14px; }
.opfc-opcao small { font-weight: 500; font-size: 12px; color: var(--ink-500); }
.opfc-ok { font-weight: 700; color: var(--ok-txt); }
/* tambor rodando sem carga apontada: ÂMBAR (atenção), nunca o vermelho da
   marca — não é falha da máquina, é pendência de quem opera (ISA-101/U9) */
#opf-semcarga { cursor: pointer; }
#opff-lista { max-height: 52dvh; overflow: auto; }

/* ORDENS DE SERVIÇO DA LINHA (Fase 1, 08/09/2026).
   Tabela densa e não cartões: aqui o operador COMPARA linhas (qual OS cabe no
   tambor, qual está atrasada), e comparar é o que uma tabela faz melhor que
   qualquer outra forma. O §3 exige que ela role dentro do próprio contêiner —
   são 8 colunas contra os ~800 px do tablete —, e é o que `.tabela-scroll`
   (aplicado no boot a toda <table>) já garante. */
#opf-os .modal { max-width: 1180px; }
#opfo-lista { margin-top: 4px; }
.opfo-abas { display: flex; gap: 6px; flex-wrap: wrap; }
.opfo-abas button { min-height: 40px; width: auto; padding: 4px 12px;
  font-size: 13px; font-weight: 700; box-shadow: none;
  background: var(--surface); color: var(--ink-500);
  border: 1.5px solid var(--divider); }
.opfo-abas button.sel { border-color: var(--success); outline: 2px solid var(--success);
  color: var(--ink-800);
  background: color-mix(in srgb, var(--success) 10%, var(--surface)); }
.opfo-tab { width: 100%; font-size: 13px; }
.opfo-tab th.n, .opfo-tab td.n { text-align: right; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
/* o item é a coluna elástica: nome de parafuso passa de 40 caracteres e não
   pode empurrar os números para fora da tela */
.opfo-tab td:nth-child(2) { max-width: 34ch; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }
.opfo-tab td .who { display: block; font-size: 11px; }
/* AMPERES é o número que o operador vai executar: é o único em corpo maior,
   com a conta inteira no `title` — número sem procedência é número em que
   ninguém confia, e este vira corrente no tambor. */
.opfo-tab td.forte { font-size: 17px; font-weight: 800; }
.opfo-tab td.forte u { text-decoration: none; font-size: 11px; margin-left: 1px; }
.opfo-tab tr.atrasado td:first-child { border-left: 3px solid var(--red-500); }

/* PAINEL DOS RETIFICADORES DO R1 — uma LINHA por banho (08/09/2026).
   ⚠ Era um CARTÃO por banho, e o cartão não servia: somando padding, leitura,
   estado, o par manual/auto, os 4 modos em 2×2, o campo (56 px pela régua de
   luva) e as ações, dava **342 px**. O posto do R1 tem **~400 px úteis** em
   paisagem — cabia UM banho, e o operador rolava para ver os outros seis.
   Pedido do Juan: "a tela de operação das 6 posições ficar em uma tela só".
   Em linha o mesmo banho ocupa ~52 px: 6 × 52 = 312 px, e os seis cabem.

   A ordem das colunas é a ordem da pergunta que o operador faz: QUAL banho →
   o que ele está dando AGORA → está tudo bem? → COMO comanda → com QUE número
   → quem liga → e as ações. O que encolhe primeiro é o texto de estado, que é
   o único redundante: a cor da borda já diz o mesmo.

   O estado migrou da borda de CIMA para a ESQUERDA — numa linha é a borda
   longa, e é ela que o olho varre ao descer a lista. */
#opf-ret .modal { max-width: 1100px; }
#opfr-lista { display: grid; gap: 6px; margin-top: 10px;
  max-height: calc(100dvh - 170px); overflow: auto; }

/* Colunas de largura FIXA, não `auto`. Cada linha é um grid independente, então
   com `auto` a largura saía do CONTEÚDO de cada uma — e a linha em modo receita
   (que tem um <select> a mais) empurrava as suas colunas para fora do prumo das
   outras cinco. Numa lista isso é ruído: o olho desce a coluna, não a linha, e
   um botão que muda de lugar entre linhas é onde o dedo erra.
   (`subgrid` resolveria sem número mágico, mas é suporte recente demais para a
   frota — número fixo é o que se sustenta aqui.) */
.opfr-banho { display: grid; align-items: center; gap: 10px;
  grid-template-columns:
    96px                  /* banho + posição */
    132px                 /* leitura ao vivo */
    minmax(0, 1fr)        /* estado — encolhe primeiro, some por último */
    202px                 /* os 4 modos */
    204px                 /* valor (no modo receita cabe o select junto) */
    92px                  /* quem liga */
    166px;                /* ações */
  min-height: 52px; padding: 4px 10px;
  border: 1px solid var(--divider); border-left: 4px solid var(--divider);
  border-radius: var(--r-md); background: var(--surface); }
.opfr-banho.ligado { border-left-color: var(--success); }
.opfr-banho.alarme { border-left-color: var(--red-500); }
.opfr-banho.local  { border-left-color: var(--warning); }

.opfr-top { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.opfr-top b { font-size: 15px; white-space: nowrap; }
.opfr-top .who { font-size: 11px; white-space: nowrap; }

/* o único número grande que sobra — é o que o operador lê de relance */
.opfr-vivo { font-size: 20px; font-weight: 800; line-height: 1.1; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.opfr-vivo u { text-decoration: none; font-size: 12px; font-weight: 600; margin-left: 1px; }
.opfr-vivo .sep { color: var(--ink-500); font-weight: 600; margin: 0 5px; }

/* Estado E retorno de ação dividem a MESMA célula, um de cada vez. Na linha
   não há altura para os dois, e eles nunca importam ao mesmo tempo: quando o
   operador acabou de tocar, o que ele quer ler é "✔ guardado". Passados 8 s
   volta o estado. Era assim que o cartão gastava mais 21 px. */
.opfr-est { font-size: 12px; font-weight: 600; color: var(--ink-500);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.opfr-est.erro { color: var(--alarme-txt); }
.opfr-est.ok { color: var(--ok-txt); }

/* Os 4 modos em UMA fila: o 2×2 existia por falta de largura no cartão, e na
   linha largura é justamente o que sobra. */
.opfr-modos { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; }
.opfr-modos button { min-height: 40px; width: auto; padding: 4px 4px;
  font-size: 12px; font-weight: 700; box-shadow: none;
  background: var(--surface); color: var(--ink-500);
  border: 1.5px solid var(--divider); }
.opfr-modos button.sel { border-color: var(--success); outline: 2px solid var(--success);
  color: var(--ink-800);
  background: color-mix(in srgb, var(--success) 10%, var(--surface)); }

/* O VALOR é botão, não <input>. Dois motivos: o campo carregava o piso de
   altura da régua de toque, e tocar nele abria o teclado do sistema por cima
   da tela inteira — que é exatamente o que o Juan pediu para acabar. */
.opfr-val { display: flex; align-items: center; gap: 6px; }
.opfr-val .bt-num { min-height: 40px; width: auto; min-width: 76px; padding: 4px 10px;
  font-size: 16px; font-weight: 800; text-align: right; box-shadow: none;
  font-variant-numeric: tabular-nums;
  background: var(--surface); color: var(--ink-800);
  border: 1.5px solid var(--divider); }
.opfr-val .bt-num.vazio { color: var(--ink-500); font-weight: 600; font-size: 13px; }
.opfr-val .un { font-weight: 700; color: var(--ink-500); font-size: 12px; white-space: nowrap; }
/* o nome da receita é longo e não pode empurrar a coluna: encolhe dentro dela */
.opfr-val select { min-height: 40px; font-size: 12px; font-weight: 600;
  flex: 1 1 auto; min-width: 0; }

/* QUEM LIGA vira um interruptor único: é binário, e o par de botões custava a
   largura de dois alvos para dizer o que um diz. Âmbar mantém a separação de
   "quem liga" × "com o quê" (verde) que o par tinha. */
.opfr-auto { min-height: 40px; width: auto; padding: 4px 9px; box-shadow: none;
  font-size: 11px; font-weight: 800; line-height: 1.15; white-space: nowrap;
  background: var(--surface); color: var(--ink-500);
  border: 1.5px solid var(--divider); }
.opfr-auto.on { border-color: var(--warning); outline: 2px solid var(--warning);
  color: var(--ink-800);
  background: color-mix(in srgb, var(--warning) 12%, var(--surface)); }

.opfr-acoes { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.opfr-acoes button { min-height: 40px; width: auto; min-width: 0; }

/* ---------------------------------------------- teclado numérico ancorado
   Reusa a gramática do teclado do Laboratório (`modulos/laboratorio/casca.css`
   `.lb-tecl`: 3 colunas, dígitos + vírgula + apagar) — §1, não reinventar.
   Muda o TAMANHO e a ÂNCORA: lá é passo a passo em tela cheia, aqui tem de
   caber ao lado da linha sem cobrir os outros cinco banhos.
   ~168 px de largura: o operador continua vendo o banho que está editando. */
/* `fixed`, e não `absolute`: a lista dos banhos rola, e um popover ancorado
   dentro dela rolaria junto, descolando do botão que o abriu. Com `fixed` as
   coordenadas saem de `getBoundingClientRect()` e valem para a janela. */
.opfr-tecl { position: fixed; z-index: 30; width: 168px; padding: 8px;
  border: 1.5px solid var(--divider); border-radius: var(--r-md);
  background: var(--surface); box-shadow: var(--shadow-md); }
.opfr-tecl .visor { display: flex; align-items: baseline; justify-content: flex-end;
  gap: 4px; padding: 4px 6px 6px; font-size: 22px; font-weight: 800;
  font-variant-numeric: tabular-nums; }
.opfr-tecl .visor .un { font-size: 12px; font-weight: 700; color: var(--ink-500); }
.opfr-tecl .faixa { font-size: 11px; font-weight: 600; color: var(--ink-500);
  text-align: right; padding: 0 6px 6px; }
.opfr-tecl .faixa.mau { color: var(--alarme-txt); }
.opfr-tecl .grade { display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; }
.opfr-tecl .grade button { min-height: 40px; width: auto; padding: 0;
  font-size: 17px; font-weight: 700; box-shadow: none;
  font-family: var(--font-mono, inherit);
  background: var(--page); color: var(--ink-800); border: 0; }
.opfr-tecl .grade button.a { background: var(--divider-soft); color: var(--ink-500);
  font-size: 11px; font-weight: 700; font-family: inherit; }
.opfr-tecl .pe { display: flex; gap: 4px; margin-top: 6px; }
.opfr-tecl .pe button { flex: 1 1 0; min-height: 40px; width: auto; font-size: 13px; }

/* ------------------------------------------------------------- CELULAR
   A linha empilha em dois andares DENTRO do mesmo bloco, em vez de virar
   rolagem lateral: o §3 proíbe scroll horizontal no corpo, e uma linha de 7
   colunas a 390 px não existe. Andar de cima = quem/quanto/como está;
   andar de baixo = o que dá para fazer. */
@media (max-width: 700px) {
  .opfr-banho { grid-template-columns: minmax(0, 1fr) auto; gap: 4px 8px;
    padding: 8px 10px; }
  .opfr-top   { grid-column: 1; }
  .opfr-vivo  { grid-column: 2; text-align: right; }
  .opfr-est   { grid-column: 1 / -1; }
  .opfr-modos { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(4, 1fr); }
  .opfr-val   { grid-column: 1; }
  .opfr-auto  { grid-column: 2; justify-self: end; }
  .opfr-acoes { grid-column: 1 / -1; }
  .opfr-acoes button { flex: 1 1 0; }
  .opfr-val select { max-width: none; flex: 1 1 auto; }
  /* ancorado ao rodapé, mas ainda sem cobrir a linha em edição */
  .opfr-tecl { position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(10px + env(safe-area-inset-bottom)); }
}

/* TABLET BAIXO (~400 px de altura útil em paisagem, que é o posto do R1).
   Agora só afina o que é folga: o alvo de 40 px NÃO encolhe, porque foi ele
   que sobrou depois de a régua de luva sair. */
@media (max-height: 620px) {
  #opfr-lista { gap: 4px; }
  .opfr-banho { min-height: 48px; padding: 3px 8px; gap: 8px; }
  .opfr-vivo { font-size: 18px; }
  .opfr-modos button { padding: 4px 5px; font-size: 11px; }
}

#opf-modal .modal { max-width: 860px; }
#opfm-pecas { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 8px; max-height: 38dvh; overflow: auto; }
.opfm-peca { display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  text-align: left; border-radius: var(--r-md); border: 1.5px solid var(--divider);
  background: var(--surface); box-shadow: none; padding: 10px 12px; min-height: 48px; }
.opfm-peca b { font-size: 16px; }
.opfm-peca span { font-weight: 600; font-size: 13px; }
.opfm-peca small { font-weight: 500; font-size: 12px; color: var(--ink-500); }
.opfm-peca.sel { border-color: var(--success); outline: 2px solid var(--success);
  background: color-mix(in srgb, var(--success) 10%, var(--surface)); }

/* Banho TRAVADO na IHM (botão PB.Zn, lido do CLP em "B"."PBZ<T>_<n>"):
   a célula fica visivelmente fora de uso em todos os mímicos. */
.lm-pos.travada { opacity: .55;
  background: repeating-linear-gradient(45deg, var(--page), var(--page) 7px,
    var(--divider) 7px, var(--divider) 9px); }
.lm-pos.travada .lm-nome::after { content: " · TRAVADO"; font-weight: 700; }
/* ⚡ SEM CORRENTE (2026-08-11): o tambor está no banho e o retificador NÃO vai
   ser comandado — sem carga apontada, sem área, sem receita utilizável. A peça
   sai sem camada, e até aqui isso era SILENCIOSO (a malha é segura por padrão:
   simplesmente não liga). É alarme de verdade, então usa o vermelho — que nesta
   tela é exclusivo de alarme justamente para continuar significando algo.
   O pulso é lento de propósito: chama o olho de longe sem virar estrobo numa
   tela que o operador olha o turno inteiro. */
.lm-pos.sem-corrente { border-color: var(--red-500);
  background: color-mix(in srgb, var(--red-500) 12%, var(--page));
  animation: lm-semcorrente 2.4s ease-in-out infinite; }
.lm-pos.sem-corrente .lm-tb { background: var(--red-500); }
@keyframes lm-semcorrente {
  50% { background: color-mix(in srgb, var(--red-500) 26%, var(--page)); } }
@media (prefers-reduced-motion: reduce) {
  .lm-pos.sem-corrente { animation: none; }
}
/* estação que ACEITA toque para apontar a carga (só as telas do operador) */
.lm-pos.apontavel { cursor: pointer; }
.lm-pos.apontavel.ocupada:active { transform: scale(.97); }

/* Tambor cuja ESTAÇÃO veio de manobra MANUAL do operador (sem comando do CLP,
   ver origem='manual' em ocupacao_estacao). Borda tracejada: o cronômetro é
   real, o que é incerto é como a estação foi determinada — diferente de
   TRAVADO, que é a estação estar fora de uso. */
.lm-pos.manual { border-style: dashed; }

/* ==================================================================
   BANCADA DE CADASTRO (.wb) — Configurações → Dispositivos / Elementos
   ==================================================================
   O formulário antigo era um card fixo NO TOPO e a lista vinha abaixo.
   Clicar "Editar" no último item disparava window.scrollTo(0,0) e a tela
   pulava para longe do que se estava olhando — em tablet, o item clicado
   sumia da vista. Aqui o editor abre AO LADO do item (tela larga) ou logo
   ABAIXO dele (tablet/celular), e o item fica destacado. Nunca há salto.

   O editor é UM nó só, movido no DOM pelo JS conforme a largura: duas
   cópias do mesmo formulário divergiriam no primeiro ajuste. */
.wb { display: grid; grid-template-columns: 1fr; gap: 14px; align-items: start; }
@media (min-width: 1080px) {
  /* Só divide em duas colunas quando há algo aberto (`.wb-aberto`, posta pelo
     JS). Sem isso a lista ficava espremida em 54% com meia tela vazia ao lado —
     e a lista é o que se olha 90% do tempo. */
  .wb.wb-aberto { grid-template-columns: minmax(0, 1fr) minmax(430px, 46%); }
  /* o editor acompanha a rolagem: em lista longa, a pessoa continua vendo o
     que está editando enquanto procura o próximo item */
  .wb-editor { position: sticky; top: 12px; max-height: calc(100vh - 24px); overflow: auto; }
}
.wb-lista { min-width: 0; }
.wb-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  justify-content: space-between; }
.wb-head h3 { margin: 0; }
.wb-filtros { display: flex; gap: 10px; flex-wrap: wrap; margin: 12px 0 6px; }
.wb-filtros > * { flex: 1; min-width: 150px; }
.wb-faixa { display: flex; gap: 12px; flex-wrap: wrap; align-items: center;
  background: var(--page); border-radius: var(--r-lg); padding: 10px 12px; margin: 10px 0; }
.wb-faixa > div:first-child { flex: 1; min-width: 220px; }
.wb-faixa-acoes { display: flex; gap: 8px; flex-wrap: wrap; }
.wb-itens { margin-top: 8px; }
.wb-vazio { padding: 18px 4px; }
.wb-grupo { margin-bottom: 14px; }
.wb-grupo-cab { font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-500); padding: 6px 2px;
  border-bottom: 1px solid var(--divider); display: flex; gap: 10px;
  align-items: baseline; flex-wrap: wrap; }
.wb-grupo-cab .who { text-transform: none; letter-spacing: 0; font-weight: 500; }

.wb-item { display: flex; gap: 12px; align-items: center; justify-content: space-between;
  padding: 10px 12px; border: 1px solid transparent; border-bottom-color: var(--divider-soft);
  border-radius: var(--r-md); cursor: pointer; min-height: 40px; }
.wb-item.sel { border-color: var(--brand, var(--red-500)); background: var(--red-wash); }
.wb-item.off { opacity: .55; }
.wb-item:active { background: var(--divider-soft); }
.wb-item-l { min-width: 0; flex: 1; }
.wb-item-nome { font-weight: 700; }
.wb-item-sub { font-size: 12px; }
.wb-item-r { text-align: right; flex: 0 0 auto; }
.wb-item-val { font-weight: 700; font-variant-numeric: tabular-nums; }
.wb-item-val.alarme { color: var(--red-500); }
/* selo de problema na linha: forma + cor, nunca só cor (ISA-101) */
.wb-flag { display: inline-block; min-width: 1.3em; text-align: center; margin-right: 6px;
  font-weight: 900; }
.wb-flag.erro { color: var(--red-500); }
.wb-flag.aviso { color: var(--aviso-txt, #B45309); }
.wb-flag.info { color: var(--ink-500); }
@media (hover: hover) and (pointer: fine) {
  .wb-item:hover { background: var(--divider-soft); }
  .wb-item.sel:hover { background: var(--red-wash); }
}

/* ------------------------------------------------------------ o editor */
.wb-editor { background: var(--surface); border: 1px solid var(--divider);
  border-radius: var(--r-xl); padding: 16px 18px; }
.ed-cab { display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: 8px; }
.ed-cab h3 { margin: 0; font-size: 19px; }
.ed-sec { border-top: 1px solid var(--divider); padding-top: 10px; margin-top: 12px; }
.ed-sec-tit { font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-500); margin-bottom: 4px; }
.ed-grid { display: flex; gap: 12px; flex-wrap: wrap; }
.ed-grid > * { flex: 1; min-width: 150px; }
.ed-campo label { margin-top: 6px; }
.ed-campo .who { font-size: 11.5px; line-height: 1.4; margin-top: 4px; }
.ed-ajuda { background: var(--page); border-radius: var(--r-md); padding: 10px 12px;
  font-size: 13px; color: var(--ink-600); margin: 8px 0; }
.ed-check { display: flex; align-items: center; gap: 10px; text-transform: none;
  font-size: 14px; font-weight: 500; color: var(--ink-800); }
.ed-check .who { font-size: 11.5px; }
.ed-acoes { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 16px; }
.ed-erro { color: var(--red-500); font-weight: 600; margin-top: 8px; }
.ed-alerta { background: var(--aviso-bg, #FEF3C7); color: var(--aviso-txt, #B45309);
  border-radius: var(--r-md); padding: 10px 12px; font-size: 13px; margin: 8px 0; }
.ed-probs { margin: 8px 0; }
.ed-prob { border-left: 3px solid var(--ink-500); padding: 6px 10px; font-size: 13px;
  background: var(--page); border-radius: 0 var(--r-md) var(--r-md) 0; margin-bottom: 6px; }
.ed-prob.erro { border-left-color: var(--red-500); }
.ed-prob.aviso { border-left-color: var(--aviso-txt, #B45309); }

/* ---------------- o construtor de endereço (hr:1@74 desmontado) --------
   O pedido que originou esta tela: "74 é o endereço modbus […] e o 1 que
   tem antes deve estar melhor explicado o que é". Cada parte ganhou campo,
   rótulo e uma linha dizendo para que serve; embaixo, o endereço montado e
   a frase por extenso. */
.addr { background: var(--page); border: 1px solid var(--divider);
  border-radius: var(--r-lg); padding: 12px; margin-top: 8px; }
.addr-tit { font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: var(--ink-500); margin-bottom: 6px; }
.addr-grid { display: flex; gap: 12px; flex-wrap: wrap; }
.addr-grid > * { flex: 1; min-width: 160px; }
.addr-frase { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--divider);
  font-size: 13px; color: var(--ink-600); }
.addr-frase code { font-family: var(--font-mono); font-weight: 700; color: var(--ink-800);
  background: var(--surface); border: 1px solid var(--divider); border-radius: 6px;
  padding: 2px 7px; margin-right: 8px; display: inline-block; }

/* --------------------- faixa de problemas de cadastro (topo da aba) ---- */
.diag { border: 1px solid var(--divider); border-radius: var(--r-xl);
  background: var(--surface); padding: 12px 16px; margin-bottom: 14px; }
.diag.erro { border-left: 5px solid var(--red-500); }
.diag.aviso { border-left: 5px solid var(--aviso-txt, #B45309); }
.diag.ok { border-left: 5px solid var(--success); }
.diag-cab { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
.diag-cab .who { flex-basis: 100%; font-size: 12px; }
.diag-selo { font-size: 12px; font-weight: 700; border-radius: 999px; padding: 2px 10px;
  background: var(--ink-100); color: var(--ink-800); }
.diag-selo.erro { background: var(--red-500); color: #fff; }
.diag-selo.aviso { background: var(--aviso-bg, #FEF3C7); color: var(--aviso-txt, #B45309); }
.diag-lista { list-style: none; margin: 10px 0 0; padding: 0; }
.diag-item { display: flex; gap: 10px; align-items: flex-start; padding: 8px 0;
  border-top: 1px solid var(--divider-soft); }
.diag-item > div { flex: 1; min-width: 0; }
.diag-item .who { font-size: 12px; }
.diag-ic { font-weight: 900; min-width: 1.2em; text-align: center; }
.diag-item.erro .diag-ic { color: var(--red-500); }
.diag-item.aviso .diag-ic { color: var(--aviso-txt, #B45309); }
.diag-item.info .diag-ic { color: var(--ink-500); }

/* Tela de toque: mesma régua de 40 px do resto (08/09/2026). */
@media (pointer: coarse) {
  .wb-item { min-height: 44px; padding: 10px; }
  .diag-item button { min-height: 40px; }
}

/* Gerenciador de grupos (painel lateral de Elementos). Grupo não é entidade —
   é o nome que os elementos carregam —, então aqui não há "criar" nem
   "excluir": só o que está EM USO, e renomear em massa. */
.wb-head-acoes { display: flex; gap: 8px; flex-wrap: wrap; }
.grp-item { display: flex; gap: 12px; align-items: flex-start;
  justify-content: space-between; padding: 10px 0;
  border-top: 1px solid var(--divider-soft); }
.grp-item:first-of-type { border-top: 0; }
.grp-item > div { flex: 1; min-width: 0; }
.grp-item .who { font-size: 12px; }
/* grupo cujo NOME o Painel reconhece (amperes_*, volts_*, temperaturas,
   estufas): marcado com barra verde, porque a diferença entre virar card de
   retificador e cair em "Outros" é invisível de outro jeito. */
.grp-item.especial { border-left: 3px solid var(--success); padding-left: 10px; }
.who.grupo-ok { color: var(--ok-txt); font-weight: 600; }
.ed-grupo-acoes { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.ed-grupo-acoes button { padding: 6px 12px; }
.ed-grupo-linha { display: flex; gap: 8px; align-items: stretch; }
.ed-grupo-linha select { flex: 1; min-width: 0; }
.ed-grupo-linha button { flex: 0 0 auto; padding: 0 12px; white-space: nowrap; }

/* ============== TELA DE OPERAÇÃO DO ROTATIVO (R1/R2/R3/PAR) ==============
   Reorganizada em 09/08/2026 a pedido do usuário: "a prioridade da tela
   deveria ser a carga que ele está colocando com a receita, carrinho e
   temperatura". Antes o card de EQUIPE (que se usa uma vez por turno) ficava
   no topo e empurrava a carga para baixo da dobra, e o botão largo de tela
   cheia comia uma faixa inteira. */
.opr-cab { display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  justify-content: space-between; padding: 10px 16px; }
.opr-cab h3 { margin: 0; }
.opr-cab-l, .opr-cab-r { display: flex; align-items: center; gap: 10px;
  flex-wrap: wrap; min-width: 0; }
.opr-cab-r .who { max-width: 42ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }

/* carga à esquerda, estado da máquina à direita — as duas coisas que o
   operador olha alternadamente o tempo todo */
.opr-op { display: grid; grid-template-columns: 1fr; gap: 14px; align-items: start; }
@media (min-width: 1100px) { .opr-op { grid-template-columns: 1fr 1fr; } }
.opr-op > .card { min-width: 0; }

/* uma linha = um tambor (ou um item da fila) */
.opr-tb { display: flex; gap: 10px; align-items: center; justify-content: space-between;
  padding: 10px 0; border-top: 1px solid var(--divider-soft); }
.opr-tb:first-of-type { border-top: 0; }
.opr-tb-l { flex: 1; min-width: 0; }
.opr-tb-nome { font-weight: 700; display: flex; gap: 8px; align-items: center;
  flex-wrap: wrap; }
.opr-tb-r { display: flex; gap: 6px; flex-wrap: wrap; flex: 0 0 auto; }
/* o PRÓXIMO da fila é o que vai entrar no tambor que chegar: destacado porque
   é a única linha da fila que muda alguma coisa nos próximos segundos */
.opr-tb.proxima { background: var(--page); border-radius: var(--r-md);
  padding: 10px; border-top: 0; box-shadow: inset 3px 0 0 var(--success); }
@media (pointer: coarse) {
  .opr-tb { padding: 10px 0; }
  .opr-tb-r button { min-height: 40px; min-width: 40px; }
}

/* ---- autocomplete de peça (montar carga do NanoGalv) --------------------
   O operador digita a peça como fala e a lista já mostra os dm². Tem de caber
   no tablet: alvo de toque na norma (≥56 px) e a lista não empurra o card — ela
   flutua, senão o botão INICIAR pula de lugar a cada tecla. */
/* `flex` vive AQUI, não no `style=` do elemento: inline vence a folha de estilo,
   e com ele inline a media query de tela estreita não conseguia dar a linha
   inteira ao campo (a lista ficava com metade da largura e o item quebrava em
   nove linhas). */
.fix-wrap { position: relative; flex: 3 1 24ch; min-width: 0; }
.fix-sug {
  position: absolute; z-index: 40; left: 0; right: 0; top: 100%; margin-top: 4px;
  background: var(--surface); border: 1px solid var(--divider);
  border-radius: var(--r-md); box-shadow: var(--shadow-md);
  max-height: clamp(220px, 42vh, 460px); overflow-y: auto;
}
.fix-item {
  display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
  padding: 10px 12px; min-height: 44px; cursor: pointer;
  border-top: 1px solid var(--divider-soft);
}
.fix-item:first-child { border-top: 0; }
.fix-item > b { flex: 1 1 14ch; min-width: 0; }
/* o número é o que ele veio buscar: monoespaçado, para bater coluna a coluna */
.fix-num { font-family: var(--font-mono); font-weight: 700; white-space: nowrap; }
.fix-tag { font-size: 12px; color: var(--ink-500); white-space: nowrap; }
.fix-tag.alerta { color: var(--aviso-txt); font-weight: 700; }
/* Peça escolhida antes da carga (27/09/2026): a dica vira o aviso do que falta
   e o campo de carga ganha a borda de atenção — nunca mais toque sem resposta. */
#op-fix-dica.fix-falta { color: var(--aviso-txt); font-weight: 600; }
#op-qtd.fix-falta { border-color: var(--aviso-txt); box-shadow: 0 0 0 2px var(--aviso-bg); }
/* sem área ainda: pesa menos na hierarquia, para ele não achar que já está pronto */
.fix-item.incompleto > b { font-weight: 400; color: var(--ink-600); }
.fix-item.sel { background: var(--red-wash); }
.fix-vazio { padding: 12px; color: var(--ink-500); font-size: 14px; }
@media (hover: hover) {
  .fix-item:hover { background: var(--red-wash); }
}
.fix-item:active { background: var(--red-wash); }
@media (pointer: coarse) {
  .fix-item { min-height: 40px; padding: 10px 12px; }
}

/* Lista de sugestão que ainda não corresponde ao que está escrito no campo:
   apaga enquanto recalcula. Nasceu da carga em kg — na janela entre digitar e a
   resposta chegar, a lista mostrava o peso NOVO vezes a peça VELHA, um número
   plausível e errado. O subtotal sai; o que fica é visivelmente "em trânsito". */
.fix-sug.desatualizada .fix-item { opacity: .45; }

/* ---- pendências para iniciar (montar carga do NanoGalv) -----------------
   Vermelho aqui é permitido pela ISA-101: não é estado, é AÇÃO pendente do
   operador — o botão não vai funcionar até ele preencher. */
.op-erros {
  margin-top: 14px; padding: 12px 16px; border-radius: var(--r-lg);
  background: var(--red-wash); border-left: 5px solid var(--red-500);
  color: var(--ink-800);
}
.op-erros ul { margin: 6px 0 0; padding-left: 20px; }
.op-erros li { margin: 3px 0; }
/* campo que faltou, marcado só depois de ele TENTAR iniciar */
input.invalido, select.invalido { border-color: var(--red-500); background: var(--red-wash); }
table.invalido { outline: 2px solid var(--red-500); outline-offset: 3px; border-radius: var(--r-md); }
/* dm² calculado das peças: travado, mas legível — é número de conferência,
   não campo apagado. O `disabled` padrão apaga demais para ler de longe. */
#op-area-manual:disabled { background: var(--divider-soft); color: var(--ink-800);
  font-weight: 700; opacity: 1; -webkit-text-fill-color: var(--ink-800); }
/* Toque sem os 300 ms de espera do navegador e sem zoom de duplo-toque: a lista
   de sugestão é tocada muitas vezes por carga e o atraso faz parecer que não
   pegou. `manipulation` mantém a rolagem (pan), que a lista precisa. */
.fix-item { touch-action: manipulation; }

/* ---- informar medida de bitola fora da norma ---------------------------
   O cliente manda o que quiser; o programa não inventa dimensão, então o
   operador mede. Alvos na norma de toque porque isto é digitado no tablet,
   de pé, muitas vezes com luva. */
/* A tag de "medir e informar: ..." é a instrução, não um selo curto: ela QUEBRA
   e toma a linha. Com o `nowrap` das outras tags ela era clipada na horizontal e
   o operador não conseguia ler o que precisava medir (medido em 375 px). */
.fix-item.medir > b { font-weight: 700; }
.fix-item.medir .fix-tag { white-space: normal; flex: 1 1 100%; }
/* nenhuma sugestão deve criar rolagem horizontal dentro da lista */
.fix-sug { overflow-x: hidden; }
.fix-medir { padding: 14px 16px; }
.fix-medir-campos { display: flex; gap: 10px; flex-wrap: wrap; }
.fix-medir-campos label { display: flex; flex-direction: column; gap: 4px;
  font-size: 12px; color: var(--ink-500); font-weight: 700;
  text-transform: uppercase; letter-spacing: .02em; flex: 1 1 12ch; min-width: 0; }
.fix-medir-campos input { min-width: 0; }
.fix-medir-acoes { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
@media (pointer: coarse) {
  .fix-medir-campos input { min-height: 40px; }
  .fix-medir-acoes button { min-height: 40px; }
}

/* Em tela estreita o campo de peça toma a linha inteira e o "+ Adicionar" desce.
   Sem isto os dois dividem a linha, a lista de sugestão herda metade da largura
   e "Porca 7/8" UNC sext. pesada 4.5735 dm²/kg" quebra em cinco linhas — ilegível
   de pé, no tablet, que é onde isto é usado. */
@media (max-width: 700px) {
  .fix-wrap { flex: 1 1 100%; }        /* linha inteira: o "+ Adicionar" desce */
}


/* ============================================================
   ALARMES — o que estava ativo agora mora na Planta (`static/planta.css`,
   02/10/2026). Ficam o HISTÓRICO filtrável (aba Histórico) e a grade do
   detalhe do retificador NanoGalv, que a Planta põe na faixa da NG.
   ============================================================ */
.alm-grid { grid-template-columns:repeat(auto-fit,minmax(130px,1fr)); }
.alm-ul { margin:4px 0 0; padding-left:18px; font-size:13px; }

/* --- histórico --- */
.alm-filtros { display:flex; flex-wrap:wrap; gap:6px; margin:8px 0; }
.alm-filtros button { font-size:12px; font-weight:600; padding:5px 12px;
  border-radius:var(--pill); border:1px solid var(--divider);
  background:var(--surface); color:var(--ink-500); box-shadow:none; }
.alm-filtros button[aria-pressed="true"] { background:var(--page); color:var(--ink-800);
  border-color:var(--ink-500); }
.alm-mono { font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  white-space:nowrap; }
.alm-hist-box { max-height:46vh; overflow:auto; border:1px solid var(--divider);
  border-radius:var(--r-lg); }
.alm-hist-box table { margin:0; }
.alm-hist-box thead th { position:sticky; top:0; z-index:1;
  background:var(--surface); }

/* A marca do topo virou link para a casca (`/`). `a.brand` precisa herdar o que
   `span.brand` tinha: sem isto o link nasce azul e sublinhado no meio da barra. */
a.brand { text-decoration: none; color: inherit; }
@media (hover: hover) and (pointer: fine) {
  a.brand:hover #brand-nome { text-decoration: underline; }
}
a.brand:focus-visible { outline: 2px solid var(--red-500); outline-offset: 2px;
  border-radius: var(--r-md); }
/* Caminho de volta no menu lateral: fica ACIMA das abas porque é para onde o
   dedo vai quando a pessoa quer SAIR desta tela, não navegar dentro dela. */
.dr-voltar { display: block; padding: 11px 16px; margin: 4px 0 8px;
  color: var(--ink-500); text-decoration: none; font-weight: 700; font-size: 14px;
  border-bottom: 1px solid var(--divider); }
@media (pointer: coarse) { .dr-voltar { min-height: 44px; } }

/* o nome no trilho do SCADA leva ao perfil do usuário (28/09/2026) */
a.who { color: inherit; text-decoration: none; }
@media (hover: hover) { a.who:hover { text-decoration: underline; } }
