/* ============================================================
   Flow — Base + Componentes reutilizables
   Requiere design-tokens.css. CSS vainilla, sin dependencias.
   Legibilidad y contraste primero (piso WCAG AA).
   ============================================================ */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  /* Raíz de la escala tipográfica (§8.5). 17px = base normal, subida desde 16.
     Defendible para una aplicación que se lee a diario y donde cada línea es un
     dato, no prosa decorativa. Los cuatro niveles de preferencia multiplican esta
     base, así que se recalculan solos: 17 · 21.25 · 25.5 · 29.75.

     MEDIDO ANTES DE APLICARLO: la densidad de escritorio NO cambia. Una fila del
     árbol pasa de 61 a 62 px y siguen entrando 14 a 1080p, porque su alto lo manda
     el objetivo táctil de 44 px de los botones, no el texto. No hizo falta
     compensar apretando el relleno vertical. */
  font-size: calc(17px * var(--escala-texto, 1));
}

body {
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-normal);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
}

/* Números SIEMPRE tabulares: columnas de dinero que alinean. */
.num, table, .stat-value, input[inputmode="decimal"] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

h1, h2, h3 { line-height: var(--lh-tight); text-wrap: balance; font-weight: var(--fw-semibold); }
h1 { font-size: var(--fs-xl); }
h2 { font-size: var(--fs-lg); }
h3 { font-size: var(--fs-md); }

a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--brand-strong); text-decoration: underline; }

::selection { background: var(--brand-soft); color: var(--text); }

/* Foco visible SIEMPRE (accesibilidad, no negociable)

   `summary` ENTRA EN LA LISTA, y no como parche. Es tabulable por naturaleza
   —es el control que abre un `<details>`— y estaba fuera, así que **cinco
   puntos de la aplicación eran invisibles con teclado**: el árbol de catálogos,
   los widgets de Clientes, la cabecera de filtros, el listado de facturas y el
   selector de categoría. Meterlo aquí los cubre los cinco y cubre los que
   vengan; cinco parches en cinco sitios son la deuda que se acaba de pagar en
   otra forma.

   `:where()` sigue con ESPECIFICIDAD CERO a propósito: la idea es que cualquier
   componente pueda dar su propio foco sin pelearse con esta. Se comprobó que
   hoy nadie la pisa —ni un `outline: none` en toda la hoja— y el auditor lo
   vigila. Si algún día hiciera falta más peso, se sube diciéndolo. */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--focus-width) solid var(--focus-color);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* Etiqueta mayúscula con tracking */
.label {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-secondary);
  font-weight: var(--fw-medium);
}
/* ─── LA MARCA DE OBLIGATORIO SE VEÍA COMO UNA MOTA ───────────────────────
 *
 * Los 35 rótulos obligatorios del producto llevan `<span aria-hidden="true">*
 * </span>` más un `sr-only` con «(obligatorio)». Quien usa lector de pantalla
 * lo oye; quien mira la pantalla tenía **toda** la señal en un asterisco
 * medido el 9 de agosto de 2026 en 9 × 20 px, del MISMO tamaño y del MISMO
 * gris —#a7b6c2— que el resto del rótulo, al final de una etiqueta alineada a
 * la derecha. Para quien no ve bien, eso no es un aviso: es una mota.
 *
 * Se le da peso y cuerpo —no color a secas (§8.5)—: el asterisco crece, se
 * pone en negrita y se separa. El `sr-only` sigue donde estaba, así que la
 * información no depende de ver la marca ni de distinguir su tono.
 *
 * Es hijo DIRECTO de `.label` y sin clase: los únicos `span[aria-hidden]` que
 * cumplen las dos cosas son los 35 asteriscos. `.monto-simbolo` es hermano del
 * rótulo, no hijo, y se comprobó antes de escribir el selector. */
.label > span[aria-hidden="true"] {
  font-size: 1.15em;
  font-weight: var(--fw-bold);
  color: var(--text);
  margin-inline-start: 0.15em;
}
.caption { font-size: var(--fs-caption); color: var(--text-muted); }

/* ---------- Layout raíz ---------- */
.app-shell {
  /* `width: 100%` es imprescindible, no decorativo: `body` es un contenedor
     flex en columna, y un ítem flex con `margin-inline: auto` se dimensiona por
     su CONTENIDO en vez de estirarse, así que el `max-width` no llegaba a
     aplicarse nunca. Sin esta línea el contenido quedaba en 1074px dentro de una
     ventana de 2178px, con el max-width computado correctamente en 1760px.
     Detectado mirando la página renderizada; por cálculo no se veía. */
  width: 100%;
  max-width: var(--maxw-app);
  margin-inline: auto;
  padding: var(--space-5);
}
.stack   { display: flex; flex-direction: column; }
.row     { display: flex; align-items: center; }
.gap-2 { gap: var(--space-2); } .gap-3 { gap: var(--space-3); }
.gap-4 { gap: var(--space-4); } .gap-5 { gap: var(--space-5); }
.grid { display: grid; gap: var(--space-4); }
.spacer { flex: 1 1 auto; }

/* ---------- Barra superior ---------- */
.topbar {
  display: flex; align-items: center; gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 10;
}
.brand-mark {
  display: inline-flex; align-items: center; gap: 10px;
  color: var(--text);
  /* SEPARADO DE LOS ACCESOS RÁPIDOS. Iban pegados, y el nombre del sistema y el
     primer destino se leían como una sola fila de palabras. */
  margin-inline-end: var(--space-5);
}
/* La palabra: texto vivo, 19 px, peso medio y un espaciado mínimo. En color de
   texto y NO en azul — en esta barra el azul ya significa sección activa y
   botón de registrar (§8.5). */
.brand-palabra {
  /* 19 → 29 px: el 50 % que pidió Kike el 5 de agosto de 2026. El isotipo sube
     de 26 a 39 px, que es la misma proporción, para que no se descompensen. */
  font-size: 29px; font-weight: var(--fw-medium); letter-spacing: 0.015em;
}
.brand-isotipo { flex: none; display: block; }
/* Dos piezas, una visible cada vez. La de caja por defecto; la de barras sueltas
   en alto contraste, donde un fondo de color medio RESTA contraste en lugar de
   sumarlo. Se cambia la pieza, no el color. */
.brand-isotipo--mono { display: none; color: var(--text); }
:root[data-contrast="high"] .brand-isotipo--caja { display: none; }
:root[data-contrast="high"] .brand-isotipo--mono { display: block; }

/* Por debajo de 640 px queda solo el isotipo: con seis secciones y el nombre de
   usuario compitiendo, la palabra es lo primero que sobra. La regla de escritorio
   va PRIMERO y la móvil después, que es el orden que §8.1 exige y el auditor
   comprueba. */
@media (max-width: 640px) {
  .brand-palabra { display: none; }
}

/* ---------- Tarjeta ----------
   ─── EL BORDE ERA `--border` Y NO LLEGABA AL MÍNIMO (§8.5) ────────────────
   Medido el 7 de agosto de 2026 en la ficha de la E001-2339: `#2a3540` daba
   **1,36:1 contra la propia tarjeta y 1,48 contra la página**, con un suelo de
   3:1. Un borde que no se distingue del fondo es una caja que no se ve, y Kike
   lo dijo con otras palabras: «no se nota que aquí es donde se asignan costos».

   ─── Y EL ARREGLO NO ERA SUBIR `--border` ─────────────────────────────────
   `--border` es el token de los **hilos DENTRO de una superficie** —separadores
   de filas, reglas entre campos— y lo usan 40 reglas. Subirlo a 3:1 haría gritar
   cada divisoria de tabla, que no es lo que la norma pide de un hilo decorativo.

   El borde EXTERIOR de una tarjeta no es un hilo dentro de una superficie: es el
   **límite de la superficie**, la línea que dice dónde empieza y acaba un área.
   Ése es el que la norma exige a 3:1, y `--border-strong` ya está medido para
   eso: 3,74:1 sobre la tarjeta y 4,08 sobre la página en oscuro; 3,94 y 3,67 en
   claro; 5,14 y 5,61 en alto contraste. Así que la tarjeta estaba usando el
   token equivocado, no el token estaba mal. Se cambia el uso y `--border`
   conserva su oficio.

   Y EL DEFECTO ESTABA EN LOS CUATRO TEMAS, alto contraste incluido: `--border`
   daba ahí **2,94:1**, por debajo del suelo, en el tema que existe justamente
   para que esto no pase.

   ALCANCE, dicho con el número porque mueve pantallas aprobadas: llega a las
   **34 vistas** que dibujan tarjetas —Movimientos y Facturas entre ellas—. En
   todas es la misma mejora: el área se ve. */
.card {
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
  box-shadow: var(--shadow-1);
}
.card--flush { padding: 0; overflow: hidden; }
.card-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-3); margin-bottom: var(--space-4);
}

/* ---------- Indicador / NÚMERO GRANDE ----------
   Jerarquía: etiqueta pequeña arriba, número dominante, contexto debajo.
   El número es lo único a tamaño display en toda la pantalla. */
.stat { display: flex; flex-direction: column; gap: var(--space-2); }
.stat-value {
  font-family: var(--font-display);
  /* El token de la cifra de widget, no `--fs-display` directo: valen lo mismo
     hoy, pero **una regla que apunta al token cambia con él** y ésta no. */
  font-size: var(--fs-cifra-widget);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--text);
}
.stat-value .currency {
  font-size: 0.42em; font-weight: var(--fw-semibold);
  color: var(--text-secondary); margin-right: 0.2em; vertical-align: 0.35em;
}
.stat-sub { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.stat-mini { display: flex; flex-direction: column; gap: var(--space-micro); }
.stat-mini .amt { font-size: var(--fs-md); font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; }

/* ---------- Señal semántica (nunca color solo) ----------
   Ingreso/gasto se distinguen por: color + signo (+/−) + flecha + posición. */
.amount { font-variant-numeric: tabular-nums; font-weight: var(--fw-semibold); white-space: nowrap; }
.amount--in  { color: var(--income-ink); }
.amount--out { color: var(--expense-ink); }
.amount--in::before  { content: "▲ +"; font-size: 0.8em; }
.amount--out::before { content: "▼ −"; font-size: 0.8em; }

.badge {
  display: inline-flex; align-items: center; gap: var(--space-mini);
  padding: var(--space-micro) 10px; border-radius: var(--radius-pill);
  font-size: var(--fs-caption); font-weight: var(--fw-semibold);
  border: 1px solid transparent;
}
.badge::before { content: ""; width: var(--space-2); height: var(--space-2); border-radius: 50%; background: currentColor; }
/* El TEXTO usa la tinta (`-ink`), que existe justo para eso; el BORDE usa el
   color semántico, que ahí es señal y no texto. Antes el texto usaba el color
   semántico y en tema claro se quedaba en 2.85:1 sobre su propio fondo teñido:
   un verde calibrado para fondo oscuro no sobrevive sobre blanco. Medido, no
   estimado — el punto ciego era que el badge nunca se había visto renderizado en
   claro. */
.badge--in   { color: var(--income-ink);  background: var(--income-bg);  border-color: var(--income); }
.badge--out  { color: var(--expense-ink); background: var(--expense-bg); border-color: var(--expense); }
.badge--warn { color: var(--warning-ink); background: var(--warning-bg); border-color: var(--warning); }
.badge--neutral { color: var(--text-secondary); background: var(--surface-2); border-color: var(--border); }

/* ---------- `hidden` tiene que ganar ----------
   El atributo `hidden` del HTML se apoya en `[hidden] { display: none }` de la
   hoja del navegador, que pierde contra CUALQUIER regla de clase que fije
   `display`. `.field` es `display: grid`, así que un `<div class="field" hidden>`
   se veía igual — el campo de tasa de cambio aparecía en un movimiento en soles
   con soles como moneda funcional.
   Detectado comparando la captura con el DOM: `element.hidden` decía `true` y la
   pantalla lo mostraba. Por cálculo no se veía. */
[hidden] { display: none !important; }

/* ---------- Iconos (sprite SVG) ----------
   ESTAS TRES DECLARACIONES SON EL MECANISMO COMPLETO, no decoración.

   `stroke`, `fill`, `stroke-width` y los remates son propiedades HEREDADAS, así
   que cruzan la frontera del shadow tree que crea `<use>` y llegan a las formas
   del sprite, que a propósito no declaran ninguna. De ahí salen las dos razones
   por las que se eligió sprite:

     · `stroke: currentColor` → el icono toma el color del texto que lo rodea, y
       por tanto cambia con el tema sin una regla por tema ni una línea de JS.
     · medidas en `em` → el icono sigue la escala de texto del usuario, porque la
       escala mueve el tamaño de fuente de la raíz.

   Como el relleno es `none`, si la herencia fallara no se dibujaría NADA. Un
   icono visible es prueba de que funcionó; no hay que creer en ello. */
.ico {
  inline-size: 1em; block-size: 1em;
  flex: none;                      /* dentro de un flex no debe encogerse */
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  vertical-align: -0.145em;        /* alineado óptico con la altura de x */
}
.ico--grande { inline-size: 1.5em; block-size: 1.5em; }
.ico--indicador { inline-size: 2em; block-size: 2em; }

/* Alto contraste: el trazo engorda. Un contorno de 1.75 sobre fondo puro tiene
   menos peso visual del que necesita quien pidió alto contraste. */
:root[data-contrast="high"] .ico { stroke-width: 2.25; }

/* En las escalas de texto grandes el trazo se afina en proporción: a 1.75 sobre
   un icono de 28px el dibujo se ve endeble. */
:root[data-escala="muy_grande"] .ico,
:root[data-escala="maximo"] .ico { stroke-width: 1.55; }
:root[data-contrast="high"][data-escala="muy_grande"] .ico,
:root[data-contrast="high"][data-escala="maximo"] .ico { stroke-width: 2; }

/* ---------- Botones ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  font: inherit; font-weight: var(--fw-semibold); line-height: 1;
  /* EL OBJETIVO TÁCTIL, DE VERDAD. El token declaraba 44 px y ningún botón lo
     usaba: el relleno los dejaba en 40. Se midió en pantalla y salieron los
     tres visibles —Salir, Guardar, Cancelar— por debajo del mínimo. No era de
     un formulario: era de todos los botones de Flow. */
  min-height: var(--tactil-minimo);
  padding: 10px 16px; border-radius: var(--radius-md);
  border: 1px solid transparent; cursor: pointer;
  background: var(--surface-2); color: var(--text);
  transition: background-color .12s ease, border-color .12s ease, transform .04s ease;
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--brand-boton); color: var(--on-brand); }
.btn--primary:hover { background: var(--brand-strong); }
.btn--secondary { background: transparent; border-color: var(--border-strong); color: var(--text); }
.btn--secondary:hover { background: var(--surface-hover); }
.btn--ghost { background: transparent; color: var(--text-secondary); }
.btn--ghost:hover { background: var(--surface-hover); color: var(--text); }
.btn--danger { background: transparent; border-color: var(--expense); color: var(--expense-ink); }
.btn--danger:hover { background: var(--expense-bg); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
/* ─── UN BOTÓN-ICONO CUMPLE EL MÍNIMO TÁCTIL POR SER LO QUE ES ─────────────
   Los 44 px de §8.5 estaban escritos en DOS sitios con alcance distinto —dentro
   de `.acciones-fila` y dentro de `.dlg-cabecera`— y en ninguno de los dos
   caía el lápiz del listado de Movimientos. Al quitarle la palabra «Editar»
   el 6 de agosto de 2026 se quedó en **19 px de ancho**: sin texto que lo
   ensanchara, y sin regla que lo sostuviera.
   Un icono sin texto mide lo que mide su icono, así que el mínimo le
   corresponde por definición y no por dónde esté. Se dice aquí una vez y las
   dos copias con alcance se retiran. */
.btn--icon {
  padding: var(--space-2);
  min-inline-size: var(--tactil-minimo); min-block-size: var(--tactil-minimo);
}

/* ═══════════ UN ENLACE DISFRAZADO DE BOTÓN SIGUE SIENDO UN BOTÓN ═══════════
 *
 * `a:hover` —arriba del archivo— cambia el color a `--brand-strong` y subraya.
 * Para un enlace de texto es lo correcto. Para los 43 enlaces que llevan la
 * clase `.btn` **le gana a la regla del botón**, porque `a:hover` es (0,1,1) y
 * `.btn--primary` es (0,1,0): un elemento más un pseudo-clase pesa más que una
 * clase sola.
 *
 * ─── Y EN EL PRIMARIO ESO DEJA EL BOTÓN EN BLANCO ─────────────────────────
 *
 * Medido el 5 de agosto de 2026 sobre «Registrar», el botón principal de
 * Movimientos: al pasar el ratón, `background` pasa a `--brand-strong` por
 * `.btn--primary:hover` y `color` pasa a `--brand-strong` por `a:hover`. Los
 * dos valores acaban siendo `rgb(76,136,245)` — **texto del mismo color que su
 * fondo**. La palabra desaparece. Se ve en la captura: un rectángulo azul
 * vacío donde estaba la acción principal de la pantalla.
 *
 * No lo veía ningún auditor: el de contraste recorre el estado en reposo, y en
 * reposo el botón está bien. Solo aparece apuntando con el ratón.
 *
 * `.btn--ghost` ya se salvaba por casualidad —su `:hover` declara `color`, y
 * eso lo sube a (0,2,0)—. Los otros tres no.
 *
 * Cada variante recupera EL SUYO, no uno común: el peligroso es el primario,
 * pero `--secondary` y `--danger` también se volvían azules al apuntarlos.
 * El orden importa: la base primero y las variantes después, porque todas
 * pesan igual. */
a.btn:hover { text-decoration: none; color: var(--text); }
a.btn--primary:hover   { color: var(--on-brand); }
a.btn--secondary:hover { color: var(--text); }
a.btn--ghost:hover     { color: var(--text); }
a.btn--danger:hover    { color: var(--expense-ink); }

/* ---------- Tabla de movimientos ---------- */
.table-wrap { overflow-x: auto; }               /* nunca scroll horizontal del body */
table.movs { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
.movs thead th {
  text-align: left; font-size: var(--fs-label); letter-spacing: var(--tracking-label);
  text-transform: uppercase; color: var(--text-secondary); font-weight: var(--fw-medium);
  padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border);
  /* Sin `nowrap`: una etiqueta larga como «Es la moneda funcional» mide ~240px
     en mayúsculas con tracking y, si no puede partirse, empuja la tabla hasta
     forzar scroll horizontal. §8.1 lo prohíbe en datos financieros, así que el
     encabezado cede en dos líneas antes que la tabla desborde. */
  vertical-align: bottom; overflow-wrap: anywhere; hyphens: auto;
}
.movs tbody td { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--border); vertical-align: middle; }

/* Densidad de escritorio: con ancho de sobra, lo escaso es el ALTO. Bajar el
   relleno vertical de 12 a 7px sube de 12 a 16 filas visibles en 1080p, que es
   la diferencia entre ver media página y ver casi toda. El objetivo táctil no
   se resiente: en escritorio se apunta con ratón. */
@media (min-width: 1024px) and (pointer: fine) {
  .movs thead th { padding-top: var(--space-2); padding-bottom: var(--space-2); }
  .movs tbody td { padding-top: 7px; padding-bottom: 7px; }
}
.movs tbody tr:hover td { background: var(--surface-2); }
.movs .col-amt { text-align: right; }

/* La columna de acciones no debe imponer ancho: sus botones se apilan antes de
   empujar la tabla. */
.movs .col-acciones { white-space: normal; }
.movs .col-acciones .row { flex-wrap: wrap; row-gap: var(--space-2); }
.movs .concept { font-weight: var(--fw-medium); }
.movs .meta { color: var(--text-muted); font-size: var(--fs-caption); }

/* ---------- Formulario ---------- */
.field { display: flex; flex-direction: column; gap: var(--space-mini); }
.field > label { font-size: var(--fs-label); color: var(--text-secondary); font-weight: var(--fw-medium); }
.input, .select {
  font: inherit; color: var(--text);
  background: var(--bg-sunken); border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); padding: 10px 12px; width: 100%;
}
.input::placeholder { color: var(--text-muted); }
/* EL `outline` TRANSPARENTE NO ES CÓDIGO MUERTO — no lo borres.
   El anillo de foco lo dibuja el `box-shadow`, y en modo de COLORES FORZADOS
   —el alto contraste de Windows— el navegador DESCARTA las sombras: el foco
   desaparecería justo para quien más lo necesita. Ese modo sí respeta el
   `outline` y le sustituye el color por el del sistema, así que un outline
   transparente es invisible aquí y visible allá.
   Es el patrón estándar para este caso y se escribe porque, sin esta nota,
   «outline transparente» parece un descuido y alguien lo quita en una
   limpieza. */
.input:focus, .select:focus {
  border-color: var(--brand);
  outline: var(--focus-width) solid transparent;
  outline-offset: 2px;
  box-shadow: 0 0 0 3px var(--brand-ring);
}
.input-money { position: relative; }
.input-money .prefix {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--text-muted); pointer-events: none;
}
.input-money .input { padding-left: 34px; font-variant-numeric: tabular-nums; }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-4); }

/* ---------- Gráfico ---------- */
.chart { width: 100%; }
.chart svg { display: block; width: 100%; height: auto; }
.chart .bar { transition: opacity .12s ease; }
.chart .bar:hover { opacity: 0.82; cursor: pointer; }
.chart .grid-line { stroke: var(--grid); stroke-width: 1; }
.chart .zero-line { stroke: var(--axis); stroke-width: 1.5; }
.chart .x-label { fill: var(--text-muted); font-size: var(--fs-caption); }
.chart-tooltip {
  position: absolute; pointer-events: none; opacity: 0;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm); padding: 6px 10px; font-size: var(--fs-caption);
  box-shadow: var(--shadow-2); transition: opacity .1s ease; white-space: nowrap; z-index: 20;
}
.legend { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.legend .item { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-caption); color: var(--text-secondary); }
.legend .swatch { width: 12px; height: 12px; border-radius: 3px; }

/* ---------- Toggles de tema/contraste ---------- */
.toggle-group { display: inline-flex; border: 1px solid var(--border-strong); border-radius: var(--radius-md); overflow: hidden; }
.toggle-group button { background: transparent; border: 0; color: var(--text-secondary); padding: 7px 11px; cursor: pointer; font: inherit; }
.toggle-group button[aria-pressed="true"] { background: var(--brand-soft); color: var(--text); }

/* ---------- Utilidades ---------- */
.hidden { display: none !important; }
.text-secondary { color: var(--text-secondary); }
.text-muted { color: var(--text-muted); }

/* ---------- Responsive ---------- */
@media (max-width: 720px) {
  .app-shell { padding: var(--space-4); }
  .form-grid { grid-template-columns: 1fr; }
  .hero-grid { grid-template-columns: 1fr !important; }
  .topbar { gap: var(--space-3); padding: var(--space-3) var(--space-4); }
  .topbar .hide-sm { display: none; }
}

/* ---------- Movimiento reducido ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; }
}
