/* ============================================================================
   Expense Manager — Design System
   ----------------------------------------------------------------------------
   Fonte única da linguagem visual. Carregado pelo base.html em toda tela.
   CSS de página vem DEPOIS deste arquivo, então sobrescreve sem !important.

   Ordem: 1) tokens  2) reset/base  3) layout  4) componentes  5) utilitários
          6) mobile  7) desktop

   BREAKPOINTS (os únicos três; não invente outro em página):
       celular   <= 640px    1 coluna, tabela empilhada, campo 16px
       tablet    641–900px   grade fluida, tabela ainda empilhada
       desktop   >= 901px    grade cheia, tabela como tabela

   O 900/901 não é chute: é o ponto que a tela de busca já usa em produção
   pra trocar tabela por cartão. Todo mundo usa o mesmo.
   ========================================================================= */

/* ============================================================================
   1. TOKENS
   ========================================================================= */
:root {
    /* ---- Cor: marca ---- */
    --c-primary:        #6200ea;
    --c-primary-dark:   #4b00b5;
    --c-primary-soft:   #f3eeff;   /* fundo de destaque/tint */
    --c-primary-line:   #e6def9;   /* borda sobre o tint */

    /* ---- Cor: superfícies ---- */
    --c-bg:             #f4f6fb;
    --c-surface:        #ffffff;
    --c-surface-alt:    #fafbfc;   /* cabeçalho de tabela, linha zebrada */

    /* ---- Cor: texto ---- */
    --c-text:           #1f2937;   /* primário */
    --c-text-2:         #4b5563;   /* secundário */
    --c-muted:          #6b7280;   /* meta, rótulos */
    --c-faint:          #9ca3af;   /* placeholder, desabilitado */

    /* ---- Cor: bordas ---- */
    --c-border:         #e6eaf1;   /* hairline padrão */
    --c-border-strong:  #dbe2ea;   /* campo de formulário, divisor forte */

    /* ---- Cor: semântica (status de orçamento e valores) ---- */
    --c-success:        #137333;
    --c-success-bg:     #e6f4ea;
    --c-success-line:   #cfe7d7;

    --c-warning:        #b45309;
    --c-warning-bg:     #fff4e5;
    --c-warning-line:   #f4dfc2;
    --c-warning-bright: #f5b700;   /* âmbar vivo pra barra de gráfico; --c-warning é pra texto */

    --c-danger:         #c5221f;
    --c-danger-bg:      #fce8e6;
    --c-danger-line:    #f5cfcc;

    /* ---- Tipografia ---- */
    --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    --font-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

    /* Escala fechada. Só estes tamanhos — nada de px avulso em página. */
    --fs-title:  22px;   /* h1 da tela */
    --fs-metric: 26px;   /* número grande do card de métrica */
    --fs-section:16px;   /* h2/h3, título de bloco */
    --fs-body:   14px;   /* corpo, campos, botões */
    --fs-meta:   13px;   /* metadados, legendas */
    --fs-label:  11px;   /* rótulo caixa-alta de campo/métrica */

    /* Só 2 pesos. Hierarquia vem de tamanho e cor, não de peso. */
    --fw-normal: 400;
    --fw-medium: 500;

    --lh-tight: 1.25;
    --lh-body:  1.5;

    /* ---- Espaçamento ---- */
    --sp-1: 4px;
    --sp-2: 8px;
    --sp-3: 12px;
    --sp-4: 16px;
    --sp-5: 20px;
    --sp-6: 24px;
    --sp-8: 32px;

    /* ---- Raio ---- */
    --r-sm:   8px;
    --r-md:   12px;   /* card */
    --r-lg:   16px;
    --r-pill: 999px;

    /* ---- Sombra: discreta. A borda hairline é que define o card. ---- */
    --shadow-sm: 0 1px 2px rgba(31, 41, 55, 0.04);
    --shadow-md: 0 4px 14px rgba(31, 41, 55, 0.06);
    --shadow-lg: 0 10px 28px rgba(31, 41, 55, 0.10);

    /* ---- Alturas de controle (44px = alvo de toque mínimo) ---- */
    --h-control: 44px;
    --h-control-sm: 36px;

    /* ---- Layout ---- */
    --page-max: 980px;
    --page-pad: var(--sp-5);
}

/* ============================================================================
   2. RESET / BASE
   ========================================================================= */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
    margin: 0;
    font-family: var(--font);
    font-size: var(--fs-body);
    font-weight: var(--fw-normal);
    line-height: var(--lh-body);
    color: var(--c-text);
    background: var(--c-bg);
    min-height: 100vh;
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; font-weight: var(--fw-medium); line-height: var(--lh-tight); }
h1 { font-size: var(--fs-title); }
h2, h3, h4 { font-size: var(--fs-section); }
p { margin: 0; }

a { color: var(--c-primary); }

/* Foco visível e consistente — acessibilidade e navegação por teclado. */
:focus-visible {
    outline: 2px solid var(--c-primary);
    outline-offset: 2px;
    border-radius: var(--r-sm);
}

/* ============================================================================
   3. LAYOUT
   ========================================================================= */
.ds-page {
    max-width: var(--page-max);
    margin: 0 auto;
    padding: var(--sp-6) var(--page-pad) var(--sp-8);
}
.ds-page.is-wide { max-width: 1300px; }
/* Formulário de duas colunas: mais estreito que a página padrão, largo demais
   pro is-narrow (a 520px o grid de campos volta pra uma coluna só). Nasceu
   como um max-width solto dentro de expenses/add.html — entrou na escala pra
   que a largura continue vindo do page_class, e só dele. */
.ds-page.is-form { max-width: 880px; }
.ds-page.is-narrow { max-width: 520px; }
/* is-full: usa a largura da janela. Só a partir do desktop — no celular a
   página não tem largura sobrando e o container volta ao padrão. A regra
   vive na seção de desktop, junto do ds-pivot que é quem pede por ela. */

/* ---- Topbar: marca + ações ---- */
.ds-topbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-4);
    margin-bottom: var(--sp-6);
}
.ds-brand { display: flex; align-items: center; gap: var(--sp-3); min-width: 0; text-decoration: none; color: inherit; }
.ds-brand-logo { width: 40px; height: 40px; border-radius: var(--r-md); flex-shrink: 0; }
.ds-brand-name {
    font-size: var(--fs-section); font-weight: var(--fw-medium);
    color: var(--c-primary); letter-spacing: -0.01em;
}
.ds-brand-sub {
    font-size: var(--fs-meta); color: var(--c-muted);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-topbar-actions { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }

/* ---- Cabeçalho da tela: título + subtítulo + voltar ---- */
.ds-header { margin-bottom: var(--sp-5); }
.ds-back {
    display: inline-flex; align-items: center; gap: var(--sp-1);
    font-size: var(--fs-meta); color: var(--c-muted);
    text-decoration: none; margin-bottom: var(--sp-2);
    min-height: 32px;
}
.ds-back:hover { color: var(--c-primary); }
/* Título da tela em ROXO, não na tinta do texto. Era o cabeçalho do manual
   e do estorno — telas que ficaram fora do padrão por acidente de porte — e
   o Andre preferiu esse: o h1 vira ponto de entrada da leitura em vez de
   mais um bloco de texto escuro. Contraste sobre o branco: 7,55:1, acima do
   7:1 exigido pelo AAA até em texto normal. */
.ds-header h1 { color: var(--c-primary); letter-spacing: -0.02em; }
.ds-header .ds-subtitle { margin-top: var(--sp-1); }
.ds-subtitle { font-size: var(--fs-meta); color: var(--c-muted); }

/* ---- Barra de ações ---- */
.ds-actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-5); }

/* ---- Seção ---- */
.ds-section { margin-bottom: var(--sp-6); }
.ds-section-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3); margin-bottom: var(--sp-3); flex-wrap: wrap;
}
.ds-section-title {
    font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--c-muted);
    text-transform: uppercase; letter-spacing: 0.06em;
}

/* ============================================================================
   4. COMPONENTES
   ========================================================================= */

/* ---------------------------------------------------------------- 4.1 Card */
.ds-card {
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-5);
}
.ds-card + .ds-card { margin-top: var(--sp-3); }
.ds-card.is-flush { padding: 0; overflow: hidden; }   /* card que embrulha lista/tabela */
.ds-card.is-tint { background: var(--c-primary-soft); border-color: var(--c-primary-line); }

.ds-card-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3); margin-bottom: var(--sp-3);
}
.ds-card-title { font-size: var(--fs-section); font-weight: var(--fw-medium); }

/* ------------------------------------------------- 4.2 Card de métrica/KPI */
.ds-metric-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: var(--sp-3);
}

.ds-metric {
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-sm);
    padding: var(--sp-4);
    min-width: 0;
}
.ds-metric-label {
    display: block;
    font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--c-muted);
    text-transform: uppercase; letter-spacing: 0.06em;
    margin-bottom: var(--sp-2);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ds-metric-value {
    display: block;
    font-size: var(--fs-metric); font-weight: var(--fw-medium);
    line-height: var(--lh-tight); letter-spacing: -0.02em;
    /* Números alinhados verticalmente entre cards e linhas de lista. */
    font-variant-numeric: tabular-nums;
}
.ds-metric-value.is-success { color: var(--c-success); }
.ds-metric-value.is-warning { color: var(--c-warning); }
.ds-metric-value.is-danger  { color: var(--c-danger); }
.ds-metric-sub { display: block; font-size: var(--fs-meta); color: var(--c-muted); margin-top: var(--sp-1); }

/* --------------------------------------------- 4.3 Barra de progresso */
.ds-progress {
    height: 6px; border-radius: var(--r-pill);
    background: #eef1f6; overflow: hidden;
}
.ds-progress-fill {
    height: 100%; border-radius: var(--r-pill);
    background: var(--c-primary);
    transition: width 0.3s ease;
}
/* Estado semântico do orçamento: ok < 80% <= alerta <= 100% < estourou */
.ds-progress-fill.is-success { background: var(--c-success); }
.ds-progress-fill.is-warning { background: var(--c-warning); }
.ds-progress-fill.is-danger  { background: var(--c-danger); }

.ds-progress-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--sp-3); margin-bottom: var(--sp-2);
    font-size: var(--fs-meta);
}
.ds-progress-head strong { font-weight: var(--fw-medium); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------- 4.3b Grade responsiva
   Utilitário de coluna. Uma classe em vez de um grid-template-columns
   diferente em cada tela. */
.ds-grid { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
.ds-grid.is-2 { grid-template-columns: repeat(2, 1fr); }
.ds-grid.is-3 { grid-template-columns: repeat(3, 1fr); }

/* --------------------------------------------------- 4.4 Linha de lista
   Substitui tabela densa: ícone + título + metadados abaixo + valor à direita.
   Funciona como <a>, <li> ou <div>. */
.ds-list {
    list-style: none; margin: 0; padding: 0;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}
.ds-list.is-plain { background: none; border: none; box-shadow: none; border-radius: 0; overflow: visible; }

.ds-row {
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    border-bottom: 1px solid var(--c-border);
    text-decoration: none; color: inherit;
    min-height: var(--h-control);
}
.ds-list > .ds-row:last-child, .ds-row:last-child { border-bottom: none; }
a.ds-row:hover, .ds-row.is-clickable:hover { background: var(--c-surface-alt); }

/* Ícone: quadrado tintado à esquerda. */
.ds-row-icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--c-primary-soft); color: var(--c-primary);
}
.ds-row-icon.is-muted { background: var(--c-surface-alt); color: var(--c-faint); }
.ds-row-icon.is-success { background: var(--c-success-bg); color: var(--c-success); }
.ds-row-icon.is-warning { background: var(--c-warning-bg); color: var(--c-warning); }
.ds-row-icon.is-danger  { background: var(--c-danger-bg);  color: var(--c-danger); }

/* Corpo: título em destaque, metadados abaixo. min-width:0 = deixa truncar. */
.ds-row-body { flex: 1 1 auto; min-width: 0; }
.ds-row-title {
    display: block; font-size: var(--fs-body); font-weight: var(--fw-medium);
    color: var(--c-text); line-height: var(--lh-tight);
    overflow-wrap: anywhere;
}
.ds-row-meta {
    display: block; font-size: var(--fs-meta); color: var(--c-muted);
    margin-top: 2px;
}
/* Separador "·" automático entre metadados. */
.ds-row-meta > * + *::before { content: ' · '; color: var(--c-faint); }

/* Valor à direita. */
.ds-row-value {
    flex-shrink: 0; text-align: right;
    font-size: var(--fs-body); font-weight: var(--fw-medium);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.ds-row-value.is-success { color: var(--c-success); }
.ds-row-value.is-danger  { color: var(--c-danger); }
.ds-row-value small { display: block; font-size: var(--fs-meta); font-weight: var(--fw-normal); color: var(--c-muted); }

/* .ds-row é display:flex, o que empata em especificidade com o [hidden] do
   navegador e ganha dele por ser regra de autor — sem isto, esconder uma
   linha pelo atributo hidden simplesmente não funciona. */
.ds-row[hidden] { display: none; }

.ds-row-caret { flex-shrink: 0; color: var(--c-faint); display: inline-flex; }
/* Pill dentro da linha não encolhe: quem cede espaço é o corpo (min-width:0). */
.ds-row > .ds-badge { flex-shrink: 0; }
.ds-row-actions { flex-shrink: 0; display: flex; align-items: center; gap: var(--sp-2); }

/* ------------------------------------------------------------- 4.5 Botões */
.ds-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
    min-height: var(--h-control); padding: 0 var(--sp-4);
    border: 1px solid transparent; border-radius: var(--r-sm);
    font-family: inherit; font-size: var(--fs-body); font-weight: var(--fw-medium);
    line-height: 1; text-decoration: none; cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
    white-space: nowrap;
}
.ds-btn-primary { background: var(--c-primary); color: #fff; }
.ds-btn-primary:hover { background: var(--c-primary-dark); }

.ds-btn-secondary { background: var(--c-surface); color: var(--c-primary); border-color: var(--c-border-strong); }
.ds-btn-secondary:hover { background: var(--c-primary-soft); border-color: var(--c-primary-line); }

.ds-btn-ghost { background: transparent; color: var(--c-muted); }
.ds-btn-ghost:hover { background: var(--c-surface-alt); color: var(--c-text); }

.ds-btn-danger { background: var(--c-danger-bg); color: var(--c-danger); }
.ds-btn-danger:hover { background: #f9d9d7; }

.ds-btn-success { background: var(--c-success-bg); color: var(--c-success); }
.ds-btn-success:hover { background: #d7ecdd; }

.ds-btn:disabled, .ds-btn.is-disabled { opacity: 0.5; pointer-events: none; }
.ds-btn.is-sm { min-height: var(--h-control-sm); padding: 0 var(--sp-3); font-size: var(--fs-meta); }
.ds-btn.is-block { width: 100%; }

/* Botão-ícone: quadrado, 44px, sem rótulo. */
.ds-btn-icon {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--h-control); height: var(--h-control); padding: 0;
    border: 1px solid var(--c-border); border-radius: var(--r-sm);
    background: var(--c-surface); color: var(--c-primary);
    cursor: pointer; text-decoration: none;
    transition: background 0.15s ease;
}
.ds-btn-icon:hover { background: var(--c-primary-soft); }
.ds-btn-icon.is-sm { width: var(--h-control-sm); height: var(--h-control-sm); }

/* Contador sobre o botão-ícone (sino de avisos). */
.ds-btn-icon .ds-count {
    position: absolute; top: -5px; right: -5px;
    min-width: 18px; height: 18px; padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--c-danger); color: #fff;
    font-size: 10px; font-weight: var(--fw-medium);
    display: inline-flex; align-items: center; justify-content: center;
}

/* -------------------------------------------------- 4.6 Badge / pill */
.ds-badge {
    display: inline-flex; align-items: center; gap: var(--sp-1);
    padding: 3px var(--sp-2);
    border-radius: var(--r-pill);
    font-size: var(--fs-label); font-weight: var(--fw-medium);
    letter-spacing: 0.03em; text-transform: uppercase;
    white-space: nowrap;
    background: var(--c-surface-alt); color: var(--c-muted);
}
.ds-badge.is-success { background: var(--c-success-bg); color: var(--c-success); }
.ds-badge.is-warning { background: var(--c-warning-bg); color: var(--c-warning); }
.ds-badge.is-danger  { background: var(--c-danger-bg);  color: var(--c-danger); }
.ds-badge.is-primary { background: var(--c-primary-soft); color: var(--c-primary); }

/* --------------------------------------------- 4.7 Campos de formulário */
.ds-field { margin-bottom: var(--sp-4); }
.ds-field-full { grid-column: 1 / -1; }
/* Teto de largura por tipo de dado, SÓ NO DESKTOP (a regra mora na seção de
   641px+). A página usa a largura padrão, como as outras telas — mas um
   select de ano não precisa de 900px pra caber "2026". No celular não há
   largura sobrando: lá o campo continua ocupando a linha inteira, que é o
   padrão do app. Ver .ds-field.is-sm / .is-md mais abaixo. */

.ds-label {
    display: block;
    font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--c-muted);
    text-transform: uppercase; letter-spacing: 0.06em;
    margin-bottom: var(--sp-1);
}

.ds-input, .ds-select, .ds-textarea {
    width: 100%;
    min-height: var(--h-control);
    padding: 0 var(--sp-3);
    border: 1px solid var(--c-border-strong);
    border-radius: var(--r-sm);
    background: var(--c-surface);
    color: var(--c-text);
    font-family: inherit; font-size: var(--fs-body); font-weight: var(--fw-normal);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.ds-textarea { padding: var(--sp-3); line-height: var(--lh-body); resize: vertical; }
.ds-input::placeholder, .ds-textarea::placeholder { color: var(--c-faint); }

.ds-input:focus, .ds-select:focus, .ds-textarea:focus {
    outline: none;
    border-color: var(--c-primary);
    box-shadow: 0 0 0 3px rgba(98, 0, 234, 0.12);
}
.ds-input:disabled, .ds-select:disabled { background: var(--c-surface-alt); color: var(--c-faint); }

.ds-hint { display: block; font-size: var(--fs-meta); color: var(--c-muted); margin-top: var(--sp-1); }
.ds-field.has-error .ds-input, .ds-field.has-error .ds-select { border-color: var(--c-danger); }
.ds-error { display: block; font-size: var(--fs-meta); color: var(--c-danger); margin-top: var(--sp-1); }

.ds-form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--sp-4);
}
/* Dentro da grade quem espaça é o gap. Sem isto o campo soma margem ao gap
   e a grade fica com o dobro do respiro entre linhas. */
.ds-form-grid > .ds-field { margin-bottom: 0; }

/* Checkbox / radio com alvo de toque de 44px. */
.ds-check {
    display: flex; align-items: center; gap: var(--sp-2);
    min-height: var(--h-control); cursor: pointer;
    font-size: var(--fs-body);
}
.ds-check input { width: 20px; height: 20px; margin: 0; accent-color: var(--c-primary); flex-shrink: 0; cursor: pointer; }

/* ------------------------------------------------------ 4.8 Flash / alerta */
.ds-flash {
    display: flex; align-items: flex-start; gap: var(--sp-2);
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--c-border);
    border-radius: var(--r-sm);
    font-size: var(--fs-body);
    margin-bottom: var(--sp-3);
    background: var(--c-surface); color: var(--c-text);
}
.ds-flash-success { background: var(--c-success-bg); border-color: var(--c-success-line); color: var(--c-success); }
.ds-flash-warning { background: var(--c-warning-bg); border-color: var(--c-warning-line); color: var(--c-warning); }
.ds-flash-danger,
.ds-flash-error   { background: var(--c-danger-bg);  border-color: var(--c-danger-line);  color: var(--c-danger); }
.ds-flash-info    { background: var(--c-primary-soft); border-color: var(--c-primary-line); color: var(--c-primary-dark); }
.ds-flash a { color: inherit; font-weight: var(--fw-medium); }

/* --------------------------------------------------------- 4.9 Estado vazio */
.ds-empty {
    padding: var(--sp-8) var(--sp-5);
    text-align: center;
    background: var(--c-surface);
    border: 1px solid var(--c-border);
    border-radius: var(--r-md);
    color: var(--c-muted);
}
.ds-empty-icon { color: var(--c-faint); margin-bottom: var(--sp-3); }
/* .ds-icon é display:block com largura fixa: sem isto não centraliza. */
.ds-empty-icon svg { margin: 0 auto; }
.ds-empty-title { font-size: var(--fs-section); font-weight: var(--fw-medium); color: var(--c-text); margin-bottom: var(--sp-1); }

/* ------------------------------------------------------------- 4.10 Tabela
   Para onde a grade realmente ajuda (comparar colunas no desktop).
   No celular a preferência é .ds-list. */
.ds-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ds-table {
    width: 100%;
    border-collapse: separate; border-spacing: 0;
    background: var(--c-surface);
    font-size: var(--fs-body);
}
.ds-table th, .ds-table td {
    padding: var(--sp-3) var(--sp-4);
    text-align: left;
    border-bottom: 1px solid var(--c-border);
}
.ds-table th {
    background: var(--c-surface-alt);
    font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--c-muted);
    text-transform: uppercase; letter-spacing: 0.06em;
    white-space: nowrap;
}
.ds-table tbody tr:last-child td { border-bottom: none; }
.ds-table .is-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------- 4.10b Pivô (ds-pivot)
   Tabela larga de rótulo × 12 meses + colunas de fechamento (média, total).
   Usada pelo relatório anual de despesas e pelo de receitas.

   Aqui a tabela é a forma certa e não vira lista: o valor está justamente
   em comparar linha a linha mês a mês, que é o que empilhar destrói. O que
   resolve a leitura no celular é a primeira coluna ficar fixa — sem ela se
   perde a linha ao arrastar.

   Marcação esperada:
       <div class="ds-pivot-wrap">
         <table class="ds-pivot">
           <thead><tr>
             <th>Rótulo</th>
             <th class="col-mes">Jan</th> ... (12x)
             <th class="col-calc col-mean">Média</th>
             <th class="col-calc col-total">Total</th>
           </tr></thead>

   A largura da primeira coluna no desktop sai de --pivot-rotulo-min, que a
   tela ajusta conforme o conteúdo dela (código curto x texto livre).
   ========================================================================= */
.ds-pivot-wrap {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    border: 1px solid var(--c-border);
    border-radius: var(--r-md);
    background: var(--c-surface);
}
.ds-pivot {
    width: 100%; border-collapse: separate; border-spacing: 0;
    font-size: var(--fs-meta);
}
.ds-pivot th, .ds-pivot td {
    padding: var(--sp-2) var(--sp-3); text-align: right;
    white-space: nowrap; border-bottom: 1px solid var(--c-border);
    font-variant-numeric: tabular-nums;
}
.ds-pivot th {
    background: var(--c-surface-alt); color: var(--c-muted);
    font-size: var(--fs-label); font-weight: var(--fw-medium);
    text-transform: uppercase; letter-spacing: 0.06em;
}
.ds-pivot tbody tr:last-child td { border-bottom: none; }

/* Coluna fixa: a de rótulo. z-index acima das células que passam por baixo. */
.ds-pivot th:first-child, .ds-pivot td:first-child {
    position: sticky; left: 0; z-index: 1;
    text-align: left; white-space: normal; min-width: 150px;
    background: var(--c-surface);
    border-right: 1px solid var(--c-border);
}
.ds-pivot th:first-child { background: var(--c-surface-alt); z-index: 2; }

/* ---- Desktop: o ano inteiro sem rolar ----
   Presa na largura padrão da página, a tabela deixava as últimas colunas
   fora da área visível. Aqui a página usa a largura da janela e as colunas
   numéricas encolhem até o tamanho do número. */
@media (min-width: 901px) {
    .ds-page.is-full { max-width: none; }

    .ds-pivot th, .ds-pivot td { padding: var(--sp-2) var(--sp-2); }

    /* width:1% + nowrap = a coluna encolhe até o tamanho do conteúdo.
       A sobra toda vai pra coluna de rótulo, que é a de texto. */
    .ds-pivot th.col-mes, .ds-pivot td.col-mes,
    .ds-pivot th.col-calc, .ds-pivot td.col-calc { width: 1%; }
    .ds-pivot th:first-child, .ds-pivot td:first-child {
        width: auto; min-width: var(--pivot-rotulo-min, 180px);
    }

    /* As colunas de fechamento (média, total) separadas dos doze meses por
       fundo leve e um divisor. O divisor fica só na PRIMEIRA delas: a regra
       do irmão adjacente tira das seguintes, então serve tanto pra tela com
       média + total quanto pra tela só com total. */
    .ds-pivot .col-calc { background: var(--c-surface-alt); font-weight: var(--fw-medium); }
    .ds-pivot th.col-calc, .ds-pivot td.col-calc { border-left: 1px solid var(--c-border-strong); }
    .ds-pivot .col-calc + .col-calc { border-left: none; }

    /* O rótulo é a âncora da linha. */
    .ds-pivot td:first-child { font-weight: var(--fw-medium); color: var(--c-text); }
}

/* ------------------------------------------- 4.10c Marcar conta como paga
   Usado no menu e na tela de contas a pagar. O comportamento mora em
   static/js/marcar-pago.js; aqui fica só a aparência. */

/* Dia do vencimento como pastilha: é o dado que o olho procura primeiro ao
   varrer a lista. */
.ds-venc {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 36px; height: 36px; flex-shrink: 0;
    border-radius: var(--r-sm);
    background: var(--c-primary-soft); color: var(--c-primary);
    font-weight: var(--fw-medium); font-variant-numeric: tabular-nums;
}

/* 44px de alvo de toque, à direita do valor. */
.ds-btn-pagar {
    flex-shrink: 0; width: var(--h-control); height: var(--h-control);
    display: inline-flex; align-items: center; justify-content: center;
    border: 1px solid var(--c-border-strong); border-radius: var(--r-sm);
    background: var(--c-surface); color: var(--c-success);
    cursor: pointer; transition: background .15s ease, border-color .15s ease;
}
.ds-btn-pagar:hover { background: var(--c-success-bg); border-color: var(--c-success-line); }
.ds-btn-pagar[disabled] { opacity: .45; cursor: progress; }

/* A linha da conta tem SEMPRE as mesmas quatro colunas:
       [dia] [descrição + categoria·meio] [valor] [botão]
   Antes era flex com wrap, posto ali pro aviso de erro ocupar uma linha
   inteira. Só que wrap não escolhe o que desce: com descrição longa e
   valor largo quem caía pra linha de baixo era o BOTÃO, desalinhado das
   outras contas. Em grid a coluna do botão existe sempre e quem cede
   espaço é a descrição — que é o que pode ceder. */
[data-marcar-pago] .ds-row[data-id] {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    column-gap: var(--sp-3);
}
/* O display:grid acima ganha do .ds-row[hidden] lá de cima (mais
   específico), e sem esta regra marcar a conta como paga deixaria de
   esconder a linha — que é como o marcar-pago.js tira a conta da lista. */
[data-marcar-pago] .ds-row[data-id][hidden] { display: none; }

/* Descrição é a única coluna elástica: encolhe até caber e para em duas
   linhas, com reticências no que passar. */
[data-marcar-pago] .ds-row[data-id] .ds-row-title {
    display: -webkit-box; -webkit-box-orient: vertical;
    -webkit-line-clamp: 2; line-clamp: 2;
    overflow: hidden;
}

/* Coluna do botão: rótulo em cima, botão embaixo, centralizados. Os dois
   juntos são a 4ª coluna do grid da linha, então o rótulo não empurra nada
   pro lado. Usado no menu e na tela de contas a pagar. */
.ds-pagar-col {
    display: flex; flex-direction: column; align-items: center;
    gap: 2px; flex-shrink: 0;
}
.ds-pagar-rotulo {
    font-size: var(--fs-label); color: var(--c-muted);
    line-height: 1; white-space: nowrap;
}

/* Aviso de erro na própria linha: a conta fica onde está e explica o que
   houve, em vez de sumir sem ter gravado. Ocupa as quatro colunas, abaixo
   do resto (o JS o insere como último filho da linha). */
.linha-erro {
    grid-column: 1 / -1; margin: var(--sp-2) 0 0;
    font-size: var(--fs-meta); color: var(--c-danger);
}

/* Aviso de desfazer, flutuante sobre a tela. */
.ds-desfazer {
    position: fixed; left: 50%; bottom: var(--sp-5); transform: translateX(-50%);
    z-index: 50; display: flex; align-items: center; gap: var(--sp-4);
    max-width: calc(100vw - 2 * var(--sp-4));
    padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
    border-radius: var(--r-pill);
    background: var(--c-text); color: #fff;
    box-shadow: var(--shadow-lg);
    font-size: var(--fs-body);
}
.ds-desfazer[hidden] { display: none; }
.ds-desfazer-texto { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ds-desfazer button {
    flex-shrink: 0; min-height: var(--h-control); padding: 0 var(--sp-4);
    border: none; border-radius: var(--r-pill);
    background: rgba(255,255,255,.14); color: #fff;
    font-family: inherit; font-size: var(--fs-body); font-weight: var(--fw-medium);
    cursor: pointer;
}
.ds-desfazer button:hover { background: rgba(255,255,255,.24); }
.ds-desfazer button[disabled] { opacity: .5; cursor: progress; }

/* ---------------------------------------------------------- 4.11 Ícones
   Padrão: SVG inline, viewBox 24, fill none, stroke currentColor, width 2.
   A cor vem do contexto; o tamanho, destas classes. */
.ds-icon { display: block; width: 20px; height: 20px; flex-shrink: 0; }
.ds-icon.is-sm { width: 16px; height: 16px; }
.ds-icon.is-lg { width: 24px; height: 24px; }

/* --------------------------------------------------- 4.12 Modo privacidade */
body.privacy-mode .money { filter: blur(7px); transition: filter 0.2s; }
body.privacy-mode .money:hover { filter: blur(0); }

/* --------------------------------------- 4.13 Modal, chips e toast (feedback)
   Modal é <dialog> nativo (showModal): foco, Esc e fundo vêm do navegador. */
.ds-modal {
    width: min(480px, calc(100% - 2 * var(--sp-4)));
    padding: var(--sp-5);
    border: 1px solid var(--c-border); border-radius: var(--r-md);
    background: var(--c-surface); color: var(--c-text);
    box-shadow: var(--shadow-lg);
}
.ds-modal::backdrop { background: rgba(31, 41, 55, 0.45); }
.ds-modal form { display: grid; gap: var(--sp-3); }
.ds-modal .ds-actions { justify-content: flex-end; margin-bottom: 0; }

.ds-chips { display: flex; flex-wrap: wrap; gap: var(--sp-2); border: 0; padding: 0; margin: 0; }
.ds-chip {
    display: inline-flex; align-items: center;
    min-height: var(--h-control-sm); padding: 0 var(--sp-3);
    border: 1px solid var(--c-border-strong); border-radius: var(--r-pill);
    color: var(--c-muted); font-size: var(--fs-body); cursor: pointer;
}
.ds-chip input { position: absolute; opacity: 0; pointer-events: none; }
.ds-chip:has(input:checked) { background: var(--c-primary-soft); border-color: var(--c-primary); color: var(--c-primary); }
.ds-chip:has(input:focus-visible) { box-shadow: 0 0 0 3px rgba(98, 0, 234, 0.12); }

.ds-toast {
    position: fixed; left: 50%; bottom: var(--sp-6); transform: translateX(-50%);
    max-width: calc(100% - 2 * var(--sp-4));
    padding: var(--sp-3) var(--sp-4);
    border-radius: var(--r-sm);
    background: var(--c-text); color: var(--c-surface);
    font-size: var(--fs-body); box-shadow: var(--shadow-lg);
    z-index: 1000;
}
/* display das classes venceria o atributo hidden. */
.ds-modal [hidden], .ds-toast[hidden] { display: none; }

/* ============================================================================
   5. UTILITÁRIOS
   ========================================================================= */
.ds-num       { font-variant-numeric: tabular-nums; }
.ds-text-right{ text-align: right; }
.ds-muted     { color: var(--c-muted); }
.ds-success   { color: var(--c-success); }
.ds-warning   { color: var(--c-warning); }
.ds-danger    { color: var(--c-danger); }
.ds-medium    { font-weight: var(--fw-medium); }
.ds-meta      { font-size: var(--fs-meta); color: var(--c-muted); }
.ds-stack > * + * { margin-top: var(--sp-3); }
.ds-sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ============================================================================
   6. MOBILE — ~95% do uso. Ajusta densidade, nunca o alvo de toque.
   ========================================================================= */
@media (max-width: 640px) {
    :root {
        --page-pad: 14px;
        --fs-title: 20px;
        --fs-metric: 20px;
    }
    .ds-page { padding: var(--sp-4) var(--page-pad) var(--sp-8); }
    .ds-card { padding: var(--sp-4); }
    .ds-metric { padding: var(--sp-3); }
    .ds-metric-grid { gap: var(--sp-2); }
    .ds-row { padding: var(--sp-3); gap: var(--sp-3); }
    .ds-form-grid { grid-template-columns: 1fr; gap: var(--sp-3); }
    /* iOS dá zoom em campo com fonte < 16px. */
    .ds-input, .ds-select, .ds-textarea { font-size: 16px; }
    .ds-actions .ds-btn { flex: 1 1 auto; }
}

/* ============================================================================
   7. TABLET E DESKTOP
   A base acima é a versão de celular. Daqui pra baixo o layout ganha as
   colunas e a densidade que a tela maior comporta. O alvo de toque de 44px
   NÃO encolhe: mouse também erra, e notebook com tela sensível existe.
   ========================================================================= */

/* ---- Tablet pra cima: a página respira ---- */
@media (min-width: 641px) {
    /* Campo com teto de largura: ver o comentário na seção de formulário. */
    .ds-field.is-sm { max-width: 240px; }
    .ds-field.is-md { max-width: 460px; }

    :root { --page-pad: var(--sp-5); }
}

/* ---- Desktop ---- */
@media (min-width: 901px) {
    /* Lista em colunas: o mesmo componente de linha, lado a lado. Sem isto
       uma lista de 10 itens vira 10 linhas soltas num container de 980px. */
    /* Duas colunas fixas, não auto-fit: a regra de borda abaixo depende de
       saber quantas colunas existem, e a 3 colunas a descrição da linha
       quebraria em quatro linhas. */
    .ds-list.is-grid { display: grid; grid-template-columns: repeat(2, 1fr); }
    /* Em grade a borda de baixo não serve mais de divisor: vira coluna. */
    .ds-list.is-grid > .ds-row { border-bottom: none; border-right: 1px solid var(--c-border); }
    .ds-list.is-grid > .ds-row:nth-child(even) { border-right: none; }
    .ds-list.is-grid > .ds-row { border-top: 1px solid var(--c-border); }
    .ds-list.is-grid > .ds-row:nth-child(-n+2) { border-top: none; }

    /* Formulário longo cabe em 3 colunas sem ficar apertado. */
    .ds-form-grid.is-dense { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

    /* Ação principal não estica pela largura toda. */
    .ds-actions .ds-btn { flex: 0 0 auto; }
}

/* ============================================================================
   8. TABELA RESPONSIVA — .ds-table.is-stacked
   ----------------------------------------------------------------------------
   Uma marcação só: tabela no desktop, cartão no celular. Nada de duplicar o
   <tbody> em <table> + <ul>, que é como a edição inline, a seleção múltipla e
   o estorno acabariam divergindo entre as duas versões.

   Cada <td> precisa de data-label="Valor" — é ele que vira o rótulo do
   cartão. <td> sem data-label não ganha rótulo (útil pra célula de ações).
   ========================================================================= */
@media (max-width: 900px) {
    .ds-table.is-stacked { display: block; background: transparent; font-size: var(--fs-body); }
    .ds-table.is-stacked thead { display: none; }
    .ds-table.is-stacked tbody { display: block; }

    /* O :not([hidden]) é obrigatório. Linha que fecha pelo atributo hidden só
       some por causa da regra display:none de baixa especificidade (0,1,0) que
       o navegador aplica a [hidden]; um display:block mais específico ganha
       dela e faz toda linha oculta nascer aberta no celular. */
    .ds-table.is-stacked tbody tr:not([hidden]) {
        display: block;
        background: var(--c-surface);
        border: 1px solid var(--c-border);
        border-radius: var(--r-md);
        box-shadow: var(--shadow-sm);
        padding: var(--sp-1) var(--sp-3) var(--sp-2);
        margin-bottom: var(--sp-3);
    }
    .ds-table.is-stacked td {
        display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
        padding: var(--sp-2) 0;
        border-bottom: 1px solid var(--c-border);
        text-align: left;
    }
    .ds-table.is-stacked tr > td:last-child { border-bottom: none; }
    .ds-table.is-stacked td::before {
        content: attr(data-label);
        flex: 0 0 92px;
        font-size: var(--fs-label); font-weight: var(--fw-medium); color: var(--c-muted);
        text-transform: uppercase; letter-spacing: 0.06em;
    }
    .ds-table.is-stacked td:not([data-label])::before { display: none; }
    .ds-table.is-stacked td.is-num { text-align: left; }

    /* flex-basis 0: sem isso o width:100% do campo empurra o rótulo pra uma
       linha só dele. */
    .ds-table.is-stacked td > input,
    .ds-table.is-stacked td > select { flex: 1 1 0; min-width: 0; min-height: var(--h-control); }
    .ds-table.is-stacked td > input[type=checkbox] { flex: 0 0 auto; min-height: 0; }

    /* Sem scroll horizontal quando a tabela virou cartão. */
    .ds-table-wrap:has(.ds-table.is-stacked) { overflow-x: visible; }
}

@media (prefers-reduced-motion: reduce) {
    * { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
}
