﻿/**
 * components.css — COMPONENTES REUTILIZÁVEIS DO PILOT (design system)
 * Consolida forms.css + inputs.css + components/datepicker.css + buttons.css
 * + tables.css (conteúdo intacto, na ordem exata em que eram carregados).
 * Regra: componente reutilizável vive aqui; estilo específico de página vai
 * para o CSS da página (carregado via $page_css_files, DEPOIS deste).
 */

/* ═══════════════ [origem: forms.css] ═══════════════ */

/* ======================== FORMS ======================== */

.form-block {
    width: 100%;
    margin: 20px 0;
}

.form-row {
    display: flex;
    align-items: flex-start; /* colunas com altura natural — sem stretch; garante posicionamento consistente de erros */
    margin: 0;
    padding: 0;
    width: 100%;
}

.form-row.actions {
    padding: 25px 0;
}

.form-row.center {
    justify-content: center;
}

/* ══════════════════════════════════════════════════════════════════════════════
   GRELHA DOS FORMULÁRIOS — 12 colunas
   As classes `col-12 col-lg-N` já vinham no markup mas não tinham CSS nenhum:
   toda a coluna era `flex: auto`, que reparte o espaço por igual. O resultado
   era uma data de nascimento tão larga como um endereço de email, e um campo
   sozinho numa linha a esticar-se por 1440px.

   A largura passa a ser a que o markup pede. `flex-grow: 0` é o essencial —
   sem ele a base percentual seria só um ponto de partida e o campo voltava a
   crescer para o espaço que sobrasse.

   Abaixo de 1024px vale o `col-12` (uma coluna por linha): num ecrã estreito
   dividir a largura por quatro não ajuda ninguém.

   REGRA DAS LARGURAS — dentro do mesmo formulário, UMA só grelha. Na ficha de
   conta (e é a que se recomenda por omissão) é a de 4 módulos: só múltiplos de
   3, ou seja `col-lg-3`, `-6`, `-9`, `-12`.
   É o que faz as bordas dos campos caírem sempre em 25%, 50% e 75%, e portanto
   ALINHAREM de linha para linha e de fieldset para fieldset. Larguras à medida
   do conteúdo (2 para uma data, 5 para um e-mail) dão um formulário em que cada
   linha se divide num sítio diferente — as bordas nunca coincidem e o conjunto
   lê-se como desarrumado, mesmo estando cada campo "certo". Entre as duas
   práticas — largura a sugerir o comprimento da resposta vs. alinhamento
   vertical — vale o alinhamento, que é o que se vê à distância de um ecrã.

   Na prática: 3 = um campo normal, 6 = um campo longo (e-mail, morada, empresa),
   9 ou 12 = um campo que ocupa a linha. Uma linha que não fecha os 12 deixa o
   buraco à direita, e isso não é defeito: o olho segue a grelha, não a densidade.

   Um formulário com outra grelha não está errado — o modal de lead novo e os
   filtros do stock são de 3 módulos (`col-lg-4`), porque aí três campos enchem
   a linha de propósito. O que não pode é MISTURAR grelhas no mesmo formulário.
   ══════════════════════════════════════════════════════════════════════════════ */
.form-row > .form-col[class*="col-"] {
    flex-grow: 0;
    flex-shrink: 1;
    flex-basis: 100%;
    max-width: 100%;
}

@media (min-width: 1025px) {
    .form-row > .form-col.col-lg-1  { flex-basis: calc(0.0833333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.0833333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-2  { flex-basis: calc(0.1666667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.1666667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-3  { flex-basis: calc(0.25 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.25 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-4  { flex-basis: calc(0.3333333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.3333333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-5  { flex-basis: calc(0.4166667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.4166667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-6  { flex-basis: calc(0.5 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.5 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-7  { flex-basis: calc(0.5833333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.5833333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-8  { flex-basis: calc(0.6666667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.6666667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-9  { flex-basis: calc(0.75 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.75 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-10 { flex-basis: calc(0.8333333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.8333333 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-11 { flex-basis: calc(0.9166667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); max-width: calc(0.9166667 * (100% + var(--space-3, 12px)) - var(--space-3, 12px)); }
    .form-row > .form-col.col-lg-12 { flex-basis: 100%; max-width: 100%; }
}

/* A linha quebra quando as colunas somam mais de 12 — é o que permite escrever
   uma linha longa sem contar os totais ao pixel, e é obrigatório em ecrã
   estreito, onde cada campo vale `col-12`.

   PORQUÊ A FÓRMULA e não `25%` limpo: a `.row` tem `gap: var(--space-3)` (12px,
   base.css). Quatro colunas de 25% dão 100%, mais três gaps de 12px dão 136px a
   mais do que a linha — e a quarta caía para baixo, com as contas todas certas.
   Uma coluna de N/12 partilha (K-1) gaps entre K colunas, o que dá
   `N/12 × (100% + gap) − gap`; para as quatro dá 25% × (100%+12px) − 12px, e a
   soma com os gaps fecha exactamente 100%. É a mesma correcção que falta à
   grelha `.col-N` de base.css (`calc(25% - 12px)`), que lá passa despercebida
   por essas colunas terem `flex-grow: 1` a comer o resto. */
.form-row:has(> .form-col[class*="col-lg-"]) {
    flex-wrap: wrap;
}

.form-col {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    margin: 5px 0;
    padding: 0 5px;
    flex: auto;
    position: relative;
}

/* Espaço reservado por baixo dos campos para a mensagem de erro. Declarar a
   variável É o contrato: quem a declara reserva espaço, e a mensagem sabe que
   pode ocupá-lo (ver "Mensagens de erro de validação"). Só em form-cols com
   inputs reais — botões, checkboxes e links não têm erro por baixo. */
.form-col:has(input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]), select, textarea, .custom-options-container) {
    /* 20px = 4 de respiro + 12,5 de linha + folga. Era 16, que já não chega
       desde que a mensagem passou a descolar-se do campo. */
    --form-error-reserve: 20px;
    padding-bottom: var(--form-error-reserve);
}

/* Uma linha SÓ de opções (rádios ou caixas) não tem mensagem de erro e por isso
   não reservava espaço nenhum — ficava com menos ar por baixo do que as linhas
   de campos, e num formulário longo lia-se como se pertencesse ao campo
   seguinte: o "Tipo de cliente" parecia a etiqueta do "Tipo de documento".

   Regra à parte, e não juntada à de cima, por duas razões: um seletor inválido
   invalida o GRUPO todo (foi o que aconteceu à primeira, e levou a reserva dos
   campos com ele), e `:has()` NÃO pode conter outro `:has()` — daí a procura do
   campo real ser feita directamente na linha, sem passar pela `.form-col`.

   `padding` e não `margin`: a ficha de conta redefine a margem das colunas num
   seletor com id, que ganharia sempre. */
.form-row:not(:has(input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]), select, textarea, .custom-options-container)) > .form-col:has(input[type="radio"], input[type="checkbox"]) {
    padding-bottom: var(--form-error-reserve, 20px);
}

/* Forms dentro de modais: compactos e coerentes com o painel de gestão
   (#manage_lead_panel usa margin:2px + reserva 13px). Evita o espaço vertical
   excessivo que a versão "solta" (margin:5px + reserva 16px) deixava nos modais. */
.modal-content .form-col { margin: 2px 0; }
.modal-content .form-col:has(input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]), select, textarea, .custom-options-container) {
    /* Modais continuam mais compactos que os forms soltos, mas 13px deixava
       de comportar o respiro novo: 17px é o mínimo exacto (4 + 12,5). */
    --form-error-reserve: 17px;
}

form .form-row .form-col:last-child {
    /*margin: 10px 0 0 0;*/
}

form .form-row .form-col:only-child {
    /*margin: 0;*/
}

.form-col.row {
    flex-direction: row;
}

.form-col.text-center {
    text-align: center;
}

/* ── Mensagens de erro de validação (Validation.js) — PADRÃO ÚNICO ──────────
   ESTE é o único sítio onde o erro é posicionado. Não repetir noutro ficheiro:
   um `position: absolute` carregado depois volta a trazer o bug de sobreposição.

   O erro fica NO FLUXO e consome com margem negativa o espaço que o contentor
   reservou, com min-height igual a essa reserva:

     mensagem de 1 linha → altura = reserva → efeito líquido ZERO (nada salta)
     mensagem de n linhas → só o EXCEDENTE empurra o que vem a seguir

   Não é `position: absolute` de propósito. Fora do fluxo a mensagem não tem
   como pedir mais espaço: uma de três linhas cresce para cima e tapa o próprio
   campo que se está a corrigir.

   O contrato é a variável: quem reserva espaço declara --form-error-reserve
   (ver .form-col acima). Onde ela não existe o fallback é 0 — a mensagem
   simplesmente empurra, que é o comportamento seguro. */
.error-messages {
    position: static;
    display: block;
    width: auto;
    min-height: var(--form-error-reserve, 0px);
    margin: 0 0 calc(-1 * var(--form-error-reserve, 0px));
    padding: 0;
    /* Respiro entre o campo e a mensagem. Estava a ZERO: o texto arrancava
       exactamente na borda inferior do input e lia-se como parte do campo.
       Vai no contentor e não no `.error-message` de propósito — há várias
       cópias desse seletor com `margin: 0` (uma delas com especificidade 0,3,0)
       que anulariam qualquer margem posta no texto.
       `box-sizing: border-box` (base.css/default.css) faz este padding ser
       consumido pela reserva do campo: a mensagem desce, o layout não mexe. */
    padding-top: 4px;
    line-height: 1.35;
    pointer-events: none;
}

.form-col.text-center .error-message {
    /*left: 50%;
    transform: translate(-50%, 100%);*/
}

.form-col.center {
    align-items: center;
}

/*.form-row > div {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 0 10px;
}*/

.form-row>p {
    display: flex;
    justify-content: center;
    align-items: center;
    margin: 50px 0;
    padding: 0;
    width: 100%;
    color: var(--color-text, #222222);
    font-size: var(--text-sm, 12px);
    font-weight: var(--weight-regular, 400);
    text-align: center;
}

.form-row .input-container {
    position: relative;
    margin: 0;
    padding: 0;
    width: 100%;
}

.form-row .input-container>div {
    width: 100%;
}

/* ======================== FORM INPUTS ======================== */

/* ========== Dropdown ========== */
select {
    margin: 0;
    padding: 10px 15px;
    width: 100%;
    max-height: 40px;
    font-size: var(--text-sm);
    font-weight: var(--weight-light);
    color: var(--color-text);
    /* Traço forte: com a borda dos tokens a hairline (2026-09-08), um campo
       precisa do contorno que se vê, senão lê-se como texto solto. */
    border: 1px solid var(--color-border-strong);
    /* Escala dos controlos, partilhada com os botões (tokens.css). O valor é o
     mesmo que o antigo `--radius-sm`, e continua alinhado ao
     `.dropdown`/`.select-trigger` do customdropdown.css. */
  border-radius: var(--radius-control-md);
    background: url("data:image/svg+xml;utf8,<svg viewBox='0 0 140 140' width='15' height='15' xmlns='http://www.w3.org/2000/svg'><g><path d='m121.3,34.6c-1.6-1.6-4.2-1.6-5.8,0l-51,51.1-51.1-51.1c-1.6-1.6-4.2-1.6-5.8,0-1.6,1.6-1.6,4.2 0,5.8l53.9,53.9c0.8,0.8 1.8,1.2 2.9,1.2 1,0 2.1-0.4 2.9-1.2l53.9-53.9c1.7-1.6 1.7-4.2 0.1-5.8z' fill='%23999999'/></g></svg>") no-repeat;
    background-position: right 9px top 50%;
    background-repeat: no-repeat;
    background-color: var(--color-surface);
    box-shadow: none !important;
    outline: none;
    -webkit-transition: all 0.3s ease-in-out;
    -moz-transition: all 0.3s ease-in-out;
    -ms-transition: all 0.3s ease-in-out;
    -o-transition: all 0.3s ease-in-out;
    transition: all 0.3s ease-in-out;
    appearance: none;
    -moz-appearance: none;
    -webkit-appearance: none;
}

/* O foco lê-se na borda, mais clara, e em mais nada (decisão do Jorge,
   2026-09-16): vale para todos os campos do CMS. */
select:focus {
    outline: none;
    border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 40%);
}

select:focus-visible {
  outline: none;
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 40%);
}

select:disabled {
    border: 1px solid var(--color-border-strong);
    background-color: var(--color-surface-alt);
    color: var(--color-text-muted);
}

select::-ms-expand {
    display: none;
}

/* Variante compacta — usar em tabelas e filtros inline */
select.small,
input[type="text"].small,
input[type="number"].small,
input[type="search"].small {
    padding: 4px 8px;
    font-size: var(--text-sm);
    max-height: 28px;
    /* Um campo `.small` tem o raio de um BOTÃO `.small` — é a escala dos
       controlos, e o tamanho é a única coisa que a faz variar (tokens.css). */
    border-radius: var(--radius-control-sm);
}

/* Variante extra-compacta — usar em sidebars e painéis de detalhe */
select.xs,
input[type="text"].xs,
input[type="number"].xs {
    padding: 2px 6px;
    font-size: var(--text-sm);
    max-height: 24px;
}

/* Variante para barras de filtro (TabularData, widgets) — altura 32px, padding lateral 8px */
input[type="text"].filter,
input[type="number"].filter,
input[type="search"].filter,
/* `select.filter` já não muda a caixa: um filtro fica lado a lado com inputs de
   texto e destoava por ser 2px mais baixo e ter outro padding. Herda a secção 4
   (NATIVE SELECT) como qualquer outro select. */

/* Variante inline para cabeçalhos de widget — altura exacta igual ao .dropdown-header */
select.widget-filter {
    width: auto;
    min-width: 110px;
    height: var(--height-input);
    box-sizing: border-box;
    padding: var(--space-2) 32px var(--space-2) var(--space-3);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%238b90a0' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-position: right var(--space-3) center;
}

select[multiple] {
    width: 200px;
    height: 100px;
}

/* ========== Custom Dropdown ========== */

.dropdown-options {
    position: relative;
    display: flex;
    flex-direction: column;
}

.dropdown-options .select-trigger {
    display: flex;
    margin: 0;
    padding: 10px 15px;
    width: 100%;
    font-size: var(--text-md);
    font-weight: var(--weight-light);
}

.select-trigger {
    margin: 0;
    padding: 10px 15px;
    width: 100%;
    max-height: 40px;
    font-size: var(--text-md);
    font-weight: var(--weight-light);
    color: var(--color-text);
    border: 1px solid var(--color-text);
    border-radius: var(--radius-sm);
    background: url("data:image/svg+xml;utf8,<svg viewBox='0 0 140 140' width='15' height='15' xmlns='http://www.w3.org/2000/svg'><g><path d='m121.3,34.6c-1.6-1.6-4.2-1.6-5.8,0l-51,51.1-51.1-51.1c-1.6-1.6-4.2-1.6-5.8,0-1.6,1.6-1.6,4.2 0,5.8l53.9,53.9c0.8,0.8 1.8,1.2 2.9,1.2 1,0 2.1-0.4 2.9-1.2l53.9-53.9c1.7-1.6 1.7-4.2 0.1-5.8z' fill='%23999999'/></g></svg>") no-repeat;
    background-position: right 9px top 50%;
    background-repeat: no-repeat;
    box-shadow: none !important;
    outline: none;
    -webkit-transition: all 0.3s ease-in-out;
    -moz-transition: all 0.3s ease-in-out;
    -ms-transition: all 0.3s ease-in-out;
    -o-transition: all 0.3s ease-in-out;
    transition: all 0.3s ease-in-out;
    appearance: none;
    -moz-appearance: none;
    -webkit-appearance: none;
}

/* custom dropdown options */
.dropdown-options .custom-options {
    position: absolute;
    display: block;
    top: 96%;
    left: 0;
    right: 0;
    max-height: 200px;
    border: 1px solid var(--color-text);
    border-top: 0;
    background-color: var(--color-surface);
    transition: all 0.5s;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    z-index: 2;
    overflow: auto;
}

.dropdown-options.open .custom-options {
    opacity: 1;
    visibility: visible;
    pointer-events: all;
}

.dropdown-options .custom-option {
    position: relative;
    display: flex;
    justify-content: space-between;
    padding: 10px 15px;
    font-size: var(--text-md);
    font-weight: var(--weight-light);
    color: var(--color-text);
    line-height: 30px;
    cursor: pointer;
    transition: all 0.5s;
}

.dropdown-options .custom-option:hover {
    cursor: pointer;
    background-color: var(--color-surface-hover);
}

.dropdown-options .custom-option.selected {
    color: var(--color-text);
    background-color: var(--color-surface-hover);
}

.dropdown-options .custom-options .disabled {
    position: relative;
    display: flex;
    justify-content: space-between;
    padding: 10px 15px;
    font-size: 15px; /* intentional: larger disabled-option emphasis */
    font-weight: var(--weight-light);
    color: var(--color-text-muted);
    line-height: 30px;
    transition: all 0.5s;
    color: var(--color-text-muted);
    background-color: var(--color-surface-alt);
}

.dropdown-options .custom-options .disabled button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    padding: 0;
    float: none;
    font-size: var(--text-md);
    font-weight: var(--weight-light);
    color: var(--color-text);
    background-color: transparent;
    text-align: center;
    text-decoration: underline;
    outline: none;
    transition: 0.2s ease-in-out;
    user-select: none;
    cursor: pointer;
    border: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
}

.dropdown-options .custom-options .disabled button:focus-visible {
  outline: calc(var(--focus-ring-width) * 2) solid var(--focus-ring-color);
  outline-offset: 2px;
}

.dropdown-options .arrow {
    position: relative;
    height: 6px;
    width: 6px;
}

.dropdown-options .arrow::before,
.dropdown-options .arrow::after {
    content: "";
    position: absolute;
    bottom: 0px;
    width: 1px;
    height: 100%;
    transition: all 0.5s;
}

.dropdown-options .arrow::before {
    left: -4px;
    transform: rotate(-45deg);
    background-color: var(--color-text);
}

.dropdown-options .arrow::after {
    left: 0px;
    transform: rotate(45deg);
    background-color: var(--color-text);
}

.open .arrow::before {
    left: -4px;
    transform: rotate(45deg);
}

.open .arrow::after {
    left: 0px;
    transform: rotate(-45deg);
}

.dropdown-options .custom-options .product-option-price {
    font-weight: var(--weight-light);
}

.dropdown-options .custom-options .product-option-price .option-old-price {
    font-weight: var(--weight-light); /* intentional: legacy thin old-price style approximation */
    text-decoration: line-through 1px var(--color-text-muted);
}

/* ========== Labels ========== */

.form-label,
label {
    margin: 0 0 4px 0;
    padding: 0 0;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--color-text);
    text-align: left;
}

.form-label a,
label a {
    font-weight: inherit;
    color: var(--color-text-muted);
}

.form-label a:hover,
label a:hover {
    color: inherit;
}

.form-label {
    display: block;
}

/* Os campos base (input/textarea/select) vivem nas secções 2, 3 e 4 deste
   ficheiro — um só sítio por controlo. Havia aqui um segundo bloco a redefini-los
   com outros valores e `box-shadow: none !important`, que anulava o anel de foco
   definido mais abaixo. Removido: era override do próprio ficheiro sobre si mesmo. */

/* Change Color of Placeholders */
input::placeholder,
textarea::placeholder {
    font-size: var(--text-sm);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    opacity: 1;
}

/* ========== Inputs (Checkbox) ========== */

/* A caixa nativa some-se e quem se vê é o ::before do <label> a seguir. Mas
   `display: none` tira o campo da ordem de tabulação, e com ele a única forma de
   marcar a opção sem rato — em toda a aplicação, dos filtros às definições.
   Escondê-lo desta maneira mantém o mesmo resultado visual (não ocupa espaço, não
   se vê) e devolve-o ao teclado. `.native` continua a sobrepor-se, por ter mais
   especificidade, para os contextos que mostram a caixa real. */
input[type="checkbox"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    opacity: 0;
}

/* Foco por teclado: quem chega à opção por Tab tem de ver onde está. O anel vai
   na caixa desenhada (o ::before do label), que é o que está no ecrã. */
input[type="checkbox"]:focus-visible + label::before {
    border-color: var(--color-text-muted, #8b90a0);
    box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color);
}

input[type="checkbox"]+label:before {
    width: 20px;
    height: 20px;
    content: "";
    display: inline-block;
    margin: 0 7px 0 0;
    font-size: 15px; /* intentional: checkbox icon fit */
    text-align: center;
    vertical-align: middle;
    cursor: pointer;
    border: 1px solid var(--color-border-strong);
}

input[type="checkbox"].validation-error+label:before {
    border: 1px solid transparent;
    border-color: var(--color-danger) !important;
}

/* NOTA: o checkmark de input[type=checkbox]:checked é definido mais abaixo com um
   SVG data-URI tema-aware (ver "Checkbox visual — checked"). A antiga regra por
   fonte de ícones (glyph) foi removida — o projeto usa apenas Phosphor + SVG. */

textarea {
    max-width: 100%;
    min-height: 150px;
    max-height: 350px;
}

/* ========== Textarea ========== */

textarea {
    max-width: 100%;
    min-height: 150px;
    max-height: 350px;
}

/* ========== Input Type Search ========== */

input[type="search"]::-ms-clear {
    /* removes close icon - IE */
    display: none;
}

input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration {
    display: none;
}

/* ========== Campo com um botão encostado (afixo) ==========
   Um campo e o botão que lhe pertence lêem-se como UM controlo: a borda, o raio
   e o foco continuam a ser do campo, e o botão vive lá dentro, à direita. Serve
   o olho que mostra a palavra-passe (utils/PasswordReveal.js constrói esta
   anatomia sozinho) e qualquer outro botão que pertença ao campo.

   O padrão nasceu no /login em 2026-09, fechado em `#login-form`. Está aqui para
   o próximo ecrã não o voltar a desenhar de outra maneira. */

.cms-affix-field {
    position: relative;
    display: block;
    width: 100%;
}

/* A reserva impede o texto de passar por baixo do botão. */
.cms-affix-field > input {
    width: 100%;
    padding-right: var(--space-6);
}

.cms-affix-btn {
    position: absolute;
    top: 50%;
    right: var(--space-2);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    color: var(--color-text-muted);
    background: transparent;
    border: 0;
    border-radius: var(--radius-sm);
    transform: translateY(-50%);
    cursor: pointer;
    transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}

.cms-affix-btn:hover,
.cms-affix-btn:focus-visible {
    color: var(--color-text);
    background: var(--color-surface-hover);
}

.cms-affix-btn:focus-visible {
    box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color);
}

/* `.form-row i { visibility: hidden; position: absolute }` apanha qualquer <i>
   numa linha de formulário, e apanhava este: o botão ficava vazio, a funcionar
   às escuras. É o mesmo antídoto que os ícones dentro dos botões .bt-* levam. */
.cms-affix-btn .ph {
    visibility: visible;
    position: static;
    transform: none;
    font-size: 16px; /* intentional: ícone dentro de um alvo de 28px */
}

/* UM OLHO SÓ, E É O DA CASA (relato do Jorge, 2026-09-18).
   O Edge desenha um olho seu dentro de qualquer campo de palavra-passe
   (`::-ms-reveal`). Ao lado do nosso ficavam dois, encostados, e o do Edge
   revela o conteúdo SEM tocar no `type` do campo: a partir do momento em que
   alguém lhe carregasse, o nosso trocava o `type` de um lado para o outro sem
   nada mudar no ecrã, e parecia que só funcionava à primeira. A password ficava
   à vista e não havia forma de a esconder sem recarregar a página.
   O marcador é o que o PasswordReveal.js põe no campo que trata, pelo que isto
   só cala o olho nativo onde já existe o nosso.
   Regra à parte: um pseudo-elemento que o browser não conhece invalida o
   seletor todo, e com ele ia a regra acima. */
input[data-olho="1"]::-ms-reveal { display: none; }

/* ========== Input Type Checkbox / Radio ========== */

input[type="checkbox"]+label,
input[type="radio"]+label {
    top: 0;
    margin: 5px 10px 5px 0;
    text-transform: none;
    letter-spacing: normal;
    color: var(--color-text);
    font-size: var(--text-sm);
    font-weight: var(--weight-light);
    cursor: pointer;
}
.checkboxes>li {
    padding: 7px 0;
}

/* O `display: none` tirava o botao da tabulacao e do leitor de ecra: em todo o
   CMS uma escolha entre opcoes so se fazia com o rato. A caixa de marcar teve
   o mesmo defeito e ja foi corrigida assim (ver acima), e o chat forcou os seus
   com `!important`. A vista nao muda nada: o circulo continua a ser desenhado
   pelo `label:before`, e o campo fica escondido mas alcancavel. */
input[type="radio"] {
    display: inline-block;
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
    opacity: 0;
}

/* Escondido o campo, o anel de foco tem de passar ao circulo que o representa,
   senao quem anda pelo teclado nao ve onde esta. E o mesmo anel da caixa de
   marcar da casa. */
input[type="radio"]:focus-visible+label:before {
    border-color: var(--color-text-muted);
    box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color); /* ds-ok: o mesmo anel da caixa de marcar */
}

input[type="radio"]+label:before {
    display: inline-block;
    content: "";
    width: 20px;
    height: 20px;
    background-color: var(--color-surface-alt);
    border-radius: 100%; /* intentional: circular radio control */
    margin: -3px 10px 0 0;
    outline: none;
    vertical-align: middle;
    cursor: pointer;
    margin-bottom: 0;
}

input[type="radio"]+label:before:focus-visible {
  outline: calc(var(--focus-ring-width) * 2) solid var(--focus-ring-color);
  outline-offset: 2px;
}

input[type="radio"].validation-error+label:before {
    border: 1px solid transparent;
    border-color: #ff6566 !important;
}

input[type="radio"]:checked+label:before {
    background-color: var(--color-text);
    border: 5px solid var(--color-surface-alt);
    padding: 1px;
}

input[type="radio"]:focus {
    outline: none;
}

input[type="radio"]:focus-visible {
  outline: calc(var(--focus-ring-width) * 2) solid var(--focus-ring-color);
  outline-offset: 2px;
}

/* ======================== FORM VALIDATION ======================== */

.validation-success {
    border-color: var(--color-green, #66cc99) !important;
}

.validation-error {
    border-color: var(--color-red, #ff6566) !important;
}

.datetimepicker-time-slots.validation-error {
    border-color: transparent !important;
}

.datetimepicker-time-slots.validation-error .time-slot,
.datetimepicker-time-slots .time-slot.validation-error {
    border-color: var(--color-red) !important;
}

.form-row i {
    visibility: hidden;
    position: absolute;
    top: 50%;
    right: 10px;
    transform: translate(0%, -50%);
}

.form-row .error-message {
    display: block;
    margin: 0;
    padding: 0;
    /* A tinta do vermelho, nao a cor que o PINTA: a 10px a `--color-red` ficava
       a 4,27:1 sobre a superficie de dentro, e texto pequeno pede 4,5:1. */
    color: var(--color-red-text);
    font-size: var(--text-2xs, 10px);
    font-weight: var(--weight-light, 300);
    line-height: 1.25;
    white-space: normal;
    visibility: hidden;
}

.form-row .error-message {
    visibility: visible;
}

/* O posicionamento do erro do SelectBoxes está no PADRÃO ÚNICO lá em cima,
   junto com o dos restantes campos. Aqui fica só a tipografia — que precisa de
   !important para vencer o selectboxes.css, carregado depois. */
.custom-options-container + .error-messages .error-message,
.error-messages--custom-input .error-message {
    display: block !important;
    margin: 0 !important;
    padding: 0 !important;
    color: var(--color-red-text) !important;
    font-size: var(--text-2xs) !important;
    font-weight: var(--weight-light) !important;
    line-height: 1.35 !important;
    text-align: left !important;
    white-space: normal !important;
    visibility: visible !important;
}

.form-row h4 {
    display: flex;
    margin: 0 0 30px 0;
    justify-content: center;
    align-items: center;
    font-weight: var(--weight-medium);
    font-size: 15px; /* intentional: heading size between text-lg and text-xl */
    color: var(--color-text);
}

.form-row .form-description {
    display: flex;
    margin: 0 0 30px 0;
    padding: 20px;
    justify-content: center;
    align-items: center;
    font-weight: var(--weight-medium);
    font-size: 15px; /* intentional: description badge size */
    color: var(--color-bg); /* faixa invertida: texto = fundo da app */
    background-color: var(--color-text);
}

/* ======================== Slide Forms ======================== */

#slide_form_container {
    margin: 0;
    top: 0;
    right: 30px;
    left: auto;
    width: 450px;
    height: 100%;
}

#slide_form_container {
    position: fixed;
    display: flex;
    flex-direction: column;
    margin: 0 auto;
    padding: 0;
    top: calc(50% + var(--main-header-height)/2);
    width: 95%;
    right: 0;
    left: 0;
    height: calc(100% - var(--main-header-height)*2);
    /* -webkit-box-shadow: 5px 5px 15px 5px #000000; */
    /* box-shadow: 0px 0px 11px 2px #454545; */
    border-radius: 15px; /* intentional: floating panel geometry */
    background-color: var(--color-surface);
    -webkit-transform: translate(100%, -50%);
    -moz-transform: translate(100%, -50%);
    -ms-transform: translate(100%, -50%);
    -o-transform: translate(100%, -50%);
    transform: translate(100%, -50%);
    -webkit-transition: all 0.3s ease-in-out;
    -moz-transition: all 0.3s ease-in-out;
    -ms-transition: all 0.3s ease-in-out;
    -o-transition: all 0.3s ease-in-out;
    transition: all 0.3s ease-in-out;
    opacity: 0;
    visibility: hidden;
    overflow: hidden;
    z-index: 1000;
}

#slide_form_container.is-active {
    -webkit-transform: translate(0%, -50%);
    -moz-transform: translate(0%, -50%);
    -ms-transform: translate(0%, -50%);
    -o-transform: translate(0%, -50%);
    transform: translate(0%, -50%);
    opacity: 1 !important;
    visibility: visible !important;
}

@media (min-width: 992px) {

    #slide_form_container {
        position: fixed;
        display: flex;
        flex-direction: column;
        margin: 0 auto;
        padding: 0;
        top: 0;
        width: 95%;
        right: 0;
        left: 0;
        height: 100%;
        /* -webkit-box-shadow: 5px 5px 15px 5px #000000; */
        /* box-shadow: 0px 0px 11px 2px #454545; */
        border-radius: 0; /* intentional: edge-to-edge desktop panel */
        background-color: var(--color-surface);
        -webkit-transform: translate(100%, 0%);
        -moz-transform: translate(100%, 0%);
        -ms-transform: translate(100%, 0%);
        -o-transform: translate(100%, 0%);
        transform: translate(100%, 0%);
        -webkit-transition: all 0.3s ease-in-out;
        -moz-transition: all 0.3s ease-in-out;
        -ms-transition: all 0.3s ease-in-out;
        -o-transition: all 0.3s ease-in-out;
        transition: all 0.3s ease-in-out;
        opacity: 0;
        visibility: hidden;
        overflow: hidden;
        z-index: 1000;
    }

    #slide_form_container.is-active {
        -webkit-transform: translate(0%, 0%);
        -moz-transform: translate(0%, 0%);
        -ms-transform: translate(0%, 0%);
        -o-transform: translate(0%, 0%);
        transform: translate(0%, 0%);
        opacity: 1 !important;
        visibility: visible !important;
    }

    #slide_form_container {
        margin: 0;
        top: 0;
        right: 0;
        left: auto;
        width: 450px;
        height: 100%;
    }
}

.form-container {
    position: relative;
    display: flex;
    flex-direction: column;
    margin: 0;
    padding: 0;
    height: 100%;
}

.form-container .form-title {
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
    height: var(--main-header-height);
    width: 100%;
    font-size: 20px; /* intentional: form title scale */
    font-weight: var(--weight-light);
    color: var(--color-text);
    text-align: center;
    background-color: color-mix(in srgb, var(--color-surface), transparent 30%);
    border-bottom: 1px solid var(--color-border);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    z-index: 1;
}

.form-container .form-wrapper {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    margin: 0 auto;
    padding: var(--main-header-height) 0 50px 0;
    max-height: 100vh;
    flex: 1;
    overflow-x: auto;
}

.form-container .form-footer {
    position: relative;
    margin: 0;
    padding: 10px 20px;
    width: 100%;
    bottom: 0;
    border-top: 1px solid var(--color-border);
    box-shadow: 0px -12px 40px 30px var(--color-surface); /* levanta o rodapé tapando o conteúdo */
}

.form-footer button {
    display: flex;
    margin: auto;
    font-size: var(--text-sm);
    transform: none;
}

.form-footer button.link,
.form-footer a {
    display: block;
    margin: 10px auto 10px auto;
    font-size: var(--text-sm);
    font-weight: var(--weight-regular);
    color: var(--color-text-muted);
    background-color: transparent;
    border: none;
    background: transparent;
    transition: all 0.3s ease-in-out;
}

.form-footer button.link:hover,
.form-footer a:hover {
    color: var(--color-text);
}

.form-footer .bt-filled,
.form-footer .bt-outline,
.form-footer button:hover,
.form-footer a:hover {
    transform: none;
}

/* A linha data+hora é flex: o erro ocupa a largura toda por baixo dos dois
   campos. O resto (reserva e margem negativa) vem do padrão único. */
.event-datetime-row > .error-messages {
    flex: 0 0 100%;
}

.event-range-picker-host {
    width: 100%;
}

.event-range-picker-host .custom-daterangepicker.event-inline-range-picker {
    width: 100%;
}

.event-range-picker-host .custom-daterangepicker.event-inline-range-picker .drp-content {
    display: block;
}

.event-range-picker-host .custom-daterangepicker.event-inline-range-picker .drp-right {
    display: block !important;
    width: 100%;
}

.event-range-picker-host .custom-daterangepicker.event-inline-range-picker .drp-calendars {
    display: flex !important;
    flex-direction: column;
    gap: 12px;
}

.event-range-picker-host .custom-daterangepicker.event-inline-range-picker .calendar-box {
    min-width: 0;
}

.datetimepicker-shell {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 16px;
    padding: 0;
}

/* Calendário: ocupa largura total */
.datetimepicker-shell .event-date-picker-host {
    width: 100%;
    min-width: 0;
}

/* Slots de horário: ocupa largura total, abaixo do calendário. A altura é a das
   horas (`flex: none`): o `.col-12` que o DateTimePicker lhe dá é
   `flex: 0 0 100%`, feito para uma linha da grelha de colunas, e nesta coluna
   esticava a grelha à altura do shell. Ficavam 340px vazios por baixo da
   última hora, e a frase de erro da grelha desenhava-se depois deles, fora da
   vista do formulário. */
.datetimepicker-shell .datetimepicker-time-slots {
    flex: none;
    width: 100%;
    min-width: 0;
    margin-top: 0 !important;
    border-top: 1px solid var(--color-border);
    padding-top: 14px;
}

.datetimepicker-time-slots .time-slot {
    position: relative;
    overflow: hidden;
}

.datetimepicker-time-slots .time-slot-corner-indicator {
    position: absolute;
    top: 0;
    right: 0;
    width: 0;
    height: 0;
    border-top: 12px solid var(--color-green);
    border-left: 12px solid transparent;
    border-top-right-radius: 8px;
}

/* Mobile-first: empilhados na base, lado a lado a partir de 901px. Esta
   variante vive dentro de um formulário e por isso empilha mais cedo que o
   picker normal (601px, mais abaixo). */
@media (min-width: 901px) {
    .event-range-picker-host .custom-daterangepicker.event-inline-range-picker .drp-calendars {
        flex-direction: row;
    }
}

/* Erros dentro de form-col: PADRÃO ÚNICO — wrap normal (mensagens longas
   expandem no espaço reservado do campo em vez de cortar com reticências). */
.form-col .error-messages .error-message {
    white-space: normal;
    line-height: 1.25;
    margin: 0;
    visibility: visible;
}

/* ============================================================----

Form Cards

============================================================---- */

/* style radio options list container */
.form-card-container {
    display: flex;
    flex-wrap: wrap;
    flex-direction: row;
    padding: 0;
    width: 100%;
    --gap: 3px;
    --columns: 1;
    gap: var(--gap);
    /*border: 3px solid #e0e1ee;*/
    transition: all 0.5s;
}

/*#car_services_dropdown [data-value="data-input"]{
    display: flex;
    align-items: center;
    flex-direction: column;
    flex-wrap: wrap;
    justify-content: center;
    flex: 0 0 auto;
    margin: 0;
    padding: 30px 20px;
    width: 190px;
    min-width: calc((100% / var(--columns)) - var(--gap) + (var(--gap) / var(--columns)));
    max-width: calc((100% / var(--columns)) - var(--gap) + (var(--gap) / var(--columns)));
    min-height: 170px;
    font-size: 15px;
    font-weight: 400;
    color: #1a1a1a;
    line-height: normal;
    border: 2px solid transparent;
    border-radius: 10px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    background-color: #ffffff;
    cursor: pointer;
    transition: all 0.5s;
}*/

.form-card-container.validation-error .form-card {
    border-color: #ff6566 !important;
}

.form-card .ph-icon {
    position: absolute;
    margin: 0;
    padding: 0;
    top: 10px;
    right: 10px;
    font-size: 20px;
    color: var(--main-theme-color);
    visibility: hidden;
}

.form-card[data-selected="true"] .ph-icon {
    visibility: visible;
}

.form-card .form-card-image {
    margin: 0 0 10px 0;
    padding: 0;
    width: 100%;
    height: 110px;
    object-fit: cover;
}

.form-card .form-card-list {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-direction: column;
    width: 100%;
    margin: 20px 0 auto 0;
    padding: 0;
    font-weight: 400;
    font-size: 12px;
    list-style-type: none;
}

.form-card .form-card-list li {
    margin: 0;
    text-align: center;
}

@media (min-width: 992px) {

    .form-card-container {
        --gap: 10px;
        --columns: 8;
        gap: var(--gap);
    }

}

#slide_form_container .form-card-container {
    --gap: 3px;
    --columns: 2;
    gap: var(--gap);
    justify-content: flex-start;
}

#slide_form_container .form-card-container .form-card {
    justify-content: center;
    align-items: flex-start;
    padding: 30px 5px;
    min-height: auto;
}

#slide_form_container .form-card span.card-title {
    font-size: 12px;
}

#slide_form_container .form-card-container .form-card .form-card-list img {
    width: 15px;
    margin: 0 7px 0 0;
}

#slide_form_container .form-card-container .form-card .form-card-list span {
    margin: 0;
    padding: 0;
    font-weight: 500;
    font-size: 12px;
    line-height: normal;
    color: #656565;
}

#slide_form_container .form-card-container .form-card .form-card-list li {
    text-align: left;
}

/* NOTA: a regra global .hidden-input-files (que tornava o input nativo de
   ficheiros VISÍVEL) foi removida — era legacy de um uploader pré-componente e
   provocava um 2.º "botão" de upload ao lado do botão real do Uploader.js. O
   input fica escondido por Shared/Components/Uploader/styles/uploader.css
   (display:none), agora auto-injetado pelo próprio Uploader.js. */

.soft-deleted {
    height: 65px;
    color: var(--color-text);
    background-color: var(--color-danger-subtle) !important;
}

.tooltip-container {
    position: relative;
    display: inline-flex;
    margin: 0 4px;
    padding: 20p;
    opacity: 1;
}

.tooltip-container .ph-icon {
    font-size: 25px;
    color: var(--color-text);
}

table tbody tr:hover .tooltip-container .ph-icon {
    color: #ffffff !important;
}

.tooltip-container span {
    visibility: hidden;
    background-color: #000;
    color: #fff;
    text-align: center;
    border-radius: 6px;
    padding: 20px;
    position: absolute;
    font-size: 12px;
    z-index: 9;
    top: 50%;
    transform: translate(0, -50%);
    margin-left: 32px;
    text-align: left;
    transition: opacity 1s;
}

.tooltip-container span::after {
    content: "";
    position: absolute;
    top: 50%;
    right: 100%;
    margin-top: -5px;
    border-width: 5px;
    border-style: solid;
    border-color: transparent black transparent transparent;
}

.tooltip-container input {
    display: none;
}

.tooltip-container input:checked+span {
    visibility: visible;
    opacity: 1;
}

/* ======================== DATETIME PICKER ======================== */

.calendar-container .calendar-nav {
    display: flex;
    align-items: center;
    margin: 0 0 10px 0;
    width: 100%;
}

/* Calendar Container */
.calendar-container {
    padding: 0;
    margin: 0;
}

/* Calendar Header */
.calendar-container .calendar-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 15px;
    font-size: 18px;
    font-weight: 600;
    color: #333;
}

/* Days Row (Week Names) */
.calendar-container .days-row {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    margin: 0;
    padding: 5px;
    font-size: 12px;
    text-align: center;
    font-weight: 600;
    color: #ffffff;
    background-color: #000000;
}

/* Days Grid */
.calendar-container .days-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 3px;
    padding: 15px 0;
    border: 1px solid #000000;
}

/* Days Styles */
.calendar-container .day{
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 12px;
    font-weight: 700;
    width: 30px;
    height: 30px;
    margin: auto;
    border-radius: 50%;
    transition: all 0.3s ease;
    cursor: pointer;
}

/* Time Slots Container */
.calendar-container + .time-slots {
    margin-top: 15px;
    padding: 0;
}

/* Time Slot Styles */
.calendar-container + .time-slots .time-slot {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 4px;
    background-color: var(--color-surface);
    color: var(--color-text);
    border: 1px solid transparent;
    border-radius: 7px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.3s ease;
}

/* ══════════════════════════════════════════════════════════════════════════
   A PERGUNTA COM TRÊS SAÍDAS  (`escolher()`, 2026-09-20)
   ─────────────────────────────────────────────────────────────────────────
   Guardar, largar, ou voltar atrás. O botão principal é o caminho SEGURO e
   fica em destaque; o que se pede com cuidado é o do meio, e leva a tinta de
   perigo só quando há mesmo alguma coisa a perder. O texto respira: era uma
   linha colada ao título e outra colada aos botões.
   ══════════════════════════════════════════════════════════════════════════ */
.modal-window-container[data-modal-id^="cms-escolher"] .cms-confirm-text {
    margin: 0;
    padding: var(--space-2) 0 var(--space-3);
    font-size: var(--text-sm);
    line-height: 1.55;
    color: var(--color-text);
    /* Texto corrido não passa de 75 caracteres por linha. */
    max-width: 62ch;
}
/* O caminho que perde alguma coisa: tinta de perigo, sem o peso de um botão
   cheio, que é o que separa «faz isto» de «cuidado com isto». */
.modal-window-container[data-modal-id="cms-escolher-perde"] .modal-action-button--neutral {
    color: var(--color-danger);
    border-color: color-mix(in srgb, var(--color-danger) 45%, transparent);
}
.modal-window-container[data-modal-id="cms-escolher-perde"] .modal-action-button--neutral:hover {
    border-color: var(--color-danger);
    background: color-mix(in srgb, var(--color-danger) 12%, transparent);
}

/* ══════════════════════════════════════════════════════════════════════════
   ONDE É QUE ISTO VAI CAIR  (2026-09-20)
   A barra que aparece no texto enquanto se arrasta um marcador para dentro
   dele, com o marcador em fantasma ao lado. Vive no `body` e por cima de
   tudo: um elemento dentro de um `contenteditable` é conteúdo, e um arrasto
   interrompido deixava-o no corpo da mensagem.
   ══════════════════════════════════════════════════════════════════════════ */
.cms-drop-caret {
    position: absolute;
    z-index: var(--z-tooltip, 900);
    display: flex;
    align-items: center;
    gap: 4px;
    pointer-events: none;
}
.cms-drop-caret-bar {
    width: 2px;
    height: 100%;
    border-radius: 1px;
    background: var(--color-primary);
    animation: cms-drop-caret-blink 1s steps(2, start) infinite;
}
/* O marcador em fantasma diz O QUÊ, e a barra diz ONDE. */
.cms-drop-caret-text {
    padding: 0 5px;
    border-radius: var(--radius-sm);
    background: var(--color-primary);
    color: #fff;   /* intentional: tinta fixa sobre o acento, nos dois temas */
    font-size: var(--text-2xs);
    line-height: 1.6;
    white-space: nowrap;
}
.cms-drop-caret-text:empty { display: none; }
@keyframes cms-drop-caret-blink {
    to { opacity: .35; }
}
@media (prefers-reduced-motion: reduce) {
    .cms-drop-caret-bar { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   A JANELA DE «ENVIAR MAIS TARDE»  (2026-09-20)
   É a mesma nos três sítios que escrevem e-mail (`Services/MailScheduleUI.js`)
   e o desenho dela vivia na folha do módulo das leads: nas outras páginas
   abria sem estilo nenhum. Aqui carrega em todo o lado, que é onde tem de
   funcionar.
   ══════════════════════════════════════════════════════════════════════════ */
.cms-mail-schedule { display: flex; flex-direction: column; gap: var(--space-3); }
.cms-mail-schedule-note,
.cms-mail-schedule-when {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}
/* A frase que diz a hora escolhida é a resposta à pergunta da janela: fica em
   tinta normal, que o resto é contexto. */
.cms-mail-schedule-when { font-weight: var(--weight-medium); color: var(--color-text); }

/* ══════════════════════════════════════════════════════════════════════════
   AS HORAS DO DateTimePicker, EM TODO O CMS  (2026-09-20)
   ─────────────────────────────────────────────────────────────────────────
   O componente é partilhado (`Shared/Components/DateTimePicker`) e o desenho
   das horas dele vivia na folha do módulo das leads (`manage.css`). Quem o
   abrisse noutra página via a lista de horas em texto cru, uma por linha: foi
   o que aconteceu ao «Enviar mais tarde» do correio, que é a mesma janela nos
   três sítios e só tinha estilo num deles. O sítio de um componente
   partilhado é esta folha, que carrega em todas as páginas do CMS.

   As regras são as que estavam lá, com os tokens da casa. A folha do módulo
   ficou sem elas.
   ══════════════════════════════════════════════════════════════════════════ */
.time-slots {
    margin-top: 15px;
    padding: 0;
}
/* Cabem as que couberem, com 60px de largura mínima: numa coluna estreita são
   três por linha, num modal largo são oito. */
.time-slots-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
    gap: 3px;
    padding: 0;
}
.time-slot {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 4px;
    background-color: var(--color-surface);
    color: var(--color-text);
    border: 1px solid transparent;
    border-radius: 7px;   /* intentional: pastilha de hora, entre o radius-sm e o radius */
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    cursor: pointer;
    transition: background-color var(--dur-lg) ease, transform var(--dur-lg) ease;
}
.time-slot.available {
    background-color: var(--color-green-subtle);
    color: var(--color-green);
}
.time-slot.available:hover:not(.selected) {
    background-color: color-mix(in srgb, var(--color-green) 20%, transparent);
    color: var(--color-green);
    transform: scale(1.05);
}
.time-slot.blocked,
.time-slot.pending,
.time-slot.scheduled,
.time-slot.reserved,
.time-slot.overdue,
.time-slot.missed,
.time-slot.done { cursor: not-allowed; }
.time-slot.blocked  { background-color: var(--color-surface-alt); color: var(--color-text-muted); }
.time-slot.pending,
.time-slot.scheduled { background-color: var(--color-text); color: var(--color-surface); }
.time-slot.reserved { background-color: var(--color-cyan-subtle);   color: var(--color-cyan); }
.time-slot.overdue  { background-color: var(--color-orange-subtle); color: var(--color-orange); }
.time-slot.missed   { background-color: var(--color-red-subtle);    color: var(--color-red); }
.time-slot.done     { background-color: var(--color-green-subtle);  color: var(--color-green); }
.time-slot.selected {
    background-color: var(--color-blue);
    color: var(--color-surface);
    font-weight: var(--weight-semibold);
}
@media (prefers-reduced-motion: reduce) {
    .time-slot { transition: none; }
    .time-slot.available:hover:not(.selected) { transform: none; }
}

/* ── Variante embutida em widget (ex: PhoneInput) ───────────────────────── */
/* Remove visualmente o input quando está encaixado dentro de um widget
   que providencia a sua própria borda e focus ring */
input.input-embedded,
input.input-embedded:focus,
input.input-embedded:hover,
input.input-embedded:focus-visible {
    border: none;
    background: transparent;
    box-shadow: none;
    outline: none;
    padding-left: 0;
    padding-right: 0;
    transition: none; /* evita tremor causado por transition:all em forms.css ao animar padding */
}

/* ═══════════════ [origem: inputs.css] ═══════════════ */

/**
 * inputs.css
 * Canonical source of truth for all form input styling in the Pilot backoffice.
 * All visual values use design tokens (vars-colors.css, vars-dimensions.css, themes.css).
 * Overrides hardcoded values in forms.css — loaded immediately after it.
 *
 * Covers:
 *   1. Labels + abbr.required
 *   2. Text / email / password / tel / number / date / time / search
 *   3. Textarea
 *   4. Native <select>
 *   5. Checkbox (appearance:none — replaces display:none + ::before pattern)
 *   6. Radio (appearance:none — replaces display:none + ::before pattern)
 *   7. Fieldset (.default-fieldset)
 *   8. Dropdown component (.dropdown · Dropdown.js + CustomDropdown.js)
 *      → supplemented by customdropdown.css rewrite (see that file)
 *   9. SelectBoxes component (.option-card)
 *  10. Validation states
 */

/* ══════════════════════════════════════════════════════════════════════════════
   1. LABELS
   ══════════════════════════════════════════════════════════════════════════════ */

.form-label,
label {
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  color: var(--color-text-muted);
  letter-spacing: 0.025em;
  margin: 0 0 5px 0;
  padding: 0;
}

.form-label a,
label a {
  color: inherit;
  font-weight: inherit;
  text-decoration: underline;
  text-decoration-color: var(--color-border);
}

/* O asterisco herda a cor do rótulo, e não a de perigo: a informação está no
   asterisco, não na cor, e o vermelho ao lado de cada campo lia-se como se
   houvesse ali alguma coisa errada. Decisão do Jorge, 2026-09-20. */
abbr.required {
  margin-left: 3px;
  color: inherit;
  text-decoration: none;
  font-style: normal;
}

/* ══════════════════════════════════════════════════════════════════════════════
   2. BASE FIELDS — todos os campos de texto/valor

   Definido por EXCLUSÃO e não por lista de tipos. A lista tinha de ser repetida
   em cada estado (base, placeholder, hover, focus, disabled, …) e um tipo novo
   ficava de fora em todos eles — foi o que aconteceu ao `datetime-local`, que
   acabou reestilizado à mão noutro ficheiro só para se parecer com os demais.
   Assim, qualquer campo de texto/data/número herda o estilo sem se tocar aqui.

   Ficam de fora os controlos com anatomia própria (checkbox e radio têm as suas
   secções mais abaixo; file, range, color e os botões são widgets nativos).
   ══════════════════════════════════════════════════════════════════════════════ */

/* Um só sítio: mudar esta lista muda TODOS os estados abaixo. */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]) {
  display: block;
  width: 100%;
  height: var(--height-input);
  margin: 0;
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--color-text);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-strong);
  /* Escala dos controlos (tokens.css) — o mesmo raio dos botões e dos inputs.
     Continua alinhado ao .dropdown/.select-trigger do customdropdown.css. */
  border-radius: var(--radius-control-md);
  outline: none;
  box-shadow: none;
  max-height: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
  -webkit-appearance: none;
  appearance: none;
}

input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"])::placeholder {
  color: var(--color-text-muted);
  /* Sem opacidade: o apagado já é um degrau abaixo do valor escrito, e a metade
     deixava o texto de ajuda a 2,1:1 no tema claro. */
  opacity: 1;
  font-weight: var(--weight-regular);
  font-size: var(--text-sm);
}

input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):hover {
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 20%);
}

input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):focus {
  /* Só a borda, mais clara (decisão do Jorge, 2026-09-16): sem anel. */
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 40%);
  box-shadow: none;
  outline: none;
}

input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):disabled {
  background: var(--color-surface);
  color: var(--color-text-muted);
  border-color: var(--color-border-strong);
  opacity: 0.65;
  cursor: not-allowed;
}

/* Só de leitura: o campo mostra um valor que não se muda aqui (o nome que vem
   da ficha, a consequência que decorre do tipo de falta). Era igual a um campo
   editável, e a pessoa clicava e não percebia porque não escrevia. Menos
   apagado do que o desativado, porque continua a poder ler-se, copiar-se e
   receber foco; o rato é que diz logo que não se escreve. Sem o realce de
   hover, pela mesma razão. Base para todo o CMS (2026-09-14). */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"])[readonly],
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"])[readonly]:hover,
textarea[readonly],
textarea[readonly]:hover {
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  border-color: var(--color-border);
  cursor: not-allowed;
}

/* ══════════════════════════════════════════════════════════════════════════════
   NÚMEROS: o campo de número e o cursor deslizante
   Os dois controlos nativos que faltavam ao sistema. O campo de número herdava
   o desenho dos campos de texto mas ficava com as setinhas do browser coladas
   ao valor, e o cursor deslizante desenhava-se com o `accent-color`, ou seja,
   com o azul do sistema operativo: uma barra de outra paleta no meio de um
   formulário que não a tem em mais lado nenhum.

   Isto é a base para todo o CMS (decisão do Jorge, 2026-09-10). Uma página que
   precise de outra coisa acrescenta uma variante; não reescreve o controlo.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── Campo de número ─────────────────────────────────────────────────────────
   Algarismos de largura fixa, para uma coluna de valores não dançar ao escrever,
   e as setas escondidas até o rato ou o foco lá chegarem: um campo em repouso
   mostra o valor, não os comandos. */
input[type="number"] {
  font-variant-numeric: tabular-nums;
}
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  opacity: 0;
  height: 22px;
  margin-left: var(--space-1);
  transition: opacity 0.15s ease;
}
input[type="number"]:hover::-webkit-inner-spin-button,
input[type="number"]:focus::-webkit-inner-spin-button {
  opacity: 1;
}
/* O Firefox não desenha as setas por omissão em `textfield`, e assim os dois
   browsers mostram o mesmo campo. */
input[type="number"] { -moz-appearance: textfield; }
input[type="number"]:hover,
input[type="number"]:focus { -moz-appearance: number-input; }

/* ── Cursor deslizante ───────────────────────────────────────────────────────
   Um carril fino da superfície interior, o percurso já feito com o gradiente de
   grandeza dos tokens (o mesmo das barras dos gráficos) e um botão redondo com
   a hairline da casa. O preenchimento em WebKit faz-se com uma sombra larga a
   sair do botão, dentro de um carril que corta o que passa das pontas: não há
   pseudo-elemento de progresso nesse motor, e o Firefox tem o seu.

   O foco lê-se num RETÂNGULO à volta do controlo inteiro e não num anel à volta
   do botão: o anel redondo do browser era a única peça arredondada do
   formulário, e num carril de 4px lia-se como um defeito de desenho. */
input[type="range"] {
  --range-track: var(--color-surface-alt);
  --range-fill: var(--color-text-muted);
  --range-thumb-size: 16px;
  --range-track-height: 4px;
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: var(--range-thumb-size);
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: var(--range-track-height);
  border-radius: var(--radius-full);
  background: var(--range-track);
  border: 1px solid var(--color-border);
  box-sizing: border-box;
  /* Corta a sombra do botão nas pontas, que é o que faz o preenchimento. */
  overflow: hidden;
}
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: var(--range-thumb-size);
  height: var(--range-thumb-size);
  margin-top: calc((var(--range-track-height) - var(--range-thumb-size)) / 2);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
  box-shadow: -1000px 0 0 calc(1000px - var(--range-thumb-size) / 2) var(--range-fill);
  transition: border-color 0.15s ease, background 0.15s ease;
}
input[type="range"]:hover::-webkit-slider-thumb { border-color: var(--color-text-muted); }

input[type="range"]::-moz-range-track {
  height: var(--range-track-height);
  border-radius: var(--radius-full);
  background: var(--range-track);
  border: 1px solid var(--color-border);
  box-sizing: border-box;
}
input[type="range"]::-moz-range-progress {
  height: var(--range-track-height);
  border-radius: var(--radius-full);
  background: var(--range-fill);
}
input[type="range"]::-moz-range-thumb {
  width: var(--range-thumb-size);
  height: var(--range-thumb-size);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  border: 1px solid var(--color-border-strong);
}

input[type="range"]:focus { outline: none; }
input[type="range"]:focus-visible {
  outline: var(--focus-ring-width) solid var(--color-text-muted);
  outline-offset: 4px;
  border-radius: var(--radius-sm);
}
input[type="range"]:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

@media (prefers-reduced-motion: reduce) {
  input[type="range"]::-webkit-slider-thumb,
  input[type="number"]::-webkit-inner-spin-button { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   GRUPO DE CAMPO — um prefixo (bandeira, lupa, unidade) e um campo, numa caixa
   Um indicativo telefónico ao lado do número, ou uma lupa ao lado da pesquisa,
   é UM controlo: uma borda, um raio, um estado de foco, um estado de erro. O
   que se via era o contrário — duas bordas encaixadas, dois raios diferentes e
   dois anéis de foco — porque o contentor desenha a caixa E o campo lá dentro
   continuava a desenhar a dele.

   Porque é que as regras do componente não chegavam: a regra base dos inputs
   (acima) é definida por exclusão, com dez `:not([type=…])`, o que lhe dá
   especificidade (0,10,1). O `.phone-input-widget .pi-number-input` do
   componente vale (0,2,0) e perdia — o `border: none` que lá está escrito nunca
   chegou a aplicar-se. Estas regras repetem a mesma lista dentro de um `:is()`,
   ficam em (0,11,1) e ganham por um cabelo, que é o mínimo para não estarem a
   atropelar outra coisa qualquer.

   Para um grupo novo, basta juntar a classe ao `:is()` — não há nada a repetir.
   ══════════════════════════════════════════════════════════════════════════════ */

/* A CAIXA: é ela que tem a anatomia de input. Traz tambem a moldura e o fundo,
   que ate 2026-09-09 cada sitio escrevia por sua conta: sem eles a classe punha
   a borda do campo a zero (a regra a seguir) e nao punha nenhuma no lugar, e o
   campo desaparecia. NAO e a celula de rotulo mais campo de um formulario, que
   e o `.form-col`; a confusao entre as duas deixou o formulario de documentos
   do pos-venda, o do servidor e dois do perfil com campos invisiveis. */
.app .input-group,
.app .phone-input-widget,
.app .uf-search,
.app .lm-search-field {
  border-radius: var(--radius-sm);
  overflow: hidden;   /* o prefixo não pinta por cima do canto arredondado */
}
.app .input-group {
  display: flex;
  align-items: center;
  border: 1px solid var(--color-border-strong);
  background: var(--color-surface);
}
.app .input-group > .input-group-prefix {
  display: flex;
  align-items: center;
  padding: 0 var(--space-3);
  border-right: 1px solid var(--color-border);
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* A UNIDADE DE UM CAMPO: «% do preço base», «% da margem disponível», «kW».
   Fica ao LADO do campo e não dentro dele, e é isso que a distingue do
   `.input-group-prefix` de cima: o prefixo é parte do que se escreve (o +351
   de um telefone, o € de um valor), e a unidade é a legenda do número, que se
   lê e não se escreve. Dentro da caixa, com fundo e traço, a unidade passava a
   parecer um controlo, e um campo de percentagem ficava com duas molduras.

   Desenho da maqueta dos três circuitos (2026-09-24), onde os três campos de
   margem de uma versão a usam. O campo encolhe para o número que leva, que é
   o que impede uma percentagem de ocupar meia linha. */
.field-unit {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}
.field-unit > input { flex: 0 1 110px; min-width: 0; }
.field-unit-suffix {
  flex: 0 0 auto;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
  white-space: nowrap;
}

/* O foco é o do input normal (a borda mais clara), e é a CAIXA que o mostra. */
.app .input-group:focus-within,
.app .phone-input-widget:focus-within,
.app .uf-search:focus-within,
.app .lm-search-field:focus-within {
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 40%);
  box-shadow: none;
}

/* Erro: a caixa inteira, como um input com `.validation-error`. */
.app .input-group.validation-error,
.app .phone-input-widget.validation-error,
.app .uf-search.validation-error,
.app .lm-search-field.validation-error {
  border-color: var(--color-red) !important;
  background-color: color-mix(in srgb, var(--color-red) 10%, var(--color-surface-alt)) !important;
}

/* O CAMPO lá dentro não desenha nada: nem borda, nem fundo, nem raio, nem anel.
   Inclui `.validation-error` porque a validação marca o campo E o contentor —
   e no campo isso repunha o fundo de erro por dentro da caixa que já o tem.

   A `.license-plate` entra nesta lista quando serve de campo (a consulta por
   matrícula, na Visão Geral de Viaturas): a chapa é o contentor, e o número
   escreve-se lá dentro. Sem isto, o campo trazia para dentro da chapa o fundo
   escuro e a borda de qualquer input, e a chapa branca ficava com um retângulo
   do tema no meio.

   A `.gs-inputrow` (pesquisa global, Ctrl K) pela mesma razão: o foco lê-se na
   linha de baixo da fila, e o campo desenhava uma segunda caixa com anel. */
:is(.input-group, .phone-input-widget, .uf-search, .lm-search-field, .license-plate, .gs-inputrow) input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]),
:is(.input-group, .phone-input-widget, .uf-search, .lm-search-field, .license-plate, .gs-inputrow) input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):hover,
:is(.input-group, .phone-input-widget, .uf-search, .lm-search-field, .license-plate, .gs-inputrow) input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):focus,
:is(.input-group, .phone-input-widget, .uf-search, .lm-search-field, .license-plate, .gs-inputrow) input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):focus-visible,
:is(.input-group, .phone-input-widget, .uf-search, .lm-search-field, .license-plate, .gs-inputrow) input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]).validation-error {
  height: 100%;
  border: none !important;
  border-radius: 0 !important;
  background: transparent !important;
  box-shadow: none !important;
  outline: none;
}

/* O prefixo enche a caixa em altura — a 32px numa caixa de 34 ficava a flutuar,
   com um fio do fundo a aparecer por cima e por baixo do separador. */
.app .input-group > .input-group-prefix,
.app .phone-input-widget > .pi-trigger {
  height: 100%;
  align-self: stretch;
}

/* Centred value variant */
input.text-center { text-align: center; }

/* Ícone nativo de calendário/relógio (Chrome) — date, time, datetime-local,
   month e week partilham o mesmo indicador. */
input::-webkit-calendar-picker-indicator {
  opacity: 0.4;
  filter: invert(0.7);
  cursor: pointer;
}

/* ══════════════════════════════════════════════════════════════════════════════
   3. TEXTAREA
   ══════════════════════════════════════════════════════════════════════════════ */

textarea {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  min-height: 100px;
  max-height: 320px;
  padding: var(--space-2) var(--space-3);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  line-height: 1.55; /* intentional: between --line-normal and --line-relaxed for multiline readability */
  color: var(--color-text);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border-strong);
  /* Escala dos controlos (tokens.css) — o mesmo raio dos botões e dos inputs.
     Continua alinhado ao .dropdown/.select-trigger do customdropdown.css. */
  border-radius: var(--radius-control-md);
  outline: none;
  box-shadow: none;
  resize: vertical;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
  -webkit-appearance: none;
  appearance: none;
}

textarea::placeholder {
  color: var(--color-text-muted);
  /* Sem opacidade: o apagado já é um degrau abaixo do valor escrito, e a metade
     deixava o texto de ajuda a 2,1:1 no tema claro. */
  opacity: 1;
  font-weight: var(--weight-regular);
  font-size: var(--text-sm);
}

textarea:hover {
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 20%);
}

textarea:focus {
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 40%);
  box-shadow: none;
  outline: none;
}

textarea:disabled {
  background: var(--color-surface);
  color: var(--color-text-muted);
  border-color: var(--color-border-strong);
  opacity: 0.65;
  cursor: not-allowed;
  resize: none;
}

/* ══════════════════════════════════════════════════════════════════════════════
   4. NATIVE SELECT
   ══════════════════════════════════════════════════════════════════════════════ */

select {
  display: block;
  width: 100%;
  height: var(--height-input);
  padding: var(--space-2) var(--space-3);
  padding-right: 32px;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--color-text);
  background-color: var(--color-surface-alt);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%238b90a0' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  border: 1px solid var(--color-border-strong);
  /* Escala dos controlos (tokens.css) — o mesmo raio dos botões e dos inputs.
     Continua alinhado ao .dropdown/.select-trigger do customdropdown.css. */
  border-radius: var(--radius-control-md);
  outline: none;
  box-shadow: none;
  max-height: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  cursor: pointer;
}

select::-ms-expand { display: none; }

select:hover {
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 20%);
}

select:focus {
  border-color: color-mix(in srgb, var(--color-border-strong), var(--color-text) 40%);
  box-shadow: none;
}

select:disabled {
  background-color: var(--color-surface);
  color: var(--color-text-muted);
  border-color: var(--color-border-strong);
  opacity: 0.65;
  cursor: not-allowed;
}

select[multiple] {
  height: auto;
  min-height: 100px;
  padding: var(--space-1);
  padding-right: var(--space-1);
  background-image: none;
}

/* Native <option> dark/light adaptation */
select option {
  background-color: var(--color-surface-alt, #f5f5f5);
  color: var(--color-text, #191919);
}

select option:checked {
  background-color: var(--color-surface-hover, #e8e8e8);
  color: var(--color-text, #191919);
}

/* ══════════════════════════════════════════════════════════════════════════════
   5. CHECKBOX
   Uses the label::before visual pattern — the real input stays hidden (forms.css
   sets display:none), the visual control lives inside the label so checkbox +
   label text are always one element → perfect horizontal alignment guaranteed.
   ══════════════════════════════════════════════════════════════════════════════ */

/* Label is a flex row so the ::before icon and text are vertically centred */
input[type="checkbox"] + label {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--color-text);
  cursor: pointer;
  margin: 0 var(--space-4) var(--space-1) 0;
  text-transform: none;
  letter-spacing: normal;
  line-height: 1;
  vertical-align: middle;
}

/* Checkbox visual — unchecked: sem fundo, só border */
input[type="checkbox"] + label::before {
  content: "";
  display: inline-flex;
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  min-width: 15px;
  margin: 0 7px 0 0;
  /* O traço de um controlo é o de 3:1 da casa (2026-09-19); com a hairline, uma
     opção por marcar media 1,3:1 e quase não se via. */
  border: 1.5px solid var(--color-border-strong, #8a8a94);
  border-radius: 0;
  background: transparent;
  vertical-align: middle;
  cursor: pointer;
  transition: border-color 0.15s ease;
  font-size: 0;
}
/* Desactivado: distingue-se do activo pela tinta e pela caixa esbatida, sem opacidade. */
input[type="checkbox"]:disabled + label { color: var(--color-text-muted); cursor: not-allowed; }
input[type="checkbox"]:disabled + label::before { background: var(--color-surface-alt); border-color: var(--color-border); cursor: not-allowed; }

/* Checkbox visual — checked: sem fundo, só o ícone check (dark theme: check claro) */
input[type="checkbox"]:checked + label::before {
  content: "";
  font-size: 0;
  border-color: var(--color-text-muted, #8b90a0);
  border-width: 1.5px;
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23e8eaf0' stroke-linecap='round' stroke-linejoin='round' stroke-width='3.5' d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 9px 9px;
}

/* Checked — light theme: check escuro */
[data-theme="light"] input[type="checkbox"]:checked + label::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23191919' stroke-linecap='round' stroke-linejoin='round' stroke-width='3.5' d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* Validation error */
input[type="checkbox"].validation-error + label::before {
  border-color: var(--color-red, #ef4444) !important;
}

/* Checkbox no formulário: etiqueta + nota de apoio por baixo.
   Fonte única — bloqueio de transferência (ficha de conta), "morada de
   correspondência = morada fiscal" (ficha e modal Novo Lead), etc. */
.form-check-label {
  font-size: var(--text-sm);
  font-weight: 500;
}

.form-check-hint {
  display: block;
  margin-top: 2px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  line-height: 1.4;
}

/* Suppress ::before for functional (non-visual) checkboxes, e.g. accordion toggles */
input[type="checkbox"].accordion-trigger-input + label::before {
  content: none !important;
  display: none !important;
}

/* Native visible checkbox — usado em contextos onde o padrão label::before não é
   possível (ex: células de tabela, seletores inline). Adicionar class="native"
   ao <input>. Sobrepõe o display:none de forms.css por ter maior especificidade. */
input[type="checkbox"].native {
  /* A regra base esconde a caixa nativa com a técnica "visualmente escondido"
     (position/clip/opacity), não com `display:none` — mudou quando se devolveu
     o campo ao teclado. `.native` repunha só o `display`, e ficava com as
     outras cinco propriedades a escondê-la à mesma: as caixas de selecção das
     tabelas e o menu de colunas do TabularData estavam INVISÍVEIS em todo o
     CRM, mesmo respondendo ao clique. Repor todas é o que faz esta classe
     cumprir o que promete. */
  position: static;
  clip: auto;
  opacity: 1;
  overflow: visible;
  margin: 0;
  white-space: normal;

  display: inline-block;
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  min-width: 15px;
  -webkit-appearance: none;
  appearance: none;
  border: 1.5px solid var(--color-border, #d0d1d2);
  border-radius: 0;
  background: transparent;
  cursor: pointer;
  transition: border-color 0.15s ease;
  vertical-align: middle;
}

/* Native checkbox checked — ícone checkmark claro (dark theme padrão) */
input[type="checkbox"].native:checked {
  border-color: var(--color-text-muted, #8b90a0);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23e8eaf0' stroke-linecap='round' stroke-linejoin='round' stroke-width='3.5' d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 9px 9px;
}

/* Light theme: checkmark escuro */
[data-theme="light"] input[type="checkbox"].native:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23191919' stroke-linecap='round' stroke-linejoin='round' stroke-width='3.5' d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* Foco por teclado na caixa nativa: o mesmo anel que a caixa desenhada pelo
   rótulo recebe. Sem isto, quem chegava por Tab às caixas das tabelas e da
   matriz de acessos não via onde estava. */
input[type="checkbox"].native:focus-visible {
  border-color: var(--color-text-muted, #8b90a0);
  box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color);
}

/* ══════════════════════════════════════════════════════════════════════════════
   6. RADIO BUTTON
   Same label::before approach as checkbox above.
   ══════════════════════════════════════════════════════════════════════════════ */

/* Label is a flex row so the ::before icon and text are vertically centred */
input[type="radio"] + label {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--color-text);
  cursor: pointer;
  margin: 0 var(--space-4) var(--space-1) 0;
  text-transform: none;
  letter-spacing: normal;
  line-height: 1;
  vertical-align: middle;
}

/* Radio visual — unchecked */
input[type="radio"] + label::before {
  content: "";
  display: inline-flex;
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  min-width: 15px;
  margin: 0 7px 0 0;
  border: 1.5px solid var(--color-border, #d0d1d2);
  border-radius: 50%;
  background: var(--color-surface-alt, #f5f5f5);
  vertical-align: middle;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
  font-size: 0;
}

/* Radio visual — checked: no background fill, inner dot in neutral mid-grey */
input[type="radio"]:checked + label::before {
  content: "";
  font-size: 0;
  border-color: var(--color-text-muted, #8b90a0);
  border-width: 1.5px;
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Ccircle cx='4' cy='4' r='2.5' fill='%238b90a0'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 7px 7px;
}

/* Validation error */
input[type="radio"].validation-error + label::before {
  border-color: var(--color-red, #ef4444) !important;
}

/* ══════════════════════════════════════════════════════════════════════════════
   7. FIELDSET
   ══════════════════════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════════════════════
   PADRÃO DE PÁGINA — painel, barra de acções e separadores
   Nasceu no /profile e é o padrão do CMS. UMA classe por peça, para as páginas
   irem convergindo:

     .page-panel        no .default-full-panel        (a superfície da página)
     .page-action-bar   barra do título + acções      (rola com o conteúdo)
     .page-sticky-tabs  no contentor .full-width-tabs (nav colada ao topo)

   A barra de acções NÃO é sticky de propósito: rola com o conteúdo e sai de
   cena ao descer, voltando a entrar assim que se sobe — é o movimento do header
   do site público. Quem fica colada é a nav dos separadores, para nunca se
   perder em que separador se está.

   Barra e nav SANGRAM (.full-bleed): o fundo e o filete vão de ponta a ponta,
   mas o que lá está dentro recolhe ao bloco de conteúdo e fica alinhado com o
   resto da página. É por isso que o padding lateral vem da .full-bleed e aqui
   só se define o vertical.
   ══════════════════════════════════════════════════════════════════════════════ */
/* `.app` no seletor por causa de `.app .default-full-panel{background:transparent}`
   em styles.css: sem ele é um empate de especificidade decidido pela ordem dos
   ficheiros, e o painel saía sem superfície. */
.app .page-panel.default-full-panel {
  /* O respiro lateral e o fim da página são tokens que cada peça aplica a si
     mesma, e NÃO padding do próprio painel: o breakout da .full-bleed conta
     com ancestrais sem padding horizontal (base.css) — seria o painel a comer
     a sangria.
     Os valores mudaram-se para o `tokens.css` a 2026-09-19, com o `--page-edge`
     ao lado: declarados aqui como literais, não acompanhavam degrau nenhum e o
     telemóvel ficava com a goteira do computador. */
  /* A página é a MESA, não o cartão: fica no fundo da escada de superfícies e
     o que assenta nela — fieldsets, tabelas, painéis — é que leva
     `--color-surface`. É assim que uma página com várias secções se lê de
     longe, sem depender de uma borda ténue para as separar. */
  background-color: var(--color-bg);
  /* A SUPERFÍCIE vai de ponta a ponta; é o conteúdo que recolhe ao bloco (o
     `padding-inline` das peças, mais abaixo). Ao contrário: se só as barras
     sangrassem e o corpo ficasse no bloco, via-se um degrau de cor nas margens
     — a barra clara de ponta a ponta e o corpo a acabar antes. */
  margin-inline: calc(-1 * var(--content-gutter, 0px));
  /* `.default-full-panel{width:100%}` fixa a largura no bloco de conteúdo: com
     as margens negativas isso DESLOCAVA o painel em vez de o esticar (a mesma
     armadilha da nav dos separadores). */
  width: auto;
  /* `.content .default-full-panel{max-width:100%}` (base.css) resolve contra a
     largura de conteúdo do .content, que já desconta a goteira — o painel
     ficava preso ao bloco por mais margem negativa que levasse. */
  max-width: none;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  min-width: 0;
  /* O .default-full-panel base tem overflow:hidden, que cria um contexto de
     recorte entre os elementos sticky e o scroller (.content) e impede o
     position:sticky de funcionar. */
  overflow: visible;
}

/* A secção interior do painel tem de ESTICAR até ao fundo dele: é isso que dá
   à `.form-nav` o espaço para o `margin-top:auto` a empurrar para baixo. Sem
   esta regra, o painel tem a altura do conteúdo e a barra fica logo a seguir à
   tabela, a meio do ecrã.

   O `min-height:0` é o que autoriza um item de uma cadeia flex a encolher
   abaixo do seu conteúdo — sem ele, uma tabela alta empurra o painel para fora
   e a barra sai do ecrã com ela.

   Isto vivia numa lista de seletores COM ID em styles.css
   (`#models-list-panel`, `#versions-list-panel`, `#pricing-grid-panel`,
   `#equipment_list_panel`, …): cada página que entrasse no padrão tinha de ser
   acrescentada à lista à mão, e a que se esquecesse ficava com a barra a meio.
   Não leva `width:100%` nem `max-width:100%` — os da lista antiga prendiam a
   secção ao bloco de conteúdo, contra a sangria do painel. */
.app .page-panel > .panel-child-element {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  overflow: visible;
}

/* A barra esconde-se ao descer e volta a aparecer assim que se sobe — o mesmo
   movimento do header do site público (Shared/Components/MainMenu). É sticky
   para poder fazer isso a meio da página; o deslizar é `transform`, que não
   força re-layout.

   A nav dos separadores cola-se por baixo dela: o `--page-bar-h` (publicado
   pela layout.js com a altura real da barra) é o `top` da nav, e passa a 0
   quando a barra está escondida — a nav sobe para o lugar dela. */
/* MOBILE-FIRST, e a partir daqui a barra é medida em altura e não só em
   largura. Num telemóvel de 844px esta barra chegou a ocupar 274px no
   /assistance/support (32% do ecrã) e sobravam 170px para a lista de pedidos:
   o caminho quebrava em duas linhas, o título noutras duas e os quatro factos
   empilhavam um por linha, ao lado de dois botões que os apertavam.

   O que o telemóvel leva: o título e as acções em cima, e os factos numa fila
   que ROLA em vez de quebrar. O caminho sai, que a navegação está na barra
   lateral e no separador aberto. O respiro vertical é o `--space-2`, e sobe
   para a medida de sempre a partir do tablet, no bloco `min-width: 641px` ao
   lado. */
.page-action-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--space-2);
  row-gap: 6px;
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 97;
  background-color: var(--color-bg);
  transition: transform var(--dur-lg) var(--ease-out);
}

/* O caminho repete o que o menu já diz, e custava duas linhas das oito. */
.page-action-bar .cms-breadcrumb { display: none; }

@media (min-width: 641px) {
  .page-action-bar {
    flex-wrap: nowrap;
    padding-block: 16px 14px;
    row-gap: 0;
  }
  .page-action-bar .cms-breadcrumb { display: flex; }
}

.app .content.is-bar-hidden .page-action-bar {
  transform: translateY(-100%);
}

/* Uma barra pendurada directamente no .content (a de identidade da ficha de
   conta) não tem painel que sangre por ela — sangra sozinha. Dentro de um
   .page-panel isto não é preciso: o painel já esticou. */
.app .content > .page-action-bar {
  margin-inline: calc(-1 * var(--content-gutter, 0px));
  width: auto;
  max-width: none;
}

/* A altura que a nav usa: a real da barra enquanto ela está à vista, zero
   quando se esconde (a nav sobe para o lugar dela). A layout.js publica
   `--page-bar-real-h`; a derivação é aqui, para o inline não atropelar. */
/* Vive no .content e não no painel porque há barras FORA do painel — a de
   identidade da ficha de conta, por exemplo. */
.app .content {
  --page-bar-h: var(--page-bar-real-h, 0px);
}

.app .content.is-bar-hidden {
  --page-bar-h: 0px;
}

@media (prefers-reduced-motion: reduce) {
  .page-action-bar { transition: none; }
}

/* Tudo o que é CONTEÚDO recolhe ao bloco e soma o respiro: goteira para alinhar
   com o resto da página, mais `--page-edge` para não encostar à borda. É por isso
   que estas peças não levam `.full-bleed`: quem sangra é o painel.

   É o `--page-edge` e não o `--page-pad` porque estas cinco peças alinham pela
   borda EXTERIOR do cartão da página (o bloco «O CARTÃO DA PÁGINA», mais
   abaixo, é quem define essa borda com a mesma conta). No computador os dois
   valem o mesmo e nada disto se nota; no telemóvel o cartão aproxima-se da
   borda e o título tem de o acompanhar, senão fica desencontrado dele. */
/* Barra de acção do fundo — o mesmo desenho de Marcas/Modelos/Versões, agora
   como parte do padrão: `margin-top:auto` empurra-a para baixo quando o
   conteúdo é curto, `sticky` mantém-na à vista ao rolar.

   REGRA (2026-08-27): a `.form-nav` está SEMPRE colada ao fundo, em qualquer
   página. Quando não está, a causa é quase sempre a mesma e não é esta regra:
   `position:sticky` prende-se ao scroller MAIS PRÓXIMO, e se o painel for mais
   alto do que esse scroller, quem rola passa a ser um ancestral — a barra sai
   do ecrã com o resto, porque em relação ao SEU scroller nunca chega a ter para
   onde colar. A correcção é amarrar o painel à altura do scroller (as listas
   rolam por dentro), não mexer aqui. Exemplo trabalhado: o separador de correio
   da ficha, em `modules/leads/assets/css/lead-mail.css`. */
.app .page-panel .form-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-top: auto;
  padding-block: var(--space-3);
  position: sticky;
  bottom: 0;
  z-index: 95;
  background-color: var(--color-bg);
  border-top: 1px solid var(--color-border);
}

/* A barra do título é a única destas peças que fica FORA do cartão, sobre a
   mesa: alinha pela borda exterior dele, e por isso lê o `--page-edge`. */
.page-action-bar {
  padding-inline: calc(var(--content-gutter, 0px) + var(--page-edge, 24px));
}

/* As outras quatro são o conteúdo, e recolhem ao respiro de dentro. O bloco do
   cartão, mais abaixo, volta a declará-las sem a goteira (lá o cartão já a
   levou na margem); estas valem para as páginas que ainda não têm cartão. */
.app .page-sticky-tabs > .full-width-tabs-nav,
.app .page-panel .form-nav,
.app .page-panel .page-sticky-tabs > .full-width-tab-panel > .wrap,
.app .page-panel > .panel-child-element > .page-content {
  padding-inline: calc(var(--content-gutter, 0px) + var(--page-pad, 24px));
}

/* O corpo de uma página SEM separadores. O `.wrap` é o corpo de um separador;
   quem não tem separadores precisava de escrever o mesmo padding à mão em cada
   módulo — e escrevia-o diferente em cada um. */
.app .page-panel > .panel-child-element > .page-content {
  padding-block: var(--space-3) var(--page-pad-end, 32px);
}

/* As acções globais da página, à direita da barra. A `.page-action-bar` é um
   flex SEM `justify-content`, pelo que sem este `margin-left:auto` o botão fica
   encostado ao título, a ler-se como parte dele.

   Vivia só em `insurances.css`, preso a `.insurances-page` — o markup era do
   padrão e a regra que o faz funcionar não era. Qualquer outra página que
   copiasse o markup herdava o botão colado ao título e ninguém percebia porquê.
   Sobe para aqui, ao pé da peça a que pertence. */
.page-action-bar .page-action-bar-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-2, 8px);
}

/* Um elemento escondido dentro do slot não pode continuar a ocupar o flex. */
.page-action-bar .page-action-bar-actions > [hidden] { display: none; }

/* O título da barra é menor que um .page-title solto: aqui ele encima uma
   página inteira já identificada pelo menu, e não precisa do tamanho de um
   cabeçalho de secção. */
.page-action-bar .page-title {
  margin: 0;
  font-size: var(--text-xl);
  display: flex;
  align-items: center;
  gap: 6px;
}

/* O PERÍODO DE UMA VISÃO GERAL (2026-09-26). As pílulas do período no topo da
   página, ao centro da barra do título, com o menu do resto ao lado. Escolher
   muda a página inteira, e não um bloco: era o que faltava nas Atividades,
   onde o período vivia dentro do bloco «No período» e só esse mudava. Quem
   enche a peça é o `Widgets/overview-period.js`, e o markup é este:

   <div class="page-action-bar-center cms-toolbar cms-period" id="…"
        role="group" aria-label="Período da página"></div>

   MOBILE-FIRST: o período tem a linha dele, por baixo do título e ao centro,
   até ao computador largo. As três peças lado a lado só cabem a partir dos
   1281px (com a barra lateral aberta sobra perto de 1000px de conteúdo, e o
   título, as pílulas e as acções pedem uns 900), e aí a barra passa a uma
   grelha de três colunas com as duas de fora iguais: é isso que põe o período
   no meio da página, e não no meio do que sobra entre o título e as acções. */
.page-action-bar > .page-action-bar-center {
  order: 3;
  flex: 1 1 100%;
  justify-content: center;
  flex-wrap: nowrap;
  column-gap: var(--space-2);
  /* A altura dos controlos, guardada antes de o widget encher a peça: a barra
     não dá um salto quando as pílulas chegam. */
  min-height: var(--toolbar-h, var(--height-input));
}

/* As pílulas cedem e rolam por dentro (a calha tem o degradê nas pontas); o
   botão do menu não encolhe, e é quadrado como os da barra de uma lista. */
.cms-period > .cms-pills { flex: 0 1 auto; min-width: 0; }
.cms-period > .cms-act-btn { flex: 0 0 auto; width: var(--toolbar-h); }

/* A partir do tablet a barra deixa de quebrar linha (bloco de cima), e com um
   período lá dentro isso espremia as três peças numa linha onde não cabem. */
@media (min-width: 641px) {
  .page-action-bar:has(> .page-action-bar-center) {
    flex-wrap: wrap;
    row-gap: var(--space-2);
  }
}

@media (min-width: 1281px) {
  .page-action-bar:has(> .page-action-bar-center) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    column-gap: var(--space-4);
  }
  .page-action-bar:has(> .page-action-bar-center) > * { grid-row: 1; }
  .page-action-bar:has(> .page-action-bar-center) > :first-child { grid-column: 1; }
  .page-action-bar > .page-action-bar-center { grid-column: 2; order: 0; }
  .page-action-bar:has(> .page-action-bar-center) > .page-action-bar-actions {
    grid-column: 3;
    justify-self: end;
  }
}

/* A cadeia que autoriza uma `.form-nav` DENTRO de um separador a colar-se ao
   fundo: painel → secção → separadores → separador activo, todos flex column a
   crescer, e `overflow:visible` em toda ela (com `overflow:hidden` a meio, o
   `position:sticky` deixa de colar).

   Vivia presa a `#paints_panel` no CSS das Pinturas. Qualquer outra página com
   separadores e barra de acção tinha de a copiar — e as duas que entraram no
   padrão a 2026-09-02 (Equipamentos e Acessórios) copiá-la-iam outra vez. */
.page-sticky-tabs.full-width-tabs {
  display: flex;
  flex-direction: column;
}

.app .page-panel > .panel-child-element > .page-sticky-tabs,
.app .page-panel .page-sticky-tabs > .full-width-tab-panel.is-active {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: visible;
}

/* O conteúdo de cada separador: respiro dos lados e um fim de página que não
   encosta — ao rodapé, ou à barra de acção quando o separador tem uma. */
.page-sticky-tabs > .full-width-tab-panel > .wrap,
.page-sticky-tabs > .full-width-tab-panel > .page-content {
  padding-block-end: var(--page-pad-end, 32px);
}

/* z-index abaixo do header da aplicação (--z-header) e acima do conteúdo. */
.app .page-sticky-tabs > .full-width-tabs-nav {
  position: sticky;
  /* Por baixo da barra de acções enquanto ela está à vista; no topo quando ela
     se esconde. A layout.js publica --page-bar-h com a altura real. */
  top: var(--page-bar-h, 0px);
  z-index: 96;
  transition: top var(--dur-lg) var(--ease-out);
  /* Acompanha a mesa: uma barra clara sobre fundo escuro leria como cartão, e
     isto é chrome — tem de desaparecer atrás do conteúdo, não competir com ele.
     Opaca à mesma, para tapar o que passa por baixo ao rolar. */
  background-color: var(--color-bg);
  /* `.app .full-width-tabs-nav` impõe `width:100%`. Com as margens negativas da
     .full-bleed isso DESLOCA a barra para a esquerda em vez de a esticar — a
     largura continuava presa ao bloco de conteúdo. */
  width: auto;
}

.default-fieldset {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-5);
  /* Cada fieldset é uma secção da página, não uma linha de formulário: o
     intervalo entre eles tem de se ler como uma pausa — e uma pausa maior do
     que o espaço entre duas linhas de campos lá dentro, senão a secção seguinte
     parece a continuação da anterior. */
  margin: 0 0 var(--space-7) 0;
  background: var(--color-surface);
}

/* A legenda vive DENTRO do fieldset, como primeira linha, e não a cavalgar a
   borda. O `float` é o que lhe retira o posicionamento especial que o browser
   dá ao <legend> — `display:block` sozinho não chega, o elemento continua
   ancorado ao traço de cima. Com o float ele sai do fluxo, daí o `clear` na
   regra seguinte.

   Passou a ser um título a sério (14px, bold, cor do texto) em vez da etiqueta
   miúda em maiúsculas que era: uma secção anuncia-se, não se sussurra. */
.default-fieldset legend {
  float: left;
  width: 100%;
  padding: 0;
  /* `--space-6` (32px) por baixo: a legenda é um título de secção e precisa de
     se descolar do que titula. Com os 16px anteriores lia-se como a etiqueta do
     primeiro campo. */
  margin: 0 0 var(--space-6);
  /* `--text-3xl` — o maior da escala. A legenda TITULA a secção e num
     formulário longo é por ela que se procura: tem de se ver de relance, sem
     ler. Subiu de 16 → 18 → 22px em pedidos sucessivos do Jorge. */
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  color: var(--color-text);
  letter-spacing: -0.01em;
  line-height: 1.3;
  text-transform: none;
}

/* O que vem a seguir à legenda tem de limpar o float, senão sobe para o lado
   dela. TODOS os irmãos, e não só o imediato (`+`): quando o primeiro irmão
   está `hidden` (a pergunta «já é cliente?» do atendimento, nos tipos que não
   a fazem), o `display: none` não limpa nada, e o primeiro bloco visível, se
   tiver largura mínima zero (uma grelha `minmax(0, 1fr)`), cabe ao lado do
   float e cola-se à direita com 0 px (2026-09-16). Um bloco que já está por
   baixo não é movido pelo `clear`, por isso a regra não mexe nos outros. */
.default-fieldset legend ~ * {
  clear: both;
}

/* ══════════════════════════════════════════════════════════════════════════════
   ESTADO VAZIO — REGRA DO PROJETO (Jorge, 2026-08-27)

   Um fieldset sem registos diz-o AO MEIO, e o botão que resolve a falta fica
   duas linhas abaixo do texto — não encostado ao legend. Vale em qualquer sítio
   do CMS.

   Porquê: o texto encostado à esquerda lê-se como mais um campo do formulário, e
   o botão no legend obriga a subir os olhos para agir sobre o que se acabou de
   ler. Ao meio, o vazio é evidente e a acção está onde o olho já está.

   COMO SE ESCREVE UM ESTADO VAZIO NOVO: `<p class="cms-empty-state">`. A regra
   apanha também os que já existiam — o padrão do CMS é uma classe terminada em
   `-empty` (`afs-empty`, `sst-empty`, `lead-deals-empty`, …), e há mais de cem
   delas espalhadas; enumerá-las era garantir que a próxima ficava de fora.

   O botão é MOVIDO por JS (`initFieldsetEmptyStates`, em layout.js): não há
   maneira de o CSS mudar um elemento de sítio, e duplicá-lo no markup deixaria
   dois botões a fazer o mesmo em cada um dos sítios onde isto se aplica.

   `is-empty` fica DE FORA: é um modificador de estado — uma data por preencher,
   um lugar vago no jogo, o sítio do avatar numa nota de continuação — e não um
   estado vazio de secção. São coisas diferentes com nomes parecidos, e a
   primeira versão desta regra esticou o avatar de uma nota para 120px de altura
   por causa disso.
   ══════════════════════════════════════════════════════════════════════════════ */

.default-fieldset :is(.cms-empty-state, [class$="-empty"], [class*="-empty "]):not([class*="is-empty"]) {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* "Duas linhas abaixo": o botão descola-se do texto o suficiente para não
       se ler como parte da frase. */
    gap: var(--space-5);
    min-height: 120px;
    margin: 0;
    padding: var(--space-6) var(--space-4);
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    line-height: 1.5;
    text-align: center;
}

/* O botão que o JS traz para cá não herda o `align-items:center` se for um
   filho de bloco — e não pode esticar-se pela caixa toda. */
.default-fieldset :is(.cms-empty-state, [class$="-empty"], [class*="-empty "]):not([class*="is-empty"]) > .bt-filled,
.default-fieldset :is(.cms-empty-state, [class$="-empty"], [class*="-empty "]):not([class*="is-empty"]) > .bt-outline,
.default-fieldset :is(.cms-empty-state, [class$="-empty"], [class*="-empty "]):not([class*="is-empty"]) > .bt-tonal {
    flex: 0 0 auto;
    align-self: center;
}

/* Uma célula de tabela vazia NÃO é um estado vazio de secção: continua a ser uma
   célula, e centrá-la a 120px de altura desfigurava a linha. */
.default-fieldset td[class$="-empty"],
.default-fieldset th[class$="-empty"],
.default-fieldset td[class*="-empty "],
.default-fieldset th[class*="-empty "] {
    display: table-cell;
    min-height: 0;
    padding: revert;
}

/* Enquanto o botão está no legend (fieldset COM registos), mantém-se onde
   estava. É só no vazio que ele desce — ver a nota do JS. */
.default-fieldset legend .legend-btn-row {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
}

/* ══════════════════════════════════════════════════════════════════════════════
   8. DROPDOWN COMPONENT — Dropdown.js + CustomDropdown.js
   These scoped rules supplement customdropdown.css (which is the canonical file).
   Scoped to .app to avoid affecting the public site where customdropdown.css
   provides its own pre-vars context.
   ══════════════════════════════════════════════════════════════════════════════ */

/* ── Trigger wrapper ── */
.app .dropdown {
  position: relative;
  background: var(--color-surface-alt);
  color: var(--color-text);
  border-color: var(--color-border-strong);
}

.app .dropdown.disabled {
  background: var(--color-surface);
  border-color: var(--color-border-strong);
  opacity: 0.65;
}

/* ── Header (trigger face) ── */
.app .dropdown .dropdown-header,
.app .dropdown .dropdown-all {
  background-color: transparent;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%238b90a0' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  min-height: var(--height-input);
  padding: var(--space-2) 32px var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--color-text);
}

.app .dropdown .dropdown-header.dropdown-header-active {
  border-color: var(--color-text-muted);
  border-bottom-color: transparent;
  box-shadow: none;
}

/* Anel de foco visível para navegação por teclado (ver customdropdown.css).
   Espelhado aqui com .app para vencer a especificidade do tema. */
.app .dropdown .dropdown-header:focus-visible {
  outline: 2px solid var(--color-blue);
  outline-offset: -2px;
}

/* Sem nada escolhido, o texto de «todos» («Todos os Utilizadores») É o valor
   em uso, e lê-se como o valor de um campo. O cinzento chega: com uma
   opacidade por cima dele media 3,32:1 no tema escuro e 2,74:1 no claro, e sem
   ela mede 5,21:1 e 4,8:1. */
.app .dropdown .dropdown-header .dropdown-header-placeholder {
  color: var(--color-text-muted);
}

/* ── Selected count badge ── */
.app .dropdown .dropdown-header .dropdown-header-option {
  background: var(--color-text-muted);
  color: var(--color-surface);
}

/* ── Selected item pills ── */
.app .dropdown .dropdown-header .dropdown-header-option-text {
  background: var(--color-surface-hover);
  border-color: var(--color-border);
  color: var(--color-text);
}

.app .dropdown .dropdown-header .remove-option-button .ph-icon,
.app .dropdown .dropdown-header .remove-option-button svg {
  color: var(--color-text-muted);
}

.app .dropdown .dropdown-header .dropdown-header-max {
  color: var(--color-text-muted);
}

/* ── DateRangePicker panel ligado ao trigger quando aberto ── */
.app .dropdown .custom-daterangepicker.show {
  margin-top: 0 !important;
  border: 1px solid var(--color-text-muted);
  border-top: none;
}

/* ── Layout estrutural do dropdown de filtro de utilizador nos widgets ── */
.app .widget-user-filter {
  position: relative;
  flex-shrink: 0;
  width: auto;
  min-width: 140px;
  cursor: pointer;
  border-radius: 0; /* Sobrepõe customdropdown.css: inputs em widget-header sem cantos arredondados */
}

.app .widget-user-filter .dropdown-options {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 100%;
  z-index: 300;
  max-height: 260px;
  overflow-y: auto;
  display: none;
  border: 1px solid var(--color-text-muted);
  border-top: none;
  box-shadow: var(--shadow);
}

.app .widget-user-filter .dropdown-options.is-open {
  display: block;
}

/* ── Dropdown panel ── */
.app .dropdown .dropdown-options {
  background: var(--color-surface);
  border-color: var(--color-border);
  box-shadow: var(--shadow);
  margin-top: 0;
}

/* ── Options ── */
.app .dropdown .dropdown-option,
.app .dropdown .dropdown-all {
  color: var(--color-text);
  border-color: var(--color-border);
}

.app .dropdown .dropdown-options .dropdown-option:hover,
.app .dropdown .dropdown-options .dropdown-option:active,
.app .dropdown .dropdown-options .dropdown-option.dropdown-option-active,
.app .dropdown .dropdown-options .dropdown-all:hover,
.app .dropdown .dropdown-options .dropdown-all:active {
  background: var(--color-surface-hover);
}

.app .dropdown .dropdown-options .dropdown-option.dropdown-selected .dropdown-option-radio,
.app .dropdown .dropdown-options .dropdown-all.dropdown-selected .dropdown-option-radio {
  background: transparent;
  border-color: var(--color-text-muted);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3E%3Ccircle cx='4' cy='4' r='2.5' fill='%238b90a0'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}

.app .dropdown .dropdown-options .dropdown-option.dropdown-selected,
.app .dropdown .dropdown-options .dropdown-all.dropdown-selected {
  background: var(--color-primary-subtle);
}

.app .dropdown .dropdown-options .dropdown-option.dropdown-selected .dropdown-option-text,
.app .dropdown .dropdown-options .dropdown-all.dropdown-selected .dropdown-option-text {
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
}

.app .dropdown .dropdown-options .dropdown-option.disabled {
  color: var(--color-text-muted);
  background: transparent;
}

.app .dropdown .dropdown-options .dropdown-all {
  border-bottom-color: var(--color-border);
}

/* ── Search inside panel ── */
.app .dropdown .dropdown-options .dropdown-search {
  background: var(--color-surface-alt);
  border-color: var(--color-border);
  color: var(--color-text);
  font-size: var(--text-sm);
}

.app .dropdown .dropdown-options .dropdown-search:focus {
  border-color: var(--color-text-muted);
  box-shadow: none;
  outline: none;
}

.app .dropdown .dropdown-options .dropdown-search::placeholder {
  color: var(--color-text-muted);
  opacity: 0.6;
}

/* ── Secção de INATIVOS dentro do painel ──
   Quem já não trabalha cá continua a aparecer, porque continua a ter registos
   com o nome dele, mas fica no fim e escondido atrás de um cabeçalho que se
   abre. Nasceu nos widgets do overview e vive aqui desde que a retrospetiva
   passou a precisar do mesmo desenho: é do componente, não daquela página. */
.app .dropdown .dropdown-inactive-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  margin-top: 4px;
  border-top: 1px solid var(--color-border);
  font-size: 10px;
  font-weight: var(--weight-semibold, 600);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  cursor: pointer;
  user-select: none;
}

.app .dropdown .dropdown-inactive-toggle:hover { color: var(--color-text); }

.app .dropdown .dropdown-inactive-toggle .ph-icon {
  font-size: 13px;
  transition: transform var(--dur-fast, 0.15s) ease;
}

.app .dropdown .dropdown-inactive-toggle.is-expanded .ph-icon { transform: rotate(180deg); }

.app .dropdown .dropdown-option.is-inactive-owner .dropdown-option-text {
  font-style: italic;
  color: var(--color-text-muted);
}

/* O `display: flex` da opção ganha ao atributo [hidden] que vem do browser. */
.app .dropdown .dropdown-option[hidden] { display: none; }

/* ── Radio indicator inside option ── */
.app .dropdown .dropdown-option-radio {
  border-color: var(--color-border);
  background: var(--color-surface-alt);
  border-radius: 50%;
}

/* ══════════════════════════════════════════════════════════════════════════════
   9. SELECTBOXES COMPONENT — .option-card (SelectBoxes.js)
   Higher specificity overrides hardcoded values in selectboxes.css (lazy-loaded).
   ══════════════════════════════════════════════════════════════════════════════ */

.app .custom-options-container .option-card {
  color: var(--color-text-muted);
  background-color: var(--color-surface-alt);
  border-color: var(--color-border);
  border-radius: var(--radius);
  transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.app .custom-options-container .option-card:hover {
  background-color: var(--color-surface-hover);
  color: var(--color-text);
  border-color: color-mix(in srgb, var(--color-border), var(--color-text) 15%);
}

.app .custom-options-container .option-card.disabled {
  color: var(--color-text-muted);
  background-color: var(--color-surface);
  border-color: var(--color-border);
  opacity: 0.65;
}

.app .custom-options-container .option-card.disabled:hover {
  background-color: var(--color-surface);
  cursor: not-allowed;
}

.app .custom-options-container .option-card[data-input-selected="true"] {
  color: var(--color-text);
  background-color: var(--option-card-accent-soft, var(--color-surface-hover));
  border-color: var(--option-card-accent, var(--color-text-muted));
}

.app .custom-options-container .option-card > .ph-icon {
  color: var(--option-card-accent, var(--color-text-muted));
}

.app .custom-options-container.validation-error .option-card,
.app .option-card.validation-error {
  border-color: var(--color-red) !important;
}

/* Event-category variant inside .app */
.app .custom-options-container.event-category-selector .event-category-opt-title {
  color: var(--color-text);
}

.app .custom-options-container.event-category-selector .event-category-opt-description {
  color: var(--color-text-muted);
}

/* O CARTÃO DE UM ESTADO: o ponto da cor, o nome, o que o estado quer dizer e,
   quando não se pode escolher, porquê (`buildOptionCardHtml`). Nasceu nos
   estados e nas prioridades de um pedido de suporte, presa aos ids dos modais
   de lá; subiu para aqui a 2026-09-25, quando a ficha da oportunidade passou a
   mudar de estado pela mesma janela. Duas cópias do mesmo cartão eram duas
   maneiras de o desenhar à espera de divergirem. */
.app .custom-options-container .option-card:has(.task-option-row) > span {
  align-items: flex-start;
  justify-content: flex-start;
  text-align: left;
}
.app .custom-options-container .task-option-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
}
.app .custom-options-container .task-option-dot {
  width: 12px;
  height: 12px;
  min-width: 12px;
  border-radius: 50%;
  display: inline-block;
  flex-shrink: 0;
}
.app .custom-options-container .task-option-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  min-width: 0;
  width: 100%;
}
.app .custom-options-container .task-option-title {
  display: block;
  width: 100%;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  line-height: 1.2;
  color: inherit;
}
.app .custom-options-container .task-option-description {
  display: block;
  width: 100%;
  font-size: var(--text-xs);
  font-weight: var(--weight-regular);
  line-height: 1.3;
  color: var(--color-text-muted);
}
/* A razão de a escolha estar travada, por baixo da descrição e separada dela:
   em itálico e na cor apagada, para se ler como um aparte. */
.app .custom-options-container .task-option-note {
  display: block;
  width: 100%;
  margin-top: 4px;
  font-size: var(--text-xs);
  font-style: italic;
  line-height: 1.3;
  color: var(--color-text-muted);
}

/* ══════════════════════════════════════════════════════════════════════════════
   10. VALIDATION STATES
   ══════════════════════════════════════════════════════════════════════════════ */

.validation-success {
  border-color: var(--color-green) !important;
}

.validation-error {
  border-color: var(--color-red) !important;
}

/* O campo em erro deixa de ficar só com a borda vermelha: ganha também um véu do
   mesmo tom, como já acontecia nos formulários do site público (request-forms.css,
   account-auth.css, store.css). A borda sozinha é 1px — num formulário longo, quem
   percorre a página à procura do que falhou precisa de ver a mancha, não o contorno.

   `color-mix` sobre `--color-surface-alt` (o fundo base destes campos: BASE FIELDS
   nesta folha, `.dropdown` em customdropdown.css) e não o `--color-red-subtle`
   translúcido: assim o campo continua opaco sobre qualquer superfície e o resultado
   acompanha o tema — o cartão claro do login e o backoffice escuro — sem precisar
   de duas declarações.

   `!important` pela mesma razão do `border-color` acima: a lista-por-exclusão dos
   BASE FIELDS pesa 0,8,1 e `.app input` (styles.css) 0,1,1; ambas venceriam uma
   classe simples.

   Só campos de entrada. Os contentores que também recebem .validation-error
   (.custom-options-container, .datetimepicker-time-slots, .form-card-container)
   já marcam o erro nas opções lá dentro — um véu por cima do contentor inteiro
   tirava-lhes leitura em vez de a dar. */
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]).validation-error,
textarea.validation-error,
select.validation-error,
.dropdown.validation-error,
.phone-input-widget.validation-error {
  background-color: color-mix(in srgb, var(--color-red) 10%, var(--color-surface-alt)) !important;
}

/* O asterisco de campo obrigatório. Vivia só no `profile.css` (2026-09-22), e
   qualquer outra página que o escrevesse ficava com um asterisco da cor do
   rótulo, que não marca nada. A marca de obrigatório é do sistema de
   formulários, não de um módulo. */
.req {
  color: var(--color-danger);
}

.error-message {
  display: block;
  margin: 4px 0 0 0;
  padding: 0;
  color: var(--color-red-text);
  font-size: var(--text-2xs);
  font-weight: var(--weight-regular);
  line-height: 1.35;
  white-space: normal;
}

/* Error-message visibility toggle (replaces visibility:hidden pattern in forms.css) */
.form-row .error-message {
  visibility: visible;
}

.custom-options-container + .error-messages .error-message,
.error-messages--custom-input .error-message {
  color: var(--color-red-text) !important;
  font-size: var(--text-2xs) !important;
  font-weight: var(--weight-regular) !important;
}

/* ═══════════════ [origem: components/datepicker.css] ═══════════════ */

/**
 * datepicker.css
 * Estilos canónicos do componente DateRangePicker.
 * Fonte única da verdade — não duplicar regras fora deste ficheiro.
 * Carregado globalmente via head.php (pilot + public).
 */

/* ── Painel dropdown principal ── */
.custom-daterangepicker {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 999;
  min-width: 100%;
  max-width: 100%;
  margin-top: 1px;
  padding: 0;
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.25s ease, transform 0.25s ease;
  background: var(--color-surface, #fff);
  border-radius: 0 0 var(--radius-sm, 6px) var(--radius-sm, 6px);
  box-shadow: var(--shadow);
  overflow-y: auto;
  overflow-x: hidden;
  box-sizing: border-box;
}

.custom-daterangepicker.drop-up {
  top: auto;
  bottom: 100%;
  margin-top: 0;
  margin-bottom: 1px;
  border-radius: var(--radius-sm, 6px) var(--radius-sm, 6px) 0 0;
  transform: translateY(10px);
}

.custom-daterangepicker.show {
  opacity: 1;
  transform: translateY(0);
}

.custom-daterangepicker.drp-inline {
  position: static;
  display: block;
  min-width: 100%;
  max-width: 100%;
  opacity: 1;
  transform: none;
  margin: 0;
  border-radius: var(--radius, 12px);
  border: 1px solid var(--color-border, rgba(18, 25, 38, 0.10));
  box-shadow: none;
  overflow: visible;
  z-index: auto;
  padding: 15px;
}

.dropdown-header.is-hidden {
  display: none !important;
}

/* ── Layout interno ── */
.drp-left.is-empty {
  display: none;
}

.drp-content {
  display: flex;
  flex-direction: column;
  width: 100%;
  gap: 12px;
}

.drp-left {
  display: flex;
  flex-direction: column;
  padding: 8px 4px;
  gap: 1px;
  min-width: 160px;
}

.drp-right {
  display: none;
  flex-direction: column;
  padding: 8px 12px;
  flex: 1 0 0%;
}

.drp-right label {
  margin-bottom: 5px;
  font-size: 13px;
}

/* ── Toggles (horas / dias soltos) ── */
.drp-toggles {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}

.drp-toggles label {
  margin-bottom: 0;
  cursor: pointer;
  user-select: none;
}

.drp-toggles input+label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.drp-toggles input+label::before {
  content: '';
  width: 18px;
  height: 18px;
  border: 1px solid var(--color-border, #ccc);
  border-radius: 3px;
  background: var(--color-surface, #fff);
  display: inline-block;
  box-sizing: border-box;
  text-align: center;
  vertical-align: middle;
}

.drp-toggles input:checked+label::before {
  content: '';
  background-color: var(--color-surface, #fff);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23e8eaf0' stroke-linecap='round' stroke-linejoin='round' stroke-width='3.5' d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 11px 11px;
  border-color: var(--color-border, #ccc);
}
[data-theme="light"] .drp-toggles input:checked+label::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23191919' stroke-linecap='round' stroke-linejoin='round' stroke-width='3.5' d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

/* ── Calendários ── */
.drp-calendars {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  gap: 0;
  margin: 0;
  padding: 0;
  width: 100%;
}

.calendar-box {
  flex: 1 0 0%;
  min-width: 0;
  border: none;
  background: transparent;
  padding: 0;
  font-size: 12px;
}

.calendar-box + .calendar-box {
  border-left: 1px solid var(--color-border, rgba(18, 25, 38, 0.10));
  padding-left: 12px;
  margin-left: 8px;
}

/* ── Cabeçalho do calendário (nav mês/ano) ── */
.calendar-header {
  display: block;
  margin-bottom: 8px;
}

.calendar-header .drp-nav {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  width: 100%;
  gap: 4px;
  overflow: hidden;
}

/* Selects de mês e ano — apenas layout; propriedades visuais em forms.css / inputs.css */
.calendar-header .drp-month-select {
  flex: 1 1 0;
  min-width: 0;
  width: 0;
}

.calendar-header .drp-year-select {
  flex: 0 0 64px;
  width: 64px;
}

/* Selects compactos específicos do DRP — sobrepõe inputs.css (especificidade necessária) */
.calendar-header .drp-nav select {
  height: auto;
  padding: 3px 20px 3px 4px;
  font-size: var(--text-xs, 11px);
  background-position: right 4px center;
}

.calendar-header .calendar-nav-button {
  display: inline-flex;
  flex: 0 0 28px;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--color-text-muted, rgba(18, 25, 38, 0.65));
  transition: color 0.18s ease;
}

.calendar-header .calendar-nav-button:hover {
  color: var(--color-text, #121926);
}

.calendar-header .calendar-nav-button.is-disabled,
.calendar-header .calendar-nav-button:disabled {
  color: var(--color-border, rgba(18, 25, 38, 0.10));
  cursor: not-allowed;
  pointer-events: none;
}

.calendar-header .calendar-nav-button .ph-icon {
  font-size: 22px;
}

.calendar-header .calendar-nav-button--prev:active {
  animation: drp-nudge-left 220ms ease;
}

.calendar-header .calendar-nav-button--next:active {
  animation: drp-nudge-right 220ms ease;
}

@keyframes drp-nudge-left {
  45% { transform: translateX(-4px); }
}

@keyframes drp-nudge-right {
  45% { transform: translateX(4px); }
}

/* ── Tabela de dias ── */
.calendar-table {
  border-collapse: collapse;
  width: 100%;
  text-align: center;
}

.calendar-table thead th {
  color: var(--color-text-muted, rgba(18, 25, 38, 0.65));
  font-weight: 500;
  padding-bottom: 6px;
  font-size: 11px;
}

.calendar-table tbody td {
  width: 36px;
  height: 36px;
  position: relative;
  cursor: pointer;
  border: none;
  padding: 0;
  background: transparent;
}

.calendar-table td .day-span {
  display: block;
  width: 32px;
  height: 32px;
  line-height: 32px;
  margin: 0 auto;
  border-radius: 50%;
  transition: background 0.14s ease, color 0.14s ease;
}

.calendar-table td:not(.invalid):not(.hover-start-date):not(.hover-end-date):hover .day-span {
  background: var(--color-surface-alt, #edf0f5);
}

.calendar-table td.other-month .day-span {
  color: var(--color-text-muted, rgba(18, 25, 38, 0.4));
}

.calendar-table td.today .day-span {
  background: var(--color-text, #121926) !important;
  color: var(--color-surface, #fff);
}

.calendar-table td.in-range {
  background: var(--color-blue-subtle, #cce5ff);
}

.calendar-table td.loose-selected .day-span {
  background: var(--color-blue, #3b82f6);
  color: var(--color-surface, #fff);
}

.calendar-table td.start-date {
  background: var(--color-blue-subtle, #cce5ff);
  border-top-left-radius: 999px;
  border-bottom-left-radius: 999px;
}

.calendar-table td.end-date {
  background: var(--color-blue-subtle, #cce5ff);
  border-top-right-radius: 999px;
  border-bottom-right-radius: 999px;
}

.calendar-table td.start-date .day-span,
.calendar-table td.end-date .day-span {
  background: var(--color-blue, #3b82f6);
  color: var(--color-surface, #fff);
}

.calendar-table td.invalid {
  cursor: not-allowed;
  opacity: 0.35;
}

.calendar-table td.hover-range {
  background: var(--color-blue-subtle, #cce5ff);
}

.calendar-table td.hover-start-date {
  background: var(--color-blue-subtle, #cce5ff);
  border-top-left-radius: 999px;
  border-bottom-left-radius: 999px;
}

.calendar-table td.hover-start-date .day-span {
  background: var(--color-blue, #3b82f6);
  color: var(--color-surface, #fff);
  opacity: 0.75;
}

.calendar-table td.hover-end-date {
  background: var(--color-blue-subtle, #cce5ff);
  border-top-right-radius: 999px;
  border-bottom-right-radius: 999px;
}

.calendar-table td.hover-end-date .day-span {
  background: var(--color-blue, #3b82f6);
  color: var(--color-surface, #fff);
  opacity: 0.75;
}

/* ── Botões de intervalo pré-definido ── */
.drp-preset-btn {
  display: block;
  width: 100%;
  padding: 6px 12px;
  text-align: left;
  font-size: 12px;
  font-weight: 500;
  color: var(--color-text, #121926);
  background: transparent;
  border: none;
  border-radius: var(--radius-sm, 6px);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.14s;
  font-family: inherit;
}

.drp-preset-btn:hover {
  background: var(--color-surface-alt, #edf0f5);
}

.drp-preset-btn.is-active {
  background: var(--color-surface-hover, #e2e5ee);
  font-weight: 600;
}

/* Botão "Limpar" — separador visual acima */
.drp-preset-btn.drp-clear-btn {
  margin-top: 4px;
  padding-top: 8px;
  border-top: 1px solid var(--color-border, rgba(18, 25, 38, 0.10));
  color: var(--color-text-muted, rgba(18, 25, 38, 0.65));
  font-size: 11px;
}

.drp-preset-btn.drp-clear-btn:hover {
  color: var(--color-text, #121926);
  background: var(--color-surface-alt, #edf0f5);
}

/* ── Time picker row ── */
.drp-time-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0 10px;
  border-bottom: 1px solid var(--color-border, rgba(18, 25, 38, 0.10));
  margin-bottom: 8px;
  flex-wrap: nowrap;
}

.drp-time-label {
  font-size: 11px;
  color: var(--color-text-muted, rgba(18, 25, 38, 0.65));
  font-weight: 500;
  white-space: nowrap;
}

/* Inputs de hora HH:MM — layout fixo; propriedades visuais em forms.css / inputs.css */
.drp-time-row .drp-time-input {
  flex: 0 0 56px;
  min-width: 56px;
  max-width: 56px;
}

/* Inputs de hora — sobrepõe input[type="text"] { width: 100% } de inputs.css */
.drp-time-row input[type="text"] {
  flex: 0 0 56px;
  min-width: 56px;
  max-width: 56px;
  width: 56px;
  text-align: center;
  display: inline-block;
}

.drp-time-sep {
  color: var(--color-text-muted, rgba(18, 25, 38, 0.65));
  font-size: 12px;
}

.drp-time-error {
  width: 100%;
  font-size: 11px;
  color: var(--color-red, #ef4444);
  display: none;
}

/* ── Rodapé (Cancelar / Confirmar) ── */
.drp-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid var(--color-border, rgba(18, 25, 38, 0.10));
  margin-top: 8px;
  position: sticky;
  bottom: 0;
  background: var(--color-surface, #fff);
  z-index: 1;
}

/* Mobile-first: empilhados na base, lado a lado a partir de 601px. */
@media (min-width: 601px) {
  .drp-calendars {
    flex-direction: row;
  }

  .drp-content {
    flex-direction: row;
  }
}

/* ── Contexto de widget-header: DRP embutido na linha de cabeçalho de um widget ─────────────── */
/* Aplicar class="drp-in-widget" ao container do DRP no template do widget.                    */
/* Fonte única da verdade para todos os widgets que usam DateRangePicker no widget-header.      */

.drp-in-widget .dropdown-header .dropdown-header-option-text {
  padding: 0;
  border: none;
  background: transparent;
}

/* Correção de layout e aparência para o container .dropdown no contexto de widget */
/* Sobrepõe customdropdown.css: width:100% e border-radius que não se aplicam aqui */
.drp-in-widget.dropdown {
  width: auto;
  min-width: 0;
  border-radius: 0;
}

.drp-in-widget .custom-daterangepicker {
  padding: 4px;
}

.drp-in-widget .custom-daterangepicker.is-custom-range {
  right: auto;
  min-width: 680px;
  max-width: none;
}

.drp-in-widget .custom-daterangepicker.is-custom-range .drp-left {
  flex: 0 0 auto;
}

.drp-in-widget .drp-left {
  flex: 1;
  gap: 0;
  padding: 0;
}

.drp-in-widget .drp-preset-btn {
  display: flex;
  align-items: center;
  height: 40px;
  border-radius: calc(var(--radius-sm, 6px) - 2px);
  padding: 4px 12px;
  font-weight: var(--weight-regular, 400);
}

.drp-in-widget .drp-preset-btn:hover {
  background: var(--color-surface-hover, #e2e5ee);
}

.drp-in-widget .drp-preset-btn.is-active {
  background: var(--color-surface-hover, #e2e5ee);
  font-weight: var(--weight-semibold, 600);
}

/* Botão Limpar: oculto por defeito, visível apenas no intervalo personalizado */
.drp-in-widget .drp-preset-btn.drp-clear-btn {
  display: none;
  height: auto;
  min-height: 36px;
  padding: 8px 12px;
  margin-top: 2px;
  border-top: 1px solid var(--color-border, rgba(18, 25, 38, 0.10));
  color: var(--color-text-muted, rgba(18, 25, 38, 0.65));
  font-weight: var(--weight-regular, 400);
  font-size: inherit;
}

.drp-in-widget .custom-daterangepicker.is-custom-range .drp-preset-btn.drp-clear-btn {
  display: flex;
}

/* ═══════════════ [origem: buttons.css] ═══════════════ */

/**
 * buttons.css
 * ═══════════════════════════════════════════════════════════════════════
 * SINGLE SOURCE OF TRUTH — todos os botões do projeto usam este ficheiro.
 *
 * ── Estrutura (sempre obrigatória) ─────────────────────────────────────
 *   .bt-filled    fundo sólido, texto branco / invertido
 *   .bt-outline   fundo transparente, borda + texto colorido
 *   .bt-tonal     fundo subtil tintado, texto colorido (baixo destaque)
 *
 * ── Tamanho (opcional, padrão = .normal) ───────────────────────────────
 *   .big          15px, padding 12px 30px
 *   .normal       11px, padding 10px 15px
 *   .small        10px, padding 5px 11px
 *
 * ── Cor semântica ──────────────────────────────────────────────────────
 *   .primary    Ação principal — inverte tokens do tema (adapta-se a dark/light)
 *   .surface    Ação secundária — blende com o painel (fundo surface)
 *   .info       Ação informacional (azul)
 *   .success    Confirmar / gravar (verde)
 *   .danger     Ação destrutiva (vermelho)
 *   .accent     Ação especial / destaque (roxo)
 *   .neutral    Baixa ênfase (cinzento mutado)
 *   .warning    Cautela (amarelo / âmbar)
 *
 * ── Aliases de retrocompatibilidade ───────────────────────────────────
 *   .dark   → .primary  |  .light  → .surface
 *   .blue   → .info     |  .green  → .success
 *   .red    → .danger   |  .purple → .accent
 *   .grey   → .neutral
 *
 * ── Modificadores utilitários ─────────────────────────────────────────
 *   .disabled / [disabled]   cinzento, não interativo
 *   .hide                    display: none
 *   .bt-animation            pulso animado
 *
 * ── Botão de ícone ─────────────────────────────────────────────────────
 *   .app-header-icon-btn + .app-header-icon-btn-sm/lg
 *   .app-header-icon-btn-danger (hover destrutivo)
 *
 * REGRA DO PROJETO: Nenhuma nova variante, cor ou modificador pode ser
 * adicionado sem primeiro ser definido aqui E aprovado explicitamente.
 * ═══════════════════════════════════════════════════════════════════════
 */

/* ══════════════════════════════════════════════════════════════════════
   BASE — reset partilhado por todas as variantes estruturais
   ══════════════════════════════════════════════════════════════════════ */

.bt-filled,
.bt-outline,
.bt-tonal {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
  width: fit-content;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  outline: none;
  background: transparent;
  border: 1px solid transparent;
  user-select: none;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  /* O MESMO raio dos campos, pela escala dos controlos (tokens.css): um botão
     e um input lado a lado na mesma linha têm de se ler como o mesmo sistema.
     Eram cápsula (`--radius-full`); ver `--radius-control-*` para o porquê. */
  border-radius: var(--radius-control-md);
  line-height: normal;
}

/* :focus (rato incluído) fica sem anel — é o que evita o contorno a aparecer a
   cada clique. */
.bt-filled:focus,
.bt-outline:focus,
.bt-tonal:focus { outline: none; }

/* :focus-visible é outra coisa: só dispara em navegação por teclado. Sem isto,
   quem percorre a aplicação por Tab não fazia ideia de em que botão estava —
   em nenhum botão, em nenhuma página. Nada muda para quem usa rato. */
.bt-filled:focus-visible,
.bt-outline:focus-visible,
.bt-tonal:focus-visible {
    outline: none;
    box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color);
}

.bt-filled.hide,
.bt-outline.hide,
.bt-tonal.hide { display: none; }

/* ── Nested <a> inside button containers ───────────────────────────
   Evita sublinhado e cor de link num <a> dentro de um container
   com classe bt-* (ex: <button class="bt-filled"><a href>label</a></button>) */
.bt-filled a,
.bt-outline a,
.bt-tonal a {
  text-decoration: none;
  color: inherit;
}

/* ── Disabled ──────────────────────────────────────────────────── */

/* O `aria-disabled` lê-se igual ao `disabled` (2026-09-24). Um botão que só se
   trava com o atributo nativo não recebe o toque, e num telemóvel não há
   `title`: a razão de estar travado não chegava a quem usa o CMS no telefone.
   Com o `aria-disabled` o botão continua a parecer travado e a ser anunciado
   como indisponível, mas aceita o toque, e quem o liga mostra a razão. */
.bt-filled.disabled, .bt-filled[disabled], .bt-filled[aria-disabled="true"],
.bt-outline.disabled, .bt-outline[disabled], .bt-outline[aria-disabled="true"],
.bt-tonal.disabled, .bt-tonal[disabled], .bt-tonal[aria-disabled="true"] {
  color: var(--color-text-muted) !important;
  background: var(--color-surface-alt) !important;
  border-color: var(--color-border) !important;
  cursor: not-allowed !important;
  opacity: 0.65;
}

/* ── Tamanhos ──────────────────────────────────────────────────── */

.bt-filled.big, .bt-outline.big, .bt-tonal.big {
  padding: 12px 30px; /* intentional: --text-xl scale, 30px horizontal */
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  gap: 10px; /* intentional: between --space-2 and --space-3 */
  border-radius: var(--radius-control-lg);   /* a escala segue o tamanho */
}
.bt-filled.big .ph-icon, .bt-filled.big i,
.bt-outline.big .ph-icon, .bt-outline.big i,
.bt-tonal.big .ph-icon, .bt-tonal.big i { font-size: var(--text-3xl); }

.bt-filled.normal, .bt-outline.normal, .bt-tonal.normal {
  padding: 10px 15px; /* intentional: 10px vertical, 15px horizontal */
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  gap: var(--space-2);
}
.bt-filled.normal .ph-icon, .bt-filled.normal i,
.bt-outline.normal .ph-icon, .bt-outline.normal i,
.bt-tonal.normal .ph-icon, .bt-tonal.normal i { font-size: 15px; /* intentional: between --text-lg and --text-xl */ }

.bt-filled.small, .bt-outline.small, .bt-tonal.small {
  padding: 5px 11px; /* intentional: compact button sizing */
  font-size: var(--text-2xs);
  font-weight: var(--weight-medium);
  gap: 6px; /* intentional: between --space-1 and --space-2 */
  border-radius: var(--radius-control-sm);   /* a escala segue o tamanho */
}
.bt-filled.small .ph-icon, .bt-filled.small i,
.bt-outline.small .ph-icon, .bt-outline.small i,
.bt-tonal.small .ph-icon, .bt-tonal.small i { font-size: var(--text-sm); }

/* Ícones e spans herdam currentColor — sem overrides por variante */
/* visibility: visible garante que o .form-row i { visibility: hidden } não oculte ícones dentro de botões */
.bt-filled .ph-icon, .bt-filled i,
.bt-outline .ph-icon, .bt-outline i,
.bt-tonal .ph-icon, .bt-tonal i {
  margin: 0;
  visibility: visible;
  position: static;
  /* Um ícone de FONTE desenha o glifo a partir da baseline, e a caixa dele não
     coincide com o desenho: o WhatsApp saía do botão pela esquerda e por baixo.
     `inline-flex` + centro + `line-height:1` obrigam o glifo a assentar no meio
     da sua própria caixa, e o `align-items:center` do botão faz o resto. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  flex-shrink: 0;
}

/* O SVG inline segue a mesma regra — o tamanho vem do `font-size` do ícone ao
   lado, para os dois ícones de uma barra de acções ficarem iguais. */
.bt-filled > svg, .bt-outline > svg, .bt-tonal > svg {
  width: 1em;
  height: 1em;
  flex-shrink: 0;
}

/* O mesmo para QUALQUER elemento interativo dentro de uma linha de formulário.
   `.form-row i { visibility: hidden }` foi escrito para reservar o lugar de um
   ícone de validação que hoje já não existe — o Validation.js desenha
   `<div class="error-messages"><p>`, sem <i> nenhum (auditado: zero ocorrências
   em 7 páginas). A regra ficou a apanhar ícones legítimos: um botão de olho num
   campo de palavra-passe aparecia vazio, a funcionar às escuras.
   Repõe-se aqui em vez de se apagar a regra de origem, que é aditivo e não muda
   nada do que hoje está escondido de propósito.

   `i.ph` na lista (2026-08-21): a versão anterior cobria os ícones dentro de um
   botão, de um link ou de uma etiqueta, e deixava de fora os que estão em texto
   corrido — o aviso de Caps Lock do login (`<p class="login-hint"><i class="ph
   ph-arrow-fat-up">`) aparecia sem seta nenhuma. Um `<i class="ph">` é sempre um
   ícone escrito de propósito; o `<i>` genérico que a regra de origem queria apanhar
   nunca leva essa classe. Auditado em todo o pilot: os únicos outros ícones dentro
   de uma .form-row são três `ph-trash`, todos dentro de <button>, já cobertos. */
.form-row button i, .form-row button .ph-icon,
.form-row a i, .form-row a .ph-icon,
.form-row label i, .form-row label .ph-icon,
.form-row i.ph { visibility: visible; position: static; transform: none; }

.bt-filled span:not([hidden]), .bt-outline span:not([hidden]), .bt-tonal span:not([hidden]) {
  display: flex;
  align-items: center;
  margin: 0;
}

/* ══════════════════════════════════════════════════════════════════════
   FILLED — fundo sólido, texto invertido
   ══════════════════════════════════════════════════════════════════════ */

/* primary | dark (alias) — acção principal do CMS: AZUL do sistema.
   REGRA: nenhum botão preto/branco fixo — o CMS tem tema dark/light, por isso a
   cor vem de --color-blue (theme-aware) e não de literais como #191919/#ffffff.
   Quem quiser mesmo a inversão por tema usa .contrast.
   Em código novo usar SEMPRE .primary — .dark é apenas alias de retrocompatibilidade. */
/* PLANO, sem gradiente, sombra ou elevação (decisão do Jorge, 2026-09-08). O
   azul é o que PINTA (--color-primary, 5,2:1 com o branco); o hover desce um
   degrau em vez de clarear, para o texto branco nunca perder contraste. */
.bt-filled.primary,
.bt-filled.dark {
  color: #fff;
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.bt-filled.primary:hover:not(.active, .disabled, [disabled]),
.bt-filled.dark:hover:not(.active, .disabled, [disabled]) {
  color: #fff;
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}
.bt-filled.primary.active,
.bt-filled.dark.active {
  color: #fff;
  background: color-mix(in srgb, var(--color-primary-hover), #000 12%);
  border-color: color-mix(in srgb, var(--color-primary-hover), #000 12%);
}

/* contrast — inversão real de tema: escuro em light, claro em dark */
.bt-filled.contrast {
  background: var(--color-text);
  color: var(--color-surface);
  border-color: var(--color-text);
}
.bt-filled.contrast:hover:not(.disabled, [disabled]) {
  background: var(--color-text-muted);
  border-color: var(--color-text-muted);
  color: var(--color-bg);
}

/* surface | light (alias) — blende com o painel, ação secundária */
.bt-filled.surface,
.bt-filled.light {
  color: var(--color-text);
  background: var(--color-surface);
  border-color: var(--color-border);
}
.bt-filled.surface:hover:not(.disabled, [disabled]),
.bt-filled.light:hover:not(.disabled, [disabled]) {
  background: var(--color-surface-hover);
  border-color: var(--color-surface-hover);
}

/* info | blue (alias) */
.bt-filled.info,
.bt-filled.blue {
  color: #fff;
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.bt-filled.info:hover:not(.disabled, [disabled]),
.bt-filled.blue:hover:not(.disabled, [disabled]) {
  background: var(--color-primary-hover);
  border-color: var(--color-primary-hover);
}

/* ── AS TRÊS VARIANTES CHEIAS DE COR (revistas a 2026-09-19) ──────────────
   As três pintavam com a cor BASE e escreviam a branco, e as três falhavam o
   4,5:1 da WCAG 1.4.3 com o texto a 11px: o verde a 3,73:1, o vermelho a
   3,64:1 e o roxo a 3,96:1. Passam a pintar com o degrau "-strong", que é
   para isto que existe (o comentário do `tokens.css` já o dizia: é deles que
   se precisa quando a cor tem de aguentar tinta clara por cima).

   E o hover ESCURECE em vez de clarear, que era o outro meio do problema:
   `color-mix(cor, #fff 14%)` subia o fundo e o texto branco perdia o pouco
   contraste que tinha, mesmo no instante em que a pessoa está a apontar para
   o botão. É a mesma correcção que o `.primary` levou a 2026-09-08. */

/* success | green (alias) */
.bt-filled.success,
.bt-filled.green {
  color: #fff;
  background: var(--color-success-strong);
  border-color: var(--color-success-strong);
}
.bt-filled.success:hover:not(.disabled, [disabled]),
.bt-filled.green:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-success-strong), #000 12%);
  border-color: color-mix(in srgb, var(--color-success-strong), #000 12%);
}

/* danger | red (alias) */
.bt-filled.danger,
.bt-filled.red {
  color: #fff;
  background: var(--color-danger-strong);
  border-color: var(--color-danger-strong);
}
.bt-filled.danger:hover:not(.disabled, [disabled]),
.bt-filled.red:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-danger-strong), #000 12%);
  border-color: color-mix(in srgb, var(--color-danger-strong), #000 12%);
}

/* accent | purple (alias) */
.bt-filled.accent,
.bt-filled.purple {
  color: #fff;
  background: var(--color-accent-strong);
  border-color: var(--color-accent-strong);
}
.bt-filled.accent:hover:not(.disabled, [disabled]),
.bt-filled.purple:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-accent-strong), #000 12%);
  border-color: color-mix(in srgb, var(--color-accent-strong), #000 12%);
}

/* neutral | grey (alias) */
.bt-filled.neutral,
.bt-filled.grey {
  color: var(--color-bg);
  background: var(--color-text-muted);
  border-color: var(--color-text-muted);
}
.bt-filled.neutral:hover:not(.disabled, [disabled]),
.bt-filled.grey:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-text-muted), #fff 16%);
  border-color: color-mix(in srgb, var(--color-text-muted), #fff 16%);
}

/* warning
   Escrevia com `--color-bg`, a tinta da mesa: no tema escuro calhava ser
   escura e no claro era quase branca sobre amarelo, a 1,98:1. Nenhuma tinta
   clara se lê sobre amarelo sem o escurecer até deixar de ser amarelo, por
   isso a tinta é fixa nos dois temas (`--color-warning-ink`), como no toast.
   Aqui o hover pode clarear: com tinta escura por cima, clarear o amarelo SOBE
   o contraste em vez de o baixar. */
.bt-filled.warning {
  color: var(--color-warning-ink);
  background: var(--color-yellow);
  border-color: var(--color-yellow);
}
.bt-filled.warning:hover:not(.disabled, [disabled]) {
  color: var(--color-warning-ink);
  background: color-mix(in srgb, var(--color-yellow), #fff 14%);
  border-color: color-mix(in srgb, var(--color-yellow), #fff 14%);
}

/* ══════════════════════════════════════════════════════════════════════
   OUTLINE — fundo transparente, borda + texto colorido
   Sem modificador de cor → .neutral (padrão)
   ══════════════════════════════════════════════════════════════════════ */

/* neutral (padrão) — .bt-outline sem modificador de cor */
.bt-outline {
  color: var(--color-text-secondary);
  border-color: var(--color-border-strong);
}
.bt-outline:hover:not(.primary, .dark, .surface, .light, .info, .blue,
  .success, .green, .danger, .red, .accent, .purple, .warning,
  .disabled, [disabled]) {
  background: var(--color-surface-alt);
  color: var(--color-text);
  border-color: var(--color-surface-hover);
}

/* primary | dark (alias) */
/* primary escreve com o azul de escrever e contorna com o de pintar: é o mesmo
   acento do cheio, só que aberto. O `dark` mantém o contorno na cor do texto. */
.bt-outline.primary {
  color: var(--color-primary-text);
  background: transparent;
  border-color: var(--color-primary);
}
.bt-outline.primary:hover:not(.disabled, [disabled]) {
  background: var(--color-primary-subtle);
  border-color: var(--color-primary-hover);
}
.bt-outline.dark {
  color: var(--color-text);
  background: transparent;
  border-color: var(--color-text);
}
.bt-outline.dark:hover:not(.disabled, [disabled]) {
  background: var(--color-surface-alt);
  border-color: var(--color-text-muted);
}

/* surface | light (alias)
   O contorno passou da hairline ao traço dos controlos: a #343432 sobre o
   painel dava 1,29:1, e um botão que se vê pelo texto e mais nada não se lê
   como botão. É o mesmo token que o `.bt-outline` sem variante já usava. */
.bt-outline.surface,
.bt-outline.light {
  color: var(--color-text-muted);
  background: transparent;
  border-color: var(--color-border-strong);
}
.bt-outline.surface:hover:not(.disabled, [disabled]),
.bt-outline.light:hover:not(.disabled, [disabled]) {
  background: var(--color-surface-alt);
  color: var(--color-text);
  border-color: var(--color-surface-hover);
}

/* info | blue (alias) */
.bt-outline.info, .bt-outline.blue {
  color: var(--color-primary-text);
  border-color: var(--color-primary);
}
.bt-outline.info:hover:not(.disabled, [disabled]),
.bt-outline.blue:hover:not(.disabled, [disabled]) { background: var(--color-primary-subtle); }

/* ── AS VARIANTES DE CONTORNO COLORIDAS (revistas a 2026-09-19) ──────────
   Escreviam com a cor BASE, que é a que PINTA, e não com a que ESCREVE. O
   `.primary` já tinha sido separado assim a 2026-09-08 e estas quatro ficaram
   para trás: o roxo dava 4,07:1 sobre o painel escuro e 3,96:1 sobre o branco,
   o verde 4,32:1, o vermelho 4,42:1, e o amarelo sobre branco 2,13:1, que é
   uma etiqueta amarela em cima de papel. O contorno fica na cor base, que é o
   que dá a identidade ao botão; quem muda é a tinta. */

/* success | green (alias) */
.bt-outline.success, .bt-outline.green {
  color: var(--color-green-text);
  border-color: var(--color-green);
}
.bt-outline.success:hover:not(.disabled, [disabled]),
.bt-outline.green:hover:not(.disabled, [disabled]) { background: var(--color-green-subtle); }

/* danger | red (alias) */
.bt-outline.danger, .bt-outline.red {
  color: var(--color-red-text);
  border-color: var(--color-red);
}
.bt-outline.danger:hover:not(.disabled, [disabled]),
.bt-outline.red:hover:not(.disabled, [disabled]) { background: var(--color-red-subtle); }

/* accent | purple (alias) */
.bt-outline.accent, .bt-outline.purple {
  color: var(--color-purple-text);
  border-color: var(--color-purple);
}
.bt-outline.accent:hover:not(.disabled, [disabled]),
.bt-outline.purple:hover:not(.disabled, [disabled]) { background: var(--color-purple-subtle); }

/* neutral | grey (alias) */
.bt-outline.neutral, .bt-outline.grey {
  color: var(--color-text-secondary);
  border-color: var(--color-border-strong);
}
.bt-outline.neutral:hover:not(.disabled, [disabled]),
.bt-outline.grey:hover:not(.disabled, [disabled]) {
  background: var(--color-surface-alt);
  color: var(--color-text);
  border-color: var(--color-surface-hover);
}

/* warning
   A ÚNICA das cinco em que a borda também muda, e por um motivo que só o
   amarelo tem: #f59f0a sobre papel branco dá 2,13:1, ou seja o contorno do
   botão desaparece no tema claro. Nas outras quatro a cor base aguenta os 3:1
   e por isso a borda fica lá. Aqui borda e tinta são a mesma cor, como no
   `.dark`, e o amarelo continua a ser quem se lê no tema escuro. */
.bt-outline.warning {
  color: var(--color-yellow-text);
  border-color: var(--color-yellow-text);
}
.bt-outline.warning:hover:not(.disabled, [disabled]) { background: var(--color-yellow-subtle); }

/* ══════════════════════════════════════════════════════════════════════
   TONAL — fundo subtil tintado, texto colorido (baixo destaque)
   Para ações em contexto: toolbars, sidebars, listas inline.
   ══════════════════════════════════════════════════════════════════════ */

.bt-tonal { border-color: transparent; }

.bt-tonal.primary {
  color: var(--color-primary-text);
  background: var(--color-primary-subtle);
}
.bt-tonal.primary:hover:not(.disabled, [disabled]) { background: color-mix(in srgb, var(--color-primary) 22%, transparent); }

.bt-tonal.info {
  color: var(--color-primary-text);
  background: var(--color-primary-subtle);
}
.bt-tonal.info:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-blue), transparent 74%);
}

/* ── AS VARIANTES TONAIS COLORIDAS (revistas a 2026-09-19) ───────────────
   O mesmo de sempre, terceira vez: escreviam com a cor que PINTA em cima do
   fundo tingido dela própria. O `.primary` e o `.info` já usavam o tom de
   escrever; estas quatro davam 3,4:1 a 3,9:1, e o amarelo 1,95:1 sobre o seu
   próprio fundo. Agora escrevem com `--color-X-text`, que é o tom medido por
   tema e o mesmo que o badge da mesma cor usa. */
.bt-tonal.success {
  color: var(--color-green-text);
  background: var(--color-green-subtle);
}
.bt-tonal.success:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-green), transparent 74%);
}

.bt-tonal.danger {
  color: var(--color-red-text);
  background: var(--color-red-subtle);
}
.bt-tonal.danger:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-red), transparent 74%);
}

.bt-tonal.warning {
  color: var(--color-yellow-text);
  background: var(--color-yellow-subtle);
}
.bt-tonal.warning:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-yellow), transparent 74%);
}

.bt-tonal.accent {
  color: var(--color-purple-text);
  background: var(--color-purple-subtle);
}
.bt-tonal.accent:hover:not(.disabled, [disabled]) {
  background: color-mix(in srgb, var(--color-purple), transparent 74%);
}

.bt-tonal.neutral {
  color: var(--color-text-muted);
  background: var(--color-surface-alt);
}
.bt-tonal.neutral:hover:not(.disabled, [disabled]) {
  background: var(--color-surface-hover);
  color: var(--color-text);
}

/* ══════════════════════════════════════════════════════════════════════
   PULSE ANIMATION — modificador .bt-animation
   ══════════════════════════════════════════════════════════════════════ */

.bt-animation {
  position: relative;
  animation: bt-pulse 2s infinite;
}
.bt-animation::after {
  content: '';
  width: 100%;
  height: 100%;
  position: absolute;
  animation: bt-pulse 2s infinite;
  animation-delay: 1.5s;
}
.bt-animation.disabled,
.bt-animation[disabled],
.bt-animation.disabled::after,
.bt-animation[disabled]::after { animation: none; }

@keyframes bt-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-text), transparent 40%); }
  100% { box-shadow: 0 0 0 15px transparent; }
}

/* ══════════════════════════════════════════════════════════════════════
   ICON-ONLY BUTTON — .app-header-icon-btn
   Para botões quadrados sem texto (header, toolbars)
   ══════════════════════════════════════════════════════════════════════ */

.app-header-icon-btn {
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.app-header-icon-btn:hover {
  border-color: var(--color-surface-hover);
  color: var(--color-text);
  background: var(--color-surface-alt);
}

.app-header-icon-btn-sm { width: var(--height-btn-sm); height: var(--height-btn-sm); font-size: var(--text-md); }

/* ═══════════════ [origem: tables.css] ═══════════════ */

/**
 * tables.css
 * Modern base for backoffice data tables.
 * Uses design tokens from vars/themes.css — light/dark automatic.
 *
 * Covers both the legacy .table-wrap pattern (used across modules)
 * and base <table> element resets applied inside .ptbl-root.
 */

/* ══════════════════════════════════════════════════════════════
   LEGACY WRAPPER  (.table-wrap)
   Kept for backward compatibility with module views.
══════════════════════════════════════════════════════════════ */

.table-wrap {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm, 8px);
    overflow: hidden;
}

.view-btn {
    width: 30px; /* intentional compact toggle size */
    height: 30px;
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-text-muted);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--text-md);
    transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.view-btn.active, .view-btn:hover {
    border-color: var(--color-blue, #3b82f6);
    color: var(--color-blue, #3b82f6);
    background: color-mix(in srgb, var(--color-blue, #3b82f6) 8%, transparent);
}

/* ══════════════════════════════════════════════════════════════
   BOTÃO DE AÇÃO DE LINHA (coluna "Ações" do TabularData)
   FONTE ÚNICA do quadrado 30×30 com ícone Phosphor usado nas listas
   (Marcas, Modelos, Versões, Pinturas, Contas…). Antes existia uma cópia
   idêntica em cada CSS de módulo — os seletores legados abaixo são aliases
   da MESMA regra para não partir o markup já existente.
══════════════════════════════════════════════════════════════ */
.cms-act-btn,
.brand-edit-btn,
.models-act-btn,
.versions-act-btn,
.paint-edit-btn,
.paint-versions-btn,
.vm-act-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm, 6px);
    background: transparent;
    color: var(--color-text-muted);
    text-decoration: none;
    cursor: pointer;
    transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.cms-act-btn:hover,
.brand-edit-btn:hover,
.models-act-btn:hover,
.versions-act-btn:hover,
.paint-edit-btn:hover,
.paint-versions-btn:hover,
.vm-act-btn:hover {
    background: var(--color-surface-alt);
    color: var(--color-text);
    border-color: var(--color-text-muted);
}
/* O `aria-disabled` lê-se igual ao `disabled`, como nos botões `bt-*`: um
   botão de linha travado com ele continua a receber o toque, que é o que diz
   porque está travado. Chegou aqui com o sino «Lembrar o cliente» dos
   financiamentos (2026-09-25). */
.cms-act-btn:disabled,
.cms-act-btn[aria-disabled="true"],
.vm-act-btn:disabled { opacity: .5; cursor: not-allowed; }

/* A mesma acção, em círculo e um bocado maior: serve fora de uma linha de
   tabela, onde o botão fica sozinho ao lado de outra coisa e o quadrado de 30px
   se lê como um resto. Nasceu na carteira por comercial da Visão Geral de
   Oportunidades (2026-09-22). */
.cms-act-btn.is-round {
    width: 36px;
    height: 36px;
    border-radius: 50%;
}

/* Texto de uma célula que abre o registo (ex.: o nome na lista de Oportunidades).
   Herda a cor da célula — o sublinhado no hover é que denuncia o link. Mesmo
   destino do botão-ícone da coluna "Ações". */
.cms-cell-link {
    color: inherit;
    text-decoration: none;
    cursor: pointer;
}

.cms-cell-link:hover,
.cms-cell-link:focus-visible {
    color: var(--color-text);
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* UM LINK NÃO É AZUL (regra absoluta, dita pelo Jorge a 2026-09-24, para todo
   o projeto). Um link lê-se pelo SUBLINHADO e pela tinta, nunca por uma cor
   que o distinga do texto à volta: o azul é a cor que PINTA (o botão
   principal, o filete do item activo), e num parágrafo ou num facto ficava a
   ser a coisa mais pesada do ecrã.

   Esta é a classe de um link de TEXTO, fora de uma lista: em repouso o traço é
   a hairline, e com o rato ou com o teclado ganha a tinta do texto. Dentro de
   uma célula de lista continua a valer o `.cms-cell-link`, que não traz traço
   nenhum porque a linha inteira já diz que abre. */
.cms-link {
    color: inherit;
    text-decoration: underline;
    text-decoration-color: var(--color-border-strong);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
    cursor: pointer;
}

.cms-link:hover,
.cms-link:focus-visible {
    color: var(--color-text);
    text-decoration-color: currentColor;
}

/* A MESMA LIGAÇÃO NUM BOTÃO. Abrir um modal é uma acção, e por isso é um
   `<button>`; quando se lê no meio de um texto, veste-se como uma ligação.
   Sem isto ficava com o fundo, a borda e o respiro do botão do navegador. */
button.cms-link {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    text-align: inherit;
}

/* ── TEXTO CORRIDO  (.cms-prose) ───────────────────────────────────────────
   Para o que se lê de seguida dentro de um modal ou de um painel: condições,
   uma explicação, uma ajuda. Títulos de secção pequenos e a meio negrito,
   parágrafos e listas no ritmo do CMS, e o respiro que o `.modal-content`
   deixa ao conteúdo. Nasceu com as condições das isenções por incapacidade
   (Jorge, 2026-09-25), que é o primeiro texto longo do CMS num modal. */
.cms-prose {
    padding: var(--space-4);
    font-size: var(--text-sm);
    line-height: var(--line-normal);
    color: var(--color-text);
}
.cms-prose > * { margin: 0; }
.cms-prose > * + * { margin-top: var(--space-2); }
.cms-prose > h3 {
    margin-top: var(--space-5);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
}
.cms-prose > h3:first-child { margin-top: 0; }
/* O marcador volta: o reset da casa tira-o às listas, e num texto corrido é
   ele que diz onde começa cada ponto. */
.cms-prose ul, .cms-prose ol { padding-left: 1.25em; }
.cms-prose ul { list-style: disc; }
.cms-prose ol { list-style: decimal; }
.cms-prose li + li { margin-top: 4px; }
.cms-prose b { font-weight: var(--weight-semibold); }
.cms-prose .ph-icon { font-size: 12px; color: var(--color-text-muted); vertical-align: -1px; }
.cms-prose .cms-prose-note { font-size: var(--text-xs); color: var(--color-text-muted); }

/* `.form-row i { visibility: hidden; position: absolute }` (ícone de validação do
   campo) apanhava TODOS os <i> descendentes — incluindo os das tabelas e botões
   de ação dentro de fieldsets, que ficavam invisíveis. Mesmo remédio já usado
   nos botões `.bt-*`: dentro de uma tabela ou de um botão-ícone, o ícone é
   conteúdo, não decoração de validação. */
.form-row .ptbl-table i,
.form-row .cms-act-btn i,
.form-row .cms-act-btn .ph-icon {
    visibility: visible;
    position: static;
    transform: none;
}
.cms-act-btn .ph-icon,
.brand-edit-btn .ph-icon,
.models-act-btn .ph-icon,
.versions-act-btn .ph-icon,
.paint-edit-btn .ph-icon,
.paint-versions-btn .ph-icon,
.vm-act-btn .ph-icon { font-size: 16px; }

/* Dois botões de acção seguidos NUNCA se encostam.

   São quadrados de 30×30 com contorno: colados, lêem-se como UM controlo
   partido ao meio, e o alvo de toque de cada um acaba exactamente onde começa
   o do vizinho — clica-se no errado. Medido no Suporte, que concatenava
   `${pegar}${abrir}` direito à célula: 0px entre os dois.

   FILHOS DIRECTOS DE UMA CÉLULA (`td >`). É essa a fronteira que separa esta
   regra do resto: quem envolve os botões num contentor — `.cms-actions-cell`
   aqui abaixo, `.vm-actions` no editor de modelos — já os espaça com `gap`, e
   uma margem por cima somava (12px em vez de 6px, medido). O contentor espaça
   os seus filhos; esta regra apanha os botões soltos, que não têm quem o faça.

   `td` e não `.ptbl-table td`: nem todas as listas do CMS são TabularData — o
   explorador de ficheiros do Uploader tem tabela própria, e tinha a sua cópia
   desta regra em `folders.css` (com 4px, que era outro valor). Uma célula de
   tabela é uma célula de tabela venha de onde vier. */
td > :is(.cms-act-btn, .brand-edit-btn, .models-act-btn, .versions-act-btn,
        .paint-edit-btn, .paint-versions-btn, .vm-act-btn)
+ :is(.cms-act-btn, .brand-edit-btn, .models-act-btn, .versions-act-btn,
        .paint-edit-btn, .paint-versions-btn, .vm-act-btn) {
    margin-left: 6px;
}

/* Eliminar destaca-se a vermelho so no hover: a accao e destrutiva, e a cor
   antes de tempo pinta a linha inteira de alarme. */
.cms-act-btn.is-danger:hover:not(:disabled) {
    color: var(--color-red);
    border-color: color-mix(in srgb, var(--color-red), transparent 60%);
    background: color-mix(in srgb, var(--color-red), transparent 92%);
}

/* Uma accao que nao existe para aquela linha: cadeado inerte no lugar do botao,
   com a borda a tracejado. Nao e um botao desactivado, porque a accao nao esta
   la para ser recusada. Nasceu nas Listas Gerais, onde uma entrada em uso nao
   se pode eliminar. */
.cms-act-btn.is-locked { cursor: default; opacity: .45; border-style: dashed; }
.cms-act-btn.is-locked:hover { background: transparent; color: var(--color-text-muted); }

/* Célula da coluna "Ações": um ou mais botões, centrados e sem quebra. */
.cms-actions-cell {
    display: inline-flex;
    gap: 6px;
    white-space: nowrap;
    justify-content: center;
}

/* NOTA (2026-09-02): não há aqui regra nenhuma a esconder coluna de acções.
   O TabularData desenhava uma coluna de acções EMBUTIDA em todas as tabelas e
   nunca a preenchia; eram 12 cópias de `display:none`, presas ao id de cada
   tabela, em 8 folhas de módulo, a escondê-la — e a lista que se esquecesse
   mostrava uma coluna vazia. A coluna deixou de ser desenhada (ver a nota em
   `_renderTable`, no TabularData.js) e as 12 cópias foram apagadas. Quem quer
   acções na linha declara a sua coluna: `key: '_actions'` + `.cms-actions-cell`
   com `.cms-act-btn`. */

/* ── Horizontal scroll container ── */
.table-wrap .table-scroll {
    overflow-x: auto;
    /* Scrollbar: scrollbar-width/color e ::webkit-scrollbar definidos em scrollbar.css (variante sb-h) */
    cursor: grab;
}
.table-wrap .table-scroll:active { cursor: grabbing; }

/* ── Table element ── */
.table-wrap table {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0;
    font-size: var(--text-sm);
    color: var(--color-text);
}

.table-wrap thead th {
    padding: 9px 14px; /* intentional compact header padding */
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--color-text-muted);
    text-align: left;
    background: var(--color-surface);
    border-bottom: 2px solid var(--color-border);
    white-space: nowrap;
    position: relative;
}

/* Sortable headers */
.table-wrap thead th[data-sort-key] {
    cursor: pointer;
    user-select: none;
    transition: background var(--dur-fast);
}
.table-wrap thead th[data-sort-key]:hover { background: var(--color-surface-hover); }

/* Sort indicator */
.table-wrap thead th .sort {
    display: inline-block;
    margin-left: var(--space-1);
    color: var(--color-text-muted);
    font-size: var(--text-xs);
    opacity: .5;
    transition: opacity var(--dur-fast);
}
.table-wrap thead th.asc  .sort,
.table-wrap thead th.desc .sort { opacity: 1; color: var(--color-blue, #3b82f6); }

/* ── Sticky header ── */
.table-wrap thead.sticky th { position: sticky; top: 0; z-index: 3; }

/* ── Table body ── */
.table-wrap tbody tr {
    border-bottom: 1px solid var(--color-border);
    transition: background var(--dur-micro);
}
.table-wrap tbody tr:last-child { border-bottom: none; }
.table-wrap tbody tr:hover { background: var(--color-surface-hover); }

.table-wrap tbody td {
    padding: 10px 14px; /* intentional compact cell padding */
    height: 48px; /* intentional row height */
    vertical-align: middle;
    font-size: var(--text-sm);
    color: var(--color-text);
}

/* ── Selected row ── */
.table-wrap tbody tr.selected {
    background: color-mix(in srgb, var(--color-blue, #3b82f6) 8%, var(--color-surface));
}

/* ── Checkboxes ── */
.table-wrap input[type=checkbox] { accent-color: var(--color-blue, #3b82f6); cursor: pointer; }

/* ── Generic inline tag/badge ── */
.tag {
    display: inline-flex;
    align-items: center;
    padding: 2px 7px; /* intentional compact tag padding */
    border-radius: 4px; /* intentional rectangular — smaller than --radius-sm */
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: .03em;
    background: var(--color-surface-hover);
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
    white-space: nowrap;
}
/* A tinta é a `-text` da família e não a cor cheia: a cor cheia sobre o pastel
   dela própria não se lê. No tema claro, o «reservado» estava a 1,61:1 e o
   «usado» a 2,04:1; no escuro, o «vendido» a 2,12:1. Medido a 2026-09-13 com
   `python scripts/ferramentas/_badges_contraste.py`, que também mede estas. O
   fundo e a borda ficam como estavam, porque o que faltava era contraste de
   texto e não outra paleta. */
.tag.stock     { background: var(--color-green-subtle);   color: var(--color-green-text);   border-color: color-mix(in srgb, var(--color-green),   transparent 70%); }
.tag.reservado { background: var(--color-yellow-subtle);  color: var(--color-yellow-text);  border-color: color-mix(in srgb, var(--color-yellow),  transparent 70%); }
.tag.vendido   { background: var(--color-slate-subtle);   color: var(--color-slate-text);   border-color: color-mix(in srgb, var(--color-slate),   transparent 70%); }
.tag.novo      { background: var(--color-blue-subtle);    color: var(--color-blue-text);    border-color: color-mix(in srgb, var(--color-blue),    transparent 70%); }
.tag.semi      { background: var(--color-purple-subtle);  color: var(--color-purple-text);  border-color: color-mix(in srgb, var(--color-purple),  transparent 70%); }
.tag.usado     { background: var(--color-orange-subtle);  color: var(--color-orange-text);  border-color: color-mix(in srgb, var(--color-orange),  transparent 70%); }
.tag.demo      { background: var(--color-pink-subtle);    color: var(--color-pink-text);    border-color: color-mix(in srgb, var(--color-pink),    transparent 70%); }

/* ── Countdowns (badges de término de campanha, etc.) ──────────────────
   FONTE ÚNICA: qualquer elemento com [data-countdown-target] tem os dígitos de
   largura fixa, para o texto não "dançar" a cada segundo do contador. Aplica-se
   a todos os countdowns do CMS, independentemente da classe do badge. */
[data-countdown-target] {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/* ══════════════════════════════════════════════════════════════
   FICHA DA MATRÍCULA  (modal `plate-lookup-result`)
   ─────────────────────────────────────────────────────────────
   A ficha que a consulta por matrícula devolve: a chapa ao centro do topo, o
   VIN por baixo dela e os dados técnicos numa tabela agrupada por assunto.

   Vive aqui, e não no CSS da Visão Geral de Viaturas onde nasceu, porque a
   pesquisa global do cabeçalho abre o mesmo modal a partir de QUALQUER página
   do CMS. Fora da Visão Geral, o modal saía sem desenho nenhum.
   ══════════════════════════════════════════════════════════════ */
/* ── O resultado da consulta por matrícula, no modal ───────────────────
   A chapa ao centro do topo, o VIN por baixo dela e a ficha técnica a seguir.
   A matrícula não se repete em texto: a chapa é que a diz, e leva na faixa
   amarela o mês e o ano da 1.ª matrícula. */
.plate-modal {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) var(--space-5);
}

.plate-modal-head {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}
/* A chapa do modal é maior do que a do campo: é o resultado, e é a primeira
   coisa que se lê ao abrir. */
.plate-modal-head .license-plate.is-lg { width: 260px; }
.plate-modal-head .license-plate.is-lg .plate-content { font-size: var(--text-3xl); }

.plate-vin {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
}
.plate-vin-label {
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.plate-vin-code {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: 1px;
  color: var(--color-text);
  word-break: break-all;
}
.plate-vin-copy {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-bg);
  color: var(--color-text-muted);
  cursor: pointer;
  transition: color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.plate-vin-copy:hover { color: var(--color-primary); border-color: var(--color-primary); }
.plate-vin-copy.is-copied { color: var(--color-success); border-color: var(--color-success); }
.plate-vin-copy:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }

.plate-modal-note {
  margin: 0;
  text-align: center;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}

/* ── A ficha técnica, na tabela do sistema ─────────────────────────────
   `.table-wrap` (components.css) traz a hairline entre linhas e o realce ao
   passar o rato. O que muda aqui é a forma: em vez de colunas com registos,
   são pares de campo e valor agrupados por assunto, e por isso a coluna da
   esquerda é estreita e apagada, como um rótulo.

   As linhas são mais baixas do que as de uma lista (a `.table-wrap` fixa-as em
   48px): dezasseis campos a essa altura davam um modal de rolar. */
.plate-table { margin: 0; }
.plate-table table { table-layout: fixed; }
.plate-table tbody th[scope="row"] {
  width: 45%;
  padding: 5px var(--space-3) 5px 0;
  text-align: right;
  font-size: var(--text-sm);
  font-weight: var(--weight-regular);
  color: var(--color-text-muted);
}
.plate-table tbody td {
  height: auto;
  padding: 5px 0 5px var(--space-3);
  text-align: left;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
}
/* A linha que abre um grupo: é a etiqueta de um bloco, não um cabeçalho de
   coluna, e por isso não leva o traço grosso nem o realce do hover. */
/* O título do grupo abre o bloco todo e vai ao centro da tabela (decisão do
   Jorge, 2026-09-10): à esquerda, ficava por cima da coluna dos rótulos, que
   está alinhada à direita, e lia-se como se pertencesse só a essa coluna. */
.plate-table .plate-table-group th {
  text-align: center;
  padding: var(--space-3) 0 4px;
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
  border-bottom: 1px solid var(--color-border);
}
.plate-table tbody:first-child .plate-table-group th { padding-top: 0; }
.plate-table .plate-table-group:hover { background: none; }
.plate-table tbody tr:last-child { border-bottom: 0; }

/* O rodapé: de onde veio a ficha, e o botão de a ir buscar outra vez. */
.plate-modal-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-border);
}
.plate-modal-foot .plate-modal-note {
  flex: 1 1 240px;
  text-align: left;
  font-size: var(--text-xs);
}

/* A ficha que se serviu da base porque as fontes não responderam. O fundo marca
   o aviso, e a frase diz de quando é a informação. */
.plate-modal-note.is-warning {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-warning-subtle);
  color: var(--color-text);
  text-align: left;
}


/* ══════════════════════════════════════════════════════════════
   CHAPA DE MATRÍCULA  (.license-plate)
   ─────────────────────────────────────────────────────────────
   A réplica da chapa portuguesa: faixa azul da União Europeia à esquerda,
   número ao centro, mês e ano do registo na faixa amarela à direita.

     <div class="license-plate">
       <div class="euro-band"><div class="stars"></div><div class="country-code">P</div></div>
       <div class="plate-content"><div>AA-00-AA</div></div>
       <div class="date-band"><div>07</div><hr><div>2024</div></div>
     </div>

   Estava escrita três vezes: em `vehicles-table.css`, em `list.css` e em
   `public_html/assets/css/pages/vehicles.css`, com medidas e cores ligeiramente
   diferentes em cada uma. Passa a viver aqui, que é onde vivem os componentes
   do CMS, e as duas cópias do backoffice foram apagadas.

   As cores NÃO vêm dos tokens de propósito: são as da chapa física e da
   bandeira europeia, e não mudam com o tema da aplicação. Uma chapa cinzenta
   no tema escuro deixava de ser uma chapa.

   As estrelas eram uma imagem carregada da Wikimedia a cada listagem de
   viaturas. Um pedido a um domínio externo numa página do CRM, que este
   projeto não faz (CLAUDE.md §3), e que deixava a chapa sem bandeira sempre
   que a rede tardasse. São agora um SVG dentro da própria folha.
══════════════════════════════════════════════════════════════ */
.license-plate {
  --plate-radius: 5px;
  position: relative;
  display: flex;
  align-items: center;
  margin: 7px auto 0;
  width: 120px;
  aspect-ratio: 4.3 / 1;
  background-color: #fff;      /* ds-ok: o fundo branco da chapa física */
  border: 1.5px solid #000;    /* ds-ok: o contorno preto da chapa física */
  border-radius: var(--plate-radius);
  font-weight: var(--weight-bold);
  color: #000;                 /* ds-ok: o número da chapa é preto */
  overflow: hidden;
}

/* O RECORTE DOS CANTOS
   O `overflow: hidden` recorta pelo `padding-box`, e as faixas de cor encostam
   exactamente à borda: nos cantos ficava um fio branco do fundo entre a cor e o
   contorno preto, do antialiasing. As faixas passam a levar elas próprias o
   canto arredondado, com o raio de dentro (o de fora menos a borda), e assim a
   cor acaba onde o contorno começa, sem depender do recorte.

   O `clip-path` é o cinto: com ele, nada do que estiver lá dentro passa a
   linha, seja qual for o motor de desenho. */
.license-plate {
  clip-path: inset(0 round var(--plate-radius));
}
.license-plate > :first-child {
  border-start-start-radius: calc(var(--plate-radius) - 1.5px);
  border-end-start-radius: calc(var(--plate-radius) - 1.5px);
}
.license-plate > :last-child {
  border-start-end-radius: calc(var(--plate-radius) - 1.5px);
  border-end-end-radius: calc(var(--plate-radius) - 1.5px);
}

/* A chapa em destaque, para quando ela é o resultado e não uma célula de
   tabela: o dobro da largura, e o número a acompanhar. */
.license-plate.is-lg {
  --plate-radius: 9px;
  width: 220px;
  margin: 0;
  border-width: 2px; /* ds-ok: a moldura da chapa cresce com ela */
}
.license-plate.is-lg > :first-child,
.license-plate.is-lg > :last-child {
  border-start-start-radius: calc(var(--plate-radius) - 2px);
  border-end-start-radius: calc(var(--plate-radius) - 2px);
}
.license-plate.is-lg > :last-child {
  border-start-start-radius: 0;
  border-end-start-radius: 0;
  border-start-end-radius: calc(var(--plate-radius) - 2px);
  border-end-end-radius: calc(var(--plate-radius) - 2px);
}
/* A chapa grande como campo: a largura acompanha o formulário e a proporção
   fica, que é o que a faz continuar a ler-se como uma chapa. */
.license-plate.is-lg.is-field { width: 100%; max-width: 300px; }
.license-plate.is-lg .plate-content { font-size: var(--text-2xl); letter-spacing: 1px; }
.license-plate.is-lg .country-code,
.license-plate.is-lg .date-band { font-size: 11px; }

.euro-band {
  position: relative;
  width: 15%;
  height: 100%;
  background-color: #003399;   /* ds-ok: o azul da bandeira europeia */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  overflow: hidden;
}

.license-plate .stars {
  width: 100%;
  height: 50%;
  background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120'><defs><path id='s' d='M0.0 -6.0L-1.3 -1.9L-5.7 -1.9L-2.2 0.7L-3.5 4.9L-0.0 2.3L3.5 4.9L2.2 0.7L5.7 -1.9L1.3 -1.9Z' fill='%23fc0'/></defs><use href='%23s' x='60.0' y='30.0'/><use href='%23s' x='75.0' y='34.0'/><use href='%23s' x='86.0' y='45.0'/><use href='%23s' x='90.0' y='60.0'/><use href='%23s' x='86.0' y='75.0'/><use href='%23s' x='75.0' y='86.0'/><use href='%23s' x='60.0' y='90.0'/><use href='%23s' x='45.0' y='86.0'/><use href='%23s' x='34.0' y='75.0'/><use href='%23s' x='30.0' y='60.0'/><use href='%23s' x='34.0' y='45.0'/><use href='%23s' x='45.0' y='34.0'/></svg>") no-repeat center;
  background-size: contain;
}

.country-code {
  font-size: 8px;              /* ds-ok: a letra do país na chapa é minúscula */
  color: #fff;                 /* ds-ok: letra branca sobre o azul da bandeira */
  height: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.plate-content {
  width: 70%;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: var(--text-md);
  white-space: nowrap;
  padding: 0 5%;
  text-align: center;
}

.date-band {
  width: 15%;
  height: 100%;
  background-color: #ffd700;   /* ds-ok: a faixa amarela do registo */
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: center;
  font-size: 8px;              /* ds-ok: o mês e o ano na chapa são minúsculos */
  font-weight: var(--weight-extrabold);
}
.date-band div { text-align: center; }
.date-band hr {
  width: 50%;
  border: none;
  height: 1.5px;
  background-color: #000;      /* ds-ok: o traço entre o mês e o ano é preto */
  margin: 0;
}

/* ── A chapa como CAMPO ────────────────────────────────────────────────
   A consulta por matrícula (Visão Geral de Viaturas) escreve o número dentro
   da própria chapa. A anatomia é a mesma; muda o que faz dela um controlo: a
   altura é a dos campos do CMS em vez da proporção 4,3:1 (a 340px de largura
   essa proporção dava 79px de altura, o triplo de um campo), e o foco lê-se
   num traço à volta, porque a borda já é preta por ser uma chapa.

   O tamanho, o peso e a COR da letra têm de vencer a regra geral dos inputs,
   que traz a tinta do tema: sem isto, no tema escuro a matrícula saía branca
   sobre o branco da chapa, ilegível. Daí a forma do seletor. */
.license-plate.is-field {
  margin: 0;
  transition: box-shadow var(--dur-fast) ease;
}
/* A chapa como campo pequeno: a altura é a dos campos do CMS em vez da
   proporção 4,3:1, que a 340px de largura dava 79px de altura. */
.license-plate.is-field:not(.is-lg) {
  height: var(--height-input);
  aspect-ratio: auto;
}
.license-plate.is-field:focus-within {
  box-shadow: 0 0 0 2px var(--color-primary); /* ds-ok: o traço de foco de um campo cuja borda é preta por ser uma chapa */
}
.license-plate.is-field:not(.is-lg) .euro-band { width: 26px; }
.license-plate.is-field .stars { height: 55%; }
.license-plate.is-field .country-code { height: 45%; }
/* O número escrito no campo tem o tamanho e a posição do número de uma chapa:
   ao centro, como o `.plate-content` da chapa ao lado. Encostado à esquerda,
   o campo deixava de se ler como uma matrícula. */
.license-plate.is-lg.is-field input {
  font-size: var(--text-2xl);
  letter-spacing: 1px;
  text-align: center;
}

.license-plate input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]) {
  flex: 1 1 auto;
  height: 100%;
  padding: 0 var(--space-3);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  letter-spacing: 2px;
  text-transform: uppercase; /* ds-ok: a matrícula é um código, não um cabeçalho */
  color: #000;               /* ds-ok: o número da chapa é preto nos dois temas */
  min-width: 0;
}
.license-plate input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):not([type="color"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"])::placeholder {
  color: #9a9a9a;            /* ds-ok: cinzento sobre o branco da chapa, nos dois temas */
  letter-spacing: 2px;
  font-weight: var(--weight-medium);
}

/* ══════════════════════════════════════════════════════════════
   EMPTY STATE  (generic, works inside any table-wrap)
══════════════════════════════════════════════════════════════ */

/* ══════════════════════════════════════════════════════════════
   KPI CARDS  (.kpi-cards > .kpi-card)
   ─────────────────────────────────────────────────────────────
   O ÚNICO cartão de indicador do CRM. Toda a faixa de números no
   topo de uma página se faz com ele — não há segundo desenho, e
   quem precisar de mais alguma coisa acrescenta aqui uma variante
   em vez de abrir um `.xxx-kpi-*` no CSS do seu módulo. Foi
   exactamente assim que se chegou a ter sete anatomias diferentes
   para a mesma ideia.

   Cartão neutro em que só a marca da esquerda ganha cor — assim uma
   fila de cartões lê-se como um conjunto e não como um semáforo.

   ANATOMIA
     .kpi-cards            grelha (auto-fit, mínimo 200px)
       .kpi-card           o cartão
         .kpi-icon         marca à esquerda: <i class="ph ph-…">
         .kpi-body
           .kpi-value      o número
           .kpi-label      o que ele conta
           .kpi-note       (opcional) segundo dado sobre o mesmo número

   COR DA MARCA
     • variantes do design system — .is-ok / .is-info / .is-warning /
       .is-danger / .is-muted
     • cor vinda da base de dados — style="--kpi-accent: #e74c3c"
       (prioridades de tarefa, estados, tipos: a cor é dado, não código)

   VARIANTES DE FORMA
     .is-dot             a marca é um .status-dot, não um ícone
     .is-accent-filled   o fundo é a cor do dado
     .is-currency        valor monetário (número mais curto, sem quebra)
     .is-clickable       o cartão é botão de drill-down; .is-active marca
                         o que está a filtrar a vista

   REGRA DE COR: a variante só entra quando há mesmo alguma coisa —
   um cartão pintado com um zero lá dentro é um alarme a dizer que
   está tudo bem, e ao fim de uma semana já ninguém lhe liga.
══════════════════════════════════════════════════════════════ */

/* MOBILE-FIRST: NUMA FILA QUE ROLA, e não empilhados (decisão do Jorge,
   2026-09-25).

   Medido a 390x844: quatro cartões empilhados são 369px, mais de 40% do ecrã,
   e seis são 550. Quem abre uma Visão Geral no telemóvel vê números e tem de
   rolar para chegar ao trabalho. Em fila ficam 92px, e a fila do que espera
   entra no primeiro ecrã.

   É a mesma saída que o §0 do CLAUDE.md dá para tudo o que não cabe: a fila
   rola, com o degradê na ponta a dizer que há mais, e NADA SE PERDE. O cartão
   leva uma largura mínima que chega para o número e o rótulo; o que passa
   disso rola.

   A partir dos 641px volta a ser a grelha de sempre, e nada muda no
   computador. */
.kpi-cards {
    display: flex;
    gap: var(--space-3);
    margin-bottom: var(--space-4);
    overflow-x: auto;
    /* Sem isto o eixo vertical passa a `auto` sozinho e a roda do rato morre
       aqui dentro em vez de passar à página (regra de 2026-09-24). */
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    padding-right: 16px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
            mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
}
.kpi-cards::-webkit-scrollbar { display: none; }
.kpi-cards > .kpi-card { flex: 0 0 auto; min-width: 210px; max-width: 78vw; }

@media (min-width: 641px) {
    .kpi-cards {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        overflow: visible;
        padding-right: 0;
        -webkit-mask-image: none;
                mask-image: none;
    }
    .kpi-cards > .kpi-card { min-width: 0; max-width: none; }
}

/* Estado vazio de uma grelha de KPI.
   Sem dados, a grelha desenhava um cartão por estado, todos a zero, a ocupar a
   largura toda para dizer o que cabe numa linha — e um ecrã de zeros lê-se à
   distância como um erro de carregamento, não como "não há trabalho".
   Ocupa a grelha inteira para não ficar encostado a uma coluna. */
.kpi-empty {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin: 0;
    padding: var(--space-5);
    color: var(--color-text-muted);
    font-size: var(--text-md);
}

.kpi-empty .ph-icon {
    font-size: var(--text-xl);
    opacity: .6;
}

.kpi-card {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    /* SUPERFÍCIE, E MOLDURA NENHUMA (regra do Jorge, 2026-09-23, para todo o
       projeto). O cartão distingue-se do que está por baixo pela terceira
       superfície, que é para isso que existe, e não por um contorno: com
       `--color-surface` dentro de um painel ficava do tom exacto do fundo e só
       a hairline dizia onde acabava. Era o que se via na Visão Geral de
       Oportunidades, com os seis números desenhados a traço ao lado de
       secções cheias. O dashboard já o fazia assim desde 2026-09-20, na folha
       dele; a decisão sobe para aqui e toda a gente herda. */
    background: var(--color-surface-alt);
    border: 0;
    /* O raio de superfície do sistema, o mesmo dos fieldsets e dos widgets.
       A 8px, uma faixa de KPI por cima de widgets a 12px lia-se como dois
       desenhos na mesma página. */
    border-radius: var(--radius);
}

/* Um cartão que depende de um serviço desligado esconde-se (ex.: o correio, na
   Visão Geral de contas). Sem esta regra o `display:flex` de cima ganha ao
   `[hidden]` da folha do browser e o cartão ficava à vista, vazio. */
.kpi-card[hidden] { display: none; }

.kpi-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    border-radius: 50%;
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
}
.kpi-icon .ph-icon { font-size: 20px; }

/* Cada variante só troca a cor do ícone — o cartão mantém-se neutro.
   .is-muted é o estado por omissão dito em voz alta: existe para quem
   quer marcar no markup que aquele número é de contexto e não de acção. */
.kpi-card.is-ok      .kpi-icon { color: var(--color-green);  background: color-mix(in srgb, var(--color-green)  14%, var(--color-surface)); }
.kpi-card.is-info    .kpi-icon { color: var(--color-blue);   background: color-mix(in srgb, var(--color-blue)   14%, var(--color-surface)); }
.kpi-card.is-warning .kpi-icon { color: var(--color-orange); background: color-mix(in srgb, var(--color-orange) 14%, var(--color-surface)); }
.kpi-card.is-danger  .kpi-icon { color: var(--color-red);    background: color-mix(in srgb, var(--color-red)    14%, var(--color-surface)); }
.kpi-card.is-muted   .kpi-icon { color: var(--color-text-muted); background: var(--color-surface-alt); }

/* Valores monetários. São mais longos do que uma contagem e, à mesma escala,
   partiam a linha em cartões estreitos — o número passava a ler-se em dois
   pedaços, que é o contrário do que um KPI serve para fazer. */
.kpi-card.is-currency .kpi-value {
    font-size: var(--text-xl);
    white-space: nowrap;
}

/* Cor vinda da BD: mesma anatomia, o valor é que chega em runtime. */
.kpi-card[style*="--kpi-accent"] .kpi-icon {
    color: var(--kpi-accent);
    background: color-mix(in srgb, var(--kpi-accent) 14%, var(--color-surface));
}

/* ── Cartão com o FUNDO na cor do dado (.is-accent-filled) ───────────
   Usado onde a cor É a informação — por exemplo as prioridades de tarefa,
   em que o cartão deve ler-se como a própria prioridade.

   A cor entra sempre MISTURADA com --color-surface, nunca pura: assim o
   mesmo hex da base de dados funciona nos dois temas sem escolher cores
   por tema, e o texto continua a usar --color-text com contraste. A dose
   é que muda — sobre fundo escuro uma cor saturada rende mais, por isso
   em dark a mistura é mais leve. */
.kpi-card.is-accent-filled {
    --kpi-fill: 16%;
    --kpi-fill-border: 38%;
    background: color-mix(in srgb, var(--kpi-accent, var(--color-primary)) var(--kpi-fill), var(--color-surface));
    border-color: color-mix(in srgb, var(--kpi-accent, var(--color-primary)) var(--kpi-fill-border), var(--color-border));
}

[data-theme="light"] .kpi-card.is-accent-filled {
    --kpi-fill: 20%;
    --kpi-fill-border: 46%;
}

/* Sobre um cartão já colorido, o ícone precisa de mais contraste do que a
   pastilha neutra — passa a sólido na cor, com o glifo a contrastar. */
.kpi-card.is-accent-filled .kpi-icon {
    color: var(--color-surface);
    background: var(--kpi-accent, var(--color-primary));
}

/* O cartão colorido não escurece no hover: a cor é o dado, mudá-la ao passar o
   rato faria parecer outro valor. O realce é só a opacidade (regra do hover). */

/* Coluna explícita: o corpo tanto é um <div> como um <span> (dentro dos
   cartões que são botões), e um <span> sem isto punha as três linhas
   lado a lado. */
.kpi-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
}
.kpi-value {
    font-size: var(--text-2xl);
    font-weight: var(--weight-bold);
    line-height: 1.1;
    color: var(--color-text);
}
.kpi-label {
    margin-top: 2px;
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}
/* Algarismos tabulares em tudo o que é número (2026-09-08): a Inter tem-nos,
   faltava ligá-los. Sem isto, uma coluna de valores dança de linha para linha. */
.kpi-value,
.ptbl-right,
.ptbl-mono,
.num { font-variant-numeric: tabular-nums; }

/* Com terceira linha o cartão cresce, e a marca ao centro deixava de estar
   ao lado do número para ficar ao lado da nota — como se fosse dela. Passa a
   acompanhar o topo, que é onde está o valor. */
.kpi-card:has(.kpi-note) { align-items: flex-start; }

/* ── Terceira linha (.kpi-note) ──────────────────────────────────────
   Um segundo dado sobre o mesmo número — "22 sem resposta", "3 negócios ·
   toda a equipa", "Base s/IVA · Novos + Serviço". Fica sempre por baixo da
   legenda, mais pequena, e nunca compete com o valor: se o que ali está
   merece o mesmo destaque que o número, então é outro cartão.

   Só ganha cor quando o que diz é uma pendência — e as cores são as mesmas
   das variantes do cartão, para o ecrã ter um vocabulário só. */
.kpi-note {
    margin-top: 3px;
    font-size: var(--text-2xs);
    color: var(--color-text-muted);
}
/* Os tons de TEXTO dos badges: a cor base media 2,1:1 no claro (laranja) e 4,4:1 no escuro (vermelho). */
.kpi-note.is-warning { color: var(--color-orange-text); font-weight: var(--weight-semibold); }
.kpi-note.is-danger  { color: var(--color-red-text);    font-weight: var(--weight-semibold); }

/* ── Marca de estado em vez de ícone (.is-dot) ───────────────────────
   Para os cartões em que o que os distingue é um estado vindo da base de
   dados e não uma ideia que um ícone possa desenhar — os pedidos de suporte
   por estado, por exemplo. O lugar do ícone passa a levar o .status-dot do
   sistema (statuses.css), à sua dimensão: uma pastilha de 40 px com um ponto
   de 8 px lá dentro seria dar o mesmo sinal duas vezes.

   A anatomia do cartão não muda — é a mesma marca à esquerda, o mesmo corpo
   à direita —, muda só o que ocupa a marca. */
.kpi-card.is-dot {
    gap: 10px;
    align-items: flex-start;
}
.kpi-card.is-dot .kpi-icon {
    width: auto;
    height: auto;
    background: none;
    border-radius: 0;
    /* O ponto acompanha o NÚMERO e não o meio do cartão — é ao número que
       ele dá cor. Ao centro, num cartão de três linhas, ficava encostado à
       legenda e parecia pertencer-lhe. Metade da linha do valor menos
       metade do ponto (8px do .status-dot). */
    margin-top: calc((var(--text-2xl) * 1.1 - 8px) / 2);
}

/* ── Cartão como drill-down ──────────────────────────────────── */
.kpi-card.is-clickable {
    width: 100%;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
    transition: border-color .15s ease, background-color .15s ease;
}
/* Na fila do telemóvel o cartão que abre fica com a largura do que diz, como
   os outros (entre 210px e 78% do ecrã), para o seguinte espreitar e se ver
   que há mais. O `width: 100%` de cima é para a grelha, e na fila esticava
   cada cartão até onde ela deixava: 272 dos 288px na Visão Geral de
   Atividades, e os 78% do ecrã nas filas mais largas. O segundo nunca
   espreitava, e o degradê caía na borda do primeiro. A fila passa a grelha
   nos 641px, e é aí que os 100% voltam. */
.kpi-cards > .kpi-card.is-clickable { width: auto; }
@media (min-width: 641px) {
    .kpi-cards > .kpi-card.is-clickable { width: 100%; }
}
/* Cartão a zero: não há nada para abrir, e o ponteiro não promete o clique.
   Até 2026-09-26 o cartão esbatia-se INTEIRO a 55%, e com ele o rótulo e a
   nota: 2,57:1 no tema escuro e 2,11:1 no claro, medidos nos cartões a zero do
   Panorama do suporte (a maqueta da Visão Geral de Atividades dá 2,59:1 e
   2,14:1). A WCAG dispensa um controlo desligado, mas este também diz um
   número, e um número é conteúdo. Esbate-se só o ícone, que é decoração; o
   texto fica com a tinta que já tem (5,21:1 e 4,8:1), e o `is-muted` põe o
   cartão a cinzento. */
.kpi-card.is-clickable[aria-disabled="true"] {
    cursor: default;
}
.kpi-card.is-clickable[aria-disabled="true"] .kpi-icon { opacity: .55; }
/* Hover: o cartão fica com a cor CHEIA, sem mais nada. Antes trocava também o
   fundo e a borda, e num cartão já colorido isso lia-se como se ele tivesse
   mudado de valor. Passar o rato só tira um esbatimento que o cartão traga.
   O cartão a zero já não traz nenhum: só o ícone se esbate, e fica assim com o
   rato por cima, porque continua a não haver nada para abrir. */
.kpi-card.is-clickable:hover,
.kpi-card.is-clickable:focus-visible {
    opacity: 1;
}
.kpi-card.is-active {
    border-color: var(--kpi-accent, var(--color-primary));
    box-shadow: inset 0 0 0 1px var(--kpi-accent, var(--color-primary));
}

/* ── Cartão à espera dos números (.is-skeleton) ──────────────────────
   Para as faixas em que os valores chegam de um pedido à parte, depois de
   a página já estar desenhada (o funil de vendas, por exemplo). Guarda o
   lugar com a forma do cartão em vez de o deixar aparecer de repente e
   empurrar o que está por baixo.

   Markup: <div class="kpi-card is-skeleton"><span></span><span></span></div> */
.kpi-card.is-skeleton {
    /* Coluna: as duas barras representam a legenda e o valor, uma por cima da
       outra. Herdando a linha do cartão ficavam lado a lado e transbordavam. */
    flex-direction: column;
    align-items: stretch;
    justify-content: center;
    min-height: 74px;
}
.kpi-card.is-skeleton span {
    display: block;
    height: 12px;
    margin: 6px 0;
    border-radius: 6px;
    background: linear-gradient(90deg, var(--color-surface-alt) 25%, var(--color-border) 37%, var(--color-surface-alt) 63%);
    background-size: 400% 100%;
    animation: kpi-shimmer 1.4s ease infinite;
}
.kpi-card.is-skeleton span:first-child { width: 55%; }
.kpi-card.is-skeleton span:last-child  { width: 80%; height: 18px; }
@keyframes kpi-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* ── Cartão sobre o filme (.is-glass) ──────────────────────────────────
   O mesmo cartão, no herói do dashboard, sobre o filme: vidro (--glass-bg,
   desfoque, sem moldura) e o valor a 28px, porque ali é o único número à
   vista. As tintas são fixas porque o filme é sempre escuro, seja qual for o
   tema: o herói define --ink-1 a --ink-3 e o cartão herda-as, com o branco
   como recurso.

   Veio do dashboard.css a 2026-09-16, quando os três avisos do herói (a
   próxima atividade, os leads em espera, o aniversário) deixaram de ter
   anatomia própria: uma segunda anatomia de indicador é o que esta secção
   proíbe. A aurora que corria na borda (um cone de cor a rodar por trás da
   peça, herdado do `create-box-hero` do Stitch) SAIU DE VEZ a 2026-09-21. Foi
   tirada com as molduras do dashboard, reposta a pedido no dia seguinte e
   tirada outra vez no mesmo dia, já com as duas versões à frente. Não repor:
   o realce destes cartões é a superfície a acender.

   Markup: o cartão pode ser um <button> (os leads) ou um <div> com um
   <button class="kpi-abrir"> lá dentro e as setas ao lado (o evento), porque
   um botão não pode ter botões dentro. */
.kpi-card.is-glass {
    --raio: var(--radius-lg);
    position: relative;
    width: 250px;
    max-width: 100%;
    padding: 12px 16px;
    margin: 0;
    background: var(--glass-bg);
    border-radius: var(--raio);
    color: var(--ink-1, #fff);
    text-align: left;
    font: inherit;
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    user-select: none;
    cursor: pointer;
    transition: background var(--dur-md) ease;
}
.kpi-card.is-glass .kpi-icon {
    background: rgba(255, 255, 255, 0.08); /* intentional: a pastilha sobre o vidro */
    color: var(--ink-2, rgba(255, 255, 255, 0.82));
    transition: color var(--dur-fast) ease, background-color var(--dur-fast) ease;
}
.kpi-card.is-glass:hover .kpi-icon {
    color: var(--ink-1, #fff);
    background: rgba(255, 255, 255, 0.14); /* intentional: a pastilha acende com o cartão */
}
.kpi-card.is-glass .kpi-value {
    font-size: var(--text-4xl);
    letter-spacing: -0.02em;
    color: var(--ink-1, #fff);
    white-space: nowrap;
}
/* A unidade colada ao número (o «h» da hora): corpo normal, tinta 2. */
.kpi-card.is-glass .kpi-value em {
    font-style: normal;
    font-weight: var(--weight-regular);
    font-size: var(--text-lg);
    letter-spacing: 0;
    color: var(--ink-2, rgba(255, 255, 255, 0.82));
    margin-left: 2px;
}
.kpi-card.is-glass .kpi-label,
.kpi-card.is-glass .kpi-note {
    color: var(--ink-3, rgba(255, 255, 255, 0.62));
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: flex;
    align-items: center;
    gap: 8px;
}
.kpi-card.is-glass .kpi-note.is-warning { color: var(--color-orange); }
/* A prioridade ao lado do nome, só quando é das mais altas: um ponto na cor
   que a tabela lhe dá (--prio vem no style, é dado) e a etiqueta dela. */
.kpi-card.is-glass .kpi-prio {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--ink-2, rgba(255, 255, 255, 0.82));
}
.kpi-card.is-glass .kpi-prio::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--prio, currentColor);
}
.kpi-card.is-glass:hover,
.kpi-card.is-glass:focus-visible,
.kpi-card.is-glass:has(.kpi-abrir:focus-visible) {
    background: rgba(255, 255, 255, 0.12); /* intentional: o vidro acende sob o rato, agora sem moldura a marcá-lo */
    outline: none;
}
/* Os cartões do herói não levam moldura nem aurora: o vidro e o desfoque já os
   separam do filme. A borda sai aqui, e não com `border: 0` em cada variante,
   porque o aniversário e os leads ainda lhe davam cor. O `::before` que fica é
   o de todos os cartões, a linha de luz do rebordo, que o dashboard desliga. */
.kpi-card.is-glass,
.kpi-card.is-glass:hover,
.kpi-card.is-glass:focus-visible,
.kpi-card.is-glass:has(.kpi-abrir:focus-visible) { border: 0; }

/* O botão que abre o cartão: é a coluna do valor e do rótulo, dentro do
   corpo, esticado à caixa toda por um pseudo-elemento. O que vier ao lado
   (a nota com a ligação à conta, as setas) fica por cima dele, no seu próprio
   plano, e o clique nesses não abre o cartão. */
.kpi-card.is-glass .kpi-abrir {
    all: unset;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
    max-width: 100%;
}
.kpi-card.is-glass .kpi-abrir::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}
/* A nota como ligação (o nome da conta abre a ficha): fica por cima do botão
   esticado, sem sublinhado, e acende com o rato. */
.kpi-card.is-glass a.kpi-note {
    position: relative;
    z-index: 1;
    text-decoration: none;
    transition: color var(--dur-fast) ease;
}
.kpi-card.is-glass a.kpi-note:hover,
.kpi-card.is-glass a.kpi-note:focus-visible {
    color: var(--ink-1, #fff);
    outline: none;
}
.kpi-card.is-glass .kpi-nav {
    position: relative;
    z-index: 1;
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 2px;
    flex-shrink: 0;
}
/* Cada seta é um botão com o alvo mínimo de 24px, e o clique nelas não abre
   o cartão. */
.kpi-card.is-glass .kpi-nav-bt {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--ink-3, rgba(255, 255, 255, 0.62));
    font: inherit;
    cursor: pointer;
    transition: color var(--dur-fast) ease, background-color var(--dur-fast) ease;
}
.kpi-card.is-glass .kpi-nav-bt .ph-icon { font-size: var(--text-md); }
.kpi-card.is-glass .kpi-nav-bt:hover {
    color: var(--ink-1, #fff);
    background: rgba(255, 255, 255, 0.10); /* intentional: a seta acende sobre o vidro */
}
.kpi-card.is-glass .kpi-nav-bt:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: 1px;
}
.kpi-card.is-glass .kpi-nav-bt:disabled {
    color: rgba(255, 255, 255, 0.25); /* intentional: a seta sem para onde ir */
    cursor: default;
    background: transparent;
}
.kpi-card.is-glass .kpi-nav-pos {
    min-width: 40px;
    text-align: center;
    font-size: var(--text-xs);
    color: var(--ink-3, rgba(255, 255, 255, 0.62));
    font-variant-numeric: tabular-nums;
}
/* Os leads em espera acendem no próprio vidro, que fica roxo enquanto houver
   algum: era o que a aurora na borda dizia, e agora di-lo a superfície. O roxo
   é fixo porque o filme é sempre escuro: o token do tema claro (#7e22ce) dava
   1,83:1 sobre o vidro. */
.kpi-card.is-glass.is-leads { background: rgba(168, 85, 247, 0.22); } /* intentional: o roxo que se lê sobre o filme nos dois temas */
.kpi-card.is-glass.is-leads:hover,
.kpi-card.is-glass.is-leads:focus-visible {
    background: rgba(168, 85, 247, 0.45); /* intentional: o roxo cheio sobre o vidro do herói */
}
/* O aniversário: vidro roxo, o hover, e não é um botão. */
.kpi-card.is-glass.is-aniv {
    background: rgba(168, 85, 247, 0.15); /* intentional: o roxo do aniversário sobre o filme */
    cursor: default;
}
.kpi-card.is-glass.is-aniv:hover {
    background: rgba(168, 85, 247, 0.30); /* intentional: o hover que o aniversário já tinha */
}
.kpi-card.is-glass.is-aniv .kpi-icon {
    color: #c084fc; /* intentional: o presente na cor do aniversário */
    background: rgba(192, 132, 252, 0.14);
}
/* O valor é um nome, não um número: corpo mais pequeno. E QUEBRA, porque
   podem ser três nomes («Jorge Nogueira, Noémia Pinto e João Torre») e num
   cartão de 250px o segundo ficava cortado a meio (2026-09-17). O nome
   inteiro continua no `title`, para o caso de nem assim caber. */
.kpi-card.is-glass.is-aniv .kpi-value {
    font-size: var(--text-xl);
    letter-spacing: -0.01em;
    white-space: normal;
    line-height: 1.25;
}
/* A FRASE DE FELICITAÇÃO QUEBRA (pedido do Jorge, 2026-09-17). Os rótulos dos
   cartões do herói são de uma linha, com reticências: aqui o rótulo é uma
   frase («faz anos hoje. Envie-lhe uma saudação de felicitação.») e cortá-la
   ao meio seria pior do que não a ter. O cartão cresce em altura, e o nome em
   cima continua a ser a primeira coisa que se lê. */
.kpi-card.is-glass.is-aniv .kpi-label {
    display: block;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    line-height: 1.35;
}
/* Os emojis são a festa, e não texto: não encolhem com o rótulo, e ficam com o
   corpo do texto normal para não pesarem mais do que o nome. */
.kpi-card.is-glass.is-aniv .kpi-emojis {
    white-space: nowrap;
    letter-spacing: 0.06em;
}

/* O evento leva as setas ao lado e é mais largo para elas nunca mexerem
   (pedido do Jorge, 2026-09-15: o cartão tem sempre o mesmo tamanho). */
.kpi-card.is-glass.is-evento { width: 380px; }


/* ══════════════════════════════════════════════════════════════════════
   .page-body — o respiro interior de um painel
   ──────────────────────────────────────────────────────────────────────
   Vive AQUI e não no layout.css por uma razão que se paga cara: o `base.css`
   faz `.panel-child-element { padding: 0 }` e o markup põe as duas classes no
   MESMO elemento (`class="panel-child-element page-body"`, no /deals, no /rh e
   em quatro painéis do painel principal). Especificidade igual (0,1,0) — quem
   ganha é quem vier DEPOIS na ordem de carga.

   Estava no `core.css`, que carregava a seguir ao `base.css` e por isso ganhava.
   Ao passar para o `layout.css`, que carrega ANTES, o reset passou a ganhar e
   essas páginas perderam 24px de respiro sem que nada o dissesse — foi preciso
   um diff visual para dar por isso. O `components.css` carrega depois do
   `base.css`, e é isso que o mantém de pé.

   O `responsive.css` põe-no a zero em telemóvel, e carrega depois deste. */
.page-body {
  padding: var(--space-5);
}

/* ══════════════════════════════════════════════════════════════
   PÁGINA DE VISÃO GERAL  (.overview)
   ─────────────────────────────────────────────────────────────
   O corpo de TODAS as /…/overview do CRM. Vive dentro do padrão
   de página (.page-panel + .page-action-bar, mais acima neste
   ficheiro) e só trata de uma coisa: a grelha.

     <div class="content">
       <div class="default-full-panel page-panel">
         <div class="panel-child-element">
           <div class="page-action-bar"><h1 class="page-title">…</h1></div>
           <div class="page-content overview">
             <div class="kpi-cards">…</div>
             <section>…</section>
             <div id="page-widgets" class="boxes"></div>
           </div>
         </div>
       </div>
     </div>

   UM espaçamento e UM raio para tudo o que assenta na página.
   Cada módulo tinha os seus: 10px de intervalo nas Contas e nas
   Viaturas, 20px nas Atividades, 12px na faixa de KPI, e um
   padding lateral de 10px que só a grelha dos widgets tinha — o
   que punha os widgets desalinhados dos cartões mesmo por cima.

   O raio é o do sistema (`--radius`), o mesmo dos fieldsets e dos
   widgets: cartões de KPI a 8 px ao lado de widgets a 12 px lêem-se
   como dois desenhos diferentes na mesma página.
   ══════════════════════════════════════════════════════════════ */

.overview {
  --overview-gap: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--overview-gap);
}

/* O espaçamento é do contentor, não das peças: uma margem própria
   somava-se ao `gap` e abria buracos diferentes conforme a peça. */
.overview > * { margin-block: 0; }
.overview > .kpi-cards { margin-bottom: 0; }

/* Grelha dos widgets: mesmo intervalo do resto da página e sem padding
   próprio — alinha com a faixa de KPI que está por cima. */
/* `minmax(0, 1fr)` e não `1fr`: por omissão uma coluna de grelha não encolhe
   abaixo do conteúdo mais largo, e a tabela do «Desempenho por Vendedor», com
   410px, alargava a coluna de TODOS os widgets e punha a página a rolar na
   horizontal a 390px. Assim o que não cabe rola dentro do cartão dele. */
.overview #page-widgets.boxes,
.overview #page-widgets-secondary.boxes {
  display: grid;
  grid-auto-flow: row;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--overview-gap);
  padding: 0;
  align-items: stretch;
}

@media (min-width: 1025px) {
  .overview #page-widgets.boxes,
  .overview #page-widgets-secondary.boxes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Os widgets da visão geral crescem com o conteúdo e esticam à altura da
   linha. Cada módulo tinha aqui um `max-height: none !important` a destravar
   um limite de 80dvh que já não existe em folha nenhuma — ficou o destravar
   sem o travão. Sem `!important`: não há nada a bater. */
.overview #page-widgets.boxes > .box,
.overview #page-widgets-secondary.boxes > .box {
  max-height: none;
  height: auto;
  overflow: visible;
  align-self: stretch;
}

/* ── TECTO DAS LISTAS LONGAS ───────────────────────────────────────────────
   A grelha alinha os pares (`align-items: stretch`), e é isso que se quer: dois
   cartões lado a lado com fundos a alturas diferentes lêem-se como um erro. O
   preço era o vizinho curto esticar-se até à altura do mais alto — os "Motivos
   de Perda" (3 linhas) ficavam com 1085px por estarem ao lado da "Distribuição
   Geográfica" (29 linhas), quase 850px de vazio.

   A correcção é pelo lado do ALTO, não do curto: nenhuma lista passa das ~36rem;
   o que não cabe rola dentro do cartão. A linha do total fica colada ao fundo
   (`sticky`), porque é a conclusão da lista e não a sua última linha.

   `:not(:has(canvas))` deixa de fora os widgets em modo gráfico — o Chart.js
   dentro de um contentor com altura limitada e `overflow` entra no ciclo de
   crescimento que já nos apanhou. As sparklines das linhas são SVG, não canvas,
   e por isso não desligam a regra. */
.overview #page-widgets.boxes > .box > .widget-data-table:not(:has(canvas)),
.overview #page-widgets-secondary.boxes > .box > .widget-data-table:not(:has(canvas)) {
  max-height: 36rem;
  overflow-y: auto;
}

.overview #page-widgets.boxes > .box > .widget-data-table:not(:has(canvas)) > .widget-data-total,
.overview #page-widgets-secondary.boxes > .box > .widget-data-table:not(:has(canvas)) > .widget-data-total {
  position: sticky;
  bottom: 0;
  background: var(--color-surface);
  /* O contentor tem `padding: 4px 0`, e o `bottom: 0` cola-se à borda do
     conteúdo — sobravam 4px por onde se via passar a linha seguinte. A sombra
     é da cor do fundo e tapa-os sem mexer no layout. */
  box-shadow: 0 4px 0 var(--color-surface); /* ds-ok: tapa a hairline por baixo do cabeçalho colado, não é sombra */
}

/* Mesmo raio em tudo o que assenta na grelha: cartões, widgets e as
   secções soltas da página. */
.overview .kpi-card,
.overview #page-widgets.boxes > .box,
.overview #page-widgets-secondary.boxes > .box,
.overview > section,
.overview > .panel-child-element {
  border-radius: var(--radius);
}

/* ── A LINHA DE LUZ NO REBORDO ──────────────────────────────────────────
   O que assenta na mesa (widget, fieldset, painel de análise) distingue-se por
   uma linha de luz no rebordo de cima, e não por uma sombra por baixo (sistema
   de design, 2026-09-08). É o --gradient-edge dos tokens: branco diluído no
   escuro, o acento diluído no claro. Só o que flutua leva sombra. O
   `position: relative` é para o pseudo-elemento; nenhuma destas peças tinha
   `::before` em uso.

   O cartão de KPI saiu desta lista a 2026-09-23, quando deixou de ter moldura:
   num cartão sem contorno o fio claro no topo lê-se como meia moldura
   esquecida. Foi a mesma conclusão a que o dashboard tinha chegado antes. */
.kpi-card,
.overview #page-widgets.boxes > .box,
.overview #page-widgets-secondary.boxes > .box,
.default-fieldset,
.analytics-panel {
  position: relative;
}
.overview #page-widgets.boxes > .box::before,
.overview #page-widgets-secondary.boxes > .box::before,
.default-fieldset::before,
.analytics-panel::before {
  content: "";
  position: absolute;
  top: 0;
  left: 10px;
  right: 10px;
  height: 1px;
  background: var(--gradient-edge);
  pointer-events: none;
  z-index: 1;
}

/* ── Aviso meteorológico no agendamento ─────────────────────────────────────
   Aparece por baixo do seletor de data/hora e do de instalação, no modal de
   atividade, quando a previsão para aquela hora é adversa (ver wxCheck em
   Shared/Services/EventManager.js). É informação, não validação: nunca impede
   de gravar. A cor SEGUE a do contexto do formulário — os campos à volta já
   têm a sua — e o ícone é o SVG animado das condições, em currentColor. */
.event-wx-warning {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  font-size: var(--text-sm);
  color: inherit;
}

.event-wx-warning[hidden] { display: none; }

.event-wx-warning .wx-ico { font-size: var(--text-lg); flex-shrink: 0; }

/* ── Revisao do supervisor, dentro do modal de atividade ────────────────────
   Diz em que pe esta a revisao e, a quem pode rever, da o botao que a fecha.
   Assenta na superficie de dentro do painel, com hairline e sem sombra: nao
   flutua, e parte do formulario. O badge, o botao e o campo da nota sao os
   componentes de sempre; o que e novo aqui e so a caixa que os arruma. */
.event-review {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface-alt);
}
.event-review[hidden] { display: none; }
.event-review-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--text-sm);
  color: var(--color-text-secondary);
}
/* A nota fica na tinta normal: e o texto que a pessoa veio ler, e nao um
   apontamento de rodape. O filete a esquerda diz que e uma citacao. */
.event-review-note {
  margin: 0;
  padding-left: var(--space-3);
  border-left: 2px solid var(--color-border-strong);
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--color-text);
  white-space: pre-line;
}
.event-review-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* ── Resposta rápida a partir do aviso de correio novo ────────────────────── */
/* Vive fora do módulo do correio: o aviso aparece em qualquer página do CRM, e
   quem responde daqui pode nem ter a ficha aberta. */
.modal-window-container[data-modal-id="mail-quick-reply"] > .modal-content-wrapper > .modal-content {
    padding: var(--space-4) var(--space-5) var(--space-5);
}
.mail-quick { display: flex; flex-direction: column; gap: var(--space-3); }
.mail-quick-head {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
}
.mail-quick-head span { font-size: .82rem; color: var(--color-text-muted); }
.mail-quick-snippet {
    margin: 0;
    padding-left: var(--space-3);
    border-left: 2px solid var(--color-border);
    font-size: .8rem;
    color: var(--color-text-muted);
}
.mail-quick textarea {
    width: 100%;
    resize: vertical;
    min-height: 120px;
}
.mail-quick-note {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: 0;
    font-size: .74rem;
    color: var(--color-text-muted);
}

/* ══════════════════════════════════════════════════════════════
   CABEÇALHO DE PAINEL  (.cms-pane-head)
   ─────────────────────────────────────────────────────────────
   Uma página com dois lados, os separadores à esquerda e um painel à direita,
   tem de os começar e acabar à mesma linha. Não estavam: na ficha de um pedido
   a barra media 39px e o cabeçalho do aside 45px, e na ficha de uma conta eram
   43px contra 42px, cada um com o seu valor escrito à mão. Seis píxeis chegam
   para o olho ver que os dois lados não encaixam (decisão do Jorge, 2026-09-18).

   Pôr a classe nos dois elementos que se encostam. Governa só a altura: o
   resto (fundo, borda, padding) continua a ser de cada um. `min-height` e não
   `height`, para um cabeçalho com mais do que cabe numa linha poder crescer em
   vez de cortar o que lá está.
   ══════════════════════════════════════════════════════════════ */
.cms-pane-head {
    box-sizing: border-box;
    min-height: var(--height-pane-head);
}

/* ══════════════════════════════════════════════════════════════
   ESTADOS DE VISTA  (.cms-state, .cms-skeleton)
   ─────────────────────────────────────────────────────────────
   Os cinco estados de qualquer lista ou painel com a mesma anatomia
   (2026-09-08): a carregar (esqueleto com a forma do conteúdo), vazio (a razão
   e a acção que resolve), erro (o que falhou e como repetir) e sem permissão
   (a quem se pede). O «com dados» é o caso normal. O `.cms-empty-state` de
   sempre continua a servir o fieldset vazio de uma linha só.

   <div class="cms-state is-empty">
     <i class="ph ph-tray ph-icon cms-state-icon" aria-hidden="true"></i>
     <p class="cms-state-title">Ainda não há pedidos nesta conta</p>
     <p class="cms-state-text">O primeiro cria-se em Novo pedido.</p>
     <div class="cms-state-actions"><button type="button" class="bt-tonal normal primary">Novo pedido</button></div>
   </div>

   É o MESMO desenho em todo o projeto (Jorge, 2026-09-25, a ver o separador
   Atividade vazio ao lado do das Cotações): um separador, uma secção ou uma
   lista sem nada. O `_estado_vazio_canonico.py` recusa o vazio desenhado à mão.
   ══════════════════════════════════════════════════════════════ */
.cms-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--space-2);
  min-height: 160px;
  padding: var(--space-6) var(--space-4);
  color: var(--color-text-muted);
}
.cms-state-icon {
  font-size: 28px;
  line-height: 1;
  color: var(--color-text-muted);
}
.cms-state.is-error .cms-state-icon { color: var(--color-danger-text, var(--color-danger)); }
.cms-state.is-forbidden .cms-state-icon { color: var(--color-text-muted); }
.cms-state-title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--color-text);
}
.cms-state-text {
  margin: 0;
  font-size: var(--text-sm);
  max-width: 38ch;
  line-height: 1.5;
}
.cms-state-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* O esqueleto tem a forma de uma lista: um avatar e três colunas por linha.
   O brilho que corre é o único movimento, e pára com o movimento reduzido. */
.cms-skeleton {
  display: grid;
  gap: 12px;
  padding: var(--space-4);
}
.cms-skeleton-row {
  display: grid;
  grid-template-columns: 28px minmax(0, 2fr) minmax(0, 3fr) 72px;
  gap: 12px;
  align-items: center;
}
.cms-skeleton-row > span {
  display: block;
  height: 10px;
  border-radius: var(--radius-full);
  background: linear-gradient(90deg, var(--color-surface-alt) 25%, var(--color-border) 40%, var(--color-surface-alt) 55%);
  background-size: 400% 100%;
  animation: cms-skeleton-shimmer 1.4s ease-in-out infinite;
}
.cms-skeleton-row > span:first-child {
  width: 28px;
  height: 28px;
  border-radius: 50%;
}
@keyframes cms-skeleton-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cms-skeleton-row > span { animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   O CARTÃO DA PÁGINA (maqueta de 2026-09-09)
   O título e a barra de acção ficam na mesa. Os separadores, as pílulas, a
   tabela e o rodapé com a paginação e o botão principal vivem num cartão:
   superfície e raio grande, SEM contorno. Foi a decisão do Jorge sobre a
   maqueta; as regras acima (painel a sangrar, barra de acção colada)
   continuam a valer para o que fica fora do cartão.
═══════════════════════════════════════════════════════════════════════ */
.app .page-panel > .page-sticky-tabs,
.app .page-panel > * > .page-sticky-tabs,
.app .page-panel > * > .page-content {
  flex: 0 0 auto;
  /* `.full-width-tabs{width:100%}` somado às margens passava da borda direita. */
  width: auto;
  max-width: none;
  /* Alinha pelo título: a goteira publicada mais a folga da borda do ecrã, que
     é a mesma conta da barra de acção. O respiro de dentro do cartão é outro
     token (`--page-pad`, nas regras logo a seguir) e no telemóvel vale mais. */
  margin: var(--space-4) calc(var(--content-gutter, 0px) + var(--page-edge, 24px)) var(--space-6);
  background-color: var(--color-surface);
  /* SEM contorno, em toda a parte (regra do Jorge, 2026-09-22, repetida a
     2026-09-23 ao ver a `.page-content.overview` outra vez com ele). O cartão
     já se distingue da mesa pela superfície, e a hairline à volta era a linha
     mais pesada de uma página feita de superfícies. Continua a levar traço o
     que SEPARA por dentro: o fim da fila de separadores e o topo do rodapé. */
  border: 0;
  border-radius: var(--radius-lg);
  overflow: visible;
}

/* Com rodapé, o cartão continua nele: sem canto nem margem entre os dois. */
.app .page-panel:has(> .form-nav) > .page-sticky-tabs,
.app .page-panel > *:has(> .form-nav) > .page-sticky-tabs,
.app .page-panel > *:has(> .form-nav) > .page-content {
  margin-bottom: 0;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}

/* Os separadores: compactos, à esquerda, dentro do cartão. */
.app .page-sticky-tabs > .full-width-tabs-nav {
  padding-inline: var(--page-pad, 24px);
  justify-content: flex-start;
  background-color: var(--color-surface);
  border-bottom-color: var(--color-border);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
.app .page-sticky-tabs > .full-width-tabs-nav > .full-width-tab,
.app .page-sticky-tabs > .full-width-tabs-nav.is-stretch > .full-width-tab {
  flex: 0 0 auto;
  padding: 12px 14px;
}

/* O contador de um separador (Interações 3, Pedidos de Suporte 23). */
.full-width-tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--radius-full);
  background: var(--color-surface-alt);
  border: 1px solid var(--color-border);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.full-width-tab.is-active .full-width-tab-count { color: var(--color-text); }
/* `hidden` tem de vencer o inline-flex acima. */
.full-width-tab-count[hidden] { display: none; }

/* O corpo do cartão recolhe ao respiro do cartão, não ao da mesa. */
.app .page-panel .page-sticky-tabs > .full-width-tab-panel > .wrap,
.app .page-panel > * > .page-content {
  padding-inline: var(--page-pad, 24px);
  padding-block: var(--space-3) var(--space-4);
}

/* A tabela vai de borda a borda do cartão: as linhas dela são as do cartão. */
.app .page-panel .wrap > .ptbl-root,
.app .page-panel .page-content > .ptbl-root {
  margin-inline: calc(-1 * var(--page-pad, 24px));
  border: 0;
  border-radius: 0;
}
.app .page-panel .wrap:has(> .ptbl-root:last-child),
.app .page-panel .page-content:has(> .ptbl-root:last-child) {
  padding-block-end: 0;
}

/* O rodapé do painel, no lugar da barra fixa: a paginação fica na tabela e o
   botão principal aqui, dentro do cartão, a seguir a ela. Nas fichas leva o
   contador de campos alterados à esquerda e as acções à direita
   (.form-nav--ficha). */
.app .page-panel .form-nav {
  position: static;
  z-index: auto;
  margin-top: 0;
  margin-inline: calc(var(--content-gutter, 0px) + var(--page-edge, 24px));
  margin-bottom: var(--space-6);
  padding: var(--space-3) var(--page-pad, 24px);
  background-color: var(--color-surface);
  /* O cartão por cima deixou de ter contorno, e este rodapé é a continuação
     dele: fica só a linha que os SEPARA. */
  border: 0;
  border-top: 1px solid var(--color-border);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
/* Uma barra que já vive DENTRO do cartão (num separador, ou a seguir ao
   módulo das Definições) é só o rodapé dele: uma linha por cima e nada mais. */
.app .page-panel .full-width-tab-panel .form-nav,
.app .page-panel .page-sticky-tabs > .form-nav,
.app .page-panel .page-content > .form-nav {
  margin: 0;
  border: 0;
  border-top: 1px solid var(--color-border);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.app .page-panel .page-sticky-tabs > .full-width-tab-panel > .wrap:has(+ .form-nav),
.app .page-panel .page-sticky-tabs > #settings-module:has(+ .form-nav) { padding-block-end: var(--space-4); }
.app .page-panel .form-nav.form-nav--ficha { justify-content: flex-end; }
.app .page-panel .form-nav.form-nav--ficha .form-nav-status {
  margin-right: auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.app .page-panel .form-nav.form-nav--ficha .form-nav-status::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--color-warning);
}
.app .page-panel .form-nav.form-nav--ficha .form-nav-status[hidden] { display: none; }

/* As células que a maqueta desenha de uma maneira só: o que falta em cinza,
   o ID a negro, o VIN monoespaçado, a data com a hora em cinza por baixo. */
.cell-missing { color: var(--color-text-muted); }
.cell-id { font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
.cell-mono { font-family: var(--font-mono); font-size: var(--text-sm); letter-spacing: .01em; }
.cell-stack { display: inline-flex; flex-direction: column; line-height: 1.3; vertical-align: middle; }
.cell-stack > strong { font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
.cell-stack > small { font-size: var(--text-xs); color: var(--color-text-muted); }
/* SEM ESTICAR. O `stretch` por omissão de um `inline-flex` em coluna dá ao
   filho de cima a largura da frase de baixo: uma pastilha «Em análise» ficava
   com a largura de «na financeira há 9 dias» e lia-se como um erro de
   desenho. Não se via porque o que ia dentro da `.cell-stack` era sempre
   texto sem fundo; com um `.status-badge` vê-se. A segunda linha existe
   porque numa coluna alinhada à direita o empilhamento tem de encostar ao
   mesmo lado que o número. Encontrado na maqueta de 2026-09-25. */
.cell-stack { align-items: flex-start; }
.ptbl-right .cell-stack { align-items: flex-end; }

/* ═══════════════════════════════════════════════════════════════════════
   AS PEÇAS DA MAQUETA (2026-09-09)
   Menu de contexto, etiquetas, caminho, passos, progresso, dica e avatares.
   Cada uma tem o seu componente ou a sua convenção de markup, e todas vivem
   no banco de ensaio (/dev/components). Nascem aqui, não num módulo.
═══════════════════════════════════════════════════════════════════════ */

/* Menu de contexto (Shared/Components/ContextMenu). Flutua: leva a sombra. */
.cms-menu {
  position: fixed;
  z-index: var(--z-dropdown);
  min-width: 200px;
  padding: 6px;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg); /* ds-ok: peça que flutua */
}
.cms-menu[hidden] { display: none; }
.cms-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  text-align: left;
  cursor: pointer;
}
.cms-menu-item:hover,
.cms-menu-item:focus-visible { background: var(--color-surface-alt); outline: 2px solid transparent; }
.cms-menu-item:disabled { color: var(--color-text-muted); cursor: default; }
.cms-menu-item .ph-icon { font-size: 16px; color: var(--color-text-muted); }
/* O ITEM VERMELHO ESCREVE COM O TOM QUE ESCREVE (maqueta do chat, 2026-09-26,
   letra O). Escrevia com `--color-danger`, o tom que PINTA, e ficava a 4,05:1
   no escuro e a 4,27:1 no claro com o foco ou o rato em cima, abaixo dos 4,5
   de um texto normal. É a regra da cor que pinta e da cor que escreve, já
   seguida pela nota de um KPI em risco (`.kpi-note.is-danger`). */
.cms-menu-item.is-danger,
.cms-menu-item.is-danger .ph-icon { color: var(--color-red-text); }
.cms-menu-label { flex: 1 1 auto; }
.cms-menu-kbd {
  padding: 1px 6px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
}
.cms-menu-sep { height: 1px; margin: 6px 4px; background: var(--color-border); }

/* A JANELA FLUTUANTE (`.cms-window`, maqueta do chat de 2026-09-26, letra R).
   Nasceu para o widget do chat, que passa a ser o chat inteiro numa janela: flutua
   sobre qualquer página, arrasta-se pela barra e muda de tamanho pelo canto
   livre. Entra como componente, e não como CSS do chat, porque é uma peça que a
   casa não tinha; o comportamento (arrasto, teclado, memória do sítio) é do
   `Shared/Components/Window/Window.js`, e o markup está na maqueta.

   A anatomia é a da casa: a superfície do cartão, a hairline, o raio dos modais
   e a sombra, que aqui tem função, porque a janela flutua. Fica POR BAIXO dos
   menus e das janelas de confirmação (`--z-dropdown` menos um degrau), porque é
   de dentro dela que eles se abrem: uma janela por cima de tudo tapava o
   «Eliminar conversa» que ela própria abriu.

   Mobile-first: no telemóvel é o ecrã todo por baixo do cabeçalho do CMS, como
   uma página. Do tablet para cima flutua, ancorada ao canto de baixo à direita,
   que é onde está o botão que a abre; por isso o canto que muda o tamanho é o
   de cima à esquerda, o canto livre. As quatro medidas (`--win-w`, `--win-h`,
   `--win-right`, `--win-bottom`) escreve-as o JS no próprio elemento, e a
   `--win-top-min` (onde acaba o cabeçalho) na raiz. */
.cms-window {
  position: fixed;
  inset: var(--win-top-min, 64px) 0 0 0;
  z-index: calc(var(--z-dropdown) - 10);
  display: flex;
  flex-direction: column;
  background: var(--color-surface);
  color: var(--color-text);
}
.cms-window[hidden] { display: none; }
.cms-window-bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 48px;
  padding: 6px 8px 6px 10px;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-surface);
}
/* O título é o botão que arrasta: com o teclado, as setas movem a janela. */
.cms-window-move {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  text-align: left;
  cursor: default;
}
.cms-window-move:focus-visible { outline: 2px solid transparent; box-shadow: inset 0 0 0 1px var(--color-border-strong); } /* ds-ok: o foco lê-se na borda do botão */
.cms-window-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cms-window-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.cms-window-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
/* No telemóvel a janela é o ecrã: não se arrasta nem muda de tamanho. */
.cms-window-resize { display: none; }
@media (min-width: 769px) {
  .cms-window {
    inset: auto;
    right: var(--win-right, 24px);
    bottom: var(--win-bottom, 76px);
    width: var(--win-w, 440px);
    height: var(--win-h, 640px);
    max-width: calc(100vw - 16px);
    max-height: calc(100dvh - var(--win-top-min, 64px) - 8px);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg); /* ds-ok: a janela flutua sobre a página, e a sombra é o que a separa dela */
    overflow: hidden;
  }
  .cms-window-bar { cursor: grab; touch-action: none; user-select: none; }
  .cms-window.is-moving .cms-window-bar { cursor: grabbing; }
  .cms-window-move { cursor: inherit; }
  .cms-window-resize {
    display: block;
    position: absolute;
    left: 0;
    top: 0;
    z-index: 2;
    width: 18px;
    height: 18px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: nwse-resize;
    touch-action: none;
  }
  /* O canto desenha-se com as duas bordas de um quadrado: é o sinal de
     «pega-se aqui» que o sistema operativo já ensinou. */
  .cms-window-resize::before {
    content: "";
    position: absolute;
    left: 5px;
    top: 5px;
    width: 8px;
    height: 8px;
    border-left: 2px solid var(--color-text-muted);
    border-top: 2px solid var(--color-text-muted);
    border-top-left-radius: 3px;
  }
  .cms-window-resize:hover::before,
  .cms-window-resize:focus-visible::before { border-color: var(--color-text); }
  .cms-window-resize:focus-visible { outline: 2px solid transparent; box-shadow: inset 0 0 0 1px var(--color-border-strong); border-top-left-radius: var(--radius-lg); } /* ds-ok: o foco lê-se na borda do canto */
}

/* Etiquetas (Shared/Components/TagsInput): um controlo só, chips e campo. */
.cms-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: 5px 8px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface-alt);
  cursor: text;
}
.cms-tags:focus-within { border-color: var(--color-primary); }
.cms-tags-list { display: contents; }
.cms-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 4px 3px 9px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
}
/* Um chip que só se lê (a coluna «Ligado a» das atividades, as caixas de uma
   pessoa nas Definições): sem o botão de tirar, o padding assimétrico do chip
   do TagsInput deixava o texto fora do centro. */
.cms-tag.is-static {
  padding-inline: 9px;
  cursor: default;
}
.cms-tag.is-static .ph-icon {
  font-size: 12px;
  color: var(--color-text-muted);
}
.cms-tag-remove {
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.cms-tag-remove:hover,
.cms-tag-remove:focus-visible { background: var(--color-surface-alt); color: var(--color-text); outline: 2px solid transparent; }
.cms-tag-remove .ph-icon { font-size: 12px; }
/* O `!important` nas cinco primeiras é a mesma armadilha da `.cms-search-input`:
   este seletor pesa (0,2,0) e perde para a regra base dos campos, o
   `input:not(...)` com dez exclusões, que pesa (0,10,1). Sem ele o campo
   desenhava uma segunda caixa, com contorno e altura próprios, dentro da caixa
   das etiquetas. Não doía enquanto o TagsInput só vivia no laboratório de
   componentes; doeu na primeira página a usá-lo. */
.cms-tags .cms-tags-input {
  flex: 1 1 120px;
  min-width: 80px;
  width: auto !important;             /* nat-ok: vence a regra base dos campos, como a .cms-search-input */
  height: auto !important;            /* nat-ok: idem */
  padding: 0 !important;              /* nat-ok: idem */
  border: 0 !important;               /* nat-ok: idem */
  background: transparent !important; /* nat-ok: idem */
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  outline: 2px solid transparent; /* o foco lê-se na borda do controlo */
}

/* ══════════════════════════════════════════════════════════════════════════
   A CAIXA DE PESQUISA  (.cms-search)
   ─────────────────────────────────────────────────────────────────────────
   Uma só em todo o CMS (decisão do Jorge, 2026-09-22). Havia 86 nomes de
   classe em 36 folhas a desenhar a mesma coisa, cada uma com a sua altura, o
   seu contorno e a sua lupa.

   A anatomia é a do botão de procura do cabeçalho (`.app-global-search`, em
   `templates/partials/header/assets/css/header.css`): a lupa à esquerda, a
   folga entre ela e o texto, o contorno de 1px, o raio dos controlos, e o
   contorno a ganhar peso no rato e no teclado. Em repouso lê-se IGUAL à do
   cabeçalho (decisão do Jorge, 2026-09-23), e só uma coisa muda de token:

     altura     `--height-input` (34px), e não os 32px do cabeçalho. Esta
                caixa fica ao lado de selects e de botões `.normal`, e 2px a
                menos vêem-se numa fila de filtros.

   Em repouso, portanto, a hairline (`--color-border`, que é o que o
   `--header-border` resolve) e fundo NENHUM. O que era o estado de repouso
   até 2026-09-22 (o traço `--color-border-strong` e o fundo
   `--color-surface-alt`) passou para o rato e para o teclado: a caixa acende
   quando se vai escrever nela, e numa barra de filtros já não é a peça mais
   pesada da fila.

   O contorno em repouso fica a 1,40:1, abaixo dos 3:1 que a WCAG 1.4.11 pede
   para o LIMITE de um controlo, e cumpre-se pela mesma razão que o cabeçalho:
   não é o contorno que diz que ali se procura, são a lupa e o texto lá
   dentro, ambos acima de 4,5:1. Uma caixa sem lupa nem rótulo não podia.

   O foco lê-se na CAIXA (`:focus-within`), nunca num anel à volta do campo:
   é a regra da casa (DESIGN_SYSTEM.md §6). O campo lá dentro não tem desenho
   próprio nenhum, só texto.

   <div class="cms-search">
     <i class="ph ph-magnifying-glass ph-icon cms-search-icon" aria-hidden="true"></i>
     <input type="search" class="cms-search-input" placeholder="Nome ou matrícula"
            autocomplete="off" aria-label="Procurar viaturas">
     <button type="button" class="cms-search-clear" aria-label="Limpar a pesquisa" hidden>
       <i class="ph ph-x ph-icon" aria-hidden="true"></i>
     </button>
   </div>

   O `x` pertence ao ecrã que a usa: nasce com `hidden` e o JS tira-lho quando
   há texto escrito. Duas variantes, e mais nenhuma: `.cms-search--block` para
   ocupar a linha toda também no computador, e `.cms-search--sm` para dentro
   de um modal ou de um cabeçalho de painel, na escala dos controlos `.small`.
   ══════════════════════════════════════════════════════════════════════════ */
.cms-search {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  /* No telemóvel a caixa ocupa a linha inteira: em 390px não há espaço para
     lhe pôr nada ao lado, e o que sobrasse à direita não servia para nada. A
     largura fixa entra no primeiro degrau em que cabe um filtro ao lado. */
  width: 100%;
  min-width: 0;
  height: var(--height-input);
  /* À direita menos do que à esquerda: quem lá vive é o botão de limpar, que
     já traz 24px de alvo. É a mesma conta do `0 10px 0 12px` do cabeçalho. */
  padding: 0 6px 0 12px;
  /* A mesma linha da caixa do cabeçalho, e fundo nenhum. */
  border: 1px solid var(--color-border);
  border-radius: var(--radius-control-md);
  background: transparent;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  /* A caixa inteira é um campo de texto, e o cursor diz isso. */
  cursor: text;
  transition: border-color var(--dur-fast) ease, background-color var(--dur-fast) ease;
}

@media (min-width: 641px) {
  /* 240px chegam para «Nome, organização, telefone ou e-mail» se ler até ao
     terceiro termo antes de cortar. Quem a quer maior usa `--block`. */
  .cms-search {
    width: 240px;
    max-width: 100%;
  }
}

/* A caixa acende quando se vai escrever nela (decisão do Jorge, 2026-09-23):
   no rato firma o traço, e com o cursor lá dentro ganha também o fundo dos
   campos. É só a borda e a superfície, nunca um anel nem uma cor de acção
   (decisão do Jorge, 2026-09-16). */
.cms-search:hover {
  border-color: var(--color-border-strong);
}
.cms-search:focus-within {
  border-color: var(--color-border-strong);
  background: var(--color-surface-alt);
}

.cms-search-icon {
  flex: 0 0 auto;
  font-size: 16px;
  line-height: 1;
  color: var(--color-text-muted);
  /* A lupa não recebe cliques: o cursor de texto da caixa não pode mudar de
     figura a meio, e não há nada para carregar aqui. */
  pointer-events: none;
}

/* O `!important` é contra a regra base dos campos, nesta mesma folha: o
   `input:not([type="checkbox"])…`, com dez exclusões, pesa mais do que
   qualquer par de classes e não há como a vencer por especificidade. Sem ele
   o campo voltava a trazer moldura, fundo e 34px de altura para dentro de uma
   caixa que já os tem, e ficava lá um retângulo de cantos direitos.
   Quem desenha aqui é a caixa; o campo é só o texto. */
.cms-search .cms-search-input {
  flex: 1 1 auto;
  align-self: stretch;
  width: auto !important;
  min-width: 0;
  height: auto !important;
  margin: 0;
  padding: 0 !important;
  border: 0 !important;
  border-radius: 0;
  background: transparent !important;
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  /* O foco lê-se na caixa, e o `outline` transparente guarda o lugar para
     quem force o contraste no sistema operativo. */
  outline: 2px solid transparent;
  transition: none;
}

.cms-search-clear {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  /* 24px é o mínimo do alvo (WCAG 2.2, 2.5.8); o `x` lá dentro tem 13px. */
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--color-text-muted);
  cursor: pointer;
}
.cms-search-clear:hover,
.cms-search-clear:focus-visible {
  background: var(--color-surface);
  color: var(--color-text);
  outline: 2px solid transparent;
}
.cms-search-clear .ph-icon { font-size: 13px; }
/* O `display` de uma classe vence o `hidden` do elemento, e o botão ficava no
   ecrã com a caixa vazia. É a armadilha que já custou o véu da paleta de
   procura (`.gs-overlay[hidden]`, no header.css). */
.cms-search-clear[hidden] { display: none; }

/* A linha toda em qualquer largura: uma pesquisa que É o conteúdo da faixa (a
   lista do Diretório, o modal dos contactos), e não um filtro ao lado. */
.cms-search--block {
  width: 100%;
  max-width: none;
}

/* A escala `.small` dos controlos, para um cabeçalho de painel ou um modal.
   A folga da direita quase desaparece porque o botão de limpar continua a ter
   os 24px de alvo que a WCAG pede, dentro de uma caixa de 26px. */
.cms-search--sm {
  gap: 8px;
  height: var(--height-btn-sm);
  padding: 0 2px 0 9px;
  border-radius: var(--radius-control-sm);
}
.cms-search--sm .cms-search-icon { font-size: 14px; }

/* A escala GRANDE, para a caixa que É a página e não um filtro ao lado de uma
   lista (decisão do Jorge, 2026-09-25, para a Knowledge Base). A altura é a do
   botão grande, que é a escada que a casa já tem escrita, e o raio é o dos
   controlos grandes. Sem isto a procura da KB ficava na altura de um filtro,
   34px, e a alternativa era CSS próprio do módulo, que é o que a casa recusa.
   É variante do componente, e não uma segunda anatomia: a lupa, o contorno, o
   foco na caixa e o `x` de limpar continuam a ser os mesmos. */
.cms-search--lg {
  gap: 12px;
  height: var(--height-btn-lg);
  padding: 0 8px 0 14px;
  border-radius: var(--radius-control-lg);
}
.cms-search--lg .cms-search-icon { font-size: 18px; }
/* O `!important` é contra a regra base dos campos, nesta mesma folha, que pesa
   mais do que qualquer par de classes: sem ele o texto ficava a 13px dentro de
   uma caixa de 44px. É a mesma razão do `.cms-search .cms-search-input`. */
.cms-search--lg .cms-search-input,
.cms-search--lg .cms-search-input::placeholder { font-size: var(--text-md) !important; }

/* ══════════════════════════════════════════════════════════════════════════
   A BARRA DE FERRAMENTAS DE UMA VISTA (regra do Jorge, 2026-09-22)

   A fila de controlos que fica por cima de uma lista: a pesquisa, as pastilhas
   de vista e de filtro, os selects do âmbito e os botões de acção.

   A REGRA É A ALTURA. Todo o controlo que entra numa barra tem a MESMA, e é
   por isso que as bordas de cima e de baixo alinham de peça para peça. Antes
   desta regra cada peça trazia a altura do seu próprio padding: no Diretório a
   pesquisa media 34px ao lado de pastilhas de 36,8px, e nos Pedidos havia três
   alturas na mesma linha (32, 34 e 38). Centradas, sim, mas com as bordas em
   sítios diferentes, que é o que se lê como desalinhado.

   A altura NÃO é nova: é o `--height-input` do `tokens.css`, que já é a do
   campo de texto e a do botão médio (`--height-btn-md`). O que faltava era os
   componentes cumprirem a escada que já estava escrita.

   <div class="cms-toolbar">
     <div class="cms-search">…</div>
     <div class="cms-pills" data-cms-pills="segmented">…</div>
     <div class="cms-toolbar-group cms-toolbar-end">
       <button class="bt-outline normal neutral">Exportar</button>
       <button class="bt-filled normal primary">Novo</button>
     </div>
   </div>

   A classe é só a fila e a altura. O desenho próprio de uma barra (o fundo, a
   moldura, a margem, a ordem das peças) continua na folha de quem a usa, e
   ganha, porque essas folhas carregam depois desta.
   ══════════════════════════════════════════════════════════════════════════ */
.cms-toolbar {
  /* A altura de tudo o que lá entra. Fica como variável para uma barra dentro
     de um modal ou de um cabeçalho de painel poder pedir a escala `.small`
     (`--toolbar-h: var(--height-btn-sm)`) sem uma segunda anatomia. */
  --toolbar-h: var(--height-input);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  /* O que não cabe passa para a linha de baixo, que esconder uma acção é a
     última saída (CLAUDE.md §0). */
  flex-wrap: wrap;
  column-gap: var(--space-3);
  row-gap: var(--space-2);
  min-width: 0;
}

/* A ALTURA, UMA SÓ. A lista dos controlos é explícita de propósito: um `> *`
   dava 34px de altura a um texto de contagem, que não é um controlo e não se
   alinha por borda nenhuma.

   Por DESCENDENTE e não por filho: uma barra envolve quase sempre as acções
   num grupo, e um botão dentro de um invólucro qualquer escapava à regra sem
   dar sinal. As peças que vivem DENTRO de outro controlo ficam de fora, que
   essas têm a medida do dono: o `x` da pesquisa tem os 24px de alvo que a WCAG
   pede, e as pastilhas preenchem a moldura delas.

   O seletor (`.dropdown`, do CustomDropdown e do DateRangePicker) é um
   controlo e entra na lista, e tudo o que vive DENTRO dele fica de fora: a
   caixa de procura da lista aberta, o «x» das etiquetas e os atalhos do
   calendário do período. Sem isto o seletor media 36px vazio e 38 com um valor
   ao lado dos 34 da barra, e a regra chegava lá dentro: a procura da lista
   ganhava 34px e os atalhos do calendário perdiam os 40 que a folha dele lhes
   dá (medido no Desempenho das atividades, 2026-09-26). A exclusão vai num
   `:where()` para a regra continuar a pesar o mesmo que pesava. */
.cms-toolbar .cms-search,
.cms-toolbar .cms-pills,
.cms-toolbar .cms-tags,
.cms-toolbar .dropdown,
.cms-toolbar button:not(.cms-search-clear):not(.cms-pill):where(:not(.dropdown *)),
.cms-toolbar select:where(:not(.dropdown *)),
.cms-toolbar input:not(.cms-search-input):where(:not(.dropdown *)),
.cms-toolbar .form-control:where(:not(.dropdown *)) {
  height: var(--toolbar-h);
  min-height: var(--toolbar-h);
}

/* O cabeçalho do seletor enche a caixa por dentro da borda dela (1px em cima
   e 1px em baixo), numa linha só. Com o respiro de 8px em cima e em baixo que
   o seletor tem fora de uma barra, uma etiqueta de 20px empurrava-o para 36px.
   Várias escolhas que não cabem na linha encolhem, e o nome de cada uma corta
   com reticências (ver o `customdropdown.css`): partida em duas linhas, a
   escolha de três pessoas punha o seletor a 64px num telemóvel. */
.cms-toolbar .dropdown > .dropdown-header {
  min-height: calc(var(--toolbar-h) - 2px);
  padding-block: 0;
  flex-wrap: nowrap;
}

/* As pastilhas têm a moldura por fora e os botões por dentro: a altura vai à
   moldura, e cada pastilha fica com o que sobra do `padding` dela. */
.cms-toolbar .cms-pills .cms-pill { height: 100%; }

/* Um grupo de peças que andam juntas (as acções à direita, um par de filtros).
   Não ganha altura própria: quem a tem são os controlos lá dentro. */
.cms-toolbar-group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

/* O que se quer encostado à direita leva isto, em vez de um `margin-left: auto`
   escrito à mão em cada página. */
.cms-toolbar > .cms-toolbar-end { margin-left: auto; }

/* Uma fila de atalhos DENTRO de um campo de formulário, por baixo ou por cima
   do controlo (os «Hoje» e «Ontem» da data da visita, na janela de envio da
   proposta, 2026-09-25). A coluna do campo não tem vão entre filhos, e sem isto
   os botões encostavam à caixa. */
.form-col > .cms-toolbar { margin-block: var(--space-2); }

/* MOBILE-FIRST, para quem tem peças largas: no telemóvel cada uma ocupa a sua
   linha, que a 390px é o que cabe, e a partir do tablet volta a ser fila. A
   altura é a mesma nos dois. Não vem por omissão porque muita barra do CMS é
   de botões estreitos, que cabem na linha mesmo a 390px. */
.cms-toolbar--stack {
  flex-direction: column;
  align-items: stretch;
}

@media (min-width: 641px) {
  .cms-toolbar--stack {
    flex-direction: row;
    align-items: center;
  }
  /* A pesquisa é quem cede espaço primeiro, e nunca desaparece: um filtro
     cortado a meio ainda se percebe, uma caixa de procura de 40px não. */
  .cms-toolbar--stack > .cms-search { flex: 1 1 260px; max-width: 420px; }
}

/* Caminho (breadcrumb): onde se está, com o último a negro e sem ligação. */
.cms-breadcrumb {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.cms-breadcrumb li { display: inline-flex; align-items: center; gap: 6px; }
.cms-breadcrumb li + li::before { content: "\203A"; color: var(--color-text-muted); }
.cms-breadcrumb a { color: var(--color-text-muted); text-decoration: none; }
.cms-breadcrumb a:hover { color: var(--color-text); text-decoration: underline; }
.cms-breadcrumb li[aria-current="page"] { color: var(--color-text); font-weight: var(--weight-medium); }

/* Passos (stepper): os feitos com visto, o actual a negro com o número acentuado.
   Só para FORMULÁRIOS com passos validados (regra do Jorge, 2026-09-26): o
   ESTADO de um registo lê-se na `.cms-path`, mais abaixo. */
.cms-stepper {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--color-text-muted);
}
.cms-step { display: inline-flex; align-items: center; gap: 8px; }
.cms-step + .cms-step::before {
  content: "";
  width: 32px;
  height: 1px;
  background: var(--color-border);
}

/* A FITA DE PASSOS ROLA NUM ECRÃ ESTREITO, e não sai dele.

   Cinco passos com os traços de 32px medem cerca de 600px, e num ecrã de 390
   o quarto e o quinto ficavam 213px para lá da borda: o documento ganhava
   scroll horizontal, que o CLAUDE.md §0 proíbe, e o passo em que o pedido
   está podia ser um dos que não se viam. Acontecia no editor de financiamento
   (os cinco estados da `financing_statuses`) e na peritagem de uma retoma (o
   circuito dela), que são as duas páginas com cinco passos.

   A variante em bloco já rolava; faltava esta, e a receita é a mesma do
   `.fiche-now-btns`. Acima dos 641px nada muda: a fita cabe e o `overflow`
   não tem para onde rolar. Encontrado ao medir a maqueta de 2026-09-25. */
@media (max-width: 640px) { /* nat-ok: o estado de desktop é o que o componente já tem, sem media query */
  .cms-stepper {
    overflow-x: auto;
    /* Sem isto o eixo vertical passa a `auto` sozinho e a roda do rato morre
       aqui dentro em vez de passar à página. */
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    padding-right: 16px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
            mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
  }
  .cms-stepper::-webkit-scrollbar { display: none; }
  .cms-step { flex: 0 0 auto; white-space: nowrap; }
}
.cms-step-num {
  display: inline-grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border: 1px solid var(--color-border);
  border-radius: 50%;
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}
.cms-step.is-done .cms-step-num { border-color: var(--color-primary); color: var(--color-primary-text); }
.cms-step.is-current { color: var(--color-text); font-weight: var(--weight-medium); }
.cms-step.is-current .cms-step-num { border-color: var(--color-primary); background: var(--color-primary-subtle); color: var(--color-primary-text); }
.cms-step-num .ph-icon { font-size: 13px; }

/* ── A BARRA DO ESTADO DE UM REGISTO (.cms-path) ───────────────────────────
   Em que estado está um registo e por onde passou: segmentos em seta, o de
   agora cheio, os feitos com o visto, os que faltam apagados.

   REGRA DA CASA (Jorge, 2026-09-26): a fita de passos com círculos
   (`.cms-stepper`) é só para FORMULÁRIOS com passos validados, como o «Criar
   pedido» dos Pedidos; o ESTADO de um registo (uma oportunidade, uma cotação,
   um financiamento, uma retoma, a conta no funil) lê-se nesta barra. É o
   desenho da primeira ficha da oportunidade (a «macro-barra Em curso/Fechado»
   de julho de 2026), refeito com os tokens.

   <ol class="cms-path" aria-label="Estado da oportunidade">
     <li class="cms-path-seg is-done"><span class="cms-path-label">…</span></li>
     <li class="cms-path-seg is-current is-green" aria-current="step">
       <span class="cms-path-label">Ganho</span>
       <span class="cms-path-sub">a 25/09/2026</span>
     </li>
   </ol>

   O de agora pinta com o azul; um desfecho pinta com a cor do estado dele, a
   mesma dos selos (`is-green`, `is-red`, `is-neutral`). A tinta é branca, e
   por isso o fundo é o forte de cada cor, como nos botões cheios (a regra de
   2026-09-19: a cor que pinta nunca é a que escreve).

   MOBILE-FIRST: num ecrã estreito a barra ROLA, e cada segmento corta o
   texto com reticências em vez de quebrar a linha; a partir dos 641px os
   segmentos repartem a largura por igual. O `utils/Caminho.js`
   (`mostrarEstadoAtual`) traz o segmento de agora para dentro da barra e põe
   o degradê nas pontas por onde ainda há estado para ver. */
.cms-path {
  --path-esbate: 24px;
  display: flex;
  gap: 3px;
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
  overflow-x: auto;
  /* Sem isto o eixo vertical passa a `auto` sozinho, e a roda do rato morre
     aqui dentro em vez de passar à página (CLAUDE.md §3). */
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.cms-path::-webkit-scrollbar { display: none; }
/* As pontas esbatem do lado por onde ainda há estado, com as classes que o
   `utils/Caminho.js` põe e tira conforme o scroll: o mesmo recurso das Pills
   e da barra de separadores. Sem sobra de um lado, sem esbatimento desse
   lado, senão o primeiro segmento nascia apagado sem razão. */
.cms-path.is-edge-left:not(.is-edge-right) {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--path-esbate), #000 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 var(--path-esbate), #000 100%);
}
.cms-path.is-edge-right:not(.is-edge-left) {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - var(--path-esbate)), transparent 100%); /* nat-ok: hífen de subtração no calc() */
          mask-image: linear-gradient(to right, #000 0, #000 calc(100% - var(--path-esbate)), transparent 100%); /* nat-ok: hífen de subtração no calc() */
}
.cms-path.is-edge-left.is-edge-right {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--path-esbate), #000 calc(100% - var(--path-esbate)), transparent 100%); /* nat-ok: hífen de subtração no calc() */
          mask-image: linear-gradient(to right, transparent 0, #000 var(--path-esbate), #000 calc(100% - var(--path-esbate)), transparent 100%); /* nat-ok: hífen de subtração no calc() */
}
.cms-path-seg {
  --path-ponta: 12px;
  flex: 1 0 auto;
  min-width: 7.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-height: 40px;
  padding: 4px calc(var(--path-ponta) + 10px);
  background: var(--color-surface-alt);
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  line-height: 1.25;
  text-align: center;
  clip-path: polygon(0 0, calc(100% - var(--path-ponta)) 0, 100% 50%, calc(100% - var(--path-ponta)) 100%, 0 100%, var(--path-ponta) 50%); /* nat-ok: hífen de subtração no calc() */
}
.cms-path-seg:first-child {
  padding-left: 14px;
  clip-path: polygon(0 0, calc(100% - var(--path-ponta)) 0, 100% 50%, calc(100% - var(--path-ponta)) 100%, 0 100%); /* nat-ok: hífen de subtração no calc() */
}
.cms-path-seg:last-child {
  padding-right: 14px;
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--path-ponta) 50%);
}
.cms-path-seg:only-child { clip-path: none; }
.cms-path-label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cms-path-label .ph-icon { flex: 0 0 auto; font-size: 14px; }
.cms-path-sub {
  max-width: 100%;
  font-size: var(--text-2xs);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cms-path-seg.is-done { background: var(--color-primary-subtle); color: var(--color-primary-text); }
.cms-path-seg.is-current { background: var(--color-primary); color: #fff; } /* ds-ok: o par do .bt-filled.primary, a tinta branca sobre o azul que pinta */
.cms-path-seg.is-current.is-green { background: var(--color-success-strong); }
.cms-path-seg.is-current.is-red { background: var(--color-danger-strong); }
.cms-path-seg.is-current.is-neutral { background: var(--color-slate); }
@media (min-width: 641px) {
  .cms-path-seg { flex: 1 1 0; }
}

/* ── A BARRA DE UMA FICHA (.fiche-bar) ─────────────────────────────────────
   A `.page-action-bar` de uma ficha de REGISTO: à esquerda a identidade
   (`.page-title-block`, como em todas as páginas do CMS), à direita o que se
   faz a seguir; o estado (`.cms-path`) fica no primeiro bloco da coluna.
   Decidido a 2026-09-26, pedido na cotação e levado às quatro fichas com este
   desenho (a oportunidade, a cotação, o financiamento e a retoma). De 25 a 26
   foi o contrário, com o estado na barra; as regras do `.cms-path` aqui dentro
   saíram quando a última ficha, a do financiamento, passou.

   <header class="page-action-bar fiche-bar">
     <div class="page-title-block">…</div>
     <div class="page-action-bar-actions" role="group" aria-label="O passo seguinte">…</div>
   </header>

   Nasceu no `manage-deal.css` a 2026-09-25 («os botões devem passar para a
   page-action-bar», o Jorge) e subiu aqui a 2026-09-26, quando a ficha do
   financiamento e a da peritagem passaram ao mesmo desenho: uma barra que
   três fichas usam é componente, não CSS de um módulo.

   MOBILE-FIRST: no telemóvel os botões vão numa fila que ROLA, com degradê à
   direita a dizer que há mais. São três no estado apresentado de uma cotação,
   e empilhados custavam uma linha cada. É o padrão da `.chat-header-right`. A
   partir dos 641px a fila deixa de rolar e os botões ficam à direita, inteiros. */
.fiche-bar > .page-action-bar-actions {
  flex: 1 1 100%;
  min-width: 0;
  overflow-x: auto;
  /* Sem isto o eixo vertical passa a `auto` sozinho e a roda do rato morre
     aqui dentro em vez de passar à página (CLAUDE.md §3). */
  overflow-y: hidden;
  scrollbar-width: none;
  padding-right: 16px;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
          mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
}
.fiche-bar > .page-action-bar-actions::-webkit-scrollbar { display: none; }
.fiche-bar > .page-action-bar-actions > * { flex: 0 0 auto; }
/* Sem passo seguinte não há faixa vazia a ocupar uma linha. */
.fiche-bar > .page-action-bar-actions:empty { display: none; }
@media (min-width: 641px) {
  .fiche-bar > .page-action-bar-actions {
    flex: 0 0 auto;
    overflow: visible;
    padding-right: 0;
    -webkit-mask-image: none;
            mask-image: none;
  }
}

/* ═══ O CORPO DE UMA FICHA DE REGISTO ══════════════════════════════════════
   Duas colunas, a página parada e a identidade no primeiro bloco: o desenho
   que o Jorge fechou a 2026-09-25 na ficha da oportunidade («deve funcionar
   como a ficha de um pedido do /support») e que a da cotação, a do
   financiamento e a da peritagem seguem.

   Subiu do `manage-deal.css` a 2026-09-26. A razão é a de sempre: a ficha do
   financiamento tinha a mesma grelha escrita outra vez com outros nomes
   (`.fe-body`, `.fe-work`), e duas maneiras de desenhar a mesma coisa é um
   defeito mesmo quando as duas estão certas.

   ATENÇÃO À ORDEM. Este bloco vinha de uma folha de módulo, que carrega ANTES
   desta, e por isso vários selectores dele levam quatro e cinco classes para
   vencerem as regras do padrão de página. Aqui em baixo o peso já não é o que
   os faz ganhar, e sim a POSIÇÃO: têm de ficar depois do bloco do
   `.page-content`, que acaba por volta da linha 6080. Deixam-se como estavam,
   porque tirar-lhes classes obrigaria a medir de novo cada uma das três
   fichas para ganhar nada.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── O CORPO DE UMA FICHA ─────────────────────────────────────────────────
   Uma coluna no telemóvel, duas a partir dos 1025px com a de detalhes a
   320px. É a grelha do /support, e 1025 é o degrau canónico do projeto. */
.fiche-body { display: flex; flex-direction: column; min-width: 0; }
.fiche-main {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-width: 0;
    /* O respiro é da COLUNA e não do cartão, porque o aside encosta às bordas
       e o filete entre as duas tem de chegar aos dois extremos. */
    padding: var(--space-3) var(--page-pad, 24px) var(--space-4);
}

/* O corpo vai de borda a borda do cartão, e o cartão acaba 16px acima do
   rodapé, como o de um pedido do /support.

   CINCO CLASSES, e não quatro. Esta folha carrega ANTES do `components.css`
   global, e a regra da casa («O corpo de uma página SEM separadores») pesa
   quatro: com o mesmo peso ganhava ela, e este `padding: 0` nunca se aplicou
   desde 2026-09-24. Com a página a rolar só se via um fundo mais comprido; com
   a página parada (2026-09-25) eram 32px mortos por baixo das duas colunas. */
.app .page-panel > .panel-child-element > .page-content:has(> .fiche-body) {
    padding: 0;
    margin-bottom: var(--space-4);
}

/* Uma lista dentro de uma COLUNA não sangra. O componente manda-a de borda a
   borda do cartão, o que está certo numa página de uma coluna e errado aqui:
   a margem negativa metia-a por baixo do aside. O selector da casa tem quatro
   classes, e por isso este leva as mesmas. */
.app .page-panel .fiche-main .wrap > .ptbl-root,
.app .page-panel .fiche-main .ov-chart-block > .ptbl-root { margin-inline: 0; }

/* ── A PÁGINA NÃO ROLA (Jorge, 2026-09-25) ─────────────────────────────────
   «Esta página não deve ter scroll, deve funcionar como a ficha de um pedido
   do /support.» O `.content` fica parado, o painel mede a janela menos o
   cabeçalho, o rodapé e a barra de cima (`--page-bar-real-h`, que a layout.js
   publica com a altura REAL dela), e quem rola é o que está lá dentro:

   - no telemóvel, o corpo inteiro, com a coluna e os detalhes empilhados. Dois
     scrollers encaixados prendiam a roda ao de dentro;
   - a partir dos 1025px, a coluna e o aside, cada um por si.

   A marca é o próprio corpo da ficha (`:has(.fiche-body)`), e não uma classe
   posta por JS: o SPA troca o `#main-container` inteiro, e a regra sai com a
   página sem ninguém a ter de tirar. */
.app .content:has(> .page-panel .fiche-body) { overflow: hidden; }
/* O `flex: 1 1 auto` é o que faz o painel caber, e não o cálculo: o `.content`
   é uma coluna flex, e o painel encolhe até ao espaço que sobra, faixas que o
   cálculo não conhece incluídas (a do ambiente local tem 26px). Sem ele o
   painel ficava com a altura do cálculo e passava por baixo do rodapé. É o
   mesmo par da ficha de um pedido (`#support-app`). */
.app .content:has(> .page-panel .fiche-body) > .page-panel {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: calc(100dvh - var(--header-height) - var(--footer-height) - var(--page-bar-real-h, 0px)); /* nat-ok: subtrações do calc() */
}
.app .page-panel:has(.fiche-body) > .panel-child-element,
.app .page-panel > .panel-child-element > .page-content:has(> .fiche-body) {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.fiche-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
}

/* Os blocos de uma coluna que ROLA não encolhem: numa coluna flex de altura
   fixa, um filho com recorte (o `.ov-chart-block` recorta os cantos) encolhia
   até zero para caber, e a identidade da cotação desaparecia por baixo do
   painel amarelo sem erro nenhum. */
.fiche-body > .fiche-main > * { flex-shrink: 0; }

/* A nav dos separadores cola ao cimo de QUEM ROLA, que deixou de ser o
   `.content`. O `top` da casa conta com a barra de cima por cima dela, e aqui
   a barra já está fora do scroller: com ele a nav colava a meio da coluna. */
.app .fiche-main > .page-sticky-tabs > .full-width-tabs-nav { top: 0; }
/* A partir dos 1025px quem rola é a própria coluna, e o `sticky` conta a partir
   do respiro de cima dela: a nav colava 12px abaixo da borda e o conteúdo via-se
   a passar por cima dela. O `top` negativo devolve-a à borda. */
@media (min-width: 1025px) {
    .app .fiche-main > .page-sticky-tabs > .full-width-tabs-nav { top: calc(-1 * var(--space-3)); }
}

@media (min-width: 1025px) {
    .fiche-body { flex-direction: row; align-items: stretch; overflow: hidden; }
    .fiche-main {
        flex: 1 1 auto;
        min-width: 0;
        min-height: 0;
        overflow-y: auto;
        overflow-x: hidden;
        overscroll-behavior: contain;
    }
    .fiche-body > .cms-aside {
        flex: 0 0 320px;
        width: 320px;
        min-width: 320px;
        max-width: 320px;
        min-height: 0;
        overflow-y: auto;
        overflow-x: hidden;
        overscroll-behavior: contain;
        border-top: 0;
        border-left: 1px solid var(--color-border);
    }
}
/* Entre os 1025 e os 1100 o aside encolhe antes de a coluna do preço ficar
   estreita de mais para uma linha de rótulo e valor. É o degrau que o
   /support também usa. */
@media (min-width: 1025px) and (max-width: 1100px) { /* nat-ok: dual exacto do 1101, que não é degrau canónico; a medida é a do /support */
    .fiche-body > .cms-aside { flex-basis: 280px; width: 280px; min-width: 280px; max-width: 280px; }
}

/* O cartão dos separadores, dentro de uma coluna. São dois os cartões do
   padrão de página, e o segundo é o que embrulha os separadores e o corpo; a
   regra da casa procura-o em `.page-panel > * > .page-sticky-tabs`, e numa
   ficha de duas colunas ele fica três níveis abaixo. Sem contorno, como
   todos.

   SEM `overflow: hidden`, e isto não é descuido: um contexto de recorte entre
   um elemento `sticky` e o scroller impede o sticky de funcionar, e é o que
   está escrito no `components.css` sobre o `.default-full-panel`. Com ele, a
   nav dos separadores deixava de colar e o cabeçalho da tabela passava-lhe à
   frente. Os cantos tratam-se peça a peça. */
.fiche-main > .page-sticky-tabs.full-width-tabs {
    margin: 0;
    background: var(--color-surface-alt);
    border: 0;
    border-radius: var(--radius-lg);
}
.fiche-main > .page-sticky-tabs > .full-width-tabs-nav {
    background: var(--color-surface-alt);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
}
/* O último painel fecha os cantos de baixo do cartão. */
.fiche-main > .page-sticky-tabs > .full-width-tab-panel {
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}

/* A acção que vive no cabeçalho de um bloco, encostada à direita. */
.fiche-head-act { margin-left: auto; }
.fiche-head-act[hidden] { display: none; }

/* ── O PRIMEIRO BLOCO DA COLUNA (.fiche-now) ──────────────────────────────
   Desde 2026-09-26 leva o ESTADO (`.cms-path`), e a identidade voltou à
   barra. De 25 a 26 levou a identidade: as regras do `.page-title` aqui
   dentro ficam para a ficha que ainda a tenha (o financiamento). O título
   guardava o tamanho que tinha na barra: a regra da casa prende-o à
   `.page-action-bar`, e solto seria um cabeçalho de secção.

   O bloco da decisão que vivia ao lado dela (`.fiche-now-act`) saiu no mesmo
   dia, a pedido do Jorge: os botões passaram para a barra de cima, e o que
   ele explicava diz-se no painel amarelo, quando há o que dizer. */
.fiche-now { display: flex; flex-direction: column; gap: var(--space-3); padding: var(--space-4); }
.fiche-now > .page-title-block { flex: 1 1 auto; min-width: 0; }
.fiche-now .page-title {
    margin: 0;
    font-size: var(--text-xl);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* Progresso: a barra e, se houver, a legenda por baixo. Sem cor decorativa. */
.cms-progress {
  position: relative;
  height: 6px;
  border-radius: var(--radius-full);
  background: var(--color-surface-alt);
  overflow: hidden;
}
.cms-progress-bar {
  height: 100%;
  border-radius: inherit;
  background: var(--color-primary);
  transition: width var(--dur-md) var(--ease-out);
}
.cms-progress.is-success .cms-progress-bar { background: var(--color-success); }
.cms-progress.is-warning .cms-progress-bar { background: var(--color-warning); }
.cms-progress.is-indeterminate .cms-progress-bar {
  width: 40%;
  animation: cms-progress-varre 1.4s var(--ease-in) infinite;
}

/* ── A FILA DE TRABALHO ────────────────────────────────────────────────────
   Uma lista curta de coisas que esperam por alguém, em que cada linha é uma
   FRASE e não uma grelha de factos: «Jorge Nogueira, há 2 horas, pede 83 % em
   vez de 70 %». Desenho da maqueta dos três circuitos (2026-09-24), autorizado
   pelo Jorge para o CMS.

   NÃO SUBSTITUI O `TabularData`, e a diferença não é de gosto. Uma LISTA tem
   registos que se ordenam, se filtram e se procuram, e é o widget que a serve,
   com tudo o que ele traz. Uma FILA tem trabalho à espera: são poucas linhas,
   leem-se de uma vez, e o que se faz a cada uma está ali. À volta de três
   pedidos, uma barra de procura, um cabeçalho de colunas e um «1 a 3 de 3» é
   mais cromo do que conteúdo, e uma coluna cujo conteúdo é uma frase inteira
   desfaz o alinhamento de que uma tabela vive.

   Regra para quem escolher: se a pergunta é «onde está o registo X», é lista;
   se é «o que falta fazer», é fila. */
.cms-queue { display: flex; flex-direction: column; }
.cms-queue-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: 12px 0;
  border-bottom: 1px solid var(--color-border);
}
.cms-queue-row:last-child { border-bottom: 0; }
/* O corpo cresce e os outros não: é ele que absorve a largura que sobra, e é
   por isso que o valor e as acções ficam encostados à direita sem `auto`. */
.cms-queue-body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1 1 220px;
}
.cms-queue-body b {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cms-queue-body small { font-size: var(--text-xs); color: var(--color-text-muted); }
.cms-queue-num {
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}
.cms-queue-acts { display: flex; gap: 6px; }
/* NO TELEMÓVEL, que é a base, a linha empilha: o badge em cima (o que é), o
   corpo a ocupar a largura toda, e o valor com as acções na mesma fila em
   baixo. Nada se esconde, e o que se faz fica sempre à vista. */
.cms-queue-row > .status-badge { flex: 0 0 auto; }
.cms-queue-body { flex: 1 1 100%; order: 2; }
.cms-queue-num { order: 3; }
.cms-queue-acts { order: 4; margin-left: auto; }

/* Num ecrã que dê para a linha inteira, as peças voltam à ordem natural. */
@media (min-width: 481px) {
  .cms-queue-body { flex: 1 1 220px; order: 0; }
  .cms-queue-num { order: 0; }
  .cms-queue-acts { order: 0; margin-left: 0; }
}

/* UMA FILA COM GRAU (maqueta do /server, 2026-09-25). Quando o badge diz a
   GRAVIDADE (Crítico, Atenção) ou o motivo (Parada, Bloqueada), as palavras
   têm comprimentos diferentes e o título de cada linha começava num sítio:
   medido a 1440, 26px entre «Crítico» e «Informação». Numa fila que se lê de
   cima para baixo, os títulos alinhados deixam ler a coluna de frases de uma
   vez. No telemóvel o badge sobe para cima do título, e a largura mínima não
   faz mal nenhum. */
.cms-queue--grau > .cms-queue-row > .status-badge:first-child { min-width: 96px; justify-content: center; }

/* A HORA NUMA FILA DE TRABALHO (`.cms-queue--agenda`, maqueta da Visão Geral
   de Atividades, 2026-09-26). Uma fila de atividades lê-se pela HORA, e a hora
   é o número da linha: o `.cms-queue-num`, em algarismos tabulares. Mas vai à
   FRENTE, como numa agenda, e não à direita, como o valor numa fila de
   financiamentos: a 1440 a hora ficava a 1.000px da frase que a pede
   (medido), e o olho fazia a viagem em cada linha. No telemóvel fica onde a
   fila a põe, em baixo, ao lado das acções. A largura mínima alinha as horas
   umas por baixo das outras; numa coluna de datas todas levam o ano, para
   terem a mesma largura. */
.cms-queue--agenda .cms-queue-num { min-width: 5ch; }
@media (min-width: 481px) {
  .cms-queue--agenda .cms-queue-num { order: -1; }
}
/* A nota de uma atividade numa linha só: é a primeira linha do relatório, e o
   relatório inteiro lê-se dentro da atividade. Sem isto uma nota de três
   linhas fazia uma linha da fila três vezes mais alta do que as outras. */
.cms-queue--agenda .cms-queue-body small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── A RÉGUA: um progresso com LIMIAR ──────────────────────────────────────
   A variante da `.cms-progress` para quando não chega dizer quanto já vai:
   é preciso dizer ONDE ESTÁ A FRONTEIRA. Nasceu na maqueta dos três circuitos
   de uma cotação (2026-09-24), onde a mesma régua responde a três perguntas
   conforme o sítio em que o desconto cai: dentro da alçada do comercial, acima
   dela mas ainda dentro da margem, ou já para lá da margem toda.

   Não é um gráfico: é o mesmo progresso de sempre, mais alto para o limiar
   caber, com a marca por fora e a legenda por baixo. A regra dos três gráficos
   da casa (bandas, sankey, donut) fala de gráficos, e uma barra de progresso
   não o é: mostra uma grandeza contra o total dela, e não compara séries.

   SEM `overflow: hidden`, ao contrário da base: a marca do limiar sobe e desce
   para fora da barra de propósito, e o rótulo dela vive por baixo. Com recorte,
   os dois desapareciam, e o limiar é justamente o que esta régua tem de dizer.
   O raio vai ao que é PINTADO, que é quem precisa dele. */
.cms-progress--gauge {
  height: 22px;
  overflow: visible;
  margin-bottom: 24px;   /* o rótulo do limiar vive aqui */
}
.cms-progress--gauge .cms-progress-bar { border-radius: var(--radius-full); }
.cms-progress-bar.is-over { background: var(--color-red); }
.cms-progress--gauge .cms-progress-bar.is-warning,
.cms-progress.is-warning .cms-progress-bar { background: var(--color-orange); }

/* A MARCA DO LIMIAR. O rótulo vem do `data-label`, para o markup dizer o que a
   linha significa sem um elemento a mais. */
.cms-progress-limit {
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 2px;
  background: var(--color-text);
}
.cms-progress-limit::after {
  content: attr(data-label);
  position: absolute;
  left: 50%;
  bottom: -20px;
  transform: translateX(-50%);
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  white-space: nowrap;
}

/* O SEGUNDO SEGMENTO: o que vem de fora da grandeza medida (na cotação, o apoio
   que a direção põe além da margem). Os dois somam a barra inteira. */
.cms-progress--gauge.is-split { display: flex; gap: 2px; }
.cms-progress--gauge.is-split > .cms-progress-bar { border-radius: var(--radius-full) 0 0 var(--radius-full); }
.cms-progress-extra {
  height: 100%;
  border-radius: 0 var(--radius-full) var(--radius-full) 0;
  background: var(--color-green);
}

/* A legenda, que diz de que cor é o quê. */
.cms-progress-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.cms-progress-legend > span { display: inline-flex; align-items: center; gap: 5px; }
.cms-progress-legend i {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: var(--radius-full);
}
@keyframes cms-progress-varre {
  from { transform: translateX(-100%); }
  to   { transform: translateX(260%); }
}
@media (prefers-reduced-motion: reduce) {
  .cms-progress.is-indeterminate .cms-progress-bar { animation: none; width: 100%; opacity: .5; }
}
.cms-progress-label {
  margin: 6px 0 0;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
  font-variant-numeric: tabular-nums;
}

/* Dica (tooltip): o texto vem de data-tooltip; aparece ao passar e ao focar.
   Não substitui o rótulo: um botão só com ícone continua a levar aria-label. */
[data-tooltip] { position: relative; }
[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 6px);
  transform: translateX(-50%);
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  background: var(--color-text);
  color: var(--color-bg);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease-out);
  z-index: var(--z-dropdown);
}
[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after { opacity: 1; }

/* Avatares empilhados: iniciais ou fotografia, o último diz quantos faltam. */
.cms-avatars { display: inline-flex; align-items: center; }
.cms-avatar {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 2px solid var(--color-surface);
  border-radius: 50%;
  background: var(--color-surface-alt);
  color: var(--color-text);
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: .02em;
  overflow: hidden;
}
.cms-avatar + .cms-avatar { margin-left: -8px; }
.cms-avatar img { width: 100%; height: 100%; object-fit: cover; }
.cms-avatar.is-more { color: var(--color-text-muted); }
/* A cara sozinha, a identificar uma pessoa e não a resumir uma lista: nessa
   escala 28px não chega para se distinguir quem é. Usa-a a carteira por
   comercial da Visão Geral de Oportunidades, onde a fila é de caras e não de
   nomes, e por isso a fotografia tem de dar para reconhecer quem é. */
.cms-avatar.is-lg { width: 52px; height: 52px; font-size: var(--text-md); }

/* ======================== NOME COM FRASE DE ESTADO ======================== */
/* Uma linha só: o nome da pessoa e, à frente, a frase de estado que ela escreveu.
   Vive aqui e não no CSS do chat porque serve dois contextos com folhas
   diferentes: o widget flutuante (presente em todas as páginas) e a página
   /chat. O components.css é a única folha que os dois já têm sem esperar.

   O nome nunca se sacrifica pela frase: cede espaço até ao seu próprio limite e
   só depois corta. A frase ocupa o que sobra e, quando não cabe, passa da direita
   para a esquerda em ciclo, como a barra de notícias do cabeçalho. A decisão de
   passar é medida em JS (`_initMoodMarquees` em chat-utils.js), porque o CSS não
   sabe comparar o texto com o espaço que lhe deram. */
.cms-nameline {
  display:     flex;
  align-items: center;
  gap:         6px;
  min-width:   0;
  line-height: normal;
}
.cms-nameline-name {
  flex:          0 1 auto;
  min-width:     0;
  overflow:      hidden;
  text-overflow: ellipsis;
  white-space:   nowrap;
}
.cms-nameline-sep {
  flex:        0 0 auto;
  color:       var(--color-text-muted);
  font-weight: var(--weight-regular);
}
/* `flex-basis` a zero de propósito: com base automática o comboio de texto
   pedia a sua largura inteira e era o nome que encolhia. */
.cms-nameline-mood {
  flex:        1 1 0%;
  min-width:   0;
  overflow:    hidden;
  font-size:   var(--text-xs);
  font-weight: var(--weight-regular);
  color:       var(--color-text-muted);
}
/* O comboio: uma cópia da frase quando ela cabe, duas quando passa. O `gap` tem
   de ser o mesmo valor que o JS usa para calcular o deslocamento (MOOD_GAP),
   senão o ciclo salta no ponto de emenda. */
.cms-nameline-mood-track {
  display:     flex;
  gap:         24px;
  width:       max-content;
  white-space: pre;
}
/* A frase só passa com o rato em cima (ou com o item focado pelo teclado). Parada,
   lê-se o princípio; quem quiser o resto aponta para lá. Uma lista inteira a
   mexer sozinha rouba a atenção ao que se estava a fazer.

   `data-mood-hover` marca a área que responde ao rato: quem escreve o item diz
   qual é (o item da lista inteiro, o cabeçalho inteiro), porque apontar à linha
   do nome pedia pontaria. Sem essa marca, vale a própria linha. */
:is(.cms-nameline:hover, [data-mood-hover]:hover, [data-mood-hover]:focus-within)
  .cms-nameline-mood.is-scrolling .cms-nameline-mood-track {
  animation:   cms-nameline-mood-scroll var(--mood-dur, 12s) linear infinite;
  will-change: transform;
}
/* Desvanecer nas duas pontas: o texto entra e sai sem ficar cortado a meio de
   uma letra, e a ponta esbatida avisa que há mais frase do que se vê. */
.cms-nameline-mood.is-scrolling {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
          mask-image: linear-gradient(to right, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
}
@keyframes cms-nameline-mood-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-1 * var(--mood-shift, 0px))); }
}

/* Quem pediu menos movimento lê o início da frase, parado. A segunda cópia é
   decorativa e sai. */
@media (prefers-reduced-motion: reduce) {
  :is(.cms-nameline:hover, [data-mood-hover]:hover, [data-mood-hover]:focus-within)
    .cms-nameline-mood.is-scrolling .cms-nameline-mood-track { animation: none; }
  .cms-nameline-mood.is-scrolling .cms-nameline-mood-txt[aria-hidden="true"] { display: none; }
}

/* ══════════════════════════════════════════════════════════════════════════════
   CABEÇALHO COM FACTOS, RODAPÉ COM TOTAL E PASSOS COM SUBTÍTULO
   (maquetas do funil de vendas, 2026-09-11)

   .page-title-block  o título e, por baixo, a linha de factos, dentro da barra
                      de acção
   .page-facts        os factos que se ditam (conta, oportunidade, tipo), com
                      ligação quando há ficha para abrir
   .form-nav-total    o total à esquerda do rodapé de um formulário com preço
   .cms-step-title / .cms-step-sub
                      a variante em bloco do stepper: cada passo traz o nome e
                      um subtítulo (o que ficou escolhido), com o estado no
                      filete de cima. A anatomia em linha (.cms-step-num)
                      fica como está; esta só se aplica quando o passo traz
                      título, e um passo pode ser um <button> (navega para o
                      que já está feito) ou um <div> (só informa).
   ══════════════════════════════════════════════════════════════════════════════ */
/* MOBILE-FIRST. O bloco do título ocupa a linha toda no telemóvel e empurra as
   acções para a linha de baixo, onde elas têm largura para se lerem; a partir
   do tablet volta a partilhar a linha com elas. */
.page-title-block { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1 1 100%; }

/* Os factos numa FILA que rola, e não numa grelha que quebra. Quatro factos
   («63 em aberto», «26 sem resposta», «2 fora do prazo», «1 urgente») davam
   quatro linhas a 390px, e é a diferença entre 18px e 90px de barra. A barra
   nativa esconde-se: o que diz que há mais é o próprio texto cortado na borda,
   e estes factos lêem-se da esquerda para a direita por ordem de urgência. */
.page-facts {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  /* Sem isto o eixo vertical passa a `auto` sozinho, e a fila fica a ser um
     contentor de scroll que não tem para onde rolar: a roda do rato morre aqui
     em vez de passar à página. Ver o `_filas_engolem_a_roda.py`. */
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  gap: 10px;
  font-size: var(--text-xs);
  color: var(--color-text-muted);
}
.page-facts::-webkit-scrollbar { display: none; }
.page-facts > * { flex: 0 0 auto; white-space: nowrap; }

@media (min-width: 641px) {
  .page-title-block { gap: 4px; flex: 0 1 auto; }
  .page-facts {
    flex-wrap: wrap;
    overflow-x: visible;
    gap: 4px 14px;
    font-size: var(--text-sm);
  }
}
.page-facts b { font-weight: var(--weight-medium); color: var(--color-text-secondary); }
/* Sem azul (regra de 2026-09-24): o facto que abre uma ficha lê-se pelo traço
   e pela tinta mais forte do que a do rótulo ao lado. */
.page-facts a {
    color: var(--color-text-secondary);
    text-decoration: underline;
    text-decoration-color: var(--color-border-strong);
    text-underline-offset: 3px;
}
.page-facts a:hover,
.page-facts a:focus-visible { color: var(--color-text); text-decoration-color: currentColor; }
.page-facts > [hidden] { display: none; }

.form-nav-total { margin-right: auto; display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }
.form-nav-total small { font-size: var(--text-xs); color: var(--color-text-muted); }
.form-nav-total b { font-size: var(--text-xl); font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }

/* A VARIANTE EM BLOCO: cada passo com título e subtítulo, e o número gerado
   por contador. A fita rola quando não cabe.

   POR FAZER: o degradê nas pontas. Quem rola sem sinal nenhum não sabe que há
   mais à direita, e uma fita de quatro passos mede 480px de mínimo (112 cada),
   o que já corta numa coluna de trabalho com um aside ao lado. A receita está
   escrita na folha da cotação (`manage-deal.css`, o `.mquote-steps`) e sobe
   para aqui quando se puder medir o efeito nas CINCO páginas que usam esta
   variante: as duas fichas de negócio, o financiamento, a peritagem e a ficha
   de um pedido. O degradê pede `padding-inline` para não esbater a primeira
   letra, e isso desloca a fita em todas elas. */
.cms-stepper:has(> .cms-step > .cms-step-title) {
  gap: 0;
  align-items: stretch;
  overflow-x: auto;
  counter-reset: passo;
}
/* Dentro de um modal o stepper não encosta às paredes (decisão do Jorge,
   2026-09-16): o conteúdo do modal vem a zero de padding por desenho, e os
   passos liam-se colados à borda. */
.modal-content .cms-stepper { padding-inline: var(--space-5); }
.cms-step:has(> .cms-step-title) {
  flex: 1 1 0;
  min-width: 112px;
  /* O subtítulo quebra a linha dentro do passo. O `nowrap` do telemóvel é da
     variante em linha; aqui, com 112px de passo, a data de um passo
     («criada a 24/09/2026 19:07») escrevia-se por cima da do seguinte. */
  white-space: normal;
  padding: 10px 12px 10px 0;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  border: 0;
  border-top: 2px solid var(--color-border);
  border-radius: 0;
  background: none;
  color: var(--color-text-muted);
  font: inherit;
  text-align: left;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.cms-step:has(> .cms-step-title) + .cms-step { padding-left: 12px; }
.cms-step:has(> .cms-step-title) + .cms-step::before { content: none; }
button.cms-step:has(> .cms-step-title) { cursor: pointer; }
button.cms-step:has(> .cms-step-title):hover:not(:disabled) { color: var(--color-text); }
button.cms-step:has(> .cms-step-title):disabled { cursor: default; }
button.cms-step:has(> .cms-step-title):focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring-color); outline-offset: 2px; }
.cms-step-title { display: inline-flex; align-items: center; gap: 6px; font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.cms-step-title::before {
  counter-increment: passo;
  content: counter(passo);
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 1px solid currentColor;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.cms-step-sub { font-size: var(--text-xs); }
.cms-step.is-done { border-top-color: var(--color-green); color: var(--color-text-secondary); }
/* O visto é o glifo do Phosphor; o número continua a contar por baixo dele. */
.cms-step.is-done .cms-step-title::before {
  content: "\e182";
  font-family: "Phosphor";
  font-size: 11px;
  color: var(--color-surface);
  background: var(--color-green);
  border-color: var(--color-green);
}
.cms-step.is-current { border-top-color: var(--color-primary); color: var(--color-text); font-weight: var(--weight-medium); }
.cms-step.is-current .cms-step-title::before { background: var(--color-primary); border-color: var(--color-primary); color: var(--color-surface); }
.cms-step.is-terminal { border-top-style: dashed; }
/* Um passo que correu mal (um apoio recusado, uma proposta negada): o filete e o
   número a vermelho, para se ler onde o circuito parou. */
.cms-step.is-danger { border-top-color: var(--color-danger); color: var(--color-text); }
.cms-step.is-danger .cms-step-title::before { background: var(--color-danger); border-color: var(--color-danger); color: var(--color-surface); }

/* Um estado de vista escondido tem de ceder ao atributo: o `display:flex` do
   .cms-state ganhava ao `[hidden]` do browser e o vazio ficava à vista por
   baixo dos dados (foi o caso das cotações da oportunidade). */
.cms-state[hidden] { display: none; }

/* ══════════════════════════════════════════════════════════════════════════════
   FINANCIAMENTO (maqueta do funil de vendas, 2026-09-11): três variantes que a
   página precisou e que valem para qualquer outra.

   .kpi-value.is-muted   um indicador por preencher («Por preencher» a cinza),
                         para não se ler como um zero
   .full-width-tab[hidden]  um separador que ainda não existe (o registo não foi
                         criado): o `display:inline-flex` do separador ganhava ao
                         atributo e o botão continuava à vista
   .status-badge[hidden] o badge de estado antes de haver estado
   ══════════════════════════════════════════════════════════════════════════════ */
.kpi-value.is-muted { color: var(--color-text-muted); font-weight: var(--weight-medium); }
.app .full-width-tab[hidden] { display: none; }
.status-badge[hidden] { display: none; }

/* Nota por baixo de um campo: o porquê de estar só de leitura, de onde vem o
   valor, o formato esperado. Fica na coluna do campo, em texto pequeno e mudo. */
.form-col .field-hint,
.cfg-code .field-hint { display: block; margin-top: 4px; font-size: var(--text-xs); color: var(--color-text-muted); line-height: 1.4; }

/* ══════════════════════════════════════════════════════════════════════════════
   A PRECISAR DE ATENÇÃO, PRAZO E PONTUAÇÃO (maquetas das retomas, 2026-09-11)

   .att-list / .att-row   a lista curta do que precisa de atenção: número à
                          esquerda, a frase, a seta. Cada linha abre a lista já
                          filtrada. O filete à esquerda diz a gravidade.
   .deadline              um prazo com um ponto de cor: a contagem de um leilão
   .score                 uma pontuação com o ponto: verde, âmbar ou vermelho
   ══════════════════════════════════════════════════════════════════════════════ */
.att-list { display: grid; gap: 1px; border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden; background: var(--color-border); margin-bottom: var(--space-4); }
.att-list[hidden] { display: none; }
.att-row { display: flex; align-items: center; gap: 12px; padding: 10px 14px; background: var(--color-surface); color: var(--color-text-secondary); text-decoration: none; border-left: 3px solid var(--color-border-strong); font-size: var(--text-md); }
.att-row b { font-size: var(--text-lg); color: var(--color-text); min-width: 24px; font-variant-numeric: tabular-nums; }
.att-row span { flex: 1; }
.att-row .ph-icon { color: var(--color-text-muted); }
.att-row:hover { background: var(--color-surface-hover); color: var(--color-text); }
.att-row.is-danger { border-left-color: var(--color-red); }
.att-row.is-warning { border-left-color: var(--color-yellow); }

.deadline, .score { display: inline-flex; align-items: center; gap: 6px; font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
.deadline::before, .score::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--color-text-muted); flex-shrink: 0; }
.deadline.is-soon { color: var(--color-yellow-text); } .deadline.is-soon::before { background: var(--color-yellow); }
.deadline.is-danger { color: var(--color-red-text); } .deadline.is-danger::before { background: var(--color-red); }
.score { color: var(--color-text-muted); } .score::before { background: var(--color-border-strong); }
.score.is-ok { color: var(--color-green-text); } .score.is-ok::before { background: var(--color-green); }
.score.is-warn { color: var(--color-yellow-text); } .score.is-warn::before { background: var(--color-yellow); }
.score.is-bad { color: var(--color-red-text); } .score.is-bad::before { background: var(--color-red); }

/* ═══════════════════════════════════════════════════════════════
   INTERRUPTOR  (.cms-switch)
   ─────────────────────────────────────────────────────────────
   Ligar e desligar uma coisa, numa lista de coisas que se ligam e
   desligam: os avisos que se querem receber, as opções de uma
   ficha, o que aparece no telemóvel.

   Nasceu tarde, e por isso o CMS tem hoje SEIS anatomias da mesma
   ideia espalhadas por módulos (`pe-notif-toggle` no perfil,
   `settings-toggle` nas definições, `brand-toggle`, `ec-switch`,
   `ins-switch`, `asr-slot-switch`). Nenhuma delas se apaga sozinha:
   quem mexer numa dessas páginas converte-a para aqui, e código
   novo não abre a sétima.

   ANATOMIA
     .cms-switch-list          a lista (linhas separadas por hairline)
       .cms-switch-row         uma linha
         .cms-switch-info
           .cms-switch-title   o que se liga
           .cms-switch-desc    o que acontece quando está ligado
         label.cms-switch      o interruptor
           input[type=checkbox]
           .cms-switch-track
             .cms-switch-thumb

   O <input> continua a existir e a receber foco: é ele que o
   teclado e o leitor de ecrã usam. O que se esconde é a caixa
   desenhada pelo browser, não o controlo.
═══════════════════════════════════════════════════════════════ */
.cms-switch-list {
    display: flex;
    flex-direction: column;
}
.cms-switch-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--color-border);
}
.cms-switch-row:last-child { border-bottom: 0; }

.cms-switch-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cms-switch-title { font-size: var(--text-sm); font-weight: var(--weight-medium); }
.cms-switch-desc  { font-size: var(--text-xs); color: var(--color-text-muted); }

.cms-switch {
    position: relative;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    /* O alvo tem de dar 24px de altura (WCAG 2.2): a calha tem 22 e o resto
       vem daqui, sem afastar a linha. */
    padding: 1px 0;
    cursor: pointer;
    user-select: none;
}
.cms-switch input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.cms-switch-track {
    position: relative;
    width: 40px;
    height: 22px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-full);
    background: var(--color-surface-alt);
    transition: background-color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.cms-switch-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--color-text-muted);
    transition: transform var(--dur-fast) ease, background-color var(--dur-fast) ease;
}
.cms-switch input:checked ~ .cms-switch-track {
    background: var(--color-primary);
    border-color: var(--color-primary);
}
/* Contra o azul cheio, o botão é branco nos dois temas: é a mesma decisão do
   texto de um botão preenchido. */
.cms-switch input:checked ~ .cms-switch-track .cms-switch-thumb {
    background: #fff;
    transform: translateX(18px);
}
/* O foco lê-se na BORDA, como em todos os campos do CMS. */
.cms-switch input:focus-visible ~ .cms-switch-track {
    border-color: var(--focus-ring-color);
    box-shadow: inset 0 0 0 1px var(--focus-ring-color);
}
.cms-switch input:disabled ~ .cms-switch-track { opacity: .5; cursor: not-allowed; }

@media (prefers-reduced-motion: reduce) {
    .cms-switch-track,
    .cms-switch-thumb { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════
   UMA PÁGINA DE DEFINIÇÕES  (maqueta das Definições, pedida pelo
   Jorge a 2026-09-25: docs/refactor/maqueta-definicoes-2026-09.html)
   ─────────────────────────────────────────────────────────────
   Três peças, e nenhuma é aspecto novo: são o interruptor, o bloco
   de secção e o texto pequeno da casa, arrumados para uma página que
   é só definições.

     .cms-switch-row--field   a linha de uma definição que não é um
                              interruptor (um campo, uma lista, um
                              botão). A anatomia é a mesma: o nome e a
                              frase à esquerda, o controlo à direita.
       .cms-field-ctl         o controlo, com a largura do que se
                              escreve (--ctl-w); .cms-field-ctl-unit
                              é a unidade ao lado (MB, minutos)
     .cms-settings-grid       os cartões (.ov-chart-block) de uma
                              secção: uma coluna, duas a partir dos
                              1281px; .is-wide ocupa a linha toda
     .cms-used-in             a última linha de um cartão: quem usa
                              aquelas definições, com a ligação

   E as miudezas que as servem: .cms-switch-title-line (o nome com
   um selo ao lado), .cms-dirty-dot (o ponto de «alterado e por
   gravar»), .cms-secret («Definida» e «Mudar» no lugar de um campo
   de palavra-passe vazio) e a .cms-save-bar (a .form-nav que só
   aparece com alterações e diz quantas e onde).
═══════════════════════════════════════════════════════════════ */

/* MOBILE-FIRST: no telemóvel o controlo desce para baixo da frase e ocupa a
   largura toda; a partir dos 769px sobe para a direita, com a largura do que
   se escreve, e o texto fica com o resto. */
.cms-switch-row--field { flex-wrap: wrap; align-items: flex-start; row-gap: var(--space-2); }
.cms-switch-row--field > .cms-switch-info { flex: 1 1 100%; }
.cms-switch-row--field > .cms-field-ctl {
    flex: 1 1 100%;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
}
.cms-field-ctl > input,
.cms-field-ctl > select,
.cms-field-ctl > textarea { flex: 1 1 0; width: 100%; min-width: 0; }
.cms-field-ctl .num { text-align: right; font-variant-numeric: tabular-nums; }
.cms-field-ctl-unit { flex: 0 0 auto; font-size: var(--text-xs); color: var(--color-text-muted); }
/* O erro de um campo fica por baixo dele, na mesma coluna. */
.cms-field-ctl > .error-messages { flex: 1 1 100%; }
@media (min-width: 769px) {
    .cms-switch-row--field { flex-wrap: nowrap; align-items: center; }
    .cms-switch-row--field > .cms-switch-info { flex: 1 1 0; }
    .cms-switch-row--field > .cms-field-ctl { flex: 0 0 var(--ctl-w, 260px); }
}

/* O nome de uma definição é quase sempre um <label> ligado ao campo, e tem de
   se ler igual ao de um interruptor, que é um <span>. As duas regras globais
   do `label` (secção 1 deste ficheiro) põem-lhe 12px, tinta apagada e margem,
   e sem isto metade dos nomes de um cartão saía mais fraca do que a outra. */
.cms-switch-list label.cms-switch-title {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--color-text);
    letter-spacing: normal;
    cursor: pointer;
}
.cms-switch-title-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }

/* O ponto de alteração por gravar, numa pílula e num separador. */
.cms-dirty-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    margin-left: 6px;
    border-radius: 50%;
    background: var(--color-primary);
    vertical-align: middle;
}
.cms-dirty-dot[hidden] { display: none; }

/* O segredo: «Definida» num selo e «Mudar» ao lado, e o campo só depois de se
   carregar. O `!important` é contra o `input:not(...)` de seis negações do
   components.css partilhado, que põe todo o campo em `display: block` e
   passava por cima do atributo `hidden`. */
.cms-secret { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.cms-field-ctl.cms-secret > [hidden] { display: none !important; }

/* Os cartões de uma secção. A superfície e o raio são os do .ov-chart-block;
   aqui só se decide a grelha e o respiro das linhas lá dentro. */
.cms-settings-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-4);
    align-items: start;
}
.cms-settings-grid > .ov-chart-block > .cms-switch-list { padding: 0 var(--space-4) var(--space-2); }
.cms-settings-grid > .ov-chart-block > .ov-panel-header { flex-wrap: wrap; row-gap: 6px; padding-inline: var(--space-4); }
.cms-settings-grid > .ov-chart-block > .ov-panel-header > .cms-toolbar-end { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 100%; }
@media (min-width: 641px) {
    .cms-settings-grid > .ov-chart-block > .cms-switch-list { padding-inline: 20px; }
    .cms-settings-grid > .ov-chart-block > .ov-panel-header { padding-inline: 20px; }
}
@media (min-width: 769px) {
    .cms-settings-grid > .ov-chart-block > .ov-panel-header > .cms-toolbar-end { flex: 0 0 auto; margin-left: auto; }
}
@media (min-width: 1281px) {
    .cms-settings-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .cms-settings-grid > .is-wide { grid-column: 1 / -1; }
}

/* QUEM USA ISTO: a última linha de um cartão diz quem lê aquelas definições,
   com a ligação para lá. É a resposta à pergunta «se eu mudar isto, o que
   muda», que as Definições nunca respondiam. */
.cms-used-in {
    margin: 0;
    padding: 10px var(--space-4) 14px;
    border-top: 1px solid var(--color-border);
    font-size: var(--text-xs);
    line-height: 1.6;
    color: var(--color-text-muted);
}
.cms-used-in b { font-weight: var(--weight-medium); color: var(--color-text-secondary); }
@media (min-width: 641px) { .cms-used-in { padding-inline: 20px; } }

/* A barra de guardar que só aparece com alterações. É a .form-nav de sempre;
   à esquerda diz quantas alterações há e onde, e Descartar fica ao lado de
   Guardar. */
.app .page-panel .form-nav.cms-save-bar { justify-content: flex-end; flex-wrap: wrap; }
.app .page-panel .form-nav.cms-save-bar[hidden] { display: none; }
.cms-save-bar-text { margin-right: auto; min-width: 0; font-size: var(--text-sm); color: var(--color-text-secondary); }
.cms-save-bar-text b { font-weight: var(--weight-semibold); color: var(--color-text); }

/* ══════════════════════════════════════════════════════════════════════════
   A COLUNA DE DETALHES DE UMA FICHA  (.cms-aside, .cms-dl, .page-eyebrow)
   ─────────────────────────────────────────────────────────────────────────
   A anatomia da ficha de um pedido do /support, escrita aqui para as fichas
   de negócio a poderem usar sem a copiar (2026-09-24).

   O QUE ISTO É: um ecrã de trabalho com um assunto (uma pessoa, uma conta,
   uma viatura), um percurso e um monte de campos que se leem mais do que se
   mudam. À esquerda o trabalho, à direita 320px de detalhes em linhas rótulo
   e valor, com secções que abrem e fecham.

   As medidas são as do /support, que por sua vez são as do painel do chat,
   medidas no chat real: 320px de largura, 14px de respiro em todas as peças,
   círculos de 40px nos atalhos, e o corpo de uma secção com o mesmo respiro
   dos quatro lados (decisão do Jorge, 2026-09-16).

   A DÍVIDA, escrita para não se perder: o /support continua a ter o gémeo
   destas regras na folha dele (`modules/support/assets/css/support.css:2101`
   em diante, com o prefixo `#support-app` e os nomes `.ta-*` e `.tk-dl`). A
   convergência é uma troca de nomes de classe, e faz-se à parte, com o pixel
   diff da ficha de um pedido a provar que não mudou nada. Não se fez aqui
   para não arriscar uma página que funciona numa alteração que é de outras
   duas.
   ══════════════════════════════════════════════════════════════════════════ */

/* MOBILE-FIRST: por baixo do conteúdo, a largura toda, separada por um filete
   em cima. A partir dos 1025px fica ao lado, com 320px fixos, que é onde há
   largura para as duas colunas e onde o aside deixa de comer mais de um terço
   do ecrã. */
.cms-aside {
    display: flex;
    flex-direction: column;
    min-width: 0;
    border-top: 1px solid var(--color-border);
    background: var(--color-surface);
}

.cms-aside-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 14px 14px 12px;
    border-bottom: 1px solid var(--color-border);
    font-size: var(--text-md);
    font-weight: var(--weight-semibold);
    color: var(--color-text);
}
.cms-aside-head > .ph-icon { color: var(--color-text-muted); font-size: 16px; }

/* O ASSUNTO DA FICHA: quem abriu o pedido, a conta do negócio, a viatura da
   cotação. Centrado, com o avatar em cima e os atalhos por baixo. */
.cms-aside-person {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    padding: 16px 14px 14px;
    border-bottom: 1px solid var(--color-border);
    text-align: center;
}
.cms-aside-person b { font-size: var(--text-md); font-weight: var(--weight-semibold); color: var(--color-text); }
.cms-aside-person small { font-size: var(--text-xs); color: var(--color-text-muted); line-height: 1.4; }
.cms-aside-avatar {
    display: grid;
    place-items: center;
    width: 56px;
    height: 56px;
    margin-bottom: 6px;
    border-radius: 50%;
    background: var(--color-surface-alt);
    color: var(--color-text);
    font-size: var(--text-lg);
    font-weight: var(--weight-semibold);
}

/* OS ATALHOS: o gesto que se faz a seguir a ler o nome. Círculo de 40px com a
   legenda por baixo, que é o alvo confortável ao toque e cabe quatro vezes em
   320px. Ligar e escrever são NAVEGAÇÃO e levam `<a>`; o resto leva `<button>`. */
.cms-aside-quick { display: flex; justify-content: center; gap: 14px; margin-top: 12px; }
.cms-aside-quick:empty { display: none; }
.cms-aside-quick-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--color-text-muted);
    font: inherit;
    font-size: var(--text-2xs);
    text-decoration: none;
    cursor: pointer;
}
.cms-aside-quick-ic {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 1px solid transparent;
    border-radius: 50%;
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
    font-size: 18px;
    transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.cms-aside-quick-btn:hover:not(:disabled) .cms-aside-quick-ic {
    background: var(--color-surface-hover);
    color: var(--color-text);
}
/* Um atalho que ainda não está pronto (o «Agendar» espera pela cronologia)
   diz que não está pelo cursor e pelo tom, e o rato não o acende. */
.cms-aside-quick-btn:disabled { cursor: not-allowed; opacity: .55; }
/* O foco lê-se no anel da casa: tirar o `outline` sem substituto na mesma
   regra é o que a skill de acessibilidade recusa. */
.cms-aside-quick-btn:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
    color: var(--color-text);
}

/* UMA SECÇÃO QUE ABRE E FECHA. O título é um botão, com contagem opcional. */
.cms-aside-sec { border-bottom: 1px solid var(--color-border); }
.cms-aside-sec-title {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 12px 14px;
    border: 0;
    background: transparent;
    color: var(--color-text);
    font: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    text-align: left;
    cursor: pointer;
}
.cms-aside-sec-title:hover { background: var(--color-surface-alt); }
.cms-aside-sec-title:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: -2px;
    background: var(--color-surface-alt);
    color: var(--color-primary-text);
}
.cms-aside-sec-title > .ph-icon { color: var(--color-text-muted); font-size: 16px; }
.cms-aside-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    margin-left: auto;
    padding: 0 6px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-full);
    background: var(--color-surface-alt);
    color: var(--color-text-muted);
    font-size: var(--text-2xs);
    font-weight: var(--weight-bold);
    white-space: nowrap;
}
.cms-aside-count:empty { display: none; }
.cms-aside-chev {
    margin-left: auto;
    font-size: 12px;
    color: var(--color-text-muted);
    transition: transform var(--dur-fast) var(--ease-out);
}
/* Com contagem, quem empurra é ela, e o chevron fica-lhe ao lado. */
.cms-aside-count:not(:empty) + .cms-aside-chev { margin-left: 2px; }
.cms-aside-sec.is-open .cms-aside-chev { transform: rotate(90deg); }
/* O mesmo respiro dos quatro lados, e nunca colado ao título que o abre. */
.cms-aside-sec-body { display: none; padding: 14px; font-size: var(--text-sm); }
.cms-aside-sec.is-open .cms-aside-sec-body { display: block; }
.cms-aside-sec.is-open .cms-aside-sec-title { padding-bottom: 4px; }
.cms-aside-sec.is-open .cms-aside-sec-body { padding-top: 6px; }

/* AS ACÇÕES DO FIM: botões TONAIS a toda a largura, uma por linha, o texto
   encostado à esquerda e o ícone à frente dele. São as medidas da ficha de um
   pedido do /support, que por sua vez são as do painel do chat: 34px de
   altura, 9px por 14px de respiro, 6px entre botões, e a caixa com 14px à
   volta menos em cima, onde o título já o dá.

   Tonais e não de contorno: uma coluna de cinco contornos lê-se como uma
   grelha, e estas acções são todas do mesmo peso. O destaque, quando existe,
   é a variante, não a moldura. Todas neutras menos as destrutivas.

   Um botão `.small` mede 24px, abaixo do `--height-btn-sm` e a metade do alvo
   de toque confortável, e por isso estas levam a escala normal. */
.cms-aside-actions-title {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 12px 14px 8px;
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    color: var(--color-text);
}
.cms-aside-actions-title .ph-icon { color: var(--color-text-muted); font-size: 16px; }
.cms-aside-actions { display: grid; gap: 6px; padding: 0 14px 14px; }
.cms-aside-actions:empty { display: none; }
/* Sem título por cima, a caixa dá o respiro de cima a si mesma. */
.cms-aside-actions:first-child,
:not(.cms-aside-actions-title) + .cms-aside-actions { padding-top: 12px; }
.cms-aside-actions .bt-tonal,
.cms-aside-actions .bt-outline,
.cms-aside-actions > a {
    justify-content: flex-start;
    width: 100%;
    min-height: 34px;
    padding: 9px 14px;
}
/* Uma lista de acções DENTRO de uma secção (os documentos da cotação, pedido
   do Jorge a 2026-09-25: «com o mesmo estilo dos botões das Ações»): o desenho
   das acções do fim, sem o respiro da caixa, que o corpo da secção já dá. */
.cms-aside-sec-body > .cms-aside-actions { padding: 0; }
.cms-aside-actions .bt-tonal.danger { color: var(--color-red-text); background: color-mix(in srgb, var(--color-red), transparent 90%); }
.cms-aside-actions .bt-tonal.danger:hover { background: color-mix(in srgb, var(--color-red), transparent 82%); }
.cms-aside-actions .bt-tonal:disabled { opacity: .55; cursor: not-allowed; }
/* Uma nota entre acções: diz porque é que uma delas não está ali. */
/* A nota entre acções tem as medidas da `.support-sidebar-note` dentro da
   `.ta-actions`: 10px, centrada, e no aviso passa a fila com o ícone. */
.cms-aside-note { margin: 0 0 4px; font-size: var(--text-2xs); line-height: 1.45; text-align: center; color: var(--color-text-muted); }
.cms-aside-note.is-warning { display: flex; align-items: flex-start; gap: 6px; font-size: var(--text-xs); text-align: left; color: var(--color-yellow-text); }
.cms-aside-note.is-warning .ph-icon { flex: 0 0 auto; margin-top: 2px; color: var(--color-yellow-text); }

@media (prefers-reduced-motion: reduce) {
    .cms-aside-chev,
    .cms-aside-quick-ic { transition: none; }
}

/* ── AS LINHAS RÓTULO E VALOR  (.cms-dl) ──────────────────────────────────
   A forma de uma ficha densa nesta casa (decisão do Jorge, 2026-09-22): o
   rótulo à direita e esbatido, o valor à esquerda, uma hairline por linha e
   nenhuma linha vertical.

   A LINHA VAI DE PONTA A PONTA. O `gap` partia-a em duas: o vão entre as
   colunas deixava um corte a meio, e o vão entre linhas afastava a borda do
   `dt` da do `dd` sempre que o valor ocupava duas linhas. O espaçamento vive
   todo no padding das células, que é o que a hairline acompanha, e as células
   esticam à altura da linha para as duas bordas se encontrarem.

   O `display: contents` no invólucro é o que permite ter um `<div>` por par e
   na mesma alinhar todos os rótulos pela mesma margem. A borda vai às
   CÉLULAS, porque um elemento com `display: contents` não desenha nada. */
.cms-dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0; margin: 0; align-items: stretch; }
.cms-dl > div { display: contents; font-size: var(--text-sm); }
.cms-dl dt,
.cms-dl dd { display: flex; align-items: center; padding-block: 8px; border-bottom: 1px solid var(--color-border); }
/* A última linha fica sem ela, senão ficava uma risca a fechar uma lista que
   já acaba ali. */
.cms-dl > div:last-child dt,
.cms-dl > div:last-child dd { border-bottom: 0; padding-bottom: 0; }
.cms-dl dt {
    justify-content: flex-end;
    padding-right: 10px;
    color: var(--color-text-muted);
    white-space: nowrap;
    text-align: right;
    font-size: var(--text-sm);
}
.cms-dl dt label { font-size: inherit; font-weight: inherit; color: inherit; margin: 0; }
.cms-dl dd {
    margin: 0;
    min-width: 0;
    padding-left: 10px;
    justify-content: flex-start;
    gap: 6px;
    flex-wrap: wrap;
    text-align: left;
    color: var(--color-text);
    font-size: var(--text-sm);
}
/* Uma nota por baixo do valor: ocupa a linha toda e desce, e é isso que a
   separa do valor a que se refere. A ORDEM manda-a para depois do botão, que
   senão o botão caía para a última linha, a seguir a duas ou três notas, longe
   do valor sobre o qual actua. */
.cms-dl dd small { order: 2; display: block; width: 100%; font-size: var(--text-xs); color: var(--color-text-muted); }
.cms-dl .num { font-variant-numeric: tabular-nums; }
/* O botão de uma linha: 24px, o alvo mínimo da WCAG 2.2, e INLINE a seguir ao
   valor sobre o qual actua. Com `margin-left: auto` num `dd` que quebra, ele
   caía para a linha de baixo, longe desse valor. */
.cms-dl-edit { order: 1; flex: 0 0 auto; width: 24px; height: 24px; font-size: 13px; }
.cms-dl-select { width: 130px; height: 28px; padding: 3px 24px 3px 8px; font-size: var(--text-sm); margin: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   A FITA DE LIGAÇÕES DE UM REGISTO  (.cms-crosslink)
   ─────────────────────────────────────────────────────────────────────────
   A cadeia de um negócio, atravessável dos dois lados: a conta, a
   oportunidade, a cotação, o financiamento e a retoma, com o elo em que se
   está marcado. Nasceu na maqueta de 2026-09-25.

   O PROBLEMA QUE RESOLVE, medido: um financiamento e uma retoma são
   instrumentos de um negócio, e nenhum dos dois sabia a que negócio pertence.
   A `financing_proposals.deal_id` existia e estava a NULL em 168 de 168
   linhas; a `deal_proposals.financing_proposal_id` era escrita pelo
   configurador e nunca lida do outro lado. Quem abria um financiamento via o
   número da conta e mais nada, e para saber se aquilo ainda era negócio vivo
   tinha de ir à conta, abrir as oportunidades e procurar.

   NASCE DE DUAS PEÇAS QUE JÁ EXISTEM, e não é nenhuma delas: a `.page-facts`
   (a fila que rola, com o degradê na ponta) e o `.cms-breadcrumb` (o
   caminho). Os factos não são navegáveis e o caminho é hierárquico; isto é
   uma cadeia de registos irmãos.

   ONDE VIVE: na `.page-action-bar`, dentro do `.page-title-block`, por baixo
   dos factos. Serve as cinco fichas da cadeia, sempre com o elo próprio
   marcado, e é UMA peça e não cinco.

   Quem a abre no elo certo é o `utils/Crosslink.js`: uma fita que rola e
   nasce no princípio esconde justamente a informação que a justifica.
   ══════════════════════════════════════════════════════════════════════════ */

.cms-crosslink {
  display: flex;
  align-items: stretch;
  gap: 0;
  min-width: 0;
  /* O degradê na ponta, que é a receita da casa para uma fila que rola (o
     `.fiche-now-btns` da ficha da cotação). Só à direita: o das duas pontas
     leva `margin-inline: -16px` para se realinhar com o bloco, e dentro da
     `.page-action-bar` isso empurrava a fita 6px para fora do documento a
     390px, com scroll horizontal na página inteira. Medido. */
  padding-right: 16px;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
          mask-image: linear-gradient(to right, #000 calc(100% - 16px), transparent 100%); /* nat-ok: hífen de subtração no calc() */
  /* Sem o `overflow-y` escrito, o eixo vertical passa a `auto` sozinho e a
     roda do rato morre aqui dentro (regra de 2026-09-24). */
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  list-style: none;
  margin: 2px 0 0;
}
.cms-crosslink::-webkit-scrollbar { display: none; }
.cms-crosslink > li { display: flex; flex: 0 0 auto; }

.cms-crosslink-item {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  min-width: 0;
  padding: 7px 12px 7px 10px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text-secondary);
  text-decoration: none;
  font-size: var(--text-xs);
}
.cms-crosslink-item + .cms-crosslink-item { margin-left: -1px; }
.cms-crosslink > li:first-child .cms-crosslink-item { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.cms-crosslink > li:last-child  .cms-crosslink-item { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.cms-crosslink-item > .ph-icon { flex: 0 0 auto; font-size: 15px; color: var(--color-text-muted); }

/* O nome do registo, que é o que se lê primeiro. Corta com reticências: uma
   razão social de setenta caracteres não empurra a fita toda. */
.cms-crosslink-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 22ch;
  font-weight: var(--weight-medium);
  color: var(--color-text);
}
.cms-crosslink-sub { color: var(--color-text-muted); white-space: nowrap; }

a.cms-crosslink-item:hover,
a.cms-crosslink-item:focus-visible,
button.cms-crosslink-item:hover,
button.cms-crosslink-item:focus-visible {
  border-color: var(--color-border-strong);
  background: var(--color-surface-alt);
  z-index: 1;
}
a.cms-crosslink-item:focus-visible,
button.cms-crosslink-item:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: -1px;
}

/* O elo em que se está: marca-se pela superfície e pelo filete, como a linha
   escolhida de uma lista. Não é um link, é um <span>. */
.cms-crosslink-item.is-here {
  background: var(--color-surface-alt);
  border-left-width: 3px;
  border-left-color: var(--color-primary);
  color: var(--color-text);
  padding-left: 9px;
}
.cms-crosslink-item.is-here > .ph-icon { color: var(--color-primary-text); }

/* O elo que ainda não existe: convite, não erro. Tracejado e esbatido, com o
   verbo no infinitivo («Associar uma retoma»). */
.cms-crosslink-item.is-empty {
  border-style: dashed;
  background: transparent;
  color: var(--color-text-muted);
}
.cms-crosslink-item.is-empty .cms-crosslink-name { font-weight: var(--weight-regular); color: var(--color-text-muted); }
a.cms-crosslink-item.is-empty:hover,
button.cms-crosslink-item.is-empty:hover { border-style: solid; color: var(--color-text-secondary); }

/* O elo que trava o negócio. Leva SEMPRE texto ao lado: nunca só a cor. */
.cms-crosslink-item.is-blocking {
  border-color: color-mix(in srgb, var(--color-yellow) 55%, var(--color-border));
  background: color-mix(in srgb, var(--color-yellow), transparent 92%);
}
.cms-crosslink-item.is-blocking > .ph-icon { color: var(--color-yellow-text); }

@media (min-width: 641px) {
  .cms-crosslink-name { max-width: 30ch; }
}

/* ── O SOBRE-TÍTULO DA BARRA DE IDENTIDADE  (.page-eyebrow) ───────────────
   O que identifica o registo antes de o nome dele aparecer: a referência em
   monoespaçado, a conta e o tipo. É a irmã da `.page-facts`, e vive na mesma
   `.page-title-block`.

   MOBILE-FIRST: uma linha que corta com reticências, porque duas linhas de
   sobre-título custam 18px de um ecrã onde o conteúdo ainda não começou. A
   partir do tablet pode quebrar à vontade. */
.page-eyebrow {
    display: flex;
    flex-wrap: nowrap;
    overflow: hidden;
    gap: 4px 10px;
    font-size: var(--text-2xs);
    font-weight: var(--weight-semibold);
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
}
.page-eyebrow > * { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.page-eyebrow-code {
    font-family: var(--font-mono);
    font-weight: var(--weight-bold);
    letter-spacing: 0;
    text-transform: none;
    color: var(--color-text);
}
/* O NOME DA CONTA não leva maiúsculas: as maiúsculas são para rótulos curtos,
   e um nome de empresa não é curto. Em maiúsculas espaçadas perde-se a forma
   das palavras, que é por onde elas se leem. Ganha ainda uma coisa: a conta,
   que é a única peça clicável desta linha, distingue-se também pela caixa. */
.page-eyebrow > a {
    text-transform: none;
    letter-spacing: 0;
    /* Sem azul (regra de 2026-09-24): o traço é que diz que abre. */
    color: var(--color-text-secondary);
    text-decoration: underline;
    text-decoration-color: var(--color-border-strong);
    text-underline-offset: 3px;
}
.page-eyebrow > a:hover,
.page-eyebrow > a:focus-visible { color: var(--color-text); text-decoration-color: currentColor; }
@media (min-width: 641px) {
    .page-eyebrow { flex-wrap: wrap; overflow: visible; font-size: var(--text-xs); }
}

/* A segunda linha de uma célula: o que qualifica o valor sem competir com
   ele. Existia presa ao `.cell-stack`, que obriga a um `<strong>` ao lado;
   solta, serve qualquer célula. */
.cell-note { display: block; font-size: var(--text-xs); color: var(--color-text-muted); }

/* ── O ESQUELETO DENTRO DE UM BLOCO DE SECÇÃO ─────────────────────────────
   O `.cms-skeleton` corre de `--color-surface-alt` para `--color-border`, e
   foi desenhado para viver sobre o cartão da página, que é `--color-surface`.
   Dentro de um `.ov-chart-block`, que já é `--color-surface-alt`, começa da
   cor exacta do fundo e desaparece: medido a rgb(40,40,38) sobre rgb(40,40,38).
   A escada certa já estava escrita para o esqueleto dos gráficos. */
.ov-chart-block .cms-skeleton-row > span {
    background: linear-gradient(90deg, var(--color-surface-hover) 25%, var(--color-border-strong) 40%, var(--color-surface-hover) 55%);
    background-size: 400% 100%;
}

/* ══════════════════════════════════════════════════════════════
   O PAINEL DE DECISÃO (`.cms-callout`), 2026-09-25

   Uma cotação à espera da direção pinta-se a AMARELO, o do toast de aviso,
   com um botão PRETO: foi o pedido do Jorge, primeiro para a caixa do apoio
   no configurador (2026-09-23) e depois para a ficha da cotação, «tal como em
   offer». Vivia só na folha do configurador; com dois ecrãs a usá-lo passou a
   componente, e a folha do módulo deixou de o desenhar.

   O amarelo e a tinta são FIXOS nos dois temas, como no toast: um aviso que
   muda de cor com o tema deixa de ser um sinal. A tinta `#4a3702` mede 6,92:1
   sobre o amarelo. Os controlos acompanham, a preto: sobre amarelo chapado o
   azul da casa deixa de pintar e o contorno cinzento desaparece.

   Anatomia: o rótulo pequeno (o tipo do pedido), o título (em que pé está), o
   texto (quem, quando, o quê) e as acções. Escrito para o telemóvel, empilha;
   a partir de 769px as acções passam para o lado do texto.
══════════════════════════════════════════════════════════════ */
.cms-callout {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 0;
    border-radius: var(--radius-md);
    background: #fdc00c;   /* ds-ok: o amarelo do toast de aviso, fixo nos dois temas como ele */
    color: #4a3702;        /* ds-ok: a tinta do toast de aviso, 6,92:1 sobre o amarelo */
}
.cms-callout[hidden] { display: none; }
.cms-callout-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cms-callout-label {
    margin: 0;
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: .06em;
    text-transform: uppercase;
}
.cms-callout-title { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-semibold); line-height: 1.35; }
.cms-callout-text { margin: 0; font-size: var(--text-sm); line-height: 1.45; overflow-wrap: anywhere; }
/* A razão de quem pediu, dita pelas palavras dele: o filete à esquerda diz
   que é uma citação, sem aspas nem itálico a pesar. */
.cms-callout-quote { margin: 2px 0 0; padding-left: 10px; border-left: 2px solid rgba(74, 55, 2, .45); font-size: var(--text-sm); line-height: 1.45; overflow-wrap: anywhere; }   /* ds-ok: a tinta do aviso, esbatida */
.cms-callout-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
@media (min-width: 769px) {
    .cms-callout { flex-direction: row; align-items: center; justify-content: space-between; gap: var(--space-4); }
    .cms-callout-body { flex: 1 1 auto; }
    .cms-callout-actions { flex: 0 0 auto; justify-content: flex-end; }
}
/* A VARIANTE do botão vai no selector (`.primary`, `.neutral`): sem ela a
   regra empatava em especificidade com a do sistema e o botão ficava azul. */
.cms-callout .bt-filled,
.cms-callout .bt-filled.primary,
.cms-callout .bt-filled.neutral,
.cms-callout .bt-filled.warning {
    background: #1b1b1b;   /* ds-ok: sobre o amarelo do toast, a acção é preta */
    border-color: #1b1b1b;
    color: #ffffff;
}
.cms-callout .bt-filled:hover:not(.disabled, [disabled]),
.cms-callout .bt-filled.primary:hover:not(.active, .disabled, [disabled]),
.cms-callout .bt-filled.neutral:hover:not(.active, .disabled, [disabled]) {
    background: #000000;
    border-color: #000000;
    color: #ffffff;
}
.cms-callout .bt-outline,
.cms-callout .bt-outline.primary,
.cms-callout .bt-outline.neutral {
    border-color: #1b1b1b;   /* ds-ok: o mesmo contorno preto */
    color: #1b1b1b;
    background: transparent;
}
.cms-callout .bt-outline:hover:not(.disabled, [disabled]),
.cms-callout .bt-outline.primary:hover:not(.disabled, [disabled]),
.cms-callout .bt-outline.neutral:hover:not(.disabled, [disabled]) {
    background: rgba(0, 0, 0, .08);
    border-color: #000000;
    color: #000000;
}
/* O foco sobre o amarelo: o anel da casa é cinzento e ali não se vê. */
.cms-callout :is(a, button, textarea, input):focus-visible { outline: 2px solid #1b1b1b; outline-offset: 2px; }
/* Um campo dentro do painel (a razão de um pedido): contorno e tinta escuros.
   A 55% de preto o contorno dá 3,34:1 sobre o amarelo, e a WCAG 1.4.11 pede 3:1. */
.cms-callout :is(textarea, input[type="text"]) {
    border-color: rgba(27, 27, 27, .55);   /* ds-ok: 3,34:1 sobre o amarelo do toast */
    background: rgba(255, 255, 255, .55);
    color: #1b1b1b;
}
.cms-callout :is(textarea, input[type="text"]):focus { border-color: #1b1b1b; }
.cms-callout :is(textarea, input[type="text"])::placeholder { color: rgba(27, 27, 27, .55); }
.cms-callout :is(label, legend, small) { color: inherit; }

/* ══════════════════════════════════════════════════════════════
   O CORPO DE UMA JANELA (`.cms-modal-body`), 2026-09-26

   O `.modal-content` do `modal.css` não tem respiro próprio («o espaçamento é
   do conteúdo de cada modal»), e cada módulo inventou o seu: 20px no iCal do
   painel (`.ical-modal-body`), 14 e 16 no do interesse de compra
   (`.pi-modal-body`), 4 e 0 no filtro da cronologia, e o `.srv-modal` do
   /server. Cinco medidas para a mesma coisa. Esta é a do cabeçalho e do
   rodapé da própria janela (`--space-4`), para o texto alinhar com o título,
   e as folhas dos módulos passam a usá-la.
══════════════════════════════════════════════════════════════ */
.cms-modal-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-3);
    padding: var(--space-1) var(--space-4) var(--space-3);
    font-size: var(--text-sm);
    line-height: 1.5;
}
.cms-modal-body > p { margin: 0; }

/* ══════════════════════════════════════════════════════════════
   A CRONOLOGIA (`.cms-timeline`), 2026-09-25

   O desenho da Auditoria de um pedido de suporte (decisão do Jorge,
   2026-09-16), que passou a servir também a oportunidade e a cotação. Vivia
   preso ao `#support-app`; com três fichas a usá-lo, veio para aqui, e o
   suporte deixou de o desenhar.

   Escrita para o telemóvel: uma coluna, a do ícone a 24px e o corpo a ocupar
   o resto. Num ecrã largo não muda nada, porque não precisa.
══════════════════════════════════════════════════════════════ */
.cms-timeline { position: relative; padding-bottom: 4px; }
/* O degradê diz que a cronologia continua por baixo, enquanto houver passos
   por trazer. Assenta no painel, e é por isso que a cor é a da superfície. */
.cms-timeline.is-collapsed::after { content: ""; position: absolute; inset: auto 0 0; height: 60px; background: linear-gradient(to bottom, transparent, var(--timeline-surface, var(--color-surface))); pointer-events: none; }
.cms-timeline-day { margin: 0; padding: 10px 16px 4px; font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: .06em; text-transform: uppercase; color: var(--color-text-muted); }
.cms-timeline > .cms-timeline-day:first-child { padding-top: 4px; }
.cms-timeline-list { list-style: none; margin: 0; padding: 0 16px; }
.cms-timeline-item { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 10px; padding: 8px 0; position: relative; }
/* O fio que liga os passos do mesmo dia. Pára no último de cada dia: o rótulo
   do dia seguinte é que retoma a leitura. */
.cms-timeline-item::after { content: ""; position: absolute; left: 11px; top: 32px; bottom: -4px; width: 1px; background: var(--color-border); }
.cms-timeline-item:last-child::after { display: none; }
.cms-timeline-dot { width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--color-border); background: var(--color-surface-alt); color: var(--color-text-muted); display: inline-flex; align-items: center; justify-content: center; margin-top: 1px; position: relative; z-index: 1; }
.cms-timeline-dot .ph-icon { font-size: 13px; }
/* Cor só onde significa alguma coisa: o que ficou decidido ou entrou a favor
   (verde), o que está à espera ou caiu (laranja), o caminho do registo
   (azul) e o que se perde ou se recusa (vermelho). O resto lê-se pela
   etiqueta e pelo ícone. As famílias são as das três fichas que a usam. */
.cms-timeline-item:is(.is-claim, .is-authorize, .is-approve, .is-deposit, .is-won) .cms-timeline-dot { border-color: var(--color-green); color: var(--color-green-text); }
.cms-timeline-item:is(.is-return, .is-release, .is-request, .is-lapse, .is-withdraw) .cms-timeline-dot { border-color: var(--color-orange); color: var(--color-orange-text); }
.cms-timeline-item:is(.is-create, .is-assign, .is-change, .is-owner, .is-send, .is-client) .cms-timeline-dot { border-color: var(--color-primary); color: var(--color-primary-text); }
.cms-timeline-item:is(.is-remove, .is-refuse, .is-lost, .is-denied) .cms-timeline-dot { border-color: var(--color-danger); color: var(--color-red-text); }
/* A mudança de estado não é uma cor, é a mais lida: ganha contorno e tinta
   fortes em vez de mais uma cor na lista. */
.cms-timeline-item.is-status .cms-timeline-dot { border-color: var(--color-border-strong); color: var(--color-text-secondary); }
.cms-timeline-body { min-width: 0; }
.cms-timeline-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; font-size: var(--text-md); color: var(--color-text); }
.cms-timeline-kind { font-size: var(--text-2xs); font-weight: var(--weight-semibold); letter-spacing: .05em; text-transform: uppercase; color: var(--color-text-secondary); }
.cms-timeline-item:is(.is-status, .is-claim, .is-create) .cms-timeline-kind { color: var(--color-text); }
/* A que registo pertence o passo, quando a cronologia junta vários (a
   oportunidade e as cotações dela). */
.cms-timeline-ref { font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; }
.cms-timeline-time { margin-left: auto; font-size: var(--text-xs); color: var(--color-text-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.cms-timeline-flow { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 1px; font-size: var(--text-sm); color: var(--color-text); overflow-wrap: anywhere; }
.cms-timeline-flow .ph-icon { font-size: 12px; color: var(--color-text-muted); }
/* De onde veio fica apagado, para onde foi fica a cheio: a leitura rápida é o
   estado novo, não o antigo. */
.cms-timeline-flow .cms-timeline-from { color: var(--color-text-muted); }
.cms-timeline-by { margin-top: 2px; font-size: var(--text-xs); color: var(--color-text-muted); display: flex; align-items: center; gap: 4px; }
.cms-timeline-by .ta-who { gap: 4px; }
.cms-timeline-reason { margin-top: 4px; padding: 5px 10px; border-left: 2px solid var(--color-border); font-size: var(--text-xs); color: var(--color-text-secondary); overflow-wrap: anywhere; white-space: pre-line; }
/* O botão que traz os seguintes, ao centro e assente no painel. */
.cms-timeline-more-wrap { display: flex; justify-content: center; padding: 4px 16px 12px; }
.cms-timeline-more { display: inline-flex; align-items: center; gap: 6px; padding: 4px 14px; min-height: 28px; border: 1px solid var(--color-border); border-radius: var(--radius-full); background: var(--color-surface); color: var(--color-text-secondary); font: inherit; font-size: var(--text-xs); font-weight: var(--weight-medium); cursor: pointer; }
.cms-timeline-more:hover { border-color: var(--color-border-strong); color: var(--color-text); }
.cms-timeline-more:focus-visible { outline: none; border-color: var(--color-text-muted); }

/* ══════════════════════════════════════════════════════════════
   DUAS VARIANTES DO BOTÃO DE LINHA (2026-09-25)
══════════════════════════════════════════════════════════════ */
/* O ATALHO DA DECISÃO: uma cotação à espera da direção, na linha dela. É o
   mesmo amarelo e o mesmo preto do painel de decisão, porque é o mesmo
   caminho: quem o vê na lista sabe, antes de abrir, que ali há uma decisão. */
.cms-act-btn.is-attention {
    background: #fdc00c;   /* ds-ok: o amarelo do painel de decisão */
    border-color: #fdc00c;
    color: #1b1b1b;
}
.cms-act-btn.is-attention:hover,
.cms-act-btn.is-attention:focus-visible {
    background: #1b1b1b;   /* ds-ok: o preto do botão do painel */
    border-color: #1b1b1b;
    color: #fdc00c;
}
/* A COTAÇÃO PRINCIPAL: a estrela cheia e amarela (Jorge, 2026-09-25). Não é
   um botão desligado, que era como se lia com o tracejado a meio tom: é o
   sinal de qual é a principal, e por isso fica a tinta cheia.
   A tinta é o amarelo DE ESCREVER, e não o `--color-warning`: no tema escuro
   são o mesmo (8,5:1), e no claro o `--color-warning` dava 2,13:1 sobre o
   branco, abaixo dos 3:1 que um ícone precisa (WCAG 1.4.11). */
.cms-act-btn.is-starred { cursor: default; color: var(--color-yellow-text); border-color: color-mix(in srgb, var(--color-yellow-text), transparent 50%); }
.cms-act-btn.is-starred:hover { background: transparent; color: var(--color-yellow-text); border-color: color-mix(in srgb, var(--color-yellow-text), transparent 50%); }

/* ══════════════════════════════════════════════════════════════
   O LUGAR DA MarIA  (.cms-ai)   (2026-09-25)
   ─────────────────────────────────────────────────────────────
   Uma peça que a MarIA preenche, desenhada mesmo quando ela está desligada:
   com ela fora, fica cinzenta a dizer porquê e até quando, em vez de o lugar
   desaparecer sem explicação (decisão do Jorge, 2026-09-25). O desenho é o
   `.wm-ai` do /webmail, que sobe a componente por passar a ter um segundo
   sítio, a procura da Knowledge Base: as mesmas declarações, menos a margem,
   que é de quem a recebe.

   O botão lá dentro trava-se com `aria-disabled` e não com `disabled`: um
   botão desativado não recebe o toque, e é o toque que diz porquê.
══════════════════════════════════════════════════════════════ */
.cms-ai {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface-alt);
    font-size: var(--text-xs);
    color: var(--color-text-muted);
}
.cms-ai > .ph-icon { font-size: 15px; }
.cms-ai-text { flex: 1 1 auto; }
/* Os botões do cabeçalho não são da família `bt-*`, e travados liam-se iguais
   aos outros: ficam com o aspecto que a casa dá a um botão travado. */
.app-header-icon-btn[aria-disabled="true"] { color: var(--color-text-muted); cursor: not-allowed; opacity: .65; }

/* ══════════════════════════════════════════════════════════════
   O CARTÃO DE PORTA (`.cms-door`), 2026-09-27

   Nasceu no átrio da Assistência (/assistance): um cartão grande por porta,
   com o ícone, o título e a frase ao centro, e um rasto muito leve do rato,
   um degradê radial no ponto onde ele está. Passou a componente quando a
   entrada de uma cotação nova (as três maneiras de começar) pediu os mesmos
   cartões. A base é a do SelectBoxes (`.option-card`, selectboxes.css, que a
   página carrega); aqui vem o que o faz porta. As coordenadas do rato dá-as o
   `utils/RastoDoRato.js`. Quem o usa diz só a largura de cada cartão.
══════════════════════════════════════════════════════════════ */
.option-card.cms-door {
    position: relative;
    display: block;
    max-width: none;
    min-height: 0;
    padding: var(--space-5) var(--space-4);
    color: var(--color-text);
    text-decoration: none;
    --mx: 50%;
    --my: 50%;
    --aurora: var(--color-text);
    transition: border-color var(--dur-fast) ease, background-color var(--dur-fast) ease;
}
.option-card.cms-door::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(360px circle at var(--mx) var(--my), color-mix(in srgb, var(--aurora), transparent 96%), transparent 70%);
    opacity: 0;
    transition: opacity var(--dur-md) ease;
    pointer-events: none;
}
.option-card.cms-door:hover,
.option-card.cms-door:focus-visible,
.option-card.cms-door:focus-within { background-color: var(--color-surface); border-color: color-mix(in srgb, var(--color-border), transparent 50%); outline: none; }
.option-card.cms-door:hover::after { opacity: 1; }
.option-card.cms-door > * { position: relative; z-index: 1; }
.cms-door-body { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-4); height: 100%; text-align: center; }
.cms-door-icons { display: inline-flex; align-items: center; gap: var(--space-3); }
.cms-door-icons .ph-icon { font-size: var(--text-4xl); color: var(--color-text); }
.cms-door-copy { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-1) 0 var(--space-2); }
.cms-door-title { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-semibold); color: var(--color-text); letter-spacing: normal; text-transform: none; }
.cms-door-text { font-size: var(--text-sm); font-weight: 400; color: var(--color-text-muted); line-height: 1.4; }
@media (min-width: 769px) {
    .option-card.cms-door { min-height: 264px; padding: var(--space-6) var(--space-5); }
    .cms-door-icons .ph-icon { font-size: 40px; }
}
@media (prefers-reduced-motion: reduce) {
    .option-card.cms-door, .option-card.cms-door::after { transition: none; }
}
