/* ============================================================================
   UNPARALLEL — tema da plataforma projetix
   ----------------------------------------------------------------------------
   Esta folha é a ÚNICA fonte da verdade do aspeto da aplicação. Tudo o que se
   vê — plataforma e módulos — sai daqui. Um módulo não escreve CSS próprio:
   usa as classes `.up-*` e, se precisar de afinar, as variáveis `--up-*`.

   Organização:
     1. Tokens          cor, tipografia, espaço, forma
     2. Base
     3. Estrutura       barra lateral, conteúdo, cabeçalho de página
     4. Navegação
     5. Botões
     6. Cartões, grelhas e o lançador (a página inicial)
     7. Campos e formulários
     8. Tabelas
     9. Etiquetas, KPI, avatar
    10. Estados         vazio, a carregar, erro, sem permissão
    11. Gaveta, diálogo e barra de guardar
    12. Utilitários

   Regra ao mexer: mudar um token muda a aplicação inteira; acrescentar uma
   classe nova é acrescentar vocabulário a toda a gente. Nunca pôr aqui nada
   que só sirva a um módulo.
   ========================================================================== */

/* 1. ------------------------------------------------------------- Tokens */
:root {
  /* — Cor de marca (manual de normas UNPARALLEL) — */
  --up-teal:      #008490;   /* acento principal */
  --up-teal-700:  #0c6e78;   /* hover / estado carregado */
  --up-teal-bg:   #e1f2f3;   /* fundo muito claro */
  --up-orange:    #F09C18;   /* acento secundário */
  --up-orange-700:#b6720a;
  --up-orange-bg: #fdeecf;

  /* — Base neutra — */
  --up-black:   #111418;
  --up-ink:     #1b1f24;   /* texto principal */
  --up-white:   #ffffff;
  --up-bg:      #f7f7f8;   /* fundo da aplicação */
  --up-surface: #ffffff;   /* cartões */
  --up-line:    #ececef;   /* traços e limites */
  --up-muted:   #6b7280;   /* texto secundário */
  --up-faint:   #f4f4f5;   /* realce leve, hover */

  /* O PAPEL numa célula (ver `.up-cells td.papel-*`). Os dois verdes são os da folha de cálculo
     por que os orçamentos se montam — quem abre a aplicação reconhece-os. */
  --up-papel-lider:       #B1CF95;
  --up-papel-lider-ink:   #22301a;
  --up-papel-contrib:     #E2EFDA;
  --up-papel-contrib-ink: #22301a;

  --up-accent:    var(--up-teal);
  --up-accent-bg: var(--up-teal-bg);
  /* O véu por trás de uma gaveta ou de um diálogo. É um token porque no escuro tem de ser
     mais denso: um cinzento a 35% por cima de um fundo já escuro não se via, e a página
     atrás lia-se como se ainda fosse clicável. */
  --up-veil: rgba(17,20,24,.35);
  /* Texto POR CIMA do acento. É um token porque no escuro o teal aclara e o branco deixa
     de ter contraste: passa a escrever-se por cima em escuro, não em branco. */
  --up-on-accent: #ffffff;

  /* — Papéis, para o escuro poder virar cada um sozinho — */
  --up-nav-ink:      #3a3f45;   /* entradas de menu em repouso */
  --up-btn-bg:       var(--up-black);   /* botão primário */
  --up-btn-bg-hover: #000000;
  --up-btn-fg:       #ffffff;

  /* — Cinzentos — */
  --up-grey-100:#f4f4f5; --up-grey-200:#e6e7e9; --up-grey-300:#d1d3d6;
  --up-grey-500:#8b9096; --up-grey-700:#4a4f55;

  /* — Estado — */
  --up-ok:  #3B6D11; --up-ok-bg:  #EAF3DE;
  --up-warn:#8a5a00; --up-warn-bg:#FAEEDA;
  --up-err: #A32D2D; --up-err-bg: #FCEBEB;

  /* — Etapas de um percurso (`.up-tag.etapa1…8`) —
     Oito pares para um fluxo com estados NOMEADOS, onde o que interessa não é "bom/mau" mas
     ONDE VAI. Os quatro de cima (`ok`, `warn`, `err`, `accent`) dizem um juízo, e é por isso que
     não servem aqui: um estado a meio de um caminho não é um aviso, e pintá-lo de amarelo diz
     que é.

     Pedido do @pmnmalo a 2026-08-20 para a lista de propostas do `machina`, que tem oito estados
     e três cores — cinco pastilhas iguais, e a coluna do estado deixava de se ler de relance.
     Está no TEMA e não lá porque a mesma pergunta existe no `proposals`, no `ec_portal` e em
     qualquer módulo com um fluxo: uma escala destas escrita num módulo é uma escala que os outros
     copiam ligeiramente diferente.

     ⚠️ **A ordem é a do percurso, não a do gosto**: frio no princípio, quente ao fim, cinzento no
     arquivo. Quem lhes trocar as cores troca o significado — a escala inteira vale como
     progressão, e uma pastilha sozinha não diz nada. Saturação baixa de propósito, como o resto:
     oito pastilhas garridas numa tabela de vinte linhas é o que faz ninguém olhar para nenhuma. */
  --up-etapa1:#5B3E8F; --up-etapa1-bg:#EFEAF7;
  --up-etapa2:#3D4A96; --up-etapa2-bg:#E8EBF8;
  --up-etapa3:#275C8F; --up-etapa3-bg:#E4EEF8;
  --up-etapa4:#1F6B66; --up-etapa4-bg:#E1F0EE;
  --up-etapa5:#3B6D11; --up-etapa5-bg:#EAF3DE;
  --up-etapa6:#8a5a00; --up-etapa6-bg:#FAEEDA;
  --up-etapa7:#8A2E63; --up-etapa7-bg:#F9E9F1;
  --up-etapa8:#4a4f55; --up-etapa8-bg:#EDEEF0;
  --up-etapa9:#5f6672; --up-etapa9-bg:#E7E9ED;
  --up-etapa10:#3f444b; --up-etapa10-bg:#DFE2E7;

  /* — Tipografia — */
  --up-font: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --up-fs-xs:12px; --up-fs-sm:13px; --up-fs-md:14px; --up-fs-lg:16px;
  --up-fs-h1:20px; --up-fs-h2:15px;
  --up-lh:1.55;

  /* — Espaço — */
  --up-sp-1:4px; --up-sp-2:8px; --up-sp-3:12px; --up-sp-4:16px;
  --up-sp-5:24px; --up-sp-6:32px; --up-sp-7:48px;
  --up-gap:var(--up-sp-4);

  /* — Forma — */
  --up-radius:10px; --up-radius-sm:8px; --up-radius-lg:14px; --up-radius-pill:999px;
  --up-shadow:0 1px 2px rgba(17,20,24,.04), 0 1px 3px rgba(17,20,24,.06);
  --up-shadow-lg:0 8px 30px rgba(17,20,24,.10);

  /* — Estrutura — */
  --up-sidebar:224px;      /* largura da barra lateral */
  --up-content:1120px;     /* largura máxima do conteúdo */
  --up-topbar:56px;        /* altura da barra no topo, em ecrã pequeno */
  --up-transition:.12s;

  color-scheme: light;
}

/* 1.1 ----------------------------------------------------------- Escuro */
/* O MESMO desenho com outros valores: só se viram tokens, nenhuma regra abaixo sabe que
   isto existe — é o que permite um módulo ganhar tema escuro sem escrever nada (issue #45).
   Quem manda é o atributo `data-theme` no `<html>`, escrito pelo servidor a partir da
   preferência da conta. "Seguir o sistema" resolve-se no browser, num script curto no
   `<head>` que troca `auto` por `light`/`dark` antes da primeira pintura — assim aqui só
   existe UM bloco escuro, e não uma cópia dentro de `prefers-color-scheme`.

   O teal ACLARA: o da marca (#008490) sobre fundo escuro fica ilegível como texto. É a
   mesma cor da marca com mais luz — a identidade mantém-se, o contraste passa. */
:root[data-theme="dark"] {
  --up-teal:      #1aa4b1;
  --up-teal-700:  #45c3ce;
  --up-teal-bg:   #10333a;
  --up-orange:    #F0A93E;
  --up-orange-700:#f5c47c;
  --up-orange-bg: #3a2a10;
  --up-on-accent: #06232a;   /* escuro por cima do teal claro */

  --up-black:   #e7e9ec;
  --up-ink:     #e7e9ec;
  --up-white:   #171a1f;     /* "branco" aqui é a superfície: cartões, menus, barra lateral */
  --up-bg:      #0f1216;
  --up-surface: #171a1f;
  --up-line:    #272b32;
  --up-muted:   #98a0aa;
  --up-faint:   #1e2229;

  /* A mesma LEITURA noutro fundo: o líder mais forte, o contribuidor mais fraco. Os verdes claros
     do tema claro brilhavam sobre uma tabela escura como se fossem um erro. */
  --up-papel-lider:       #3c5a2c;
  --up-papel-lider-ink:   #dbead0;
  --up-papel-contrib:     #23331b;
  --up-papel-contrib-ink: #c3d6b6;

  --up-nav-ink:      #c3c8cf;
  --up-btn-bg:       #e7e9ec;
  --up-btn-bg-hover: #ffffff;
  --up-btn-fg:       #12151a;

  --up-grey-100:#1e2229; --up-grey-200:#272b32; --up-grey-300:#3b414a;
  --up-grey-500:#8b9096; --up-grey-700:#c3c8cf;

  --up-ok:  #96cf67; --up-ok-bg:  #1a2a12;
  --up-warn:#e0b169; --up-warn-bg:#332711;
  --up-err: #ef8f8f; --up-err-bg: #331919;

  /* As oito etapas, viradas: texto claro sobre um fundo escuro da mesma família. Sem esta metade
     as pastilhas ficavam com texto escuro sobre fundo escuro — ilegíveis, e só para metade das
     pessoas, que é a pior maneira de descobrir um problema de cor. */
  --up-etapa1:#c4aee8; --up-etapa1-bg:#241a33;
  --up-etapa2:#aab6ea; --up-etapa2-bg:#1b2038;
  --up-etapa3:#93bfe6; --up-etapa3-bg:#152634;
  --up-etapa4:#84cfc9; --up-etapa4-bg:#122b29;
  --up-etapa5:#96cf67; --up-etapa5-bg:#1a2a12;
  --up-etapa6:#e0b169; --up-etapa6-bg:#332711;
  --up-etapa7:#e79bc2; --up-etapa7-bg:#2e1622;
  --up-etapa8:#c3c8cf; --up-etapa8-bg:#272b32;
  --up-etapa9:#aeb5be; --up-etapa9-bg:#22262c;
  --up-etapa10:#98a0aa; --up-etapa10-bg:#1d2126;

  --up-shadow:    0 1px 2px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.36);
  --up-shadow-lg: 0 8px 30px rgba(0,0,0,.55);
  --up-veil:      rgba(0,0,0,.60);

  color-scheme: dark;
}

/* 2. --------------------------------------------------------------- Base */
*, *::before, *::after { box-sizing:border-box }

/* O atributo `hidden` tem de vencer os `display:flex/grid` das classes abaixo — senão
   um `<div class="up-savebar" hidden>` continua à vista. Vale para tudo, de uma vez. */
[hidden] { display:none !important }

body.up, .up {
  font-family:var(--up-font); font-size:var(--up-fs-md);
  color:var(--up-ink); background:var(--up-bg); line-height:var(--up-lh);
}
body.up { margin:0 }

.up h1{ font-size:var(--up-fs-h1); font-weight:650; margin:0 0 var(--up-sp-1) }
.up h2{ font-size:var(--up-fs-h2); font-weight:600; margin:0 0 var(--up-sp-3) }
/* O terceiro nível existe mesmo — "Consortium", "Participants", "Documents" dentro de uma
   ficha. Sem ele no tema, cada módulo escrevia `<h3 style="font-size:14px">`. */
.up h3{ font-size:var(--up-fs-md); font-weight:600; margin:0 0 var(--up-sp-2) }
/* O azul do browser fora daqui. É uma regra BASE — o chão de qualquer `<a>` — e por isso o
 * `.up` vai dentro de um `:where()`.
 *
 * ⚠️ **Sem ele isto pesava (0,1,1) e ganhava ao `.up-btn`, que pesa (0,1,0).** Um
 * `<a class="up-btn">` recebia a cor do TEXTO em vez da cor do BOTÃO: no tema claro, preto sobre
 * um botão preto. Foi o "botão que não dá para ler" que o Bruno reportou **duas vezes** — a
 * correcção dos botões nus, na 0.19.0, era para `<button>` e não lhe tocou.
 *
 * **Estavam 31 âncoras com `up-btn` na árvore, em nove repositórios — e só 11 se viam.** As
 * outras 20 são `.up-btn.ghost`, cujo texto é `--up-ink` de propósito: a cor errada calhava ser
 * a certa. É por isso que isto viveu meses e apareceu num sítio só de cada vez.
 *
 * Com `:where(.up)` a regra passa a (0,0,1) e perde para qualquer classe — que é o que uma base
 * tem de fazer. Não é uma escolha de estilo: uma base que ganha a um componente **é** o defeito,
 * e subir o `.up-btn` para lhe responder só adiava a próxima colisão. Mesmo raciocínio do
 * `button:where(:not([class]))` mais abaixo. */
:where(.up) a { color:var(--up-ink) }
/* Um salto DENTRO da aplicação — o nome de uma organização que abre a ficha dela, o número de
   um projeto que abre o projeto. Distingue-se do texto à volta sem ser um botão; os módulos
   tinham cada um a sua `.crumb` no CSS local. Serve em `<a>` e em `<button>`.
   O `.up` à frente não é enfeite: sem ele, a regra `.up a` acima é mais específica e a cor
   nunca chegava a aparecer num `<a class="up-link">`. */
.up .up-link { color:var(--up-teal); text-decoration:none; cursor:pointer; border:none;
  background:none; font:inherit; padding:0; text-align:left }
.up .up-link:hover { color:var(--up-teal-700); text-decoration:underline }
.up .muted { color:var(--up-muted) }
.up .small { font-size:var(--up-fs-sm) }
.up code { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:var(--up-fs-sm);
  background:var(--up-faint); padding:1px 5px; border-radius:5px;
  /* Um `<code>` guarda um identificador — um âmbito de OAuth, um id, um caminho — e isso não
     tem espaços por onde partir. Num telemóvel a palavra sai do ecrã e leva a página inteira
     atrás dela: o `/admin/signin` empurrava 76px à direita por causa do
     `https://graph.microsoft.com/Group.ReadWrite.All`.

     ⚠️ E é `anywhere`, não `break-word` — que é o remédio óbvio e chega a meio do caminho.
     Medido nessa página, a 375px: sem regra **76px** de transbordo, com `break-word` **36px**,
     com `anywhere` **zero**. A diferença é que o `break-word` não entra no cálculo da largura
     MÍNIMA do conteúdo, portanto uma caixa que se encolhe ao que tem dentro — e a `.up-tag` é
     `inline-block` — continua a pedir a palavra toda. Metade dos elementos que transbordavam
     eram `.up-tag`, e o `break-word` não lhes tocava.

     Ver `tests/test_um_identificador_longo_quebra.py`. */
  overflow-wrap:anywhere }

/* Foco visível — a mesma marca em todo o lado, para quem navega por teclado. */
.up :focus-visible { outline:2px solid var(--up-teal); outline-offset:2px }

/* 3. ---------------------------------------------------------- Estrutura */
/* A página é: barra lateral fixa + conteúdo. Em ecrã pequeno a barra passa
   para o topo e o conteúdo ocupa tudo. */
.up-app { min-height:100vh }

/* Página de um cartão só, ao centro, sem barra lateral: entrar, mudar a password, o ecrã de
   erro. São as páginas que se veem ANTES de haver aplicação — e por isso as que mais depressa
   acabam com CSS próprio se o tema não as cobrir. */
.up-solo { min-height:100vh; display:flex; align-items:center; justify-content:center;
  padding:var(--up-sp-4) }
.up-solo > * { width:100%; max-width:360px }
.up-solo .up-brand-img { height:30px; width:auto; display:block; margin-bottom:var(--up-sp-3) }

/* A assinatura do produto nas páginas SEM sessão — o login, o convite, a autorização e as duas
 * de reposição de password. A marca à esquerda, "projetix" e "by UNPARALLEL" à direita.
 *
 * **Existe porque estas medidas estavam em `style=` nas cinco páginas**, e a plataforma pô-las
 * num parcial (`marca.html`) à espera desta regra, para se apagarem de um sítio só. Dívida
 * declarada em vez de escondida — o que só funciona se alguém a vier pagar.
 *
 * A forma é a mesma do `.up-brand-img` aqui em cima: **o `.up-solo` é o contexto que dá as
 * medidas.** A `.up-brand-top` sozinha continua a servir a barra lateral, com os seus 24px, e
 * nada disto lhe toca.
 *
 * O `9.5px` e o `.10em` não são números redondos e são de propósito: a linha "by UNPARALLEL" tem
 * de ler-se como uma assinatura e não como texto — pequena e espaçada. Foram medidos na issue
 * projetix#139, e ficam aqui em vez de repetidos em cinco ficheiros. */
.up-solo .up-brand-top { align-items:center; gap:var(--up-sp-2); margin:0 0 var(--up-sp-3) }
.up-solo .up-brand-top b { font-size:17px }
.up-solo .up-brand-top .muted { display:block; font-size:9.5px; letter-spacing:.10em }

.up-main { padding:var(--up-sp-5) var(--up-sp-5) var(--up-sp-7);
  view-transition-name:up-conteudo }   /* ver «A BARRA NÃO REPINTA», no fim */
.up-content { max-width:var(--up-content); margin:0 auto }
.up-content.narrow { max-width:760px }
/* E o contrário: um ecrã de trabalho a duas colunas (o editor da viagem) precisa de mais do
   que os 1120px do conteúdo normal. A convenção é a mesma do `.narrow` — um modificador no
   `{% block largura %}` — para não haver duas formas de dizer a mesma coisa. */
.up-content.wide { max-width:1480px }

/* Cabeçalho de página: título, migalhas, ações à direita. */
.up-page-head { display:flex; align-items:flex-start; justify-content:space-between;
  gap:var(--up-sp-4); flex-wrap:wrap; margin-bottom:var(--up-sp-5) }
.up-page-head .up-actions { display:flex; gap:var(--up-sp-2); flex-wrap:wrap }
/* O fim do cabeçalho de página: as acções do módulo e, à direita delas, o «minimizar» da
   plataforma (`projetix#227`). Um contentor a mais para o `justify-content:space-between` do
   `.up-page-head` continuar a ter DOIS filhos — com três, o do meio ficava a flutuar. */
.up-head-fim { display:flex; align-items:center; gap:var(--up-sp-2); flex-wrap:wrap }
/* ⚠️ **Discreto, e não invisível** — é o mesmo critério do rodapé de privacidade. Um botão que
   ninguém distingue do fundo tem o mesmo efeito prático que não existir; um botão com o peso de
   uma acção do módulo rouba-lhe a atenção numa página que não é sobre ele. */
.up-min { display:inline-flex; align-items:center; gap:6px; padding:5px 10px; border:0;
  background:none; border-radius:var(--up-radius); cursor:pointer; font:inherit;
  font-size:var(--up-fs-xs); color:var(--up-muted) }
.up-min:hover { background:var(--up-faint); color:var(--up-ink) }
.up-min .up-i { color:var(--up-grey-500) }
.up-min:hover .up-i { color:var(--up-ink) }
/* A ESTRELA dos favoritos (`projetix#266`, item 10) — o mesmo critério do `.up-min` ao lado:
   discreta, e nunca invisível (é uma acção, não decoração). O glifo é TEXTO (★/☆), como o «⋮» dos
   três pontos da barra já é — nenhum dos dois pede um ícone novo ao tema. */
.up-fav { display:inline-flex; align-items:center; justify-content:center; width:30px; height:30px;
  padding:0; border:0; background:none; border-radius:var(--up-radius); cursor:pointer;
  font-size:16px; line-height:1; color:var(--up-grey-500) }
.up-fav:hover { background:var(--up-faint); color:var(--up-ink) }
.up-fav[aria-pressed="true"] { color:var(--up-teal) }
.up-fav:disabled { opacity:.5; cursor:default }
/* A linha de um favorito na zona da barra: a entrada (`.up-mod.direto`, largura 100% por dentro)
   e o botão de remover, LADO A LADO. ⚠️ **O par embrulha-se por FORA do `.up-mod`, nunca por
   dentro** — a mesma guarda que o `.up-nav-cab` já escreveu para o cabeçalho de uma zona: o
   `.up-mod > .cab` do tema é um selector de FILHO DIRECTO, e um `<div>` entre os dois é o que já
   partiu este estilo uma vez (a nota da 6.3.0, acima). */
.up-nav-fav-linha { display:flex; align-items:center }
.up-nav-fav-linha > .up-mod { flex:1 1 auto; min-width:0 }
.up-fav-remover { flex:none; width:20px; height:20px; margin-right:6px; padding:0; border:0;
  background:none; border-radius:var(--up-radius-sm); cursor:pointer; font-size:15px; line-height:1;
  color:var(--up-grey-300); opacity:0 }
.up-nav-fav-linha:hover .up-fav-remover, .up-fav-remover:focus-visible { opacity:1 }
.up-fav-remover:hover { background:var(--up-faint); color:var(--up-ink) }
.up-crumbs { font-size:var(--up-fs-xs); color:var(--up-muted); margin-bottom:var(--up-sp-1) }
.up-crumbs a { color:var(--up-muted); text-decoration:none }
.up-crumbs a:hover { color:var(--up-ink) }
.up-crumbs .sep { margin:0 6px; color:var(--up-grey-300) }

/* A goteira da barra lateral. A `.up-sidebar` é `position:fixed`, portanto sem este espaço o
   conteúdo passava por baixo dela.

   A segunda regra é para as páginas que NÃO têm barra lateral — o login, o convite, o ecrã de
   consentimento do OAuth. Elas usam `body class="up"` (querem o tipo de letra, as cores e o fundo
   do tema) e herdavam com isso uma goteira de 224px para uma barra que não está lá: o `.up-solo`
   centrava no espaço que sobrava e o cartão ficava **112px à direita do centro**, num ecrã de
   1280. Ver a issue Projetix/projetix#138.

   Porque é que é `:has(> .up-solo)` e não uma classe nova no `<body>`: **assim corrige-se
   sozinha**. Qualquer página futura que use o `.up-solo` nasce centrada sem ninguém se lembrar
   disto — e a regra que depende de alguém se lembrar é a que falha.

   Só acontece acima de 901px, que é a razão de ninguém ter dado por isto: em telemóvel a goteira
   não existe e o cartão está centrado. Quem verificou num ecrã pequeno viu-o certo. */
@media (min-width:901px) {
  body.up { padding-left:var(--up-sidebar) }
  body.up:has(> .up-solo) { padding-left:0 }
}

/* 4. ---------------------------------------------------------- Navegação */
.up-sidebar { position:fixed; top:0; left:0; bottom:0; width:var(--up-sidebar);
  background:var(--up-white); border-right:1px solid var(--up-line);
  display:flex; flex-direction:column; padding:18px 14px; z-index:30;
  /* É o que faz o browser TRANSPORTAR a barra de um documento para o outro em vez de a
     repintar — ver «A BARRA NÃO REPINTA», no fim do ficheiro. Está aqui dentro e não num
     bloco próprio lá em baixo porque é o que a `projetix#190` pede: o mesmo selector
     declarado duas vezes ao nível de topo obriga quem lê a juntar dois sítios de cabeça. */
  view-transition-name:up-barra }
/* ⚠️ A ALTURA É `dvh`. O `bottom:0` da regra acima fica como recurso para quem não tenha `dvh`,
   e não é ele que manda onde há.

   Num `position:fixed`, o `bottom:0` assenta na viewport **grande** — a altura que o ecrã tem
   com as barras do Safari retraídas. Com as barras à vista vê-se menos, e a diferença cai para
   fora do ecrã. Medido com a gaveta aberta, viewport grande 812 e visível 682:

   | | |
   |---|---:|
   | a caixa da barra | 0 → 812 |
   | o bloco da conta (`.up-side-foot`) | 754 → 794 — **112px abaixo da dobra** |
   | o acordeão `Admin` | 705 → 754 — abaixo da dobra |

   E nada disso se alcança: a barra não rola (o `overflow` dela é visível), a página atrás também
   não, e o único scroller — a `.up-nav` da regra mais abaixo — acaba **acima** da dobra. Foi o
   que o Bruno relatou como *«o menu lateral não faz scroll no iPhone»*: o que não rola não é a
   navegação, é a barra à volta dela.

   ⚠️ **E pôr `overflow-y:auto` na `.up-sidebar` NÃO resolve** — medido, e é o remédio que parece
   óbvio: o conteúdo cabe na caixa (`scrollHeight == clientHeight == 812`), portanto não há nada
   para rolar. O que transborda não é a caixa, é o ecrã. A caixa tem de valer o que se vê, e é o
   que o `dvh` faz: acompanha as barras do browser, e o `vh`/`bottom:0` não.

   Com `height:100dvh` na mesma medição o bloco da conta volta para dentro (624 → 664, com 18px
   de folga) e a `.up-nav` absorve a diferença — que é o que a regra dela já dizia querer. */
@supports (height:100dvh) {
  .up-sidebar { bottom:auto; height:100dvh }
}
/* A MARCA. O símbolo é do produto (`projetix.svg`); a UNPARALLEL assina por baixo, em
   pequeno, porque a aplicação é da casa mas já não se chama pelo nome dela. Quem só quiser
   um logótipo continua a poder pôr um `<img>` aqui dentro — a regra do `img` fica. */
.up-sidebar .up-brand { display:flex; align-items:center; gap:10px;
  padding:4px 6px 14px; text-decoration:none;
  border-bottom:1px solid var(--up-line); margin-bottom:var(--up-sp-2) }
.up-sidebar .up-brand img { height:38px; width:auto; display:block }

/* ─────────────────── A MARCA DE AMBIENTE (`.up-ambiente`) ───────────────────

   🔴 **Só existe FORA de produção**, e é o que a torna útil. Nasce da issue #243: houve código de
   teste a escrever na base a sério durante dias sem ninguém dar por isso, e uma aplicação que não
   diz o que é deixa quem lá está a supor.

   ⚠️ **Marca-se o que NÃO é produção.** Uma etiqueta permanente a dizer «produção» na aplicação
   que toda a gente usa passa a fazer parte do fundo em duas semanas e deixa de se ver; esta
   aparece raramente, e é aí que tem de saltar à vista.

   Usa o amarelo de AVISO e não o vermelho de erro: isto não é uma avaria, é o contexto — e
   vermelho permanente numa barra lateral treina quem lá trabalha a ignorá-lo. */
.up-sidebar .up-ambiente { align-self:flex-start; margin:-2px 0 var(--up-sp-2) 12px;
  padding:2px 8px; border-radius:999px; font-size:var(--up-fs-xs); font-weight:600;
  letter-spacing:.04em; text-transform:uppercase;
  color:var(--up-warn); background:var(--up-warn-bg); border:1px solid var(--up-warn) }
.up-sidebar .up-brand .marca { width:28px; height:28px; flex:none; color:var(--up-ink) }
.up-sidebar .up-brand .nome { display:block; font-size:17px; font-weight:650;
  letter-spacing:-.3px; color:var(--up-ink); line-height:1.1 }
.up-sidebar .up-brand .casa { display:block; font-size:9.5px; letter-spacing:.10em;
  text-transform:uppercase; color:var(--up-muted); margin-top:1px }

/* Comutador de módulos: diz em que módulo se está e leva aos outros.
   É a peça que falta hoje — com dois módulos deixa de haver contexto. */
.up-switch { position:relative; margin-bottom:var(--up-sp-2) }
.up-switch > button { display:flex; align-items:center; gap:var(--up-sp-2); width:100%;
  padding:8px 10px; border:1px solid var(--up-line); border-radius:var(--up-radius);
  background:var(--up-white); font:inherit; font-size:var(--up-fs-md); font-weight:600;
  color:var(--up-ink); cursor:pointer; text-align:left; transition:var(--up-transition) }
.up-switch > button:hover { background:var(--up-faint) }
.up-switch > button .up-i { color:var(--up-teal) }
.up-switch > button .caret { margin-left:auto; color:var(--up-muted); font-size:11px }
.up-switch-menu { position:absolute; top:calc(100% + 4px); left:0; right:0; background:var(--up-white);
  border:1px solid var(--up-line); border-radius:var(--up-radius); box-shadow:var(--up-shadow-lg);
  overflow:hidden; z-index:40 }
.up-switch-menu[hidden] { display:none }
.up-switch-menu a { display:flex; align-items:center; gap:var(--up-sp-2); padding:9px 11px;
  font-size:var(--up-fs-md); text-decoration:none; color:var(--up-ink) }
.up-switch-menu a:hover { background:var(--up-faint) }
.up-switch-menu a.on { color:var(--up-teal); font-weight:600 }

.up-nav { display:flex; flex-direction:column; gap:2px }
/* ═══════ O DESENHO CLEAN (2026-09-11, item 1) — entradas ACHATADAS ═══════
   Era `padding:9px 11px`, `gap:11px`, texto a `--up-fs-md` (14px): uma linha mais alta e mais
   larga do que a maqueta aprovada pedia. Agora `6px 14px` / `gap:8px` / `--up-fs-sm` (13px) — os
   valores da aba «Barra lateral» do `conectores-modelo.html`. O `font-weight` desce de 500 para
   o normal do corpo: só o item ACTIVO fica a negrito agora (ver o marcador novo, mais abaixo), e
   uma entrada em repouso a negrito competia com ele. */
.up-nav a { display:flex; align-items:center; gap:8px; padding:6px 14px;
  border-radius:var(--up-radius); color:var(--up-nav-ink); text-decoration:none;
  font-size:var(--up-fs-sm); font-weight:400; transition:var(--up-transition) }
.up-nav a .up-i { color:var(--up-grey-500) }
/* Um módulo pode trazer SVG próprio nas entradas de menu (`NavEntry.icone`), sem classe
   nenhuma. Dimensionar só os `.up-i` deixava esses do tamanho que o ficheiro trouxesse — e
   um SVG sem `width` ocupa a linha toda. Vale para qualquer svg dentro da navegação.

   16px e não 18: o item 1 do desenho clean pede ícones mais pequenos e cinzentos — `.up-i`
   já herda `color:var(--up-grey-500)` das regras ao lado, só faltava o tamanho. */
.up-nav a svg, .up-nav-plataforma a svg, .up-mod > .cab svg, .up-syncbtn svg {
  width:16px; height:16px; flex:none }
.up-nav a svg:not(.up-i), .up-nav-plataforma a svg:not(.up-i) { color:var(--up-grey-500) }
.up-nav a:hover { background:var(--up-faint); color:var(--up-ink) }
/* ═══════ O MARCADOR de «estás aqui» (item 2) — a pílula cheia SAIU ═══════
   Era `background:var(--up-teal); color:var(--up-on-accent)` — uma pastilha cheia de teal, que o
   desenho clean apagava sem deixar nada no lugar: uma barra bonita e muda sobre onde se está.
   Substituída por DOIS sinais e não um (um só — só a cor do texto — confunde-se com um `:hover`
   ou com o estado `.aberto` do acordeão, que já usa teal): uma barra à esquerda, o mesmo padrão
   que a conversa por ler do cliente já usa (`.up-live-la`) e o NOME a negrito. Ver a mesma troca
   em `.up-mod.direto > .cab.on` e `.up-mod-sub a.on`, mais abaixo — este selector genérico fica
   pela promessa do nome da classe (`.up-*` é pública, `theme/CLAUDE.md`), ainda que a barra
   actual não construa mais um `<a class="up-nav">` a direito.

   🔴 **VARIANTE A (2026-09-11, mais tarde no mesmo dia) — o FUNDO passa a fazer o trabalho.**
   A barra de 3px + texto teal + negrito eram TRÊS sinais ao mesmo tempo e nenhum pesava sozinho:
   o teal já é a cor normal de uma zona ABERTA (`.up-mod.aberto > .cab`), portanto «texto teal»
   não distingue «estás aqui» de «isto está aberto» — só a barra fina é que o fazia, e uma barra
   de 3px sozinha é fácil de não ver. Escolhida pelo Bruno de propósito entre as variantes
   propostas: um TINTE de fundo (12% de opacidade — visível sem competir com o `.up-tag`/`.up-chip`
   que usam fundo cheio) e a barra desce para 2px, só a ANCORAR a zona pintada ao bordo esquerdo, e
   não a carregar o sinal sozinha. O `border-radius` assimétrico (`0 6px 6px 0`) é o que faz essa
   ancoragem ler-se como tal: cantos direitos no lado da barra dava um rectângulo flutuante, sem
   ligação ao bordo. O `color-mix()` lê o `--up-teal` do TEMA ACTIVO — vira sozinho no escuro,
   como qualquer coisa que use um token em vez de uma cor literal (regra do `theme/CLAUDE.md`). */
.up-nav a.on, .up-nav-plataforma a.on {
  background:color-mix(in srgb, var(--up-teal) 12%, transparent);
  box-shadow:inset 2px 0 0 0 var(--up-teal); border-radius:0 6px 6px 0;
  color:var(--up-teal); font-weight:650 }
.up-nav a.on svg, .up-nav-plataforma a.on svg,
.up-nav a.on .up-i, .up-nav-plataforma a.on .up-i { color:var(--up-teal) }
.up-nav .up-nav-group { font-size:var(--up-fs-xs); color:var(--up-muted); font-weight:600;
  letter-spacing:.04em; text-transform:uppercase; padding:14px 11px 4px }
.up-sep { height:1px; background:var(--up-line); margin:var(--up-sp-2) 6px }

/* UM SCROLLER SÓ, e é a decisão desta barra.
   ────────────────────────────────────────────────────────────────────────────────────────────
   A barra tem três coisas: a marca, a NAVEGAÇÃO, e o bloco da conta. A marca e a conta ficam
   presas; **tudo o que é navegação — módulos e administração — rola junto, aqui dentro.**

   ⚠️ **Havia dois scrollers, e o Bruno não os entendeu: *«o menu do admin ocupa o espaço do de
   cima, devia ser só um scroll, não entendo pq isto tá dividido em dois»*.** Tinha razão, e o
   defeito era de desenho e não de medida — as duas versões anteriores estavam ambas erradas, de
   maneiras opostas:

   | | o que acontecia |
   |---|---|
   | `.up-nav-plataforma { flex:none }` (ecrã grande) | o acordeão `Admin` aberto **nunca encolhe** e esmaga a lista dos módulos — chegou a 6px, medido |
   | `+ overflow-y:auto` (telemóvel) | deixou de esmagar, mas passaram a ser **duas caixas a rolar**, encostadas, sem nada a dizer onde acaba uma |

   A segunda foi escrita para corrigir a primeira e trocou um problema por outro. O que as duas
   partilham é a premissa: que a administração é *outra coisa* e merece a sua caixa. **Não é** —
   é mais uma entrada da navegação, e a prova é que ninguém consegue dizer, a olhar, porque é que
   ela está separada.

   Com um scroller só não há nada a esmaçar nem a dividir: a lista é uma, tem o tamanho que tem, e
   rola. O `min-height:0` é o que permite a um filho de um flex encolher abaixo do conteúdo — sem
   ele o `overflow` nunca chega a morder. */
.up-sidebar .up-nav-scroll { flex:1 1 auto; overflow-y:auto; min-height:0;
  display:flex; flex-direction:column; margin:0 -4px; padding:0 4px }
/* A `.up-nav` deixa de ser scroller: quem rola é o pai. Se ficasse com o `overflow` dela, voltava
   a haver duas caixas — a de dentro a rolar primeiro, e a de fora só quando aquela acabasse. */
.up-sidebar .up-nav { flex:none; overflow:visible; min-height:auto; margin:0; padding:0 }

/* — Módulos em acordeão —
   A barra mostra TODOS os módulos a que a pessoa tem acesso; só o módulo onde ela
   está abre as suas entradas. É o que permite saber o que existe (e saltar num
   clique) sem pôr trinta links numa coluna: o módulo de viagens sozinho traz dez
   entradas a quem aprova. */
.up-mod { border-radius:var(--up-radius) }
/* Achatado como o `.up-nav a` ao lado (item 1, 2026-09-11): `6px 14px`/`gap:8px`/`--up-fs-sm`, e
   o peso desce de 600 para o normal — só o `.on` (mais abaixo) continua a negrito. */
.up-mod > .cab { display:flex; align-items:center; gap:8px; width:100%; padding:6px 14px;
  border:none; background:none; border-radius:var(--up-radius); cursor:pointer;
  font:inherit; font-size:var(--up-fs-sm); font-weight:400; color:var(--up-ink);
  text-align:left; text-decoration:none; transition:var(--up-transition) }
.up-mod > .cab:hover { background:var(--up-faint) }
.up-mod > .cab .up-i { color:var(--up-grey-500) }
/* A SETA APONTA O QUE VAI ACONTECER (Projetix/projetix#266, decisão do Bruno: *«a seta devia ser
   para cima e para baixo pq é o comportamento q ela faz»*).
   O glifo base (`›`) aponta para o LADO — e um chevron de lado significa «vai para ali» em toda a
   aplicação (é o que faz o `.up-appcard`, que NÃO muda aqui). Um acordeão não navega: fechado
   aponta para BAIXO («abre para baixo»), aberto aponta para CIMA («fecha para cima»). */
.up-mod > .cab .seta { margin-left:auto; color:var(--up-grey-300); transition:transform .15s;
  transform:rotate(90deg) }
/* Nada de números ao lado do nome do módulo: numa navegação, um número lê-se como
   "tens três coisas por fazer", e não é isso que ali estaria. */

/* O módulo onde se está: cabeçalho marcado e entradas à vista. */
.up-mod.aberto > .cab { color:var(--up-teal) }
.up-mod.aberto > .cab .up-i { color:var(--up-teal) }
.up-mod.aberto > .cab .seta { transform:rotate(-90deg) }

/* — Um módulo com UMA ENTRADA SÓ: o cabeçalho é o link —
   A plataforma marca-o `.direto` e não lhe põe `.seta` nem sub-árvore. Cinco módulos têm uma
   entrada e ela chama-se o que o módulo se chama: abrir o acordeão mostrava o mesmo nome outra
   vez, uma linha abaixo.

   **A seta é a indicação, e a diferença faz-se por ausência dela:** a `›` promete que aquilo
   abre. O que não abre não a tem — e em troca acende-se como qualquer entrada quando é a página
   onde se está, coisa que um cabeçalho de acordeão nunca faz. É o mesmo par de sinais que as
   entradas da árvore já usam, e por isso não é preciso aprender nada de novo.

   ⚠️ Estas regras têm de ganhar ao `.up-mod > .cab` (0,2,0), que fixa `color` e `background` para
   os cabeçalhos todos — daí a classe a mais em cada seletor.

   🔴 **O MARCADOR MUDOU (desenho clean, 2026-09-11, item 2).** Era uma pastilha CHEIA
   (`background:var(--up-teal); color:var(--up-on-accent)`) — a mais forte da barra, e o desenho
   clean apagava-a sem lhe pôr nada no lugar: ficava bonito e mudo sobre onde se está. Fica agora
   uma barra à esquerda (`box-shadow`, o mesmo padrão do `.up-live-la`/`.up-table.correio
   tr.nao-lida`) e o NOME a negrito e teal — dois sinais em vez de um, para não se confundir com o
   `:hover` nem com o `.up-mod.aberto > .cab` (que já é teal, só que sem negrito nem barra).

   🔴 **VARIANTE A, mais tarde no mesmo dia — o mesmo ajuste do `.up-nav a.on` ao lado.** A barra de
   3px sozinha com texto teal+negrito era fraca precisamente onde o teal já é a cor de uma zona
   ABERTA: aqui confundia-se com o `.up-mod.aberto > .cab`, que também já é teal. O tinte de fundo
   (12%) é o que passa a distinguir «estás aqui» de «isto está aberto» à distância — ver a nota
   completa junto ao `.up-nav a.on`. */
.up-mod.direto > .cab.on {
  background:color-mix(in srgb, var(--up-teal) 12%, transparent);
  box-shadow:inset 2px 0 0 0 var(--up-teal); border-radius:0 6px 6px 0;
  color:var(--up-teal); font-weight:650 }
.up-mod.direto > .cab.on .up-i, .up-mod.direto > .cab.on svg { color:var(--up-teal) }
/* A ÁRVORE fecha por CSS, não por o servidor não a mandar.
   Antes só o `up-mod-sub` do módulo atual era renderizado, e a consequência aparecia no uso:
   clicar num módulo não mostrava nada até a página nova chegar do servidor — a árvore abria
   depois da navegação, e a espera dava a sensação de a aplicação estar pendurada.

   Com todas as sub-árvores no HTML e a visibilidade aqui, um clique pode abrir a certa
   **imediatamente** e deixar a navegação seguir em paralelo. Custa uns 35 links a mais no HTML
   (sete módulos), que é nada, e ganha-se a resposta instantânea. */
/* A BARRA DE CARREGAMENTO, no topo da janela.
   Numa ligação lenta, clicar num link deixava a página anterior no ecrã sem sinal nenhum de que
   algo estava a acontecer — e a única leitura possível disso é "não funcionou", que leva a
   clicar outra vez. Reportado pelo Bruno de uma ligação lenta, 2026-08-10.

   **Indeterminada, e não uma percentagem.** A primeira tentativa animava a `width` de 0 a 96%,
   e não funcionava: uma animação CSS **sobrepõe** o valor da propriedade que anima, portanto a
   largura media sempre o que a keyframe dissesse no instante — e no instante 0 isso é zero. Uma
   faixa que desliza dentro de uma calha de largura fixa não tem esse problema, e é honesta:
   não sabemos a percentagem, e prometer uma é pior do que dizer só "estou a trabalhar".

   A calha é `left:0; right:0` (não `width:%`) e quem se move é o `::before` com `transform`, que
   é a propriedade que o browser anima sem recalcular layout. */
.up-carregando { position:fixed; top:0; left:0; right:0; height:2px; z-index:9999;
  overflow:hidden; opacity:0; pointer-events:none; transition:opacity .2s }
.up-carregando.on { opacity:1 }
.up-carregando::before { content:''; position:absolute; top:0; bottom:0; left:0; width:40%;
  background:linear-gradient(90deg, transparent, var(--up-teal), transparent);
  transform:translateX(-100%) }
.up-carregando.on::before { animation:up-carrega 1.1s ease-in-out infinite }
@keyframes up-carrega {
  0%   { transform:translateX(-100%) }
  100% { transform:translateX(350%) }
}
@media (prefers-reduced-motion: reduce) {
  /* Quem pediu menos movimento continua a precisar do sinal — uma faixa parada, sem deslizar. */
  .up-carregando.on::before { animation:none; transform:none; width:100%;
    background:var(--up-teal); opacity:.5 }
}

/* O CABEÇALHO de um módulo é um `<button>` e não um link (a plataforma mudou-o a 2026-08-10:
   abre e fecha em vez de navegar). Um botão traz consigo estilo do browser — fundo, borda,
   tipo de letra — que aqui não se quer. */
.up-mod > .cab { background:none; border:0; font:inherit; text-align:left; cursor:pointer }

/* A SECÇÃO da barra lateral (issue #66). Enquanto nenhum módulo declarar `seccao=`, isto não
   aparece — há uma secção só, sem nome. Fica montado para quando declararem. */
.up-nav-seccao { font-size:var(--up-fs-xs); font-weight:650; letter-spacing:.08em;
  text-transform:uppercase; color:var(--up-grey-500);
  padding:var(--up-sp-3) 11px var(--up-sp-1) }
/* A primeira não precisa de espaço em cima: já tem o da barra. */
.up-nav-seccao:first-child { padding-top:var(--up-sp-1) }

/* O TIPO de um Projetix — instituição/proposta/… (item 6, desenho clean de 2026-09-11). Só se lê
   com o item ABERTO (`.up-mod:not(.aberto) > .up-nav-tipo`, junto ao `.up-mod-sub`): antes só
   estava no `title` do cabeçalho, que pede o rato para aparecer. Leva o MESMO tratamento da
   `.up-nav-seccao` — é prosa sobre o que está ali dentro, e não uma entrada; o alinhamento
   (38px) casa com o recuo dos filhos, mesmo não sendo um deles. */
.up-nav-tipo { padding:0 14px 6px 38px; font-size:var(--up-fs-xs); font-weight:650;
  letter-spacing:.08em; text-transform:uppercase; color:var(--up-grey-500); cursor:default }
.up-mod:not(.aberto) > .up-nav-tipo { display:none }

/* A ÁREA da aplicação — My Area · Services · Institutions (2026-08-17).
 *
 * ⚠️ **É uma classe própria e não o `.up-nav-seccao`, e a razão não é o desenho: é que uma área e
 * uma secção não são a mesma coisa.** Uma secção agrupa módulos por conveniência dentro de uma
 * área; uma área é a estrutura da aplicação, e a das instituições diz que os dados lá dentro são
 * de uma instituição. Com as duas a partilhar a classe, ficavam iguais no ecrã — e a barra passava
 * a ter dois níveis com o mesmo peso, que é precisamente o que a mudança veio desfazer.
 *
 * Mais escura, mais forte, e com um traço em cima a separar as áreas. A secção continua
 * cinzenta e miúda, e por isso lê-se como o que está DENTRO da área.
 *
 * ⚠️ **E A ÁREA ABRE E FECHA (2026-08-17).** Era um `<div>` fixo, e com dezanove módulos a barra
 * ficou com **1314px numa janela de 568** — quem aterrava na Contabilidade tinha o módulo aberto a
 * 1229px, fora do ecrã. A queixa que chegou foi «perdeu-se a parte boa do abrir e fechar», e o
 * acordeão dos módulos estava intacto: **o que faltava era caber.** Vinte linhas planas não cabem
 * por nenhuma política de acordeão; só encolher a área.
 *
 * Por isso é agora um `<button>` e leva a `.seta`, como o cabeçalho de um módulo — a mesma forma
 * quer dizer a mesma coisa. O que a distingue continua a ser o peso e o traço, não a estrutura. */
/* ⚠️ **Sem folga HORIZONTAL** (Projetix/projetix#266, decisão 27): dava `11px` de cada lado, por
   cima dos 14px da goteira da barra — o título começava a 25px e a seta acabava 25px antes da
   borda, os dois a flutuar no meio de uma linha larga. Encostados às pontas (título à goteira, seta
   ao `margin-left:auto`), a linha ganha a tensão que ele descreveu — só o VERTICAL fica (a
   densidade é outra decisão, item 4, e não se mexe no espaço horizontal por causa dela). */
.up-nav-area { display:flex; align-items:center; gap:6px; width:100%;
  font-family:inherit; font-size:var(--up-fs-xs); font-weight:750; letter-spacing:.08em;
  text-transform:uppercase; color:var(--up-ink); text-align:left;
  background:none; border:0; cursor:pointer;
  padding:var(--up-sp-4) 0 var(--up-sp-1); border-radius:var(--up-radius);
  transition:var(--up-transition) }
.up-nav-area:hover { color:var(--up-teal) }
/* A seta gira, como a do módulo — e com o MESMO significado novo: fechada aponta para BAIXO
   («abre para baixo»), aberta aponta para CIMA («fecha para cima»). Ver o comentário junto ao
   `.up-mod > .cab .seta`, que tem a razão inteira. */
.up-nav-area .seta { margin-left:auto; color:var(--up-grey-500); transition:var(--up-transition);
  transform:rotate(90deg) }
.up-nav-area-bloco.aberto > .up-nav-area .seta { transform:rotate(-90deg) }
/* A SETA de uma zona com TRÊS PONTOS (item 3, desenho clean de 2026-09-11) — «My Dashboards» e
   «Projetix's». Dentro do `.up-nav-area` ela ficava à ESQUERDA dos três pontos: o botão é
   `flex:1 1 auto` (regra do `.up-nav-cab > .up-nav-area`, abaixo) e tudo o que está dentro dele
   fica ANTES do resto da linha — a seta nunca chegava à ponta a sério. Um `<span>` irmão, depois
   do `.up-nav-mais`, resolve isso: fica na MESMA linha (o `.up-nav-cab` é `display:flex`), à
   direita de tudo — o pedido era a seta à direita em TODAS as zonas, e isso inclui a
   «Favourites», que não tem três pontos e por isso já tinha a seta na ponta (dentro do próprio
   `.up-nav-area`, sem concorrência).

   ⚠️ **`aria-hidden` e `tabindex` nenhum a mais**: continua a chamar `upAlternar(this)` — o mesmo
   `.closest('.up-nav-area-bloco')` que o botão do rótulo já usa —, mas é um afazer a mais para
   quem navega por teclado (o botão do rótulo já alterna o mesmo acordeão) e por isso não entra na
   ordem de tabulação: um alvo de rato extra, não um segundo controlo. */
.up-nav-cab > .seta { flex:none; margin-left:2px; padding:2px; cursor:pointer;
  color:var(--up-grey-500); transition:var(--up-transition); transform:rotate(90deg) }
.up-nav-area-bloco.aberto > .up-nav-cab > .seta { transform:rotate(-90deg) }
.up-nav-area-bloco { display:flex; flex-direction:column; gap:2px;
  margin-top:var(--up-sp-2); border-top:1px solid var(--up-faint) }
.up-nav-area-bloco > .up-nav-area-corpo { display:flex; flex-direction:column; gap:2px }
.up-nav-area-bloco:not(.aberto) > .up-nav-area-corpo { display:none }
/* A primeira não leva traço nem espaço: já tem o da barra, e um traço no topo da lista lê-se
   como o fim da marca e não como o início da área. */
.up-nav-area-bloco:first-child { margin-top:0; border-top:0 }
.up-nav-area-bloco:first-child > .up-nav-area { padding-top:var(--up-sp-1) }

/* TROCAR DE INSTITUIÇÃO. É um `<form>` porque muda o estado da sessão (um `<a>` que troca de
   instituição é trocado por qualquer prefetch), e o botão tem de se ler como as entradas ao lado:
   quem olha para a árvore aberta não deve ver metade links e metade botões. */
.up-trocar { margin:0 }
.up-trocar > button { display:flex; align-items:center; gap:11px; width:100%;
  font-family:inherit; font-size:var(--up-fs-md); font-weight:500; text-align:left;
  background:none; border:0; cursor:pointer; color:var(--up-nav-ink);
  padding:7px 10px; border-radius:var(--up-radius); transition:var(--up-transition) }
.up-trocar > button:hover { background:var(--up-faint); color:var(--up-ink) }

.up-mod:not(.aberto) > .up-mod-sub { display:none }
/* ═══════ OS FILHOS, achatados (desenho clean, 2026-09-11, item 1) ═══════
   O traço vertical + o recuo de margem SAÍRAM: eram o que amarrava visualmente as entradas ao
   módulo a que pertencem, mas competiam com a barra teal do marcador novo (`.on`, mais acima) —
   duas linhas verticais coloridas ao lado uma da outra. O recuo faz-se agora só pelo
   `padding-left` do próprio link (38px — o mesmo valor da aba «Barra lateral» do
   `conectores-modelo.html`), e o que substitui o traço como sinal de hierarquia é o PONTO
   (`::before`, abaixo) em vez do ícone.

   🔴 **A FOLGA DA QUEIXA 3 (2026-09-11) estava AQUI, não no `.up-nav-area-corpo`** — esse já
   tinha `gap:2px`, medido antes de mexer. O `padding-bottom` desta regra era `var(--up-sp-3)`
   (12px) contra um `padding-top` de 2px — assimetria que sobrou de antes do desenho clean, quando
   tudo à volta era mais largo e a diferença não se notava.

   **Medido no `mail_real_completo.html` (a página real, com a barra de produção), antes de
   mexer:** entre duas entradas IRMÃS dentro do mesmo módulo aberto (p.ex. «files» → «Mail») o
   espaço é **2px** — o `gap` do `.up-mod-sub`. Entre a ÚLTIMA entrada de um módulo aberto e o
   módulo seguinte (p.ex. «Dashboard» → o próximo `.up-mod`) o espaço media **12px (este
   `padding-bottom`) + 2px (o `gap` do `.up-nav-area-corpo`, ao nível de fora) = 14px** — **sete
   vezes** o espaço entre duas apps quaisquer. É esta a distância por trás de «demasiado
   afastadas»: não o espaço normal entre apps, mas o salto sempre que a app anterior estava
   aberta — que é o caso mais comum (é a app onde a pessoa está).

   Desce para `var(--up-sp-1)` (4px): 4+2=6px depois de um módulo aberto, contra os 2px de sempre
   — ainda distinguível (os filhos de um módulo continuam a fechar com um pouco mais de fôlego do
   que entre si), mas já não um salto de sete vezes. */
.up-mod-sub { display:flex; flex-direction:column; gap:2px; padding:2px 0 var(--up-sp-1) }
.up-mod-sub a { font-weight:400; padding:5px 14px 5px 38px; font-size:var(--up-fs-sm) }
/* O PONTO que substitui o ícone (item 1): um círculo desenhado por `radial-gradient` e não um
   carácter tipográfico (`·`/`•`) — o app já tem este padrão (`.up-tbchip.mudou > .abrir::before`,
   `.up-achado .ponto`), e um glifo de fonte varia de alinhamento vertical consoante o tipo de
   letra do sistema; um círculo desenhado não. A caixa de 16px é a mesma largura de um ícone de
   primeiro nível — é o que faz o texto de um filho começar sempre na mesma coluna, tenha ele
   ícone declarado ou não (o `nav.html` já não desenha o `e.icone`/`decl.icone` destas entradas). */
.up-mod-sub a::before { content:''; width:16px; height:16px; flex:none;
  background:radial-gradient(circle at 50% 50%, var(--up-muted) 0 2px, transparent 2.5px) }
/* O MARCADOR de «estás aqui» de um filho (item 2) — o mesmo padrão do `.up-mod.direto > .cab.on`
   ao lado: uma barra teal à esquerda e o nome a negrito, em vez da pastilha cheia antiga.

   🔴 **VARIANTE A** — o mesmo tinte de fundo dos outros dois marcadores (ver a nota junto ao
   `.up-nav a.on`). O PONTO (`::before`, abaixo) não muda: já era `--up-teal` cheio e opaco (o
   `radial-gradient` desenha um círculo com contorno endurecido — `0 2px` opaco, `2.5px` já
   transparente — e não um degradê), portanto continua a ler-se por cima do tinte de 12% sem
   precisar de mais nada. */
.up-mod-sub a.on { background:color-mix(in srgb, var(--up-teal) 12%, transparent);
  box-shadow:inset 2px 0 0 0 var(--up-teal); border-radius:0 6px 6px 0;
  color:var(--up-teal); font-weight:650 }
.up-mod-sub a.on::before {
  background:radial-gradient(circle at 50% 50%, var(--up-teal) 0 2px, transparent 2.5px) }

/* As entradas da plataforma (Administração) ficam DENTRO da zona que rola, a seguir aos módulos.
   O traço em cima é o que as separa — e é quanto baste.

   ⚠️ **Esta regra já disse *"ficam FORA da zona que rola, agarradas ao rodapé"*, e era essa a
   causa.** A intenção era boa (estarem sempre no mesmo sítio), mas custava mais do que dava: com
   o acordeão aberto, «sempre no mesmo sítio» passa a querer dizer *«a ocupar a barra toda»*, e o
   que fica sem sítio é a lista dos módulos. Ver o `.up-nav-scroll`. */
.up-nav-plataforma { flex:none; display:flex; flex-direction:column; gap:2px;
  border-top:1px solid var(--up-line); margin-top:var(--up-sp-2); padding-top:var(--up-sp-2) }
.up-nav-plataforma:empty { display:none }   /* quem não é administrador não vê o traço */
/* Achatado como o resto da barra (item 1, 2026-09-11) — o `.on` (a pastilha cheia) já saiu
   daqui: fica no bloco combinado `.up-nav a.on, .up-nav-plataforma a.on`, lá em cima, para as
   duas famílias de entrada não terem cada uma o seu marcador a divergir com o tempo. */
.up-nav-plataforma a { display:flex; align-items:center; gap:8px; padding:6px 14px;
  border-radius:var(--up-radius); color:var(--up-nav-ink); text-decoration:none;
  font-size:var(--up-fs-sm); font-weight:400; transition:var(--up-transition) }
.up-nav-plataforma a .up-i { color:var(--up-grey-500) }
.up-nav-plataforma a:hover { background:var(--up-faint); color:var(--up-ink) }

/* Uma pessoa com um módulo só não vê cabeçalho nenhum — é o caso mais comum hoje,
   e não pode ficar pior do que está.

   ⚠️ **O `:not(.direto)` não é arrumação — sem ele o menu fica VAZIO.** Num módulo `.direto` o
   cabeçalho é o único link que existe (não há `up-mod-sub` para ficar à vista), portanto escondê-lo
   esconde a navegação toda. Acontece a quem tenha um módulo só e ele tenha uma entrada só, que é
   exactamente o caso mais comum. */
.up-nav.um-modulo .up-mod:not(.direto) > .cab { display:none }
/* ⚠️ **O recuo agora vive no `padding-left` do link, não no container** (desde o desenho clean
   de 2026-09-11 — ver a nota junto ao `.up-mod-sub` acima), por isso desfazê-lo em modo
   `um-modulo` (só um módulo instalado: não há irmãos para distinguir, e o filho pode ficar à
   face) mexe nos DOIS sítios — o container já não tinha margem/traço para tirar, mas o link
   continua com os 38px se ninguém lho tirar aqui. */
.up-nav.um-modulo .up-mod-sub a { padding-left:14px }
.up-nav.um-modulo .up-mod-sub a::before { display:none }

/* — Gaveta (ecrã pequeno) —
   Com módulos e subopções, achatar a navegação numa fila no topo deixa de dar. */
.up-topbar { display:none; align-items:center; gap:var(--up-sp-3); height:var(--up-topbar);
  padding:0 var(--up-sp-3); background:var(--up-white); border-bottom:1px solid var(--up-line);
  position:sticky; top:0; z-index:28 }
.up-topbar img { height:22px }
/* `text-decoration:none` explícito: sem regra base para `a` (de propósito — ver o comentário
   nos links de prosa do `up-note`/`up-link`), o logo herdava o sublinhado do browser. Medido em
   produção a 2026-09-08: «o logo ficou com sublinhado». */
.up-brand-top { display:inline-flex; color:var(--up-ink); text-decoration:none }
.up-brand-top .marca { width:24px; height:24px }
.up-navbtn { display:inline-flex; align-items:center; justify-content:center; width:36px;
  height:36px; border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  background:var(--up-white); cursor:pointer; color:var(--up-ink) }
.up-scrim { position:fixed; inset:0; background:rgba(17,20,24,.35); z-index:29 }

/* — Sincronizar —
   Só aparece no ambiente local (é o único que puxa a base do servidor). Acende quando há
   coisas novas lá: um botão apagado que muda de cor diz mais do que um aviso. */
.up-syncbtn { display:flex; align-items:center; gap:11px; padding:9px 11px; width:100%;
  border:1px solid var(--up-line); border-radius:var(--up-radius); background:var(--up-white);
  color:var(--up-nav-ink); font:inherit; font-size:var(--up-fs-md); font-weight:500; cursor:pointer;
  text-align:left; transition:var(--up-transition) }
.up-syncbtn .up-i { color:var(--up-grey-500) }
.up-syncbtn:hover:not(:disabled) { background:var(--up-faint); color:var(--up-ink) }
.up-syncbtn:disabled { opacity:.55; cursor:default }
.up-syncbtn.has { background:var(--up-orange-bg); border-color:#f0c46b; color:var(--up-warn) }
.up-syncbtn.has .up-i { color:var(--up-orange) }
.up-syncbadge { margin-left:auto; background:var(--up-orange); color:var(--up-on-accent);
  border-radius:var(--up-radius-pill); min-width:18px; text-align:center;
  font-size:11px; font-weight:700; padding:0 6px; line-height:18px }

/* Rodapé da barra: quem está autenticado. */
.up-side-foot { margin-top:auto; border-top:1px solid var(--up-line);
  padding-top:var(--up-sp-2); position:relative }
.up-user { display:flex; align-items:center; gap:var(--up-sp-2); width:100%; padding:7px 8px;
  border:none; background:none; cursor:pointer; border-radius:var(--up-radius);
  text-align:left; font:inherit }
.up-user:hover { background:var(--up-faint) }
.up-user .who { display:flex; flex-direction:column; line-height:1.2; overflow:hidden }
.up-user .who .nm { font-size:var(--up-fs-sm); font-weight:600; color:var(--up-ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:120px }
.up-user .who .rl { font-size:11px; color:var(--up-muted); text-transform:capitalize }
.up-user .caret { margin-left:auto; color:var(--up-muted); font-size:16px; letter-spacing:1px }
.up-menu { position:absolute; bottom:56px; left:6px; right:6px; background:var(--up-white);
  border:1px solid var(--up-line); border-radius:var(--up-radius);
  box-shadow:var(--up-shadow-lg); overflow:hidden; z-index:40 }
.up-menu[hidden] { display:none }
.up-menu a { display:flex; align-items:center; gap:10px; padding:10px 13px; font-size:var(--up-fs-sm);
  color:var(--up-ink); text-decoration:none }
.up-menu a:hover { background:var(--up-faint) }
.up-menu-langs { display:flex; gap:4px; padding:8px 13px; border-top:1px solid var(--up-line) }
.up-menu-langs a { padding:2px 8px; border-radius:6px; font-size:var(--up-fs-xs);
  color:var(--up-muted); background:var(--up-faint); text-decoration:none }
.up-menu-langs a.on { background:var(--up-teal); color:var(--up-on-accent) }
.up-menu .ver { padding:8px 13px; font-size:11px; color:var(--up-grey-300);
  border-top:1px solid var(--up-line); letter-spacing:.02em }

/* O RODAPÉ (`projetix#115`). Uma linha discreta com o link para o que a aplicação guarda sobre
   pessoas — no `layout.html` e no ecrã de entrada.

   ⚠️ **Discreto e não invisível.** A cor é a `--up-muted` e não a `--up-grey-300`: um link que
   ninguém distingue do fundo tem o mesmo efeito prático que não existir, e a razão de este rodapé
   ter sido escrito é precisamente não haver onde ler aquilo. Sublinha ao passar. */
.up-rodape { padding:18px 20px 28px; text-align:center; font-size:var(--up-fs-xs) }
.up-rodape a { color:var(--up-muted); text-decoration:none }
.up-rodape a:hover { text-decoration:underline }

/* ------------------------------------------------- A FAIXA DAS MINIMIZADAS (`projetix#227`)

   Uma pastilha por página que se pôs de lado. Pedido do Bruno a 2026-08-27: *«ter tipo uma taskbar
   dinamica ca em baixo em q eu podesse qq pagina minimizar para la para tar sempre disponivel»*.

   ⚠️ **Começa onde a barra lateral acaba, e não por cima dela.** A barra é navegação permanente:
   uma faixa a tapá-la esconde o sítio onde se está para mostrar os sítios onde se esteve. Em ecrã
   pequeno (onde a barra é uma gaveta) passa a ocupar a largura toda — ver a media query no fim
   deste bloco, que é o MESMO `901px` que a estrutura usa lá em cima.

   ⚠️ **E o `body` reserva-lhe espaço.** Sem isso ela tapa o fim de TODAS as páginas e o rodapé, e
   o sintoma — «a última linha da minha tabela não aparece» — não aponta para aqui.

   O `:has()` é o que faz o espaço existir só quando a faixa existe: ela é desenhada pelo servidor
   e só quando há alguma coisa lá dentro, portanto uma regra fixa reservava 56px a toda a gente. */
body.up:has(.up-taskbar) { padding-bottom:56px }
.up-taskbar { position:fixed; left:0; right:0; bottom:0; z-index:35;
  display:flex; align-items:center; gap:7px; padding:9px 14px; overflow-x:auto;
  background:var(--up-white); border-top:1px solid var(--up-line) }
.up-taskbar-rot { flex:none; font-size:var(--up-fs-xs); font-weight:650; letter-spacing:.08em;
  text-transform:uppercase; color:var(--up-grey-300); padding-right:4px }
.up-tbchip { flex:none; display:inline-flex; align-items:center; background:var(--up-faint);
  border:1px solid var(--up-line); border-radius:var(--up-radius-pill); overflow:hidden }
.up-tbchip:hover { border-color:var(--up-grey-300) }
.up-tbchip > .abrir { display:inline-flex; align-items:center; gap:7px;
  padding:5px 4px 5px 11px; font-size:var(--up-fs-sm); color:var(--up-ink);
  text-decoration:none; max-width:210px; white-space:nowrap; overflow:hidden;
  text-overflow:ellipsis }
.up-tbchip > .abrir svg { width:15px; height:15px; flex:none; color:var(--up-grey-500) }
.up-tbchip > .fechar { border:0; background:none; cursor:pointer; font:inherit;
  padding:5px 9px 5px 5px; font-size:14px; line-height:1; color:var(--up-grey-300) }
.up-tbchip > .fechar:hover { color:var(--up-err) }
/* A pastilha da página onde ESTAMOS. A faixa diz as duas coisas — o que se pôs de lado, e em qual
   delas se está agora; sem isto, voltar a uma pastilha não tinha confirmação nenhuma. */
.up-tbchip.on { background:var(--up-accent-bg); border-color:transparent }
.up-tbchip.on > .abrir { color:var(--up-teal-700); font-weight:600 }
.up-tbchip.on > .abrir svg { color:var(--up-teal) }
/* ⚠️ **A PASTILHA CUJO REGISTO MUDOU enquanto esteve minimizada** (projetix#227). Pedido do Bruno
   à letra: *«tem de aparecer quando ta minimizada, temos de ter uma forma de mostrar»* — com seis
   pastilhas na faixa, o que se precisa é de saber QUAL mudou sem abrir as seis.

   O ponto é **um ponto e não uma cor de fundo**, e a razão está na pastilha ao lado: o fundo já
   está gasto a dizer em qual delas se está (`.on`). Duas informações no mesmo canal davam uma
   pastilha acesa-e-mudada indistinguível de uma acesa. Assim as duas lêem-se juntas.

   E fica à ESQUERDA do rótulo, no sítio onde o olho entra na pastilha: à direita ficava colado ao
   `×`, que é o botão que apaga a pastilha — a marca a chamar a atenção para o gesto errado. */
.up-tbchip.mudou > .abrir { padding-left:7px }
.up-tbchip.mudou > .abrir::before { content:""; flex:none; width:7px; height:7px;
  border-radius:50%; background:var(--up-warn) }
.up-tbchip.mudou { border-color:var(--up-warn) }

@media (min-width:901px) { .up-taskbar { left:var(--up-sidebar) } }

/* Ecrã pequeno: barra no topo com o botão, e a mesma navegação numa gaveta. */
@media (max-width:900px) {

  .up-topbar { display:flex }
  .up-sidebar { transform:translateX(-100%); transition:transform .18s ease; width:min(86vw, 300px);
    box-shadow:var(--up-shadow-lg) }
  .up-sidebar.aberta { transform:none }
  .up-main { padding:var(--up-sp-4) var(--up-sp-3) var(--up-sp-6) }
  .up-savebar { padding:10px var(--up-sp-3) }

  /* ⚠️ COM A GAVETA ABERTA, A PÁGINA DE TRÁS NÃO SE MEXE.
     Relatado pelo Bruno **depois** da correcção da altura: *«faço scroll na lista do menu e o que
     faz scroll é a parte de trás»*. Não é altura — é o gesto a ir para o documento em vez de ir
     para a navegação.

     Medido a 375×812, ponto a ponto na gaveta, com o scroller mais próximo por cima do alvo — que
     é quem recebe o gesto:

     | a gaveta | a `.up-nav` rola? | pontos cujo gesto vai para o DOCUMENTO |
     |---|---|---:|
     | acordeões todos fechados | **não** — cabe, não há o que rolar | **100%** |
     | três abertos | sim (369px de sobra) | 24% |
     | tudo aberto | sim (2179px) | 66% |

     Os 24% e os 66% são a marca, o bloco da administração, o rodapé da conta e os intervalos —
     nenhum deles dentro de um scroller. E com o menu **fechado** não há scroller nenhum na
     gaveta: aí vai para trás o gesto inteiro, que é exactamente o que ele descreve.

     Por isso quem se trava é o DOCUMENTO enquanto a gaveta está aberta. O `:has()` faz isso sem
     JavaScript — e é essa a razão de não ser o `position:fixed` no `body` que se costuma ver:
     **a posição de leitura é preservada**. Medido: 250px antes de abrir, 250px depois de fechar.
     Com `position:fixed`, sem alguém guardar o `scrollTop`, a página salta para o topo.

     ⚠️ **Isto sozinho não chegava, e a regra a seguir é que é a causa.** Esta trava o fundo; a
     outra devolve um scroller ao dedo. Uma versão anterior deste bloco também punha
     `overscroll-behavior` na `.up-nav` — saiu, e a razão está escrita lá em baixo. */
  html:has(.up-sidebar.aberta) { overflow:hidden }

  /* ⚠️ **AQUI NÃO VAI REGRA NENHUMA PARA A `.up-nav-plataforma`, e é uma remoção deliberada.**

     Esteve aqui `.up-sidebar .up-nav-plataforma { flex:0 1 auto; overflow-y:auto; min-height:0 }`,
     escrita para corrigir o *«não consigo navegar na lista»*: com `flex:none` e sem `overflow`, o
     acordeão `Admin` aberto ocupava 660px e **esmagava a `.up-nav` até 6 pixéis** — a lista dos
     módulos desaparecia, e a que se via não era scroller nenhum, portanto o dedo em cima dela
     rolava a página de trás.

     Corrigiu isso e criou o defeito seguinte: **duas caixas a rolar, encostadas.** Foi o que o
     Bruno veio dizer a 2026-08-16 — *«devia ser só um scroll, não entendo pq isto tá dividido em
     dois»*.

     Agora quem rola é a `.up-nav-scroll`, que é a mesma no telemóvel e no ecrã grande, portanto
     esta média não tem nada de especial a dizer sobre a administração. **Não voltar a pôr aqui um
     `overflow`** — é o que traria as duas caixas de volta.

     E continua a não haver `overscroll-behavior`: ele só impede a propagação de um scroller que
     está a funcionar, e agora há um a funcionar debaixo do dedo em toda a navegação. Se um dia se
     quiser, para o arrastão no fim da lista, é outra linha e outra medição. */
}
@media (min-width:901px) {
  .up-scrim { display:none }
}
@media (prefers-reduced-motion:reduce) { .up-sidebar { transition:none } }

/* ══════════════════ A BARRA DE CIMA VIRA A MOLDURA, E A LATERAL COLAPSA ══════════════════
   Projetix/projetix#266 (2026-09-08). Decidido numa maqueta que transformava a página real
   (`deployment/dev/maqueta-topbar.py`) — aqui é a mesma folha a sério: a marca, o selo do
   ambiente, a procura e o menu da pessoa saem da barra lateral e vão para a `.up-topbar`, que
   deixa de ser só do telemóvel. A barra lateral fica só com NAVEGAÇÃO.

   ⚠️ **Duas variantes no MESMO `<header>`, por classe, e não duas marcações.** O `.up-hide-sm`/
   `.up-hide-lg` (já no ficheiro) escondem por ecrã os elementos que não são dessa largura — a
   gaveta/marca-ícone/lupa-ícone do telemóvel ficam `.up-hide-lg`; a marca com nome, o selo, a
   procura e os ícones da pessoa ficam `.up-hide-sm`. O `nav.html` explica a razão de não ser
   `@media` a esconder blocos inteiros: cada um continua a precisar de existir no DOM para o
   `.up-side-foot` do `upMenuUtilizador` ser encontrado por um `querySelector`. */
/* ⚠️ **Tudo dentro do `@media (min-width:901px)`, e não a regra de sempre.** Em ecrã pequeno a
   `.up-topbar` continua `position:sticky` (já estava, mais acima) — só o tamanho GRANDE muda de
   posição, para `fixed` e acima da lateral. Misturar as duas tirava a barra do fluxo no
   telemóvel também, sem a compensação de espaço que só este bloco acrescenta. */
@media (min-width:901px) {
  .up-topbar { display:flex; position:fixed; left:0; right:0; top:0; z-index:40;
    padding:0 14px; gap:12px }
  .up-sidebar { top:var(--up-topbar) }
  body.up { padding-top:var(--up-topbar) }
}
/* A MARCA no topo ganha o nome e o «by UNPARALLEL» debaixo dele — ele recusou tirá-lo:
   *«perdemos o by unparallel no icon, não é boa ideia»*. Empilhado, para caber nos 56px. */
.up-topbar .up-brand-top { gap:9px }
.up-topbar .up-brand-top > span { display:flex; flex-direction:column; justify-content:center }
.up-topbar .up-brand-top .nome { display:block; font-size:14.5px; font-weight:650;
  letter-spacing:-.01em; line-height:1.1; color:var(--up-ink) }
.up-topbar .up-brand-top .casa { display:block; font-size:8.5px; letter-spacing:.10em;
  text-transform:uppercase; opacity:.55; line-height:1.2 }
/* O SELO DO AMBIENTE sobe para aqui e passa a CHEIO (fundo sólido, não o pálido da barra
   lateral) — *«mete o development na mesma com uma cor q chame a atenção»*. Continua na família
   do AVISO e não passa a vermelho: essa decisão já estava escrita mais acima, no comentário do
   `.up-sidebar .up-ambiente` — muda o PESO, não a cor. */
.up-topbar .up-ambiente { margin:0 0 0 -4px; align-self:center; flex:none;
  background:var(--up-warn) !important; color:var(--up-white) !important; border:0 !important;
  font-weight:700; letter-spacing:.07em; padding:3px 9px }
/* A PROCURA, centrada contra a JANELA e não contra o espaço que sobra — os dois lados do
   `.up-topbar` têm larguras diferentes (a marca à esquerda, cinco coisas à direita), e um
   `flex:1` com `margin:auto` não teria folga para distribuir. `position:absolute` resolve-o. */
.up-topbar-busca { position:absolute; left:50%; transform:translateX(-50%);
  width:min(520px, 40vw); display:flex; align-items:center }
.up-topbar-busca input { width:100%; height:34px; padding:0 12px 0 34px;
  border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  background:var(--up-bg); color:var(--up-ink); font:inherit; font-size:var(--up-fs-sm) }
.up-topbar-busca .up-i { position:absolute; left:10px; opacity:.55; pointer-events:none }
/* Marca a página onde já se está a procurar — o `.up-btn` que fazia isto na barra lateral saiu
   com ela; o `test_a_lupa_acende_na_pagina_da_procura` lê este `.on` agora. */
.up-topbar-busca.on input { border-color:var(--up-teal) }

/* OS ÍCONES E A PESSOA, à direita. Um ritmo e não um número só: os quatro ícones são a mesma
   família e ficam juntos (4px); a pessoa é outra coisa e ganha um intervalo maior antes dela
   (8px), que a lê como um grupo separado sem precisar de um traço. */
.up-topbar-direita { display:flex; align-items:center; gap:4px; margin-left:auto }
.up-topbar-direita .up-side-foot { margin-left:8px }
.up-topbar-direita .up-navbtn { position:relative }
/* O CONTADOR só nos ÍCONES, e não no que está dentro do menu da pessoa — o `>` directo é o que
   distingue as duas coisas. Sem ele, o selo das notificações que vive numa linha do menu ficava
   absoluto e saltava para o canto de cima de qualquer ícone à frente dele. */
.up-topbar-direita > .up-dica .up-tag {
  position:absolute; top:-2px; right:-2px; transform:none;
  min-width:15px; height:15px; padding:0 3px; border-radius:8px;
  font-size:9.5px; line-height:15px; text-align:center }

/* O MENU DA PESSOA MUDOU-SE PARA AQUI, e o `.up-side-foot` é a classe que o `upMenuUtilizador`
   já procura por `querySelector` — dar-lhe o elemento onde ele espera é melhor do que reescrever
   o script por causa de uma mudança de sítio. */
.up-topbar .up-side-foot { position:relative; display:flex; align-items:center; line-height:1;
  padding:0; border:0; margin:0 }
.up-topbar .up-side-foot .up-user { padding:0; background:none; border:0 }
.up-topbar .up-side-foot .up-user .up-avatar { margin:0 }
.up-topbar .up-side-foot .up-user .who { display:none }     /* no topo só cabe o avatar */
.up-topbar .up-side-foot .up-user .caret { display:none }   /* «⋯» sem sentido ao lado de 26px */
/* A largura do menu era herdada do RODAPÉ da barra lateral (~140px, o «Report a problem» e o
   «Change password» partiam em duas linhas); aqui mede-se pelo CONTEÚDO — item 12. E abre para
   BAIXO e à DIREITA (o tema abria-o para cima, porque vivia no rodapé): com `top` E `bottom`
   fixos ao mesmo tempo um elemento absoluto deixa de se dimensionar pelo conteúdo — é `bottom:auto`
   que devolve a altura ao que lá está dentro. */
.up-topbar .up-side-foot .up-menu { position:absolute; right:0; left:auto;
  top:calc(100% + 6px); bottom:auto;
  width:max-content; min-width:220px; max-width:320px }
.up-topbar .up-side-foot .up-menu > a { white-space:nowrap }

/* ══════════════════ A DENSIDADE (item 4): só no ecrã GRANDE ══════════════════
   O `@media (max-width:900px)`, onde a barra é uma gaveta, fica como estava — 28px de linha serve
   rato e teclado mas não toque (a recomendação é 44px). Medido antes de mexer: o passo era
   41,7px (`padding:9px 11px` + 2px de margem); passa a ~34. NÃO se aperta o horizontal — é o
   vertical que enche o ecrã. */
@media (min-width:901px) {
  .up-sidebar .up-nav-area-corpo a,
  .up-sidebar .up-nav-area-corpo .cab,
  .up-sidebar .up-mod-sub a {
    padding-top:5px; padding-bottom:5px; min-height:0 }
  .up-sidebar .up-mod-sub { margin:0 }
  /* Já não há marca/procura/conversa/ambiente na barra: a folga de 18px que o `padding` do
     `.up-sidebar` reservava para a marca fica sem para quê e a fila de controlos (a seguir)
     ficava a 16px do topo, «perdida» — encosta-se ao canto. */
  .up-sidebar { padding-top:2px }
}

/* ══════════════════ OS TRÊS CONTROLOS (item 7/8): painel · atrás · à frente ══════════════════
   Como no Claude Desktop, pedido dele — e SEMPRE VISÍVEIS (ao contrário do chevron antigo): atrás
   e à frente são navegação, e navegação que só aparece com o rato é navegação que não se encontra.
   No RAIL fica só o painel a fazer sentido sozinho, mas os três CABEM nos 68px (20px cada) e
   NENHUM se esconde — é a mesma regra do resto da barra: nada desaparece ao colapsar.

   ENCOSTADOS AO CANTO e não alinhados com a coluna dos ícones dos módulos: são MOLDURA e não
   conteúdo, e alinhar coisas de naturezas diferentes lê-se como se fossem a mesma — *«não alinhes
   com o texto, encosta mesmo, para ficar como se fosse tipo pinned»*. As margens negativas cancelam
   a goteira da barra (14px). */
.up-nav-controlos { display:flex; align-items:center; gap:1px; margin:0 0 8px -10px; flex:none }
.up-nav-bt { width:20px; height:20px; padding:0; display:grid; place-items:center; flex:none;
  border:0; border-radius:6px; background:none; color:var(--up-ink); cursor:pointer;
  opacity:.42; transition:background .1s ease, opacity .1s ease }
.up-nav-bt svg { width:14px; height:14px }
.up-nav-bt:hover { background:var(--up-bg); opacity:.95 }
.up-nav-bt:focus-visible { outline:2px solid var(--up-accent); outline-offset:1px }

/* ══════════════════ O CABEÇALHO DE UMA ZONA COM ACÇÕES (item 7/11): os TRÊS PONTOS ══════════════
   *«os 3 pontos costumam ser na vertical na verdade»* — o `⋮` e não o `⋯`: o horizontal já
   significa «continua/truncado» nesta aplicação (as células cortadas da grelha), e o mesmo glifo
   com dois significados no mesmo ecrã obriga a olhar para o contexto antes de o ler.
   ⚠️ O cabeçalho da zona é um `<button>` — é ele que abre e fecha o acordeão — portanto os pontos
   NÃO PODEM estar dentro dele (um botão dentro doutro é HTML inválido, e o clique ia para o
   acordeão). Ficam ao lado, com o par embrulhado numa linha: o `.up-nav-cab` absorve o `padding`
   horizontal que o `.up-nav-area` tinha, para os dois ficarem à mesma altura por construção. */
.up-nav-cab { display:flex; align-items:center; padding:var(--up-sp-4) 0 var(--up-sp-1) }
.up-nav-cab > .up-nav-area { flex:1 1 auto; min-width:0; padding:0 }
/* VISÍVEIS, e não só no `:hover` — um controlo que só aparece com o rato é um controlo que
   ninguém descobre, e num rail colapsado não há rato nenhum para lá passar. Discretos pela
   opacidade e não pela ausência. */
.up-nav-mais { position:relative; flex:none }
.up-nav-mais-bt { width:18px; height:22px; padding:0; border:0; background:none; cursor:pointer;
  color:var(--up-ink); border-radius:5px; font-size:15px; line-height:1; opacity:.45;
  transition:opacity .1s ease, background .1s ease }
.up-nav-area-bloco:hover .up-nav-mais-bt { opacity:.7 }
.up-nav-mais-bt:hover, .up-nav-mais-bt:focus-visible { opacity:1; background:var(--up-bg) }
.up-nav-menu { position:absolute; top:calc(100% + 4px); right:0; z-index:50;
  min-width:190px; padding:4px; border-radius:9px;
  background:var(--up-white); border:1px solid var(--up-line); box-shadow:var(--up-shadow-lg) }
.up-nav-menu[hidden] { display:none }
.up-nav-menu-item { display:flex; align-items:center; gap:8px; width:100%; white-space:nowrap;
  padding:7px 9px; border-radius:6px; text-decoration:none; color:var(--up-ink);
  font-size:12.5px; border:0; background:none; text-align:left; font-family:inherit;
  cursor:pointer }
.up-nav-menu-item:hover { background:var(--up-bg) }
.up-nav-menu-item:disabled { opacity:.5; cursor:default }
.up-nav-menu-item:disabled:hover { background:none }
/* Alfabético/Recente (item 5, desenho clean): duas OPÇÕES que se escolhem, não duas acções — o
   `.on` marca a activa (cor+peso, como qualquer entrada activa da barra) e o `.up-nav-menu-v` é a
   coluna fixa do visto, sempre com a MESMA largura esteja ou não marcada, para o texto das duas
   opções começar alinhado. */
.up-nav-menu-item.on { color:var(--up-teal-700); font-weight:600 }
.up-nav-menu-v { width:16px; flex:none; display:inline-flex; align-items:center; justify-content:center }
.up-nav-menu-v .up-i, .up-nav-menu-v svg { width:14px; height:14px; color:var(--up-teal) }

/* O «ALL PROJETIX» à cabeça da zona (item 13): mais DISCRETO do que a lista e com um traço a
   separá-lo — é a saída para todos, não um item, e um atalho com mais peso do que o conteúdo
   rouba-lhe a atenção. */
.up-nav-todos > .cab { font-size:11.5px; opacity:.6; letter-spacing:.01em }
.up-nav-todos > .cab:hover { opacity:1 }
.up-nav-todos { border-bottom:1px solid var(--up-line); margin-bottom:4px; padding-bottom:3px }

/* ══════════════════ O RAIL (item 8): A MESMA BARRA, SEM RÓTULOS ══════════════════
   🔴 **Regra única, dada três vezes:** *«isto não pode ser»* · *«tás a mudar as coisas de
   sítio»* · **«tem de tudo ficar no mesmo sítio quando colapsa»**. Nada sai do fluxo, nada muda
   de posição — um Projetix aberto continua aberto no rail, com as apps dele como ícones. É o que o
   `test_o_rail_nao_move_icones.py` (bateria de integração) mede: 0px de desvio por ícone.

   O que fica invisível é só a COR do texto — os rótulos vivem agora num `<span class="up-lbl">`
   (item 9), e é isso que torna isto possível: sem o `<span>` eram nós de texto soltos, sem
   selector CSS nenhum, e a única maneira de os apagar era `color:transparent` no PAI (que também
   apagava o ícone, obrigando a repor a cor dele à parte). Com o `<span>` esconde-se só o que é
   texto — `visibility:hidden` e não `display:none`, para o espaço ficar reservado e a coluna não
   saltar.

   **68px e não 64/60**: os ícones centram todos em 34 (a goteira de 14 mais o padding da linha),
   e a moldura tem de ser `2 × 34` para os centrar SEM deslocamento — mover os ícones para o
   centro do rail está proibido pela regra de cima; quem se ajusta é a CAIXA. */
:root { --up-sidebar-rail:68px }
body.up-colapsada { --up-sidebar:var(--up-sidebar-rail) }
@media (min-width:901px) {
  body.up-colapsada .up-sidebar {
    width:var(--up-sidebar); overflow-x:hidden;
    transition:width .16s ease .26s, box-shadow .16s ease .26s }
  body.up-colapsada .up-sidebar > *,
  body.up-colapsada .up-sidebar .up-nav,
  body.up-colapsada .up-sidebar .up-nav-area-corpo {
    width:196px; min-width:196px; box-sizing:border-box }
  body.up-colapsada .up-sidebar:hover,
  body.up-colapsada .up-sidebar:focus-within {
    width:224px; box-shadow:0 12px 32px rgba(17,20,24,.14); transition-delay:.20s, .20s }
  /* 🔴 **COLAPSAR NÃO SE VIA, e não era o JS nem o CSS a faltar** — relatado por ele a
     2026-09-11: *«o botão de colapsar a barra deixou de funcionar»*.

     O botão vive DENTRO da barra (`.up-nav-controlos`, no `nav.html`). Portanto, no instante em
     que se carrega nele, o rato está sobre a barra — e a regra mesmo acima devolve-lhe os 224px.
     A classe entra, o rail acontece, e a largura é a mesma: colapsa e reabre no mesmo gesto.

     ⚠️ **O mecanismo funcionava; o que não funcionava era a MEDIÇÃO** — o olho de quem carrega não
     distingue «não aconteceu» de «aconteceu e desfez-se». É a mesma família do verde pela razão
     errada, do lado do interface.

     O JS marca `up-sem-abrir` ao colapsar e tira-a quando o rato SAI da barra (ver o
     `nav_scripts.html`): a expansão por hover fica suspensa até haver um hover novo, de verdade. */
  body.up-colapsada.up-sem-abrir .up-sidebar:hover,
  body.up-colapsada.up-sem-abrir .up-sidebar:focus-within {
    width:var(--up-sidebar); box-shadow:none }
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-lbl,
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-tag,
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-nav-etiqueta {
    visibility:hidden }
  /* A linha do TIPO (item 6, desenho clean de 2026-09-11) é prosa como o `.up-lbl` acima — sem
     ela escondida, um rail de 68px mostrava «INSTITUTION» a fugir para fora da caixa, cortado a
     meio de uma maiúscula. Regra PRÓPRIA e não juntada à lista de cima: o
     `test_o_rail_esconde_o_ROTULO_e_nao_o_ICONE` procura aquele bloco pelo texto exacto, e
     misturar-lhe uma classe nova partia-o sem lhe tocar uma vírgula no que ele verifica. */
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-nav-tipo {
    visibility:hidden }
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-tag {
    background:transparent; border-color:transparent }
  /* A seta e os três pontos não se leem num rail de 68px; o painel/atrás/à frente FICAM (regra de
     cima: nada desaparece ao colapsar). */
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .seta,
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-nav-mais {
    visibility:hidden }
  /* O PONTO dos filhos (item 1) vai com eles: sem o `up-lbl` ao lado, um ponto sozinho não diz
     nada — e um `.up-mod-sub` só é visível no rail se já estivesse `.aberto` antes de colapsar (a
     regra de cima: nada MUDA ao colapsar, incluindo o que já estava aberto). Regra própria pela
     MESMA razão da `.up-nav-tipo`, acima: o `test_os_controlos_ficam_visiveis_no_rail` procura o
     bloco `.seta,.up-nav-mais {` por texto exacto. */
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-mod-sub a::before {
    visibility:hidden }
  /* A cor volta só depois de a largura assentar, e desaparece antes de ela mexer — senão há um
     quadro com a barra a meio caminho e o texto cortado. */
  body.up-colapsada .up-sidebar:not(:hover):not(:focus-within) .up-lbl { transition:none }
  body.up-colapsada .up-sidebar:hover .up-lbl,
  body.up-colapsada .up-sidebar:focus-within .up-lbl { transition:color .06s linear .34s }
}
@media (prefers-reduced-motion:reduce) { body.up-colapsada .up-sidebar { transition:none } }

/* 5. ------------------------------------------------------------- Botões */

/* Um `<button>` a que ninguém pôs classe nenhuma.
 *
 * **A convenção era escrever sempre `.up-btn`, e a convenção perdeu.** A 2026-08-12 havia quatro
 * botões nus na árvore (`projetix` ×3, `module-trips` ×1) mais dois com uma classe que não tem CSS
 * em sítio nenhum. Sem esta regra ficam com o botão por omissão do browser: cinzento médio, cantos
 * direitos, um corpo estranho no meio da página — e pior no tema escuro, onde nem sequer é a
 * mesma família de cinzentos. Foi o que o Bruno viu ("ainda há botões com as cores erradas").
 * Uma convenção que depende de toda a gente se lembrar falha uma vez por mês, para sempre.
 *
 * ⚠️ **Duas coisas a mais no selector, e cada uma custou uma medição.**
 *
 * **O `:not([class])`.** A primeira versão era `button { … }`. O receio da issue #154 — que o
 * preto do primário escorresse para o `.up-switch` e o `.up-pager` — não se confirmou: esses
 * declaram fundo próprio. O que se confirmou foi outra coisa, que ninguém tinha levantado: **os
 * botões redondos dos módulos não declaram `padding`**. O `.docx` do `trips` é um círculo de
 * 20×20 e passava a um oval de 30×20; o `.xdel` ia de 21×18 para 39×34. Medido com a página
 * aberta, nos dois temas.
 *
 * **O `:where()`, que é o que impede isto de estragar o comutador de módulos.** Um
 * `button:not([class])` pesa (0,1,1) — o `:not()` conta a especificidade do que leva dentro. É
 * EXACTAMENTE o mesmo peso que `.up-switch > button`, e aí decide a ordem no ficheiro: o
 * comutador está na linha 248 e isto na 451, portanto isto ganhava-lhe. O `.up-pager button`
 * escapava por acaso, só por estar na linha 735.
 *
 * "Por acaso" e "por ordem no ficheiro" não são garantias: alguém reorganiza as secções e o
 * defeito nasce sem ninguém lhe ter tocado. O `:where()` não conta para a especificidade, logo
 * isto pesa (0,0,1) — **perde para qualquer regra com uma classe, esteja ela onde estiver.**
 *
 * E o `:not` continua a fazer falta ao lado dele: sem ele, isto aplicava-se a TODOS os botões e
 * só a especificidade os salvava, propriedade a propriedade. Com ele, os que têm classe própria
 * nem sequer entram na conversa.
 *
 * Não tem `font-weight` de propósito: o `.up-pager button` também não o declara.
 *
 * Isto NÃO substitui o `.up-btn`. É o chão por baixo de quem se esqueceu — um botão a sério
 * continua a escrever-se com classe. */
button:where(:not([class])) { font:inherit; font-size:var(--up-fs-md); padding:9px 15px;
  border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  background:var(--up-white); color:var(--up-ink); cursor:pointer;
  transition:var(--up-transition) }
button:where(:not([class])):hover { background:var(--up-faint) }
button:where(:not([class])):disabled { opacity:.55; cursor:default }

.up-btn { display:inline-flex; align-items:center; gap:var(--up-sp-2);
  border:1px solid var(--up-btn-bg); background:var(--up-btn-bg); color:var(--up-btn-fg);
  font:inherit; font-size:var(--up-fs-md); font-weight:500; padding:9px 15px;
  border-radius:var(--up-radius-sm); cursor:pointer; transition:var(--up-transition);
  text-decoration:none; white-space:nowrap }
.up-btn:hover { background:var(--up-btn-bg-hover); border-color:var(--up-btn-bg-hover) }
.up-btn.ghost { background:var(--up-white); color:var(--up-ink); border-color:var(--up-line) }
.up-btn.ghost:hover { background:var(--up-faint) }
.up-btn.danger { background:var(--up-err); border-color:var(--up-err) }
/* **A cor de um botão diz o RISCO da acção. Nunca o resultado dela.** (issue projetix#160)
 *
 * Por isso aqui só há três: o principal, o `.ghost` para o que é reversível, e o `.danger` para
 * o que não é. O verde e o teal saíram — o `.up-btn.ok` e o `.up-btn.accent` **já não existem**.
 *
 * O verde vive no `.up-tag.ok`, que **não é clicável**: diz *"correu bem"*, e um botão nunca sabe
 * isso — quando ele está a ser desenhado a acção ainda não aconteceu.
 *
 * A pergunta que decidiu isto foi: *se o verde quer dizer "faz isto", com que cor se diz "correu
 * bem"?* Não havia resposta, porque a cor estava a ser gasta duas vezes. O `/admin/backups` era a
 * página onde as duas se encontravam: um `Test connection and save` verde, e o resultado do teste
 * a precisar da mesma cor.
 *
 * ⚠️ **Tirar isto não parte nada, e isso é uma propriedade e não sorte.** Um template que ainda
 * diga `up-btn ok` renderiza como `up-btn` — que é exactamente o alvo. O `.ok` nem sequer tinha
 * `:hover` próprio, portanto o comportamento fica idêntico ao do principal. Por isso a ordem em
 * relação aos outros repositórios não interessa: não há janela em que alguma coisa fique feia.
 *
 * Contado antes de tirar: o `.accent` **não tinha uso nenhum** em toda a árvore fora do
 * styleguide, e o `.ok` tinha oito, todos a querer dizer *"esta é a acção afirmativa principal"*
 * — `Save`, `Sign in`, `Accept`, `Submit`. Ou seja, todos a pedir o `up-btn` simples. */
.up-btn.sm { padding:5px 10px; font-size:var(--up-fs-sm) }
.up-btn:disabled, .up-btn[disabled] { opacity:.45; cursor:not-allowed }
.up-btn .up-i { width:16px; height:16px }

/* 6. --------------------------------------------------- Cartões e grelhas */
.up-card { background:var(--up-surface); border:1px solid var(--up-line);
  border-radius:var(--up-radius); padding:18px 20px; box-shadow:var(--up-shadow) }
.up-card + .up-card { margin-top:var(--up-sp-4) }
.up-card-head { display:flex; align-items:center; justify-content:space-between;
  gap:var(--up-sp-3); margin-bottom:var(--up-sp-3) }
.up-card.flush { padding:0 }          /* para cartões que só têm uma tabela dentro */
.up-card.flush .up-card-head { padding:16px 20px 0 }

.up-grid { display:grid; gap:var(--up-gap);
  grid-template-columns:repeat(auto-fill, minmax(240px, 1fr)) }
.up-grid.sm { grid-template-columns:repeat(auto-fit, minmax(150px, 1fr)) }  /* KPIs */
.up-row { display:flex; gap:var(--up-sp-3); align-items:center; flex-wrap:wrap }
/* Campos dentro de uma linha partilham o espaço em vez de ocuparem a largura toda — a mesma
   história da `.up-table-filters` (issue #46), aqui para a barra de filtros feita à mão. */
.up-row > .up-input, .up-row > .up-select { width:auto; min-width:140px; flex:1 1 140px }

/* Pastilha clicável — um atalho curto, ao contrário da `.up-tag`, que é só um rótulo.
   Serve em `<a>` e em `<button>`: daí o `font:inherit` e o `cursor`. */
.up-chip { display:inline-flex; align-items:center; gap:6px; padding:6px 12px;
  border:1px solid var(--up-line); border-radius:var(--up-radius-pill);
  background:var(--up-white); font:inherit; font-size:var(--up-fs-sm); color:var(--up-ink);
  text-decoration:none; cursor:pointer; transition:var(--up-transition); white-space:nowrap }
.up-chip:hover:not(:disabled) { border-color:var(--up-teal); background:var(--up-teal-bg);
  color:var(--up-teal-700) }
.up-chip:disabled { opacity:.45; cursor:not-allowed }
/* A pastilha que está ESCOLHIDA — uma lista de estados onde um deles é o de agora. Sem isto,
   cinco pastilhas iguais não dizem em qual delas a coisa está. */
.up-chip.is-on { border-color:var(--up-teal); background:var(--up-teal-bg);
  color:var(--up-teal-700); font-weight:600 }
.up-chip.danger:hover:not(:disabled) { border-color:var(--up-err); background:var(--up-err-bg);
  color:var(--up-err) }
.up-chip .up-i { width:15px; height:15px; color:var(--up-grey-500) }
.up-chip:hover .up-i { color:var(--up-teal) }
/* Uma FILA de pastilhas — um filtro de escolha múltipla, onde cada uma liga e desliga a sua.
   Existe porque um `<div>` de pastilhas é um bloco: sem isto elas passavam a linha em vez de
   ficarem lado a lado, e cada módulo resolvia-o com um `style=` seu (que é o que este ficheiro
   existe para evitar). O `wrap` é para uma barra estreita não empurrar a pesquisa para fora. */
.up-chips { display:inline-flex; align-items:center; gap:6px; flex-wrap:wrap }

/* 6.1 ------------------------------------------------------------ Lançador */
/* A página inicial da plataforma: a saudação, o filtro e a grelha de aplicações.
   Fica aqui, no tema, porque um módulo principal também vai querer uma entrada
   assim — e porque a página não escreve CSS nenhum. */

.up-hero { display:flex; align-items:flex-end; justify-content:space-between;
  gap:var(--up-sp-4); flex-wrap:wrap; margin-bottom:var(--up-sp-5) }
.up-hero h1 { font-size:26px; font-weight:650; letter-spacing:-.2px; margin:0 0 2px }
.up-hero p { margin:0 }

/* Filtro. O atalho `/` está desenhado na tecla à direita — quem não o vir, escreve na mesma. */
.up-find { position:relative; flex:0 1 300px; min-width:220px }
.up-find .up-i { position:absolute; left:11px; top:50%; transform:translateY(-50%);
  color:var(--up-grey-500); pointer-events:none }
.up-find .up-input { padding-left:34px; padding-right:34px }
.up-kbd { position:absolute; right:9px; top:50%; transform:translateY(-50%);
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:11px;
  color:var(--up-muted); background:var(--up-grey-100); border:1px solid var(--up-line);
  border-radius:5px; padding:1px 6px; pointer-events:none }
.up-find .up-input:focus ~ .up-kbd { opacity:0 }

.up-apps { display:grid; gap:var(--up-gap);
  grid-template-columns:repeat(auto-fill, minmax(290px, 1fr)) }

.up-appcard { display:flex; flex-direction:column; background:var(--up-surface);
  border:1px solid var(--up-line); border-radius:var(--up-radius-lg);
  box-shadow:var(--up-shadow); overflow:hidden; transition:var(--up-transition) }
.up-appcard:hover { border-color:var(--up-grey-300); box-shadow:var(--up-shadow-lg);
  transform:translateY(-2px) }
.up-appcard > .cab { display:flex; align-items:flex-start; gap:var(--up-sp-3);
  padding:18px 20px 14px; text-decoration:none; color:inherit }
.up-appcard > .cab .t { min-width:0; flex:1 }
.up-appcard > .cab .nm { display:block; font-weight:650; font-size:var(--up-fs-lg);
  line-height:1.3 }
.up-appcard > .cab p { margin:3px 0 0 }
.up-appcard > .cab .seta { color:var(--up-grey-300); font-size:18px; line-height:1;
  transition:var(--up-transition) }
.up-appcard:hover > .cab .seta { color:var(--up-teal); transform:translateX(2px) }

/* ── O LADRILHO DE UMA MARCA (`.up-marca`) ───────────────────────────────────────────────
   🔴 Um logo de serviço, e não um ícone da casa. A diferença que o CSS tem de respeitar: o
   `.up-tile` pinta o FUNDO com a cor da casa e deixa o ícone herdar a cor do texto; aqui o
   fundo é **neutro** e o que está dentro traz as cores próprias.

   ⚠️ **O fundo é pintado e não herdado, e isso é o que faz uma marca clara sobreviver ao tema
   escuro.** Uma marca quase toda branca — o `m-teams`, e qualquer futura — desaparecia num
   fundo escuro herdado. O `--up-white` é literal de propósito: é a superfície sobre a qual os
   logos das marcas são desenhados por quem os desenha.

   ⚠️ **E não leva `overflow:hidden`.** Os cantos do `m-teams` e do `m-revolut` já vêm
   arredondados no próprio símbolo (um `rect` com `rx`), portanto cortar aqui outra vez dava
   dois raios diferentes sobrepostos — visível como um canto sujo, e só nesses dois. */
.up-marca { width:42px; height:42px; border-radius:12px; flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--up-white); border:1px solid var(--up-line) }
.up-marca-i { width:22px; height:22px }

/* ── A MARCA PEQUENA (`.up-marca.pequeno`) ───────────────────────────────────────────────
   O mesmo logo à altura de uma linha de texto, para uma lista onde ele identifica a linha em
   vez de encabeçar um cartão. ⚠️ **Não é o `.up-marca` com `transform:scale()`**: escalar
   também escala a borda e o raio, e a 20px isso dá um contorno grosso e um canto errado. */
/* ⚠️ **Esta regra esteve declarada DUAS vezes** (a 0.66.0 saiu assim, e foi minha): uma a 20px
   aqui e outra a 24px mais abaixo, com a de baixo a ganhar. Juntas: fica o que já corria —
   **24px** — mais o `vertical-align`, que só existia na de cima e continuava a aplicar-se.

   🔴 **E o `test_uma_classe_nao_se_declara_duas_vezes` não o apanhou por desenho:** ele só conta
   selectores de UMA classe sozinha e exclui `.x.y`, porque um modificador é uma declaração
   legítima da mesma classe. É verdade — mas **dois `.x.y`** são o mesmo defeito das duas
   definições concorrentes. O check passou a olhar para os compostos também. */

/* ── O `?` QUE EXPLICA (`.up-porque`) ────────────────────────────────────────────────────
   🔴 **É só o GATILHO; o cartão que abre é o `.up-dica` que já existe** — com o `:hover` e o
   `:focus-within` dele, e o `tabindex="0"` a pôr o gatilho no caminho do `Tab`.

       <span class="up-dica" tabindex="0"><span class="up-porque">?</span>
         <span class="up-dica-card"><p class="titulo">…</p>…</span></span>

   Pedido dele a 2026-09-10: *«se calhar convém ter uma explicação, meter um ponto de
   interrogação ao pé, para passarmos o rato e explicar, tipo um manual de utilização»*.

   ⚠️ **Um por CAIXA, e não um por campo.** A primeira tentativa pôs um em cada campo e em cada
   botão, e ele recusou-a: *«ficou confuso, mete só o ponto de interrogação nas caixas q
   precisam»*. Nove gatilhos numa página são nove coisas a pedir atenção; um por secção é uma
   por assunto — e o cartão dela tem espaço para explicar os campos e os botões todos de uma
   vez, que é o que um manual faz. */
.up-porque { display:inline-flex; align-items:center; justify-content:center;
  width:15px; height:15px; border-radius:50%; flex:none; vertical-align:1px;
  border:1px solid var(--up-line); background:var(--up-surface);
  color:var(--up-muted); font-size:10px; font-weight:700; line-height:1 }
.up-dica:hover > .up-porque, .up-dica:focus-within > .up-porque {
  border-color:var(--up-teal); color:var(--up-teal); background:var(--up-teal-bg) }

/* ── O CARTÃO DE UM CONECTOR (`.up-conector`) ────────────────────────────────────────────
   🔴 **A quinta forma da lista de `/my/connectors`, e a primeira que ele aprovou** («tá
   óptimo», 2026-09-10). As quatro anteriores estão escritas no `connectors.html`; o que esta
   tem de diferente é a REGRA:

       só o CONECTOR se abre, e as contas dele estão escritas por dentro — sem link.

   Palavras dele, e é a razão de o cartão inteiro ser **um** `<a>`: *«só o conector é q devia
   aparecer aqui em grande, e um resumo do q tá dentro mas sem ser uma tabela, sem podermos ir
   a cada um dos q tá dentro, temos de abrir sempre a página do conector»*.

   ⚠️ **Uma tabela aqui foi tentada e recusada** — e a razão não é gosto: com vários conectores
   do mesmo tipo, as linhas das contas ficavam irmãs das linhas dos conectores, e filtrar a
   lista desfazia o agrupamento (é a mesma armadilha que o `Ligacao.credencial` já documenta).
   Num cartão, o que está dentro **não pode** mudar de vizinho.

   ⚠️ **As contas escrevem-se TODAS.** Houve uma versão a truncar às três com um «+ N mais», e
   ele preferiu o contrário: *«não metas os + 4, mete mesmo todas prefiro»*. Quem ordena põe as
   que estão MAL primeiro (é o `connectors.py` que ordena, não o CSS) — cortar pela ordem de
   entrada escondia exactamente aquilo que o cartão existe para mostrar. */
/* ⚠️ **A folga vem do bloco de CIMA e não do cartão.** Entre dois conectores há o
   `margin-bottom` de cada um; entre o cabeçalho da lista e o primeiro não havia nada, e ficavam
   colados — ele viu-o num print. Dar `margin-top` ao `.up-conector` era a saída errada: duplicava
   a folga entre dois conectores seguidos. */
.up-card + .up-conector { margin-top:var(--up-sp-4) }
.up-conector { display:block; text-decoration:none; color:inherit; margin-bottom:var(--up-sp-3);
  background:var(--up-surface); border:1px solid var(--up-line);
  border-radius:var(--up-radius-lg); box-shadow:var(--up-shadow);
  transition:var(--up-transition) }
.up-conector:hover { border-color:var(--up-teal); box-shadow:var(--up-shadow-lg) }
.up-conector > .topo { display:flex; align-items:center; gap:var(--up-sp-3);
  padding:16px 18px 12px }
.up-conector > .topo > .t { min-width:0; flex:1 }
/* O TIPO por cima do nome, e não ao lado: pode haver vários conectores do mesmo tipo
   (*«pode haver varios do mesmo tipo»*), portanto o tipo é contexto e o nome é a identidade. */
.up-conector .tipo { font-size:10px; letter-spacing:.09em; text-transform:uppercase;
  color:var(--up-muted); margin-bottom:3px }
.up-conector .tipo .up-marca.pequeno { margin-right:5px }
.up-conector .nm { display:block; margin:0; font-size:19px; font-weight:650; line-height:1.2 }
.up-conector .est { white-space:nowrap }
.up-conector > .topo .seta { color:var(--up-grey-300); font-size:20px; line-height:1; flex:none;
  transition:var(--up-transition) }
.up-conector:hover > .topo .seta { color:var(--up-teal); transform:translateX(2px) }
/* AS CONTAS, em surdina: sem moldura, sem hover, sem link — *«mas de forma mais discreta»*.
   O ponto de cor é o estado, e é ele que faz uma conta parada saltar à vista num cartão de
   sete linhas sem precisar de uma etiqueta por linha. */
.up-conector .dentro { list-style:none; margin:10px 18px 0; padding:0 }
.up-conector .dentro li { display:flex; align-items:baseline; gap:var(--up-sp-2);
  padding:3px 0; font-size:var(--up-fs-sm) }
.up-conector .dentro .p { width:6px; height:6px; border-radius:50%; flex:none;
  background:var(--up-grey-300); transform:translateY(-1px) }
.up-conector .dentro .p.ok { background:var(--up-ok) }
.up-conector .dentro .p.warn { background:var(--up-warn) }
.up-conector .dentro .p.err { background:var(--up-err) }
.up-conector .dentro .nm2 { color:var(--up-ink); flex:none; font-weight:500 }
.up-conector .dentro .en { color:var(--up-muted); min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.up-conector .dentro .no { margin-left:auto; color:var(--up-muted); flex:none;
  padding-left:var(--up-sp-3); text-align:right }
.up-conector .dentro .no.err { color:var(--up-err) }
.up-conector .dentro .no.warn { color:var(--up-warn) }
.up-conector .dentro .vazio { color:var(--up-muted); font-style:italic }
/* O PÉ é o que identifica a instância — a organização, o servidor, com quem está partilhada.
   Está aqui e não no nome porque é isto que distingue dois conectores do mesmo tipo. */
.up-conector > .pe { margin-top:12px; padding:9px 18px;
  border-top:1px solid var(--up-line); background:var(--up-faint);
  border-radius:0 0 var(--up-radius-lg) var(--up-radius-lg);
  font-size:var(--up-fs-xs); color:var(--up-muted) }
.up-conector > .pe .sep { opacity:.5; padding:0 3px }

/* ── A LINHA ALTA DE UM ALCANÇÁVEL (`.up-alcanca`) ───────────────────────────────────────
   Uma coisa que a credencial vê do outro lado (um grupo, uma caixa partilhada, uma team) com
   as permissões DELA na própria linha — pedido dele a 2026-09-10: *«acho q era melhor tar aqui
   as permissões todas, talvez fazer uma linha maior e poder incluir isso tudo?»*.

   ⚠️ **Saiu de uma tabela de propósito.** Uma célula cheia de chips dá colunas de larguras
   absurdas — a coluna adapta-se ao caso com mais chips e as outras linhas ficam vazias à
   direita.

   🔴 **E o que vai nos chips são PROJETIX e nunca pessoas.** Decisão dele de 2026-09-10:
   *«serve o projetix como tava antes»*. Uma caixa dada a uma pessoa era um segundo eixo de
   autorização, e «quem pode ler esta caixa?» passava a ter duas respostas em dois sítios. */
.up-alcanca { border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  background:var(--up-surface); margin-bottom:var(--up-sp-2) }
.up-alcanca.por-ligar { background:var(--up-faint); border-style:dashed }
.up-alcanca > .cab { display:flex; align-items:center; gap:var(--up-sp-3); padding:11px 14px }
.up-alcanca > .cab > .t { min-width:0; flex:1; display:flex; align-items:baseline;
  gap:var(--up-sp-2); flex-wrap:wrap }
.up-alcanca > .cab .ac { margin-left:auto; white-space:nowrap }
.up-alcanca > .perm { border-top:1px solid var(--up-line); padding:8px 14px 11px;
  display:flex; flex-direction:column; gap:7px }
.up-alcanca.por-ligar > .perm { border-top-style:dashed }
.up-alcanca .lin { display:flex; align-items:center; gap:var(--up-sp-3); flex-wrap:wrap }
.up-alcanca .rot { font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--up-muted); width:132px; flex:none }
/* A fila «Lê» de um alcançável, e as acções no cabeçalho dele — as duas vieram da PÁGINA DE UMA
   CAIXA, que desapareceu porque não tinha nada que o conector não tivesse (foi ele que o viu). */
.up-alcanca .up-le { font-size:var(--up-fs-sm) }
.up-alcanca .up-le-q { margin-left:auto }
.up-alcanca > .cab .ac { display:flex; align-items:center; gap:6px; font-size:var(--up-fs-sm) }
.up-alcanca > .cab .ac a.perigo { color:var(--up-err) }
/* O separador entre acções: o ÚLTIMO esconde-se, e é o que evita um `·` pendurado no fim. Um
   `:not(:last-child)` no `<a>` não servia — o que sobra é o `<span>` depois dele. */
.up-sep-i { color:var(--up-line) }
/* As formas de ACRESCENTAR à lista, no fim da caixa que elas enchem — separadas do que já lá
   está por um traço, porque são de outra natureza: aquilo é o que existe, isto é o que se faz. */
.up-acrescentar { margin-top:var(--up-sp-4); padding-top:var(--up-sp-3);
  border-top:1px solid var(--up-line) }
/* ── UM BOTÃO QUE PARECE UM LINK (`.up-linkbt`) ──────────────────────────────────────────
   🔴 **Existe para as acções que ESCREVEM.** Um `POST` não se escreve num `<a href>` — já falhou
   três vezes nesta casa, duas relatadas no mesmo dia — portanto a plataforma desenha um
   `<button>` dentro de um formulário com o token. Mas o sítio onde ele aparece é uma linha de
   texto, e um botão com moldura ali dentro pesava como se fosse a acção principal.

   ⚠️ **`font:inherit` e não uma família nova**: um `<button>` traz a fonte do sistema, e sem
   isto a acção saía com outra letra do que o texto ao lado — visível, e sempre nos mesmos
   sítios. */
.up-linkbt { background:none; border:0; padding:0; margin:0; font:inherit; color:var(--up-teal);
  cursor:pointer; text-decoration:none }
.up-linkbt:hover { text-decoration:underline }
.up-linkbt.perigo { color:var(--up-err) }
/* Com a classe de botão a sério, volta a ser um botão — é o caso das acções de uma credencial.

   🔴 **FALTAVA O FUNDO, e é o defeito medido a 2026-09-11: o «Sync» (`connector.html`,
   `accoes_alcanca`) saía CINZENTO e os `.ghost` ao lado saíam bem.** A causa é a cascata, não o
   selector: o `.up-linkbt` (0,1,0), 283 linhas ABAIXO do `.up-btn` (0,1,0) neste ficheiro, dá
   `background:none; border:0` — MESMA especificidade, e quem vem depois no ficheiro ganha. Esta
   regra só corrigia a COR (branco); o fundo continuava `none`, e texto branco sem fundo por baixo
   é o fundo da PÁGINA a aparecer — lido como «cinzento», com o texto ilegível por cima dele.

   ⚠️ **`:not(.ghost)`, e não uma segunda regra a repetir o fundo branco do `.ghost`.** O
   `.up-btn.ghost` (0,2,0) já dá fundo e borda próprios; declará-los aqui também só voltava a pôr
   dois sítios a decidir o MESMO fundo por ordem no ficheiro — a fragilidade que causou isto.
   Excluindo o `.ghost` deste selector, ele nem entra na conversa: quem pinta um
   `.up-linkbt.up-btn.ghost` continua a ser só o `.up-btn.ghost`, tal como estava.

   ⚠️ **SEM `padding` aqui.** O Sync usa `up-btn sm` (`.up-btn.sm`, 0,2,0, dá `5px 10px`); uma
   regra de padding neste selector seria (0,3,0) — MAIS específica do que o `.up-btn.sm` — e
   ganhava-lhe: o Sync perdia o `.sm` e voltava ao padding do botão normal. É o MESMO mecanismo do
   defeito, ao contrário: uma guarda nova a tirar uma antiga (`theme/CLAUDE.md`).

   ⚠️ **E o `:hover` tem regra própria.** Sem ela, o fundo constante desta regra (0,3,0) ganhava
   também ao `.up-btn:hover` (0,2,0) — o botão deixava de escurecer ao passar o rato, sempre,
   porque uma regra sem `:hover` no selector continua a aplicar-se DURANTE o hover.

   ⚠️ **A cor mudou de `--up-on-accent` para `--up-btn-fg`.** O `--up-on-accent` é o par do
   `--up-teal` (texto por cima do ACENTO) — sobrou de quando existia um `.up-btn.accent`; esse já
   não existe (ver a nota do `.up-btn.sm`, acima), e o fundo aqui é `--up-btn-bg` (o botão PRETO),
   cujo par declarado é o `--up-btn-fg`. Nos dois temas os valores calham perto (ambos brancos no
   claro), mas o token errado é o que a regra "declarar o PAR" deste repositório existe para
   apanhar — mesmo quando o efeito visual de hoje é parecido. */
.up-linkbt.up-btn:not(.ghost) { background:var(--up-btn-bg); border:1px solid var(--up-btn-bg);
  color:var(--up-btn-fg) }
.up-linkbt.up-btn:not(.ghost):hover { background:var(--up-btn-bg-hover); border-color:var(--up-btn-bg-hover) }
.up-linkbt.up-btn.ghost { color:var(--up-ink) }
.up-alcanca > .cab .ac > .up-sep-i:last-child { display:none }

/* ── O CHIP DE DADOS (`.up-chip.dado`) ───────────────────────────────────────────────────
   O `.up-chip` é um CONTROLO (um filtro que se clica). Este é o contrário: mostra uma coisa
   que já está dada, e a única parte clicável dele é o `×` que a tira. Por isso não tem
   `cursor:pointer` no corpo — um chip inteiro clicável prometia uma acção que não existe. */
.up-chip.dado { padding:3px 8px; font-size:var(--up-fs-xs); cursor:default;
  border-color:var(--up-teal); background:var(--up-teal-bg); color:var(--up-teal-700) }
.up-chip.dado .dir { opacity:.8; border-left:1px solid var(--up-teal);
  padding-left:6px; margin-left:2px }
.up-chip.dado .x { color:var(--up-muted); cursor:pointer; margin-left:2px; font-size:13px;
  text-decoration:none }
.up-chip.dado .x:hover { color:var(--up-err) }
/* Numa lista ou numa linha de tabela, a marca é pequena e sem moldura: ela está a identificar
   uma linha e não a ser o assunto dela. */
.up-marca.pequeno { width:24px; height:24px; border-radius:6px; vertical-align:-6px }
.up-marca.pequeno .up-marca-i { width:15px; height:15px }
.up-marca-i.pequeno { width:15px; height:15px }

/* Ladrilho do ícone. Duas cores só, as da marca: alternam por módulo para a grelha
   não ser uma parede de teal, e mantêm-se estáveis (a escolha vem do slug). */
.up-tile { width:42px; height:42px; border-radius:12px; flex:none;
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--up-teal-bg); color:var(--up-teal) }
.up-tile.warm { background:var(--up-orange-bg); color:var(--up-orange-700) }
.up-tile .up-i { width:22px; height:22px }

/* O ladrilho CHEIO — para a ação principal de um ecrã ("criar", "novo"), onde o contorno leve
   não chega para dizer que é ali que se começa.
   `--up-on-accent` e não `#fff`: no tema escuro o teal aclara, e branco por cima dele dá
   contraste **3.00** — foi o defeito medido no cartão "Nova viagem" do `trips`. Esta variável
   existe precisamente para "texto por cima do acento", e vira com o tema.

   No tema claro o par dá **4.47** — o mesmo número que ficou por decidir para o teal da marca
   (ver o `CLAUDE.md`). Aqui **passa**, e a distinção é a que evita uma correção desnecessária: o
   que vai neste ladrilho é um ícone ou um glifo grande, e a WCAG pede 3:1 a objetos gráficos e a
   texto grande, não os 4.5:1 do texto corrido. O caso que não passava era um botão de 13px. */
.up-tile.forte { background:var(--up-teal); color:var(--up-on-accent) }

/* Um cartão que É UMA AÇÃO, com o número do que está à espera: "Por aprovar — 3".
   Modificador do `.up-appcard` e não um componente novo: a anatomia é a mesma (ladrilho, nome,
   descrição), só o arranjo muda de coluna para linha. Um componente paralelo com os mesmos
   ingredientes é como o tema engorda sem ganhar nada.

     <a class="up-appcard acao" href="…">
       <span class="cab"><span class="up-tile">…</span>
         <span class="t"><b class="nm">Por aprovar</b><p class="muted small">…</p></span>
         <span class="n">3</span></span></a>

   O `tabular-nums` no número existe para a contagem não dançar quando passa de 9 para 10. */
.up-appcard.acao > .cab { align-items:center; padding:16px 18px }
.up-appcard.acao .n { margin-left:auto; font-size:26px; font-weight:700; line-height:1;
  color:var(--up-teal); font-variant-numeric:tabular-nums }

/* Cabeçalho de uma SECÇÃO dentro da página: o título à esquerda, a saída à direita.
   É o que permite mostrar "os 6 primeiros de 40" sem mentir — sem a ligação, uma lista cortada
   parece a lista toda. Aparecia três vezes só no painel do `trips`. */
.up-sec-h { display:flex; align-items:baseline; justify-content:space-between;
  gap:var(--up-sp-3); margin:0 0 var(--up-sp-2) }
.up-sec-h h2 { font-size:var(--up-fs-lg); margin:0 }
.up-sec-h a { font-size:var(--up-fs-sm); color:var(--up-teal); text-decoration:none;
  white-space:nowrap }
.up-sec-h a:hover { text-decoration:underline }

/* As entradas do módulo, para saltar lá para dentro sem passar pela raiz. */
.up-appcard .atalhos { display:flex; flex-direction:column; gap:1px; padding:0 12px 8px }
.up-appcard .atalhos a { display:flex; align-items:center; gap:8px; padding:6px 8px;
  border-radius:var(--up-radius-sm); font-size:var(--up-fs-sm); color:var(--up-grey-700);
  text-decoration:none }
.up-appcard .atalhos a:hover { background:var(--up-faint); color:var(--up-ink) }
.up-appcard .atalhos a svg { width:15px; height:15px; flex:none; color:var(--up-grey-500) }

.up-appcard .rodape { margin-top:auto; display:flex; align-items:center; gap:var(--up-sp-2);
  padding:10px 20px; border-top:1px solid var(--up-line); background:var(--up-grey-100);
  font-size:var(--up-fs-xs); color:var(--up-muted) }

@media (prefers-reduced-motion:reduce) {
  .up-appcard, .up-appcard > .cab .seta { transition:none }
  .up-appcard:hover { transform:none }
}

/* 7. ------------------------------------------------ Campos e formulários */
/* O PISO: um campo sem classe nenhuma. Existe porque a falta dele partiu a lista das viagens no
   tema escuro — o `<select>` dos filtros ficava com o branco por omissão do browser e herdava a
   cor de texto clara do tema: branco sobre branco, rácio de contraste 1.00, invisível (#93).

   Um módulo devia usar `.up-select`, e é o que se documenta. Mas um `<select>` nu é o que
   qualquer pessoa escreve à primeira, e o custo de ele ficar ilegível é alto de mais para
   depender de alguém se lembrar. Sem `width:100%` nem espaçamentos: isto só arruma as CORES,
   para não mudar o desenho de nada que já exista. */
input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]),
select, textarea {
  background:var(--up-white); color:var(--up-ink);
  border-color:var(--up-line);
}
/* O texto que o campo mostra quando está vazio também segue o tema — sem isto fica no cinzento
   do browser, que sobre superfície escura é ilegível. */
input::placeholder, textarea::placeholder { color:var(--up-muted); opacity:1 }
/* As opções de um `<select>` aberto são desenhadas pelo sistema, não pela página: o
   `color-scheme` é o que lhes diz em que tema estão. Sem ele, uma lista aberta no tema escuro
   aparece em claro. */
:root[data-theme="dark"] { color-scheme:dark }
:root[data-theme="light"] { color-scheme:light }


.up-input, .up-select, .up-textarea { width:100%; font:inherit; font-size:var(--up-fs-md);
  padding:9px 11px; border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  background:var(--up-white); color:var(--up-ink) }
.up-input:focus, .up-select:focus, .up-textarea:focus { outline:none; border-color:var(--up-teal);
  box-shadow:0 0 0 3px rgba(0,132,144,.15) }
.up-input:disabled, .up-select:disabled, .up-textarea:disabled { background:var(--up-faint);
  color:var(--up-muted) }
.up-input.err, .up-select.err, .up-textarea.err { border-color:var(--up-err) }
.up-label { display:block; font-size:var(--up-fs-xs); color:var(--up-muted); margin:0 0 var(--up-sp-1) }
.up-field { margin-bottom:var(--up-sp-4) }
.up-field .up-hint { font-size:var(--up-fs-xs); color:var(--up-muted); margin-top:var(--up-sp-1) }
.up-field .up-error { font-size:var(--up-fs-xs); color:var(--up-err); margin-top:var(--up-sp-1) }
.up-form-grid { display:grid; gap:0 var(--up-sp-4);
  grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)) }

/* Escolha de UMA opção entre poucas, todas à vista — melhor do que um `<select>` quando as
   hipóteses são duas ou três e a diferença entre elas importa (claro/escuro/sistema).
   Marcação: um `<label>` por opção, com o `<input type="radio">` lá dentro, escondido. */
.up-seg { display:inline-flex; padding:3px; gap:2px; background:var(--up-grey-100);
  border:1px solid var(--up-line); border-radius:var(--up-radius); flex-wrap:wrap }
.up-seg label { display:inline-flex; align-items:center; gap:7px; padding:7px 13px;
  border-radius:var(--up-radius-sm); font-size:var(--up-fs-sm); font-weight:500;
  color:var(--up-grey-700); cursor:pointer; transition:var(--up-transition) }
.up-seg input { position:absolute; opacity:0; width:0; height:0 }
.up-seg label:hover { color:var(--up-ink) }
.up-seg label:has(input:checked) { background:var(--up-surface); color:var(--up-ink);
  box-shadow:var(--up-shadow) }
.up-seg label:has(input:focus-visible) { outline:2px solid var(--up-teal); outline-offset:1px }
.up-seg .up-i { width:15px; height:15px }

/* 8. ------------------------------------------------------------ Tabelas */
.up-table { width:100%; border-collapse:collapse; font-size:var(--up-fs-md) }
/* O CABEÇALHO em maiúsculas pequenas — pedido do Bruno a 2026-09-08, a partir da maqueta do
   dashboard do Proposals DB: «este design é mesmo mto melhor, gostava de rever o tema para usar
   este». São três mudanças e mais nada: `600` em vez de `500`, `uppercase`, e o `letter-spacing`
   que as maiúsculas pedem — sem ele ficam apertadas e lêem-se PIOR do que as minúsculas.

   ⚠️ **Custa largura.** Maiúsculas são mais largas do que minúsculas e o `th` é `nowrap`,
   portanto um cabeçalho comprido («Position in organisation») pede mais coluna e a tabela chega
   ao `overflow-x` do `.up-table-wrap` mais cedo. É o preço, e é conhecido.

   ⚠️ **E NÃO leva `position:sticky`** — nem a maqueta o tinha a funcionar, apesar de o ter
   escrito. Uma tabela larga vive dentro do `.up-table-wrap`, que é `overflow-x:auto`; com o Y em
   `visible` o browser computa o Y para `auto` também, portanto o wrap É um contentor de rolagem,
   e sem altura máxima não há rolagem vertical de que um `th` se descole. Um cabeçalho colado a
   sério pede uma altura máxima no wrap — outra decisão, e uma que muda como todas as listas se
   lêem. */
.up-table th { text-align:left; color:var(--up-muted); font-weight:600;
  font-size:var(--up-fs-xs); letter-spacing:.055em; text-transform:uppercase;
  padding:10px 12px; border-bottom:1px solid var(--up-line); white-space:nowrap }
.up-table td { padding:10px 12px; border-bottom:1px solid var(--up-line); vertical-align:top }
.up-table tr:last-child td { border-bottom:none }
.up-table tr.click:hover { background:var(--up-faint); cursor:pointer }
.up-table td.num, .up-table th.num { text-align:right; font-variant-numeric:tabular-nums }
/* Contagens curtas (um `5`, um `36`) e colunas com muitos vazios: à direita ficavam coladas
   à coluna seguinte e pareciam pertencer-lhe. `.num` continua a ser o certo para dinheiro. */
.up-table td.mid, .up-table th.mid { text-align:center; font-variant-numeric:tabular-nums }
.up-table-wrap { overflow-x:auto }   /* tabela larga rola dentro do cartão, não na página */

/* ─────────────────────────── A GRELHA EDITÁVEL (`.up-cells`) ───────────────────────────

   ⚠️ **Chama-se `.up-cells` e NÃO `.up-grid`, e a razão é uma colisão medida.** A `.up-grid` já
   existe neste ficheiro desde há muito e é outra coisa: um `display:grid` de cartões e de KPIs
   (`.up-grid.sm`), usado em dezenas de sítios da árvore. Um `.up-grid { background; font-size }`
   escrito aqui em baixo ganhava-lhes por ordem e repintava todas essas grelhas de cartões — sem
   erro nenhum, e só visível em produção. Uma classe `.up-*` é uma promessa: não se reaproveita
   um nome que já a fez a outra gente.

   A tabela que se escreve como uma folha de cálculo. O comportamento — teclado, colar do Excel,
   células derivadas — é do `/static/upgrid.js` da plataforma; aqui está só o aspeto, que é o que
   este repositório faz.

   ⚠️ **A célula É um `<input>`, e a caixa dele desaparece.** Um campo com moldura dentro de cada
   célula desenha uma segunda grelha por cima da primeira: vinte linhas × doze colunas dão
   duzentas e quarenta molduras, e o que se lê deixa de ser uma tabela. O campo fica invisível até
   ter o foco, e aí ganha o anel — que é a única coisa que precisa de se ver, porque é a que diz
   onde se está a escrever.

   ⚠️ **`tabular-nums` nas colunas de número, como na `.up-table`.** Sem ele os algarismos têm
   larguras diferentes e uma coluna de valores alinhada à direita fica com as casas decimais a
   dançar de linha para linha — num orçamento, é o que faz duas quantias parecidas parecerem
   diferentes.

   O CABEÇALHO e o RODAPÉ colam-se: numa grelha de vinte parceiros, rolar a lista sem os totais à
   vista é rolar às cegas — o número que se está a tentar acertar é precisamente o que sai do ecrã. */
.up-cells-wrap { overflow:auto; max-height:60vh; border:1px solid var(--up-line);
  border-radius:var(--up-radius) }
.up-cells { width:100%; border-collapse:separate; border-spacing:0;
  font-size:var(--up-fs-md); background:var(--up-surface) }
.up-cells th { position:sticky; top:0; z-index:2; text-align:left; padding:7px 10px;
  color:var(--up-muted); font-weight:500; font-size:var(--up-fs-xs);
  background:var(--up-surface); border-bottom:1px solid var(--up-line); white-space:nowrap }
.up-cells td { padding:0; border-bottom:1px solid var(--up-line); color:var(--up-ink) }
.up-cells tbody tr:last-child td { border-bottom:none }
.up-cells td.num, .up-cells th.num, .up-cells tfoot td { text-align:right;
  font-variant-numeric:tabular-nums }
/* Uma célula que não se escreve é TEXTO e não um campo desactivado — ver a nota no `upgrid.js`:
   um campo cinzento continua a apanhar o `Tab` e a fazer parar quem anda pela tabela. */
.up-cells td.ro { padding:7px 10px; color:var(--up-muted); background:var(--up-faint) }
/* 🔴 **A cadeia de `:not()` está aqui para GANHAR ao reset, e não por gosto.** O reset base desta
   folha pinta `input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"])`
   de `--up-white` — quatro selectores de atributo, especificidade (0,4,1). Um `.up-cells input`
   vale (0,1,1) e PERDE: o `background:transparent` desta regra nunca chegou a aplicar-se, e todos
   os campos de todas as grelhas estavam brancos.

   ⚠️ **E isso torna invisível a COR DA CÉLULA por baixo deles** — o `td.papel-lider`, o
   `td.papel-contrib`, o `td.sel` e qualquer fundo que um módulo ponha numa célula com campo. Foi
   assim que se descobriu (@pmnmalo, 2026-09-13, sobre o fundo vermelho de duas linhas do orçamento
   do `machina`): a célula estava pintada, o campo por cima estava branco, e o que se via era
   branco. A promessa desta regra existia desde sempre; o que faltava era ela poder cumpri-la. */
.up-cells input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]) {
  width:100%; box-sizing:border-box; padding:7px 10px; border:none;
  background:transparent; color:inherit; font:inherit; text-align:inherit;
  border-radius:0; outline:none }
.up-cells td.num input { text-align:right; font-variant-numeric:tabular-nums }
/* O foco leva a mesma cadeia, e pela mesma razão levada um passo à frente: tem de ganhar ao reset
   (0,4,1) **e** à regra de cima (0,5,1), senão o campo focado ficava transparente e a moldura de
   foco desenhava-se sobre a cor da célula. Também esta nunca se aplicou. */
.up-cells input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):focus {
  background:var(--up-surface); color:var(--up-ink);
  box-shadow:inset 0 0 0 2px var(--up-accent) }
/* ─── O PAPEL de quem está numa célula: quem LIDERA e quem CONTRIBUI ─────────────────────────
   Nasceu do orçamento de uma proposta (`machina`), onde substituiu uma coluna `Lead`: uma coluna
   cabia um nome, e a cor responde no cruzamento onde o número está — *este parceiro, nesta linha,
   é o quê?* Está aqui e não lá pela razão de sempre: o **aspeto** vive no tema, e uma cor escrita
   num módulo não vira com o tema (é o que o `projetix.lint` recusa, e com razão).

   As duas cores claras são as da folha de cálculo por que estes orçamentos se montam há anos —
   quem abre a aplicação reconhece-as. As escuras são a mesma LEITURA noutro fundo: o líder mais
   forte, o contribuidor mais fraco. Um verde claro sobre uma tabela escura brilhava como um erro.

   ⚠️ **Cada uma diz a sua TINTA, e não a herda.** É um fundo fixo por token; herdar a cor do texto
   punha tinta clara sobre verde claro no tema escuro, e vice-versa — que é exactamente o que a
   regra `tema-no-modulo` do lint aponta quando um módulo põe um fundo e se cala sobre o texto.

   🔴 **E vêm DEPOIS do `td.ro` de propósito.** As duas regras têm a mesma especificidade, portanto
   decide a ordem — e uma célula só de leitura (a linha de um work package, que é a soma das
   tarefas) tem de continuar a mostrar o papel: o «não se escreve aqui» já se vê por ela ser texto
   e não um campo. */
.up-cells td.papel-lider   { background:var(--up-papel-lider);   color:var(--up-papel-lider-ink) }
.up-cells td.papel-contrib { background:var(--up-papel-contrib); color:var(--up-papel-contrib-ink) }
/* A amostra da legenda — um quadradinho da cor, para ela querer dizer alguma coisa a quem não a
   escolheu. `.up-papel` põe as duas coisas: o fundo e a borda que o separa do papel branco. */
.up-papel { display:inline-block; width:11px; height:11px; border-radius:2px;
  margin-right:5px; vertical-align:-1px; border:1px solid var(--up-line) }
.up-papel.lider   { background:var(--up-papel-lider) }
.up-papel.contrib { background:var(--up-papel-contrib) }
.up-cells tfoot td { position:sticky; bottom:0; z-index:2; padding:7px 10px; font-weight:600;
  background:var(--up-surface); border-top:1px solid var(--up-line); border-bottom:none }
.up-cells tfoot td:first-child { text-align:left; color:var(--up-muted); font-weight:500 }

/* A SELECÇÃO e a ALÇA DE PREENCHIMENTO — o que faz a grelha parecer uma folha de cálculo e não um
   formulário em grade.

   ⚠️ **O bloco marca-se com FUNDO e não com moldura.** Uma moldura à volta de um rectângulo obriga
   a saber que célula é canto, que célula é aresta e que célula é meio — quatro regras e oito casos,
   e todos eles errados quando o bloco tem uma célula só. O fundo é uma regra, e lê-se à mesma.

   A célula do canto de baixo é a única com desenho próprio: é onde a alça se agarra. A alça está
   DENTRO da `<td>` (posicionada por ela) e não sobreposta à tabela — assim acompanha o scroll
   horizontal sem ninguém ter de a reposicionar. */
.up-cells td.sel { background:var(--up-accent-bg) }
.up-cells td.sel input { background:transparent }
.up-cells td.sel-fim { position:relative }
.up-cells-fill { position:absolute; right:-3px; bottom:-3px; width:7px; height:7px;
  background:var(--up-accent); border:1px solid var(--up-surface); border-radius:1px;
  cursor:crosshair; z-index:3 }
/* A arrastar, o cursor não deve seleccionar TEXTO pelo caminho — sem isto o arrasto pinta as
   letras de meia tabela de azul por baixo do bloco. */
.up-cells.arrastando { user-select:none }
.up-table th.up-sort { cursor:pointer; user-select:none }
.up-table th.up-sort:hover { color:var(--up-ink) }

/* A BARRA por cima da lista: os filtros do módulo à esquerda, quantos se estão a ver, o
   selector de quantos por página, e a caixa de filtrar à direita.

   ⚠️ **Estava declarada DUAS vezes**, aqui e outra vez a quarenta linhas de distância, ambas ao
   nível de topo e cada uma com um comentário a dizer que era *a* barra. O que se via era a soma
   das duas — a segunda ganhava nas propriedades em conflito (o `font-size` da contagem) e as que
   só existiam na primeira continuavam a aplicar-se (o `justify-content`, o `padding`, o
   `border-bottom`). Juntas aqui em 2026-08-23, ao acrescentar o selector: acrescentar um campo a
   um dos dois blocos era escolher metade da barra à sorte. As propriedades efectivas foram
   medidas antes e depois e são as mesmas (issue `projetix#190`). */
.up-table-bar { display:flex; align-items:center; justify-content:space-between;
  gap:var(--up-sp-2); flex-wrap:wrap; padding:10px 12px;
  border-bottom:1px solid var(--up-line); margin-bottom:var(--up-sp-3) }
.up-table-count { color:var(--up-muted); font-size:var(--up-fs-sm); white-space:nowrap;
  font-variant-numeric:tabular-nums }
/* A pesquisa é a última a ceder — é o campo que se usa mais —, mas cede: com um chão de 200px
   era ela a empurrar a barra para o segundo andar depois de os filtros já terem encolhido. */
.up-table-search { margin-left:auto; width:auto; min-width:150px; max-width:260px;
  flex:1 1 150px }
/* Quantos por página, colado à contagem: lêem-se como uma frase só — `1–25 de 900 · [25 ▾]`.
   Escondido pelo widget quando a lista inteira cabe na menor opção. */
.up-table-perpage { width:auto; min-width:0; padding-top:2px; padding-bottom:2px;
  font-size:var(--up-fs-xs); font-variant-numeric:tabular-nums }
.up-table-perpage[hidden] { display:none }
/* os filtros próprios do módulo, na mesma barra da pesquisa e à esquerda dela */
.up-table-filters { display:flex; align-items:center; gap:var(--up-sp-2); flex-wrap:wrap;
  margin-right:auto; min-width:0 }
.up-table-filters:empty { display:none }
/* O conteúdo é que manda na largura: sem isto o `width:100%` dos campos põe cada filtro numa
   linha, e uma barra com dois filtros vira dois andares (issue #46).

   E ENCOLHEM. O `min-width:150px` que aqui esteve era um chão fixo, e com QUATRO filtros isso
   punha a barra a precisar de 992px — medido, com a marcação real do `upTable`: acima disso uma
   linha, abaixo dois andares, e um painel com barra lateral fica quase sempre abaixo. Agora cada
   filtro pede o que o seu rótulo precisa e cede o resto (`flex:0 1 auto`), com um chão baixo o
   suficiente para não cortar uma palavra. Medido a seguir: uma linha dos 900px para cima, dois
   andares abaixo, três em telemóvel — e nunca transborda. */
.up-table-filters > .up-select, .up-table-filters > .up-input,
.up-table-filters > select, .up-table-filters > input { width:auto; min-width:96px;
  max-width:210px; flex:0 1 auto }
.is-loading { opacity:.55; transition:opacity .12s }

/* 8a-bis. --------------------------------------------------- ABAS (`.up-abas`)
   Uma barra de abas, e as abas são LINKS — não um comutador de formulário como o `.up-seg`.
   A diferença não é estética: cada aba é um endereço (`?aba=…`), portanto pode ser mandada a um
   colega, o «voltar» do browser recua entre elas e a página serve sem JavaScript. Foi
   exactamente o que se perdeu quando o `proposals` tinha oito separadores atrás de uma palavra,
   e o que se recuperou ao dar um endereço a cada vista.

   O `margin-bottom:-1px` põe o traço da aba activa POR CIMA do traço da barra, em vez de dois
   traços colados; sem ele, a activa fica com uma linha dupla de 3px que se lê como um erro.

   Nasceu para o dashboard com abas (`projetix#232`), a partir da maqueta do Proposals DB. */
.up-abas { display:flex; gap:2px; overflow-x:auto; border-bottom:1px solid var(--up-line) }
.up-abas a { flex:0 0 auto; font-weight:600; font-size:var(--up-fs-sm); color:var(--up-muted);
  padding:9px 12px 11px; border-bottom:2px solid transparent; text-decoration:none;
  white-space:nowrap; margin-bottom:-1px;
  border-radius:var(--up-radius-sm) var(--up-radius-sm) 0 0 }
.up-abas a:hover { color:var(--up-ink); background:var(--up-faint) }
.up-abas a:focus-visible { outline:2px solid var(--up-accent); outline-offset:-2px }
/* A activa marca-se com `aria-current="page"` e não com uma classe: quem lê o ecrã com um leitor
   ouve qual é, e o CSS aproveita a mesma marca em vez de pedir duas. */
.up-abas a[aria-current="page"] { color:var(--up-accent); border-bottom-color:var(--up-accent) }

/* 8b. ------------------------------------------------ Paginação: 1 | 2 | 3 | …
   Uma lista nunca se serve inteira. Isto é o rodapé que a atravessa, e é IGUAL em todas as
   aplicações — o comportamento está no `upTable` da plataforma, aqui só o aspeto.
   As barras entre os números são do CSS: no markup só há botões, que é o que eles são. */
.up-pager[hidden] { display:none }
.up-pager button { font:inherit; font-size:var(--up-fs-sm); font-variant-numeric:tabular-nums;
  min-width:30px; padding:5px 8px; border:1px solid transparent; border-radius:var(--up-radius-sm);
  background:none; color:var(--up-ink); cursor:pointer }
.up-pager button:hover:not(:disabled):not(.is-current) { background:var(--up-faint) }
.up-pager button:disabled { color:var(--up-grey-300); cursor:default }
.up-pager .is-current { border-color:var(--up-accent); color:var(--up-accent); font-weight:600;
  cursor:default }
.up-pager .up-pager-gap { color:var(--up-muted); padding:0 2px; user-select:none }
/* o separador `|`, entre números seguidos e nunca nas pontas */
.up-pager .up-pager-n + .up-pager-n::before,
.up-pager .up-pager-gap + .up-pager-n::before,
.up-pager .up-pager-n + .up-pager-gap::before { content:"|"; color:var(--up-line);
  margin-right:6px; margin-left:-4px; font-weight:400 }
.up-pager .up-pager-step { color:var(--up-muted); font-size:var(--up-fs-md) }

/* A barra do `poll`: «3 new · show», entre os filtros e a tabela (issue `projetix#101`).

   ⚠️ Ela **não** é um alerta e não deve gritar. O que está no ecrã continua a valer — o que
   mudou é que há mais lá atrás —, e uma barra vermelha a dizer isso ensina a fechar a página.
   Usa a cor de informação e a mesma altura de uma linha da tabela, para não empurrar o que se
   está a ler quando aparece.

   O `hidden` é do widget, e o `display:none !important` existe porque o `display:flex` daqui
   ganhava ao `[hidden]` do browser — uma barra vazia com padding é uma faixa cinzenta que
   aparece do nada e não diz nada. */
.up-table-novas[hidden] { display:none !important }
.up-table-novas { display:flex; align-items:center; gap:var(--up-sp-2);
  padding:var(--up-sp-2) var(--up-sp-3); margin-bottom:var(--up-sp-2);
  border:1px solid var(--up-line); border-radius:var(--up-radius);
  background:var(--up-faint); color:var(--up-ink); font-size:var(--up-fs-sm) }

.up-table-empty { text-align:center; color:var(--up-muted); padding:var(--up-sp-5) }
.up-table th.up-sort { cursor:pointer; user-select:none }
.up-table th.up-sort:hover { color:var(--up-ink) }

/* Paginação: anterior/seguinte nas pontas, os números no meio.
   ⚠️ A regra base estava declarada duas vezes, aqui e na secção da tabela. Juntas em
   2026-08-23 (`projetix#190`), com as propriedades efectivas medidas antes e depois — o `gap`
   é o desta e o `padding`/`border-top` vinham da outra. **As regras dos FILHOS continuam
   duplicadas de propósito**: `.up-pager button` e o `is-current` têm aqui dois desenhos
   diferentes que se somam, e escolher entre eles é uma decisão de aspeto, não uma arrumação. */
.up-pager { display:flex; align-items:center; justify-content:center; flex-wrap:wrap;
  gap:4px; padding:10px 12px; border-top:1px solid var(--up-line);
  margin-top:var(--up-sp-3) }
.up-pager button { font:inherit; font-size:var(--up-fs-sm); min-width:32px; padding:5px 10px;
  border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  background:var(--up-surface); color:var(--up-ink); cursor:pointer;
  font-variant-numeric:tabular-nums; transition:var(--up-transition) }
.up-pager button:hover:not(:disabled) { background:var(--up-faint) }
.up-pager button:disabled { opacity:.4; cursor:default }
.up-pager .up-pager-n.is-current { background:var(--up-teal); border-color:var(--up-teal);
  color:var(--up-on-accent); font-weight:600 }
.up-pager .up-pager-gap { color:var(--up-muted); padding:0 2px }

/* 8b. ----------------------------------------- A DICA: um cartão que abre em cima de algo
   Uma etiqueta ou um ícone dizem *que há algo*; a dica diz *o quê*, e com marcação a sério —
   negrito, cores, uma lista. O atributo `title` não serve para isso: é texto simples, o navegador
   demora a mostrá-lo e não há como destacar uma linha dentro dele.

   Nasceu no `machina` (2026-08-23): o aviso de parceiro partilhado tinha de mostrar **os parceiros
   do lugar na outra proposta, com as pontuações, e o parceiro em questão destacado** — nada disso
   cabe num `title`.

   Está no TEMA porque é vocabulário: qualquer módulo que queira explicar um ícone sem abrir uma
   gaveta tem o mesmo problema, e um cartão inventado num módulo é um cartão diferente por módulo.

       <span class="up-dica">⚠
         <span class="up-dica-card">…marcação a sério…</span>
       </span>

   ⚠️ **`:focus-within` ao lado do `:hover`, e não é zelo**: um cartão que só abre com o rato é um
   cartão que não existe para quem navega por teclado — e o `tabindex="0"` no gatilho é o que o põe
   no caminho do `Tab`.

   ⚠️ **Abre PARA BAIXO e para a DIREITA** (`top:100%; left:0`), e isto é uma correcção medida no
   ecrã, não uma preferência.

   A primeira versão abria para cima e para a esquerda, por um raciocínio que parecia sólido: os
   ícones ficam à direita de uma tabela dentro de uma gaveta que rola, portanto para baixo sairia
   pelo fundo e para a direita pela borda. **Em uso, o cartão simplesmente não se via** — pedido do
   @pmnmalo a 2026-08-23: *«the pop-up cannot see because it is opening to the left of the icon —
   put it opening down-right»*. A gaveta rola, portanto o que sai por baixo alcança-se; o que sai
   para a esquerda fica debaixo das colunas que já lá estão.

   *Uma geometria deduzida do CSS não substitui alguém a olhar para o ecrã.*

   Um `max-height` com rolagem própria fecha o resto — um cartão mais alto do que a gaveta não se lê
   de nenhuma maneira.

   **O que ele NÃO faz:** não se reposiciona sozinho. Isso obriga a medir o ecrã em JavaScript, e o
   ganho não paga a complexidade. As duas saídas são `.up-dica.acima` (abre para cima, para um
   gatilho no fim de uma lista) e `.up-dica.esquerda` (abre para a esquerda, para um gatilho
   encostado à borda direita). */
.up-dica { position:relative; display:inline-flex; align-items:center; cursor:help }
.up-dica > .up-dica-card {
  position:absolute; top:100%; left:0; z-index:60; margin-top:6px;
  display:none; min-width:220px; max-width:340px; max-height:280px; overflow:auto;
  padding:var(--up-sp-3); text-align:left; white-space:normal; cursor:default;
  background:var(--up-surface); color:var(--up-ink);
  border:1px solid var(--up-line); border-radius:var(--up-radius-sm);
  box-shadow:var(--up-shadow-lg); font-size:var(--up-fs-sm); line-height:1.45 }
.up-dica:hover > .up-dica-card, .up-dica:focus-within > .up-dica-card { display:block }
.up-dica.acima > .up-dica-card { top:auto; bottom:100%; margin:0 0 6px }
.up-dica.esquerda > .up-dica-card { left:auto; right:0 }
/* Uma linha do cartão, e a que é O MOTIVO do cartão. O destaque é fundo e peso, não só cor: quem
   não distingue as duas cores continua a ver qual é a linha que importa. */
.up-dica-card .linha { display:flex; gap:var(--up-sp-2); align-items:baseline;
  padding:1px 4px; border-radius:var(--up-radius-sm) }
.up-dica-card .linha.alvo { background:var(--up-accent-bg); font-weight:600 }
.up-dica-card .titulo { font-weight:600; margin:0 0 2px }
.up-dica-card .titulo + .titulo, .up-dica-card .linha + .titulo { margin-top:var(--up-sp-2) }

/* A DICA CURTA — variante de UMA PALAVRA, para um ícone só (Projetix/projetix#266).
   ⚠️ **`.up-dica-card` sozinho não serve para isto.** Tem `min-width:220px` porque é pensado para
   EXPLICAÇÕES (uma lista de linhas, um título) — uma palavra lá dentro dava uma caixa branca enorme
   e desalinhada com o ícone. Foi o que aconteceu na maqueta da barra de cima: *«os nomes nos icons
   lá em cima ainda tão mto feios»*.
   Reaproveita o `.up-dica` que já existe (o `:hover`/`:focus-within` a mostrar o cartão, o
   `tabindex="0"` no gatilho para o teclado) e troca só a FORMA do cartão: escura, pequena,
   centrada por baixo do ícone em vez de larga e alinhada à esquerda. */
.up-dica-card.curta { min-width:0; width:max-content; max-width:none; padding:5px 8px;
  left:50%; transform:translateX(-50%); text-align:center; white-space:nowrap;
  background:var(--up-ink); color:var(--up-white); border:0;
  font-size:11px; line-height:1; letter-spacing:.01em }

/* 9. ------------------------------------------- Etiquetas, KPI, avatar */
.up-tag { display:inline-block; border-radius:var(--up-radius-pill); padding:2px 10px;
  font-size:var(--up-fs-xs); background:var(--up-grey-100); color:var(--up-grey-700) }
.up-tag.accent { background:var(--up-teal-bg); color:var(--up-teal-700) }
.up-tag.ok   { background:var(--up-ok-bg);   color:var(--up-ok) }
.up-tag.warn { background:var(--up-warn-bg); color:var(--up-warn) }
.up-tag.err  { background:var(--up-err-bg);  color:var(--up-err) }
/* As oito ETAPAS de um percurso. Ver os tokens `--up-etapa*`: a escala vale como progressão, e
   quem escolhe qual estado é qual etapa é o módulo que conhece o fluxo. */
.up-tag.etapa1 { background:var(--up-etapa1-bg); color:var(--up-etapa1) }
.up-tag.etapa2 { background:var(--up-etapa2-bg); color:var(--up-etapa2) }
.up-tag.etapa3 { background:var(--up-etapa3-bg); color:var(--up-etapa3) }
.up-tag.etapa4 { background:var(--up-etapa4-bg); color:var(--up-etapa4) }
.up-tag.etapa5 { background:var(--up-etapa5-bg); color:var(--up-etapa5) }
.up-tag.etapa6 { background:var(--up-etapa6-bg); color:var(--up-etapa6) }
.up-tag.etapa7 { background:var(--up-etapa7-bg); color:var(--up-etapa7) }
.up-tag.etapa8 { background:var(--up-etapa8-bg); color:var(--up-etapa8) }
/* ⚠️ **A NONA etapa, e o que a obrigou a existir.** As classes são POSICIONAIS — o módulo faz
   `etapa{i}` a contar os estados dele —, portanto um caminho com nove estados pedia uma
   `.etapa9` que não existia: a etiqueta saía **sem regra nenhuma**, com o fundo por omissão, e
   sem erro em lado nenhum. É o modo de falha que este ficheiro evita noutros sítios, e foi o
   `machina` a ganhar o «Concept note» à cabeça que o encontrou (@pmnmalo, 2026-09-01).

   Neutra de propósito: num caminho de nove, a nona é o fim da linha (arquivada, fechada), e o
   fim da linha não é um estado por que se esteja à espera. Fica um cinzento mais fundo do que o
   da oitava para as duas se distinguirem quando aparecem lado a lado. */
.up-tag.etapa9 { background:var(--up-etapa9-bg); color:var(--up-etapa9) }
/* A DÉCIMA. Entrou um dia depois da nona, e a razão é a mesma — um caminho ganhou mais um passo.
   ⚠️ **Isto não escala por acrescentar uma de cada vez.** Se um módulo passar dos dez estados, a
   resposta deixa de ser «mais uma cor» e passa a ser reciclar a paleta (`etapa{i mod N}`) ou dar
   ao caminho uma escala contínua. Fica dito aqui para a décima primeira não entrar sem se pensar
   nisso — e a régua que conta os estados contra as classes vive no `module-machina`. */
.up-tag.etapa10 { background:var(--up-etapa10-bg); color:var(--up-etapa10) }

.up-kpi { background:var(--up-surface); border:1px solid var(--up-line);
  border-radius:var(--up-radius); padding:14px 16px }
.up-kpi .v { font-size:24px; font-weight:700; font-variant-numeric:tabular-nums }
.up-kpi .l { color:var(--up-muted); font-size:var(--up-fs-xs); margin-top:2px }

/* A fila de KPI. Existia como `.cards` dentro de dois módulos, com a mesma grelha copiada nos
   dois — e um `.up-kpi` sem sítio onde viver obrigava a isso (issue projetix#25).
   `auto-fit` e não um número de colunas: no telemóvel passa a uma coluna sozinho, que é metade
   da issue #80 resolvida sem media query nenhuma. */
.up-kpis { display:grid; gap:var(--up-sp-3);
  grid-template-columns:repeat(auto-fit, minmax(150px, 1fr)) }

/* Uma barra de proporção — "este valor contra o maior da lista". Dentro de uma célula de tabela
   é o que transforma uma coluna de números numa coisa que se lê de relance.
   `<span class="up-meter"><i style="width:42%"></i></span>`, e o `i` é a parte cheia.
   Não é um `<progress>`: o nativo não se estiliza igual nos browsers todos, e aqui o que se quer
   é uma marca visual e não um controlo — quem precisa do valor tem-no na coluna ao lado.

   Sobre o contraste, porque a medição engana: a parte cheia contra a vazia dá **3.87** (claro) e
   **4.49** (escuro) — acima do 3:1 que a WCAG pede a um objeto gráfico, e é esta a fronteira que
   transporta a informação. Já o **fundo** da barra contra o cartão dá **1.15**, e isso fica
   assim de propósito: cheguei a pôr-lhe um contorno `--up-line` e não resolve nada (medido:
   1.18), porque essa variável é discreta por desenho — é a mesma de todas as bordas do tema.

   Fica assim porque **a barra nunca é a única fonte do valor**: quem a usa põe o número ao lado,
   na mesma célula. É uma ajuda redundante, e a WCAG 1.4.11 aplica-se ao que é preciso para
   ENTENDER o conteúdo. Se um dia alguém a usar sozinha, aí falta-lhe um `title` ou um
   `aria-label` com o número — e não um contorno mais forte. */
.up-meter { display:inline-block; height:7px; min-width:70px; border-radius:4px;
  background:var(--up-teal-bg); overflow:hidden; vertical-align:middle }
.up-meter > i { display:block; height:100%; background:var(--up-teal) }

/* Uma ÁREA PARA LARGAR FICHEIROS. Qualquer módulo que receba documentos precisa de uma, e
   estava escrita à mão com `border:2px dashed #cfd2d6` — uma cor fixa, portanto igual nos dois
   temas. O `.quente` é o estado "estás a arrastar por cima": muda a borda para o acento, e é o
   que diz que largar ali vai fazer algo.

     <div class="up-drop"><div class="grande">Arrasta os ficheiros</div>
       <div class="muted small">ou clica para escolher</div></div> */
.up-drop { border:2px dashed var(--up-grey-300); border-radius:var(--up-radius);
  background:var(--up-bg); padding:34px 20px; text-align:center; cursor:pointer;
  transition:var(--up-transition) }
.up-drop:hover { border-color:var(--up-grey-500) }
.up-drop.quente { border-color:var(--up-teal); background:var(--up-teal-bg) }
.up-drop .grande { font-size:var(--up-fs-lg); margin-bottom:2px }

/* A lista de ACHADOS de uma verificação — erros, avisos, o que está bem. Um ponto colorido, o
   texto ao lado, e uma contagem por gravidade em cima.

     <div class="up-achado"><span class="ponto err"></span><div>o que se encontrou</div></div>

   Não se chama `.up-find`: essa já existe e é o CAMPO DE PESQUISA. Dois nomes quase iguais para
   coisas diferentes é como se escolhe o errado — vale a pena olhar para o que o componente FAZ.
   As gravidades usam os mesmos sufixos do `.up-tag` (`ok`, `warn`, `err`), para não haver um
   segundo vocabulário. */
.up-achado { display:flex; gap:9px; align-items:flex-start; padding:6px 0;
  font-size:var(--up-fs-md) }
.up-achado .ponto { width:9px; height:9px; border-radius:50%; margin-top:6px; flex:none;
  background:var(--up-grey-300) }
.up-achado .ponto.ok   { background:var(--up-ok) }
.up-achado .ponto.warn { background:var(--up-warn) }
.up-achado .ponto.err  { background:var(--up-err) }

/* Um AVISO que ocupa espaço: falhou uma coisa, e há algo a fazer sobre isso. Diferente do
   `.up-tag`, que é uma etiqueta, e do `.up-empty`, que é a ausência de conteúdo. */
.up-aviso { border:1px solid var(--up-line); border-radius:var(--up-radius);
  padding:14px 16px; background:var(--up-surface) }
.up-aviso .msg { font-size:var(--up-fs-md); font-weight:500 }
.up-aviso.err { border-color:var(--up-err); background:var(--up-err-bg) }
.up-aviso.err .msg { color:var(--up-err) }
.up-aviso.warn { border-color:var(--up-warn); background:var(--up-warn-bg) }
.up-aviso.warn .msg { color:var(--up-warn) }

/* Um FIO DE COMENTÁRIOS — quem disse o quê sobre um registo, por baixo dele.

     <div class="up-thread">
       <div class="up-msg"><b>Ana</b> <span class="up-when">2026-08-10 14:20</span>
         <div>o texto</div></div>
     </div>

   Existe em três sítios com três nomes (`.coms` nas melhorias, o `chat` do editor, a resposta
   nos relatos do #48), e nenhum deles ficava bem no escuro: um usava
   `var(--up-linha, #e3e3e3)` — uma variável que **não existe** (o nome é `--up-line`), portanto
   o cinzento claro do fallback era o que se via sempre, em qualquer tema. É o tipo de defeito
   que uma variável com nome errado esconde para sempre, porque nunca dá erro. */
.up-thread { margin:var(--up-sp-2) 0 0; padding-left:var(--up-sp-3);
  border-left:2px solid var(--up-line) }
.up-msg { margin:var(--up-sp-2) 0; font-size:var(--up-fs-sm) }
.up-msg .up-when { color:var(--up-muted); font-size:var(--up-fs-xs);
  margin-left:var(--up-sp-2) }

/* `.up-when` — uma data/hora que NÃO SE PARTE POR DENTRO.
 *
 *   <span class="up-when"><span>2026-08-13</span> <span>03:15</span></span>
 *
 * Numa coluna estreita, `2026-08-13 03:15` aparecia partido em `2026-` / `08-13 03:15`, e uma data
 * partida ao meio deixa de se ler como uma data: o `2026-` sozinho parece um ano por acabar.
 *
 * **Não é um descuido do browser.** O hífen é uma oportunidade de quebra legítima (UAX #14, o
 * mesmo que parte `bem-vindo`), portanto ele está a fazer o que lhe compete — e não há
 * propriedade de CSS que desligue *só* essa quebra. Por isso a proteção é nas duas metades, e o
 * `.up-when` existe como âncora delas.
 *
 * A quebra ENTRE a data e a hora fica PERMITIDA, de propósito: numa gaveta estreita, cada uma na
 * sua linha é o que se quer. Um `white-space:nowrap` no conteúdo todo era a correção de uma linha,
 * e tirava isso — empurrava a coluna para fora da tabela em vez de a deixar respirar.
 *
 * A hora nunca partiria (os dois pontos não são oportunidade de quebra), mas leva a mesma marca:
 * quem escreve a marcação não tem de saber essa diferença para acertar.
 *
 * O HTML é do `upWhen()` da plataforma (`/static/uptable.js`), ao lado do `upMoney` — mesmo
 * princípio, um valor que vem de fora e tem de se ler igual em todo o lado. */
.up-when > span { white-space:nowrap }

.up-avatar { width:32px; height:32px; border-radius:50%;
  background:var(--up-btn-bg); color:var(--up-btn-fg);
  display:inline-flex; align-items:center; justify-content:center;
  font-size:var(--up-fs-md); font-weight:600; flex:none }
.up-avatar.accent { background:var(--up-teal) }

/* Ícones: `<svg class="up-i"><use href="/theme/static/icons.svg#i-home"></use></svg>` */
.up-i { width:18px; height:18px; flex:none; fill:none; stroke:currentColor;
  stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round }
.up-i.sm { width:14px; height:14px }
.up-i.lg { width:24px; height:24px }
/* A excepção dos LOGÓTIPOS (projetix#229). O `.up-i` fixa `fill:none; stroke:currentColor` — e um
   atributo `fill` no `<path>` PERDE para o CSS, portanto uma marca preenchida desenhava-se como
   nada: sem erro, um espaço em branco onde devia estar o ícone. Daí uma classe, e não um atributo.
   `currentColor` nos dois lados: a marca acompanha a cor do texto e é certa no tema escuro. Hoje
   só o `github` a usa: `icon("github", "marca")`. */
.up-i.marca { fill:currentColor; stroke:none }

/* 10. ----------------------------------------------------------- Estados */
/* Os ecrãs que ninguém desenha e toda a gente vê. */
.up-empty { text-align:center; padding:var(--up-sp-7) var(--up-sp-5); color:var(--up-muted) }
.up-empty .up-i { width:32px; height:32px; color:var(--up-grey-300); margin-bottom:var(--up-sp-3) }
.up-empty h2 { color:var(--up-ink); margin-bottom:var(--up-sp-1) }
.up-empty p { margin:0 0 var(--up-sp-4); font-size:var(--up-fs-md) }

.up-spin { display:inline-block; width:15px; height:15px; border:2px solid var(--up-grey-200);
  border-top-color:var(--up-ink); border-radius:50%; animation:up-spin .8s linear infinite;
  vertical-align:-3px }
@keyframes up-spin { to { transform:rotate(360deg) } }
@media (prefers-reduced-motion:reduce) { .up-spin { animation-duration:2.4s } }
.up-loading { display:flex; align-items:center; gap:var(--up-sp-2); color:var(--up-muted);
  padding:var(--up-sp-5); justify-content:center }

/* Esqueleto, para quando o conteúdo demora e já se sabe a forma que vai ter. */
.up-skel { background:linear-gradient(90deg, var(--up-grey-100), var(--up-grey-200), var(--up-grey-100));
  background-size:200% 100%; animation:up-skel 1.2s ease-in-out infinite;
  border-radius:6px; height:12px; margin:6px 0 }
@keyframes up-skel { 0%{background-position:200% 0} 100%{background-position:-200% 0} }

/* TEXTO LONGO — uma página de documentação, escrita em markdown e desenhada em HTML.
   Entrou com o `module-docs` (Projetix/projetix#184).

   É o único componente do tema em que se estilizam ELEMENTOS NUS (`h2`, `p`, `ul`, `pre`) em vez
   de classes, e a razão é que aqui não há quem lhes ponha uma classe: o HTML vem de um
   renderizador de markdown, não de um template que alguém escreveu. Por isso está tudo debaixo do
   `.up-prose` — fora dele nada disto se aplica, e um módulo que escreva `<p>` à mão não muda de
   aspecto por causa deste bloco.

   O corpo é mais largo do que o `--up-fs-sm` do resto da aplicação de propósito: isto lê-se de
   uma ponta à outra, não se percorre à procura de um valor. O `max-width` é a mesma ideia — uma
   linha de texto num ecrã de 27" sem limite nenhum é ilegível, e a medida clássica são ~70
   caracteres. */
.up-prose { font-size:var(--up-fs-md); line-height:1.65; max-width:68ch }
.up-prose > :first-child { margin-top:0 }
.up-prose > :last-child { margin-bottom:0 }
.up-prose h1, .up-prose h2, .up-prose h3 { margin:var(--up-sp-6) 0 var(--up-sp-2) }
.up-prose h1 { font-size:var(--up-fs-lg) }
.up-prose p, .up-prose ul, .up-prose ol { margin:0 0 var(--up-sp-3) }
.up-prose ul, .up-prose ol { padding-left:var(--up-sp-5) }
.up-prose li { margin-bottom:var(--up-sp-1) }
.up-prose a { color:var(--up-teal); text-decoration:underline }
.up-prose a:hover { color:var(--up-teal-700) }
.up-prose blockquote { margin:0 0 var(--up-sp-3); padding:var(--up-sp-1) var(--up-sp-4);
  border-left:3px solid var(--up-line); color:var(--up-muted) }
.up-prose pre { background:var(--up-faint); border:1px solid var(--up-line);
  border-radius:var(--up-radius-sm); padding:var(--up-sp-3); overflow-x:auto;
  margin:0 0 var(--up-sp-3) }
/* Dentro de um bloco o `code` já tem a moldura do `<pre>`: sem isto ficava uma caixa
   dentro de outra, e com o padding do inline a desalinhar a primeira linha. */
.up-prose pre code { background:none; padding:0; font-size:var(--up-fs-sm) }
.up-prose table { border-collapse:collapse; width:100%; margin:0 0 var(--up-sp-3);
  font-size:var(--up-fs-sm) }
.up-prose th, .up-prose td { border-bottom:1px solid var(--up-line); padding:6px 10px;
  text-align:left }
.up-prose th { font-weight:600; color:var(--up-muted) }
.up-prose hr { border:none; border-top:1px solid var(--up-line); margin:var(--up-sp-5) 0 }
.up-prose img { max-width:100%; height:auto; border-radius:var(--up-radius-sm) }

/* FIGURA — um desenho dentro de texto longo, com legenda.
   Entrou com as guias do `module-docs` (Projetix/projetix#98).

   ⚠️ **O SVG vai INSERIDO no HTML, e não num `<img>`, e a razão é este ficheiro.** O tema escuro
   é o `data-theme` no `<html>` (ver 1.1), não o `prefers-color-scheme` — e um SVG dentro de um
   `<img>` é um documento à parte: não vê o atributo do pai nem as variáveis `--up-*`. Ficaria um
   desenho claro no meio de uma página escura, e nenhuma media query dentro do ficheiro
   resolveria, porque quem escolhe o tema é a conta e pode não coincidir com o sistema.

   Inserido, o desenho é filho da página: `fill="var(--up-ink)"` vira sozinho. É por isso que as
   regras abaixo são poucas — o trabalho está nas variáveis, como em todo o resto. */
.up-figura { margin:var(--up-sp-5) 0; padding:0 }
.up-figura svg { display:block; width:100%; height:auto }
.up-figura figcaption { margin-top:var(--up-sp-2); font-size:var(--up-fs-xs);
  color:var(--up-muted); text-align:center }
/* A moldura é leve de propósito: o desenho já tem os seus próprios contornos, e uma segunda
   caixa à volta transforma uma figura numa gaveta. */
.up-figura > svg { border:1px solid var(--up-line); border-radius:var(--up-radius);
  background:var(--up-bg); padding:var(--up-sp-4) }

.up-note { border:1px solid var(--up-line); border-left-width:3px; border-radius:var(--up-radius-sm);
  padding:11px 14px; font-size:var(--up-fs-sm); background:var(--up-white) }
.up-note.ok   { border-left-color:var(--up-ok);   background:var(--up-ok-bg);   color:var(--up-ok) }
.up-note.warn { border-left-color:var(--up-warn); background:var(--up-warn-bg); color:var(--up-warn) }
.up-note.err  { border-left-color:var(--up-err);  background:var(--up-err-bg);  color:var(--up-err) }

/* O QUE ACONTECEU AO RASCUNHO ao restaurar uma pastilha (projetix#227): o que voltou, o que não
   voltou, e porquê.

   ⚠️ **Não é um `.warn`.** O caso normal é bom — «pusemos de volta o que tinhas escrito» — e
   pintar isso de aviso ensinava a ler amarelo como ruído; o caso mau (perdeu-se, ou o registo
   mudou) vem na mesma caixa porque é a mesma notícia vista de outro lado. Fica com a moldura
   neutra do `.up-note`, que é a que diz «lê isto» sem dizer «corre».

   Aparece no topo do `<main>`, empurrado pelo JavaScript, e por isso tem margem em baixo: sem ela
   colava-se ao cabeçalho da página, e uma frase colada a um título lê-se como parte dele. */
.up-draft-aviso { display:flex; align-items:flex-start; gap:var(--up-sp-2);
  margin-bottom:var(--up-sp-3) }
.up-draft-aviso > span { flex:1 }
.up-draft-aviso > .fechar { border:0; background:none; cursor:pointer; font:inherit;
  padding:0 2px; font-size:16px; line-height:1.2; color:var(--up-grey-300) }
.up-draft-aviso > .fechar:hover { color:var(--up-ink) }

/* 11. ---------------------------------- Gaveta, diálogo e barra de guardar */

/* A GAVETA — o sítio onde se vê UM item de uma lista.
   Clicar numa linha não leva a lado nenhum: abre um painel por cima, à direita, com a lista
   ainda lá atrás. Quem anda a percorrer trinta tópicos abre, lê, fecha e continua na mesma
   linha, na mesma página, com o mesmo filtro — que é o contrário do que acontecia quando o
   detalhe era uma página nova e o "voltar" recomeçava tudo.

   O detalhe crescia por baixo da lista, e cada módulo tinha a sua cópia — outro tamanho de
   letra, outra moldura, outro "a carregar". Agora é um componente só, e a lista deixa de ter
   de encolher para caber ao lado dele.

   O comportamento (abrir, fechar, o endereço, a pilha de gavetas) é do `upSheet` da
   plataforma; aqui está só o aspeto. Nunca se escreve este markup à mão.

     .up-sheet-bg > .up-sheet > .up-sheet-head + .up-sheet-body + .up-sheet-foot

   `.wide` é para quando lá dentro há um formulário ou uma tabela e metade do ecrã aperta. */
.up-sheet-bg { position:fixed; inset:0; background:var(--up-veil); z-index:40;
  display:flex; justify-content:flex-end; animation:up-fade .15s ease-out }
.up-sheet-bg[hidden] { display:none }
/* METADE do ecrã, e nunca menos de 560px. A largura é uma proporção e não um número fixo
   porque o que ela tem de equilibrar é uma proporção: a ficha tem de caber sem se ler em
   coluna estreita, e a lista atrás tem de continuar a ler-se como uma lista — se a gaveta
   fosse sempre 560px, num ecrã de 2560 era uma tira ao lado de um vazio, e num de 1280 já
   apertava. O mínimo fica porque abaixo dos ~1120px metade do ecrã deixa de chegar para um
   par rótulo/valor sem partir linhas. */
.up-sheet { display:flex; flex-direction:column; width:clamp(560px, 50%, 100%); height:100%;
  background:var(--up-surface); border-left:1px solid var(--up-line);
  box-shadow:var(--up-shadow-lg); animation:up-sheet-in .18s ease-out }
.up-sheet.wide { width:clamp(920px, 70%, 100%) }
/* TUDO menos a barra lateral. Pedido para a ficha da proposta (@pmnmalo, 2026-09-01): a ficha
   passou a ter oito assuntos, um Gantt e uma grelha de orçamento, e 70% do ecrã deixaram de
   chegar — mas *«para ficar à vista o menu»*.

   ⚠️ **A largura sozinha não bastava, e é por isso que o fundo também recua.** Com o `.up-sheet-bg`
   em `inset:0`, uma gaveta larga deixa o menu por baixo do véu: visível, escurecido, e **a fechar
   a gaveta quando se lhe carrega** — que é o contrário de o ter à vista. Recuando o fundo, a barra
   lateral fica de fora do painel: nítida, e clicável para o que ela serve, que é ir a outro sítio.

   O `:has()` faz isto sem a plataforma ter de marcar o fundo — a classe está no painel. Num
   browser sem `:has()` a regra é ignorada e volta-se ao véu por cima do menu, que é o
   comportamento de sempre: degrada para o que havia, não para o partido. */
.up-sheet.full { width:100% }
.up-sheet-bg:has(> .up-sheet.full) { left:var(--up-sidebar) }
/* Abaixo dos 901px a barra lateral sai do ecrã (`translateX(-100%)`), portanto não há nada de
   onde recuar — e recuar deixava uma faixa vazia à esquerda de uma gaveta que devia ser tudo. */
@media (max-width:900px) { .up-sheet-bg:has(> .up-sheet.full) { left:0 } }

.up-sheet-head { flex:none; display:flex; align-items:flex-start; gap:var(--up-sp-3);
  padding:var(--up-sp-4) var(--up-sp-5); border-bottom:1px solid var(--up-line) }
.up-sheet-titles { min-width:0; flex:1 }   /* sem isto um título comprido empurra o × para fora */
.up-sheet-titles h2 { font-size:var(--up-fs-lg); line-height:1.35; margin:0 }
.up-sheet-sub { color:var(--up-muted); font-size:var(--up-fs-sm); margin:2px 0 0;
  overflow-wrap:anywhere }
.up-sheet-head .up-actions { display:flex; gap:var(--up-sp-2); flex-wrap:wrap }

/* ---------------------------------------------------------------- o canal ao vivo (issue #221)

   Quem está nesta ficha (as pastilhas no cabeçalho), onde está cada um (a cor no campo), e o que
   mudou desde que a página abriu (a marca).

   A COR de cada pessoa vem do JavaScript, em `--up-live-cor`: é um hash do nome, portanto a mesma
   pessoa tem sempre a mesma cor em todos os ecrãs sem o servidor lha atribuir. Aqui só se diz
   ONDE ela se aplica — e por isso estas regras não têm cores próprias nenhumas. */
/* ⚠️ **Declarada aqui com um valor de omissão, e não só usada.** Um `var(--up-x)` sem declaração
   nenhuma fica sem valor e herda — e uma cor herdada parece uma escolha (é o
   `test_nao_ha_var_sem_declaracao`, que apanhou isto). Quem lhe põe o valor de verdade é o
   JavaScript, elemento a elemento; o que fica aqui é o que se vê se ele não chegar a correr. */
:root { --up-live-cor:var(--up-muted) }

.up-live-gente { flex:none; display:flex; align-items:center; gap:calc(var(--up-sp-1) * -1) }
.up-live-quem { width:26px; height:26px; border-radius:50%; display:inline-flex;
  align-items:center; justify-content:center; font-size:11px; font-weight:600; color:#fff;
  background:var(--up-live-cor, var(--up-muted)); border:2px solid var(--up-surface);
  margin-left:-6px; cursor:default; user-select:none }

/* O campo onde outra pessoa está AGORA. Um traço na margem e não um fundo: o fundo competia com
   o foco e com os estados de erro, que são do próprio campo e dizem coisas mais urgentes. */
.up-live-la { box-shadow:inset 3px 0 0 0 var(--up-live-cor, var(--up-accent)) }

/* A MARCA do que mudou desde que a página abriu. Apaga ao passar o rato — ver o `upsheet.js`.
   `currentColor` não serve aqui: a marca tem de se ver por igual num campo desactivado e num
   texto normal, e esses têm cores diferentes. */
.up-live-novo { position:relative; border-radius:var(--up-radius-sm);
  animation:up-live-acende 1.2s ease-out }
.up-live-novo::after { content:''; position:absolute; inset:-2px -4px; pointer-events:none;
  border-radius:inherit; background:var(--up-accent); opacity:.14 }
@keyframes up-live-acende { from { background:var(--up-accent); } to { background:transparent } }

/* Quem tem o movimento reduzido ligado não leva o pisca: a marca fica na mesma, parada. Uma
   animação que não se pode desligar é a diferença entre uma dica e uma distracção. */
@media (prefers-reduced-motion: reduce) { .up-live-novo { animation:none } }

/* Os dois botões da moldura: recuar (só quando há uma gaveta por baixo) e fechar. Redondos e
   sem fundo, para não competirem com as ações do próprio conteúdo. */
.up-sheet-nav { flex:none; width:30px; height:30px; display:inline-flex; align-items:center;
  justify-content:center; border:none; border-radius:50%; background:none; cursor:pointer;
  color:var(--up-muted); font:inherit; font-size:var(--up-fs-lg); line-height:1;
  transition:var(--up-transition) }
.up-sheet-nav:hover { background:var(--up-faint); color:var(--up-ink) }

/* O que rola é ISTO, e não a página — por isso um texto comprido lá dentro já não precisa da
   caixinha com `overflow` que cada módulo inventava. */
.up-sheet-body { flex:1; overflow-y:auto; padding:var(--up-sp-5) }
/* ⚠️ **O RODAPÉ ROLA NA HORIZONTAL quando as acções não cabem.** Pedido do @pmnmalo a 2026-08-22
   sobre os botões de estado de uma proposta: são oito, e num telefone os primeiros ficavam **fora
   do ecrã e inalcançáveis** — não havia como chegar ao botão que se queria.

   Três declarações, e cada uma tapa uma metade do problema:

   · `overflow-x:auto` — o que passa da largura rola AQUI, e não na página (é a mesma decisão do
     `.up-sheet-body`: o que rola é a caixa, não o documento);
   · `justify-content:safe flex-end` — **é esta que resolve o caso do telefone.** Com um
     `flex-end` simples, um conteúdo maior do que a caixa fica com o PRINCÍPIO cortado e sem
     maneira de o alcançar: o navegador não deixa rolar para trás do bordo inicial. O `safe` diz
     *«alinha à direita enquanto couber; quando não couber, alinha ao princípio»*, que é
     exactamente a diferença entre rolar e não rolar. A linha sem `safe` fica ANTES, como recuo
     para quem não a conheça;
   · `> * { flex:none }` — sem isto os botões encolhem em vez de transbordar, e em vez de um
     rodapé que rola aparece uma fila de pastilhas estreitas com o texto cortado. O `.estado` é a
     excepção (pode e deve encolher: é uma mensagem, não um alvo de clique).

   O `.estado` mantém o `margin-right:auto`, que é o que empurra os botões para a direita. */
.up-sheet-foot { flex:none; display:flex; align-items:center; gap:var(--up-sp-2);
  flex-wrap:wrap;
  justify-content:flex-end; justify-content:safe flex-end; overflow-x:auto;
  padding:var(--up-sp-3) var(--up-sp-5);
  border-top:1px solid var(--up-line) }
.up-sheet-foot > * { flex:none }
.up-sheet-foot:empty { display:none }
/* 🔴 **O RECADO tem LINHA PRÓPRIA, e antes tinha uma coluna.**

   Era `flex:0 1 auto; min-width:0` numa linha com `justify-content:flex-end`: partilhava a fila
   com as pastilhas do estado e **encolhia até quase nada** — o `flex-shrink:1` com `min-width:0`
   deixa-o ir a zero. Um aviso como *«este lugar tem dados; carrega outra vez para o apagar»* saía
   numa tira estreita à esquerda, com ar de coluna de tabela, e quem carregou no ✕ não o lia
   (@pmnmalo, 2026-09-01). Piorou sozinho quando o caminho de uma proposta passou de oito estados
   para dez: mais pastilhas, menos espaço para o recado.

   `flex:1 0 100%` dá-lhe a largura toda e o `order:-1` põe-no ACIMA das pastilhas — primeiro o
   que aconteceu, depois o que se pode fazer. É o `flex-wrap` no contentor que torna as duas
   coisas possíveis.

   ⚠️ **O `overflow-x:auto` FICA, e não é redundante.** Ele é de uma decisão anterior (2026-08-22):
   num telefone, os primeiros botões de estado ficavam fora do ecrã e **inalcançáveis**, e é o par
   `overflow-x` + `safe flex-end` que os torna alcançáveis. Com a quebra de linha isso quase nunca
   dispara — as pastilhas passam para a linha de baixo em vez de transbordar —, mas continua a
   valer para o caso que a quebra não resolve: um único item mais largo do que o rodapé.

   ⚠️ **`:empty` volta a escondê-lo.** Sem isso, uma linha de 100% vazia deixava o rodapé com o
   dobro da altura em todas as fichas que não têm recado nenhum — que são quase todas. */
.up-sheet-foot .estado { flex:1 0 100%; order:-1; min-width:0; text-align:left;
  font-size:var(--up-fs-sm); color:var(--up-ink) }
.up-sheet-foot .estado:empty { display:none }

/* Ecrã pequeno: a gaveta sobe do fundo e ocupa a largura toda — o mínimo de 560px num
   telemóvel de 390px seria a página inteira, sem se perceber que é um painel. */
@media (max-width:700px) {
  .up-sheet-bg { align-items:flex-end }
  .up-sheet { width:100%; height:auto; max-height:92vh; border-left:none;
    border-radius:var(--up-radius-lg) var(--up-radius-lg) 0 0; animation:up-sheet-up .2s ease-out }
  .up-sheet-head, .up-sheet-body, .up-sheet-foot { padding-left:var(--up-sp-4);
    padding-right:var(--up-sp-4) }
}

@keyframes up-fade { from { opacity:0 } to { opacity:1 } }
@keyframes up-sheet-in { from { transform:translateX(28px) } to { transform:none } }
@keyframes up-sheet-up { from { transform:translateY(28px) } to { transform:none } }
@media (prefers-reduced-motion:reduce) {
  .up-sheet-bg, .up-sheet { animation:none }
}

/* A FICHA — rótulo à esquerda, valor à direita. É o miolo de quase todas as gavetas, e era a
   grelha que os três módulos tinham copiado para o CSS local, cada um com o seu tamanho.
   Em ecrã estreito passa a rótulo POR CIMA do valor: duas colunas com "Contribution per
   proposal" à esquerda não deixavam nada para a direita. */
.up-fields { display:grid; grid-template-columns:max-content 1fr; gap:6px var(--up-sp-4);
  margin:0; font-size:var(--up-fs-sm) }
.up-fields dt { color:var(--up-muted) }
.up-fields dd { margin:0; min-width:0; overflow-wrap:anywhere }
@media (max-width:520px) {
  .up-fields { grid-template-columns:1fr; gap:0 }
  .up-fields dt { margin-top:var(--up-sp-3) }
  .up-fields dt:first-child { margin-top:0 }
}

.up-dialog-bg { position:fixed; inset:0; background:var(--up-veil); z-index:50;
  display:flex; align-items:center; justify-content:center; padding:var(--up-sp-4) }
.up-dialog-bg[hidden] { display:none }
.up-dialog { background:var(--up-white); border-radius:var(--up-radius-lg); box-shadow:var(--up-shadow-lg);
  max-width:440px; width:100%; padding:22px 24px }
.up-dialog h2 { font-size:var(--up-fs-lg); margin-bottom:var(--up-sp-2) }
.up-dialog p { color:var(--up-muted); margin:0 0 var(--up-sp-5) }
.up-dialog .up-actions { display:flex; gap:var(--up-sp-2); justify-content:flex-end }

/* Barra fixa no fundo, para guardar sem ter de rolar até baixo. */
.up-savebar { position:fixed; left:0; right:0; bottom:0; background:var(--up-white);
  border-top:1px solid var(--up-line); padding:10px var(--up-sp-5); z-index:25;
  display:flex; align-items:center; gap:var(--up-sp-3); justify-content:flex-end;
  box-shadow:0 -1px 3px rgba(17,20,24,.05) }
.up-savebar .estado { margin-right:auto; font-size:var(--up-fs-sm); color:var(--up-muted) }
@media (min-width:901px) { .up-savebar { left:var(--up-sidebar) } }

/* A mesma barra sem botões: só diz o que acabou de acontecer ("Guardado.", o erro da última
   chamada). É o rodapé das páginas de administração, que o tinham em CSS próprio. Vazia não
   ocupa espaço nenhum — senão fica uma tira branca no fundo de todas as páginas. */
.up-statusbar { position:fixed; left:0; right:0; bottom:0; background:var(--up-white);
  border-top:1px solid var(--up-line); padding:11px var(--up-sp-5); z-index:25;
  font-size:var(--up-fs-sm); color:var(--up-muted) }
.up-statusbar:empty { display:none }
@media (min-width:901px) { .up-statusbar { left:var(--up-sidebar) } }

/* 12. ------------------------------------------------------- Utilitários */
/* Poucos de propósito: se faltar um, quase sempre falta antes um componente. */
.up-stack > * + * { margin-top:var(--up-sp-3) }
.up-right { margin-left:auto }
.up-nowrap { white-space:nowrap }
/* Identificadores, caminhos, nomes de tabela: o que se lê carácter a carácter. */
.up-mono { font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:var(--up-fs-xs); color:var(--up-muted) }

/* Um BLOCO de texto cru para LER — o corpo de uma notificação, uma mensagem tal como chegou.
   Para um `<pre>`.
 *
 * **Não é o `.up-mono`, e a diferença é o ponto.** O `.up-mono` é `xs` e `muted` de propósito: um
 * identificador lê-se carácter a carácter e não compete com o texto à volta. Um corpo de email
 * lê-se de fio a pavio — pô-lo pequeno e cinzento passava o lint e piorava a leitura. Por isso
 * aqui o tamanho é de **corpo** e a cor é a do texto normal.
 *
 * As duas linhas que parecem detalhe e não são:
 *
 *   `pre-wrap`      um `<pre>` NÃO quebra linha. O corpo de uma notificação formal tem linhas
 *                   longas, e estas fichas abrem numa GAVETA — sem isto, transborda.
 *   `overflow-x`    para o que continua a não caber mesmo com quebra: uma tabela em texto, um
 *                   URL comprido. Rola dentro do bloco em vez de esticar a gaveta.
 *
 * É o mesmo modo de falha do `.up-when`: uma coisa que só se vê no sítio estreito.
 *
 * **O nome.** Chama-se `up-pre` porque foi o nome que alguém já lhe deu sozinho: o `ec_portal`
 * escreveu `class="up-pre"` num `<pre>` antes de esta classe existir. Um nome que a primeira
 * pessoa adivinha é melhor do que um nome bonito que ninguém procura. */
.up-pre { font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:var(--up-fs-md); color:var(--up-ink); line-height:1.5;
  white-space:pre-wrap; overflow-x:auto; tab-size:4;
  margin:0; padding:var(--up-sp-3);
  background:var(--up-grey-100); border:1px solid var(--up-line);
  border-radius:var(--up-radius) }

.up-hide { display:none !important }
@media (max-width:900px) { .up-hide-sm { display:none !important } }
/* O par do `.up-hide-sm`: some no ecrã GRANDE. Existe para a barra de cima ter duas versões no
   mesmo `<header>` — a compacta do telemóvel (gaveta, marca, lupa em ícone) e a inteira do
   desktop (marca com nome, procura, ícones) — sem duplicar o HTML por página nem por módulo
   (Projetix/projetix#266). */
@media (min-width:901px) { .up-hide-lg { display:none !important } }

/* A faixa de "estou a ver como outra pessoa" (projetix#55). Fixa no topo e com a cor de aviso:
   quem se esquece de que está noutra identidade tira conclusões erradas sobre o que "está mal
   na aplicação", e um ícone discreto não chega para o impedir. */
.up-viewas{position:sticky;top:0;z-index:60;display:flex;gap:.75rem;align-items:center;
  flex-wrap:wrap;padding:.5rem .9rem;background:var(--up-warn-bg);color:var(--up-warn);
  border-bottom:1px solid currentColor;font-size:.875rem}
.up-viewas strong{font-weight:600}
.up-viewas span{opacity:.85}
.up-viewas a{margin-left:auto}

/* ============================================================ A BARRA NÃO REPINTA (2026-08-24)

   Pedido do Bruno, à letra: *«comportamento de carregar no menu e mexer, parece-me que é porque faz
   refresh em vez de ser um site one page»*, e depois *«quero que faças a barra não repintar, é muito
   importante»*.

   ## O que isto faz, e o que NÃO faz

   Continua a haver um documento novo a cada clique — isto não é uma aplicação de página única. O que
   muda é o browser passar a **transportar a barra** de um documento para o outro: ela é fotografada
   antes e depois, e como lhe damos o mesmo `view-transition-name` nos dois, o browser trata-as como o
   MESMO elemento. Sem piscar, sem fade, sem saltar. O conteúdo é o que faz a transição.

   ## Porque é que isto chega, aqui

   Porque **o estado da barra vive na conta e não no browser** (o `upGuardarMenu` do `nav.html` grava
   os acordeões abertos em `/api/menu`, issue #22). Portanto o servidor desenha-a igual à que já lá
   estava, e o que faltava era só o browser não a piscar entre as duas. Numa aplicação que guardasse
   esse estado no cliente, isto não bastaria — era preciso não recarregar de todo.

   ## Porque não foi feita uma aplicação de página única

   Medido antes de decidir: **43 dos 54 templates** com `<script>` inline declaram `const`/`let`/`class`
   ao nível de topo. Trocar só o miolo e reexecutar esses scripts no MESMO documento dá
   `SyntaxError: redeclaration` na segunda visita à mesma página — uma página que fica em branco, em
   43 sítios, oito repositórios. Isso é um trabalho a sério e com um dono; isto são trinta linhas de
   CSS que degradam sozinhas (um browser sem suporte navega como sempre navegou).

   ⚠️ **O `view-transition-name` tem de ser ÚNICO na página.** A `.up-sidebar` e a `.up-main` são uma
   só cada; se algum dia houver duas, a transição desliga-se em silêncio para esse par — que é o modo
   de falha benigno, mas é bom saber que é esse. */
@view-transition { navigation: auto }

/* ⚠️ **Os dois `view-transition-name` NÃO estão aqui** — estão dentro dos blocos da `.up-sidebar` e
   da `.up-main`, lá em cima. Escrevê-los aqui era declarar o mesmo selector de topo duas vezes, que
   é exactamente o que a `projetix#190` proíbe e o `test_uma_classe_nao_se_declara_duas_vezes.py`
   recusa. *A regra apanhou-me a mim ao escrever isto, que é o melhor sinal de que serve.* */

/* A barra: nenhuma animação, e o retrato velho sai do caminho de imediato. Sem o `opacity:0` no
   `old`, os dois retratos ficam sobrepostos a cruzar-se — que é precisamente o piscar que isto
   existe para tirar. */
::view-transition-group(up-barra) { animation:none }
::view-transition-old(up-barra) { animation:none; opacity:0 }
::view-transition-new(up-barra) { animation:none; opacity:1 }

/* O conteúdo troca com um fundido curto. 120 ms é deliberadamente pouco: o que se quer é tirar o
   salto, não pôr uma animação a que se espera. */
@keyframes up-vt-sai { to { opacity:0 } }
@keyframes up-vt-entra { from { opacity:0 } }
::view-transition-old(up-conteudo) { animation:up-vt-sai 70ms ease-out both }
::view-transition-new(up-conteudo) { animation:up-vt-entra 120ms ease-in both }

/* Quem pediu menos movimento fica sem o fundido — e continua com a barra parada, que não é
   movimento nenhum: é a ausência dele. */
@media (prefers-reduced-motion:reduce) {
  ::view-transition-old(up-conteudo), ::view-transition-new(up-conteudo) { animation:none }
  ::view-transition-old(up-conteudo) { opacity:0 }
}

/* ============================================================ OS DASHBOARDS (`projetix#232`)

   Um dashboard é uma lista de BLOCOS, e um bloco é *uma leitura + uma forma + os eixos*. O que
   está aqui é a moldura — a grelha e o cartão de cada bloco. As FORMAS (o número, a tabela, e os
   gráficos que vêm a seguir) usam o vocabulário que já existe: `.up-kpi`, `.up-table`, `.up-meter`.

   ⚠️ **É o tema que desenha, e nenhum pixel vem de terceiros.** Decisão do Bruno a 2026-08-27,
   contra a alternativa de trazer uma biblioteca de gráficos: de fora vem só a matemática (escalas,
   marcas de eixos, posições de uma rede), e o desenho é daqui. O que isso compra não é só não ter
   uma dependência — é que **tudo o que alguém inventa continua a parecer a aplicação**, que é a
   razão que ele deu para preferir uma gramática ao desenho gerado por um modelo.

   A grelha é `auto-fill` com um mínimo, como a `.up-kpis`: no telemóvel passa a uma coluna sozinha,
   sem media query nenhuma. O `--up-block-span` é quantas colunas um bloco ocupa (1 a 3), e é uma
   variável no `style=` do elemento porque quem a decide é o bloco e não a folha.

   ⚠️ **O `min()` no `grid-column` não é zelo.** Um bloco de três colunas numa grelha que só coube
   com duas transbordava para fora do conteúdo — e num ecrã estreito a grelha tem UMA coluna, onde
   qualquer `span` maior do que 1 faz o mesmo. O `min()` corta o span ao que existe. */
.up-dash { display:grid; gap:var(--up-sp-4);
  grid-template-columns:repeat(auto-fill, minmax(280px, 1fr)) }
.up-block { background:var(--up-surface); border:1px solid var(--up-line);
  border-radius:var(--up-radius); padding:16px 18px; min-width:0;
  /* Declarado AQUI e não só como omissão do `var()`: um `var(--x, 1)` sem `--x` declarado em lado
     nenhum passa despercebido ao olho e é exactamente o que o
     `test_as_cores_estao_completas.py::test_nao_ha_var_sem_declaracao` recusa — e recusou esta
     linha na primeira versão. O `style=` de cada bloco ganha a isto, que é a ordem certa: quem
     decide a largura é o bloco. */
  --up-block-span:1;
  grid-column:span min(var(--up-block-span), 3) }
.up-block-head { display:flex; align-items:center; justify-content:space-between;
  gap:var(--up-sp-3); margin-bottom:var(--up-sp-3) }
/* As acções só aparecem com o rato em cima do bloco — mas ficam sempre visíveis a quem navega por
   teclado (`:focus-within`) e a quem usa toque, onde não há hover nenhum. Escondê-las sem esta
   segunda metade era torná-las inalcançáveis no telemóvel. */
.up-block-acoes { display:flex; gap:4px; opacity:0; transition:opacity 120ms ease }
.up-block:hover .up-block-acoes, .up-block:focus-within .up-block-acoes { opacity:1 }
.up-block-pe { margin-top:var(--up-sp-2) }
/* O `.up-block` inteiro é o cartão, portanto a tabela lá dentro não leva a sua própria borda. */
.up-block .up-table-wrap { margin:0 -18px -16px; padding:0 2px }
/* Um KPI dentro de um bloco não é um segundo cartão: o bloco já é o cartão. */
.up-block .up-kpi { background:none; border:none; padding:0 }

/* A ETIQUETA de uma entrada de menu — `AI` num dashboard pedido por palavras.
   Texto e não ícone, e a razão está no `NavEntry.etiqueta`: nenhum dos ícones do tema diz «isto foi
   feito por um modelo», e um escolhido por semelhança não se lê. */
.up-nav-etiqueta { margin-left:auto; flex:none; font-size:9px; font-weight:700; letter-spacing:.06em;
  padding:1px 5px; border-radius:var(--up-radius-pill);
  background:var(--up-teal-bg); color:var(--up-teal-700) }

@media (hover:none) { .up-block-acoes { opacity:1 } }

/* ---------------------------------------- AS FORMAS DE COMPARAÇÃO (`projetix#232`)

   Barras, linha e proporção. ⚠️ **Nenhum pixel vem de terceiros** — de fora vem só a matemática
   das marcas do eixo (`projetix.scales`), e o que chega aqui são **percentagens**: a plataforma
   não calcula tamanhos, porque não sabe em que largura de bloco o gráfico vai cair.

   ⚠️ **As barras são HTML e a linha é SVG, e a diferença tem razão.** Uma barra é um rectângulo
   com uma altura, e um `span` faz isso com o tipo de letra do tema e sem escalar nada. Uma linha
   precisa de um `polyline`, e um `viewBox` esticado (`preserveAspectRatio:none`) distorce tudo o
   que esteja lá dentro — por isso **os rótulos ficam em HTML, à volta**, e o SVG desenha só o
   traço, com `vector-effect` para ele não engordar com o esticão.

   A grelha e os números do eixo saem das MESMAS marcas: desenhá-las de fontes diferentes punha a
   linha do 20 num sítio e o «20» noutro. */
.up-chart { display:grid; grid-template-columns:auto 1fr; grid-template-rows:1fr auto;
  gap:0 var(--up-sp-2); height:180px }
.up-chart-y { position:relative; width:2.6em }
.up-chart-y span { position:absolute; right:0; transform:translateY(50%);
  font-size:var(--up-fs-xs); color:var(--up-muted); font-variant-numeric:tabular-nums;
  white-space:nowrap }
.up-chart-plot { position:relative; border-left:1px solid var(--up-line) }
.up-chart-grelha { position:absolute; left:0; right:0; height:1px; background:var(--up-line) }
/* A linha do ZERO é mais forte do que a grelha: com valores dos dois lados, é ela a referência
   contra a qual se lê o sinal — e não uma linha de grelha qualquer. */
.up-chart-grelha.zero { background:var(--up-grey-300) }
.up-chart-x { grid-column:2; display:flex; justify-content:space-between; gap:2px;
  padding-top:var(--up-sp-1); font-size:var(--up-fs-xs); color:var(--up-muted) }
.up-chart-x span { flex:1 1 0; min-width:0; text-align:center; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap }

/* AS BARRAS. Cada uma cresce a partir do zero — `bottom` é a base e `height` a distância até ao
   valor —, e é assim que um valor negativo desce sem precisar de uma regra própria. */
.up-bars { position:absolute; inset:0; display:flex; align-items:flex-end; gap:2px }
.up-bar { position:relative; flex:1 1 0; min-width:2px; background:var(--up-teal);
  border-radius:2px 2px 0 0 }
.up-bar:hover { background:var(--up-teal-700) }

/* A LINHA. Sem preenchimento por baixo: uma área diz «acumula», e uma série que não acumula
   desenhada como área afirma uma coisa que os dados não dizem. */
.up-line { position:absolute; inset:0; width:100%; height:100% }
.up-line polyline { fill:none; stroke:var(--up-teal); stroke-width:2; stroke-linejoin:round;
  stroke-linecap:round }

/* A PROPORÇÃO — uma barra empilhada e a legenda. Barra e não queijo: um queijo obriga a comparar
   ângulos, que é o que se faz pior, e o vocabulário da barra proporcional já existe (`.up-meter`).
   As cores são as oito ETAPAS, a única escala do tema com oito pares e com claro/escuro feitos. */
.up-parts { display:flex; height:22px; border-radius:var(--up-radius-pill); overflow:hidden;
  background:var(--up-faint) }
.up-parts > i { display:block; height:100%; min-width:2px }
.up-parts-legenda { list-style:none; margin:var(--up-sp-3) 0 0; padding:0;
  display:flex; flex-wrap:wrap; gap:var(--up-sp-2) var(--up-sp-4);
  font-size:var(--up-fs-sm) }
.up-parts-legenda li { display:flex; align-items:center; gap:6px; min-width:0 }
.up-parts-legenda i { width:10px; height:10px; border-radius:3px; flex:none }
.up-parts > i.etapa1, .up-parts-legenda i.etapa1 { background:var(--up-etapa1) }
.up-parts > i.etapa2, .up-parts-legenda i.etapa2 { background:var(--up-etapa2) }
.up-parts > i.etapa3, .up-parts-legenda i.etapa3 { background:var(--up-etapa3) }
.up-parts > i.etapa4, .up-parts-legenda i.etapa4 { background:var(--up-etapa4) }
.up-parts > i.etapa5, .up-parts-legenda i.etapa5 { background:var(--up-etapa5) }
.up-parts > i.etapa6, .up-parts-legenda i.etapa6 { background:var(--up-etapa6) }
.up-parts > i.etapa7, .up-parts-legenda i.etapa7 { background:var(--up-etapa7) }
.up-parts > i.etapa8, .up-parts-legenda i.etapa8 { background:var(--up-etapa8) }
.up-parts > i.etapa9, .up-parts-legenda i.etapa9 { background:var(--up-etapa9) }
.up-parts > i.etapa10, .up-parts-legenda i.etapa10 { background:var(--up-etapa10) }

/* A REDE — as ligações entre dois conjuntos (`projetix#232`).

   ⚠️ **Duas colunas e arcos, e não um grafo de força.** É a resposta à escala medida na issue
   (906 690 organizações, 821 107 participações): *«com mais de umas dezenas de nós dá uma bola de
   cabelo ilegível, com qualquer biblioteca»*. Duas colunas **não conseguem** virar uma bola de
   cabelo — o que não cabe corta-se, e o bloco diz quanto.

   Os nomes são HTML (tipo de letra do tema, seleccionáveis, sem escalar) e só os arcos são SVG,
   pela mesma razão da linha: um `viewBox` esticado distorce tudo o que tenha texto lá dentro. */
.up-rede { display:grid; grid-template-columns:minmax(0,1fr) minmax(60px,1.2fr) minmax(0,1fr);
  gap:var(--up-sp-2); height:220px }
.up-rede-lado { position:relative; font-size:var(--up-fs-xs) }
.up-rede-lado span { position:absolute; right:0; transform:translateY(-50%); max-width:100%;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.up-rede-lado.direita span { right:auto; left:0 }
.up-rede-arcos { width:100%; height:100% }
.up-rede-arcos path { fill:none; stroke:var(--up-teal); opacity:.55; stroke-linecap:round }
.up-rede-arcos path:hover { opacity:1; stroke:var(--up-teal-700) }
/* ------------------------------------------------- O SEGUNDO NÍVEL DE NAVEGAÇÃO (`.up-subnav`)
   Uma coluna estreita entre a barra global e o conteúdo, para as páginas DE DENTRO de um módulo.

   ⚠️ **Existe por uma medição, e o problema não era estético.** O Admin tem 16 páginas e a barra
   global achatava-as numa lista de 1239px dentro de um scroller de 431px (viewport 1440x700),
   **sem barra de scroll visível** — no macOS ela só aparece enquanto se rola. Resultado medido:
   nunca mais de 9 dos 17 links alcançáveis, e no `/admin` apenas **1**. A barra nova nem os mostra:
   o `/admin` era o link 126 de 137 na gaveta e o `/admin/llm` não estava lá (#230, #231).

   **O agrupamento não é novo: as entradas já declaram `seccao=`** — no admin dão Overview, People
   (3), Access (2), Data (4) e Operations (6). A barra global passa a mostrar só o nível 1 e é aqui
   que os 16 vivem, agrupados, sem rolar.

   🔴 **Vive DENTRO do `<main>`, e isso é deliberado.** O `upnav.js` troca só o `<main>` e nunca
   toca na barra lateral — é o que lhe preserva o scroll. Uma subnav FORA do `<main>` sobrevivia à
   navegação e ficava a mostrar as páginas do módulo ANTERIOR, sem dar erro. Dentro, actualiza-se
   com o conteúdo; e não tem estado próprio para perder (o único é qual está activa, e essa vem da
   página).

   ⚠️ **O `min-width:0` no `.up-content` não é cosmético.** Um filho de flex não encolhe abaixo do
   seu conteúdo por omissão, e sem ele uma tabela larga empurra a página inteira — que é exactamente
   o overflow horizontal que o `.up-table-wrap` existe para não haver. */
.up-main.tem-subnav { display:flex; gap:var(--up-sp-5); align-items:flex-start }
.up-main.tem-subnav > .up-content { margin:0; flex:1 1 auto; min-width:0 }
/* A largura é FIXA e não do conteúdo: sem ela a coluna mudava de largura entre páginas do mesmo
   módulo (os rótulos têm comprimentos diferentes) e o conteúdo saltava de sítio a cada clique. */
.up-subnav { flex:none; width:180px; display:flex; flex-direction:column; gap:1px }
.up-subnav-grupo { font-size:var(--up-fs-xs); font-weight:650; letter-spacing:.08em;
                   text-transform:uppercase; color:var(--up-grey-500);
                   padding:var(--up-sp-3) 10px var(--up-sp-1) }
.up-subnav-grupo:first-child { padding-top:0 }
.up-subnav a { display:flex; align-items:center; gap:9px; padding:7px 10px;
               border-radius:var(--up-radius); color:var(--up-nav-ink);
               text-decoration:none; font-size:var(--up-fs-sm); white-space:nowrap }
.up-subnav a:hover { background:var(--up-faint); color:var(--up-ink) }
.up-subnav a.on { background:var(--up-teal); color:var(--up-on-accent); font-weight:600 }
.up-subnav a svg { width:16px; height:16px; flex:none; color:var(--up-grey-500) }
.up-subnav a.on svg { color:var(--up-on-accent) }

/* ⚠️ **ESTE BLOCO TEM DE VIR DEPOIS das regras base, e a razão é ordem e não especificidade.**
   Uma media query NÃO acrescenta especificidade: com as regras base declaradas mais abaixo no
   ficheiro, elas ganhavam a estas e a faixa continuava em coluna. Medido a 375px antes de mudar:
   `flex-direction: column`, 783px de altura, e a página a transbordar 24px — a coluna a comer meio
   telemóvel e o conteúdo cortado. */
@media (max-width:900px) {
  /* O SEGUNDO NÍVEL num ecrã estreito: faixa horizontal por cima do conteúdo, e não uma terceira
     coluna — abaixo dos 901px a barra global já é gaveta e não há largura para duas colunas.
     Os rótulos de grupo ficam em linha com os itens, para a faixa ter uma altura só. */
  .up-main.tem-subnav { display:block }
  .up-main.tem-subnav > .up-content { margin:0 auto }
  /* O `width:auto` desfaz os 180px da coluna: em faixa ela ocupa a largura do conteúdo. Sem
     ele a faixa ficava com 180px e cortava o segundo item a meio da palavra. */
  .up-subnav { width:auto; flex-direction:row; align-items:center; gap:var(--up-sp-1);
               overflow-x:auto; margin-bottom:var(--up-sp-4);
               border-bottom:1px solid var(--up-line); padding-bottom:var(--up-sp-2) }
  .up-subnav-grupo { padding:0 var(--up-sp-1) 0 var(--up-sp-3) }
  .up-subnav-grupo:first-child { padding-left:0 }
}

/* ══ O CLIENTE DE CORREIO ═════════════════════════════════════════════════════════════════
   As três colunas — caixas, lista, mensagem — e o que vive dentro delas.

   🔴 **Vem do estático que ele aprovou a 2026-09-10**, e é a razão de estar aqui inteiro em vez
   de crescer aos bocados: o desenho foi fechado com ele antes de existir uma linha de código, e
   *«o estático é a especificação»*. O que mudou no caminho foram só os NOMES — lá eram `.cliente`
   e `.fio`, soltos, porque era um ficheiro isolado; aqui `.up-*` é a promessa pública da casa
   (ver o `CLAUDE.md` deste repositório), e um `.fio` sem prefixo colidia com qualquer módulo que
   escrevesse uma classe com esse nome.

   ⚠️ **A coluna das caixas colapsa com `<details>`, e o `<summary>` NÃO leva `display:flex`.** No
   WebKit isso mata o acordeão — está medido, e foi ele que o apanhou noutra página: *«carrego no
   grupo e ando para trás»*. O flex vive num `<span>` lá dentro (`.li`).

   ⚠️ **E a tira estreita é um `<input type=checkbox>` + label, sem JS.** Um `☰` que dependesse de
   JavaScript deixava a coluna presa aberta enquanto o script não chegasse — e esta coluna é a
   navegação do cliente inteiro.
   ═════════════════════════════════════════════════════════════════════════════════════════ */
/* O SINAL na lista: deu factos / lida e nada / por ler. TRÊS estados, ver a nota da aba. */
.up-cliente .up-fio .up-sinal {margin-left:auto;font-size:11px;flex:none}
.up-sinal.tem {color:var(--up-teal);font-weight:700}
.up-sinal.nada {color:var(--up-grey-300)}
.up-sinal.por-ler {color:var(--up-grey-300);font-size:15px;line-height:1}
/* OS FACTOS junto da mensagem */
.up-cliente .up-extraido {margin-top:18px;padding:13px 15px;border:1px solid var(--up-teal);
  border-radius:10px;background:var(--up-teal-bg)}
.up-cliente .up-rot-ex {display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px;
  font-size:12.5px}
.up-cliente .up-rot-ex .ic {width:15px;height:15px;color:var(--up-teal-700)}
.up-cliente .up-rot-ex a {margin-left:auto}
.up-facto.compacto {background:var(--up-surface);padding:11px 13px;margin-bottom:8px}
.up-facto.compacto:last-child {margin-bottom:0}
.up-facto.compacto .campos {font-size:12.5px}
.up-facto.compacto .acs {margin-top:9px;padding-top:9px}
.up-facto {border:1px solid var(--up-line);border-radius:10px;background:var(--up-surface);
  padding:13px 15px}
.up-facto .cab {display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:10px}
.up-facto .cab .orig {display:inline-flex;align-items:center;gap:5px;font-size:11.5px;
  color:var(--up-muted)}
.up-facto .cab .orig .ic {width:14px;height:14px}
.up-facto .cab .quando {margin-left:auto}
.up-facto .campos {margin:0}
.up-facto .donde {margin-top:10px;font-size:12.5px;display:flex;gap:6px;flex-wrap:wrap;
  align-items:baseline}
.up-facto .acs {display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin-top:12px;
  padding-top:11px;border-top:1px solid var(--up-line)}
.up-facto .acs .auto {margin-left:auto}
.up-cliente .up-caixas .up-cx-px {padding-left:30px;font-size:12.5px}
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .up-cx-px {padding-left:0;justify-content:center}
/* ⚠️ Os dois itens de topo levam ÍCONES DO TEMA e não um `content:"✉"` — *«os icons é q não são
   fixes do all qdo colapsa»*. Um glifo de texto muda de forma e de peso com a fonte do sistema;
   encolhida a coluna a 54px, é o ícone que fica a ser a única coisa legível. */
.up-cliente .up-caixas .ic {width:15px;height:15px;flex:none;color:var(--up-muted)}
.up-cliente .up-caixas .up-cx-todas.on .ic {color:var(--up-teal)}
/* A COLUNA ENCOLHIDA — «deixar tipo uma coluna estreita». Sem JavaScript: um `checkbox`
   escondido e um `label`, o mesmo mecanismo das abas deste ficheiro. */
.up-rail-ch {position:absolute;opacity:0;width:0;height:0}
.up-cliente .up-rail-bt {display:flex;align-items:center;gap:8px;padding:8px 12px;cursor:pointer;
  font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--up-muted);
  border-bottom:1px solid var(--up-line);margin-bottom:6px;user-select:none}
.up-cliente .up-rail-bt:hover {background:var(--up-line);color:var(--up-ink)}
.up-cliente .up-rail-bt .ico {font-size:13px}
.up-rail-ch:checked + .up-cliente-rolo .up-cliente {grid-template-columns:54px minmax(272px,332px) 1fr}
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .nm,
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .n,
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .up-pasta,
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .up-pe-caixas,
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .seta {display:none}
.up-rail-ch:checked + .up-cliente-rolo .up-caixas a,
.up-rail-ch:checked + .up-cliente-rolo .up-caixas > .up-grupo-cx > summary > .li {justify-content:center}
.up-rail-ch:checked + .up-cliente-rolo .up-caixas .up-cx-todas::before {content:"\2709";
  color:var(--up-muted);font-size:15px}
.up-rail-ch:checked + .up-cliente-rolo .up-rail-bt {justify-content:center}
/* 🔴 **O cliente ROLA em vez de empilhar, e isto foi um defeito medido.** As três colunas
   empilhavam abaixo de 880px, e num painel estreito ele deixava de ver um cliente de correio —
   via três blocos por cima uns dos outros: *«não percebo o colapsar, não me aparece nada»*.

   ⚠️ **Um layout que se desfaz num painel estreito não se APROVA num painel estreito.** Um
   desenho de três colunas precisa de largura; o que ele tem de ver é o desenho, e não a degradação
   dele. Portanto a caixa tem largura mínima e o contentor rola na horizontal — a regra da casa
   para conteúdo largo, e a mesma que as tabelas usam.

   A degradação para uma coluna fica só para o TELEMÓVEL a sério (≤640px), onde rolar de lado é
   pior do que empilhar. */
.up-cliente-rolo {overflow-x:auto;padding-bottom:4px}
.up-cliente .up-caixas .up-grupo-cx {margin-top:8px;padding-top:6px;border-top:1px solid var(--up-line)}
/* 🔴 **O `<summary>` NÃO leva `display:flex`, e isto foi um defeito medido no ecrã dele.**
   No WebKit um `summary` com `display:flex` deixa de ser o gatilho do `<details>`: o marcador
   desaparece e **o clique não abre nada** — *«continuo a não conseguir colapsar as caixas, pq?»*.
   E não dá erro: o elemento fica lá, com o cursor de mão, sem fazer nada.

   ⚠️ **A saída é um `<span>` por dentro.** O `summary` fica em `display:block` (o que ele é) e a
   flexbox vai para o filho, que é onde ela era precisa. O marcador esconde-se pelo `::marker` e
   pelo `::-webkit-details-marker` — os dois, porque os motores não concordam em qual usam. */
.up-cliente .up-caixas .up-grupo-cx > summary {display:block;padding:6px 12px;cursor:pointer;
  font-size:12.5px;font-weight:600;list-style:none}
.up-cliente .up-caixas .up-grupo-cx > summary::marker {content:""}
.up-cliente .up-caixas .up-grupo-cx > summary::-webkit-details-marker {display:none}
.up-cliente .up-caixas .up-grupo-cx > summary:hover {background:var(--up-line)}
.up-cliente .up-caixas .up-grupo-cx > summary > .li {display:flex;align-items:center;gap:7px}
.up-cliente .up-caixas .up-grupo-cx > summary .seta {flex:none;color:var(--up-muted);font-size:13px;
  transition:transform .12s}
.up-cliente .up-caixas .up-grupo-cx[open] > summary .seta {transform:rotate(90deg)}
/* A COLUNA DAS CAIXAS — a pasta debaixo da caixa dela, como num cliente de correio.
   ⚠️ A largura está na ÚNICA declaração de `.up-cliente`, mais abaixo: duas declarações do mesmo
   selector ao nível de topo davam duas grelhas concorrentes, e ganhava a de baixo — foi o que
   partiu esta aba («isto tá um disparate total»). */
.up-cliente .up-caixas {border-right:1px solid var(--up-line);padding:8px 0;overflow:auto;
  max-height:70vh;background:var(--up-faint)}
.up-cliente .up-caixas a {display:flex;align-items:center;gap:7px;padding:6px 12px;
  text-decoration:none;color:var(--up-ink);font-size:13px}
.up-cliente .up-caixas a:hover {background:var(--up-line)}
.up-cliente .up-caixas .nm {min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.up-cliente .up-caixas .n {flex:none;color:var(--up-muted);font-size:11.5px;
  font-variant-numeric:tabular-nums}
.up-cliente .up-caixas .n.destaque {color:var(--up-teal);font-weight:700}
.up-cliente .up-caixas .up-cx-todas {font-weight:600}
.up-cliente .up-caixas .up-cx-todas.on {background:var(--up-teal-bg);color:var(--up-teal-700);
  box-shadow:inset 3px 0 0 var(--up-teal)}
.up-cliente .up-caixas .cx {font-weight:600;font-size:12.5px}
.up-cliente .up-caixas .up-pasta {padding-left:30px;font-size:12.5px;color:var(--up-muted)}
.up-cliente .up-caixas .up-pasta:hover {color:var(--up-ink)}
.up-cliente .up-caixas .up-pasta.apagada {opacity:.55;font-style:italic}
.up-cliente .up-caixas .up-pe-caixas {display:flex;gap:6px;align-items:baseline;padding:12px 12px 4px;
  margin-top:8px;border-top:1px solid var(--up-line);font-size:12px}
.up-cliente .up-fio .up-fio-n {display:inline-flex;align-items:center;justify-content:center;
  min-width:17px;height:17px;padding:0 4px;border-radius:9px;background:var(--up-grey-100);
  color:var(--up-muted);font-size:10.5px;font-weight:700;flex:none}
.up-cliente .up-fio.aberto .up-fio-n {background:var(--up-teal);color:var(--up-on-accent)}
.up-cliente .up-conversa {margin-top:20px;padding-top:16px;border-top:1px solid var(--up-line)}
.up-cliente .up-rot-conv {font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--up-muted);margin-bottom:8px;display:flex;gap:8px;align-items:baseline}
.up-cliente .up-msg-antiga {border:1px solid var(--up-line);border-radius:8px;padding:9px 12px;
  margin-bottom:7px;background:var(--up-faint)}
.up-cliente .up-msg-antiga > summary {cursor:pointer;display:flex;align-items:center;gap:8px;
  font-size:13px;flex-wrap:wrap}
.up-cliente .up-msg-antiga p {margin:10px 0 2px;font-size:13.5px;line-height:1.55}
.up-cliente .up-msg-antiga .up-avatar {width:22px;height:22px;font-size:11px}
/* a BARRA DOS NÚMEROS — «isto é tudo?» */
.up-barra-nums {display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12.5px;
  color:var(--up-muted);padding:8px 12px;margin-bottom:12px;
  background:var(--up-surface);border:1px solid var(--up-line);border-radius:8px}
.up-barra-nums strong {color:var(--up-ink)}
.up-barra-nums .sep {opacity:.4}
.up-barra-nums .pag {margin-left:auto;display:flex;align-items:center;gap:6px}
.up-barra-nums .up-chip {padding:2px 9px;font-size:12px}
/* O CLIENTE DE CORREIO: lista à esquerda, conteúdo à direita. */
.up-cliente {display:grid;gap:0;min-width:880px;
  grid-template-columns:minmax(196px,224px) minmax(272px,332px) 1fr;
  background:var(--up-surface);border:1px solid var(--up-line);border-radius:12px;
  overflow:hidden;min-height:560px}
.up-cliente .up-fios {border-right:1px solid var(--up-line);overflow:auto;max-height:70vh}
.up-cliente .up-fio {display:block;padding:10px 13px;border-bottom:1px solid var(--up-line);
  text-decoration:none;color:inherit}
.up-cliente .up-fio:hover {background:var(--up-faint)}
.up-cliente .up-fio.aberto {background:var(--up-teal-bg);box-shadow:inset 3px 0 0 var(--up-teal)}
.up-cliente .up-fio .l1 {display:flex;align-items:center;gap:7px;font-size:12.5px}
.up-cliente .up-fio .quem {color:var(--up-ink);font-weight:600;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.up-cliente .up-fio.nova .quem::before {content:"";display:inline-block;width:7px;height:7px;
  border-radius:50%;background:var(--up-teal);margin-right:6px;vertical-align:1px}
.up-cliente .up-fio .quando {margin-left:auto;color:var(--up-muted);font-size:11.5px;flex:none}
.up-cliente .up-fio .l2 {font-size:13px;margin-top:2px;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.up-cliente .up-fio.nova .l2 {font-weight:650}
.up-cliente .up-fio .clipe {color:var(--up-muted);font-size:11px}
.up-cliente .up-fio .l3 {font-size:12px;color:var(--up-muted);margin-top:2px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.up-cliente .up-fio .l4 {display:flex;align-items:center;gap:8px;margin-top:5px}
.up-cliente .corpo {padding:18px 22px;overflow:auto;max-height:70vh}
.up-cliente .up-cab-msg h2 {margin:0 0 12px;font-size:19px;line-height:1.25}
.up-cliente .up-linha-de {display:flex;align-items:flex-start;gap:11px}
.up-cliente .up-linha-de .qd {min-width:0;flex:1}
.up-cliente .up-linha-de .qd strong {display:inline-block;margin-right:6px}
.up-cliente .up-linha-de .acs {display:flex;gap:6px;flex:none}
.up-cliente .up-entregue {display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  margin:14px 0 0;padding:9px 11px;background:var(--up-faint);border-radius:8px}
.up-cliente .up-entregue .rot {font-size:10px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--up-muted)}
.up-cliente .up-anexos {display:flex;gap:8px;flex-wrap:wrap;margin:14px 0 0}
.up-cliente .up-anexo {display:inline-flex;align-items:center;gap:8px;padding:7px 11px;
  border:1px solid var(--up-line);border-radius:8px;text-decoration:none;color:inherit;
  font-size:12.5px}
.up-cliente .up-anexo:hover {border-color:var(--up-teal);background:var(--up-teal-bg)}
.up-cliente .texto {margin-top:18px;padding-top:16px;border-top:1px solid var(--up-line);
  max-width:70ch;font-size:14px;line-height:1.6}
.up-cliente .texto p {margin:0 0 11px}
