/* ===================================================================
   EFICÁCIA CRM — DESIGN SYSTEM (ds-)
   ===================================================================
   O alicerce visual de todos os módulos novos (~50 ecrãs).

   REGRAS DESTE FICHEIRO
   - Reutiliza os design tokens de style.css (:root): --primary-*,
     --neutral-*, --success/warning/error/info-*, --radius-*,
     --shadow-*, --transition-*, --text-*, --font-family.
     NÃO redefine a paleta da marca.
   - Todas as classes têm prefixo `ds-`. Estados usam `is-*`
     (e aceitam também `.active` onde a app já usa essa convenção).
   - MOBILE-FIRST: o estilo base é para telemóvel (>=320px);
     os @media (min-width: ...) fazem crescer para tablet/desktop.
   - Breakpoints: 480px, 640px, 768px, 1024px, 1440px
     (+ @media (max-height: 520px) para telemóvel em paisagem).
   - Alvos de toque >= 44x44px (em ponteiro fino/desktop a densidade
     é ligeiramente reduzida via tokens --ds-control-h-*).
   - box-sizing: border-box em tudo; min-width:0 nos filhos de
     flex/grid para nunca gerar scroll horizontal.
   - Respeita prefers-reduced-motion (bloco no fim do ficheiro).
   - Não altera style.css nem monopostes.css.

   ÍNDICE
   00. Tokens ds- e primitivas (sr-only, foco, scrollbars)
   01. Layout de página (page, page-header, section, card)
   02. Botões (variantes, tamanhos, grupos, loading)
   03. Badges de estado, chips, tags
   04. Formulários (grelha, campos, inputs, combobox, rich text,
       secções colapsáveis, barra de ações sticky, erros)
   05. Listagem (filtros colapsáveis, resultados, tabela->cartões,
       ordenação, paginação, estado vazio, loading)
   06. Sub-tabela compacta + fieldlist de detalhe
   07. KPI / cartões de métrica
   08. Abas
   09. Modal (bottom sheet -> centrado)
   10. Alertas / avisos inline
   11. Skeletons (shimmer)
   12. Utilitários mínimos
   13. prefers-reduced-motion
   =================================================================== */

/* ===================================================================
   00. TOKENS ds- E PRIMITIVAS
   =================================================================== */
:root {
    /* Alturas de controlo FIXAS por tamanho — botões, inputs, selects
       e paginação partilham exatamente a mesma altura por tamanho,
       para nunca haver assimetrias dentro de uma linha/grupo. */
    --ds-control-h-sm: 36px;
    --ds-control-h-md: 44px;   /* >= alvo de toque */
    --ds-control-h-lg: 52px;

    /* Alvo de toque mínimo */
    --ds-touch: 44px;

    /* Espaçamento semântico (cresce com o ecrã) */
    --ds-page-pad: 16px;       /* padding lateral da página */
    --ds-page-gap: 18px;       /* espaçamento vertical entre blocos */
    --ds-card-pad: 16px;       /* padding interno de cards */
    --ds-field-gap: 14px;      /* espaçamento entre campos de formulário */

    /* Anel de foco (vermelho Eficácia, suave) */
    --ds-ring-color: rgba(218, 10, 13, 0.13);
    --ds-ring: 0 0 0 3px var(--ds-ring-color);

    /* Tipografia auxiliar */
    --ds-font-mono: 'SFMono-Regular', ui-monospace, 'Cascadia Code', Consolas, monospace;

    /* Camadas (style.css usa 900 para .modal-overlay) */
    --ds-z-sticky: 40;
    --ds-z-dropdown: 80;
    --ds-z-modal: 950;
}

/* Densidade desktop: com rato (ponteiro fino) os controlos ficam
   ligeiramente mais compactos; em ecrã tátil (iPad 1024x768 incluído)
   mantêm-se os 44px de alvo de toque. */
@media (min-width: 1024px) and (pointer: fine) {
    :root {
        --ds-control-h-sm: 32px;
        --ds-control-h-md: 40px;
        --ds-control-h-lg: 48px;
    }
}

@media (min-width: 768px) {
    :root {
        --ds-page-pad: 24px;
        --ds-page-gap: 24px;
        --ds-card-pad: 20px;
    }
}

@media (min-width: 1280px) {
    :root {
        --ds-card-pad: 24px;
    }
}

/* Tudo o que o design system cria é border-box (reforço local,
   caso o ficheiro seja usado numa página sem o reset de style.css) */
[class^="ds-"],
[class^="ds-"] *,
[class^="ds-"] *::before,
[class^="ds-"] *::after,
[class*=" ds-"],
[class*=" ds-"] *,
[class*=" ds-"] *::before,
[class*=" ds-"] *::after {
    box-sizing: border-box;
}

/* Acessibilidade: conteúdo só para leitores de ecrã */
.ds-sr-only {
    position: absolute !important;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Foco visível consistente (teclado) em todos os interativos ds- */
.ds-btn:focus-visible,
.ds-tab:focus-visible,
.ds-chip:focus-visible,
.ds-tag-x:focus-visible,
.ds-page-btn:focus-visible,
.ds-icon-btn:focus-visible,
.ds-card-toggle:focus-visible,
.ds-filters-toggle:focus-visible,
.ds-form-section-toggle:focus-visible,
.ds-combobox-clear:focus-visible,
.ds-richtext-btn:focus-visible,
.ds-modal-close:focus-visible,
.ds-link:focus-visible,
.ds-option:focus-visible,
.ds-th-sortable:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

/* Scrollbars finas e discretas dentro de componentes ds- */
.ds-table-wrap::-webkit-scrollbar,
.ds-modal-body::-webkit-scrollbar,
.ds-combobox-menu::-webkit-scrollbar,
.ds-tabs::-webkit-scrollbar {
    width: 8px;
    height: 8px;
}
.ds-table-wrap::-webkit-scrollbar-thumb,
.ds-modal-body::-webkit-scrollbar-thumb,
.ds-combobox-menu::-webkit-scrollbar-thumb {
    background: var(--neutral-300);
    border-radius: var(--radius-full);
    border: 2px solid transparent;
    background-clip: content-box;
}
.ds-table-wrap::-webkit-scrollbar-track,
.ds-modal-body::-webkit-scrollbar-track,
.ds-combobox-menu::-webkit-scrollbar-track {
    background: transparent;
}

/* ===================================================================
   01. LAYOUT DE PÁGINA
   =================================================================== */
.ds-page {
    display: flex;
    flex-direction: column;
    gap: var(--ds-page-gap);
    min-width: 0;
    width: 100%;
}

/* Cabeçalho de página: título + subtítulo à esquerda, ações à direita.
   Em mobile empilha e as ações ocupam a largura toda. */
.ds-page-header {
    display: flex;
    flex-direction: column;
    gap: 12px;
    min-width: 0;
}
.ds-page-header-titles {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}
.ds-page-title {
    font-size: var(--text-xl);
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--neutral-900);
    line-height: 1.15;
    margin: 0;
    overflow-wrap: anywhere;
}
.ds-page-subtitle {
    font-size: var(--text-sm);
    color: var(--neutral-500);
    font-weight: 500;
    margin: 0;
}
/* Migalhas (opcional) */
.ds-breadcrumb {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--neutral-400);
    min-width: 0;
}
.ds-breadcrumb a {
    color: var(--neutral-500);
    text-decoration: none;
}
.ds-breadcrumb a:hover { color: var(--primary-600); }
.ds-breadcrumb-sep { color: var(--neutral-300); }

/* UM ÚNICO DONO DAS MIGALHAS.
   O shell da aplicação já desenha as migalhas no cabeçalho fixo (#breadcrumbs),
   que acompanha o scroll e é igual nas páginas antigas e nas novas. Todos os
   módulos da réplica desenham também as suas dentro do .ds-page-header —
   verificado em 10 rotas, todas com duas linhas de migalhas sobrepostas.
   Quem manda é o shell; as do módulo ficam ocultas em vez de se andar a
   editar dez módulos (e os que ainda estão a ser construídos herdam a regra).
   Fora do #pageContent — por exemplo na demonstração do design system — a
   classe continua a funcionar normalmente. */
#pageContent .ds-breadcrumb { display: none; }

.ds-page-header-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}
/* Em mobile os botões do cabeçalho dividem a largura em partes iguais */
.ds-page-header-actions > .ds-btn {
    flex: 1 1 auto;
    min-width: 0;
}

@media (min-width: 768px) {
    .ds-page-header {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: 16px;
    }
    .ds-page-title { font-size: var(--text-2xl); }
    .ds-page-header-actions { flex-wrap: nowrap; flex-shrink: 0; }
    .ds-page-header-actions > .ds-btn { flex: 0 0 auto; }
}

/* Secção lógica dentro da página */
.ds-section {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}
.ds-section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--neutral-900);
    margin: 0;
}
.ds-section-title svg {
    width: 18px;
    height: 18px;
    color: var(--neutral-400);
    flex-shrink: 0;
}

/* Card — bloco branco base de todo o conteúdo */
.ds-card {
    background: var(--neutral-0);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xs);
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.ds-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 14px var(--ds-card-pad);
    border-bottom: 1px solid var(--neutral-100);
    min-height: 56px;
    min-width: 0;
}
.ds-card-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--neutral-900);
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}
.ds-card-title svg { width: 18px; height: 18px; color: var(--neutral-400); flex-shrink: 0; }
.ds-card-title .ds-card-count {
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--neutral-500);
    background: var(--neutral-100);
    padding: 2px 8px;
    border-radius: var(--radius-full);
}
.ds-card-header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}
.ds-card-body {
    padding: var(--ds-card-pad);
    min-width: 0;
}
.ds-card-body--flush { padding: 0; }
.ds-card-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    padding: 12px var(--ds-card-pad);
    border-top: 1px solid var(--neutral-100);
    background: var(--neutral-25);
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    flex-wrap: wrap;
}

/* Card colapsável (ex.: "Lista de Ficheiros") */
.ds-card--collapsible > .ds-card-header { cursor: pointer; user-select: none; }
.ds-card-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ds-control-h-sm);
    height: var(--ds-control-h-sm);
    min-width: var(--ds-control-h-sm);
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    color: var(--neutral-400);
    cursor: pointer;
    transition: var(--transition-fast);
}
.ds-card-toggle svg {
    width: 18px;
    height: 18px;
    transition: transform var(--transition-base);
}
.ds-card-toggle:hover { background: var(--neutral-100); color: var(--neutral-600); }
.ds-card.is-collapsed > .ds-card-header { border-bottom-color: transparent; }
.ds-card.is-collapsed .ds-card-toggle svg { transform: rotate(-90deg); }
.ds-card.is-collapsed > .ds-card-body,
.ds-card.is-collapsed > .ds-card-footer { display: none; }

/* ===================================================================
   02. BOTÕES
   -------------------------------------------------------------------
   Altura SEMPRE fixa por tamanho (var --ds-control-h-*): num grupo,
   todos os botões têm exatamente a mesma altura, com ou sem ícone.
   Estado :active bem visível para toque (escala + tom mais escuro).
   =================================================================== */
.ds-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: var(--ds-control-h-md);
    padding: 0 18px;
    font-family: var(--font-family);
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1;
    border-radius: var(--radius-md);
    border: 1px solid transparent;
    cursor: pointer;
    white-space: nowrap;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    text-decoration: none;
    transition: background-color var(--transition-fast),
                border-color var(--transition-fast),
                color var(--transition-fast),
                box-shadow var(--transition-fast),
                transform 80ms ease;
    min-width: 0;
}
.ds-btn svg {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
}
.ds-btn:disabled,
.ds-btn.is-disabled {
    opacity: 0.55;
    cursor: not-allowed;
    pointer-events: none;
}

/* Primário — vermelho Eficácia */
.ds-btn--primary {
    background: var(--primary-600);
    border-color: var(--primary-600);
    color: #fff;
    box-shadow: 0 1px 2px rgba(218, 10, 13, 0.25);
}
.ds-btn--primary:hover {
    background: var(--primary-700);
    border-color: var(--primary-700);
    box-shadow: 0 4px 12px rgba(218, 10, 13, 0.25);
}
.ds-btn--primary:active {
    background: var(--primary-800);
    border-color: var(--primary-800);
    transform: scale(0.97);
    box-shadow: none;
}

/* Secundário — contorno neutro */
.ds-btn--secondary {
    background: var(--neutral-0);
    border-color: var(--neutral-200);
    color: var(--neutral-700);
}
.ds-btn--secondary:hover {
    background: var(--neutral-50);
    border-color: var(--neutral-300);
}
.ds-btn--secondary:active {
    background: var(--neutral-100);
    border-color: var(--neutral-300);
    transform: scale(0.97);
}

/* Perigo — ações destrutivas */
.ds-btn--danger {
    background: var(--error-50);
    border-color: var(--error-200);
    color: var(--error-600);
}
.ds-btn--danger:hover {
    background: var(--error-500);
    border-color: var(--error-500);
    color: #fff;
}
.ds-btn--danger:active {
    background: var(--error-600);
    border-color: var(--error-600);
    color: #fff;
    transform: scale(0.97);
}
/* Perigo sólido (confirmação final) */
.ds-btn--danger-solid {
    background: var(--error-500);
    border-color: var(--error-500);
    color: #fff;
}
.ds-btn--danger-solid:hover { background: var(--error-600); border-color: var(--error-600); }
.ds-btn--danger-solid:active { background: var(--error-700); border-color: var(--error-700); transform: scale(0.97); }

/* Fantasma — sem fundo */
.ds-btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--neutral-600);
}
.ds-btn--ghost:hover { background: var(--neutral-100); color: var(--neutral-800); }
.ds-btn--ghost:active { background: var(--neutral-200); transform: scale(0.97); }

/* Tamanhos (altura fixa por token) */
.ds-btn--sm {
    height: var(--ds-control-h-sm);
    padding: 0 12px;
    font-size: var(--text-xs);
    gap: 6px;
    border-radius: var(--radius-sm);
}
.ds-btn--sm svg { width: 15px; height: 15px; }
.ds-btn--lg {
    height: var(--ds-control-h-lg);
    padding: 0 24px;
    font-size: var(--text-base);
    border-radius: var(--radius-md);
}
.ds-btn--lg svg { width: 20px; height: 20px; }

/* Só ícone — quadrado perfeito, mesmo eixo dos restantes */
.ds-btn--icon {
    width: var(--ds-control-h-md);
    min-width: var(--ds-control-h-md);
    padding: 0;
}
.ds-btn--icon.ds-btn--sm { width: var(--ds-control-h-sm); min-width: var(--ds-control-h-sm); }
.ds-btn--icon.ds-btn--lg { width: var(--ds-control-h-lg); min-width: var(--ds-control-h-lg); }

/* Largura total (mobile / rodapés) */
.ds-btn--block { width: 100%; flex: 1 1 auto; }

/* Estado a carregar */
.ds-btn.is-loading { pointer-events: none; position: relative; }
.ds-btn.is-loading > *:not(.ds-spinner) { opacity: 0.5; }

.ds-spinner {
    width: 17px;
    height: 17px;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: var(--radius-full);
    animation: dsSpin 0.7s linear infinite;
    flex-shrink: 0;
}
@keyframes dsSpin { to { transform: rotate(360deg); } }

/* Grupo de botões segmentado (alturas e cantos coerentes) */
.ds-btn-group {
    display: inline-flex;
    align-items: stretch;
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-xs);
    max-width: 100%;
}
.ds-btn-group .ds-btn {
    border-radius: 0;
    margin-left: -1px;
    flex: 0 1 auto;
}
.ds-btn-group .ds-btn:first-child { border-radius: var(--radius-md) 0 0 var(--radius-md); margin-left: 0; }
.ds-btn-group .ds-btn:last-child { border-radius: 0 var(--radius-md) var(--radius-md) 0; }
.ds-btn-group .ds-btn.is-active,
.ds-btn-group .ds-btn.active {
    background: var(--primary-50);
    border-color: var(--primary-200);
    color: var(--primary-700);
    z-index: 1;
    position: relative;
}

/* Fila de ações genérica: em mobile cada botão cresce por igual */
.ds-actions {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}
.ds-actions > .ds-btn { flex: 1 1 auto; }
@media (min-width: 640px) {
    .ds-actions > .ds-btn { flex: 0 0 auto; }
}

/* Botão de ação de linha (ícone pequeno em tabelas) */
.ds-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ds-control-h-sm);
    height: var(--ds-control-h-sm);
    min-width: var(--ds-control-h-sm);
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    color: var(--neutral-400);
    cursor: pointer;
    transition: var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
}
.ds-icon-btn svg { width: 16px; height: 16px; }
.ds-icon-btn:hover { background: var(--neutral-100); color: var(--neutral-700); }
.ds-icon-btn:active { background: var(--neutral-200); transform: scale(0.94); }
.ds-icon-btn--danger:hover { background: var(--error-50); color: var(--error-600); }
.ds-icon-btn--primary:hover { background: var(--primary-50); color: var(--primary-600); }

/* Link de texto */
.ds-link {
    color: var(--primary-600);
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    border-radius: var(--radius-sm);
}
.ds-link:hover { color: var(--primary-700); text-decoration: underline; }

/* ===================================================================
   03. BADGES DE ESTADO, CHIPS, TAGS
   -------------------------------------------------------------------
   Badges por COR SEMÂNTICA. Mapeamento sugerido (Boopgest):
   Contratos: Aberto=blue · Reserva=violet · Efetivo=teal ·
   Em Curso=green · Por Finalizar=amber · Finalizado=gray · Anulado=red
   Folhas de obra: Aberto=blue · Em Curso=green · Fechado=gray
   =================================================================== */
.ds-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 10px;
    min-height: 24px;
    border-radius: var(--radius-full);
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1.4;
    white-space: nowrap;
    border: 1px solid transparent;
    background: var(--neutral-100);
    color: var(--neutral-600);
    letter-spacing: 0.1px;
    max-width: 100%;
}
.ds-badge svg { width: 13px; height: 13px; flex-shrink: 0; }

/* Ponto indicador de estado */
.ds-badge--dot::before {
    content: '';
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
    background: currentColor;
    opacity: 0.85;
}

.ds-badge--green  { background: var(--success-50);  color: var(--success-700); border-color: var(--success-200); }
.ds-badge--blue   { background: var(--info-50);     color: #1e40af;            border-color: #bfdbfe; }
.ds-badge--amber  { background: var(--warning-50);  color: #92400e;            border-color: #fde68a; }
.ds-badge--red    { background: var(--error-50);    color: var(--error-700);   border-color: var(--error-200); }
.ds-badge--violet { background: #f3e8ff;            color: #6b21a8;            border-color: #e9d5ff; }
.ds-badge--teal   { background: #ccfbf1;            color: #115e59;            border-color: #99f6e4; }
.ds-badge--orange { background: #ffedd5;            color: #9a3412;            border-color: #fed7aa; }
.ds-badge--indigo { background: #e0e7ff;            color: #3730a3;            border-color: #c7d2fe; }
.ds-badge--gray   { background: var(--neutral-100); color: var(--neutral-500); border-color: var(--neutral-200); }
.ds-badge--brand  { background: var(--primary-50);  color: var(--primary-700); border-color: var(--primary-200); }

/* Chips de filtro rápido (interativos) */
.ds-chips {
    display: flex;
    gap: 8px;
    overflow-x: auto;
    flex-wrap: nowrap;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
    padding: 2px;
    margin: -2px;
    min-width: 0;
}
.ds-chips::-webkit-scrollbar { display: none; }
@media (min-width: 768px) {
    .ds-chips { flex-wrap: wrap; overflow: visible; }
}
.ds-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: var(--ds-control-h-sm);
    padding: 0 14px;
    border-radius: var(--radius-full);
    border: 1px solid var(--neutral-200);
    background: var(--neutral-0);
    color: var(--neutral-600);
    font-family: var(--font-family);
    font-size: var(--text-xs);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    transition: var(--transition-fast);
    flex-shrink: 0;
}
.ds-chip:hover { border-color: var(--neutral-300); background: var(--neutral-50); }
.ds-chip:active { transform: scale(0.96); }
.ds-chip.is-active,
.ds-chip.active {
    background: var(--primary-600);
    border-color: var(--primary-600);
    color: var(--neutral-0);
}
.ds-chip-count {
    font-size: 10px;
    font-weight: 700;
    padding: 1px 7px;
    border-radius: var(--radius-full);
    background: var(--neutral-100);
    color: var(--neutral-500);
}
.ds-chip.is-active .ds-chip-count,
.ds-chip.active .ds-chip-count {
    background: rgba(255, 255, 255, 0.22);
    color: var(--neutral-0);
}
/* Em ecrã tátil, os chips crescem para o alvo de toque */
@media (pointer: coarse) {
    .ds-chip { min-height: var(--ds-touch); padding: 0 16px; }
}

/* Tags informativas pequenas (não interativas) */
.ds-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: var(--radius-sm);
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    background: var(--neutral-100);
    color: var(--neutral-500);
    border: 1px solid var(--neutral-200);
    white-space: nowrap;
}
.ds-tag svg { width: 12px; height: 12px; }
.ds-tag--brand { background: var(--primary-50); color: var(--primary-700); border-color: var(--primary-200); }
.ds-tag--info  { background: var(--info-50);    color: #1e40af;           border-color: #bfdbfe; }
.ds-tag--warn  { background: var(--warning-50); color: var(--warning-600); border-color: #fde68a; }

/* Tag removível (com X) */
.ds-tag-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin: -2px -4px -2px 0;
    border: none;
    background: transparent;
    border-radius: var(--radius-full);
    color: inherit;
    cursor: pointer;
    opacity: 0.7;
}
.ds-tag-x:hover { opacity: 1; background: rgba(0, 0, 0, 0.08); }
.ds-tag-x svg { width: 11px; height: 11px; }

/* ===================================================================
   04. FORMULÁRIOS
   =================================================================== */
.ds-form {
    display: flex;
    flex-direction: column;
    gap: var(--ds-page-gap);
    min-width: 0;
}

/* Grelha responsiva: 1 coluna -> 2 (>=640) -> 3 (>=1024) */
.ds-form-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--ds-field-gap) 20px;
    min-width: 0;
}
.ds-form-grid > * { min-width: 0; }
@media (min-width: 640px) {
    .ds-form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ds-col-2 { grid-column: span 2; }
    .ds-col-full { grid-column: 1 / -1; }
}
@media (min-width: 1024px) {
    .ds-form-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .ds-form-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ds-form-grid--2 .ds-col-2,
    .ds-form-grid--2 .ds-col-full { grid-column: 1 / -1; }
}
/* Nota: .ds-col-2 / .ds-col-full só têm efeito >=640px (1 coluna em mobile). */

/* Campo (label + controlo + ajuda/erro) */
.ds-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}
.ds-label {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--neutral-700);
    display: inline-flex;
    align-items: baseline;
    gap: 4px;
    min-width: 0;
}
.ds-req {
    color: var(--primary-600);
    font-weight: 700;
}
.ds-label-hint {
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--neutral-400);
    margin-left: auto;
}

/* Controlos base — altura fixa = mesma dos botões (linhas alinhadas) */
.ds-input,
.ds-select {
    width: 100%;
    height: var(--ds-control-h-md);
    padding: 0 14px;
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-md);
    font-family: var(--font-family);
    font-size: var(--text-sm);
    color: var(--neutral-800);
    background: var(--neutral-0);
    outline: none;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-fast);
    min-width: 0;
    -webkit-appearance: none;
    appearance: none;
}
.ds-input::placeholder { color: var(--neutral-400); }
.ds-input:hover,
.ds-select:hover { border-color: var(--neutral-300); }
.ds-input:focus,
.ds-select:focus,
.ds-input:focus-visible,
.ds-select:focus-visible {
    border-color: var(--primary-400);
    box-shadow: var(--ds-ring);
}
.ds-input:disabled,
.ds-select:disabled,
.ds-input[readonly].is-locked {
    background: var(--neutral-50);
    color: var(--neutral-400);
    cursor: not-allowed;
    border-color: var(--neutral-200);
}

/* Tamanho compacto (filtros densos em desktop) */
.ds-input--sm,
.ds-select--sm {
    height: var(--ds-control-h-sm);
    padding: 0 10px;
    font-size: var(--text-xs);
    border-radius: var(--radius-sm);
}

/* Select nativo com seta própria */
.ds-select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    padding-right: 36px;
    cursor: pointer;
}

/* Textarea (única exceção à altura fixa) */
.ds-textarea {
    width: 100%;
    min-height: 110px;
    padding: 10px 14px;
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-md);
    font-family: var(--font-family);
    font-size: var(--text-sm);
    line-height: 1.6;
    color: var(--neutral-800);
    background: var(--neutral-0);
    outline: none;
    resize: vertical;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    min-width: 0;
}
.ds-textarea::placeholder { color: var(--neutral-400); }
.ds-textarea:hover { border-color: var(--neutral-300); }
.ds-textarea:focus { border-color: var(--primary-400); box-shadow: var(--ds-ring); }

/* Input com ícone à esquerda (pesquisa, datas, €) */
.ds-input-wrap {
    position: relative;
    display: flex;
    align-items: center;
    min-width: 0;
}
.ds-input-wrap > svg {
    position: absolute;
    left: 13px;
    width: 16px;
    height: 16px;
    color: var(--neutral-400);
    pointer-events: none;
    z-index: 1;
}
.ds-input-wrap > .ds-input { padding-left: 38px; }
/* Sufixo (unidade: €, m², kg) */
.ds-input-suffix {
    position: absolute;
    right: 13px;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--neutral-400);
    pointer-events: none;
}
.ds-input-wrap--suffix > .ds-input { padding-right: 44px; }

/* Datepicker nativo coerente */
.ds-input[type="date"],
.ds-input[type="datetime-local"],
.ds-input[type="time"] {
    cursor: pointer;
}
.ds-input[type="date"]::-webkit-calendar-picker-indicator,
.ds-input[type="datetime-local"]::-webkit-calendar-picker-indicator {
    opacity: 0.55;
    cursor: pointer;
}
.ds-input[type="number"] { font-variant-numeric: tabular-nums; }

/* ===================================================================
   Campo de número com cadeado (ds-num-lock)
   O número que o servidor vai atribuir vem pré-preenchido e o campo
   nasce BLOQUEADO (readonly, cinzento). Clicar no cadeado destranca-o
   para edição manual.  O botão é uma CAMADA PRÓPRIA (position:absolute
   + z-index) — assim o auditor responsivo trata a sobreposição com o
   input como intencional (camadas diferentes) e não a assinala.
   Alvo de toque do cadeado = 44px (WCAG 2.5.5 / mobile-toque.css).
   =================================================================== */
.ds-num-lock {
    position: relative;
    display: block;
    min-width: 0;
}
.ds-num-lock__input {
    /* folga à direita para o texto nunca ficar por baixo do cadeado */
    padding-right: 52px;
    font-family: var(--ds-font-mono);
    letter-spacing: 0.02em;
}
.ds-num-lock__input--plain { font-family: var(--font-family); letter-spacing: normal; }
.ds-num-lock__btn {
    position: absolute;
    top: 50%;
    right: 4px;
    transform: translateY(-50%);
    z-index: 2;                      /* camada própria: sobreposição intencional */
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ds-touch);          /* 44px de alvo de toque */
    height: var(--ds-control-h-md);  /* acompanha a altura do input */
    min-width: var(--ds-touch);
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--neutral-400);
    border-radius: var(--radius-sm);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: color var(--transition-fast), background-color var(--transition-fast);
}
.ds-num-lock__btn:hover { color: var(--neutral-600); background: var(--neutral-100); }
.ds-num-lock__btn:focus-visible {
    outline: none;
    box-shadow: var(--ds-ring);
    color: var(--primary-600);
}
.ds-num-lock__btn > svg { width: 18px; height: 18px; pointer-events: none; }
/* Estado destrancado: cadeado aberto, tom de destaque; input volta a editável */
.ds-num-lock[data-locked="false"] .ds-num-lock__btn { color: var(--primary-600); }
.ds-num-lock[data-locked="false"] .ds-num-lock__btn:hover { background: var(--primary-50); }
/* Marca visível de que o utilizador escreveu um número manual */
.ds-num-lock[data-num-tocado="true"] .ds-num-lock__input {
    border-color: var(--primary-400);
    color: var(--neutral-800);
    font-weight: 600;
}

/* Checkbox e radio — alvo de toque de linha inteira */
.ds-check {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    min-height: var(--ds-touch);
    padding: 6px 2px;
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    border-radius: var(--radius-sm);
    min-width: 0;
}
.ds-check input[type="checkbox"],
.ds-check input[type="radio"] {
    width: 20px;
    height: 20px;
    margin-top: 2px;
    accent-color: var(--primary-600);
    cursor: pointer;
    flex-shrink: 0;
}
.ds-check input:focus-visible {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
}
.ds-check-label {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--text-sm);
    color: var(--neutral-700);
    font-weight: 500;
    min-width: 0;
}
.ds-check-desc {
    font-size: var(--text-xs);
    color: var(--neutral-400);
    font-weight: 400;
}
.ds-check-group {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.ds-check-group--row {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 4px 18px;
}

/* Interruptor (switch) */
.ds-switch {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-height: var(--ds-touch);
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
}
.ds-switch input {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
}
.ds-switch-track {
    width: 44px;
    height: 24px;
    border-radius: var(--radius-full);
    background: var(--neutral-300);
    position: relative;
    transition: background-color var(--transition-base);
    flex-shrink: 0;
}
.ds-switch-track::after {
    content: '';
    position: absolute;
    top: 3px;
    left: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--neutral-0);
    box-shadow: var(--shadow-xs);
    transition: transform var(--transition-base);
}
.ds-switch input:checked + .ds-switch-track { background: var(--primary-600); }
.ds-switch input:checked + .ds-switch-track::after { transform: translateX(20px); }
.ds-switch input:focus-visible + .ds-switch-track {
    outline: 2px solid var(--primary-500);
    outline-offset: 2px;
}
.ds-switch-label { font-size: var(--text-sm); font-weight: 500; color: var(--neutral-700); }

/* Ajuda e erro */
.ds-help {
    font-size: var(--text-xs);
    color: var(--neutral-400);
    line-height: 1.45;
}
.ds-error-msg {
    display: none;
    align-items: flex-start;
    gap: 5px;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--error-600);
    line-height: 1.4;
}
.ds-error-msg svg { width: 13px; height: 13px; flex-shrink: 0; margin-top: 1px; }
.ds-field.is-invalid .ds-error-msg { display: inline-flex; }
.ds-field.is-invalid .ds-label { color: var(--error-700); }
.ds-field.is-invalid .ds-input,
.ds-field.is-invalid .ds-select,
.ds-field.is-invalid .ds-textarea,
.ds-field.is-invalid .ds-combobox-control {
    border-color: var(--error-500);
    background: var(--error-50);
}
.ds-field.is-invalid .ds-input:focus,
.ds-field.is-invalid .ds-select:focus,
.ds-field.is-invalid .ds-textarea:focus {
    box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.12);
}

/* ---------- Combobox / select pesquisável (padrão react-select) ---- */
.ds-combobox { position: relative; min-width: 0; }
.ds-combobox-control {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    height: var(--ds-control-h-md);
    padding: 0 8px 0 14px;
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-md);
    background: var(--neutral-0);
    cursor: pointer;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    min-width: 0;
}
.ds-combobox-control:hover { border-color: var(--neutral-300); }
.ds-combobox.is-open .ds-combobox-control,
.ds-combobox-control:focus-within {
    border-color: var(--primary-400);
    box-shadow: var(--ds-ring);
}
.ds-combobox-input {
    flex: 1;
    min-width: 0;
    border: none;
    outline: none;
    background: transparent;
    font-family: var(--font-family);
    font-size: var(--text-sm);
    color: var(--neutral-800);
    height: 100%;
    padding: 0;
}
.ds-combobox-input::placeholder { color: var(--neutral-400); }
.ds-combobox-value {
    flex: 1;
    min-width: 0;
    font-size: var(--text-sm);
    color: var(--neutral-800);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.ds-combobox-value.is-placeholder { color: var(--neutral-400); }
.ds-combobox-clear {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    min-width: 28px;
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    color: var(--neutral-400);
    cursor: pointer;
    transition: var(--transition-fast);
}
.ds-combobox-clear:hover { background: var(--neutral-100); color: var(--neutral-600); }
.ds-combobox-clear svg { width: 14px; height: 14px; }
.ds-combobox-caret {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    color: var(--neutral-400);
    pointer-events: none;
    flex-shrink: 0;
}
.ds-combobox-caret svg {
    width: 16px;
    height: 16px;
    transition: transform var(--transition-base);
}
.ds-combobox.is-open .ds-combobox-caret svg { transform: rotate(180deg); }

.ds-combobox-menu {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    right: 0;
    z-index: var(--ds-z-dropdown);
    background: var(--neutral-0);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lg);
    max-height: 280px;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 5px;
}
.ds-combobox.is-open .ds-combobox-menu {
    display: block;
    animation: dsPop var(--transition-fast);
}
@keyframes dsPop {
    from { opacity: 0; transform: translateY(-4px); }
    to { opacity: 1; transform: translateY(0); }
}
.ds-option {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: var(--ds-touch);
    padding: 8px 12px;
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    color: var(--neutral-700);
    cursor: pointer;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    min-width: 0;
}
.ds-option > span {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.ds-option:hover,
.ds-option.is-focused { background: var(--neutral-50); }
.ds-option.is-selected {
    background: var(--primary-50);
    color: var(--primary-700);
    font-weight: 600;
}
.ds-option.is-selected::after {
    content: '';
    margin-left: auto;
    width: 16px;
    height: 16px;
    flex-shrink: 0;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23CC1616' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") no-repeat center / contain;
}
.ds-option-sub {
    font-size: var(--text-xs);
    color: var(--neutral-400);
    font-weight: 500;
}
.ds-optgroup {
    padding: 8px 12px 4px;
    font-size: 10.5px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.6px;
    color: var(--neutral-400);
}
.ds-combobox-empty {
    padding: 18px 12px;
    text-align: center;
    font-size: var(--text-sm);
    color: var(--neutral-400);
}

/* ---------- Editor de texto rico (casca para Quill ou equivalente) - */
.ds-richtext {
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-md);
    background: var(--neutral-0);
    overflow: hidden;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
    min-width: 0;
}
.ds-richtext:focus-within {
    border-color: var(--primary-400);
    box-shadow: var(--ds-ring);
}
.ds-richtext-toolbar {
    display: flex;
    align-items: center;
    gap: 2px;
    flex-wrap: wrap;
    padding: 6px 8px;
    background: var(--neutral-50);
    border-bottom: 1px solid var(--neutral-200);
}
.ds-richtext-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    color: var(--neutral-500);
    font-size: 13px;
    font-weight: 700;
    font-family: var(--font-family);
    cursor: pointer;
    transition: var(--transition-fast);
}
.ds-richtext-btn svg { width: 15px; height: 15px; }
.ds-richtext-btn:hover { background: var(--neutral-200); color: var(--neutral-700); }
.ds-richtext-btn.is-active,
.ds-richtext-btn.active { background: var(--primary-50); color: var(--primary-600); }
.ds-richtext-sep {
    width: 1px;
    height: 20px;
    background: var(--neutral-200);
    margin: 0 4px;
    flex-shrink: 0;
}
@media (pointer: coarse) {
    .ds-richtext-btn { width: 40px; height: 40px; }
}
.ds-richtext-content {
    padding: 12px 14px;
    min-height: 130px;
    max-height: 340px;
    overflow-y: auto;
    outline: none;
    font-size: var(--text-sm);
    color: var(--neutral-700);
    line-height: 1.7;
}
.ds-richtext-content:empty::before {
    content: attr(data-placeholder);
    color: var(--neutral-400);
    pointer-events: none;
}
.ds-richtext-content p { margin: 0 0 8px; }
.ds-richtext-content p:last-child { margin-bottom: 0; }
.ds-richtext-content ul,
.ds-richtext-content ol { padding-left: 22px; margin: 0 0 8px; }
.ds-richtext-content blockquote {
    border-left: 3px solid var(--neutral-200);
    padding-left: 12px;
    margin: 0 0 8px;
    color: var(--neutral-500);
}

/* ---------- Secção de formulário colapsável ----------------------- */
.ds-form-section {
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-lg);
    background: var(--neutral-0);
    min-width: 0;
}
.ds-form-section-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    min-height: 52px;
    padding: 10px var(--ds-card-pad);
    border: none;
    background: transparent;
    cursor: pointer;
    font-family: var(--font-family);
    text-align: left;
    border-radius: var(--radius-lg);
    -webkit-tap-highlight-color: transparent;
}
.ds-form-section-toggle:hover { background: var(--neutral-25); }
.ds-form-section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--neutral-800);
    min-width: 0;
}
.ds-form-section-title svg { width: 17px; height: 17px; color: var(--neutral-400); flex-shrink: 0; }
.ds-form-section-meta {
    font-size: var(--text-xs);
    color: var(--neutral-400);
    font-weight: 500;
}
.ds-form-section-chevron {
    display: inline-flex;
    color: var(--neutral-400);
    flex-shrink: 0;
}
.ds-form-section-chevron svg {
    width: 18px;
    height: 18px;
    transition: transform var(--transition-base);
}
.ds-form-section-body {
    padding: 4px var(--ds-card-pad) var(--ds-card-pad);
}
.ds-form-section.is-collapsed .ds-form-section-body { display: none; }
.ds-form-section.is-collapsed .ds-form-section-chevron svg { transform: rotate(-90deg); }

/* ---------- Barra de ações do formulário --------------------------
   Mobile: FIXA no fundo (sticky) — o Guardar está sempre acessível.
   Desktop (>=768px): volta a estática, alinhada à direita.          */
.ds-form-actions {
    position: sticky;
    bottom: 0;
    z-index: var(--ds-z-sticky);
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px var(--ds-page-pad);
    padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    margin: 4px calc(-1 * var(--ds-page-pad)) 0;
    background: rgba(255, 255, 255, 0.92);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border-top: 1px solid var(--neutral-200);
    box-shadow: 0 -6px 16px -8px rgba(15, 23, 42, 0.12);
}
.ds-form-actions > .ds-btn { flex: 1 1 auto; min-width: 0; }
.ds-form-actions > .ds-btn--ghost,
.ds-form-actions > .ds-btn--icon { flex: 0 0 auto; }
.ds-form-actions-spacer { display: none; }
@media (min-width: 768px) {
    .ds-form-actions {
        position: static;
        margin: 0;
        padding: 16px 0 0;
        background: transparent;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        box-shadow: none;
        border-top: 1px solid var(--neutral-200);
        justify-content: flex-end;
    }
    .ds-form-actions > .ds-btn { flex: 0 0 auto; }
    .ds-form-actions-spacer { display: block; flex: 1; }
}

/* ===================================================================
   05. LISTAGEM (filtros + tabela + paginação)
   =================================================================== */

/* ---------- Barra de filtros --------------------------------------
   Mobile: colapsada atrás do botão "Filtros" (com contador de ativos).
   Desktop: sempre aberta, campos em linha.                          */
.ds-filters {
    background: var(--neutral-0);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-xs);
    min-width: 0;
}
.ds-filters-toggle {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    min-height: 52px;
    padding: 8px var(--ds-card-pad);
    border: none;
    background: transparent;
    font-family: var(--font-family);
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--neutral-700);
    cursor: pointer;
    border-radius: var(--radius-lg);
    -webkit-tap-highlight-color: transparent;
}
.ds-filters-toggle-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.ds-filters-toggle-label svg { width: 17px; height: 17px; color: var(--neutral-400); }
.ds-filters-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--radius-full);
    background: var(--primary-600);
    color: #fff;
    font-size: 11px;
    font-weight: 700;
}
.ds-filters-badge:empty { display: none; }
.ds-filters-toggle-chevron { display: inline-flex; color: var(--neutral-400); }
.ds-filters-toggle-chevron svg {
    width: 18px;
    height: 18px;
    transition: transform var(--transition-base);
}
.ds-filters.is-open .ds-filters-toggle-chevron svg { transform: rotate(180deg); }

.ds-filters-body {
    display: none;
    flex-direction: column;
    gap: 12px;
    padding: 2px var(--ds-card-pad) var(--ds-card-pad);
    min-width: 0;
}
.ds-filters.is-open .ds-filters-body { display: flex; }

.ds-filter {
    display: flex;
    flex-direction: column;
    gap: 5px;
    min-width: 0;
}
.ds-filter-label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--neutral-500);
    text-transform: uppercase;
    letter-spacing: 0.5px;
}
.ds-filters-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
.ds-filters-actions > .ds-btn { flex: 1 1 auto; }

/* ---- Barra de filtros em ecrã largo -------------------------------------------
   Grelha de colunas iguais, e não flex. A troca corrige a assimetria que o
   cliente apontou (Propostas e Folhas de Obra) SEM tocar no HTML de módulo
   nenhum: a estrutura já é `.ds-filters-body > .ds-filter`, que é exatamente
   o que uma grelha precisa.

   O que estava errado, medido nas 10 listagens × 5 larguras (17 de 45
   combinações desalinhadas):

     · `flex: 1 1 170px` — os campos que sobram na última fila ESTICAM para a
       encher. Na mesma barra chegavam a diferir **334,7 px** de largura
       (Propostas a 1920) — mesma classe, mesmo tipo de controlo.
     · `align-items: flex-end` — quando um campo é mais alto do que os vizinhos
       (tem texto de ajuda ou uma checkbox por baixo do controlo), alinhar
       pelo fundo faz o RÓTULO desse campo subir acima dos outros. É o que se
       via em Propostas (**39,0 px**) e em Artigos (**36,9 px**), os dois ecrãs
       apontados. Alinhar pelo topo põe todos os rótulos na mesma linha e
       deixa o texto de ajuda pendurado por baixo, que é onde pertence.
     · `flex-wrap` — o par de botões ia parar onde a quebra o deixasse.

   `auto-fill` (e não `auto-fit`) é deliberado: o `auto-fit` colapsa as colunas
   vazias e devolve o esticão que se quer eliminar.
   ------------------------------------------------------------------------- */
@media (min-width: 768px) {
    .ds-filters-toggle { display: none; }

    /* ⚠️ OS DOIS SELETORES SÃO OBRIGATÓRIOS, e a razão é especificidade.
       Mais acima existe `.ds-filters.is-open .ds-filters-body { display: flex }`
       — duas classes, (0,2,0). Uma regra só com `.ds-filters-body` é (0,1,0) e
       PERDE, mesmo estando depois e dentro de uma media query: a especificidade
       decide antes da ordem. Resultado, quando o módulo punha `is-open` (e vários
       põem, mesmo em ecrã largo, onde o botão de abrir nem aparece): a barra
       voltava a `display: flex` e herdava o `flex-direction: column` da regra
       base — os campos empilhavam-se uns sob os outros, cada um do tamanho do
       seu conteúdo, com meia barra vazia à direita. Foi o que o cliente viu em
       Clientes, Colaboradores e Fornecedores. Medido: `is-open` true →
       flex/column/225px; false → grid/5 colunas. */
    /* A COLUNA É ESTREITA DE PROPÓSITO (140px). O cliente pediu que os campos
       coubessem TODOS na mesma linha, mesmo na barra de 7 filtros das Folhas de
       Obra. Com colunas de 200px isso era impossível: a 1600px de janela sobram
       ~1230px úteis, e 8 posições (a pesquisa vale 2) precisavam de 1284px.
       A 140px cabem em 1204px — e a partir de ~1500px ainda ficam folgadas.
       Quem perde largura são os `<select>`, que truncam com reticências sem
       perder sentido; quem a mantém é a pesquisa, que aceita texto livre. */
    /* O NÚMERO DE COLUNAS VEM DE UMA VARIÁVEL, e a razão é prática: cada módulo
       precisa do seu (§ mais abaixo, e nos `mod-*.css`), e uma variável HERDA em
       vez de competir. Tentar o mesmo com seletores obriga a bater a
       especificidade de `.ds-filters.is-open .ds-filters-body`, que são três
       classes — foi o que me falhou duas vezes. Com `--ds-filtros-cols` basta
       pô-la no contentor da página e o valor desce sozinho. */
    .ds-filters-body,
    .ds-filters.is-open .ds-filters-body {
        display: grid;
        grid-template-columns: repeat(var(--ds-filtros-cols, auto-fill), minmax(140px, 1fr));
        align-items: start;
        gap: 12px;
        padding: var(--ds-card-pad);
    }
    .ds-filter { min-width: 0; }
    /* Com a coluna a 140px o texto de um combobox sobra. Sem isto ficava cortado
       a meio da palavra ("Selecior"); com reticências lê-se como abreviatura e
       não como defeito. O valor completo continua no `title`/`aria-label`. */
    .ds-filter .ds-input,
    .ds-filter .ds-select,
    .ds-filter .ds-combo-input {
        text-overflow: ellipsis;
    }
    /* Largura mínima garantida aos campos que dela precisam: a pesquisa (texto
       livre) e os que trazem um nome de pessoa ou de empresa lá dentro. Duas
       colunas = 292px, contra os 140px de um `<select>` de "Todos/Ativos". */
    .ds-filter--search,
    .ds-filter--wide { grid-column: span 2; }
    /* Os botões ocupam a ÚLTIMA FILA inteira, encostados à direita. Foi a única
       regra que se portou bem com 1 filtro e com 9: presos às duas últimas
       colunas ficavam a meio da fila quando os filtros não a enchiam, e a seguir
       ao último filtro ficavam onde a quebra os deixasse. */
    /* Os botões ficam SEMPRE encostados à margem direita — nas duas últimas
       colunas, em qualquer listagem. Foi o pedido do cliente: "têm de ser todos
       dinâmicos e estarem sempre encostados ao canto direito para garantir
       consistência visual". Segui-los no fluxo punha-os a meio da barra, logo
       a seguir ao último filtro, com meia barra vazia à direita.
       Para não caírem numa fila só deles, é o `--ds-filtros-cols` de cada módulo
       que garante que a última fila de campos deixa duas colunas livres. */
    .ds-filters-actions {
        grid-column: -3 / -1;
        justify-content: flex-end;
        /* ALINHADOS COM OS CONTROLOS, não com o fundo da fila. Com `align-self: end`
           os botões desciam até ao fundo da célula mais alta — e basta um campo
           trazer uma checkbox (Propostas, +39px) ou uma nota (Artigos, +95px) para
           os botões deixarem de estar ao lado do último filtro e passarem a
           flutuar por baixo dele. Medido: desvio 39px, 74px e 95px conforme a
           listagem; nas que só têm rótulo+controlo, 0.
           Encostados ao topo e descidos exatamente a altura do rótulo, ficam
           sempre na mesma linha dos `<select>`, em qualquer listagem. */
        align-self: start;
        margin-top: var(--ds-filtro-rotulo-h, 23px);
    }
    .ds-filters-actions > .ds-btn { flex: 0 0 auto; }
}

/* ---------- Barra de resultados ----------------------------------- */
.ds-results {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}
.ds-results-count {
    font-size: var(--text-sm);
    color: var(--neutral-500);
    font-weight: 500;
}
.ds-results-count strong { color: var(--neutral-800); font-weight: 700; }

/* ---------- Tabela responsiva --------------------------------------
   MOBILE-FIRST: por defeito (<768px) cada linha é um CARTÃO empilhado
   com pares rótulo:valor — cada <td> precisa de data-label="...".
   >=768px: tabela clássica. NUNCA scroll lateral partido em mobile. */
.ds-table-wrap {
    min-width: 0;
    /* mobile: os cartões vivem soltos, sem moldura */
}
.ds-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-family: var(--font-family);
}

/* -- modo cartões (base, <768px) -- */
.ds-table,
.ds-table tbody { display: block; min-width: 0; }
.ds-table thead { display: none; }
.ds-table tbody { display: flex; flex-direction: column; gap: 10px; }
.ds-table tbody tr {
    display: block;
    background: var(--neutral-0);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-lg);
    padding: 6px 14px;
    box-shadow: var(--shadow-xs);
    transition: var(--transition-fast);
    min-width: 0;
}
.ds-table tbody tr:active { background: var(--neutral-50); }
.ds-table tbody td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 8px 0;
    border-bottom: 1px dashed var(--neutral-100);
    font-size: var(--text-sm);
    color: var(--neutral-700);
    min-width: 0;
    text-align: right;
    overflow-wrap: anywhere;
}
.ds-table tbody td:last-child { border-bottom: none; }
.ds-table tbody td::before {
    content: attr(data-label);
    /* Era 10.5px fixos. Este bloco só existe abaixo de 768px, onde o
       `--txt-xs` vale 12px — o piso de legibilidade que o resto da app já
       respeita. Um rótulo de 10.5px num telemóvel não se lê. */
    font-size: var(--txt-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    color: var(--neutral-400);
    text-align: left;
    flex-shrink: 0;
}
.ds-table tbody td:not([data-label])::before { content: none; }

/* Valor de DOIS NÍVEIS (nome + marca, nome + morada, …) dentro do cartão.
   O `<td>` é `display:flex`, por isso o segundo nível ficava LADO A LADO
   com o primeiro e os dois a disputar a mesma largura: em Folhas de Obra a
   390px lia-se «Leais &  ELIÓ - Leais & Oliveira Lda |» na mesma linha.
   O `:has()` limita a regra às células que têm mesmo dois níveis — as
   restantes mantêm exatamente o comportamento de hoje, e num browser sem
   `:has()` cai tudo e volta ao estado anterior sem partir nada. */
.ds-table tbody td:has(> * + .ds-cell-muted):not(.ds-td-hero) {
    flex-wrap: wrap;
}

.ds-table tbody td:not(.ds-td-hero) > * + .ds-cell-muted {
    flex: 1 0 100%;
}

/* Célula-título do cartão (ex.: código do contrato + estado) */
.ds-table td.ds-td-hero {
    justify-content: space-between;
    padding: 10px 0;
    border-bottom: 1px solid var(--neutral-100);
    text-align: left;
}
.ds-table td.ds-td-hero::before { content: none; }

/* Célula de ações no cartão */
.ds-table td.ds-td-actions {
    justify-content: flex-end;
    gap: 6px;
    padding-top: 10px;
}
.ds-table td.ds-td-actions::before { content: none; }

/* -- modo tabela (>=768px) -- */
@media (min-width: 768px) {
    .ds-table-wrap {
        background: var(--neutral-0);
        border: 1px solid var(--neutral-200);
        border-radius: var(--radius-lg);
        box-shadow: var(--shadow-xs);
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }
    .ds-table { display: table; }
    .ds-table thead { display: table-header-group; }
    .ds-table tbody { display: table-row-group; }
    .ds-table tbody tr {
        display: table-row;
        background: transparent;
        border: none;
        border-radius: 0;
        padding: 0;
        box-shadow: none;
    }
    .ds-table tbody tr:hover { background: var(--neutral-50); }
    .ds-table tbody tr:active { background: var(--neutral-100); }
    .ds-table tbody tr.is-selected { background: var(--primary-50); }
    .ds-table tbody td {
        display: table-cell;
        padding: 12px 16px;
        border-bottom: 1px solid var(--neutral-100);
        text-align: left;
        vertical-align: middle;
        white-space: nowrap;
        overflow-wrap: normal;
    }
    .ds-table tbody td::before { content: none !important; }
    .ds-table tbody tr:last-child td { border-bottom: none; }
    .ds-table td.ds-td-hero { padding: 12px 16px; border-bottom: 1px solid var(--neutral-100); }
    .ds-table tbody tr:last-child td.ds-td-hero { border-bottom: none; }
    .ds-table td.ds-td-wrap { white-space: normal; min-width: 180px; max-width: 340px; overflow-wrap: anywhere; }
    .ds-table td.ds-td-actions { text-align: right; }

    .ds-table thead th {
        padding: 11px 16px;
        font-size: var(--text-xs);
        font-weight: 700;
        color: var(--neutral-500);
        text-transform: uppercase;
        letter-spacing: 0.7px;
        text-align: left;
        background: var(--neutral-50);
        border-bottom: 1px solid var(--neutral-200);
        white-space: nowrap;
        position: sticky;
        top: 0;
        z-index: 2;
    }
    .ds-th-num, .ds-td-num { text-align: right !important; }
    .ds-th-center, .ds-td-center { text-align: center !important; }
}

/* Linha inteira clicável — cursor explícito (correção UX vs Boopgest) */
.ds-table tbody tr.ds-row-link { cursor: pointer; }

/* Ordenação: <th class="ds-th-sortable" aria-sort="ascending|descending|none"> */
.ds-th-sortable {
    cursor: pointer;
    user-select: none;
    transition: color var(--transition-fast);
}
.ds-th-sortable:hover { color: var(--neutral-700); }
.ds-th-sortable .ds-sort-ind {
    display: inline-block;
    width: 1.1em;
    margin-left: 2px;
    color: var(--neutral-300);
    font-size: 11px;
}
.ds-th-sortable .ds-sort-ind::after { content: '↕'; }
.ds-th-sortable[aria-sort="ascending"] { color: var(--primary-600); }
.ds-th-sortable[aria-sort="ascending"] .ds-sort-ind { color: var(--primary-600); }
.ds-th-sortable[aria-sort="ascending"] .ds-sort-ind::after { content: '↑'; }
.ds-th-sortable[aria-sort="descending"] { color: var(--primary-600); }
.ds-th-sortable[aria-sort="descending"] .ds-sort-ind { color: var(--primary-600); }
.ds-th-sortable[aria-sort="descending"] .ds-sort-ind::after { content: '↓'; }

/* Ajudantes de célula */
.ds-cell-primary { font-weight: 600; color: var(--neutral-900); }
.ds-cell-muted { color: var(--neutral-400); font-size: var(--text-xs); }
.ds-cell-ref {
    font-family: var(--ds-font-mono);
    font-weight: 700;
    color: var(--neutral-900);
    letter-spacing: -0.01em;
}
.ds-cell-num { font-variant-numeric: tabular-nums; }
.ds-cell-neg { color: var(--error-600); font-weight: 600; }
.ds-cell-pos { color: var(--success-600); font-weight: 600; }
.ds-cell-stack { display: flex; flex-direction: column; gap: 2px; min-width: 0; align-items: flex-end; }
@media (min-width: 768px) {
    .ds-cell-stack { align-items: flex-start; }
}

/* Ações por linha: sempre visíveis em toque; em rato aparecem no hover */
.ds-row-actions {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    justify-content: flex-end;
}
@media (hover: hover) and (pointer: fine) {
    .ds-table tbody tr .ds-row-actions { opacity: 0; transition: opacity var(--transition-fast); }
    .ds-table tbody tr:hover .ds-row-actions,
    .ds-table tbody tr:focus-within .ds-row-actions { opacity: 1; }
}

/* ---------- Paginação ----------------------------------------------
   Mobile: compacta — « ‹ [2 / 23] › » (números escondidos).
   >=640px: janela de números completa + tamanho de página.         */
.ds-pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    min-width: 0;
}
.ds-pagesize {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--text-xs);
    color: var(--neutral-500);
    font-weight: 600;
    white-space: nowrap;
}
.ds-pagesize .ds-select {
    width: auto;
    min-width: 74px;
    height: var(--ds-control-h-sm);
    padding: 0 30px 0 10px;
    font-size: var(--text-xs);
    border-radius: var(--radius-sm);
    background-position: right 8px center;
}
.ds-pager {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
}
.ds-page-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--ds-control-h-md);
    height: var(--ds-control-h-md);
    padding: 0 6px;
    border: 1px solid var(--neutral-200);
    background: var(--neutral-0);
    border-radius: var(--radius-md);
    color: var(--neutral-600);
    font-family: var(--font-family);
    font-size: var(--text-sm);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
}
.ds-page-btn svg { width: 16px; height: 16px; }
.ds-page-btn:hover { border-color: var(--neutral-300); background: var(--neutral-50); }
.ds-page-btn:active { transform: scale(0.94); }
.ds-page-btn.is-active,
.ds-page-btn.active {
    background: var(--primary-600);
    border-color: var(--primary-600);
    color: #fff;
    cursor: default;
}
.ds-page-btn:disabled {
    opacity: 0.4;
    cursor: not-allowed;
    pointer-events: none;
}
.ds-page-btn--num { display: none; }
.ds-pager-status {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--neutral-600);
    font-variant-numeric: tabular-nums;
    padding: 0 8px;
    white-space: nowrap;
}
@media (min-width: 640px) {
    .ds-page-btn--num { display: inline-flex; }
    .ds-pager-status { display: none; }
}

/* ---------- Estado vazio ------------------------------------------ */
.ds-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 44px 20px;
    text-align: center;
    background: var(--neutral-0);
    border: 1px dashed var(--neutral-200);
    border-radius: var(--radius-lg);
    min-width: 0;
}
.ds-empty--plain { border: none; background: transparent; }
.ds-empty-icon {
    width: 58px;
    height: 58px;
    border-radius: var(--radius-full);
    background: var(--neutral-100);
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 16px;
}
.ds-empty-icon svg { width: 26px; height: 26px; color: var(--neutral-400); }
.ds-empty-title {
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--neutral-700);
    margin: 0 0 5px;
}
.ds-empty-text {
    font-size: var(--text-sm);
    color: var(--neutral-400);
    max-width: 360px;
    line-height: 1.55;
    margin: 0 0 16px;
}
.ds-empty-text:last-child { margin-bottom: 0; }
@media (min-width: 768px) {
    .ds-empty { padding: 60px 24px; }
}

/* ===================================================================
   06. SUB-TABELA COMPACTA + FIELDLIST DE DETALHE
   =================================================================== */

/* Sub-tabela dentro de páginas de detalhe: mais densa.
   Usa a mesma mecânica cartões->tabela da .ds-table. */
@media (min-width: 768px) {
    .ds-table--compact thead th { padding: 8px 12px; font-size: 10.5px; letter-spacing: 0.5px; }
    .ds-table--compact tbody td { padding: 8px 12px; font-size: var(--text-xs); }
    .ds-table--compact .ds-icon-btn {
        width: 30px;
        height: 30px;
        min-width: 30px;
    }
    /* dentro de um card, a sub-tabela não precisa de moldura própria */
    .ds-card .ds-table-wrap {
        border: none;
        border-radius: 0;
        box-shadow: none;
    }
    .ds-card .ds-card-body--flush .ds-table-wrap { border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
}
/* rodapé de totais da sub-tabela */
.ds-table tfoot { display: none; }
@media (min-width: 768px) {
    .ds-table tfoot { display: table-footer-group; }
    .ds-table tfoot td {
        padding: 10px 16px;
        font-size: var(--text-sm);
        font-weight: 700;
        color: var(--neutral-900);
        background: var(--neutral-50);
        border-top: 1px solid var(--neutral-200);
        white-space: nowrap;
    }
    .ds-table--compact tfoot td { padding: 8px 12px; font-size: var(--text-xs); }
}
/* totais em mobile: usar um cartão .ds-totals em alternativa ao tfoot */
.ds-totals {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 12px 14px;
    background: var(--neutral-50);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-md);
}
.ds-totals-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: var(--text-sm);
    color: var(--neutral-600);
}
.ds-totals-row strong { color: var(--neutral-900); font-variant-numeric: tabular-nums; }
.ds-totals-row--grand {
    padding-top: 6px;
    border-top: 1px solid var(--neutral-200);
    font-weight: 700;
    color: var(--neutral-900);
}
@media (min-width: 768px) {
    .ds-totals--mobile-only { display: none; }
}

/* Fieldlist: pares rótulo/valor das páginas de detalhe (9+ campos) */
.ds-fieldlist {
    display: grid;
    grid-template-columns: 1fr;
    gap: 14px 24px;
    min-width: 0;
}
.ds-fieldlist > * { min-width: 0; }
@media (min-width: 640px) {
    .ds-fieldlist { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    .ds-fieldlist { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .ds-fieldlist--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.ds-field-ro {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}
.ds-field-ro--full { grid-column: 1 / -1; }
.ds-field-ro-label {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--neutral-400);
    text-transform: uppercase;
    letter-spacing: 0.4px;
}
.ds-field-ro-value {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--neutral-800);
    overflow-wrap: anywhere;
    line-height: 1.5;
}
.ds-field-ro-value.is-empty {
    color: var(--neutral-300);
    font-weight: 500;
    font-style: italic;
}
.ds-field-ro-value.is-empty::before { content: '—'; font-style: normal; }
/* bloco de rich text no detalhe (Observações) */
.ds-field-ro-value--rich {
    font-weight: 400;
    color: var(--neutral-700);
    background: var(--neutral-25);
    border: 1px solid var(--neutral-100);
    border-radius: var(--radius-md);
    padding: 12px 14px;
    line-height: 1.65;
}
.ds-field-ro-value--rich p { margin: 0 0 8px; }
.ds-field-ro-value--rich p:last-child { margin-bottom: 0; }
.ds-field-ro-value--rich ul,
.ds-field-ro-value--rich ol { padding-left: 20px; margin: 0 0 8px; }

/* ===================================================================
   07. KPI / CARTÕES DE MÉTRICA
   =================================================================== */
.ds-kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 148px), 1fr));
    gap: 12px;
    min-width: 0;
}
.ds-kpi-grid > * { min-width: 0; }
@media (min-width: 768px) {
    .ds-kpi-grid { gap: 16px; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
}
@media (min-width: 1440px) {
    .ds-kpi-grid { gap: 20px; }
}

.ds-kpi {
    position: relative;
    background: var(--neutral-0);
    border: 1px solid var(--neutral-200);
    border-radius: var(--radius-lg);
    padding: 16px;
    overflow: hidden;
    transition: var(--transition-base);
    min-width: 0;
}
@media (hover: hover) {
    .ds-kpi:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); }
}
/* faixa de acento à esquerda */
.ds-kpi::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 4px;
    height: 100%;
    background: var(--primary-500);
    opacity: 0.9;
}
.ds-kpi--blue::before   { background: var(--info-500); }
.ds-kpi--green::before  { background: var(--success-500); }
.ds-kpi--amber::before  { background: var(--warning-500); }
.ds-kpi--violet::before { background: #8b5cf6; }
.ds-kpi--red::before    { background: var(--error-500); }
.ds-kpi--gray::before   { background: var(--neutral-300); }

.ds-kpi-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 12px;
}
.ds-kpi-icon {
    width: 38px;
    height: 38px;
    border-radius: var(--radius-md);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--primary-50);
    color: var(--primary-500);
    flex-shrink: 0;
}
.ds-kpi-icon svg { width: 19px; height: 19px; }
.ds-kpi--blue .ds-kpi-icon   { background: var(--info-50);    color: var(--info-500); }
.ds-kpi--green .ds-kpi-icon  { background: var(--success-50); color: var(--success-600); }
.ds-kpi--amber .ds-kpi-icon  { background: var(--warning-50); color: var(--warning-600); }
.ds-kpi--violet .ds-kpi-icon { background: #f3e8ff;           color: #8b5cf6; }
.ds-kpi--red .ds-kpi-icon    { background: var(--error-50);   color: var(--error-500); }
.ds-kpi--gray .ds-kpi-icon   { background: var(--neutral-100); color: var(--neutral-500); }

.ds-kpi-value {
    font-size: var(--text-2xl);
    font-weight: 800;
    color: var(--neutral-900);
    letter-spacing: -0.025em;
    line-height: 1.05;
    font-variant-numeric: tabular-nums;
    overflow-wrap: anywhere;
}
.ds-kpi-value small {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--neutral-400);
    margin-left: 2px;
}
.ds-kpi-label {
    font-size: var(--text-xs);
    color: var(--neutral-500);
    margin-top: 5px;
    font-weight: 600;
    letter-spacing: 0.2px;
}
.ds-kpi-trend {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--text-xs);
    font-weight: 700;
    margin-top: 10px;
    padding: 2px 8px;
    border-radius: var(--radius-full);
    background: var(--neutral-100);
    color: var(--neutral-500);
    font-variant-numeric: tabular-nums;
}
.ds-kpi-trend svg { width: 13px; height: 13px; }
.ds-kpi-trend--up { background: var(--success-50); color: var(--success-600); }
.ds-kpi-trend--down { background: var(--error-50); color: var(--error-600); }
@media (min-width: 1024px) {
    .ds-kpi { padding: 20px; }
    .ds-kpi-value { font-size: var(--text-3xl); }
    .ds-kpi-label { font-size: var(--text-sm); font-weight: 500; }
    .ds-kpi-icon { width: 42px; height: 42px; }
    .ds-kpi-icon svg { width: 21px; height: 21px; }
}

/* ===================================================================
   08. ABAS
   -------------------------------------------------------------------
   Em mobile: scroll horizontal com fade indicador nas margens.
   =================================================================== */
.ds-tabs {
    display: flex;
    gap: 2px;
    border-bottom: 1px solid var(--neutral-200);
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
    -webkit-overflow-scrolling: touch;
    min-width: 0;
    /* fade nas margens a indicar que há mais abas */
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 28px), transparent 100%);
}
.ds-tabs::-webkit-scrollbar { display: none; }
@media (min-width: 768px) {
    .ds-tabs {
        -webkit-mask-image: none;
        mask-image: none;
        gap: 4px;
    }
}
.ds-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    min-height: var(--ds-touch);
    padding: 0 14px;
    border: none;
    background: transparent;
    font-family: var(--font-family);
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--neutral-500);
    cursor: pointer;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: var(--transition-fast);
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
}
.ds-tab svg { width: 16px; height: 16px; }
.ds-tab:hover { color: var(--neutral-700); }
.ds-tab:active { background: var(--neutral-50); }
.ds-tab.is-active,
.ds-tab.active {
    color: var(--primary-600);
    border-bottom-color: var(--primary-600);
}
.ds-tab-badge {
    font-size: 10px;
    font-weight: 700;
    padding: 1px 7px;
    border-radius: var(--radius-full);
    background: var(--neutral-100);
    color: var(--neutral-500);
}
.ds-tab.is-active .ds-tab-badge,
.ds-tab.active .ds-tab-badge {
    background: var(--primary-50);
    color: var(--primary-600);
}
.ds-tab-panel { display: none; min-width: 0; }
.ds-tab-panel.is-active,
.ds-tab-panel.active {
    display: block;
    animation: dsFadeUp var(--transition-base);
}
@keyframes dsFadeUp {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
}

/* ===================================================================
   09. MODAL
   -------------------------------------------------------------------
   Mobile (base): folha de fundo (bottom sheet) com pega, desliza de
   baixo, cabe em qualquer altura. >=768px: modal centrado clássico.
   @media (max-height: 520px): modo paisagem — compacto, cabe em
   844x390 com cabeçalho + rodapé visíveis e corpo com scroll.
   =================================================================== */
.ds-modal-overlay {
    position: fixed;
    inset: 0;
    z-index: var(--ds-z-modal);
    background: rgba(15, 23, 42, 0.48);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding: 0;
    animation: dsFade var(--transition-fast);
}
@keyframes dsFade { from { opacity: 0; } to { opacity: 1; } }
.ds-modal-overlay.is-closing { animation: dsFadeOut var(--transition-fast) forwards; }
@keyframes dsFadeOut { to { opacity: 0; } }

.ds-modal {
    background: var(--neutral-0);
    width: 100%;
    max-width: 100%;
    max-height: calc(100vh - 40px);
    max-height: calc(100dvh - 40px);
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    box-shadow: var(--shadow-xl);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    animation: dsSheetUp var(--transition-slow);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    min-width: 0;
}
@keyframes dsSheetUp {
    from { transform: translateY(48px); opacity: 0.4; }
    to { transform: translateY(0); opacity: 1; }
}

/* pega de arrasto (só mobile) */
.ds-modal-handle {
    width: 44px;
    height: 5px;
    border-radius: var(--radius-full);
    background: var(--neutral-200);
    margin: 10px auto 0;
    flex-shrink: 0;
}

.ds-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px var(--ds-card-pad);
    border-bottom: 1px solid var(--neutral-200);
    flex-shrink: 0;
    min-width: 0;
}
.ds-modal-title {
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--neutral-900);
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
}
.ds-modal-subtitle {
    font-size: var(--text-xs);
    color: var(--neutral-400);
    font-weight: 500;
    margin-top: 2px;
}
.ds-modal-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ds-control-h-sm);
    height: var(--ds-control-h-sm);
    min-width: var(--ds-control-h-sm);
    border: none;
    background: transparent;
    border-radius: var(--radius-sm);
    color: var(--neutral-400);
    cursor: pointer;
    transition: var(--transition-fast);
    -webkit-tap-highlight-color: transparent;
}
.ds-modal-close svg { width: 18px; height: 18px; }
.ds-modal-close:hover { background: var(--neutral-100); color: var(--neutral-600); }
.ds-modal-close:active { transform: scale(0.94); }

.ds-modal-body {
    padding: var(--ds-card-pad);
    overflow-y: auto;
    overflow-x: hidden;
    overscroll-behavior: contain;
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
}
.ds-modal-footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    padding: 12px var(--ds-card-pad);
    border-top: 1px solid var(--neutral-200);
    background: var(--neutral-25);
    flex-shrink: 0;
    min-width: 0;
}
/* mobile: botões do rodapé dividem a largura */
.ds-modal-footer > .ds-btn { flex: 1 1 auto; min-width: 0; }
.ds-modal-footer > .ds-btn--ghost { flex: 0 0 auto; }

/* >=768px: centrado */
@media (min-width: 768px) {
    .ds-modal-overlay {
        align-items: center;
        padding: 32px 24px;
    }
    .ds-modal {
        width: min(var(--ds-modal-w, 640px), 100%);
        max-height: min(85vh, 900px);
        border-radius: var(--radius-lg);
        animation: dsModalIn var(--transition-spring);
        padding-bottom: 0;
    }
    .ds-modal-handle { display: none; }
    .ds-modal-header { padding: 18px var(--ds-card-pad); }
    .ds-modal-title { font-size: var(--text-xl); }
    .ds-modal-footer > .ds-btn { flex: 0 0 auto; }
    .ds-modal--sm { --ds-modal-w: 460px; }
    .ds-modal--lg { --ds-modal-w: 900px; }
    .ds-modal--xl { --ds-modal-w: 1140px; }
}
@keyframes dsModalIn {
    from { opacity: 0; transform: translateY(-14px) scale(0.98); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}

/* Telemóvel em PAISAGEM (ex.: 844x390): compactar tudo,
   garantir cabeçalho e rodapé visíveis e corpo com scroll interno. */
@media (max-height: 520px) {
    .ds-modal-overlay {
        align-items: center;
        padding: 8px;
    }
    .ds-modal {
        width: min(var(--ds-modal-w, 640px), 100%);
        max-height: calc(100vh - 16px);
        max-height: calc(100dvh - 16px);
        border-radius: var(--radius-lg);
        padding-bottom: 0;
    }
    .ds-modal-handle { display: none; }
    .ds-modal-header { padding: 10px 16px; }
    .ds-modal-title { font-size: var(--text-base); }
    .ds-modal-body { padding: 12px 16px; }
    .ds-modal-footer { padding: 8px 16px; }
    .ds-modal-footer > .ds-btn { flex: 0 0 auto; }
}

/* ===================================================================
   10. ALERTAS INLINE
   =================================================================== */
.ds-alert {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 14px;
    border-radius: var(--radius-md);
    border: 1px solid var(--neutral-200);
    background: var(--neutral-50);
    color: var(--neutral-700);
    font-size: var(--text-sm);
    line-height: 1.5;
    min-width: 0;
}
.ds-alert svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }
.ds-alert-title { font-weight: 700; display: block; margin-bottom: 2px; }
.ds-alert-body { min-width: 0; overflow-wrap: anywhere; }
.ds-alert--info    { background: var(--info-50);    border-color: #bfdbfe;             color: #1e40af; }
.ds-alert--success { background: var(--success-50); border-color: var(--success-200);  color: var(--success-700); }
.ds-alert--warning { background: var(--warning-50); border-color: #fde68a;             color: #92400e; }
.ds-alert--error   { background: var(--error-50);   border-color: var(--error-200);    color: var(--error-700); }

/* ===================================================================
   11. SKELETONS (carregamento)
   =================================================================== */
.ds-skeleton {
    position: relative;
    background: var(--neutral-100);
    border-radius: var(--radius-sm);
    overflow: hidden;
    min-width: 0;
}
.ds-skeleton::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.65) 50%,
        transparent 100%);
    transform: translateX(-100%);
    animation: dsShimmer 1.5s ease-in-out infinite;
}
@keyframes dsShimmer {
    100% { transform: translateX(100%); }
}

/* variantes de forma */
.ds-skeleton--text   { height: 12px; }
.ds-skeleton--title  { height: 18px; max-width: 45%; }
.ds-skeleton--label  { height: 10px; max-width: 30%; }
.ds-skeleton--btn    { height: var(--ds-control-h-md); width: 128px; border-radius: var(--radius-md); }
.ds-skeleton--input  { height: var(--ds-control-h-md); border-radius: var(--radius-md); }
.ds-skeleton--circle { width: 40px; height: 40px; border-radius: 50%; }
.ds-skeleton--thumb  { width: 56px; height: 42px; border-radius: var(--radius-sm); }
.ds-skeleton--badge  { height: 24px; width: 84px; border-radius: var(--radius-full); }
.ds-skeleton--row    { height: 46px; border-radius: var(--radius-md); }
.ds-skeleton--card   { height: 116px; border-radius: var(--radius-lg); }

/* larguras utilitárias */
.ds-skeleton--w25 { width: 25%; }
.ds-skeleton--w40 { width: 40%; }
.ds-skeleton--w60 { width: 60%; }
.ds-skeleton--w75 { width: 75%; }
.ds-skeleton--w100 { width: 100%; }

/* pilha de skeletons */
.ds-skeleton-stack {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}
.ds-skeleton-stack--row {
    flex-direction: row;
    align-items: center;
}

/* linha de tabela em carregamento (pronta a usar) */
.ds-skeleton-table-row {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--neutral-100);
}
.ds-skeleton-table-row:last-child { border-bottom: none; }
.ds-skeleton-table-row .ds-skeleton { height: 12px; flex: 1; }
.ds-skeleton-table-row .ds-skeleton:first-child { max-width: 90px; }
.ds-skeleton-table-row .ds-skeleton:last-child { max-width: 72px; height: 22px; border-radius: var(--radius-full); }

/* ===================================================================
   12. UTILITÁRIOS MÍNIMOS
   =================================================================== */
.ds-u-right { text-align: right !important; }
.ds-u-center { text-align: center !important; }
.ds-u-muted { color: var(--neutral-400) !important; }
.ds-u-nowrap { white-space: nowrap !important; }
.ds-u-num { font-variant-numeric: tabular-nums; }
.ds-u-mono { font-family: var(--ds-font-mono); }
.ds-u-truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
.ds-u-flex-1 { flex: 1 1 auto; min-width: 0; }
.ds-divider {
    height: 1px;
    background: var(--neutral-200);
    border: none;
    margin: 4px 0;
}

/* visível só em mobile / só em desktop */
.ds-only-desktop { display: none !important; }
@media (min-width: 768px) {
    .ds-only-mobile { display: none !important; }
    .ds-only-desktop { display: revert !important; }
}
/* O `hidden` MANDA SEMPRE.
   Um `!important` do autor ganha ao stylesheet do browser, portanto o `display: revert
   !important` de cima anulava a regra `[hidden] { display: none }` da UA: a partir dos
   768 px, um elemento com AS DUAS COISAS ficava à vista apesar de estar marcado como
   escondido. Foi assim que o botão «Contabilidade» das Folhas de Obra apareceu em
   desktop a quem não pode ver contabilidade — o JavaScript punha `hidden = true` e não
   acontecia nada, sem erro nenhum a dizer porquê.
   É a mesma armadilha que obrigou o `mod-mascara.js` a inventar a classe `.mc-oculto`:
   quando se esconde por CSS com `!important`, o `[hidden]` deixa de contar.
   A regra fica AGARRADA a estas duas classes, e não a `[hidden]` em geral: um
   `[hidden] { display: none !important }` global passaria a ganhar também a quem mostra
   um elemento por `style.display` sem lhe tirar o atributo, e isso mexeria em ecrãs que
   nada têm que ver com este problema. */
.ds-only-desktop[hidden],
.ds-only-mobile[hidden] { display: none !important; }

/* ===================================================================
   13. MOVIMENTO REDUZIDO (acessibilidade)
   =================================================================== */
@media (prefers-reduced-motion: reduce) {
    [class^="ds-"],
    [class*=" ds-"],
    [class^="ds-"]::before,
    [class*=" ds-"]::before,
    [class^="ds-"]::after,
    [class*=" ds-"]::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
    .ds-skeleton::after { animation: none; content: none; }
    .ds-spinner { animation: none; opacity: 0.7; }
    .ds-kpi:hover,
    .ds-btn:active,
    .ds-chip:active,
    .ds-page-btn:active,
    .ds-icon-btn:active { transform: none; }
}
