/* Traqueo de Ingresos — app styles.
   Construido sobre los tokens de marca de Naosfi (css/tokens/*, copia
   sincronizada del design system que vive en
   /Users/eabp/Proyectos/naosfi/branding/design-system).

   ── El reparto de papeles ────────────────────────────────────────────────
   Esto es una herramienta de trabajo: el operador anota decenas de pagos por
   día y lo que conoce es una planilla. Esa familiaridad es un activo y no se
   tira, así que la app toma de cada lado lo que cada lado sabe hacer:

   · DE LA PLANILLA, LA ESTRUCTURA. Filas densas, grilla vertical entre
     columnas, encabezado con fondo y fijo al hacer scroll, números a la
     derecha en monoespaciada, verde/rojo contable, totales con su propia
     franja. Nada de eso consume identidad: es tipografía y layout.
   · DEL DESIGN SYSTEM, LA IDENTIDAD. Tinta y superficies, el cian de marca en
     la marca, la navegación y el foco, las tres familias tipográficas, los
     radios, las sombras y el movimiento.

   ── El acento NO es el color del dato ────────────────────────────────────
   El cian `#00D9FF` mide 1,70:1 sobre blanco: como letra es ilegible y como
   relleno es perfecto (10,66:1 con la tinta de marca encima). Así que el
   acento se usa para identidad —rellenos, estado activo, foco, serie del
   gráfico— y NUNCA para decir si un número está bien o mal. Lo que significa
   algo (positivo, negativo, aviso) se deriva del mismo tono de la paleta
   hasta pasar 4,5:1 sobre el peor fondo donde de verdad cae.

   Y los derivados no pelean con lo que el operador ya conoce: el positivo
   `#027A45` queda a 9 unidades RGB del verde que la app usaba (`#0A7C42`) y a
   15 del verde de Excel (`#107C41`). El punto medio ya estaba resuelto.

   Los contrastes de cada token están medidos y verificados por
   `test/contraste.mjs`, que falla si alguno baja del mínimo.

   ── Los tres modos ───────────────────────────────────────────────────────
   Claro, oscuro y automático. Lo que se guarda es la PREFERENCIA, no el
   color: en automático no hay atributo `data-theme` y mandan las media
   queries, así que a las 7 de la tarde la app se oscurece sola. La elección
   manual escribe el atributo y le gana al sistema — por eso la regla de
   sistema se acota con `:root:not([data-theme="light"])`.

   Las dos reglas oscuras de acá abajo DICEN LO MISMO a propósito (una para la
   elección manual, otra para la preferencia del sistema). No se pueden unir en
   un solo selector porque una vive dentro de una media query. `test/tema.mjs`
   compara las dos declaración por declaración y falla si se separan. */

/* ══════════════════════════════════════════════════════ geometría y ritmo ══
   Naosfi define radios, sombras y layout de marketing, pero no una escala de
   espaciado ni de movimiento: eso es de la app y se declara acá.
   La escala de 4px viene del branding anterior y se conserva tal cual — es
   geometría, no identidad, y sus 130 usos no cambian de significado. */
:root {
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-10: 40px;
  --space-12: 48px;
  --space-16: 64px;

  /* Naosfi da --radius (22px), --radius-sm (14px) y --radius-pill (100px).
     Los 22px de tarjeta son de una card de marketing: sobre una tabla densa,
     de 40 filas y bordes a ras, ese radio deja media docena de esquinas
     enormes discutiendo con la grilla. La app baja a 14px la tarjeta y a 10px
     las superficies de escala ítem, y conserva la píldora tal cual, que es la
     forma con la que la marca dibuja sus botones. */
  --app-radius-card: 14px;
  --app-radius-item: 10px;
  --radius-md: 12px;   /* inputs y botones de formulario */
  --radius-xl: 20px;   /* la hoja modal */

  /* Movimiento: el design system pide `cubic-bezier(.2,.7,.2,1)` a .7s para
     los reveals de scroll de la landing. En una herramienta no hay reveals;
     hay transiciones de estado, que arrancan rápido y frenan al llegar. Se
     conserva la ease-out quint que la app ya usaba y se acortan los tiempos:
     una landing puede tomarse 700ms, un chip de filtro no. */
  --ease-standard: cubic-bezier(0.22, 1, 0.36, 1);
  --duration-fast: 160ms;
  --duration-base: 420ms;

  --sidebar-w: 216px;
  --tabbar-h: 68px;
  /* Alto del mobiliario flotante en móvil: 18 (aire) + 56 (FAB). Lo consume el
     padding inferior de .view, para que el último dato del scroll despeje el
     botón. */
  --fab-stack: 74px;
  /* Alto de la barra fija de cabecera. Lo usa el `thead` pegajoso de las
     tablas para engancharse justo debajo y no debajo del borde de la ventana,
     que lo dejaría tapado. Medido sobre .page-head en escritorio. */
  --head-h: 73px;

  /* Escala de apilamiento, por rol y no por número suelto. */
  --z-sticky: 30;
  --z-nav: 40;
  --z-fab: 45;
  --z-backdrop: 60;
  --z-toast: 80;

  /* Tracking de los eyebrows. La marca los escribe en mono, mayúsculas y
     0.18em (--tracking-eyebrow). A 9,5px, que es el tamaño de la etiqueta en
     la tira de cuatro indicadores en móvil, 0.18em desborda la tarjeta:
     "MOVIMIENTOS" pide 104px y hay 73. Se toma la mitad, que sigue leyéndose
     como eyebrow de marca y entra. Donde hay aire —.calc-label— manda el
     valor del design system. */
  /* Muestras del selector de color base. No participan de la paleta: son la
     vista previa de cada base y por eso valen siempre lo mismo, esté activa o
     no. */
  --muestra-naosfi: #00D9FF;
  --muestra-excel: #107C41;

  --tracking-caption: 0.09em;
  --tracking-display: var(--tracking-heading);
}

/* Que los controles nativos —scrollbar, calendario del date, spinner— sigan
   al tema. Sin esto, en oscuro la barra de scroll queda blanca. */
:root { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}

/* ═══════════════════════════════════════════════════════════ tema claro ══ */
:root {
  /* -------------------------------------------------------- superficies -- */
  --app-bg: #F4F6F9;          /* --canvas del design system */
  --app-surface: #FFFFFF;     /* --surface */
  --app-surface-2: #FAFBFD;   /* --surface-2 */
  /* Un escalón más, que el design system no define porque su landing no tiene
     hover de chip: el hairline al 85% sobre blanco. Es el fondo MÁS OSCURO
     donde cae texto en tema claro, así que es contra éste que se derivan los
     colores de letra. */
  --app-surface-3: #E7EBF1;
  --app-line: #E3E7EE;        /* --line */
  --app-line-strong: #CBD2DE; /* derivado: el hairline un paso más marcado */

  /* --------------------------------------------------------------- texto -- */
  --app-text: #13161D;        /* --ink · 15,1:1 sobre el peor fondo */
  --app-text-2: #3E4453;      /* --ink-soft · 8,1:1 */
  /* El --muted del design system (#6A7080) da 4,14:1 sobre --app-surface-3:
     pasa sobre el canvas y falla sobre el hover de un chip. Se oscurece el
     mismo tono hasta 4,5:1 en su peor fondo. */
  --app-muted: #656A7A;       /* 4,50:1 sobre surface-3 · 5,39:1 sobre blanco */
  --app-placeholder: #656A7A; /* 5,21:1 sobre --app-input-bg */

  /* ------------------------------------------------------ acento (marca) -- */
  --app-accent: #00D9FF;      /* --accent · SOLO relleno, nunca letra */
  --on-accent: #13161D;       /* --accent-ink · 10,66:1 sobre el acento */
  --app-accent-bright: #7FECFF;
  /* El mismo cian oscurecido hasta ser legible como texto: enlaces y
     etiquetas de estado activo. El design system prohíbe texto LARGO en cian
     y esto no lo es — son etiquetas de dos palabras. */
  --app-accent-text: #03748A; /* 4,54:1 sobre surface-3 · 5,43:1 sobre blanco */
  --app-accent-soft: #E0FAFF;   /* acento al 12% sobre blanco: fila activa */
  --app-accent-soft-2: #CCF7FF; /* al 20%: hover de ese estado */
  --app-accent-line: #66E8FF;
  /* El canto del relleno de marca. El design system dibuja sus botones con
     `border: 1px solid transparent`, y en una landing está bien: el botón vive
     sobre secciones oscuras o sobre imágenes. Acá vive sobre blanco, donde el
     cian mide 1,70:1 y el BORDE del control desaparece — la letra se lee, pero
     dónde empieza y termina el botón, no (WCAG 1.4.11 pide 3:1 para eso).
     Es el mismo tono, oscurecido a 3,25:1: se ve el canto y el relleno sigue
     siendo el cian exacto de la marca. En oscuro no hace falta —el relleno ya
     mide 10,66:1 contra la superficie— y el canto se apaga. */
  --app-accent-edge: #059CB8;

  /* --------------------------------------------------- datos y estados --- */
  /* Derivados del verde de marca (#09E989) y del rojo/ámbar que el kit de
     producto del design system ya usa para "sube"/"baja" (ui_kits/wallet:
     #059B5C, #D64545, #E8A13C), oscurecidos hasta 4,5:1 sobre el peor fondo.
     El #059B5C del kit da 3,59:1 sobre blanco: sirve de chip, no de cifra. */
  --app-pos: #027A45;         /* 4,53:1 · a 9 de #0A7C42 y a 15 del verde Excel */
  --app-pos-soft: #DDFCEE;
  --app-pos-line: #90F5CA;
  --app-neg: #C43337;         /* 4,53:1 · a 38 del rojo que la app usaba */
  --app-neg-soft: #FAE9E9;
  --app-neg-line: #F1BEBE;
  --app-warn: #945F04;        /* 4,50:1 — el #E8A13C del kit da 2,19 */
  --app-warn-soft: #FCF2E4;

  /* --------------------------------------------------------- formularios -- */
  --app-input-bg: #FAFBFD;
  /* El anillo de foco no puede ser el cian a secas: 1,70:1 contra el fondo es
     un foco que no se ve. El trazo lleva el cian legible y el halo, el cian
     de marca. */
  --app-focus: #03748A;
  --app-focus-ring: 0 0 0 3px rgba(0, 217, 255, .35);
  /* La flecha de los <select>: va dentro de un data URI, donde var() no
     resuelve, así que el color viaja en el token y cambia con el tema. */
  --app-caret: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23656A7A' stroke-width='2.4' stroke-linecap='round'><path d='M6 9l6 6 6-6'/></svg>");

  /* ------------------------------------------------------------ elevación -- */
  /* Las dos primeras son las del design system (tokens/layout.css) tal cual;
     --shadow-md es el paso intermedio que la app necesita para popovers y no
     existe allá. */
  --shadow-sm: 0 1px 2px rgba(20, 21, 15, 0.04), 0 8px 24px rgba(20, 21, 15, 0.05);
  --shadow-md: 0 1px 3px rgba(20, 21, 15, 0.07), 0 12px 32px -12px rgba(20, 21, 15, 0.14);
  --shadow-lg: 0 2px 6px rgba(20, 21, 15, 0.06), 0 30px 60px -20px rgba(20, 21, 15, 0.22);
  /* Sombra de color: el design system no las tiene (sus botones no llevan
     sombra). Se conserva SOLO para los dos controles que flotan sobre el
     contenido —el FAB y el atajo de registro—, donde la separación es
     funcional y no decorativa. */
  --shadow-accent: 0 6px 16px rgba(0, 217, 255, .34);

  /* -------------------------------------------------------------- chrome -- */
  --app-backdrop: rgba(12, 15, 22, .42);
  --app-nav-bg: rgba(255, 255, 255, .94);
  --app-toast-bg: #13161D;    /* la tinta de marca */
  --app-toast-ink: #F4F6F9;

  /* -------------------------------------------------------------- gráfico -- */
  /* La barra no puede ser el cian de marca a secas: 1,70:1 contra la tarjeta
     blanca no separa la barra del fondo (el mínimo para un elemento gráfico
     es 3:1). Es el MISMO tono, oscurecido hasta 3,01:1 sobre el canvas y
     3,25:1 sobre la tarjeta. En oscuro el cian sí llega solo y la barra vuelve
     a ser el color de marca exacto. */
  --chart-bar: #059CB8;
  /* Los periodos que NO son el que se está mirando. No pueden ser el mismo
     cian más claro: para separarse del fondo blanco un cian tiene que ser tan
     oscuro que ya es el de la barra emphasis (medido: cualquier cian por
     encima de 3:1 cae en #059CB8). Así que la diferencia la hace el CROMA y no
     la luminosidad — gris azulado a 3,19:1 para el pasado, cian de marca para
     el tramo en curso. Es además lo que hace una planilla: la serie en color,
     el resto en gris. */
  --chart-bar-dim: #7E93A1;
  --chart-bar-zero: #DBFAFF;
  --chart-grid: rgba(19, 22, 29, .07);
  --chart-axis: rgba(19, 22, 29, .16);
  --chart-line: #3E4453;
  --chart-label: #656A7A;       /* el mismo texto secundario de la app */
  --chart-label-emph: #03748A;
  --chart-bar-hover: #00D9FF;   /* al pasar el ratón la barra sube al cian de marca */

  /* ── Alias del branding anterior ────────────────────────────────────────
     Los nombres viejos siguen existiendo porque hay estilos inline en el JS
     y un port a Compose que los leen. Cada uno apunta al ROL que cumplía, no
     al color que tenía: `--app-green` era "acción y TEXTO", así que hereda el
     acento legible y no el relleno cian —mapearlo al cian daría texto de
     1,7:1, que compila y no se lee—. */
  --app-green: var(--app-accent-text);
  --app-green-hover: var(--app-text);
  --app-green-bright: var(--app-accent);
  --app-green-soft: var(--app-accent-soft);
  --app-green-soft-2: var(--app-accent-soft-2);
  --app-green-line: var(--app-accent-line);
  --on-green: var(--on-accent);
  --app-danger: var(--app-neg);
  --app-danger-soft: var(--app-neg-soft);
  --app-danger-line: var(--app-neg-line);
  --shadow-green: var(--shadow-accent);
}

/* ═══════════════════════════════════ tema oscuro — elección manual ══ */
:root[data-theme="dark"] {
  --app-bg: #0C0F16;
  --app-surface: #13161D;
  --app-surface-2: #1A1F2B;
  --app-surface-3: #2C313C;
  /* El hairline del design system en oscuro es rgba(255,255,255,.10). Su
     propio kit de producto lo sube a .18 porque sobre estas superficies casi
     no se ve; acá la grilla de la tabla es información —separa columnas— así
     que se toma .12 para la celda y .22 para el borde estructural. */
  --app-line: rgba(255, 255, 255, .12);
  --app-line-strong: rgba(255, 255, 255, .22);

  --app-text: #EAF1F7;
  --app-text-2: #B4BCCB;
  /* El --muted oscuro del design system (#8A92A6) da 4,18:1 sobre el hover de
     un chip. Se aclara el mismo tono hasta 4,5:1. */
  --app-muted: #8F98AC;
  --app-placeholder: #8F98AC;

  /* Acá pasa lo bueno: sobre fondo oscuro el cian de marca mide 9,7:1, así que
     la marca se usa como fue diseñada —sin oscurecer— también en el texto. */
  --app-accent: #00D9FF;
  --on-accent: #13161D;
  --app-accent-bright: #7FECFF;
  --app-accent-text: #00D9FF;
  --app-accent-soft: #103541;
  --app-accent-soft-2: #0E4553;
  --app-accent-line: rgba(0, 217, 255, .45);
  --app-accent-edge: transparent;

  /* Los semánticos se INVIERTEN: los tonos oscurecidos para fondo claro miden
     3,7:1 acá. Vuelven los vivos de la paleta. */
  --app-pos: #09E989;         /* 8,08:1 sobre el peor fondo oscuro */
  --app-pos-soft: #12342C;
  --app-pos-line: rgba(9, 233, 137, .35);
  --app-neg: #FF7B7B;         /* el rojo claro del kit de producto · 5,19:1 */
  --app-neg-soft: #34242A;
  --app-neg-line: rgba(255, 123, 123, .35);
  --app-warn: #E8A13C;        /* 5,95:1 — acá sí sirve sin derivar */
  --app-warn-soft: #312921;

  --app-input-bg: #1A1F2B;
  --app-focus: #00D9FF;
  --app-focus-ring: 0 0 0 3px rgba(0, 217, 255, .30);
  --app-caret: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238F98AC' stroke-width='2.4' stroke-linecap='round'><path d='M6 9l6 6 6-6'/></svg>");

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
  --shadow-md: 0 2px 6px rgba(0, 0, 0, .35), 0 12px 32px -12px rgba(0, 0, 0, .5);
  --shadow-lg: 0 30px 60px -20px rgba(0, 0, 0, .6);
  --shadow-accent: 0 6px 16px rgba(0, 217, 255, .18);

  --app-backdrop: rgba(0, 0, 0, .62);
  --app-nav-bg: rgba(19, 22, 29, .94);
  /* El toast no se invierte a blanco: sobre una interfaz oscura, una píldora
     blanca es un flash. Sube un escalón de superficie, que es lo que lo
     despega del fondo sin encandilar. */
  --app-toast-bg: #2C313C;
  --app-toast-ink: #EAF1F7;

  --chart-bar: #00D9FF;
  --chart-bar-dim: #5E7382;
  --chart-bar-zero: #26292F;
  --chart-grid: rgba(255, 255, 255, .08);
  --chart-axis: rgba(255, 255, 255, .20);
  --chart-line: #B4BCCB;
  --chart-label: #8F98AC;
  --chart-label-emph: #00D9FF;
  --chart-bar-hover: #7FECFF;
}

/* ═══════════════════════════ tema oscuro — preferencia del sistema ══
   Mismas declaraciones que el bloque de arriba. Se acota con
   :not([data-theme="light"]) para que elegir "claro" a mano le gane al
   sistema. Si tocás una, tocá la otra: test/tema.mjs las compara. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --app-bg: #0C0F16;
    --app-surface: #13161D;
    --app-surface-2: #1A1F2B;
    --app-surface-3: #2C313C;
    --app-line: rgba(255, 255, 255, .12);
    --app-line-strong: rgba(255, 255, 255, .22);

    --app-text: #EAF1F7;
    --app-text-2: #B4BCCB;
    --app-muted: #8F98AC;
    --app-placeholder: #8F98AC;

    --app-accent: #00D9FF;
    --on-accent: #13161D;
    --app-accent-bright: #7FECFF;
    --app-accent-text: #00D9FF;
    --app-accent-soft: #103541;
    --app-accent-soft-2: #0E4553;
    --app-accent-line: rgba(0, 217, 255, .45);
    --app-accent-edge: transparent;

    --app-pos: #09E989;
    --app-pos-soft: #12342C;
    --app-pos-line: rgba(9, 233, 137, .35);
    --app-neg: #FF7B7B;
    --app-neg-soft: #34242A;
    --app-neg-line: rgba(255, 123, 123, .35);
    --app-warn: #E8A13C;
    --app-warn-soft: #312921;

    --app-input-bg: #1A1F2B;
    --app-focus: #00D9FF;
    --app-focus-ring: 0 0 0 3px rgba(0, 217, 255, .30);
    --app-caret: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238F98AC' stroke-width='2.4' stroke-linecap='round'><path d='M6 9l6 6 6-6'/></svg>");

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .3);
    --shadow-md: 0 2px 6px rgba(0, 0, 0, .35), 0 12px 32px -12px rgba(0, 0, 0, .5);
    --shadow-lg: 0 30px 60px -20px rgba(0, 0, 0, .6);
    --shadow-accent: 0 6px 16px rgba(0, 217, 255, .18);

    --app-backdrop: rgba(0, 0, 0, .62);
    --app-nav-bg: rgba(19, 22, 29, .94);
    --app-toast-bg: #2C313C;
    --app-toast-ink: #EAF1F7;

    --chart-bar: #00D9FF;
    --chart-bar-dim: #5E7382;
    --chart-bar-zero: #26292F;
    --chart-grid: rgba(255, 255, 255, .08);
    --chart-axis: rgba(255, 255, 255, .20);
    --chart-line: #B4BCCB;
    --chart-label: #8F98AC;
    --chart-label-emph: #00D9FF;
    --chart-bar-hover: #7FECFF;
  }
}

/* ══════════════════════════════════════════ el color base: Naosfi o Excel ══
   Dos ejes independientes. El MODO (claro/oscuro/automático) decide las
   superficies y la tinta; el COLOR BASE decide sólo el acento —lo que rellena
   botones, marca el estado activo, dibuja el foco y pinta la serie del
   gráfico—. Se combinan: hay cuatro pantallas posibles y las cuatro están
   medidas en test/contraste.mjs.

   Lo que el color base NO toca, nunca: las superficies, la tinta y los
   semánticos. Verde sigue siendo crédito y rojo, variación negativa, elija lo
   que elija el cliente. Si el acento se llevara el verde del dato, el usuario
   dejaría de poder distinguir "esto está bien" de "éste es el color de la
   empresa", que es el error que toda esta capa existe para evitar.

   ⚠️ Con la base Excel los dos verdes conviven —el acento a 15 unidades RGB
   del positivo—, así que la separación NO la hace el tono sino el ROL: el
   acento sólo rellena y enlaza, el semántico sólo escribe cifras. Es lo que
   hace la propia planilla, que tiene la cinta verde y los números en negro.
   Con la base Naosfi el problema no existe: cian y verde son tonos distintos.

   El default es Naosfi y no escribe atributo, igual que el modo automático. */

:root[data-acento="excel"] {
  /* Verde de Excel (#107C41, el de su logo y su cinta). Al revés que el cian:
     es oscuro, así que lleva BLANCO encima (5,27:1) y no tinta, y no necesita
     canto —contra la tarjeta blanca ya mide 5,27:1—. */
  --app-accent: #107C41;
  --on-accent: #FFFFFF;
  --app-accent-bright: #17A15A;
  /* Como texto, el verde de la cinta da 4,09:1 sobre su propio relleno suave.
     Se oscurece el mismo tono hasta pasar en el peor de sus fondos. */
  --app-accent-text: #0A7038;      /* 4,80:1 sobre soft-2 · 6,20:1 sobre blanco */
  --app-accent-soft: #E7F2EC;      /* el verde al 10% sobre blanco */
  --app-accent-soft-2: #D4E7DD;    /* al 18% */
  --app-accent-line: #9BC8AF;      /* al 42% */
  --app-accent-edge: transparent;
  --app-focus: #0A7038;
  --app-focus-ring: 0 0 0 3px rgba(16, 124, 65, .22);
  --shadow-accent: 0 6px 16px rgba(16, 124, 65, .26);
  --chart-bar: #107C41;            /* 4,87:1 sobre el canvas */
  --chart-bar-hover: #17A15A;
  --chart-label-emph: #0A7038;
}

/* Base Excel en oscuro — elección manual. Va DESPUÉS del bloque claro y con
   más especificidad a propósito: el bloque de arriba (0,2,0) empata con el del
   tema oscuro y le ganaría por orden, así que el acento oscuro se vuelve a
   declarar entero acá (0,3,0). Los dos bloques declaran el MISMO juego de
   tokens; test/tema.mjs lo comprueba. */
:root[data-acento="excel"][data-theme="dark"] {
  /* Sobre fondo oscuro el verde de la cinta se apaga, así que sube a un verde
     medio que sí lleva la tinta de marca encima (6,59:1). */
  --app-accent: #21B26C;
  --on-accent: #13161D;
  --app-accent-bright: #2ECC80;
  /* A 87 unidades RGB del verde positivo (#09E989): en oscuro los dos verdes
     conviven en la misma pantalla y tienen que poder distinguirse. */
  --app-accent-text: #4FB874;      /* 5,24:1 sobre el hover de chip */
  --app-accent-soft: #17332D;
  --app-accent-soft-2: #194235;
  --app-accent-line: rgba(46, 204, 128, .38);
  --app-accent-edge: transparent;
  --app-focus: #2ECC80;
  --app-focus-ring: 0 0 0 3px rgba(46, 204, 128, .28);
  --shadow-accent: 0 6px 16px rgba(33, 178, 108, .20);
  --chart-bar: #21B26C;
  --chart-bar-hover: #2ECC80;
  --chart-label-emph: #4FB874;
}

/* Base Excel en oscuro — preferencia del sistema. Mismas declaraciones. */
@media (prefers-color-scheme: dark) {
  :root[data-acento="excel"]:not([data-theme="light"]) {
    --app-accent: #21B26C;
    --on-accent: #13161D;
    --app-accent-bright: #2ECC80;
    --app-accent-text: #4FB874;
    --app-accent-soft: #17332D;
    --app-accent-soft-2: #194235;
    --app-accent-line: rgba(46, 204, 128, .38);
    --app-accent-edge: transparent;
    --app-focus: #2ECC80;
    --app-focus-ring: 0 0 0 3px rgba(46, 204, 128, .28);
    --shadow-accent: 0 6px 16px rgba(33, 178, 108, .20);
    --chart-bar: #21B26C;
    --chart-bar-hover: #2ECC80;
    --chart-label-emph: #4FB874;
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  min-height: 100%;
  background: var(--app-bg);
  color: var(--app-text);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

body { overscroll-behavior-y: none; }

button, input, select, textarea {
  font-family: inherit;
  color: inherit;
}

button { cursor: pointer; border: 0; background: none; padding: 0; }

::selection { background: var(--app-accent); color: var(--on-accent); }

/* Sin border-radius acá: el outline ya sigue el radio propio de cada elemento.
   Fijarlo aquí deformaba los botones pill y sólo no se notaba porque todas las
   reglas de componente vienen después en este archivo — una reordenación lo
   habría hecho aparecer. */
:focus-visible {
  outline: 2px solid var(--app-focus);
  outline-offset: 2px;
}

/* Chillax trae 400/500/600/700 de verdad, así que los títulos van en el peso
   que el design system pide (500–600) y no en una negrita sintetizada. */
.display {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: var(--tracking-heading);
  line-height: var(--leading-heading);
}

.muted { color: var(--app-muted); }
/* Verde y rojo contables. Verde es crédito —acá todo movimiento lo es— y rojo,
   variación negativa. Son INFORMACIÓN: por eso no son el cian de la marca. */
.green { color: var(--app-pos); }
.negative { color: var(--app-neg); }
/* Cifras y columnas numéricas en la monoespaciada de la marca: los dígitos
   quedan alineados por construcción, que es lo que hace que una tabla se lea
   como una planilla.

   El alcance importa y se midió en pantalla: `.tnum` está puesto también sobre
   valores MIXTOS —"R2 · Bs 46.300", "Sin filtros · todo el periodo"—, y ahí la
   mono no alinea nada y parte la frase en tres renglones. Así que `.tnum` deja
   sólo las cifras tabulares y la familia se reserva para lo que de verdad es
   una columna de números o un importe suelto. */
.tnum { font-variant-numeric: tabular-nums; }
.num, .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.tnum { font-variant-numeric: tabular-nums; }
.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;
}

/* ---------------------------------------------------------------- shell -- */

.app {
  display: flex;
  min-height: 100vh;
  min-height: 100dvh;
}

/* Fondo un escalón por debajo de la tarjeta, como el sidebar del kit de
   producto del design system: así el ítem activo puede ser la superficie
   elevada y no un relleno de color. */
.sidebar {
  width: var(--sidebar-w);
  flex: none;
  background: var(--app-surface-2);
  border-right: 1px solid var(--app-line);
  display: flex;
  flex-direction: column;
  padding: var(--space-5) 14px;
  gap: var(--space-1);
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px var(--space-5);
}
/* Lockup del producto bajo su marca madre: el nombre en Chillax y, debajo,
   "naosfi" como eyebrow en mono y minúsculas, que es como la marca escribe
   siempre su wordmark. */
.brand-text { display: flex; flex-direction: column; line-height: 1.05; min-width: 0; }
.brand-sub { display: flex; align-items: baseline; gap: 4px; min-width: 0; }
.brand-parent {
  font-family: var(--font-mono);
  font-size: 8.5px;
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--app-muted);
}
/* La versión comparte renglón con la marca madre en vez de agregar un tercero:
   el lockup queda de dos líneas, a la altura del chip del icono. Va SIN
   versalitas ni tracking de eyebrow —un "V1.0.0" espaciado se lee como parte
   del nombre— y con el punto de separación puesto acá, que es presentación. */
.brand-version {
  font-family: var(--font-mono);
  font-size: 8.5px;
  color: var(--app-muted);
}
.brand-version::before { content: "·"; margin-right: 4px; }
/* El icono del producto sobre su chip: el design system dibuja así sus chips
   de icono —cuadrado redondeado en cian con el símbolo en tinta—. */
.brand-mark {
  width: 30px; height: 30px;
  border-radius: var(--app-radius-item);
  background: var(--app-accent);
  color: var(--on-accent);
  display: flex; align-items: center; justify-content: center;
  flex: none;
}
.brand-mark svg { display: block; }
/* El wordmark de la marca va en minúsculas, siempre. */
.brand-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 17px;
  letter-spacing: var(--tracking-heading);
}

/* Registro rápido en el menú: es la acción que se repite todo el día, así que
   va antes de la navegación y con el peso de un botón, no de un ítem de menú.
   El resto del menú es para mirar; esto es para escribir. */
.sidebar-cta {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-bottom: var(--space-3);
  padding: 11px var(--space-3);
  border-radius: var(--app-radius-item);
  background: var(--app-accent);
  color: var(--on-accent);
  border: 1px solid var(--app-accent-edge);
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  box-shadow: var(--shadow-accent);
  transition: background var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.sidebar-cta:hover { filter: brightness(1.06); }
.sidebar-cta:active { transform: scale(.98); }
.sidebar-cta svg { flex: none; }
.sidebar-cta kbd {
  margin-left: auto;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .18);
  font-family: inherit;
  font-size: 10px;
  font-weight: 700;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px var(--space-3);
  border-radius: var(--app-radius-item);
  color: var(--app-text-2);
  font-size: 13px;
  font-weight: 500;
  text-align: left;
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.nav-item:hover { background: var(--app-surface-3); color: var(--app-text); }
/* Estado activo, tal como lo resuelve el design system en su propio producto:
   la superficie elevada con su sombra corta, el texto en tinta plena y el
   ACENTO sólo en el icono. Un relleno cian acá gritaría más que el dato. */
.nav-item[aria-current="page"] {
  background: var(--app-surface);
  color: var(--app-text);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
}
.nav-item[aria-current="page"] svg { color: var(--app-accent); }
.nav-item svg { flex: none; }

.sidebar-spacer { flex: 1; }

.sidebar-user {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px var(--space-3);
  border-top: 1px solid var(--app-line);
}
.avatar {
  width: 30px; height: 30px;
  border-radius: var(--radius-pill);
  background: var(--app-accent-soft);
  color: var(--app-accent-text);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: 11px;
  flex: none;
}

.main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.view {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: var(--space-6) 28px var(--space-10);
  min-width: 0;
}

/* ── Aviso de licencia ─────────────────────────────────────────────────────
   Dos tonos: `aviso` (por vencer o en gracia) y `bloqueada` (modo lectura).
   Los dos usan tokens que ya existen y se invierten solos por tema; no se
   agregan colores nuevos, que obligarían a declararlos en los DOS bloques
   oscuros —elección manual y preferencia del sistema— que `test/tema.mjs`
   compara.

   El riel de la izquierda hace el trabajo que el color no puede hacer solo:
   con 8% de daltonismo entre hombres, un cartel que sólo se distingue por ser
   ámbar o rojo no se distingue. El icono cambia con el tono por lo mismo. */
.licencia {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  margin: var(--space-6) 28px 0;
  padding: 13px 16px;
  border: 1px solid var(--app-line);
  border-left: 4px solid var(--app-warn);
  border-radius: var(--app-radius-item);
  background: var(--app-warn-soft);
  color: var(--app-text);
}

.licencia-bloqueada {
  border-left-color: var(--app-neg);
  background: var(--app-neg-soft);
  border-color: var(--app-neg-line);
}

.licencia-icono {
  display: flex;
  flex: 0 0 auto;
  padding-top: 1px;
  color: var(--app-warn);
}

.licencia-bloqueada .licencia-icono { color: var(--app-neg); }

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

.licencia-titulo { font-size: 13.5px; font-weight: 700; }

.licencia-cuerpo {
  font-size: 12.5px;
  color: var(--app-text-2);
  line-height: 1.45;
}

/* El motivo y el contacto son lo que convierte el cartel en algo accionable:
   sin a quién llamar, "modo lectura" es un callejón sin salida. */
.licencia-pie {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 4px;
  font-size: 12px;
  color: var(--app-text-2);
}

.licencia-motivo { font-style: italic; }
.licencia-contacto { font-weight: 600; }

@media (max-width: 900px) {
  .licencia { margin: 12px 16px 0; }
}

/* Modo lectura: los controles que escriben se apagan.
 *
 * La lista TIENE que coincidir con `ESCRIBEN` de `js/licencia.js`, que es la
 * frontera de verdad — `test/licencia.mjs` compara las dos y falla si se
 * separan. Sin eso, agregar una acción de escritura la dejaría con el aspecto
 * de un botón vivo que no hace nada, que es peor que no atenuar ninguno.
 *
 * Se atenúan pero NO se desactivan: siguen recibiendo el click, y el
 * despachador contesta con el aviso de por qué. `pointer-events: none` los
 * dejaría mudos, y un control muerto que no explica nada es indistinguible de
 * un bug — el operador tocaría tres veces y llamaría por teléfono.
 *
 * `cursor: not-allowed` es el que anuncia el estado antes de tocar, como en
 * `.pager-nav[disabled]`. */
body.sin-licencia [data-action="new"],
body.sin-licencia [data-action="edit-mov"],
body.sin-licencia [data-action="save-movement"],
body.sin-licencia [data-action="delete-movement"],
body.sin-licencia [data-action="quick"],
body.sin-licencia [data-action="save-quick"],
body.sin-licencia [data-action="save-quick-close"],
body.sin-licencia [data-action="undo-quick"],
body.sin-licencia [data-action="new-account"],
body.sin-licencia [data-action="edit-account"],
body.sin-licencia [data-action="save-account"],
body.sin-licencia [data-action="delete-account"],
body.sin-licencia [data-action="new-rate"],
body.sin-licencia [data-action="edit-rate"],
body.sin-licencia [data-action="save-rate"],
body.sin-licencia [data-action="delete-rate"],
body.sin-licencia [data-action="rate-value"],
body.sin-licencia [data-action="save-rate-value"],
body.sin-licencia [data-action="import-json"],
body.sin-licencia [data-action="seed"],
body.sin-licencia [data-action="wipe"] {
  opacity: .45;
  cursor: not-allowed;
}

/* Sin realce al pasar por encima ni al apretar: un botón apagado que igual se
   ilumina sigue prometiendo lo que no va a cumplir. */
body.sin-licencia [data-action="new"]:hover,
body.sin-licencia [data-action="quick"]:hover,
body.sin-licencia [data-action="new-account"]:hover,
body.sin-licencia [data-action="new-rate"]:hover,
body.sin-licencia [data-action="rate-value"]:hover {
  filter: none;
}

body.sin-licencia [data-action="new"]:active,
body.sin-licencia [data-action="quick"]:active {
  transform: none;
}

/* mobile-only chrome */
.topbar, .tabbar, .fab { display: none; }

/* ---------------------------------------------------------------- header -- */

/* Queda fija arriba: el selector de periodo es el control que más se usa y vivía
   al tope de la vista, así que al bajar a mirar la tabla o el gráfico se perdía
   de pantalla y había que volver arriba para cambiar de periodo.

   Los márgenes negativos la estiran hasta los bordes de .view y el padding se
   los devuelve: sin eso el contenido asomaría por los costados al pasar por
   debajo, porque un sticky sólo cubre el ancho de contenido de su contenedor.
   El margen superior negativo compensa el padding-top de .view para que el
   fondo llegue al borde y no quede una franja por la que se cuele el scroll. */
.page-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  margin: calc(var(--space-6) * -1) -28px 0;
  padding: var(--space-6) 28px var(--space-3);
  background: var(--app-bg);
  /* Una línea al pie: sin ella la barra tiene el mismo fondo que el contenido y
     lo que pasa por debajo aparece cortado sin que nada explique por qué. Con la
     línea se lee como capa fija y no como un defecto de dibujo. */
  border-bottom: 1px solid var(--app-line);
}
.page-title { display: flex; flex-direction: column; gap: 2px; }
.page-title h1 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 24px;
  letter-spacing: var(--tracking-display);
  margin: 0;
  text-wrap: balance;
}
.page-sub { font-size: 12px; color: var(--app-muted); }
.page-actions { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }

/* ------------------------------------------------------------- controles -- */

.segmented {
  display: flex;
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--radius-pill);
  padding: 4px;
  gap: 2px;
  box-shadow: var(--shadow-sm);
}
.segmented button {
  padding: 7px 14px;
  border-radius: var(--radius-pill);
  color: var(--app-muted);
  font-size: 12px;
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.segmented button:hover { color: var(--app-text); background: var(--app-surface-2); }
/* Segmentado activo en TINTA, como el del kit de producto de la marca. El
   selector de periodo es el control más usado de la app y pintarlo de cian
   dejaría al acento compitiendo con los datos en cada pantalla. */
.segmented button[aria-pressed="true"] {
  background: var(--app-text);
  color: var(--app-bg);
  font-weight: 600;
}

/* ── Selector de tema ─────────────────────────────────────────────────────
   Tres estados, no dos: claro, automático y oscuro, siempre visibles. Toma la
   forma del segmentado —contenedor con hairline y píldora dentro— y el activo
   se marca en tinta, igual que el segmentado de periodo del kit de producto
   de la marca. Un relleno cian acá pondría el acento a competir con el dato
   en las cinco pantallas. */
.tema {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--app-line);
  border-radius: var(--radius-pill);
  background: var(--app-surface);
}
/* ── Etiqueta al pasar por encima ─────────────────────────────────────────
   Los controles de apariencia son SÓLO iconos, y un círculo mitad relleno no
   dice "automático" por sí mismo. El `title` del navegador ya decía el nombre,
   pero tarda más de un segundo, se dibuja como un cartel del sistema operativo
   y no existe en la app de escritorio: para el operador, no existía.

   Va en la misma píldora oscura del toast —el otro mensaje flotante de la app—,
   sale HACIA ARRIBA porque estos controles viven al pie del menú, y aparece con
   retardo: barrer el mouse por la fila no puede encender tres carteles. El
   retardo va sólo en el estado de hover, así que se apaga al instante.

   `pointer-events: none` no es decoración: sin eso el cartel se mete debajo del
   cursor y el botón pierde su propio hover.

   No reemplaza al `aria-label` —ese es para quien no ve el cartel—, y por eso
   `data-tip` es un atributo aparte y no se lee de `title`, que dibujaría los
   dos. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
  padding: 4px 8px;
  border-radius: var(--app-radius-item);
  background: var(--app-toast-bg);
  color: var(--app-toast-ink);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
[data-tip]:hover::after,
[data-tip]:focus-visible::after { opacity: 1; transition-delay: 350ms; }

.tema-btn {
  width: 28px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  color: var(--app-muted);
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
/* El hover va a `surface-3`, que es el fondo con el que la app marca lo que
   está bajo el cursor (`.nav-item`, `.icon-btn`). Con `surface-2` —#FAFBFD
   sobre el #FFFFFF del contenedor— el realce existía en el CSS y no en la
   pantalla. El activo no cambia al pasar por encima: ya está elegido. */
.tema-btn:hover { color: var(--app-text); background: var(--app-surface-3); }
.tema-btn[aria-pressed="true"] { background: var(--app-text); color: var(--app-bg); }
/* El elegido también responde al puntero, con la tinta suave: sin esto, dos de
   los tres botones se realzan y el tercero parece muerto justo cuando el
   operador lo está apuntando. */
.tema-btn[aria-pressed="true"]:hover { background: var(--app-text-2); }
.sidebar-tema {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
  padding: 0 var(--space-3) var(--space-2);
}

/* ── Color base ───────────────────────────────────────────────────────────
   Un botón y un panel, no dos muestras sueltas: el color se elige una vez y
   después no se vuelve a tocar, así que no merece espacio permanente al lado
   de los controles que sí se usan todo el día.

   El botón ES la rueda de los dos colores —mitad cian, mitad verde—, así que
   dice qué hay adentro sin abrirlo y sin depender de un icono que haya que
   interpretar. Las muestras se pintan con su color propio SIEMPRE, no con el
   acento activo: son la vista previa de lo que pasa al tocarlas, y son los dos
   únicos colores literales fuera de la paleta —por eso viven en un token con
   nombre—. */
.acento-btn { flex: none; }
.acento-rueda {
  width: 17px;
  height: 17px;
  border-radius: var(--radius-pill);
  /* La partición va en diagonal y no vertical: a 17px una línea recta se lee
     como un icono partido, y la diagonal como una rueda de color. */
  background: linear-gradient(135deg, var(--muestra-naosfi) 0 50%, var(--muestra-excel) 50% 100%);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .16);
}

/* El panel es el mismo popover que las otras tasas: capa superior del
   navegador, cierre al tocar afuera o con Esc, y anclado al botón por el
   despachador. Sin display en la clase a secas —eso le ganaría al display:none
   con que el navegador lo mantiene cerrado y nacería abierto—. */
.acento-panel {
  position: fixed;
  inset: auto;
  margin: 0;
  width: min(240px, calc(100vw - 16px));
  padding: 8px;
  border: 1px solid var(--app-line-strong);
  border-radius: var(--app-radius-card);
  background: var(--app-surface);
  box-shadow: var(--shadow-lg);
}
.acento-panel:popover-open { display: flex; flex-direction: column; gap: 2px; }
.acento-titulo {
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: var(--tracking-caption);
  text-transform: uppercase;
  color: var(--app-muted);
  padding: 4px 8px 6px;
}
.acento-opcion {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px;
  border-radius: var(--app-radius-item);
  text-align: left;
  font-size: 13px;
  font-weight: 600;
  color: var(--app-text);
  transition: background var(--duration-fast) var(--ease-standard);
}
.acento-opcion:hover { background: var(--app-surface-2); }
.acento-opcion[aria-pressed="true"] { background: var(--app-surface-2); }
.acento-opcion svg { margin-left: auto; color: var(--app-accent-text); flex: none; }
.acento-nombre { display: flex; flex-direction: column; min-width: 0; }
.acento-pie { font-size: 10.5px; font-weight: 500; color: var(--app-muted); }
.acento-punto {
  width: 20px;
  height: 20px;
  flex: none;
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .16);
}
.acento-punto[data-base="naosfi"] { background: var(--muestra-naosfi); }
.acento-punto[data-base="excel"] { background: var(--muestra-excel); }

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  border-radius: var(--radius-pill);
  padding: 10px 18px;
  font-size: 13px;
  font-weight: 700;
  transition: transform var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard),
              opacity var(--duration-fast) var(--ease-standard);
}
.btn:active { transform: scale(.98); }
/* Las tres variantes son las del design system (components/core/Button.jsx):
   acento con tinta encima, fantasma con hairline, y el hover por brillo y
   borde en vez de por otro color. */
.btn-primary {
  background: var(--app-accent);
  color: var(--on-accent);
  border-color: var(--app-accent-edge);
}
.btn-primary:hover { filter: brightness(1.06); }
.btn-ghost {
  background: transparent;
  color: var(--app-text-2);
  border: 1px solid var(--app-line-strong);
  font-weight: 600;
}
.btn-ghost:hover { border-color: var(--app-text); color: var(--app-text); }
.btn-danger {
  background: transparent;
  border: 1px solid var(--app-neg-line);
  color: var(--app-neg);
  font-weight: 600;
}
.btn-danger:hover { background: var(--app-neg-soft); }
.btn-block { width: 100%; justify-content: center; padding: 16px; font-size: 15px; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--app-surface-2);
  color: var(--app-muted);
  font-size: 11px;
  font-weight: 500;
  border: 1px solid var(--app-line);
}
button.chip:hover { background: var(--app-surface-3); color: var(--app-text); }
.chip.is-on {
  background: var(--app-accent);
  border-color: var(--app-accent-edge);
  color: var(--on-accent);
  font-weight: 700;
}

.select-chip {
  appearance: none;
  padding: 6px 28px 6px 12px;
  border-radius: var(--radius-pill);
  background: var(--app-surface-2) var(--app-caret) no-repeat right 10px center;
  background-size: 12px 12px;
  color: var(--app-muted);
  font-size: 11px;
  font-weight: 500;
  border: 1px solid var(--app-line);
}
.select-chip:hover { color: var(--app-text); background-color: var(--app-surface-3); }
.select-chip option { background: var(--app-surface); color: var(--app-text); }

/* ---------------------------------------------------------------- cards -- */

.card {
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-radius-card);
  padding: var(--space-4) 18px;
  box-shadow: var(--shadow-sm);
}
.card-accent { border-color: var(--app-accent-line); }
.card-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
}
.card-title { font-size: 14px; font-weight: 600; }

/* Flex y no grid, a propósito. La cantidad de KPI es variable: 2 fijos + una
   tarjeta por cuenta + una de "Sin cuenta" si hay movimientos sin asignar, así
   que 6, 7 u 8 tarjetas. Ninguna cuenta fija de columnas —ni auto-fit— evita
   que la última quede huérfana, porque el grid reparte por columna y no por
   fila. Flex sí: cada línea estira sus tarjetas para llenarla, así que una
   tarjeta sola en la última fila ocupa el ancho completo y se lee como
   deliberada en vez de como un hueco. Es una tira que envuelve, o sea 1D. */
.kpis {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}
/* 240px de base, medido y no a ojo. Con 168px las seis tarjetas entraban en una
   fila de 185px cada una, y ahí el monto principal caía a 13,5px: más chico que
   los 19px de las tarjetas por cuenta, o sea jerarquía invertida. A 240px entran
   cuatro por fila a 283px y el total sube a 22,5px, por encima de sus
   secundarios. Dos filas legibles antes que una fila ilegible. La capacidad de
   4 también aguanta mejor la cantidad variable: sólo deja una tarjeta sola con
   5 KPI, mientras una base más grande la dejaría sola con 7. */
.kpis > .kpi { flex: 1 1 240px; }

/* La tira de indicadores es un set fijo de cuatro: grid de cuatro columnas
   iguales en todos los anchos, sin que ninguna tarjeta se estire ni baje de
   fila. El contenido se adapta al ancho disponible (número corto, pie oculto);
   la cuadrícula no. Así el mismo dato queda siempre en la misma posición. */
.kpi-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}
/* Techo más bajo que el de .kpis: allá conviven dos tarjetas anchas, acá cuatro
   angostas. El piso lo cubre la forma corta del número, no una fuente ilegible. */
.kpi-strip .kpi-value { font-size: clamp(12px, 7.6cqi, 23px); }

/* Casilla vacía: ocupa el mismo lugar que una tarjeta para que las que sí están
   no se muevan, y se ve como lo que es —un espacio por llenar— en vez de como
   una tarjeta sin datos. */
.kpi-hueco {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: none;
  border: 1px dashed var(--app-line-strong);
  box-shadow: none;
  color: var(--app-muted);
  transition: border-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.kpi-hueco:hover { border-color: var(--app-accent); color: var(--app-accent-text); }
.kpi-hueco .kpi-label { text-align: center; }

/* Número de posición dentro del chip elegido: dice en qué casilla va a salir. */
.chip-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-pill);
  background: var(--app-accent);
  color: var(--on-accent);
  font-size: 10px;
  font-weight: 700;
}
.chip[aria-disabled="true"] { opacity: .45; }

/* Forma larga y corta del mismo dato. El corte no es el de la app (900px, que
   separa tabla de lista) sino el ancho en que cuatro tarjetas dejan de poder
   mostrar un monto entero: medido, ~760px. Por debajo manda la forma corta, y
   se declara primero para que ese sea el caso base. */
.kpi-largo { display: none; }
.kpi-corto { display: inline; }
@media (min-width: 761px) {
  .kpi-largo { display: inline; }
  .kpi-corto { display: none; }
}

/* Las cuatro siguen en fila: lo que cede es el relleno y el tamaño del texto,
   no la cuadrícula. Con 414px cada tarjeta queda en ~86px. */
@media (max-width: 760px) {
  .kpi-strip { gap: 8px; }
  .kpi-strip .card { padding: 10px 9px; }
  .kpi-strip .kpi-label { font-size: 9.5px; letter-spacing: 0; }
  .kpi-strip .kpi-value { font-size: clamp(12px, 13cqi, 18px); }
  .kpi-strip .kpi-foot { font-size: 10px; line-height: 1.3; }
}

/* Por debajo de ~400px el piso de 12px deja de alcanzar y el número se
   RECORTA, que es lo que el requisito 4 prohíbe: un total cortado es un número
   equivocado, y a diferencia de uno chico no se nota que falta algo.

   Medido a 360px con las cuatro tarjetas: la caja útil son 53px y la forma
   corta más larga que la app produce —`Bs 999,9M`, nueve caracteres— pide 63.
   Las cuatro se recortaban, no una.

   Ceden las dos cosas que pueden ceder, en ese orden:

   1. El relleno lateral, de 9px a 5px. Es lo primero porque no cuesta
      legibilidad: en una tarjeta de 73px, 9px de aire a cada lado es más de la
      cuarta parte del ancho.
   2. El piso del tamaño, de 12px a 11px. Un punto, no más: como a este ancho
      el término `13cqi` queda por debajo del piso, el piso es el tamaño de
      TODOS los valores — bajarlo a 10 para que entre el caso extremo achicaría
      también los normales, que entran de sobra.

   Con 61px útiles y 11px de cuerpo, `Bs 999,9M` mide 57 y entra. Por encima de
   nueve caracteres —mil millones de bolívares en un periodo— vuelve a apretar;
   ahí la salida es que `numCorto` acorte, no que el texto siga encogiendo. */
@media (max-width: 400px) {
  .kpi-strip .card { padding: 10px 5px; }
  .kpi-strip .kpi-value { font-size: clamp(11px, 13cqi, 18px); }
}
/* min-width: 0 para que la celda de grilla pueda encogerse por debajo del ancho
   de su contenido; sin esto un monto largo estira la columna y desborda.
   container-type: inline-size expone el ancho de la tarjeta a .kpi-value, que es
   la medida que de verdad limita al número. */
.kpi {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
  container-type: inline-size;
}
/* Eyebrow de marca: mono, mayúsculas y tracking abierto. */
.kpi-label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  color: var(--app-muted);
  letter-spacing: var(--tracking-caption);
  text-transform: uppercase;
}
/* El monto escala contra el ancho de la TARJETA (cqi), no de la ventana: con la
   cantidad de KPI variable, una tarjeta puede ser angosta en pantalla ancha, así
   que vw no servía.

   Coeficiente y piso están medidos, no estimados. En Signia Pro 700, dentro de
   una caja de 138px: "Bs 12.345.678,90" entra a 16px, que sobre una tarjeta de
   174px son 9,2cqi. Con el 7,6cqi anterior el clamp caía al piso y los montos
   reales de hoy se dibujaban a 14px pudiendo estar a 18,7px — se perdía
   legibilidad en el caso común, que es el que importa.

   El piso baja a 13px porque era él, y no el coeficiente, el que ataba.

   Sin elipsis: un total truncado ("Bs 987.654.32…") es un número equivocado, y
   en una app de dinero eso es peor que uno chico. overflow hidden queda como red
   de último recurso, para que un caso imprevisto se corte en su propia tarjeta
   antes que derramarse sobre la vecina y ensuciar el dato del vecino. Si alguna
   cifra llega a cortarse, la salida correcta es notación compacta en el JS
   (fmtCompact), no seguir bajando el tamaño. nowrap porque una cifra no debe
   partirse a mitad de los miles. */
.kpi-value {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(13px, 9.2cqi, 26px);
  letter-spacing: var(--tracking-heading);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  white-space: nowrap;
}

/* Rejilla de tarjetas que se estira: auto-fit evita columnas vacías. */
.grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
}
.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  gap: var(--space-3);
}
@media (max-width: 900px) {
  .grid-auto { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
  .grid-2 { grid-template-columns: minmax(0, 1fr); }
}
.kpi-value-sm { font-family: var(--font-mono); font-weight: 700; font-size: 17px; letter-spacing: 0; }
.kpi-foot { font-size: 12px; color: var(--app-muted); }

.grid-chart {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--space-3);
}

.legend { display: flex; gap: 14px; align-items: center; }
.legend span { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--app-muted); }
.swatch-bar { width: 10px; height: 10px; border-radius: 3px; background: var(--chart-bar); }
.swatch-line { width: 10px; height: 2px; background: var(--chart-line); }

/* position: relative para que el panel de foco de abajo se ancle al gráfico. */
.chart { margin-top: 10px; position: relative; }
.chart svg { display: block; width: 100%; }
/* Las etiquetas del SVG ya no llevan la familia como atributo: la pone el CSS,
   que le gana a los atributos de presentación. Así el gráfico habla la
   tipografía de la marca sin que el nombre de la fuente viva en el JS, y las
   cifras del eje quedan en la monoespaciada como las de la tabla. */
.chart text { font-family: var(--font-mono); }

/* El drill de las barras existe también como botón en la tabla .sr-only, porque
   las barras viven bajo un role="img" que las esconde de la tecnología asistiva
   y además no reciben foco. Pero un control enfocable dentro de un contenedor
   clipeado es una parada de tabulación INVISIBLE: medido, el foco entra en el
   botón y el anillo no se pinta —elementFromPoint en su centro devuelve la
   tarjeta de atrás—, así que quien navega con teclado y ve la pantalla pierde el
   foco en la nada, una vez por periodo.

   Al entrar el foco, la tabla se revela como panel sobre el gráfico. Sigue
   absoluta, así que no empuja el layout ni desplaza el contenido de alrededor, y
   vuelve a ocultarse al salir. Quien usa lector de pantalla la tenía siempre;
   esto es para quien usa teclado y ve. */
.chart .sr-only:focus-within {
  inset: 0 0 auto 0;
  width: auto;
  height: auto;
  max-height: 100%;
  margin: 0;
  clip: auto;
  overflow: auto;
  white-space: normal;
  padding: var(--space-3);
  background: var(--app-surface);
  border: 1px solid var(--app-green-line);
  border-radius: var(--app-radius-card);
  box-shadow: var(--shadow-md);
  /* Capa local del gráfico, no de la escala global de apilamiento. */
  z-index: 1;
}
.chart .sr-only:focus-within table { width: 100%; border-collapse: collapse; font-size: 12px; }
.chart .sr-only:focus-within caption {
  text-align: left;
  font-size: 11px;
  font-weight: 500;
  color: var(--app-muted);
  padding-bottom: var(--space-2);
}
.chart .sr-only:focus-within th,
.chart .sr-only:focus-within td {
  padding: 5px var(--space-2);
  border-bottom: 1px solid var(--app-line);
  text-align: left;
  font-weight: 500;
}
.chart .sr-only:focus-within td { text-align: right; font-variant-numeric: tabular-nums; }
.chart .sr-only:focus-within tr:last-child th,
.chart .sr-only:focus-within tr:last-child td { border-bottom: 0; }
.chart .sr-only:focus-within button {
  font: inherit;
  color: var(--app-accent-text);
  font-weight: 600;
  text-align: left;
}
.chart .sr-only:focus-within button:hover { text-decoration: underline; }
.chart .bar { transition: fill var(--duration-fast) var(--ease-standard); }
.chart .bar:hover { fill: var(--chart-bar-hover); }

/* ------------------------------------------------------------- breakdown -- */

.breakdown { display: flex; flex-direction: column; gap: 14px; }
.bd-row { display: flex; flex-direction: column; gap: 6px; }
.bd-top { display: flex; justify-content: space-between; font-size: 12px; font-weight: 600; gap: var(--space-3); }
.bd-track { height: 8px; border-radius: var(--radius-pill); background: var(--app-surface-3); overflow: hidden; }
/* Anima width a propósito, no transform. Con scaleX el border-radius se comprime
   en el mismo factor y la punta derecha de la barra queda cuadrada (al 15% el
   radio efectivo de 4px se dibuja como 0.6px). La transición corre una vez por
   render sobre un puñado de barras, así que el costo de layout es irrelevante y
   la punta redondeada sí se ve. */
.bd-fill {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--chart-bar-dim);
  transition: width var(--duration-base) var(--ease-standard);
}
.bd-fill.is-top { background: var(--chart-bar); }
.bd-total {
  border-top: 1px solid var(--app-line);
  padding-top: var(--space-3);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.bd-total div { display: flex; justify-content: space-between; font-size: 12px; font-weight: 500; color: var(--app-muted); gap: var(--space-3); }

/* Panel de métricas del periodo: filas etiqueta/valor apoyadas en .bd-total.
   Se separa de .breakdown porque acá el valor puede ser largo y de dos líneas
   ("BBVA Provincial · Bs 157.000"), así que la fila alinea arriba en vez de al
   centro y la etiqueta cede el ancho que haga falta. */
.metrics {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
}
.metrics .bd-total div {
  align-items: start;
  gap: var(--space-4);
}
/* La etiqueta no se estira ni se corta; el valor se lleva el resto y envuelve. */
.metrics .bd-total div > :first-child {
  flex: 0 1 auto;
  max-width: 48%;
}
.metrics .bd-total div > :last-child {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  text-wrap: pretty;
}

/* La jerarquía de la fila: la etiqueta hereda el muted de .bd-total y el valor
   sube a texto pleno. Sin esto los dos salen idénticos y la fila no se lee. */
.metric-value {
  color: var(--app-text);
  font-weight: 700;
}
/* Los modificadores de color van después y con más peso, para que .green y
   .negative sigan mandando sobre el color base del valor. */
.metric-value.green { color: var(--app-pos); }
.metric-value.negative { color: var(--app-neg); }

/* Primer bloque de una tarjeta: no lleva el separador de arriba, porque el
   título de la tarjeta ya hace de división. */
.bd-total.is-flush {
  border-top: 0;
  padding-top: 0;
}

/* ---------------------------------------------------------------- tabla -- */

.table-card {
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-radius-card);
  /* clip y no hidden: los dos recortan las esquinas redondeadas, pero hidden
     crea un contenedor de scroll y el thead pegajoso se ancla a ÉL —que se va
     con la página— en vez de a la ventana. Medido: con hidden el encabezado
     desaparecía al bajar; con clip se queda. */
  overflow: clip;
  box-shadow: var(--shadow-sm);
}
.table-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-bottom: 1px solid var(--app-line);
  flex-wrap: wrap;
}
.table-filters { display: flex; gap: var(--space-2); flex-wrap: wrap; }
/* El scroll lateral existe para las pantallas angostas. A partir de 1200px la
   tabla más ancha (860px de mínimo) entra entera junto al menú, así que el
   contenedor deja de ser scrollport: es la ÚNICA forma de que el thead
   pegajoso se enganche a la ventana —dentro de un overflow-x:auto, el sticky
   se ancla a un contenedor que no scrollea verticalmente y no se pega nunca—. */
.table-scroll { overflow-x: auto; }
@media (min-width: 1200px) { .table-scroll { overflow: visible; } }
/* El mínimo es un parámetro, no un valor fijo: la tabla de movimientos necesita
   860px para sus ocho columnas, pero las tablas cortas (log de tasas, reportes)
   arrancarían con scroll horizontal sobrándoles ancho. Se ajusta poniendo
   --tbl-min en la propia tabla, que es un parámetro y no un style que le gane
   a esta hoja. */
/* ── Lo que la app le toma a la planilla ──────────────────────────────────
   Densidad de fila, grilla VERTICAL entre columnas, encabezado con fondo y
   fijo al hacer scroll, números a la derecha en mono y una franja de totales
   que no se confunde con una fila más. Es lo que hace que el operador sepa
   leer la pantalla el primer día, y no gasta identidad de marca: es
   tipografía y layout. */
.tbl { width: 100%; border-collapse: collapse; min-width: var(--tbl-min, 860px); }
.tbl th {
  text-align: left;
  padding: 9px var(--space-5);
  border-bottom: 1px solid var(--app-line-strong);
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 400;
  color: var(--app-muted);
  letter-spacing: var(--tracking-caption);
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--app-surface-2);
}
/* El encabezado se queda a la vista: en una tabla de 50 filas, saber qué
   columna se está mirando importa más que ver una fila más. Se engancha
   debajo de la barra fija de la vista, no del borde de la ventana. */
.tbl thead th {
  position: sticky;
  top: var(--head-h);
  z-index: 2;
}
.tbl td {
  padding: 10px var(--space-5);
  border-bottom: 1px solid var(--app-line);
  font-size: 12.5px;
  font-weight: 500;
  white-space: nowrap;
}
/* La grilla vertical: una línea entre columnas, nunca en el borde exterior.
   Es la diferencia entre "un listado" y "una hoja de cálculo". */
.tbl th + th,
.tbl td + td { border-left: 1px solid var(--app-line); }
.tbl tbody tr { cursor: pointer; transition: background var(--duration-fast) var(--ease-standard); }
.tbl tbody tr:hover { background: var(--app-accent-soft); }
.tbl tbody tr:last-child td { border-bottom: 0; }
/* Fila de totales, diferenciada como en una planilla: fondo propio, línea
   superior gruesa y peso. No es una fila más de datos. */
.tbl tfoot th,
.tbl tfoot td {
  background: var(--app-surface-2);
  border-top: 2px solid var(--app-line-strong);
  border-bottom: 0;
  padding: 11px var(--space-5);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--app-text);
  white-space: nowrap;
}
.tbl tfoot tr:first-child th:first-child { text-align: left; }
.num { text-align: right; }
.center { text-align: center; }

.tag {
  display: inline-block;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  background: var(--app-surface-3);
  color: var(--app-text-2);
  font-size: 11px;
  font-weight: 700;
}
.tag.is-top { background: var(--app-accent-soft); color: var(--app-accent-text); }
/* "Sin cuenta" es un pendiente, no un error: el contorno punteado se lee como
   casilla por llenar, sin el peso ni el rojo de un estado de fallo. Relleno
   transparente para que no compita con el .tag normal, que sí es un dato. El
   padding baja 1px porque acá sí hay borde y el alto tiene que coincidir. */
.tag.is-none {
  padding: 2px 9px;
  background: transparent;
  border: 1px dashed var(--app-line-strong);
  color: var(--app-muted);
  font-weight: 600;
}

/* ---------------------------------------------------------------- tasas -- */

/* La barra de acciones de la vista: el botón grande manda y los dos iconos
   —ayuda y datos— lo acompañan sin competir. En móvil no hay .page-head, así
   que la misma tríada se repite acá con el botón estirado. */
.rate-toolbar { display: flex; align-items: center; gap: var(--space-2); }
.rate-toolbar .btn { flex: 1; justify-content: center; }

/* Acciones dentro de una fila o de una ficha: la principal con texto, las de
   mirar como iconos, para que una tabla de seis columnas no se vuelva una
   pared de botones. */
.row-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  flex-wrap: wrap;
}
/* .icon-btn-sm se eliminó: era INERTE y engañaba. Fijaba 30px acá, pero .icon-btn
   fija 34px más abajo con la misma especificidad (0,1,0), y en un empate gana la
   última: cualquier elemento con las dos clases tomaba 34. Borrarla no cambia un
   pixel, y saca un nombre que prometía un tamaño que nunca aplicó — dos sesiones
   (y yo) razonamos mal sobre la distinción escritorio/móvil creyendo que esos
   iconos medían 30. El botón de icono estándar de la app es 34 en escritorio y
   44 en móvil, y eso sale entero de la media query.
   Si alguna vez hace falta una variante compacta de verdad, no alcanza con
   repetir este patrón: hay que usar .icon-btn.icon-btn-sm (0,2,0) Y subir a la
   misma especificidad la regla de 44px del bloque móvil, o la compacta se
   quedaría chica también con el dedo. */
.chip[disabled] { opacity: .55; cursor: default; }
.chip svg { flex: none; }

/* Un rango ("735,00 – 800,00") son dos números en el espacio de uno: en móvil,
   con tres tarjetas por fila, no entra en una línea, y .kpi-value no envuelve
   —correcto para un monto, que cortado sería un número falso—. Acá el corte
   está en el guión, así que envolver es lo que no miente. */
.kpi-value.kpi-range { white-space: normal; font-size: 15px; }

.rate-name { display: inline-flex; align-items: center; gap: var(--space-2); min-width: 0; }
.rate-name strong { font-weight: 600; }

/* Las filas de esta tabla no se editan al tocarlas (los cambios de una tasa
   son históricos, no registros que se corrigen), así que no fingen serlo. */
.tbl-static tbody tr { cursor: default; }
.tbl tbody tr[data-action] { cursor: pointer; }

.rate-list { display: flex; flex-direction: column; gap: var(--space-2); }
.rate-item {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-radius-card);
  padding: var(--space-3) 14px;
  box-shadow: var(--shadow-sm);
}
.rate-item-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  text-align: left;
}
.rate-item-value { font-size: 16px; font-weight: 700; flex: none; }

/* Selector de ventana del histórico. Son ocho chips cortos: en pantalla
   angosta se desplazan de lado en vez de partirse en dos filas. */
.range-bar { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
.range-bar::-webkit-scrollbar { display: none; }
.range-bar .chip { padding: 6px 11px; font-weight: 600; }

.detail-head { display: flex; align-items: center; gap: var(--space-3); min-width: 0; }

/* Fórmulas de la hoja de ayuda: bloque tranquilo, sin peso de tarjeta. */
.calc {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 14px;
  border-radius: var(--app-radius-item);
  background: var(--app-surface-2);
  border: 1px solid var(--app-line);
}
.calc-label {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--app-muted);
  letter-spacing: var(--tracking-caption);
  text-transform: uppercase;
}
.calc-formula { font-size: 14px; font-weight: 700; text-wrap: pretty; }

/* ----------------------------------------------------------- paginación -- */

/* Vive dentro de .table-card, así que hereda su fondo: sólo necesita la línea
   de arriba para despegarse de la última fila. */
.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-5);
  border-top: 1px solid var(--app-line);
}
.pager-count { font-size: 12px; font-weight: 500; color: var(--app-muted); }
.pager-controls { display: flex; align-items: center; gap: 2px; }

.pager-num,
.pager-nav {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 var(--space-2);
  border-radius: var(--radius-sm);
  background: none;
  color: var(--app-text-2);
  font-size: 12px;
  font-weight: 600;
  transition: background var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard);
}
.pager-num:hover,
.pager-nav:not([disabled]):hover { background: var(--app-surface-3); color: var(--app-text); }
/* La página actual no es un botón "apagado": es el estado seleccionado, y se
   marca con el mismo verde suave que el resto de los activos de la app. */
.pager-num[aria-current="page"] {
  background: var(--app-accent-soft);
  color: var(--app-accent-text);
  box-shadow: inset 0 0 0 1px var(--app-accent-line);
}
.pager-nav[disabled] { color: var(--app-line-strong); cursor: not-allowed; }
.pager-gap { padding: 0 2px; color: var(--app-muted); font-size: 12px; }
.pager-size { display: flex; }

/* --------------------------------------------------------------- listas -- */

.list { display: flex; flex-direction: column; gap: var(--space-2); }
.list-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--app-radius-card);
  padding: var(--space-3) 14px;
  text-align: left;
  box-shadow: var(--shadow-sm);
  transition: border-color var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard);
}
.list-item:hover { border-color: var(--app-accent-line); background: var(--app-surface-2); }
.li-badge {
  width: 36px; height: 36px;
  border-radius: var(--app-radius-item);
  background: var(--app-surface-2);
  color: var(--app-muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
  flex: none;
}
.li-badge.is-top { background: var(--app-accent-soft); color: var(--app-accent-text); }
.li-main { flex: 1; display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.li-title { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.li-sub { font-size: 11px; color: var(--app-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.li-side { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; flex: none; }
.li-amount { font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--app-pos); font-variant-numeric: tabular-nums; }
.li-usd { font-family: var(--font-mono); font-size: 10.5px; color: var(--app-muted); font-variant-numeric: tabular-nums; }

.section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
}
.section-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
/* Enlace de acento. Lleva el cian legible —no el de marca, que sobre blanco
   mide 1,7:1— y nunca corre más de dos o tres palabras. */
.link-green { color: var(--app-accent-text); font-size: 12px; font-weight: 600; background: none; }
.link-green:hover { color: var(--app-text); text-decoration: underline; }

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-10) var(--space-5);
  text-align: center;
  color: var(--app-muted);
  font-size: 13px;
  /* El mensaje no se estira a lo ancho de la tarjeta: se lee mejor acotado. */
  max-width: 46ch;
  margin-inline: auto;
  text-wrap: pretty;
}
.empty strong { color: var(--app-text); font-size: 15px; font-weight: 600; }

/* ------------------------------------------------------------ formulario -- */

.field { display: flex; flex-direction: column; gap: 6px; }
.field > label, .field-label { font-size: 12px; font-weight: 500; color: var(--app-text-2); }
.input, .select {
  background: var(--app-input-bg);
  border: 1px solid var(--app-line-strong);
  border-radius: var(--radius-md);
  padding: 13px 14px;
  font-size: 14px;
  font-weight: 600;
  color: var(--app-text);
  width: 100%;
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.input::placeholder { color: var(--app-placeholder); font-weight: 500; }
.input:focus, .select:focus {
  outline: none;
  border-color: var(--app-accent);
  box-shadow: var(--app-focus-ring);
}
.input.is-strong {
  font-family: var(--font-mono);
  font-size: 17px;
  font-weight: 700;
  border-color: var(--app-accent);
}
.input.is-quiet { background: var(--app-surface-2); color: var(--app-muted); }
.select {
  appearance: none;
  background-image: var(--app-caret);
  background-repeat: no-repeat;
  background-position: right 14px center;
  padding-right: 38px;
}
.select option { background: var(--app-surface); color: var(--app-text); }
.field-row { display: flex; gap: var(--space-3); }
.field-row > * { flex: 1; min-width: 0; }
.field-hint { font-size: 11px; color: var(--app-muted); }

/* ── Pantalla de entrada ─────────────────────────────────────────────────
   Sin sesión no hay nada que navegar, así que la navegación se va: dejarla
   visible invita a tocar botones que no pueden hacer nada. */
body.sin-sesion .sidebar,
body.sin-sesion .tabbar,
body.sin-sesion .topbar,
body.sin-sesion .fab { display: none; }

.login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  padding: var(--space-4);
  /* El velo verde sube apenas desde abajo: es el mismo canvas de la app, no
     una portada. Restrained: la identidad la ponen la marca y el botón. */
  background:
    radial-gradient(120% 70% at 50% 108%, var(--app-accent-soft) 0%, transparent 60%),
    var(--app-bg);
}

.login-card {
  width: 100%;
  max-width: 400px;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-5);
  border-color: var(--app-line-strong);
  box-shadow: var(--shadow-md);
  animation: login-entrar 0.36s var(--ease-standard) both;
}

@keyframes login-entrar {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .login-card { animation: none; }
}

/* La misma marca del sidebar, un paso más grande: quien abre la tablet tiene
   que saber dónde está parado antes de escribir nada. */
.login-brand .brand-mark { width: 40px; height: 40px; font-size: 19px; border-radius: var(--radius-md); }

.login-head { display: flex; flex-direction: column; gap: 4px; }
.login-title { font-size: 24px; font-weight: 700; margin: 0; letter-spacing: -0.01em; text-wrap: balance; }
.login-sub { margin: 0; font-size: 13px; color: var(--app-text-2); }

/* El error es una alerta con cuerpo, no un renglón rojo suelto: en una tablet
   al sol un texto fino desaparece. role="alert" lo anuncia; esto lo hace ver. */
.login-error {
  margin: 0;
  padding: 11px 14px;
  border: 1px solid var(--app-neg-line);
  border-radius: var(--radius-md);
  background: var(--app-neg-soft);
  color: var(--app-neg);
  font-size: 13px;
  font-weight: 500;
}

/* 16px: debajo de eso, iOS hace zoom al enfocar y descoloca la pantalla justo
   en el gesto más repetido del día. 48px de alto por la regla táctil. */
.login-input { font-size: 16px; min-height: 48px; }

.login-pass { position: relative; display: block; }
.login-pass .login-input { padding-right: 48px; }

.login-ver {
  position: absolute;
  top: 50%; right: 4px;
  transform: translateY(-50%);
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius-sm);
  background: transparent;
  color: var(--app-text-2);
  cursor: pointer;
}
.login-ver:hover { background: var(--app-surface-2); color: var(--app-text); }
.login-ver:focus-visible { outline: 2px solid var(--app-focus); outline-offset: 1px; }

.login-mayus { color: var(--app-neg); font-weight: 500; }

/* 48px de alto: la regla táctil vale también acá, y en tablet este botón se
   toca con el dedo. aria-busy cubre el estado que el texto ya cuenta.

   Radio 12 y no píldora, a propósito: en esta columna el botón es la tercera
   barra debajo de dos inputs de radio 12 con el mismo ancho y alto — como
   píldora hablaba otro idioma de forma justo donde más se compara. El resto
   de la app conserva sus botones píldora; acá manda la geometría del
   formulario. El margen extra separa la acción de los campos: son dos cosas
   distintas y el ritmo lo tiene que decir. */
.login-entrar {
  min-height: 48px;
  font-size: 15px;
  justify-content: center;
  border-radius: var(--radius-md);
  margin-top: var(--space-2);
}
.login-entrar[disabled] { opacity: 0.7; cursor: default; }

/* `altoDisponible()` descuenta los hermanos del gráfico sumando sus
   `getBoundingClientRect()`. Un elemento inline devuelve ahí su caja inline
   —11px— y no la línea que ocupa en el flujo, así que quedaban 9px sin
   descontar: el gráfico crecía 9px en cada resize hasta clavarse en su techo.
   Como bloque, la caja medida es la línea real y la cuenta cierra.
   Sólo la tarjeta del gráfico: `.field-hint` tiene 23 usos y varios son hijos
   directos de un `.card`. Medido con `apps/web/test/alto-grafico.mjs`. */
.grid-chart > .card > .field-hint { display: block; }

.preview {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
  background: var(--app-accent-soft);
  border: 1px solid var(--app-accent-line);
  border-radius: var(--app-radius-card);
  padding: 14px var(--space-4);
}
.preview-main { display: flex; flex-direction: column; gap: 2px; }
.preview-label { font-size: 11px; font-weight: 500; color: var(--app-text-2); }
/* El equivalente calculado va en tinta y en mono: es un dato, y el panel ya
   está marcado como de marca por su fondo. Pintarlo de acento haría que el
   número dijera "empresa" en vez de decir cuánto. */
.preview-value {
  font-family: var(--font-mono);
  font-size: 20px;
  font-weight: 700;
  color: var(--app-text);
  font-variant-numeric: tabular-nums;
}
.preview-side { display: flex; flex-direction: column; gap: 2px; align-items: flex-end; }
.preview-side .preview-value { font-size: 14px; font-weight: 700; color: var(--app-text-2); }

/* ── equivalente de la tasa seleccionada, con las demás en un popover ────
   La caja compacta vive en la misma fila que el select de la tasa —misma
   altura que un .select, mismo ancho por el flex de la fila— y el icono de
   información abre las demás tasas en un popover a su derecha. */
.preview-eq {
  justify-content: space-between;
  gap: var(--space-2);
  height: 48px;
  padding: 4px 4px 4px 14px;
}
.preview-eq .preview-main { min-width: 0; gap: 0; }
.preview-eq .preview-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.preview-eq .preview-value { font-size: 17px; }

/* Hereda el tamaño táctil de .icon-btn; acá sólo se le da el color del panel
   verde y el estado abierto, que se marca con el relleno. */
.preview-otras-btn { color: var(--app-accent-text); flex: none; }
.preview-otras-btn:hover { background: var(--app-accent-soft-2); }
.preview-otras-btn[aria-expanded="true"] { background: var(--app-accent-soft-2); }
.preview-otras-btn:focus-visible { outline: 2px solid var(--app-focus); outline-offset: 2px; }

/* El popover flota en la capa superior —la hoja no lo recorta— y lo ubica el
   despachador en fixed junto al icono. OJO: el display de apertura vive en
   :popover-open y NUNCA en la clase a secas, que le ganaría al display:none
   con que el navegador lo mantiene cerrado. */
.preview-otras {
  position: fixed;
  inset: auto;
  margin: 0;
  width: min(300px, calc(100vw - 16px));
  padding: 10px 14px 8px;
  border: 1px solid var(--app-line-strong);
  border-radius: var(--app-radius-card);
  background: var(--app-surface);
  box-shadow: var(--shadow-lg);
}
.preview-otras:popover-open { display: flex; flex-direction: column; }
.preview-otras-titulo {
  font-size: 12px;
  font-weight: 700;
  color: var(--app-text);
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--app-line);
}
.preview-otra {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  padding: 5px 0;
  font-size: 12px;
  color: var(--app-text-2);
}
.preview-otra strong { font-weight: 700; color: var(--app-text); }
.preview-otra > span:last-child { font-weight: 700; color: var(--app-text); }

.pills { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.pill {
  padding: 10px 20px;
  border-radius: var(--radius-pill);
  background: var(--app-surface);
  border: 1px solid var(--app-line-strong);
  font-size: 13px;
  font-weight: 600;
  color: var(--app-text-2);
  transition: background var(--duration-fast) var(--ease-standard);
}
.pill:hover { background: var(--app-surface-2); color: var(--app-text); }
.pill[aria-pressed="true"] {
  background: var(--app-accent);
  border-color: var(--app-accent-edge);
  color: var(--on-accent);
  font-weight: 700;
}
/* .pill-add se eliminó: quedó huérfana cuando la hoja completa pasó a <select>
   para elegir la cuenta. Verificado, cero usos en el marcado. */

/* ---------------------------------------------------------------- sheet -- */

.backdrop {
  position: fixed;
  inset: 0;
  background: var(--app-backdrop);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6);
  z-index: var(--z-backdrop);
  animation: fade var(--duration-fast) var(--ease-standard);
}
.backdrop[hidden] { display: none; }

.sheet {
  width: min(520px, 100%);
  max-height: min(86vh, 900px);
  background: var(--app-surface);
  border: 1px solid var(--app-line);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: rise var(--duration-fast) var(--ease-standard);
}
.sheet-head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 18px var(--space-5) var(--space-3);
}
.sheet-head h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 20px;
  letter-spacing: var(--tracking-heading);
  margin: 0;
  flex: 1;
}
.icon-btn {
  width: 34px; height: 34px;
  border-radius: var(--radius-pill);
  background: var(--app-surface-2);
  color: var(--app-text-2);
  display: flex; align-items: center; justify-content: center;
  flex: none;
  transition: background var(--duration-fast) var(--ease-standard);
}
.icon-btn:hover { background: var(--app-surface-3); color: var(--app-text); }
.sheet-body {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: var(--space-2) var(--space-5) var(--space-4);
  overflow-y: auto;
  flex: 1;
}
.sheet-foot {
  padding: var(--space-3) var(--space-5) var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  border-top: 1px solid var(--app-line);
}

/* ---------------------------------------------------- registro rápido -- */

/* Lo que la hoja rápida da por supuesto —fecha, hora y tasa vigente— se
   enuncia arriba en una línea. Es un dato, no un campo: si estuviera como
   input habría que leerlo antes de escribir el monto. */
.quick-context {
  font-size: 11px;
  color: var(--app-muted);
  font-variant-numeric: tabular-nums;
}

/* Cuenta corrida de la ronda: al anotar cuatro pagos seguidos lo que se
   pierde de vista es cuántos van, y el deshacer tiene que estar donde se
   nota el error, no en otra vista. */
.quick-tally {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: 10px var(--space-3);
  border-radius: var(--app-radius-item);
  background: var(--app-accent-soft);
  border: 1px solid var(--app-accent-line);
}
.quick-tally-main { flex: 1; min-width: 0; font-size: 12px; color: var(--app-text-2); }
.quick-tally-main strong { color: var(--app-text); }

@keyframes fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes rise { from { opacity: 0; transform: translateY(12px) } to { opacity: 1; transform: none } }

/* ---------------------------------------------------------------- toast -- */

/* El toast se queda oscuro a propósito: sobre una interfaz clara es lo que
   lee como confirmación puntual sin robarle jerarquía al contenido. */
.toast {
  position: fixed;
  left: 50%;
  bottom: 28px;
  transform: translate(-50%, 20px);
  background: var(--app-toast-bg);
  border: 1px solid transparent;
  color: var(--app-toast-ink);
  border-radius: var(--radius-pill);
  padding: 12px 20px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: var(--shadow-md);
  z-index: var(--z-toast);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }

/* -------------------------------------------------------------- responsive */

@media (max-width: 1180px) {
  /* .kpis es flex: se baja la base de la tarjeta, no columnas. .kpi-strip no
     aparece acá a propósito: sus cuatro columnas no cambian con el ancho. */
  .kpis > .kpi { flex-basis: 150px; }
  .grid-chart { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 900px) {
  .sidebar { display: none; }

  .app { flex-direction: column; }

  /* El fondo tiene que despejar la tabbar Y los dos FAB apilados, o el último
     dato del scroll queda debajo del botón. Se deriva de --fab-stack en vez de
     un valor a ojo, así que si cambia el tamaño de los FAB esto acompaña. */
  .view {
    padding: 0 22px calc(var(--tabbar-h) + var(--fab-stack) + var(--space-4)
                         + env(safe-area-inset-bottom));
    gap: var(--space-4);
  }

  .topbar {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: var(--space-3);
    padding: 18px 22px 0;
  }
  .topbar-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  /* Sin barra lateral, el selector de tema vive acá, al lado del avatar. */
  .topbar-side { display: flex; align-items: center; gap: var(--space-2); flex: none; }
  .topbar-label { font-size: 12px; color: var(--app-muted); }
  .topbar-total {
    font-family: var(--font-display);
    font-weight: 600;
    font-size: 28px;
    letter-spacing: var(--tracking-heading);
    font-variant-numeric: tabular-nums;
  }
  .topbar .avatar { width: 38px; height: 38px; font-size: 14px; }

  .page-head { display: none; }
  /* La cabecera del histórico sí se muestra: es la que trae el botón de volver.
     El nombre de la tasa no se repite acá porque la topbar ya lo dice justo
     encima y en mayor tamaño; queda el volver, el subtítulo y las acciones. */
  .page-head.is-mobile { display: flex; margin-top: var(--space-4); }
  .detail-head .page-title h1 { display: none; }

  /* En la ficha de tasa los botones se reparten el ancho: la mano toca al
     centro de la pantalla, no en la esquina derecha. Y acá los iconos SÍ suben
     a 44: en la tabla de escritorio son secundarios y densos porque el ratón
     apunta fino, pero en la ficha táctil son el único acceso a la tasa y 30px
     es un objetivo que se falla. Con 44 la fila entra igual en una línea
     (btn elástico + 3 iconos = 342px de ancho útil a 414). */
  .rate-item .row-actions { justify-content: stretch; }
  .rate-item .row-actions .btn { flex: 1; justify-content: center; padding-inline: var(--space-3); }

  /* Se sale del padding de .view con margen negativo para que el scroll táctil
     llegue al borde de la pantalla, y lo recupera como padding propio. */
  /* En móvil la barra de periodo es un bloque suelto dentro de .view, así que se
     fija ella misma. La topbar de arriba sí se va con el scroll: lo que tiene
     que quedar al alcance es el control, no el encabezado. El fondo es opaco
     porque el contenido pasa justo por debajo. */
  .segmented {
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
    background: var(--app-bg);
    border: 0;
    gap: var(--space-2);
    overflow-x: auto;
    scrollbar-width: none;
    margin: 0 -22px;
    padding: var(--space-2) 22px;
    box-shadow: none;
    /* Misma razón que en .page-head: separa la capa fija del contenido que le
       pasa por debajo. */
    border-bottom: 1px solid var(--app-line);
  }
  .segmented::-webkit-scrollbar { display: none; }
  /* En móvil la barra fija es el segmentado, más baja que .page-head. */
  :root { --head-h: 56px; }
  .segmented button { background: var(--app-surface); border: 1px solid var(--app-line); }

  .kpis { gap: 10px; }
  .kpis > .kpi { flex-basis: 104px; }
  .kpi { gap: 2px; }
  .card { padding: var(--space-3) 14px; }
  .kpi-label { font-size: 10px; }
  /* Se mantiene el escalado por tarjeta; en móvil sólo baja el techo. */
  .kpi-value { font-size: clamp(13px, 9.2cqi, 20px); }
  .kpi-value-sm { font-size: 15px; }
  .kpi-foot { font-size: 11px; }

  .tabbar {
    display: flex;
    justify-content: space-around;
    align-items: center;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--app-nav-bg);
    backdrop-filter: blur(12px);
    border-top: 1px solid var(--app-line);
    z-index: var(--z-nav);
  }
  .tab {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    color: var(--app-muted);
    font-size: 10px;
    font-weight: 500;
    padding: 6px 10px;
    flex: 1;
  }
  /* Mismo patrón que el tabbar del kit de producto: la etiqueta activa en
     tinta y el acento sólo en el icono. */
  .tab[aria-current="page"] { color: var(--app-text); font-weight: 600; }
  .tab[aria-current="page"] svg { color: var(--app-accent); }

  .fab {
    display: flex;
    position: fixed;
    right: 22px;
    bottom: calc(var(--tabbar-h) + 18px + env(safe-area-inset-bottom));
    width: 56px; height: 56px;
    border-radius: var(--radius-pill);
    background: var(--app-accent);
    color: var(--on-accent);
    border: 1px solid var(--app-accent-edge);
    align-items: center;
    justify-content: center;
    box-shadow: var(--shadow-accent);
    z-index: var(--z-fab);
    transition: transform var(--duration-fast) var(--ease-standard);
  }
  .fab:active { transform: scale(.94); }


  .backdrop { padding: 0; align-items: flex-end; }
  .sheet {
    width: 100%;
    max-height: 92dvh;
    border-radius: var(--radius-xl) var(--radius-xl) 0 0;
    border-bottom: 0;
    animation: slide-up var(--duration-fast) var(--ease-standard);
  }
  .sheet-foot { padding-bottom: calc(var(--space-5) + env(safe-area-inset-bottom)); }

  /* La tarjeta que envuelve la lista de movimientos se aplana en móvil:
     los ítems ya son tarjetas y anidar dos superficies iguales se ve plano. */
  .table-card.is-plain { background: none; border: 0; overflow: visible; box-shadow: none; }

  /* Sin esa tarjeta el paginador se quedaría con una línea suelta colgando bajo
     la lista, así que ahí se dibuja como tarjeta propia. */
  .table-card.is-plain .pager {
    margin-top: var(--space-2);
    padding: var(--space-3) 14px;
    border: 1px solid var(--app-line);
    border-radius: var(--app-radius-card);
    background: var(--app-surface);
  }
  /* El conteo se lleva su propia línea; abajo quedan los controles y el tamaño.
     De la tira de números sólo sobrevive la página actual: el resto no cabe
     junto al selector sin volverse un objetivo táctil imposible. */
  .pager { gap: var(--space-2); }
  .pager-count { order: -1; width: 100%; }
  .pager-num:not([aria-current="page"]),
  .pager-gap { display: none; }
  .pager-controls { gap: var(--space-2); }
  .pager-num, .pager-nav { min-width: 38px; height: 38px; }
  .table-card.is-plain .table-head { padding: 0 0 var(--space-3); border-bottom: 0; }
  .table-head { padding: var(--space-3) 14px; }

  .toast { bottom: calc(var(--tabbar-h) + 24px + env(safe-area-inset-bottom)); }

  /* La cuenta corrida toma su propia línea (la franja ya envuelve) y el deshacer
     baja con su ancho natural. Medido a 390 y 360px: así el texto se lee de un
     tirón en una línea, mientras darle flex-grow al botón lo partía en cuatro
     —el que cede es el texto, porque .quick-tally-main tiene min-width: 0— y
     estirar el botón a ancho completo lo dejaba con la misma forma y ancho que
     "Guardar y seguir" justo encima, invitando al toque equivocado. */
  .quick-tally-main { flex-basis: 100%; }

  /* Objetivos de toque: en móvil todo lo accionable llega a 44px. Antes .btn
     no-block medía 38, .icon-btn 34 y el paginador 38.

     Va por min-height y no por padding: el alto de un .btn depende de su
     contenido, porque line-height normal en Aventa a 13px da 16px de caja y un
     botón con icono da otra. Subir el padding a 13px dejaba en 44 los botones de
     sólo texto pero en 42 los que llevan SVG ("Nueva tasa"), medido. min-height
     los iguala sea cual sea el contenido, y el align-items: center de .btn ya lo
     mantiene centrado. .btn-block queda afuera: mide 51 y su padding es
     intencional. */
  .btn:not(.btn-block) { min-height: 44px; }
  /* Un solo tamaño para todos los botones de icono en móvil, sin excepciones ni
     variantes: acá todo es dedo. Con la base de 34px de escritorio, ésta es la
     única distinción de tamaño que hace falta en toda la app. */
  .icon-btn { width: 44px; height: 44px; }
  .pager-num, .pager-nav { min-width: 44px; height: 44px; }
  /* Los select de filtro eran los más chicos de la app con 25px, y son controles
     reales (cuenta, banco, tamaño de página). El padding lateral se conserva
     porque la flecha vive en el padding derecho. */
  .select-chip { min-height: 44px; }
  /* Las pastillas de cuenta y de tasa de la hoja rápida: median 38px y son el
     control MÁS tocado de la app, una o dos veces por registro cuando se anotan
     pagos en ráfaga. Más que cualquier botón. */
  .pill { min-height: 44px; }
}

@keyframes slide-up { from { transform: translateY(100%) } to { transform: none } }

/* En escritorio la lista compacta de movimientos cede el paso a la tabla. */
@media (min-width: 901px) {
  .mobile-only { display: none !important; }
}
@media (max-width: 900px) {
  .desktop-only { display: none !important; }
}

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

/* ── La negrita falsa dejó de existir ─────────────────────────────────────
   Del branding anterior sólo había llegado `SigniaPro-Regular.ttf`, así que
   los seis lugares que pedían 700 los sintetizaba el navegador engordando el
   trazo, y los números grandes tuvieron que emigrar a la familia de cuerpo
   para tener un bold real.

   Chillax llega con 400, 500, 600 y 700 en archivos propios: la corrección
   completa se borra y cada título vuelve a su familia y a su peso —600, que
   es el que el design system pide para titulares—. Las cifras grandes vuelven
   a la display, que es donde la marca las quiere. */
