/* =============================================================================
   Spark — folha de estilos partilhada da plataforma
   =============================================================================

   Servida estaticamente a partir de public/ (não há build de assets). O link
   está em templates/_head.html.twig com ?v= a apontar para a versão em
   execução, porque o servidor de produção serve ficheiros antigos enquanto
   puder — ver deploy/README.md.

   ORGANIZAÇÃO
     1. Design tokens (dois temas: cyber = escuro, widgets = claro)
     2. Base e tipografia
     3. Cabeçalho partilhado + menu hamburguer
     4. Componentes comuns: painéis, botões, tabelas, etiquetas, formulários,
        modais, avisos, carregamento

   TEMAS
     O :root traz o tema "cyber" (escuro), que é o tema base da plataforma.
     O html[data-theme="widgets"] traz o tema claro, que é o predefinido para
     quem nunca escolheu nada — o public/js/spark.js aplica-o antes do primeiro
     pixel ser pintado. O html[data-theme="custom"] lê as cores que o utilizador
     desenhou no seletor de cores das Iniciativas.

     Regra a respeitar: nada aqui usa cores fixas em código a não ser dentro
     dos blocos de tokens. Assim uma página nova nasce já com os dois temas.

   NOMES DE CLASSES
     O prefixo spark- marca o que é partilhado e evita colisões com os nomes que
     as páginas já usavam (.header, .logo, .modal, .toast existem, com outro
     significado, dentro das Iniciativas). As excepções são .hamburger-* e
     .badge-migracoes: esses nomes estão presos ao JavaScript e ao
     _hamburger_menu.html.twig, e renomeá-los partia o menu.
   ========================================================================== */


/* =============================================================================
   0. KIT DE CONTROLOS — as normas, escritas uma vez

   Um controlo é qualquer coisa que se toca numa barra ou num formulário: um
   botão, um select, um campo, um interruptor, um contador. Antes de isto
   existir, a barra das Iniciativas tinha QUATRO fundos (transparente, azul a
   10%, branco e #f3f6f9), QUATRO cores de contorno e TRÊS raios de canto — e
   dois desses azuis eram o acento do tema ESCURO escrito à mão dentro de
   regras que corriam no tema claro.

   Cada controlo novo escolhia sozinho, e a barra lia-se como cinco barras.
   Estas fichas são a resposta: quem escreve um controlo usa-as e não decide
   nada. Quem precisa de decidir alguma coisa, decide-a AQUI, uma vez.

   São só três estados:

     NEUTRO   — o que a maioria é. Fundo de painel, contorno da grelha, texto
                de corpo. Um select, um campo de procura, um botão secundário.
     ACENTO   — a acção PRINCIPAL de um ecrã, e só uma por ecrã. Se houver duas
                a azul, nenhuma é a principal.
     PERIGO   — o que apaga. Nunca é o que está mais à mão.

   O raio interno é para o que vive DENTRO de outro controlo: os separadores
   dentro do tabuleiro das vistas. Um canto de 8 dentro de outro de 8 encosta
   e desenha uma linha dupla.
   ========================================================================== */

:root {
    --controlo-altura: 36px;
    --controlo-altura-toque: 44px;
    --controlo-raio: 8px;
    --controlo-raio-interno: 6px;
    --controlo-espaco: 6px;

    /* NEUTRO.
       O texto de um controlo é SEMPRE o --text-primary: o que se escreveu ou
       escolheu é conteúdo, e conteúdo lê-se a preto. Andava aqui um cinzento
       (--text-body) que é para texto corrido, e o resultado eram valores
       cinzentos ao lado de valores pretos na mesma linha.
       O cinzento fica para o RÓTULO, que é o que acompanha e não o que se lê. */
    --controlo-fundo: var(--bg-panel);
    --controlo-borda: var(--border-color);
    --controlo-texto: var(--text-primary);
    --controlo-rotulo: var(--text-muted);
    --controlo-fundo-hover: var(--fill-hover);
    --controlo-borda-hover: var(--accent-blue-line);

    /* ACENTO */
    --controlo-fundo-acento: var(--accent-blue-soft);
    --controlo-borda-acento: var(--accent-blue-line);
    --controlo-texto-acento: var(--text-link);

    /* PERIGO.
       O --perigo-cheio é o vermelho SÓLIDO, para quando o texto por cima é
       branco — um contador, uma etiqueta. Não é o mesmo que o --text-danger:
       esse é vermelho para se LER sobre o fundo da página, e por isso tem de ser
       claro no tema escuro; este é vermelho para o branco se ler EM CIMA dele, e
       por isso tem de ser escuro nos dois. São requisitos opostos, e durante um
       tempo foram o mesmo token: clarear o texto piorava o contador.

       Um valor só para os três temas, e não um por tema: tinha aqui um #b42318
       que era código morto — o bloco do tema escuro é também um :root, com a
       mesma especificidade e mais abaixo no ficheiro, por isso ganhava sempre.
       O #c1272d deixa ler o branco por cima (5.84:1) e destaca-se do painel
       quase preto por baixo (3.24:1, e para superfícies o mínimo é 3). */
    --controlo-perigo-cheio: #c1272d;
    /* O AZUL SÓLIDO, o par do de cima. O --accent-blue é o azul da marca, e é
       claro: o branco por cima dele fica a 2,69:1 e não se lê. Este mede 5,73
       com o branco e 3,31 contra o painel quase preto por baixo — quase o
       mesmo perfil do vermelho, para os dois lados do gesto de arrastar uma
       nota se lerem igualmente bem. */
    --controlo-primario-cheio: #0f6d99;
    /* E o verde sólido, o terceiro do conjunto. O --text-success é #00dd77 no
       tema escuro — verde de néon, feito para se ler como texto: o branco por
       cima dele fica a 1,7:1. Este mede 5,93 com o branco e 3,19 contra o
       painel, a par dos outros dois. */
    --controlo-sucesso-cheio: #17724a;
    --controlo-fundo-perigo: var(--accent-red-soft);
    --controlo-borda-perigo: var(--accent-red-line);
    --controlo-texto-perigo: var(--text-danger);

    /* CAMADAS.
       Cada número escrito à mão num z-index é uma aposta sobre o que mais há na
       página — e perde-se. O diálogo que pergunta "queres deixar um comentário?"
       estava na 500 e o modal que o abriu na 2000: aparecia POR TRÁS de quem o
       tinha chamado, e só se via ao fechar o modal. O aviso flutuante estava na
       999, também por baixo.

       A ordem é a ordem da conversa: o que está por cima é o que acabou de
       falar, e o que bloqueia está sempre acima do que bloqueou. */
    --camada-menu: 60;         /* um menu que cai de um botão */
    --camada-cabecalho: 200;   /* o cabeçalho colado ao topo */
    --camada-modal: 2000;      /* uma janela sobre a página */
    --camada-dialogo: 3000;    /* uma pergunta sobre uma janela */
    --camada-aviso: 4000;      /* o que informa e não bloqueia: vê-se sempre */
}


/* -----------------------------------------------------------------------------
   0.1 SEPARADORES E PROCURA — dois controlos que estavam escritos numa página só

   O .vista-btn e a .procura viviam dentro do <style> do /tarefas. Quem os
   reutilizou noutra página herdou o NOME e não o desenho: no modal do correio
   das Iniciativas os três filtros saíam com o botão nativo do sistema — no
   Safari do iPhone, cápsulas cinzentas ao lado de campos de cantos quadrados —
   e a lupa da procura, sem o position:relative do envelope, flutuava por cima
   do campo em vez de estar dentro dele.

   Não é um descuido de uma página: uma classe usada em duas páginas é
   partilhada, e o sítio de uma classe partilhada é aqui.
   -------------------------------------------------------------------------- */

/* Um separador: escolhe entre vistas, filtros ou estados que se excluem.
   Leva o raio normal — está solto numa barra, não dentro de um tabuleiro. O
   raio interno é para o .view-tab, que vive mesmo dentro de um. */
.vista-btn {
    box-sizing: border-box;
    font: inherit; font-size: 13px; line-height: 1;
    min-height: var(--controlo-altura);
    padding: 0 14px;
    cursor: pointer;
    border: 1px solid var(--controlo-borda);
    border-radius: var(--controlo-raio);
    background: transparent;
    color: var(--controlo-texto);
    -webkit-appearance: none; appearance: none;
}
.vista-btn:hover { background: var(--controlo-fundo-hover); }

/* O azul de texto sobre o azul esbatido do fundo dava 4,12:1 — o fundo também
   é azul, e os dois aproximam-se. Com o fundo do painel, o separador aceso
   destaca-se pela borda e pela cor, e lê-se. */
.vista-btn.activa {
    background: var(--bg-panel);
    border-color: var(--controlo-borda-acento);
    color: var(--controlo-texto-acento);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.10);
}

/* Um campo de procura com a lupa DENTRO. O envelope é que posiciona: sem o
   position:relative, o absolute da lupa procura o antepassado seguinte e vai
   parar ao canto do modal. */
.procura { position: relative; display: block; }
.procura svg {
    position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
    color: var(--controlo-rotulo); pointer-events: none;
}
.procura input {
    box-sizing: border-box;
    width: 100%;
    min-height: var(--controlo-altura);
    padding-left: 34px; padding-right: 32px;
}
.procura .limpar {
    position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    display: none; background: none; border: none; cursor: pointer;
    color: var(--controlo-rotulo); font-size: 15px; line-height: 1;
    padding: 4px 6px; border-radius: var(--controlo-raio-interno);
}
.procura.com-texto .limpar { display: block; }

/* Ao toque, tudo o que se toca tem 44px. O .spark-select andava de fora e
   ficava a 42 no painel de leitura do correio — a fasquia é do próprio
   projecto, e um controlo que a falha por dois pixéis falha-a na mesma. */
@media (hover: none) {
    .vista-btn, .procura input, .spark-select, .spark-input {
        min-height: var(--controlo-altura-toque);
    }
}

/* -----------------------------------------------------------------------------
   0.2 O CALENDÁRIO DO SPARK

   O que caía dos campos de data era o calendário do browser: um objecto
   cinzento do sistema no meio de um ecrã que é todo do Spark, e ao qual o CSS
   não chega — é interface do Safari, não da página.

   O campo continua a ser um <input type="date">: o que muda é quem desenha o
   calendário. Esconder o ícone nativo é o que tira o do sistema do caminho —
   só ele o abre. Escrever a data à mão nos segmentos continua a funcionar, e
   para quem sabe a data é mais rápido do que qualquer calendário.

   Num telemóvel não se mexe: ali o picker nativo ocupa o ecrã todo, tem roda e
   é o que a pessoa já sabe usar.
   -------------------------------------------------------------------------- */
@media (hover: hover) {
    input[type="date"]::-webkit-calendar-picker-indicator {
        display: none;
        -webkit-appearance: none;
    }
    /* Sem o ícone do sistema, o campo deixava de se anunciar como data: ficava
       um rectângulo com números lá dentro. Este é desenhado por nós, herda a
       cor do texto e não recebe cliques — quem carrega, carrega no campo. */
    input[type="date"] {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238a96a6' stroke-width='2' stroke-linecap='round'%3E%3Crect x='3' y='5' width='18' height='16' rx='2'/%3E%3Cpath d='M3 10h18M8 3v4M16 3v4'/%3E%3C/svg%3E");
        background-repeat: no-repeat;
        background-position: right 10px center;
        background-size: 14px 14px;
        padding-right: 32px;
    }
}

.spark-cal[hidden] { display: none; }
.spark-cal {
    position: absolute;
    z-index: var(--camada-dialogo);
    width: 268px; padding: 10px;
    background: var(--bg-panel);
    border: 1px solid var(--controlo-borda);
    border-radius: var(--controlo-raio);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
    font-size: 12.5px;
    -webkit-user-select: none; user-select: none;
}

.spark-cal-topo {
    display: flex; align-items: center; gap: 2px;
    margin-bottom: 8px;
}
.spark-cal-mes {
    flex: 1 1 auto; text-align: center;
    font-weight: 700; font-size: 13px;
    color: var(--controlo-texto);
}
.spark-cal-nav {
    flex: 0 0 auto;
    width: 26px; height: 26px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 1px solid transparent;
    border-radius: var(--controlo-raio-interno);
    color: var(--controlo-rotulo);
    font: inherit; font-size: 15px; line-height: 1; cursor: pointer;
}
.spark-cal-nav:hover {
    background: var(--controlo-fundo-hover);
    border-color: var(--controlo-borda);
    color: var(--controlo-texto);
}

/* Sete colunas iguais, em cima os nomes e em baixo os dias: as duas grelhas
   têm de ser a mesma, senão a coluna do sábado não fica por baixo do "S". */
.spark-cal-semana,
.spark-cal-dias {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}
.spark-cal-semana span {
    padding: 2px 0; text-align: center;
    font-size: 10.5px; font-weight: 700;
    color: var(--controlo-rotulo);
}

.spark-cal-dia {
    height: 30px; padding: 0;
    background: none; border: 1px solid transparent;
    border-radius: var(--controlo-raio-interno);
    color: var(--controlo-texto);
    font: inherit; font-size: 12.5px; cursor: pointer;
}
.spark-cal-dia:hover:not(:disabled) { background: var(--controlo-fundo-hover); }
/* Os dias do mês vizinho ficam, mas recuam: sem eles a grelha salta de semana
   em semana e perde-se a noção de continuidade. */
.spark-cal-dia.fora { color: var(--controlo-rotulo); opacity: 0.55; }
.spark-cal-dia:disabled { opacity: 0.3; cursor: default; }
/* Hoje marca-se pelo contorno e o escolhido pelo preenchimento: assim vêem-se
   os dois ao mesmo tempo, que é o caso normal. */
.spark-cal-dia.hoje { border-color: var(--controlo-borda-acento); font-weight: 700; }
.spark-cal-dia.escolhido {
    background: var(--controlo-fundo-acento);
    border-color: var(--controlo-borda-acento);
    color: var(--controlo-texto-acento);
    font-weight: 700;
}

.spark-cal-fundo {
    display: flex; justify-content: space-between; gap: 8px;
    margin-top: 8px; padding-top: 8px;
    border-top: 1px solid var(--line-soft, var(--controlo-borda));
}
.spark-cal-accao[hidden] { display: none; }
.spark-cal-accao {
    padding: 5px 10px;
    background: none; border: 1px solid var(--controlo-borda);
    border-radius: var(--controlo-raio-interno);
    color: var(--controlo-texto);
    font: inherit; font-size: 11.5px; cursor: pointer;
}
.spark-cal-accao:hover { background: var(--controlo-fundo-hover); }

/* -----------------------------------------------------------------------------
   0.2 DICAS — a frase que acompanha um campo, e o ⓘ que a esconde

   A .campo-dica estava escrita só no iniciativas.css, que é a folha de UMA
   página. O /admin usava-a na mesma e ficava sem estilo nenhum: a explicação
   da caixa de correio saía maior do que o rótulo do campo e mais alta do que
   o próprio campo. Uma classe usada em duas páginas pertence aqui.

   O ⓘ é para as explicações compridas. Uma frase de quatro linhas debaixo de
   um campo empurra o formulário todo para baixo e, de tanto lá estar, deixa de
   se ler. Atrás de um ícone, está lá quando se precisa dela e desaparece
   quando não. Não é um title do HTML: esse não existe ao toque, e metade das
   pessoas usa isto no telemóvel.
   -------------------------------------------------------------------------- */
.campo-dica {
    display: block; margin-top: 5px;
    font-size: 11.5px; line-height: 1.4;
    color: var(--controlo-rotulo);
}

/* A AJUDA DE UM CAMPO PERSONALIZADO, com o mesmo desenho da de um campo
   nativo. O /js/campos.js escreve-a com a classe .form-hint, que só tinha
   tamanho e cor na iniciativas.css — a folha de UMA página. No /tarefas não
   apanhava nada: a explicação do «Estimado» saía com o tamanho do corpo do
   texto, ao lado de duas dicas de 11,5 e a gritar mais do que o campo que
   explica.

   E passa a ser apagada como as outras, em vez do âmbar que tinha: uma
   explicação não é um aviso. O aviso é o .error aqui em baixo, que continua a
   ter cor — e é isso que lhe devolve o significado. */
.form-hint {
    display: block; margin-top: 5px;
    font-size: 11.5px; line-height: 1.4;
    color: var(--controlo-rotulo);
}
.form-hint.error { color: var(--text-danger); font-weight: 600; display: block; }

/* Dois campos lado a lado — datas de início e fim, portas e segurança. Estava
   também só no iniciativas.css, e no /admin os dois campos caíam um por baixo
   do outro em vez de partilharem a linha. */
.campo-duplo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 10px; }

@media (max-width: 700px) {
    /* Num ecrã estreito, dois campos numa linha ficam ambos pequenos de mais
       para se escrever neles. */
    .campo-duplo { grid-template-columns: minmax(0, 1fr); }
}

/* O ⓘ vive ao lado do rótulo, e não do campo: pertence à pergunta, não à
   resposta. */
.dica-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; padding: 0; margin-left: 6px;
    vertical-align: middle;
    background: none; border: 1px solid var(--controlo-borda);
    border-radius: 50%;
    color: var(--controlo-rotulo);
    font: inherit; font-size: 10px; font-weight: 700; font-style: italic;
    line-height: 1; cursor: pointer;
    -webkit-appearance: none; appearance: none;
}
.dica-btn:hover { border-color: var(--controlo-borda-acento); color: var(--controlo-texto-acento); }
.dica-btn[aria-expanded="true"] {
    background: var(--controlo-fundo-acento);
    border-color: var(--controlo-borda-acento);
    color: var(--controlo-texto-acento);
}

.dica-caixa[hidden] { display: none; }
.dica-caixa {
    /* display: block porque isto é um <span>, e um <span> é inline: o fundo
       partia-se linha a linha, ficava aos degraus e passava a direita do
       modal. O .dica-caixa[hidden] acima tem mais especificidade, por isso
       continua a esconder. */
    display: block; box-sizing: border-box; max-width: 100%;
    margin-top: 6px; padding: 9px 11px;
    font-size: 11.5px; line-height: 1.45;
    color: var(--controlo-texto);
    background: var(--controlo-fundo-acento);
    border: 1px solid var(--controlo-borda-acento);
    border-radius: var(--controlo-raio-interno);
}

@media (hover: none) {
    .dica-btn { width: 26px; height: 26px; font-size: 13px; }
}

/* A janela do agendador: três caminhos, cada um com o seu passo e o seu
   comando. Mais larga do que uma janela de formulário, porque um comando de
   cem caracteres partido em quatro linhas deixa de se ler como um comando. */
.cron-modal { width: min(760px, calc(100vw - 40px)); max-width: none; }
.cron-intro { margin: 0 0 4px; font-size: 13.5px; line-height: 1.55; color: var(--text-body); }
.cron-passo { margin-top: 22px; }
.cron-passo h4 {
    margin: 0 0 6px; font-size: 12px; font-weight: 700;
    letter-spacing: 0.6px; text-transform: uppercase;
    color: var(--controlo-texto);
}
.cron-passo p { margin: 0 0 8px; font-size: 13px; line-height: 1.5; color: var(--text-body); }
.cron-passo .campo-dica { margin-top: 6px; }
.cron-fecho { margin: 22px 0 0; font-size: 13px; line-height: 1.5; color: var(--text-body); }

/* O aviso de que falta o segredo: veste-se de perigo porque, sem ele, tudo o
   que está por baixo não funciona — e isso não se descobre a olhar. */
.cron-aviso[hidden] { display: none; }
.cron-aviso {
    margin-top: 14px; padding: 10px 12px;
    font-size: 12.5px; line-height: 1.5;
    color: var(--controlo-texto-perigo);
    background: var(--controlo-fundo-perigo);
    border: 1px solid var(--controlo-borda-perigo);
    border-radius: var(--controlo-raio-interno);
}

/* Um comando para copiar: monoespaçado, com quebra, e o botão de copiar ao
   lado. Um comando cortado no fim do contentor é um comando que se cola pela
   metade — e um segredo colado pela metade não dá erro, dá 403 em silêncio. */
.cron-bloco { margin-top: 12px; }
.cron-etiqueta {
    display: block; margin-bottom: 4px;
    font-size: 10.5px; letter-spacing: 0.6px; text-transform: uppercase;
    color: var(--controlo-rotulo);
}
.cron-linha {
    display: flex; align-items: flex-start; gap: 8px;
    padding: 9px 11px;
    background: var(--controlo-fundo);
    border: 1px solid var(--controlo-borda);
    border-radius: var(--controlo-raio-interno);
}
.cron-linha code {
    flex: 1 1 auto; min-width: 0;
    font-family: 'Share Tech Mono', ui-monospace, monospace;
    font-size: 12px; line-height: 1.5;
    color: var(--controlo-texto);
    word-break: break-all; white-space: pre-wrap;
}
.cron-linha .copiar-btn { flex: 0 0 auto; }

/* -----------------------------------------------------------------------------
   0.2 COPIAR — o botão que leva um código daqui para o email

   O código de uma iniciativa existe para ser escrito no assunto de um email.
   Levá-lo daqui para o Outlook com o rato dá um espaço a mais ou uma letra a
   menos, e um código com um espaço a mais não é encontrado por ninguém.

   Anda sempre colado a um código, e por isso é pequeno e discreto: não compete
   com o que está a acompanhar. Ao toque cresce para os 44px como tudo o resto,
   porque é aí que um alvo pequeno deixa de se acertar.
   -------------------------------------------------------------------------- */
.copiar-btn {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; padding: 0;
    vertical-align: middle;
    background: none; border: 1px solid transparent;
    border-radius: var(--controlo-raio-interno);
    color: var(--controlo-rotulo);
    cursor: pointer;
    -webkit-appearance: none; appearance: none;
}
.copiar-btn:hover {
    background: var(--controlo-fundo-hover);
    border-color: var(--controlo-borda);
    color: var(--controlo-texto);
}
/* O visto fica verde e sem fundo: já não é um botão a convidar, é uma resposta
   a uma pergunta que foi feita. */
.copiar-btn.copiado {
    color: var(--text-success, #2e9e5b);
    background: none; border-color: transparent;
}

@media (hover: none) {
    .copiar-btn {
        width: var(--controlo-altura-toque);
        height: var(--controlo-altura-toque);
    }
}

/* =============================================================================
   1. DESIGN TOKENS
   ========================================================================== */

/* Tema Cyber (escuro) — a base -------------------------------------------- */
/* O browser desenha os controlos dele — vistos, botões de rádio, barras de
   rolamento, o selector de data nativo — com as cores do sistema, e por
   omissão assume fundo claro. Num fundo preto, um visto por marcar era um
   quadrado BRANCO cheio, do tamanho de um dedo, ao lado do nome de cada
   empresa. Uma linha resolve os quatro. */
:root { color-scheme: dark; }
html[data-theme="widgets"], html.ui-light { color-scheme: light; }

:root {
    --bg-dark: #0a0a0f;
    --bg-panel: #0d1117;
    --bg-card: #111827;
    --border-color: #1a3a4a;

    --text-primary: #ffffff;
    --text-secondary: #00aadd;   /* acento: no tema cyber isto é ciano, não texto */
    --text-body: #c2d2dc;        /* texto corrido, legível nos dois temas */
    --text-muted: #7d97a6;       /* 5.79:1 no pior fundo escuro; o #5a7a8a dava 3.87:1 */
    --text-success: #00dd77;
    --text-warning: #dd9900;
    /* Era #dd4444: 4.49 no painel, 4.21 no cartão e 4.08 sobre o vermelho a 12%
       do próprio badge — abaixo do mínimo nos três, e em texto de 10 e 11px.
       É o mesmo erro que eu tinha feito com o --text-link no tema claro: a cor
       foi escolhida como ACENTO, para se ver, e depois usada como TEXTO, para
       se ler. Um acento distingue-se; um texto tem de se ler.
       #e85c5c: 5.51 no painel, 5.17 no cartão, 5.01 sobre o soft. */
    --text-danger: #e85c5c;


    /* Acentos. Cada um tem um papel fixo em toda a plataforma:
         blue    — acção principal, navegação, valores neutros
         green   — execução, concluído, confirmação
         gold    — o grupo/holding: Board, perfil BOARD, destaque de holding
         red     — perigo, atraso, sair, migrações por correr
         orange  — em curso, por iniciar, atenção sem gravidade
         purple  — pilares e agrupamentos (só nas Iniciativas)                */
    --accent-blue: #00aadd;
    --accent-green: #00dd77;
    --accent-gold: #d4af37;
    --accent-red: #dd4444;
    --accent-orange: #dd9900;
    --accent-purple: #9966ff;

    /* No tema escuro os acentos já se lêem sobre o fundo — aqui os tokens de
       texto são os próprios acentos, e existem só para o CSS poder usar sempre
       o mesmo nome, sem saber em que tema está. */
    --text-link: var(--accent-blue);
    --text-gold: var(--accent-gold);

    /* Superfícies derivadas — véus neutros sobre o fundo, em vez de cores
       fixas repetidas em cada página. */
    --fill-soft: rgba(255, 255, 255, 0.04);
    --fill-hover: rgba(255, 255, 255, 0.08);
    --line-soft: rgba(255, 255, 255, 0.08);
    --input-bg: rgba(0, 0, 0, 0.35);

    --accent-blue-soft: rgba(0, 170, 221, 0.12);
    --accent-blue-line: rgba(0, 170, 221, 0.35);
    --accent-red-soft: rgba(221, 68, 68, 0.12);
    --accent-red-line: rgba(221, 68, 68, 0.38);
    --accent-gold-soft: rgba(212, 175, 55, 0.14);
    --accent-gold-line: rgba(212, 175, 55, 0.40);
    --accent-green-soft: rgba(0, 221, 119, 0.12);
    --accent-green-line: rgba(0, 221, 119, 0.35);

    --shadow-sm: 0 4px 14px rgba(0, 0, 0, 0.35);
    --shadow-lg: 0 18px 50px rgba(0, 0, 0, 0.55);

    /* A quarta superfície: o que está À FRENTE de um cartão — menus, popovers,
       modais. Existia como valor à mão em cada sítio; passa a token porque o
       tema personalizado tem de o poder derivar como deriva os outros três. */
    --bg-elevated: #16202e;

    /* Cores de DADOS, e não de estado. Uma série de um gráfico não quer dizer
       «correu bem» nem «correu mal» — quer dizer «é esta e não aquela». Por
       isso são cinco cores distinguíveis e não os tokens semânticos: usar o
       vermelho de perigo para a terceira barra de um gráfico é dizer uma coisa
       que não é verdade. */
    --chart-1: #00aadd;
    --chart-2: #00dd77;
    --chart-3: #d4af37;
    --chart-4: #9966ff;
    --chart-5: #dd9900;

    --card-radius: 10px;
    --header-bg: linear-gradient(180deg, #0d1a24 0%, #0a0a0f 100%);
    --header-line: var(--border-color);

    --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --font-display: 'Orbitron', var(--font-ui);
}

/* Tema Widgets (claro) — o predefinido ------------------------------------
   A paleta clara reconcilia as cinco que existiam. Os neutros vêm do Board /
   Administração / Ajuda (quatro das cinco páginas usavam-nos, e são os mesmos
   do login). Os acentos ficam com a leitura de marca: o azul é o ciano
   da marca e não o azul genérico que só as Iniciativas usavam; o dourado é o
   do Board; o roxo, que só existia nas Iniciativas, mantém-se para os pilares. */
html[data-theme="widgets"] {
    --bg-dark: #eef1f5;
    --bg-panel: #ffffff;
    --bg-card: #ffffff;
    --border-color: rgba(20, 40, 60, 0.14);

    --text-primary: #1a2530;
    --text-secondary: #55677a;
    --text-body: #55677a;
    /* 4.51:1 sobre o FUNDO DA PÁGINA (#eef1f5) e 5.11:1 sobre os painéis
       brancos. O valor anterior — #6b7785 — foi calibrado contra branco e dava
       4.56:1 lá, mas 4.03:1 sobre o fundo, que é precisamente onde vivem os
       cabeçalhos de grupo, os subtítulos e as horas do Diário. Um token de
       texto tem de passar no pior dos fundos onde é usado, não no melhor. */
    --text-muted: #646f7d;
    --text-success: #0a7a4a;
    --text-warning: #8d640a;
    --text-danger: #b42318;

    --accent-blue: #0092c0;
    --accent-green: #0a9e5f;
    --accent-gold: #a8842a;

    /* Tokens de TEXTO para os acentos.
       Um acento tem dois trabalhos que pedem valores diferentes: pintar uma
       barra, um ponto ou um fundo — onde a cor viva é o que se quer — e escrever
       uma palavra, onde tem de se ler. Os --accent-* continuam a servir o
       primeiro; estes servem o segundo, e passam 4,5:1 tanto no fundo da página
       como nos painéis brancos.

       Sem esta separação, ou os textos ficavam ilegíveis (era o caso: as
       ligações a 3,16:1, a contagem "Por iniciar" a 2,45:1) ou as barras
       ficavam sujas por se ter escurecido o acento. */
    /* Escolhi o #00779c medindo-o contra o fundo da página e contra o painel
       branco — e esqueci o terceiro fundo em que ele assenta: o azul a 8% do
       .spark-btn. Sobre esse dava 4.11:1, abaixo do mínimo, e qualquer botão
       colocado directamente sobre a página falhava sem que nada o denunciasse
       (os outros estavam todos dentro de painéis brancos). */
    --text-link: #006e90;        /* 5.10 na página · 5.78 no painel · 4.66 no botão */
    --text-gold: #876a22;        /* 4.50 · 5.10 */
    --accent-red: #d35449;
    --accent-orange: #d99a10;
    --accent-purple: #7c3aed;

    --fill-soft: rgba(20, 40, 60, 0.035);
    --fill-hover: rgba(20, 40, 60, 0.075);
    --line-soft: rgba(20, 40, 60, 0.08);
    /* Era #f3f6f9 — o QUARTO fundo da barra. Um campo cinzento ao lado de um
       select branco na mesma linha lê-se como dois sistemas. Um só. */
    --input-bg: #ffffff;

    --accent-blue-soft: rgba(0, 146, 192, 0.08);
    --accent-blue-line: rgba(0, 146, 192, 0.45);
    --accent-red-soft: rgba(211, 84, 73, 0.09);
    --accent-red-line: rgba(211, 84, 73, 0.40);
    --accent-gold-soft: rgba(212, 175, 55, 0.12);
    --accent-gold-line: rgba(168, 132, 42, 0.45);
    --accent-green-soft: rgba(10, 158, 95, 0.09);
    --accent-green-line: rgba(10, 158, 95, 0.38);

    --shadow-sm: 0 4px 14px rgba(30, 50, 70, 0.06);
    --shadow-lg: 0 18px 50px rgba(30, 50, 70, 0.16);

    --bg-elevated: #ffffff;

    --chart-1: #0092c0;
    --chart-2: #0a9e5f;
    --chart-3: #a8842a;
    --chart-4: #7c3aed;
    --chart-5: #d99a10;

    --card-radius: 20px;
    --header-bg: #ffffff;
    --header-line: rgba(20, 40, 60, 0.12);
}

/* Tema personalizado ---------------------------------------------------------

   Esta é a PONTE. De um lado, o motor de temas do spark.js, que escreve as
   variáveis --custom-* no elemento <html>; do outro, os tokens semânticos com
   que toda a plataforma está escrita. Aqui só se liga um ao outro.

   Antes ligavam-se seis. Ficavam de fora as superfícies elevadas, os estados,
   os gráficos, os véus e as sombras — que continuavam com os valores do tema
   cyber, mesmo num tema claro. Era essa a metade que faltava para o «dashboard
   moderno branco com fundo bege»: escolhia-se uma paleta retro e o que mudava
   eram três acentos, enquanto o resto do ecrã continuava a ser outro tema.

   O valor a seguir à vírgula é o que vale se o motor não tiver escrito nada.
   São os do tema cyber, porque é o que estava cá antes e não há razão para
   mudar o que já funciona a quem tenha um tema meio configurado. */
html[data-theme="custom"] {
    /* As quatro superfícies, da mais funda para a mais à frente. */
    --bg-dark: var(--custom-bg-dark, #0a0a0f);
    --bg-panel: var(--custom-bg-panel, #0d1117);
    --bg-card: var(--custom-bg-card, #111827);
    --bg-elevated: var(--custom-bg-elevated, #16202e);
    --border-color: var(--custom-border-color, rgba(255, 255, 255, 0.1));

    /* Texto. */
    --text-primary: var(--custom-text-primary, #ffffff);
    --text-secondary: var(--custom-text-secondary, #e0e0e0);
    --text-body: var(--custom-text-secondary, #e0e0e0);
    --text-muted: var(--custom-text-muted, #8a8a8a);

    /* Acentos — a identidade. Cada um mantém o papel que tem em toda a
       plataforma; o que muda é a cor da paleta que o preenche.
         1ª cor → acção principal        4ª cor → agrupamentos
         2ª cor → segunda voz            5ª cor → o tom escuro/âncora
         3ª cor → destaque                                                   */
    --accent-blue: var(--custom-accent-1, #6366f1);
    --accent-purple: var(--custom-accent-2, #8b5cf6);
    --accent-gold: var(--custom-warning, #d4af37);
    --accent-green: var(--custom-success, #00dd77);
    --accent-orange: var(--custom-warning, #dd9900);
    --accent-red: var(--custom-danger, #dd4444);

    /* ESTADOS EM TEXTO. Isto faltava por inteiro: um «atrasado» a vermelho num
       tema personalizado ficava com o vermelho do tema cyber, escrito por cima
       de um fundo claro. O motor deriva-os já com contraste medido sobre a
       superfície do tema. */
    --text-success: var(--custom-text-success, #00dd77);
    --text-warning: var(--custom-text-warning, #dd9900);
    --text-danger: var(--custom-text-danger, #e85c5c);
    --text-link: var(--custom-text-link, #6366f1);
    --text-gold: var(--custom-text-gold, #d4af37);

    /* Gráficos: as cinco da paleta, que é exactamente para o que servem. */
    --chart-1: var(--custom-chart-1, #00aadd);
    --chart-2: var(--custom-chart-2, #00dd77);
    --chart-3: var(--custom-chart-3, #d4af37);
    --chart-4: var(--custom-chart-4, #9966ff);
    --chart-5: var(--custom-chart-5, #dd9900);

    /* Os véus de cada acento — o fundo a 12% de um badge, a linha a 40% de uma
       borda. Derivados da paleta em vez de fixos, senão um badge «concluído»
       num tema quente saía com um verde-ciano que não está no tema. */
    --accent-blue-soft: var(--custom-primary-soft, rgba(0, 170, 221, 0.12));
    --accent-blue-line: var(--custom-primary-line, rgba(0, 170, 221, 0.35));
    --accent-green-soft: var(--custom-success-soft, rgba(0, 221, 119, 0.12));
    --accent-green-line: var(--custom-success-line, rgba(0, 221, 119, 0.35));
    --accent-gold-soft: var(--custom-warning-soft, rgba(212, 175, 55, 0.14));
    --accent-gold-line: var(--custom-warning-line, rgba(212, 175, 55, 0.40));
    --accent-red-soft: var(--custom-danger-soft, rgba(221, 68, 68, 0.12));
    --accent-red-line: var(--custom-danger-line, rgba(221, 68, 68, 0.38));

    /* A sombra também vem do tema: preta sobre um creme suja-o. */
    --shadow-sm: var(--custom-shadow-sm, 0 4px 14px rgba(0, 0, 0, 0.35));
    --shadow-lg: var(--custom-shadow-lg, 0 18px 50px rgba(0, 0, 0, 0.55));

    --header-bg: var(--custom-bg-dark, #0a0a0f);
    --header-line: var(--custom-border-color, rgba(255, 255, 255, 0.1));
}

/* Fundo claro: os véus neutros têm de inverter, senão desaparecem. O
   JavaScript marca html.ui-light. As sombras NÃO se repõem aqui — o motor já
   as deu com o matiz do tema, e um valor fixo desfazia-o. */
html.ui-light[data-theme="custom"] {
    --fill-soft: rgba(20, 40, 60, 0.035);
    --fill-hover: rgba(20, 40, 60, 0.075);
    --line-soft: rgba(20, 40, 60, 0.08);
    --input-bg: rgba(0, 0, 0, 0.04);
}


/* =============================================================================
   2. BASE
   ========================================================================== */

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: var(--font-ui);
    background: var(--bg-dark);
    color: var(--text-primary);
}

/* Duas larguras de conteúdo, não sete.
   Cada página tinha inventado a sua — 1500, 1280, 1200, 1100, 900, 560 — e
   navegar entre secções deslocava a margem do conteúdo até meio ecrã, com o
   mesmo cabeçalho fixo por cima. Ficam duas: a larga, para tabelas e grelhas, e
   a de leitura, para texto corrido e formulários, onde uma linha de 130
   caracteres cansa mais do que ajuda. */
.spark-page {
    max-width: 1500px;
    margin: 0 auto;
    padding: 22px 24px 80px;
}

/* Tabelas e grelhas densas. Larga o suficiente para as sete colunas do /admin
   sem as espremer, estreita o suficiente para o olho não atravessar o ecrã. */
.spark-page.dados { max-width: 1280px; }

/* Texto corrido e formulários. O limite real é o das linhas, não o do painel:
   os parágrafos param aos 68 caracteres e o resto da página respira à volta. */
.spark-page.leitura { max-width: 900px; }
.spark-page.leitura p,
.spark-page.leitura li { max-width: 68ch; }

.spark-title {
    font-family: var(--font-display);
    font-size: 20px;
    font-weight: 700;
    color: var(--text-primary);
}

.spark-sub {
    font-size: 12px;
    color: var(--text-muted);
    margin: 3px 0 18px;
}

.spark-muted { color: var(--text-muted); font-size: 11px; }


/* =============================================================================
   3. CABEÇALHO PARTILHADO
   -----------------------------------------------------------------------------
   Três zonas, iguais em todas as páginas: identidade à esquerda, ferramentas
   da página ao centro (vazio na maioria), conta e menu à direita.
   ========================================================================== */

.spark-header {
    display: flex;
    align-items: center;
    gap: 15px;
    padding: 10px 22px;
    background: var(--header-bg);
    border-bottom: 1px solid var(--header-line);
    position: sticky;
    top: 0;
    z-index: var(--camada-cabecalho);
    flex-shrink: 0;
    flex-wrap: nowrap;
}

.spark-header-left { display: flex; align-items: center; gap: 16px; flex: 1; min-width: 0; }
.spark-header-center { display: flex; align-items: center; justify-content: center; gap: 15px; flex: 1; min-width: 0; }
.spark-header-right { display: flex; align-items: center; justify-content: flex-end; gap: 12px; flex: 1; min-width: 0; }

.spark-brand {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    text-decoration: none;
    white-space: nowrap;
}

/* Dois logótipos, positivo e negativo, trocados conforme a luminosidade do
   fundo. O html.ui-light é posto pelo motor de temas (public/js/spark.js). */
.spark-logo { display: block; height: 34px; }
/* O logotipo positivo era um JPG com fundo branco, e o multiply servia para
   o dissolver. O ficheiro novo e PNG com transparencia: o multiply passaria a
   escurecer o logotipo sobre fundos de cor, por isso saiu. */
.spark-logo-pos { display: none; }
html.ui-light .spark-logo-neg { display: none; }
html.ui-light .spark-logo-pos { display: block; }

.spark-seccao {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--text-secondary);
    border-left: 1px solid var(--border-color);
    padding-left: 12px;
    white-space: nowrap;
}

.spark-user {
    display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
    line-height: 1.25;
    max-width: 170px;
}
.spark-user-nome {
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.spark-user-perfil {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text-muted);
}

@media (max-width: 820px) {
    .spark-header { flex-wrap: wrap; row-gap: 8px; padding: 8px 14px; }
    .spark-header-center { order: 3; flex-basis: 100%; justify-content: flex-start; }
    /* O NOME VOLTOU. Saía daqui porque não cabia: eram seis botões de 44px a
       disputar a linha com a marca. Agora que os atalhos e o menu desceram
       para a barra do polegar, o cabeçalho tem espaço — e o nome por cima do
       perfil é o que diz de que conta é este ecrã, que num telemóvel não se
       descobre pairando com o rato por cima de coisa nenhuma.

       Mais estreito do que no computador, e com reticências: um nome comprido
       não pode empurrar a marca para fora. */
    .spark-user { max-width: 46vw; }

    /* Sem isto, os três blocos disputavam a mesma linha com min-width: 0 e o
       texto saía para fora da sua caixa: a marca, o relógio e o nome da secção
       apareciam escritos uns por cima dos outros. Medido a 390px. */
    .spark-header-left { flex: 1 1 auto; min-width: 0; }
    /* flex: 1 e não 0: se ainda assim quebrar para uma segunda linha, o bloco
       ocupa-a inteira e o justify-content encosta os botões à direita. Antes
       ficava agarrado à esquerda e o resto da linha era um buraco. */
    .spark-header-right { flex: 1 1 auto; }
    .spark-brand { flex-shrink: 0; }
    .spark-seccao { border-left: none; padding-left: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

@media (max-width: 480px) {
    /* Num telemóvel não cabem a marca, o nome da secção e as cinco ferramentas
       na mesma linha — são cerca de 430px para 362 disponíveis. Em vez de
       partir em duas linhas com um buraco no meio, sai o que se repete: o nome
       da secção, que o título da própria página volta a dizer, e o ponto de
       ligação, que só acende quando o botão ao lado já está a girar. */
    .spark-seccao { display: none; }
    .refresh-indicator { display: none; }
    .spark-header { gap: 8px; }
}


/* --- Menu hamburguer -----------------------------------------------------
   Nomes de classe fixos: o _hamburger_menu.html.twig e o JavaScript das
   Iniciativas dependem deles. */

.hamburger-menu { position: relative; flex-shrink: 0; }

.hamburger-btn {
    background: transparent;
    border: 1px solid var(--border-color);
    border-radius: 6px;
    padding: 8px 10px;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    gap: 4px;
    transition: all 0.2s;
}

.hamburger-btn:hover { background: var(--accent-blue-soft); border-color: var(--accent-blue-line); }
.hamburger-btn .hamburger-traco { display: block; width: 18px; height: 2px; border-radius: 1px; background: var(--text-secondary); }

.hamburger-dropdown {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 8px;
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: 8px;
    min-width: 320px;
    max-height: calc(100vh - 70px); max-height: calc(100dvh - 70px);
    overflow-y: auto;
    box-shadow: var(--shadow-lg);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-10px);
    transition: all 0.2s ease;
    z-index: var(--camada-aviso);
}

.hamburger-dropdown.active { opacity: 1; visibility: visible; transform: translateY(0); }

.hamburger-dropdown-item {
    display: block;
    padding: 10px 14px;
    color: var(--text-primary);
    font-size: 12px;
    border-bottom: 1px solid var(--border-color);
    cursor: pointer;
}

.hamburger-dropdown-item:last-child { border-bottom: none; }
.hamburger-dropdown-item:hover { background: var(--accent-blue-soft); }
.hamburger-dropdown-item.active-page { background: var(--accent-blue-soft); box-shadow: inset 3px 0 0 var(--accent-blue); }

.hamburger-dropdown-item.logout {
    color: var(--text-danger);
    border-top: 1px solid var(--border-color);
    margin-top: 5px;
    padding-top: 12px;
}

.hamburger-dropdown-item.logout:hover { background: var(--accent-red-soft); }

.hamburger-dropdown-section {
    font-size: 10px;
    color: var(--text-muted);
    text-transform: uppercase;
    letter-spacing: 1px;
    padding: 12px 14px 6px;
    border-top: 1px solid var(--border-color);
    margin-top: 5px;
}

/* O MENU EM DOIS NÍVEIS (5/10/2026): a secção é um botão que abre e fecha o grupo dela. Fechado, o menu são cinco linhas;
   aberto, só os destinos de uma secção. O grupo da página em que se está fica marcado mesmo fechado. */
.hamburger-grupo-cab { display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box; background: none; border: 0; border-top: 1px solid var(--border-color); margin-top: 0; padding: 13px 14px; font: inherit; font-size: 10.5px; text-align: left; cursor: pointer; color: var(--text-secondary); font-weight: 700; }
.hamburger-grupo:first-child .hamburger-grupo-cab { border-top: 0; }
.hamburger-grupo-cab:hover { background: var(--fill-soft); color: var(--text-primary); }
.hamburger-grupo-cab:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: -2px; }
.hamburger-grupo-nome { flex: 1 1 auto; }
.hamburger-grupo-seta { flex: none; color: var(--text-muted); transition: transform .15s ease; transform: rotate(-90deg); }
.hamburger-grupo.aberto .hamburger-grupo-seta { transform: rotate(0deg); }
.hamburger-grupo.aberto > .hamburger-grupo-cab { color: var(--text-primary); }
.hamburger-grupo.tem-pagina > .hamburger-grupo-cab .hamburger-grupo-nome { color: var(--text-link); }
.hamburger-grupo-cab .badge-migracoes { flex: none; }
.hamburger-grupo.aberto > .hamburger-grupo-cab .badge-migracoes { display: none; }
.hamburger-grupo-itens { display: none; }
.hamburger-grupo.aberto > .hamburger-grupo-itens { display: block; }
.hamburger-grupo-itens .hamburger-dropdown-item { padding-left: 24px; border-bottom: 0; border-top: 1px solid var(--border-color); }
.hamburger-grupo-itens .hamburger-dropdown-item.active-page { box-shadow: inset 3px 0 0 var(--accent-blue); }
@media (prefers-reduced-motion: reduce) { .hamburger-grupo-seta { transition: none; } }

/* SECÇÕES SEMPRE À VISTA (5/10/2026, o José: «a navegação pode ficar no 1.º nível, o resto fica em 2.º nível como está
   agora, e a conta também no primeiro nível»). Um grupo com a classe fixo não abre nem fecha: não tem cabeçalho e os
   destinos dele estão sempre lá, no computador e no telemóvel. É o que se usa todos os dias e o que diz de quem é a conta. */
.hamburger-grupo.fixo > .hamburger-grupo-cab { display: none; }
.hamburger-grupo.fixo > .hamburger-grupo-itens { display: block; }
.hamburger-grupo.fixo > .hamburger-grupo-itens .hamburger-dropdown-item { padding-left: 14px; }
.hamburger-grupo.fixo:first-child > .hamburger-grupo-itens .hamburger-dropdown-item:first-child { border-top: 0; }

/* O MENU EM LEQUE (5/10/2026, o José: «ao clicar ou passar por cima deslizava um segundo menu à esquerda»). No computador
   as secções que não são fixas são uma linha só, e os destinos de cada uma deslizam num segundo painel, encostado à
   esquerda da linha que o abriu. A classe em-leque é o spark.js que a põe, e só no menu do cabeçalho em ecrã largo: a folha
   do telemóvel e o menu de demonstração da Ajuda continuam a abrir os grupos no sítio.
   O PAINEL É FIXO AO ECRÃ, NÃO AO MENU: o menu rola quando não cabe (um portátil baixo), e quem rola corta tudo o que lhe
   sai dos lados. Um position: fixed escapa ao corte — desde que nenhum antepassado esteja transformado, e por isso em
   leque o menu aparece sem o deslize de cima (um transform, mesmo de zero, prendia o painel a ele). O sítio do painel é o
   spark.js que o calcula (assentarLeque), a partir da linha que o abriu.
   A seta fica à esquerda, que é para onde o painel sai, e todo o texto do primeiro nível alinha por ela. */
.hamburger-dropdown.em-leque { min-width: 336px; transform: none; transition: opacity .2s ease, visibility .2s; }
.em-leque .hamburger-grupo-cab { padding: 13px 14px 13px 10px; font-size: 12.5px; font-weight: 600; letter-spacing: 0; text-transform: none; color: var(--text-primary); }
.em-leque .hamburger-grupo.fixo > .hamburger-grupo-itens .hamburger-dropdown-item,
.em-leque > .hamburger-dropdown-item.logout { padding-left: 32px; }
.em-leque > .hamburger-dropdown-item.logout { margin-top: 0; padding-top: 12px; padding-bottom: 12px; }
.em-leque .hamburger-grupo-seta,
.em-leque .hamburger-grupo.aberto .hamburger-grupo-seta { order: -1; transform: rotate(90deg); }
.em-leque .hamburger-grupo.aberto > .hamburger-grupo-cab { background: var(--accent-blue-soft); }
.em-leque .hamburger-grupo.aberto > .hamburger-grupo-cab .hamburger-grupo-seta { color: var(--text-link); }
.em-leque .hamburger-grupo:not(.fixo) > .hamburger-grupo-itens {
    display: block; position: fixed; width: 320px; box-sizing: border-box;
    max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px); overflow-y: auto; overscroll-behavior: contain;
    background: var(--bg-panel); border: 1px solid var(--border-color); border-radius: 8px; box-shadow: var(--shadow-lg);
    opacity: 0; visibility: hidden; transform: translateX(14px);
    transition: transform .18s ease, opacity .18s ease, visibility .18s;
}
.hamburger-dropdown.active.em-leque .hamburger-grupo.aberto:not(.fixo) > .hamburger-grupo-itens { opacity: 1; visibility: visible; transform: translateX(0); }
.em-leque .hamburger-grupo:not(.fixo) > .hamburger-grupo-itens .hamburger-dropdown-item { padding-left: 14px; }
.em-leque .hamburger-grupo:not(.fixo) > .hamburger-grupo-itens .hamburger-dropdown-item:first-child { border-top: 0; }
@media (prefers-reduced-motion: reduce) { .em-leque .hamburger-grupo:not(.fixo) > .hamburger-grupo-itens { transition: none; transform: none; } }

.hamburger-dropdown-item.has-desc { display: flex; flex-direction: column; gap: 2px; }
.hamburger-item-title { font-size: 12px; font-weight: 600; }
.hamburger-item-desc { font-size: 10px; color: var(--text-muted); line-height: 1.3; }

/* Bolinha com o número de migrações por correr (só aparece ao SUPERADMIN) */
/* O [hidden] é da folha do BROWSER e vale 0,0,1; a regra a seguir vale 0,1,0
   e ganha-lhe. O JavaScript põe `conta.hidden = !n` em três sítios — no
   depósito, nas notas e nas migrações — e não acontecia nada: os crachás
   ficavam a dizer «0», que é exactamente o ruído que o hidden ia evitar. Na
   barra do polegar, com um nome por baixo de cada ícone, ficou impossível de
   não ver. Quem escreve display num elemento que também usa [hidden] tem de o
   dizer aqui. */
.badge-migracoes[hidden] { display: none; }
.badge-migracoes {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    margin-left: 5px;
    border-radius: 9px;
    /* Branco sobre vermelho a 11px. Usava o --text-danger, que é o vermelho
       calibrado para se LER — e no tema escuro esse tem de ser claro, o que
       deixava o branco a 3.43:1 por cima dele. O --controlo-perigo-cheio existe
       para isto: vermelho sólido para o branco assentar. */
    background: var(--controlo-perigo-cheio);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    vertical-align: 1px;
}


/* =============================================================================
   4. COMPONENTES COMUNS
   ========================================================================== */

/* --- Painéis -------------------------------------------------------------- */
.spark-panel {
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: 14px;
    padding: 20px;
    box-shadow: var(--shadow-sm);
}

.spark-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.spark-panel-head h2 { font-size: 15px; font-weight: 700; }

/* Num telemóvel o cabeçalho de um painel empilha: o título numa linha, as
   acções noutra. Lado a lado num ecrã de 393px, o "Empresas do grupo" ficava
   espremido a 112px e o botão "+ Nova caixa" a 66x63 — mais alto do que largo,
   com o texto partido em três. Um botão nesse estado já não se lê como botão. */
@media (max-width: 700px) {
    .spark-panel-head {
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
    }
    .spark-panel-head > div:first-child,
    .spark-panel-head h2 { min-width: 0; }
    /* As acções numa linha própria, encostadas à esquerda como o título: ao
       centro ou à direita, deixam de se ler como parte do mesmo bloco. */
    .spark-panel-head .painel-accoes,
    .spark-panel-head > .spark-btn,
    .spark-panel-head > button {
        align-self: flex-start;
    }
    .spark-panel-head .painel-accoes { width: 100%; }
    /* E um botão não encolhe abaixo do seu texto. */
    .spark-panel-head .spark-btn { flex: 0 0 auto; white-space: nowrap; }
}

/* Moldura dourada: o grupo/holding. */
.spark-panel.destaque { border: 2px solid var(--accent-gold-line); box-shadow: var(--shadow-sm), 0 0 24px var(--accent-gold-soft); }


/* --- Botões --------------------------------------------------------------- */
/* UM BOTÃO ESCONDIDO TEM DE FICAR ESCONDIDO.
   O atributo [hidden] vale «display:none» por omissão do browser, e essa
   omissão perde para QUALQUER declaração de display numa classe — como este
   inline-flex. Havia meia dúzia de regras a corrigir isto caso a caso pela
   folha fora; esta resolve-o para todos os botões de uma vez, e o próximo
   `botao.hidden = true` passa a fazer o que quem o escreveu esperava. */
.spark-btn[hidden] { display: none !important; }

.spark-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 1px solid var(--accent-blue-line);
    background: var(--accent-blue-soft);
    color: var(--text-link);
    border-radius: var(--controlo-raio);
    /* A ALTURA É A DA FICHA. O botão media 33px — 8 de ar em cima e em baixo
       sobre 12 de letra — ao lado de campos e separadores de vistas a 36: três
       alturas na mesma linha, no próprio kit. Dezenas de sítios já lhe punham
       min-height por conta própria, que é o sintoma de a base não a ter. Os 6px
       de ar ficam para o raro botão que parte a frase em duas linhas. */
    box-sizing: border-box;
    min-height: var(--controlo-altura);
    padding: 6px 14px;
    font-family: inherit;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
    text-decoration: none;
    transition: all 0.15s ease;
}
@media (hover: none) { .spark-btn { min-height: var(--controlo-altura-toque); } }

.spark-btn:hover { background: var(--accent-blue); color: #fff; }

/* O ghost passa a ter o MESMO fundo dos outros controlos e não transparente:
   os botões "+" da aplicação estavam a azul cheio e destacavam-se de tudo o
   resto; a transparência deixava-os a flutuar sobre o painel, sem forma. Com o
   fundo do controlo lêem-se como botões, ao lado dos seus vizinhos. */
.spark-btn.ghost { border-color: var(--controlo-borda); background: var(--controlo-fundo); color: var(--controlo-texto); }
.spark-btn.ghost:hover { color: var(--text-primary); background: var(--fill-hover); }

.spark-btn.danger { border-color: var(--accent-red-line); background: var(--accent-red-soft); color: var(--text-danger); }
.spark-btn.danger:hover { background: var(--accent-red); color: #fff; }

/* Botão de acção principal de um formulário, à largura toda.

   O VERDE SAIU DAQUI. Este botão era um gradiente do azul da marca para o
   verde, e o verde não quer dizer nada no kit: o acento é azul, e é a única
   cor que diz «carrega aqui». Ao lado de qualquer .spark-btn normal — que é
   azul — lia-se como um segundo sistema, e é a queixa que deu origem a esta
   correcção («estes botões verdes grandes não é o estilo UI que fizemos»).

   Fica o azul SÓLIDO do kit, o --controlo-primario-cheio, que é o par do
   vermelho e do verde cheios da secção 0 e existe precisamente para o branco
   se ler em cima dele (5,73:1, medido). Não é o --accent-blue: esse é o azul
   claro da marca, e o branco por cima dele fica a 2,69:1.

   O que este botão mantém é o PAPEL — largura toda, maiúsculas, o único
   botão de submissão de um formulário. É por isso que continua a haver uma
   classe: a ênfase aqui é de forma, não de cor. */
.spark-btn.primary {
    width: 100%;
    border: 1px solid var(--controlo-primario-cheio);
    background: var(--controlo-primario-cheio);
    color: #ffffff;
    padding: 13px;
    font-size: 13px;
    text-transform: uppercase;
    letter-spacing: 1px;
}

.spark-btn.primary:hover { filter: brightness(1.06); box-shadow: 0 8px 22px var(--accent-blue-soft); }
.spark-btn:disabled { opacity: 0.6; cursor: default; filter: none; box-shadow: none; }

/* A ESPERA VÊ-SE. O :disabled sozinho é seis décimos de opacidade — não se
   distingue de um botão normal a olho, e havia esperas de cinco segundos com
   um modelo a escrever do outro lado. A roda entra pelo ::before para não
   mexer no conteúdo do botão: o rótulo fica onde está, ganha companhia. */
.spark-btn.ocupado::before {
    content: ''; flex: 0 0 auto; width: 13px; height: 13px;
    border: 2px solid currentColor; border-right-color: transparent;
    border-radius: 50%; opacity: 0.75;
    animation: spark-spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
    /* Sem volta, mas com marca: o anel aberto continua a dizer «espera». */
    .spark-btn.ocupado::before { animation: none; }
}

/* Botão discreto de uma linha de tabela (editar, desactivar…). */
.spark-row-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--text-muted);
    cursor: pointer;
    font-size: 13px;
    padding: 5px 6px;
    border-radius: 6px;
    vertical-align: middle;
}

.spark-row-btn:hover { color: var(--text-primary); background: var(--fill-hover); }

/* A acção marcada — a empresa que é o grupo — fica na cor de destaque do BOARD,
   a mesma da etiqueta GRUPO ao lado do nome. */
.spark-row-btn.marcado { color: var(--text-gold); }
/* O que não volta atrás fica vermelho ANTES de se lhe tocar, e não só no hover:
   quem percorre uma linha de botões iguais só descobria qual era o perigoso
   depois de lá estar com o rato em cima. */
.spark-row-btn.perigo { color: var(--text-danger, #b3261e); }
.spark-row-btn.perigo:hover { color: #fff; background: var(--text-danger, #b3261e); }


/* --- Uma tabela que vira cartões no telemóvel ------------------------------
   Sete colunas não cabem em 393px, e a saída até aqui era deixar a tabela
   rolar de lado dentro da sua caixa. Rolar de lado é a pior das saídas: a
   coluna dos botões — que é a que interessa — fica sempre escondida, e nada no
   ecrã diz que ela existe.

   Marca-se a tabela com .cartoes e cada célula com data-rotulo="…", que é o
   texto do cabeçalho dela. Em ecrã estreito o cabeçalho desaparece e cada
   linha passa a ser um cartão com «rótulo: valor» por linha.

   Duas células fogem à regra, e são marcadas à parte:
     data-cabeca  o nome — ocupa a largura toda, sem rótulo, em destaque
     data-accoes  os botões — descem para o fim, com um traço a separá-los
   -------------------------------------------------------------------------- */
@media (max-width: 720px) {
    .spark-table.cartoes,
    .spark-table.cartoes tbody,
    .spark-table.cartoes td { display: block; width: auto; }
    .spark-table.cartoes tr { width: auto; }

    .spark-table.cartoes thead { display: none; }

    /* A .spark-scroll-x impõe uma largura mínima à tabela para ela poder rolar
       de lado. Em cartões não há nada para rolar, e essa mínima era o que
       continuava a empurrar 480px para dentro de 363. */
    .spark-scroll-x .spark-table.cartoes { min-width: 0; }

    .spark-table.cartoes tr {
        /* flow-root e não block: o visto que sobe para junto do nome é um
           float, e um float por limpar deixava dez pixéis de vazio no fim de
           cada cartão. */
        display: flow-root;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        padding: 12px 14px;
        margin-bottom: 10px;
        background: var(--bg-card, transparent);
    }
    /* A linha arquivada continua a distinguir-se — era uma opacidade posta na
       <tr>, e a <tr> continua a existir. */

    .spark-table.cartoes td {
        border: none;
        padding: 5px 0;
        display: flex;
        align-items: center;
        gap: 10px;
        min-height: 0;
        /* Um email sem espaços não tem onde partir e saía pela direita do
           cartão fora — «barbara.dias@newanderthal.p» e o resto cortado. */
        overflow-wrap: anywhere;
    }
    .spark-table.cartoes td::before {
        content: attr(data-rotulo);
        /* 0 0 e não 0 1: o rótulo não encolhe para dar lugar a um valor
           comprido — se encolhesse, cada cartão tinha os rótulos alinhados de
           maneira diferente. */
        flex: 0 0 40%;
        font-size: 11px;
        text-transform: uppercase;
        letter-spacing: 0.5px;
        color: var(--text-muted);
    }
    /* Uma célula sem rótulo não abre um espaço vazio de 40%. */
    .spark-table.cartoes td:not([data-rotulo])::before { content: none; }

    /* Uma célula que se LÊ tem o rótulo ao lado; uma que se ESCREVE tem-no por
       cima. Com o rótulo ao lado sobram 60% da largura para um campo de texto,
       e num telemóvel isso é uma caixa onde não cabe uma frase — no /labels
       cortava as traduções a meio da primeira linha. */
    .spark-table.cartoes td:has(textarea),
    .spark-table.cartoes td:has(input:not([type="checkbox"]):not([type="hidden"])),
    .spark-table.cartoes td:has(select) {
        flex-direction: column;
        align-items: stretch;
        gap: 4px;
    }
    .spark-table.cartoes td:has(textarea)::before,
    .spark-table.cartoes td:has(input:not([type="checkbox"]):not([type="hidden"]))::before,
    .spark-table.cartoes td:has(select)::before { flex: 0 0 auto; }

    /* E uma caixa de texto de uma linha não serve para ver o que lá está. */
    .spark-table.cartoes td textarea { min-height: 68px; width: 100%; }

    .spark-table.cartoes td[data-cabeca] {
        font-size: 15px;
        padding-bottom: 8px;
        flex-wrap: wrap;
    }
    .spark-table.cartoes td[data-cabeca]::before { content: none; }

    .spark-table.cartoes td[data-accoes] {
        justify-content: flex-end;
        gap: 4px;
        margin-top: 8px;
        padding-top: 10px;
        border-top: 1px solid var(--border-color);
        text-align: left;
    }
    .spark-table.cartoes td[data-accoes]::before { content: none; }

    /* Uma célula de acções SEM acções não desenha um traço e um vazio ao fim de
       cada cartão — no /migrations, onde só as que faltam correr têm botão,
       eram doze traços a separar coisa nenhuma. O :empty não serve: a célula
       tem espaços e um comentário do Twig lá dentro. */
    .spark-table.cartoes td[data-accoes]:not(:has(button)):not(:has(a)) { display: none; }

    /* No computador, o que cada ícone faz descobre-se pousando o rato. Num
       telemóvel não há rato, e três desenhos sem nome ao fim de cada cartão
       são três adivinhas — logo aqui, onde uma delas arquiva a empresa.

       O nome já está escrito em cada botão, no title. Basta mostrá-lo. */
    .spark-table.cartoes td[data-accoes] {
        flex-wrap: wrap;
        justify-content: flex-start;
        gap: 8px;
    }
    .spark-table.cartoes td[data-accoes] .spark-row-btn,
    .spark-table.cartoes td[data-accoes] .spark-btn {
        gap: 7px;
        padding: 0 12px;
        border: 1px solid var(--border-color);
        border-radius: 8px;
        font-size: 12px;
        color: var(--text-body);
    }
    .spark-table.cartoes td[data-accoes] .spark-row-btn::after {
        content: attr(title);
    }
    /* O title não se mostra duas vezes: quem tem texto próprio já se explica. */
    .spark-table.cartoes td[data-accoes] .spark-btn::after { content: none; }

    /* O visto sobe para junto do nome: sozinho numa linha «: ▢» não se percebia
       o que estava a escolher. */
    .spark-table.cartoes td.col-visto {
        float: left;
        padding: 0 10px 0 0;
        min-height: 0;
    }
    .spark-table.cartoes td.col-visto::before { content: none; }

    /* A linha do «não há nada» é uma célula só, e não um cartão. */
    .spark-table.cartoes td[colspan] { display: block; text-align: center; padding: 14px 0; }
    .spark-table.cartoes td[colspan]::before { content: none; }
}

/* --- Menu que cai de um botão ---------------------------------------------
   Nasceu no /tarefas, dentro do <style> daquela página. Estava a ser copiado
   para o /board e ia ficar em duas folhas ao mesmo tempo — que é exactamente
   como as .vista-btn e a .procura acabaram com aspecto de Safari no iPhone.
   Vive aqui, e quem precisar chama o sparkMenu().

   As medidas são as do menu da roldana das Iniciativas: fundo de cartão,
   contorno de 1px, cantos de 8, itens de 13px e o azul da casa a acender por
   baixo. Não é um diálogo — sai-se dele com Tab, e por isso não entra na
   armadilha de foco das .spark-overlay. */
.menu-rapido {
    position: absolute; z-index: var(--camada-menu); min-width: 190px; padding: 6px;
    background: var(--bg-card, #0d1420);
    border: 1px solid var(--border-color, rgba(0,170,221,0.25));
    border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.4);
    /* Um rótulo traduzido comprido empurrava o menu para fora do ecrã. */
    max-width: min(280px, calc(100vw - 16px));
    /* E UM MENU COMPRIDO EMPURRAVA-O PARA FORA POR CIMA. Com o travão do topo
       no spark.js, um menu mais alto do que a janela ficava encostado ao topo e
       com o fim de fora; assim rola por dentro e chega-se a todos os itens. */
    max-height: calc(100vh - 16px);
    overflow-y: auto;
}
.menu-rapido button,
.menu-rapido a {
    display: flex; align-items: center; gap: 9px; width: 100%;
    font: inherit; font-size: 13px; text-align: left; text-decoration: none;
    background: none; border: none; border-radius: 6px;
    padding: 9px 12px; color: var(--text-primary); cursor: pointer;
}
.menu-rapido button:hover,
.menu-rapido a:hover { background: var(--accent-blue-soft, rgba(0,170,221,0.12)); }
.menu-rapido button.perigo:hover { color: var(--text-danger); background: rgba(255,90,90,0.10); }
/* UM ITEM QUE NÃO SE PODE USAR FICA, EM CINZENTO. Escondê-lo é dizer que a
   acção não existe; mostrá-lo apagado, com o porquê no title, é dizer que
   existe e não é para esta pessoa — e é isso que se quer que ela saiba. */
.menu-rapido button:disabled { opacity: 0.45; cursor: not-allowed; }
.menu-rapido button:disabled:hover { background: none; color: var(--text-primary); }
.menu-rapido button svg,
.menu-rapido a svg { opacity: 0.75; flex-shrink: 0; }
.menu-rapido .separador { height: 1px; background: var(--border-color); margin: 5px 6px; }
/* UM MENU ABERTO DE DENTRO DE UMA JANELA FICA POR CIMA DELA. O menu nasce no
   <body>, na camada dos menus (60); a janela está na dos diálogos (3000). Com
   uma janela aberta, o «⋯» de uma linha dela abria o menu atrás do véu — via-se
   uma sombra e não se carregava em nada. Enquanto houver uma janela aberta, o
   menu sobe para cima dela; os avisos continuam por cima de tudo. */
body:has(.spark-overlay.active) .menu-rapido { z-index: calc(var(--camada-dialogo) + 1); }
.menu-rapido .cabeca-menu {
    font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px;
    color: var(--text-muted); padding: 7px 12px 4px;
}

/* O DIÁLOGO DAS DUAS DATAS (sparkDialogoDatas). Nasce do menu rápido, que
   veste qualquer botão lá dentro de linha de menu — à largura toda, sem
   fundo, com o realce ao passar. Os dois do pé são botões de formulário e
   vestem-se aqui outra vez do que o .spark-btn lhes dava. */
.menu-rapido.dialogo-datas { padding: 12px; min-width: 250px; }
.menu-rapido.dialogo-datas .cabeca-menu { padding-left: 0; padding-top: 0; }
.menu-rapido.dialogo-datas .spark-field { margin-bottom: 10px; }
.menu-rapido.dialogo-datas .spark-field input { width: 100%; box-sizing: border-box; }
.menu-rapido.dialogo-datas .datas-pe { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
.menu-rapido.dialogo-datas .datas-pe .spark-btn {
    display: inline-flex; width: auto; justify-content: center; gap: 6px;
    padding: 8px 14px; font-size: 12px; font-weight: 700; border-radius: 8px;
    border: 1px solid var(--accent-blue-line); background: var(--accent-blue-soft); color: var(--text-link);
}
.menu-rapido.dialogo-datas .datas-pe .spark-btn:hover { background: var(--accent-blue); color: #fff; }
.menu-rapido.dialogo-datas .datas-pe .spark-btn.ghost {
    border-color: var(--controlo-borda); background: var(--controlo-fundo); color: var(--controlo-texto);
}
.menu-rapido.dialogo-datas .datas-pe .spark-btn.ghost:hover { color: var(--text-primary); background: var(--fill-hover); }

/* --- UMA ENTRADA QUE ABRE PARA O LADO ------------------------------------
   Quatro entradas do Azure num menu que já tinha nove fizeram-no passar de
   243px para 443 — e o próprio spark.js conta a história: a esse tamanho o
   menu deixava de caber em baixo num telemóvel, virava-se para cima, e os
   primeiros itens ficavam fora do ecrã. Uma funcionalidade nova a tornar
   inacessível a que já lá estava.

   Agrupadas atrás de uma linha só, o menu volta ao tamanho de antes e o grupo
   ganha um nome — que é informação que não havia: quatro entradas soltas com o
   mesmo ícone não diziam que eram a mesma família.

   AO TOQUE NÃO HÁ «PASSAR COM O RATO». O painel deixa de ser painel e as
   entradas ficam no menu, em linha, com um filete e o nome do grupo por cima.
   É a mesma marcação a servir as duas mãos. */
/* A BOLINHA DO MENU. Um número pequeno, redondo e do lado da entrada — o que
   ele diz é «há isto à espera de ti», e por isso tem de se ver sem se ler: a
   cor faz o trabalho antes de o número ser lido. Nasceu no /board e veio para
   aqui quando o grupo do Azure passou a existir também na roldana das
   Iniciativas. */
.menu-bolinha {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px; margin-left: 6px;
    border-radius: 9px; background: var(--accent-blue); color: #fff;
    font-size: 11px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums;
}
.menu-bolinha[hidden] { display: none; }

.submenu { position: relative; }
/* A roldana das Iniciativas desenha os itens com `display: block`; o abridor
   precisa de linha para encostar a seta à direita. */
.config-dropdown .submenu-abre { display: flex; align-items: center; gap: 9px; width: 100%; }
.submenu-abre { justify-content: flex-start; }
.submenu-abre .seta { margin-left: auto; opacity: 0.55; flex-shrink: 0; }
.submenu.aberto > .submenu-abre { background: var(--accent-blue-soft, rgba(0,170,221,0.12)); }

/* EM `fixed` E COLOCADO PELO GUIÃO, e não um `left: 100%` em CSS: o
   .menu-rapido tem `overflow-y: auto`, e um overflow num eixo faz o outro
   deixar de ser `visible`. Um painel a sair para o lado seria cortado pela
   própria caixa que o contém. */
.submenu-painel {
    position: fixed; z-index: calc(var(--camada-menu) + 1);
    display: none; min-width: 200px; padding: 6px;
    background: var(--bg-card, #0d1420);
    border: 1px solid var(--border-color, rgba(0,170,221,0.25));
    border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.4);
    max-width: min(280px, calc(100vw - 16px));
    max-height: calc(100vh - 16px); overflow-y: auto;
}
.submenu.aberto > .submenu-painel { display: block; }

/* AS CABEÇAS E OS FILETES VALEM EM QUALQUER MENU, e não só no rápido: o
   painel de um submenu e a roldana das Iniciativas (.config-dropdown) têm
   grupos para nomear, e sem isto a cabeça saía lá como texto solto. */
.submenu-painel .separador,
.config-dropdown .separador { height: 1px; background: var(--border-color); margin: 5px 6px; }
.submenu-painel .cabeca-menu,
.config-dropdown .cabeca-menu {
    font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.6px;
    color: var(--text-muted); padding: 7px 12px 4px;
}

/* O que só existe quando o grupo está em linha. */
.so-em-linha { display: none; }

@media (hover: none) {
    /* O SELECTOR TEM DE GANHAR AO DO KIT. O `.menu-rapido button` vale 0,1,1 e
       põe `display: flex`; um `.submenu-abre` sozinho vale 0,1,0 e perdia — o
       abridor continuava à vista ao toque, por cima das entradas que ele devia
       estar a substituir. É a mesma armadilha do `.sep-btn[hidden]`, que já
       mordeu nesta casa: a regra estava escrita e não fazia nada. */
    .menu-rapido .submenu-abre,
    .config-dropdown .submenu-abre,
    .submenu-abre { display: none; }
    .menu-rapido .submenu-painel,
    .config-dropdown .submenu-painel,
    .submenu-painel {
        position: static; display: block; min-width: 0; max-width: none; max-height: none;
        padding: 0; background: none; border: 0; box-shadow: none; overflow: visible;
    }
    .so-em-linha { display: block; }
}

/* --- Tabelas -------------------------------------------------------------- */
.spark-table { width: 100%; border-collapse: collapse; font-size: 13px; }

.spark-table th {
    text-align: left;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.7px;
    color: var(--text-muted);
    padding: 8px 10px;
    border-bottom: 1px solid var(--border-color);
    white-space: nowrap;
}

.spark-table td { padding: 10px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; color: var(--text-primary); }
.spark-table tbody tr:hover td { background: var(--fill-soft); }
.spark-table td.spark-muted { color: var(--text-muted); }


/* --- Etiquetas de perfil e estado ---------------------------------------- */
.spark-tag {
    display: inline-block;
    font-size: 10px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 4px;
    letter-spacing: 0.5px;
    vertical-align: 1px;
    white-space: nowrap;
    border: 1px solid transparent;
}

/* Cada perfil tem sempre a mesma cor, em toda a plataforma. */
.spark-tag.SUPERADMIN { color: var(--text-danger); border-color: var(--accent-red-line); background: var(--accent-red-soft); }
.spark-tag.BOARD { color: var(--text-gold); border-color: var(--accent-gold-line); background: var(--accent-gold-soft); }
.spark-tag.GESTOR { color: var(--text-link); border-color: var(--accent-blue-line); background: var(--accent-blue-soft); }
.spark-tag.USER { color: var(--text-body); border-color: var(--border-color); }
.spark-tag.ok { color: var(--text-success); border-color: var(--accent-green-line); background: var(--accent-green-soft); }
.spark-tag.inativo { color: var(--text-danger); border-color: var(--accent-red-line); }

.spark-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 8px; vertical-align: -1px; }


/* --- Tipologia da empresa -------------------------------------------------
   A marca que identifica a categoria da empresa, a seguir ao nome.

   A cor é escolhida por quem cria a tipologia, e por isso não pode ser a cor
   do texto: um amarelo claro sobre o tema claro ficava ilegível, e não há
   forma de o impedir na altura da escolha. Fica no ponto e no contorno; o
   texto usa a cor do tema e lê-se sempre.                                   */
.tipologia-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 10.5px;
    font-weight: 600;
    line-height: 1.6;
    padding: 1px 8px 1px 6px;
    margin-left: 8px;
    border-radius: 999px;
    white-space: nowrap;
    vertical-align: 1px;
    color: var(--text-body);
    border: 1px solid var(--tipologia-cor, var(--border-color));
}
.tipologia-badge .ponto {
    display: inline-block;
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--tipologia-cor, var(--border-color));
}
/* Com ícone, o ponto é redundante: o emoji já é a identificação. */


/* --- Escolha de tema ------------------------------------------------------
   O motor de temas (public/js/spark.js) marca com .tema-activo todo o elemento
   que traga um data-tema igual ao tema em vigor. A marcação é dele, o aspecto
   é daqui; qualquer página que ofereça aparência herda os dois. Hoje só o
   /perfil o faz. */

.spark-temas { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }

.spark-tema {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    text-align: left;
    background: var(--fill-soft);
    border: 1px solid var(--border-color);
    border-radius: 12px;
    padding: 12px;
    font-family: inherit;
    cursor: pointer;
    transition: all 0.15s ease;
}

.spark-tema:hover { border-color: var(--accent-blue-line); background: var(--accent-blue-soft); }

.spark-tema.tema-activo {
    border-color: var(--text-link);
    background: var(--accent-blue-soft);
    box-shadow: inset 0 0 0 1px var(--accent-blue);
}

/* A marca fica em baixo à direita e não sobre a tira de cores, onde ficava
   a competir com elas e podia calhar sobre um tom claro. */
.spark-tema.tema-activo::after {
    content: '✓';
    position: absolute;
    right: 12px;
    bottom: 10px;
    font-size: 14px;
    font-weight: 700;
    color: var(--text-link);
}

/* Tira de cores do tema. São valores fixos de propósito: mostram o tema que se
   pode escolher, não o que está em vigor. */
.spark-tema-amostra {
    display: flex;
    width: 100%;
    height: 34px;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--border-color);
}

.spark-tema-amostra i { flex: 1; }
.spark-tema-nome { font-size: 13px; font-weight: 700; color: var(--text-primary); }
.spark-tema-desc { font-size: 11px; color: var(--text-muted); line-height: 1.35; padding-right: 16px; }


/* --- Formulários ---------------------------------------------------------- */
.spark-field { margin-bottom: 14px; }

.spark-field label {
    display: block;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--text-muted);
    margin-bottom: 6px;
}

/* A mesma regra, alcançável por classe solta.
   Só existia dentro de .spark-field, e é por isso que três páginas a copiaram à
   mão ou inventaram um nome — o /perfil chegou a usar uma classe .spark-input
   que nunca existiu, e os campos apareciam como caixas de sistema dentro de um
   painel desenhado. */
.spark-input,
.spark-select,
.spark-textarea,
.spark-field input,
.spark-field select,
.spark-field textarea,
/* OS CAMPOS PERSONALIZADOS SÃO CAMPOS COMO OS OUTROS.
   O /js/campos.js constrói-os sem classe de controlo, e no /iniciativas eles
   apanhavam por acaso as regras do .form-field daquela folha. No /tarefas não
   apanhavam nada: caixas de 19px de altura ao lado de campos nativos de 38, na
   mesma janela. Entram aqui, no kit, com o mesmo desenho de toda a gente. */
.campos-bloco .form-field input,
.campos-bloco .form-field select,
.campos-bloco .form-field textarea {
    width: 100%;
    /* background-color e não o atalho: o atalho apagava a seta que a regra
       global do <select> desenha. */
    background-color: var(--input-bg);
    border: 1px solid var(--border-color);
    border-radius: var(--controlo-raio);
    padding: 10px 12px;
    font-family: inherit;
    font-size: 13px;
    color: var(--text-primary);
    /* outline: none só quando o foco NÃO vem do teclado.
       Em cru, apagava também o anel de :focus-visible, e os campos de pesquisa
       das Iniciativas e do Diário ficavam sem qualquer sinal de onde estava o
       foco — os únicos da plataforma. Com o rato continua sem anel, que é o que
       se quer: aí o cursor a piscar já diz onde se está. */
}
/* Os campos de uma linha têm a altura da ficha, e não a que o ar vertical
   lhes dava (10+10 sobre 13 de letra: 38 com o contorno). O texto centra-se
   sozinho num <input> com altura; a textarea fica de fora, que essa cresce. */
.spark-input:not(textarea),
.spark-select,
.spark-field input:not([type="checkbox"]):not([type="radio"]),
.spark-field select,
.campos-bloco .form-field input:not([type="checkbox"]):not([type="radio"]),
.campos-bloco .form-field select {
    box-sizing: border-box;
    height: var(--controlo-altura);
    padding-top: 0;
    padding-bottom: 0;
}
@media (hover: none) {
    .spark-input:not(textarea),
    .spark-select,
    .spark-field input:not([type="checkbox"]):not([type="radio"]),
    .spark-field select,
    .campos-bloco .form-field input:not([type="checkbox"]):not([type="radio"]),
    .campos-bloco .form-field select { height: var(--controlo-altura-toque); }
}
.spark-input:focus:not(:focus-visible),
.spark-select:focus:not(:focus-visible),
.spark-textarea:focus:not(:focus-visible),
.spark-field input:focus:not(:focus-visible),
.spark-field select:focus:not(:focus-visible),
.spark-field textarea:focus:not(:focus-visible) {
    outline: none;
}
/* ...excepto os campos de data.
   Com o seletor de elemento (0,2,1) perdia para o reset acima (0,3,0) e o anel
   continuava apagado — a mesma armadilha de sempre, a especificidade e não a
   ordem. Com a classe, ganha. */
.spark-input[type="date"]:focus,
.spark-input[type="time"]:focus,
.spark-field input[type="date"]:focus,
input[type="date"]:focus,
input[type="time"]:focus,
input[type="color"]:focus {
    outline: 2px solid var(--accent-blue);
    outline-offset: 2px;
}

/* =============================================================================
   CAMPOS DE DATA: O IOS NÃO OS DEIXA CABER SOZINHO

   Um <input type="date"> não é uma caixa de texto: o Safari do iOS dá-lhe uma
   largura intrínseca calculada a partir do formato e do tamanho da letra, e essa
   largura GANHA ao width: 100% que lhe dermos. Com os 16px que os campos têm de
   ter no telemóvel (abaixo disso o iOS dá zoom ao tocar e nunca o desfaz), a
   largura intrínseca passa a maior do que o modal e o campo sai pela direita.

   O -webkit-appearance: none é o que desliga o desenho nativo e, com ele, a
   largura intrínseca. O ::-webkit-date-and-time-value é o interior do campo:
   por omissão vem centrado — é por isso que se vê "17/08/2026" ao meio de uma
   caixa que transborda — e com margem própria que empurra o conteúdo.

   Isto NÃO se reproduz em Chrome, nem com a emulação de telemóvel ligada: a
   emulação é o renderizador do Chrome com um ecrã pequeno, não o WebKit. Medi
   os campos a 390px e davam os mesmos 296px de todos os outros. É um defeito
   que só um telemóvel a sério mostra.
   ========================================================================== */
input[type="date"],
input[type="time"],
input[type="datetime-local"],
input[type="month"] {
    -webkit-appearance: none;
    appearance: none;
    min-width: 0;
    max-width: 100%;
    box-sizing: border-box;
}
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value {
    text-align: left;
    margin: 0;
}
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit {
    padding: 0;
}
/* O ícone do calendário fica, mas sem empurrar o valor para fora. */
input[type="date"]::-webkit-calendar-picker-indicator {
    margin-left: auto;
    opacity: 0.55;
    cursor: pointer;
    flex-shrink: 0;
    /* Sem medida própria, o ícone é o elemento mais alto da linha e é ele que
       dá a altura ao campo — dois pixéis a mais do que um campo de texto com a
       mesma letra, que se veem quando os dois estão lado a lado. Medido: com o
       ícone escondido o interior dá 16px, igual ao do texto; a 12px dá os
       mesmos 16, porque o ícone traz 2px de espaço próprio de cada lado. */
    width: 12px;
    height: 12px;
    padding: 0;
}

/* O interior do campo é uma caixa flexível própria. Sem isto ela mede-se pelo
   conteúdo e ignora o campo que a contém — que é a razão de o campo sair pela
   direita mesmo com width: 100% e box-sizing: border-box na caixa de fora. */
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit {
    display: flex;
    align-items: center;
    /* Sem o align-items, o flex encosta o valor ao TOPO da caixa: foi o que a
       correcção da largura para o iOS trouxe de lado, e via-se em qualquer
       campo de data com mais altura do que a letra — o "29/08/2026" alto,
       desalinhado do rótulo e dos campos vizinhos. */
    height: 100%;
    /* E a altura tem de ser a mesma com valor e sem valor. No iOS um campo de
       data VAZIO não desenha nada aqui dentro, a caixa colapsa e o campo fica
       mais baixo do que os vizinhos — até alguém escolher uma data, e aí salta
       para o tamanho certo. Um formulário onde os campos mudam de altura à
       medida que se preenche lê-se como se estivesse a partir-se.

       A entrelinha reservada é a mesma que o campo já usa (1.2), por isso com
       valor nada muda: em Chrome o interior mede 16px nos dois casos, medido.
       Isto não se reproduz em Chrome — é do WebKit, como o resto deste bloco. */
    min-height: 1.2em;
    min-width: 0;
    overflow: hidden;
}
/* O mesmo para o interior que o Safari usa quando não há nada escrito. */
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value {
    min-height: 1.2em;
    /* Também aqui a cor: no Safari, o valor todo passa por este pseudo-elemento
       e as partes ainda não editadas ficavam num azul esbatido, com as barras
       a preto — três tons dentro da mesma caixa. O -webkit-text-fill-color é o
       que ele respeita; só o color não chega. */
    color: inherit;
    -webkit-text-fill-color: currentColor;
}
/* O contentor de tudo, pela mesma razão: no Safari é ele que pinta o que
   ainda não foi escrito. */
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit {
    color: inherit;
    -webkit-text-fill-color: currentColor;
}
input[type="date"]::-webkit-datetime-edit-fields-wrapper {
    min-width: 0;
    flex-shrink: 1;
    padding: 0;
}
/* Cada campo — dia, mês, ano — traz 1px de espaço próprio em cima e em baixo.
   São os dois pixéis que faziam o campo de data ficar mais alto do que o campo
   de texto ao lado, na mesma linha e com a mesma letra. */
input[type="date"]::-webkit-datetime-edit-day-field,
input[type="date"]::-webkit-datetime-edit-month-field,
input[type="date"]::-webkit-datetime-edit-year-field,
input[type="date"]::-webkit-datetime-edit-text,
input[type="time"]::-webkit-datetime-edit-hour-field,
input[type="time"]::-webkit-datetime-edit-minute-field,
input[type="time"]::-webkit-datetime-edit-text {
    padding: 0;
    /* Uma cor só para o campo inteiro. O Safari pinta as partes AINDA NÃO
       EDITADAS de um tom próprio e deixa os separadores com a cor do campo: o
       resultado eram os números a azul e as barras a preto, dentro da mesma
       caixa. O -webkit-text-fill-color é o que ele respeita nestas partes; só
       o color não chega.

       Em Chrome já estava tudo igual — medi as cinco partes e davam todas
       rgb(26,37,48) — por isso isto não se reproduz aqui e é do WebKit, como o
       resto deste bloco. */
    color: inherit;
    -webkit-text-fill-color: currentColor;
}

/* E o campo inteiro comporta-se como as outras caixas: altura própria, conteúdo
   alinhado à esquerda. O iOS dá-lhe uma altura maior do que aos vizinhos — é o
   que faz o campo de data parecer o dobro dos outros no mesmo formulário. */
input[type="date"],
input[type="time"] {
    /* inline-flex e não block: com block, o interior do campo assenta onde o
       WebKit quiser e o campo fica 2px mais alto do que os irmãos na mesma
       linha. Assim tem a altura dos outros e o conteúdo ao meio — que é a
       regra desta plataforma para tudo o que partilha uma linha. */
    display: inline-flex;
    align-items: center;
    /* O interior do campo de data tem caixa de linha própria e vinha 2px mais
       alta do que a de um campo de texto com a mesma letra — dois pixéis que se
       veem quando os dois estão lado a lado. Uma entrelinha explícita nos dois
       lados iguala-os. */
    line-height: 1.2;
}
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit { line-height: 1.2; }


/* O texto de sugestão é do sistema por omissão — rgb(117,117,117) em Chrome,
   outro em Safari — e não tem nada que ver com as fichas desta plataforma. É o
   cinzento do que ainda não foi escrito: o --text-muted. */
::placeholder { color: var(--text-muted); opacity: 1; }

/* =============================================================================
   NENHUM SELECT COM O DESENHO DO SISTEMA

   Um <select> que não seja estilado é desenhado pelo sistema operativo: no macOS
   é uma caixa cinzenta com uma seta dupla e um raio de canto próprio, ao lado de
   campos e botões desenhados por nós. Bastava um por estilar numa linha para
   toda a linha parecer desalinhada — e havia-os na barra dos filtros e em cada
   linha de acção da ficha da iniciativa.

   A regra é do elemento, não de uma classe, precisamente para não haver um
   select novo que escape. Quem quiser outro aspecto acrescenta por cima; o que
   não pode é ficar por decidir.

   Cuidado ao escrever regras sobre selects: usa background-color, nunca o
   atalho background — o atalho apaga a seta.
   ========================================================================== */
select {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%237d97a6' d='M3 5l3 3 3-3z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 9px center;
    padding-right: 26px;
    font-family: inherit;
    cursor: pointer;
    text-overflow: ellipsis;
}
/* O select nativo do macOS ignorava a maior parte disto enquanto tivesse
   appearance auto; com appearance none, herda o resto do tema. */

/* UM CAMPO DESACTIVADO TEM DE SE VER DESACTIVADO. Só o select tinha esta
   regra — um <input type="date"> ou um <textarea> desligados ficavam
   pixel-a-pixel iguais aos editáveis: mesmo fundo, mesma cor, mesmo
   contorno. A única pista era o cursor a mudar de "texto" para "seta" ao
   passar por cima, e num telemóvel nem isso existe — a diferença era zero.
   Isto lê-se como "partido", não como "não é teu".

   E O SELECT TEM DE DIZER O MESMO QUE ELES. Ficou de fora quando esta regra
   foi escrita: levava só `opacity: 0.6`, que sobre branco quase não se lê.
   Numa linha de acção da ficha da iniciativa, as duas datas ficavam
   visivelmente desligadas e o responsável ao lado continuava com fundo branco
   e texto preto — três campos igualmente bloqueados, dois a dizê-lo e um a
   dizer o contrário. Medido: opacity 0.6 com fundo rgb(255,255,255) e cor
   rgb(26,37,48), ao lado de fill-soft com text-muted.

   background-color e NUNCA o atalho background: o atalho apagava a seta, que
   é uma imagem de fundo. */
input:disabled, textarea:disabled {
    cursor: default;
    opacity: 0.65;
    background: var(--fill-soft);
    color: var(--text-muted);
}
select:disabled {
    cursor: default;
    opacity: 0.65;
    background-color: var(--fill-soft);
    color: var(--text-muted);
}

/* E OS CAMPOS PERSONALIZADOS TAMBÉM SE DESLIGAM À VISTA.
   As duas regras acima valem (0,1,1) e a que dá fundo aos campos
   personalizados vale (0,2,1) — ganhava-lhes. Numa ficha aberta em consulta,
   o título e as datas ficavam cinzentos e o «Esforço» ao lado continuava
   branco: campos igualmente bloqueados, uns a dizê-lo e outro a dizer o
   contrário. É o mesmo defeito que o comentário aqui em cima descreve, uma
   camada mais abaixo.

   Escrito com a mesma força da regra que dá a cor, para não voltar a perder. */
.campos-bloco .form-field input:disabled,
.campos-bloco .form-field textarea:disabled,
.campos-bloco .form-field select:disabled {
    background-color: var(--fill-soft);
    color: var(--text-muted);
}

/* Caixa de selecção com texto ao lado.
   Uma classe partilhada porque cada página tinha resolvido isto à sua maneira e
   nenhuma resistia à cascata: dentro de um .spark-field, a regra
   `.spark-field label { display: block }` tem mais especificidade do que uma
   classe solta, o display:flex não pegava e a caixa e o texto passavam a
   alinhar-se pela linha de base — a caixa cinco pixels acima do meio da
   palavra. O selector abaixo cobre os dois casos, dentro e fora do campo.

   Regra da casa: um controlo e o seu texto alinham sempre pelo meio, nunca pela
   linha de base. Uma caixa de 18px ao lado de letras de 12px não tem linha de
   base comum — só um meio. */
.spark-check,
.spark-field label.spark-check,
.form-field label.spark-check {
    display: flex;
    align-items: center;
    gap: 9px;
    margin-bottom: 0;
    font-size: 13px;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0;
    line-height: 1.35;
    color: var(--text-primary);
    cursor: pointer;
}

/* A margem que o browser põe à volta da caixa desloca-a dentro da linha. */
/* A cor de marca em todos, e não só nos que estão dentro de um .spark-check:
   os das linhas das tabelas ficavam com o azul do sistema operativo. */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent-blue); }

/* O «hidden» TEM DE GANHAR AO display DA CLASSE. Um pisco escondido com
   [hidden] continuava a aparecer — o display:flex de cima tem a mesma
   especificidade que a regra do browser e vem depois. Aparecia um quadrado
   marcado, sozinho, sem uma palavra ao lado. */
.spark-check[hidden],
.spark-field label.spark-check[hidden],
.form-field label.spark-check[hidden] { display: none; }

/* A MESMA ARMADILHA, EM TODO O LADO. Qualquer elemento com display escrito
   numa classe ganha ao [hidden] do browser — mesma especificidade, e a regra
   da classe vem depois. Apareceu hoje três vezes: um pisco marcado sem texto,
   um palco por cima de outro, e um aviso vazio. Isto fecha a família: o
   atributo que diz «não mostres» passa a ganhar sempre.

   É de propósito que leva !important e um selector curto — o que está aqui
   escrito é «hidden quer dizer escondido», e nada que se escreva a seguir
   deve poder contrariá-lo. */
[hidden] { display: none !important; }

.spark-check input[type="checkbox"] {
    width: 18px;
    height: 18px;
    margin: 0;
    flex-shrink: 0;
    accent-color: var(--accent-blue);
    cursor: pointer;
}

.spark-input:focus,
.spark-select:focus,
.spark-textarea:focus,
.spark-field input:focus,
.spark-field select:focus,
.spark-field textarea:focus { border-color: var(--text-link); box-shadow: 0 0 0 3px var(--accent-blue-soft); }

/* --- Foco visível ---------------------------------------------------------

   Sem border-radius aqui. Esteve, com a ideia de arredondar o anel — mas o
   border-radius é do elemento, não do outline: o que fazia era mudar a forma do
   próprio botão ao receber foco. Um campo de 8px de canto passava a 6, o atalho
   das acções de 10 para 6, e como essas propriedades têm transição, os cantos
   ANIMAVAM a encolher quando se lhes chegava por teclado. O outline já segue o
   raio do elemento sozinho.

   Não havia nenhum em toda a plataforma, tirando os campos de formulário: quem
   navega por teclado via o foco andar sem saber onde estava. O :focus-visible
   só se acende quando o foco vem do teclado, por isso o rato continua sem
   contorno nenhum.
   -------------------------------------------------------------------------- */
.spark-btn:focus-visible,
.spark-row-btn:focus-visible,
.menu-rapido button:focus-visible,
.menu-rapido a:focus-visible,
.spark-tema:focus-visible,
.hamburger-btn:focus-visible,
.hamburger-dropdown-item:focus-visible,
.refresh-btn:focus-visible,
.toolbar-btn:focus-visible,
.view-tab:focus-visible,
.pilar-header:focus-visible,
.iniciativa-row:focus-visible,
.filter-select:focus-visible,
.vista-btn:focus-visible,
.sep-btn:focus-visible,
.accao-btn:focus-visible,
.gestao-stat-card:focus-visible,
.exec-aloquete:focus-visible,
.empresa-card:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
a:focus-visible,
/* Um campo de data é um conjunto de segmentos: o foco vai para o dia ou para o
   mês, e o Chrome não dá :focus-visible ao campo que os contém. Sem esta linha,
   os campos de data eram os únicos da plataforma sem sinal nenhum de onde
   estava o foco. Aqui é :focus e não :focus-visible de propósito — num campo de
   data não há cursor a piscar a dizer onde se está. */
input[type="date"]:focus,
input[type="time"]:focus,
input[type="color"]:focus {
    outline: 2px solid var(--accent-blue);
    outline-offset: 2px;
}

/* Salto para o conteúdo: fora do ecrã até receber foco de teclado. */
.spark-skip {
    position: absolute;
    left: 12px;
    top: -60px;
    z-index: 10000;
    background: var(--bg-panel);
    color: var(--text-primary);
    border: 1px solid var(--accent-blue);
    border-radius: 8px;
    padding: 10px 16px;
    font-size: 13px;
    font-weight: 600;
    text-decoration: none;
    transition: top 0.15s;
}
.spark-skip:focus { top: 12px; }

/* Campo de ficheiro que continua a existir para o teclado e para os leitores de
   ecrã. O hidden tirava-o da ordem de tabulação e deixava a função inalcançável. */
.campo-escondido {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}


/* --- Mensagens e avisos --------------------------------------------------- */
.spark-msg { border-radius: 8px; padding: 11px 14px; font-size: 13px; margin-bottom: 16px; display: none; }
.spark-msg.err { display: block; background: var(--accent-red-soft); border: 1px solid var(--accent-red-line); color: var(--text-danger); }
.spark-msg.ok { display: block; background: var(--accent-green-soft); border: 1px solid var(--accent-green-line); color: var(--text-success); }
/* Um estado que não é avaria — «ainda não estás em nenhum projeto» — lê-se
   como informação, não como erro. */
.spark-msg.info { display: block; background: var(--accent-blue-soft, rgba(37, 99, 235, .08)); border: 1px solid var(--accent-blue-line, rgba(37, 99, 235, .25)); color: var(--text-body); }

.spark-aviso {
    background: rgba(217, 154, 16, 0.10);
    border: 1px solid rgba(217, 154, 16, 0.40);
    border-radius: 8px;
    padding: 12px 14px;
    margin: 0 0 20px;
    font-size: 13.5px;
    color: var(--text-warning);
}

.spark-toast {
    position: fixed;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--bg-panel);
    border: 1px solid var(--accent-blue);
    color: var(--text-primary);
    border-radius: 10px;
    padding: 10px 18px;
    font-size: 13px;
    z-index: var(--camada-aviso);
    display: none;
    box-shadow: var(--shadow-lg);
}

.spark-toast.error { border-color: var(--text-danger); }


/* --- Modais --------------------------------------------------------------- */
.spark-overlay {
    position: fixed;
    inset: 0;
    background: rgba(10, 16, 26, 0.55);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: var(--camada-dialogo);
    padding: 16px;
}

.spark-overlay.active { display: flex; }

.spark-modal {
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    border-radius: 14px;
    width: 440px;
    max-width: 100%;
    max-height: calc(100vh - 48px); max-height: calc(100dvh - 48px);
    overflow-y: auto;
    padding: 22px;
    box-shadow: var(--shadow-lg);
}

.spark-modal { position: relative; }
.spark-modal h3 { font-size: 15px; margin-bottom: 16px; padding-right: 34px; }

/* UM MODAL ALTO ROLA POR DENTRO, E NÃO POR FORA.
   O .spark-modal rola inteiro: o título, o × e a fila de botões vão embora com
   o conteúdo. Numa janela curta isso não se nota; numa alta, é o desenho a
   partir-se. O resumo do tempo num telemóvel de 390 media 1754px de conteúdo
   dentro de 796 — o «Submeter» ficava 960px abaixo da dobra, dois ecrãs e
   meio, e quem rolasse até lá já não tinha o × em lado nenhum (ia parar a
   y = −925).

   Com esta classe, o corpo é a única coisa que rola. O título e os botões
   ficam onde a mão os procura: em cima e em baixo. */
.spark-modal-alto { display: flex; flex-direction: column; overflow: hidden; }
.spark-modal-alto > h3 { flex: 0 0 auto; }
.spark-modal-alto > .spark-modal-corpo { flex: 1 1 auto; overflow-y: auto; min-height: 0; margin: 0 -22px; padding: 0 22px; }
.spark-modal-alto > .spark-modal-actions { flex: 0 0 auto; }

/* A ZONA SEGURA DO TELEFONE. Sem isto, a fila de botões — que é justamente
   onde é obrigatório chegar — assenta em cima do indicador de casa do iPhone. */
@media (max-width: 680px) {
    .spark-overlay { padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px)); }
    .spark-modal { max-height: calc(100dvh - 48px - env(safe-area-inset-bottom, 0px)); }
}

/* O × de fechar. As janelas do /admin só se fechavam por "Cancelar" ou pelo
   véu; o × que existe nas Iniciativas não estava cá, e é onde a mão vai. */
.spark-modal-close {
    position: absolute;
    top: 10px;
    right: 12px;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    border-radius: 8px;
    font-size: 22px;
    line-height: 1;
    color: var(--text-muted);
    cursor: pointer;
    transition: color 0.15s, background 0.15s;
}
.spark-modal-close:hover { color: var(--text-primary); background: var(--fill-hover); }
.spark-modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 18px; }

/* Diálogo de confirmação (sparkConfirm). Mais estreito do que um modal de
   formulário: é uma pergunta, não um ecrã. */
.spark-confirm, .spark-pergunta { max-width: 460px; }
.spark-pergunta .spark-textarea { margin-top: 12px; resize: vertical; min-height: 74px; }
.spark-confirm .spark-confirm-texto { font-size: 13.5px; line-height: 1.55; color: var(--text-body); white-space: pre-line; }
/* A ESCOLHA DENTRO DA PERGUNTA. Fica entre o texto e o campo de escrever o
   nome, encostada à esquerda como o resto. A caixa e a frase alinham pelo
   MEIO — estão na mesma linha, e o que está na mesma linha alinha pelo meio;
   com `align-items: start` a caixa subia acima da primeira letra em frases de
   duas linhas. */
.spark-confirm .spark-confirm-opcao { display: flex; align-items: center; gap: 8px;
    margin-top: 14px; font-size: 13px; line-height: 1.45; color: var(--text-body); cursor: pointer; }
.spark-confirm .spark-confirm-opcao input { width: 15px; height: 15px; flex: 0 0 auto; margin: 0; cursor: pointer; }
/* A LISTA DE ESCOLHAS DENTRO DA PERGUNTA. Uma linha por coisa, com o nome em
   cima e o que ela leva por baixo — é a segunda linha que faz a decisão: «12
   requisitos, 40 respostas do cliente, 2 entregas» é outra coisa do que «6
   requisitos, sem respostas». Rola quando são muitas, para a janela não
   crescer até fora do ecrã. */
.spark-confirm .spark-confirm-lista { margin-top: 14px; max-height: 260px; overflow: auto;
    border: 1px solid var(--border-color); border-radius: var(--controlo-raio); }
.spark-confirm .spark-confirm-lista-titulo { padding: 9px 12px 7px; font-size: 11px; font-weight: 600;
    text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted);
    border-bottom: 1px solid var(--border-color); }
.spark-confirm .spark-confirm-lista-linha { display: flex; align-items: flex-start; gap: 10px;
    padding: 9px 12px; cursor: pointer; }
.spark-confirm .spark-confirm-lista-linha + .spark-confirm-lista-linha { border-top: 1px solid var(--border-color); }
.spark-confirm .spark-confirm-lista-linha:hover { background: var(--bg-hover); }
.spark-confirm .spark-confirm-lista-linha input { width: 15px; height: 15px; flex: 0 0 auto; margin: 2px 0 0; cursor: pointer; }
.spark-confirm .spark-confirm-lista-linha span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.spark-confirm .spark-confirm-lista-linha b { font-size: 13px; font-weight: 600; color: var(--text-body); }
.spark-confirm .spark-confirm-lista-linha small { font-size: 11.5px; color: var(--text-muted); }
/* O texto entra por textContent — é o que impede que um nome de empresa com um
   < lá dentro se transforme em marcação. O preço é que o \n deixa de valer
   nada, e o resumo do que se vai apagar saía num parágrafo só, com as
   iniciativas coladas às contas. O pre-line devolve as quebras sem devolver o
   HTML. */
.spark-confirm .spark-field { margin-top: 14px; }
.spark-confirm .spark-field label { font-size: 12px; }

/* UMA PERGUNTA FICA SEMPRE POR CIMA DE TUDO, incluindo da nota aberta dos
   post-its. O quadro precisou de um z-index acima de --camada-dialogo (3000)
   para não ficar por trás da gaveta do telemóvel, que também vive nessa
   camada — mas isso deixava "Apagar esta nota?" a nascer por baixo da
   própria nota, sem se ver, como se o clique não tivesse feito nada. Uma
   pergunta interrompe o que estiver a acontecer; não pode perder a um ecrã
   que já estava aberto. */
.spark-overlay:has(.spark-confirm),
.spark-overlay:has(.spark-pergunta),
.spark-overlay.spark-escolher {
    z-index: 3500;
}

@media (max-width: 820px) {
    /* No telemóvel os dois botões ficam lado a lado e do tamanho do dedo. */
    .spark-confirm .spark-modal-actions, .spark-pergunta .spark-modal-actions { gap: 10px; }
    .spark-confirm .spark-modal-actions .spark-btn,
    .spark-pergunta .spark-modal-actions .spark-btn { flex: 1; min-height: 44px; justify-content: center; }
    .spark-pergunta .spark-textarea { font-size: 16px; }
}


/* --- Carregamento --------------------------------------------------------- */
.spark-loading { text-align: center; padding: 60px 0; color: var(--text-muted); }

.spark-spinner {
    width: 34px;
    height: 34px;
    border: 3px solid var(--fill-hover);
    border-top-color: var(--text-link);
    border-radius: 50%;
    margin: 0 auto 12px;
    animation: spark-spin 0.9s linear infinite;
}

@keyframes spark-spin { to { transform: rotate(360deg); } }

.spark-empty { text-align: center; padding: 60px 20px; color: var(--text-muted); font-size: 14px; }
.spark-empty a { color: var(--text-link); }


/* =============================================================================
   5. IMPRESSÃO
   -----------------------------------------------------------------------------
   Ao imprimir (ou ao gerar PDF a partir do browser) a cromagem não interessa:
   sai o conteúdo, não a navegação.
   ========================================================================== */
@media print {
    .spark-header { display: none !important; }
}

/* Atalho para as minhas acções, no cabeçalho. Discreto: é um sinal, não um
   botão principal. */
.spark-atalho-tarefas,
.spark-atalho-tickets,
.spark-atalho-deposito,
.spark-atalho-postits,
.spark-atalho-ideias {
    display: inline-flex; align-items: center; gap: 4px;
    position: relative;
    background: none; font-family: inherit; cursor: pointer;
    font-size: 13px; text-decoration: none; color: var(--text-body);
    padding: 5px 9px; border: 1px solid var(--border-color); border-radius: 8px;
}
/* OS TRÊS ATALHOS SÃO A MESMA COISA: uma porta pequena no cabeçalho. Um
   dourado, um azul e um cinzento lado a lado leem-se como três estados de
   alguma coisa — e não são estados de nada. A cor fica para o contador
   vermelho, que é o único que ali tem mesmo de saltar à vista. */
.spark-atalho-ideias[hidden] { display: none; }
.spark-atalho-deposito[hidden] { display: none; }
.spark-atalho-postits[hidden] { display: none; }
.spark-atalho-tarefas:hover,
.spark-atalho-tickets:hover,
.spark-atalho-deposito:hover,
.spark-atalho-postits:hover,
.spark-atalho-ideias:hover { color: var(--text-primary); background: var(--fill-hover); }


/* =============================================================================
   TELEMÓVEL E TABLET ESTREITO
   =============================================================================

   A plataforma nasceu para ecrãs largos: até aqui, o breakpoint mais baixo de
   todo o projecto era 620px, numa página só. O que se segue é o mínimo para
   que o que ela mostra continue alcançável com um dedo — sem mexer na
   linguagem visual: mesma paleta, mesmos cantos, mesmas sombras.

   As duas regras que mais resolvem estão aqui: os campos a 16px e as tabelas
   com scroll próprio.
   ========================================================================== */

@media (max-width: 820px) {
    .spark-page { padding: 16px 14px 60px; }

    /* Nenhum campo abaixo de 16px. O Safari do iOS faz zoom ao focar um campo
       com letra mais pequena — e não desfaz o zoom ao sair dele. A página fica
       presa, com o cabeçalho fora do ecrã, até o utilizador afastar os dedos.
       Acontecia em todos os campos da plataforma, a começar pelo login.

       O !important é raro nesta folha e aqui é deliberado: quase todos os
       campos são estilados por classe (.filter-select, .exec-input, .inline-select),
       e uma classe ganha sempre a um seletor de elemento, esteja onde estiver na
       folha. Medi a página das Iniciativas depois da primeira tentativa: dos 50
       campos, nenhum tinha obedecido. Isto não é uma preferência de estilo — é
       uma limitação do dispositivo, e tem de ganhar a tudo. */
    input, select, textarea { font-size: 16px !important; }

}

@media (max-width: 480px) {
    /* O menu tinha 320px fixos e ficava a 4px da margem num ecrã de 390px.
       Abaixo de 364px transbordava — e 360px é a largura de meio Android. */
    .hamburger-dropdown { min-width: 0; width: calc(100vw - 24px); right: -12px; }
}

/* A BARRA DE "ESCOLHER VÁRIAS", partilhada — nasceu nas Acções da Iniciativa e
   passou a servir também as Tarefas, que juntam acções de várias empresas
   numa lista só. Uma folha, um sítio a mudar quando o desenho mudar.

   O [hidden] é da folha do BROWSER e vale 0,0,1; este selector vale 0,1,0 e
   ganha-lhe. O display: flex apagava o hidden, a barra ficava sempre no ecrã
   com o contador vazio e três botões que não faziam nada. Quem escreve
   display num elemento que também usa [hidden] tem de o dizer aqui. */
.tarefas-lote[hidden] { display: none; }
.tarefas-lote {
    display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
    margin-bottom: 10px; padding: 8px 12px;
    background: var(--controlo-fundo-acento);
    border: 1px solid var(--controlo-borda-acento);
    border-radius: var(--controlo-raio);
}
.tarefas-lote-conta { font-size: 12px; font-weight: 600; color: var(--controlo-texto-acento); margin-right: auto; }
.tarefas-lote .spark-btn { font-size: 11px; padding: 6px 12px; }

/* Sem rato: o que se revelava ao passar por cima tem de estar sempre visível, e
   o que se toca tem de ter tamanho de dedo. O mínimo é 44px (Apple) / 48px
   (Android); nada na plataforma lá chegava — os botões de apagar tinham 18px. */
@media (hover: none) {
    .spark-row-btn, .btn-mini, .board-busca .limpar {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
    }
    /* O padding sozinho dava 42px — perto, e perto não conta num dedo. */
    .menu-rapido button, .menu-rapido a { padding: 12px; min-height: 44px; }
    .hamburger-btn { padding: 13px 14px; min-height: 44px; }
    .tarefas-lote .spark-btn { flex: 1 1 auto; min-height: 44px; justify-content: center; }

    /* A moldura comum é a que aparece em todas as páginas, e era toda pequena
       de mais: o botão de actualizar tinha 12px de largura, as entradas do menu
       38px de altura, o atalho das acções 30px. Medido com o Chrome a emular um
       telemóvel de 390px, não a olho. */
    .spark-brand, .spark-btn, .vista-btn, .spark-tema { min-height: 44px; }
    /* O relógio do tempo é classe própria do template das Acções e ficou de
       fora desta lista — 36px ao lado de um botão de actualizar de 44. E o ×
       dos modais tinha 32: num modal alto é o único caminho para fechar sem
       rolar até ao fundo. */
    .ts-relogio { min-height: 44px; }
    .spark-modal-close { width: 44px; height: 44px; top: 6px; right: 8px; }
    /* min-height e não só padding: a entrada de sair tem medidas próprias e
       ficava dois pixéis abaixo das outras. */
    .hamburger-dropdown-item { padding-top: 14px; padding-bottom: 14px; min-height: 44px; }
}

/* Tiras que deslizam ------------------------------------------------------
   Blocos de cartões pequenos que, empilhados, enchiam o ecrã antes de se ver o
   conteúdo. Em vez de crescerem para baixo, passam a correr de lado, com
   paragem em cada cartão. É a forma de manter seis indicadores à mão sem gastar
   meio telemóvel com eles. */
@media (max-width: 820px) {
    .spark-slide {
        display: flex;
        gap: 10px;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 4px;
        /* Sangra até às margens: dá a entender que há mais para lá do rebordo. */
        margin-left: -14px;
        margin-right: -14px;
        padding-left: 14px;
        padding-right: 14px;
        /* O scroll-snap ignora o padding e encosta o primeiro cartão à borda do
           contentor — que a margem negativa pôs a zero. Resultado: a tira
           arrancava 14px à esquerda de tudo o resto da página, e via-se logo por
           cima e por baixo dela. O scroll-padding é o que diz ao snap onde é
           que a área útil começa mesmo. */
        scroll-padding-left: 14px;
        scroll-padding-right: 14px;
    }
    .spark-slide > * {
        flex: 0 0 46%;
        scroll-snap-align: start;
        min-width: 0;
    }
    /* Barra de rolamento discreta: presente para se perceber que há mais. */
    .spark-slide::-webkit-scrollbar { height: 3px; }
    .spark-slide::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 3px; }
}

/* Tabela larga: rola dentro da sua própria caixa em vez de arrastar a página
   inteira de lado. Sem isto, o cabeçalho fixo desalinhava-se e cada rolamento
   vertical saía na diagonal. */
.spark-scroll-x {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.spark-scroll-x .spark-table { min-width: 640px; }
/* Com as colunas de contexto escondidas num telemóvel, o mínimo de 640px
   obrigava a arrastar a tabela de lado para chegar ao estado e às acções — que
   é o que se veio fazer. Sobram cinco colunas: cabem em 480. */
@media (max-width: 700px) {
    .spark-scroll-x .spark-table { min-width: 480px; }
    /* E o padding de 10px em cada célula são 100px numa linha de sete: num
       ecrã estreito é largura que falta ao nome. */
    .spark-scroll-x .spark-table th,
    .spark-scroll-x .spark-table td { padding-left: 7px; padding-right: 7px; }
}


/* =============================================================================
   FERRAMENTAS DO CABEÇALHO, TODAS IGUAIS
   =============================================================================

   Tinham cada uma a sua forma: o atalho das acções era uma pastilha com texto,
   a ajuda um círculo de 32px, o menu um rectângulo de cantos 6 e o botão de
   actualizar um quadrado azul de 33. Lado a lado, liam-se como peças vindas de
   sítios diferentes.

   Passam todas pela mesma medida e pelo mesmo canto. A cor continua a
   distinguir o que é distinto — o azul do actualizar, o vermelho do contador —
   mas a forma deixa de ser mais um sinal a competir.
   ========================================================================== */
.spark-atalho-tarefas,
.spark-atalho-tickets,
.spark-atalho-deposito,
.spark-atalho-postits,
.spark-atalho-ideias,
.hamburger-btn,
.refresh-btn {
    box-sizing: border-box;
    /* 44 e não 42: é o mínimo em que um dedo acerta. Cabem os quatro no
       cabeçalho de um telemóvel de 390px com a marca ao lado. */
    width: 44px;
    height: 44px;
    min-width: 44px;
    padding: 0;
    border-radius: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

/* ATENÇÃO A QUEM ACRESCENTAR REGRAS AQUI: o bloco acima é UMA LISTA DE
   SELECTORES de cinco linhas. Uma regra nova enfiada entre a penúltima e a
   última linha dela não é uma regra nova — é mais um selector, e o que fica a
   valer para os outros quatro botões é o corpo errado. Foi o que aconteceu com
   estas cores: os quatro atalhos perderam a medida de 44px, o canto, o
   alinhamento ao centro — e os riscos do hamburguer encostaram-se à esquerda
   dentro do próprio botão. Regra nova começa DEPOIS do } de fecho. */

/* AS CORES DO ACTUALIZAR. Viviam na iniciativas.css, de quando ele era só
   daquela página; o botão passou a estar em todas, e uma folha que só uma
   carrega deixava-o sem fundo nem contorno em todas as outras.

   O mesmo fundo e o mesmo contorno das outras ferramentas do cabeçalho: o azul
   fazia dele o elemento mais forte da barra, quando é o menos usado. */
.refresh-btn {
    background: var(--bg-panel);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.refresh-btn:hover {
    background: var(--accent-blue-soft);
    border-color: var(--accent-blue-line);
    color: var(--text-link);
}

/* A VOLTA ENQUANTO CARREGA. Sem ela, tocar no botão não dá sinal nenhum de
   que alguma coisa aconteceu — e quem não vê resposta toca outra vez. */
.refresh-btn.a-rodar svg { animation: refresh-roda .8s linear infinite; }
@keyframes refresh-roda { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    .refresh-btn.a-rodar svg { animation: none; opacity: .5; }
}

/* O contador vai por cima do canto, em vez de empurrar o conteúdo para o lado
   e alargar o botão para fora da medida comum. */
.spark-atalho-tarefas, .spark-atalho-tickets { position: relative; gap: 0; }
.spark-atalho-tarefas .badge-migracoes,
.spark-atalho-tickets .badge-migracoes,
.spark-atalho-postits .badge-migracoes,
.spark-atalho-deposito .badge-migracoes { position: absolute; top: -5px; right: -5px; margin-left: 0; }

/* O nome de cada atalho só existe para a barra do polegar (a seguir). No
   cabeçalho o ícone chega e o title diz o resto. */
.atalho-rotulo { display: none; }


/* =============================================================================
   O PONTO DE PRAZO DOS CARTÕES
   =============================================================================

   Vermelho já passou, amarelo está a chegar, verde há folga. Um sinal que se
   lê sem contar dias: com vinte cartões no ecrã, saber quais ardem era ler
   vinte datas e fazer vinte contas.

   No canto de cima à direita, em todas as vistas e nos dois tamanhos de ecrã
   — é o canto que fica livre em qualquer cartão, e o olho já lá vai buscar
   estado noutros sítios da plataforma.

   Cinco de raio, dez de diâmetro: mais pequeno perde-se ao lado do texto,
   maior passa a ser um selo e compete com o título. */
.cartao-ponto, .kanban-ponto {
    position: absolute; top: 9px; right: 9px;
    width: 10px; height: 10px; border-radius: 50%;
    /* O anel do fundo separa-o do cartão sem lhe dar contorno próprio: sobre
       um cartão claro, um verde sem anel encosta-se ao branco e some-se. */
    box-shadow: 0 0 0 2px var(--bg-card);
}
.cartao-ponto.tarde, .kanban-ponto.tarde { background: var(--controlo-perigo-cheio); }
.cartao-ponto.perto, .kanban-ponto.perto { background: var(--text-warning); }
.cartao-ponto.ok, .kanban-ponto.ok { background: var(--controlo-sucesso-cheio); }
/* O cartão passa a ser a régua do ponto, e o título encolhe para não passar
   por baixo dele. */
.cartao, .kanban-card { position: relative; }
.cartao .cartao-titulo, .kanban-card-title { padding-right: 16px; }

/* =============================================================================
   A BARRA DO POLEGAR
   =============================================================================

   No telemóvel, os atalhos e o menu saem do cabeçalho e descem para uma barra
   colada ao fundo do ecrã — o sítio onde o polegar está, e o padrão que o iOS
   e o Android usam há anos para a navegação principal. O cabeçalho fica só com
   a marca e o nome da secção, numa linha.

   Quem move os botões para cá é o spark.js (montarBarraPolegar), a partir dos
   820px — a mesma largura em que o cabeçalho já começa a dobrar. A barra não
   depende de (hover: none) de propósito: é a LARGURA que faz faltar espaço no
   cabeçalho, não o dedo.

   A altura vai numa variável porque três coisas dependem dela: a margem em
   baixo do <body> (para a barra não tapar o fim da página), os avisos que
   nascem em baixo (sobem para cima da barra) e a folha do menu.

   A zona do indicador de casa do iPhone (env(safe-area-inset-bottom)) só
   existe com viewport-fit=cover no _head.html.twig. Sem ele mede zero. */
:root { --barra-polegar-altura: 0px; }
html.com-barra-polegar {
    --barra-polegar-altura: calc(58px + env(safe-area-inset-bottom, 0px));
}

/* O body das Iniciativas tem height: 100% e overflow: hidden (só a caixa de
   dentro rola). Sem border-box, a margem somava-se à altura e a barra tapava
   na mesma o fim da lista. Com border-box serve as duas espécies de página. */
/* ===================================================================
   A PÁGINA NUA: SÓ A JANELA.

   O Board abre janelas de um projeto sem sair do Board, num iframe (ver o
   /projecto/{id}/janela/...). Três delas moram em parciais próprios e
   servem-se sozinhas. As outras quatro — editar o projeto, as fases, os
   utilizadores, os campos — são janelas do /iniciativas e do /gestao, com
   os rótulos, os ajudantes e o recarregar da página onde nasceram: tirá-las
   de lá era mudar seiscentas linhas por causa da moldura.

   Então não se tiram. A página carrega inteira dentro do iframe e veste-se
   de nua: o cabeçalho, o conteúdo e a barra do polegar escondem-se, e fica
   só a janela que o ?abrir= mandou abrir. É a mesma janela, com o mesmo
   código a correr — o que muda é que não se vê o que está por baixo.

   Nunca acontece sem o ?nu=1 no endereço, e esse só o Board o escreve.
   =================================================================== */
html.pagina-nua { background: transparent; }
/* O `min-height: 100vh` de algumas páginas (o /gestao tem um) é o que
   prende o documento à altura do frame: com o frame a 600, o documento mede
   600 mesmo que o que lá está ocupe 300. Aqui não há ecrã para encher. */
html.pagina-nua, html.pagina-nua body {
    background: none; padding: 0 !important;
    min-height: 0 !important; height: auto !important;
}
html.pagina-nua > body > .spark-header,
html.pagina-nua > body > #conteudo,
html.pagina-nua > body > main,
html.pagina-nua > body > .barra-polegar,
html.pagina-nua > body > .fsa-fab { display: none !important; }

/* O VÉU DEIXA DE SER VÉU. Não há página por baixo para escurecer, e uma
   janela centrada num véu do tamanho do ecrã é o que fazia a janela flutuar
   no meio de um vazio branco dentro da moldura do Board. Passa a ser uma
   caixa normal, do tamanho do que tem. */
html.pagina-nua .spark-overlay,
html.pagina-nua .modal-overlay {
    position: static !important; display: none;
    background: none !important; padding: 0 !important;
    height: auto !important; max-height: none !important;
    align-items: stretch !important; justify-content: flex-start !important;
}
html.pagina-nua .spark-overlay.active,
html.pagina-nua .modal-overlay.active { display: block !important; }
html.pagina-nua .spark-overlay > .spark-modal,
html.pagina-nua .modal-overlay > .modal {
    width: 100% !important; max-width: none !important;
    height: auto !important; max-height: none !important;
    margin: 0 !important; border-radius: 0 !important; box-shadow: none !important;
}
/* O CORPO DA JANELA DEIXA DE SE CORTAR. Corta-se em vh para caber num ecrã;
   aqui o «ecrã» é o iframe, e um iframe que se mede pela janela que se mede
   pelo iframe é uma cobra a morder a cauda. Quem põe o tecto é o Board. */
html.pagina-nua .modal-body { max-height: none !important; overflow: visible !important; }
/* E QUEM ROLA É O FRAME. O body das Iniciativas tem overflow: hidden (só a
   caixa de dentro rola, na página a sério). Dentro da moldura a caixa de
   dentro já não se corta — o tecto é do Board — e o documento fica mais alto
   do que o frame; com o body a esconder o que transborda, a lista acabava
   onde o frame acabava, sem barra e sem maneira de chegar ao resto. O
   documento rola como qualquer documento, e a moldura corta-o à altura
   do ecrã. */
html.pagina-nua, html.pagina-nua body { overflow: visible !important; }
html.pagina-nua .modal-overlay > .modal,
html.pagina-nua .spark-overlay > .spark-modal { overflow: visible !important; }

/* E O CABEÇALHO NÃO SE DIZ DUAS VEZES. A moldura do Board já escreve o
   título — com o nome do projeto, que a janela de dentro não sabe — e já tem
   o seu ×. Repetidos, liam-se como uma janela dentro de outra, que foi
   exactamente o que pareceu. */
html.pagina-nua .modal-header,
html.pagina-nua .spark-overlay > .spark-modal > h3:first-child,
html.pagina-nua .spark-modal-close,
html.pagina-nua .modal-close { display: none !important; }

html.com-barra-polegar body {
    box-sizing: border-box;
    padding-bottom: var(--barra-polegar-altura);
}

.barra-polegar[hidden] { display: none; }
.barra-polegar {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    /* Abaixo do cabeçalho (200) e do quadro das notas (160), acima dos menus
       que caem de botões (60). Uma ficha ou uma janela (2000+) tapa-a — e é
       isso que se quer: não há dois níveis de botões a competir. */
    z-index: 150;
    display: flex;
    align-items: stretch;
    justify-content: space-around;
    gap: 2px;
    padding: 4px 6px calc(4px + env(safe-area-inset-bottom, 0px));
    background: var(--bg-panel);
    border-top: 1px solid var(--border-color);
    box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.08);
}

/* Cada atalho deixa de ser um quadrado de 44 e passa a ser uma coluna:
   ícone em cima, nome em baixo, a ocupar a sua fatia da barra. */
.barra-polegar .spark-atalho-tarefas,
.barra-polegar .spark-atalho-tickets,
.barra-polegar .spark-atalho-deposito,
.barra-polegar .spark-atalho-postits,
.barra-polegar .spark-atalho-ideias,
.barra-polegar .hamburger-btn {
    position: relative;
    flex: 1 1 0;
    width: auto; min-width: 0;
    height: auto; min-height: 50px;
    padding: 6px 2px 4px;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    border: none;
    border-radius: 10px;
    background: transparent;
    color: var(--text-secondary);
    font: inherit;
    line-height: 1;
}
.barra-polegar .hamburger-menu { flex: 1 1 0; min-width: 0; display: flex; position: static; }
.barra-polegar .hamburger-btn { width: 100%; }
.barra-polegar .hamburger-traco { width: 20px; }
/* Os três traços têm 2px cada e 4 de intervalo: 14px ao todo. O SVG dos
   outros tem 18. O nome ficava 2px mais alto neste — nivela-se com margem. */
.barra-polegar .hamburger-traco:first-child { margin-top: 2px; }
.barra-polegar .hamburger-traco:nth-child(3) { margin-bottom: 2px; }
.barra-polegar .atalho-rotulo {
    display: block;
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: .01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
/* O contador vai ao canto do ÍCONE, não ao canto da fatia — que é larga e
   deixava o número a meio caminho de lado nenhum. O ícone tem 18px e está
   centrado: o seu canto direito fica a 9px do meio. */
.barra-polegar .spark-atalho-tarefas .badge-migracoes,
.barra-polegar .spark-atalho-tickets .badge-migracoes,
.barra-polegar .spark-atalho-postits .badge-migracoes,
.barra-polegar .spark-atalho-deposito .badge-migracoes {
    top: 0; right: calc(50% - 20px);
}

/* A FOLHA DO MENU. O mesmo #hamburger-dropdown, mas em vez de cair do canto
   superior direito sobe do fundo, a toda a largura, com uma pega em cima.

   Assenta EM CIMA da barra, não por cima dela: a barra continua à vista com
   a folha aberta, e o botão do menu — que agora desenha um × — é o que a
   fecha. A folha está dentro da barra, que é um contexto de empilhamento
   (position: fixed + z-index): o z-index dela vale só cá dentro. */
.barra-polegar .hamburger-dropdown {
    position: fixed;
    top: auto; left: 0; right: 0; bottom: var(--barra-polegar-altura);
    margin: 0;
    width: auto; min-width: 0;
    max-height: calc(80vh - var(--barra-polegar-altura)); max-height: calc(80dvh - var(--barra-polegar-altura));
    border-radius: 18px 18px 0 0;
    padding-bottom: 8px;
    transform: translateY(20px);
    transition: transform .22s ease, opacity .22s ease, visibility .22s;
    /* Chegar ao fim da lista não pode passar a rolar a página por baixo. */
    overscroll-behavior: contain;
}
.barra-polegar .hamburger-dropdown.active { transform: translateY(0); }

/* O VÉU. É um ::before da barra e não da folha: a folha está transformada, e
   um position: fixed dentro de um elemento transformado fica preso a ele.
   Pára no topo da barra, para a barra não ficar escurecida por baixo dos
   seus próprios botões. Os botões são positioned e vêm depois na árvore,
   por isso pintam por cima do véu. */
.barra-polegar::before {
    content: '';
    position: fixed;
    top: 0; left: 0; right: 0; bottom: var(--barra-polegar-altura);
    background: rgba(10, 16, 26, 0.45);
    opacity: 0;
    visibility: hidden;
    transition: opacity .22s ease, visibility .22s;
}
.barra-polegar:has(.hamburger-dropdown.active)::before { opacity: 1; visibility: visible; }
/* Com a folha aberta a barra sobe acima do cabeçalho (200): senão o véu
   parava por baixo dele e o cabeçalho ficava aceso e a responder ao dedo.
   E nenhum gesto na barra ou no véu pode rolar a página por trás. */
.barra-polegar:has(.hamburger-dropdown.active) { z-index: 210; touch-action: none; }
html:has(.barra-polegar .hamburger-dropdown.active) { overflow: hidden; }

/* Os três traços viram um ×: o mesmo botão abre e fecha, e diz qual dos dois
   vai fazer. */
.barra-polegar .hamburger-traco { transition: transform .2s ease, opacity .2s ease; }
.barra-polegar:has(.hamburger-dropdown.active) .hamburger-traco:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.barra-polegar:has(.hamburger-dropdown.active) .hamburger-traco:nth-child(2) { opacity: 0; }
.barra-polegar:has(.hamburger-dropdown.active) .hamburger-traco:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }
.barra-polegar .hamburger-dropdown::before {
    content: '';
    display: block;
    width: 38px; height: 4px;
    border-radius: 2px;
    background: var(--border-color);
    margin: 10px auto 6px;
}
/* Entradas mais altas na folha: é para o dedo, e há espaço. */
.barra-polegar .hamburger-dropdown-item { padding: 13px 18px; }
.barra-polegar .hamburger-dropdown-section { padding-left: 18px; }
.barra-polegar .hamburger-grupo-cab { padding: 15px 18px; min-height: 48px; font-size: 11.5px; }
.barra-polegar .hamburger-grupo-itens .hamburger-dropdown-item { padding-left: 30px; }
.barra-polegar .hamburger-grupo.fixo > .hamburger-grupo-itens .hamburger-dropdown-item { padding-left: 18px; }

@media (prefers-reduced-motion: reduce) {
    .barra-polegar .hamburger-dropdown,
    .barra-polegar::before,
    .barra-polegar .hamburger-traco { transition: none; }
}

/* Os avisos que nascem colados ao fundo sobem para cima da barra: o
   .spark-toast de todas as páginas, o .toast das Iniciativas e o
   .aviso-flutuante das Tarefas. Sem isto nasciam por baixo dela. */
html.com-barra-polegar .spark-toast,
html.com-barra-polegar .toast,
html.com-barra-polegar .aviso-flutuante {
    bottom: calc(24px + var(--barra-polegar-altura));
}


/* =============================================================================
   DUAS CLASSES QUE ANDAVAM PELAS PÁGINAS
   =============================================================================
   Ambas nasceram numa página e passaram a ser usadas noutras — o .scope-check
   nas Iniciativas e agora também na Administração e na Gestão, o .def-desligado
   na Administração e agora também na Gestão. Enquanto o desenho ficou onde
   nasceu, a segunda página herdava o NOME e não o desenho: a caixa de escolha
   aparecia sem alinhamento e a nota de «desligado» com o tamanho do texto
   corrido. O bin/verifica-classes.mjs existe para apanhar exactamente isto. */

/* Uma etiqueta com uma caixa de escolha ao lado, alinhadas pelo meio. */
.scope-check {
    display: flex; align-items: center; gap: 8px;
    text-transform: none; letter-spacing: normal;
    font-size: 13px; color: var(--text-body); cursor: pointer;
}
/* A largura automática é o que impede a caixa de esticar dentro de um
   formulário onde os campos ocupam a linha toda. */
.scope-check input { width: auto; }

/* A nota que explica o que acontece quando um interruptor está desligado. */
.def-desligado {
    margin: 16px 0 0; font-size: 12.5px; line-height: 1.6;
    color: var(--text-muted); max-width: 62ch;
}


/* =========================================================================
   OS CAMPOS PERSONALIZADOS E OS ANEXOS DE UMA ACÇÃO

   Isto vivia no iniciativas.css, e o /js/campos.js que os desenha é carregado
   em TODA a plataforma. O resultado: a janela «Editar acção» do /tarefas
   desenhava os campos e os anexos SEM CSS NENHUM — rótulos a 16px em caixa
   normal, caixas de escrita com 19px de altura ao lado de campos nativos de
   38, e nenhum traço a separar os blocos. O JS estava certo; o desenho é que
   não chegava lá.

   É o mesmo erro do CSS do /admin, na direcção contrária: ali escrevi regras
   num ficheiro que ninguém carrega, aqui deixei-as num que só uma das páginas
   carrega. A regra que ambos violam é a mesma — o que duas páginas usam
   desenha-se no kit.
   ========================================================================= */

/* O bloco separa-se do resto por uma LINHA e não por uma caixa: uma moldura
   pesava mais do que o conteúdo, e o que se quer dizer é «isto é outro
   assunto», não «isto é outra coisa». */
.campos-bloco { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border-color); }
.campos-bloco + .campos-bloco { margin-top: 18px; }
.campos-bloco-titulo {
    font-size: 11px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase;
    color: var(--text-muted); margin-bottom: 14px;
}
.campos-bloco .form-field select[multiple] { padding: 6px; }
.campos-obrigatorio { color: var(--text-danger); font-weight: 700; }

/* OS RÓTULOS DOS CAMPOS PERSONALIZADOS SÃO OS RÓTULOS DA FICHA.
   Estavam a herdar o .form-field das Iniciativas — 11px, peso 400 — ao lado
   dos nativos .spark-field a 10px peso 600, um por baixo do outro na mesma
   coluna. Quatro diferenças de uma vez, no sítio onde o código diz que os
   campos «são a continuação da mesma ficha». */
.campos-bloco .form-field > label, #accao-anexos + * .form-field > label {
    font-size: 10px; font-weight: 600; letter-spacing: 0.6px; text-transform: uppercase;
    color: var(--text-muted); margin-bottom: 6px; display: block;
}

/* O aviso de uma fonte em baixo fica AO LADO DO CAMPO que ela alimenta: é ali
   que alguém está a tentar escolher e a não encontrar o que procura. Âmbar e
   não vermelho — os valores que estão à vista servem, só não são de agora. */
.campos-fonte-aviso {
    margin-top: 6px; padding: 7px 10px; border-radius: 6px;
    font-size: 11.5px; line-height: 1.45;
    color: var(--text-warning); background: var(--accent-gold-soft);
    border: 1px solid var(--accent-gold-line);
}

/* A GRELHA TEM DOZE COLUNAS, e cada campo diz quantas ocupa. Era «tantas
   colunas quantas couberem com 200 pixéis», e por isso um número de três
   dígitos ficava com a largura de uma morada. */
.campos-bloco .form-grid,
.tarefa-campos .form-grid {
    display: grid; grid-template-columns: repeat(12, minmax(0, 1fr));
}
.campos-bloco .form-grid { gap: 18px 16px; }
.form-field.larg-3  { grid-column: span 3; }
.form-field.larg-4  { grid-column: span 4; }
.form-field.larg-6  { grid-column: span 6; }
.form-field.larg-12 { grid-column: span 12; }
/* Num ecrã estreito qualquer campo ocupa a linha toda: um quarto de um
   telemóvel não é um campo, é uma ranhura. */
@media (max-width: 700px) {
    .campos-bloco .form-grid .form-field,
    .tarefa-campos .form-grid .form-field { grid-column: 1 / -1; }
}
.campos-bloco .form-hint { margin-top: 6px; }

/* OS ANEXOS DE UMA ACÇÃO. Uma lista e não uma grelha de cartões: numa ficha
   são dois ou três ficheiros, e a grelha de miniaturas dos Documentos ocupava
   mais espaço do que a acção toda. */
.accao-anexos { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border-color); }
.accao-anexos-cabeca { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
/* O MESMO DESENHO DO TÍTULO DE UM BLOCO DE CAMPOS. Eram 10px/600/0.5 contra
   11px/700/0.6 no mesmo diálogo, com o mesmo separador por cima: depois de os
   campos e os anexos passarem a ser uma implementação só, deviam ler-se como
   uma. */
.accao-anexos-cabeca > span {
    font-size: 11px; font-weight: 700; letter-spacing: 0.6px; text-transform: uppercase;
    color: var(--text-muted);
}
.accao-anexos-cabeca .spark-btn.pequeno { margin-left: auto; padding: 5px 12px; font-size: 11.5px; }
.accao-anexos-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.accao-anexos-lista[hidden] { display: none; }   /* o display: flex ganhava ao [hidden] do browser */
.accao-anexos-lista li {
    display: grid; grid-template-columns: minmax(0, 1fr) auto auto; align-items: center; gap: 10px;
    padding: 8px 10px; border: 1px solid var(--border-color); border-radius: 7px; background: var(--bg-panel);
}
.accao-anexos-lista a { color: var(--text-link); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.accao-anexos-lista a:hover { text-decoration: underline; }
.accao-anexos-lista span { font-size: 11px; color: var(--text-muted); white-space: nowrap; }
.accao-anexos-vazio { font-size: 12px; color: var(--text-muted); margin: 0; }
.accao-anexos-nota { font-size: 11px; color: var(--text-muted); line-height: 1.5; margin: 10px 0 0; }

/* A CAIXA QUE PROCURA — para os campos cuja fonte tem opções a mais para uma
   combo. Uma lista de três mil produtos virava três mil <option>. */
.campo-procura-caixa { position: relative; }
.campo-procura { width: 100%; }
.campo-procura-lista {
    position: absolute; z-index: var(--camada-menu); left: 0; right: 0; top: calc(100% + 4px);
    max-height: 260px; overflow-y: auto; padding: 4px;
    background: var(--bg-panel); border: 1px solid var(--border-color);
    border-radius: 8px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
}
.campo-procura-lista[hidden] { display: none; }
.campo-procura-op {
    display: block; width: 100%; text-align: left; border: 0; background: none;
    padding: 8px 10px; border-radius: 6px; cursor: pointer;
    font: inherit; font-size: 12.5px; color: var(--text-primary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.campo-procura-op:hover, .campo-procura-op:focus { background: var(--fill-soft); outline: none; }
.campo-procura-op:focus-visible { box-shadow: inset 0 0 0 2px var(--text-link); }
.campo-procura-nada { padding: 10px; font-size: 12px; color: var(--text-muted); }
/* Quantas ficaram de fora: sem isto, quem vê trinta linhas acredita que são
   todas — e escolhe a primeira que serve em vez da que queria. */
.campo-procura-mais {
    padding: 8px 10px; margin-top: 2px; border-top: 1px solid var(--border-color);
    font-size: 11px; color: var(--text-muted);
}


/* =============================================================================
   ESCOLHER UM DE UMA LISTA
   =============================================================================
   O sparkConfirm responde sim ou não; isto responde QUAL. A lista pode ter
   dezenas de nomes — daí a procura por cima, que é o que poupa percorrê-la com
   os olhos. */
.spark-escolher .spark-modal { width: 460px; max-width: calc(100vw - 32px); }
.spark-escolher-nota { font-size: 11.5px; line-height: 1.55; color: var(--text-muted); margin: -8px 0 14px; }
.spark-escolher-procura { margin-bottom: 10px; }
.spark-escolher-lista {
    max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px;
    border: 1px solid var(--border-color); border-radius: 8px; padding: 4px;
}
/* flex: none — a lista é uma coluna flex com altura máxima, e o overflow
   hidden de cada opção deixava-a encolher: com mais opções do que cabiam,
   ficavam todas com meia altura e o texto cortado ao meio, em vez de a
   lista rolar. */
.spark-escolher-op {
    display: block; flex: none; width: 100%; text-align: left; border: 0; background: none;
    padding: 9px 10px; border-radius: 6px; cursor: pointer;
    font: inherit; font-size: 13px; color: var(--text-primary);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.spark-escolher-op:hover, .spark-escolher-op:focus { background: var(--fill-soft); outline: none; }
.spark-escolher-op:focus-visible { box-shadow: inset 0 0 0 2px var(--text-link); }
.spark-escolher-nada { padding: 12px; font-size: 12px; color: var(--text-muted); }


/* =============================================================================
   O FORMULÁRIO DA IDEIA
   =============================================================================

   Nasceu dentro da página pública /ideias/{token} e ficou lá enquanto foi o
   único sítio onde se escrevia uma ideia. Agora são três — a pública, a
   /ideia do telemóvel e a janela do computador — e um desenho que vive dentro
   de uma das três é um desenho que as outras duas não têm.

   Fica aqui e não na iniciativas.css: o cabeçalho inclui a janela em TODAS as
   páginas, e a spark.css é a única folha que todas carregam.
   ========================================================================== */
/* O MICROFONE, DENTRO DA CAIXA E AO CANTO.
   Fica por cima do texto, e por isso a caixa abre espaço à direita —
   sem isso, a primeira linha do que se escreve passa por baixo do
   botão e some-se meia palavra. */
.com-microfone { position:relative; }
.com-microfone textarea { padding-right:48px; }
.microfone {
    position:absolute; top:8px; right:8px;
    display:flex; align-items:center; justify-content:center;
    width:34px; height:34px; padding:0; cursor:pointer;
    border:1px solid var(--border-color); border-radius:9px;
    background:var(--bg-panel); color:var(--text-muted);
    transition:color .15s, border-color .15s, background .15s;
}
.microfone:hover { color:var(--text-primary); border-color:var(--text-link); }
.microfone[hidden] { display:none; }
/* A GRAVAR: vermelho e a pulsar. Um microfone aberto que pareça
   fechado é a pior coisa que um microfone pode fazer. */
.microfone.a-ouvir {
    color:#fff; border-color:var(--text-danger);
    background:var(--text-danger); animation:microfone-pulsa 1.3s ease-in-out infinite;
}
@keyframes microfone-pulsa {
    0%,100% { box-shadow:0 0 0 0 color-mix(in srgb,var(--text-danger) 45%,transparent); }
    50%     { box-shadow:0 0 0 7px color-mix(in srgb,var(--text-danger) 0%,transparent); }
}
@media (prefers-reduced-motion: reduce) { .microfone.a-ouvir { animation:none; } }
.mobile-msg { padding:12px 14px; border-radius:9px; margin:0 0 16px; font-size:14px; }
.mobile-msg.ok { background:color-mix(in srgb,var(--text-success) 13%,transparent); color:var(--text-success); }
.mobile-msg.err { background:color-mix(in srgb,var(--text-danger) 13%,transparent); color:var(--text-danger); }
.mobile-msg.aviso { background:color-mix(in srgb,var(--text-warning) 13%,transparent); color:var(--text-warning); }
.mobile-uploader { display:flex; align-items:center; flex-wrap:wrap; gap:12px; padding:14px; border:1px dashed var(--border-color); border-radius:9px; transition:border-color .15s,background .15s; }
.mobile-uploader.drag-over { border-color:var(--accent-blue); background:var(--accent-blue-soft); }
.mobile-uploader .spark-btn { flex:0 0 auto; text-transform:uppercase; letter-spacing:.5px; }
.ficheiros-nota { flex:1 1 220px; font-size:12px; line-height:1.4; color:var(--text-muted); }
.ficheiros-lista { display:grid; gap:8px; margin-top:10px; }
.ficheiro-escolhido { display:flex; align-items:center; gap:10px; min-width:0; padding:9px 11px; border:1px solid var(--border-color); border-radius:8px; background:var(--fill-soft); }
.ficheiro-escolhido-info { flex:1; min-width:0; }
.ficheiro-escolhido-nome { display:block; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:13px; color:var(--text-primary); }
.ficheiro-escolhido-tamanho { display:block; margin-top:2px; font-size:11px; color:var(--text-muted); }
.ficheiro-remover { flex:0 0 auto; width:30px; height:30px; padding:0; border:0; border-radius:7px; background:transparent; color:var(--text-muted); font-size:20px; line-height:1; cursor:pointer; }
.ficheiro-remover:hover { color:var(--text-danger); background:color-mix(in srgb,var(--text-danger) 10%,transparent); }
@media (max-width:480px) {
    .mobile-uploader .spark-btn { width:100%; justify-content:center; }
    .ficheiros-nota { flex-basis:100%; text-align:center; }
}


/* A JANELA DO COMPUTADOR. Mais larga do que uma .spark-modal normal: são nove
   campos e dois deles são caixas de texto — a 440px ficava uma coluna estreita
   e comprida que obrigava a rolar por tudo. */
/* A JANELA TOMA UMA ALTURA E AS CAIXAS DE TEXTO REPARTEM-NA — o mesmo que a
   página /ideia faz no telemóvel. Sem isto, a descrição e o benefício nasciam
   com 54px, duas linhas, para escrever aquilo que é o corpo da ideia: a
   janela ficava com um terço de espaço em branco por baixo e quem escrevia
   via três palavras de cada vez.

   min() e não uma altura fixa: num portátil baixo a janela encolhe e o corpo
   rola, em vez de sair pelo ecrã fora. */
.ideia-modal {
    width: 560px; padding: 0; display: flex; flex-direction: column;
    height: min(760px, calc(100vh - 48px)); height: min(760px, calc(100dvh - 48px));
}
.ideia-cabeca {
    display: flex; align-items: flex-start; gap: 12px;
    padding: 20px 22px 14px; border-bottom: 1px solid var(--border-color);
}
.ideia-cabeca h3 { margin: 0; font-size: 16px; padding-right: 0; }
.ideia-sub { margin: 4px 0 0; font-size: 11.5px; color: var(--text-muted); }
.ideia-cabeca .spark-modal-close { position: static; margin-left: auto; }
.ideia-corpo { padding: 18px 22px 22px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; }
#ideia-form { flex: 1; display: flex; flex-direction: column; }
/* Os campos chegam do servidor dentro desta caixa: se ela não crescer, o que
   está lá dentro não tem por onde crescer — a janela alta e as caixas
   pequenas, com o vazio entre elas e o botão. */
#ideia-campos { flex: 1; display: flex; flex-direction: column; min-height: 0; }
/* As duas caixas com microfone são a descrição e o benefício — as únicas que
   crescem. Um título com 200px de altura não é mais fácil de preencher, é só
   estranho, e os campos da empresa ficam com a altura que pediram. */
#ideia-form .spark-field:has(> .com-microfone) { flex: 1 1 0; min-height: 104px; display: flex; flex-direction: column; }
#ideia-form .spark-field:has(> .com-microfone) .com-microfone { flex: 1; display: flex; }
#ideia-form .spark-field:has(> .com-microfone) textarea { flex: 1; resize: none; }
/* O botão encosta ao fim da janela, seja qual for a altura do que está acima. */
#ideia-form .spark-btn.primary { margin-top: auto; }

/* Sem :has() fica o que interessa — caixas generosas — sem a repartição. */
@supports not selector(:has(> .com-microfone)) {
    .ideia-modal { height: auto; }
    #ideia-form textarea { min-height: 140px; }
}
.ideia-corpo .spark-field { margin-bottom: 16px; }
.ideia-corpo .spark-btn.primary { width: 100%; min-height: 44px; justify-content: center; }

/* Depois de enviar: o formulário sai e fica isto no lugar dele. */
.ideia-feito { display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; padding: 22px 0 10px; }
.ideia-feito[hidden] { display: none; }
.ideia-feito span { font-size: 13px; color: var(--text-muted); max-width: 46ch; }
.ideia-visto { font-size: 40px; line-height: 1; color: var(--text-success); }
.ideia-feito .spark-btn { margin-top: 8px; }

/* ============================================================
   A FICHA DE UMA ACÇÃO
   Aproveita o .spark-overlay/.spark-modal; aqui só o que é próprio
   dela. Vivia dentro do /tarefas — mudou-se para cá quando a mesma
   janela passou a servir também a aba Acções de uma iniciativa: uma
   classe usada em duas páginas, desenhada numa só, é uma página a
   herdar o nome e não o desenho.
   ============================================================ */
/* De acção em acção, dentro da ficha. */
.ficha-passo {
    display: flex; align-items: center; gap: 10px;
    margin: -4px 0 12px; padding-bottom: 10px;
    border-bottom: 1px solid var(--border-color);
}
.ficha-passo[hidden] { display: none; }
.ficha-passo-conta {
    flex: 1; text-align: center; font-size: 11.5px; color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ficha-passo-seta {
    flex: 0 0 auto; width: 34px; height: 34px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 20px; line-height: 1; cursor: pointer;
    border: 1px solid var(--controlo-borda); border-radius: 8px;
    background: transparent; color: var(--controlo-texto);
}
.ficha-passo-seta:hover:not(:disabled) { background: var(--fill-hover); color: var(--text-primary); }
/* NOS EXTREMOS NÃO SE DÁ A VOLTA: bate contra o limite. Passar da
   última para a primeira faz perder o sítio sem se dar por isso. */
.ficha-passo-seta:disabled { opacity: .35; cursor: default; }
@media (hover: none) {
    .ficha-passo-seta { width: 44px; height: 44px; }
}

/* Ficha da acção. Aproveita o .spark-overlay/.spark-modal partilhados;
   aqui só o que é próprio desta ficha. */
.ficha-contexto { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-bottom: 16px;
    font-size: 12px; color: var(--text-muted); }
.ficha-contexto .ponto { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.ficha-contexto .sep { opacity: 0.5; }
.ficha-dica { display: block; margin-top: 6px; font-size: 11.5px; color: var(--text-muted); }
/* MESMA DIVISÓRIA DOS BLOCOS DE CIMA (.campos-bloco, .accao-anexos,
   em spark.css) — os mesmos 20px/18px e a mesma linha, para o
   "Estado" ler-se como mais um bloco da ficha, e não como uma
   checkbox perdida a seguir à nota dos anexos. */
.ficha-estado { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border-color); }
.ficha-estado .ficha-comentario-bloco,
.ficha-estado #ficha-comentario-bloco { margin-top: 14px; }
/* O código da acção na linha de contexto, antes da empresa: é o que se
   dita ao telefone e o que se cola num email, por isso o user-select:
   all — um clique selecciona-o inteiro. */
.accao-codigo {
    font-family: 'Share Tech Mono', ui-monospace, monospace;
    font-size: 11px; color: var(--text-muted);
    white-space: nowrap; user-select: all;
}
.ficha-feita { display: flex; align-items: center; gap: 9px; cursor: pointer;
    font-size: 13px; color: var(--text-primary); text-transform: none; letter-spacing: 0; }
.ficha-feita input { width: 18px; height: 18px; accent-color: var(--accent-blue); cursor: pointer; }
#ficha-titulo { resize: vertical; min-height: 56px; line-height: 1.45; }

/* As duas datas da ficha, lado a lado. Ver a nota no partial: o minmax(0,1fr)
   é o que impede um campo de data de pedir a sua largura intrínseca e sair
   pela direita da janela. */
/* DUAS COLUNAS PARA DOIS CAMPOS, TRÊS PARA TRÊS. Era `repeat(2, …)` fixo, e a
   ficha da acção tem três campos aqui — início, prazo e estimativa: o terceiro
   caía sozinho para uma segunda linha, com metade da largura e o resto vazio
   ao lado. Com auto-fit a fila arruma-se sozinha conforme o que lhe puserem
   dentro, e continua a partir-se numa coluna quando não há espaço. */
.ficha-datas { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
@media (max-width: 480px) { .ficha-datas { grid-template-columns: minmax(0, 1fr); } }

/* A UNIDADE FICA À VISTA. Era o placeholder «h» — e um placeholder desaparece
   assim que se escreve o primeiro algarismo, que é exactamente o momento em
   que a unidade passa a fazer falta: «4,5» sozinho não diz se são horas ou
   dias. Fica encostada à direita, dentro do campo, e o padding do input abre
   espaço para ela em vez de lhe passar por cima. */
.campo-com-unidade { position: relative; display: block; }
.campo-com-unidade > input { width: 100%; box-sizing: border-box; padding-right: 30px; }
.campo-com-unidade > .unidade {
    position: absolute; right: 11px; top: 50%; transform: translateY(-50%);
    font-size: 12px; color: var(--controlo-rotulo); pointer-events: none;
}

/* O work item do Azure na linha de contexto da acção. No azul do Azure e não
   no do Spark: é uma referência a outra casa, e a cor diz de quem é o número
   antes de se ler a etiqueta. */
.ficha-workitem { color: #0078D4; font-family: 'Share Tech Mono', ui-monospace, monospace;
    font-size: 11px; text-decoration: none; white-space: nowrap; }
.ficha-workitem[href]:hover { text-decoration: underline; }

/* =====================================================================
   O «+ NOVO» — UMA REGRA PARA A PLATAFORMA INTEIRA. É sempre «+ Novo»,
   azul cheio, o único botão cheio da linha; vive no FIM da linha da
   pesquisa e dos filtros, nunca na linha das vistas; a dica diz o que
   cria. No telemóvel encolhe para o «+». (24/9/2026)
   ===================================================================== */
.btn-novo { display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex: none; margin-left: auto;
    height: var(--controlo-altura); padding: 0 14px 0 12px; border: 1px solid var(--accent-blue); border-radius: var(--controlo-raio);
    background: var(--accent-blue); color: #fff; font: inherit; font-size: 13px; font-weight: 600; line-height: 1; cursor: pointer; white-space: nowrap; }
.btn-novo svg { width: 16px; height: 16px; flex: none; }
.btn-novo:hover { filter: brightness(.92); }
.btn-novo:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: 2px; }
.btn-novo[hidden] { display: none; }
@media (hover: none) { .btn-novo { height: var(--controlo-altura-toque); } }
@media (max-width: 640px) { .btn-novo span { display: none; } .btn-novo { padding: 0; width: var(--controlo-altura-toque); } }

/* O COMUTADOR DE VISTAS — Quadro | Detalhe, Cartões | Compacto — o mesmo
   desenho em todas as páginas. */
.vistas-toggle { display: inline-flex; border: 1px solid var(--controlo-borda); border-radius: var(--controlo-raio); overflow: hidden; height: var(--controlo-altura); flex: none; }
.vistas-toggle button { display: inline-flex; align-items: center; gap: 6px; border: 0; background: var(--controlo-fundo); font: inherit; font-size: 12px;
    color: var(--controlo-texto, var(--text-secondary)); padding: 0 12px; cursor: pointer; white-space: nowrap; }
.vistas-toggle button + button { border-left: 1px solid var(--controlo-borda); }
.vistas-toggle button.activa { background: var(--controlo-fundo-acento); color: var(--text-link); font-weight: 600; }
.vistas-toggle svg { width: 15px; height: 15px; }
@media (hover: none) { .vistas-toggle { height: var(--controlo-altura-toque); } }
@media (max-width: 900px) { .vistas-toggle button span { display: none; } .vistas-toggle button { padding: 0 10px; } }

/* O ESTADO DA LIGAÇÃO AO AZURE, à cabeça do submenu: um ponto verde e
   «Ligado a organização / projeto», ou cinzento e «Sem ligação». */
.menu-estado-azure { display: flex; align-items: center; gap: 8px; padding: 8px 12px; font-size: 13px; color: var(--text-muted); max-width: 320px; }
.menu-estado-azure[hidden] { display: none; }
.menu-estado-azure b { font-weight: 600; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.menu-estado-azure .mea-ponto { width: 8px; height: 8px; border-radius: 50%; background: var(--text-muted); flex: 0 0 auto; }
.menu-estado-azure.ligado .mea-ponto { background: var(--accent-green, #16a34a); }
/* O submenu «CRM» do Board: um projeto por linha, com o seu «Desligar» à direita. */
.menu-crm-linha { padding-right: 6px; max-width: none; }
/* O NOME LÊ-SE INTEIRO: dois projetos com códigos quase iguais só se
   distinguem pelo nome, e cortado a meio não se distinguiam. Até duas linhas,
   e por baixo o cliente e o estado, em pequeno. */
.menu-crm-linha .mcl-texto { display: flex; flex-direction: column; min-width: 0; }
.menu-crm-linha .mcl-texto b { white-space: normal; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.3; }
.menu-crm-linha .mcl-texto small { font-size: 11px; color: var(--text-muted); white-space: normal; line-height: 1.3; }
.menu-crm-linha .mcl-omissao { color: var(--text-warning); font-size: 12px; flex: 0 0 auto; }
.menu-crm-linha .mcl-desligar { margin-left: auto; flex: 0 0 auto; background: none; border: 0; padding: 2px 6px; border-radius: 6px;
    font: inherit; font-size: 12px; color: var(--text-danger, #dc2626); cursor: pointer; width: auto; }
/* 24px com rato; ao toque manda a regra dos 44px do .menu-rapido, que esta
   (mais específica) calava se valesse para os dois. */
@media (hover: hover) { .menu-crm-linha .mcl-desligar { min-height: 24px; } }
/* O painel do CRM é mais largo que os outros: os nomes dos projetos são
   compridos e é pelo nome que dois códigos parecidos se distinguem. */
/* Só com rato e com lugar ao lado do menu: no telemóvel o painel vai em linha
   dentro do menu e tem de caber nele, e abaixo dos ~860px os 400px já não
   cabem à direita e tapavam a borda do menu. */
@media (hover: hover) and (min-width: 860px) {
    .submenu-painel:has(.menu-crm-linha) { width: 400px; max-width: 400px; }
}
.menu-crm-linha .mcl-desligar:hover { background: var(--bg-danger, rgba(220, 38, 38, .08)); }

/* =============================================================================
   AS DATAS VÊM DO PLANEAMENTO — um campo de data preso, a pastilha «do
   planeamento» e a faixa da ficha da atividade que leva ao calendário.
   ========================================================================== */
.spark-field.preso input[type="date"] { background: var(--controlo-fundo); border-style: dashed; color: var(--text-secondary); }
/* «HOJE» AO LADO DE UMA DATA: a linha com o campo e o pisco. */
.spark-data-linha { display: flex; align-items: center; gap: 6px; }
.spark-data-linha > .spark-input, .spark-data-linha > input[type="date"] { flex: 1 1 auto; min-width: 0; }
.spark-hoje { flex: 0 0 auto; height: 36px; padding: 0 10px; border: 1px solid var(--controlo-borda, var(--border-color)); border-radius: 8px; background: var(--bg-card); color: var(--text-secondary); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.spark-hoje:hover { border-color: var(--accent-blue-line); color: var(--text-primary); }
.spark-hoje[aria-pressed="true"] { background: var(--accent-blue-soft); color: var(--text-link); border-color: var(--accent-blue-line); }
.spark-hoje:disabled { opacity: .5; cursor: default; }
/* O MICROFONE: o campo e o botão na mesma linha; num textarea o botão fica no canto de cima. */
.spark-mic-linha { display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0; }
.spark-mic-linha.alto { align-items: flex-start; }
.spark-mic-linha > textarea, .spark-mic-linha > input { flex: 1 1 auto; min-width: 0; }
.spark-mic { flex: 0 0 auto; width: 36px; height: 36px; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--controlo-borda, var(--border-color)); border-radius: 8px; background: var(--bg-card); color: var(--text-secondary); cursor: pointer; padding: 0; }
.spark-mic:hover { border-color: var(--accent-blue-line); color: var(--text-primary); }
.spark-mic.a-ouvir { background: color-mix(in srgb, var(--text-danger) 14%, var(--bg-card)); border-color: var(--text-danger); color: var(--text-danger); animation: spark-mic-pulso 1.2s ease-in-out infinite; }
@keyframes spark-mic-pulso { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--text-danger) 45%, transparent); } 50% { box-shadow: 0 0 0 6px transparent; } }
@media (prefers-reduced-motion: reduce) { .spark-mic.a-ouvir { animation: none; } }
/* AS DATAS PRESAS NA LISTA DAS ATIVIDADES da ficha do entregável: o mesmo
   lugar dos dois campos de data (a grelha não muda), a data em cima e a
   pastilha «do planeamento» em baixo. */
.tarefa-data-presa { display: flex; flex-direction: column; justify-content: center; gap: 1px; line-height: 1.15; border-style: dashed; color: var(--text-secondary); font-size: 12px; padding: 3px 8px; min-height: 36px; box-sizing: border-box; }
.tarefa-data-presa b { font-weight: 500; }
.tarefa-data-presa.tarde b { color: var(--text-danger); font-weight: 600; }
.tarefa-data-presa small { font-size: 9.5px; color: var(--text-link); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pastilha-plan, .ficha-datas-plan .pastilha { display: inline-flex; align-items: center; border-radius: 999px; padding: 1px 8px; font-size: 11px; font-weight: 500; text-transform: none; letter-spacing: 0; background: color-mix(in srgb, var(--accent-blue) 14%, var(--bg-card)); color: var(--text-link); vertical-align: middle; }
.ficha-datas-plan { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; border: 1px dashed var(--border-color); border-radius: 8px; padding: 6px 10px; margin: 0 0 8px; font-size: 12.5px; color: var(--text-secondary); }
.ficha-datas-plan .fdp-sp { flex: 1; }
.ficha-datas-plan .spark-btn { min-height: 28px; padding: 0 10px; font-size: 12.5px; }


/* O BONECO DO SPARK: a pixel art que anda de um lado para o outro enquanto o
   servidor trabalha (sincronizar com o Azure, importar o plano, notas de
   versão). Um canvas pequeno esticado sem suavizar; ver sparkBoneco() no
   spark.js. Some com prefers-reduced-motion. */
.spark-boneco { display: block; width: 100%; height: 56px; image-rendering: pixelated; image-rendering: crisp-edges; margin: 6px 0 2px;
    border-radius: 8px; background: linear-gradient(180deg, transparent 0 70%, var(--fill-soft) 70% 100%); }
@media (prefers-reduced-motion: reduce) { .spark-boneco { display: none; } }

/* O ESCOLHEDOR DE TICKETS (2/10/2026): a mesma caixa nas três janelas que
   registam tempo — procura, «só os atribuídos a mim», estado, a lista e o
   escolhido. Ver sparkTicketPicker() no spark.js. */
.spark-tk { position: relative; }
/* A PROCURA É UMA PROCURA: lupa à esquerda, para não se ler como um rótulo. */
/* A caixa inteira: a procura é a cabeça da lista (3/10/2026). */
.spark-tk-caixa { border: 1px solid var(--controlo-borda, var(--border-color)); border-radius: 10px; background: var(--bg-card); overflow: hidden; }
.spark-tk-caixa:focus-within { border-color: var(--accent-blue); box-shadow: 0 0 0 3px var(--accent-blue-soft); }
.spark-tk-q { display: flex; align-items: center; gap: 10px; padding: 0 12px; background: var(--fill-soft); border-bottom: 1px solid var(--border-color); }
.spark-tk-q svg { width: 18px; height: 18px; color: var(--accent-blue); flex: 0 0 auto; pointer-events: none; }
.spark-tk-q input { flex: 1; min-width: 0; height: 42px; border: 0; background: transparent; box-shadow: none; padding: 0; font: inherit; font-size: 14px; color: var(--text-primary); outline: 0; }
.spark-tk-q input::placeholder { color: var(--text-muted); }
.spark-tk-n { font-size: 11.5px; color: var(--text-muted); white-space: nowrap; flex: 0 0 auto; }
/* Os filtros: não herdam o rótulo em maiúsculas do campo onde a caixa vive. */
.spark-tk-f { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0; padding: 8px 12px; border-bottom: 1px solid var(--border-color); font-size: 12.5px; color: var(--text-secondary); }
.spark-field .spark-tk .spark-tk-f label, .spark-tk-f label { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; margin: 0; font-size: 12.5px; font-weight: 500; letter-spacing: 0; text-transform: none; color: var(--text-secondary); }
.spark-tk-f input[type="checkbox"] { width: 15px; height: 15px; margin: 0; }
.spark-tk-f select { width: auto; min-width: 0; padding: 0 28px 0 10px; height: 30px; font-size: 12.5px; }
.spark-tk-l { border: 0; border-radius: 0; margin: 0; max-height: 240px; overflow: auto; background: var(--bg-card); }
.spark-tk-l:empty { min-height: 44px; }
.spark-tk-l button { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 10px; row-gap: 5px; align-items: baseline; width: 100%; border: 0; background: transparent; padding: 10px 12px; font: inherit; font-size: 13.5px; text-align: left; cursor: pointer; color: var(--text-primary); border-bottom: 1px solid var(--border-color); }
.spark-tk-l button:last-child { border-bottom: 0; }
.spark-tk-l button:hover, .spark-tk-l button:focus-visible { background: var(--accent-blue-soft); outline: 0; }
.spark-tk-l button .n { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11.5px; color: var(--text-muted); }
.spark-tk-l button b { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spark-tk-l button small { grid-column: 2; display: flex; align-items: center; gap: 8px; color: var(--text-muted); font-size: 11.5px; min-width: 0; }
.spark-tk-l button small span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spark-tk-l .vazio { padding: 10px 12px; color: var(--text-muted); font-size: 12.5px; line-height: 1.45; }
.spark-tk-l .vazio.aviso { background: var(--fill-soft); border-bottom: 1px solid var(--border-color); }
.spark-tk-badge { display: inline-block; padding: 1px 7px; border-radius: 999px; font-size: 10.5px; font-weight: 600; white-space: nowrap; color: #fff; background: var(--text-muted); flex: 0 0 auto; }
.spark-tk-esc { display: flex; align-items: center; gap: 8px; margin-top: 0; padding: 8px 12px; border-radius: 8px; background: var(--accent-blue-soft); color: var(--text-primary); font-weight: 600; font-size: 13px; }
.spark-tk-esc span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.spark-tk-esc button { border: 0; background: transparent; color: var(--text-muted); font-size: 16px; line-height: 1; cursor: pointer; padding: 0 2px; }
.spark-tk-esc button:hover { color: var(--text-danger); }
.spark-tk-aviso { margin-top: 8px; padding: 8px 12px; border-radius: 8px; background: #fff6e0; border: 1px solid #f0c36d; font-size: 12.5px; color: var(--text-body); }
.spark-tk-aviso a { color: var(--text-link); font-weight: 600; }
/* No telemóvel (controlos de toque a 44px) o estado vai para a sua linha, a toda a largura, e a procura diz menos. */
@media (max-width: 480px), (hover: none) and (max-width: 600px) {
    .spark-tk-f { gap: 8px 14px; }
    .spark-tk-f select { flex: 1 1 100%; }
}

/* OS PASSOS DE QUARTO DE HORA (o José, 2/10/2026): o tempo regista-se a 15,
   30, 45, 1h… — chips rápidos e um − / + que anda de 15 em 15. */
.spark-passos { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.spark-passos button { height: 28px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--border-color); background: var(--bg-card); color: var(--text-secondary); font: inherit; font-size: 12.5px; cursor: pointer; }
.spark-passos button:hover { border-color: var(--accent-blue-line); color: var(--text-link); }
.spark-passos button.mais-menos { width: 32px; padding: 0; font-weight: 700; }
.spark-passos .sep { flex: 1; }
/* O cabeçalho que ordena (sparkOrdenavel, 6/10/2026): o texto do cabeçalho passa a botão, com a seta da ordem ao lado. */
.spark-ordenavel th[data-ord] { padding: 0; }
.spark-ordenavel th[data-ord] button.spark-ord { all: unset; box-sizing: border-box; display: flex; align-items: center; gap: 4px; width: 100%; padding: 7px 8px; cursor: pointer; font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit; }
.spark-ordenavel th.num button.spark-ord, .spark-ordenavel th.n button.spark-ord { justify-content: flex-end; text-align: right; }
.spark-ordenavel th[data-ord] button.spark-ord:hover, .spark-ordenavel th.ord button.spark-ord { color: var(--text-link); }
.spark-ordenavel th[data-ord] button.spark-ord:focus-visible { outline: 2px solid var(--accent-blue); outline-offset: -2px; }
.spark-ordenavel .spark-ord-seta { font-size: 9px; flex: none; min-width: 0; }

/* ============================================================================
   O CARIMBO (o José, 7/10/2026: «os cartões em validação devem fazer-se notar
   com um amarelo tipo semáforo, ou um símbolo no meio do cartão como se fosse
   um carimbo, meio de lado; os concluídos também»). Um selo inclinado, a tinta,
   com moldura dupla e o desgaste de um carimbo de borracha. Fica por cima do
   cartão sem apanhar cliques. Âmbar para «Em validação», verde para «Concluído».
   Usado nos cartões de /tarefas e de /planeamento.
   ============================================================================ */
:root { --carimbo-validacao: #ffc21a; --carimbo-concluido: #3ddc84; --carimbo-veu-validacao: rgba(255, 194, 26, .10); }
html.ui-light { --carimbo-validacao: #8a5200; --carimbo-concluido: #15803d; --carimbo-veu-validacao: rgba(245, 179, 1, .13); }
.carimbo {
    position: absolute; left: 50%; top: 50%; z-index: 2;
    transform: translate(-50%, -50%) rotate(-12deg);
    pointer-events: none; user-select: none; white-space: nowrap;
    padding: 4px 10px 3px; border: 2px solid currentColor; border-radius: 6px;
    outline: 1px solid currentColor; outline-offset: 2px;
    font-family: "Courier New", ui-monospace, monospace; font-size: 11px; font-weight: 800; line-height: 1.1;
    letter-spacing: .14em; text-transform: uppercase;
    color: var(--carimbo-validacao);
    background: color-mix(in srgb, var(--bg-card, #fff) 72%, transparent);
    opacity: .9;
    /* A tinta que não pegou: riscas finas e uns falhos, como um carimbo gasto. */
    -webkit-mask-image: radial-gradient(circle at 18% 32%, rgba(0,0,0,.55) 0 1.5px, transparent 2px), radial-gradient(circle at 71% 68%, rgba(0,0,0,.6) 0 1.5px, transparent 2px), repeating-linear-gradient(-28deg, #000 0 3px, rgba(0,0,0,.78) 3px 4px);
    -webkit-mask-composite: source-over;
    mask-image: radial-gradient(circle at 18% 32%, rgba(0,0,0,.55) 0 1.5px, transparent 2px), radial-gradient(circle at 71% 68%, rgba(0,0,0,.6) 0 1.5px, transparent 2px), repeating-linear-gradient(-28deg, #000 0 3px, rgba(0,0,0,.78) 3px 4px);
    mask-composite: add;
}
.carimbo.concluido { color: var(--carimbo-concluido); transform: translate(-50%, -50%) rotate(-8deg); }
/* O semáforo: um cartão em validação ganha um fundo âmbar leve e um friso âmbar em baixo. */
.cartao.em-validacao, .pk.em-validacao { background-image: linear-gradient(var(--carimbo-veu-validacao), var(--carimbo-veu-validacao)); box-shadow: inset 0 -3px 0 var(--carimbo-validacao); }
