/* =========================================================================
   Design system do Agenda Beauty
   =========================================================================
   Este arquivo era um <style> de 390 linhas dentro de templates/base.html.
   Saiu de la para poder ser cacheado e para os tokens terem um lugar so.

   AS CORES DA CLINICA NAO MORAM AQUI. `--ds-primaria` e `--ds-secundaria`
   vem do banco, por tenant, e por isso continuam inline em base.html. Escrever
   uma cor fixa para elas aqui quebra o white-label.

   As decisoes por tras dos valores estao em PLANO-DESIGN.md (D1-D12).
   ========================================================================= */

:root {
    /* --- texto ------------------------------------------------------- */
    --ds-tinta:          #111827;  /* titulo, valor em destaque */
    --ds-tinta-fraca:    #374151;  /* rotulo de campo, texto de botao secundario */
    --ds-apoio:          #6b7280;  /* subtitulo, texto de ajuda */
    --ds-fraco:          #9ca3af;  /* rotulo em maiuscula, placeholder */

    /* --- superficie e traco ------------------------------------------- */
    --ds-superficie:     #ffffff;
    --ds-fundo:          #f9fafb;  /* fundo de painel dentro do cartao */
    --ds-borda:          #e5e7eb;  /* borda de cartao e regua */
    --ds-borda-campo:    #d1d5db;  /* borda de campo de formulario */
    --ds-regua:          #f3f4f6;  /* separador interno, mais leve que a borda */

    /* --- situacao (D4: nunca so a cor — o rotulo carrega o significado) */
    --ds-ok-fundo:       #dcfce7;
    --ds-ok-texto:       #166534;
    --ds-atencao-fundo:  #fef3c7;
    --ds-atencao-texto:  #92400e;
    --ds-erro-fundo:     #fee2e2;
    --ds-erro-texto:     #991b1b;
    --ds-neutro-fundo:   #f3f4f6;
    --ds-neutro-texto:   #4b5563;
    --ds-info-fundo:     #eef2ff;
    --ds-info-texto:     #4338ca;

    /* --- erro de formulario (D11) ------------------------------------- */
    --ds-erro-borda:     #dc2626;  /* borda do campo que falhou */
    --ds-erro-msg:       #b91c1c;  /* texto da mensagem sob o campo */
    --ds-erro-aviso:     #fef2f2;  /* fundo do resumo no topo */
    --ds-erro-aviso-borda: #fecaca;

    /* --- forma -------------------------------------------------------- */
    --ds-raio-campo:     8px;
    --ds-raio-cartao:    12px;
    --ds-raio-chip:      10px;

    /* --- toque (D2: 44px e o piso de qualquer alvo) -------------------- */
    --ds-toque:          44px;
    --ds-toque-chip:     36px;     /* so para fila rolavel de filtros */

    /* --- tipografia --------------------------------------------------- */
    --ds-campo:          16px;     /* D9: no celular NUNCA abaixo disto */
    --ds-campo-desktop:  14px;
    --ds-rotulo:         14px;
    --ds-ajuda:          12.5px;
    --ds-rotulo-tabela:  11.2px;   /* o ::before do cartao de tabela */

    /* --- folga -------------------------------------------------------- */
    --ds-folga-campo:    6px;      /* entre rotulo, campo, ajuda e erro */
    --ds-folga-entre:    20px;     /* entre um campo e o proximo */
}

/* No desktop o campo pode voltar a 14px: o zoom do Safari movel que motiva
   os 16px (D9) nao existe la. O piso de 44px continua valendo. */
@media (min-width: 640px) {
    :root { --ds-campo: var(--ds-campo-desktop); }
}

/* ========================================================================= */
/* Abaixo: o CSS que ja existia em base.html, movido sem alteracao.          */
/* ========================================================================= */

/* Enquanto o Alpine nao inicia, x-show ainda nao escondeu nada. Sem esta
   regra o bloco de filtros PISCA aberto no carregamento — o exato efeito que o
   colapso veio evitar. */
[x-cloak] { display: none !important; }

/* --- moldura do app (shell) ----------------------------------------------
   Substitui o `h-screen` do Tailwind em base.html.

   `h-screen` e `height: 100vh`, e no Chrome do celular 100vh e a altura com a
   barra de endereco RECOLHIDA — ou seja, maior que o visivel enquanto ela esta
   aberta. Com o `overflow-hidden` que a moldura tambem tem, o rodape ficava
   fora da tela e sem como rolar ate ele.

   O par 100vh/100dvh e o mesmo que `.modal-panel` ja usava; aqui ele estava
   faltando justamente na moldura de TODA tela autenticada. A primeira linha e
   o fallback para navegador sem dvh. */
.ds-shell {
    height: 100vh;
    height: 100dvh;
    min-height: 0;
}

/* O Chrome do celular trata dois containers rolando ao mesmo tempo
   (body + main) como “trava”: o dedo sobe, a página não volta. Só vale
   na moldura autenticada — o portal público e o login continuam com o
   body rolando. */
html:has(.ds-shell),
html:has(.ds-shell) body {
    overflow: hidden;
    height: 100%;
    height: 100dvh;
    overscroll-behavior: none;
}
html:has(.ds-shell) body.bg-gray-100.min-h-screen {
    min-height: 0;
}
.ds-shell > main {
    min-width: 0;
    min-height: 0;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-y: contain;
    scroll-padding-bottom: 40vh;
}

/* Accessibility: visible keyboard focus indicator */
*:focus-visible {
    outline: 2px solid #6366f1;
    outline-offset: 2px;
}
/* Smooth scrollbar for sidebar */
.sidebar-scroll::-webkit-scrollbar {
    width: 4px;
}
.sidebar-scroll::-webkit-scrollbar-thumb {
    background: #4b5563;
    border-radius: 2px;
}

/* Form field polish: consistent hover/focus/disabled states and a custom
   dropdown arrow so every <select> matches the rest of the design system,
   applied once here instead of per-page so all forms stay consistent. */
input[type="text"],
input[type="search"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="number"],
input[type="password"],
input[type="date"],
input[type="datetime-local"],
input[type="time"],
select,
textarea {
    transition: border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease;
}
input[type="text"]:hover:not(:disabled):not(:focus),
input[type="search"]:hover:not(:disabled):not(:focus),
input[type="email"]:hover:not(:disabled):not(:focus),
input[type="tel"]:hover:not(:disabled):not(:focus),
input[type="url"]:hover:not(:disabled):not(:focus),
input[type="number"]:hover:not(:disabled):not(:focus),
input[type="password"]:hover:not(:disabled):not(:focus),
input[type="date"]:hover:not(:disabled):not(:focus),
input[type="datetime-local"]:hover:not(:disabled):not(:focus),
input[type="time"]:hover:not(:disabled):not(:focus),
select:hover:not(:disabled):not(:focus),
textarea:hover:not(:disabled):not(:focus) {
    border-color: #9ca3af;
}
input:disabled, select:disabled, textarea:disabled {
    background-color: #f3f4f6;
    color: #9ca3af;
    cursor: not-allowed;
}
textarea {
    line-height: 1.5;
}
::placeholder {
    color: #9ca3af;
    opacity: 1;
}
/* Custom chevron for single-select dropdowns (native arrow looks inconsistent
   across browsers); multi-selects keep native rendering. */
select:not([multiple]) {
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.75rem center;
    background-size: 1.1em;
    padding-right: 2.5rem !important;
}

/* -- Responsive table -> card list (mobile-first design system component) --
   Usage: add class="responsive-table" to a <table>, and data-label="Coluna"
   to every <td> that should show its column name as a label below 640px.
   Leave data-label off a <td> (e.g. an actions cell, or an empty-state
   colspan cell) to keep it rendering as a plain block instead of a
   label/value row. Add class="card-actions" to an actions <td> to get a
   separator line above it once it drops onto its own row. */
@media (max-width: 639px) {
    table.responsive-table thead { display: none; }
    /* tfoot entra na lista junto com tbody. Sem ele o grupo ficava em
       `display: table-footer-group` com filhos ja convertidos em bloco: o
       rodape encolhia para a largura do conteudo — 190px numa tabela de
       334px — e os rotulos saiam cortados a esquerda. Aparecia no detalhe do
       pacote e vale para as outras duas telas com tfoot (relatorio de
       desempenho de vendas e o recibo). */
    table.responsive-table,
    table.responsive-table tbody,
    table.responsive-table tfoot,
    table.responsive-table tr,
    table.responsive-table td {
        display: block !important;
        width: 100%;
    }
    /* O rodape nao e um cartao — e o fechamento da lista. Ganha so o recuo
       lateral dos cartoes para os rotulos nao ficarem colados na borda. */
    table.responsive-table tfoot td {
        padding-left: 1rem !important;
        padding-right: 1rem !important;
    }
    table.responsive-table tbody tr {
        margin-bottom: 0.75rem;
        border: 1px solid #e5e7eb;
        border-radius: 0.75rem;
        padding: 0.25rem 1rem;
        background: #fff;
    }
    table.responsive-table tbody tr:last-child { margin-bottom: 0; }
    table.responsive-table td {
        padding: 0.5rem 0 !important;
        border: none !important;
    }
    /* !important: a data-label cell is meant to surface in the card even if
       it carries a "hidden md:table-cell" utility for narrower real-table
       breakpoints (640-1023px) - there's no column-width constraint once
       rows become cards, so nothing should stay hidden here. */
    table.responsive-table td[data-label]:not([data-label=""]) {
        display: flex !important;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        text-align: right;
    }
    table.responsive-table td[data-label]:not([data-label=""])::before {
        content: attr(data-label);
        font-size: 0.7rem;
        font-weight: 600;
        letter-spacing: 0.03em;
        text-transform: uppercase;
        color: #9ca3af;
        text-align: left;
        flex-shrink: 0;
    }
    table.responsive-table td.card-actions {
        border-top: 1px solid #f3f4f6 !important;
        margin-top: 0.25rem;
        padding-top: 0.75rem !important;
    }

    /* Piso tatil de 44px na celula de acoes.
       As acoes sao icones escritos como
           <a class="text-gray-500 p-1 -m-1"><i class="fas fa-eye"></i></a>
       O p-1 da 4px de folga e o -m-1 a retira de volta: a area de toque fica
       em ~24px. Sao 70 ocorrencias de py-1/py-1.5 espalhadas por 23 das 34
       telas que ja usam este componente — corrigir aqui evita edita-las uma a
       uma, e evita que a proxima tela nasca com o mesmo problema.
       A margem vai a zero de proposito: o -m-1 anularia o ganho. */
    table.responsive-table td.card-actions a,
    table.responsive-table td.card-actions button,
    table.responsive-table td.card-actions input[type="submit"] {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        margin: 0 !important;
    }

    /* Nada dentro do cartao pode se recusar a encolher.
       Com justify-content: space-between, um valor que nao quebra (e-mail
       longo, whitespace-nowrap) empurra a largura para fora da tela em vez de
       passar para a linha de baixo. */
    table.responsive-table td[data-label]:not([data-label=""]) {
        min-width: 0;
        overflow-wrap: anywhere;
        /* O valor costuma ser um NO DE TEXTO solto, nao um elemento. Dentro de
           um flex ele vira um item anonimo, e item anonimo nao aceita
           min-width: 0 — nao ha seletor que o alcance. Sem isto o e-mail longo
           era CORTADO no meio da palavra em vez de quebrar. Com wrap, quando
           nao cabe ao lado do rotulo ele desce para a linha de baixo inteiro. */
        flex-wrap: wrap;
    }
    table.responsive-table td .whitespace-nowrap,
    table.responsive-table td.whitespace-nowrap {
        white-space: normal !important;
    }

    /* truncate e max-w existem para segurar a LARGURA DA COLUNA. No modo
       cartao nao ha coluna: cada valor tem a linha inteira. Mantidos, eles
       cortam o texto sem motivo — o e-mail da cliente aparecia como
       "adrianacoimbra@gmail.c", com o resto perdido e sem nem o "...". Sao 55
       ocorrencias nas telas que usam o componente. */
    table.responsive-table td,
    table.responsive-table td * {
        max-width: none !important;
    }
    table.responsive-table td.truncate,
    table.responsive-table td .truncate {
        overflow: visible !important;
        text-overflow: clip !important;
        white-space: normal !important;
    }

    /* ── Anatomia do cartao (vinda do mockup aprovado) ──────────────────
       O modo cartao funcionava, mas rendia um cartao burocratico: TODA coluna
       virava uma linha "ROTULO ....... valor", inclusive o nome da cliente.
       Um registro ocupava ~640px e cabia 1,3 por tela.

       O mockup separa o que e IDENTIDADE do que e DADO: a primeira coluna vira
       o titulo do cartao, sem rotulo, porque "CLIENTE: Maria" e uma forma
       arrastada de escrever "Maria". O resto continua rotulo/valor. */
    table.responsive-table tbody tr {
        padding: 0.875rem 1rem;
    }
    table.responsive-table td {
        padding: 0.1875rem 0 !important;
    }
    table.responsive-table tbody td[data-label]:first-child {
        display: block !important;
        text-align: left !important;
        font-size: 1rem;
        font-weight: 600;
        color: #111827;
        padding-bottom: 0.5rem !important;
    }
    table.responsive-table tbody td[data-label]:first-child::before {
        content: none !important;
    }
    table.responsive-table td[data-label]:not([data-label=""]) {
        font-size: 0.875rem;
        color: #374151;
    }

    /* Acoes: botoes que dividem a largura, nao icones soltos.
       Tres icones nus encostados a esquerda nao se leem como acoes — leem-se
       como decoracao do ultimo dado. Com borda e largura dividida viram o que
       sao, e de quebra cada um passa a ocupar um terco da tela em vez de 24px. */
    table.responsive-table td.card-actions > div {
        display: flex;
        gap: 0.5rem;
        width: 100%;
    }
    table.responsive-table td.card-actions > div > a,
    table.responsive-table td.card-actions > div > button,
    table.responsive-table td.card-actions > div > form {
        flex: 1 1 0;
        min-width: 0;
    }
    table.responsive-table td.card-actions > div > a,
    table.responsive-table td.card-actions > div > button {
        border: 1px solid #d1d5db;
        border-radius: 0.5rem;
        background: #ffffff;
    }
    table.responsive-table td.card-actions > div > form > button {
        width: 100%;
        border: 1px solid #d1d5db;
        border-radius: 0.5rem;
        background: #ffffff;
        min-height: 44px;
    }

    /* O link que E o valor tambem e um alvo de toque.
       O piso de 44px acima cobre a celula de ACOES. Faltava o outro caso: o
       nome da cliente, o numero da venda, o codigo do lote — valores que
       linkam para o detalhe e sao, na pratica, a forma mais comum de abrir um
       registro. Mediam 24px de altura, a altura da linha de texto.
       A alternativa seria esticar a area de toque com um ::after invisivel,
       mantendo o tamanho visual. Ficaria pior: as linhas do cartao distam ~6px
       uma da outra, entao a area esticada invadiria a linha vizinha e o toque
       passaria a abrir o registro errado. Crescer de verdade e mais honesto. */
    table.responsive-table td[data-label] a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
    }
}

/* ── Acoes fixas no rodape (celular) ────────────────────────────────────
   A acao principal de uma tela — Salvar, Criar, Confirmar — ficava no fim de
   um formulario longo. No celular isso significa rolar ate o fim para agir, e
   rolar de volta para conferir. Abaixo de 640px a barra de acoes gruda no
   rodape e fica sempre ao alcance do polegar.

   Aplicacao: `class="acoes-fixas"` no container que ja agrupa os botoes. Ele
   continua sendo um bloco normal no desktop — nada muda a partir de sm.

   `env(safe-area-inset-bottom)` mantem a barra acima da faixa do iPhone; sem
   isso o botao fica por baixo do indicador de home e o toque nao chega nele.

   O espaco no fim do conteudo e dado pelo JS logo abaixo, e nao aqui: a altura
   da barra varia com o numero de botoes e com a quebra de linha do rotulo, e
   um valor fixo em CSS ora sobraria ora deixaria a barra cobrindo o ultimo
   campo do formulario. */
@media (max-width: 639px) {
    .acoes-fixas {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 40;
        display: flex;
        /* !important porque varias dessas linhas ja trazem `flex-col
           sm:flex-row`: no celular elas empilhavam os botoes, e a barra fixa
           virava um bloco de 157px comendo um quinto da tela. Mesma
           especificidade que a utilitaria do Tailwind, e a ordem de injecao do
           CDN nao e nossa para garantir. */
        flex-direction: row !important;
        flex-wrap: nowrap;
        align-items: center;
        gap: 0.5rem;
        margin: 0;
        padding: 0.75rem 1rem;
        padding-bottom: calc(0.75rem + env(safe-area-inset-bottom, 0px));
        background: #ffffff;
        border-top: 1px solid #e5e7eb;
        box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.07);
    }
    /* Cada botao divide a largura e respeita o piso tatil. */
    .acoes-fixas > a,
    .acoes-fixas > button,
    .acoes-fixas > input[type="submit"],
    .acoes-fixas > span[aria-disabled] {
        flex: 1 1 0;
        min-width: 0;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        white-space: nowrap;
        margin: 0;
    }
    /* A acao principal pesa mais que "Cancelar". */
    .acoes-fixas > .acao-primaria,
    .acoes-fixas > button[type="submit"] {
        flex: 2 1 0;
    }
    /* Indicador de envio nao e acao: ocupava uma coluna inteira da barra com
       o texto "Enviando...", mesmo parado. O proprio botao ja sinaliza o
       envio em curso. */
    .acoes-fixas > .htmx-indicator {
        display: none;
    }
}

/* ── Correcoes que dependem de :has() ───────────────────────────────────
   EM BLOCO SEPARADO, e isso nao e organizacao: um navegador sem suporte a
   :has() considera o seletor INVALIDO e descarta a REGRA INTEIRA. Se estas
   estivessem na mesma lista das regras acima, elas cairiam juntas — um
   navegador antigo perderia ate o que hoje ja funciona. Isoladas, o pior caso
   e ficar exatamente como era antes.
   Suporte: Safari 15.4+, Chrome 105+, Firefox 121+. */
@media (max-width: 639px) {
    /* O cartao nascia DENTRO de um rolador lateral.
       Em 27 das 34 telas, <div class="overflow-x-auto"> e o pai direto da
       tabela (ver clients/client_list.html e finance/sale_list.html). A linha
       vira cartao, mas o pai continua podendo rolar de lado — entao qualquer
       filho que estoure rola em vez de quebrar linha. E a razao de as telas
       "responsivas" continuarem quebrando no celular. */
    .overflow-x-auto:has(> table.responsive-table) {
        overflow-x: visible;
    }

    /* Separador da linha de acoes em quem nunca ganhou .card-actions (18 das
       34 telas). A celula de acoes e reconhecivel sem ambiguidade: e a ultima
       da linha, contem link ou botao, e NAO tem data-label — a ausencia do
       rotulo e o que a distingue de uma celula de dado, conforme documentado
       no bloco acima. */
    table.responsive-table tbody td:last-child:not([data-label]):has(a, button) {
        border-top: 1px solid #f3f4f6 !important;
        margin-top: 0.25rem;
        padding-top: 0.75rem !important;
    }
    table.responsive-table tbody td:last-child:not([data-label]):has(a, button) a,
    table.responsive-table tbody td:last-child:not([data-label]):has(a, button) button,
    table.responsive-table tbody td:last-child:not([data-label]):has(a, button) input[type="submit"] {
        min-width: 44px;
        min-height: 44px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        margin: 0 !important;
    }
}

/* ── Full-screen-on-mobile modal (design system component) ──────────────
   Usage: add class="modal-panel" to a modal's inner panel (the rounded
   white card, e.g. the div with rounded-2xl/max-w-*). Below 640px it
   expands edge-to-edge and becomes a flex column so a scrollable body
   (class="modal-body") can sit between a fixed header/footer. Reserved for
   content-heavy modals (forms, previews, option pickers) where a small
   centered dialog would clip content or force nested scrolling. Short
   confirm/delete dialogs are intentionally left as compact centered cards —
   see the audit notes for why. */
@media (max-width: 639px) {
    .modal-panel {
        width: 100% !important;
        max-width: 100% !important;
        height: 100%;
        max-height: 100vh !important;
        max-height: 100dvh !important;
        margin: 0 !important;
        border-radius: 0 !important;
        display: flex;
        flex-direction: column;
    }
    .modal-panel .modal-body {
        overflow-y: auto;
        flex: 1 1 auto;
    }
}

/* =========================================================================
   COMPONENTES
   =========================================================================
   Estas classes existem para acabar com a sopa de utilitarias: hoje o mesmo
   botao primario aparece escrito de 12 jeitos diferentes em ~115 lugares.
   Use os includes em templates/componentes/ — nao copie as classes na mao.
   ========================================================================= */

/* --- botao (Fase 2.2) ---------------------------------------------------- */
.ds-botao {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: var(--ds-toque);       /* D2: 44px e o piso */
    padding: 0 16px;
    border: 1px solid transparent;
    border-radius: var(--ds-raio-campo);
    font-size: 14px;
    font-weight: 500;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: opacity .15s ease, background-color .15s ease;
}
.ds-botao:hover { text-decoration: none; }
.ds-botao:active:not([disabled]):not([aria-disabled="true"]) {
    transform: translateY(1px);
    opacity: .92;
}
.ds-botao.is-loading,
.ds-botao[aria-busy="true"] {
    pointer-events: none;
    opacity: .7;
}

.ds-botao[disabled],
.ds-botao[aria-disabled="true"] {
    opacity: .5;
    cursor: not-allowed;
    pointer-events: none;
}

.ds-botao--primaria {
    background: var(--ds-primaria);
    color: #ffffff;
}
.ds-botao--primaria:hover { opacity: .9; color: #ffffff; }

.ds-botao--secundaria {
    background: var(--ds-superficie);
    color: var(--ds-tinta-fraca);
    border-color: var(--ds-borda-campo);
}
.ds-botao--secundaria:hover { background: var(--ds-fundo); color: var(--ds-tinta-fraca); }

.ds-botao--perigo {
    background: var(--ds-superficie);
    color: var(--ds-erro-borda);
    border-color: var(--ds-erro-aviso-borda);
}
.ds-botao--perigo:hover { background: var(--ds-erro-aviso); color: var(--ds-erro-borda); }

/* So icone: quadrado de 44px, sem padding lateral. O rotulo vai em
   aria-label — um botao sem texto acessivel e invisivel para leitor de tela. */
.ds-botao--icone {
    width: var(--ds-toque);
    padding: 0;
    gap: 0;
}

/* Ocupa a linha inteira. No celular e o normal; no desktop, raro. */
.ds-botao--bloco { width: 100%; }

/* Atalho para botoes ad-hoc que ainda nao passaram pelo include:
   mesma altura, raio e tipografia do design system, sem mudar a cor
   de cada tela. */
button.bg-primary,
a.bg-primary {
    min-height: var(--ds-toque);
    border-radius: var(--ds-raio-campo);
    font-weight: 500;
    line-height: 1.2;
}
button.bg-primary:active,
a.bg-primary:active {
    opacity: .9;
}

/* Chip de filtro: a UNICA coisa que pode ficar abaixo de 44px (D2), porque
   vem em fila rolavel com folga entre os alvos. */
.ds-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: var(--ds-toque-chip);
    padding: 0 14px;
    border: 1px solid var(--ds-borda-campo);
    border-radius: 999px;
    background: var(--ds-superficie);
    color: var(--ds-tinta-fraca);
    font-size: 13px;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
}
.ds-chip--ativo {
    border-color: var(--ds-primaria);
    background: var(--ds-info-fundo);
    color: var(--ds-info-texto);
}

/* --- campo de formulario (Fase 2.3) -------------------------------------- */
/* Ordem obrigatoria dentro do grupo (D12): rotulo -> campo -> ajuda -> erro.
   A ajuda explica como preencher; o erro diz o que deu errado. */
.ds-campo-grupo {
    display: flex;
    flex-direction: column;
    gap: var(--ds-folga-campo);
}

.ds-rotulo {
    font-size: var(--ds-rotulo);
    font-weight: 500;
    color: var(--ds-tinta-fraca);
}

/* D10: marca-se o obrigatorio, nunca o opcional. O asterisco e decorativo —
   quem carrega o significado para o leitor de tela e o aria-required no campo
   mais o texto escondido ao lado. */
.ds-obrigatorio { color: var(--ds-erro-borda); }

.ds-so-leitor {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* D9: --ds-campo vale 16px no celular. Abaixo disso o Safari do iPhone da
   zoom sozinho ao focar o campo, e o zoom quebra a regra de nao rolar de lado
   (D3) no exato momento de digitar. No desktop o token volta a 14px. */
.ds-campo {
    width: 100%;
    min-height: var(--ds-toque);
    padding: 10px 12px;
    border: 1px solid var(--ds-borda-campo);
    border-radius: var(--ds-raio-campo);
    font-size: var(--ds-campo);
    font-family: inherit;
    line-height: 1.4;
    color: var(--ds-tinta);
    background-color: var(--ds-superficie);
}

.ds-campo--erro { border-color: var(--ds-erro-borda); }

textarea.ds-campo {
    min-height: 88px;
    resize: vertical;
}

.ds-ajuda {
    font-size: var(--ds-ajuda);
    color: var(--ds-apoio);
}

.ds-erro {
    display: flex;
    align-items: flex-start;
    gap: 5px;
    font-size: var(--ds-ajuda);
    color: var(--ds-erro-msg);
}
.ds-erro > i { margin-top: .15em; flex-shrink: 0; }

/* D11: erro em dois lugares. Este e o resumo no topo do formulario, para quem
   enviou e nao sabe onde olhar. Ele NOMEIA os campos — "corrija os erros
   abaixo" nao ajuda ninguem num formulario de 14 campos. */
.ds-resumo-erro {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    padding: 11px 13px;
    border: 1px solid var(--ds-erro-aviso-borda);
    border-radius: var(--ds-raio-chip);
    background: var(--ds-erro-aviso);
}
.ds-resumo-erro__titulo {
    font-size: 13.5px;
    font-weight: 600;
    color: var(--ds-erro-texto);
}
.ds-resumo-erro__lista {
    margin: 3px 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--ds-ajuda);
    color: var(--ds-erro-msg);
}

/* Secao de formulario: uma coluna no celular, duas no desktop. Nunca tres. */
.ds-secao-titulo {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: var(--ds-rotulo-tabela);
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
    color: var(--ds-fraco);
}
.ds-secao-titulo::after {
    content: "";
    flex-grow: 1;
    height: 1px;
    background: var(--ds-borda);
}

.ds-grade {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--ds-folga-entre);
}
@media (min-width: 768px) {
    .ds-grade { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ds-grade > .ds-campo-grupo--largo { grid-column: 1 / -1; }
}

/* --- situacao (Fase 2.4) -------------------------------------------------- */
/* D4: NUNCA so a cor. O rotulo carrega o significado, para quem nao distingue
   as cores. Hoje o sistema tem 10+ pares de cor inconsistentes para as mesmas
   quatro situacoes (green-100/700 vs green-100/800, gray-100/600 vs /700...). */
.ds-situacao {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex-shrink: 0;              /* nunca encolhe: quem trunca e o nome ao lado */
    padding: 3px 9px;
    border-radius: var(--ds-raio-chip);
    font-size: 11px;
    font-weight: 600;
    white-space: nowrap;
}
.ds-situacao--ok      { background: var(--ds-ok-fundo);      color: var(--ds-ok-texto); }
.ds-situacao--atencao { background: var(--ds-atencao-fundo); color: var(--ds-atencao-texto); }
.ds-situacao--erro    { background: var(--ds-erro-fundo);    color: var(--ds-erro-texto); }
.ds-situacao--neutro  { background: var(--ds-neutro-fundo);  color: var(--ds-neutro-texto); }
.ds-situacao--info    { background: var(--ds-info-fundo);    color: var(--ds-info-texto); }

/* --- estado vazio (Fase 2.5) ---------------------------------------------- */
/* Diz o que FAZER, nao so que esta vazio. Sao 154 telas com um "Nenhum..."
   improvisado, cada uma com sua diagramacao. */
.ds-vazio {
    padding: 24px 16px;
    border: 1px dashed var(--ds-borda-campo);
    border-radius: var(--ds-raio-cartao);
    background: var(--ds-superficie);
    text-align: center;
}
.ds-vazio__icone {
    width: var(--ds-toque);
    height: var(--ds-toque);
    margin: 0 auto 10px;
    border-radius: 50%;
    background: var(--ds-info-fundo);
    color: var(--ds-info-texto);
    display: flex;
    align-items: center;
    justify-content: center;
}
.ds-vazio__titulo { font-size: 15px; font-weight: 600; color: var(--ds-tinta); }
.ds-vazio__texto  { margin-top: 4px; font-size: 13px; color: var(--ds-apoio); }
.ds-vazio__acao   { margin-top: 14px; display: inline-flex; }

/* --- cabecalho de pagina (Fase 2.6) --------------------------------------- */
.ds-cabecalho {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 24px;
}
.ds-cabecalho__texto { flex-grow: 1; min-width: 0; }
.ds-cabecalho__titulo {
    font-size: 20px;
    font-weight: 600;
    color: var(--ds-tinta);
    text-wrap: pretty;
}
.ds-cabecalho__linha { margin-top: 2px; font-size: 13.5px; color: var(--ds-apoio); }
.ds-cabecalho__acoes { display: flex; gap: 8px; flex-shrink: 0; }
/* No celular a acao primaria ocupa a linha inteira em vez de espremer o titulo. */
@media (max-width: 639px) {
    .ds-cabecalho__acoes { width: 100%; }
    .ds-cabecalho__acoes > * { flex-grow: 1; }
}

/* --- paginacao (Fase 2.7) ------------------------------------------------- */
/* Alvos de 44px (D2). Nada de setas minusculas. */
.ds-paginacao {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 16px;
}
.ds-paginacao__posicao {
    font-size: 13px;
    color: var(--ds-apoio);
    text-align: center;
    font-variant-numeric: tabular-nums;
}
/* Ocupa o lugar do botao ausente na primeira e na ultima pagina, para que o
   "Pagina 2 de 4" nao pule de lado ao navegar. */
.ds-paginacao__espaco { min-width: 44px; }

/* --- esqueleto do formulario (Fase 3.3) ---------------------------------- */
.ds-formulario {
    display: flex;
    flex-direction: column;
    gap: 28px;
}
.ds-formulario__secao {
    display: flex;
    flex-direction: column;
    gap: 16px;
}
/* Rodape de acoes. No celular a classe `acoes-fixas` (ja existente) prende a
   barra no fim da tela; aqui fica so o alinhamento do desktop. */
.ds-formulario__acoes {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding-top: 4px;
}

/* A utilitaria `hidden` do Tailwind vem de um <style> injetado em tempo de
   execucao pelo CDN, entao ela normalmente ganha de .ds-campo-grupo por ordem
   de fonte. "Normalmente" nao serve para esconder campo: esta regra torna o
   resultado independente da ordem de carga. */
.ds-campo-grupo.hidden { display: none; }


/* --- secao sanfonada de formulario longo --------------------------------
   Medido em 393px (Pixel 5), antes desta regra existir:

       Configuracoes da Clinica   3727px = 5,1 telas · 43 campos · 7 secoes
       Configuracoes do Sistema   3379px = 4,6 telas · 23 campos · 8 secoes

   Chegar em "Backup" custava rolar por Agendamentos, Calendario, Notificacoes,
   WhatsApp, Financeiro e Estoque. No computador as secoes se distribuem em
   colunas e o custo nao aparece — foi por isso que passou.

   No celular cada secao vira uma linha tocavel; a partir de 640px todas se
   abrem e a tela volta a ser exatamente o que era.

   POR QUE <details> E NAO x-show
   -------------------------------
   Campo `required` dentro de bloco com `display:none` faz o Chrome abortar o
   envio com "An invalid form control is not focusable" NO CONSOLE — a tela nao
   reage e nada explica por que. Com <details>, o proprio navegador abre a
   secao para mostrar a mensagem de validacao (comportamento padronizado no
   HTML e disponivel no Chrome desde a 130). Sobra ainda o teclado, o leitor de
   tela e o "buscar na pagina", que <details> resolve de graca e uma div com
   x-show nao resolve.

   A regra de 640px usa !important de proposito: ela precisa vencer o
   `details:not([open]) > *:not(summary) { display: none }` da folha do
   navegador, que tem a mesma especificidade em jogo aqui. Sem isso, uma secao
   fechada no celular continuaria fechada ao girar para paisagem.
   ------------------------------------------------------------------------ */
.ds-secao > summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-height: var(--ds-toque);      /* D2: 44px e o piso */
    padding: 12px 20px;
    cursor: pointer;
    font-weight: 600;
    color: var(--ds-tinta);
    list-style: none;                 /* tira o triangulo padrao */
}
.ds-secao > summary::-webkit-details-marker { display: none; }

.ds-secao > summary .ds-secao__seta {
    color: var(--ds-fraco);
    transition: transform .15s ease;
    flex-shrink: 0;
}
.ds-secao[open] > summary .ds-secao__seta { transform: rotate(180deg); }

/* Separador entre o titulo e o corpo, so quando aberta: fechada, a linha
   pareceria uma secao vazia. */
.ds-secao[open] > summary { border-bottom: 1px solid var(--ds-regua); }

@media (min-width: 640px) {
    /* No computador nao ha sanfona: o cabecalho proprio da secao (<h3>) volta
       a ser o titulo, e o corpo fica sempre visivel. */
    .ds-secao > summary { display: none; }
    .ds-secao > .ds-secao__corpo { display: block !important; }
}

/* --- filtro de periodo (datas inicio/fim) -------------------------------- */
.ds-filtro-datas {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
    align-items: end;
}
@media (min-width: 640px) {
    .ds-filtro-datas {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
        gap: 16px;
    }
}
.ds-filtro-datas .ds-campo,
.ds-filtro-datas input[type="date"] {
    width: 100%;
    min-height: var(--ds-toque);
    min-width: 0;
}
.ds-filtro-datas .ds-filtro-datas__acoes {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    align-items: center;
}

/* Campos nunca estouram a tela no Chrome Mobile. */
input, select, textarea {
    max-width: 100%;
}
.ds-campo-grupo, .ds-grade {
    min-width: 0;
}
