﻿/* Ricentra Broadcast OS — sistema visual do cockpit.
   Sem @import de fontes externas de propósito: os sites vivem atrás de VPN e há
   locais técnicos sem saída para a Internet. Uma fonte que não carrega faz o cockpit
   abrir torto exatamente onde ele é mais preciso. */

:root {
    color-scheme: dark;
    --bg: #050a11;
    --panel: #0b1420;
    --panel2: #0f1b29;
    --line: #1d3348;
    --line2: #294863;
    --text: #f2f9ff;
    --ink: #e6f0f8;   /* texto sobre fundos ligeiramente mais claros (chips, barras) */
    /* Legibilidade: o cinzento-texto anterior (#7891a6) tinha pouco contraste sobre o
       fundo escuro. Este passa o rácio de contraste para nível legível. */
    --muted: #93a9bd;
    --cyan: #00c8ff;
    --green: #25e47b;
    --amber: #ffb020;
    --red: #ff435d;
    --purple: #9b5cff;
    --shadow: 0 24px 80px rgba(0,0,0,.32);
    --mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", Consolas, monospace;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    min-height: 100%;
    background: var(--bg);
    color: var(--text);
    /* "Text" e não "Display": a variante Display é desenhada para títulos grandes e fica
       mal definida em texto pequeno, que é a maior parte deste cockpit. */
    font-family: "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif;
    font-size: 15.5px;              /* base maior para leitura mais folgada */
    /* SEM -webkit-font-smoothing: antialiased. É uma definição de Mac; no Windows desliga
       o ClearType (subpixel) e é o que fazia as letras parecerem esborratadas. Também sem
       text-rendering: optimizeLegibility, que não traz nada aqui e piora o espaçamento. */
}

body {
    background-image:
        radial-gradient(circle at 70% -20%, rgba(0,200,255,.10), transparent 36rem),
        linear-gradient(rgba(15,37,57,.12) 1px, transparent 1px),
        linear-gradient(90deg, rgba(15,37,57,.12) 1px, transparent 1px);
    background-size: auto, 40px 40px, 40px 40px;
    background-attachment: fixed;
}

a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; font-weight: 600; }

/* --- Estrutura ---------------------------------------------------------- */

.shell { min-height: 100vh; display: grid; grid-template-columns: 246px minmax(0,1fr); }

/* A barra lateral acompanha a página; não tem barra de rolamento própria.
 *
 * Tinha `height: 100vh` fixo e o menu ficava cortado num ecrã baixo — as últimas entradas
 * existiam e não se alcançavam. A primeira correcção pôs `overflow-y: auto` no menu, e foi
 * pior: um contentor com overflow num eixo passa a rolar nos DOIS (o CSS não deixa ter
 * `overflow-x: visible` ao lado de `overflow-y: auto`), apareceram duas barras feias, e o
 * flyout do submenu Administração — que é absoluto e sai para fora da barra — ficava
 * cortado por ela.
 *
 * Agora a barra cresce com o conteúdo e é a PÁGINA que rola. Fica presa ao topo enquanto
 * couber, e num ecrã baixo chega-se ao fundo do menu a rolar a página, como em qualquer
 * outro sítio. Sem barras dentro de barras, sem nada cortado. */
.sidebar {
    position: sticky; top: 0; align-self: start; padding: 22px 16px;
    /* dvh acompanha a barra de endereço do telemóvel; vh fica de reserva para browsers velhos */
    height: 100vh; height: 100dvh;
    /* A BARRA ROLA POR DENTRO, e agora pode.
     *
     * Já esteve assim e teve de sair: o submenu era um painel absoluto que saía para fora
     * da barra, e um contentor com rolamento cortava-o. Removido o painel, deixou de haver
     * nada a sair — e sem isto a barra fica presa ao topo com o fim do menu para lá do
     * fundo do ecrã, inalcançável mesmo rolando a página.
     *
     * overflow-x explícito: um contentor que rola num eixo passa a rolar nos dois, e foi
     * essa barra horizontal que apareceu da última vez. */
    overflow-y: auto; overflow-x: hidden;
    background: linear-gradient(180deg, rgba(6,13,22,.98), rgba(7,15,24,.97));
    border-right: 1px solid var(--line);
    display: flex; flex-direction: column;
    z-index: 100; /* acima do conteúdo, senão o flyout do submenu fica atrás das caixas */
}

.brand { display: flex; align-items: center; gap: 12px; padding: 4px 2px; }
/* O logótipo é um link, mas não se pinta como um: sem sublinhado, sem cor de link. */
.logo-home { display: flex; align-items: center; text-decoration: none; color: inherit; }
/* O logo é um PNG com fundo preto; screen faz o preto desaparecer e deixa só o
   desenho brilhar sobre a barra lateral, sem a caixa preta a destoar. */
.brand-logo { width: 100%; max-width: 200px; height: auto; display: block; }
/* Um logótipo por tema (têm fundo próprio): mostra-se o certo, esconde-se o outro. */
.light-only { display: none; }
:root[data-theme="light"] .light-only { display: block; }
:root[data-theme="light"] .dark-only { display: none; }

.nav-menu { display: grid; gap: 4px; margin-top: 32px; }
.nav-menu a {
    display: flex; align-items: center; gap: 12px; padding: 10px 13px;
    color: #89a0b3; border: 1px solid transparent; border-radius: 10px;
    font-size: .87rem; font-weight: 600; transition: .18s;
}
.nav-menu a i { width: 20px; text-align: center; font-style: normal; font-size: 1rem; color: #618097; }
.nav-menu a:hover { color: #fff; background: #0d1a27; }
.nav-menu a.active {
    color: #fff; background: linear-gradient(90deg, rgba(22,119,255,.25), rgba(0,200,255,.07));
    border-color: #1d65ae; box-shadow: inset 3px 0 var(--cyan);
}
.nav-menu a.active i { color: var(--cyan); }
.nav-menu a .count {
    margin-left: auto; min-width: 20px; padding: 1px 6px; border-radius: 999px;
    background: var(--red); color: #fff; font-size: .65rem; text-align: center;
}

/* Grupo Administração. Um <div> simples — o <details> era escondido pelo Chrome através
   do pseudo-elemento ::details-content e o flyout nunca aparecia. */
.nav-group { display: grid; gap: 4px; }
.nav-group-head {
    display: flex; align-items: center; gap: 12px; width: 100%; padding: 10px 13px; cursor: pointer;
    color: #89a0b3; background: transparent; border: 1px solid transparent; border-radius: 10px;
    font-size: .87rem; font-weight: 600; font-family: inherit; text-align: left; transition: .18s;
}
.nav-group-head i { width: 20px; text-align: center; font-style: normal; font-size: 1rem; color: #618097; }
.nav-group-head::after { content: "⌄"; margin-left: auto; font-size: .9rem; transition: transform .18s; }
.nav-group.open > .nav-group-head::after { transform: rotate(180deg); }
.nav-group-head:hover { color: #fff; background: #0d1a27; }
:root[data-theme="light"] .nav-group-head { color: #2a3f52; }
:root[data-theme="light"] .nav-group-head:hover { background: #e9f0f7; }

/* Telemóvel: acordeão por toque. Fechado esconde-se, aberto empurra a lista. */
.nav-sub { display: none; gap: 4px; margin: 4px 0 4px 12px; padding-left: 8px; border-left: 1px solid var(--line); }
.nav-group.open > .nav-sub { display: grid; }

/* UM SÓ mecanismo para o submenu: acordeão, em qualquer largura.
 *
 * Havia dois. Em ecrã grande o submenu era um flyout absoluto ancorado ao topo do grupo,
 * que só existia enquanto o rato lá estivesse; em ecrã pequeno era um acordeão. Dois
 * comportamentos para o mesmo menu, e o do ecrã grande tinha um defeito sem solução: com
 * seis entradas precisa de ~250px para baixo, e num portátil de 768px de altura o grupo
 * fica a meio da barra — as últimas entradas caíam fora da janela, sem forma de lá chegar.
 * Não se ajustava porque um elemento absoluto não sabe onde acaba o ecrã.
 *
 * O acordeão não tem esse problema: empurra a lista para baixo, a barra cresce, e a página
 * rola. Já é assim que o telemóvel funciona — passa a ser assim em todo o lado. Menos
 * código, um comportamento só, e nada que possa ficar fora do ecrã.
 */

.pulse {
    width: 9px; height: 9px; border-radius: 50%; background: var(--green); flex: 0 0 auto;
    box-shadow: 0 0 0 5px rgba(37,228,123,.09), 0 0 15px var(--green);
    animation: pulse 2s infinite;
}
.pulse.off { background: var(--red); box-shadow: 0 0 0 5px rgba(255,67,93,.09), 0 0 15px var(--red); }
@keyframes pulse { 50% { opacity: .4; } }

.main { min-width: 0; }

.topbar {
    height: 72px; position: sticky; top: 0; z-index: 15;
    display: flex; align-items: center; justify-content: space-between; padding: 0 26px;
    border-bottom: 1px solid var(--line); background: rgba(5,10,17,.86); backdrop-filter: blur(20px);
}
/* Tagline por baixo do título, discreta. */
.eyebrow { display: block; color: var(--muted); font-size: .62rem; font-weight: 600; letter-spacing: .04em; margin-top: 2px; }
/* Título da página em destaque — grande, forte, com brilho da marca. */
.topbar-title {
    font-weight: 700; font-size: 1.5rem; line-height: 1; letter-spacing: -.02em;
    background: linear-gradient(90deg, #eaf3fb, #7fd0ff);
    -webkit-background-clip: text; background-clip: text; color: transparent;
}
:root[data-theme="light"] .topbar-title { background: linear-gradient(90deg, #12324a, #1f77ff); -webkit-background-clip: text; background-clip: text; color: transparent; }
.topbar-actions { display: flex; gap: 10px; align-items: center; }

.topbar-search { display: flex; align-items: center; gap: 7px; padding: 7px 12px; width: 260px;
    border: 1px solid var(--line); background: #09131e; border-radius: 999px; }
.topbar-search .ts-icon { color: var(--muted); font-size: .95rem; }
.topbar-search input { border: 0; background: transparent; padding: 0; font-size: .78rem; box-shadow: none; }
.topbar-search input:focus { box-shadow: none; }
.icon-btn { width: 38px; height: 38px; border-radius: 10px; border: 1px solid var(--line);
    background: #101c29; color: #cfe0ec; cursor: pointer; font-size: 1rem; display: grid; place-items: center; position: relative; }
.icon-btn:hover { border-color: var(--line2); }
.icon-btn.notif { text-decoration: none; }
.notif-count { position: absolute; top: -5px; right: -5px; min-width: 17px; height: 17px; padding: 0 4px;
    border-radius: 999px; background: var(--red); color: #fff; font-size: .6rem; font-weight: 700;
    display: grid; place-items: center; }
.chip.clock { font-variant-numeric: tabular-nums; border-radius: 10px; }
/* O chip de vigilância também com cantos de botão (é clicável, como a ajuda/alertas). */
.chip.live { border-radius: 10px; }
.topbar-user { display: flex; align-items: center; gap: 9px; padding: 5px 12px 5px 6px;
    border: 1px solid var(--line); background: #09131e; border-radius: 999px; }
.avatar-sm { width: 30px; height: 30px; border-radius: 50%; display: grid; place-items: center;
    background: linear-gradient(135deg, #0879c9, #00b8eb); color: #fff; font-size: .7rem; font-weight: 700; }
.tu-id { display: grid; }
.tu-id b { font-size: .74rem; }
.tu-id small { font-size: .62rem; color: var(--cyan); }
.tu-logout { display: flex; margin: 0; }
.tu-logout button {
    width: 32px; height: 32px; border-radius: 8px; cursor: pointer;
    border: 1px solid var(--line); background: #101c29; color: #ff93a1;
    font-size: 1rem; display: grid; place-items: center; padding: 0;
}
.tu-logout button:hover { border-color: var(--red); background: rgba(255,67,93,.1); }

@media (max-width: 1350px) { .topbar-search { display: none; } }
@media (max-width: 1100px) { .topbar-user .tu-id { display: none; } }
.chip {
    display: flex; align-items: center; gap: 8px; padding: 8px 12px;
    border: 1px solid var(--line); background: #09131e; border-radius: 999px;
    font-size: .74rem; color: #9db1c1; font-variant-numeric: tabular-nums;
}
.chip.live { border-color: rgba(37,228,123,.28); background: rgba(37,228,123,.07); color: #9ef4c2; }
.chip.live span { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 12px var(--green); }

.content { padding: 24px 26px 60px; max-width: 1740px; margin: auto; }

/* --- KPIs ---------------------------------------------------------------- */

/* 4 caixas sobre a coluna dos estúdios; a "Sintomas" vive na coluna do mapa (alinhada). */
.kpi-grid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; margin-bottom: 14px; }
.kpi {
    position: relative; overflow: hidden; padding: 16px;
    border: 1px solid var(--line); background: linear-gradient(145deg, #0e1b29, #09131e);
    border-radius: 13px; box-shadow: var(--shadow);
}
.kpi:before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(circle at 100% 0, rgba(0,200,255,.12), transparent 48%);
}
.kpi.green:before { background: radial-gradient(circle at 100% 0, rgba(37,228,123,.14), transparent 48%); }
.kpi.red:before   { background: radial-gradient(circle at 100% 0, rgba(255,67,93,.16), transparent 48%); }
.kpi.amber:before { background: radial-gradient(circle at 100% 0, rgba(255,176,32,.15), transparent 48%); }
.kpi .label { display: block; color: #89a1b4; font-size: .7rem; text-transform: uppercase; letter-spacing: .09em; }
.kpi .value { display: block; font-size: 2rem; font-weight: 700; margin-top: 8px; letter-spacing: -.04em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.kpi .foot { display: block; font-size: .68rem; color: var(--muted); margin-top: 4px; }

/* KPI enriquecido: ícone temático + mini-gráfico */
.kpi-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; position: relative; z-index: 1; }
.kpi-icon { width: 40px; height: 40px; flex: 0 0 auto; color: var(--cyan); opacity: .85; }
.kpi-icon svg { width: 100%; height: 100%; }
.kpi.green .kpi-icon { color: var(--green); }
.kpi.red .kpi-icon { color: var(--red); }
.kpi.amber .kpi-icon { color: var(--amber); }
.kpi-spark { position: absolute; left: 0; right: 0; bottom: 0; height: 34px; color: var(--cyan);
             opacity: .5; pointer-events: none; }
.kpi-spark svg { width: 100%; height: 100%; display: block; }
.kpi.green .kpi-spark { color: var(--green); }
.kpi.red .kpi-spark { color: var(--red); }
.kpi.amber .kpi-spark { color: var(--amber); }
.kpi { padding-bottom: 22px; }
.kpi.red .value { color: var(--red); }
.kpi.amber .value { color: var(--amber); }
.kpi.green .value { color: var(--green); }

/* --- Estação e cadeia de sinal ------------------------------------------- */

.station {
    border: 1px solid var(--line);
    background: linear-gradient(180deg, rgba(11,22,34,.96), rgba(7,15,24,.96));
    border-radius: 15px; padding: 16px; margin-bottom: 14px; box-shadow: var(--shadow);
}
.station.alarm { border-color: rgba(255,67,93,.45); }
.station-head { display: flex; justify-content: space-between; align-items: center; gap: 16px;
                border-bottom: 1px solid var(--line); padding-bottom: 13px; }
.station-head h2 { font-size: 1.15rem; }
.station-head small { color: var(--muted); font-size: .74rem; }
/* A bola antes do nome = estado do FMDX (verde responde, vermelho sem dados). A cor da
   rádio não vem aqui — vai no nome, senão perdia-se o estado, que é o que importa ler. */
.site-dot { display: inline-block; width: 11px; height: 11px; border-radius: 50%; margin-right: 8px;
    vertical-align: middle; }
.site-dot.ok { background: var(--green); box-shadow: 0 0 8px var(--green); }
.site-dot.bad { background: var(--red); box-shadow: 0 0 8px var(--red); }
.color-field { display: flex; align-items: center; gap: 8px; }
.color-field input[type="color"] { width: 42px; height: 34px; padding: 2px; cursor: pointer; }
.color-field .color-hex { flex: 1; min-width: 0; }
/* Quebra de linha permitida: sem isto, num ecrã estreito o nome empurrava os botões
   "ao vivo"/"minimizar" para fora do ecrã e ficavam cortados. */
.station-head { flex-wrap: wrap; }
.station-id { flex: 1 1 auto; min-width: 0; }
.station-id h2 { overflow-wrap: anywhere; }
.station-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

@media (max-width: 860px) {
    /* Nome numa linha, estado + botões na seguinte, sempre dentro do ecrã. */
    .station-id { flex: 1 1 100%; }
    .station-head h2 { font-size: 1.02rem; }
    .station-actions { flex: 1 1 auto; }
}
/* (vista minimizada da estação removida — a janela abre sempre inteira) */

.badge {
    display: inline-flex; align-items: center; gap: 7px; padding: 6px 10px;
    border: 1px solid; border-radius: 999px; font-size: .63rem; font-weight: 700;
    letter-spacing: .06em; white-space: nowrap;
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 9px currentColor; }
.s-ok       { color: var(--green);  border-color: rgba(37,228,123,.28); background: rgba(37,228,123,.07); }
.s-warn     { color: var(--amber);  border-color: rgba(255,176,32,.28); background: rgba(255,176,32,.07); }
.s-bad      { color: var(--red);    border-color: rgba(255,67,93,.32);  background: rgba(255,67,93,.08); }
.s-unknown  { color: #8196a7;       border-color: var(--line);          background: #0b1621; }

.chain { display: flex; align-items: stretch; overflow-x: auto; padding: 14px 0 6px; }
/* Todo o nó a 90%: larguras, espaçamentos e letra. Reduzir só o desenho lá dentro deixava
   as caixas do mesmo tamanho — e o que ocupava espaço a mais eram as caixas. */
.chain-node {
    flex: 1 0 162px; max-width: 225px; position: relative; padding: 11px 11px 9px;
    border: 1px solid var(--line); background: linear-gradient(180deg, #0d1a28, #0a1420); border-radius: 12px;
}
.chain-node:before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
                     border-radius: 12px 12px 0 0; background: var(--green); }
.chain-node.warn:before    { background: var(--amber); }
.chain-node.bad:before     { background: var(--red); }
.chain-node.unknown:before { background: #33506b; }
.node-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.chain-node .kind { color: #8aa2b8; font-size: .54rem; font-weight: 700; letter-spacing: .12em; }
.node-state { font-size: .5rem; font-weight: 700; letter-spacing: .06em; padding: 2px 6px; border-radius: 999px;
              color: var(--green); background: rgba(37,228,123,.1); }
.node-state.warn { color: var(--amber); background: rgba(255,176,32,.1); }
.node-state.bad { color: var(--red); background: rgba(255,67,93,.1); }
.node-state.unknown { color: #7891a6; background: #0d1a27; }
.chain-node .name { font-size: .78rem; margin: 3px 0 8px; display: block; font-weight: 600; color: var(--text); }
.device-img { background: radial-gradient(circle at 50% 0, #0a1a2b, #060e17); border: 1px solid #1a2f43;
              border-radius: 9px; padding: 9px 11px; box-shadow: inset 0 0 24px rgba(0,0,0,.4); }
.device-img svg { width: 100%; height: auto; display: block; min-height: 56px;
                  filter: drop-shadow(0 2px 6px rgba(0,0,0,.4)); }
.chain-node .mini { display: flex; gap: 11px; border-top: 1px solid var(--line); padding-top: 8px; margin-top: 8px; }
.chain-node .mini span { display: grid; }
.chain-node .mini small { font-size: .52rem; color: #8397aa; }
.chain-node .mini b { font-size: .67rem; margin-top: 2px; font-family: var(--mono); color: var(--text); }

/* --- Fila de cartões de destaque (como no conceito) ----------------------
   SEIS COLUNAS ESCRITAS À MÃO, e a fila tem sete cartões: Nível Entrada, Saída, MPX,
   Silêncio, Pacotes, Latência, Perda, Temperatura. O sétimo caía sempre para uma segunda
   linha — não por falta de espaço, mas porque o número 6 estava lá fixo desde o dia em que
   eram seis.

   Mesma solução das caixas dos equipamentos, pela mesma razão: acima dos 1000px a fila não
   quebra e cada cartão fica com uma fatia igual do que houver, sejam cinco ou oito. Abaixo
   disso quebra, que num telemóvel é o que tem de fazer. */
.keymetrics { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 12px; }
.keymetrics > * { flex: 1 1 150px; min-width: 0; }

@media (min-width: 1000px) {
    .keymetrics { flex-wrap: nowrap; }
    .keymetrics > * { flex: 1 1 0; }
}

.km-card { position: relative; overflow: hidden; padding: 13px 14px 8px; border: 1px solid var(--line);
    background: linear-gradient(160deg, #0e1b29, #09131e); border-radius: 12px; min-height: 108px; }
.km-card.km-sim { border-style: dashed; border-color: #2a3f56; }
.km-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.km-label { color: #8ba1b6; font-size: .62rem; text-transform: uppercase; letter-spacing: .06em;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.km-simtag { font-size: .52rem; font-weight: 700; color: var(--amber); background: rgba(255,176,32,.1);
    padding: 2px 6px; border-radius: 999px; }
.km-value { display: block; margin-top: 8px; font-family: var(--mono); font-size: 1.5rem; font-weight: 700;
    letter-spacing: -.02em; color: var(--text); }
.km-value small { font-size: .62rem; color: var(--muted); font-weight: 600; }
.km-spark { height: 28px; margin-top: 6px; color: var(--cyan); }
.km-spark.green { color: var(--green); } .km-spark.amber { color: var(--amber); }
.km-spark svg { width: 100%; height: 100%; display: block; }
/* Havia aqui duas regras a mandar 3 e 2 colunas em ecrãs estreitos. Deixaram de fazer
   sentido quando isto passou a flex — `grid-template-columns` não se aplica a um contentor
   flex, e ficariam a dizer uma coisa que já não acontece. O embrulho com base de 150px faz
   o mesmo trabalho e sozinho. */

/* --- Painéis FMDX (simulados até haver hardware) ------------------------- */
.fmdx-grid { display: grid; grid-template-columns: 1.3fr .9fr 1.3fr; gap: 12px; margin-top: 12px; }
.fmdx-grid .panel { padding: 13px; }
.panel-title .sim { font-size: .58rem; font-weight: 700; letter-spacing: .08em; color: var(--amber);
                    background: rgba(255,176,32,.1); padding: 3px 8px; border-radius: 999px; }
.spectrum { width: 100%; height: 130px; background: linear-gradient(180deg, #041019, #071a2b); border-radius: 8px; }
.audiowave { width: 100%; height: 90px; background: #050f0a; border-radius: 8px; }
.stereo { font-size: .62rem; color: var(--green); font-weight: 700; }
.rds { display: grid; gap: 9px; padding-top: 4px; }
.rds-row { display: flex; align-items: baseline; gap: 12px; }
.rds-row span { width: 24px; color: #8397aa; font-size: .64rem; font-weight: 700; }
.rds-row b { font-family: var(--mono); font-size: .82rem; color: var(--cyan); }
.rds-row b.rt { color: var(--text); font-size: .74rem; }
.rds-flags { display: flex; gap: 8px; margin-top: 4px; }
.rds-flags .flag { font-size: .58rem; font-weight: 700; padding: 3px 8px; border-radius: 999px;
                   color: #7891a6; background: #0d1a27; }
.rds-flags .flag.on { color: var(--green); background: rgba(37,228,123,.1); }

@media (max-width: 1250px) { .fmdx-grid { grid-template-columns: 1fr; } }

.chain-link { flex: 0 0 30px; display: flex; align-items: center; justify-content: center; position: relative; color: var(--green); }
.chain-link.bad { color: var(--red); }
.chain-link.unknown { color: #33506b; }
.chain-link i { position: absolute; width: 80%; height: 1px; background: currentColor; opacity: .5; }
.chain-link span { position: relative; background: #08121c; padding: 2px 3px; font-style: normal; }

/* --- Telemetria ---------------------------------------------------------- */

/* AS CAIXAS DE UM EQUIPAMENTO NUMA LINHA SÓ.
   As medições do mesmo equipamento são um conjunto: partidas em duas fiadas leem-se como
   dois grupos, e na cadeia de sinal isso é a leitura errada.

   DUAS TENTATIVAS FALHADAS, e ambas ensinam alguma coisa:

   1. Encolher o mínimo (164 → 128px). Resolveu para sete caixas e voltou a partir com oito:
      é apostar na largura da janela de quem está a olhar.
   2. Fazer a grelha seguir o número de caixas, com `repeat(var(--n), ...)`. **Não existe.**
      O `repeat()` exige um número literal — o `var()` só é substituído mais tarde, e a
      declaração inteira é deitada fora. Ficou lá a parecer que resolvia e não fazia nada.

   O que funciona é flexbox: acima dos 1000px a fila NÃO quebra e cada caixa fica com uma
   fatia igual do que houver, sejam quatro ou nove. Abaixo disso quebra à vontade, e é de
   propósito — num telemóvel, nove colunas não são nove caixas, são nove riscos. */
.tiles { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.tiles > * { flex: 1 1 112px; min-width: 0; }

@media (min-width: 1000px) {
    .tiles { flex-wrap: nowrap; }
    .tiles > * { flex: 1 1 0; }
}

/* Menos 20% de altura. É um MÍNIMO, não uma altura fixa: as caixas com mais informação —
   o RadioText, a lista de frequências alternativas — continuam a crescer com o conteúdo,
   que era a condição. O que encolheu foi o espaço à volta de um número e o próprio número,
   que estava a 1,5rem numa caixa de 128px. */
.tile {
    position: relative; overflow: hidden; min-height: 84px; padding: 9px 11px;
    border: 1px solid var(--line); background: linear-gradient(160deg, #0e1b29, #09131e); border-radius: 11px;
}
.tile .metric { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                color: #8ba1b6; font-size: .64rem; text-transform: uppercase; letter-spacing: .06em; }
.tile .value { display: block; margin-top: 5px; font-family: var(--mono); font-size: 1.2rem; font-weight: 700;
               letter-spacing: -.02em; color: var(--text); }
.tile .when { display: block; font-size: .58rem; color: #708aa0; margin-top: 2px; }
.tile.warn { border-color: rgba(255,176,32,.5); background: #1d1a10; }
.tile.bad  { border-color: rgba(255,67,93,.5);  background: #1d1114; }
.tile.stale { opacity: .45; }
.tile.stale .value:after { content: " ⟳"; color: var(--amber); }

/* --- Zonas: cada equipamento no seu bloco -------------------------------
   Eram títulos soltos com caixas por baixo. Numa estação com sete equipamentos deixava de
   se ver onde acaba um e começa o outro — sobretudo desde que as caixas passaram a encher a
   linha toda, porque a fila de um equipamento ficou igual à fila do seguinte.

   A COR VEM DO ELO DA CADEIA, e não de uma paleta rodada por ordem de chegada. Uma cor por
   equipamento seria bonita e não diria nada, e mudaria de estação para estação consoante
   quem publicou primeiro. Assim a mesma cor quer sempre dizer a mesma coisa, e quem olha
   duas vezes aprende a ler as zonas sem ler os títulos.

   Tons quase transparentes, de propósito: a cor que GRITA continua reservada ao estado.
   Numa emergência, o vermelho tem de ser a única coisa vermelha no ecrã. */
/* O fundo é o do PAINEL, não um quase-preto próprio. A primeira versão usava #0a141f — mais
   escuro do que tudo o que está à volta — e as zonas apareciam como buracos no ecrã em vez
   de blocos. Sobre a superfície dos painéis, a mesma percentagem de cor lê-se como um tom, e
   não como uma mancha. */
/* A COR VAI NUMA VARIÁVEL, e não directamente no `background`.
   Os painéis do FMDX ficavam sem cor nenhuma e a razão era a ORDEM do ficheiro: a regra
   `.panel` aparece mais abaixo, com a mesma especificidade das `.z-*`, e o gradiente dela
   ganhava. Pôr `background-image: none` tirava o gradiente mas deixava o fundo transparente
   — porque a forma abreviada `background:` do `.panel` também repõe a cor. Resultado: uns
   blocos coloridos e outros a mostrar o que estava por trás.

   Com a cor numa variável, quem a define (`.z-rf`) e quem a usa (o fundo) deixam de
   competir: as variáveis não entram na cascata do `background`. */
/* O TRAÇO TAMBÉM VAI NUMA VARIÁVEL, pela mesma razão do fundo: no tema claro há uma regra
   que repõe o `border-color` de todos os painéis, e levava o traço da esquerda com ela.
   Com a cor guardada em `--zona-traco`, quem a define e quem a pinta deixam de competir. */
.zona-equipamento {
    --zona: var(--panel2);
    --zona-traco: var(--line);
    margin-top: 12px; padding: 2px 13px 13px; border-radius: 12px;
    border: 1px solid var(--line);
    border-left: 4px solid var(--zona-traco);
    background: var(--zona);
}
.zona-equipamento .device-group { margin-top: 10px; }
.zona-equipamento .tiles { margin-top: 8px; }

/* Um painel que TAMBÉM é zona não leva fundo duas vezes: fica o tom da zona, e a moldura à
   esquerda diz de que elo é.
   A LARGURA tem de ser repetida aqui. O `.panel` aparece mais abaixo no ficheiro com
   `border: 1px solid`, e a forma abreviada repõe as quatro larguras — o traço da esquerda
   ficava com 1px nestes e 4px nos outros, que foi a diferença que se viu. */
.panel.zona-equipamento {
    background: var(--zona); background-image: none; padding: 16px;
    border-left: 4px solid var(--zona-traco);
}
.panel.zona-equipamento .panel-title { margin-bottom: 10px; }

/* AS CORES SEGUEM O PERCURSO DO SINAL, do estúdio para a antena: começam frias na origem e
   vão aquecendo até ao ar. Não é gosto — é para a ordem das zonas se sentir mesmo quando se
   percorre o ecrã sem ler os títulos. O rosa do RF saía do padrão e ia buscar atenção que
   não é dele; passou a laranja, que é o fim natural desta escala. */
.z-playout       { --zona: color-mix(in srgb, #4d8df0 10%, var(--panel2)); --zona-traco: #4d8df0; }
.z-processamento { --zona: color-mix(in srgb, #16b3c9 10%, var(--panel2)); --zona-traco: #16b3c9; }
.z-codificacao   { --zona: color-mix(in srgb, #1fbf8f 10%, var(--panel2)); --zona-traco: #1fbf8f; }
.z-rede          { --zona: color-mix(in srgb, #8db32a 10%, var(--panel2)); --zona-traco: #8db32a; }
.z-emissor       { --zona: color-mix(in srgb, #e0a318 10%, var(--panel2)); --zona-traco: #e0a318; }
.z-rf            { --zona: color-mix(in srgb, #e8722e 10%, var(--panel2)); --zona-traco: #e8722e; }
.z-sistema       { --zona: color-mix(in srgb, #7b8ea3 8%,  var(--panel2)); --zona-traco: #7b8ea3; }

/* O resumo da estação atravessa a cadeia toda — nível de entrada, pacotes, temperatura — e
   por isso NÃO leva cor de elo: seria dizer que pertence a um sítio quando pertence a todos.
   Fica neutro, mas com moldura, para deixar de parecer um buraco entre duas zonas com cor. */
.z-resumo { --zona: var(--panel2); --zona-traco: #4a5c70; }

/* As fotografias do local técnico. Mesma regra das outras filas: acima dos 1000px cabem
   todas numa linha, abaixo quebram. */
.camaras { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
/* O TECTO É O QUE FAZ A DIFERENÇA COM UMA CÂMARA SÓ.
   Sem ele, uma única fotografia estica-se à largura da página e o 16:9 transforma isso em
   quase oitocentos pixéis de altura — uma parede de imagem a empurrar para baixo tudo o que
   interessa vigiar. Com várias, a grelha continua a dividir o espaço como antes. */
.camaras > * { flex: 1 1 240px; min-width: 0; max-width: 380px; }
@media (min-width: 1000px) { .camaras { flex-wrap: nowrap; } .camaras > * { flex: 1 1 0; } }

.camara { margin: 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
          background: #050d16; }
.camara img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.camara figcaption { display: flex; justify-content: space-between; align-items: baseline;
                     gap: 8px; padding: 6px 9px; font-size: .62rem; }
.camara figcaption b { color: var(--text); font-weight: 600; overflow: hidden;
                       text-overflow: ellipsis; white-space: nowrap; }
.camara figcaption span { color: var(--muted); font-family: var(--mono); flex: none; }

/* Uma imagem velha continua a mostrar-se — mas dizendo-o. Escondê-la seria perder a única
   pista do local; mostrá-la como se fosse de agora seria pior ainda. */
.camara.velha img { opacity: .45; filter: grayscale(.5); }
.camara.velha figcaption span { color: var(--amber); }
.camara.velha figcaption span:after { content: " ⟳"; }
.z-resumo .keymetrics { margin-top: 8px; }

.device-group { margin: 14px 0 6px; font-size: .64rem; font-weight: 700; letter-spacing: .13em;
                text-transform: uppercase; color: var(--muted); display: flex; align-items: center; gap: 8px; }
.device-group:after { content: ""; flex: 1; height: 1px; background: var(--line); }
/* Passou a botão: sem moldura de botão, largura toda, e a dizer-se clicável ao passar. */
button.device-group { width: 100%; background: none; border: 0; padding: 0; cursor: pointer;
                      text-align: left; font-family: inherit; }
button.device-group:hover { color: var(--cyan); }
button.device-group:hover:after { background: color-mix(in srgb, var(--cyan) 45%, transparent); }

/* --- Incidentes ---------------------------------------------------------- */

.incident {
    border: 1px solid var(--line); border-left: 3px solid var(--amber);
    background: #0a1520; border-radius: 11px; padding: 15px; margin-top: 12px;
}
.incident.sev-Critico    { border-left-color: var(--red); background: linear-gradient(90deg, rgba(255,67,93,.07), #0a1520 30%); }
.incident.sev-Emergencia { border-left-color: var(--red); background: linear-gradient(90deg, rgba(255,67,93,.12), #0a1520 34%); }
.incident header { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
.incident h3 { font-size: 1rem; }
.incident .explain { color: #9cb0bf; font-size: .82rem; line-height: 1.55; margin: 8px 0 0; }
.incident .since { font-size: .66rem; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

.symptoms { margin: 12px 0 0; padding: 10px 12px; background: #08121b; border: 1px solid var(--line); border-radius: 8px; }
.symptoms .cap { font-size: .55rem; color: var(--cyan); font-weight: 700; letter-spacing: .1em; }
.symptoms ul { margin: 6px 0 0; padding-left: 18px; }
.symptoms li { font-size: .74rem; color: #92a7b7; margin: 3px 0; }
.symptoms.unrelated .cap { color: var(--amber); }

.runbook { margin-top: 12px; }
.runbook summary { cursor: pointer; font-size: .7rem; font-weight: 700; letter-spacing: .09em;
                   color: var(--cyan); text-transform: uppercase; }
.runbook p { margin: 8px 0 0; font-size: .78rem; line-height: 1.6; color: #a4b5c2; white-space: pre-line; }

.alert-row { display: flex; align-items: center; gap: 10px; padding: 10px;
             border-left: 2px solid var(--amber); background: #0b1621; border-radius: 6px; margin-bottom: 6px; }
.alert-row.sev-Critico, .alert-row.sev-Emergencia { border-color: var(--red); background: rgba(255,67,93,.06); }
.alert-row i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; color: var(--amber); font-style: normal; flex: 0 0 auto; }
.alert-row.sev-Critico i, .alert-row.sev-Emergencia i { color: var(--red); }
.alert-row div { display: grid; flex: 1; min-width: 0; }
.alert-row b { font-size: .78rem; }
.alert-row span { font-size: .64rem; color: var(--muted); margin-top: 2px; }
.alert-row time { font-size: .64rem; color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }

/* --- Painéis e vazios ---------------------------------------------------- */

.panels { display: grid; grid-template-columns: 1.4fr 1fr; gap: 14px; margin-top: 14px; align-items: start; }
.panel { border: 1px solid var(--line); background: linear-gradient(180deg, #0d1925, #08131d);
         border-radius: 14px; padding: 16px; box-shadow: var(--shadow); }
.panel-title { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.panel-title h3 { font-size: .95rem; }
.panel-title span { font-size: .68rem; color: var(--cyan); }

.empty { padding: 46px 20px; text-align: center; color: var(--muted);
         border: 1px dashed var(--line2); border-radius: 12px; }
.empty strong { display: block; font-size: 2.4rem; color: var(--cyan); margin-bottom: 10px; }
.empty small { display: block; margin-top: 6px; font-size: .75rem; }
.empty code { font-family: var(--mono); color: #9ef4c2; }

.all-good { display: flex; align-items: center; gap: 10px; padding: 14px;
            border: 1px solid rgba(37,228,123,.25); background: rgba(37,228,123,.05);
            border-radius: 10px; color: #9ef4c2; font-size: .82rem; }

/* --- Responsivo ---------------------------------------------------------- */

@media (max-width: 1250px) {
    .kpi-grid { grid-template-columns: repeat(3, minmax(0,1fr)); }
    .panels { grid-template-columns: 1fr; }
}

/* Havia aqui um TERCEIRO modo de navegação: entre 821 e 900px a barra lateral achatava-se
   numa tira horizontal onde só cabiam "Cockpit" e "Estações", e o resto ficava a rolar de
   lado sem se ver. Era um resto de antes de existir a gaveta.
   Dois padrões a competir pelo mesmo ecrã é o pior dos dois — ficou a gaveta, que é o que
   funciona no telemóvel e no portátil. */
@media (max-width: 1024px) {
    .kpi-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
}

/* --- Cockpit: mapa + sparklines ------------------------------------------ */

.cockpit-top { display: grid; grid-template-columns: minmax(0,1fr) 320px; gap: 14px; align-items: start; }
.stations-col { min-width: 0; }

.map-wrap { display: flex; flex-direction: column; align-items: center; }
.map { width: 100%; max-width: 240px; height: auto; }
.map-distritos .distrito {
    fill: rgba(17,42,68,.55); stroke: #35618a; stroke-width: .6;
    vector-effect: non-scaling-stroke;   /* borda fina, não engorda ao escalar */
    transition: fill .15s;
}
.map-distritos .distrito:hover { fill: rgba(0,200,255,.20); stroke: var(--cyan); }
/* Pontos clicáveis. O <g> exterior leva a posição; este só o efeito — nunca pôr um
   transform de CSS no mesmo elemento que tem o translate do SVG (substitui-o). */
.map-pin { cursor: pointer; transform-origin: 0 0; }
.map-pin:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; border-radius: 50%; }

/* Só onde HÁ rato: no telemóvel o :hover fica "colado" depois do toque e o ponto
   ficava crescido e fora do sítio. E anima-se o raio, não o transform. */
@media (hover: hover) {
    .map-pin .map-dot, .map-pin .map-halo { transition: r .12s ease-out; }
    .map-pin:hover .map-dot { r: 8.5; }
    .map-pin:hover .map-halo { r: 16; opacity: .32; }
}

.map-halo { opacity: .2; animation: haloPulse 2.4s infinite; }
.map-dot, .map-halo { }
.map-dot.ok, .map-halo.ok { fill: var(--green); }
.map-dot.warn, .map-halo.warn { fill: var(--amber); }
.map-dot.bad, .map-halo.bad { fill: var(--red); }
.map-dot.unknown, .map-halo.unknown { fill: #5c6577; }
@keyframes haloPulse { 50% { opacity: .04; } }

.map-legend { display: grid; gap: 5px; margin-top: 12px; width: 100%; font-size: .68rem; color: var(--muted); }
.map-legend span { display: flex; align-items: center; gap: 7px; }
.map-legend .dot { width: 8px; height: 8px; border-radius: 50%; }
.map-legend .dot.ok { background: var(--green); }
.map-legend .dot.warn { background: var(--amber); }
.map-legend .dot.bad { background: var(--red); }
.map-legend .dot.unknown { background: #5c6577; }
.map-note { margin-top: 10px; font-size: .64rem; color: var(--amber); line-height: 1.4; }

.spark { display: block; width: 100%; height: 18px; margin: 3px 0 1px; color: var(--cyan); }
.spark.warn { color: var(--amber); }
.spark.bad { color: var(--red); }

/* Lista de estações (painel lateral) */
.station-list { display: grid; gap: 2px; }
.station-item { display: flex; align-items: center; gap: 10px; padding: 8px 6px;
                border-bottom: 1px solid rgba(29,51,72,.5); }
.station-item:last-child { border-bottom: 0; }
.station-item .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.station-item .dot.ok { background: var(--green); box-shadow: 0 0 8px var(--green); }
.station-item .dot.warn { background: var(--amber); box-shadow: 0 0 8px var(--amber); }
.station-item .dot.bad { background: var(--red); box-shadow: 0 0 8px var(--red); }
.station-item .grow { flex: 1; min-width: 0; display: grid; }
.station-item b { font-size: .78rem; }
.station-item small { font-size: .64rem; color: var(--muted); }
.st-state { font-size: .58rem; font-weight: 700; letter-spacing: .05em; padding: 3px 7px;
            border-radius: 999px; white-space: nowrap; }
.st-state.ok { color: var(--green); background: rgba(37,228,123,.08); }
.st-state.warn { color: var(--amber); background: rgba(255,176,32,.08); }
.st-state.bad { color: var(--red); background: rgba(255,67,93,.08); }
.map-col { display: grid; gap: 14px; }

/* O MAPA DESCE MAIS CEDO — 1700px e não 1150px.
 *
 * A coluna do mapa leva 320px fixos, e é ao lado dela que vive a tabela das estações. Entre
 * os 1150 e os 1640 a tabela ficava com 700 a 900px para mostrar sete elos, um nome, dois
 * números e seis atalhos: rolava de lado numa página que ninguém espera que role, e os
 * valores ficavam cortados a meio ("stream OK" não cabe em 58px).
 *
 * O número não foi escolhido a olho: medido, abaixo dos 1700 a tabela recebia menos de mil
 * pixels com o mapa ao lado, e precisa de 1010. Abaixo disso o mapa desce e ela fica com a
 * largura toda. O mapa é contexto; a tabela é o que se vem cá ver. */
@media (max-width: 1700px) {
    .cockpit-top { grid-template-columns: 1fr; }
    .map-col { position: static; order: -1; }
    .map { max-width: 180px; }
}

/* --- Backoffice ---------------------------------------------------------- */

.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 7px;
    padding: 9px 14px; min-height: 36px;
    border: 1px solid #1f84cf; border-radius: 9px;
    background: linear-gradient(135deg, #0879c9, #00b8eb); color: #fff;
    font-weight: 700; font-size: .8rem; cursor: pointer; white-space: nowrap;
    text-decoration: none; line-height: 1;
    /* Um pouco de relevo e um movimento curto ao passar/carregar: dá a sensação de
       botão a sério em vez de rectângulo pintado. */
    box-shadow: 0 1px 2px rgba(0,0,0,.18);
    transition: filter .14s, transform .06s, border-color .14s, background .14s;
}
.btn:hover { filter: brightness(1.08); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.btn:disabled { opacity: .45; cursor: not-allowed; transform: none; }
/* Todos os "pequenos" com a MESMA altura: antes uns eram 28px e outros 36px na mesma
   linha, e ficava desalinhado. */
.btn.sm { padding: 6px 11px; font-size: .73rem; min-height: 30px; }

/* --- Família de botões, com cor e tema ------------------------------------- */
/* Estavam aqui cores FIXAS de tema escuro (#0c1b29 com texto pálido): no tema claro
   davam azul-escuro sobre branco. É o mesmo defeito que já tinha apagado os valores
   da tabela e os atalhos do /config — daqui para a frente, color-mix e variáveis.
   Cada variante carrega significado: o primário age, o "ok" confirma, o âmbar
   avisa, o vermelho destrói. Um botão cinzento para tudo obriga a ler o texto. */
.btn.ghost {
    background: var(--panel2); border-color: var(--line2); color: var(--text);
    box-shadow: none;
}
.btn.ghost:hover {
    background: color-mix(in srgb, var(--cyan) 12%, var(--panel2));
    border-color: var(--cyan); filter: none;
}
.btn.ok {
    background: linear-gradient(135deg, #17a34a, #22c55e);
    border-color: #17a34a; color: #fff;
}
.btn.warn {
    background: linear-gradient(135deg, #d97706, #f59e0b);
    border-color: #d97706; color: #fff;
}
.btn.danger {
    background: linear-gradient(135deg, #dc2626, #f2555f);
    border-color: #dc2626; color: #fff;
    box-shadow: 0 1px 2px rgba(220,38,38,.28);
}
/* O secundário: contorno com a cor, fundo tingido. Para acções importantes que não
   são A acção principal do ecrã. */
.btn.outline {
    background: color-mix(in srgb, var(--cyan) 12%, transparent);
    border-color: color-mix(in srgb, var(--cyan) 45%, transparent);
    color: var(--cyan); box-shadow: none;
}
.btn.outline:hover { background: color-mix(in srgb, var(--cyan) 22%, transparent); filter: none; }
/* Relevo maior no primário, para se destacar de relance numa página cheia. */
.btn:not(.ghost):not(.outline) { box-shadow: 0 2px 6px -1px color-mix(in srgb, var(--cyan) 40%, transparent); }
.btn.ok { box-shadow: 0 2px 6px -1px rgba(34,197,94,.4); }
.btn.warn { box-shadow: 0 2px 6px -1px rgba(245,158,11,.4); }
.btn:not(.ghost):not(.outline):hover { transform: translateY(-1px); filter: brightness(1.06); }
.btn:not(.ghost):not(.outline):active { transform: translateY(0); }

.kpi.cfg { text-decoration: none; transition: .15s; }
.kpi.cfg:hover { border-color: var(--line2); transform: translateY(-2px); }

.how { margin: 0; padding-left: 20px; color: #a4b5c2; font-size: .82rem; line-height: 1.8; }
.how b { color: var(--text); }
.shortcuts { display: grid; gap: 8px; }
/* var(--text), nunca #cfe0ec fixo: essa cor é de tema escuro e no claro o fundo passa
   a quase branco mas a letra ficava clara — os atalhos liam-se mal ou nada. O mesmo
   defeito que já tinha apagado os valores da tabela de estações. */
.shortcuts a { padding: 12px 14px; border: 1px solid var(--line); background: var(--panel2);
               border-radius: 9px; font-size: .82rem; font-weight: 650; color: var(--text);
               text-decoration: none; transition: border-color .15s, background .15s; }
.shortcuts a:hover { border-color: var(--cyan);
                     background: color-mix(in srgb, var(--cyan) 10%, transparent); }

.head-controls { display: flex; gap: 10px; align-items: center; }

.catalog-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; }
.cat-card { border: 1px solid var(--line); background: linear-gradient(180deg, #0d1925, #08131d);
            border-radius: 13px; padding: 12px; display: grid; gap: 10px; }
.cat-img { background: #060e17; border: 1px solid #16283a; border-radius: 9px; padding: 8px; min-height: 70px;
           display: grid; place-items: center; }
.cat-img svg, .cat-img img { width: 100%; height: auto; display: block; border-radius: 6px; }
.cat-body b { font-size: .9rem; display: block; }
.cat-body span { font-size: .68rem; color: var(--muted); }
.cat-body .tag { display: inline-block; margin-top: 4px; font-style: normal; font-size: .58rem;
                 color: var(--cyan); background: rgba(0,200,255,.08); padding: 2px 7px; border-radius: 999px; }
.cat-upload { font-size: .64rem; color: #8397aa; display: grid; gap: 4px; }

.form { margin-bottom: 16px; }
.field-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px 16px; align-items: start; }
/* A etiqueta ocupa uma faixa de altura fixa e o campo vem a seguir: assim os campos
   alinham todos na mesma linha de base, mesmo quando umas etiquetas têm duas linhas de
   texto e outras uma. Sem isto, cada coluna começava a uma altura diferente. */
.field-grid label { display: grid; grid-template-rows: minmax(2.5em, auto) auto; gap: 6px; align-content: start; color: #8199aa; font-size: .74rem; }
.field-grid label > input, .field-grid label > select, .field-grid label > .color-field { align-self: start; }
.field-grid label.wide { grid-column: 1 / -1; }
.field-grid label.inline { display: flex; align-items: center; gap: 8px; color: #cfe0ec; font-size: .8rem; }
.field-grid label.inline input { width: auto; }
.field-grid small { color: #5f788c; font-size: .66rem; line-height: 1.4; }
.field-grid code { font-family: var(--mono); color: #9ef4c2; }
/* Os sete dias de uma janela de PI numa linha só, sem cores próprias — herda o tema. */
.dias-semana { display: flex; flex-wrap: wrap; gap: 4px 12px; align-items: center; }

.form-subhead { margin: 20px 0 10px; border-top: 1px solid var(--line); padding-top: 16px; }
.form-subhead h4 { margin: 0 0 3px; font-size: .82rem; color: #cfe0ec; }
.form-subhead p { margin: 0; font-size: .68rem; color: var(--muted); }

/* Confirmação inline de apagar (ConfirmButton) — sem diálogo do browser. */
.confirm-inline { display: inline-flex; align-items: center; gap: 8px; }
.confirm-q { font-size: .72rem; color: var(--amber); font-weight: 600; }

input, select, textarea {
    width: 100%; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px;
    background: #07111a; color: #fff; outline: 0; font-size: .82rem;
}
input:focus, select:focus, textarea:focus { border-color: var(--cyan); box-shadow: 0 0 0 3px rgba(0,200,255,.08); }
input:disabled, select:disabled { opacity: .55; }
input[type=checkbox] { width: 16px; height: 16px; }

.model-pick { margin-bottom: 16px; }
.model-pick-label { display: block; font-size: .72rem; color: #8397aa; margin-bottom: 10px; }
.model-strip { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; }
.model-chip { flex: 0 0 130px; display: grid; gap: 4px; padding: 8px; cursor: pointer; text-align: left;
              border: 1px solid var(--line); background: #0a1520; border-radius: 10px; color: var(--text); }
.model-chip:hover { border-color: var(--line2); }
.model-chip.on { border-color: var(--cyan); box-shadow: 0 0 0 2px rgba(0,200,255,.15); }
.model-chip-img { background: #060e17; border: 1px solid #16283a; border-radius: 6px; padding: 3px; }
.model-chip-img svg, .model-chip-img img { width: 100%; height: auto; display: block; }
.model-chip b { font-size: .72rem; }
.model-chip small { font-size: .6rem; color: var(--muted); }

.subform { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.subblock { margin-top: 16px; }
.subblock h4 { margin: 0 0 4px; font-size: .8rem; color: #cfe0ec; }

/* "cloud": este valor não veio da rede local. Discreta mas legível — não é um alarme, é
   uma qualificação do número que está ao lado. */
.et-cloud { display: inline-block; margin-left: 5px; padding: 1px 5px; border-radius: 999px;
            font-size: .55rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
            color: var(--cyan); background: rgba(0,200,255,.12);
            border: 1px solid color-mix(in srgb, var(--cyan) 35%, transparent); }

/* Configurado e calado. Âmbar e não vermelho: pode ser o agente que morreu e não o
   aparelho, e chamar avariado a um Shelly que está bom manda alguém ao sítio errado. */
.ac-tile.mudo { border-color: color-mix(in srgb, var(--amber) 45%, transparent);
                background: color-mix(in srgb, var(--amber) 8%, transparent); }
.ac-tile.mudo .act-valor { color: var(--amber); font-style: italic; }

/* O estado do relé, por cima dos botões que o vão mudar. */
.estado-saida { display: flex; align-items: baseline; gap: 9px; margin: 0 0 12px;
                padding: 10px 13px; border-radius: 10px; border: 1px solid var(--line);
                background: var(--panel2); }
.estado-saida .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted);
                     align-self: center; }
.estado-saida small { color: var(--muted); font-size: .68rem; }
.estado-saida.on { border-color: color-mix(in srgb, var(--green) 40%, transparent);
                   background: color-mix(in srgb, var(--green) 8%, transparent); }
.estado-saida.on .dot { background: var(--green); box-shadow: 0 0 9px var(--green); }
.estado-saida.on b { color: var(--green); }
/* Desligada não é vermelho: um interruptor está como alguém o deixou, e um vermelho que
   aparece quando está tudo bem ensina a ignorar vermelhos. */
.estado-saida.off .dot { background: #7891a6; }

/* Acesso remoto: a lista na configuração da estação, e as fichas no modal do cockpit. */
.acessos-editor { display: grid; gap: 8px; margin-top: 6px; }
.acesso-linha { display: grid; grid-template-columns: 1.2fr 118px 1.1fr 1.2fr auto; gap: 8px;
                align-items: center; }
@media (max-width: 760px) { .acesso-linha { grid-template-columns: 1fr; } }

.acessos-grid { display: grid; gap: 10px; }
.acesso { display: flex; align-items: center; justify-content: space-between; gap: 12px;
          padding: 11px 13px; border-radius: 12px; border: 1px solid var(--line);
          background: var(--panel2); }
.acesso-id { display: grid; gap: 3px; min-width: 0; }
.acesso-id code { font-family: var(--mono); font-size: .82rem; overflow-wrap: anywhere; }
.acesso-accao { flex: none; }
@media (max-width: 760px) {
    .acesso { flex-direction: column; align-items: stretch; }
    .acesso-accao .btn { width: 100%; }
}

/* Uma escrita declarada num equipamento SNMP. Fica em caixa e não em linha de tabela
   porque não é um par chave→valor: são sete campos que só fazem sentido juntos, e um
   deles em branco muda o que o comando faz. */
.escrita { margin: 10px 0; padding: 12px 14px; border-radius: 12px;
           border: 1px solid var(--line); background: var(--panel2); }
.escrita-topo { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.escrita-topo select { flex: 1 1 auto; min-width: 0; }
.escrita-topo .btn { flex: none; }
/* Declarada mas a recusar tudo: tem de se ver de longe que ali falta alguma coisa. */
.escrita.vazia { border-color: color-mix(in srgb, var(--amber) 45%, transparent);
                 background: color-mix(in srgb, var(--amber) 7%, transparent); }
.subblock .hint { margin: 0 0 10px; font-size: .68rem; color: #5f788c; line-height: 1.5; }
.subblock .hint code { font-family: var(--mono); color: #9ef4c2; }

.kv { display: grid; gap: 6px; }
.kv-head, .kv-row { display: grid; grid-template-columns: 1fr 1fr 34px; gap: 8px; align-items: center; }
.kv-head span { font-size: .6rem; text-transform: uppercase; letter-spacing: .08em; color: #688398; }

/* Acções à DIREITA, como em qualquer formulário sério: a acção principal é a última. */
.form-actions { display: flex; gap: 10px; margin-top: 18px; justify-content: flex-end; }
.form-error { margin-top: 14px; padding: 10px; border: 1px solid rgba(255,67,93,.35);
              background: rgba(255,67,93,.08); color: #ff93a1; border-radius: 8px; font-size: .76rem; }
.form-ok { margin-top: 14px; padding: 10px; border: 1px solid rgba(37,228,123,.3);
           background: rgba(37,228,123,.06); color: #9ef4c2; border-radius: 8px; font-size: .76rem; }

/* Painel do ar condicionado: o estado pedido em cima, o que a sala mede a seguir. */
.ac-now { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 12px 14px;
          border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.02); }
.ac-now b { font-size: .9rem; }
.ac-now .dot.off { background: #55697c; box-shadow: none; }

.ac-sala { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
           margin-top: 8px; padding: 10px 14px; border-radius: 10px;
           border: 1px solid rgba(0,200,255,.22); background: rgba(0,200,255,.05); }
.ac-sala span { font-size: .68rem; color: #7f97a9; }
.ac-sala b { font-size: .95rem; color: #cfe8f6; font-variant-numeric: tabular-nums; }
.ac-sala .stale { color: #ffb27a; }

/* Diz em texto o que a configuração vai fazer, porque a diferença é invisível. */
.mode-note { margin-top: 14px; padding: 10px 12px; border-radius: 8px; font-size: .72rem;
             line-height: 1.55; display: grid; gap: 3px; }
.mode-note b { font-size: .76rem; }
.mode-note.ok { border: 1px solid rgba(37,228,123,.28); background: rgba(37,228,123,.05); color: #9ef4c2; }
.mode-note.ok span { color: #7fae94; }
.mode-note.warn { border: 1px solid rgba(255,178,122,.3); background: rgba(255,178,122,.06); color: #ffc79c; }
.mode-note.warn span { color: #b58e70; }
.lbl-hint { color: #5f788c; font-weight: 400; }

.cfg-list { display: grid; gap: 8px; }
.cfg-row { display: flex; align-items: center; gap: 12px; padding: 12px 14px;
           border: 1px solid var(--line); background: #09131e; border-radius: 10px; }
.cfg-row.off { opacity: .5; }
.cfg-row .grow { flex: 1; min-width: 0; display: grid; }
.cfg-row .grow b { font-size: .84rem; display: flex; align-items: center; gap: 8px; }
.cfg-row .grow span { font-size: .68rem; color: var(--muted); margin-top: 2px; }
.cfg-row .avatar { width: 42px; height: 42px; border-radius: 10px; display: grid; place-items: center;
                   background: rgba(0,200,255,.08); border: 1px solid #1c6582; color: var(--cyan);
                   font-family: var(--mono); font-size: .72rem; font-weight: 700; flex: 0 0 auto; overflow: hidden; }
.cfg-row .avatar img, .avatar img { width: 100%; height: 100%; object-fit: cover; }
.cfg-row .avatar.ph { }
.freq { font-family: var(--mono); font-size: .62rem; color: var(--cyan); font-weight: 600; }
.warn-txt { color: var(--amber); font-size: .62rem; font-weight: 700; }
.preview { max-width: 120px; max-height: 60px; border-radius: 8px; border: 1px solid var(--line); margin-top: 6px; }

/* --- Autenticação -------------------------------------------------------- */

/* Cartão ao centro do ecrã, nos dois eixos. A fotografia é o cenário e ele é o assunto. */
.login-page { min-height: 100vh; position: relative; overflow: hidden; background: #04080f;
              display: grid; place-items: center;
              padding: 5vh max(20px, 4vw); box-sizing: border-box; }

/* A sala de controlo. `fixed` para não deslizar com o teclado do telemóvel a abrir. */
.login-bg { position: fixed; inset: 0; z-index: 0;
            background: url('login-bg.jpg') center/cover no-repeat; }
/* Véu: sem ele o texto branco perde-se nas zonas claras da fotografia. */
/* Véu leve e só do lado do cartão: a fotografia nova tem painéis desenhados à esquerda
   que se querem ver, e escurecê-los apagava o trabalho todo. */
.login-veil { position: fixed; inset: 0; z-index: 1; pointer-events: none;
    background: linear-gradient(90deg, rgba(4,8,15,.15) 0%, rgba(4,8,15,0) 40%, rgba(4,8,15,.45) 100%); }

/* A marca vive DENTRO do cartão. Sobre a fotografia colidia com os painéis que ela já
   traz desenhados, e o recorte da imagem muda com o formato do ecrã — o cartão é a única
   superfície onde se garante que fica legível.
   logo-login.png é o logo.png com o fundo preto recortado (nenhum dos ficheiros originais
   é transparente, e mix-blend-mode não pega dentro de um contexto com z-index). */
.login-logo { width: 354px; max-width: 92%; height: auto; display: block; margin: 0 auto 22px; }

/* O cartão: vidro sobre a fotografia, com um canto aceso em cima à esquerda.
   Largura própria porque já não vive numa coluna da grelha — está centrado. */
.login-card { width: min(460px, 100%); z-index: 2; position: relative; padding: 34px 34px 26px;
    border-radius: 18px; border: 1px solid rgba(120,180,215,.22);
    background: linear-gradient(160deg, rgba(13,27,42,.88), rgba(7,16,26,.92));
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    box-shadow: 0 30px 70px rgba(0,0,0,.55); }
.login-card::before, .login-card::after { content: ''; position: absolute; width: 26px; height: 26px;
    border: 2px solid #29d9ff; border-radius: 4px; }
.login-card::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; border-top-left-radius: 18px; }
.login-card::after { top: -1px; right: -1px; border-left: 0; border-bottom: 0; border-top-right-radius: 18px; }

/* Branco explícito: o h1 herdava a cor de título do tema e ficava escuro sobre escuro —
   lia-se só o "volta!" em ciano e o resto desaparecia. */
.login-card h1 { font-size: 1.85rem; text-align: center; margin: 0 0 6px; letter-spacing: -.02em;
                 color: #f2f8fc; }
.login-card h1 span { color: #29d9ff; }
.login-sub { text-align: center; color: #8ba5b8; font-size: .8rem; margin: 0 0 24px; }

.login-form { display: grid; gap: 13px; }
/* Campo com ícone dentro: o ícone não pode roubar o clique ao input. */
.field-icon { position: relative; display: flex; align-items: center; }
.field-icon > svg { position: absolute; left: 14px; width: 17px; height: 17px;
                    color: #6d879b; pointer-events: none; }
.field-icon input { width: 100%; box-sizing: border-box; padding: 14px 44px;
    border-radius: 11px; border: 1px solid rgba(120,180,215,.2); background: rgba(6,14,23,.7);
    color: #e6eef6; font: inherit; font-size: .86rem; }
.field-icon input::placeholder { color: #6d879b; }
.field-icon input:focus { outline: 0; border-color: rgba(41,217,255,.6);
    box-shadow: 0 0 0 3px rgba(41,217,255,.12); }
.field-icon .peek { position: absolute; right: 8px; width: 32px; height: 32px; display: grid;
    place-items: center; background: none; border: 0; color: #6d879b; cursor: pointer; }
.field-icon .peek svg { width: 17px; height: 17px; }
.field-icon .peek:hover { color: #29d9ff; }

.login-row { display: flex; align-items: center; justify-content: space-between; font-size: .76rem; }
.remember { display: flex; align-items: center; gap: 8px; color: #9db3c3; cursor: pointer; }
.remember input { width: 15px; height: 15px; accent-color: #00aeef; }

.login-go { width: 100%; justify-content: center; gap: 10px; padding: 14px; margin-top: 4px;
            font-size: .9rem; border-radius: 11px;
            background: linear-gradient(90deg, #0090d6, #29d9ff); border: 0; color: #04121c; font-weight: 600; }
.login-go:hover { filter: brightness(1.08); }

.login-error { padding: 11px; border: 1px solid rgba(255,77,97,.35);
    background: rgba(255,77,97,.1); color: #ff93a1; border-radius: 9px; font-size: .78rem; }

.login-foot { display: flex; align-items: center; justify-content: center; gap: 7px;
    margin-top: 22px; padding-top: 16px; border-top: 1px solid rgba(120,180,215,.14);
    color: #6d879b; font-size: .68rem; }

.login-copy { position: absolute; bottom: 16px; left: 0; right: 0; z-index: 2; text-align: center;
              color: #4d6a7f; font-size: .66rem; }

@media (max-width: 900px) {
    /* No telemóvel a fotografia passa a pano de fundo e o cartão manda. */
    .login-page { grid-template-columns: 1fr; gap: 26px; padding: 32px 20px 60px; }
    .login-side { text-align: center; margin: 0 auto; }
    .login-logo { width: 90%; max-width: 90%; }
    .login-claim { font-size: .92rem; }
    .login-card { width: 100%; max-width: 100%; box-sizing: border-box; padding: 28px 20px 22px; }
    .login-card h1 { font-size: 1.5rem; }
}
.logout { width: 34px; height: 34px; border-radius: 8px; border: 1px solid var(--line2);
    background: #101c29; color: #ff93a1; cursor: pointer; font-size: 1rem; }
.logout:hover { border-color: var(--red); }

.auth-loading { padding: 60px; text-align: center; color: var(--muted); }

/* auditoria */
.audit { display: grid; gap: 2px; }
.audit-head, .audit-row { display: grid; grid-template-columns: 130px 160px 150px 1fr 1.4fr; gap: 12px;
    align-items: center; padding: 9px 12px; }
.audit-head { color: #688398; font-size: .62rem; text-transform: uppercase; letter-spacing: .08em;
    border-bottom: 1px solid var(--line); }
.audit-row .act { font-weight: 600; color: var(--cyan); }
.audit-row.bad .act { color: var(--red); }
.audit-row .det { color: var(--muted); font-size: .68rem; }

/* --- Comandos ------------------------------------------------------------ */
.cmd-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px,1fr)); gap: 12px; margin-top: 8px; }
.cmd-card { border: 1px solid var(--line); background: #0a1520; border-radius: 12px; padding: 15px;
    display: grid; gap: 8px; border-left: 3px solid var(--green); }
.cmd-card.risk-Fisico { border-left-color: var(--amber); }
.cmd-card.risk-Critico { border-left-color: var(--red); opacity: .85; }
.cmd-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.cmd-head b { font-size: .9rem; }
.cmd-card p { margin: 0; color: #9cb0bf; font-size: .78rem; line-height: 1.5; }
.risk-tag { font-size: .56rem; font-weight: 700; letter-spacing: .06em; padding: 3px 8px; border-radius: 999px; }
.risk-tag.risk-Seguro { color: var(--green); background: rgba(37,228,123,.1); }
.risk-tag.risk-Fisico { color: var(--amber); background: rgba(255,176,32,.1); }
.risk-tag.risk-Critico { color: var(--red); background: rgba(255,67,93,.1); }
.cmd-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 4px; }
.cmd-rev { font-size: .62rem; color: var(--muted); }
.cmd-human { font-size: .64rem; color: var(--red); font-weight: 600; }
.empty-inline { padding: 20px; text-align: center; color: var(--muted); font-size: .82rem; }

/* --- Tabelas, barras e pills (automações, relatórios, gravações) --------- */

.grid-table { width: 100%; border-collapse: collapse; font-size: .82rem; }
.grid-table th { text-align: left; padding: 8px 10px; color: var(--muted); font-weight: 700; font-size: .68rem; text-transform: uppercase; letter-spacing: .05em; border-bottom: 1px solid var(--line); }
.grid-table td { padding: 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.grid-table tr:last-child td { border-bottom: none; }
.grid-table code { font-size: .74rem; color: #b98bff; background: rgba(150,110,255,.1); padding: 1px 6px; border-radius: 5px; }
.grid-table .row-off { opacity: .5; }
.grid-table td.num, .grid-table th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.grid-table td.row-actions { text-align: right; white-space: nowrap; }
/* "já está configurado? qual é" — vai a seguir aos botões e não os pode empurrar para fora
   da linha. Sem cores próprias: herda o tema como o resto da tabela. */
.lig-existente { max-width: 190px; margin-left: 6px; font-size: .72rem; vertical-align: middle; }

/* A sonda ao router. Sem cores próprias — herda o tema, como o bloco dos descobertos.
   A amostra é a única coisa com regra a sério: são páginas inteiras encolhidas a uma linha,
   e sem quebra forçada esticavam a tabela para fora do ecrã. */
.sonda-router { margin-top: 14px; }
.sr-topo { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.sr-host { width: 150px; font-size: .78rem; }
.sr-amostra { display: block; max-width: 46ch; overflow-wrap: anywhere; font-size: .68rem; line-height: 1.35; }

/* O ficheiro do router, inteiro. Rola dentro da própria caixa: um .js de 20 KB solto
   arrastava a página para o lado e desalinhava a tabela toda. */
.sr-codigo { display: block; max-height: 22rem; max-width: 46ch; overflow: auto; white-space: pre-wrap;
             overflow-wrap: anywhere; font-size: .66rem; line-height: 1.35;
             background: var(--bg); padding: 8px; border-radius: 6px; margin: 6px 0 0; }

/* Página Dispositivos: uma linha por equipamento, colunas alinhadas.
   Larguras fixas para as colunas caírem no mesmo sítio em TODAS as estações — com
   larguras automáticas cada painel media-se a si próprio e os cabeçalhos dançavam. */
.dev-table { table-layout: fixed; }
.dev-table th:nth-child(1), .dev-table td:nth-child(1) { width: 26%; }
.dev-table th:nth-child(2), .dev-table td:nth-child(2) { width: 16%; }
.dev-table th:nth-child(3), .dev-table td:nth-child(3) { width: 10%; }
.dev-table th:nth-child(4), .dev-table td:nth-child(4) { width: 26%; }
.dev-table th:nth-child(5), .dev-table td:nth-child(5) { width: 12%; }
.dev-table th:nth-child(6), .dev-table td:nth-child(6) { width: 10%; }
.dev-table td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dev-table td.vals { color: var(--muted); font-family: var(--mono); font-size: .74rem; }
.dev-table .tag {
    display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: .64rem;
    font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
    color: var(--cyan); background: rgba(0,200,255,.10); border: 1px solid rgba(0,200,255,.22);
}
.dev-table time { color: var(--muted); font-size: .74rem; font-variant-numeric: tabular-nums; }
/* Interface do recetor dentro do cockpit: quase ecrã inteiro, é uma página completa. */
.modal.ui-modal { max-width: min(1200px, 96vw); width: 96vw; height: 88vh; max-height: 88vh; }
.fmdx-frame { flex: 1; width: 100%; border: 0; background: #0b1420; border-radius: 0 0 14px 14px; }

/* Árvore SNMP lida do equipamento (mapear OIDs à mão). */
.walk-tools { display: flex; gap: 8px; margin: 8px 0; }
.walk-tools input { flex: 1; }
.walk-tools select { min-width: 190px; }
.walk-list { max-height: 320px; overflow: auto; border: 1px solid var(--line); border-radius: 10px; }
.walk-list td.vals { font-family: var(--mono); font-size: .72rem; color: var(--muted); }

/* Lista de equipamentos vinda da conta Shelly Cloud. */
.cloud-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 8px; margin: 8px 0 10px; }
.cloud-item { display: flex; align-items: center; gap: 8px; padding: 9px 11px; cursor: pointer;
    border: 1px solid var(--line); border-radius: 10px; background: var(--panel); text-align: left; }
.cloud-item:hover:not(:disabled) { border-color: var(--cyan); }
.cloud-item.no-ip { opacity: .5; cursor: default; }
.cloud-item b { font-size: .78rem; }
.cloud-item small { color: var(--muted); font-size: .66rem; margin-left: auto; }

/* Setas de reordenar (lista de estações). */
.order-arrows { display: grid; gap: 2px; margin-right: 2px; }
.order-arrows button {
    width: 24px; height: 18px; padding: 0; cursor: pointer; font-size: .6rem; line-height: 1;
    border: 1px solid var(--line); border-radius: 5px; background: var(--panel); color: var(--muted);
}
.order-arrows button:hover:not(:disabled) { border-color: var(--cyan); color: var(--cyan); }
.order-arrows button:disabled { opacity: .3; cursor: default; }

/* Construtor da cadeia por estação (backoffice). */
.chain-builder { display: grid; gap: 12px; }
.chain-picked { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; min-height: 40px;
    padding: 10px; border: 1px dashed var(--line2); border-radius: 10px; }
.chain-picked .dim { color: var(--muted); font-size: .74rem; }
.chain-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 8px; border-radius: 8px;
    background: rgba(0,200,255,.10); border: 1px solid rgba(0,200,255,.28); color: var(--cyan);
    font-size: .74rem; font-weight: 700; }
.chain-chip b { display: inline-grid; place-items: center; width: 17px; height: 17px; border-radius: 50%;
    background: rgba(0,200,255,.22); font-size: .6rem; }
.chain-chip button { border: 0; background: transparent; color: inherit; cursor: pointer; padding: 0 2px;
    font-size: .74rem; opacity: .75; }
.chain-chip button:hover:not(:disabled) { opacity: 1; }
.chain-chip button:disabled { opacity: .25; cursor: default; }
.chain-chip button.x { color: var(--red); }
.chain-arrow { color: var(--muted); font-size: .8rem; }
.chain-add { display: flex; flex-wrap: wrap; gap: 6px; }

/* Barra de filtros (histórico de alertas) — alinhada, quebra em ecrã estreito. */
.filter-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; padding: 4px 0 14px; }
.filter-bar label { display: grid; gap: 5px; font-size: .68rem; color: var(--muted); font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em; }
.filter-bar select, .filter-bar input { min-width: 150px; }
.filter-bar .btn { margin-left: auto; }

.hist-table td small.dim { color: var(--muted); font-size: .68rem; }
.hist-table time { font-variant-numeric: tabular-nums; }
.sev-pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: .62rem;
    font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.sev-pill.sev-emergencia, .sev-pill.sev-critico { background: rgba(255,67,93,.14); color: var(--red); border: 1px solid rgba(255,67,93,.34); }
.sev-pill.sev-aviso { background: rgba(255,176,32,.14); color: var(--amber); border: 1px solid rgba(255,176,32,.34); }
.sev-pill.sev-info { background: rgba(0,200,255,.12); color: var(--cyan); border: 1px solid rgba(0,200,255,.3); }

.panel-actions { display: inline-flex; align-items: center; gap: 10px; }
.muted-count { color: var(--muted); font-size: .72rem; }
.muted { color: var(--muted); font-size: .74rem; }

/* Um controlo sem rotulo obriga o utilizador a adivinhar. Estes explicam-se. */
.inline-lbl { display: grid; gap: 4px; font-size: .64rem; color: var(--muted);
              text-transform: uppercase; letter-spacing: .05em; }
.inline-lbl.grow { flex: 1; min-width: 160px; }
.inline-lbl select, .inline-lbl input { text-transform: none; letter-spacing: 0;
                                        font-size: .8rem; color: inherit; }
.passos { margin: 0 0 10px; }
.passos b { color: var(--accent, #00c8ff); }

/* A versao do agente ao lado do botao de descarregar: e por este numero que se sabe
   se o ficheiro ja tem a correccao la dentro. */
.agent-ver { display: inline-block; margin-right: 8px; padding: 2px 8px; border-radius: 20px;
             font-size: .66rem; letter-spacing: .04em;
             color: #9ef4c2; border: 1px solid rgba(37,228,123,.32); background: rgba(37,228,123,.09); }
:root[data-theme="light"] .agent-ver { color: #0a6b3d; border-color: rgba(10,107,61,.32); background: rgba(37,228,123,.14); }

/* --- Ficha do equipamento -------------------------------------------------------- */
/* Um nome que abre uma ficha tem de parecer clicável sem parecer um botão. */
.link-cell { background: none; border: 0; padding: 0; cursor: pointer; color: inherit;
             font: inherit; text-align: left; border-bottom: 1px dashed transparent; }
.link-cell:hover { color: var(--accent, #00c8ff); border-bottom-color: currentColor; }

z-index: 2; position: relative; padding: 34px 32px 26px; { max-width: min(920px, 94vw); width: 100%; }
.dev-head { display: flex; align-items: center; gap: 12px; }
.dev-head h3 { margin: 0; }
.dev-icon { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 10px;
            background: rgba(0,200,255,.1); border: 1px solid rgba(0,200,255,.25);
            color: #7fd8f5; font-size: .72rem; font-weight: 700; letter-spacing: .02em; }
.dev-sub { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
           font-size: .72rem; color: var(--muted); }

.dev-state { display: flex; align-items: center; gap: 10px; padding: 10px 14px;
             border-radius: 10px; border: 1px solid var(--line); font-size: .78rem; }
.dev-state span:last-child { color: var(--muted); font-size: .72rem; }
.dev-state.ok { border-color: rgba(37,228,123,.28); background: rgba(37,228,123,.05); }
.dev-state.bad { border-color: rgba(255,67,93,.3); background: rgba(255,67,93,.06); }

.dev-tabs { display: flex; gap: 4px; margin: 16px 0 12px; border-bottom: 1px solid var(--line); }
.dev-tabs button { background: none; border: 0; border-bottom: 2px solid transparent;
                   padding: 8px 14px; cursor: pointer; color: var(--muted);
                   font: inherit; font-size: .78rem; }
.dev-tabs button:hover { color: #cfe0ec; }
.dev-tabs button.on { color: #e6eef6; border-bottom-color: var(--accent, #00c8ff); }
.dev-tabs .pill { margin-left: 6px; padding: 1px 6px; border-radius: 10px;
                  background: rgba(255,67,93,.18); color: #ff93a1; font-size: .66rem; }

.dev-grid { display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.dev-cell { padding: 12px; border: 1px solid var(--line); border-radius: 10px;
            background: rgba(255,255,255,.02); display: grid; gap: 4px; }
.dev-cell .metric { font-size: .68rem; color: var(--muted); text-transform: uppercase;
                    letter-spacing: .05em; }
.dev-cell .value { font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.dev-cell small.vals { font-size: .62rem; color: #52697c; }
.dev-cell.bad { border-color: rgba(255,67,93,.35); background: rgba(255,67,93,.05); }
.dev-cell.warn { border-color: rgba(255,178,122,.3); background: rgba(255,178,122,.05); }

/* UMA MEDIÇÃO PARADA NÃO PODE TER O ASPECTO DE UM VALOR ACTUAL.
 *
 * Sem isto, o contador de erros do Seixal mostrou 10 durante um dia inteiro, no mesmo tipo
 * de cartão e com a mesma cor dos que estavam a chegar naquele segundo. Fica apagada e com
 * a idade colada por baixo: continua a ler-se, e deixa de se acreditar nela. */
.dev-cell.parada { border-style: dashed; opacity: .6; }
.dev-cell.parada .value { color: var(--muted); }
.dev-cell small.parou { font-size: .62rem; color: var(--amber); }
.dev-cell small.tipico { font-size: .66rem; color: var(--muted); }

/* Medidor ou botao, na lista de widgets do Stereo Tool. */
.wtag { margin-left: 6px; padding: 1px 6px; border-radius: 10px; font-size: .62rem;
        text-transform: uppercase; letter-spacing: .04em; white-space: nowrap; }
.wtag.mede  { background: rgba(120,220,160,.16); color: #7bd8a4; }
.wtag.manda { background: rgba(255,255,255,.06); color: var(--muted); }

.row-open td { background: rgba(255,67,93,.06); }

/* --- Estado geral das estações --------------------------------------------------- */
.estacoes-geral { margin-bottom: 16px; }
.tabela-wrap { overflow-x: auto; }
/* LARGURAS FIXAS, e não "o que o conteúdo pedir".
 *
 * Com `table-layout: auto` cada coluna era medida pelo que tinha dentro, e o que tem dentro
 * MUDA: uma estação com "stream OK" alargava aquela coluna, a do lado ficava com os pontos
 * e encolhia, e os títulos por cima ficavam desalinhados uns dos outros. Pior: mudava
 * sozinho ao longo do dia, conforme os valores que chegavam.
 *
 * Os elos da cadeia não levam largura declarada de propósito — em layout fixo, as colunas
 * sem largura repartem o que sobra EM PARTES IGUAIS. É o que se quer: são todas a mesma
 * coisa, e passam a encolher juntas em vez de se atropelarem.
 *
 * E a tabela deixa de precisar de 1113px. Precisava disso em qualquer ecrã, e num monitor de
 * 1769px só recebia 1103 — falhava por dez pixels, com barra de rolamento lateral. */
/* Os 900px continuam a fazer falta, e agora por uma razão medida: com sete elos, é abaixo
   disto que cada um cai dos 64px de que os quatro pontos precisam. Em layout fixo, um
   `min-width` na célula não estica a tabela — quem manda é este. Abaixo, rola. */
.estacoes-table { width: 100%; min-width: 1010px; table-layout: fixed; }
.estacoes-table thead th:first-child { width: 148px; }
.estacoes-table thead th.num { width: 72px; }
/* O estado é a penúltima: rótulos curtos e sempre do mesmo tamanho. */
.estacoes-table thead th:nth-last-child(2) { width: 84px; }
.estacoes-table thead th.col-icons { width: 266px; }
/* Um chão para os elos: 64px é o que os quatro pontos ocupam, e eles não dobram.
 *
 * Abaixo disto os pontos ficavam cortados — e um ponto que falta lê-se como um elo que não
 * existe, que é uma mentira sobre a cadeia. Com este mínimo, quando já não cabe, a tabela
 * ganha barra de rolamento em vez de esconder informação. Rolar é honesto; cortar não é.
 * Cresce sozinho com o número de elos, que varia de estação para estação. */
.estacoes-table th.col-elo { text-align: center; font-size: .6rem; min-width: 64px; }
.estacoes-table td { padding-top: 13px; padding-bottom: 13px; }
/* DOBRA A LINHA em vez de cortar o valor.
 *
 * Com as colunas de largura fixa, um "78,6 dBm" numa coluna de 60px ficava cortado a meio —
 * e um número cortado não é um número mais curto, é um número ERRADO. Numa tabela que se lê
 * de relance para decidir se se vai a um monte de madrugada, isso é a pior coisa que este
 * ecrã pode fazer. A dobrar, o valor sobrevive inteiro e a linha cresce um pouco. */
.estacoes-table td.col-elo { text-align: center; white-space: normal; overflow-wrap: anywhere; }
/* Os números da cadeia são o conteúdo da tabela, não legendas — pesam e lêem-se.
   Estava aqui um #cfe0ec fixo: escolhido para o fundo escuro, no tema claro ficava
   quase branco sobre branco. Os únicos valores reais da linha eram os menos visíveis.
   var(--text) resolve nos dois temas de uma vez. */
.estacoes-table td.col-elo .valor { font-size: .9rem; font-weight: 650;
                                    font-variant-numeric: tabular-nums;
                                    letter-spacing: -.01em; color: var(--text); }
.estacoes-table td.col-elo .valor.mau { color: var(--red); font-weight: 750; }
.estacoes-table td.col-elo .valor.aviso { color: #d97a1f; font-weight: 700; }
:root[data-theme="dark"] .estacoes-table td.col-elo .valor.aviso,
:root:not([data-theme="light"]) .estacoes-table td.col-elo .valor.aviso { color: #ffb27a; }
/* "Não estou a medir isto" continua discreto de propósito: é ausência, não valor. */
.estacoes-table td.col-elo .valor.sem-dados { color: var(--muted); opacity: .55; font-weight: 500; }

/* A estação numa janela: larga, com o conteúdo a rolar por dentro e o ✕ sempre à vista. */
/* Larga porque a cadeia de emissão é horizontal: 1600 px cabem os sete elos lado a lado
   sem rolar. min() em vez de max-width fixo para no portátil encolher em vez de cortar. */
/* ".modal.station-modal" e não só ".station-modal": a regra base .modal aparece MAIS
   ABAIXO neste ficheiro com max-width: 520px e, com a mesma especificidade, ganhava —
   a janela da estação saía estreita e a cadeia ficava a rolar. */
.modal.station-modal { width: min(1600px, 90vw); max-width: none; height: 90vh; max-height: 90vh;
                       padding: 0; display: flex; flex-direction: column; position: relative; }
.modal.station-modal > .station { margin: 0; border-radius: 14px 14px 0 0; overflow-y: auto; flex: 1; }
.station-modal-x { position: absolute; top: 14px; right: 14px; z-index: 3; }
.modal.station-modal .modal-foot { border-top: 1px solid var(--line); }

/* Identidade da estação: logo + nome + frequência, como numa lista de canais. */
.col-estacao { display: flex; align-items: center; gap: 11px; }
.est-logo { width: 34px; height: 34px; border-radius: 9px; object-fit: cover; flex: none;
            border: 1px solid var(--line); background: #0b1622; }
.est-logo.ph { display: grid; place-items: center; font-size: .68rem; font-weight: 700;
               color: #7fd8f5; border-color: rgba(0,200,255,.25); background: rgba(0,200,255,.08); }
.est-id b { display: block; letter-spacing: .01em; }
.est-id span { font-size: .68rem; color: var(--muted); }

/* Medidor de 4 segmentos: mostra QUANTO, não só se está bom. */
/* Todos os segmentos com a MESMA geometria: 9px, sem contorno. O apagado usa um
   box-shadow para dentro em vez de border — um contorno mudava o tamanho aparente e
   os que não estavam verdes pareciam maiores que os outros. */
.medidor { display: inline-flex; gap: 5px; align-items: center; }
.medidor .seg { display: block; width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px;
                background: #25e47b; box-shadow: 0 0 6px rgba(37,228,123,.4); }
.medidor .seg.aviso { background: #ffb27a; box-shadow: 0 0 6px rgba(255,178,122,.4); }
.medidor .seg.mau { background: #ff435d; box-shadow: 0 0 6px rgba(255,67,93,.45); }
/* currentColor herda a cor do texto da célula, por isso funciona nos dois temas:
   um cinzento fixo ficava invisível no escuro ou agressivo no claro. */
.medidor .seg.vazio { background: transparent; box-shadow: inset 0 0 0 1px currentColor;
                      color: var(--muted); opacity: .45; }
.medidor .seg.sem-dados { background: currentColor; color: var(--muted); opacity: .35; box-shadow: none; }

/* O ponto é o que se varre com os olhos. Tem de ser legível a um metro do ecrã. */
.ponto { display: inline-block; width: 10px; height: 10px; border-radius: 50%;
         background: #2f4759; }
.ponto.bom { background: #25e47b; box-shadow: 0 0 8px rgba(37,228,123,.5); }
.ponto.aviso { background: #ffb27a; box-shadow: 0 0 8px rgba(255,178,122,.5); }
.ponto.mau { background: #ff435d; box-shadow: 0 0 10px rgba(255,67,93,.6); }
.ponto.sem-dados { background: #2f4759; box-shadow: none; }

.estacoes-table .idade { font-size: .72rem; color: var(--muted); }
.estacoes-table .idade.velha { color: #ffb27a; }
/* Nome próprio, não ".vazio": esse nome já era dos segmentos apagados do medidor, e o
   padding desta regra aplicava-se-lhes — 9 px de bola passavam a 44 px. */
.estacoes-table .sem-linhas { text-align: center; color: var(--muted); padding: 22px; }
.linha-aviso td { background: rgba(255,178,122,.05); }
.linha-ma td { background: rgba(255,67,93,.06); }

.badge { display: inline-block; padding: 3px 9px; border-radius: 20px;
         font-size: .62rem; letter-spacing: .06em; font-weight: 600; border: 1px solid; }
.badge.no-ar { color: #9ef4c2; border-color: rgba(37,228,123,.35); background: rgba(37,228,123,.08); }
.badge.degradada { color: #ffc79c; border-color: rgba(255,178,122,.4); background: rgba(255,178,122,.1); }
.badge.critica { color: #ff93a1; border-color: rgba(255,67,93,.42); background: rgba(255,67,93,.1); }
.badge.offline { color: #94a8b8; border-color: rgba(148,168,184,.35); background: rgba(148,168,184,.08); }

/* No tema claro estas pastilhas eram claro-sobre-claro e não se liam. Texto escuro e
   contorno mais firme; a cor continua a ser a mesma linguagem, só legível. */
:root[data-theme="light"] .badge.no-ar { color: #0a6b3d; border-color: rgba(10,107,61,.35); background: rgba(37,228,123,.16); }
:root[data-theme="light"] .badge.degradada { color: #8a4a0a; border-color: rgba(138,74,10,.35); background: rgba(255,178,122,.22); }
:root[data-theme="light"] .badge.critica { color: #a11226; border-color: rgba(161,18,38,.35); background: rgba(255,67,93,.16); }
:root[data-theme="light"] .badge.offline { color: #3f5364; border-color: rgba(63,83,100,.3); background: rgba(148,168,184,.16); }

/* Mesmo problema, mesmas causas: ciano claro sobre branco. */
:root[data-theme="light"] .est-logo.ph { color: #0b6b86; border-color: rgba(11,107,134,.3); background: rgba(0,174,239,.12); }
:root[data-theme="light"] .dev-icon { color: #0b6b86; border-color: rgba(11,107,134,.28); background: rgba(0,174,239,.1); }
:root[data-theme="light"] .est-logo { background: #f3f7fb; }

/* Diário de bordo: linhas densas, a gravidade a dar a cor pela esquerda. */
.log-list { display: grid; gap: 2px; max-height: 46vh; overflow-y: auto; }
.log-row { display: grid; grid-template-columns: 130px 1fr auto auto; gap: 10px;
           align-items: baseline; padding: 8px 10px; border-left: 3px solid #2a4258;
           background: rgba(255,255,255,.02); border-radius: 0 6px 6px 0; font-size: .76rem; }
.log-row time { color: var(--muted); font-size: .7rem; font-variant-numeric: tabular-nums; }
.log-row div span { display: block; color: var(--muted); font-size: .7rem; margin-top: 2px; }
.log-row.aviso { border-left-color: #ffb27a; }
.log-row.critico, .log-row.emergencia { border-left-color: #ff435d; background: rgba(255,67,93,.05); }

/* Etiqueta de "isto ainda não está confirmado". Tem de se distinguir de um facto. */
.tag.warn { border-color: rgba(255,178,122,.4); background: rgba(255,178,122,.1); color: #ffc79c; }

.bar-wrap { position: relative; display: flex; align-items: center; gap: 8px; min-width: 160px; }
.bar-wrap { background: rgba(255,255,255,.05); border-radius: 6px; height: 18px; overflow: hidden; }
.bar-wrap .bar { height: 100%; border-radius: 6px; transition: width .3s; }
.bar-wrap span { position: absolute; right: 8px; font-size: .72rem; font-weight: 700; color: var(--ink); }

.kpi-strip { display: flex; gap: 12px; flex-wrap: wrap; }
.kpi-strip .kpi { flex: 1; min-width: 140px; background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px; display: flex; flex-direction: column; gap: 4px; }
.kpi-strip .kpi span { font-size: .64rem; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; }
.kpi-strip .kpi b { font-size: 1.4rem; }

.pill { font-size: .62rem; font-weight: 700; padding: 3px 9px; border-radius: 999px; color: var(--muted); background: rgba(255,255,255,.06); }
.pill.ok { color: var(--green); background: rgba(37,228,123,.1); }
.pill.bad { color: var(--red); background: rgba(255,67,93,.1); }

/* Extras / plugins */
.plugins { display: grid; gap: 12px; }
.plugin { display: flex; align-items: center; gap: 14px; padding: 16px 18px; }
.plugin.on { border-color: rgba(37,228,123,.4); }
.plugin-icon { font-size: 1.5rem; color: var(--cyan); flex: 0 0 auto; }
.plugin-body { flex: 1; min-width: 0; }
.plugin-body b { display: flex; align-items: center; gap: 10px; font-size: .9rem; }
.plugin-body p { margin: 4px 0 0; font-size: .74rem; color: var(--muted); line-height: 1.45; }
.plugin-ro { font-size: .72rem; color: var(--muted); }

/* Interruptor on/off reutilizável */
.switch { position: relative; display: inline-block; width: 44px; height: 24px; flex: 0 0 auto; cursor: pointer; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider { position: absolute; inset: 0; background: #33475a; border-radius: 999px; transition: background .15s; }
.switch .slider::before { content: ""; position: absolute; height: 18px; width: 18px; left: 3px; top: 3px;
    background: #fff; border-radius: 50%; transition: transform .15s; }
.switch input:checked + .slider { background: var(--green); }
.switch input:checked + .slider::before { transform: translateX(20px); }

/* Botão de tema na barra de topo — ícone dado pelo tema real. */
.theme-btn::before { content: "☾"; font-size: .95rem; }
:root[data-theme="dark"] .theme-btn::before { content: "☀"; }
/* Botão de ajuda */
.help-btn { font-weight: 700; }
/* Chip "no ar" apagado quando não há sites ligados. */
.chip.live.off span { background: var(--muted); animation: none; }
.chip.live.off { color: var(--muted); }

/* Janelas modais (alertas, ajuda) — abrem por cima, sem mudar de página. */
.modal-scrim { position: fixed; inset: 0; background: rgba(3,8,14,.55); backdrop-filter: blur(2px);
    display: flex; align-items: flex-start; justify-content: center; padding: 68px 16px 16px; z-index: 200; }
.modal { width: 100%; max-width: 520px; max-height: 82vh; display: flex; flex-direction: column;
    background: var(--panel); border: 1px solid var(--line2); border-radius: 14px; box-shadow: var(--shadow); overflow: hidden; }
.modal.help-modal { max-width: 640px; }
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 14px 16px; border-bottom: 1px solid var(--line); }
.modal-head h3 { margin: 0; font-size: .95rem; display: flex; align-items: center; gap: 10px; }
.modal-body { padding: 14px 16px; overflow-y: auto; }
.modal-foot { padding: 12px 16px; border-top: 1px solid var(--line); display: flex; justify-content: flex-end; gap: 8px; }

/* Modal de formulário (criar/editar no backoffice): largo, com corpo que rola. */
.modal.form-modal { max-width: 780px; max-height: 88vh; }
.modal.form-modal .modal-body { padding: 18px; }
.modal.form-modal .form { margin: 0; }
.modal.form-modal .panel-title { display: none; }
.modal.form-modal .form-actions { margin-top: 0; }

/* (modal intermédio do mapa removido — o mapa abre a estação directamente) */
.help-lead { margin: 0 0 14px; font-size: .82rem; color: var(--text); line-height: 1.5; }
.help-sec { padding: 10px 0; border-top: 1px solid var(--line); }
.help-sec b { font-size: .8rem; color: var(--cyan); }
.help-sec p { margin: 4px 0 0; font-size: .76rem; color: var(--muted); line-height: 1.5; }
@media (max-width: 640px) { .modal-scrim { padding: 12px; align-items: center; } }

.form-info { padding: 12px 14px; border-radius: 10px; font-size: .8rem; color: #9cc4e8; background: rgba(90,160,240,.08); border: 1px solid rgba(90,160,240,.2); }
.heading-actions { display: flex; align-items: center; gap: 10px; }

/* --- Copilot ------------------------------------------------------------- */

.copilot { display: flex; flex-direction: column; height: calc(100vh - 210px); min-height: 420px; max-width: 900px; border: 1px solid var(--line); border-radius: 14px; background: var(--panel); overflow: hidden; }
.copilot-thread { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
.copilot-empty { color: var(--muted); font-size: .82rem; margin: auto 0; }
.copilot-empty p { margin: 0 0 10px; }
.copilot-suggests { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { border: 1px solid var(--line); background: rgba(255,255,255,.02); color: var(--ink); font-size: .74rem; padding: 8px 12px; border-radius: 999px; cursor: pointer; transition: border-color .15s, background .15s; }
.chip:hover:not(:disabled) { border-color: #966eff; background: rgba(150,110,255,.08); }
.chip:disabled { opacity: .5; cursor: default; }
.copilot-msg { display: flex; flex-direction: column; gap: 4px; max-width: 88%; }
.copilot-msg.me { align-self: flex-end; align-items: flex-end; }
.copilot-msg.ai { align-self: flex-start; }
.copilot-who { font-size: .58rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.copilot-body { white-space: pre-wrap; line-height: 1.55; font-size: .86rem; padding: 11px 14px; border-radius: 12px; }
.copilot-msg.me .copilot-body { background: rgba(150,110,255,.14); border: 1px solid rgba(150,110,255,.25); }
.copilot-msg.ai .copilot-body { background: rgba(255,255,255,.03); border: 1px solid var(--line); }
.copilot-thinking { color: var(--muted); font-style: italic; }
.copilot-input { display: flex; gap: 10px; padding: 14px; border-top: 1px solid var(--line); }
.copilot-input input { flex: 1; }

/* --- Mobile (tablet e telemóvel) ---------------------------------------- */
/* O layout é desktop-first; aqui só se ajusta o que não cabe em ecrã estreito.
   Nada disto toca no desktop — está tudo dentro de media queries. */

/* Barra móvel e escurecimento: escondidos no desktop, ligados nas media queries. */
.mobile-bar { display: none; }
.scrim { display: none; }

@media (max-width: 1024px) {
    /* Backstop contra scroll horizontal do corpo. 'clip' não parte o sticky. */
    html, body { overflow-x: clip; }

    /* --- Menu próprio do telemóvel: barra de topo + gaveta lateral --- */
    .mobile-bar {
        display: flex; align-items: center; gap: 12px;
        position: sticky; top: 0; z-index: 40; height: 56px; padding: 10px 14px;
        background: rgba(5,10,17,.92); backdrop-filter: blur(18px);
        border-bottom: 1px solid var(--line);
    }
    .mobile-logo { height: 26px; width: auto; }
    .hamburger {
        width: 40px; height: 40px; padding: 0; cursor: pointer;
        border: 1px solid var(--line); border-radius: 10px; background: #101c29;
        display: grid; place-content: center; gap: 4px;
    }
    .hamburger span { display: block; width: 18px; height: 2px; background: #cfe0ec; border-radius: 2px; }

    .shell { display: block; }

    /* A barra lateral vira gaveta: fora do ecrã até se abrir o menu. */
    .sidebar {
        position: fixed; top: 0; left: 0; z-index: 50;
        width: 268px; max-width: 84vw; height: 100vh; height: 100dvh; overflow-y: auto;
        transform: translateX(-100%); transition: transform .22s ease;
        border-right: 1px solid var(--line);
    }
    .shell.menu-open .sidebar { transform: translateX(0); }

    /* Na gaveta a navegação é uma LISTA vertical (o breakpoint antigo de 900px punha-a
       horizontal e só cabiam Cockpit/Estações). Aqui repõe-se vertical, a menu a sério. */
    .nav-menu { display: grid; gap: 4px; overflow: visible; margin-top: 22px; }
    .nav-menu a { flex: none; }

    /* Fundo escurecido por trás da gaveta; tocar fecha. */
    .scrim {
        display: block; position: fixed; inset: 0; z-index: 45;
        background: rgba(0,0,0,.5); opacity: 0; pointer-events: none; transition: opacity .22s;
    }
    .shell.menu-open .scrim { opacity: 1; pointer-events: auto; }

    /* A barra de topo da página deixa de ser fixa (não colide com a barra móvel). */
    .topbar { position: static; height: auto; flex-wrap: wrap; gap: 8px; padding: 8px 14px; }
    .content { padding: 16px 14px 48px; }

    .heading-actions { flex-wrap: wrap; }

    /* Grelhas lado a lado passam a caber: no máximo duas colunas. */
    .kpi-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }

    /* Lista "Estações" abaixo do mapa é redundante no mobile (já há a caixa Estações + os estúdios). */
    .stations-list-panel { display: none; }

    /* Tabelas largas deslizam DENTRO do painel, não empurram a página. */
    .panel { overflow-x: auto; }
    .grid-table { min-width: 480px; }

    .copilot { height: calc(100vh - 200px); }
}

@media (max-width: 520px) {
    /* Em telemóvel estreito, tudo numa coluna. */
    .kpi-grid,
    .cmd-grid,
    .field-grid { grid-template-columns: 1fr; }
    .kpi-strip .kpi { min-width: 100%; }
    /* Menos ruído na barra de topo estreita. */
    .topbar-search, .chip.live, .chip.clock { display: none; }
}

/* --- FMDX ao vivo -------------------------------------------------------- */

.live-tag { font-size: .56rem; font-weight: 700; letter-spacing: .06em; color: var(--green); }
.fmdx-sig { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; font-size: .72rem; color: var(--muted); }
.fmdx-sig b { color: var(--text); font-family: var(--mono); }
.rds-row .pi-ok { color: var(--green); font-style: normal; margin-left: 6px; }
.rds-row .pi-bad { color: var(--red); font-style: normal; margin-left: 6px; }
.rds-row .tx { font-size: .74rem; color: #9ef4c2; font-weight: 600; }
/* Valor RDS nas tiles do FMDX: o RT pode ser longo, por isso mais pequeno e quebra linha. */
.tile .value.rds-val { font-size: .82rem; line-height: 1.25; word-break: break-word; }
.tile-add { font-size: .62rem; color: var(--green); font-weight: 700; margin-left: 4px; }
/* RDS em linhas dentro da caixa. */
.rds-lines { display: grid; gap: 3px; margin-top: 2px; }
.rds-line { font-family: var(--mono); font-size: .8rem; color: var(--cyan); line-height: 1.25; word-break: break-word; }
.rds-line:first-child { font-weight: 700; }
.tile-add-line { display: block; font-size: .7rem; color: var(--green); font-weight: 700; margin-top: 2px; }
.mono { font-size: .58rem; font-weight: 700; letter-spacing: .06em; color: var(--amber); }
.fmdx-player { margin-top: 14px; }
.fmdx-player-row { display: flex; align-items: center; gap: 10px; margin-top: 4px; }
.fmdx-audio-player { flex: 1 1 auto; min-width: 0; }
.btn.live-now {
    flex: 0 0 auto; padding: 8px 14px; border-radius: 999px; cursor: pointer;
    border: 1px solid rgba(37,228,123,.5); background: rgba(37,228,123,.12); color: var(--green);
    font-size: .72rem; font-weight: 700; letter-spacing: .3px; white-space: nowrap;
}
.btn.live-now:hover { background: rgba(37,228,123,.22); border-color: var(--green); }
@media (max-width: 640px) { .fmdx-player-row { flex-wrap: wrap; } .btn.live-now { width: 100%; } }
.fmdx-note { margin: 8px 0 0; font-size: .68rem; color: var(--muted); line-height: 1.4; }
.rds-loop { margin: 8px 0 4px; padding: 7px 10px; font-size: .72rem; color: var(--amber); background: rgba(255,176,32,.08); border: 1px solid rgba(255,176,32,.3); border-radius: 8px; line-height: 1.4; }

/* --- Segurança / 2FA ----------------------------------------------------- */
.sec-note { color: var(--muted); font-size: .84rem; margin: 4px 0 14px; }
.sec-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
.sec-steps { color: #a9bccd; font-size: .82rem; line-height: 1.7; margin: 4px 0 14px; padding-left: 20px; }
.sec-qr { display: inline-block; background: #fff; padding: 10px; border-radius: 12px; }
.sec-qr svg { width: 190px; height: 190px; display: block; }
.sec-key { font-size: .8rem; color: var(--muted); margin: 12px 0; }
.sec-key code { color: var(--text); font-family: var(--mono); letter-spacing: .05em; background: rgba(255,255,255,.04); padding: 3px 8px; border-radius: 6px; }
.sec-recovery { margin-top: 18px; padding: 14px 16px; border: 1px solid var(--amber); background: rgba(255,176,32,.06); border-radius: 10px; }
.sec-recovery b { color: var(--amber); } .sec-recovery p { font-size: .76rem; color: var(--muted); margin: 6px 0 10px; }
.sec-codes { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
.sec-codes code { font-family: var(--mono); font-size: .82rem; background: rgba(255,255,255,.04); padding: 5px 8px; border-radius: 6px; text-align: center; }
.login-form .chk { display: flex; flex-direction: row; align-items: center; gap: 8px; color: #9db1c1; font-size: .78rem; }
.login-form .chk input { width: auto; }

/* --- Página de estado pública ------------------------------------------- */
.status-page { min-height: 100vh; display: grid; place-items: center; padding: 40px 20px; }
.status-card { width: 100%; max-width: 560px; background: var(--panel); border: 1px solid var(--line); border-radius: 18px; padding: 28px; box-shadow: var(--shadow); }
.status-brand { display: flex; align-items: center; gap: 14px; margin-bottom: 20px; }
.status-brand img { height: 40px; width: auto; mix-blend-mode: screen; }
.status-brand h1 { font-size: 1.5rem; }
.status-overall { display: flex; align-items: center; gap: 12px; padding: 16px 18px; border-radius: 12px; margin-bottom: 18px; }
.status-overall .dot { width: 12px; height: 12px; border-radius: 50%; }
.status-overall b { font-size: 1.05rem; }
.status-overall time { margin-left: auto; font-size: .68rem; color: var(--muted); }
.status-overall.green { background: rgba(37,228,123,.08); border: 1px solid rgba(37,228,123,.3); }
.status-overall.green .dot { background: var(--green); box-shadow: 0 0 12px var(--green); }
.status-overall.amber { background: rgba(255,176,32,.08); border: 1px solid rgba(255,176,32,.3); }
.status-overall.amber .dot { background: var(--amber); box-shadow: 0 0 12px var(--amber); }
.status-overall.red { background: rgba(255,67,93,.08); border: 1px solid rgba(255,67,93,.3); }
.status-overall.red .dot { background: var(--red); box-shadow: 0 0 12px var(--red); }
.status-list { display: grid; gap: 8px; }
.status-site { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; }
.status-site b { font-size: .9rem; } .status-site small { color: var(--muted); font-size: .7rem; margin-left: 8px; }
.status-pill { font-size: .66rem; font-weight: 700; padding: 4px 10px; border-radius: 999px; }
.status-pill.green { color: var(--green); background: rgba(37,228,123,.1); }
.status-pill.amber { color: var(--amber); background: rgba(255,176,32,.1); }
.status-pill.red { color: var(--red); background: rgba(255,67,93,.1); }
.status-empty { padding: 16px; text-align: center; color: var(--muted); font-size: .82rem; }
.status-foot { margin: 18px 0 0; text-align: center; font-size: .68rem; color: var(--muted); }

/* --- Tema claro ---------------------------------------------------------- */
/* O design é dark-first; este bloco reescreve as superfícies e as cores de texto
   fixas para um modo claro coerente. Ativa-se pelo atributo data-theme="light" no
   <html> (aplicado no App.razor, alternado no TopBar, guardado no localStorage). */

:root[data-theme="light"] {
    color-scheme: light;
    --bg: #eef2f7;
    /* Verde mais escuro no tema claro — o verde vivo tinha pouca leitura sobre branco. */
    --green: #0f9d58;
    --panel: #ffffff;
    --panel2: #f3f7fb;
    --line: #d7e0ea;
    --line2: #c3d2df;
    --text: #10202e;
    --ink: #14222f;
    --muted: #566a7d;
    --shadow: 0 18px 50px rgba(30,55,90,.10);
}

:root[data-theme="light"] body {
    background-image:
        radial-gradient(circle at 70% -20%, rgba(0,150,210,.06), transparent 36rem),
        linear-gradient(rgba(40,90,130,.05) 1px, transparent 1px),
        linear-gradient(90deg, rgba(40,90,130,.05) 1px, transparent 1px);
}

/* Superfícies (fundos escuros → claros) */
:root[data-theme="light"] .sidebar { background: linear-gradient(180deg, #ffffff, #eef3f8); }
:root[data-theme="light"] .topbar { background: rgba(255,255,255,.86); }
:root[data-theme="light"] .mobile-bar { background: rgba(255,255,255,.92); }
:root[data-theme="light"] .chip,
:root[data-theme="light"] .topbar-search,
:root[data-theme="light"] .topbar-user,
:root[data-theme="light"] .icon-btn,
:root[data-theme="light"] .hamburger,
:root[data-theme="light"] .sidebar-foot,
:root[data-theme="light"] .tu-logout button,
:root[data-theme="light"] .shortcuts a,
:root[data-theme="light"] input,
:root[data-theme="light"] select,
:root[data-theme="light"] textarea { background: #f3f7fb; }
:root[data-theme="light"] .panel,
:root[data-theme="light"] .kpi,
:root[data-theme="light"] .cmd-card,
:root[data-theme="light"] .cat-card,
:root[data-theme="light"] .chain-node,
:root[data-theme="light"] .tile,
:root[data-theme="light"] .node,
:root[data-theme="light"] .copilot,
:root[data-theme="light"] .copilot-msg.ai .copilot-body,
:root[data-theme="light"] .device-img { background: #ffffff; }

/* AS ZONAS SOBREVIVEM AO TEMA CLARO.
   A regra acima repinta todos os `.panel` de branco, e tem especificidade maior do que
   `.panel.zona-equipamento` — por isso os painéis do FMDX e o de ouvir o stream ficavam
   sem cor nenhuma no tema claro, enquanto as zonas que NÃO são painéis mantinham a sua.
   Foi o que se viu: umas coloridas e outras brancas, sem padrão aparente.

   A cor da zona já é calculada sobre `--panel2`, que muda com o tema — portanto no claro
   sai um pastel sobre branco em vez de um tom sobre azul-escuro. Só faltava deixá-la ganhar. */
:root[data-theme="light"] .panel.zona-equipamento,
:root[data-theme="dark"] .panel.zona-equipamento {
    background: var(--zona); background-image: none;
    border-left: 4px solid var(--zona-traco);
}

/* O mesmo para o traço: há uma regra mais abaixo que repõe o `border-color` de todos os
   painéis no tema claro, e levava com ela a barra que diz de que elo é a zona. */
:root[data-theme="light"] .zona-equipamento,
:root[data-theme="dark"] .zona-equipamento { border-left: 4px solid var(--zona-traco); }
:root[data-theme="light"] .kpi { background: linear-gradient(145deg, #ffffff, #f2f6fb); }
:root[data-theme="light"] .nav-menu a:hover { background: #e9f0f7; }
:root[data-theme="light"] .s-unknown,
:root[data-theme="light"] .node-state.unknown,
:root[data-theme="light"] .chain-link span { background: #eef3f8; }
:root[data-theme="light"] .incident,
:root[data-theme="light"] .incident.sev-Critico,
:root[data-theme="light"] .incident.sev-Emergencia,
:root[data-theme="light"] .symptoms { background: #f4f8fb; }
:root[data-theme="light"] .map-distritos .distrito { fill: rgba(70,120,165,.20); stroke: #7ba0c2; }
:root[data-theme="light"] .station { background: linear-gradient(180deg, #ffffff, #f4f8fb); }
:root[data-theme="light"] .km-card,
:root[data-theme="light"] .tile { background: #ffffff; }
:root[data-theme="light"] .tile.warn { background: #fff6e6; }
:root[data-theme="light"] .tile.bad { background: #ffe9ec; }
/* Linhas/cartões que tinham fundo escuro fixo — no claro ficavam texto escuro sobre escuro. */
:root[data-theme="light"] .alert-row { background: #ffffff; border-left-color: var(--line2); }
:root[data-theme="light"] .cfg-row { background: #ffffff; }
:root[data-theme="light"] .model-chip { background: #ffffff; }
:root[data-theme="light"] .alert-row b,
:root[data-theme="light"] .cfg-row .grow b { color: var(--text); }
/* (o .btn.danger passou a gradiente sólido com texto branco — legível nos dois temas) */

/* Texto que estava fixo em tons claros → escuro */
:root[data-theme="light"] .icon-btn,
:root[data-theme="light"] .topbar-title,
:root[data-theme="light"] .chip,
:root[data-theme="light"] .nav-menu a,
:root[data-theme="light"] .kpi .label,
:root[data-theme="light"] .chain-node .kind,
:root[data-theme="light"] .chain-node .mini small,
:root[data-theme="light"] .symptoms li,
:root[data-theme="light"] input,
:root[data-theme="light"] select,
:root[data-theme="light"] textarea { color: #2a3f52; }
:root[data-theme="light"] .hamburger span { background: #2a3f52; }

/* --- Blazor -------------------------------------------------------------- */

h1:focus { outline: none; }
.blazor-error-boundary { background: #b32121; padding: 1rem; color: white; }
.blazor-error-boundary::after { content: "Ocorreu um erro."; }

/* ============================================================================
   Atalhos por linha, elos clicáveis e sinais de vida.

   Regra que governa o movimento aqui: SÓ SE MEXE O QUE ESTÁ MESMO A ACONTECER.
   Animação decorativa num painel de emissão é ruído — passados dez minutos ninguém
   distingue o que é animação do que é sinal. Por isso o pulso verde só existe onde há
   leitura fresca, e pára sozinho quando a leitura envelhece.
   ============================================================================ */

/* --- Atalhos por linha ---------------------------------------------------- */
.estacoes-table th.col-icons { text-align: center; font-size: .6rem; }
.estacoes-table td.row-icons { white-space: nowrap; text-align: center; }

.icon-act {
    display: inline-grid; place-items: center; width: 30px; height: 30px;
    margin: 0 1px; padding: 0; border-radius: 8px; cursor: pointer;
    background: transparent; border: 1px solid transparent; color: var(--muted);
    transition: color .15s, background .15s, border-color .15s, transform .12s;
}
.icon-act:hover {
    color: var(--cyan); background: rgba(0,200,255,.10);
    border-color: rgba(0,200,255,.30); transform: translateY(-1px);
}
.icon-act:active { transform: translateY(0); }
.icon-act:focus-visible { outline: 2px solid var(--cyan); outline-offset: 1px; }
.icon-act.play:hover { color: var(--green); background: rgba(37,228,123,.12);
                       border-color: rgba(37,228,123,.32); }

/* --- Elo clicável: abre a ficha do equipamento ---------------------------- */
/* Sem cursor nem elevação nos elos SEM equipamento: um cartão que parece clicável e
   não abre nada é pior do que um cartão parado. */
.chain-node.clicavel { cursor: pointer; transition: transform .14s, box-shadow .14s, border-color .14s; }
.chain-node.clicavel:hover {
    transform: translateY(-3px);
    border-color: rgba(0,200,255,.45);
    box-shadow: 0 10px 26px rgba(0,140,200,.18);
}
.chain-node.clicavel:active { transform: translateY(-1px); }
/* "ABRIR FICHA": uma FAIXA no fundo da caixa, e não texto solto por cima.
   Estava como um ::after posicionado no fundo sem fundo nenhum — sobrepunha-se ao que já lá
   estava e lia-se por trás do texto, que é o pior de dois mundos: tapava e não se lia. Agora
   é uma banda com cor própria e duas linhas: o que faz, e a QUEM. Saber que se vai abrir a
   ficha "de quê" antes de clicar vale mais do que a palavra "abrir". */
.chain-node { position: relative; overflow: hidden; }

/* UMA BARRA, SEM TEXTO. Três tentativas para chegar aqui: duas linhas tapavam o nome, uma
   linha tapava um valor, e a lição é que numa caixa cheia não sobra sítio para escrever —
   tudo o que se escreve fica em cima de alguma coisa. O que era preciso dizer é "isto
   clica-se", e para isso basta a caixa levantar-se, acender a moldura e acender esta barra.

   A COR É A DO ESTADO, e não um azul fixo: com o azul, um elo VERDE ficava azul ao passar o
   rato — a única coisa que a caixa diz de relance, contrariada pelo próprio destaque. */
.node-abrir {
    position: absolute; left: 0; right: 0; bottom: 0;
    height: 4px; padding: 0;
    background: color-mix(in srgb, var(--green) 26%, transparent);
    border-top: 1px solid color-mix(in srgb, var(--green) 55%, transparent);
    opacity: 0; transform: translateY(6px);
    transition: opacity .14s, transform .14s;
    pointer-events: none;   /* o clique é da caixa, não desta faixa */
}
.chain-node.warn .node-abrir {
    background: color-mix(in srgb, var(--amber) 26%, transparent);
    border-top-color: color-mix(in srgb, var(--amber) 55%, transparent);
}
.chain-node.bad .node-abrir {
    background: color-mix(in srgb, var(--red) 26%, transparent);
    border-top-color: color-mix(in srgb, var(--red) 55%, transparent);
}
.chain-node.unknown .node-abrir {
    background: color-mix(in srgb, #7891a6 26%, transparent);
    border-top-color: color-mix(in srgb, #7891a6 55%, transparent);
}

.chain-node.clicavel:hover .node-abrir { opacity: 1; transform: none; }

/* --- Sinal de vida: o ponto do estado respira enquanto houver leitura fresca -- */
@keyframes ricentra-pulso {
    0%   { box-shadow: 0 0 0 0 rgba(37,228,123,.55); }
    70%  { box-shadow: 0 0 0 7px rgba(37,228,123,0); }
    100% { box-shadow: 0 0 0 0 rgba(37,228,123,0); }
}
.badge.no-ar .dot, .badge.no-ar::before { animation: ricentra-pulso 2.4s ease-out infinite; }

/* O medidor acende segmento a segmento em vez de aparecer feito: dá a leitura de que
   o valor foi medido agora, e não de que está ali desde ontem. */
@keyframes ricentra-acende { from { opacity: .25; transform: scale(.7); } to { opacity: 1; transform: scale(1); } }
.medidor .seg:not(.vazio):not(.sem-dados) { animation: ricentra-acende .45s ease-out both; }
.medidor .seg:nth-child(1) { animation-delay: 0s; }
.medidor .seg:nth-child(2) { animation-delay: .06s; }
.medidor .seg:nth-child(3) { animation-delay: .12s; }
.medidor .seg:nth-child(4) { animation-delay: .18s; }

/* Quem pediu ao sistema para não animar, não anima. Não é acessório: há quem fique
   enjoado com movimento, e um cockpit é para se olhar horas seguidas. */
@media (prefers-reduced-motion: reduce) {
    .icon-act, .chain-node.clicavel, .medidor .seg, .badge.no-ar .dot {
        transition: none !important; animation: none !important; transform: none !important;
    }
}

/* --- Central de registos --------------------------------------------------- */
.filtros { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end; padding: 4px 2px; }
.filtros .inline-lbl.grow { flex: 1 1 260px; }

.registos-table { width: 100%; min-width: 860px; }
.registos-table td { vertical-align: top; padding-top: 10px; padding-bottom: 10px; }
.registos-table time { font-variant-numeric: tabular-nums; color: var(--muted); font-size: .74rem; }
.registos-table .det { display: block; margin-top: 3px; font-size: .7rem; color: var(--muted); }
/* A gravidade pinta a borda esquerda e não a linha toda: uma tabela de linhas
   coloridas deixa de ter hierarquia e passa a ser um arco-íris onde nada salta. */
.registos-table tr.sev-critico td:first-child { box-shadow: inset 3px 0 0 var(--red); }
.registos-table tr.sev-aviso td:first-child { box-shadow: inset 3px 0 0 var(--amber); }
.registos-table tr.sev-info td:first-child { box-shadow: inset 3px 0 0 var(--line2); }

.tag.k-incidente { color: var(--red); border-color: rgba(255,67,93,.35); }
.tag.k-comando { color: var(--cyan); border-color: rgba(0,200,255,.35); }
.tag.k-agente { color: var(--purple); border-color: rgba(155,92,255,.35); }
.tag.k-config { color: var(--amber); border-color: rgba(255,176,32,.35); }
.tag.k-equipamento { color: var(--green); border-color: rgba(37,228,123,.35); }

/* --- Janela de alertas / registos de uma estação --------------------------- */
/* Larga e alta: são tabelas com datas, e uma janela estreita punha cada linha a
   partir em três. Rola por dentro, como a da estação. */
.modal.janela-estacao { width: min(1200px, 92vw); max-width: none; height: 82vh; max-height: 82vh;
                        padding: 0; display: flex; flex-direction: column; }
.modal.janela-estacao .modal-body { overflow-y: auto; flex: 1; padding: 16px 20px; }
.modal.janela-estacao .modal-head { padding: 16px 20px; border-bottom: 1px solid var(--line); }
.modal.janela-estacao .modal-foot { border-top: 1px solid var(--line);
                                    display: flex; justify-content: space-between; align-items: center; }

.janela-filtros { gap: 14px; padding-bottom: 14px; margin-bottom: 4px; border-bottom: 1px solid var(--line); }
.janela-filtros .atalhos-data { display: flex; gap: 6px; }
.janela-filtros .inline-lbl.chk { flex-direction: row; align-items: center; gap: 6px; }

/* Os ícones dos atalhos ganham a cor da acção ao passar por cima: o alerta fica
   âmbar, o play verde. É o que diz o que vai acontecer antes de se carregar. */
.icon-act svg { display: block; }
.icon-act[title^="Alertas"]:hover { color: var(--amber); background: rgba(255,176,32,.12);
                                    border-color: rgba(255,176,32,.32); }

/* ============================================================================
   PASSAGEM DE DESIGN — acabamento moderno, cor com significado.

   Trazido dos painéis de referência: pastilhas de ícone tingidas, números grandes
   como protagonista, cartões macios com raio generoso e sombra difusa, chips de
   estado em cápsula, e mais ar entre as coisas.

   NÃO trazido, de propósito: os cartões com gradiente roxo decorativo. Ali a cor é
   enfeite; aqui a cor é informação — verde é NO AR, âmbar é AVISO, vermelho é
   CRÍTICO. Um gradiente roxo bonito ensinaria o olho a ignorar a cor, e a leitura de
   relance é a única coisa que este painel tem de fazer bem.

   color-mix() em vez de rgba() fixo: as tintas seguem as variáveis do tema e
   funcionam no claro e no escuro sem duas regras para cada uma.
   ============================================================================ */

/* --- Cartões: mais raio, sombra mais suave, contorno mais discreto --------- */
.kpi, .panel, .station, .modal {
    border-radius: 16px;
}
.kpi {
    padding: 18px 18px 24px;
    box-shadow: 0 2px 4px rgba(16,40,70,.04), 0 12px 32px -12px rgba(16,40,70,.16);
}
:root[data-theme="light"] .kpi,
:root[data-theme="light"] .panel { border-color: color-mix(in srgb, var(--line) 70%, transparent); }

/* --- Pastilha de ícone: a marca visual destes painéis --------------------- */
/* Deixa de ser um traço solto no canto e passa a ser um objecto com peso. */
.kpi-icon {
    width: 46px; height: 46px; padding: 11px; border-radius: 14px; box-sizing: border-box;
    background: color-mix(in srgb, var(--cyan) 14%, transparent);
    color: var(--cyan); opacity: 1;
    display: grid; place-items: center;
}
.kpi.green .kpi-icon { background: color-mix(in srgb, var(--green) 16%, transparent); }
.kpi.red   .kpi-icon { background: color-mix(in srgb, var(--red) 15%, transparent); }
.kpi.amber .kpi-icon { background: color-mix(in srgb, var(--amber) 18%, transparent); }
.kpi-icon svg { width: 100%; height: 100%; stroke-width: 1.9; }

/* --- O número manda ------------------------------------------------------- */
.kpi .value { font-size: 2.35rem; font-weight: 750; letter-spacing: -.045em; margin-top: 10px; }
.kpi .label { font-size: .66rem; letter-spacing: .1em; font-weight: 700; }

/* O rodapé vira cápsula quando diz estado — como os chips "+22%" das referências.
   Só nos cartões com tom: nos neutros seria uma cápsula à volta de texto corrido. */
.kpi.green .foot, .kpi.red .foot, .kpi.amber .foot {
    display: inline-block; margin-top: 9px; padding: 3px 9px; border-radius: 999px;
    font-size: .66rem; font-weight: 700; letter-spacing: .01em;
}
.kpi.green .foot { background: color-mix(in srgb, var(--green) 16%, transparent); color: var(--green); }
.kpi.red   .foot { background: color-mix(in srgb, var(--red) 15%, transparent); color: var(--red); }
.kpi.amber .foot { background: color-mix(in srgb, var(--amber) 20%, transparent); color: #b3760a; }
:root[data-theme="dark"] .kpi.amber .foot,
:root:not([data-theme="light"]) .kpi.amber .foot { color: var(--amber); }

/* O mini-gráfico ganha corpo e encosta ao fundo, como nos cartões de referência. */
.kpi-spark { height: 42px; opacity: .32; }

/* --- Painéis: cabeçalho com mais ar --------------------------------------- */
.panel-title { padding-bottom: 12px; }
.panel-title h3 { font-size: .95rem; letter-spacing: -.01em; }

/* --- Tabela: linhas que respondem ao rato -------------------------------- */
/* Um painel de operação lê-se com o dedo a seguir a linha. Sem realce, salta-se de
   linha ao passar do nome para o estado — e numa tabela de estações isso é ler o
   estado da estação errada. */
.estacoes-table tbody tr { transition: background .12s; }
.estacoes-table tbody tr:hover td { background: color-mix(in srgb, var(--cyan) 6%, transparent); }
.estacoes-table td:first-child { border-left: 3px solid transparent; }
.estacoes-table tr:hover td:first-child { border-left-color: var(--cyan); }
.linha-aviso td:first-child { border-left-color: var(--amber); }
.linha-ma td:first-child { border-left-color: var(--red); }

/* O emblema da estação com o mesmo acabamento das pastilhas de ícone. */
.est-logo { width: 38px; height: 38px; border-radius: 12px; }
.est-logo.ph { font-size: .72rem; font-weight: 800; letter-spacing: .02em; }
.est-id b { font-size: .94rem; font-weight: 650; }

/* --- Cápsulas: estado sempre em pílula ----------------------------------- */
.badge { border-radius: 999px; font-weight: 700; letter-spacing: .04em; }
.btn { border-radius: 10px; font-weight: 650; }
.btn.sm { border-radius: 9px; }
.tag { border-radius: 999px; padding: 2px 10px; font-weight: 650; }

/* --- Cadeia: cartões com o mesmo acabamento ------------------------------ */
.chain-node { border-radius: 14px; }
.chain-node .kind { font-size: .58rem; letter-spacing: .1em; font-weight: 750; }
.chain-node .name { font-weight: 650; letter-spacing: -.01em; }

/* --- Ícones do menu: SVG numa caixa tingida, uma cor por secção ------------ */
/* A cor AQUI é decoração com função: identifica a secção e faz o menu ler-se de
   relance. Não colide com a cor dos ESTADOS nas tabelas — essa é informação e
   obedece sempre a verde/âmbar/vermelho. */
.nav-ic {
    --ic: var(--cyan);
    width: 30px; height: 30px; padding: 5px; border-radius: 9px; box-sizing: border-box;
    display: inline-grid; place-items: center; flex: 0 0 30px;
    background: color-mix(in srgb, var(--ic) 15%, transparent);
    color: var(--ic); font-style: normal; text-align: initial;
    transition: background .15s, transform .12s;
}
.nav-ic svg { width: 100%; height: 100%; display: block; }
.nav-menu a:hover .nav-ic,
.nav-group-head:hover .nav-ic { background: color-mix(in srgb, var(--ic) 26%, transparent); transform: scale(1.06); }
/* Na secção activa a caixa enche: é o que diz "estás aqui" sem precisar de outra marca. */
.nav-menu a.active .nav-ic { background: color-mix(in srgb, var(--ic) 32%, transparent); }
.nav-menu a.active .nav-ic { color: var(--ic); }

/* A regra antiga tratava o <i> como texto (largura 20px, tamanho de letra). */
.nav-menu a i.nav-ic { width: 30px; font-size: 0; color: var(--ic); }

.c-cyan   { --ic: #06b6d4; }
.c-indigo { --ic: #6366f1; }
.c-red    { --ic: #ef4456; }
.c-teal   { --ic: #14b8a6; }
.c-amber  { --ic: #f59e0b; }
.c-purple { --ic: #a855f7; }
.c-rose   { --ic: #f43f7e; }
.c-violet { --ic: #8b5cf6; }
.c-blue   { --ic: #3b82f6; }
.c-green  { --ic: #22c55e; }
.c-slate  { --ic: #64748b; }
:root[data-theme="light"] .c-slate { --ic: #52637a; }

/* O menu ganha altura por causa das caixas: mais espaço entre itens. */
.nav-menu a, .nav-group-head { gap: 11px; padding-top: 7px; padding-bottom: 7px; }

/* --- Painel de actividade: a metade de baixo do cockpit -------------------- */
.actividade { display: grid; grid-template-columns: 1.15fr 1fr; gap: 14px; margin-top: 14px; }
@media (max-width: 1100px) { .actividade { grid-template-columns: 1fr; } }

/* O ponto de gravidade do diário da rede. */
.fluxo-ponto { width: 9px; height: 9px; border-radius: 50%; margin-top: 5px; flex: 0 0 9px;
               background: var(--muted); }

/* --- Atalhos da linha: mesmo acabamento do menu ---------------------------- */
/* Passam a caixa tingida com cor própria, como os ícones da navegação. O "abrir"
   também: era o único botão de texto no meio de ícones e partia o alinhamento. */
/* O valor por omissão vai na PRÓPRIA var, com var(--ic, fallback), e não numa
   declaração "--ic: var(--cyan)" aqui.
   Porquê: ".icon-act" e ".c-amber" têm a mesma especificidade, e esta regra aparece
   depois no ficheiro — declarar --ic aqui sobrepunha-se a TODAS as classes de cor e
   os quatro atalhos saíam ciano. */
.icon-act {
    width: 32px; height: 32px; padding: 6px; border-radius: 10px; box-sizing: border-box;
    background: color-mix(in srgb, var(--ic, var(--cyan)) 13%, transparent);
    border: 1px solid transparent; color: var(--ic, var(--cyan));
}
.icon-act:hover {
    background: color-mix(in srgb, var(--ic, var(--cyan)) 26%, transparent);
    border-color: color-mix(in srgb, var(--ic, var(--cyan)) 34%, transparent);
    color: var(--ic, var(--cyan)); transform: translateY(-1px);
}
.icon-act:focus-visible { outline: 2px solid var(--ic, var(--cyan)); outline-offset: 1px; }
/* O "abrir" é a acção principal da linha: caixa mais cheia para se distinguir. */
.icon-act.abrir { width: 36px; height: 36px; padding: 6px;
                  background: color-mix(in srgb, var(--ic, var(--cyan)) 20%, transparent); }
.icon-act.abrir:hover { background: color-mix(in srgb, var(--ic, var(--cyan)) 34%, transparent); }
.estacoes-table td.row-icons { display: flex; gap: 6px; align-items: center; justify-content: center;
                               height: 100%; }

/* --- Barra de atalhos: um controlo, não cinco botões soltos --------------- */
/* Encaixados uns nos outros e maiores. Estavam em duas colunas separadas, com um
   cabeçalho a dizer "Atalhos" — palavra a ocupar espaço sem informar, e o "abrir"
   parecia outra coisa qualquer por estar isolado. São quatro acções sobre a mesma
   estação: lêem-se como um objecto só. */
.estacoes-table td.row-icons { display: table-cell; text-align: right; white-space: nowrap; }

/* NUNCA DOBRA. Tentei pô-la a dobrar em duas filas para poupar largura à tabela, e ficou
   feio de uma maneira que não se justifica: uma barra de atalhos partida ao meio deixa de
   se ler como uma barra. A coluna é que tem de ter a largura dos seis botões. */
.barra-atalhos {
    display: inline-flex; flex-wrap: nowrap; align-items: stretch; gap: 0;
    border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
    background: color-mix(in srgb, var(--panel2) 60%, transparent);
}
/* Botão 20% maior; o desenho lá dentro 50% maior, à custa da margem interna
   (8px → 6px). O ícone é o que se lê à distância — a moldura só o segura. */
.barra-atalhos .icon-act {
    width: 43px; height: 41px; padding: 8.5px; border-radius: 0; border: 0;
    border-right: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
    background: transparent; transform: none;
}
.barra-atalhos .icon-act:last-child { border-right: 0; }
.barra-atalhos .icon-act:hover {
    background: color-mix(in srgb, var(--ic, var(--cyan)) 20%, transparent);
    transform: none; border-color: color-mix(in srgb, var(--line) 70%, transparent);
}
/* O "abrir" fecha a barra e é a acção principal: fundo permanente e um pouco mais largo. */
.barra-atalhos .icon-act.abrir {
    width: 50px; height: 41px; padding: 8.5px;
    background: color-mix(in srgb, var(--ic, var(--cyan)) 16%, transparent);
}
.barra-atalhos .icon-act.abrir:hover { background: color-mix(in srgb, var(--ic, var(--cyan)) 32%, transparent); }

/* --- Leitor mínimo do FMDX ------------------------------------------------ */
/* Só o controlo de áudio. Quem carrega em "ouvir" quer saber se há som, não quer
   esperar pelo espectro, pelo RDS e pelo histórico a carregar primeiro. */
.modal.ouvir-modal { width: min(460px, 92vw); padding: 0; }
.ouvir-modal .modal-head { padding: 16px 18px; border-bottom: 1px solid var(--line); }
.ouvir-id { display: flex; align-items: center; gap: 12px; }
.ouvir-ic { width: 42px; height: 42px; padding: 8px; border-radius: 13px; flex: none;
            display: grid; place-items: center;
            background: color-mix(in srgb, var(--green) 16%, transparent); color: var(--green); }
.ouvir-id h3 { margin: 0; font-size: 1rem; }
.ouvir-corpo { padding: 18px; }
.ouvir-corpo .fmdx-audio-player { width: 100%; }
.ouvir-nota { margin: 12px 0 0; font-size: .7rem; color: var(--muted); text-align: center; }

/* Aviso de filtro activo: uma lista filtrada tem de dizer que está filtrada, senão
   parece que o resto desapareceu. */
.filtro-activo { display: flex; align-items: center; gap: 12px; margin-bottom: 12px;
                 padding: 9px 14px; border-radius: 11px; font-size: .82rem;
                 background: color-mix(in srgb, var(--cyan) 10%, transparent);
                 border: 1px solid color-mix(in srgb, var(--cyan) 26%, transparent); }

/* --- Equipamentos da estação, em janela ----------------------------------- */
.modal.equip-modal { width: min(920px, 92vw); max-width: none; max-height: 82vh;
                     padding: 0; display: flex; flex-direction: column; }
.equip-modal .modal-head { padding: 16px 20px; border-bottom: 1px solid var(--line); }
.equip-modal .modal-body { overflow-y: auto; flex: 1; padding: 8px 20px 16px; }
.equip-modal .modal-foot { border-top: 1px solid var(--line); }
.equip-modal .det { display: block; font-size: .66rem; color: var(--muted); margin-top: 2px; }

/* --- Separação entre estações --------------------------------------------- */
/* Estava aqui uma barra de 3px em "td:first-child". Só na PRIMEIRA célula, o que
   empurrava o nome da estação 3px para a direita e fazia a linha divisória parecer
   partida a meio. Um contorno que muda o alinhamento do conteúdo é sempre a solução
   errada. */
.estacoes-table td:first-child,
.estacoes-table tr:hover td:first-child,
.linha-aviso td:first-child,
.linha-ma td:first-child { border-left: 0; }

/* Cada estação é um bloco com ar à volta, não uma linha de tabela colada à seguinte.
   border-spacing em vez de margens: numa tabela é a única forma de ter espaço REAL
   entre linhas sem desalinhar as colunas. */
.estacoes-table { border-collapse: separate; border-spacing: 0 5px; }
.estacoes-table td { border-bottom: 0; }

/* O fundo tingido com a cor da rádio, muito diluído. É o que dá a separação e, de
   bónus, faz a estação reconhecer-se pela cor sem se ler o nome. */
.estacoes-table tbody tr td {
    background: color-mix(in srgb, var(--cor-estacao, transparent) 8%, transparent);
}
.estacoes-table tbody tr:hover td {
    background: color-mix(in srgb, var(--cor-estacao, var(--cyan)) 16%, transparent);
}
/* Cantos arredondados nas pontas da linha, para ler como um bloco. */
.estacoes-table tbody td:first-child { border-radius: 11px 0 0 11px; }
.estacoes-table tbody td:last-child { border-radius: 0 11px 11px 0; }

/* Aviso e avaria continuam a mandar sobre a cor da estação: o estado é informação,
   a cor da rádio é identidade. */
.linha-aviso td { background: color-mix(in srgb, var(--amber) 12%, transparent) !important; }
.linha-ma td { background: color-mix(in srgb, var(--red) 12%, transparent) !important; }

/* --- Diário da rede: uma tabela só, com a estação em coluna --------------- */
.actividade-panel { margin-top: 14px; }
.diario-table { width: 100%; min-width: 720px; }
.diario-table td { padding-top: 9px; padding-bottom: 9px; vertical-align: top; }
.diario-table .col-ponto { width: 22px; padding-right: 0; }
.diario-table .col-ponto .fluxo-ponto { margin-top: 6px; }
.diario-table .col-estacao-nome { white-space: nowrap; }
.diario-table .det { display: block; font-size: .68rem; color: var(--muted); margin-top: 2px; }
.diario-table time { font-size: .7rem; color: var(--muted); white-space: nowrap; }
/* O ponto herda a gravidade, como no fluxo antigo. */
.diario-table tr.sev-critico .fluxo-ponto,
.diario-table tr.sev-emergencia .fluxo-ponto { background: var(--red); box-shadow: 0 0 0 3px color-mix(in srgb, var(--red) 18%, transparent); }
.diario-table tr.sev-aviso .fluxo-ponto { background: var(--amber); box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber) 20%, transparent); }
.diario-table tr.sev-info .fluxo-ponto { background: var(--green); box-shadow: 0 0 0 3px color-mix(in srgb, var(--green) 16%, transparent); }
.por-resolver { color: var(--red); }

/* --- Célula da estação: volta a ser uma célula ---------------------------- */
/* Tinha display:flex directamente no <td>. Uma célula de tabela com display:flex
   deixa de participar na linha — ganha altura própria e desalinha-se de tudo o
   resto. Com o fundo tingido e os cantos arredondados, passou a parecer um bloco
   solto ao lado da linha. O flex mora agora num div interior. */
.estacoes-table td.col-estacao { display: table-cell; vertical-align: middle; }
.est-wrap { display: flex; align-items: center; gap: 11px; }

/* Todas as células alinhadas ao meio: com alturas diferentes por coluna (medidores,
   cápsulas, barra de atalhos) o alinhamento ao topo dava degraus dentro da linha. */
.estacoes-table tbody td { vertical-align: middle; }

/* --- Bola de estado ao lado de cada valor --------------------------------- */
/* O número diz QUANTO; a bola diz se está bom. Sem ela, "1 800 W" e "1 200 W" têm o
   mesmo aspecto e é preciso saber de cor a potência certa daquele emissor para ver
   que há problema. A bola lê-se a um metro do ecrã; o número não. */
.valor-com-ponto { display: inline-flex; align-items: center; gap: 7px; }
.valor-com-ponto .ponto { flex: 0 0 9px; width: 9px; height: 9px; }

/* --- Atalhos: caixa tingida, igual à do menu ------------------------------ */
/* Estavam transparentes dentro de uma moldura comum — não liam como os do menu.
   Cada botão passa a ter o seu próprio fundo na sua cor, e continuam encaixados. */
.barra-atalhos { background: transparent; }
.barra-atalhos .icon-act {
    /* Altura mantida; largura −20%. A margem interna horizontal desce com ela (9→6px)
       para o desenho não encolher junto com a caixa — o SVG é quadrado e ajusta-se
       sempre ao lado mais curto. */
    width: 38px; height: 49px; padding: 9px 6px;
    background: color-mix(in srgb, var(--ic, var(--cyan)) 14%, transparent);
    border-right: 1px solid color-mix(in srgb, var(--panel) 55%, transparent);
}
.barra-atalhos .icon-act:hover {
    background: color-mix(in srgb, var(--ic, var(--cyan)) 30%, transparent);
}
.barra-atalhos .icon-act.abrir {
    width: 44px; height: 49px; padding: 9px 6px;
    background: color-mix(in srgb, var(--ic, var(--cyan)) 24%, transparent);
}
.barra-atalhos .icon-act.abrir:hover { background: color-mix(in srgb, var(--ic, var(--cyan)) 40%, transparent); }

/* --- KPI: todos os cartões com a mesma altura ----------------------------- */
/* O cartão dos sintomas absorvidos ficava mais baixo que os quatro de cima. A culpa
   era do rodapé: nos cartões com estado ele vira cápsula e ganha 6px de altura, o
   que esticava a linha da grelha; o cartão da coluna do mapa, sem estado, ficava
   pela altura natural e desalinhava.
   A caixa do rodapé passa a ser SEMPRE a mesma — só muda o fundo e a cor. Sem
   margem lateral quando não há cápsula, para o texto não ficar indentado. */
.kpi .foot {
    display: inline-block; margin-top: 9px; padding: 3px 0;
    border-radius: 999px; font-size: .66rem; font-weight: 700;
}
.kpi.green .foot, .kpi.red .foot, .kpi.amber .foot { padding: 3px 9px; }

/* --- KPI mais baixos (−20%) ------------------------------------------------ */
/* O número continua a ser o protagonista; o que encolheu foi o ar à volta. Num
   cockpit, o espaço vertical do topo é espaço tirado à lista de estações — e é a
   lista que se olha o dia todo. */
.kpi { padding: 12px 18px 16px; }
.kpi .value { font-size: 2rem; margin-top: 6px; }
.kpi .foot { margin-top: 6px; }
.kpi-icon { width: 40px; height: 40px; padding: 9px; border-radius: 12px; }
.kpi-spark { height: 34px; }

/* Correcção: a altura vem só do ESPAÇO, não do conteúdo.
   A regra anterior encolhia o número (2,35→2rem), o ícone (46→40px) e o gráfico —
   isso é mudar a apresentação, não o tamanho da caixa. O número é o protagonista e
   fica como estava; o que sai é o ar à volta. */
.kpi { padding: 10px 18px 14px; }
.kpi .value { font-size: 2.35rem; margin-top: 5px; }
.kpi .foot { margin-top: 5px; }
.kpi-icon { width: 46px; height: 46px; padding: 11px; border-radius: 14px; }
.kpi-spark { height: 38px; }

/* --- Tabela das estações: começa logo, sem faixa nem linha ----------------- */
/* Os nomes das colunas chegam para dizer o que é. A linha divisória por baixo
   deles somava-se ao espaço do título e empurrava as estações para baixo sem
   acrescentar nada. */
.estacoes-geral { padding-top: 6px; }
.estacoes-table th { border-bottom: 0; padding-bottom: 4px; }
/* O rótulo da coluna dos atalhos alinha com os botões, à direita. */
.estacoes-table th.col-icons { text-align: right; padding-right: 12px; }

/* --- Cabeçalhos alinhados com o que está por baixo ------------------------- */
/* Cada rótulo assenta sobre a sua coluna: os elos e o estado ao centro, a leitura
   ao centro (é um número curto, não uma coluna de contas), a estação à esquerda e
   os atalhos à direita, onde os botões estão. Um rótulo desalinhado da coluna faz
   ler a coluna errada — numa tabela de sete elos isso é apontar para o sítio errado. */
.estacoes-table th,
.estacoes-table td.num,
.estacoes-table td.idade { text-align: center; }
.estacoes-table th:first-child { text-align: left; }
.estacoes-table th.col-icons { text-align: right; }
/* A cápsula de estado centrada na sua coluna, como o rótulo. */
.estacoes-table tbody td:has(> .badge) { text-align: center; }

/* Todas as caixas KPI com a mesma altura.
   As quatro do topo esticam-se umas às outras por estarem na mesma linha de grelha —
   a cápsula do "No ar" puxa-as todas a 114px. A dos sintomas absorvidos vive noutra
   coluna, ficava à altura natural (110px) e via-se a diferença. Medido no browser,
   não adivinhado. */
.kpi { min-height: 114px; }

/* --- Biblioteca ------------------------------------------------------------ */
.biblioteca-tenants { display: flex; flex-wrap: wrap; gap: 14px; align-items: center;
                      padding: 10px 2px; }
.biblioteca-tenants .inline-lbl.chk { flex-direction: row; align-items: center; gap: 6px; }

/* --- Biblioteca em duas colunas: o que se carrega vs o que vem com o produto -- */
.biblioteca-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 14px; align-items: start; }
@media (max-width: 1100px) { .biblioteca-grid { grid-template-columns: 1fr; } }
.biblioteca-col { display: flex; flex-direction: column; gap: 14px; }

.fixos-lista { display: flex; flex-direction: column; }
.fixo-linha { display: flex; align-items: center; gap: 10px; padding: 9px 2px;
              border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent); }
.fixo-linha:last-child { border-bottom: 0; }
.fixo-linha b { display: block; font-size: .82rem; }
.fixo-linha small { display: block; font-size: .66rem; color: var(--muted); margin-top: 2px; }

/* --- Alerta crítico ao vivo ------------------------------------------------ */
/* Fixo no topo, por cima de tudo MENOS do trabalho: impõe-se pela cor e pelo pulso,
   não por bloquear o cockpit — numa emergência é preciso USAR o cockpit. */
.alerta-vivo {
    position: fixed; top: 14px; left: 50%; transform: translateX(-50%);
    z-index: 300; width: min(680px, calc(100vw - 28px));
    display: flex; align-items: center; gap: 14px;
    padding: 14px 16px; border-radius: 14px;
    background: var(--panel); border: 1px solid var(--red);
    box-shadow: 0 18px 60px rgba(255,67,93,.35), 0 4px 14px rgba(0,0,0,.3);
    animation: av-entra .3s cubic-bezier(.2,.9,.3,1.2) both;
}
@keyframes av-entra { from { transform: translate(-50%,-130%); opacity: 0; }
                      to { transform: translate(-50%,0); opacity: 1; } }
.av-pulso { flex: 0 0 12px; width: 12px; height: 12px; border-radius: 50%;
            background: var(--red); animation: ricentra-pulso-red 1.6s ease-out infinite; }
@keyframes ricentra-pulso-red {
    0% { box-shadow: 0 0 0 0 rgba(255,67,93,.6); }
    70% { box-shadow: 0 0 0 10px rgba(255,67,93,0); }
    100% { box-shadow: 0 0 0 0 rgba(255,67,93,0); }
}
.av-corpo { flex: 1; min-width: 0; }
.av-tipo { display: block; font-size: .6rem; font-weight: 800; letter-spacing: .12em;
           color: var(--red); }
.av-titulo { display: block; font-size: .95rem; margin-top: 2px; }
.av-onde { display: block; font-size: .68rem; color: var(--muted); margin-top: 2px; }
.av-expl { display: block; font-size: .72rem; color: var(--muted); margin-top: 4px; }
.av-acoes { display: flex; flex-direction: column; gap: 6px; align-items: stretch; }
.av-mais { font-size: .64rem; color: var(--red); text-align: center; font-weight: 700; }
/* Emergência distingue-se do crítico: fundo tingido, não só o contorno. */
.alerta-vivo.sev-emergencia { background: color-mix(in srgb, var(--red) 14%, var(--panel)); }
@media (prefers-reduced-motion: reduce) {
    .alerta-vivo, .av-pulso { animation: none !important; }
}

/* --- Diário da rede: nuance entre registos --------------------------------- */
/* Linhas alternadas muito ao de leve. Numa lista de 14 acontecimentos com texto
   corrido, o olho salta de linha entre a coluna da estação e a da hora — e ler o
   acontecimento errado num diário de avarias é o género de engano que custa caro. */
.diario-table tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--line) 22%, transparent);
}
.diario-table tbody tr:hover td {
    background: color-mix(in srgb, var(--cyan) 8%, transparent);
}
.diario-table tbody td:first-child { border-radius: 8px 0 0 8px; }
.diario-table tbody td:last-child { border-radius: 0 8px 8px 0; }

/* --- Lista de estações sob o mapa ------------------------------------------ */
/* Nome e frequência, e o estado numa cápsula que se lê. Mostrava códigos internos
   ("rds_montejunto / rds-radio") em vez do nome da rádio. */
.station-item { cursor: pointer; border-radius: 10px; padding: 9px 10px;
                transition: background .12s; }
.station-item:hover { background: color-mix(in srgb, var(--cyan) 9%, transparent); }
.station-item .grow b { font-size: .84rem; font-weight: 650; display: block; }
.station-item .grow small { font-size: .68rem; color: var(--muted); }
/* O estado em cápsula com peso — era texto solto e perdia-se ao lado do nome. */
.station-item .st-state {
    padding: 4px 10px; border-radius: 999px; font-size: .62rem; font-weight: 800;
    letter-spacing: .06em;
    background: color-mix(in srgb, var(--green) 16%, transparent); color: var(--green);
}
.station-item .st-state.warn { background: color-mix(in srgb, var(--amber) 18%, transparent); color: #b3760a; }
.station-item .st-state.bad { background: color-mix(in srgb, var(--red) 16%, transparent); color: var(--red); }
:root[data-theme="dark"] .station-item .st-state.warn,
:root:not([data-theme="light"]) .station-item .st-state.warn { color: var(--amber); }

/* --- Barra de acções das páginas ------------------------------------------- */
/* Substitui o antigo page-heading: o título e a descrição saíram (já estão na barra
   de topo, muitas vezes pelas mesmas palavras), ficaram só os botões. */
.page-actions { display: flex; justify-content: flex-end; align-items: center;
                gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }

/* --- Cabeçalho do modal da estação: identidade | ESTADO | fechar ----------- */
/* Três zonas com o estado no eixo central. Antes o ONLINE encostava ao ✕ no canto
   e liam-se como um par de controlos — ONLINE não é um botão, é o estado da
   estação. E o "Fechar" duplica no topo: numa janela que rola, quem chega ao fundo
   não tem de voltar ao princípio para a fechar. */
.modal.station-modal .station-head {
    display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 16px;
    padding-right: 46px;   /* espaço do ✕ absoluto */
}
.modal.station-modal .station-head .estado-central { justify-self: center; }
.modal.station-modal .station-fechar-topo { justify-self: end; }
@media (max-width: 720px) {
    /* No telemóvel três colunas esmagam-se: empilha e mantém a ordem de leitura. */
    .modal.station-modal .station-head { grid-template-columns: 1fr; justify-items: start; }
    .modal.station-modal .station-head .estado-central,
    .modal.station-modal .station-fechar-topo { justify-self: start; }
}

/* --- Dispositivos: barra da estação + linhas legíveis ---------------------- */
/* A estação passa a ter uma FAIXA própria, tingida pela cor da rádio, em vez de um
   título solto sobre a tabela. Com seis estações na página não se via onde acabava
   uma e começava a seguinte. */
.dev-panel { padding: 0; overflow: hidden; }
.dev-site-bar {
    display: flex; align-items: center; gap: 12px; padding: 12px 16px;
    background: color-mix(in srgb, var(--cor-estacao, var(--cyan)) 12%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--cor-estacao, var(--line)) 30%, transparent);
    border-left: 4px solid var(--cor-estacao, var(--cyan));
}
.dev-site-id { flex: 1; min-width: 0; }
.dev-site-id b { display: block; font-size: .95rem; font-weight: 650; }
.dev-site-id small { display: block; font-size: .66rem; color: var(--muted); margin-top: 1px; }
.dev-site-count {
    font-size: .68rem; font-weight: 700; color: var(--muted);
    padding: 4px 10px; border-radius: 999px;
    background: color-mix(in srgb, var(--muted) 14%, transparent);
}
.dev-panel .dev-table { width: 100%; }
.dev-panel .dev-table th { padding-top: 12px; }
.dev-panel .dev-table th:first-child,
.dev-panel .dev-table td:first-child { padding-left: 16px; }
.dev-panel .dev-table th:last-child,
.dev-panel .dev-table td:last-child { padding-right: 16px; }

/* Alternância suave por equipamento — a mesma nuance do diário da rede. */
.dev-table tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--line) 20%, transparent);
}
.dev-table tbody tr:hover td { background: color-mix(in srgb, var(--cyan) 8%, transparent); }
.dev-table tbody td { padding-top: 11px; padding-bottom: 11px; }

/* --- Botões de acção agrupados, como a barra de atalhos do cockpit --------- */
/* Encaixados uns nos outros em vez de soltos com espaço entre eles: são acções
   sobre a MESMA linha e lêem-se como um objecto só. */
td.row-actions { white-space: nowrap; text-align: right; }
td.row-actions > .btn,
td.row-actions > a.btn { border-radius: 0; margin: 0; }
td.row-actions > .btn:first-child,
td.row-actions > a.btn:first-child { border-radius: 9px 0 0 9px; }
td.row-actions > .btn:last-child,
td.row-actions > a.btn:last-child { border-radius: 0 9px 9px 0; }
td.row-actions > .btn:only-child,
td.row-actions > a.btn:only-child { border-radius: 9px; }
/* Sem o salto vertical dentro de um bloco: uma peça a subir parte a barra. */
td.row-actions > .btn:hover { transform: none; }

/* --- Relatórios: linha a linha ---------------------------------------------- */
/* Uma tabela de SLA lê-se em linha (a estação e a percentagem dela), não em coluna.
   Sem alternância, com dez estações e quatro colunas de números, salta-se de linha
   entre o nome e o valor — e um número de disponibilidade lido na linha errada vai
   parar a um relatório para o cliente. */
.rel-table tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--line) 24%, transparent);
}
.rel-table tbody tr:hover td { background: color-mix(in srgb, var(--cyan) 10%, transparent); }
.rel-table tbody td { padding-top: 11px; padding-bottom: 11px; }
.rel-table tbody td:first-child { border-radius: 8px 0 0 8px; }
.rel-table tbody td:last-child { border-radius: 0 8px 8px 0; }

/* --- Gravações: a mesma nuance, mas mais leve ------------------------------- */
/* Aqui as listas são longas (clips de 5 em 5 minutos, dias a fio). Com o mesmo peso
   dos relatórios ficava um padrão de riscas a cansar — metade da intensidade. */
.grav-table tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--line) 12%, transparent);
}
.grav-table tbody tr:hover td { background: color-mix(in srgb, var(--cyan) 7%, transparent); }
.grav-table tbody td { padding-top: 9px; padding-bottom: 9px; }

/* Explicação de quem mede numa ligação STL: sem isto o formulário parece pedir dois
   equipamentos e só usa um endereço. */
.link-explica { padding: 11px 14px; margin-bottom: 14px; border-radius: 11px; font-size: .78rem;
                background: color-mix(in srgb, var(--cyan) 10%, transparent);
                border: 1px solid color-mix(in srgb, var(--cyan) 28%, transparent); }

/* --- Formulários: cores do tema e apresentação ----------------------------- */
/* As etiquetas e as notas tinham cores FIXAS de tema escuro (#8199aa, #5f788c,
   #cfe0ec). No tema claro ficavam cinzento-pálido sobre branco — era isso que fazia
   os campos da Biblioteca parecerem desformatados. É o mesmo defeito de sempre. */
.field-grid label { color: var(--muted); font-weight: 600; letter-spacing: .01em; }
.field-grid label.inline { color: var(--text); }
.field-grid small { color: var(--muted); opacity: .85; }
.field-grid code { color: var(--cyan); }
.form-subhead h4 { color: var(--text); }

/* O campo em si ganha presença: contorno visível e realce ao focar, para se ver
   onde se está a escrever num formulário com dez campos. */
.field-grid input:not([type="checkbox"]),
.field-grid select,
.field-grid textarea {
    width: 100%; padding: 9px 11px; border-radius: 9px;
    border: 1px solid var(--line2); background: var(--panel2); color: var(--text);
    font-size: .82rem; transition: border-color .14s, box-shadow .14s;
}
.field-grid input:focus, .field-grid select:focus, .field-grid textarea:focus {
    outline: none; border-color: var(--cyan);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--cyan) 18%, transparent);
}
/* O selector de ficheiro nativo não se estiliza por dentro; o que se pode é dar-lhe
   uma moldura coerente com o resto em vez de o deixar cru no meio dos campos. */
.field-grid input[type="file"] {
    padding: 8px; background: color-mix(in srgb, var(--cyan) 6%, transparent);
    border-style: dashed; cursor: pointer;
}
/* Caixa de verificação alinhada com o campo ao lado, não colada ao topo da célula. */
.field-grid label.inline-lbl.chk,
.biblioteca-tenants .inline-lbl.chk {
    display: flex; flex-direction: row; align-items: center; gap: 8px;
    color: var(--text); font-size: .8rem; font-weight: 600;
    grid-template-rows: none; align-self: end; padding-bottom: 9px;
}
.field-grid label.inline-lbl.chk input[type="checkbox"],
.biblioteca-tenants .inline-lbl.chk input[type="checkbox"] {
    width: 17px; height: 17px; accent-color: var(--cyan); cursor: pointer; margin: 0;
}
/* Um campo que ocupa a linha inteira quando o conteúdo o justifica. */
.field-grid label.grow { grid-column: 1 / -1; }

/* --- Auditoria: agrupada por dia, com natureza da acção -------------------- */
.aud-dia { margin-bottom: 12px; }
.aud-table tbody tr:nth-child(even) td {
    background: color-mix(in srgb, var(--line) 18%, transparent);
}
.aud-table tbody tr:hover td { background: color-mix(in srgb, var(--cyan) 8%, transparent); }
.aud-table td { padding-top: 9px; padding-bottom: 9px; }
.aud-table .col-ponto { width: 20px; padding-right: 0; }
.aud-table .det { font-size: .72rem; color: var(--muted); }
/* Uma falha pinta a linha: é o que se procura numa auditoria. */
.aud-table tr.aud-falha td { background: color-mix(in srgb, var(--red) 9%, transparent) !important; }

.aud-ponto { display: block; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.aud-ponto.falha { background: var(--red); }
.aud-ponto.destrutiva { background: var(--red); }
.aud-ponto.comando { background: var(--amber); }
.aud-ponto.sessao { background: var(--cyan); }
.aud-ponto.config { background: var(--green); }

/* A etiqueta da acção herda a mesma leitura por cor. */
.tag.t-falha, .tag.t-destrutiva { color: var(--red); border-color: color-mix(in srgb, var(--red) 35%, transparent); }
.tag.t-comando { color: #b3760a; border-color: color-mix(in srgb, var(--amber) 35%, transparent); }
:root[data-theme="dark"] .tag.t-comando,
:root:not([data-theme="light"]) .tag.t-comando { color: var(--amber); }
.tag.t-sessao { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 35%, transparent); }
.tag.t-config { color: var(--green); border-color: color-mix(in srgb, var(--green) 35%, transparent); }

/* --- Biblioteca: formulário de carregamento -------------------------------- */
/* Estrutura própria em três passos em vez da grelha genérica: o ficheiro primeiro
   (é o que se vem cá fazer), depois o nome, depois quem vê. A caixa de verificação e
   o selector de ficheiro nunca alinhavam lado a lado — têm alturas naturais
   diferentes, e era isso que fazia o painel parecer montado à pressa. */
.upload-panel { display: flex; flex-direction: column; gap: 18px; }

/* 1. A zona do ficheiro: grande e clicável, como convém à acção principal. */
.upload-zona {
    position: relative; display: grid; justify-items: center; gap: 4px;
    padding: 22px 18px; border-radius: 14px; cursor: pointer; text-align: center;
    border: 2px dashed color-mix(in srgb, var(--cyan) 38%, transparent);
    background: color-mix(in srgb, var(--cyan) 7%, transparent);
    transition: background .15s, border-color .15s;
}
.upload-zona:hover { background: color-mix(in srgb, var(--cyan) 13%, transparent);
                     border-color: var(--cyan); }
.upload-zona.escolhido { border-style: solid;
                         border-color: color-mix(in srgb, var(--green) 50%, transparent);
                         background: color-mix(in srgb, var(--green) 9%, transparent); }
/* O input nativo cobre a zona toda, invisível: o clique em qualquer ponto abre o
   selector, e o que se vê é o nosso desenho e não o botão cinzento do browser. */
.upload-zona input[type="file"] {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; cursor: pointer; border: 0; padding: 0; background: none;
}
.uz-icone { width: 34px; height: 34px; color: var(--cyan); }
.upload-zona.escolhido .uz-icone { color: var(--green); }
.uz-nome { font-size: .92rem; font-weight: 650; color: var(--text); }
.uz-nota { font-size: .7rem; color: var(--muted); }

/* 2. Os campos, em duas colunas que colapsam no telemóvel. */
.upload-campos { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 720px) { .upload-campos { grid-template-columns: 1fr; } }
.upload-campos label { display: grid; gap: 6px; font-size: .74rem; font-weight: 600;
                       color: var(--muted); }
.upload-campos input {
    width: 100%; padding: 10px 12px; border-radius: 9px; font-size: .84rem;
    border: 1px solid var(--line2); background: var(--panel2); color: var(--text);
}
.upload-campos input:focus { outline: none; border-color: var(--cyan);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--cyan) 18%, transparent); }

/* 3. Visibilidade: duas opções que se excluem, ditas como tal. */
.upload-visib { display: grid; gap: 10px; padding-top: 16px;
                border-top: 1px solid var(--line); }
.uv-titulo { font-size: .74rem; font-weight: 700; color: var(--muted);
             letter-spacing: .05em; text-transform: uppercase; }
.uv-opcoes { display: inline-flex; border: 1px solid var(--line2); border-radius: 10px;
             overflow: hidden; width: fit-content; }
.uv-op { padding: 9px 16px; border: 0; cursor: pointer; font-size: .8rem; font-weight: 650;
         background: transparent; color: var(--muted); transition: background .14s, color .14s; }
.uv-op + .uv-op { border-left: 1px solid var(--line2); }
.uv-op:hover { background: color-mix(in srgb, var(--cyan) 10%, transparent); }
.uv-op.on { background: color-mix(in srgb, var(--cyan) 20%, transparent); color: var(--cyan); }

/* Os clientes como pastilhas que se ligam e desligam: um visto por cliente numa lista
   longa obriga a mirar caixas de 14px; uma pastilha é um alvo inteiro. */
.uv-clientes { display: flex; flex-wrap: wrap; gap: 8px; }
.uv-cliente {
    padding: 7px 14px; border-radius: 999px; cursor: pointer; font-size: .78rem;
    font-weight: 650; border: 1px solid var(--line2); background: transparent;
    color: var(--muted); transition: background .14s, color .14s, border-color .14s;
}
.uv-cliente:hover { border-color: var(--cyan); color: var(--text); }
.uv-cliente.on { background: color-mix(in srgb, var(--green) 18%, transparent);
                 border-color: color-mix(in srgb, var(--green) 50%, transparent);
                 color: var(--green); }

/* O rodapé fora do painel: a acção que fecha o formulário não é mais um campo dele. */
.upload-rodape { display: flex; align-items: center; gap: 14px; margin: -4px 0 14px; }
.upload-aviso { font-size: .76rem; color: var(--muted); }

/* O botão que fecha um formulário vive DENTRO dele, alinhado ao fim dos campos.
   Solto na linha de baixo e encostado à esquerda parecia um controlo da página e
   não o passo final do que se acabou de preencher. */
.form-rodape { display: flex; justify-content: flex-end; gap: 10px;
               margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); }
.upload-rodape { display: flex; justify-content: flex-end; align-items: center; gap: 14px;
                 margin: 0; padding-top: 16px; border-top: 1px solid var(--line); }

/* --- Painel da VPN --------------------------------------------------------- */
.vpn-panel { margin-bottom: 12px; }
.vpn-grelha { display: flex; flex-wrap: wrap; gap: 10px; }
/* `flex: 1 1 240px` e não `auto`: com uma só VPN a caixa encolhia ao tamanho do nome e
   ficava a 70% da largura, com um vazio à direita que parecia falta de alguma coisa. Assim
   uma VPN ocupa a linha inteira e várias repartem-na, sem passar dos 240px cada. */
.vpn-srv { display: flex; align-items: center; gap: 8px; padding: 9px 12px;
           flex: 1 1 240px; min-width: 0;
           border-radius: 12px; border: 1px solid var(--line);
           background: color-mix(in srgb, var(--green) 8%, transparent); }
.vpn-srv.off { background: color-mix(in srgb, var(--red) 9%, transparent);
               border-color: color-mix(in srgb, var(--red) 35%, transparent); }
/* O nome cede a largura; a etiqueta e o estado não. Ao contrário, era o nome do túnel que
   ficava inteiro e o "WIREGUARD" ao lado é que partia ao meio — e a caixa passava de uma
   linha a três num painel de 320px. Quem cede é a parte que se pode encurtar sem perder
   sentido: um nome cortado ainda se reconhece, "WIRE / GUARD" é só ruído. */
.vpn-srv-nome { font-weight: 650; font-size: .88rem; flex: 1 1 auto; min-width: 0;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A etiqueta do tipo herdava o tamanho do corpo — 15px, maior que o nome do túnel ao lado,
   quando é a informação menos importante da caixa. */
.vpn-srv .tag { flex: none; white-space: nowrap; font-size: .58rem; letter-spacing: .05em;
                padding: 2px 7px; }
.vpn-srv .badge { flex: none; white-space: nowrap; }

.vpn-ligados { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.vpn-lig-titulo { display: block; font-size: .68rem; font-weight: 700; color: var(--muted);
                  text-transform: uppercase; letter-spacing: .06em; margin-bottom: 8px; }
.vpn-cli { display: flex; align-items: center; gap: 10px; padding: 7px 2px;
           border-bottom: 1px solid color-mix(in srgb, var(--line) 55%, transparent); }
.vpn-cli .vals { font-family: var(--mono); font-size: .72rem; color: var(--muted); }

/* Números da VPN: sessões, inactivas e tráfego lado a lado.
 *
 * A base é 110px e não 140. No cockpit este painel vive numa coluna de 320px, e duas caixas
 * de 140 mais o intervalo davam 290 contra 286 de largura útil: falhava por quatro pixels e
 * a segunda caixa caía para baixo. Duas linhas onde cabia uma, num painel onde a altura é
 * o que falta. Com três caixas — quando há peers ligados sem tráfego — parte na mesma, e
 * deve: três números a 95px não se leem. */
.vpn-numeros { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.vpn-num { flex: 1 1 110px; min-width: 0; padding: 10px 12px; border-radius: 12px;
           border: 1px solid var(--line); background: var(--panel2); }
.vpn-num b { display: block; font-size: 1.25rem; font-weight: 750; letter-spacing: -.03em;
             font-variant-numeric: tabular-nums; white-space: nowrap; }
.vpn-num small { display: block; font-size: .66rem; color: var(--muted); margin-top: 2px;
                 white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.vpn-num.bom b { color: var(--green); }
.vpn-num.mau { background: color-mix(in srgb, var(--red) 10%, transparent);
               border-color: color-mix(in srgb, var(--red) 35%, transparent); }
.vpn-num.mau b { color: var(--red); }

/* Os peers com nome. A barra da esquerda diz o estado sem obrigar a ler o texto. */
.vpn-peers { list-style: none; margin: 0 0 8px; padding: 0;
             display: flex; flex-direction: column; gap: 3px; }
.vpn-peer { display: grid; grid-template-columns: 1fr auto auto; gap: 4px 10px; align-items: center;
            padding: 7px 10px; border-radius: 8px; background: var(--panel2);
            border-left: 3px solid var(--line); font-size: .78rem; }
.vpn-peer.on { border-left-color: var(--green); }
.vpn-peer.mudo { border-left-color: var(--red);
                 background: color-mix(in srgb, var(--red) 8%, var(--panel2)); }
/* Linha própria e inteiro: cortado, "RDS-MONTEJUNTO-EMISSOR…" e "RDS-MONSANTO-EMISSOR…"
   ficavam iguais — e é o fim do nome que diz de quem se trata. */
.vpn-peer-nome { grid-column: 1 / -1; font-weight: 600; font-size: .76rem;
                 overflow-wrap: anywhere; line-height: 1.25; }
.vpn-peer-ip { color: var(--muted); font-size: .7rem; font-variant-numeric: tabular-nums; }
.vpn-peer-taxa { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 74px;
                 text-align: right; }
.vpn-peer.mudo .vpn-peer-taxa { color: var(--red); font-weight: 650; }
.vpn-peer.on .vpn-peer-taxa { color: var(--text); }

/* Página de Agentes: o código da estação é para conferir, não para competir com o nome. */
.grid-table .det.cod { display: block; margin-top: 2px; font-size: .68rem;
                       color: var(--muted); font-variant-numeric: tabular-nums; }

/* A latência: o facto mais forte da linha, por isso em tabular e com peso. */
.vpn-peer-ping { font-variant-numeric: tabular-nums; min-width: 56px; text-align: right;
                 color: var(--muted); font-size: .72rem; }
.vpn-peer.on .vpn-peer-ping { color: var(--green); font-weight: 650; }
.vpn-peer.mudo .vpn-peer-ping { color: var(--text); }

/* A tranca da comutação do Shelly: tem de saltar à vista, é o que separa ler de agir. */
.destaque-tranca { display: flex; gap: 10px; align-items: flex-start; padding: 12px 14px;
                   border: 1px solid color-mix(in srgb, var(--amber) 35%, transparent);
                   background: color-mix(in srgb, var(--amber) 8%, transparent);
                   border-radius: 10px; margin: 12px 0; }
.destaque-tranca input { margin-top: 3px; flex: none; }
.destaque-tranca b { display: block; font-size: .82rem; }
.destaque-tranca small { display: block; color: var(--muted); font-size: .72rem; margin-top: 3px; }

/* Cartão sem histórico: um traço apagado em vez de uma linha inventada. */
.km-spark.vazio { display: flex; align-items: center; justify-content: center;
                  color: var(--muted); opacity: .35; font-size: .9rem; }

/* Diário: por onde saiu o alerta. O canal RISCADO é o que interessa — foi tentado e
   não saiu, e até aqui isso só se descobria a ler ficheiros de log. */
.canais-cel { white-space: nowrap; }
.canal-tag { display: inline-block; padding: 1px 6px; margin-right: 3px; border-radius: 5px;
             font-size: .62rem; font-weight: 700; letter-spacing: .02em;
             border: 1px solid transparent; font-variant-numeric: tabular-nums; }
.canal-tag.ok { color: var(--green); border-color: color-mix(in srgb, var(--green) 30%, transparent);
                background: color-mix(in srgb, var(--green) 10%, transparent); }
.canal-tag.falhou { color: var(--red); border-color: color-mix(in srgb, var(--red) 45%, transparent);
                    background: color-mix(in srgb, var(--red) 12%, transparent);
                    text-decoration: line-through; }
.registos-table .resolvido { color: var(--muted); }

/* Aplicações vigiadas: um bloco só. Cada uma era um "equipamento" na telemetria e ocupava
   uma linha inteira com duas caixas — "a correr" e "quantas". A contagem não acrescenta
   nada; seis aplicações davam seis linhas para dizer seis SIM. */
.apps-linha { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
/* Mais altos e mais legiveis: sao o estado do playout e do processamento, e num ecra de
   parede lidos de longe. Uns chips finos escondiam a informacao mais importante da linha. */
.app-chip { display: inline-flex; align-items: center; gap: 9px; padding: 11px 16px;
            border-radius: 12px; font-size: .84rem; font-weight: 650; letter-spacing: -.01em;
            border: 1px solid var(--line); background: var(--panel2); min-height: 42px; }
.app-chip i { width: 10px; height: 10px; border-radius: 50%; background: currentColor;
              box-shadow: 0 0 10px currentColor; flex: none; }
.app-chip.on  { color: var(--green); border-color: color-mix(in srgb, var(--green) 30%, transparent);
                background: color-mix(in srgb, var(--green) 8%, transparent); }
.app-chip.off { color: var(--red); border-color: color-mix(in srgb, var(--red) 40%, transparent);
                background: color-mix(in srgb, var(--red) 10%, transparent); }
.app-chip.sem-dados { color: var(--muted); }
/* Leitura velha: a mesma regra do resto do cockpit — mostra-se, mas apagado. */
.app-chip.stale { opacity: .45; }
/* A correr, mas com processos a menos. Nem verde (não está inteiro) nem vermelho
   (não está morto) — a diferença entre incompleto e morto é a que evita ir a um monte
   de madrugada por nada. */
.app-chip.parcial { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 40%, transparent);
                    background: color-mix(in srgb, var(--amber) 10%, transparent); }
.app-chip b { font-variant-numeric: tabular-nums; font-size: .74rem; opacity: .85;
              padding: 1px 6px; border-radius: 6px; background: color-mix(in srgb, currentColor 14%, transparent); }

/* A saída de emergência do Shelly: abre o aparelho na cloud, fora do Ricentra. */
.saida-cloud { display: flex; align-items: flex-start; gap: 12px; margin: 10px 0;
               padding: 10px 12px; border-radius: 10px;
               border: 1px dashed color-mix(in srgb, var(--muted) 40%, transparent); }
.saida-cloud small { color: var(--muted); font-size: .72rem; line-height: 1.4; }

/* Comandos críticos: o botão e a janela de confirmação têm de se distinguir de tudo o
   resto. Quem está a pôr um emissor em standby não pode achar que é mais um botão. */
.btn.crit { background: var(--red); border-color: var(--red); color: #fff; font-weight: 700; }
.btn.crit:disabled { opacity: .4; }
.confirmar-critico { border: 2px solid color-mix(in srgb, var(--red) 55%, transparent); }
/* Uma nota que não é instrução, é armadilha: destaca-se do resto das dicas sem gritar. */
.hint.aviso { padding: 9px 11px; border-radius: 8px; color: var(--text);
              background: color-mix(in srgb, #d97a1f 9%, transparent);
              border: 1px solid color-mix(in srgb, #d97a1f 32%, transparent); }

.aviso-critico { color: var(--text); font-size: .84rem; line-height: 1.5; margin: 0 0 10px; }
.aviso-critico.forte { padding: 10px 12px; border-radius: 8px;
                       background: color-mix(in srgb, var(--red) 10%, transparent);
                       border: 1px solid color-mix(in srgb, var(--red) 35%, transparent); }

/* Alertas silenciados. Impossivel de ignorar e impossivel de fechar: um sistema calado que
   parece normal e pior do que nao ter alertas nenhuns. */
.silencio-banner { display: flex; align-items: center; gap: 14px; margin: 0 0 12px;
                   padding: 12px 16px; border-radius: 10px;
                   background: color-mix(in srgb, var(--amber) 14%, transparent);
                   border: 1px solid color-mix(in srgb, var(--amber) 45%, transparent); }
.silencio-banner .sb-icone { font-size: 1.4rem; flex: none; }
.silencio-banner .sb-texto { flex: 1; line-height: 1.45; }
.silencio-banner b { display: block; color: var(--amber); font-size: .86rem; }
.silencio-banner span { font-size: .76rem; color: var(--muted); }

/* Extras: as caixas pequenas lado a lado, o resto a toda a largura. Empilhadas e estreitas
   sobrava metade do ecrã vazia à direita. */
.extras-par { display: grid; grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
              gap: 14px; align-items: start; margin-bottom: 14px; }
.plugins { max-width: none; }

/* Extras: acção à direita do cartão. O molde .plugin passou a servir tudo nesta página
   (alertas, conta, produtos) — e nem todos têm um só interruptor: alguns têm três botões.
   Alinhar ao topo evita que um cartão alto empurre a acção para o meio do nada. */
.plugin { align-items: flex-start; }
.plugin-icon { line-height: 1.4; }
.plugin-act { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end;
              align-items: center; flex: 0 0 auto; max-width: 45%; }
.plugin-body .in-sm { margin-top: 8px; max-width: 260px; font-size: .74rem; padding: 6px 8px; }

/* Rádios (barra de topo): resumo + lista clicável. A cor é o estado, não decoração. */
.radios-modal { max-width: 720px; }
.radios-resumo { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 14px; }
.rr-item { text-align: center; padding: 10px 6px; border: 1px solid var(--line);
           border-radius: 10px; background: rgba(255,255,255,.02); }
.rr-item b { display: block; font-size: 1.5rem; line-height: 1.1; color: var(--muted); }
.rr-item span { font-size: .68rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.rr-item.ok b { color: var(--green); }
.rr-item.bad b { color: var(--red); }
.rr-item.warn b { color: var(--amber); }

.radios-lista { display: grid; gap: 8px; }
.radio-linha { display: grid; grid-template-columns: 10px 1fr auto auto; align-items: center;
               gap: 12px; width: 100%; text-align: left; cursor: pointer;
               padding: 10px 14px; border-radius: 10px; font: inherit;
               border: 1px solid var(--line); background: rgba(255,255,255,.02); }
.radio-linha:hover { border-color: var(--cyan); background: rgba(255,255,255,.05); }
.radio-linha.ok { border-left: 3px solid var(--green); }
.radio-linha.bad { border-left: 3px solid var(--red); background: rgba(255,67,93,.06); }
.rl-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.radio-linha.ok .rl-dot { background: var(--green); }
.radio-linha.bad .rl-dot { background: var(--red); }
.rl-id { min-width: 0; }
.rl-id b { display: block; font-size: .85rem; color: var(--text); }
.rl-id small { font-size: .68rem; color: var(--muted); }
.rl-meio { min-width: 0; text-align: right; }
.rl-limpo { font-size: .7rem; color: var(--muted); }
.rl-motivo { font-size: .7rem; color: var(--red); }
.rl-estado { text-align: right; min-width: 82px; }
.rl-estado b { display: block; font-size: .7rem; letter-spacing: .05em; }
.radio-linha.ok .rl-estado b { color: var(--green); }
.radio-linha.bad .rl-estado b { color: var(--red); }
.rl-estado small { font-size: .64rem; color: var(--muted); }

/* Tarefas: cada incidente aberto é trabalho com dono e prazo. A cor da margem diz em que
   estado está sem obrigar a ler — por assumir, assumido, adiado. */
.tarefa.nova { border-left: 3px solid var(--red); }
.tarefa.assumida { border-left: 3px solid var(--cyan); }
.tarefa.adiada { border-left: 3px solid var(--amber); opacity: .88; }

.tarefa-estado { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 12px;
                 padding: 10px 16px 0; }
.te-chip { font-size: .74rem; padding: 3px 10px; border-radius: 999px;
           border: 1px solid var(--line); }
.te-chip.nova { color: var(--red); border-color: rgba(255,67,93,.4); background: rgba(255,67,93,.08); }
.te-chip.assumida { color: var(--cyan); border-color: rgba(80,200,255,.35); background: rgba(80,200,255,.08); }
.te-chip.adiada { color: var(--amber); border-color: rgba(255,176,32,.4); background: rgba(255,176,32,.08); }
.te-nota { font-size: .72rem; color: var(--muted); }

.tarefa-accoes { display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
                 padding: 10px 16px 14px; }
.tarefa-accoes .in-sm { max-width: 240px; font-size: .74rem; padding: 6px 8px; }

/* --- Toque: o Ricentra é usado sobretudo do telemóvel --------------------------------
 *
 * Um alvo de 24px acerta-se com o rato e falha-se com o polegar. Estas regras só valem
 * onde o apontador é grosso (dedo), por isso não incham nada no desktop: `pointer: coarse`
 * é o telemóvel e o tablet, não uma janela estreita no PC.
 *
 * 44px é o mínimo recomendado, e aqui não é cosmética: alguns destes botões desligam
 * emissores. */
@media (pointer: coarse) {
    .btn, .icon-btn, .icon-act, .nav-group-head { min-height: 40px; }
    .btn.sm { min-height: 36px; padding-left: 14px; padding-right: 14px; }
    .hamburger { width: 44px; height: 44px; }
    .nav-menu a { padding: 13px 13px; }
    select, input[type="text"], input[type="date"], input[type="number"], .in { min-height: 40px; }
    /* Linhas clicáveis (rádios, alertas, equipamentos) com espaço para o dedo. */
    .radio-linha, .alert-row, .station-item { min-height: 48px; }
}

/* --- Nada empurra a página para o lado ---------------------------------------------
 *
 * A regra é simples e vale para todas as larguras: uma tabela larga desliza DENTRO da
 * caixa dela; o corpo da página nunca desliza. Uma página que se mexe de lado no telemóvel
 * dá a sensação de estar partida, e num ecrã pequeno esconde a coluna do estado — que é
 * justamente a que se vai lá ver. */
.panel { max-width: 100%; }
.tabela-rola { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Folha que sobe do fundo: SÓ no telemóvel.
 *
 * Estava a partir dos 1024px e apanhava portáteis, onde um modal com cantos de telemóvel
 * não faz sentido nenhum. */
@media (max-width: 700px) {
    /* AO CENTRO, e não colado ao fundo.
     *
     * Esteve como folha encostada em baixo — é o gesto habitual num telemóvel e põe os
     * botões perto do polegar. Mas nesta aplicação os modais são de LEITURA antes de
     * serem de acção (a ficha de um equipamento, os valores, o registo), e uma folha
     * empurra o conteúdo para a metade de baixo do ecrã com um vazio escuro por cima.
     * Ao centro, o que se veio ler fica onde os olhos já estão. */
    .modal { width: 100%; max-width: 100%; border-radius: 14px; max-height: 92dvh; }
    .modal-scrim { padding: 10px; align-items: center; }
    .modal-body { max-height: 70dvh; overflow-y: auto; }

    /* O rodapé dos modais é onde vivem Confirmar/Cancelar: nunca pode ficar fora do ecrã. */
    .modal-foot { position: sticky; bottom: 0; background: inherit; }

    /* Formulários de duas colunas passam a uma: dois campos lado a lado num telemóvel
       ficam ambos estreitos de mais para se escrever neles. */
    .field-grid, .filter-bar { display: grid; grid-template-columns: 1fr; }
}

@media (max-width: 560px) {
    /* Uma coluna para tudo o que ainda tentava duas. */
    .kpi-grid, .cmd-grid, .extras-par, .plugins,
    .cockpit-top, .actividade, .fmdx-grid { grid-template-columns: 1fr; }
    .plugin { flex-wrap: wrap; }
    .plugin-act { max-width: 100%; justify-content: flex-start; }
    .content { padding: 14px 12px 44px; }
    .panel-title h3 { font-size: .95rem; }
}

/* Agentes: o que cada um recolhe. Aceso = está a recolher; apagado com risco = NÃO está.
   Um agente com REDE apagado e equipamentos atribuídos é uma avaria de configuração, e
   tem de se ver de relance em vez de se deduzir da telemetria que falta. */
/* NAO por display:flex numa <td>: a celula deixa de participar na distribuicao de larguras
   da tabela e a linha inteira sai do sitio -- foi o que desconfigurou a pagina dos Agentes.
   A celula fica celula; quem se arruma sao as pastilhas la dentro. */
.recolhe { white-space: nowrap; }
.recolhe .cap { margin: 0 3px 2px 0; }
.cap { font-size: .6rem; font-weight: 700; letter-spacing: .05em; padding: 2px 6px;
       border-radius: 6px; border: 1px solid var(--line); }
.cap.on { color: var(--green); border-color: rgba(37,228,123,.35); background: rgba(37,228,123,.08); }
/* Terceiro estado: o centro nunca decidiu isto, e vale o que está no ficheiro do PC.
   Cinzento e tracejado porque não é uma afirmação — é a ausência de uma. Verde ou
   vermelho aqui seriam a página a inventar o que não sabe. */
.cap.ind { color: var(--muted); border-style: dashed; border-color: var(--line);
           background: transparent; }
.cap.off { color: var(--red); border-color: rgba(255,67,93,.35); background: rgba(255,67,93,.08);
           text-decoration: line-through; }

/* As pastilhas do "Recolhe" são botões: clicam-se para ligar/desligar. */
button.cap { cursor: pointer; font-family: inherit; }
button.cap:hover:not(:disabled) { filter: brightness(1.35); }
button.cap:disabled { cursor: default; opacity: .5; }

/* Escolha de estações por utilizador. Duas colunas até caber; uma no telemóvel. */
.campo-largo { grid-column: 1 / -1; }
.sites-escolha { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
                 gap: 4px 14px; margin: 6px 0 2px; }
.sites-escolha .chk { font-size: .8rem; }
.cap.acao { color: var(--cyan); border-color: rgba(80,200,255,.35); background: rgba(80,200,255,.08);
            text-transform: none; letter-spacing: 0; }
.papel-sel { font-size: .68rem; padding: 1px 4px; margin-top: 2px; max-width: 170px; }

/* Parâmetros do sistema: um por linha, com a explicação ao lado do número.
   Um número sem a frase que diz o que se ganha e o que se arrisca é um convite a mexer
   às cegas — e estes números decidem quando alguém é acordado. */
.param-lista { display: grid; gap: 2px; }
.param { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 16px;
         align-items: start; padding: 12px 4px; border-top: 1px solid var(--line); }
.param:first-child { border-top: 0; }
.param.alterado { background: rgba(255,176,32,.05); border-left: 3px solid var(--amber);
                  padding-left: 10px; }
.param-id b { display: block; font-size: .86rem; }
.param-id small { display: block; font-size: .74rem; color: var(--muted); line-height: 1.45; margin-top: 3px; }
.param-id small.dim { font-family: var(--mono); font-size: .66rem; color: #52697c; }
.param-id .pill { font-size: .6rem; margin-left: 6px; }
.param-valor { display: flex; align-items: center; gap: 8px; }
.param-valor .in, .param-valor select { width: 110px; text-align: right; }
.param-un { font-size: .72rem; color: var(--muted); min-width: 28px; }
@media (max-width: 700px) {
    .param { grid-template-columns: 1fr; }
    .param-valor { justify-content: flex-start; }
}

/* --- Cockpit no telemóvel: a ordem de quem está a olhar, não a das colunas -------------
 *
 * Em ecrã largo são duas colunas — estações à esquerda, mapa e resto à direita. Ao empilhar,
 * a ordem passava a ser "toda a coluna da esquerda, depois toda a direita", e o mapa caía
 * DEPOIS do diário. Quem abre isto no telemóvel quer, por esta ordem: quantas estão no ar,
 * quais estão mal, onde ficam, e só depois o histórico.
 *
 * `display: contents` desfaz as colunas sem lhes mexer no HTML: os filhos passam a ser
 * irmãos e o `order` manda em todos de uma vez. */
@media (max-width: 1024px) {
    /* `align-items: stretch` NÃO é decoração: sem ele o cockpit ficava a 30% da largura.
     *
     * A regra de base tem `align-items: start`. Numa grelha isso alinha em cima (vertical) e
     * não faz mal nenhum. Ao trocar para `flex-direction: column`, a mesma palavra passa a
     * querer dizer "encosta à esquerda com a largura do conteúdo" — o eixo transversal muda
     * de sentido. Os cartões encolhiam ao tamanho do texto e sobrava ecrã vazio à direita. */
    .cockpit-top { display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
    .stations-col, .map-col { display: contents; }

    /* A grelha dos cartões traz uma margem por baixo, boa para o desenho de duas colunas.
       Empilhada, essa margem SOMA-SE ao intervalo do flex: o salto entre "Avisos" e
       "Sintomas absorvidos" ficava com o dobro do que há entre os outros. O intervalo é
       do contentor, e é só dele. */
    .cockpit-top .kpi-grid { margin-bottom: 0; }

    /* Primeiro o resto, para as regras seguintes lhe ganharem (mesma especificidade:
       em CSS a última é que manda). */
    .stations-col > *, .map-col > * { order: 7; }

    .cockpit-top .kpi-grid        { order: 1; }   /* no ar · críticos · avisos */
    /* Os sintomas absorvidos são um cartão como os de cima e pertencem ao pé deles — é a
       mesma leitura de relance. Ao empilhar caía lá para baixo, depois do mapa, e ninguém
       o via. */
    .cockpit-top .map-col > .kpi  { order: 2; }
    .cockpit-top .estacoes-geral  { order: 3; }   /* a grelha das estações */
    .cockpit-top .map-col > .panel:has(.map-wrap) { order: 4; }   /* o mapa */
    .cockpit-top .actividade-panel{ order: 5; }   /* diário */
    .cockpit-top .vpn-panel       { order: 6; }

}

/* --- Estações no telemóvel: cada uma é uma caixa, e não uma linha para arrastar --------
 *
 * A tabela tem nove colunas e 900px de largura mínima. Num telemóvel isso dava uma barra
 * de rolamento horizontal: via-se o nome da estação e mais nada, e para saber se o encoder
 * estava bom era preciso arrastar — e depois arrastar de volta para saber de QUEM era.
 * Ler o estado de uma rádio não pode custar dois gestos.
 *
 * A tabela deixa de ser tabela e passa a caixas: nome em cima, os sete elos numa grelha de
 * dois com o nome do elo à frente do valor, e os atalhos em baixo. O <thead> desaparece
 * porque cada célula já traz a sua etiqueta no `data-col`.
 *
 * O HTML é o mesmo nas duas versões — uma segunda marcação só para o telemóvel divergia da
 * primeira no dia em que se acrescentasse um elo à cadeia. */
@media (max-width: 760px) {
    /* O painel perde a moldura: cada estação já é uma caixa, e uma caixa dentro de outra
       caixa só rouba largura ao que interessa. Num ecrã de 420px eram 32px de branco dos
       lados a servir de nada. */
    .estacoes-geral { padding: 0; background: none; border: 0; box-shadow: none; }
    .estacoes-geral .tabela-wrap { overflow-x: visible; }
    .estacoes-table { min-width: 0; border-spacing: 0; }
    .estacoes-table thead { display: none; }
    .estacoes-table tbody { display: grid; gap: 10px; }

    .estacoes-table tbody tr {
        display: grid; grid-template-columns: 1fr 1fr; gap: 8px 14px;
        padding: 12px 13px; border-radius: 13px;
        border: 1px solid var(--line);
        /* O fundo da estação é uma cor diluída a 8% — sozinho não chega para dizer onde
           acaba uma caixa e começa a seguinte. A sombra é que levanta o cartão do fundo
           da página; a cor continua a ser a identidade da rádio, não a moldura. */
        box-shadow: 0 1px 3px rgb(0 0 0 / .07), 0 4px 12px rgb(0 0 0 / .05);
        background: color-mix(in srgb, var(--cor-estacao, var(--panel)) 8%, var(--panel));
    }
    /* O fundo passa para a linha; nas células deixa de fazer sentido e sobrepunha-se. */
    .estacoes-table tbody tr td,
    .estacoes-table tbody tr:hover td { background: none; }
    .estacoes-table tbody tr:hover { background: color-mix(in srgb, var(--cor-estacao, var(--cyan)) 16%, transparent); }

    .estacoes-table td {
        display: flex; align-items: center; justify-content: space-between; gap: 10px;
        padding: 0; border: 0; border-radius: 0; text-align: left; min-height: 24px;
    }
    .estacoes-table tbody td:first-child,
    .estacoes-table tbody td:last-child { border-radius: 0; }
    .estacoes-table td:first-child { border-left: 0; }

    /* A etiqueta vem da própria célula — é o cabeçalho que se escondeu. */
    .estacoes-table td[data-col]::before {
        content: attr(data-col);
        font-size: .64rem; text-transform: uppercase; letter-spacing: .05em;
        color: var(--muted); font-weight: 650; flex: 0 0 auto;
    }

    /* Nome e estado ocupam a largura toda: são o cabeçalho da caixa. */
    .estacoes-table td.col-estacao { grid-column: 1 / -1; padding-bottom: 4px;
                                     border-bottom: 1px solid var(--line); margin-bottom: 2px; }
    .estacoes-table td.col-estado  { grid-column: 1 / -1; justify-content: flex-start; }
    .estacoes-table td.row-icons   { grid-column: 1 / -1; padding-top: 6px;
                                     border-top: 1px solid var(--line); margin-top: 2px; }
    /* Os atalhos repartem a largura em partes iguais e ficam colados: `margin: 0 1px` mais
       o traço branco da direita davam quatro frestas pálidas que se liam como espaçamento
       entre botões soltos. São UM controlo com cinco acções — cada cor encosta à seguinte. */
    .estacoes-table td.row-icons .barra-atalhos { width: 100%; }
    .estacoes-table td.row-icons .barra-atalhos .icon-act {
        flex: 1 1 0; width: auto; margin: 0; border-right: 0;
    }

    .estacoes-table td.col-elo { text-align: right; white-space: normal; }
    .estacoes-table td.idade   { justify-content: space-between; }

    /* --- Diário da rede: mesma doença, mesmo remédio -----------------------------------
     *
     * Cinco colunas em 720px, e a descrição — que é a única coluna que se lê — era a que
     * ficava cortada. Cada acontecimento passa a três linhas: onde e quando em cima, o que
     * aconteceu por baixo, o equipamento no fim. A colocação é explícita porque a ordem no
     * HTML (ponto · estação · texto · equipamento · quando) não é a ordem de leitura. */
    .diario-table { min-width: 0; }
    .diario-table thead { display: none; }
    .diario-table tbody { display: grid; gap: 2px; }
    .diario-table tbody tr {
        display: grid; grid-template-columns: 1fr auto; gap: 2px 8px;
        align-items: baseline; padding: 9px 2px 9px 9px;
        border-bottom: 1px solid var(--line);
        border-left: 3px solid transparent;
    }
    .diario-table td { display: block; padding: 0; border: 0; }

    /* A bola da gravidade sai, a cor fica na aresta da linha.
     *
     * Uma coluna inteira para nove pixéis de bola custava uns bons vinte de branco à
     * esquerda, num ecrã onde a descrição já anda apertada. A aresta diz o mesmo — e diz
     * melhor, porque tinge a entrada toda em vez de um ponto ao lado dela. */
    .diario-table td.col-ponto { display: none; }
    .diario-table tr.sev-critico, .diario-table tr.sev-emergencia { border-left-color: var(--red); }
    .diario-table tr.sev-aviso   { border-left-color: var(--amber); }
    .diario-table tr.sev-info    { border-left-color: var(--green); }

    .diario-table td.col-estacao-nome { grid-area: 1 / 1; }
    .diario-table td.num              { grid-area: 1 / 2; text-align: right; }
    .diario-table tbody tr td:nth-child(3) { grid-area: 2 / 1 / 3 / 3; }
    .diario-table td.vals             { grid-area: 3 / 1 / 4 / 3; font-size: .72rem;
                                        color: var(--muted); }

    /* --- Equipamentos: seis colunas de largura fixa, e os botões a cair fora -----------
     *
     * A `.dev-table` é `table-layout: fixed` com percentagens por coluna e 480px de mínimo.
     * Num ecrã estreito a última coluna ficava com 10% de nada, e como as células cortam o
     * que sobra (`overflow: hidden`), os botões "Ouvir", "Editar" e "Ver agente" eram
     * simplesmente ceifados a meio — um botão cortado é um botão que não existe.
     *
     * Cada equipamento passa a uma ficha: nome e hora em cima, elo e contagem a seguir,
     * os valores por baixo, e os botões numa linha só para eles. Serve as duas tabelas que
     * usam esta classe — Dispositivos e Descobertos. */
    .dev-table { table-layout: auto; min-width: 0; }
    .dev-table thead { display: none; }
    .dev-table tbody { display: grid; gap: 8px; }
    .dev-table tbody tr {
        display: grid; grid-template-columns: 1fr auto; gap: 4px 10px;
        align-items: baseline; padding: 11px 12px; border-radius: 12px;
        border: 1px solid var(--line);
        box-shadow: 0 1px 3px rgb(0 0 0 / .07);
    }
    /* As percentagens por coluna (26%, 16%, 10%…) continuavam a mandar porque `:nth-child`
       conta como classe e ganhava à minha regra — a célula dos botões ficava com 10% de
       nada. O `:nth-child(n)` aqui só serve para empatar a especificidade. */
    .dev-table tbody td:nth-child(n) { width: auto; }

    /* As percentagens por coluna e o corte deixam de fazer sentido fora da tabela.
       O `display` fica SÓ nesta regra: metê-lo também na das linhas pares dava-lhe mais
       especificidade do que a da célula dos botões, e em cada segunda ficha os botões
       voltavam a empilhar-se. O zebrado só precisa de apagar o fundo. */
    .dev-table tbody td {
        display: block; width: auto; padding: 0; border: 0;
        overflow: visible; white-space: normal; text-overflow: clip;
    }
    .dev-table tbody tr:nth-child(even) td,
    .dev-table tbody tr:hover td { background: none; }
    .dev-table tbody td:nth-child(1) { grid-area: 1 / 1; }
    .dev-table tbody td:nth-child(5) { grid-area: 1 / 2; text-align: right; }
    .dev-table tbody td:nth-child(2) { grid-area: 2 / 1; }
    .dev-table tbody td:nth-child(3) { grid-area: 2 / 2; text-align: right; }
    .dev-table tbody td:nth-child(3)::before {
        content: "métricas: "; font-size: .68rem; color: var(--muted);
    }
    .dev-table tbody td.vals         { grid-area: 3 / 1 / 4 / 3; word-break: break-word; }
    /* O painel dá 16px de recheio à primeira e à última coluna — bom numa tabela, mau
       numa ficha que já tem o seu próprio recheio: o conteúdo ficava indentado à esquerda
       e os botões afastados da margem direita. */
    .dev-panel .dev-table tbody td:first-child { padding-left: 0; }
    .dev-panel .dev-table tbody td:last-child  { padding-right: 0; }

    /* Os botões numa linha só, a repartir a largura e encaixados uns nos outros — é UM
       grupo de acções sobre o mesmo equipamento. Soltos e cada um do seu tamanho ficavam
       dois carimbos a meio de uma ficha vazia. */
    .dev-table tbody td.row-actions  { grid-area: 4 / 1 / 5 / 3; text-align: left;
                                       display: flex; gap: 0;
                                       white-space: normal; padding-top: 6px; }
    .dev-table tbody td.row-actions > * {
        flex: 1 1 0; justify-content: center; border-radius: 0;
    }
    .dev-table tbody td.row-actions > *:first-child { border-radius: 9px 0 0 9px; }
    .dev-table tbody td.row-actions > *:last-child  { border-radius: 0 9px 9px 0; }
    .dev-table tbody td.row-actions > *:only-child  { border-radius: 9px; }
    /* Célula vazia (equipamento sem acções) não deve deixar uma linha em branco. */
    .dev-table tbody td.row-actions:empty { display: none; }

    /* O cabeçalho de cada estação também não cabe em linha. */
    .dev-site-bar { flex-wrap: wrap; row-gap: 8px; }

    /* --- Linhas de configuração (utilizadores, clientes, estações, equipamentos) --------
     *
     * `avatar · identidade · botões` numa linha só. Os botões não encolhem, por isso quem
     * pagava a conta era a identidade: o email e o papel ficavam espremidos numa coluna de
     * três palavras de largura, e o último botão saía fora.
     *
     * A identidade fica com a linha de cima ao lado do avatar; os botões descem para uma
     * linha só deles e repartem-na. Uma regra serve as quatro páginas — são todas `cfg-row`. */
    .cfg-row { flex-wrap: wrap; row-gap: 10px; }
    .cfg-row .grow { flex: 1 1 calc(100% - 56px); }
    .cfg-row > .btn,
    .cfg-row > button,
    .cfg-row > a.btn { flex: 1 1 0; justify-content: center; }

    /* --- Tabelas largas em geral: uma ficha por linha ---------------------------------
     *
     * As estações, o diário e os equipamentos têm cada um o seu arranjo, porque neles a
     * disposição é informação (a cadeia lê-se em grelha, o diário lê-se em prosa). O resto
     * das tabelas — biblioteca, gravações, registos, clientes — só precisa de deixar de ser
     * tabela: etiqueta à esquerda, valor à direita, uma linha por coluna.
     *
     * A etiqueta vem do `data-col` de cada célula, que é o cabeçalho guardado onde ele faz
     * falta. Sem isso, uma ficha empilhada é uma coluna de valores sem se saber de quê.
     *
     * Marca-se a tabela com `tab-cartoes` em vez de apanhar todas as `.grid-table`: há
     * tabelas estreitas que já cabem, e transformá-las seria estragar o que está bem. */
    .tab-cartoes { min-width: 0; table-layout: auto; border-spacing: 0; }
    .tab-cartoes thead { display: none; }
    .tab-cartoes tbody { display: grid; gap: 8px; }
    .tab-cartoes tbody tr {
        display: block; padding: 11px 12px; border-radius: 12px;
        border: 1px solid var(--line); box-shadow: 0 1px 3px rgb(0 0 0 / .07);
    }
    /* `display: block` e a etiqueta em linha, e não uma linha de flex com o valor à direita.
       Numa flex, tudo o que está dentro da célula vira coluna: uma célula com título mais
       duas notas — como "o que aconteceu" nos registos — ficava com as três coisas lado a
       lado e espremidas a três letras de largura. Assim um valor curto fica ao lado da
       etiqueta e um conteúdo com vários blocos desce para baixo dela, sozinho. */
    .tab-cartoes tbody td {
        display: block; padding: 3px 0; border: 0; text-align: left;
        white-space: normal; overflow: visible; text-overflow: clip;
        overflow-wrap: anywhere;
    }
    /* `:nth-child(n)` e `.num` só para empatar a especificidade das larguras por coluna e
       do alinhamento à direita, que numa ficha punha um valor sozinho contra a margem. */
    .tab-cartoes tbody td:nth-child(n) { width: auto; }
    .tab-cartoes tbody td.num { text-align: left; }
    .tab-cartoes tbody tr:nth-child(even) td,
    .tab-cartoes tbody tr:hover td { background: none; }

    .tab-cartoes tbody td[data-col]::before {
        content: attr(data-col) " ";
        font-size: .64rem; text-transform: uppercase; letter-spacing: .05em;
        color: var(--muted); font-weight: 650; margin-right: 7px;
    }
    /* A primeira célula é o título da ficha: sem etiqueta e com um traço por baixo. */
    .tab-cartoes tbody td:first-child {
        font-weight: 650; text-align: left; padding-bottom: 6px; margin-bottom: 4px;
        border-bottom: 1px solid var(--line);
    }
    /* Célula sem nada não deve deixar uma linha em branco com uma etiqueta pendurada. */
    .tab-cartoes tbody td:empty { display: none; }

    /* Botões: uma linha só para eles, a repartir a largura e encaixados. */
    .tab-cartoes tbody td.row-actions {
        display: flex; gap: 0; padding-top: 8px; margin-top: 4px;
        border-top: 1px solid var(--line);
    }
    .tab-cartoes tbody td.row-actions::before { content: none; }
    .tab-cartoes tbody td.row-actions > * {
        flex: 1 1 0; justify-content: center; border-radius: 0;
    }
    .tab-cartoes tbody td.row-actions > *:first-child { border-radius: 9px 0 0 9px; }
    .tab-cartoes tbody td.row-actions > *:last-child  { border-radius: 0 9px 9px 0; }
    .tab-cartoes tbody td.row-actions > *:only-child  { border-radius: 9px; }
}

/* A versão, por baixo do menu. Discreta mas sempre lá: é a primeira pergunta de qualquer
   chamada de apoio, e até aqui obrigava a ir ao servidor ver um ficheiro. */
.versao-app { margin-top: auto; padding: 14px 13px 4px; font-size: .68rem; color: #52697c;
              letter-spacing: .03em; }
.versao-app b { color: #7e97ab; font-weight: 700; }

/* --- Parâmetros: alinhar a coluna dos valores e separar cada linha -------------------
 *
 * As caixas ficavam desalinhadas porque cada explicação tem um comprimento diferente e a
 * coluna da direita acompanhava a altura do texto. Passa a ter largura fixa e a colar ao
 * topo, e as linhas alternam de fundo — com vinte campos seguidos, o olho precisa de um
 * carril para não saltar de linha. */
.param { grid-template-columns: minmax(0,1fr) 260px; align-items: start; }
/* A alternância tem de SE VER. A 1,8% era teoricamente correcta e praticamente invisível —
   com vinte campos seguidos, o olho precisa de um carril a sério para não saltar de linha. */
.param:nth-child(odd) { background: rgba(255,255,255,.045); }
:root[data-theme="light"] .param:nth-child(odd) { background: rgba(10,30,50,.045); }
.param.alterado:nth-child(odd) { background: rgba(255,176,32,.07); }
.param-valor { justify-content: flex-end; padding-top: 2px; }
.param-valor .in, .param-valor select { width: 96px; }
.param-valor .btn { flex: 0 0 auto; }

/* Telemóvel: nada fica com largura de desktop.
   Uma caixa com largura fixa dentro de um ecrã de 390px empurra a página para o lado, e a
   página inteira passa a deslizar por causa de um elemento. Aqui a regra é dura: no
   telemóvel tudo cabe na largura que há. */
@media (max-width: 700px) {
    .content, .panel, .modal, .form, .field-grid, .filter-bar,
    .kpi-grid, .cmd-grid, .plugins, .extras-par { max-width: 100%; }
    .content { width: 100%; }
    .panel { min-width: 0; }
    /* Inputs e selects a toda a largura: um campo de 96px num telemóvel não se acerta. */
    .field-grid .in, .field-grid input, .field-grid select, .form .in { width: 100%; max-width: 100%; }
    .param-valor .in, .param-valor select { width: 100%; }
    .param-valor { flex-wrap: wrap; }
}

/* Gravações: uma caixa por estação. Cem clips de seis estações misturados por hora não
   respondem à única pergunta que se faz aqui — "o que tenho gravado da rádio X?". */
.grav-estacoes { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
                 gap: 12px; }
.grav-card { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; align-items: center;
             text-align: left; cursor: pointer; font: inherit; padding: 14px 16px; }
.grav-card:hover { border-color: var(--cyan); }
.gc-nome b { display: block; font-size: .95rem; }
.gc-nome small { font-size: .68rem; color: var(--muted); font-family: var(--mono); }
.gc-num { text-align: right; }
.gc-num b { display: block; font-size: 1.5rem; line-height: 1; color: var(--cyan); }
.gc-num span { font-size: .62rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.gc-meta { grid-column: 1 / -1; display: flex; justify-content: space-between; gap: 10px;
           font-size: .7rem; color: var(--muted); margin-top: 6px;
           border-top: 1px solid var(--line); padding-top: 8px; }

/* Matriz de perfis: uma coluna por capacidade, uma linha por papel. */
.perfis-table td:first-child b { display: block; font-size: .88rem; }
.perfis-table td:first-child small { font-size: .7rem; color: var(--muted); }
.perfis-table input[type="checkbox"] { width: 17px; height: 17px; cursor: pointer; }
.perfis-table input[type="checkbox"]:disabled { cursor: not-allowed; opacity: .45; }
.perfis-table .linha-fixa { background: rgba(80,200,255,.05); }

/* --- Manual: um documento longo tem de se ler como um documento --------------------- */
.manual-layout { display: grid; grid-template-columns: 220px minmax(0,1fr); gap: 18px;
                 align-items: start; }
.manual-indice { position: sticky; top: 78px; display: grid; gap: 2px;
                 max-height: calc(100dvh - 100px); overflow-y: auto; padding-right: 6px; }
.manual-indice b { font-size: .66rem; text-transform: uppercase; letter-spacing: .07em;
                   color: var(--muted); padding: 4px 8px 8px; }
/* Número e título em colunas: o olho desce pelos títulos, não por dentro do texto. */
.manual-indice a { display: grid; grid-template-columns: 18px 1fr; gap: 7px; align-items: baseline;
                   font-size: .76rem; color: var(--muted); padding: 5px 8px 5px 6px;
                   border-radius: 7px; border-left: 2px solid transparent; line-height: 1.35; }
.manual-indice a .n { font-family: var(--mono); font-size: .68rem; text-align: right;
                      color: var(--muted); opacity: .55; }
.manual-indice a:hover { color: var(--text); background: rgba(255,255,255,.04);
                         border-left-color: var(--cyan); }
/* O capítulo em que se está. É o que transforma a lista em índice: deixa de dizer só para
   onde se pode ir e passa a dizer onde se está. */
.manual-indice a.atual { color: var(--text); font-weight: 600; background: rgba(0,200,255,.08);
                         border-left-color: var(--cyan); }
.manual-indice a.atual .n { color: var(--cyan); opacity: 1; }

/* A escolha de capítulo: só existe onde o índice ao lado não cabe. */
.manual-picker { display: none; }

/* A largura de leitura importa: uma linha de 1700px não se lê, salta-se. */
.manual { max-width: 900px; padding: 26px 34px 40px; line-height: 1.65; font-size: .87rem; }
.manual h1 { font-size: 1.5rem; margin: 0 0 4px; }
.manual h2 { font-size: 1.15rem; margin: 34px 0 10px; padding-top: 18px;
             border-top: 1px solid var(--line); scroll-margin-top: 80px; }
.manual h3 { font-size: .98rem; margin: 22px 0 8px; color: var(--cyan); }
.manual h4 { font-size: .88rem; margin: 16px 0 6px; }
.manual p, .manual li { color: var(--text); }
.manual ul, .manual ol { padding-left: 22px; }
.manual li { margin: 3px 0; }
.manual code { font-family: var(--mono); font-size: .82em; padding: 1px 5px; border-radius: 5px;
               background: rgba(255,255,255,.07); }
.manual pre { background: rgba(0,0,0,.28); border: 1px solid var(--line); border-radius: 10px;
              padding: 12px 14px; overflow-x: auto; }
.manual pre code { background: none; padding: 0; font-size: .78rem; line-height: 1.5; }
.manual blockquote { margin: 14px 0; padding: 10px 16px; border-left: 3px solid var(--amber);
                     background: rgba(255,176,32,.06); border-radius: 0 8px 8px 0; }
.manual blockquote p { margin: 4px 0; }
.manual table { width: 100%; border-collapse: collapse; margin: 14px 0; font-size: .8rem;
                display: block; overflow-x: auto; }
.manual th { text-align: left; font-size: .66rem; text-transform: uppercase; letter-spacing: .06em;
             color: var(--muted); padding: 8px 10px; border-bottom: 1px solid var(--line2); }
.manual td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
.manual tr:last-child td { border-bottom: 0; }
.manual hr { border: 0; border-top: 1px solid var(--line); margin: 26px 0; }
.manual a { color: var(--cyan); }
.manual details { margin: 10px 0; padding: 10px 14px; border: 1px solid var(--line);
                  border-radius: 10px; }
.manual summary { cursor: pointer; font-weight: 600; font-size: .82rem; }
:root[data-theme="light"] .manual code { background: rgba(10,30,50,.07); }
:root[data-theme="light"] .manual pre { background: rgba(10,30,50,.05); }

@media (max-width: 1024px) {
    .manual-layout { grid-template-columns: 1fr; }
    /* A lista ao lado desaparece e fica a escolha. Colada ao topo: num documento longo o
       índice tem de estar onde a pessoa já está — se ficasse em cima, voltar a ele obrigava
       a rolar o manual todo para trás, e ninguém rola para trás, desiste. */
    .manual-indice { display: none; }
    .manual-picker { display: block; position: sticky; top: 8px; z-index: 5; margin-bottom: 12px; }
    .manual { padding: 18px 16px 30px; max-width: 100%; }
}

/* --- Telemóvel: as caixas do cockpit a 100%, não a 30% ------------------------------
 *
 * As grelhas usam `repeat(auto-fill, minmax(...))`, e num ecrã de 390px o `minmax` de 260px
 * cabia uma vez — mas a coluna ficava com a largura MÍNIMA em vez de esticar, e as caixas
 * apareciam a ocupar um terço com o resto em branco.
 *
 * Abaixo dos 700px não há decisão a tomar: é uma coluna, e ocupa tudo. */
@media (max-width: 700px) {
    .kpi-grid, .cmd-grid, .plugins, .extras-par, .grav-estacoes,
    .cockpit-top, .actividade, .fmdx-grid, .biblioteca-grid, .agentes-kpis,
    .keymetrics, .panels, .sites-escolha, .radios-resumo {
        grid-template-columns: 1fr !important;
    }

    /* Nada com largura mínima maior do que o ecrã. */
    .kpi, .panel, .grav-card, .plugin, .rr-item, .kpi-strip .kpi {
        min-width: 0; width: auto;
    }

    /* O mapa acompanha a largura em vez de ficar com o tamanho do desenho. */
    .map-wrap, .map-wrap svg { width: 100%; height: auto; }

    /* Barra de topo: só o essencial cabe, o resto sai. */
    .topbar-title { font-size: 1.05rem; }
    .eyebrow { display: none; }
}

/* Entre 700 e 1024 (tablet e portátil pequeno) duas colunas chegam — três apertavam. */
@media (min-width: 701px) and (max-width: 1024px) {
    .kpi-grid, .agentes-kpis, .grav-estacoes, .plugins {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

/* --- Descobertos na rede, debaixo do agente que os encontrou ---------------------------
 *
 * O botão "procurar agora" está na linha do agente e o resultado vivia noutra página,
 * noutro menu: carregava-se e não acontecia nada visível. A lista abre aqui, fechada por
 * omissão — normalidade ocupa pouco espaço, e uma rede varrida sem novidades não tem de
 * empurrar os outros agentes para fora do ecrã. */
.linha-descobertos > td { padding: 0 12px 10px !important; background: none !important; }
.descobertos-toggle {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: .74rem; color: var(--muted); padding: 4px 0;
}
.descobertos-toggle:hover { color: var(--cyan); }
.descobertos-toggle .det { color: var(--muted); opacity: .8; }

/* Um varrimento por agente, dentro do painel da estação. O traço à esquerda separa as
   redes sem precisar de mais uma caixa dentro de uma caixa. */
.descoberta-agente { border-left: 3px solid var(--line); padding-left: 12px; margin: 10px 0 4px; }
.descoberta-agente .da-cab { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
                             margin-bottom: 6px; }
.descoberta-agente .da-cab b { font-size: .84rem; }
.descoberta-agente .da-cab .det { font-size: .7rem; color: var(--muted); }

/* --- Comandos dentro da ficha do equipamento ------------------------------------------
 *
 * Uma linha por comando: o que faz à esquerda, os argumentos ao meio, o botão à direita.
 * A cor da borda esquerda é o RISCO e não decoração — um "reiniciar pela tomada" e um
 * "alterar potência" não podem ler-se com o mesmo peso a três metros do ecrã. */
.cmd-lista { display: grid; gap: 8px; margin-top: 4px; }
.cmd-linha {
    /* Areas explicitas: o que o comando FAZ em cima a esquerda, o botao em cima a direita,
       e os argumentos numa linha so deles por baixo. Com tres colunas simples, um comando
       com tres campos espremia o titulo ate a coluna ficar com tres letras de largura --
       e o titulo e a unica parte que diz o que se esta prestes a fazer. */
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "id acao" "args args";
    gap: 10px 12px; align-items: center;
    padding: 11px 13px; border-radius: 12px;
    border: 1px solid var(--line); border-left: 3px solid var(--line);
    background: color-mix(in srgb, var(--panel2) 45%, transparent);
}
.cmd-id { grid-area: id; }
.cmd-args { grid-area: args; }
.cmd-accao { grid-area: acao; }
.cmd-linha.risco-fisico  { border-left-color: var(--amber); }
.cmd-linha.risco-critico { border-left-color: var(--red); }
.cmd-id b { display: block; font-size: .86rem; }
.cmd-id small { color: var(--muted); font-size: .72rem; line-height: 1.35; display: block; margin-top: 2px; }
.cmd-args { display: flex; gap: 8px; flex-wrap: wrap; }
.cmd-args label { font-size: .64rem; text-transform: uppercase; letter-spacing: .04em;
                  color: var(--muted); display: grid; gap: 3px; }
.cmd-args input { width: 110px; font-size: .8rem; padding: 5px 8px; }
.cmd-accao { display: flex; align-items: center; gap: 8px; }
.cmd-accao .tag.risco { font-size: .6rem; }
.risco-fisico  .tag.risco { color: var(--amber); border-color: color-mix(in srgb, var(--amber) 40%, transparent); }
.risco-critico .tag.risco { color: var(--red);   border-color: color-mix(in srgb, var(--red) 40%, transparent); }

@media (max-width: 760px) {
    .cmd-linha { grid-template-columns: 1fr; grid-template-areas: "id" "args" "acao"; }
    .cmd-accao { justify-content: flex-end; }
}

/* --- Apoio e controlo: o que não é etapa do sinal ------------------------------------
 *
 * NÃO é uma cadeia e por isso não leva setas. Entre três Shellys, um AC e um router não
 * há sequência nenhuma — setas ali seriam uma mentira visual, e a cadeia de cima perderia
 * o significado que tem por ser a única.
 *
 * Mosaicos pequenos, agrupados por natureza: numa estação grande são quinze aparelhos, e
 * uma faixa corrida deles é uma parede. Procura-se pelo que se quer FAZER. */
.apoio-band { display: flex; flex-wrap: wrap; gap: 16px 22px; margin-bottom: 6px; }
.apoio-cat { display: grid; gap: 6px; }
.ac-titulo { font-size: .58rem; font-weight: 750; letter-spacing: .12em; text-transform: uppercase;
             color: var(--muted); }
.ac-tiles { display: flex; flex-wrap: wrap; gap: 6px; }

.ac-tile {
    display: grid; gap: 1px; text-align: left; cursor: pointer;
    padding: 7px 11px; border-radius: 10px; font-family: inherit;
    border: 1px solid var(--line);
    background: color-mix(in srgb, var(--panel2) 55%, transparent);
    min-width: 108px;
}
.ac-tile:hover { border-color: color-mix(in srgb, var(--cyan) 50%, transparent); }
.act-nome  { font-size: .72rem; font-weight: 650; }
.act-valor { font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* A cor é o estado do pior valor do aparelho — a mesma regra do resto do cockpit. */
.ac-tile.warn { border-color: color-mix(in srgb, var(--amber) 45%, transparent);
                background: color-mix(in srgb, var(--amber) 9%, transparent); }
.ac-tile.warn .act-valor { color: var(--amber); font-weight: 700; }
.ac-tile.bad  { border-color: color-mix(in srgb, var(--red) 50%, transparent);
                background: color-mix(in srgb, var(--red) 10%, transparent); }
.ac-tile.bad .act-valor { color: var(--red); font-weight: 750; }
/* Leitura velha: o valor pode ser bom e já não valer nada. */
.ac-tile.stale { opacity: .55; }

/* --- Scripts da Biblioteca: o nome não chega -----------------------------------------
 *
 * Metade destes ficheiros foi substituída pelo instalador, e um nome de ficheiro não diz
 * a ninguém qual usar. A explicação é a parte útil da linha; o nome do ficheiro passou
 * para o rodapé, onde serve para o encontrar no disco e mais nada. */
.fixo-linha .fixo-que-faz { display: block; font-size: .74rem; line-height: 1.45;
                            color: var(--muted); margin: 3px 0 2px; }
.fixo-linha .fixo-aviso { display: block; font-size: .72rem; line-height: 1.4;
                          color: var(--amber); margin-bottom: 3px; }
.fixo-linha .fixo-aviso::before { content: "▲ "; }

/* A alcunha na lista dos descobertos: um campo discreto, que não parece um formulário.
   Escreve-se e sai-se — grava ao perder o foco. */
.alcunha-campo { width: 100%; min-width: 130px; font-size: .8rem; padding: 5px 8px;
                 background: transparent; border: 1px dashed var(--line); border-radius: 8px;
                 color: var(--text); }
.alcunha-campo:hover { border-color: color-mix(in srgb, var(--cyan) 40%, transparent); }
.alcunha-campo:focus { border-style: solid; border-color: var(--cyan); background: var(--panel2); }
.alcunha-campo::placeholder { color: var(--muted); opacity: .6; font-style: italic; }

/* --- Página dos agentes: tudo com o mesmo corpo -------------------------------------
 *
 * O "Mudar estação" e o "Tornar principal" são botões a sério — 30px de altura, cantos
 * redondos, moldura. Ao lado deles, os interruptores do RECOLHE, as cápsulas do ESTADO e a
 * caixa do PAPEL eram etiquetas magras de alturas diferentes: a linha lia-se como duas
 * coisas distintas coladas, e o olho tropeçava a cada agente.
 *
 * Passam todos a ter o mesmo corpo. A COR não muda — é ela que diz o que cada um é, e
 * uniformizar cores seria trocar um problema de forma por um de significado. */
.recolhe .cap,
.agentes-estado .badge,
.papel-sel {
    min-height: 30px; padding: 6px 11px;
    border-radius: 9px; border: 1px solid var(--line2);
    font-size: .73rem; font-weight: 650; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
    white-space: nowrap; vertical-align: middle;
}

/* Os interruptores mantêm a cor que os distingue, agora com corpo por baixo dela. */
.recolhe .cap.on {
    color: var(--green);
    border-color: color-mix(in srgb, var(--green) 42%, transparent);
    background: color-mix(in srgb, var(--green) 13%, var(--panel2));
}
.recolhe .cap.off {
    color: var(--red);
    border-color: color-mix(in srgb, var(--red) 42%, transparent);
    background: color-mix(in srgb, var(--red) 11%, var(--panel2));
}
/* O terceiro estado continua a não afirmar nada: cinzento e tracejado. */
.recolhe .cap.ind {
    color: var(--muted); border-style: dashed; border-color: var(--line);
    background: var(--panel2);
}
.recolhe .cap.acao {
    color: var(--cyan);
    border-color: color-mix(in srgb, var(--cyan) 42%, transparent);
    background: color-mix(in srgb, var(--cyan) 12%, var(--panel2));
}
.recolhe .cap:hover:not(:disabled) { filter: brightness(1.18); }
.recolhe .cap:disabled { opacity: .45; cursor: default; }
.recolhe { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }

/* O selector do papel deixa de ser uma caixa de formulário no meio de botões. */
.papel-sel {
    background: var(--panel2); color: var(--text);
    padding-right: 26px; cursor: pointer; min-width: 168px;
    justify-content: flex-start;
}
.papel-sel:hover { border-color: color-mix(in srgb, var(--cyan) 45%, transparent); }

/* As cápsulas de estado com o mesmo corpo, cada uma na sua cor. */
.agentes-estado { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.agentes-estado .badge.s-ok {
    color: var(--green); border-color: color-mix(in srgb, var(--green) 42%, transparent);
    background: color-mix(in srgb, var(--green) 13%, var(--panel2));
}
.agentes-estado .badge.s-bad {
    color: var(--red); border-color: color-mix(in srgb, var(--red) 45%, transparent);
    background: color-mix(in srgb, var(--red) 12%, var(--panel2));
}
.agentes-estado .badge.s-warn {
    color: var(--amber); border-color: color-mix(in srgb, var(--amber) 45%, transparent);
    background: color-mix(in srgb, var(--amber) 12%, var(--panel2));
}
.agentes-estado .badge.s-unknown {
    color: var(--muted); border-color: var(--line); background: var(--panel2);
}

/* O traço separa AGENTES, não um agente da sua própria linha de descobertos.
 *
 * Cada agente ocupa duas linhas da tabela: a dele e a do varrimento que fez. O traço
 * vinha da primeira, e o resultado era um risco a cortar o agente ao meio — a informação
 * dele em cima, o que ele encontrou em baixo, com uma fronteira entre as duas como se
 * fossem coisas de gente diferente. Passa para o fim do par. */
.grid-table tbody tr:has(+ .linha-descobertos) > td { border-bottom: 0; }
.linha-descobertos > td { border-bottom: 1px solid var(--line) !important; }
.grid-table tbody tr.linha-descobertos:last-child > td { border-bottom: 0 !important; }

/* --- As colunas alinhadas ENTRE painéis ------------------------------------------------
 *
 * Cada estação tem a sua tabela, e tabelas independentes dimensionam as colunas cada uma à
 * sua maneira: a VERSÃO do Seixal caía muito à frente da do Algarve, porque o conteúdo de
 * uma era mais largo do que o da outra. Com seis estações, cada painel tinha o seu
 * alinhamento e a página deixava de se ler de cima a baixo.
 *
 * `table-layout: fixed` com larguras declaradas resolve-o: as colunas passam a depender do
 * que está escrito AQUI e não do que cada estação tem lá dentro. O preço é que as larguras
 * passam a ser uma decisão — e é isso mesmo que se quer. */
.agentes-tabela { table-layout: fixed; }
.agentes-tabela th:nth-child(1), .agentes-tabela td:nth-child(1) { width: 150px; }
.agentes-tabela th:nth-child(2), .agentes-tabela td:nth-child(2) { width: 152px; }
.agentes-tabela th:nth-child(3), .agentes-tabela td:nth-child(3) { width: auto; }
.agentes-tabela th:nth-child(4), .agentes-tabela td:nth-child(4) { width: 52px; }
.agentes-tabela th:nth-child(5), .agentes-tabela td:nth-child(5) { width: 96px; }
.agentes-tabela th:nth-child(6), .agentes-tabela td:nth-child(6) { width: 400px; }












/* A linha dos descobertos atravessa tudo e não obedece às larguras acima. */
.agentes-tabela tr.linha-descobertos > td { width: auto; }

/* No telemóvel a tabela vira fichas e as percentagens deixam de fazer sentido. */
@media (max-width: 760px) {
    .agentes-tabela { table-layout: auto; }
    .agentes-tabela tbody td:nth-child(n) { width: auto; }
}

/* Um pouco mais apertadas AQUI, e só aqui: sete pastilhas numa linha não cabem com o
   recheio do resto da aplicação, e reduzi-lo em todo o lado por causa desta tabela seria
   arrastar uma decisão local para páginas que não têm este problema. */
.agentes-tabela .recolhe .cap { padding: 6px 8px; font-size: .69rem; }
.agentes-tabela .agentes-estado .badge { padding: 6px 8px; font-size: .69rem; }
.agentes-tabela .papel-sel { min-width: 0; width: 100%; }
.agentes-tabela td.acoes-cel { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }

/* O estado e as acções na MESMA linha, lado a lado.
 *
 * Estavam em colunas separadas: uma cápsula à esquerda, um botão à direita, e um vão de
 * espaço branco entre as duas — largura gasta a separar coisas que se lêem juntas ("como
 * está" e "o que faço"). Juntas, sobra a largura que faltava ao Recolhe. Só quebram para
 * a linha de baixo quando deixar mesmo de caber. */
.agentes-tabela td.agentes-estado,
.agentes-tabela td.acoes-cel { vertical-align: middle; }
.agentes-tabela td.agentes-estado > .caixa,
.agentes-tabela td.acoes-cel > .caixa {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
}
.agentes-tabela td.agentes-estado > .caixa { justify-content: flex-end; }

/* As pastilhas do Recolhe em LINHA até deixar de caber — nunca uma por linha por o
   navegador ter decidido que a coluna era estreita. */
.agentes-tabela .recolhe { flex-wrap: wrap; gap: 5px; }
.agentes-tabela .recolhe .cap { flex: 0 0 auto; }

/* Num quadro de larguras fixas, um cabeçalho maior do que a coluna transborda para cima do
   seguinte — e "ÚLTIMA LEITURA" ia escrever-se por cima de "ESTADO E ACÇÕES". Que corte,
   em vez de invadir: um título cortado lê-se mal, dois títulos sobrepostos não se lêem. */
.agentes-tabela th { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- A ligação ao servidor, à vista ---------------------------------------------------
 *
 * O Blazor mostra este elemento sozinho: põe-lhe a classe `components-reconnect-show`
 * enquanto tenta, e `components-reconnect-failed` quando desiste. Sem ele no HTML, uma
 * queda de ligação deixa a página com bom aspecto e completamente inerte — que foi
 * exactamente o "os botões não funcionam no telemóvel".
 *
 * Fica em BAIXO e não no meio: é um aviso, não um bloqueio. Quem está a olhar para um
 * valor no ecrã continua a poder lê-lo enquanto a ligação não volta. */
#components-reconnect-modal { display: none; }
#components-reconnect-modal.components-reconnect-show,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 9000;
    padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
    background: var(--panel); border-top: 2px solid var(--amber);
    box-shadow: 0 -8px 26px rgb(0 0 0 / .28);
}
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-rejected { border-top-color: var(--red); }

.reconecta-caixa { display: flex; align-items: center; gap: 12px; max-width: 760px; margin: 0 auto; }
.reconecta-caixa b { display: block; font-size: .86rem; }
.reconecta-caixa small { display: block; font-size: .74rem; color: var(--muted); margin-top: 2px; }
.reconecta-caixa > div { flex: 1; min-width: 0; }

/* Uma bola a pulsar enquanto tenta; parada e vermelha quando desistiu. */
.reconecta-bola { width: 12px; height: 12px; border-radius: 50%; flex: 0 0 12px; background: var(--amber);
                  animation: reconecta-pulso 1.1s ease-in-out infinite; }
@keyframes reconecta-pulso { 0%,100% { opacity: 1; transform: scale(1); } 50% { opacity: .35; transform: scale(.8); } }

/* Mostra-se o texto do estado em que se está, e só esse. */
.components-reconnect-show .reconecta-falhou { display: none; }
.components-reconnect-failed .reconecta-a-tentar,
.components-reconnect-rejected .reconecta-a-tentar { display: none; }
.components-reconnect-failed .reconecta-bola,
.components-reconnect-rejected .reconecta-bola { background: var(--red); animation: none; }

/* Auditoria no telemóvel: a HORA é o título da ficha.
 *
 * Numa auditoria procura-se sempre pela mesma pergunta — "o que aconteceu às 18:53?" — e
 * por isso é a hora que encabeça cada entrada, e não o utilizador. A bola do tipo sai: uma
 * coluna inteira para nove pixéis, quando a cor já está na etiqueta da acção ao lado. */
@media (max-width: 760px) {
    .aud-table { min-width: 0; }
    .aud-table tbody td.col-ponto { display: none; }
    .aud-table tbody td.num {
        font-weight: 700; font-size: .9rem; text-align: left;
        padding-bottom: 6px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
    }
    /* A linha inteira ganha a cor quando a acção falhou — numa auditoria é o que se procura. */
    .aud-table tbody tr.aud-falha { border-left: 3px solid var(--red); }
}

/* --- As linhas de parâmetro empilham no telemóvel (outra vez) --------------------------
 *
 * Havia uma regra a fazer isto lá atrás, e uma outra MAIS ABAIXO no ficheiro voltava a
 * declarar duas colunas — sem media query, por isso valia em todas as larguras e ganhava
 * por vir depois. O resultado era a coluna do texto espremida a três palavras de largura
 * nos Perfis, nos Parâmetros e nas Ligações, todos ao mesmo tempo.
 *
 * Fica no fim do ficheiro de propósito: é o último a falar, e nenhuma regra futura o
 * desfaz sem que quem a escrever passe por aqui. */
@media (max-width: 760px) {
    .param { grid-template-columns: 1fr !important; gap: 8px; }
    .param-valor { justify-content: flex-start; }
    .param-valor .in, .param-valor select { width: 100%; max-width: 220px; text-align: left; }
}

/* --- A matriz de perfis no telemóvel: uma ficha por PAPEL ------------------------------
 *
 * A matriz é uma comparação — papéis em linhas, capacidades em colunas — e num telemóvel
 * não há largura para comparar coisa nenhuma. Rolava de lado, e quem rolava perdia de vista
 * o nome do papel que estava a editar: marcava-se um visto sem saber a quem.
 *
 * Cada papel passa a ser uma ficha com as capacidades em lista, cada uma com o seu nome ao
 * lado do visto. Deixa de se poder comparar dois papéis de relance — mas isso já não se
 * conseguia fazer, e assim pelo menos sabe-se o que se está a marcar. */
@media (max-width: 760px) {
    .perfis-table { min-width: 0; table-layout: auto; }
    .perfis-table thead { display: none; }
    .perfis-table tbody { display: grid; gap: 10px; }
    .perfis-table tbody tr {
        display: block; padding: 12px 13px; border-radius: 12px;
        border: 1px solid var(--line); box-shadow: 0 1px 3px rgb(0 0 0 / .07);
    }
    .perfis-table tbody td { display: flex; align-items: center; justify-content: space-between;
                             gap: 12px; padding: 6px 0; border: 0; text-align: left; width: auto; }
    .perfis-table tbody td:first-child {
        display: block; padding-bottom: 8px; margin-bottom: 4px;
        border-bottom: 1px solid var(--line);
    }
    /* A etiqueta vem do cabeçalho guardado na célula — sem ela era uma coluna de vistos. */
    .perfis-table tbody td[data-col]::before {
        content: attr(data-col); font-size: .78rem; color: var(--text); font-weight: 600;
    }
    .perfis-table tbody tr.linha-fixa { opacity: .7; }
}

/* Dentro de um modal, a largura útil é ainda menor do que numa página — e o `min-width`
   das tabelas largas empurrava o conteúdo para fora da janela em vez de a fazer rolar. */
@media (max-width: 760px) {
    .modal .tabela-wrap { overflow-x: auto; }
    .modal .grid-table.tab-cartoes { min-width: 0; }
}

/* --- Cadeias técnicas longas partem-se; não empurram a página -------------------------
 *
 * Um caminho de ficheiro, uma cadeia de ligação ou um MAC não têm espaços, e por isso o
 * navegador trata-os como uma palavra só: estica o contentor até caberem. Num telemóvel
 * isso arrasta o painel inteiro para fora do ecrã — e o sintoma que se vê não é "aquele
 * caminho é comprido", é "a página tem scroll horizontal".
 *
 * `anywhere` e não `break-word`: o `break-word` só parte quando a palavra sozinha não cabe
 * na linha, e aqui há sempre uma etiqueta ao lado a ocupar espaço primeiro. */
code, .det.cod, .param-id small, .param-valor .det {
    overflow-wrap: anywhere; word-break: break-word;
}
.param, .param-id, .param-valor { min-width: 0; }
.panel { overflow-wrap: anywhere; }

/* MAS UM CABEÇALHO DE TABELA NÃO PARTE PALAVRAS.
 *
 * A regra acima é larga de propósito — apanha caminhos, MACs e cadeias de ligação, que não
 * têm espaços e esticam o painel. Só que apanhava também os títulos das colunas: com uma
 * coluna estreita, "EQUIPAMENTO" partia-se em "EQUIPA / MENTO" e o cabeçalho da tabela dos
 * registos passava a ter quatro linhas de palavras cortadas ao meio. Um título partido não
 * se lê — e é o título que diz o que está por baixo.
 *
 * Uma coluna estreita de mais resolve-se dando-lhe largura ou deixando a tabela rolar, não
 * partindo a palavra. */
.grid-table thead th { overflow-wrap: normal; word-break: normal; hyphens: none; }

/* Uma tabela pode continuar a rolar dentro da sua caixa — o que não pode é a caixa crescer. */
.tabela-wrap { max-width: 100%; }

/* ==========================================================================
   A ESTAÇÃO NUMA JANELA DE TEMPO   —   /estacao/{tenant}/{site}

   Vive aqui e não num `.razor.css` de propósito: este projecto não usa CSS
   isolado em página nenhuma, e um ficheiro isolado sozinho no meio das outras
   é uma convenção a mais para manter. Escrevi-o primeiro isolado e as regras
   não pegavam — estavam no pacote e o HTML não trazia o atributo do scope.
   ========================================================================== */

.janela-barra {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

.janelas { display: flex; gap: .35rem; }

.equip { margin-bottom: .6rem; }

.equip-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: .5rem;
}

.equip-nome { display: flex; align-items: baseline; gap: .5rem; min-width: 0; }
.equip-nums { display: flex; align-items: baseline; gap: .9rem; flex-wrap: wrap; }

/* A PERCENTAGEM É O QUE SE LÊ PRIMEIRO, e por isso é a única coisa grande aqui. */
.equip-nums .uptime {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    font-size: 1.05rem;
}

.equip-nums .uptime.bom  { color: #15803d; }
.equip-nums .uptime.meio { color: #b45309; }
.equip-nums .uptime.mau  { color: #b91c1c; }

/* A FAIXA.

   Sem espaço entre fatias e com altura generosa: o que interessa ver é o PADRÃO — um corte
   longo tem uma forma diferente de vinte curtos, e essas duas coisas mandam procurar em
   sítios diferentes. Fatias separadas por margens partem o padrão e fazem tudo parecer
   intermitente. */
.faixa {
    display: flex;
    height: 26px;
    border-radius: 4px;
    overflow: hidden;
    background: #eef1f4;
}

.faixa .fatia { flex: 1 1 0; min-width: 0; }
.faixa .fatia.ok  { background: #22c55e; }
.faixa .fatia.mau { background: #ef4444; }

/* O centro cego é CINZENTO e não vermelho, deliberadamente: não é uma falha do equipamento,
   e pintá-lo da cor das falhas seria a mesma mentira que a percentagem evita. */
.faixa .fatia.cego { background: #cbd5e1; }
/* Nunca foi fonte de telemetria: nem verde nem vermelho, porque nao ha nada a
   avaliar. Mais apagado do que o "cego" de proposito -- aquele e o centro que nao
   estava a ouvir, isto e um aparelho que nunca teve nada para dizer. */
.faixa .fatia.sem-dados { background: #cbd5e1; opacity: .35; }

.faixa-legenda {
    display: flex;
    justify-content: space-between;
    margin-top: .25rem;
    opacity: .6;
}

.nota-cego {
    border-left: 3px solid #cbd5e1;
    font-size: .9rem;
    line-height: 1.5;
}

/* O atalho para a saúde, na lista principal do cockpit.

   Discreto de propósito: aquela lista serve para ler o AGORA de seis estações de relance, e
   um botão a sério em cada linha competia com o que interessa. Ganha contraste ao passar o
   rato, que é quando alguém está a decidir clicar. */
.est-saude {
    margin-left: .5rem;
    font-size: .78rem;
    opacity: .55;
    text-decoration: none;
    white-space: nowrap;
}

.est-saude:hover { opacity: 1; text-decoration: underline; }

/* As medições na janela, dentro do painel de cada equipamento. */
.ver-medicoes { margin-top: .5rem; }

/* Números alinhados à direita e com largura fixa por dígito: é o que deixa comparar mínimo,
   média e máximo de relance, sem os ler um a um. Com largura variável, o olho perde a coluna. */
.medicoes .num {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.medicoes td .cod { display: block; opacity: .55; }

/* A porta de volta ao todo, quando a página da estação está filtrada a um equipamento.
   Discreta de propósito: quem veio ver UM aparelho não deve ser puxado para fora dele, mas
   também não pode ficar sem saber que está a ver um recorte. */
.janela-barra .ver-tudo {
    margin-left: .55rem; color: var(--cyan); text-decoration: none; font-weight: 650;
}
.janela-barra .ver-tudo:hover { text-decoration: underline; }

/* «sem telemetria» não é um estado de alarme: é a ausência de medida num aparelho que existe
   para outra coisa (abrir a interface, mandar um SMS). Cinzento neutro de propósito — verde
   dizia que está bem, e ninguém mediu nada para o poder dizer. */
.uptime.sem-telemetria {
    color: var(--muted); font-weight: 600; font-size: .82rem; letter-spacing: .01em;
}

/* AS DUAS VERDADES DE UMA ESTAÇÃO: esteve no ar, e conseguimos vê-la. Lado a lado porque é
   a comparação que informa — separadas em painéis diferentes, ninguém as cruza. */
.duas-verdades { display: flex; flex-wrap: wrap; gap: 26px; align-items: flex-start; }
.duas-verdades .verdade { display: flex; flex-direction: column; gap: 2px; min-width: 190px; }
.duas-verdades .rotulo {
    font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); font-weight: 700;
}
.duas-verdades .verdade > b { font-size: 1.9rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
.duas-verdades .verdade > b.bom  { color: var(--green); }
.duas-verdades .verdade > b.meio { color: var(--amber); }
.duas-verdades .verdade > b.mau  { color: var(--red); }
.duas-verdades .verdade > b.sem-testemunha { font-size: 1.15rem; color: var(--muted); font-weight: 650; }
.duas-verdades .verdade > small { color: var(--muted); font-size: .76rem; max-width: 30ch; }
/* A leitura já feita, para quem não quer cruzar os dois números de cabeça. */
.duas-verdades .nota {
    border-left: 3px solid var(--green); padding-left: 12px; justify-content: center;
}
.duas-verdades .nota > span { font-weight: 700; }

/* ── OUVIR: a página pública ────────────────────────────────────────────────
   Escura e fixa, sem seguir o tema do cockpit. Quem abre isto não tem sessão,
   logo não tem preferência guardada — e a antessala (login) já é escura. Um
   claro/escuro que dependesse do sistema do visitante dava duas páginas
   diferentes para a mesma coisa, sem ninguém as poder comparar.            */
.ouvir-publico {
    /* Cores PROPRIAS, e nao os tokens do cockpit. Os tokens mudam com
       :root[data-theme="light"], e quem abre isto nao tem sessao nem preferencia
       guardada -- herdava a do dono do PC, que e arbitraria para um visitante.
       Aqui e sempre escuro, como a antessala de onde se chega. */
    --op-bg: #050a11; --op-painel: #0b1420; --op-linha: #1d3348;
    --op-texto: #f2f9ff; --op-muted: #93a9bd;
    /* Faz o browser desenhar os controlos nativos do <audio> em escuro. Sem isto
       ficavam seis caixas brancas a saltar de um fundo escuro. */
    color-scheme: dark;
    min-height: 100vh; background: var(--op-bg); color: var(--op-texto);
    padding: 26px 20px 40px; position: relative; overflow-x: hidden; }

/* Véu mais forte do que o do login: ali há um cartão só, aqui há nove, e a fotografia
   a passar por trás de tudo tirava a legibilidade aos números pequenos. */
.op-veu { position: fixed; inset: 0; z-index: 1; pointer-events: none;
    background: linear-gradient(180deg, rgba(5,10,17,.72) 0%, rgba(5,10,17,.88) 45%, rgba(5,10,17,.95) 100%); }
.ouvir-publico > *:not(.login-bg):not(.op-veu) { position: relative; z-index: 2; }

.op-topo { display: flex; align-items: center; justify-content: space-between; gap: 16px;
    max-width: 1180px; margin: 0 auto 26px; flex-wrap: wrap; }
.op-marca { display: flex; align-items: center; gap: 14px; }
.op-logo { width: 190px; max-width: 60vw; height: auto; display: block; }
/* "LIVE" ao lado da marca e nao dentro do logo: o ficheiro do logo e o mesmo do login
   e nao se toca nele, senao passavam a existir duas marcas para manter. */
.op-live { font-size: .62rem; letter-spacing: .18em; font-weight: 700; padding: 4px 8px;
    border-radius: 999px; background: #e02626; color: #fff; align-self: center; }
.op-lead { max-width: 1180px; margin: -12px auto 22px; color: var(--op-muted); font-size: .8rem; }
.op-entrar { color: #29d9ff; font-size: .82rem; text-decoration: none; padding: 9px 14px;
    border: 1px solid var(--op-linha); border-radius: 10px; white-space: nowrap; }
.op-entrar:hover { border-color: #294863; background: var(--op-painel); }

.op-grelha { max-width: 1180px; margin: 0 auto; display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); }

/* Vidro sobre a fotografia, como o cartao do login. Opaco ficava um bloco morto
   por cima da imagem; translucido deixa-a viver por tras sem comer a leitura. */
.op-cartao { --op-cor: #29d9ff; border-radius: 14px; padding: 16px;
    background: linear-gradient(160deg, rgba(13,27,42,.86), rgba(7,16,26,.92));
    border: 1px solid rgba(120,180,215,.20); border-top: 3px solid var(--op-cor);
    backdrop-filter: blur(6px); }
.op-cab { display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
    margin-bottom: 14px; }
.op-cab h2 { margin: 0; font-size: 1rem; }
.op-ouvintes { font-variant-numeric: tabular-nums; color: var(--op-texto); font-size: .95rem; }
.op-ouvintes small { color: var(--op-muted); font-size: .68rem; margin-left: 3px; }

.op-fonte + .op-fonte { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--op-linha); }
.op-etq { margin-bottom: 7px; }
.op-etq b { font-size: .78rem; }
/* A etiqueta explica a diferença entre as duas: sem ela, dois leitores no mesmo
   cartão parecem redundância em vez de emissão contra confirmação. */
.op-etq span { display: block; color: var(--op-muted); font-size: .68rem; line-height: 1.35; margin-top: 1px; }
.op-ar .op-etq b { color: var(--op-cor); }
.ouvir-publico audio { width: 100%; height: 38px; }

.op-vazio { max-width: 1180px; margin: 0 auto; padding: 26px; text-align: center;
    background: var(--op-painel); border: 1px solid var(--op-linha); border-radius: 14px; }
.op-vazio span { display: block; color: var(--op-muted); font-size: .8rem; margin-top: 6px; }

.op-rodape { max-width: 1180px; margin: 30px auto 0; padding-top: 16px;
    border-top: 1px solid var(--op-linha); color: #4d6a7f; font-size: .68rem; text-align: center; }

/* O botão de serviço no ecrã de entrada. Discreto por decisão: a porta principal
   é a de entrar, e esta não lhe pode fazer concorrência visual. */
.login-ouvir { display: flex; align-items: center; justify-content: center; gap: 8px;
    margin-top: 14px; padding: 12px; border: 1px solid rgba(120,180,215,.22);
    border-radius: 11px; color: #9db3c3; font-size: .8rem; text-decoration: none; }
.login-ouvir:hover { border-color: rgba(41,217,255,.45); color: #29d9ff; }

/* O botao leva a COR DA ESTACAO em cheio. E a unica accao da pagina -- o leitor da
   emissao ja esta desenhado pelo browser e nao se destaca de nada -- e sem peso
   proprio lia-se como legenda em vez de botao.
   Texto quase preto: as cores das estacoes sao todas claras, e branco sobre elas
   perde-se. */
.op-play { display: inline-flex; align-items: center; gap: 8px; width: 100%;
    justify-content: center; padding: 11px; border-radius: 9px; cursor: pointer;
    background: var(--op-cor); color: #06121d; border: 0;
    font-size: .82rem; font-weight: 600; letter-spacing: .02em; font-family: inherit; }
.op-play:hover:not(:disabled) { filter: brightness(1.1); }
.op-play:disabled { cursor: default; filter: saturate(.3) brightness(.8); }
/* O atributo hidden perde para o display da classe -- e o botao ficava por cima do
   leitor depois de ligar. Um [hidden] que nao esconde e pior do que nao existir. */
.op-play[hidden] { display: none; }
.op-play-ic { font-size: .7rem; }

.op-tematicas { max-width: 1180px; margin: 34px auto 0; padding-top: 26px;
    border-top: 1px solid var(--op-linha); }
.op-tematicas > h2 { margin: 0; font-size: 1rem; }
.op-tematicas > p { margin: 3px 0 16px; color: var(--op-muted); font-size: .76rem; }
/* Sem barra de cor no topo: a cor identifica uma estação da rede, e estas não são
   estações. Dar-lhes a mesma marca punha-as a competir com as de cima. */
.op-cartao-web { border-top: 1px solid var(--op-linha); }

/* O ponto de estado. Verde = confirmado a dar som; apagado = não vale a pena carregar.
   O anel à volta é o que o torna visível sobre a fotografia de fundo, onde um ponto
   liso de 9px se perdia contra as zonas claras da imagem. */
.op-estado { display: inline-block; width: 9px; height: 9px; border-radius: 50%;
    margin-right: 8px; vertical-align: middle; background: #3fd07f;
    box-shadow: 0 0 0 3px rgba(63,208,127,.16); }
/* Cinzento e não vermelho: as três causas incluem "o receptor está calado", e aí a
   estação pode estar no ar na mesma. Vermelho acusaria a rádio de uma avaria que
   pode ser do receptor. */
.op-estado.off { background: #6d879b; box-shadow: 0 0 0 3px rgba(109,135,155,.14); }

/* O botão do DVR é o segundo: ao vivo é o que se quer quase sempre, e recuar é a
   excepção. Sem cor de fundo para não competir com ele. */
.op-play-dvr { margin-top: 8px; background: transparent; color: var(--op-muted);
    border: 1px solid var(--op-linha); font-weight: 400; }
.op-play-dvr:hover:not(:disabled) { border-color: var(--op-cor); color: var(--op-cor); filter: none; }
.op-play-dvr:disabled { filter: none; opacity: .5; }

/* ══ AUTOMAÇÕES ══════════════════════════════════════════════════════════════
 *
 * O construtor de receitas. Três blocos numerados — QUANDO, SÓ SE, FAZ — porque é
 * essa a estrutura de uma automação, e a numeração aqui não é enfeite: diz que a
 * ordem importa e que nenhum dos três se pode saltar.
 *
 * Cores tiradas das variáveis do tema. Havia a tentação de fixar o azul-escuro dos
 * painéis, e o tema claro é o que se usa aqui todos os dias — texto pálido sobre
 * fundo branco não se lê, e descobre-se tarde. */

.au-editor { display: flex; flex-direction: column; gap: 14px; }

.au-bloco-cab { display: flex; gap: 12px; align-items: flex-start; margin-bottom: 14px; }
.au-bloco-cab h3 { font-size: .95rem; margin: 0 0 2px; }
.au-bloco-cab p { font-size: .74rem; color: var(--muted); margin: 0; max-width: 62ch; line-height: 1.5; }

.au-passo-n {
    flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
    display: grid; place-items: center;
    font-size: .74rem; font-weight: 700; font-variant-numeric: tabular-nums;
    border: 1px solid var(--line2); color: var(--cyan); background: rgba(0,0,0,.12);
}
.au-passo-n.sm { width: 22px; height: 22px; font-size: .68rem; }
.au-passo-n.ensaio { color: var(--green); border-color: rgba(37,228,123,.35); }

/* Uma condição por linha, com os campos que aquele tipo precisa e mais nenhum.
 * Mostrar tudo e desactivar o que não serve enche o ecrã de campos mortos. */
.au-linha { display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
            padding: 8px 0; border-bottom: 1px solid var(--line); }
.au-linha:last-of-type { border-bottom: 0; }
.au-linha select, .au-linha input { min-width: 0; }
.au-e { color: var(--muted); font-size: .74rem; }
.au-tirar { color: var(--muted); }
.au-tirar:hover { color: var(--red); border-color: var(--red); }

/* Um passo é um cartão e não uma linha: leva texto, marcas e notas, e uma linha
 * só espremia tudo isso na horizontal até deixar de se perceber a ordem. */
.au-passo { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
            margin-bottom: 10px; background: rgba(255,255,255,.015); }
.au-passo-cab { display: flex; gap: 10px; align-items: center; }
.au-passo-cab select { flex: 1 1 auto; }
.au-passo-mover { display: flex; gap: 4px; margin-left: auto; }
.au-passo-corpo { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.au-passo-corpo label { display: flex; flex-direction: column; gap: 4px;
                        font-size: .7rem; color: var(--muted); min-width: 180px; flex: 1 1 200px; }
.au-passo-corpo label.campo-largo { flex: 1 1 100%; }
.au-passo-corpo textarea { width: 100%; resize: vertical; font-family: inherit; }

.au-nota { flex: 1 1 100%; font-size: .68rem; color: var(--muted); margin: 0; line-height: 1.5; }
.au-nota.aviso { color: var(--amber); }

/* As marcas do texto. Carregar mete-a no fim — escrever a marca de uma medição à
 * mão é onde se engana um nome e a mensagem sai com "(sem leitura)" para sempre. */
.au-marcas { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: 5px; }
.au-marca {
    font-family: var(--mono); font-size: .62rem; padding: 3px 7px; border-radius: 5px;
    border: 1px solid var(--line2); background: transparent; color: var(--cyan); cursor: pointer;
}
.au-marca:hover { border-color: var(--cyan); background: rgba(0,200,255,.07); }

.au-dias { display: flex; gap: 5px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.au-dias.inline { margin-top: 0; }
.au-dias-lbl { font-size: .7rem; color: var(--muted); margin-right: 4px; }
.au-dias-nota { font-size: .68rem; color: var(--muted); margin-left: 6px; }
.au-dia {
    font-size: .68rem; padding: 5px 9px; border-radius: 6px; cursor: pointer;
    border: 1px solid var(--line2); background: transparent; color: var(--muted);
}
.au-dia.on { border-color: var(--cyan); color: var(--cyan); background: rgba(0,200,255,.09); }

/* A RECEITA POR PALAVRAS, sempre à vista enquanto se edita.
 *
 * É a peça que apanha o engano antes do ensaio: uma condição "entre as 06:00 e as
 * 01:00" parece bem em dois campos separados e lê-se logo como errada aqui. */
.au-resumo { border-left: 3px solid var(--cyan); }
.au-resumo-linha { font-size: .78rem; line-height: 1.7; color: var(--text); }
.au-resumo-linha b { color: var(--cyan); font-weight: 700; margin-right: 6px;
                     text-transform: uppercase; font-size: .64rem; letter-spacing: .06em; }

.au-ensaio { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 12px; }
.au-ensaio-sim { font-size: .78rem; color: var(--green); margin-bottom: 8px; }
.au-ensaio-nao { font-size: .78rem; color: var(--muted); }
.au-ensaio-passos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.au-ensaio-passos li {
    display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap;
    font-size: .74rem; padding: 7px 10px; border-radius: 8px;
    border: 1px solid var(--line); border-left-width: 3px;
}
.au-ensaio-accao { font-weight: 600; min-width: 140px; }
.au-ensaio-det { color: var(--muted); flex: 1 1 260px; }
.au-ensaio-res, .au-res {
    font-size: .62rem; text-transform: uppercase; letter-spacing: .06em;
    padding: 2px 7px; border-radius: 999px; border: 1px solid currentColor;
}

/* O resultado por cor: feito, recusado, parou, erro. São quatro coisas diferentes e
 * um booleano dizia-as todas como "não" — "recusado" (a tranca não deixou) e "erro"
 * (rebentou) mandam procurar em sítios opostos. */
.r-feito    { color: var(--green);  border-left-color: var(--green) !important; }
.r-recusado { color: var(--amber);  border-left-color: var(--amber) !important; }
.r-parou    { color: var(--cyan);   border-left-color: var(--cyan)  !important; }
.r-saltado  { color: var(--muted);  border-left-color: var(--line2) !important; }
.r-erro     { color: var(--red);    border-left-color: var(--red)   !important; }

.au-rodape { display: flex; gap: 12px; align-items: center; justify-content: flex-end; }
.au-ligar { display: flex; gap: 7px; align-items: center; font-size: .76rem;
            color: var(--text); margin-right: auto; }

.au-vazio { padding: 26px 20px; border: 1px dashed var(--line2); border-radius: 12px; }
.au-vazio p { font-size: .8rem; color: var(--text); line-height: 1.65;
              margin: 0 0 10px; max-width: 68ch; }
.au-vazio-nota { color: var(--muted) !important; font-size: .76rem !important; }

.au-ajuda { font-size: .76rem; color: var(--muted); line-height: 1.6;
            margin: 0 0 12px; max-width: 72ch; }

.au-modelos { display: grid; gap: 10px;
              grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.au-modelo {
    display: flex; flex-direction: column; gap: 5px; text-align: left; cursor: pointer;
    padding: 12px 14px; border-radius: 10px; border: 1px solid var(--line);
    background: rgba(255,255,255,.015); transition: border-color .15s, background .15s;
}
.au-modelo:hover { border-color: var(--cyan); background: rgba(0,200,255,.05); }
.au-modelo-nome { font-size: .8rem; font-weight: 600; color: var(--text); }
.au-modelo-desc { font-size: .7rem; color: var(--muted); line-height: 1.5; }

.au-cel-quando, .au-cel-faz { font-size: .74rem; max-width: 34ch; }
.au-num { font-variant-numeric: tabular-nums; text-align: right; }
.au-erro { font-size: .66rem; color: var(--red); margin-top: 3px; }
.au-tag-ensaio {
    font-size: .58rem; text-transform: uppercase; letter-spacing: .06em;
    color: var(--muted); border: 1px solid var(--line2);
    padding: 1px 5px; border-radius: 999px; margin-left: 6px;
}

/* TEMA CLARO. Os fundos de branco-sobre-branco acima são quase invisíveis num tema
 * claro — e é nele que este cockpit se usa todos os dias. */
:root[data-theme="light"] .au-passo,
:root[data-theme="light"] .au-modelo { background: #f6f9fc; }
:root[data-theme="light"] .au-modelo:hover { background: #eaf6fc; }
:root[data-theme="light"] .au-passo-n { background: #eef4fa; }
:root[data-theme="light"] .au-dia.on { background: #e2f4fb; }

@media (max-width: 700px) {
    .au-passo-corpo label { flex: 1 1 100%; }
    .au-rodape { flex-wrap: wrap; }
    .au-ligar { margin-right: 0; flex: 1 1 100%; }
}

/* UMA CONDIÇÃO NUMA LINHA, e não quatro campos empilhados.
 *
 * O `input, select, textarea { width: 100% }` global apanhava também estes, e uma
 * condição de «entre as 01:00 e as 05:30» ocupava quatro linhas de ecrã inteiro —
 * com o «e» sozinho numa delas. Numa receita com três condições, a parte do SÓ SE
 * ficava maior do que a receita toda e deixava de se ler como uma lista. */
.au-linha > select { flex: 0 1 260px; width: auto; }
.au-linha > input[type="time"] { flex: 0 0 130px; width: auto; }
.au-linha > input[type="number"] { flex: 0 0 120px; width: auto; }
.au-linha > input[list] { flex: 0 1 220px; width: auto; }
.au-linha > .au-tirar { flex: 0 0 auto; width: auto; margin-left: auto; }

/* O mesmo nos passos: um campo só não tem de atravessar o painel inteiro. Uma
 * caixa de texto de 1200px convida a escrever um parágrafo — e isto vai para uma
 * SMS. */
.au-passo-corpo label { max-width: 420px; }
.au-passo-corpo label.campo-largo { max-width: 100%; }

/* ─── Terminal de porta série ────────────────────────────────────────────────
   O ecrã do emissor vem em texto com molduras desenhadas a caracteres, e a
   grelha já traz as colunas certas. Reformatá-lo aqui desalinhava as caixas
   todas — daí o pre e a largura fixa por caractere.

   O overflow-x é obrigatório e não é enfeite: o painel tem 80 colunas, e num
   ecrã estreito sem isto as linhas partem-se ao meio e o painel deixa de se
   ler como painel. */
.terminal-serie {
    font-family: var(--mono);
    font-size: .78rem;
    line-height: 1.25;
    white-space: pre;
    overflow-x: auto;
    background: #0b1520;
    color: #cfe3f2;
    border: 1px solid #1d2c3a;
    border-radius: 6px;
    padding: 12px 14px;
    margin: 8px 0 0;
    min-height: 320px;
}

/* Uma linha só: o que se escreve à esquerda, as teclas à direita.
   A caixa de texto ocupava a largura do modal para receber uma letra -- num
   painel que se navega às setas, o espaço tem de seguir o uso. */
.terminal-controlos {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 10px;
}

.terminal-escrita {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Os comandos deste painel são uma letra ou duas. 9em chegam para uma sequência
   inteira e deixam o resto da linha para as setas. */
.terminal-escrita input {
    width: 9em;
    flex: 0 0 auto;
    margin: 0;
}

.terminal-teclas {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

/* A porta em baixo, e sozinha: corta a leitura do emissor, e não pode ficar ao
   lado dos botões que só olham. */
.terminal-porta {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    margin-top: 8px;
}

/* As setas ficam quadradas e com o mesmo peso das outras: num terminal
   navega-se com elas, e um botão de seta mais pequeno do que o "Enter" ao lado
   lê-se como sendo menos importante do que é. */
.terminal-teclas .btn { min-width: 44px; }

/* O modal do terminal é mais largo do que os outros de propósito: o painel do
   emissor tem 80 colunas, e num modal estreito passava a vida a ter barra de
   deslocamento horizontal — que num ecrã que se lê de relance o torna inútil. */
.modal.terminal-modal { max-width: 860px; width: 92vw; }

/* O ecrã do terminal tem altura FIXA e não cresce com o conteúdo.
   Sem isto, um painel com mais linhas do que o anterior fazia o modal saltar de
   tamanho a cada actualização — e o que se estava a ler mudava de sítio. Num ecrã
   que se lê de relance, mexer é pior do que não actualizar. */
.terminal-serie { height: 420px; overflow: auto; }

/* ── Alarmes do emissor: a lista onde se escolhe quais é que acordam alguém ──────
   Duas colunas e altura limitada: são 43 linhas, e uma lista dessa altura empurrava
   o resto do formulário para fora do ecrã sempre que se abrisse. Fica dentro de um
   <details>, fechada por omissão — quem não quer mexer nisto nem a vê. */
.alarmes-emissor {
    margin-top: 10px;
    border: 1px solid var(--linha);
    border-radius: 8px;
    padding: 10px 12px;
}

.alarmes-emissor > summary {
    cursor: pointer;
    font-weight: 600;
    display: flex;
    gap: 8px;
    align-items: baseline;
}

.alarmes-grelha {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 2px 16px;
    max-height: 340px;
    overflow-y: auto;
    margin-top: 10px;
}

.alarmes-grelha .check {
    display: grid;
    grid-template-columns: auto 3.2em 1fr auto;
    gap: 8px;
    align-items: center;
    margin: 0;
    padding: 3px 0;
    font-size: .82rem;
}

/* O número é o que está escrito no painel do aparelho — é por ele que se fala com
   quem lá está, e por isso não é decoração: é a chave. */
.alarme-cod {
    font-family: var(--mono);
    opacity: .6;
}



/* ============================================================================
   QUEBRAS DE ÁUDIO

   Bancada de trabalho, não artigo: números em cima, fila densa por baixo.

   A LETRA É A DO MENU, e não uma monoespaçada. A mono alinha melhor mas dá um ar de
   consola que não é o do resto do cockpit — e esta página vai ser vista todos os dias. O
   alinhamento dos números resolve-se com `tabular-nums`, que faz o mesmo trabalho sem
   mudar de tipo de letra.
   ========================================================================== */

/* --- Faixa de números. O estado da fila numa linha, sem se ler uma frase. --- */
.qb-topo { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.qb-kpi {
    display: grid; gap: 1px; padding: 8px 15px; min-width: 96px;
    background: var(--panel); border: 1px solid var(--line); border-radius: 9px;
}
.qb-kpi b { font-size: 1.2rem; font-variant-numeric: tabular-nums; line-height: 1.15; }
.qb-kpi span { font-size: .66rem; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.qb-kpi.destaque { border-color: var(--amber); }
.qb-kpi.destaque b { color: var(--amber); }
.qb-kpi.mau b { color: var(--red); }

/* --- Abas por estação. Aparecem só quando há mais do que uma. --- */
.qb-abas { display: flex; gap: 2px; flex-wrap: wrap; border-bottom: 1px solid var(--line); margin-bottom: 10px; }
.qb-abas button {
    background: none; border: 0; border-bottom: 2px solid transparent;
    padding: 8px 14px; cursor: pointer; color: var(--muted);
    font: inherit; font-size: .84rem; font-weight: 600; white-space: nowrap;
}
.qb-abas button:hover { color: var(--text); }
.qb-abas button.on { color: var(--text); border-bottom-color: var(--cyan); }
.qb-abas button .n {
    margin-left: 6px; padding: 1px 6px; border-radius: 10px; font-size: .7rem; font-weight: 500;
    background: color-mix(in srgb, var(--muted) 22%, transparent);
}
.qb-abas button.on .n { background: color-mix(in srgb, var(--cyan) 25%, transparent); color: var(--cyan); }
.qb-abas button .n.urgente { background: color-mix(in srgb, var(--amber) 28%, transparent); color: var(--amber); }

/* --- A fila. --- */
.qb-cab, .qb-linha {
    display: grid;
    /* NOVE colunas, porque a linha tem nove filhos. Ficaram sete aqui quando se
       acrescentou a perda de pacotes e depois o veredicto: o que sobra nao desaparece,
       passa para a linha de baixo, e o cabecalho deixa de descrever o que esta por baixo
       dele. Uma coluna a mais na marcacao e menos aqui nao da erro nenhum -- desalinha. */
    grid-template-columns: 3px 78px minmax(120px, 1fr) 60px 52px 56px 92px minmax(200px, 1.5fr) auto;
    gap: 12px; align-items: center;
}
.qb-cab {
    padding: 0 12px 6px 0; font-size: .64rem; text-transform: uppercase;
    letter-spacing: .07em; color: var(--muted); border-bottom: 1px solid var(--line);
}
.qb-cab .dir { text-align: right; }

.qb-fila { display: grid; gap: 4px; margin-top: 5px; }
.qb-linha {
    padding: 7px 12px 7px 0; border-radius: 7px;
    border: 1px solid transparent; background: var(--panel);
}
.qb-linha:hover { border-color: var(--line2); }
.qb-linha.por-ouvir { background: color-mix(in srgb, var(--amber) 7%, var(--panel)); }

.qb-barra { align-self: stretch; border-radius: 4px; }
.qb-barra.curta { background: var(--cyan); }
.qb-barra.media { background: var(--amber); }
.qb-barra.longa { background: var(--red); }

.qb-hora { font-size: .74rem; color: var(--muted); font-variant-numeric: tabular-nums; line-height: 1.25; }
.qb-hora b { display: block; color: var(--text); font-size: .84rem; font-weight: 600; }
.qb-est { font-size: .84rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qb-num { font-size: .88rem; text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
.qb-num.fraco { color: var(--muted); font-size: .78rem; font-weight: 400; }

/* O VEREDICTO DAS TESTEMUNHAS. Duas palavras, porque e para se ler de relance numa fila
   de quarenta; o raciocinio completo vive no title. */
.qb-causa {
    font-size: .7rem; text-align: right; letter-spacing: .02em;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.qb-causa.fraco { color: var(--muted); }
.qb-causa.mau { color: var(--amber); font-weight: 600; }

/* O VEREDICTO DA CONTRA-PROVA. Verde quando ela desmente a quebra, porque e uma boa
   noticia: a radio esteve no ar. Vermelho quando a confirma. */
.qb-causa b { display: block; font-size: .64rem; font-weight: 600; letter-spacing: .02em; }
.qb-causa b.cp-nao-foi-no-ar { color: var(--green); }
.qb-causa b.cp-confirmada    { color: var(--red); }
.qb-causa b.cp-em-parte      { color: var(--amber); }

/* OUVIR NA ESTACAO. Fica a seguir aos botoes de classificar porque e da mesma familia:
   sao as coisas que se FAZEM a uma quebra, e nao as que se leem dela. */
.qb-local {
    font-size: .68rem; padding: 3px 9px; border-radius: 999px; white-space: nowrap;
    border: 1px solid var(--line2); background: transparent; color: var(--muted); cursor: pointer;
}
.qb-local:hover:not(:disabled) { border-color: var(--cyan); color: var(--cyan); }
.qb-local:disabled { opacity: .55; cursor: default; }

.qb-local-som {
    grid-column: 1 / -1; display: flex; align-items: center; gap: 10px;
    padding: 6px 0 2px 15px; margin-top: 2px;
    border-left: 2px solid var(--cyan);
}
.qb-local-som .rot {
    font-size: .66rem; letter-spacing: .06em; text-transform: uppercase; color: var(--cyan);
    white-space: nowrap;
}
.qb-local-som audio { height: 30px; flex: 1 1 auto; max-width: 420px; }
.qb-local-erro { font-size: .74rem; color: var(--amber); }

/* --- O som: onda por cima, leitor por baixo. --- */
.qb-som { display: grid; gap: 3px; min-width: 0; }
.qb-onda {
    width: 100%; height: 34px; display: block; border-radius: 5px;
    background: color-mix(in srgb, var(--line) 45%, transparent);
}
.qb-som audio { height: 26px; width: 100%; }

.qb-accoes { display: flex; gap: 4px; align-items: center; justify-content: flex-end; }
.qb-accoes button {
    background: none; border: 1px solid var(--line2); border-radius: 6px;
    padding: 4px 10px; font: inherit; font-size: .76rem; cursor: pointer; color: var(--muted);
    white-space: nowrap;
}
.qb-accoes button:hover { color: var(--text); border-color: var(--cyan); }
.qb-accoes button.on { color: #05121c; font-weight: 600; border-color: transparent; }
.qb-accoes button.on.avaria { background: var(--red); }
.qb-accoes button.on.programa { background: var(--green); }
.qb-accoes button.on.curta-de-mais { background: var(--cyan); }
.qb-accoes button.on.nao-sei-dizer { background: var(--muted); }

.qb-marca { font-size: .66rem; padding: 2px 7px; border-radius: 5px; margin-left: 6px; }
.qb-marca.cortado { background: color-mix(in srgb, var(--purple) 22%, transparent); color: var(--purple); }

/* --- O que as classificações ensinam. --- */
.qb-ensina { display: grid; gap: 5px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.qb-ensina:last-child { border-bottom: 0; padding-bottom: 0; }
.qb-ensina > b { font-size: .88rem; }
.qb-esc { display: grid; grid-template-columns: 62px 1fr 110px; gap: 10px; align-items: center; font-size: .78rem; }
.qb-esc .nome { color: var(--muted); font-variant-numeric: tabular-nums; }
.qb-esc .barra { height: 6px; border-radius: 999px; background: color-mix(in srgb, var(--green) 32%, transparent); overflow: hidden; }
.qb-esc .barra i { display: block; height: 100%; background: var(--red); }
.qb-esc .conta { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; }
.qb-esc .conta b { color: var(--text); }

.qb-veredicto { margin-top: 6px; padding: 9px 12px; border-radius: 7px; font-size: .8rem;
                background: color-mix(in srgb, var(--cyan) 8%, transparent);
                border-left: 3px solid var(--cyan); }
.qb-veredicto.sem-prova { background: color-mix(in srgb, var(--muted) 8%, transparent); border-left-color: var(--muted); }
.qb-veredicto b { display: block; font-size: .84rem; margin-bottom: 3px; }

@media (max-width: 1040px) {
    .qb-cab { display: none; }
    .qb-linha { grid-template-columns: 3px 1fr auto; row-gap: 6px; padding: 9px 12px 10px 0; }
    .qb-som, .qb-accoes { grid-column: 2 / -1; justify-content: flex-start; }
    .qb-num { text-align: left; }
}

/* Um acontecimento em cadeia. Nas horas de cadeia uma estação alimenta o som de todas as
   outras: se ela cala, calam todas. Não é ruído — é a pior avaria que esta rede pode ter,
   e por isso destaca-se em vez de se diluir. */
.qb-linha.cadeia { border-left: 0; box-shadow: inset 3px 0 0 var(--purple); }
.qb-cadeia { color: var(--purple); }
.qb-quem { display: block; font-size: .72rem; color: var(--muted);
           overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qb-marca.origem { background: color-mix(in srgb, var(--purple) 24%, transparent); color: var(--purple); }
.qb-marca.sem-origem { background: color-mix(in srgb, var(--muted) 20%, transparent); color: var(--muted); }


/* --- Gravacao na estacao --- */
.gl-intro { border-left: 3px solid var(--cyan); }
.gl-form { display: flex; flex-wrap: wrap; gap: 14px; align-items: flex-end; }
.gl-form label { display: flex; flex-direction: column; gap: 4px; font-size: .78rem; color: var(--muted); }
.gl-form label small { color: var(--muted); font-size: .68rem; }
.gl-botoes { display: flex; gap: 8px; flex-wrap: wrap; }
.gl-recado { margin-top: 12px; font-size: .84rem; color: var(--text); }
.gl-recado.mau { color: var(--amber); }
.gl-leitor {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    margin-top: 14px; padding: 10px 14px; border-radius: 8px;
    background: color-mix(in srgb, var(--cyan) 8%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--cyan) 25%, transparent);
}
.gl-leitor .rot {
    font-size: .68rem; letter-spacing: .05em; text-transform: uppercase;
    color: var(--cyan); white-space: nowrap;
}
.gl-leitor audio { height: 34px; flex: 1 1 260px; min-width: 240px; }


/* --- Estas vivo? --- */
.ev-topo { display: flex; flex-wrap: wrap; gap: 26px; }
.ev-kpi { display: flex; flex-direction: column; gap: 2px; }
.ev-kpi b { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; }
.ev-kpi span { font-size: .72rem; color: var(--muted); }
.ev-kpi.mau b { color: var(--red); }
.ev-kpi.av b { color: var(--amber); }

.ev-lista { display: grid; gap: 3px; margin-top: 6px; }
.ev-linha {
    display: grid;
    grid-template-columns: 8px minmax(150px, 1.4fr) 84px minmax(120px, 2fr) 64px 78px 96px;
    gap: 10px; align-items: center;
    padding: 6px 12px; border-radius: 7px; background: var(--panel);
    border: 1px solid transparent;
}
.ev-linha:hover { border-color: var(--line2); }
.ev-luz { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); }
.ev-linha.bem   .ev-luz { background: var(--green); }
.ev-linha.houve .ev-luz { background: var(--green); box-shadow: 0 0 0 3px color-mix(in srgb, var(--amber) 35%, transparent); }
.ev-linha.mal   .ev-luz { background: var(--red); }
.ev-linha.mal   { background: color-mix(in srgb, var(--red) 8%, var(--panel)); }
.ev-linha.nao-sei .ev-luz { background: var(--muted); opacity: .5; }
.ev-linha.nao-sei { opacity: .6; }

.ev-nome { overflow: hidden; }
.ev-nome b { display: block; font-size: .84rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev-nome small { color: var(--muted); font-size: .68rem; font-variant-numeric: tabular-nums; }
.ev-agora { font-size: .78rem; color: var(--muted); font-variant-numeric: tabular-nums; text-align: right; }
.ev-linha.mal .ev-agora { color: var(--red); }

.ev-onda { display: flex; align-items: flex-end; gap: 1px; height: 22px; }
.ev-onda i { flex: 1 1 auto; min-width: 1px; border-radius: 1px; background: var(--cyan); opacity: .75; }
.ev-onda i.nada { background: var(--red); opacity: .9; }

.ev-num { font-size: .76rem; text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
.ev-num.mau { color: var(--red); font-weight: 600; }
.ev-num.av { color: var(--amber); font-weight: 600; }

@media (max-width: 900px) {
    .ev-linha { grid-template-columns: 8px 1fr 70px 60px; }
    .ev-onda, .ev-linha .ev-num:last-child { display: none; }
}


/* Medicoes configuradas que nao chegam. Nao e vermelho: nao e avaria, e configuracao
   por acertar -- e a diferenca importa a quem le. */
.dev-aviso {
    margin: 4px 0 12px; padding: 10px 14px; border-radius: 7px;
    background: color-mix(in srgb, var(--amber) 9%, var(--panel));
    border: 1px solid color-mix(in srgb, var(--amber) 30%, transparent);
}
.dev-aviso b { font-size: .84rem; color: var(--amber); }
.dev-aviso p { margin: 5px 0 0; font-size: .82rem; color: var(--text); }
.dev-aviso p.det { font-size: .76rem; color: var(--muted); }
