/* ===================================================================
   EFICÁCIA CRM — ds-ficha.css
   FASE 1 (proposta-design §3 e §6.2) — a folha do MOTOR DE FICHA.
   Topbar · hero · heróis · alertas · timebar · abas · secções ·
   campos (ler⇄editar) · sub-tabela · ficheiros · atividade · rodapé.

   Par obrigatório: mod-ficha.js (window.MOD.ficha).
   Depende de: ds-tokens.css (paleta, --sp-*, --txt-*, --r-*, --sh-*).

   ===================================================================
   DUAS REGRAS DE OURO — ler antes de mexer
   ===================================================================

   1) TODO o elemento que o motor estiliza leva uma classe de UMA das
      dez famílias da ficha:

        ds-ficha·  ds-heroi·  ds-secao·  ds-campo·  ds-timebar·
        ds-alerta· ds-subtabela· ds-ficheiro· ds-atividade· ds-etq

      Elementos que reaproveitam componentes JÁ EXISTENTES (.ds-table,
      .ds-badge, .ds-skeleton) NÃO levam classe destas famílias — a
      ficha marca-os por `data-*` (data-anulada, data-total, data-sub).
      Porquê: ver a regra 2.

   2) A PONTE DE MIGRAÇÃO (fim do ficheiro) só alcança essas famílias.

   ===================================================================
   PORQUÊ A PONTE EXISTE — a regra de cascata que apanha toda a gente
   ===================================================================

   `@layer` decide ANTES da especificidade, e CSS *sem* camada ganha a
   CSS de *qualquer* camada. O legado (style.css) está todo sem camada
   e faz, logo no reset:

       *, *::before, *::after { margin: 0; padding: 0 }
       button { border: none; background: none }
       a      { color: inherit; text-decoration: none }
       ul, ol { list-style: none }
       input, select, textarea { font-size: inherit }

   Consequência medida (não estimada) num browser real: qualquer
   `padding:` escrito dentro de `@layer ficha` computa **0px** — por
   mais específico que seja o seletor. Uma folha inteiramente em camada
   ficaria sem caixa, sem fundos de botão e sem sublinhados até à Fase 6.

   Solução, também verificada em browser: um bloco FORA de camada que
   devolve essas oito propriedades à camada, com `revert-layer`. Numa
   regra sem camada, `revert-layer` recua para a ÚLTIMA camada — ou
   seja, para o que este ficheiro escreveu em `@layer ficha`. A ponte
   ganha ao reset por especificidade (0-1-0 > 0-0-0) e devolve o valor
   à camada. Zero `!important` (já há 335 na app; são o problema, não a
   solução) e zero duplicação de valores.

   A ponte é restrita às dez famílias de propósito: se apanhasse
   `.ds-btn` ou `.ds-table` (que vivem sem camada no design-system.css)
   o `revert-layer` deitaria fora o estilo deles. Daí a regra 1.

   FASE 6: quando o legado passar a ser servido dentro de
   `@layer legado{}`, apaga-se o bloco da ponte — e mais nada. O resto
   desta folha já está escrito para esse dia.

   ===================================================================
   MOBILE-FIRST (§5.1) — só `min-width`
   ===================================================================
   Base = 320-390 px. As únicas exceções permitidas, e usadas no fim
   do ficheiro, são as duas queries não-dimensionais: `pointer: coarse`
   (toque em qualquer largura) e `max-height: 520px` (telemóvel em
   paisagem). Breakpoints: 480 · 640 · 768 · 1024 · 1440.
   =================================================================== */

@layer ficha {

    /* ===============================================================
       0. RAIZ E VARIÁVEIS LOCAIS
       =============================================================== */

    .ds-ficha {
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
        min-width: 0;

        /* altura de controlo: 44px = alvo de toque mínimo (WCAG 2.5.5) */
        --fh-ctrl: 44px;
        /* cor da faixa de identidade; o JS injeta a do estado (validada) */
        --fh-estado: var(--ink-300);
    }

    /* §5.6 — um item de flex/grid nunca cresce acima do pai.
       Sozinha, esta regra resolve 80 % dos bugs de mobile. */
    .ds-ficha *,
    .ds-ficha *::before,
    .ds-ficha *::after {
        min-width: 0;
    }

    /* …e nunca ENCOLHE abaixo do seu conteúdo. Se a ficha for colocada
       num contentor com altura definida (uma coluna flex, o corpo de um
       modal), os blocos de topo espremiam-se: medido em teste, o hero
       colapsava de 677 px para 34 px. */
    .ds-ficha>* {
        flex-shrink: 0;
    }

    .ds-ficha-ico {
        flex-shrink: 0;
        width: 16px;
        height: 16px;
    }

    /* Rótulo repetido milhares de vezes na app — definição única,
       SEM dois-pontos (§2.3). */
    .ds-campo-rot,
    .ds-heroi-rot,
    .ds-atividade-dia-rot,
    .ds-ficha-serie {
        font-size: var(--txt-xs);
        font-weight: var(--fw-semibold);
        text-transform: uppercase;
        letter-spacing: var(--track-caps);
        color: var(--text-3);
    }

    /* ===============================================================
       1. TOPBAR (§3.2) — sticky, condensa ao rolar
       =============================================================== */

    .ds-ficha-topbar {
        position: sticky;
        top: 0;
        z-index: 5;
        display: flex;
        align-items: center;
        gap: var(--sp-2);
        min-height: 56px;
        padding: var(--sp-2) 0;
        background: var(--surface);
        border-bottom: 1px solid transparent;
        transition: min-height .15s ease, box-shadow .15s ease, border-color .15s ease;
    }

    /* Assim que o hero sai do viewport (IntersectionObserver), a barra
       encolhe e revela código + estado: nunca se perde a identidade do
       registo ao rolar 3 000 px de linhas. */
    .ds-ficha-topbar[data-condensada="1"] {
        min-height: 44px;
        border-bottom-color: var(--border);
        box-shadow: var(--sh-1);
        background: color-mix(in srgb, var(--surface) 90%, transparent);
        backdrop-filter: blur(8px);
        -webkit-backdrop-filter: blur(8px);
    }

    .ds-ficha-voltar {
        display: inline-flex;
        align-items: center;
        gap: var(--sp-1);
        min-height: var(--fh-ctrl);
        min-width: var(--fh-ctrl);
        padding: 0 var(--sp-3) 0 var(--sp-2);
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        color: var(--text-2);
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-md);
        transition: background .15s ease, border-color .15s ease;
    }

    .ds-ficha-voltar:hover {
        background: var(--surface-sunken);
        border-color: var(--border-strong);
        color: var(--text-1);
    }

    /* Substitui as migalhas, que gastavam uma linha inteira para dizer
       "Início / Lista de Contratos / Detalhe de Contrato". */
    .ds-ficha-voltar-txt {
        display: none;
    }

    /* Fora do estado condensado NÃO pode ocupar largura: a 320 px, o
       código + badge escondidos chegavam para empurrar a barra e criar
       scroll horizontal na página inteira. `width:0` + `overflow`
       tira-o do cálculo sem o tirar do DOM (a transição continua a ser
       de opacidade).
       É `width`, e não `max-width`, de propósito: o style.css declara
       `.page-content * { max-width: 100vw }` sem camada em ≤768 px, e
       isso ganharia a qualquer max-width escrito aqui. */
    .ds-ficha-topbar-id {
        display: flex;
        align-items: center;
        gap: var(--sp-2);
        min-width: 0;
        width: 0;
        overflow: hidden;
        opacity: 0;
        transform: translateY(-4px);
        pointer-events: none;
        transition: opacity .15s ease, transform .15s ease;
    }

    .ds-ficha-topbar[data-condensada="1"] .ds-ficha-topbar-id {
        width: auto;
        opacity: 1;
        transform: none;
    }

    .ds-ficha-topbar-cod {
        font-family: var(--font-mono);
        font-size: var(--txt-sm);
        font-weight: var(--fw-bold);
        color: var(--text-1);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .ds-ficha-topbar-acoes {
        display: flex;
        align-items: center;
        gap: var(--sp-2);
        margin-left: auto;
    }

    /* ===============================================================
       2. HERO (§3.3) — identidade + cockpit de heróis
       =============================================================== */

    /* `--fh-pad` existe para que um filho POSSA sangrar até ao bordo do
       cartão sem adivinhar o respiro (a capa da variante `foto`, §3.3a:
       `margin: calc(var(--fh-pad) * -1)`). O respiro muda em três sítios
       — base, ≥768 e paisagem curta — e a variável é o que os mantém
       sincronizados. Quem não sangra não nota diferença nenhuma. */
    .ds-ficha-hero {
        --fh-pad: var(--sp-4);
        position: relative;
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
        padding: var(--fh-pad);
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-xl);
        box-shadow: var(--sh-1);
        overflow: hidden;
    }

    /* A marca de assinatura nº 1 (§2.8): faixa de 3 px na cor do
       estado, no topo do cartão-herói. */
    .ds-ficha-faixa {
        position: absolute;
        inset: 0 0 auto 0;
        height: 3px;
        background: var(--fh-estado);
    }

    .ds-ficha-hero-topo {
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
        min-width: 0;
    }

    /* ---- 2.1 identidade: três variantes, uma classe ---- */

    .ds-ficha-ident {
        display: flex;
        align-items: flex-start;
        gap: var(--sp-3);
        min-width: 0;
    }

    .ds-ficha-simbolo {
        position: relative;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 2px;
        flex: 0 0 auto;
        width: 56px;
        height: 56px;
        border-radius: var(--r-lg);
        background: var(--brand-50);
        color: var(--brand-700);
        overflow: hidden;
    }

    .ds-ficha-simbolo .ds-ficha-ico {
        width: 22px;
        height: 22px;
    }

    /* --pessoa: avatar circular com iniciais e cor da BD */
    .ds-ficha-ident--pessoa .ds-ficha-simbolo {
        border-radius: var(--r-full);
        background: var(--fh-avatar, var(--ink-100));
        color: var(--text-1);
        font-size: var(--txt-xl);
        font-weight: var(--fw-bold);
    }

    /* --foto: miniatura 16:9 com a foto real do suporte */
    .ds-ficha-ident--foto .ds-ficha-simbolo {
        width: 96px;
        height: 54px;
        border-radius: var(--r-lg);
        object-fit: cover;
        background: var(--surface-sunken);
        color: var(--text-3);
    }

    /* ---------------------------------------------------------------
       A CAPA E UMA MOLDURA COM UMA FOTO DENTRO

       O `object-fit: cover` da regra acima resolvia o recorte enquanto o
       recorte fosse sempre CENTRADO. Com o enquadramento por suporte
       (`--capa-pos`) e o zoom (`--capa-zoom`), a foto tem de poder ser
       maior do que a banda — e alguem tem de a cortar.

       A moldura corta; a foto la dentro enche-a e obedece as duas
       variaveis. Quando nao ha enquadramento gravado as variaveis nao
       existem e os valores por omissao dao exatamente o que a capa
       fazia antes: centro, zoom 1. Uma funcionalidade que so se nota
       quando alguem a usa.

       `transform-origin` IGUAL ao `object-position` de proposito: sem
       isso o zoom ampliava a partir do centro e fugia com o suporte
       para fora da banda — que e o defeito que isto veio corrigir.
       --------------------------------------------------------------- */
    .ds-ficha-simbolo--capa {
        display: block;
        position: relative;
        overflow: hidden;
        padding: 0;
        /* A moldura e um <span>: sem isto herdava o `line-height` do
           cabecalho e ficava uns pixeis mais alta do que a foto. */
        line-height: 0;
    }

    .ds-ficha-capa-img {
        display: block;
        width: 100%;
        height: 100%;
        max-width: none;
        object-fit: cover;
        object-position: var(--capa-pos, 50% 50%);
        transform: scale(var(--capa-zoom, 1));
        transform-origin: var(--capa-pos, 50% 50%);
    }

    .ds-ficha-serie {
        color: inherit;
        opacity: .8;
    }

    .ds-ficha-ident-txt {
        display: flex;
        flex-direction: column;
        gap: 2px;
        min-width: 0;
    }

    /* Códigos em mono: é a assinatura visual das referências (§2.3). */
    .ds-ficha-codigo {
        font-family: var(--font-mono);
        font-variant-numeric: var(--num-tabular);
        font-size: var(--txt-xl);
        font-weight: var(--fw-bold);
        color: var(--text-1);
        overflow-wrap: anywhere;
    }

    .ds-ficha-codigo--sec {
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        color: var(--brand-700);
    }

    .ds-ficha-titulo {
        font-size: var(--txt-base);
        font-weight: var(--fw-semibold);
        color: var(--text-2);
        overflow-wrap: anywhere;
    }

    .ds-ficha-titulo--grande {
        font-size: var(--txt-xl);
        font-weight: var(--fw-bold);
        color: var(--text-1);
        letter-spacing: var(--track-tight);
    }

    .ds-ficha-sub {
        font-size: var(--txt-sm);
        color: var(--text-3);
        overflow-wrap: anywhere;
    }

    /* O «·» separa duas informações distintas — a 1.63:1 desaparecia e as
       duas colavam-se numa só frase. `--ink-400` é o token decorativo
       (§2.2) e chega para o ponto se ver. */
    .ds-ficha-sep {
        color: var(--ink-400);
    }

    .ds-ficha-marcas {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--sp-1);
        margin-top: var(--sp-1);
    }

    /* Etiquetas (marca anunciante, categoria…) — a marca é a linguagem
       do negócio OOH, por isso tem variante própria. */
    .ds-etq {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        padding: 2px var(--sp-2);
        font-size: var(--txt-xs);
        font-weight: var(--fw-semibold);
        color: var(--text-2);
        background: var(--ink-100);
        border: 1px solid transparent;
        border-radius: var(--r-full);
        white-space: nowrap;
        /* Uma marca comprida — «Sara Gregoria Cilia Immobiliare/ Art
           Studios Melides», das folhas de obra — era CORTADA a meio da
           palavra em mobile: `nowrap` sem `text-overflow` dá um corte
           seco, que se lê como texto partido e não como texto que
           continua. Com reticências o utilizador vê que há mais, e o
           `title` no elemento (posto pelo motor) dá-lhe o valor todo. */
        max-width: 100%;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .ds-etq--marca {
        background: var(--brand-50);
        border-color: var(--brand-100);
        color: var(--brand-700);
    }

    .ds-etq--info {
        background: var(--info-50);
        color: var(--info-700);
    }

    .ds-etq--aviso {
        background: var(--warning-50);
        color: var(--warning-700);
    }

    /* Ocupação de um suporte OOH: «Ocupado hoje» é receita a correr,
       «Livre hoje» é a oportunidade comercial da matriz de alertas
       (§20 #16). São o mesmo eixo lido nos dois sentidos, por isso
       partilham a forma e distinguem-se só pela cor. */
    .ds-etq--ocupado {
        background: var(--success-50);
        border-color: var(--success-100);
        color: var(--success-700);
    }

    .ds-etq--livre {
        background: var(--info-50);
        border-color: var(--info-100);
        color: var(--info-700);
    }

    /* Estado: propriedade, não ação de rodapé (§3.0). */
    .ds-ficha-estado {
        display: inline-flex;
        align-items: center;
        gap: 2px;
        background: transparent;
        border: none;
        padding: 0;
    }

    /* Alvo de toque completo (44 px) sem engordar a pílula: o botão é
       maior do que o badge que mostra. */
    .ds-ficha-estado--editavel {
        min-height: var(--fh-ctrl);
        padding: 0 var(--sp-1) 0 0;
        color: var(--text-3);
        border-radius: var(--r-full);
    }

    .ds-ficha-estado--editavel:hover {
        background: var(--surface-sunken);
    }

    /* ---- 2.2 cockpit de heróis: 2-4 mosaicos, nunca mais ---- */

    .ds-heroi {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sp-2);
    }

    .ds-heroi-item {
        display: flex;
        flex-direction: column;
        gap: 2px;
        padding: var(--sp-3);
        background: var(--surface-alt);
        border: 1px solid var(--border);
        border-radius: var(--r-lg);
        min-width: 0;
    }

    .ds-heroi-linha {
        display: flex;
        align-items: baseline;
        gap: var(--sp-1);
        min-width: 0;
    }

    /* É a diferença entre "ler a ficha" e "perceber a ficha" (§2.8-2).
       `overflow-wrap` fica em `normal` DE PROPÓSITO: com `anywhere`,
       "15 252,00 €" partia-se a meio do número em 320 px ("15 252,0" /
       "0 €"). Um valor monetário só pode quebrar no espaço — e, para
       nem isso ser preciso, o corpo arranca em 18 px e só cresce a
       partir dos 480 px, onde a coluna já lhe dá espaço. */
    .ds-heroi-valor {
        /* O ÚNICO valor fluido da folha, e com motivo: em 2 colunas o
           mosaico mede ~104 px a 320 e ~135 px a 390, por isso um corpo
           fixo ou não cabe em baixo ou é pequeno em cima. O clamp cresce
           ao mesmo ritmo da coluna (16 px → 22 px) e trava a 480. Em
           ≥1024, onde os mosaicos ficam em linha, passa a 28 px fixos. */
        font-size: clamp(var(--txt-lg), 4.6vw, var(--txt-2xl));
        font-weight: var(--fw-extra);
        line-height: 1.05;
        letter-spacing: var(--track-tight);
        font-variant-numeric: var(--num-tabular);
        color: var(--text-1);
        overflow-wrap: normal;
        hyphens: none;
    }

    /* `--text-3` e não `--ink-400`: o valor «vazio» do herói quase nunca é
       um traço — é uma FRASE («não registado», «sem renda», «não agendada»)
       e o §2.2 proíbe `--ink-400` em texto (2.6:1). Com `--text-3` sobe
       para 5.6:1 e continua visivelmente mais leve que um valor real,
       porque o peso desce de `extra` para `semibold`. */
    .ds-heroi-valor--vazio {
        color: var(--text-3);
        font-weight: var(--fw-semibold);
    }

    .ds-heroi-sufixo {
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        color: var(--text-3);
    }

    .ds-heroi-nota {
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    /* UM mosaico por ficha leva --principal: é o número que responde à
       pergunta do gestor. */
    .ds-heroi-item--principal {
        background: var(--brand-50);
        border-color: var(--brand-200);
    }

    .ds-heroi-item--principal .ds-heroi-valor {
        color: var(--brand-700);
    }

    .ds-heroi-item--positivo .ds-heroi-valor {
        color: var(--success-700);
    }

    .ds-heroi-item--negativo .ds-heroi-valor {
        color: var(--danger-700);
    }

    /* ===============================================================
       3. RAIL DE ALERTAS (§3.3c)
       Três níveis, o mesmo vocabulário em toda a app. Sem alertas o
       rail não existe — nunca uma caixa vazia a dizer "sem alertas".
       =============================================================== */

    /* CONTRATO com o mod-alertas.js (documentado no cabeçalho dele):
         ul.ds-ficha-alertas > li.ds-alerta.ds-alerta--<nivel>
           span.ds-alerta-icone · span.ds-alerta-corpo
             ( strong.ds-alerta-texto + span.ds-alerta-detalhe )
           button.ds-alerta-accao
       Níveis: critico · acao · aviso · info (`--erro` fica como sinónimo
       de `--critico`, que é o nome usado na proposta §3.3c).
       Serve tanto o rail que o motor da ficha desenha como o HTML que o
       `MOD.alertas.html()` devolve — mesma folha, os dois caminhos. */
    .ds-ficha-alertas {
        display: flex;
        flex-direction: column;
        gap: var(--sp-2);
        list-style: none;
        padding: 0;
        margin: 0;
    }

    .ds-alerta {
        display: flex;
        flex-wrap: wrap;
        align-items: flex-start;
        gap: var(--sp-2);
        padding: var(--sp-2) var(--sp-3);
        border-left: 3px solid;
        border-radius: var(--r-sm);
        font-size: var(--txt-sm);
    }

    .ds-alerta-icone {
        display: flex;
        flex: 0 0 auto;
        padding-top: 2px;
    }

    /* `flex-basis: 0` e não `auto`. Com `auto` a base do corpo é o seu
       max-content: num cartão com texto longo o corpo já não cabia ao lado
       do ícone e a repartição por linhas (que acontece ANTES do shrink,
       porque o `.ds-alerta` é `flex-wrap: wrap`) empurrava-o para a linha
       seguinte — o ícone ficava sozinho numa linha só dele. Resultado: na
       mesma lista, uns cartões com o ícone ao lado do título e outros com
       o ícone por cima. Com base 0 o corpo nunca é o que quebra. */
    .ds-alerta-corpo {
        display: flex;
        flex-direction: column;
        gap: 1px;
        flex: 1 1 0;
        min-width: 0;
    }

    .ds-alerta-texto {
        font-weight: var(--fw-semibold);
    }

    .ds-alerta-detalhe {
        opacity: .88;
    }

    .ds-alerta--critico,
    .ds-alerta--erro {
        background: var(--danger-50);
        border-color: var(--danger-500);
        color: var(--danger-700);
    }

    .ds-alerta--aviso {
        background: var(--warning-50);
        border-color: var(--warning-500);
        color: var(--warning-700);
    }

    /* `acao` é a inovação face aos dois CRMs analisados: não é um
       problema, é dinheiro em cima da mesa — por isso usa a cor da
       marca e traz sempre um botão. */
    .ds-alerta--acao {
        background: var(--brand-50);
        border-color: var(--brand-500);
        color: var(--brand-700);
    }

    /* `info` não é um problema — é contexto (contrato anulado, sem
       gestor). Fica calmo, para não gastar a atenção que os outros
       três níveis precisam. */
    .ds-alerta--info {
        background: var(--info-50);
        border-color: var(--info-500);
        color: var(--info-700);
    }

    /* `background` explícito é OBRIGATÓRIO: o `.ds-btn` base não declara
       nenhum (todos os `--variante` é que o fazem) e este botão não usa
       variante. Sem isto o Chromium pintava-o com o `buttonface` do
       sistema (#F0F0F0) — 56 botões da app com o cinzento por omissão do
       browser em cima de cartões tingidos, e o rótulo a 4.41:1 (falha AA).
       Sobre `--surface` o mesmo rótulo dá 5.05:1. */
    /* Seletor com DOIS níveis de propósito. O botão do rail da ficha vem
       com `ds-ficha-acao--fantasma` (transparente, cinzento) e essa classe
       está declarada mais abaixo NESTA folha: com a mesma especificidade
       ganhava a última e o «Definir PVP» ficava texto cinzento centrado no
       meio de um cartão âmbar — nem parecia um botão. `.ds-alerta` +
       `.ds-alerta-accao` = (0,2,0) e a ação volta a ser um botão, com a
       cor do nível do alerta. */
    .ds-alerta .ds-alerta-accao {
        flex: 1 0 100%;
        justify-content: center;
        min-height: var(--fh-ctrl);
        border-color: currentColor;
        color: inherit;
        background: var(--surface);
    }

    /* Variante de linha de listagem: o mesmo vocabulário, sem caixa. */
    .ds-ficha-alertas--compacto {
        flex-direction: row;
        flex-wrap: wrap;
        gap: var(--sp-1);
    }

    .ds-ficha-alertas--compacto .ds-alerta {
        padding: 1px var(--sp-2);
        font-size: var(--txt-xs);
        border-radius: var(--r-full);
        border-left-width: 3px;
    }

    .ds-ficha-alertas--compacto .ds-alerta-detalhe {
        display: none;
    }

    /* ===============================================================
       4. TIMEBAR (§3.5) — uma primitiva, sete usos
       contrato · validade de proposta · licença · seguro/inspeção/IUC ·
       próxima inspeção · plafond de férias · payback de estrutura.
       =============================================================== */

    .ds-timebar {
        display: flex;
        flex-direction: column;
        gap: var(--sp-1);
        min-width: 0;
    }

    .ds-timebar-trilho {
        height: 6px;
        background: var(--ink-100);
        border-radius: var(--r-full);
        overflow: hidden;
    }

    .ds-timebar-preenchido {
        width: calc(var(--pct, 0) * 1%);
        height: 100%;
        background: var(--brand-500);
        border-radius: inherit;
        transition: width .4s cubic-bezier(.4, 0, .2, 1);
    }

    .ds-timebar-txt {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        gap: var(--sp-1) var(--sp-3);
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    .ds-timebar-intervalo {
        font-variant-numeric: var(--num-tabular);
    }

    .ds-timebar-etiqueta {
        font-weight: var(--fw-semibold);
    }

    .ds-timebar--futuro .ds-timebar-preenchido {
        background: var(--ink-300);
    }

    .ds-timebar--a-terminar .ds-timebar-preenchido {
        background: var(--warning-500);
    }

    .ds-timebar--a-terminar .ds-timebar-etiqueta {
        color: var(--warning-700);
    }

    .ds-timebar--expirado .ds-timebar-preenchido {
        background: var(--danger-500);
    }

    .ds-timebar--expirado .ds-timebar-etiqueta {
        color: var(--danger-700);
    }

    /* Sem datas não gera barras na timeline: assinalar, nunca fingir. */
    .ds-timebar--sem-datas .ds-timebar-trilho {
        background: repeating-linear-gradient(90deg, var(--ink-100) 0 6px, transparent 6px 12px);
    }

    /* ===============================================================
       5. ABAS (§3.4) — são ROTAS, não `role="tab"`
       aria-current="page", contadores em pílula, scroll-x sem barra.
       =============================================================== */

    .ds-ficha-abas {
        display: flex;
        gap: var(--sp-1);
        overflow-x: auto;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        border-bottom: 1px solid var(--border);
    }

    .ds-ficha-abas::-webkit-scrollbar {
        display: none;
    }

    .ds-ficha-aba {
        display: inline-flex;
        align-items: center;
        gap: var(--sp-2);
        flex: 0 0 auto;
        min-height: var(--fh-ctrl);
        padding: 0 var(--sp-3);
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        color: var(--text-3);
        background: transparent;
        border: none;
        border-bottom: 2px solid transparent;
        border-radius: var(--r-sm) var(--r-sm) 0 0;
        white-space: nowrap;
        transition: color .15s ease, border-color .15s ease, background .15s ease;
    }

    .ds-ficha-aba:hover {
        color: var(--text-1);
        background: var(--surface-sunken);
    }

    .ds-ficha-aba[aria-current="page"] {
        color: var(--brand-700);
        border-bottom-color: var(--brand-500);
    }

    .ds-ficha-aba-cont {
        padding: 1px var(--sp-2);
        font-size: var(--txt-2xs);
        font-weight: var(--fw-bold);
        font-variant-numeric: var(--num-tabular);
        color: var(--text-3);
        background: var(--ink-100);
        border-radius: var(--r-full);
    }

    .ds-ficha-aba[aria-current="page"] .ds-ficha-aba-cont {
        color: var(--brand-700);
        background: var(--brand-50);
    }

    /* ===============================================================
       6. CORPO
       A altura do painel é min-height, NUNCA height fixa — a altura
       fixa produziu a "barriga branca" documentada no ElectroDaniel.
       =============================================================== */

    .ds-ficha-corpo {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sp-4);
        min-width: 0;
    }

    .ds-ficha-painel,
    .ds-ficha-lateral {
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
        min-height: 120px;
        min-width: 0;
    }

    /* ===============================================================
       7. SECÇÃO (§3.5) — cabeçalho canónico (substitui ~7 variantes)
       =============================================================== */

    .ds-secao {
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-xl);
        box-shadow: var(--sh-1);
        min-width: 0;
    }

    .ds-secao-cab {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--sp-2);
        padding: var(--sp-3) var(--sp-4);
        border-bottom: 1px solid var(--border);
    }

    .ds-secao-titulo {
        display: inline-flex;
        align-items: center;
        gap: var(--sp-2);
        min-width: 0;
        font-size: var(--txt-sm);
        font-weight: var(--fw-bold);
        text-transform: uppercase;
        letter-spacing: var(--track-caps);
        color: var(--text-1);
        background: transparent;
        border: none;
        padding: 0;
        text-align: left;
    }

    /* Ícone 16 px SEMPRE na cor da marca: é o que dá coesão a uma
       ficha com 8 secções. */
    .ds-secao-titulo .ds-ficha-ico {
        color: var(--brand-600);
    }

    /* O título colapsável é um botão: precisa de 44 px de alvo. O par
       padding/margem simétrico faz o alvo crescer para dentro do
       padding do cabeçalho — a altura visível não muda. */
    .ds-secao-toggle {
        min-height: var(--fh-ctrl);
        padding: var(--sp-3) 0;
        margin: calc(var(--sp-3) * -1) 0;
    }

    .ds-secao-toggle .ds-ficha-ico:last-child {
        color: var(--text-3);
        transition: transform .15s ease;
    }

    .ds-secao[data-colapsada="1"] .ds-secao-toggle .ds-ficha-ico:last-child {
        transform: rotate(-90deg);
    }

    .ds-secao-txt {
        overflow-wrap: anywhere;
    }

    .ds-secao-cont {
        padding: 1px var(--sp-2);
        font-size: var(--txt-2xs);
        font-weight: var(--fw-bold);
        font-variant-numeric: var(--num-tabular);
        letter-spacing: 0;
        color: var(--text-3);
        background: var(--ink-100);
        border-radius: var(--r-full);
    }

    .ds-secao-acoes {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: var(--sp-2);
        margin-left: auto;
    }

    .ds-secao-corpo {
        padding: var(--sp-4);
    }

    .ds-secao[data-colapsada="1"] .ds-secao-corpo {
        display: none;
    }

    /* Realce momentâneo — dá retorno visível a uma ação que só levaria o
       utilizador a uma secção onde ele já estava (ex.: "Rever preços" na
       aba Artigos). Sem isto o clique parece não fazer nada. */
    .ds-secao--realce {
        animation: dsSecaoRealce 1.4s ease-out;
    }

    @keyframes dsSecaoRealce {
        0% { box-shadow: 0 0 0 3px var(--danger-500); }
        100% { box-shadow: var(--sh-1); }
    }

    .ds-secao-vazio,
    .ds-subtabela-vazio,
    .ds-ficheiros-vazio,
    .ds-atividade-vazio {
        display: block;
        padding: var(--sp-4) 0;
        font-size: var(--txt-sm);
        color: var(--text-3);
        text-align: center;
    }

    /* ===============================================================
       8. CAMPOS (§3.5) — a grelha, e o ler⇄editar sem salto
       =============================================================== */

    .ds-campos {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sp-4) var(--sp-6);
        min-width: 0;
    }

    .ds-campo {
        display: flex;
        flex-direction: column;
        gap: 2px;
        min-width: 0;
    }

    .ds-campo-val {
        font-size: var(--txt-md);
        font-weight: var(--fw-medium);
        color: var(--text-2);
        overflow-wrap: anywhere;
    }

    .ds-campo-nota {
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    .ds-campo--num .ds-campo-val {
        font-variant-numeric: var(--num-tabular);
    }

    .ds-campo--mono .ds-campo-val {
        font-family: var(--font-mono);
        font-variant-numeric: var(--num-tabular);
        font-weight: var(--fw-semibold);
        color: var(--brand-700);
    }

    .ds-campo--forte .ds-campo-val {
        font-size: var(--txt-lg);
        font-weight: var(--fw-bold);
        color: var(--text-1);
    }

    /* Nunca deixa a célula em branco: evita o colapso da grelha e o
       efeito "faltou carregar".
       A premissa de que aqui só vivia um traço era falsa: o `def.vazio`
       do MOD.ficha traz FRASES em 20 sítios («sem família», «não
       registado», «sem horário definido», «sem coordenadas — não aparece
       no mapa»). Ou seja, `--ink-400` estava a pintar texto legível a
       2.69:1, contra a regra do próprio §2.2. `--text-3` dá 5.63:1 e a
       distinção face a um valor real continua a fazer-se pelo peso
       (`normal` contra `medium`), que é o sinal certo — não pelo
       apagamento. */
    .ds-campo--vazio .ds-campo-val {
        color: var(--text-3);
        font-weight: var(--fw-normal);
    }

    .ds-campo--full {
        grid-column: 1 / -1;
    }

    /* O valor com ligação é o alvo de toque mais frequente da ficha
       (cliente, proposta, equipamento). Em toque leva a altura de um
       controlo — que é, não por acaso, a mesma altura que o campo terá
       quando entrar em edição: a linha não muda de tamanho ao editar. */
    .ds-campo-lig {
        display: inline-flex;
        align-items: center;
        min-height: var(--fh-ctrl);
        gap: 4px;
        padding: 0;
        font: inherit;
        font-size: var(--txt-md);
        font-weight: var(--fw-medium);
        color: var(--brand-600);
        background: none;
        border: none;
        text-align: left;
        text-decoration: none;
    }

    .ds-campo-lig:hover {
        color: var(--brand-700);
        text-decoration: underline;
    }

    .ds-campo-lig .ds-ficha-ico {
        width: 12px;
        height: 12px;
        opacity: .7;
    }

    /* ---- 8.1 edição in-place ----------------------------------
       A MESMA grelha troca .ds-campo por .ds-campo-edit: o layout não
       se mexe e não há segundo ecrã para manter (ElectroDaniel §3.1-5).
       Controlos a 16 px na base para evitar o auto-zoom do iOS (§2.3). */

    .ds-campo-edit {
        gap: var(--sp-1);
    }

    .ds-ficha-input,
    .ds-ficha-select,
    .ds-ficha-textarea {
        width: 100%;
        max-width: 100%;
        min-height: var(--fh-ctrl);
        padding: 0 var(--sp-3);
        font-family: inherit;
        font-size: 16px;
        color: var(--text-1);
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-md);
        transition: border-color .15s ease, box-shadow .15s ease;
    }

    .ds-ficha-textarea {
        min-height: 84px;
        padding: var(--sp-2) var(--sp-3);
        line-height: 1.5;
        resize: vertical;
    }

    .ds-ficha-input:hover,
    .ds-ficha-select:hover,
    .ds-ficha-textarea:hover {
        border-color: var(--border-strong);
    }

    .ds-ficha-input:focus,
    .ds-ficha-select:focus,
    .ds-ficha-textarea:focus {
        outline: none;
        border-color: var(--brand-400);
        box-shadow: var(--ring);
    }

    /* `appearance:none` abaixo de 900 px: o input[type=date] do iOS
       ignora width:100% enquanto mantiver aparência nativa (bug real,
       já pago pelo ElectroDaniel). */
    .ds-ficha-input[type="date"],
    .ds-ficha-input[type="datetime-local"],
    .ds-ficha-input[type="time"] {
        appearance: none;
        -webkit-appearance: none;
    }

    /* ===============================================================
       9. SUB-TABELA (§3.5)
       O <table> usa .ds-table (design-system): desktop = tabela,
       ≤768 px = cartões pelo mecanismo `data-label` que já existe —
       estratégia ÚNICA em toda a app (§5.3). Aqui só se acrescenta o
       que é próprio da ficha, e sempre por `data-*` para não colidir
       com o estilo (sem camada) do .ds-table. Ver regra de ouro 1.
       =============================================================== */

    .ds-subtabela {
        min-width: 0;
    }

    .ds-subtabela-cel {
        display: inline-flex;
        flex-direction: column;
        gap: 1px;
        min-width: 0;
    }

    .ds-subtabela-nota {
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    .ds-subtabela-vazio-cel {
        color: var(--ink-400);
    }

    /* Linha anulada: esbatida, com risco SÓ no valor, e contada no
       rodapé — os totais não a incluem e isso tem de ser visível. */
    .ds-subtabela [data-anulada="1"] {
        opacity: .55;
    }

    .ds-subtabela [data-anulada="1"] .ds-subtabela-cel {
        text-decoration: line-through;
    }

    .ds-subtabela [data-total="1"] {
        font-weight: var(--fw-bold);
    }

    .ds-subtabela-rodape {
        margin-top: var(--sp-2);
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    /* Linha inteira clicável, com teclado (role="link" + tabindex). */
    .ds-subtabela [role="link"] {
        cursor: pointer;
    }

    /* ===============================================================
       10. FICHEIROS (§3.5)
       =============================================================== */

    .ds-ficheiros {
        display: grid;
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sp-2);
    }

    .ds-ficheiro {
        display: flex;
        align-items: flex-start;
        gap: var(--sp-3);
        padding: var(--sp-3);
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-lg);
        min-width: 0;
    }

    .ds-ficheiro-ico {
        display: flex;
        flex: 0 0 auto;
        color: var(--text-3);
    }

    .ds-ficheiro-ico .ds-ficha-ico {
        width: 18px;
        height: 18px;
    }

    .ds-ficheiro-corpo {
        display: flex;
        flex-direction: column;
        gap: 2px;
        min-width: 0;
    }

    .ds-ficheiro-nome {
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        color: var(--text-1);
        overflow-wrap: anywhere;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    .ds-ficheiro-meta {
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    .ds-ficheiro-validade {
        align-self: flex-start;
        margin-top: 2px;
        padding: 1px var(--sp-2);
        font-size: var(--txt-2xs);
        font-weight: var(--fw-bold);
        text-transform: uppercase;
        letter-spacing: var(--track-caps);
        border-radius: var(--r-full);
        background: var(--success-50);
        color: var(--success-700);
    }

    .ds-ficheiro-validade--aviso {
        background: var(--warning-50);
        color: var(--warning-700);
    }

    .ds-ficheiro-validade--erro {
        background: var(--danger-50);
        color: var(--danger-700);
    }

    /* 25 MB é o limite real da API de envio por email. */
    .ds-ficheiros-limite {
        margin-top: var(--sp-2);
        font-size: var(--txt-xs);
        font-variant-numeric: var(--num-tabular);
        color: var(--text-3);
    }

    .ds-ficheiros-limite--excedido {
        font-weight: var(--fw-bold);
        color: var(--danger-700);
    }

    /* ===============================================================
       11. ATIVIDADE (§3.5) — a linha do tempo unificada
       Funde *_atividades, comentários e o bloco AUDIT. É o que dá a
       sensação de "sistema vivo" que nenhum dos dois CRMs tem.
       =============================================================== */

    .ds-atividade {
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
    }

    .ds-atividade-dia-rot {
        margin-bottom: var(--sp-2);
    }

    .ds-atividade-lista {
        display: flex;
        flex-direction: column;
        list-style: none;
        padding: 0;
        margin: 0;
    }

    .ds-atividade-item {
        position: relative;
        display: flex;
        flex-direction: column;
        gap: 2px;
        padding: 0 0 var(--sp-4) var(--sp-5);
    }

    .ds-atividade-item:last-child {
        padding-bottom: 0;
    }

    /* rail de 2 px + ponto colorido por tipo */
    .ds-atividade-item::before {
        content: "";
        position: absolute;
        left: 4px;
        top: 6px;
        bottom: -2px;
        width: 2px;
        background: var(--ink-200);
    }

    .ds-atividade-item:last-child::before {
        bottom: auto;
        height: 0;
    }

    .ds-atividade-item::after {
        content: "";
        position: absolute;
        left: 0;
        top: 4px;
        width: 10px;
        height: 10px;
        border-radius: var(--r-full);
        background: var(--surface);
        border: 2px solid var(--brand-500);
    }

    .ds-atividade-item[data-tipo="criado"]::after,
    .ds-atividade-item[data-tipo="auditoria"]::after {
        border-color: var(--ink-300);
    }

    .ds-atividade-cab {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: var(--sp-2);
    }

    .ds-atividade-tipo {
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        color: var(--text-1);
    }

    .ds-atividade-quem,
    .ds-atividade-hora {
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    .ds-atividade-hora {
        margin-left: auto;
        font-variant-numeric: var(--num-tabular);
    }

    .ds-atividade-txt {
        font-size: var(--txt-sm);
        color: var(--text-2);
        overflow-wrap: anywhere;
    }

    .ds-atividade-meta {
        font-size: var(--txt-xs);
        color: var(--text-3);
    }

    /* ===============================================================
       12. AÇÕES (botões próprios da ficha)
       Reforço por FORMA, não só por cor (§2.2): a ação principal é
       preenchida; a destrutiva é contorno + ícone. Assim a distinção
       aguenta daltonismo e ecrãs maus.
       =============================================================== */

    .ds-ficha-acao {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: var(--sp-2);
        min-height: var(--fh-ctrl);
        padding: 0 var(--sp-4);
        font-size: var(--txt-sm);
        font-weight: var(--fw-semibold);
        line-height: 1;
        color: var(--text-2);
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-md);
        white-space: nowrap;
        text-decoration: none;
        transition: background .15s ease, border-color .15s ease, box-shadow .15s ease, transform .15s ease;
    }

    /* O :not(:disabled) nos hovers não é cosmética (30/08/2026): a regra
       ".ds-ficha-rodape .ds-ficha-acao--perigo" lá em baixo empata em
       especificidade (0-2-0) com o hover do perigo e, declarada depois,
       ganhava-lhe o fundo — no rodapé, passar o rato pelo Apagar pintava o
       texto de branco sobre danger-50 e o botão ficava EM BRANCO. Subir os
       hovers para 0-3-0 põe-nos a ganhar outra vez; e um botão desativado
       deixa de mudar de cara ao passar-lhe o rato por cima. */
    .ds-ficha-acao:not(:disabled):hover {
        background: var(--surface-sunken);
        border-color: var(--border-strong);
        color: var(--text-1);
    }

    .ds-ficha-acao:disabled,
    .ds-ficha-acao[aria-busy="true"] {
        opacity: .55;
    }

    /* O desativado FICA sob o rato: é isso que deixa o "title" contar a razão
       («Não se pode apagar a si próprio — peça a outro administrador»).
       Com pointer-events:none o tooltip nunca aparecia e o botão apagado
       ficava mudo — um botão apagado sem razão à vista é só um botão partido
       (30/08/2026, encontrado pelo dono). O clique continua morto por conta
       do próprio <button disabled>: o browser não dispara nada. */
    .ds-ficha-acao:disabled {
        /* o cursor vive na ponte SEM CAMADA la em baixo: o button{cursor:pointer}
           do style.css e unlayered e ganha a qualquer camada, por mais especifica. */
    }

    /* A meio de uma ação ninguém carrega segunda vez. */
    .ds-ficha-acao[aria-busy="true"] {
        pointer-events: none;
    }

    .ds-ficha-acao--primario {
        color: var(--text-inverse);
        background: linear-gradient(180deg, var(--brand-500), var(--brand-600));
        border-color: var(--brand-600);
        box-shadow: 0 1px 2px rgba(218, 10, 13, .25);
    }

    .ds-ficha-acao--primario:not(:disabled):hover {
        color: var(--text-inverse);
        background: linear-gradient(180deg, var(--brand-600), var(--brand-700));
        border-color: var(--brand-700);
        box-shadow: var(--sh-brand);
        transform: translateY(-1px);
    }

    .ds-ficha-acao--perigo {
        color: var(--danger-700);
        background: var(--danger-50);
        border-color: var(--danger-200);
    }

    .ds-ficha-acao--perigo:not(:disabled):hover {
        color: var(--text-inverse);
        background: var(--danger-600);
        border-color: var(--danger-600);
    }

    .ds-ficha-acao--fantasma {
        background: transparent;
        border-color: transparent;
        color: var(--text-3);
    }

    .ds-ficha-acao--fantasma:not(:disabled):hover {
        background: var(--surface-sunken);
        color: var(--text-1);
    }

    /* Até 768 px a topbar guarda só a ação principal; as secundárias
       vivem no rodapé, que em mobile é sticky e está mais à mão (§3.2).
       Declarado DEPOIS de `.ds-ficha-acao` de propósito: mesma
       especificidade, ganha a última. */
    .ds-ficha-acao--extra {
        display: none;
    }

    /* Ação de cabeçalho de secção: compacta na tipografia, nunca no
       alvo de toque (encolhe só onde há rato — ver §16, 1024/fine). */
    .ds-secao-acao {
        min-height: var(--fh-ctrl);
        padding: 0 var(--sp-3);
        font-size: var(--txt-xs);
    }

    /* ===============================================================
       13. BARRA DE AÇÕES (§3.6) — FIXA no fundo, ordem canónica
       ---------------------------------------------------------------
           [Apagar]  ·····················  [outras] [Editar] [Fechar]

       FIXA quer dizer fixa em TODAS as larguras. Era exatamente aqui
       que estava o defeito apontado pelo cliente: a partir de 640 px a
       barra voltava a `position:static` e, dentro do modal (onde quem
       rola é o painel, não a janela), fugia com o conteúdo.

       A ORDEM VEM DO MOTOR, não daqui: `mod-ficha.js` ordena as ações
       por papel (destrutiva → secundária → primária → fechar) e reparte
       -as por três zonas. Ao CSS cabe só a repartição do ESPAÇO —
       assim nenhum dos dez esquemas consegue enganar-se na ordem, e o
       CSS não tem de adivinhar papéis a partir de `--perigo`/`--primario`.

       Uma linha só, em todas as larguras. A zona do meio é a única
       elástica e a única que rola: Apagar, Editar e Fechar nunca saem
       do ecrã, nem a 320 px com cinco ações declaradas.
       =============================================================== */

    .ds-ficha-rodape {
        position: sticky;
        bottom: 0;
        z-index: 4;
        display: flex;
        flex-wrap: nowrap;
        align-items: center;
        /* Aperto de mobile, medido a 320 px e não estimado: com 8 px de
           intervalo e 16 px de padding, as âncoras com rótulo mais duas
           secundárias (44 px de ícone, o mínimo de toque, que não se
           negoceia) transbordavam 11 px e a segunda ficava meio cortada
           por baixo do «Editar». Com 4/12 — e o «Apagar» em ícone abaixo
           de 375 px — sobram 18 px de folga. */
        gap: var(--sp-1);
        padding: var(--sp-3);
        padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--r-xl);
        box-shadow: var(--sh-2);
    }

    /* Zona elástica das secundárias: ocupa o espaço que sobra entre o
       destrutivo e as âncoras da direita, e é a única que cede quando
       não há espaço nenhum. */
    .ds-ficha-rodape-meio {
        flex: 1 1 auto;
        display: flex;
        align-items: center;
        gap: var(--sp-1);
        min-width: 0;
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;
        /* `overflow` corta no limite da caixa de padding, e o anel de
           foco do ds-base.css são 2px + 2px de desvio: sem estes 4px o
           foco de teclado ficava decepado nas bordas da zona. A margem
           negativa devolve exatamente os mesmos 4px ao layout, por isso
           nem a altura da barra nem os intervalos entre botões mudam. */
        padding: 4px;
        margin: -4px;
    }

    .ds-ficha-rodape-meio::-webkit-scrollbar {
        display: none;
    }

    /* Encosta as secundárias à direita com `margin-left:auto` e NÃO com
       `justify-content:flex-end`. Medido: com `flex-end`, o que não cabe
       transborda pelo lado do INÍCIO — fica cortado e é inalcançável por
       scroll (o `scrollWidth` nem sequer cresce). Com margem automática,
       sobra espaço → empurra para a direita; falta espaço → a margem vale
       zero e a zona rola normalmente. */
    .ds-ficha-rodape-meio> :first-child {
        margin-left: auto;
    }

    /* Nenhum botão da barra encolhe: ou cabe, ou a zona do meio rola. */
    .ds-ficha-rodape .ds-ficha-acao {
        flex: 0 0 auto;
    }

    /* Base (mobile): uma linha de 320 px tem de chegar, com alvos de
       toque de 44 px que não se negoceiam. A repartição é:
         · secundárias  → só ÍCONE
         · Apagar       → só ÍCONE até 374 px (ver a query mais abaixo)
         · Editar/Fechar→ só RÓTULO
       O motor só marca `data-icone` nos ícones que desenha ELE (SVG
       inline): num `<i data-lucide>` por hidratar, esconder o rótulo
       deixaria um quadrado vazio se o CDN falhasse. Sem essa marca o
       rótulo fica e, quando muito, a zona do meio rola. O nome
       acessível vem do `aria-label`, que o motor põe sempre. */
    .ds-ficha-rodape>.ds-ficha-acao {
        padding: 0 var(--sp-2);
    }

    .ds-ficha-rodape>[data-papel="destrutiva"][data-icone="1"],
    .ds-ficha-rodape-meio>.ds-ficha-acao[data-icone="1"] {
        width: var(--fh-ctrl);
        padding: 0;
    }

    .ds-ficha-rodape>[data-papel="destrutiva"][data-icone="1"]>span,
    .ds-ficha-rodape-meio>.ds-ficha-acao[data-icone="1"]>span {
        display: none;
    }

    .ds-ficha-rodape>[data-papel="primaria"][data-icone="1"]>.ds-ficha-ico,
    .ds-ficha-rodape>[data-papel="fechar"][data-icone="1"]>.ds-ficha-ico {
        display: none;
    }

    /* O destrutivo mantém o contorno de perigo mesmo sem ícone. */
    .ds-ficha-rodape .ds-ficha-acao--perigo {
        background: var(--danger-50);
    }

    /* ===============================================================
       14. SELETOR DE ESTADO (pílulas, position:fixed posicionado por
       JS para escapar aos overflow:auto dos contentores da app)
       =============================================================== */

    .ds-ficha-estados {
        display: flex;
        flex-direction: column;
        gap: 2px;
        z-index: 1200;
        min-width: 200px;
        max-height: 60vh;
        overflow-y: auto;
        padding: var(--sp-2);
        background: var(--surface-raised);
        border: 1px solid var(--border);
        border-radius: var(--r-lg);
        box-shadow: var(--sh-3);
    }

    .ds-ficha-estado-op {
        display: flex;
        align-items: center;
        min-height: var(--fh-ctrl);
        padding: 0 var(--sp-2);
        font-size: var(--txt-sm);
        background: transparent;
        border: none;
        border-radius: var(--r-sm);
        text-align: left;
    }

    .ds-ficha-estado-op:hover {
        background: var(--surface-sunken);
    }

    .ds-ficha-estado-op[aria-selected="true"] {
        background: var(--brand-50);
    }

    /* ===============================================================
       15. ESTADOS DE PÁGINA — skeleton, erro, vazio
       Nunca uma área em branco (regra do MOD.ui).
       =============================================================== */

    .ds-ficha-skel {
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
    }

    .ds-ficha-skel-topo,
    .ds-ficha-skel-sec {
        display: flex;
        flex-direction: column;
        gap: var(--sp-2);
        padding: var(--sp-4);
        border: 1px solid var(--border);
        border-radius: var(--r-xl);
    }

    .ds-ficha-skel-hero {
        display: flex;
        flex-direction: column;
        gap: var(--sp-4);
        padding: var(--sp-4);
        border: 1px solid var(--border);
        border-radius: var(--r-xl);
    }

    .ds-ficha-skel-ident {
        display: flex;
        gap: var(--sp-3);
    }

    .ds-ficha-skel-herois {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--sp-2);
    }

    .ds-ficha-erro,
    .ds-ficha-vazio {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--sp-2);
        padding: var(--sp-10) var(--sp-4);
        text-align: center;
        color: var(--text-3);
    }

    .ds-ficha-erro .ds-ficha-ico,
    .ds-ficha-vazio .ds-ficha-ico {
        width: 28px;
        height: 28px;
        color: var(--ink-400);
    }

    .ds-ficha-erro-tit {
        font-size: var(--txt-lg);
        font-weight: var(--fw-bold);
        color: var(--text-1);
    }

    .ds-ficha-erro-txt {
        font-size: var(--txt-sm);
        max-width: 46ch;
    }

    /* ===============================================================
       16. BREAKPOINTS — mobile-first, só min-width (§5.1)
       =============================================================== */

    /* -- 375: o «Apagar» recupera o rótulo --------------------------
       Sexto ponto de corte da folha, e é o único justificado por uma
       MEDIÇÃO e não por uma família de aparelhos: a 320 px, cinco ações
       com rótulo no destrutivo transbordavam a barra em 11 px e a
       segunda secundária ficava meio cortada por baixo do «Editar».
       Acima de 374 px (todo o iPhone moderno é ≥375) sobra espaço e o
       gesto irreversível volta a dizer o seu nome. */
    @media (min-width: 375px) {
        .ds-ficha-rodape>[data-papel="destrutiva"][data-icone="1"] {
            width: auto;
            padding: 0 var(--sp-2);
        }

        .ds-ficha-rodape>[data-papel="destrutiva"][data-icone="1"]>span {
            display: inline;
        }

        .ds-ficha-rodape>[data-papel="destrutiva"][data-icone="1"]>.ds-ficha-ico {
            display: none;
        }
    }

    /* -- 480: os heróis ganham ar; o número cresce -- */
    @media (min-width: 480px) {
        .ds-heroi-item {
            padding: var(--sp-3) var(--sp-4);
        }

        .ds-alerta-accao {
            flex: 0 0 auto;
            margin-left: auto;
        }
    }

    /* -- 640: campos a 2 colunas; rodapé volta a linha única -- */
    @media (min-width: 640px) {
        .ds-campos {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }

        .ds-campos--1 {
            grid-template-columns: minmax(0, 1fr);
        }

        .ds-ficha-voltar-txt {
            display: inline;
        }

        .ds-ficheiros {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }

        /* Há espaço: toda a gente volta a ícone + rótulo e a barra
           respira. CONTINUA sticky — só o aperto de mobile é que passa. */
        .ds-ficha-rodape {
            gap: var(--sp-2);
            padding: var(--sp-3) var(--sp-4);
            padding-bottom: calc(var(--sp-3) + env(safe-area-inset-bottom));
        }

        .ds-ficha-rodape-meio {
            gap: var(--sp-2);
        }

        .ds-ficha-rodape>.ds-ficha-acao,
        .ds-ficha-rodape>[data-papel="destrutiva"][data-icone="1"],
        .ds-ficha-rodape-meio>.ds-ficha-acao[data-icone="1"] {
            width: auto;
            padding: 0 var(--sp-4);
        }

        .ds-ficha-rodape .ds-ficha-acao[data-icone="1"]>.ds-ficha-ico {
            display: block;
        }

        .ds-ficha-rodape .ds-ficha-acao[data-icone="1"]>span {
            display: inline;
        }
    }

    /* -- 768: tablet retrato — a topbar mostra as ações secundárias -- */
    @media (min-width: 768px) {
        .ds-ficha-acao--extra {
            display: inline-flex;
        }

        .ds-ficha-hero {
            --fh-pad: var(--sp-5);
        }

        .ds-ficha-input,
        .ds-ficha-select,
        .ds-ficha-textarea {
            font-size: var(--txt-base);
        }
    }

    /* -- 1024: desktop — hero a 2 colunas, heróis em linha, 3 col -- */
    @media (min-width: 1024px) {
        .ds-ficha {
            gap: var(--sp-6);
            --fh-ctrl: 40px;
        }

        .ds-ficha-hero-topo {
            display: grid;
            grid-template-columns: minmax(0, 1fr) auto;
            align-items: start;
            gap: var(--sp-6);
        }

        .ds-heroi {
            grid-auto-flow: column;
            grid-template-columns: none;
        }

        .ds-heroi-item {
            min-width: 132px;
        }

        /* Em desktop o mosaico tem largura própria: é aqui que o número
           herói pode finalmente ser o número herói (28 px). */
        .ds-heroi-valor {
            font-size: var(--txt-3xl);
        }

        .ds-campos {
            grid-template-columns: repeat(3, minmax(0, 1fr));
        }

        .ds-campos--2 {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }

        .ds-ficheiros {
            grid-template-columns: repeat(3, minmax(0, 1fr));
        }

        .ds-ficha-corpo,
        .ds-ficha-painel,
        .ds-ficha-lateral {
            gap: var(--sp-6);
        }

        .ds-secao-cab {
            padding: var(--sp-4) var(--sp-5);
        }

        .ds-secao-corpo {
            padding: var(--sp-5);
        }
    }

    /* Desktop COM RATO: só aqui as ações de secção podem encolher
       abaixo de 44 px. Num tablet de 1024 com toque, `pointer: coarse`
       mantém-nas grandes. */
    @media (min-width: 1024px) and (pointer: fine) {
        .ds-secao-acao {
            min-height: 34px;
        }

        .ds-secao-toggle {
            min-height: 0;
            padding: 0;
            margin: 0;
        }

        .ds-ficha-estado--editavel {
            min-height: 32px;
        }

        .ds-campo-lig {
            min-height: 0;
        }
    }

    /* -- 1440: 4 colunas e coluna lateral de 320 px -- */
    @media (min-width: 1440px) {
        .ds-campos {
            grid-template-columns: repeat(4, minmax(0, 1fr));
        }

        .ds-campos--3 {
            grid-template-columns: repeat(3, minmax(0, 1fr));
        }

        .ds-ficha-corpo--lateral {
            grid-template-columns: minmax(0, 1fr) 320px;
            align-items: start;
        }
    }

    /* ===============================================================
       17. AS DUAS QUERIES NÃO-DIMENSIONAIS (as únicas permitidas)
       =============================================================== */

    /* Toque, em QUALQUER largura — apanha tablets de 1024 e híbridos.
       Nada escondido atrás de :hover, sem transforms de hover. */
    @media (pointer: coarse) {
        .ds-ficha {
            --fh-ctrl: 44px;
        }

        .ds-ficha-acao:not(:disabled):hover {
            transform: none;
        }

        .ds-subtabela [data-acoes] {
            opacity: 1;
        }

        .ds-ficha {
            -webkit-tap-highlight-color: rgba(218, 10, 13, .08);
        }
    }

    /* Telemóvel em paisagem: altura é o recurso escasso. */
    @media (max-height: 520px) {
        .ds-ficha-topbar {
            min-height: 44px;
            padding: var(--sp-1) 0;
        }

        .ds-ficha-hero {
            --fh-pad: var(--sp-3);
            gap: var(--sp-2);
        }

        .ds-heroi-valor {
            font-size: var(--txt-xl);
        }

        .ds-ficha-rodape {
            padding: var(--sp-2) var(--sp-3);
            padding-bottom: calc(var(--sp-2) + env(safe-area-inset-bottom));
        }
    }

    /* ===============================================================
       18. IMPRESSÃO — a ficha em papel não leva cromo de navegação
       =============================================================== */
    @media print {

        .ds-ficha-topbar,
        .ds-ficha-abas,
        .ds-ficha-rodape,
        .ds-secao-acoes,
        .ds-ficha-estados {
            display: none;
        }

        .ds-ficha-hero,
        .ds-secao {
            box-shadow: none;
            break-inside: avoid;
        }

        .ds-secao[data-colapsada="1"] .ds-secao-corpo {
            display: block;
        }
    }

    /* ===============================================================
       19. CASCA DE MODAL (Fase 2) — `MOD.ficha.abrir()`
       ---------------------------------------------------------------
       A ficha por CIMA da lista, como o CRM do cliente faz. A anatomia
       lá dentro é a mesma de sempre; isto é só a moldura.

         base (mobile)  folha a subir de baixo, quase ecrã inteiro,
                        cantos superiores redondos, rodapé sticky (já
                        vem sticky da §13, aqui só se garante o fundo)
         ≥768           painel centrado, largura generosa, cantos todos
                        redondos, `min(1200px, 94vw)` × `92vh`

       O SCROLL É INTERNO: quem rola é `.ds-fichamod-corpo`. É ele o
       scrollport das barras sticky da ficha — a topbar cola a 0 e as
       abas colam a `--fm-topo`, que o JS mede na topbar real (§10 do
       mod-ficha.js). Sem a medição ficava uma fresta entre as duas por
       onde se via o hero a passar.

       Z-INDEX: 900 + nível. ABAIXO de --ds-z-modal (950) de propósito:
       os modais do módulo (linha, ficheiro, atividade, confirmação)
       abrem POR CIMA da ficha, que é o que se espera de um formulário
       lançado a partir dela.
       =============================================================== */

    .ds-fichamod {
        position: fixed;
        inset: 0;
        z-index: calc(900 + var(--fm-nivel, 0) * 2);
        display: flex;
        align-items: flex-end;
        justify-content: center;
        background: color-mix(in srgb, var(--ink-900) 44%, transparent);
        -webkit-backdrop-filter: blur(3px);
        backdrop-filter: blur(3px);
        animation: dsFmFundo .18s ease both;
    }

    /* A lista continua visível por trás — é esse o objetivo do padrão.
       Do 2.º nível para cima escurece menos, para não empilhar véus até
       o fundo ficar preto. */
    .ds-fichamod:not([data-nivel="1"]) {
        background: color-mix(in srgb, var(--ink-900) 24%, transparent);
    }

    .ds-fichamod.is-closing {
        animation: dsFmFundoFora .16s ease both;
        pointer-events: none;
    }

    .ds-fichamod-painel {
        display: flex;
        flex-direction: column;
        width: 100%;
        max-width: 100%;
        height: 96vh;
        height: 96dvh;
        max-height: 96vh;
        max-height: 96dvh;
        padding-top: var(--sp-2);
        background: var(--surface);
        border-radius: var(--r-xl) var(--r-xl) 0 0;
        box-shadow: var(--sh-3);
        overflow: hidden;
        animation: dsFmSubir .22s cubic-bezier(.2, .8, .3, 1) both;
    }

    .ds-fichamod.is-closing .ds-fichamod-painel {
        animation: dsFmDescer .16s ease both;
    }

    .ds-fichamod-painel:focus {
        outline: none;
    }

    /* Pega visual da folha (só no modo folha, ≤767).
       Sem `margin`: o reset sem camada do style.css apaga margens de
       pseudo-elementos (`*, *::before, *::after`) e a ponte de migração
       só alcança ELEMENTOS. O respiro vem do `padding-top` do painel,
       que é um elemento e passa pela ponte. */
    .ds-fichamod-painel::before {
        content: "";
        flex: 0 0 auto;
        align-self: center;
        width: 36px;
        height: 4px;
        background: var(--border-strong);
        border-radius: 999px;
    }

    /* SEM respiro em baixo, de propósito: quem fecha o painel é a barra
       de ações, colada ao fundo. Um `padding-bottom` aqui deixaria uma
       fresta por onde se veria o conteúdo a passar POR BAIXO da barra.
       O respiro volta (por margem do último bloco) nas fichas que não
       têm barra nenhuma. `--fm-lado` existe para a barra poder anular
       este respiro lateral e ficar de bordo a bordo sem o repetir. */
    .ds-fichamod-corpo {
        --fm-lado: var(--sp-4);
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
        padding: 0 var(--fm-lado);
    }

    /* A ficha dentro do modal não precisa do respiro de página.

       `min-height:100%` + `margin-top:auto` na barra: sem isto, uma
       ficha de conteúdo curto (a folha de mobile tem 96dvh fixos)
       deixava a barra a meio do painel, com um vazio por baixo. Com
       conteúdo longo a margem automática vale zero e não muda nada. */
    .ds-fichamod .ds-ficha {
        gap: var(--sp-3);
        min-height: 100%;
    }

    .ds-fichamod .ds-ficha> :last-child:not(.ds-ficha-rodape) {
        margin-bottom: var(--fm-lado);
    }

    /* A topbar é o cabeçalho do diálogo: opaca sempre (condensada ou
       não), senão via-se o hero a passar-lhe por baixo. E não encolhe
       em altura — é dela que sai o `--fm-topo` das abas. */
    .ds-fichamod .ds-ficha-topbar,
    .ds-fichamod .ds-ficha-topbar[data-condensada="1"] {
        top: 0;
        z-index: 6;
        background: var(--surface);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }

    .ds-fichamod .ds-ficha-topbar[data-condensada="1"] {
        min-height: 56px;
    }

    /* Em modal o botão do canto superior esquerdo é SÓ o X: quadrado,
       alvo de toque cheio, sem rótulo. O «Fechar» com rótulo está no
       canto inferior direito da barra de ações (pedido do cliente) —
       duas etiquetas «Fechar» em cantos opostos lêem-se como dois
       destinos diferentes. */
    .ds-ficha-voltar--x {
        justify-content: center;
        width: var(--fh-ctrl);
        padding: 0;
    }

    /* `-1px`: a topbar ganha 1 px de borda ao condensar e um arredondamento
       para baixo deixaria uma fresta por onde se via o hero a passar. Um
       píxel de sobreposição não se vê (a barra tem z-index maior); uma
       fresta vê-se sempre. */
    .ds-fichamod .ds-ficha-abas {
        position: sticky;
        top: calc(var(--fm-topo, 56px) - 1px);
        z-index: 5;
        background: var(--surface);
    }

    /* A barra de ações é o RODAPÉ DO PAINEL: colada ao fundo, de bordo
       a bordo, com o corpo a rolar por baixo. As margens negativas
       anulam o respiro lateral do `.ds-fichamod-corpo` (o scrollport)
       sem o duplicar em número — daí `--fm-lado`.

       `z-index:6` = o mesmo da topbar: as duas barras são o cromo do
       diálogo e nada do corpo lhes passa por cima. */
    .ds-fichamod .ds-ficha-rodape {
        z-index: 6;
        margin-top: auto;
        margin-left: calc(var(--fm-lado) * -1);
        margin-right: calc(var(--fm-lado) * -1);
        padding-left: var(--fm-lado);
        padding-right: var(--fm-lado);
        border-width: 1px 0 0;
        border-radius: 0;
        /* Tingida em `--brand-950` como todas as outras (§2.6). Era a
           única sombra da app ainda em rgba(15,23,42) — o azul-ardósia do
           legado — e sobre superfícies quentes lia-se fria. */
        box-shadow: 0 -8px 20px -14px rgba(43, 6, 7, .55);
    }

    @keyframes dsFmFundo {
        from { opacity: 0; }
        to { opacity: 1; }
    }

    @keyframes dsFmFundoFora {
        from { opacity: 1; }
        to { opacity: 0; }
    }

    @keyframes dsFmSubir {
        from { transform: translateY(6%); opacity: .4; }
        to { transform: translateY(0); opacity: 1; }
    }

    @keyframes dsFmDescer {
        from { transform: translateY(0); opacity: 1; }
        to { transform: translateY(4%); opacity: 0; }
    }

    @keyframes dsFmEntrar {
        from { transform: translateY(8px) scale(.99); opacity: 0; }
        to { transform: translateY(0) scale(1); opacity: 1; }
    }

    /* -- 768: deixa de ser folha e passa a painel centrado -- */
    @media (min-width: 768px) {
        .ds-fichamod {
            align-items: center;
            padding: var(--sp-4);
        }

        .ds-fichamod-painel {
            width: min(1200px, 94vw);
            height: auto;
            max-height: 92vh;
            max-height: 92dvh;
            padding-top: 0;
            border-radius: var(--r-xl);
            animation-name: dsFmEntrar;
        }

        .ds-fichamod[data-largura="media"] .ds-fichamod-painel {
            width: min(860px, 94vw);
        }

        .ds-fichamod[data-largura="total"] .ds-fichamod-painel {
            width: 96vw;
            max-height: 96vh;
            max-height: 96dvh;
        }

        .ds-fichamod-painel::before {
            display: none;
        }

        .ds-fichamod-corpo {
            --fm-lado: var(--sp-5);
        }
    }

    /* -- 1440: o painel acompanha os ecrãs largos sem esticar demais -- */
    @media (min-width: 1440px) {
        .ds-fichamod-painel {
            width: min(1320px, 90vw);
        }
    }

    /* Telemóvel em paisagem: a folha ocupa tudo, não sobra altura. */
    @media (max-height: 520px) {
        .ds-fichamod-painel {
            height: 100vh;
            height: 100dvh;
            max-height: 100vh;
            max-height: 100dvh;
            padding-top: 0;
            border-radius: 0;
        }

        .ds-fichamod-painel::before {
            display: none;
        }
    }

    @media (prefers-reduced-motion: reduce) {

        .ds-fichamod,
        .ds-fichamod-painel,
        .ds-fichamod.is-closing,
        .ds-fichamod.is-closing .ds-fichamod-painel,
        .ds-secao--realce {
            animation: none;
        }
    }

    /* ===============================================================
       16. EDITAR EM MÓDULO — o formulário dentro da casca da ficha
       ---------------------------------------------------------------
       Pedido do cliente: «não quero uma página com as edições, quero
       simplesmente um módulo onde vou editar consoante os campos que
       me aparecem no ecrã.»

       O formulário é o MESMO que a página tinha — não se reescreve,
       muda-se-lhe a moldura. Por isso esta secção é quase toda a
       DESLIGAR o cromo de página que vem agarrado ao formulário:
       migalhas, cabeçalho de página e a linha de ações do próprio
       formulário (que a barra do modal substitui — duas linhas de
       «Guardar» no mesmo ecrã lêem-se como dois botões diferentes).
       =============================================================== */

    .ds-ficha--form {
        gap: 0;
    }

    /* O título vive na topbar, ao lado do X. Trunca em vez de partir a
       barra em duas linhas: a altura da topbar é o `--fm-topo` de que
       tudo o resto depende. */
    .ds-ficha-form-id {
        display: flex;
        flex: 1 1 auto;
        gap: var(--sp-2);
        align-items: baseline;
        min-width: 0;
    }

    .ds-ficha-form-titulo {
        overflow: hidden;
        font-size: var(--fs-md, 1rem);
        font-weight: 650;
        color: var(--ink-900);
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    /* O subtítulo é uma cortesia, o título é a identificação. Abaixo de
       768 px sai de cena para o título não ficar em «Editar Cont…»:
       medido a 390 px, com os dois na mesma linha o título perdia 11
       caracteres e o subtítulo ganhava três. */
    .ds-ficha-form-sub {
        display: none;
        overflow: hidden;
        font-size: var(--fs-xs, .78rem);
        color: var(--ink-500);
        white-space: nowrap;
        text-overflow: ellipsis;
    }

    @media (min-width: 768px) {
        .ds-ficha-form-sub {
            display: block;
        }
    }

    /* O corpo é o hospedeiro do formulário do módulo. `min-height:0` +
       `flex:1` para a barra de ações ficar colada ao fundo mesmo com
       um formulário curto (o painel tem altura fixa em mobile). */
    .ds-ficha-form-corpo {
        flex: 1 1 auto;
        min-width: 0;
        min-height: 0;
        padding-top: var(--sp-3);
        padding-bottom: var(--sp-4);
    }

    /* O cromo de página que não entra no modal (cabeçalho, migalhas e
       a linha de ações do próprio formulário) NÃO se desliga aqui: as
       classes `.ds-page-header`, `.ds-form-actions` e `.ds-card` vivem
       SEM CAMADA no design-system.css, e sem camada ganha sempre a
       qualquer camada, por mais específico que seja o seletor. Está
       tudo no bloco «EDIÇÃO EM MÓDULO» do fim desta folha, fora da
       camada, ao lado da ponte de migração. */

    /* Imprimir a ficha aberta em modal imprime a FICHA, não um véu. */
    @media print {
        .ds-fichamod {
            position: static;
            display: block;
            background: none;
            -webkit-backdrop-filter: none;
            backdrop-filter: none;
        }

        .ds-fichamod-painel {
            width: auto;
            max-width: none;
            height: auto;
            max-height: none;
            box-shadow: none;
            border-radius: 0;
        }

        .ds-fichamod-painel::before {
            display: none;
        }

        .ds-fichamod-corpo {
            overflow: visible;
            padding: 0;
        }
    }
}


/* ===================================================================
   PONTE DE MIGRAÇÃO — FORA de camada, de propósito.
   ===================================================================
   Apaga-se INTEIRA na Fase 6, quando o legado passar a ser servido
   dentro de `@layer legado{}`. Até lá é o que faz a folha acima
   funcionar: devolve à camada `ficha` as oito propriedades que o reset
   sem camada do style.css lhe rouba (ver explicação no cabeçalho).

   Restrita às dez famílias da ficha, de propósito: se apanhasse
   `.ds-btn` ou `.ds-table` — que vivem sem camada no design-system.css
   — o `revert-layer` deitaria fora o estilo deles.

   Não acrescentar propriedades aqui sem confirmar que o legado as
   apaga mesmo: cada linha desta lista é uma linha a apagar na Fase 6.
   =================================================================== */

[class*="ds-ficha"]:not(.ds-btn),
[class*="ds-heroi"]:not(.ds-btn),
[class*="ds-secao"]:not(.ds-btn),
[class*="ds-campo"]:not(.ds-btn),
[class*="ds-timebar"]:not(.ds-btn),
[class*="ds-alerta"]:not(.ds-btn),
[class*="ds-subtabela"]:not(.ds-btn),
[class*="ds-ficheiro"]:not(.ds-btn),
[class*="ds-atividade"]:not(.ds-btn),
[class*="ds-etq"]:not(.ds-btn) {
    /* contra  *, *::before, *::after { margin: 0; padding: 0 } */
    margin: revert-layer;
    padding: revert-layer;
    /* contra  button { border: none; background: none } */
    background: revert-layer;
    border: revert-layer;
    /* contra  ul, ol { list-style: none } */
    list-style: revert-layer;
}

/* O `:not(.ds-btn)` acima fecha um buraco que o próprio comentário desta
   ponte previa: «se apanhasse `.ds-btn` … o `revert-layer` deitaria fora o
   estilo dele». Apanhava — o `.ds-alerta-accao` do MOD.alertas é
   `ds-btn ds-btn--pequeno ds-alerta-accao`, entrava por `[class*="ds-alerta"]`
   e o `revert-layer` sem camada devolvia-lhe os valores do BROWSER:
   `background: buttonface` (#F0F0F0) e `border: 2px outset`. Eram 56 botões
   da app com o cinzento e o biselado 3D do sistema por cima de cartões
   tingidos — e o rótulo a 4.41:1, abaixo do AA.

   Fora da ponte, o `.ds-btn` (também sem camada) volta a mandar no `border`,
   e por isso a cor e o fundo do botão de alerta têm de ser repostos aqui,
   sem camada, para jogarem no mesmo campo. (0,2,0) > (0,1,0). */
.ds-alerta-accao.ds-btn {
    border: 1px solid currentColor;
    background: var(--surface);
}

/* contra  a { color: inherit; text-decoration: none } — só os âncoras
   que a ficha emite, para não mexer em links de HTML à medida. */
.ds-campo-lig,
.ds-ficha-acao,
.ds-alerta-accao {
    color: revert-layer;
    text-decoration: revert-layer;
}

/* contra  button { cursor: pointer } — um botao desativado que convida ao
   clique e um botao que mente; o not-allowed diz logo ao rato que nao da
   (o porque esta no title, que agora aparece — ver o bloco :disabled la
   em cima). Tem de viver AQUI fora da camada pela mesma razao que os
   vizinhos: dentro dela perdia sempre. */
.ds-ficha-acao:disabled {
    cursor: not-allowed;
}

/* contra  input, select, textarea { font-size: inherit } — os 16 px de
   base existem para o iOS não dar zoom ao focar (§2.3/§5.5). */
.ds-ficha-input,
.ds-ficha-select,
.ds-ficha-textarea {
    font-size: revert-layer;
}


/* ===================================================================
   EDIÇÃO EM MÓDULO — FORA de camada, pela mesma razão que a ponte.
   ===================================================================
   O formulário que entra na casca é o MESMO da página, e o que ele
   traz agarrado (cabeçalho de página, migalhas, cartões, a sua linha
   de «Cancelar/Guardar») está declarado SEM CAMADA no
   design-system.css. Sem camada ganha a qualquer camada — por isso
   estas regras têm de viver aqui e não dentro de `@layer ficha`.

   Tudo isto é DESLIGAR, não redesenhar: a moldura passa a ser o
   painel do modal e a barra de ações passa a ser a do motor.
   =================================================================== */

/* A página inteira que o módulo pinta perde o respiro e a moldura:
   dentro do modal o cartão é o painel. */
.ds-ficha-form-corpo>.ds-page {
    padding: 0;
    margin: 0;
    background: none;
    border: 0;
    box-shadow: none;
}

/* Cabeçalho de página e migalhas: a topbar do diálogo já diz o que
   isto é, e quem está num modal não navega por migalhas. */
.ds-ficha-form-corpo .ds-page-header,
.ds-ficha-form-corpo .ds-breadcrumb,
.ds-ficha-form-corpo .ds-migalhas {
    display: none;
}

/* A barra do modal É a barra do formulário. A do formulário fica
   ESCONDIDA — mas presente no DOM, porque é o `submit` dela que o
   «Guardar» do modal dispara: validações, máscaras e o cadeado da
   numeração continuam a valer sem uma linha nova de wiring. */
.ds-ficha-form-corpo .ds-form-actions {
    display: none;
}

/* Cartões sem moldura: dentro do painel, caixas dentro de caixas só
   estreitam os campos. Fica a divisória entre secções, que é o que dá
   a leitura de grupos. */
.ds-ficha-form-corpo .ds-card {
    background: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

.ds-ficha-form-corpo .ds-card+.ds-card {
    padding-top: var(--sp-3);
    border-top: 1px solid var(--border);
}

.ds-ficha-form-corpo .ds-card-body,
.ds-ficha-form-corpo .ds-card-header {
    padding-right: 0;
    padding-left: 0;
}

/* O mesmo para as secções de formulário do padrão antigo
   (`ds-form-section`), que os módulos mais velhos ainda usam. */
.ds-ficha-form-corpo .ds-form-section {
    padding-right: 0;
    padding-left: 0;
    background: none;
    border: 0;
    box-shadow: none;
}

/* =====================================================================================
   FIO DE ATIVIDADES — extensões para a ficha de cliente (2026-08-05)
   Linha clicável e botão de apagar. O `.ds-atividade-item` já era `position: relative`
   na base do componente; o botão encosta ao canto sem tirar espaço ao texto.
   ===================================================================================== */
.ds-atividade-item {
    position: relative;
    padding-right: 26px;
}

.ds-atividade-link {
    display: block;
    color: inherit;
    text-decoration: none;
    border-radius: 6px;
}

.ds-atividade-link:hover .ds-atividade-tipo,
.ds-atividade-link:focus-visible .ds-atividade-tipo {
    text-decoration: underline;
}

.ds-atividade-link:focus-visible {
    outline: 2px solid var(--brand-500, #DA0A0D);
    outline-offset: 2px;
}

/* Alvo de toque de 32px — a regra do gate responsivo é 24px mínimo; um ícone de 14px
   sozinho ficava por baixo disso em telemóvel. */
.ds-atividade-apagar {
    position: absolute;
    top: 2px;
    right: 0;
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    background: none;
    border-radius: 6px;
    color: var(--text-3, #898792);
    cursor: pointer;
}

.ds-atividade-apagar:hover,
.ds-atividade-apagar:focus-visible {
    background: var(--perigo-100, #FDECEC);
    color: var(--perigo-600, #C62828);
}
