/* ============================================================================
   Agrupa — sistema de diseño
   ----------------------------------------------------------------------------
   Portado del dashboard de tiendapp. Este archivo define los tokens y las
   piezas que NO dependen de módulos ni de permisos: lienzo, tipografía,
   tarjetas, formularios, botones y los orbes de fondo.

   El kernel de UI (P0-c) construye el shell sobre estos tokens; no los
   redefine. Si un componente necesita un color nuevo, se agrega aquí como
   variable, nunca como literal en una plantilla.
   ========================================================================= */

/* ── Inter, vendorizada ────────────────────────────────────────────────────
   Sin CDN, por la misma razón que HTMX y Alpine: §5.5 del spec de P0-c2 dice
   que varios clientes van a correr detrás de redes cerradas. Una hoja de
   estilo de un tercero es peor que un script: `<link rel=stylesheet>` BLOQUEA
   el render, así que detrás de una red que no resuelve fonts.googleapis.com
   la pantalla se queda en blanco hasta que el navegador se rinde.

   Solo los subconjuntos `latin` y `latin-ext`: es un ERP colombiano, y los
   quince archivos de cirílico, griego y vietnamita no los pide nadie.
   ------------------------------------------------------------------------ */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inter-400-latin-ext.01ba6c2a184b.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/inter-400-latin.65850a373e25.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/inter-500-latin-ext.01ba6c2a184b.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../fonts/inter-500-latin.65850a373e25.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/inter-600-latin-ext.01ba6c2a184b.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/inter-600-latin.65850a373e25.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/inter-700-latin-ext.01ba6c2a184b.woff2") format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("../fonts/inter-700-latin.65850a373e25.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    /* Marca */
    --primary: #7c5cf0;
    --primary-dark: #5b3ec8;
    --primary-soft: #f1edfe;
    /* El contorno del bloque morado suave. `--border` es gris y sobre
       `--primary-soft` se lee como suciedad; `--primary` a plena fuerza
       convierte un enlace en un botón. Éste es el borde que la caja de la
       factura necesita: visible, del mismo tinte, sin gritar. */
    --primary-soft-border: #ddd3fb;
    /* «Tus campos» (2026-10-29): la sección de los campos que creó la
       empresa en Campos, tintada para que se distinga de lo que trae
       Agrupa. Lila de la marca, nunca el ámbar o el rojo de un aviso. */
    --campos-tinte: #faf8ff;
    --campos-borde: #cfc3f8;
    /* Cuánto del color claro de un agente tiñe la baldosa de su personaje
       (las tarjetas de un equipo). En oscuro, mucho menos: sobre fondo
       negro el pastel al 60 % se leía como un parche gris. */
    --equipo-tinte-fuerza: 60%;
    /* Las zonas seguras del teléfono, con el mismo nombre que en `pos.css`:
       valen 0 en un navegador de escritorio y lo que mida el notch en la app. */
    --sat: env(safe-area-inset-top, 0px);
    --sab: env(safe-area-inset-bottom, 0px);
    /* El borde de la casilla SIN marcar. NO es `--border-strong`: ése da
       1.5:1 sobre blanco y la casilla hay que buscarla. Hay una prueba que
       calcula la cifra.

       `#9691ac` desde el 2026-08-17: Renzo lo quería MÁS CLARO —pidió
       `#d2cfdf`— y ése da 1.53:1, o sea la casilla que hay que buscar otra
       vez. Éste es lo más claro que sigue pasando el 3:1 que la WCAG 1.4.11
       pide para el contorno de un control: da 3.02:1. Se eligió con la
       calculadora de la propia guarda enfrente, no a ojo. */
    --checkbox-border: #9691ac;

    /* Lienzo y superficies */
    --bg: #f7f6fb;
    --card-bg: #ffffff;
    --canvas: #0d0b16;

    /* El banner de sección va sobre `--canvas`, que es el mismo negro de la
       topbar: la pantalla lee como una pieza y no como dos temas pegados.
       Por eso el par de texto vive acá y no es un hex suelto dentro de la
       regla — el día que el tema cambie, se cambia junto a su fondo.

       `--banner-sub` NO es `--text-light`: aquel está calibrado contra
       blanco y sobre el negro da 3.1:1, que para texto normal es ilegible.
       Hay dos pruebas que calculan las dos cifras. */
    --banner-text: #ffffff;
    --banner-sub: #b3aecb;
    /* La banda es un DEGRADADO de dos morados oscuros, no un negro con un
       resplandor encima. Es la diferencia entre una banda que se ve profunda y
       una que se ve apagada, y son los dos valores exactos de `.page-hero` del
       dashboard de tiendapp. */
    --banner-from: #16132a;
    --banner-to:   #241e42;

    /* Texto */
    --text: #1c1830;
    --text-light: #5d5873;
    --text-faint: #8d88a3;

    /* ── Tipografía ──────────────────────────────────────────────────────
       SIETE pasos, nombrados por PAPEL y no por tamaño. Antes había catorce
       números sueltos repartidos en 1.585 líneas, elegidos de a uno: por eso
       las pantallas no se sentían una pieza — no lo eran.

       Los valores salen de medir el dashboard de tiendapp, que es la
       referencia visual. Ahí también están sueltos —diecinueve tamaños
       distintos en 2.247 líneas— así que lo que se porta no es su hoja sino
       los valores que de hecho funcionan, ya ordenados.

       El paso que más cambia es el cuerpo: 15px → 13px. Todo lo nuestro venía
       un 15% más grande, y esa sola cifra es la mitad de la diferencia.

       Nombrar por papel es lo que vuelve barato el cambio que viene: el día
       que una empresa quiera su propia densidad, se reasignan siete valores y
       no se buscan cuarenta en la hoja. */
    --fs-hero:    34px;   /* el saludo de la portada, y nada más */
    --fs-page:    22px;   /* el título de la pantalla, en el banner */
    --fs-number:  30px;   /* una cifra que ES el contenido */
    --fs-stat:    17px;   /* la cifra de la tira del escritorio: un vistazo,
                             no el contenido. Dos pasos por debajo de
                             `number` — la tira se lee de reojo, y a 22px
                             empujaba la píldora a partirse en dos filas */
    --fs-section: 13.5px; /* el título de una tarjeta o de una sección */
    --fs-body:    13px;   /* texto, celdas, campos, botones */
    --fs-small:   12px;   /* el subtítulo de algo, un dato de apoyo */
    --fs-label:   11.5px; /* el rótulo de un campo */
    --fs-meta:    11px;   /* ayuda, subtítulos, lo secundario */
    --fs-badge:   10px;   /* pastillas y estados, en versalitas */
    /* Un GLIFO, no un texto: el tamaño de un icono dibujado con una letra.
       Está en la escala porque si no queda como el único número suelto de la
       hoja, y un número suelto es por donde vuelven los otros cuarenta. */
    --fs-glyph:   16px;
    --fs-columna: 15px;   /* el nombre de una columna de tablero (Tareas) */

    /* ── Espaciado ───────────────────────────────────────────────────────
       La escala de ESPACIO (Renzo, 2026-10-30): la de Atlassian, sobre una
       base de 8px. El número dice cuánto mide en porcentaje de la base:
       `--space-100` son 8px, `--space-050` la mitad, `--space-150` uno y
       medio. Todo margen, relleno y hueco sale de aquí; un px escrito a mano
       en `margin`, `padding` o `gap` lo frena el portón (1px queda, el
       ajuste óptico de un borde). Para elegir: de 2 a 8 lo chico y apretado
       (ícono y texto, una pastilla, un botón de ícono, la celda); de 12 a 24
       lo de adentro de las tarjetas y los botones; de 32 a 80 el aire entre
       las partes de la página. Guía: `/guia/espacios/`. */
    --space-025: 2px;
    --space-050: 4px;
    --space-075: 6px;
    --space-100: 8px;
    --space-150: 12px;
    --space-200: 16px;
    --space-250: 20px;
    --space-300: 24px;
    --space-400: 32px;
    --space-500: 40px;
    --space-600: 48px;
    --space-800: 64px;
    --space-1000: 80px;
    /* La escala vieja, ordinal (`--sp-1` = 4px … `--sp-8` = 40px), solo como
       ALIAS mientras las ramas que ya estaban abiertas se ponen al día: en el
       repo ya nadie la usa y el portón frena un `var(--sp-…)` nuevo. Se
       borra cuando ninguna rama viva la tenga. */
    --sp-1: var(--space-050);
    --sp-2: var(--space-100);
    --sp-3: var(--space-150);
    --sp-4: var(--space-200);
    --sp-5: var(--space-250);
    --sp-6: var(--space-300);
    --sp-7: var(--space-400);
    --sp-8: var(--space-500);
    /* El respiro lateral del contenido. Es una variable y no un número
       dentro de `.content` porque la portada lo tiene que CANCELAR: los
       orbes van de borde a borde de la aplicación, y para eso necesitan
       saber cuánto padding deshacer. */
    --content-pad: clamp(14px, 2.4vw, 28px);

    /* Bordes y profundidad */
    --border: #e7e4ef;
    --border-strong: #d6d1e4;
    /* La banda del encabezado de una tabla, y la línea entre sus filas.

       Los dos son PROPIOS y no `--bg` y `--border` porque la tabla los usa
       en dosis distintas de todo lo demás: el fondo tiene que separarse del
       blanco de la tarjeta sin llegar a leerse como una fila, y la línea se
       repite quinientas veces —con `--border` la lista se lee como una reja
       y las filas dejan de ser filas—. Tenerlos nombrados es lo que deja
       ajustar la densidad de la tabla sin tocar el resto del ERP. */
    --table-head-bg: #faf9fc;
    --table-line: #f0eef6;
    /* Lo que ABRE una fila: la miniatura de un producto, la inicial de un
       tercero, la baldosa con el icono de una ubicación. Un solo tamaño
       para los tres (Renzo, 2026-08-14).

       Eran DOS familias con dos tamaños: `.cell-thumb` a 36px en cuatro
       plantillas —las que llevan foto— y `.tile-sm` a 26px en treinta y
       cinco. Puestas una debajo de otra al navegar entre listas, las filas
       cambiaban de altura de pantalla en pantalla sin que nada lo
       justificara: la diferencia no decía nada, solo se notaba.

       Token y no un literal porque hay TRES sitios que tienen que valer lo
       mismo: las dos familias y el corrimiento del rótulo de esa columna
       (`.th-lead`), que se calcula a partir de acá. Escritos como literales,
       cambiar el tamaño desalinea el encabezado y nada lo avisa. */
    --cell-thumb: 35px;

    /* El alto de un botón de fila, y el de la BANDA que tiene que poder
       contenerlo.

       Derivado y no dos números sueltos: la banda existe para llevar un
       título y, a veces, los verbos de esa tarjeta. Si el botón crece y la
       banda no, o el botón se sale o la banda da un salto al aparecer — que
       es exactamente lo que pasó el 2026-08-14 al subir `.btn-small` de 22
       a 30: la banda de selección medía 55 y el encabezado que reemplaza
       46, y la tabla brincaba nueve píxeles en cada clic.

       Con el alto atado al del botón, cambiar el tamaño de los botones de
       fila reajusta las bandas solo. El `+ 1px` es el borde inferior. */
    /* El botón de pantalla mide 38 por padding + interlínea; el nombre existe
       para que los tres puntos de la banda midan LO MISMO sin repetir la
       cuenta. */
    --btn-h: 38px;
    --btn-small-h: 30px;
    --band-h: calc(var(--btn-small-h) + var(--space-150) * 2 + 1px);
    --radius: 12px;
    /* Existía en dos pantallas como referencia sin definición —el navegador
       ignoraba el `border-radius` entero y nadie lo notó. Definida el
       2026-08-08 al armar la de reseñas. */
    --radius-sm: 8px;
    --radius-lg: 20px;
    /* El radio de una TARJETA. Era `--radius-lg` (20px) y se veía blando al
       lado de la referencia; tiendapp usa 12px en todas. `--radius-lg` se
       queda para lo que de verdad es grande —el drawer, el shell— porque ahí
       sí lo pide la escala de la pieza. */
    --radius-card: 12px;
    /* El de un control. Más cerrado que el de la tarjeta que lo contiene: al
       revés, el campo se come visualmente su propia caja. */
    --radius-field: 10px;
    --shadow: 0 1px 3px rgba(16, 18, 22, .05), 0 8px 24px rgba(16, 18, 22, .06);
    --shadow-lg: 0 2px 6px rgba(16, 18, 22, .06), 0 18px 48px rgba(16, 18, 22, .10);
    /* El gris de las series que acompañan en una gráfica de Analíticas
       (Renzo, 2026-10-05, con referente: la principal manda). Lo leen
       `analiticas.js` para la línea y `.series-dot-1` para el punto de la
       leyenda, así no se pueden separar. Literal y no `color-mix()`: el
       lienzo no entiende funciones de CSS. */
    --chart-muted: #aeabb9;
    /* Y a dónde llega el trazo de la principal por la derecha: el rosa de
       los orbes. Literal por lo mismo. */
    --chart-accent: #f05ce4;
    /* ── El vidrio de la paleta (Renzo, 2026-09-15) ──────────────────────
       La paleta de ⌘K flota SOBRE la pantalla y no dentro de ella: es la
       única superficie del ERP que se pinta oscura encima de lo que haya
       debajo, así que trae su propio vocabulario en vez de torcer los
       tokens claros de las tarjetas. Si el vidrio no queda, se van estos
       siete y la paleta vuelve a ser una tarjeta. */
    /* 62 % y no 78 (Renzo, 2026-09-15): con más transparencia el desenfoque
       de atrás se nota, que es de lo que vive el vidrio. */
    --glass-bg: rgb(26 24 40 / 62%);
    --glass-line: rgb(255 255 255 / 12%);
    --glass-soft: rgb(255 255 255 / 8%);
    --glass-hover: rgb(255 255 255 / 6%);
    --glass-text: #f4f2ff;
    --glass-text-soft: rgb(244 242 255 / 62%);
    --glass-activa: rgb(124 92 240 / 42%);
    /* Va HACIA ARRIBA: el contenedor del ERP flota sobre el lienzo oscuro y
       la sombra tiene que despegarlo de la topbar, no hundirlo. Ninguna de
       las dos de arriba sirve, y un literal en el shell dejaría la única
       sombra del ERP fuera del sistema. */
    --shadow-shell: 0 -2px 40px -20px rgba(0, 0, 0, .5);
    /* El aire entre el menú y el contenido, y el radio de las dos tarjetas
       del shell. */
    --shell-gap: 10px;
    --radius-shell: 20px;
    /* El fondo del menú. Propio y no `--card-bg` para que el tema oscuro
       pueda separarlo del contenido sin tocar las tarjetas. */
    --sidebar-bg: var(--card-bg);
    /* El fondo de un campo. Blanco en claro; en oscuro, más hundido que la
       tarjeta que lo contiene. */
    --field-bg: #fff;
    /* El vidrio del listón: la banda de sección y `.site-nav` de la landing. */
    --bar-glass-top: rgba(255, 255, 255, .46);
    --bar-glass-bottom: rgba(255, 255, 255, .22);
    --bar-glass-edge: rgba(255, 255, 255, .72);
    --bar-glass-shine: rgba(255, 255, 255, .95);
    /* La sombra de adentro del canto, que le da grosor al vidrio. */
    --bar-glass-depth: rgba(23, 20, 31, .13);
    /* La sombra de afuera de la banda: suave, pareja alrededor y sin el
       canto (Renzo, 2026-10-10). El oscuro la hereda. */
    --bar-glass-shadow: 0 0 15px 0 rgba(0, 0, 0, .11);
    /* La LISTA: la tarjeta de una lista con la piel de las tarjetas del
       tablero de Tareas —degradado blanco, filo blanco, sombra larga lila—
       (Renzo, 2026-10-11). La usan la tabla de cada lista y sus filtros. */
    --lista-arriba: rgba(255, 255, 255, .9);
    --lista-abajo: rgba(255, 255, 255, .68);
    --lista-filo: rgba(255, 255, 255, .95);
    --lista-brillo: #fff;
    --lista-sombra: 0 1px 2px rgba(60, 40, 120, .05), 0 10px 22px -16px rgba(60, 40, 120, .32);
    --lista-encabezado: #77728f;
    /* La CÁPSULA de vidrio de las listas —el buscador y «Columnas»—, la del
       conmutador Tablero / Tabla / Calendario de Tareas; y su botón puesto,
       blanco con sombra lila. */
    --lista-vidrio-arriba: rgba(255, 255, 255, .62);
    --lista-vidrio-abajo: rgba(255, 255, 255, .36);
    --lista-vidrio-filo: rgba(255, 255, 255, .78);
    --lista-vidrio-brillo: rgba(255, 255, 255, .95);
    --lista-vidrio-sombra: 0 22px 48px -26px rgba(70, 50, 150, .32), 0 2px 8px -3px rgba(70, 50, 150, .08);
    --lista-puesto: rgba(255, 255, 255, .92);
    --lista-puesto-sombra: inset 0 1px 0 #fff, 0 4px 14px -6px rgba(108, 76, 245, .4);
    /* El PANEL de vidrio grande que envuelve una vista entera: el del
       tablero de Tareas y la barra del Asistente ancho. Más
       transparente que la cápsula; el filo, el brillo y la sombra son los de
       la cápsula (`--lista-vidrio-*`). */
    --panel-vidrio-arriba: rgba(255, 255, 255, .5);
    --panel-vidrio-abajo: rgba(255, 255, 255, .26);
    /* El CANTO del vidrio, entero y en un solo sitio: lo usan la banda, la
       tira del inicio y las tarjetas de Analíticas (2026-10-04). Un filo de
       luz arriba y otro más tenue abajo y a los lados; por dentro una sombra
       corta que le da grosor al borde, como una lente; afuera una sombra
       pegada y otra larga. */
    --glass-rim:
        inset 0 1.5px 1px var(--bar-glass-shine),
        inset 0 -1.5px 1px color-mix(in srgb, var(--bar-glass-shine) 70%, transparent),
        inset 1.5px 0 1px color-mix(in srgb, var(--bar-glass-shine) 60%, transparent),
        inset -1.5px 0 1px color-mix(in srgb, var(--bar-glass-shine) 60%, transparent),
        inset 0 7px 9px -6px var(--bar-glass-depth),
        inset 0 -9px 10px -8px var(--bar-glass-depth),
        0 1px 2px rgba(23, 20, 31, .12),
        0 16px 28px -18px rgba(23, 20, 31, .45);

    /* Semántica: cada estado lleva su par fondo/texto */
    /* Fortaleza de contraseña. Son tonos de BARRA, no de texto: sobre blanco
       dan entre 1.6 y 3.1 de contraste, suficiente para una forma e ilegible
       para una palabra. Por eso el nivel no se escribe, se cuenta.
       El nivel 2 interpola entre el rojo y el ámbar para que la rampa de
       cuatro pasos no salte. */
    --strength-1: #ff5a4c;
    --strength-2: #ff823e;
    --strength-3: #ffaa30;
    --strength-4: #3de775;

    --ok-bg: #e8f6ec;      --ok-text: #1f7a3d;
    /* El verde de «esto ya está» en los medidores (Integrantes, inducciones):
       más lima que el `--ok-text` de las pastillas, que Renzo veía «muy
       pasto» (2026-09-14). Dos tonos: el claro para la barra y la baldosa,
       el oscuro para la cifra, que tiene que leerse sobre blanco. */
    --done: #53cf54;       --done-text: #2e9a2f;
    --warn-bg: #fff6dc;    --warn-text: #9a7b00;
    --danger-bg: #fdebeb;  --danger-text: #ff213e;  /* Renzo 2026-10-06 */
    --info-bg: #eaf2fd;    --info-text: #2c66ad;

    /* Los mismos cuatro estados, VIVOS: los de la pastilla (Renzo,
       2026-08-14). Son un juego aparte y no un reemplazo porque los de
       arriba tiñen superficies grandes —el fondo de un aviso, el de una fila
       degradada— y a ese tamaño un tono saturado grita. La pastilla es del
       tamaño de una palabra: ahí el tono pálido no se lee como estado, se
       lee como texto con un halo.

       El texto es más oscuro que el de la familia pálida, y no por gusto: el
       fondo vivo sube la luminancia, y con `--ok-text` sobre `#7effa3` el
       contraste cae a 4.28 — por debajo del 4.5 que pide un texto de 12px.
       Con estos pares queda entre 5.3 y 7.1, medido, los cuatro. Un estado
       que no se puede leer no es un estado. */
    --ok-vivid: #7effa3;      --ok-vivid-text: #0f5527;
    --warn-vivid: #ffe27a;    --warn-vivid-text: #6b4e00;
    --danger-vivid: #ffb3ad;  --danger-vivid-text: #8c1f16;
    --info-vivid: #a8d4ff;    --info-vivid-text: #0f4a8a;
    /* La pastilla SIN modificador. Va acá, con las otras cuatro, y no como
       un hex suelto dentro de la regla: el día que el tema cambie, se cambia
       en un solo sitio junto a sus hermanas. */
    --pill-bg: #ece9f6;    --pill-text: #514c66;

    --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* ── Tema oscuro ───────────────────────────────────────────────────────────
   Se pinta desde el servidor con `data-theme` en el `<html>` (cookie `tema`,
   `ui:toggle_theme`), en tres valores: `light`, `mixed` y `dark`. Solo
   reasigna TOKENS: lo que en la hoja todavía es un
   literal claro no se entera, y eso es lo que falta por barrer. */
:root[data-theme="dark"],
/* Mixto: el menú lleva los tokens oscuros y el contenido se queda claro. Las
   variables se heredan, así que redefinirlas en `.sidebar` alcanza a todo
   lo de adentro. */
/* `.como-menu`: lo que se viste COMO el menú (2026-10-08, la lista del chat
   del equipo, spec chat cara §1). Los mismos tokens, no una copia: en mixto
   se oscurece con el menú, y en claro y oscuro sigue al tema solo. */
:root[data-theme="mixed"] .sidebar,
:root[data-theme="mixed"] .como-menu {
    color-scheme: dark;

    --primary-dark: #b7a4ff;
    --primary-soft: #2a2248;
    --primary-soft-border: #3f3370;
    --campos-tinte: #1d1930;
    --campos-borde: #4a3d80;
    --equipo-tinte-fuerza: 14%;
    --checkbox-border: #6f6a88;

    --canvas: #07060c;
    --bg: #110f1a;
    --card-bg: #1b1827;
    --chart-muted: #625c7c;
    --chart-accent: #ff7af2;
    --sidebar-bg: #1c1929;
    --field-bg: #13111c;
    --bar-glass-top: rgba(40, 36, 58, .88);
    --bar-glass-bottom: rgba(27, 24, 39, .8);
    --bar-glass-edge: rgba(255, 255, 255, .08);
    --bar-glass-shine: rgba(255, 255, 255, .22);
    --bar-glass-depth: rgba(0, 0, 0, .45);
    --lista-arriba: rgba(48, 43, 70, .95);
    --lista-abajo: rgba(38, 34, 58, .9);
    --lista-filo: rgba(255, 255, 255, .08);
    --lista-brillo: rgba(255, 255, 255, .06);
    --lista-sombra: 0 1px 2px rgba(0, 0, 0, .3), 0 10px 22px -16px rgba(0, 0, 0, .7);
    --lista-encabezado: #a19bbd;
    --lista-vidrio-arriba: rgba(52, 46, 78, .62);
    --lista-vidrio-abajo: rgba(36, 32, 56, .5);
    --lista-vidrio-filo: rgba(255, 255, 255, .1);
    --lista-vidrio-brillo: rgba(255, 255, 255, .1);
    --lista-vidrio-sombra: 0 22px 48px -26px rgba(0, 0, 0, .7), 0 2px 8px -3px rgba(0, 0, 0, .3);
    --lista-puesto: rgba(255, 255, 255, .1);
    --lista-puesto-sombra: inset 0 1px 0 rgba(255, 255, 255, .1), 0 4px 14px -6px rgba(0, 0, 0, .6);
    --panel-vidrio-arriba: rgba(40, 36, 58, .55);
    --panel-vidrio-abajo: rgba(28, 25, 42, .35);
    /* Repetido para que tome el filo y la sombra del tema oscuro. */
    --glass-rim:
        inset 0 1.5px 1px var(--bar-glass-shine),
        inset 0 -1.5px 1px color-mix(in srgb, var(--bar-glass-shine) 70%, transparent),
        inset 1.5px 0 1px color-mix(in srgb, var(--bar-glass-shine) 60%, transparent),
        inset -1.5px 0 1px color-mix(in srgb, var(--bar-glass-shine) 60%, transparent),
        inset 0 7px 9px -6px var(--bar-glass-depth),
        inset 0 -9px 10px -8px var(--bar-glass-depth),
        0 1px 2px rgba(23, 20, 31, .12),
        0 16px 28px -18px rgba(23, 20, 31, .45);

    --text: #ece9f7;
    --text-light: #aaa5c0;
    --text-faint: #7d7894;

    --border: #2d2940;
    --border-strong: #3d3856;
    --table-head-bg: #201d2e;
    --table-line: #262337;

    --shadow: 0 1px 3px rgba(0, 0, 0, .4), 0 8px 24px rgba(0, 0, 0, .35);
    --shadow-lg: 0 2px 6px rgba(0, 0, 0, .45), 0 18px 48px rgba(0, 0, 0, .5);
    --shadow-shell: 0 -2px 40px -20px rgba(0, 0, 0, .9);

    --ok-bg: #16301f;      --ok-text: #7fdc9b;
    --done-text: #6fd870;
    --warn-bg: #33290d;    --warn-text: #f0cf5a;
    --danger-bg: #3a1a1a;  --danger-text: #ff8f86;
    --info-bg: #16263d;    --info-text: #8dbcf5;

    --pill-bg: #2a2740;    --pill-text: #c9c4de;
}

/* ── Base ──────────────────────────────────────────────────────────────── */

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

body {
    margin: 0;
    font-family: var(--font);
    font-size: var(--fs-body);
    line-height: 1.55;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

a { color: var(--primary-dark); text-decoration: none; }
/* Sin subrayado al pasar por encima. Con la mitad de los enlaces del ERP
   pintados como botones —tarjetas, íconos de acción, pastillas— el subrayado
   solo aparecía en unos pocos y se leía como un enlace roto entre los demás.
   El estado se lo dan el color y el fondo, que ya cambian en `:hover`. */
a:hover { text-decoration: none; }

/* ── Orbes de fondo ────────────────────────────────────────────────────────
   `mix-blend-mode: multiply` es lo que los hace funcionar sobre fondo claro:
   tiñen el lienzo en vez de flotar encima como manchas. Con `normal` se ven
   sucios y con opacidad alta, lavados.
   ------------------------------------------------------------------------ */

.orbs {
    position: fixed;
    inset: -10vh -10vw;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
}

/* Los mismos orbes, DENTRO de la tarjeta de una pantalla (Analíticas,
   2026-10-04, de prueba): para que el vidrio de las tarjetas tenga qué dejar
   pasar. QUIETOS mientras la pantalla corre y desde el techo de la tarjeta,
   sin corte (Renzo): la primera versión arrancaba debajo de la banda y se le
   veía el borde.

   No puede ser `fixed`: se saldría de la tarjeta y taparía el menú. Es un
   `sticky` que nace YA pegado. El marcado va después de la banda, así que el
   margen negativo lo sube de sobra —200px, más que cualquier banda— y el
   `top` lo deja ahí: empieza por encima del techo de la tarjeta, que recorta
   lo que sobra, y no se mueve con el desplazamiento. El margen de abajo le
   quita el alto: no empuja nada. Lo que viene después se pinta encima. */
.orbs.orbs-pantalla {
    position: sticky; top: -132px; inset-inline: auto; bottom: auto;
    height: calc(100vh + 132px);
    margin: -200px calc(var(--content-pad) * -1) calc(var(--space-800) - 100vh);
}
.orbs-pantalla ~ * { position: relative; z-index: 1; }
/* El rango abre su menú sobre la rejilla, que viene después. */
.orbs-pantalla ~ .analytics-toolbar { z-index: 5; }
/* Rebotan suave, y se nota: las mismas derivas de la casa, más rápidas —a
   20 s detrás de un vidrio esmerilado parecían quietas—, y cada una a su
   paso para que no vayan en coro. */
.orbs-pantalla .orb { animation-duration: 11s; }
.orbs-pantalla .orb:nth-child(odd) { animation-duration: 8s; }
/* El rosado sube: pegado al piso de la ventana casi no se veía. */
.orbs-pantalla .orb-violet { bottom: 8%; }
/* En oscuro `multiply` los apaga contra el fondo: ahí suman luz. */
:root[data-theme="dark"] .orbs-pantalla .orb,
:root[data-theme="dark"] .orbs-dentro .orb { mix-blend-mode: screen; opacity: .3; }
/* Los orbes DENTRO de una caja (2026-10-09, el chat sin conversación): la
   caja los recorta y lo que viene después se pinta encima. */
.orbs.orbs-dentro { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.orbs-dentro ~ * { position: relative; z-index: 1; }
.orbs-dentro .orb { animation-duration: 11s; }
.orbs-dentro .orb:nth-child(odd) { animation-duration: 8s; }

.orb {
    position: absolute;
    border-radius: 50%;
    filter: blur(58px);
    mix-blend-mode: multiply;
    will-change: transform;
}

.orb-lavender {
    width: 34vmax; height: 34vmax; top: 40%; left: 12%; opacity: .26;
    background: radial-gradient(circle, #ded4ff 0%, #bda9ff 48%, transparent 72%);
    animation: orb-drift-a 19s ease-in-out infinite;
}
.orb-cyan {
    width: 28vmax; height: 28vmax; top: 2%; right: 2%; opacity: .42;
    background: radial-gradient(circle, #7ee8f2 0%, #3ec9de 42%, transparent 70%);
    animation: orb-drift-b 23s ease-in-out infinite;
}
.orb-yellow {
    width: 22vmax; height: 22vmax; top: -14%; left: 4%; opacity: .36;
    background: radial-gradient(circle, #fff08a 0%, #ffd84d 50%, transparent 88%);
    animation: orb-drift-c 17s ease-in-out infinite;
}
.orb-violet {
    width: 26vmax; height: 26vmax; bottom: -8%; right: 18%; opacity: .32;
    background: radial-gradient(circle, #ff48f3a6 0%, #f05ce4a8 45%, transparent 70%);
    animation: orb-drift-d 21s ease-in-out infinite;
}
.orb-glow {
    width: 36vmax; height: 36vmax; top: 0; left: 50%;
    transform: translateX(-50%); opacity: .30;
    background: radial-gradient(circle, rgba(201, 184, 255, .9) 0%,
                rgba(126, 232, 242, .35) 38%, transparent 66%);
    filter: blur(70px);
    mix-blend-mode: normal;
    animation: orb-pulse 9s ease-in-out infinite;
}

@keyframes orb-drift-a {
    0%, 100% { transform: translate(0, 0) scale(1); }
    33%      { transform: translate(5vw, 4vh) scale(1.06); }
    66%      { transform: translate(-3vw, -3vh) scale(.95); }
}
@keyframes orb-drift-b {
    0%, 100% { transform: translate(0, 0) scale(1); }
    33%      { transform: translate(-6vw, 5vh) scale(1.08); }
    66%      { transform: translate(3vw, -2vh) scale(.94); }
}
@keyframes orb-drift-c {
    0%, 100% { transform: translate(0, 0) scale(1); }
    50%      { transform: translate(4vw, 5vh) scale(1.12); }
}
@keyframes orb-drift-d {
    0%, 100% { transform: translate(0, 0) scale(1); }
    33%      { transform: translate(-4vw, -5vh) scale(1.05); }
    66%      { transform: translate(3vw, 2vh) scale(.97); }
}
@keyframes orb-pulse {
    0%, 100% { opacity: .22; transform: translateX(-50%) scale(1); }
    50%      { opacity: .34; transform: translateX(-50%) scale(1.1); }
}

/* ── Pantallas de autenticación ────────────────────────────────────────── */

.auth-stage {
    position: relative;
    z-index: 1;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: calc(var(--space-400) + var(--sat)) var(--space-250) calc(var(--space-400) + var(--sab));
}

.auth-card {
    width: 100%;
    max-width: 408px;
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-500) var(--space-400) var(--space-400);
    animation: card-rise .55s cubic-bezier(.16, 1, .3, 1) both;
}

@keyframes card-rise {
    from { opacity: 0; transform: translateY(16px); }
    to   { opacity: 1; transform: translateY(0); }
}

.auth-brand {
    display: flex;
    align-items: center;
    gap: var(--space-100);
    margin-bottom: var(--space-300);
}
/* El logo de verdad, desde el 2026-08-04. Antes había un cuadrado con
   degradado y la palabra al lado: un marcador de posición que se quedó
   desde que existe la pantalla. */
.auth-brand .brand-logo { height: 30px; }
/* La tienda apagada (`storefront/tienda_cerrada.html`) lleva la marca de la
   empresa en vez de la de Agrupa, y a 32 px se perdía en la tarjeta. */
.auth-brand .company-mark { width: 56px; height: 56px; font-size: var(--fs-page); }

.auth-title {
    font-size: var(--fs-page);
    font-weight: 700;
    letter-spacing: -.5px;
    margin: 0 0 var(--space-075);
}
.auth-subtitle {
    color: var(--text-light);
    font-size: var(--fs-body);
    margin: 0 0 var(--space-300);
}

/* El correo con el que se está entrando, en los pasos 2 y 3: ya se escribió
   en el paso 1 y no se vuelve a pedir, pero tiene que estar A LA VISTA — es
   la única forma de darse cuenta de un dedazo antes de pelearse con una
   contraseña que sí era la correcta. */
.auth-who {
    font-size: var(--fs-small);
    color: var(--text-light);
    margin: 0 0 var(--space-200);
}
.auth-who a { margin-left: var(--space-100); }

/* Un botón que se lee como enlace. Existe porque «entrar con mi contraseña»
   tiene que MANDAR el correo que ya se escribió —y eso es un POST— pero no
   es una acción principal: pintarlo de botón compite con «Entrar». */
.btn-link {
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    color: var(--primary);
    cursor: pointer;
    text-decoration: underline;
}

/* La otra puerta: es un formulario aparte —no se puede anidar uno dentro de
   otro— así que sin esto los dos botones quedan pegados. */
.auth-alt { margin-top: var(--space-100); }

.auth-foot {
    margin: var(--space-200) 0 0;
    text-align: center;
    font-size: var(--fs-small);
}

/* Lo que la IA conectada podrá y no podrá (pantalla de autorizar del MCP,
   2026-10-28): dos tonos y un glifo en vez de un párrafo, para leerlo de un
   vistazo antes de tocar «Autorizar». */
.auth-permisos {
    list-style: none; margin: 0 0 var(--space-250); padding: 0;
    display: flex; flex-direction: column; gap: var(--space-100);
    font-size: var(--fs-small); color: var(--text);
}
.auth-permisos li { display: flex; gap: var(--space-100); align-items: flex-start; }
.auth-permisos svg { flex-shrink: 0; width: 16px; height: 16px; margin-top: var(--space-025); }
.auth-permiso-si svg { color: var(--ok-text); }
.auth-permiso-no svg { color: var(--text-light); }
.auth-permiso-no { color: var(--text-light); }
.auth-vuelta { margin: var(--space-200) 0 0; text-align: center; }

/* Entrar con huella (2026-10-11): el paso de /entrar/ después del correo. */
.auth-huella { text-align: center; }
.auth-huella .huella-glifo { margin: var(--space-075) auto var(--space-200); color: var(--primary); }

/* La huella animada (`identity/_huella_glifo.html`, 2026-10-11): los nueve
   trazos del glifo `huella` se dibujan de a uno, después baja una luz de
   escaneo y queda un pulso. Todo con `currentColor`, así que la misma pieza
   sirve morada sobre blanco (/entrar/) y blanca sobre la banda. Con
   `.is-listo` en la pieza que la contiene, el dibujo cede al check. Con
   «reducir movimiento» la deja quieta y completa el `*` universal del
   bloque de movimiento reducido: los trazos terminan dibujados (`both`) y
   la luz termina apagada. */
.huella-glifo {
    position: relative;
    display: inline-grid;
    place-items: center;
    flex-shrink: 0;
    width: 52px;
    height: 52px;
    border-radius: 16px;
    overflow: hidden;
    background: color-mix(in srgb, currentColor 14%, transparent);
    animation: hg-pulso 2.8s ease-in-out 1.4s infinite;
}
.huella-glifo-grande { width: 76px; height: 76px; border-radius: 22px; }
.huella-glifo > svg { grid-area: 1 / 1; width: 62%; height: 62%; }
.huella-glifo-trazos path {
    stroke-dasharray: 30;
    stroke-dashoffset: 0;
    animation: hg-trazo .7s ease-out both;
}
.huella-glifo-trazos path:nth-child(2) { animation-delay: .08s; }
.huella-glifo-trazos path:nth-child(3) { animation-delay: .16s; }
.huella-glifo-trazos path:nth-child(4) { animation-delay: .24s; }
.huella-glifo-trazos path:nth-child(5) { animation-delay: .32s; }
.huella-glifo-trazos path:nth-child(6) { animation-delay: .40s; }
.huella-glifo-trazos path:nth-child(7) { animation-delay: .48s; }
.huella-glifo-trazos path:nth-child(8) { animation-delay: .56s; }
.huella-glifo-trazos path:nth-child(9) { animation-delay: .64s; }
.huella-glifo-escaneo {
    position: absolute;
    left: 14%;
    right: 14%;
    top: 12%;
    height: 2px;
    border-radius: 2px;
    background: currentColor;
    box-shadow: 0 0 10px 2px currentColor;
    opacity: 0;
    animation: hg-escaneo 2.8s ease-in-out 1.1s infinite;
}
.huella-glifo-check { opacity: 0; transform: scale(.5); }
.is-listo .huella-glifo { animation: none; }
.is-listo .huella-glifo-trazos,
.is-listo .huella-glifo-escaneo { visibility: hidden; }
.is-listo .huella-glifo-check {
    opacity: 1;
    transform: none;
    transition: opacity .25s ease-out, transform .4s cubic-bezier(.34, 1.56, .64, 1);
}

@keyframes hg-trazo { from { stroke-dashoffset: 30; } to { stroke-dashoffset: 0; } }
@keyframes hg-escaneo {
    0%   { top: 12%; opacity: 0; }
    12%  { opacity: 1; }
    50%  { top: 86%; opacity: 0; }
    100% { top: 86%; opacity: 0; }
}
@keyframes hg-pulso {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 32%, transparent); }
    50%      { box-shadow: 0 0 0 7px transparent; }
}
@keyframes hg-entra { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }

/* La lista de huellas del Perfil. */
.huellas { list-style: none; margin: 0; padding: 0; }
.huella {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-050) var(--space-150);
    align-items: center;
    padding: var(--space-150) 0;
    border-bottom: 1px solid var(--border);
}
.huella:last-child { border-bottom: 0; }
.huella .field-hint { grid-column: 1; margin: 0; }
.huella-nombre { display: flex; gap: var(--space-100); align-items: center; }
.huella-nombre input { max-width: 260px; }
.huella-confirmar { margin-bottom: var(--space-150); }

/* La invitación de después de entrar: una banda de la marca y no una
   tarjeta blanca (Renzo, 2026-10-11: «tiene que ser un banner más
   visible»). Nace con `hidden` hasta que passkey.js confirma que hay
   Touch ID / Face ID. */
.huella-invitar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-200) var(--space-200);
    align-items: center;
    margin-bottom: var(--space-200);
    padding: var(--space-200) var(--space-300);
    border-radius: var(--radius);
    color: #fff;
    background:
        radial-gradient(120% 160% at 0% 0%, color-mix(in srgb, var(--primary) 55%, #fff) 0%, transparent 55%),
        linear-gradient(115deg, var(--primary-dark), var(--primary));
    box-shadow: 0 10px 28px color-mix(in srgb, var(--primary) 32%, transparent);
    animation: hg-entra .45s ease-out both;
}
.huella-invitar .huella-glifo { background: rgba(255, 255, 255, .16); }
/* El texto pide 220px antes de dejar que los botones le quiten espacio: con
   el Asistente abierto la banda mide la mitad, y entonces los botones bajan a
   su propio renglón en vez de apretar la frase palabra por palabra. Mide la
   banda, no la pantalla. */
.huella-invitar-texto { flex: 1 1 220px; min-width: 0; }
.huella-invitar-titulo { display: block; font-size: var(--fs-stat); line-height: 1.25; }
.huella-invitar-texto p { margin: var(--space-050) 0 0; font-size: var(--fs-small); color: rgba(255, 255, 255, .86); }
.huella-invitar-texto .huella-invitar-error { color: #fff; font-weight: 600; }
.huella-invitar-botones { display: flex; gap: var(--space-100); flex-shrink: 0; }
.huella-invitar .huella-invitar-activar {
    background: #fff;
    color: var(--primary-dark);
    border-color: transparent;
    font-weight: 600;
}
.huella-invitar .huella-invitar-activar:hover:not(:disabled) { background: var(--primary-soft); }
.huella-invitar .huella-invitar-no {
    background: transparent;
    color: rgba(255, 255, 255, .86);
    border-color: rgba(255, 255, 255, .35);
}
.huella-invitar .huella-invitar-no:hover { color: #fff; border-color: rgba(255, 255, 255, .7); background: rgba(255, 255, 255, .08); }

/* `hidden` TIENE que ganarle al `display` de estas piezas (ver `.bulkbar`):
   passkey.js muestra y esconde con el atributo, y esta hoja no trae un
   `[hidden]` global. Lo que se apaga así y declara `display`: la tarjeta de
   la invitación (flex), sus botones (flex), el aviso `.alert` del Perfil
   (flex: sin esto sale una barra roja vacía) y todo `.btn` (inline-flex:
   «Agregar huella» sin soporte). El resto —`.field-error`, `.field-hint`,
   `.auth-huella`, `.huella-confirmar`, los formularios de /entrar/— no
   declara `display`, pero se cubren igual para que un cambio futuro no los
   deje a la vista. */
.huella-invitar[hidden], .huella-invitar [hidden],
#seccion-huellas [hidden], .huella-confirmar[hidden], .huella-confirmar [hidden],
.auth-huella[hidden], .auth-huella [hidden],
form[data-passkey-correo][hidden], form[data-passkey-entrar][hidden] { display: none; }

@media (max-width: 600px) {
    .huella-invitar { padding: var(--space-200); }
    .huella-invitar-botones { width: 100%; }
    .huella-invitar-botones .btn { flex: 1; justify-content: center; }
    .huella { grid-template-columns: 1fr; }
}

/* Reveal escalonado: la tarjeta sube, y su contenido aparece detrás. */
.auth-card > * { animation: fade-up .45s ease both; }
.auth-card > *:nth-child(1) { animation-delay: .10s; }
.auth-card > *:nth-child(2) { animation-delay: .16s; }
.auth-card > *:nth-child(3) { animation-delay: .21s; }
.auth-card > *:nth-child(4) { animation-delay: .26s; }
.auth-card > *:nth-child(5) { animation-delay: .30s; }

@keyframes fade-up {
    from { opacity: 0; transform: translateY(7px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ── Formularios ───────────────────────────────────────────────────────── */

.field { margin-bottom: var(--space-200); }
/* Un grupo de casillas con su rótulo («Enviar a» de la OC, el cambio de
   producto de un pedido): `<fieldset class="field">` + `<legend>`. Sin esto
   salía la caja gris del navegador alrededor de los correos (2026-10-27). La
   `legend` se lee como el rótulo de cualquier campo. */
fieldset.field { border: 0; padding: 0; margin: 0 0 var(--space-200); min-width: 0; }
fieldset.field > legend {
    padding: 0; margin-bottom: var(--space-100);
    font-size: var(--fs-small); font-weight: 600; color: var(--text-light);
}

.field label {
    display: block;
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--text-light);
    margin-bottom: var(--space-100);
}
/* El rótulo y su `(i)` en una fila. El rótulo es `display: block`, así que sin
   esto el ícono caía al renglón siguiente y quedaba flotando sobre el control.
   El `margin-bottom` se muda del rótulo a la fila, o se sumaban dos. */
.field-label { display: flex; align-items: center; gap: var(--space-075); margin-bottom: var(--space-100); }
.field-label > label { margin-bottom: 0; }
/* La casilla «bolsas + sueltos» (ui/_cantidad_operativa.html): las dos cajitas lado a lado. */
.cantidad-operativa { display: flex; gap: var(--space-150); }
.cantidad-operativa > .field { flex: 1 1 0; min-width: 0; margin-bottom: 0; }
/* El botoncito que pasa a la otra forma de anotar, un `--sp-2` bajo las cajitas. */
.cantidad-operativa-cambio { margin-top: var(--space-100); }

/* Ajustar la cifra de una celda (ui/_ajuste_celda.html, 2026-10-21): la
   cifra es el disparador, con cursor de mano y subrayado de puntos para que
   se lea tocable; la ventanita es un `.menu-pop` más ancho que en el
   teléfono es hoja. Lo que no se toca es un `<span>` sin nada de esto. */
.ajuste-celda { display: inline-block; }
.ajuste-celda-cifra {
    list-style: none; cursor: pointer;
    border-radius: var(--radius-sm); padding: var(--space-025) var(--space-050); margin: calc(-1 * var(--space-025)) calc(-1 * var(--space-050));
    text-decoration: underline dotted; text-underline-offset: 3px;
}
.ajuste-celda-cifra::-webkit-details-marker { display: none; }
/* Con `boton`, el disparador es un botón de la casa: sin el triángulo del
   `<summary>`, y oprimido mientras la ventanita está abierta. */
.ajuste-celda > summary.btn { list-style: none; }
.ajuste-celda > summary.btn::-webkit-details-marker { display: none; }
.ajuste-celda[open] > summary.btn { background: var(--primary-soft); color: var(--primary-dark); }
.ajuste-celda-cifra:hover,
.ajuste-celda[open] > .ajuste-celda-cifra { background: var(--primary-soft); color: var(--primary-dark); }
.ajuste-celda-cifra:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* Con `icono` (2026-11-05, la alerta de precio de la orden): solo el glifo,
   en su tono, con su área de toque. Sin JavaScript se ve el enlace del
   `<noscript>` y no el `<details>`, que no cargaría su ventanita. */
.ajuste-celda-icono {
    list-style: none; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; border-radius: var(--radius-sm);
}
.ajuste-celda-icono::-webkit-details-marker { display: none; }
.ajuste-celda-icono svg { width: 15px; height: 15px; }
.ajuste-celda-icono--danger { color: var(--danger-text); }
.ajuste-celda-icono--warn { color: var(--warn-text, var(--danger-text)); }
.ajuste-celda-icono:hover,
.ajuste-celda[open] > .ajuste-celda-icono { background: var(--primary-soft); }
.ajuste-celda-icono:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
html:not(.js) .ajuste-celda--icono { display: none; }
/* LA TABLA DE LA ORDEN DE COMPRA, pareja (Santiago, 2026-11-05: «Sí,
   así»). Solo `.oc-lineas` (la `clase` que la orden le pasa a
   `ui/_lineas.html`): las demás tablas de líneas no cambian.
   - Cada columna mide lo que lleva: así el aire entre columnas es el mismo,
     el del padding, y no el reparto del navegador. El Producto, lo que pide
     su nombre, hasta 22em (más largo, baja de renglón); lo que sobra se lo
     lleva el SKU después de su código, así el SKU queda junto al producto y
     el aire va antes de Cantidad (Santiago, 2026-11-05: «la columna del
     SKU puede estar un poco más a la izquierda»).
   - Las cifras a la derecha, con su encabezado. Cantidad y Precio, casillas
     a la medida de su cifra; el Precio con el «$» en la cifra («$10.644»).
   - La Unidad se lee como texto en todas las filas: el desplegable de la
     línea escrita a mano, sin borde ni flecha hasta pasarle el puntero o
     enfocarlo. */
.oc-lineas { --oc-cantidad: 60px; --oc-precio: 100px; }
.oc-lineas.lineas-apretadas > thead > tr > th,
.oc-lineas.lineas-apretadas > tbody > tr > td { white-space: nowrap; width: 1%; }
.oc-lineas.lineas-apretadas > tbody > tr > td:has(> .cell-media) { white-space: normal; }
.oc-lineas.lineas-apretadas > tbody > tr > td > .cell-media > .cell-text { width: max-content; max-width: 22em; }
.oc-lineas.lineas-apretadas > thead > tr > th.oc-col-sku,
.oc-lineas.lineas-apretadas > tbody > tr > td[data-sku] { width: auto; }
.oc-lineas.lineas-apretadas > thead > tr > th.num,
.oc-lineas.lineas-apretadas > tbody > tr > td.num { text-align: right; }
.oc-lineas.lineas-apretadas input[name="line_quantity"] { width: var(--oc-cantidad); }
.oc-lineas.lineas-apretadas input[name="line_unit_price"] { width: var(--oc-precio); }
/* CADA ENCABEZADO SOBRE LO SUYO (Santiago, 2026-11-05: «mira los títulos
   de la columna, no están alineados»): el texto del encabezado arranca donde
   arranca el texto de su columna, y el de una cifra termina en el borde
   derecho de su casilla.
   - Producto: la casa corre el rótulo para la foto con el padding de una
     tabla normal; esta tabla es apretada (`--space-075`).
   - Unidad e Impuesto: el texto de un desplegable arranca después de su
     padding y su borde (`--space-150` + 1px).
   - Precio: con alguna alerta, la casilla deja a su derecha el lugar del
     glifo (`--space-300`), y el encabezado termina donde ella. */
.oc-lineas.lineas-apretadas > thead > tr > th.cell-check ~ th.th-lead {
    padding-left: calc(var(--space-075) + var(--cell-thumb) + var(--space-150));
}
.oc-lineas.lineas-apretadas > thead > tr > th.oc-col-unidad,
.oc-lineas.lineas-apretadas > thead > tr > th.oc-col-impuesto {
    padding-left: calc(var(--space-075) + var(--space-150) + 1px);
}
.oc-lineas.lineas-apretadas:has(.oc-precio-alerta) > thead > tr > th.oc-col-precio {
    padding-right: calc(var(--space-075) + var(--space-300));
}
/* La unidad fija arranca donde arranca el texto del desplegable (su padding
   y su borde), para que la columna se lea pareja. */
.oc-lineas.lineas-apretadas td[data-unidad] .cell-unidad { padding-left: calc(var(--space-150) + 1px); }
.oc-lineas.lineas-apretadas td[data-unidad] select:not(:hover):not(:focus) {
    border-color: transparent; background-color: transparent; background-image: none;
    box-shadow: none;
}
/* Ya facturada (Santiago, 2026-11-05: «avisar antes»): el precio, el
   impuesto y la unidad de una línea recibida con su factura relacionada se
   ven en gris y no se tocan; al pasar el puntero, «Ya facturada». */
.oc-lineas .oc-facturada input[readonly],
.oc-lineas .oc-facturada select:disabled {
    color: var(--text-light); background-color: var(--bg); cursor: not-allowed;
}
/* La alerta de precio AFUERA de la casilla, a su derecha y con aire
   (Santiago, 2026-11-05: «afuera pero bien puesta»): solo el triángulo rojo,
   6 px después del borde, centrado en el alto de la casilla. En la orden con
   alguna alerta, la casilla del Precio de TODAS las filas lleva a su derecha
   el lugar del glifo (24 px), así que las filas no se corren entre sí. Las
   cifras van a la derecha: el glifo se ancla al borde derecho de la celda. */
.oc-precio-alerta { position: relative; }
.table:has(.oc-precio-alerta) td.num > input[name="line_unit_price"] { margin-right: var(--space-300); }
.oc-precio-alerta:has(input[name="line_unit_price"]) > .ajuste-celda--icono,
.oc-precio-alerta:has(input[name="line_unit_price"]) > noscript > .ajuste-celda-icono {
    /* Centrado con `calc` y NO con `transform`: un `transform` vuelve al
       glifo el marco de la ventanita fija de `menu.js`, y salía fuera de
       la pantalla. */
    position: absolute; top: calc(50% - 9px);
    right: var(--space-075);
}
/* Sin casilla (la orden que ya no se edita): el glifo va en el renglón,
   pegado a la cifra. */
.oc-precio-alerta:not(:has(input[name="line_unit_price"])) > .ajuste-celda--icono { display: inline-block; vertical-align: middle; }
/* Los dos glifos se ven iguales (Santiago, 2026-11-05: uno salía con el aro
   morado): `menu.js` le devuelve el foco al cerrar con Esc, y el aro se
   quedaba. Con foco, el glifo toma el fondo suave del hover, sin aro: se
   nota con el teclado y no queda distinto del de al lado. */
.oc-precio-alerta .ajuste-celda-icono:focus-visible { outline: none; background: var(--primary-soft); }
.oc-precio-alerta .ajuste-celda-icono { width: 18px; height: 18px; }
.oc-precio-alerta .ajuste-celda-icono svg { width: 14px; height: 14px; }
/* La ventanita de la alerta (`_order_price_alert_pop.html`): las dos cifras
   lado a lado, la diferencia en su pastilla y el botón con aire. La letra es
   la de la ventanita de Existencias; nada de esto la toca. */
.oc-alerta-cifras { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-100); margin-bottom: var(--space-150); }
.oc-alerta-cifra { display: flex; flex-direction: column; gap: var(--space-025);
                   padding: var(--space-100) var(--space-150); border-radius: var(--radius-sm); background: var(--bg); }
.oc-alerta-rotulo { font-size: var(--fs-meta); color: var(--text-light); }
.oc-alerta-valor { font-size: var(--fs-stat); font-weight: 700; color: var(--text); white-space: nowrap; }
.oc-alerta-desde { font-size: var(--fs-meta); color: var(--text-faint); white-space: nowrap; }
.oc-alerta-diferencia { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-050);
                        margin: 0 0 var(--space-150); }
.oc-alerta-ayuda { margin: 0; font-size: var(--fs-small); color: var(--text-light); }
.oc-alerta-acciones { margin-top: var(--space-200); }
/* La tabla de Diferencias de la factura (`_invoice_work.html`, 2026-11-05):
   lo que la factura cobra de más, en rojo; de menos, en verde. Los avisos
   que solo informan, debajo, con su pastilla y su frase en un renglón. */
.dif-mas { color: var(--danger-text); white-space: nowrap; }
.dif-menos { color: var(--ok-text); white-space: nowrap; }
/* El aviso de «Relacionar» (2026-11-05): una frase en rojo bajo la
   coincidencia; parte línea con su celda (`.cell-wrap`), con tope en `ch`
   para que la frase no le dé el ancho a la columna y saque la tabla del
   panel. */
.table .cell-sub.dif-mas { color: var(--danger-text); white-space: normal; max-width: 24ch; }
/* La tabla Diferencias de la factura (Santiago, 2026-11-05: «el producto
   quedó apretadísimo»): el producto toma lo que sobra, con el mínimo de una
   descripción de línea (`.cell-descripcion`, 24ch), y lo demás se ciñe a su
   contenido. Sin esto la tabla repartía el ancho parejo y «Café Especial
   Peligro - Molido / 250g / Supremo» iba en seis renglones. */
.dif-tabla > thead > tr > th:first-child { width: 100%; }
.dif-tabla > tbody > tr > td:first-child { min-width: 24ch; }
/* El rótulo del pie abarca cuatro columnas: de una sola tirada las obligaba
   a medir lo que él mide y el producto no ganaba nada. */
.dif-tabla > tfoot > tr > td:first-child,
.dif-tabla > tfoot > tr > td:first-child .cell-sub { white-space: normal; }
.dif-tabla > thead > tr > th:not(:first-child),
.dif-tabla > tbody > tr > td:not(:first-child),
.dif-tabla > tfoot > tr > td:not(:first-child) { width: 1%; white-space: nowrap; }
.dif-avisos li { gap: var(--space-100); flex-wrap: nowrap; }
/* `.menu-pop.` delante: `.menu-pop` vive más abajo en la hoja con
   `white-space: nowrap`, y con la misma especificidad ganaba — el título
   largo se salía del panel y «Consumo interno» se montaba en su radio
   (2026-10-22). El tope de alto es el último recurso: `menu.js` lo ubica
   dentro de la ventana, y si aun así no cabe, se desplaza por dentro. */
.menu-pop.ajuste-celda-pop {
    width: 340px; max-height: calc(100vh - 16px); overflow-y: auto;
    white-space: normal; text-align: left; font-weight: 400;
}
.ajuste-celda-cargando { margin: 0; padding: var(--space-150); color: var(--text-faint); }
.ajuste-celda-form { padding: var(--space-150); }
.ajuste-celda-titulo { margin: 0 0 var(--space-025); font-weight: 600; font-size: var(--fs-section); overflow-wrap: anywhere; }
.ajuste-celda-ahora { margin: 0 0 var(--space-150); color: var(--text-light); }
.ajuste-celda-modos { display: flex; gap: var(--space-100); margin-bottom: var(--space-150); }
.ajuste-celda-modos .btn { flex: 1 1 0; justify-content: center; }
.ajuste-celda-modos .btn[aria-pressed="true"] { border-color: var(--primary); color: var(--primary-dark); background: var(--primary-soft); }
.ajuste-celda-form [data-ajuste-cantidad],
.ajuste-celda-form .option-cards { margin-bottom: var(--space-150); }
.ajuste-celda-lote { display: flex; gap: var(--space-150); }
.ajuste-celda-lote > .field { flex: 1 1 0; min-width: 0; }
/* El `display: flex` de arriba le gana al `[hidden]` del navegador (la trampa
   de `.bulkbar`): sin esto el lote se ve aunque no se suba nada. */
.ajuste-celda-lote[hidden] { display: none; }
/* La regla del lote (2026-10-29): el lote que ya existe se lee en una línea,
   el proveedor fijo como texto a la altura de las casillas, y la tabla de
   escoger lote con la casilla angosta de las cifras. */
.lote-nuevo, .lote-elegir { margin-bottom: var(--space-150); }
/* La pieza del lote debajo de la captura de Movimientos (paso 2). */
.captura-lote { margin-top: var(--space-150); }
/* La cantidad de la captura se repinta sola al elegir producto; su
   envoltura no es una celda de la rejilla, su `.field` sí. */
.captura-cantidad { display: contents; }
.lote-nuevo-conocido { margin: 0 0 var(--space-100); }
.lote-nuevo-proveedor { margin: 0; padding: var(--space-100) 0; }
.lote-elegir-tabla input[type="number"] { width: 6rem; text-align: right; }
/* La captura con lote (2026-11-05): la tarjeta envuelve el formulario de
   captura y, hermanos, los lotes del producto. Una fila por lote, la
   marcada con el filo y el fondo del primario: ahí cae el próximo escaneo
   (el mismo dibujo que los lotes abiertos de Alistamiento). */
/* La fila del PRODUCTO en la tabla de líneas, encima de sus lotes
   (`_linea_producto.html`, 2026-11-05): el total, con un tinte apenas
   distinto para que se lea como cabeza de su grupo. */
.table tr.row-padre td { background: var(--bg); }
.captura-form { margin: 0; }
.lotes-captura { margin-top: var(--space-200); padding-top: var(--space-200);
                 border-top: 1px solid var(--border); display: grid; gap: var(--space-150); }
.lotes-captura-producto { display: flex; align-items: center; gap: var(--space-150); }
.lotes-captura-producto p { margin: 0; }
.lotes-captura-nombre { margin: 0; font-size: var(--fs-section); }
.lotes-captura-filas { display: grid; gap: var(--space-100); }
.lote-fila { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-150);
             padding: var(--space-100) var(--space-100) var(--space-100) var(--space-250);
             border: 1px solid var(--border); border-radius: var(--radius-card); background: var(--card-bg); }
/* El filo de la marcada es de 2 px: se le quita 1 px al relleno para que el
   punto no salte al cambiar de fila, y queda con aire (Santiago, 2026-11-05). */
.lote-fila.is-activo { border: 2px solid var(--primary); background: var(--primary-soft);
                       padding: calc(var(--space-100) - 1px) calc(var(--space-100) - 1px)
                                calc(var(--space-100) - 1px) calc(var(--space-250) - 1px); }
.lote-fila-marcar { flex: 1 1 14rem; min-width: 0; margin: 0; }
.lote-fila-marcar > button { width: 100%; min-height: 44px; display: flex; align-items: center;
                             gap: var(--space-150); padding: 0; border: 0; background: none;
                             color: inherit; font: inherit; text-align: left; cursor: pointer; }
.lote-fila-punto { flex: none; width: 18px; height: 18px; border-radius: 50%;
                   border: 2px solid var(--border-strong); }
.is-activo .lote-fila-punto { border: 5px solid var(--primary); background: var(--card-bg); }
.lote-fila-que { flex: 1; min-width: 0; }
.lote-fila-que strong { display: block; }
.lote-fila-que small { color: var(--text-light); font-size: var(--fs-small); }
.lote-fila-cifra { margin: 0; }
.lotes-captura-pregunta { display: grid; gap: var(--space-100); margin: 0; }
.lotes-captura-pregunta .form-section-head { margin: 0; }
.lotes-captura-acciones { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-150); }
.ajuste-celda-queda { margin: 0 0 var(--space-150); font-weight: 600; }
.ajuste-celda-pie { display: flex; justify-content: flex-end; gap: var(--space-100); }
/* De qué está comprometido (2026-10-29): la misma ventanita, sin formulario.
   Un renglón por documento, el nombre a la izquierda y la cifra a la derecha. */
.ajuste-celda-detalle { padding: var(--space-150); }
.comprometido-lista { list-style: none; margin: 0 0 var(--space-150); padding: 0; }
.comprometido-lista li { display: flex; justify-content: space-between; gap: var(--space-150); padding: var(--space-100) 0; border-top: 1px solid var(--border); }
.comprometido-lista li:first-child { border-top: 0; }
.comprometido-lista .cell-sub { display: block; }
/* «Usar saldo a favor» (2026-10-31): la nota de abajo con aire antes de los botones. */
.ajuste-celda-form .saldo-pop-nota { margin: 0 0 var(--space-150); }
/* En el teléfono el título ya lo dice la cabeza de la hoja. */
@media (max-width: 640px) { .ajuste-celda-titulo { display: none; } }

/* Por ELEMENTO y no por `.field`, y es la tercera vez que esta forma aparece.

   Estaba como `.field input[...]`, así que un `<input>` que no viviera dentro de
   un `.field` caía al control por omisión del navegador — más angosto, con otra
   letra y sin el foco del ERP. Ya pasó con el filtro de texto de la F1-b, que
   por eso tiene su propia guarda, y volvió a pasar con la casilla de comentario
   de la ficha del tercero. Dos veces es casualidad; tres es el selector.

   El ancho es lo único que se queda en `.field`: adentro de un campo el control
   llena su columna, pero en una fila —la casilla de comentario al lado de su
   botón— es el `flex` quien decide, y un `width: 100%` ahí lo empuja fuera. */
/* `number` e `input:not([type])` entran los dos, y llegaron por caminos
   distintos a la MISMA forma de defecto: la regla estaba escrita y no alcanzaba
   al control.

   `number` lo midió la ficha del producto —el monto de una línea de costo salía
   con el borde gris del navegador, radio 0 y 22px de alto contra los 34 del
   concepto de al lado—. Son once controles en tres pantallas.

   `input:not([type])` lo midió el editor de formularios: un `<input>` sin
   atributo `type` ES un campo de texto —lo dice el navegador— pero
   `input[type="text"]` NO lo selecciona, y hay 24 escritos así. El título del
   paso medía 240px al lado de un textarea de ancho completo, sin padding y sin
   foco.

   Que dos fases lo encontraran por separado es el dato: la lista de selectores
   es la que se queda atrás cada vez que alguien escribe un control nuevo. */
input[type="email"],
input[type="text"],
input[type="number"],
input[type="password"],
input[type="datetime-local"],
input[type="month"],
input:not([type]) {
    padding: var(--space-100) var(--space-150);
    font-family: inherit;
    font-size: var(--fs-body);
    color: var(--text);
    background: var(--field-bg);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-field);
    outline: none;
    transition: border-color .16s, box-shadow .16s;
}
/* `number` NO va acá: su `width` ya lo fija el bloque de controles del final,
   y repetirlo es exactamente lo que `test_no_selector_sets_the_same_property_in_two_places`
   existe para impedir. */
.field input[type="email"],
.field input[type="text"],
.field input[type="password"],
.field input[type="datetime-local"],
.field input[type="month"],
.field input:not([type]) { width: 100%; }

input[type="email"]:focus,
input[type="text"]:focus,
input[type="number"]:focus,
input[type="password"]:focus,
input[type="datetime-local"]:focus,
input[type="month"]:focus,
input:not([type]):focus {
    border-color: var(--primary);
    box-shadow: 0 0 0 3.5px var(--primary-soft);
}

/* Un campo APAGADO se ve apagado (2026-10-22). Sin esto salía igual de
   blanco que uno abierto, y «Cuánto trae» en «Unidad» parecía un campo que
   no respondía al clic en vez de uno que no aplica. */
input[type="email"]:disabled,
input[type="text"]:disabled,
input[type="number"]:disabled,
input[type="password"]:disabled,
input[type="datetime-local"]:disabled,
input:not([type]):disabled {
    background: var(--table-head-bg);
    color: var(--text-faint);
    cursor: not-allowed;
}

input[aria-invalid="true"] {
    border-color: var(--danger-text);
}

/* Con el foco también: el campo que falta se enfoca solo (confirmar
   escribiendo, 2026-10-18) y el morado del foco le tapaba el rojo. */
input[aria-invalid="true"]:focus {
    border-color: var(--danger-text);
    box-shadow: 0 0 0 3.5px var(--danger-bg);
}

/* La equis para borrar de TODO buscador (`type="search"`): la del navegador
   sale azul y gruesa. Nació en la paleta ⌘K (2026-10-06) y subió acá cuando
   el buscador del perfil mostró la misma (Renzo, 2026-10-10: «la x del input
   no me gusta»). Una cruz fina del color del texto suave. */
input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none; appearance: none;
    width: 12px; height: 12px; margin-left: var(--space-100); cursor: pointer;
    background: var(--text-faint);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 2l10 10M12 2L2 12' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 2l10 10M12 2L2 12' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
input[type="search"]::-webkit-search-cancel-button:hover { background: var(--text); }

/* Las reglas de contraseña las emite AUTH_PASSWORD_VALIDATORS como un <ul>
   ya armado (password_validators_help_text_html), así que el estilo tiene que
   apuntar a esa lista y no a un marcado propio. Se muestran como ayuda antes
   de escribir, no como reproche después. */
/* La ayuda que escribe la plantilla. `.field-help` es la del `help_text` del
   widget —que en la pantalla de contraseña trae una lista dentro— y por eso
   siguen siendo dos clases; comparten la tipografía en una sola regla para
   que no se separen. `.field-hint` venía de la F1-a sin ninguna. */
/* La etiqueta de un campo con un control a su derecha. Lo estrena la ficha del
   artículo: el interruptor «Editar como HTML» tiene que estar PEGADO a lo que
   gobierna —el cuerpo— y no perdido al final del formulario, porque la
   pregunta que responde es «con qué edito esto». */
/* Dos casillas que son UN filtro: desde/hasta, mínimo/máximo. Lado a lado
   porque son los dos extremos de lo mismo — una debajo de la otra las lee
   como dos preguntas distintas. En el celular caen a una columna. */
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-100); }
@media (max-width: 600px) { .field-pair { grid-template-columns: 1fr; } }
/* El par donde el primero solo CALIFICA al segundo: tipo y número de un
   documento («CC 1.017…»). Angosto el primero y sin partirse en el celular:
   separados son dos preguntas, y el tipo solo no identifica a nadie. */
.field-pair.field-pair-lead { grid-template-columns: 5.5rem minmax(0, 1fr); }

.field-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); margin-bottom: var(--space-075); flex-wrap: wrap;
}
.field-head label { margin-bottom: 0; }

.field-hint, .field-help {
    font-size: var(--fs-small); line-height: 1.45; color: var(--text-light);
}
.field-hint { margin: var(--space-075) 0 0; }
.field-help { margin-top: var(--space-100); }
.field-help ul {
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: var(--fs-small);
    color: var(--text-faint);
    line-height: 1.55;
}
.field-help li { padding-left: var(--space-200); position: relative; }
.field-help li::before {
    content: '';
    position: absolute;
    left: 3px; top: 8px;
    width: 4px; height: 4px;
    border-radius: 50%;
    background: var(--border-strong);
}

.field-error {
    margin: var(--space-100) 0 0;
    font-size: var(--fs-small);
    font-weight: 500;
    color: var(--danger-text);
}

/* ── Fortaleza de contraseña ───────────────────────────────────────────────
   El estado lo calcula el servidor (kernel/identity/password_feedback.py) con
   los MISMOS validadores que rechazan al enviar. Aquí solo se pinta.
   ------------------------------------------------------------------------ */

.pw-feedback { margin-top: var(--space-100); }
.pw-feedback:empty { display: none; }

.pw-strength {
    display: flex;
    gap: var(--space-050);
}

.pw-seg {
    flex: 1;
    height: 5px;
    border-radius: 99px;
    background: var(--border);
    transition: background .28s ease;
}

/* El nivel llena tantos segmentos como su número. La CANTIDAD es la señal
   que sobrevive a la ceguera al color; el tono solo la refuerza. */
.pw-strength[data-level="1"] .pw-seg:nth-child(-n+1) { background: var(--strength-1); }
.pw-strength[data-level="2"] .pw-seg:nth-child(-n+2) { background: var(--strength-2); }
.pw-strength[data-level="3"] .pw-seg:nth-child(-n+3) { background: var(--strength-3); }
.pw-strength[data-level="4"] .pw-seg:nth-child(-n+4) { background: var(--strength-4); }

/* Solo se listan las reglas pendientes: una que se cumple, desaparece. */
.pw-rules {
    margin: var(--space-150) 0 0;
    padding: 0;
    list-style: none;
    font-size: var(--fs-small);
    line-height: 1.45;
    color: var(--text-light);
}
.pw-rules li {
    display: flex;
    align-items: flex-start;
    gap: var(--space-100);
    padding: var(--space-025) 0;
    animation: rule-in .2s ease both;
}
.pw-rules svg {
    width: 13px; height: 13px;
    flex-shrink: 0;
    margin-top: var(--space-050);
    color: var(--danger-text);
}

@keyframes rule-in {
    from { opacity: 0; transform: translateY(-2px); }
    to   { opacity: 1; transform: translateY(0); }
}

.pw-ready {
    display: flex;
    align-items: center;
    gap: var(--space-100);
    margin: var(--space-150) 0 0;
    font-size: var(--fs-small);
    font-weight: 600;
    color: var(--ok-text);
    animation: rule-in .22s ease both;
}
.pw-ready svg { width: 14px; height: 14px; flex-shrink: 0; }

.alert {
    display: flex;
    gap: var(--space-100);
    padding: var(--space-150) var(--space-150);
    border-radius: var(--radius);
    font-size: var(--fs-body);
    font-weight: 500;
    margin-bottom: var(--space-250);
}
.alert-danger { background: var(--danger-bg); color: var(--danger-text); }
.alert-warn   { background: var(--warn-bg);   color: var(--warn-text); }
.alert-ok     { background: var(--ok-bg);     color: var(--ok-text); }
.alert svg { flex-shrink: 0; margin-top: 1px; }
/* Un botón dentro del aviso va a su derecha, centrado: «Aprobar diferencias»
   en el recuadro de la diferencia de una factura (2026-11-01). */
.alert > .btn { margin-left: auto; flex-shrink: 0; align-self: center; }

/* ── Botones ───────────────────────────────────────────────────────────── */

/* 12px, decidido mirando la pantalla. El escalón de arriba —14.5— dejaba los
   controles pesados al lado del texto de la interfaz.

   Los campos de TEXTO no bajaron con ellos, y eso no es una inconsistencia:
   iOS Safari hace zoom a la página al enfocar un campo de menos de 16px, así
   que ahí el tamaño no es una decisión estética sino una página que salta y
   hay que volver a alejar a mano.

   El relleno se queda en 12px/20px: bajar la letra sin bajar el relleno es lo
   que mantiene el botón por encima de los 40px de alto, que es lo que un
   pulgar necesita. */
/* ── El sistema de botones ─────────────────────────────────────────────────
   TRES tamaños y UN radio (Renzo, 2026-08-14). Antes había dos tamaños con
   ocho píxeles de diferencia y ningún criterio escrito de cuándo usar cuál,
   así que la misma acción salía chica en una pantalla y grande en la de al
   lado.

       .btn            38px   la acción de una pantalla o de un formulario
       .btn-small      30px   dentro de una fila, una banda o una tarjeta
       .btn-icon       30px   cuadrado, solo glifo, con `aria-label`

   El RADIO es `--radius-field` y no `--radius`: aquél es el token de un
   CONTROL —el mismo del campo de texto y del `select`— y éste el de una
   TARJETA. Un botón al lado de un campo con dos radios distintos se lee
   como dos sistemas; con el mismo, como una fila de controles. Es el mismo
   argumento que ya está escrito en `--radius-field`.
   ------------------------------------------------------------------------ */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-100);
    padding: var(--space-150) var(--space-250);
    font-family: inherit;
    font-size: var(--fs-body);
    font-weight: 600;
    line-height: 1;
    border: 1px solid transparent;
    border-radius: var(--radius-field);
    cursor: pointer;
    transition: background .16s, box-shadow .16s, transform .08s;
}
/* El glifo de un botón se dimensiona ACÁ y no en el `<svg>` de cada plantilla:
   `{% icon %}` emite los trazos y quien llama pone el elemento, así que sin
   esta regla cada botón elegiría su propio tamaño. `flex: none` porque el
   `.btn` es flex y un `<svg>` sin ancho intrínseco se deja aplastar por el
   texto largo — «Empezar preparación» le comía el icono. */
.btn svg { width: 15px; height: 15px; flex: none; }
/* La cara de un personaje en lugar del glifo (`cara`, 2026-10-12): un poco
   más grande que un glifo para que se le vea la cara, y lo mismo en 3D. */
.btn .btn-cara { width: 20px; height: 20px; flex: none; margin: calc(-1 * var(--space-025)) 0; }

/* `width: auto`, que es lo que un botón es.

   Estaba en `100%` por omisión —salió de la pantalla de entrar, donde el
   botón sí ocupa la tarjeta— y dos reglas lo devolvían a `auto` para el
   formulario y para el banner. La tercera pantalla que lo usó sin saberlo fue
   la barra de filtros, y ahí «Filtrar» salió como una franja morada de lado a
   lado. Una omisión que necesita excepciones es la omisión equivocada: ahora
   el que quiere ocupar todo lo pide con `btn-block`. */
.btn-primary {
    color: #fff;
    background: var(--primary);
    box-shadow: 0 2px 8px rgba(124, 92, 240, .28);
}
.btn-primary:hover  { background: var(--primary-dark); box-shadow: 0 4px 14px rgba(124, 92, 240, .34); }
.btn-primary:active { transform: translateY(1px); }

/* El que SÍ ocupa todo el ancho, y lo pide. Es el de entrar y el de aceptar
   una invitación: pantallas de una sola tarjeta angosta donde el botón es la
   única acción que hay. */
.btn-block { width: 100%; }

.btn-ghost {
    color: var(--text-light);
    background: transparent;
    border-color: var(--border-strong);
}
/* ── El botón de AGREGAR UNO MÁS ───────────────────────────────────────────
   Punteado y en tinta de marca. El punteado es la convención de «acá cabe otro
   y todavía no está»: un borde entero promete una cosa que ya existe.

   No es un tono nuevo por capricho — es lo que resuelve una competencia real.
   El botón de agregar vive DENTRO de una tarjeta, y en primario competía con
   «Guardar» de la barra, que es el único que cierra la ficha; en fantasma se
   perdía contra el panel gris en el que ahora está parado. */
.btn-dashed {
    color: var(--primary-dark);
    background: transparent;
    border-style: dashed; border-color: var(--primary);
}
.btn-dashed:hover { background: var(--primary-soft); }
/* El fondo se OSCURECE, no se aclara. El hover viejo pintaba `#fff` sobre
   tarjetas que ya son blancas: no se notaba nada, y por eso parecía que el
   botón no reaccionaba. Texto blanco tampoco sirve acá —`.btn-ghost` es
   transparente y son 110 pantallas sobre fondo claro— así que el estado lo da
   el fondo. Los del banner y los de la savebar tienen su propia regla porque
   viven sobre oscuro. */
.btn-ghost:hover { background: var(--bg); color: var(--text); }

/* APAGADO se tiene que VER apagado (2026-10-19). Un morado lleno que no hace
   nada se lee como un botón roto: así llegó «Enviar a revisión» de
   Mantenimiento, `disabled` mientras falta el documento del tercero y con la
   misma cara que uno prendido. Antes cada contexto ponía su copia
   (`.period-foot`, `.value-row`, `.selector`, `.atm-toolbar`,
   `.al-seleccion`); ésta es la de la casa, y esas cinco quedan para borrar.
   `aria-disabled` cubre el enlace que se apaga sin dejar de ser enlace. */
.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: .45; cursor: not-allowed; box-shadow: none; transform: none;
}
/* Y pasar encima no lo «prende»: cada tono vuelve a su fondo de reposo. La
   especificidad (tono + estado + hover) le gana a los `:hover` de cada tono
   estén donde estén en la hoja. */
.btn-primary:disabled:hover, .btn-primary[aria-disabled="true"]:hover,
.btn-primary:disabled:active, .btn-primary[aria-disabled="true"]:active {
    background: var(--primary); box-shadow: none; transform: none;
}
.btn-ghost:disabled:hover, .btn-ghost[aria-disabled="true"]:hover {
    background: transparent; color: var(--text-light);
}
.btn-dashed:disabled:hover, .btn-dashed[aria-disabled="true"]:hover { background: transparent; }
.btn-danger:disabled:hover, .btn-danger[aria-disabled="true"]:hover,
.btn-danger-ghost:disabled:hover, .btn-danger-ghost[aria-disabled="true"]:hover {
    background: transparent; color: var(--text-light); border-color: var(--border-strong);
}

.btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3.5px var(--primary-soft);
}

/* ── El shell del ERP ──────────────────────────────────────────────────────
   Portado del dashboard de tiendapp (static/dashboard/css/style.css:404-520).
   `--canvas` lleva declarado desde P0-a sin que lo usara nadie: es esto.

   Lo que había acá antes —`.shell*` de la landing, `.placeholder-card` y
   `.module-slots`— eran el andamio con las ocho ranuras escritas a mano. Se
   pusieron para que se vieran al reemplazarlas, y esto es reemplazarlas.
   ------------------------------------------------------------------------ */

:root {
    --sidebar: 250px; --rail: 68px;
    /* Con la muesca del iPhone: todo lo que se apoya en la barra de arriba
       lee esta medida, y así ninguno tiene que acordarse del área segura.
       En Safari vale 0; en la app, lo que mida el teléfono. */
    --topbar-franja: 64px;
    /* Lo menos que mide lo que se toca con el dedo. Lo usa el teléfono. */
    --toque: var(--space-600);
    /* Lo que mide el menú en el teléfono: al lado asoma la pantalla. */
    --cajon: 86vw;
    /* Un toque, el relleno de la cápsula y su borde. */
    --pestanas-casa-h: calc(var(--toque) + 2 * var(--space-050) + 2px);
    --topbar: calc(var(--topbar-franja) + env(safe-area-inset-top));
}

body.app { background: var(--canvas); }

.topbar {
    position: fixed; inset: 0 0 auto 0; height: var(--topbar);
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150);
    /* Acostado, la muesca queda a un lado: `max()` la esquiva sin tocar el
       escritorio, donde el área segura vale 0. */
    padding: env(safe-area-inset-top)
             max(clamp(var(--space-200), 3vw, var(--space-400)), env(safe-area-inset-right))
             0
             max(clamp(var(--space-200), 3vw, var(--space-400)), env(safe-area-inset-left));
    background: var(--canvas); color: #fff; z-index: 100;
}
.topbar-left { display: flex; align-items: center; gap: var(--space-100); min-width: 0; }
/* El logo, con la altura como única medida: el `<svg>` no trae `width` ni
   `height` y el ancho sale de su relación de aspecto. `currentColor` es lo
   que pinta el wordmark, así que el color se hereda de donde esté puesto —
   blanco en la barra oscura, tinta en la tarjeta de entrar. */
.brand-logo { display: block; width: auto; }
.topbar-brand .brand-logo { height: 30px; }
.topbar-brand {
    display: inline-flex; align-items: center;
    color: inherit; text-decoration: none; flex-shrink: 0;
}
.topbar-brand:focus-visible {
    outline: 2px solid #fff; outline-offset: 4px; border-radius: 4px;
}
.topbar-crumb {
    color: rgba(255, 255, 255, .62); font-size: var(--fs-body);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topbar-crumb a { color: inherit; }
.topbar-crumb strong { color: #fff; font-weight: 600; }

.icon-btn {
    display: grid; place-items: center;
    width: 32px; height: 32px; padding: 0;
    color: rgba(255, 255, 255, .8); background: transparent;
    border: 0; border-radius: 9px; cursor: pointer; font-size: var(--fs-glyph);
}
.icon-btn:hover { background: rgba(255, 255, 255, .1); color: #fff; }
.icon-btn:focus-visible { outline: 2px solid #fff; outline-offset: 1px; }

.user-menu { position: relative; }
.user-menu summary {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-050) var(--space-100) var(--space-050) var(--space-050); border-radius: 99px; cursor: pointer;
    list-style: none; color: #fff; font-size: var(--fs-body); font-weight: 600;
}
.user-menu summary::-webkit-details-marker { display: none; }
.user-menu summary:hover { background: rgba(255, 255, 255, .1); }
.user-menu summary:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.user-menu-name {
    max-width: 22ch; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap;
}
.avatar {
    width: 32px; height: 32px; flex-shrink: 0;
    display: grid; place-items: center;
    border-radius: 50%; font-size: var(--fs-small); font-weight: 700; color: #fff;
    background: linear-gradient(135deg, var(--primary) 0%, #a78bfa 100%);
}
/* La foto en el círculo de cualquier avatar (`ui/_avatar.html`): la clase
   del tamaño la pone quien llama; esta solo evita que una foto vertical se
   aplaste. */
.avatar-foto { object-fit: cover; }
/* La cara de la empresa en el disparador (`ui/_company_mark.html`): un
   cuadrado con esquinas y no el círculo del avatar, porque un logo recortado
   en círculo es otro logo. Con logo, fondo blanco y `contain`: el archivo se
   ve entero, sea cuadrado u horizontal; sin logo, la inicial sobre el
   degradado de la casa. */
.company-mark {
    width: 32px; height: 32px; flex-shrink: 0;
    display: grid; place-items: center;
    border-radius: 20px; font-size: var(--fs-small); font-weight: 700; color: #fff;
    background: linear-gradient(135deg, var(--primary) 0%, #a78bfa 100%);
}
/* Redondo y sin relleno ni fondo blanco (Renzo, 2026-09-14): el logo llena
   el círculo entero, como la foto de un avatar. */
img.company-mark-logo { object-fit: contain; padding: 0; box-sizing: border-box; }
/* El menú del usuario es un `.menu-pop` más (misma caja, mismos ítems); lo
   suyo es la cabecera de quién es —foto, nombre y correo— que abre el bloque
   personal, debajo del bloque de la empresa. */
.user-menu-pop { top: calc(100% + 8px); min-width: 240px; }
.user-menu-who {
    display: grid; grid-template-columns: auto minmax(0, 1fr);
    column-gap: var(--space-100); row-gap: 1px; align-items: center;
    padding: var(--space-100) var(--space-100) var(--space-075);
}
.user-menu-who .avatar { grid-row: 1 / span 2; width: 30px; height: 30px; }
/* Con gorrito, la envoltura es la que ocupa los dos renglones. */
.user-menu-who > .avatar-cumple { grid-row: 1 / span 2; }
.user-menu-nombre { font-size: var(--fs-body); font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu-correo { font-size: var(--fs-small); color: var(--text-light); overflow: hidden; text-overflow: ellipsis; }
/* ── La campanita de los avisos ─────────────────────────────────────────
   El disparador es el mismo `<summary>` blanco de la topbar que ya tiene el
   menú de la casa; lo suyo es el glifo y el punto. El desplegable hereda de
   `.menu-pop` la caja entera (fondo, borde, sombra, posición): acá solo se
   le da el ancho y el desplazamiento, que es lo único que una lista de
   avisos necesita y un menú de cuatro filas no. */
.notices { position: relative; }
.notices summary {
    display: flex; align-items: center; padding: var(--space-100); border-radius: 99px;
    list-style: none; cursor: pointer; color: #fff; position: relative;
}
.notices summary::-webkit-details-marker { display: none; }
.notices summary:hover { background: rgba(255, 255, 255, .1); }
.notices summary:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
/* El eje de giro arriba, donde cuelga la campana: girada por el centro se
   ve como un glifo que tiembla, no como una campana que suena. */
.notices summary svg { width: 20px; height: 20px; transform-origin: 50% 3px; }
/* MIENTRAS HAYA ALGO SIN LEER, LA CAMPANA VIBRA: un vaivén corto cada
   cinco segundos, el resto del ciclo quieta. `:has(.notices-dot)` porque el
   punto solo existe con avisos. Un punto quieto en la esquina de una barra
   oscura no lo ve nadie —la gente no se enteraba de que le habían
   escrito—, y un anillo que late en rojo con opacidad sobre negro tampoco
   («no se ve casi… mejor un buzz o algo que se mueva en la campanita»,
   Renzo, 2026-09-22). Cuando llega uno nuevo, `shell-live.js` reinicia la
   animación para que vibre en ese instante y le pone `is-nueva` al
   `<summary>` para que el punto salte. El bloque universal de movimiento
   reducido apaga las dos. */
.notices summary:has(.notices-dot) svg { animation: campana-vibra 5s ease-in-out infinite; }
.notices summary.is-nueva .notices-dot { animation: campana-salta .6s cubic-bezier(.2, 1.4, .4, 1); }
@keyframes campana-vibra {
    0%, 18%, 100% { transform: rotate(0deg); }
    3% { transform: rotate(16deg); }
    6% { transform: rotate(-14deg); }
    9% { transform: rotate(10deg); }
    12% { transform: rotate(-6deg); }
    15% { transform: rotate(3deg); }
}
@keyframes campana-salta {
    0% { transform: scale(.3); }
    60% { transform: scale(1.3); }
    100% { transform: scale(1); }
}
/* El punto se apoya en la esquina del glifo. `--danger-text` es el mismo
   rojo del `.nav-badge.tone-danger`: dos rojos para «tienes algo pendiente»
   sería el ERP hablando con dos voces. */
.notices-dot {
    position: absolute; top: 2px; right: 2px;
    min-width: 17px; padding: 0 var(--space-050); border-radius: 99px;
    background: var(--danger-text); color: #fff;
    font-size: var(--fs-badge); font-weight: 700; line-height: 17px;
    text-align: center;
}
/* 360 y no 320: con 320 «Renzo: Te aparece notificación en la campanita»
   se cortaba en la segunda palabra útil. `min()` con el ancho de la
   ventana para que en el teléfono no se salga por la izquierda. */
.notices-pop { top: calc(100% + 8px); width: min(360px, calc(100vw - 24px));
    max-height: min(70vh, 540px); overflow-y: auto; padding: var(--space-050); }
/* EL TEXTO ENVUELVE. `.menu-pop` fija `white-space: nowrap` —lo suyo son
   filas de una línea— y está MÁS ABAJO en la hoja que `.notices-pop`, así
   que con la misma especificidad ganaba y el aviso salía cortado por el
   borde derecho («…en la campanita de ag», Renzo, 2026-09-22). Dos clases
   le ganan por peso, no por orden. */
.notices .notices-pop { white-space: normal; }
/* El vacío de la campanita: el estado vacío de la casa, centrado y con la
   campana, pero sin el aire que `.empty` reserva para un botón que acá no
   hay — ese hueco de abajo era lo que se veía «raro». */
.notices-pop .empty { padding: var(--space-300) var(--space-200) var(--space-200); }
.notices-pop .empty-text { margin-bottom: 0; font-size: var(--fs-small); }

.avisos-lista { list-style: none; margin: 0; padding: 0; }
.avisos-item + .avisos-item { border-top: 1px solid var(--border); }
/* Tres columnas: la inicial a la izquierda ocupando las dos filas, el texto
   en el medio y a la derecha lo pequeño —la hora arriba, el contador
   abajo—, como una lista de chats. `minmax(0, 1fr)` y no `1fr`: `1fr` es
   `minmax(auto, 1fr)` y una palabra larga estiraría la columna por fuera
   de la caja en vez de cortarse. La primera, FIJA de 40px (2026-10-11): la
   cara del Asistente es más grande que la inicial, y con `auto` el texto de
   su fila arrancaba más a la derecha que el de las otras. */
.avisos-item > a {
    display: grid; grid-template-columns: 40px minmax(0, 1fr) auto;
    column-gap: var(--space-100); row-gap: var(--space-025); align-items: start;
    padding: var(--space-100); border-radius: var(--radius-sm);
    color: var(--text); text-decoration: none;
}
.avisos-item > a:hover { background: var(--primary-soft); }
/* La inicial del título con el `.avatar` de la casa, a lo alto de las dos
   filas. Sin glifo por `kind`: el kernel no sabe qué módulo publica cada
   familia y la guarda del shell se lo prohíbe. Centrada en el alto del
   mensaje, no arriba (Renzo, 2026-10-11). */
.avisos-item > a > .avatar { grid-row: 1 / span 2; align-self: center; justify-self: center; }
/* La cara de un personaje cuando quien avisa es el Asistente o un agente
   (`Notice.avatar`): el mismo lugar que la inicial, centrada igual, y MÁS
   GRANDE (Renzo, 2026-10-11: «que sea más grandecito»). El dibujo trae aire
   por dentro, así que a 32px se veía más chico que la inicial. 40px, lo
   que mide la columna (se probaron 48 y 42). */
.avisos-cara { grid-row: 1 / span 2; align-self: center; width: 40px; height: 40px; }
.avisos-cara > svg { width: 100%; height: 100%; display: block; }
.avisos-title {
    font-size: var(--fs-body); font-weight: 600; line-height: 1.35;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.avisos-when { font-size: var(--fs-meta); color: var(--text-faint); white-space: nowrap; padding-top: var(--space-025); }
/* Dos líneas y puntos suspensivos: el aviso trae el último mensaje entero
   y un párrafo de WhatsApp haría la fila de diez renglones. */
.avisos-text {
    grid-column: 2; font-size: var(--fs-small); color: var(--text-light);
    line-height: 1.4; overflow: hidden; display: -webkit-box;
    -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow-wrap: anywhere;
}
.avisos-count {
    grid-column: 3; justify-self: end; min-width: 20px; padding: 1px var(--space-075);
    border-radius: 99px; background: var(--primary); color: #fff;
    font-size: var(--fs-meta); font-weight: 700; text-align: center;
}

/* La ranura vacía no ocupa lugar: existe para que el JS tenga dónde
   escribir, no para reservar un hueco que se vea. */
.badge-slot:empty { display: none; }
/* El `display` del sub-ítem vive en SU bloque (`.nav-subitem`, más abajo):
   declarado acá perdía contra el `block` de allá y la ranura quedaba pegada
   al texto en vez de irse a la derecha. */
.nav-subitem .badge-slot { margin-left: auto; }

/* Las filas del menú son `_btn.html` con `extra_class="menu-item"`: lo de
   botón (borde, alto, centrado) se apaga y queda la fila del menú. */
.menu-pop .btn.menu-item {
    height: auto; min-height: 0; border: 0; box-shadow: none;
    justify-content: flex-start; font-weight: 500;
}
.menu-pop .btn.menu-item:hover { background: var(--primary-soft); color: var(--primary-dark); border: 0; }

/* El selector de tema: tres botones de ícono en un renglón del menú. El
   puesto se marca desde `data-tema` del formulario. */
.tema-picker {
    display: flex; align-items: center; gap: var(--space-050);
    padding: var(--space-050) var(--space-150);
}
.tema-picker-label { flex: 1; font-size: var(--fs-body); font-weight: 500; }
.tema-picker .btn { border-color: transparent; box-shadow: none; }
.tema-picker[data-tema="claro"] .btn[value="claro"],
.tema-picker[data-tema="mixto"] .btn[value="mixto"],
.tema-picker[data-tema="oscuro"] .btn[value="oscuro"] {
    background: var(--primary-soft); color: var(--primary-dark);
    border-color: var(--primary-soft-border);
}

/* ── El shell: dos tarjetas y no una ──────────────────────────────────────
   Hasta el 2026-09-29 el shell era UNA tarjeta con el menú y el contenido
   pegados por una raya. Ahora es transparente y cada pieza flota por su
   cuenta sobre el lienzo, con `--shell-gap` de aire entre las dos: el menú
   se lee como navegación y no como la primera columna de la pantalla. */
.shell {
    max-width: 1480px; margin: var(--topbar) auto 0;
    min-height: calc(100dvh - var(--topbar));
    display: flex;
    padding: 0 max(var(--shell-gap), env(safe-area-inset-right)) 0 max(var(--shell-gap), env(safe-area-inset-left));
}

.sidebar {
    flex: none; width: var(--sidebar);
    margin-right: var(--shell-gap);
    padding: var(--space-200) var(--space-100);
    position: sticky; top: var(--topbar);
    height: calc(100dvh - var(--topbar)); overflow-y: auto;
    background: var(--sidebar-bg);
    border-radius: var(--radius-shell) var(--radius-shell) 0 0;
    box-shadow: var(--shadow-shell);
    /* Se desplaza con la rueda o el dedo, pero la barra no se pinta: en una
       columna de 250px estorbaba más de lo que ayudaba. */
    scrollbar-width: none;
}
.sidebar::-webkit-scrollbar { display: none; }

/* La tarjeta de segundo nivel (`ui/_menu_lateral.html`, Renzo, 2026-10-31):
   lo que era «Administración» en una tarjeta APARTE entre el menú y el
   contenido —su propio aire de los dos lados, sus esquinas, su sombra—, no
   una extensión del menú. Mide y se pega como el menú; la piel se la da
   `.como-menu` (en mixto, oscura con él). */
:root { --menu-lateral: 232px; }
.menu-lateral {
    flex: none; width: var(--menu-lateral);
    margin-right: var(--shell-gap);
    padding: var(--space-200) var(--space-100);
    position: sticky; top: var(--topbar);
    height: calc(100dvh - var(--topbar)); overflow-y: auto;
    background: var(--sidebar-bg);
    border-radius: var(--radius-shell) var(--radius-shell) 0 0;
    box-shadow: var(--shadow-shell);
    scrollbar-width: none;
}
.menu-lateral::-webkit-scrollbar { display: none; }
/* En la Guía, la pieza de muestra no se pega a la ventana ni la mide. */
.guia-menu-lateral .menu-lateral { position: static; height: auto; margin: 0; }

/* ── El panel lateral que un módulo publica ────────────────────────────── */
:root { --side-panel: 380px; }

.topbar-right { display: flex; align-items: center; gap: var(--space-050); }
.topbar-sep { width: 1px; height: 24px; background: rgba(255, 255, 255, .22); margin: 0 var(--space-075); }
.side-panel-form { display: inline-flex; }
.side-panel-toggle { width: 48px; height: 48px; padding: var(--space-050); }
.side-panel-toggle[aria-expanded="true"] { background: rgb(255 255 255 / 0%); }
/* El personaje del panel: las burbujas del loader con cara. En reposo
   respira, parpadea y sonríe; con `is-thinking` en el botón mira arriba,
   aparecen los puntitos y el anillo gira. Prefijo `compi-` para no chocar
   con las clases y keyframes del loader de la barra. 40 px desde el
   2026-10-03 (Renzo): es el principal, se ve más que los íconos de al lado. */
.side-panel-mark { display: block; width: 40px; height: 40px; }
.compi { --compi-pulse: 2s; --compi-loop: 6s; --compi-think: 2.4s; }
.compi-ring, .compi-arm, .compi-core, .compi-au, .compi-white,
.compi-size, .compi-body, .compi-face, .compi-gaze, .compi-smile, .compi-eye { transform-origin: 0 0; }
.compi-white { fill: #fff; opacity: 0; }
.compi-ring { transform: rotate(30deg); }
.compi-arm { transform: translateX(-70px) scale(1.08); }
.compi-core { transform: scale(1.14); }
.compi-size { transform: scale(1.95); }
.compi .compi-ring { animation: compi-form-turn calc(var(--compi-pulse) * .5) linear both; }
.compi .compi-arm { animation: compi-form-arm calc(var(--compi-pulse) * .5) cubic-bezier(.45,0,.55,1) both; }
.compi .compi-core { animation: compi-form-core calc(var(--compi-pulse) * .5) cubic-bezier(.45,0,.55,1) both; }
.compi .compi-white { animation: compi-form-color calc(var(--compi-pulse) * .5) cubic-bezier(.45,0,.55,1) both; }
.compi .compi-face { animation: compi-face-in .46s cubic-bezier(.2,.7,.25,1.15) calc(var(--compi-pulse) * .5) both; }
.compi .compi-size { animation: compi-grow calc(var(--compi-pulse) * .5) cubic-bezier(.45,0,.55,1) both; }
.compi .compi-body { animation: compi-breathe var(--compi-loop) ease-in-out calc(var(--compi-pulse) * .5) infinite; }
.compi .compi-gaze { animation: compi-gaze var(--compi-loop) ease-in-out calc(var(--compi-pulse) * .5 + .46s) infinite; }
.compi .compi-eye { animation: compi-blink var(--compi-loop) linear calc(var(--compi-pulse) * .5 + .46s) infinite; }
.compi .compi-smile { animation: compi-smile var(--compi-loop) ease-in-out calc(var(--compi-pulse) * .5) infinite; }
.compi .compi-au1 { animation: compi-drift1 var(--compi-loop) ease-in-out infinite; }
.compi .compi-au2 { animation: compi-drift2 var(--compi-loop) ease-in-out infinite; }
.compi .compi-au3 { animation: compi-drift3 var(--compi-loop) ease-in-out infinite; }
.is-thinking .compi-gaze { animation: compi-think-gaze var(--compi-think) ease-in-out infinite; }
.is-thinking .compi-eye { animation: compi-think-blink calc(var(--compi-think) * 2) linear infinite; }
.is-thinking .compi-body { animation: compi-think-tilt var(--compi-think) ease-in-out infinite; }
.is-thinking .compi-ring { animation: compi-think-spin 9s linear infinite; }
.is-thinking .compi-arm { animation: compi-think-arm calc(var(--compi-think) * 1.25) ease-in-out infinite; }
.is-thinking .compi-ring > g:nth-child(2) .compi-arm { animation-delay: -.5s; }
.is-thinking .compi-ring > g:nth-child(3) .compi-arm { animation-delay: -1s; }
.is-thinking .compi-ring > g:nth-child(4) .compi-arm { animation-delay: -1.5s; }
.is-thinking .compi-ring > g:nth-child(5) .compi-arm { animation-delay: -2s; }
.is-thinking .compi-ring > g:nth-child(6) .compi-arm { animation-delay: -2.5s; }
.is-thinking .compi-core { animation: compi-think-core calc(var(--compi-think) * 1.25) ease-in-out infinite; }
.is-thinking .compi-au1, .is-thinking .compi-au2, .is-thinking .compi-au3 { animation-duration: calc(var(--compi-loop) * .35); }
@keyframes compi-form-turn { from { transform: rotate(0deg); } to { transform: rotate(30deg); } }
@keyframes compi-form-arm { from { transform: translateX(0) scale(1); } to { transform: translateX(-70px) scale(1.08); } }
@keyframes compi-form-core { from { transform: scale(.94); } to { transform: scale(1.14); } }
@keyframes compi-form-color { from { opacity: 1; } to { opacity: 0; } }
@keyframes compi-grow { from { transform: scale(1); } to { transform: scale(1.95); } }
@keyframes compi-face-in { from { opacity: 0; transform: translateY(9px) scale(.82); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes compi-breathe { 0%, 100% { transform: translateY(0) rotate(0deg) scale(1); } 25% { transform: translateY(-4px) rotate(-1.2deg) scale(1.014); } 50% { transform: translateY(-7px) rotate(0deg) scale(1.025); } 75% { transform: translateY(-3px) rotate(1.2deg) scale(1.01); } }
@keyframes compi-blink { 0%, 39%, 43%, 70%, 74%, 100% { transform: scaleY(1); } 41%, 72% { transform: scaleY(.08); } }
@keyframes compi-gaze { 0%, 17%, 48%, 66%, 100% { transform: translate(0, 0); } 26%, 36% { transform: translate(3px, -1.5px); } 76%, 86% { transform: translate(-2.5px, .5px); } }
@keyframes compi-smile { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06, 1.035); } }
@keyframes compi-drift1 { 0%, 100% { transform: translate(-40px, 30px); } 50% { transform: translate(110px, -80px); } }
@keyframes compi-drift2 { 0%, 100% { transform: translate(80px, 60px); } 50% { transform: translate(-70px, -40px); } }
@keyframes compi-drift3 { 0%, 100% { transform: translate(20px, -90px); } 50% { transform: translate(-60px, 90px); } }
@keyframes compi-think-spin { from { transform: rotate(30deg); } to { transform: rotate(390deg); } }
@keyframes compi-think-arm { 0%, 100% { transform: translateX(-70px) scale(1.08); } 50% { transform: translateX(-52px) scale(1.2); } }
@keyframes compi-think-core { 0%, 100% { transform: scale(1.14); } 50% { transform: scale(1.24); } }
@keyframes compi-think-gaze { 0%, 38%, 100% { transform: translate(4px, -6px); } 50%, 88% { transform: translate(-3.5px, -6.5px); } }
@keyframes compi-think-blink { 0%, 44%, 48%, 100% { transform: scaleY(.82); } 46% { transform: scaleY(.08); } }
@keyframes compi-think-tilt { 0%, 100% { transform: translateY(-2px) rotate(-3.5deg) scale(1.01); } 50% { transform: translateY(-4px) rotate(3.5deg) scale(1.015); } }
/* Las variantes del personaje (equipos, 2026-10-02): los agentes de un
 * equipo. Mismos keyframes de respirar, parpadear y formarse; el anillo de
 * lóbulos tiene su propia clase porque `.compi-ring` arranca rotado 30° para
 * la geometría del Asistente. Al pensar gira; la Intersección se balancea.
 * `prefers-reduced-motion` ya las apaga con `.compi *`. */
.compi-v, .compi-vring, .compi-lobe, .compi-vcore, .compi-lens { transform-origin: 0 0; }
.is-thinking .compi-spin .compi-vring { animation: compi-vspin 9s linear infinite; }
.is-thinking .compi-sway .compi-vring { animation: compi-vsway var(--compi-think) ease-in-out infinite; }
.compi-quieto .compi-vring { animation: none; }
@keyframes compi-vspin { to { transform: rotate(360deg); } }
@keyframes compi-vsway { 0%, 100% { transform: rotate(-14deg); } 50% { transform: rotate(14deg); } }
/* El personaje con su rótulo (`ui/_companion_mark.html`, 2026-10-03): quién
 * hizo algo cuando fue el Asistente o un agente. 28 px es el compacto de una
 * fila; `md` (32 px) y `lg` (44 px) para la cabecera de un detalle. El texto
 * en columna: el rótulo arriba y, si hay, la línea apagada debajo. */
.compi-mark { display: inline-flex; align-items: center; gap: var(--space-100); min-width: 0; vertical-align: middle; }
.compi-mark-art { width: 28px; height: 28px; flex: none; }
.compi-mark-md .compi-mark-art { width: 32px; height: 32px; }
.compi-mark-lg .compi-mark-art { width: 44px; height: 44px; }
.compi-mark-xl .compi-mark-art { width: 64px; height: 64px; }
.compi-mark-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.35; }
.compi-mark-hint { font-size: var(--fs-meta); color: var(--text-faint); }
/* La mesa del equipo (2026-10-02). Rejillas con los tokens de la casa. */
/* `.equipo-cifras`, `.equipo-cifra*` y `.equipo-ver` son, desde el 2026-10-17,
   la pieza de la casa `ui/_cifra.html` (la usa Mantenimiento): el nombre se
   quedó para no cambiar Agentes. Una cifra va dentro de `.equipo-cifras`. */
.equipo-resumen { display: flex; gap: var(--space-150); align-items: flex-start; }
.equipo-avatar-lg { width: 56px; height: 56px; flex: none; }
.equipo-avatar-md { width: 44px; height: 44px; flex: none; }
.equipo-cifras { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-150); }
.equipo-cifra { font-size: var(--fs-number); font-weight: 500; }
/* La cifra que quedó negativa (el flujo de caja, 2026-10-14). */
.equipo-cifra-danger { color: var(--danger-text); }
/* En la mesa cada cifra es una COLUMNA: baldosa arriba, rótulo, número (y
 * la barra del avance) y el enlace siempre al pie. Con la baldosa al lado y
 * todo centrado, el rótulo de dos renglones («Revisando el Jefe») bajaba el
 * número y el enlace, y las cuatro quedaban desalineadas (Renzo,
 * 2026-10-03); a lo ancho el rótulo cabe en uno. El relleno es el de las
 * tarjetas de agente: la fila respira igual que el resto de la mesa. */
.equipo-cifras > .card {
    display: flex; flex-direction: column; align-items: stretch;
    gap: var(--space-150); padding: var(--space-250);
}
.equipo-cifras > .card > .tile { align-self: flex-start; }
.equipo-cifras > .card > div:last-child { display: flex; flex-direction: column; gap: var(--space-025); flex: 1; min-width: 0; }
.equipo-cifras > .card .equipo-ver { margin-top: auto; padding-top: var(--space-100); }
.equipo-cifras > .card p { margin: 0; }
.equipo-cifras > .card .equipo-cifra { font-size: var(--fs-page); line-height: 1.15; }
/* `--desliza`: en el teléfono, una fila que se pasa con el dedo, como en una
   app, y la siguiente cifra se asoma para decir que hay más (Renzo,
   2026-10-29, la ficha del tercero). A lo ancho, la rejilla de siempre. */
@media (max-width: 720px) {
    .equipo-cifras--desliza {
        display: flex; overflow-x: auto; overscroll-behavior-x: contain;
        scroll-snap-type: x mandatory; scrollbar-width: none;
    }
    .equipo-cifras--desliza::-webkit-scrollbar { display: none; }
    .equipo-cifras--desliza > .card { flex: 0 0 42%; scroll-snap-align: start; }
}
/* El contenedor de consultas `tarjeta` es la REJILLA y no cada tarjeta: una
   tarjeta contenedora queda aislada y no puede tomar las filas de la mesa
   (`subgrid`, ver abajo). Aquí una tarjeta nunca baja de 240px, así que la
   consulta de «tarjeta angosta» responde igual. */
.equipo-tarjetas { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-200); container: tarjeta / inline-size; }
/* El vidrio de la mesa (Renzo, 2026-10-04): los reflejos y cuánto tiñe el
   color de cada personaje. Van en la mesa y no en cada tarjeta: los usan las
   de los agentes y la del Jefe. En oscuro el reflejo blanco encandila y el
   tinte pálido se come el texto claro: ahí bajan los dos. */
/* `.equipo-vidrio` (2026-10-09): los ajustes del equipo muestran la MISMA
   tarjeta de agente fuera de la mesa, y la tarjeta necesita estas variables. */
/* `.equipo-heroe` (2026-10-19): el héroe de la casa (`ui/_heroe.html`) pinta
   su fondo, su borde y el recuadro del lado con estas variables. Mudado a
   la casa sin ellas, fuera de la mesa salía sin fondo ni borde y su lado
   flotaba suelto debajo de la banda (Mantenimiento). */
.equipo-mesa, .equipo-vidrio, .panel-vidrio, .equipo-heroe {
    --vidrio-luz: rgba(255, 255, 255, .92);
    --vidrio-luz-suave: rgba(255, 255, 255, .55);
    --vidrio-borde: rgba(255, 255, 255, .78);
    --vidrio-caja: color-mix(in srgb, var(--card-bg) 64%, transparent);
    --vidrio-caja-tenue: color-mix(in srgb, var(--card-bg) 40%, transparent);
    --vidrio-sombra: rgba(40, 30, 90, .16);
    --vidrio-brillo: 58%; --vidrio-eco: 26%; --vidrio-base: 72%; --vidrio-base-pie: 30%;
}
:root[data-theme="dark"] .equipo-mesa, :root[data-theme="dark"] .equipo-vidrio,
:root[data-theme="dark"] .panel-vidrio, :root[data-theme="dark"] .equipo-heroe {
    --vidrio-luz: rgba(255, 255, 255, .16);
    --vidrio-luz-suave: rgba(255, 255, 255, .06);
    --vidrio-borde: rgba(255, 255, 255, .12);
    --vidrio-sombra: rgba(0, 0, 0, .5);
    --vidrio-brillo: 30%; --vidrio-eco: 14%; --vidrio-base: 14%; --vidrio-base-pie: 5%;
}
/* El fondo donde flotan: una mancha suave de cada color, para que el vidrio
   tenga algo que dejar pasar. `.card.` delante por la regla general de
   `.card`, que está más abajo. */
.card.equipo-area-agentes {
    border-radius: 28px;
    background:
        radial-gradient(60% 90% at 0% 100%, color-mix(in srgb, #a7f3d0 34%, transparent) 0%, transparent 70%),
        radial-gradient(50% 80% at 38% 0%, color-mix(in srgb, #fed7aa 30%, transparent) 0%, transparent 70%),
        radial-gradient(50% 90% at 68% 100%, color-mix(in srgb, #bfdbfe 34%, transparent) 0%, transparent 70%),
        radial-gradient(60% 90% at 100% 0%, color-mix(in srgb, #fbcfe8 34%, transparent) 0%, transparent 70%),
        var(--card-bg);
}
:root[data-theme="dark"] .card.equipo-area-agentes { background: var(--card-bg); }
/* Un agente trabajando en algo (`ui/_agente_trabajando.html`, 2026-10-28):
 * la cara a la izquierda —piensa solo mientras trabaja—, la pastilla y el
 * renglón «equipo · agente · paso · hace N s», el motivo si no se pudo, y
 * «Abrir» a la derecha. Compacta, un renglón para la mesa y el panel. */
.agente-trabajando {
    display: flex; align-items: flex-start; gap: var(--space-150);
    padding: var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius-card); background: var(--card-bg);
}
.agente-trabajando.is-compacta { padding: var(--space-100) var(--space-150); align-items: center; }
.agente-trabajando-cara { flex: none; }
.agente-trabajando-cuerpo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-050); }
.agente-trabajando-renglon {
    margin: 0; display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-050) var(--space-100); font-size: var(--fs-small);
}
.agente-trabajando-quien { font-weight: 600; }
.agente-trabajando-paso, .agente-trabajando-hace { color: var(--text-light); }
.agente-trabajando-motivo { margin: 0; font-size: var(--fs-small); }
/* Lo que preguntó se lee como cita; el botón va abajo, con los pasos, para
 * no partir el texto en dos columnas (2026-10-29). */
.agente-trabajando-pregunta {
    margin: var(--space-050) 0 0; padding: var(--space-050) 0 var(--space-050) var(--space-150);
    border-left: 2px solid var(--warn-text); font-size: var(--fs-body);
    line-height: 1.5; color: var(--text); overflow-wrap: anywhere;
}
.agente-trabajando-pie {
    display: flex; flex-wrap: wrap; align-items: flex-start;
    justify-content: space-between; gap: var(--space-100); margin-top: var(--space-050);
}
.agente-trabajando-pie .hilo-hecho { flex: 1; min-width: 0; font-size: var(--fs-small); }
.agente-trabajando-pie .hilo-hecho-resumen { min-height: 30px; display: flex; align-items: center; }
.agente-trabajando-pie .agente-trabajando-abrir { margin-left: auto; }
.agente-trabajando-abrir { flex: none; align-self: center; }
/* Dentro de la tarjeta del pedido, sin segundo recuadro: una línea fina lo
 * separa. La raya de la tarjeta toma el color del trabajo, aunque el socket
 * solo reemplace el trabajo. */
.propuesta-trabajo { margin-top: var(--space-050); padding-top: var(--space-150); border-top: 1px solid var(--border); }
.propuesta-trabajo .agente-trabajando { padding: 0; border: 0; border-radius: 0; background: none; }
.propuesta:has(.agente-trabajando-trabajando) { border-left-color: var(--info-text); }
.propuesta:has(.agente-trabajando-esperando),
.propuesta:has(.agente-trabajando-te_pregunta) { border-left-color: var(--warn-text); }
.propuesta:has(.agente-trabajando-listo) { border-left-color: var(--ok-text); }
.propuesta:has(.agente-trabajando-no_se_pudo) { border-left-color: var(--danger-text); }
/* «Trabajando ahora» arriba del panel del Asistente (`panel.js` la llena) y
 * la cuenta en la cara de la barra, en la esquina del botón. */
.hilo-trabajando:empty { display: none; }
.hilo-trabajando { padding: var(--space-100) var(--space-150) 0; }
.trabajando-ahora { display: flex; flex-direction: column; gap: var(--space-100); }
.trabajando-ahora-titulo { margin: 0; font-size: var(--fs-small); font-weight: 600; color: var(--text-light); }
.side-panel-toggle { position: relative; }
.side-panel-cuenta {
    position: absolute; top: -2px; right: -4px; min-width: 18px; height: 18px;
    padding: 0 var(--space-050); border-radius: 9px; background: var(--primary); color: #fff;
    font-size: var(--fs-meta); font-weight: 700; line-height: 18px; text-align: center;
    pointer-events: none;
}
/* «Trabajando ahora» en la mesa propia: los renglones compactos, uno debajo del otro. */
.card.equipo-trabajando { display: flex; flex-direction: column; gap: var(--space-150); }
.equipo-trabajando-lista { display: flex; flex-direction: column; gap: var(--space-100); }
/* La tarjeta de un agente, en vidrio (Renzo, 2026-10-04, sobre su referente;
 * la clara del 2026-10-03 era plana). Tres colores de su personaje, que pone
 * la plantilla: `--equipo-brillo` entra por la esquina de arriba,
 * `--equipo-eco` asoma por la de abajo y `--equipo-tinte` lava el resto. El
 * borde y el filo de arriba son el reflejo; la sombra lleva el color del
 * cuerpo (`--equipo-fondo`), no gris. Adentro, el personaje grande en su
 * baldosa, nombre y rol, «IA» en la esquina, el estado debajo y lo demás en
 * recuadros de vidrio más claro. Todas las tarjetas de la fila miden lo
 * mismo y «Ver ficha» se va al pie. */
.equipo-tarjeta {
    position: relative; display: flex; flex-direction: column; gap: var(--space-150);
    container: tarjeta / inline-size; isolation: isolate;
}
.equipo-tarjeta-estado { display: flex; }
/* `.card.` delante: la regla general de `.card` está más abajo y le ganaba. */
.card.equipo-tarjeta {
    padding: var(--space-250); border-radius: 26px;
    border: 1px solid var(--vidrio-borde);
    background:
        radial-gradient(120% 85% at 0% 0%, color-mix(in srgb, var(--equipo-brillo, var(--primary-soft)) var(--vidrio-brillo), transparent) 0%, transparent 64%),
        radial-gradient(90% 60% at 100% 100%, color-mix(in srgb, var(--equipo-eco, var(--primary-soft)) var(--vidrio-eco), transparent) 0%, transparent 72%),
        linear-gradient(165deg,
            color-mix(in srgb, var(--equipo-tinte, var(--primary-soft)) var(--vidrio-base), var(--card-bg)) 0%,
            color-mix(in srgb, var(--equipo-tinte, var(--primary-soft)) var(--vidrio-base-pie), var(--card-bg)) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz),
        inset 0 -22px 30px -26px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 45%, transparent),
        0 26px 40px -28px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 60%, transparent),
        0 2px 6px rgba(16, 18, 22, .05);
}
/* El reflejo: una banda de luz que cae desde arriba, detrás del contenido. */
.equipo-tarjeta::before {
    content: ""; position: absolute; inset: 1px; z-index: -1; pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(180deg, var(--vidrio-luz-suave) 0%, transparent 34%);
}
.equipo-tarjeta > * { margin: 0; }
.equipo-tarjeta-ia { position: absolute; top: var(--space-200); right: var(--space-200); }
/* Las pastillas, con relieve: filo de luz arriba y una sombra corta. */
.equipo-tarjeta .pill {
    padding: var(--space-050) var(--space-150); font-weight: 600;
    border: 1px solid var(--vidrio-borde);
    box-shadow: inset 0 1px 0 var(--vidrio-luz-suave), 0 6px 12px -8px var(--vidrio-sombra);
}
/* El hueco para «IA» lo deja solo el nombre: el rol, que va debajo de la
   pastilla, usa todo el ancho. */
.equipo-tarjeta-cabeza { display: flex; gap: var(--space-200); align-items: center; }
/* En una tarjeta angosta el personaje va arriba y el nombre debajo: al lado,
   el rol quedaba a una palabra por renglón. */
@container tarjeta (max-width: 195px) {
    .equipo-tarjeta-cabeza { flex-direction: column; align-items: flex-start; gap: var(--space-150); }
}
.equipo-tarjeta-cara {
    display: grid; place-items: center; flex: none; width: 80px; height: 80px;
    border-radius: 22px; border: 1px solid var(--vidrio-borde);
    background: linear-gradient(150deg,
        var(--vidrio-luz-suave) 0%,
        color-mix(in srgb, var(--equipo-brillo, var(--primary-soft)) 42%, transparent) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz),
        inset 0 -10px 16px -10px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 35%, transparent),
        0 14px 22px -16px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 65%, transparent);
}
/* Apenas más grande que su baldosa: el personaje se asoma, no queda
   enmarcado. A 112 px quedaba demasiado grande (Renzo, 2026-10-04). */
.equipo-tarjeta-cara .equipo-avatar-md { width: 84px; height: 84px; margin: calc(-1 * var(--space-025)); overflow: visible; }
.equipo-tarjeta-quien { display: flex; flex-direction: column; gap: var(--space-025); min-width: 0; }
.equipo-tarjeta-quien > * { margin: 0; }
.equipo-tarjeta-quien .equipo-nombre { font-size: var(--fs-section); font-weight: 700; line-height: 1.2; padding-right: var(--space-400); }
.equipo-tarjeta-rol { font-size: var(--fs-small); color: var(--text-light); line-height: 1.35; }
.equipo-tarjeta-caja {
    display: block; padding: var(--space-200); border-radius: 20px;
    border: 1px solid var(--vidrio-borde);
    background: var(--vidrio-caja);
    -webkit-backdrop-filter: blur(14px) saturate(150%); backdrop-filter: blur(14px) saturate(150%);
    box-shadow: inset 0 1.5px 0 var(--vidrio-luz), 0 12px 20px -16px var(--vidrio-sombra);
    font-size: var(--fs-body); font-weight: 600; line-height: 1.35; color: var(--text);
}
.equipo-tarjeta-ahora > p { margin: 0; }
.equipo-tarjeta-ahora > .card-hint { font-weight: 400; margin-top: var(--space-025); }
/* «Último: …» mide siempre dos renglones: uno largo (un correo con su
   segmento, código y hora) empujaba lo de abajo y desalineaba las
   tarjetas vecinas (Renzo, 2026-10-21). Lo que sobra se recorta; el texto
   entero queda en el `title`. */
.equipo-tarjeta-ultimo {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
}
/* Las tarjetas de una misma fila, alineadas por franja (Renzo, 2026-10-21:
   «se deben de ver alineados»): cada tarjeta ocupa seis filas de la
   rejilla de la mesa y las toma prestadas (`subgrid`), así la caja de lo
   último, los datos del mes y «Ver ficha» empiezan a la misma altura en
   todas aunque una diga más que otra. Cada parte va a su franja fija: la
   que falta deja la suya vacía. */
.equipo-tarjetas > .equipo-tarjeta {
    display: grid; grid-row: span 6; grid-template-rows: subgrid;
    row-gap: var(--space-150); align-content: start; container-type: normal;
}
.equipo-tarjetas > .equipo-tarjeta > .equipo-tarjeta-cabeza { grid-row: 1; }
.equipo-tarjetas > .equipo-tarjeta > .equipo-tarjeta-estado { grid-row: 2; }
.equipo-tarjetas > .equipo-tarjeta > .equipo-tarjeta-ahora { grid-row: 3; }
.equipo-tarjetas > .equipo-tarjeta > .equipo-tarjeta-datos { grid-row: 4; align-self: start; }
.equipo-tarjetas > .equipo-tarjeta > .equipo-tarjeta-proximo { grid-row: 5; }
.equipo-tarjetas > .equipo-tarjeta > .btn { grid-row: 6; align-self: end; }
.equipo-tarjeta-espera {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-150);
    text-decoration: none; transition: transform .16s, box-shadow .16s;
}
.equipo-tarjeta-espera > span:first-child { display: flex; align-items: center; gap: var(--space-150); }
.equipo-tarjeta-espera strong { font-size: 2.75rem; font-weight: 800; line-height: 1; letter-spacing: -.02em; color: var(--text); }
.equipo-tarjeta-espera:hover {
    text-decoration: none; transform: translateY(-1px);
    box-shadow: inset 0 1.5px 0 var(--vidrio-luz), 0 18px 26px -18px var(--vidrio-sombra);
}
/* La flecha es un botón de verdad: abombado, con su luz y su sombra. */
.equipo-tarjeta-flecha {
    display: grid; place-items: center; flex: none; width: 46px; height: 46px;
    border-radius: 50%; color: var(--text); border: 1px solid var(--vidrio-borde);
    background: linear-gradient(160deg,
        color-mix(in srgb, var(--equipo-brillo, var(--primary-soft)) 45%, var(--card-bg)) 0%,
        color-mix(in srgb, var(--equipo-brillo, var(--primary-soft)) 85%, var(--card-bg)) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz), inset 0 -4px 6px -3px rgba(16, 18, 22, .12),
        0 8px 14px -8px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 70%, transparent);
}
.equipo-tarjeta-flecha svg { width: 20px; height: 20px; }
.datos.equipo-tarjeta-datos {
    background: var(--vidrio-caja-tenue); border-radius: 20px;
    border-color: var(--vidrio-luz-suave); padding: var(--space-150) 0;
    box-shadow: inset 0 1px 0 var(--vidrio-luz-suave);
}
.equipo-tarjeta-datos .dato {
    grid-template-columns: 20px minmax(0, 1fr); align-items: start;
    gap: var(--space-150); padding: var(--space-100) var(--space-200); line-height: 1.45;
}
.equipo-tarjeta-datos .dato + .dato { border-top: 0; }
.equipo-tarjeta-datos .dato-glifo { padding-top: 1px; color: var(--text); }
.equipo-tarjeta-datos .dato-glifo svg { width: 19px; height: 19px; }
.equipo-tarjeta-datos .dato-valor { font-size: var(--fs-small); color: var(--text-light); }
.equipo-tarjeta-proximo {
    padding: var(--space-150) var(--space-200); border-radius: 20px;
    border: 1px solid var(--vidrio-luz-suave);
    background: var(--vidrio-caja-tenue);
    box-shadow: inset 0 1px 0 var(--vidrio-luz-suave);
}
.equipo-tarjeta-proximo-titulo { display: flex; align-items: center; gap: var(--space-100); margin: 0 0 var(--space-100); font-size: var(--fs-small); font-weight: 600; }
.equipo-tarjeta-proximo-titulo svg { width: 18px; height: 18px; color: var(--primary); }
.equipo-tarjeta-proximo ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-075); font-size: var(--fs-small); color: var(--text-light); }
.equipo-tarjeta-proximo li { display: flex; align-items: center; gap: var(--space-100); }
.equipo-tarjeta-proximo li svg { width: 15px; height: 15px; color: var(--primary); flex: none; }
/* «Ver ficha»: una tecla de vidrio con el color de la tarjeta. Los tres
   `.card.` y el `:where()` del apagado, para ganarle al tono del botón sin
   tocarle el peso al `:disabled`. */
.card.equipo-tarjeta > .btn:last-child {
    margin-top: auto; padding: var(--space-200) var(--space-250); border-radius: 18px;
    color: var(--text); border: 1px solid var(--vidrio-borde);
    background: linear-gradient(180deg,
        var(--vidrio-caja) 0%,
        color-mix(in srgb, var(--equipo-brillo, var(--primary-soft)) 34%, var(--vidrio-caja-tenue)) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz), inset 0 -6px 10px -6px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 30%, transparent),
        0 12px 18px -14px color-mix(in srgb, var(--equipo-fondo, var(--primary)) 70%, transparent);
}
.card.equipo-tarjeta > .btn:last-child:where(:not(:disabled)):hover {
    transform: translateY(-1px);
    background: linear-gradient(180deg,
        var(--vidrio-luz-suave) 0%,
        color-mix(in srgb, var(--equipo-brillo, var(--primary-soft)) 50%, var(--vidrio-caja-tenue)) 100%);
}
.card.equipo-tarjeta > .btn:last-child:where(:not(:disabled)):active { transform: translateY(1px); }
.card.equipo-tarjeta > .btn:last-child svg { width: 18px; height: 18px; }
.equipo-nombre { font-weight: 500; }
/* El detalle de una propuesta (2026-10-03): una `.card-stack`; arriba la
 * banda de quién, teñida con el tono de la confianza. */
.equipo-quien {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-150);
    padding: var(--space-150) var(--space-200); border-radius: var(--radius);
    border: 1px solid var(--border); background: var(--bg);
}
.equipo-quien .compi-mark-text > span:first-child { font-weight: 600; }
.equipo-quien-ok { background: var(--ok-bg); border-color: transparent; }
.equipo-quien-warn { background: var(--warn-bg); border-color: transparent; }
.equipo-quien-danger { background: var(--danger-bg); border-color: transparent; }
.dato-glifo .compi-mark-art { width: 24px; height: 24px; }
/* La bitácora del agente en su ficha (2026-10-03): filas de `.datos` con
 * la hora a la derecha y el detalle apagado debajo de lo que pasó. */
.equipo-bitacora .dato { grid-template-columns: 20px minmax(0, 1fr) auto; align-items: start; padding: var(--space-100) var(--space-200); }
.equipo-bitacora .dato-glifo svg { width: 16px; height: 16px; }
.equipo-bitacora .dato-accion { white-space: nowrap; }
/* La mesa rehecha (Renzo, 2026-10-03, dos maquetas): el trabajo a la
   izquierda y, a la derecha, el calendario del mes, los documentos recientes
   y una casilla para preguntarle al Jefe. En pantallas angostas la columna
   de la derecha baja debajo. */
.equipo-mesa {
    display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--space-200); align-items: start;
    grid-template-areas: "heroe cal" "cifras cal" "agentes agentes" "feed lado";
    /* Lo que el calendario mide de más se lo lleva la fila de cifras, no un
       hueco debajo del Jefe: entre tarjetas queda el mismo `gap`. */
    grid-template-rows: auto 1fr auto auto;
}
.equipo-area-heroe { grid-area: heroe; }
/* Las cifras se estiran hasta el pie del calendario: sin esto la fila
   dejaba un hueco debajo (Renzo, 2026-10-03). */
.equipo-area-cifras { grid-area: cifras; align-self: stretch; }
.equipo-area-cal { grid-area: cal; }
/* El calendario llega al pie de las cifras (Renzo, 2026-10-04): sin la lista
   «Lo que viene» quedaba más bajo que el Jefe y las cifras juntas. La
   tarjeta se estira y el alto que sobra se reparte entre las semanas —cada
   eslabón de flatpickr crece con `flex: 1`—, no queda como un hueco abajo.
   En una columna (`@container` de abajo) no hay nada con qué emparejar. */
@container content (min-width: 1041px) {
    .equipo-area-cal { align-self: stretch; display: flex; flex-direction: column; }
    .equipo-area-cal .equipo-mes,
    .equipo-area-cal .flatpickr-calendar.inline { flex: 1; display: flex; flex-direction: column; }
    .equipo-area-cal .flatpickr-innerContainer { flex: 1; }
    .equipo-area-cal .flatpickr-rContainer { display: flex; flex-direction: column; }
    .equipo-area-cal .flatpickr-days { flex: 1; align-items: stretch; }
    .equipo-area-cal .dayContainer { align-content: space-evenly; }
}
.equipo-area-agentes { grid-area: agentes; }
.equipo-area-feed { grid-area: feed; align-self: stretch; }
.equipo-feed-que { white-space: normal; min-width: 28ch; }
.equipo-feed-cuando { white-space: nowrap; }
/* Las novedades de la norma por decidir (vigía, 2026-10-09: «mejora este
 * ui»). Una fila ancha por novedad, no una tabla: la cara del agente al que
 * le toca, la norma con su resumen, la nota LITERAL en su recuadro de cita
 * —es lo que entraría a sus instrucciones— y los dos botones juntos. En el
 * teléfono se apila. */
.norma-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.norma-item {
    display: grid; grid-template-columns: 11rem minmax(0, 1fr) auto; gap: var(--space-200);
    align-items: start; padding: var(--space-200) 0; border-top: 1px solid var(--border);
}
.norma-item:first-child { border-top: 0; padding-top: var(--space-100); }
.norma-cuerpo { display: flex; flex-direction: column; gap: var(--space-100); min-width: 0; }
.norma-cuerpo > * { margin: 0; }
.norma-titulo { font-weight: 600; line-height: 1.35; }
.norma-titulo a { color: inherit; text-decoration: none; }
.norma-titulo a:hover { color: var(--primary-dark); }
.norma-titulo svg { width: 14px; height: 14px; vertical-align: -1px; color: var(--primary-dark); }
.norma-nota {
    margin: 0; padding: var(--space-150) var(--space-200); border-radius: var(--radius);
    background: var(--primary-soft); border-left: 3px solid var(--primary-dark);
    color: var(--text); line-height: 1.5;
}
.norma-nota-rotulo {
    display: flex; align-items: center; gap: var(--space-075); margin-bottom: var(--space-050);
    font-size: var(--fs-small); font-weight: 600; color: var(--primary-dark);
}
.norma-nota-rotulo svg { width: 14px; height: 14px; }
.norma-botones { display: flex; gap: var(--space-100); }
@media (max-width: 720px) {
    .norma-item { grid-template-columns: minmax(0, 1fr); gap: var(--space-150); }
}
.equipo-area-lado { grid-area: lado; }
/* Por el ancho del CONTENIDO (`content`), no el de la ventana: con el panel
   lateral abierto la mesa pierde 380 px sin que la ventana cambie, y el Jefe
   quedaba con una palabra por renglón (Renzo, 2026-10-03). */
@container content (max-width: 1040px) {
    .equipo-mesa { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; grid-template-areas: "heroe" "cifras" "agentes" "cal" "feed" "lado"; }
}
/* El aviso del responsable: una nota de una línea con su botón a Ajustes
   (`ui/_tip.html`), no el panel ilustrado, que es para cómo quedó un
   trámite y aquí ocupaba media pantalla con aire a los lados. El tope
   de la nota lo pone el hueco de la columna. */
.mant-riel-abajo > .product-main > .tip { margin-top: 0; }
/* `.riel-abajo`: el riel NO sube al plegarse (el de las demás fichas sí,
   como el editor del integrante). En la portada y la ficha de un
   mantenimiento, primero las cifras y «Para hoy», o la lista con Empezar,
   que es donde se trabaja frente al equipo; en la ficha del tercero, cómo va
   el cliente antes que quién es (2026-10-29). Nació como `.mant-riel-abajo`
   y se mudó a la casa cuando la necesitó la segunda ficha. */
@media (max-width: 1100px) {
    .riel-abajo > .product-rail { order: 0; }
}
/* Tres cifras (la hoja de vida): las tres en fila y, en el teléfono, dos y
   la tercera a todo el ancho en vez de sola en media fila. Columnas fijas:
   con `auto-fit` el corte caía en otro ancho que el de esta regla. */
.mant-cifras-tres { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@container content (max-width: 520px) {
    .mant-cifras-tres { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .mant-cifras-tres > .card:last-child { grid-column: 1 / -1; }
}
/* Cuatro cifras (la portada): las cuatro en fila o dos y dos, nunca tres y
   una sola (lo que hacía `auto-fit` en la tablet). Se mide la columna en que
   van, no la pantalla: con el riel al lado la columna es angosta aunque la
   pantalla no lo sea. */
.mant-riel-abajo > .product-main { container-type: inline-size; container-name: mant-columna; }
.mant-cifras-cuatro { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@container mant-columna (max-width: 640px) {
    .mant-cifras-cuatro { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* El Jefe en grande: su cara, lo que espera a la persona en un titular y
   «Para hoy» al lado. El fondo es el tinte de la marca, apenas. */
/* `.equipo-heroe*`, `.equipo-kicker`, `.equipo-titular`, `.equipo-jefe-botones`
   y `.equipo-hoy*` son, desde el 2026-10-17, la pieza de la casa
   `ui/_heroe.html` (la usa Nómina); el nombre se quedó para no
   cambiar Agentes. */
.equipo-heroe {
    display: grid; grid-template-columns: 104px minmax(0, 1fr) minmax(220px, 300px);
    gap: var(--space-250); align-items: center;
    position: relative; isolation: isolate;
    /* Los colores del Asistente, que es el Jefe en todo equipo: el cian y el
       rosado de sus auras, sobre el lila de la casa. */
    --equipo-brillo: #67e8f9; --equipo-eco: #f9a8d4; --equipo-tinte: #ddd6fe; --equipo-fondo: #4338ca;
}
/* La tarjeta del Jefe, en el mismo vidrio que las de los agentes (Renzo,
   2026-10-04). `.card.` delante: la regla general de `.card` está más abajo
   y le ganaba el fondo —el degradado lila de antes nunca se vio—. */
.card.equipo-heroe {
    padding: var(--space-250) var(--space-300); border-radius: 28px;
    border: 1px solid var(--vidrio-borde);
    background:
        radial-gradient(70% 120% at 0% 0%, color-mix(in srgb, var(--equipo-brillo) var(--vidrio-brillo), transparent) 0%, transparent 62%),
        radial-gradient(60% 110% at 100% 100%, color-mix(in srgb, var(--equipo-eco) var(--vidrio-eco), transparent) 0%, transparent 70%),
        linear-gradient(120deg,
            color-mix(in srgb, var(--equipo-tinte) var(--vidrio-base), var(--card-bg)) 0%,
            color-mix(in srgb, var(--equipo-tinte) var(--vidrio-base-pie), var(--card-bg)) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz),
        inset 0 -22px 30px -26px color-mix(in srgb, var(--equipo-fondo) 45%, transparent),
        0 26px 40px -28px color-mix(in srgb, var(--equipo-fondo) 60%, transparent),
        0 2px 6px rgba(16, 18, 22, .05);
}
.equipo-heroe::before {
    content: ""; position: absolute; inset: 1px; z-index: -1; pointer-events: none;
    border-radius: inherit;
    background: linear-gradient(180deg, var(--vidrio-luz-suave) 0%, transparent 40%);
}
/* El Jefe en su baldosa de vidrio, como los agentes. */
.equipo-heroe-cara {
    width: 104px; height: 104px; padding: var(--space-100); border-radius: 28px;
    border: 1px solid var(--vidrio-borde);
    background: linear-gradient(150deg,
        var(--vidrio-luz-suave) 0%,
        color-mix(in srgb, var(--equipo-brillo) 42%, transparent) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz),
        inset 0 -10px 16px -10px color-mix(in srgb, var(--equipo-fondo) 35%, transparent),
        0 14px 22px -16px color-mix(in srgb, var(--equipo-fondo) 65%, transparent);
}
@container content (max-width: 720px) {
    .equipo-heroe { grid-template-columns: 96px minmax(0, 1fr); }
    .equipo-hoy { grid-column: 1 / -1; }
    .equipo-heroe-cara { width: 96px; height: 96px; }
}
@container content (max-width: 480px) { .card.equipo-heroe { grid-template-columns: minmax(0, 1fr); padding: var(--space-200); } .equipo-heroe-cara { width: 72px; height: 72px; padding: var(--space-075); border-radius: 20px; } }
.equipo-heroe-cara .compi { width: 100%; height: 100%; overflow: visible; }  /* el resplandor no se corta en el borde: dejaba una raya */
/* La cara con FOTO (2026-10-19, la hoja de vida del equipo): la miniatura
   llena la baldosa de vidrio y toma su curva, en vez del ícono. */
.equipo-heroe-foto {
    display: block; width: 100%; height: 100%;
    object-fit: cover; border-radius: 20px;
}
.equipo-heroe-texto > * { margin: 0; }
.equipo-kicker { font-size: var(--fs-meta); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--text-light); }
.equipo-heroe-texto > .equipo-titular { font-size: clamp(1.35rem, 1rem + 1vw, 1.85rem); line-height: 1.15; font-weight: 700; margin-top: var(--space-100); }
.equipo-heroe-texto .equipo-heroe-resumen { color: var(--text-light); margin: var(--space-100) 0 0; max-width: 52ch; }
/* La tarjeta del Jefe no crece sin fin (2026-10-04): el resumen a cuatro
 * renglones con «Ver más», y cada línea de «Para hoy» a dos con «…» (la
 * explicación entera, al pasar el mouse). */
/* `.equipo-heroe-resumen-entero` (nómina, 2026-10-17): un resumen corto y
 * que es la explicación de la pantalla —la ayuda de la planilla, cuándo se
 * paga— no se corta: a 375 px dos renglones se comían la mitad, sin «Ver
 * más» que la devolviera. */
.equipo-heroe-texto .equipo-heroe-resumen:not(.is-abierto):not(.equipo-heroe-resumen-entero) {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.equipo-ver-mas { font-size: var(--fs-small); text-decoration: none; margin-top: var(--space-050); }
.equipo-hoy li a {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
}
.equipo-heroe-texto > .equipo-jefe-botones { display: flex; flex-wrap: wrap; gap: var(--space-100); margin-top: var(--space-150); }
/* Los dos botones del Jefe, con relieve: el morado abombado y con su brillo,
   el otro una tecla de vidrio. El `:where()` del apagado no le sube el peso. */
.equipo-jefe-botones .btn { border-radius: 14px; padding: var(--space-150) var(--space-250); white-space: nowrap; }
/* Los tres botones del Jefe en UNA línea (Renzo, 2026-10-14: «Pendientes ·
   Crear · Preguntar»): en una columna angosta (con el calendario al lado)
   pierden relleno y su ícono antes que partirse en dos renglones. */
.equipo-heroe-texto > .equipo-jefe-botones { container-type: inline-size; }
@container (max-width: 420px) {
    .equipo-jefe-botones .btn { padding: var(--space-150) var(--space-150); }
    .equipo-jefe-botones .btn:not(.btn-primary) svg { display: none; }
}
@container (max-width: 300px) {
    .equipo-jefe-botones .btn svg { display: none; }
}
.equipo-jefe-botones .btn-primary {
    color: #fff;
    background: linear-gradient(180deg, color-mix(in srgb, var(--primary) 78%, #fff) 0%, var(--primary) 100%);
    box-shadow:
        inset 0 1.5px 0 rgba(255, 255, 255, .45), inset 0 -4px 8px -3px rgba(16, 18, 22, .28),
        0 12px 18px -10px color-mix(in srgb, var(--primary) 85%, transparent);
}
.equipo-heroe .equipo-jefe-botones .btn-primary:where(:not(:disabled)):hover {
    background: linear-gradient(180deg, var(--primary) 0%, var(--primary-dark) 100%);
}
.equipo-jefe-botones .btn:not(.btn-primary) {
    color: var(--text); border: 1px solid var(--vidrio-borde);
    background: linear-gradient(180deg, var(--vidrio-caja) 0%, color-mix(in srgb, var(--equipo-tinte) 40%, var(--vidrio-caja-tenue)) 100%);
    box-shadow:
        inset 0 1.5px 0 var(--vidrio-luz), inset 0 -6px 10px -6px color-mix(in srgb, var(--equipo-fondo) 30%, transparent),
        0 12px 18px -14px color-mix(in srgb, var(--equipo-fondo) 70%, transparent);
}
.equipo-heroe .equipo-jefe-botones .btn:not(.btn-primary):where(:not(:disabled)):hover {
    transform: translateY(-1px);
    background: linear-gradient(180deg, var(--vidrio-luz-suave) 0%, color-mix(in srgb, var(--equipo-tinte) 60%, var(--vidrio-caja-tenue)) 100%);
}
/* «Para hoy» en su recuadro de vidrio (antes, una raya a la izquierda). */
.equipo-hoy {
    align-self: stretch; padding: var(--space-200); border-radius: 20px;
    border: 1px solid var(--vidrio-borde); background: var(--vidrio-caja);
    -webkit-backdrop-filter: blur(14px) saturate(150%); backdrop-filter: blur(14px) saturate(150%);
    box-shadow: inset 0 1.5px 0 var(--vidrio-luz), 0 12px 20px -16px var(--vidrio-sombra);
}
.equipo-hoy p { margin: 0; }
.equipo-fecha { font-size: var(--fs-meta); font-weight: 400; color: var(--text-light); }
.equipo-hoy .equipo-hoy-titulo { font-weight: 600; margin: 0 0 var(--space-100); }
.equipo-hoy ul { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-150); font-size: var(--fs-small); }
.equipo-hoy li { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: var(--space-100); align-items: start; }
.equipo-hoy li svg { width: 18px; height: 18px; color: var(--primary); margin-top: 1px; }
/* El lado como DATOS (2026-10-19, Mantenimiento: «Cuándo y quién»): filas
   rótulo + valor en vez de íconos sueltos. Un `<dl>` de `<div>`: el rótulo
   gris y chico a la izquierda, el valor a la derecha, una raya entre filas. */
.equipo-hoy-datos { margin: 0; display: grid; font-size: var(--fs-small); }
.equipo-hoy-datos > div {
    display: grid; grid-template-columns: minmax(5.5rem, auto) minmax(0, 1fr);
    gap: var(--space-150); align-items: baseline; padding: var(--space-100) 0;
}
.equipo-hoy-datos > div + div { border-top: 1px solid var(--border); }
.equipo-hoy-datos dt { color: var(--text-light); }
.equipo-hoy-datos dd { margin: 0; font-weight: 600; text-align: right; overflow-wrap: anywhere; }
/* El aviso que trae su «Preguntar» (Finanzas, 2026-10-14): el ícono en una
   tercera columna, en el mismo renglón del texto. */
.equipo-hoy li.equipo-hoy-pregunta { grid-template-columns: 18px minmax(0, 1fr) auto; align-items: center; }

/* Cada cifra dice a dónde lleva. */
.equipo-cifra-enlace { color: inherit; text-decoration: none; transition: border-color .15s, box-shadow .15s; }
.equipo-cifra-enlace:hover { border-color: var(--primary-soft-border); box-shadow: var(--shadow-hover, var(--shadow)); text-decoration: none; }
.equipo-ver { display: inline-flex; align-items: center; gap: var(--space-050); margin-top: var(--space-100); font-size: var(--fs-meta); color: var(--primary); font-weight: 500; }
.equipo-ver svg { width: 14px; height: 14px; }

/* El calendario para mirar: el de la casa, fijo y a lo ancho. */
.equipo-mes > input { display: none; }
/* Un mes no necesita más de ~560 px (2026-10-04): en una tarjeta a todo lo
   ancho (Contabilidad → Calendario) el encabezado se estiraba y los días se
   quedaban a la izquierda. Centrado, con la rejilla al ancho del mes. */
.equipo-mes .flatpickr-calendar.inline { width: 100%; max-width: 560px; margin-inline: auto; box-shadow: none; border: 0; background: transparent; }
.equipo-mes .flatpickr-rContainer, .equipo-mes .flatpickr-weekdays,
.equipo-mes .flatpickr-days, .equipo-mes .dayContainer { width: 100%; min-width: 0; max-width: none; }
.equipo-mes .flatpickr-day { max-width: none; height: 40px; line-height: 40px; }
.equipo-mes .flatpickr-current-month { font-size: var(--fs-section); font-weight: 600; }
.equipo-docs { list-style: none; margin: var(--space-150) 0 0; padding: 0; display: grid; }
/* Lo de un día, al tocarlo (2026-10-04): en el modal de la casa
   (`ui/_calendar_month.html`), una fila por cosa con un punto del mismo tono
   que el del día, «Viene» si es calculado y la flecha si abre algo. El punto
   (`.cal-dia-punto`) es también el de la leyenda de Contabilidad → Calendario. */
.cal-dia-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cal-dia-lista li + li { border-top: 1px solid var(--border); }
.cal-dia-fila { display: flex; align-items: center; gap: var(--space-150); min-width: 0;
    margin: 0 calc(-1 * var(--space-100)); padding: var(--space-150) var(--space-100); border-radius: var(--radius);
    color: var(--text); text-decoration: none; }
.cal-dia-fila[href]:hover { background: var(--bg); }
.cal-dia-fila[href]:hover .cal-dia-nombre { color: var(--primary); }
.cal-dia-fila .cal-dia-punto { width: 10px; height: 10px; }
.cal-dia-nombre { flex: 1; min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
.cal-dia-fila .pill { flex: none; }
.cal-dia-ir { flex: none; width: 16px; height: 16px; color: var(--text-faint); }
.cal-dia-fila:not([href]) .cal-dia-ir { display: none; }
.cal-dia-punto { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--info-vivid); }
.cal-dia-punto.has-mark-warn { background: var(--warn-vivid); }
.cal-dia-punto.has-mark-danger { background: var(--danger-vivid); }
.cal-dia-punto.has-mark-ok { background: var(--ok-vivid); }
.cal-dia-punto.is-planned { background: transparent; box-shadow: inset 0 0 0 1.5px var(--text-faint); }
/* El buzón de facturas de Compras (retoque 2026-10-04): la dirección y los
   dos caminos lado a lado; en una tarjeta angosta, uno debajo del otro. El
   apagado deja ver su dirección, en gris. */
#buzon-facturas .url-box-valor.is-muted { color: var(--text-light); }
.buzon-aviso { align-items: flex-start; margin-bottom: var(--space-200); }
.buzon-aviso p { margin: 0; }
/* La pastilla del estado no baja debajo del título: el título cede (base 0),
   la pastilla no. Sin esto, con el panel lateral abierto, la pista larga
   empujaba la pastilla a su propio renglón. */
#buzon-facturas .card-head-lead { flex: 1 1 0; min-width: 0; }
#buzon-facturas .card-header > .pill { flex: none; }
/* En el teléfono el título va entero y la pastilla abajo: al lado le dejaba
   una columna de tres palabras por renglón. */
@container content (max-width: 560px) { #buzon-facturas .card-head-lead { flex-basis: 100%; } }
.buzon-cuerpo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--space-200) var(--space-300); }
@container content (max-width: 960px) { .buzon-cuerpo { grid-template-columns: minmax(0, 1fr); } }
.buzon-rotulo { margin: 0 0 var(--space-100); font-size: var(--fs-meta); font-weight: 600; color: var(--text-light);
    text-transform: uppercase; letter-spacing: .04em; }
/* La dirección se corta por la arroba o por un punto, no por la mitad de
   una palabra: `break-all` de `.url-box` es para URLs con UUID. */
.buzon-cuerpo .url-box-valor { flex: 1 1 auto; font-size: var(--fs-body); word-break: normal; overflow-wrap: anywhere; }
/* Si la dirección entera no cabe al lado de «Copiar», el botón baja: una
   dirección partida en dos renglones se copia a mano mal. */
.buzon-cuerpo .url-box { flex-wrap: wrap; }
.buzon-cuerpo .card-hint { margin: 0; }
.buzon-cuerpo .step-line { align-items: center; }
.buzon-archivo { font-weight: 500; color: var(--text); overflow-wrap: anywhere; }
.buzon-llego { white-space: nowrap; color: var(--text-light); }
.buzon-detalle { color: var(--text-light); font-size: var(--fs-small); min-width: 16em; }
/* El color y el peso son los de todo enlace de tabla (`.table tbody a`). */
.buzon-ver { display: inline-flex; align-items: center; gap: var(--space-050); }
.buzon-ver svg { width: 14px; height: 14px; }
/* «Nuevo centro de costo» desde una línea de factura (2026-10-04): el
   formulario dentro del modal de la casa, un campo debajo del otro. */
.cc-nuevo { display: grid; gap: var(--space-150); }
.cc-nuevo .alert { margin: 0; }
/* `.alert` es `flex` y le gana al `hidden` del navegador: sin esto el aviso
   vacío salía como una franja rosada. */
.cc-nuevo .alert[hidden] { display: none; }
/* Contabilidad → Calendario (2026-10-04): el mes a la izquierda y las
 * listas a la derecha; en pantalla angosta, uno debajo del otro. Lo que
 * falta configurar, en una sola tarjeta compacta. */
.calendario-vista { display: grid; grid-template-columns: minmax(300px, 380px) minmax(0, 1fr); gap: var(--space-200); align-items: start; }
@container content (max-width: 860px) { .calendario-vista { grid-template-columns: minmax(0, 1fr); } }
.calendario-leyenda { list-style: none; margin: var(--space-150) 0 0; padding: var(--space-150) 0 0; border-top: 1px solid var(--border);
    display: flex; flex-wrap: wrap; gap: var(--space-100) var(--space-200); font-size: var(--fs-meta); color: var(--text-light); }
.calendario-leyenda li { display: inline-flex; align-items: center; gap: var(--space-100); }
.calendario-falta ul { margin: 0; padding-left: var(--space-250); display: grid; gap: var(--space-050); font-size: var(--fs-small); color: var(--text-light); }
/* «Este año» del calendario de obligaciones (2026-10-04): una tabla por
 * mes, con las columnas fijas para que se alineen de un mes al otro. */
.calendario-ano .table { table-layout: fixed; }
.calendario-ano .table th:first-child { width: 26%; }
.calendario-ano .table th:last-child { width: 26%; }
/* Documentos recientes: cada fila con la MISMA forma —nombre, fecha y
 * estado, uno debajo del otro— y la baldosa centrada a su lado. Con la
 * fecha y el estado en un renglón, el estado corto quedaba a la derecha y
 * el largo bajaba, y la lista se veía desalineada (Renzo, 2026-10-03). La
 * cabecera ya trae su raya: la primera fila no pinta otra. Sin relleno
 * lateral propio: el de la tarjeta alcanza, y la baldosa queda a plomo con
 * el ícono de la cabecera. */
.equipo-docs { margin-top: 0; }
.equipo-docs li { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-025) var(--space-150); align-items: center; padding: var(--space-150) 0; border-top: 1px solid var(--border); }
.equipo-docs li:first-child { border-top: 0; }
.equipo-docs li > .tile { grid-row: 1 / span 2; }
.equipo-docs li > a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.equipo-docs-meta { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-050); }

/* «¿Necesitas algo más?»: abre el panel del Jefe con lo que se escribió. */
.equipo-pregunta { background: linear-gradient(160deg, var(--primary-soft) 0%, var(--card-bg) 70%); }
.equipo-pregunta form { margin-top: var(--space-150); flex-wrap: nowrap; }
.equipo-pregunta input { flex: 1 1 auto; min-width: 0; }

/* El cierre en el panel: avance y fecha límite arriba, una tarjeta por
   etapa con su franja de estado, y una frase al final. */
.equipo-cierre-cabeza { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-250); align-items: center; margin-bottom: var(--space-200); }
@media (max-width: 560px) { .equipo-cierre-cabeza { grid-template-columns: minmax(0, 1fr); } }
.equipo-cierre-cabeza p { margin: 0; }
.equipo-cierre-listas { font-size: var(--fs-section); font-weight: 700; }
.equipo-cierre-barra { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--space-150); align-items: center; margin-top: var(--space-100); }
.equipo-cierre-limite { display: flex; gap: var(--space-150); align-items: center; border-left: 1px solid var(--border); padding-left: var(--space-250); }
@media (max-width: 560px) { .equipo-cierre-limite { border-left: 0; padding-left: 0; } }
.equipo-etapas { display: grid; gap: var(--space-150); }
.equipo-etapa {
    display: grid; grid-template-columns: 64px 210px minmax(0, 1fr) 140px;
    gap: var(--space-200); align-items: center; border: 1px solid var(--border);
    border-radius: var(--radius-card); background: var(--card-bg); overflow: hidden; padding-right: var(--space-200);
}
@media (max-width: 640px) { .equipo-etapa { grid-template-columns: 52px minmax(0, 1fr); } .equipo-etapa-texto, .equipo-etapa-quien { grid-column: 2; } .equipo-etapa-quien { padding-bottom: var(--space-150); } }
.equipo-etapa-franja { align-self: stretch; display: flex; align-items: center; justify-content: center; }
.equipo-etapa-ok .equipo-etapa-franja { background: var(--ok-bg); }
.equipo-etapa-warn .equipo-etapa-franja { background: var(--warn-bg); }
.equipo-etapa-nombre, .equipo-etapa-texto { padding: var(--space-150) 0; display: grid; gap: var(--space-075); justify-items: start; }
.equipo-etapa p { margin: 0; }
.equipo-etapa-texto { font-size: var(--fs-small); }
.equipo-etapa-texto .btn { white-space: nowrap; }
.equipo-etapa-persona { display: inline-flex; align-items: center; gap: var(--space-100); color: var(--text-light); font-size: var(--fs-small); }
.equipo-cierre-pie { display: flex; gap: var(--space-150); align-items: center; margin-top: var(--space-200); padding: var(--space-200); border: 1px solid var(--border); border-radius: var(--radius-card); background: linear-gradient(160deg, var(--primary-soft) 0%, var(--card-bg) 70%); }
.equipo-cierre-pie svg { width: 26px; height: 26px; color: var(--primary); flex: none; }
.equipo-cierre-pie p { margin: 0; }
.equipo-bitacora .tone-ok .dato-glifo { color: var(--ok-text); }
.equipo-bitacora .tone-warn .dato-glifo { color: var(--warn-text); }
.equipo-bitacora .tone-danger .dato-glifo { color: var(--danger-text); }
.equipo-bitacora-detalle { display: block; margin-top: var(--space-025); font-size: var(--fs-meta); color: var(--text-faint); }
.equipo-bitacora-ahora { margin: 0 0 var(--space-150); }
/* Los pagos que se enlazan, en el detalle: pocas filas, el enlace en el
 * color de la marca para que se lea que abre, y la fila con aire. */
.equipo-enlaces .table tbody td { padding-top: var(--space-150); padding-bottom: var(--space-150); }
.equipo-enlaces .table tbody a:not(.btn) { color: var(--primary); }
.equipo-enlaces .table tbody a:not(.btn):hover { color: var(--primary-dark); }
.hilo-aviso .pill { white-space: normal; text-align: center; line-height: 1.4; }

.side-panel {
    flex: none; width: 0; overflow: hidden;
    border-radius: var(--radius-shell) var(--radius-shell) 0 0;
    position: sticky; top: var(--topbar);
    height: calc(100dvh - var(--topbar));
    background: var(--card-bg);
    transition: width .22s ease;
}
body.panel-open .side-panel { width: var(--side-panel); margin-left: var(--shell-gap); box-shadow: var(--shadow-shell); }
.side-panel-body {
    width: var(--side-panel); height: 100%;
    display: flex; flex-direction: column; min-height: 0;
}
.side-panel-body noscript a {
    display: block; padding: var(--space-200); font-size: var(--fs-body);
}
/* Dos inquilinos (Renzo, 2026-10-09): el Asistente y los Filtros comparten
   el hueco y se ve uno. `panel.js` pone `panel-filtros` en el body. */
.filtros-panel { display: none; }
body.panel-filtros .side-panel-body { display: none; }
body.panel-filtros .filtros-panel {
    display: flex; flex-direction: column; min-height: 0;
    width: var(--side-panel); height: 100%;
}

/* ── El hilo del asistente ──────────────────────────────────────────────── */
.hilo { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.hilo-pagina { padding: 0; }
.hilo-pagina .hilo { height: calc(100dvh - var(--topbar) - 48px); }
.hilo-cabecera {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-100); padding: var(--space-150) var(--space-200);
    border-bottom: 1px solid var(--border);
}
.hilo-titulo {
    margin: 0; font-size: var(--fs-section); font-weight: 600;
    min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Quién contesta cuando no es el Asistente a secas (el Jefe o un agente
 * de un equipo, 2026-10-03): una franja a lo ancho bajo la cabecera, y la
 * misma línea sobre la casilla antes de mandar. */
.hilo-quien.hilo-quien-banda { margin: 0; padding: var(--space-100) var(--space-200); border-bottom: 1px solid var(--border); background: var(--primary-soft); }
.hilo-quien { margin: var(--space-025) 0 0; font-size: var(--fs-meta); color: var(--primary-dark); font-weight: 500; }
.hilo-quien-composer { margin: 0 0 var(--space-050); padding: 0 var(--space-050); }
.hilo-quien .compi-mark { gap: var(--space-075); }
.hilo-quien .compi-mark-art { width: 20px; height: 20px; }
/* En la banda es quien contesta: más grande, y vivo (Renzo, 2026-10-03). */
.hilo-quien-banda .compi-mark { gap: var(--space-100); }
.hilo-quien-banda .compi-mark-art { width: 40px; height: 40px; }
.hilo-acciones { display: flex; align-items: center; gap: var(--space-100); flex: none; }
/* El menú de hilos vive dentro del panel: cabe en su ancho y los títulos
   largos se cortan con puntos suspensivos en vez de estirarlo. */
.hilo-acciones .menu-pop {
    white-space: normal; width: min(320px, calc(var(--side-panel) - 40px));
    max-width: calc(100vw - 32px);
    /* Con veinte conversaciones el menú medía más que la ventana, y `menu.js`,
       que lo topa contra el borde, dejaba fuera el rótulo y los primeros
       hilos (Renzo, 2026-10-17, «se cortan las conversaciones»). Tope al alto
       de la ventana menos la barra y la cabecera del panel (96px), y se
       desplaza adentro. */
    max-height: calc(100vh - var(--topbar) - 96px);
    /* Y lo que se desplaza es SOLO la lista: «Nueva conversación» y las
       acciones quedan quietas arriba (Renzo, 2026-10-31, «fixed»). Columna
       flexible y la lista toma el resto con su propio scroll; en el
       teléfono la cabeza de la hoja (`::before`) es un renglón más. */
    display: flex; flex-direction: column; overflow: hidden;
}
/* Lo quieto no se encoge: el logo, «Nueva conversación», el bloque de
   acciones y la raya. Sin `flex: none` la raya (1px, sin contenido) es la
   primera que la columna aplasta. */
.hilo-marca,
.hilo [data-asistente-nuevo],
.hilo-menu-acciones,
.hilo-acciones .menu-pop > .menu-sep,
.hilo-barra > .menu-sep { flex: none; }
.hilo-acciones .hilo-lista,
.hilo-barra .hilo-lista { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.hilo-mensajes {
    flex: 1; min-height: 0; overflow-y: auto;
    padding: var(--space-200); display: flex; flex-direction: column; gap: var(--space-150);
}
.hilo-bienvenida { margin: auto 0; padding: var(--space-250) var(--space-025); }
.hilo-bienvenida h3 { margin: 0 0 var(--space-150); font-size: var(--fs-page); line-height: 1.2; letter-spacing: -.6px; font-weight: 600; }
.hilo-bienvenida p { margin: 0; color: var(--text-light); font-size: var(--fs-body); line-height: 1.6; }
.hilo-bienvenida .hilo-bienvenida-voz { margin-top: var(--space-250); }

/* ── El Asistente ancho (Renzo, 2026-10-11) ─────────────────────────────
   «Expandir» (Opciones del cajón) pone `asistente-expandido` en el body: el
   MISMO cajón crece a pantalla completa encima del ERP, sin cambiar de
   página ni perder el hilo. La página `/asistente/` (`.hilo-ancho`) usa la
   misma disposición siempre. A la izquierda la barra de hilos; al centro
   una columna de lectura; el hilo vacío con el saludo y la caja al centro.
   Encima de la topbar (100) y debajo de un `<dialog>`, que va en la capa
   superior: la ficha que abre una mención se ve encima. */
.hilo-principal { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; }
.hilo-barra { display: none; }
/* `.menu-item` (más abajo en la hoja) pinta `display: flex`: estas llevan
   las dos clases para ganarle. */
.menu-item.hilo-contraer { display: none; }
body.asistente-expandido .menu-item.hilo-expandir { display: none; }
body.asistente-expandido .menu-item.hilo-contraer { display: flex; }
.hilo-ancho .menu-item.hilo-expandir, .hilo-ancho .menu-item.hilo-contraer { display: none; }
body.asistente-expandido.panel-open:not(.panel-filtros) .side-panel {
    position: fixed; inset: 0; z-index: 200;
    width: auto; height: 100dvh; margin: 0;
    border-radius: 0; transition: none;
}
body.asistente-expandido.panel-open:not(.panel-filtros) .side-panel-body { width: 100%; }
/* Con la cara del tablero de Tareas (Renzo, 2026-10-11: «que se parezca
   más a tableros»): el fondo gris lila del ERP, la barra en un panel
   de vidrio que flota, la conversación suelta, la banda de la casa como
   cabecera, las burbujas del Asistente con la piel de la tarjeta, la caja
   de escribir con la cápsula del buscador, y lo puesto (el hilo abierto,
   «Nueva conversación») con el botón puesto del conmutador. Todo con tokens de la
   casa (`--panel-vidrio-*`, `--lista-*`, `--bar-glass-*`), que ya traen su
   noche. SIN `backdrop-filter`: atrapa el «⋯» que `menu.js` saca a `fixed`. */
body.asistente-expandido .side-panel .hilo,
.hilo-ancho .hilo { flex-direction: row; gap: var(--space-200); }
body.asistente-expandido .side-panel .hilo { padding: var(--space-200); }
.card.hilo-pagina.hilo-ancho { background: none; border: 0; box-shadow: none; }
body.asistente-expandido .side-panel .hilo-barra,
.hilo-ancho .hilo-barra {
    display: flex; flex-direction: column; gap: var(--space-150);
    width: 272px; flex: none; min-height: 0; overflow-y: auto;
    padding: var(--space-200) var(--space-200);
    border-radius: 36px;
    background: linear-gradient(180deg, var(--panel-vidrio-arriba), var(--panel-vidrio-abajo));
    border: 1px solid var(--lista-vidrio-filo);
    box-shadow: inset 0 1px 0 var(--lista-vidrio-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--lista-vidrio-sombra);
}
/* «Nueva conversación» y la abierta: el botón puesto, blanco con sombra lila y
   la tinta en el primario. Los demás, en el gris lila de los encabezados.
   Con `.hilo-barra` delante: `.menu-item` va más abajo en la hoja. */
.hilo-barra [data-asistente-nuevo] .btn {
    height: 40px; border-radius: 13px; font-weight: 600; color: var(--primary);
    background: var(--lista-puesto); border: 1px solid var(--lista-vidrio-filo);
    box-shadow: var(--lista-puesto-sombra);
}
.hilo-barra [data-asistente-nuevo] .btn:hover { background: var(--lista-puesto); color: var(--primary-dark); }
.hilo-barra .menu-item { border-radius: 13px; color: var(--lista-encabezado); }
.hilo-barra .menu-item:hover { background: color-mix(in srgb, var(--lista-puesto) 60%, transparent); color: var(--text); }
.hilo-barra .menu-item[aria-current] {
    color: var(--primary); font-weight: 600;
    background: var(--lista-puesto); box-shadow: var(--lista-puesto-sombra);
}
.hilo-barra .menu-titulo { color: var(--lista-encabezado); }
/* La lista nació para el desplegable (`min-width: 280px`); en la barra
   toma su ancho, o la fecha de cada hilo se corta. */
.hilo-barra .hilo-lista { min-width: 0; max-width: none; }
/* La lectura: una columna angosta al centro, como un documento. */
body.asistente-expandido .side-panel .hilo-mensajes,
.hilo-ancho .hilo-mensajes {
    padding-inline: max(var(--space-200), calc((100% - 760px) / 2));
}
body.asistente-expandido .side-panel .hilo-composer,
.hilo-ancho .hilo-composer {
    width: 100%; max-width: calc(760px + 2 * var(--space-200)); margin: 0 auto;
    padding-bottom: var(--space-250);
}
/* El hilo vacío: el saludo y la caja juntos, al centro de la pantalla. */
body.asistente-expandido .side-panel .hilo-principal:has(.hilo-bienvenida),
.hilo-ancho .hilo-principal:has(.hilo-bienvenida) { justify-content: center; }
body.asistente-expandido .side-panel .hilo-principal:has(.hilo-bienvenida) .hilo-cabecera,
.hilo-ancho .hilo-principal:has(.hilo-bienvenida) .hilo-cabecera { position: absolute; top: 0; right: 0; left: auto; border-bottom: 0; }
body.asistente-expandido .side-panel .hilo-principal:has(.hilo-bienvenida) .hilo-mensajes,
.hilo-ancho .hilo-principal:has(.hilo-bienvenida) .hilo-mensajes { flex: 0 0 auto; overflow: visible; }
body.asistente-expandido .side-panel .hilo-principal:has(.hilo-bienvenida) .hilo-titulo,
.hilo-ancho .hilo-principal:has(.hilo-bienvenida) .hilo-titulo { display: none; }
body.asistente-expandido .side-panel .hilo-bienvenida,
.hilo-ancho .hilo-bienvenida { margin: 0; text-align: center; }
body.asistente-expandido .side-panel .hilo-bienvenida h3,
.hilo-ancho .hilo-bienvenida h3 { font-size: calc(var(--fs-page) * 1.4); }
body.asistente-expandido .side-panel .hilo-principal,
.hilo-ancho .hilo-principal { position: relative; gap: var(--space-200); }
/* Desde aquí, lo mismo en el cajón angosto (Renzo, el mismo día: «portar
   este mismo estilo al drawer compacto»): la cabecera en banda, la
   conversación suelta, las burbujas y la cápsula. El cajón toma el gris
   lila del ERP, porque vidrio blanco sobre blanco no se ve; el de Filtros
   sigue blanco. */
body.panel-open:not(.panel-filtros) .side-panel { background: var(--bg); }
/* La cabecera, con la banda de la casa: píldora de vidrio claro, el
   personaje en su baldosa redonda (solo ancho; angosto ya está en la
   topbar) y «Opciones» como el botón de la banda. Angosta flota con un
   margen; ancha, el hilo ya trae el suyo. */
.side-panel .hilo-cabecera,
.hilo-ancho .hilo-cabecera {
    padding: var(--space-100) var(--space-100); border-radius: 999px;
    background: linear-gradient(180deg, var(--bar-glass-top), var(--bar-glass-bottom));
    border: 1px solid var(--bar-glass-edge);
    box-shadow: var(--bar-glass-shadow);
}
.side-panel .hilo-cabecera { margin: var(--space-100) var(--space-100) 0; }
body.asistente-expandido .side-panel .hilo-cabecera { margin: 0; }
.side-panel .hilo-titulo,
.hilo-ancho .hilo-titulo { font-size: var(--fs-columna); letter-spacing: -.01em; }
/* Angosta no hay baldosa: el título sale de la curva de la píldora con
   aire, y un paso más chico (Renzo, el mismo día: «muy pegado del borde»). */
body:not(.asistente-expandido) .side-panel .hilo-titulo { padding-left: var(--space-100); font-size: var(--fs-section); }
.side-panel .hilo-acciones .menu > .menu-trigger,
.hilo-ancho .hilo-acciones .menu > .menu-trigger {
    height: var(--btn-h); padding: 0 var(--space-200) 0 var(--space-100); font-size: var(--fs-body);
    border: 1px solid color-mix(in srgb, var(--text) 28%, transparent); border-radius: 999px;
}
/* La conversación va SUELTA sobre el fondo, sin panel: se probó el panel
   de vidrio del tablero y quedaba como tarjeta (Renzo, el mismo día). */
.side-panel .hilo-mensajes,
.hilo-ancho .hilo-mensajes { padding-block: var(--space-200); }
/* Las burbujas: la del Asistente con la piel de la tarjeta de Tareas; la
   tuya con el tinte lila de la columna «En curso». */
.side-panel .burbuja,
.hilo-ancho .burbuja { padding: var(--space-100) var(--space-200); border-radius: 20px; }
.side-panel .burbuja-asistente,
.hilo-ancho .burbuja-asistente {
    border-bottom-left-radius: 6px;
    background: linear-gradient(180deg, var(--lista-arriba), var(--lista-abajo));
    border-color: var(--lista-filo);
    box-shadow: inset 0 1px 0 var(--lista-brillo), var(--lista-sombra);
}
.side-panel .burbuja-usuario,
.hilo-ancho .burbuja-usuario {
    border-bottom-right-radius: 6px;
    border: 1px solid var(--lista-vidrio-filo);
    box-shadow: inset 0 1px 0 var(--lista-vidrio-brillo), var(--lista-sombra);
}
/* La caja de escribir: la cápsula de vidrio del buscador de las listas; el
   foco, solo el filo en el primario, sin halo. */
.side-panel .hilo-composer,
.hilo-ancho .hilo-composer { padding-top: 0; }
.side-panel .hilo-caja,
.hilo-ancho .hilo-caja {
    border-radius: 24px;
    background: linear-gradient(180deg, var(--lista-vidrio-arriba), var(--lista-vidrio-abajo));
    border-color: var(--lista-vidrio-filo);
    box-shadow: inset 0 1px 0 var(--lista-vidrio-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--lista-vidrio-sombra);
}
.side-panel .hilo-caja:focus-within,
.hilo-ancho .hilo-caja:focus-within {
    border-color: color-mix(in srgb, var(--primary) 45%, var(--lista-vidrio-filo));
    box-shadow: inset 0 1px 0 var(--lista-vidrio-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--lista-vidrio-sombra);
}
/* El hilo vacío no lleva banda: el saludo flota sobre el fondo. */
body.asistente-expandido .side-panel .hilo-principal:has(.hilo-bienvenida) .hilo-cabecera,
.hilo-ancho .hilo-principal:has(.hilo-bienvenida) .hilo-cabecera { background: none; border: 0; box-shadow: none; }
/* Lo que la topbar tapada se llevaba (Renzo, 2026-10-11: «quiero seguir
   manteniendo… logo de Agrupa, avatar 3D»). El logo, arriba de la barra,
   solo expandido: en `/asistente/` la topbar se ve. El personaje, grande en
   el saludo y chico junto al título; con «Personajes: 3D» los dos se cambian
   por su `<agrupa-avatar>`. En el cajón angosto no se ven: manda la topbar. */
.hilo-marca { padding: var(--space-025) var(--space-100) var(--space-050); color: var(--text); }
.hilo-marca .brand-logo { height: 28px; }
.hilo-ancho .hilo-marca { display: none; }
/* Un paso más grande que una baldosa común (Renzo, 2026-10-11: «crecer un
   poco más el avatar, no mucho»). */
.tile.hilo-cara { display: none; width: 42px; height: 42px; border-radius: 999px; }
/* Con `.tile.hilo-cara` delante: `.tile svg` (19px, más abajo) le ganaba,
   el dibujo salía de 19 de ancho y el 3D copiaba esa medida. */
.tile.hilo-cara .hilo-cara-compi { width: 36px; height: 36px; }
body.asistente-expandido .side-panel .tile.hilo-cara,
.hilo-ancho .tile.hilo-cara { display: grid; }
body.asistente-expandido .side-panel .hilo-titulo,
.hilo-ancho .hilo-titulo { flex: 1; }
/* En el hilo vacío ya está el grande. */
body.asistente-expandido .side-panel .hilo-principal:has(.hilo-bienvenida) .tile.hilo-cara,
.hilo-ancho .hilo-principal:has(.hilo-bienvenida) .tile.hilo-cara { display: none; }
.hilo-bienvenida-compi { display: none; }
body.asistente-expandido .side-panel .hilo-bienvenida-compi,
.hilo-ancho .hilo-bienvenida-compi { display: block; width: 88px; height: 88px; margin: 0 auto var(--space-200); }
/* En el teléfono no hay lado: la barra se esconde y los hilos siguen en
   Opciones. */
@media (max-width: 767px) {
    body.asistente-expandido .side-panel .hilo-barra,
.hilo-ancho .hilo-barra { display: none; }
}
.burbuja {
    max-width: 92%; padding: var(--space-100) var(--space-150);
    border-radius: var(--radius); font-size: var(--fs-body); line-height: 1.45;
}
.burbuja-usuario { background: var(--primary-soft); color: var(--text); border-bottom-right-radius: 6px; }
.burbuja-asistente { background: var(--card-bg); border: 1px solid var(--border); border-bottom-left-radius: 6px; }
/* «Usar en otra pantalla»: el drawer de las pantallas con PIN. */
.pantallas-crear { margin-bottom: var(--space-250); }
.pantallas-pins { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-150); }
.pantalla-ficha { margin-bottom: var(--space-150); }
.pantalla-ficha-cuerpo { display: flex; gap: var(--space-200); align-items: flex-start; }
.pantalla-qr { flex: none; width: 120px; height: 120px; border-radius: var(--radius-sm); background: #fff; padding: var(--space-050); border: 1px solid var(--border); }
.pantalla-qr svg { width: 100%; height: 100%; display: block; }
.pantalla-ficha-datos { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-150); }
.pantalla-pin-form { display: flex; gap: var(--space-100); align-items: center; flex-wrap: wrap; }
.pantalla-pin-form input { width: 110px; }
@media (max-width: 560px) { .pantalla-ficha-cuerpo { flex-direction: column; } .pantallas-pins { grid-template-columns: 1fr; } }
/* La pantalla del Asistente (URL con PIN): el hilo a pantalla completa, sin
 * shell. La cabecera es una banda con la empresa, el nombre y «Salir». */
/* Sobre los orbes del lienzo (`position: fixed; z-index: 0`): la pantalla
 * es una capa encima, como `.auth-stage`, y la tarjeta del chat es cristal
 * —fondo casi transparente con desenfoque— para que los orbes se vean
 * detrás y no encima. */
/* `height` y no `min-height` (Renzo, 2026-09-15): la pantalla del Asistente
   mide EXACTAMENTE la ventana, así que la tarjeta del chat es lo que queda
   bajo la cabecera y los mensajes se desplazan por dentro
   (`.hilo-mensajes` ya trae su `overflow-y: auto`). Con `min-height`, una
   conversación larga estiraba la página entera y la caja de escribir se iba
   al fondo del documento: en una tablet del mostrador había que bajar toda
   la pantalla para contestar. Esta clase la usa una sola plantilla
   (`assistant/screen.html`), que es lo que hace seguro fijarle el alto. */
.pantalla { position: relative; z-index: 1; height: 100dvh; display: flex; flex-direction: column; padding: 0 max(var(--space-150), env(safe-area-inset-right)) max(var(--space-150), env(safe-area-inset-bottom)) max(var(--space-150), env(safe-area-inset-left)); gap: var(--space-200); }
/* La cabecera calca la topbar del ERP, más alta que la del cajón: fondo
 * oscuro, el logo en blanco por `currentColor`, la empresa y el nombre. */
.pantalla-cabecera { display: flex; align-items: center; justify-content: space-between; gap: var(--space-150); margin: 0 calc(-1 * max(var(--space-150), env(safe-area-inset-right))) 0 calc(-1 * max(var(--space-150), env(safe-area-inset-left))); padding: calc(max(var(--space-200), env(safe-area-inset-top)) + var(--space-050)) clamp(var(--space-200), 3.5vw, var(--space-400)) var(--space-200); background: var(--canvas); color: #fff; border-radius: 0 0 var(--radius-card) var(--radius-card); box-shadow: 0 8px 30px rgba(13, 11, 22, .18); }
.pantalla-marca { display: flex; align-items: center; gap: var(--space-200); min-width: 0; }
.pantalla-logo .brand-logo { height: 36px; }
.pantalla-sep { width: 1px; height: 36px; background: rgba(255, 255, 255, .25); flex: none; }
.pantalla-textos { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; gap: var(--space-025); }
.pantalla-empresa { font-size: var(--fs-small); color: rgba(255, 255, 255, .7); text-transform: uppercase; letter-spacing: .06em; }
.pantalla-nombre { font-size: var(--fs-stat); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pantalla-salir { color: #fff; border-color: rgba(255, 255, 255, .35); }
.pantalla-salir:hover { background: rgba(255, 255, 255, .12); }
/* Cristal: blanco al 55 % con el fondo desenfocado y saturado, un borde
 * claro y un brillo interior arriba. `-webkit-` primero por Safari. */
.pantalla-cuerpo { flex: 1; min-height: 0; display: flex; flex-direction: column; background: rgba(255, 255, 255, .55); border: 1px solid rgba(255, 255, 255, .65); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .8), 0 12px 40px rgba(60, 40, 120, .12); -webkit-backdrop-filter: blur(22px) saturate(160%); backdrop-filter: blur(22px) saturate(160%); }
.pantalla-cuerpo .hilo { flex: 1; min-height: 0; height: auto; }
.pantalla-cuerpo .burbuja-asistente { background: rgba(255, 255, 255, .72); }
.pantalla-cuerpo .hilo-caja { background: rgba(255, 255, 255, .8); }
/* Cada mensaje es una fila: avatar, burbuja y hora. El Asistente a la
 * izquierda con el personaje; la persona a la derecha con su foto o
 * inicial (la misma de la topbar). */
.hilo-fila { display: flex; align-items: flex-end; gap: var(--space-100); max-width: 100%; }
.hilo-fila-asistente { align-self: flex-start; }
.hilo-fila-usuario { align-self: flex-end; }
.hilo-fila-cuerpo { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.hilo-fila-usuario .hilo-fila-cuerpo { align-items: flex-end; }
.hilo-fila .burbuja { max-width: 100%; }
.hilo-avatar { width: 28px; height: 28px; flex: none; border-radius: 50%; display: grid; place-items: center; font-size: var(--fs-meta); font-weight: 700; color: #fff; background: var(--primary); }
/* `overflow` oculto a propósito: con `visible` se asomaban, arriba y a la
 * izquierda, los bordes de la máscara del arte (rayitas). */
.hilo-avatar-compi { background: transparent; border-radius: 0; overflow: hidden; }
/* El personaje del hilo se FORMA (la animación de aparición corre una
 * vez) y después queda quieto: sin respirar, parpadear ni derivar. La
 * clase va en la copia del símbolo, así todas las caritas del hilo la
 * heredan y la del panel sigue viva. */
.compi-quieto .compi-body, .compi-quieto .compi-gaze, .compi-quieto .compi-eye, .compi-quieto .compi-smile,
.compi-quieto .compi-au1, .compi-quieto .compi-au2, .compi-quieto .compi-au3 { animation: none; }
/* `compi-fijo` (2026-10-03): el personaje de una fila, un aviso o una
 * cabecera NACE formado —sin la aparición, que en su primer segundo lo
 * pinta blanco y sin cara— y no se mueve. Es la marca de quién hizo algo,
 * no un personaje que entra en escena. */
.compi.compi-fijo * { animation: none; }
.hilo-compi-defs { position: absolute; }
.hilo-hora { font-size: var(--fs-meta); color: var(--text-faint); padding: 0 var(--space-050); }
/* La respuesta en vivo solo se ve mientras la burbuja no esté oculta. */
.hilo-fila-escribiendo:not(:has(#hilo-escribiendo:not([hidden]))) { display: none; }
/* Las cifras de un resumen: tarjetas con el número grande y el rótulo. */
.hilo-cifras { margin: 0; padding: 0; list-style: none; display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: var(--space-100); align-self: stretch; }
.cifra { display: flex; flex-direction: column; gap: var(--space-025); padding: var(--space-150); border-radius: var(--radius); background: var(--primary-soft); border: 1px solid var(--primary-soft-border); min-width: 0; }
.cifra-valor { font-size: var(--fs-stat); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cifra-rotulo { font-size: var(--fs-meta); color: var(--text-light); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cifra-2 { background: var(--ok-bg); border-color: color-mix(in srgb, var(--ok-text) 18%, transparent); }
.cifra-3 { background: var(--warn-bg); border-color: color-mix(in srgb, var(--warn-text) 18%, transparent); }
.cifra-4 { background: var(--danger-bg); border-color: color-mix(in srgb, var(--danger-text) 14%, transparent); }
.cifra-mas { justify-content: center; align-items: center; color: var(--text-faint); font-size: var(--fs-meta); background: transparent; border-style: dashed; }
.burbuja p { margin: 0 0 var(--space-100); }
.burbuja p:last-child { margin-bottom: 0; }
.burbuja ul, .burbuja ol { margin: 0 0 var(--space-100); padding-left: var(--space-250); }
.burbuja code { font-size: var(--fs-small); }
.hilo-escribiendo:empty { display: none; }
.hilo-pensando { margin: 0; font-size: var(--fs-meta); color: var(--text-faint); }
/* El paso a paso: lo que el asistente va haciendo, tachado a medida que
   termina; al final queda plegado en `.hilo-hecho`. */
.hilo-proceso { align-self: stretch; }
.hilo-pasos { margin: 0; padding: 0 0 0 var(--space-100); list-style: none; display: flex; flex-direction: column; gap: var(--space-075); }
.hilo-paso {
    position: relative; padding-left: var(--space-200);
    font-size: var(--fs-small); color: var(--text-light); line-height: 1.4;
}
.hilo-paso::before {
    content: ''; position: absolute; left: 0; top: .45em; width: 8px; height: 8px;
    border-radius: 50%; border: 2px solid var(--text-faint); box-sizing: border-box;
}
.hilo-paso.is-activo { color: var(--text); }
.hilo-paso.is-activo::before { border-color: var(--ok-text); animation: live-pulse 1.2s ease-in-out infinite; }
.hilo-paso.is-listo::before { background: var(--ok-text); border-color: var(--ok-text); }
.hilo-paso.is-fallo::before { background: var(--danger-text); border-color: var(--danger-text); }
.hilo-paso-detalle { color: var(--text-faint); }
.hilo-paso-resumen { color: var(--text-faint); font-size: var(--fs-meta); }
/* Mientras trabaja, UN renglón: el último paso, que `asistente.js`
   reemplaza con cada evento (Renzo, 2026-10-11: «una sola línea y va
   cambiando»). Lo largo se corta con puntos suspensivos. Al terminar la
   lista entera queda plegada en «N pasos · ver qué hizo», sin los números:
   ahí la lista ya es la cuenta. */
.hilo-proceso .hilo-paso { display: none; }
.hilo-proceso .hilo-paso:last-child { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hilo-paso-n { color: var(--text-faint); }
.hilo-hecho .hilo-paso-n { display: none; }
.hilo-hecho { align-self: stretch; font-size: var(--fs-small); }
.hilo-hecho-resumen { cursor: pointer; color: var(--text-faint); list-style: none; }
.hilo-hecho-resumen::-webkit-details-marker { display: none; }
.hilo-hecho[open] .hilo-hecho-resumen { margin-bottom: var(--space-100); }
.hilo-fichas { margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-100); align-self: stretch; }
/* Lo que generó la calculadora (un PDF, una hoja): la tarjeta de la casa,
   una debajo de otra, después de la respuesta. */
.hilo-archivos { display: flex; flex-direction: column; gap: var(--space-100); align-self: stretch; }
/* «Lo que hice» (spec 2026-10-16 §7): lo que cambió una respuesta del
   Asistente, cada cosa con el enlace a su pantalla, y «Deshacer». */
.hilo-cambios { align-self: stretch; display: flex; flex-direction: column; gap: var(--space-100);
    padding: var(--space-150); border: 1px solid var(--border); border-radius: var(--radius);
    font-size: var(--fs-small); }
.hilo-cambios-titulo { margin: 0; font-weight: 600; color: var(--text); }
.hilo-cambios-lista { margin: 0; padding-left: var(--space-200); display: flex; flex-direction: column; gap: var(--space-050); }
.hilo-cambios-deshacer { align-self: flex-start; }
/* Las preguntas con opciones (Renzo, 2026-10-16): una por una con
   JavaScript; sin él, todas a la vista. Cada pregunta es `.option-cards`. */
.hilo-preguntas { align-self: stretch; display: flex; flex-direction: column; gap: var(--space-150);
    padding: var(--space-150); border: 1px solid var(--border); border-radius: var(--radius); }
.hilo-pregunta-paso { margin: 0; font-size: var(--fs-small); color: var(--text-faint); }
.hilo-preguntas.is-js .hilo-pregunta:not(.is-actual) { display: none; }
.hilo-preguntas-acciones { display: flex; gap: var(--space-100); justify-content: flex-end; flex-wrap: wrap; }
.hilo-preguntas-acciones .hilo-pregunta-otra { margin-right: auto; }
.hilo-preguntas:not(.is-js) .hilo-pregunta-siguiente { display: none; }
.hilo-preguntas.is-enviada { opacity: .6; pointer-events: none; }
/* `.btn` es inline-flex y le gana al [hidden] del navegador. */
.hilo-preguntas [hidden], .hilo-preguntas[hidden] { display: none; }
.hilo-preguntas-error { margin: 0; font-size: var(--fs-small); color: var(--danger-text); }
.hilo-preguntas.is-respondida { gap: var(--space-050); }
/* Tocar la ficha es RESPONDER (la pone en la caja); la flechita de la
   esquina abre la ficha en otra pestaña (Renzo, 2026-10-12). */
.ficha-mini { margin: 0; padding: 0; position: relative; }
.ficha-mini-link {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-150); padding-right: calc(var(--space-150) + var(--space-300));
    cursor: pointer;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card-bg); color: var(--text); text-decoration: none;
    transition: border-color .12s ease, box-shadow .12s ease;
}
.ficha-mini-link:hover { border-color: var(--primary-soft-border); box-shadow: 0 2px 10px color-mix(in srgb, var(--primary) 10%, transparent); }
.ficha-mini-abrir {
    position: absolute; top: 6px; right: 6px; width: 24px; height: 24px;
    display: grid; place-items: center; border-radius: var(--radius-sm);
    color: var(--text-faint);
}
.ficha-mini-abrir:hover { color: var(--primary-dark); background: var(--primary-soft); }
.ficha-mini-abrir svg { width: 14px; height: 14px; }
.ficha-mini-icono {
    flex: none; width: 32px; height: 32px; display: grid; place-items: center;
    border-radius: var(--radius-sm); background: var(--primary-soft); color: var(--primary-dark);
}
.ficha-mini-icono svg { width: 18px; height: 18px; }
.ficha-mini-cuerpo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.ficha-mini-titulo { font-size: var(--fs-body); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ficha-mini-meta { font-size: var(--fs-meta); color: var(--text-light); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ficha-mini-monto { flex: none; font-size: var(--fs-body); font-weight: 600; font-variant-numeric: tabular-nums; }
.hilo-fichas-mas { color: var(--text-faint); font-size: var(--fs-meta); padding: 0 var(--space-150); }
.hilo-aviso { align-self: center; }
.hilo-composer {
    display: flex; flex-direction: column; gap: var(--space-100);
    padding: var(--space-150) var(--space-200); flex: none;
}
.hilo-composer.is-arrastrando { outline: 2px dashed var(--primary); outline-offset: -4px; }
/* La caja del mensaje: un solo borde redondo para adjuntar, texto y
   enviar; el foco lo pinta la caja, no el textarea. */
.hilo-caja {
    display: flex; flex-direction: column; gap: var(--space-050);
    padding: var(--space-100);
    border: 1px solid var(--border); border-radius: 18px;
    background: var(--card-bg);
}
.hilo-composer-herramientas { display: flex; align-items: center; gap: var(--space-075); width: 100%; }
.hilo-composer-herramientas > [type=submit] { margin-left: auto; }
.hilo-composer-herramientas .btn, .hilo-composer-herramientas .hilo-adjuntar { min-height: 40px; }
.hilo-composer-herramientas .btn-icon, .hilo-composer-herramientas .hilo-adjuntar { width: 40px; }
.hilo-caja:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.hilo-texto {
    width: 100%; flex: none; min-width: 0; resize: none; margin: 0;
    padding: var(--space-075) var(--space-050); min-height: 32px; max-height: 160px;
    border: 0; background: transparent; box-shadow: none;
    font-size: var(--fs-body); line-height: 1.4;
}
.hilo-texto:focus { outline: none; box-shadow: none; }
.hilo-caja .btn-icon { flex: none; border-radius: 50%; }
/* La voz tiene su propio espacio. El nivel SIEMPRE representa al micrófono,
   y los estados escritos siguen disponibles sin animación ni color. */
.hilo-hablar { color: var(--primary); }
/* «Parar»: solo mientras el Asistente trabaja (spec 2026-10-16 §9). */
.hilo-parar { display: none; }
.hilo-caja.is-pensando .hilo-parar { display: inline-flex; }
.hilo-hablar[aria-pressed=true] { background: color-mix(in srgb, var(--primary) 10%, var(--card-bg)); }
.hilo-voz {
    flex: none; margin: 0 var(--space-200); padding: var(--space-150) var(--space-200);
    border: 1px solid color-mix(in srgb, var(--primary) 22%, var(--border));
    border-radius: 20px; background: color-mix(in srgb, var(--primary) 4%, var(--card-bg));
    color: var(--text); min-width: 0;
}
.hilo-voz[hidden], .hilo-voz [hidden] { display: none; }
.hilo-voz-cabecera { display: flex; align-items: center; gap: var(--space-100); min-height: 36px; }
.hilo-voz-identidad { display: flex; align-items: center; gap: var(--space-100); min-width: 0; flex: 1; font-size: var(--fs-small); color: var(--text-light); white-space: nowrap; }
.hilo-voz-titulo, .hilo-voz-resumen { overflow: hidden; text-overflow: ellipsis; }
.hilo-voz-punto { width: 8px; height: 8px; border-radius: 50%; background: var(--primary); flex: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.hilo-voz[data-estado=listening] .hilo-voz-punto, .hilo-voz[data-estado=hearing] .hilo-voz-punto { background: var(--ok-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok-text) 22%, transparent); }
.hilo-voz[data-estado=hearing] .hilo-voz-punto { animation: live-pulse .9s ease-in-out infinite; }
.hilo-voz[data-estado=thinking] .hilo-voz-punto { animation: live-pulse 1.4s ease-in-out infinite; }
.hilo-voz[data-estado=muted] .hilo-voz-punto { background: var(--text-faint); box-shadow: none; }
.hilo-voz[data-estado=error] .hilo-voz-punto { background: var(--danger-text); box-shadow: none; }
.hilo-voz-botones { display: flex; align-items: center; gap: var(--space-025); flex: none; }
.hilo-voz-icono { display: inline-grid; place-items: center; width: 34px; height: 34px; border: 0; border-radius: 50%; background: transparent; color: var(--text-light); cursor: pointer; padding: 0; }
.hilo-voz-icono svg { width: 18px; height: 18px; }
.hilo-voz-icono:hover { background: color-mix(in srgb, var(--primary) 10%, transparent); color: var(--text); }
.hilo-voz-icono[aria-pressed=true] { color: var(--danger-text); background: color-mix(in srgb, var(--danger-text) 10%, transparent); }
.hilo-voz-icono[data-voz-plegar] svg { transition: transform .18s ease; }
.hilo-voz-icono[aria-expanded=true] svg { transform: rotate(180deg); }
.hilo-voz-terminar { color: var(--danger-text); }
.hilo-voz-terminar:hover { background: color-mix(in srgb, var(--danger-text) 10%, transparent); color: var(--danger-text); }
/* Desplegado: solo título; el micrófono y el medidor viven en la escena. */
.hilo-voz:not(.is-plegado) .hilo-voz-resumen, .hilo-voz:not(.is-plegado) .hilo-voz-nivel-mini, .hilo-voz:not(.is-plegado) .hilo-voz-mini-silenciar { display: none; }
.hilo-voz-escena { text-align: center; padding: var(--space-100) 0 var(--space-150); }
.hilo-voz-estado { margin: 0; font-size: var(--fs-page); line-height: 1.25; font-weight: 600; letter-spacing: -.4px; }
.hilo-voz-ayuda { margin: var(--space-075) 0 0; font-size: var(--fs-small); color: var(--text-light); line-height: 1.4; }
.hilo-voz-nivel { display: flex; align-items: center; justify-content: center; gap: var(--space-075); height: 58px; margin: var(--space-100) 0 var(--space-025); color: var(--primary); }
.hilo-voz-nivel i { display: block; width: 5px; height: calc(5px + 43px * var(--nivel, 0)); border-radius: 4px; background: currentColor; transition: height 80ms linear; }
/* Plegado: una píldora de una línea —estado, mini medidor, tres iconos—.
 * Se pliega solo al llegar la primera respuesta del ERP para que se lea
 * en el hilo; la persona lo vuelve a abrir con el chevron. */
.hilo-voz.is-plegado { padding: var(--space-050) var(--space-075) var(--space-050) var(--space-200); border-radius: 999px; }
.hilo-voz.is-plegado .hilo-voz-titulo { display: none; }
.hilo-voz.is-plegado .hilo-voz-resumen { color: var(--text); font-weight: 500; }
.hilo-voz.is-plegado .hilo-voz-escena, .hilo-voz.is-plegado .hilo-voz-subtitulos, .hilo-voz.is-plegado .hilo-voz-controles { display: none; }
.hilo-voz-nivel-mini { height: 22px; gap: var(--space-050); margin: 0; }
.hilo-voz-nivel-mini i { width: 3px; height: calc(4px + 14px * var(--nivel, 0)); }
.hilo-voz[data-estado=muted] .hilo-voz-nivel { color: var(--text-faint); }
.hilo-voz[data-estado=connecting] .hilo-voz-punto { animation: live-pulse 1.2s ease-in-out infinite; }
.hilo-voz-subtitulos { display: flex; flex-direction: column; gap: var(--space-100); max-height: 110px; overflow-y: auto; overflow-wrap: anywhere; }
.hilo-voz-subtitulos p { display: flex; align-items: baseline; gap: var(--space-100); margin: 0; font-size: var(--fs-body); line-height: 1.5; }
.hilo-voz-quien { width: 56px; flex: none; font-size: var(--fs-meta); color: var(--text-light); }
.hilo-voz-controles { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-100); padding-top: var(--space-150); }
.hilo-voz-control { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-100); min-height: 40px; padding: var(--space-100) var(--space-150); border-radius: 12px; border: 1px solid var(--border); background: var(--card-bg); color: var(--text); font: inherit; font-size: var(--fs-small); cursor: pointer; }
.hilo-voz-control svg { width: 16px; height: 16px; }
.hilo-voz-control:hover { border-color: var(--primary); }
.hilo-voz-control[aria-pressed=true] { color: var(--danger-text); border-color: currentColor; }
.hilo-voz button:focus-visible { outline: 3px solid color-mix(in srgb, var(--primary) 45%, transparent); outline-offset: 2px; }
.hilo-caja.is-pensando { border-color: color-mix(in srgb, var(--primary) 40%, var(--border)); }
@media (max-height: 640px) {
    .hilo-voz-escena { padding: var(--space-025) 0 var(--space-075); }
    .hilo-voz-nivel { height: 30px; margin: var(--space-025) 0; }
    .hilo-voz-nivel i { max-height: 28px; }
    .hilo-voz-subtitulos { max-height: 66px; }
}
/* Con movimiento reducido, el bloque general de la hoja ya apaga la
   transición de las barras y el pulso del punto: no se enumeran acá. */
/* Adjuntar: el <label> abre el selector; el <input> queda oculto pero vivo
   (camino sin script). La miniatura con × aparece antes de enviar. */
.hilo-adjuntar {
    flex: none; width: 30px; height: 30px; display: grid; place-items: center;
    border-radius: 50%; color: var(--text-light); cursor: pointer;
}
.hilo-adjuntar:hover { color: var(--text); background: var(--bg); }
.hilo-adjuntar svg { width: 18px; height: 18px; }
.hilo-adjunto { position: relative; align-self: flex-start; }
.hilo-adjunto-miniatura {
    display: block; max-width: 120px; max-height: 120px;
    border-radius: var(--radius-sm); border: 1px solid var(--border);
}
.hilo-adjunto-quitar {
    position: absolute; top: -8px; right: -8px; width: 22px; height: 22px;
    display: grid; place-items: center; padding: 0;
    border: 1px solid var(--border); border-radius: 50%;
    background: var(--card-bg); color: var(--text); cursor: pointer;
}
.hilo-adjunto-quitar svg { width: 12px; height: 12px; }
.burbuja-imagen {
    display: block; max-width: 100%; max-height: 240px;
    border-radius: var(--radius-sm); margin-bottom: var(--space-100);
}
.burbuja-imagen:last-child { margin-bottom: 0; }
.burbuja-texto { white-space: pre-wrap; }
/* La lista de hilos es un menú de la casa: el ítem, el glifo, la raya y el
   rótulo salen de `.menu-item`. Acá solo vive lo que es del hilo — cuánto
   mide la hoja y cómo se reparten el título y la fecha. El tope de ancho es
   para que un título largo se corte con elipsis en vez de estirar el menú
   hasta el otro lado de la pantalla. */
.hilo-lista { min-width: 280px; max-width: 360px; }
.hilo-lista-titulo { flex: 1; min-width: 0; overflow: hidden;
                     text-overflow: ellipsis; white-space: nowrap; }
.hilo-lista-fecha { flex: none; color: var(--text-faint); font-size: var(--fs-meta); }
.hilo-lista-vacia .empty { padding: var(--space-200) var(--space-150) var(--space-150); }
.hilo-lista-vacia .empty-text { margin-bottom: 0; font-size: var(--fs-small); }

/* La tarjeta que pide el visto bueno (rehecha 2026-10-25, Renzo: «mejorar
   las tarjetas de confirmación»). Mientras espera, la raya es morada —la
   del botón que la contesta—; verde solo cuando quedó hecha. El texto va
   sin negrita: un pedido de diez renglones en negrita no se leía. Los dos
   botones van en una fila, el que confirma primero. */
.propuesta {
    align-self: stretch; padding: var(--space-150) var(--space-200);
    border: 1px solid var(--border); border-left: 3px solid var(--text-light);
    border-radius: var(--radius); background: var(--card-bg);
    display: flex; flex-direction: column; gap: var(--space-100);
}
.propuesta-pendiente { border-left-color: var(--primary); }
.propuesta-confirmada { border-left-color: var(--ok-text); }
.propuesta-corriendo { border-left-color: var(--info-text); }
.propuesta-rechazada, .propuesta-vencida { border-left-color: var(--text-faint); opacity: .8; }
.propuesta-enlace { margin-left: var(--space-100); font-size: var(--fs-small); }
.propuesta-fallida { border-left-color: var(--danger-text); }
.propuesta-titulo { margin: 0; font-size: var(--fs-small); font-weight: 600; color: var(--text-light); }
.propuesta-texto { font-size: var(--fs-body); line-height: 1.55; color: var(--text); overflow-wrap: anywhere; display: grid; gap: var(--space-100); }
.propuesta-texto > * { margin: 0; }
.propuesta-texto ol, .propuesta-texto ul { padding-left: var(--space-250); display: grid; gap: var(--space-050); }
.propuesta-texto li::marker { color: var(--text-faint); font-variant-numeric: tabular-nums; }
.propuesta-texto strong { font-weight: 600; }
/* Si hay más de un párrafo, el primero es el título de lo que pide. */
.propuesta-texto > p:first-child:not(:only-child) { font-weight: 600; }
.propuesta-params { margin: 0; display: grid; grid-template-columns: max-content 1fr; gap: var(--space-025) var(--space-150); font-size: var(--fs-small); }
.propuesta-params dt { color: var(--text-faint); }
.propuesta-params dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.propuesta-acciones {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-100);
    margin-top: var(--space-050); padding-top: var(--space-150); border-top: 1px solid var(--border);
}
.propuesta-form { display: flex; flex-direction: column; gap: var(--space-100); }
/* Con contraseña, el formulario que confirma se lleva el ancho y el «No»
   queda abajo a la derecha, a la altura del botón. */
.propuesta-acciones > .propuesta-form:has([data-reauth-campo]) { flex: 1 1 0; min-width: 0; }
.propuesta-botones { display: flex; gap: var(--space-100); }
.propuesta-estado { margin: 0; font-size: var(--fs-small); display: flex; align-items: center; gap: var(--space-100); }
.propuesta-resumen { margin: 0; font-size: var(--fs-body); line-height: 1.55; }
.propuesta-de-quien { color: var(--text-faint); }
.propuesta-pasos { margin: 0; padding-left: var(--space-200); display: grid; gap: var(--space-050); font-size: var(--fs-body); line-height: 1.5; overflow-wrap: anywhere; }
.propuesta-paso-fallo { color: var(--danger-text); }
.propuesta-paso-no_corrio { color: var(--text-faint); }

.ajustes-consumo { display: grid; grid-template-columns: max-content 1fr; gap: var(--space-050) var(--space-200); margin: 0; font-size: var(--fs-body); }
.ajustes-consumo dt { color: var(--text-faint); }
.ajustes-consumo dd { margin: 0; }
.sidebar-label {
    margin: var(--space-200) var(--space-100) var(--space-075); font-size: var(--fs-badge); font-weight: 700;
    letter-spacing: .1em; text-transform: uppercase;
    color: var(--text-faint);
}
.sidebar-label:first-child { margin-top: var(--space-050); }
/* El primer rótulo con la chincheta a la derecha (2026-10-31). La fila
   toma el margen que el rótulo tenía por ser el primero. */
.sidebar-label-row {
    display: flex; align-items: center; justify-content: space-between;
    margin: var(--space-050) var(--space-050) var(--space-075) 0;
}
.sidebar-label-row .sidebar-label { margin: 0 var(--space-100); }
.menu-fijar { display: flex; margin: 0; }
/* Inclinada y tenue mientras el menú anda suelto; derecha y en el color de
   la marca cuando lo deja fijo. El giro es el gesto de clavarla. */
.menu-chincheta {
    display: grid; place-items: center; width: 28px; height: 28px; padding: 0;
    color: var(--text-faint); background: transparent; border: 0;
    border-radius: var(--radius); cursor: pointer;
}
.menu-chincheta:hover { color: var(--text); background: var(--bg); }
.menu-chincheta:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.menu-chincheta svg { width: 16px; height: 16px; transform: rotate(45deg); transition: transform .22s ease; }
/* Fija: derecha, rellena y en el color de la marca (Renzo, 2026-10-31). Al
   clavarla se levanta, golpea y rebota: el gesto de clavarla. */
.menu-chincheta[aria-pressed="true"] { color: var(--primary); }
.menu-chincheta[aria-pressed="true"] svg { transform: none; fill: currentColor; }
.menu-chincheta.is-clavando svg { animation: chincheta-clava .5s cubic-bezier(.3, .7, .4, 1); transform-origin: 50% 90%; }
@keyframes chincheta-clava {
    0%   { transform: rotate(45deg); }
    35%  { transform: translateY(-5px) rotate(8deg); }
    50%  { transform: translateY(2px) scale(1.15, .8); }
    68%  { transform: translateY(-2px) scale(.95, 1.05); }
    84%  { transform: translateY(0) scale(1.03, .97); }
    100% { transform: none; }
}
.nav-list { list-style: none; margin: 0; padding: 0; }
.nav-item {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-050) var(--space-150); margin-bottom: var(--space-025);
    color: var(--text-light); font-size: var(--fs-body); font-weight: 500;
    border-radius: var(--radius); text-decoration: none;
    transition: background .16s, color .16s;
}
.nav-item:hover { background: var(--bg); color: var(--text); text-decoration: none; }
.nav-item.active { background: var(--primary); color: #fff; font-weight: 600; }
.nav-item:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-soft); }
.nav-icon { width: 17px; height: 17px; flex-shrink: 0; overflow: visible; }

/* ── La entrada del menú ────────────────────────────────────────────────────
   En la primera carga de la pestaña y en cada recarga (`html.menu-entra`,
   lo pone el `<head>` del shell); navegando con enlaces, no. Cada renglón llega desde la izquierda, desenfocado,
   con un turno de 22ms detrás del anterior: la lista se lee como una cascada
   y no como un bloque que aparece. Todo dura menos de un segundo. Con
   movimiento reducido la apaga el `*` del bloque general, como a todas. */
@keyframes menu-entra {
    from { opacity: 0; transform: translateX(-10px); filter: blur(4px); }
    to   { opacity: 1; transform: none; filter: none; }
}
html.menu-entra .sidebar .sidebar-label,
html.menu-entra .sidebar .nav-item,
html.menu-entra .sidebar .nav-subitem {
    animation: menu-entra .45s cubic-bezier(.2, .7, .2, 1) both;
    animation-delay: calc(var(--i, 0) * 22ms + 60ms);
}

/* ── El ícono del menú se mueve al pasar ──────────────────────────────────
   Cada glifo actúa lo que es, por partes y en secuencia: el techo de Inicio
   se levanta y las paredes se agachan cuando vuelve a caer, el camión sale
   por la derecha y entra por la izquierda, el televisor se apaga como un
   tubo y se vuelve a prender, el chulo de Tareas se borra y se vuelve a
   trazar. Una vez por cada entrada del puntero, y sin JavaScript.

   El gancho es `data-icon`, que el shell ya conoce por el `NavItem.icon` del
   módulo; un glifo sin gesto propio no se mueve. Las partes se nombran por su
   orden dentro del glifo (`icons.py`): si alguien redibuja uno, el gesto se
   revisa con él. `fill-box` hace que cada parte gire y escale sobre sí misma
   y no sobre el centro del lienzo de 24×24.

   «Trazar» es `stroke-dasharray: 100` con el desfase de 100 a 0: el trazo se
   dibuja solo, desde su punto de partida. Sirve sin `pathLength` porque
   ninguna parte que se traza mide más de 100 unidades en un lienzo de 24; el
   engranaje sí, y por eso no se traza. Las ventanas de Empresa son seis
   rayas de 2 en un solo trazo de 12: con `steps(6)` se prende una por paso. El bloque universal de
   `prefers-reduced-motion` las apaga todas. */
.nav-icon > * { transform-box: fill-box; transform-origin: center; }
.nav-item:hover .nav-icon {
    --nav-spring: cubic-bezier(.34, 1.56, .64, 1);
    --nav-soft: cubic-bezier(.45, 0, .2, 1);
}

/* Inicio: el techo sube, cae y las paredes amortiguan el golpe. */
.nav-item:hover [data-icon="inicio"] > :nth-child(1) { animation: nav-techo .8s var(--nav-soft); }
.nav-item:hover [data-icon="inicio"] > :nth-child(2) { transform-origin: 50% 100%; animation: nav-amortigua .8s var(--nav-soft); }

/* Ajustes: el engranaje da media vuelta con inercia y el eje late. */
.nav-item:hover [data-icon="ajustes"] > :nth-child(2) { animation: nav-engrana 1s var(--nav-soft); }
.nav-item:hover [data-icon="ajustes"] > :nth-child(1) { animation: nav-eje 1s var(--nav-soft); }

/* Automatizaciones: el rayo se apaga, se vuelve a trazar y destella. */
.nav-item:hover [data-icon="automatizaciones"] { animation: nav-sacude .9s linear; }
.nav-item:hover [data-icon="automatizaciones"] > :nth-child(1) { animation: nav-traza .45s ease-out, nav-destella .45s ease-out .45s; }

/* Biblioteca: el sol hace su arco y las montañas se corren por detrás. */
.nav-item:hover [data-icon="biblioteca"] > :nth-child(2) { animation: nav-arco 1s var(--nav-soft); }
.nav-item:hover [data-icon="biblioteca"] > :nth-child(3) { animation: nav-paralaje 1s var(--nav-soft); }

/* Billete: la moneda del centro gira como moneda y las esquinas titilan. */
.nav-item:hover [data-icon="billete"] > :nth-child(1) { animation: nav-desliza .9s var(--nav-soft); }
.nav-item:hover [data-icon="billete"] > :nth-child(2) { animation: nav-moneda .9s var(--nav-soft); }
.nav-item:hover [data-icon="billete"] > :nth-child(n+3) { animation: nav-titila .9s linear; }

/* Bitácora: la esquina se dobla y los renglones se escriben uno tras otro. */
.nav-item:hover [data-icon="bitacora"] > :nth-child(2) { transform-origin: 100% 0; animation: nav-dobla .8s var(--nav-soft); }
.nav-item:hover [data-icon="bitacora"] > :nth-child(3) { animation: nav-traza .4s ease-out .15s backwards; }
.nav-item:hover [data-icon="bitacora"] > :nth-child(4) { animation: nav-traza .4s ease-out .35s backwards; }

/* Chat: la burbuja se infla, tiembla como gelatina y se asienta. */
.nav-item:hover [data-icon="burbuja"] { transform-origin: 20% 90%; animation: nav-gelatina .9s var(--nav-soft); }

/* Caja: la pantalla parpadea, el cajón se abre y vuelve. */
.nav-item:hover [data-icon="caja"] > :nth-child(2) { transform-origin: 0 50%; animation: nav-escribe .5s ease-out .1s backwards; }
.nav-item:hover [data-icon="caja"] > :nth-child(1) { animation: nav-pantalla .9s linear; }
.nav-item:hover [data-icon="caja"] > :nth-child(n+3) { animation: nav-cajon .9s var(--nav-spring); }

/* Calendario: las argollas saltan y el día marcado pasa al siguiente. */
.nav-item:hover [data-icon="calendario"] > :nth-child(3) { animation: nav-argolla .5s var(--nav-soft); }
.nav-item:hover [data-icon="calendario"] > :nth-child(4) { animation: nav-argolla .5s var(--nav-soft) .08s; }
.nav-item:hover [data-icon="calendario"] > :nth-child(5) { animation: nav-siguiente-dia 1s var(--nav-soft); }

/* Envíos: el camión se echa atrás, arranca, sale y vuelve a entrar. */
.nav-item:hover [data-icon="camion"] { animation: nav-viaje 1.1s var(--nav-soft); }
.nav-item:hover [data-icon="camion"] > :nth-child(-n+2) { transform-origin: 30% 100%; animation: nav-cabecea 1.1s var(--nav-soft); }
.nav-item:hover [data-icon="camion"] > :nth-child(n+3) { animation: nav-rueda 1.1s var(--nav-soft); }

/* Campos: la tabla se arma, primero la fila y después la columna. */
.nav-item:hover [data-icon="campos"] > :nth-child(2) { transform-origin: 0 50%; animation: nav-escribe .4s ease-out; }
.nav-item:hover [data-icon="campos"] > :nth-child(3) { transform-origin: 50% 0; animation: nav-crece-abajo .4s ease-out .3s backwards; }
.nav-item:hover [data-icon="campos"] > :nth-child(1) { animation: nav-late .8s var(--nav-spring); }

/* Empresa: se prenden las ventanas una por una y la puerta se abre. */
.nav-item:hover [data-icon="empresa"] > :nth-child(3) { animation: nav-ventanas .9s steps(6, end); }
.nav-item:hover [data-icon="empresa"] > :nth-child(2) { transform-origin: 0 50%; animation: nav-puerta .9s var(--nav-soft); }

/* Estrella: gira un pico, crece y se vuelve a trazar. */
.nav-item:hover [data-icon="estrella"] > :nth-child(1) { animation: nav-estrella .9s var(--nav-spring), nav-traza .5s ease-out .4s; }

/* Flechas: cada una sale por su lado y vuelve a entrar por el otro. */
.nav-item:hover [data-icon="flechas"] > :nth-child(-n+2) { animation: nav-vuelta-derecha .8s var(--nav-soft); }
.nav-item:hover [data-icon="flechas"] > :nth-child(n+3) { animation: nav-vuelta-izquierda .8s var(--nav-soft) .1s; }

/* Gente: el carnet se mece del cordón y la cara asiente. */
.nav-item:hover [data-icon="gente"] { transform-origin: 50% 0; animation: nav-cuelga 1s var(--nav-soft); }
.nav-item:hover [data-icon="gente"] > :nth-child(n+3) { animation: nav-asiente 1s var(--nav-soft) .15s; }

/* Analíticas: las barras se vacían y vuelven a crecer, cada una a su turno. */
.nav-item:hover [data-icon="grafica"] > :nth-child(1) { transform-origin: 0 50%; animation: nav-escribe .35s ease-out; }
.nav-item:hover [data-icon="grafica"] > :nth-child(2) { transform-origin: 50% 100%; animation: nav-barra .7s var(--nav-spring) .1s backwards; }
.nav-item:hover [data-icon="grafica"] > :nth-child(3) { transform-origin: 50% 100%; animation: nav-barra .7s var(--nav-spring) .2s backwards; }
.nav-item:hover [data-icon="grafica"] > :nth-child(4) { transform-origin: 50% 100%; animation: nav-barra .7s var(--nav-spring) .3s backwards; }

/* Integraciones: el enchufe sube, las patas entran y el cable se tensa. */
.nav-item:hover [data-icon="integraciones"] > :nth-child(-n+3) { animation: nav-enchufa .9s var(--nav-soft); }
.nav-item:hover [data-icon="integraciones"] > :nth-child(4) { transform-origin: 50% 100%; animation: nav-cable .9s var(--nav-soft); }

/* Cajas (productos, módulos, existencias): saltan, aterrizan y se
   vuelven a trazar las aristas. */
.nav-item:hover [data-icon="productos"],
.nav-item:hover [data-icon="modulos"],
.nav-item:hover [data-icon="inventario"] { animation: nav-salta .9s var(--nav-soft); }
.nav-item:hover [data-icon="productos"] > :nth-child(2),
.nav-item:hover [data-icon="modulos"] > :nth-child(n+2),
.nav-item:hover [data-icon="inventario"] > :nth-child(n+2) { animation: nav-traza .5s ease-out .35s backwards; }

/* Contabilidad: el libro se abre de golpe y el lomo se vuelve a trazar. */
.nav-item:hover [data-icon="libro"] > :nth-child(1) { animation: nav-abre .8s var(--nav-spring); }
.nav-item:hover [data-icon="libro"] > :nth-child(2) { animation: nav-traza .4s ease-out .25s backwards; }

/* Pedidos: la bolsa se levanta por el asa y se balancea. */
.nav-item:hover [data-icon="pedidos"] > :nth-child(2) { transform-origin: 50% 100%; animation: nav-asa .9s var(--nav-soft); }
.nav-item:hover [data-icon="pedidos"] > :nth-child(1) { transform-origin: 50% 0; animation: nav-balancea .9s var(--nav-soft) .1s; }

/* Plantillas: el sobre sube, abre la solapa, la cierra y se sella. */
.nav-item:hover [data-icon="plantillas"] { animation: nav-sube-baja 1s var(--nav-soft); }
.nav-item:hover [data-icon="plantillas"] > :nth-child(2) { transform-origin: 50% 0; animation: nav-solapa 1s var(--nav-soft); }

/* Preparar: la bolsa se sacude y la sonrisa se dibuja. */
.nav-item:hover [data-icon="preparar"] { animation: nav-sacude .7s linear; }
.nav-item:hover [data-icon="preparar"] > :nth-child(3) { animation: nav-traza .5s ease-out .2s backwards; }

/* Producción: entra la materia prima, la máquina gira y sale el producto. */
.nav-item:hover [data-icon="produccion"] > :nth-child(n+2):nth-child(-n+3) { animation: nav-entra 1s var(--nav-soft); }
.nav-item:hover [data-icon="produccion"] > :nth-child(1) { animation: nav-procesa 1s var(--nav-soft); }
.nav-item:hover [data-icon="produccion"] > :nth-child(n+4) { animation: nav-sale 1s var(--nav-soft); }

/* Roles: el escudo se planta y el chulo se traza. */
.nav-item:hover [data-icon="roles"] > :nth-child(1) { animation: nav-late .7s var(--nav-spring); }
.nav-item:hover [data-icon="roles"] > :nth-child(2) { animation: nav-traza .45s ease-out .2s backwards; }

/* Solicitudes: la esquina se dobla y el chulo se traza. */
.nav-item:hover [data-icon="solicitudes"] > :nth-child(2) { transform-origin: 100% 0; animation: nav-dobla .8s var(--nav-soft); }
.nav-item:hover [data-icon="solicitudes"] > :nth-child(3) { animation: nav-traza .45s ease-out .2s backwards; }

/* Suscripciones: las flechas dan la vuelta entera, acelerando y frenando. */
.nav-item:hover [data-icon="suscripciones"] { animation: nav-vuelta 1s var(--nav-soft); }
.nav-item:hover [data-icon="suscripciones"] > :nth-child(2n) { animation: nav-late .5s var(--nav-spring) .5s; }

/* Tareas: la casilla se aprieta y el chulo se vuelve a trazar. */
.nav-item:hover [data-icon="tareas"] > :nth-child(1) { animation: nav-aprieta .6s var(--nav-spring); }
.nav-item:hover [data-icon="tareas"] > :nth-child(2) { animation: nav-traza .45s ease-out .15s backwards; }

/* Pantallas: el televisor se apaga como tubo y se vuelve a prender. */
.nav-item:hover [data-icon="televisor"] > :nth-child(1) { animation: nav-tubo 1s var(--nav-soft); }

/* Terceros: la tarjeta se voltea, la cara aparece y se escriben los datos. */
.nav-item:hover [data-icon="terceros"] { animation: nav-voltea .6s var(--nav-soft); }
.nav-item:hover [data-icon="terceros"] > :nth-child(-n+3):nth-child(n+2) { animation: nav-aparece .4s var(--nav-spring) .35s backwards; }
.nav-item:hover [data-icon="terceros"] > :nth-child(4) { transform-origin: 0 50%; animation: nav-escribe .3s ease-out .45s backwards; }
.nav-item:hover [data-icon="terceros"] > :nth-child(5) { transform-origin: 0 50%; animation: nav-escribe .3s ease-out .6s backwards; }

/* Tienda: el toldo ondea y la puerta se abre. */
.nav-item:hover [data-icon="tienda"] > :nth-child(2) { transform-origin: 50% 0; animation: nav-toldo .9s var(--nav-soft); }
.nav-item:hover [data-icon="tienda"] > :nth-child(3) { transform-origin: 0 50%; animation: nav-puerta .9s var(--nav-soft) .15s; }

/* Turnos: las argollas saltan y los turnos se llenan uno después del otro. */
.nav-item:hover [data-icon="turnos"] > :nth-child(3) { animation: nav-argolla .5s var(--nav-soft); }
.nav-item:hover [data-icon="turnos"] > :nth-child(4) { animation: nav-argolla .5s var(--nav-soft) .08s; }
.nav-item:hover [data-icon="turnos"] > :nth-child(5) { transform-origin: 50% 100%; animation: nav-barra .6s var(--nav-spring) .2s backwards; }
.nav-item:hover [data-icon="turnos"] > :nth-child(6) { transform-origin: 50% 100%; animation: nav-barra .6s var(--nav-spring) .35s backwards; }

/* Ubicaciones: el pin se levanta, gira como moneda, cae y el punto late. */
.nav-item:hover [data-icon="ubicaciones"] { animation: nav-pin 1.1s var(--nav-soft); }
.nav-item:hover [data-icon="ubicaciones"] > :nth-child(2) { animation: nav-late .4s var(--nav-spring) .75s; }

/* Usuarios: el de atrás llega y el de adelante lo saluda con la cabeza. */
.nav-item:hover [data-icon="usuarios"] > :nth-child(3) { animation: nav-llega .7s var(--nav-soft); }
.nav-item:hover [data-icon="usuarios"] > :nth-child(2) { animation: nav-asiente .8s var(--nav-soft) .2s; }
.nav-item:hover [data-icon="usuarios"] > :nth-child(1) { transform-origin: 50% 100%; animation: nav-respira .8s var(--nav-soft) .2s; }

@keyframes nav-traza { from { stroke-dasharray: 100; stroke-dashoffset: 100; } to { stroke-dasharray: 100; stroke-dashoffset: 0; } }
@keyframes nav-ventanas { from { stroke-dasharray: 12; stroke-dashoffset: 12; } to { stroke-dasharray: 12; stroke-dashoffset: 0; } }
@keyframes nav-escribe { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes nav-crece-abajo { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes nav-techo { 0%, 100% { transform: translateY(0); } 35% { transform: translateY(-4px) rotate(-4deg); } 60% { transform: translateY(.6px); } 80% { transform: translateY(-.6px); } }
@keyframes nav-amortigua { 0%, 50%, 100% { transform: scaleY(1); } 62% { transform: scale(1.06, .86); } 80% { transform: scale(.98, 1.03); } }
@keyframes nav-engrana { 0% { transform: rotate(0); } 15% { transform: rotate(-20deg); } 75% { transform: rotate(200deg); } 100% { transform: rotate(180deg); } }
@keyframes nav-eje { 0%, 100% { transform: scale(1); } 15% { transform: scale(.6); } 60% { transform: scale(1.35); } }
@keyframes nav-sacude { 0%, 100% { transform: rotate(0); } 10% { transform: rotate(-9deg); } 20% { transform: rotate(8deg); } 30% { transform: rotate(-6deg); } 40% { transform: rotate(4deg); } 50% { transform: rotate(0); } }
@keyframes nav-destella { 0%, 100% { transform: scale(1); opacity: 1; } 30% { transform: scale(1.25); opacity: .35; } 60% { transform: scale(1.1); opacity: 1; } }
@keyframes nav-arco { 0% { transform: translate(0, 0); } 30% { transform: translate(-3px, 4px) scale(.5); opacity: 0; } 31% { transform: translate(8px, 4px) scale(.5); opacity: 0; } 65% { transform: translate(4px, -2px) scale(1.15); opacity: 1; } 100% { transform: translate(0, 0); } }
@keyframes nav-paralaje { 0%, 100% { transform: translateX(0); } 40% { transform: translateX(-1.5px); } 70% { transform: translateX(1px); } }
@keyframes nav-desliza { 0%, 100% { transform: translateX(0); } 30% { transform: translateX(-1.5px) skewX(-6deg); } 60% { transform: translateX(1px) skewX(4deg); } }
@keyframes nav-moneda { 0%, 100% { transform: scaleX(1); } 25% { transform: scaleX(0); } 50% { transform: scaleX(-1) scale(1.2); } 75% { transform: scaleX(0); } }
@keyframes nav-titila { 0%, 100% { opacity: 1; } 20%, 60% { opacity: 0; } 40%, 80% { opacity: 1; } }
@keyframes nav-dobla { 0%, 100% { transform: scale(1); } 40% { transform: scale(1.35); } 65% { transform: scale(.9); } }
@keyframes nav-gelatina { 0%, 100% { transform: scale(1); } 25% { transform: scale(1.18, .88); } 40% { transform: scale(.9, 1.14); } 55% { transform: scale(1.08, .95); } 70% { transform: scale(.97, 1.03); } }
@keyframes nav-pantalla { 0%, 100% { opacity: 1; } 10% { opacity: .2; } 20% { opacity: 1; } 30% { opacity: .4; } 40% { opacity: 1; } }
@keyframes nav-cajon { 0%, 100% { transform: translateY(0); } 35%, 60% { transform: translateY(3px); } }
@keyframes nav-argolla { 0%, 100% { transform: translateY(0); } 40% { transform: translateY(-2.5px); } 70% { transform: translateY(.5px); } }
@keyframes nav-siguiente-dia { 0%, 100% { transform: translate(0, 0); } 20% { transform: translate(0, 0) scale(.6); } 45%, 65% { transform: translate(6px, 0) scale(1.1); } 85% { transform: translate(0, 0) scale(.9); } }
@keyframes nav-viaje { 0%, 100% { transform: translateX(0); opacity: 1; } 15% { transform: translateX(-1.5px); } 45% { transform: translateX(9px); opacity: 0; } 46% { transform: translateX(-9px); opacity: 0; } 75% { transform: translateX(.8px); opacity: 1; } }
@keyframes nav-cabecea { 0%, 100% { transform: rotate(0); } 15% { transform: rotate(3deg); } 30% { transform: rotate(-4deg); } 75% { transform: rotate(3deg); } 88% { transform: rotate(-1deg); } }
@keyframes nav-rueda { 0%, 100% { transform: translateY(0); } 30% { transform: translateY(-.8px); } 40% { transform: translateY(0); } 78% { transform: translateY(-.8px); } }
@keyframes nav-late { 0%, 100% { transform: scale(1); } 40% { transform: scale(1.2); } 70% { transform: scale(.95); } }
@keyframes nav-puerta { 0%, 100% { transform: scaleX(1); } 35%, 65% { transform: scaleX(.2); } }
@keyframes nav-estrella { 0% { transform: rotate(0) scale(1); } 40% { transform: rotate(-20deg) scale(.8); } 75% { transform: rotate(84deg) scale(1.25); } 100% { transform: rotate(72deg) scale(1); } }
@keyframes nav-vuelta-derecha { 0%, 100% { transform: translateX(0); opacity: 1; } 40% { transform: translateX(6px); opacity: 0; } 41% { transform: translateX(-6px); opacity: 0; } 80% { transform: translateX(.5px); opacity: 1; } }
@keyframes nav-vuelta-izquierda { 0%, 100% { transform: translateX(0); opacity: 1; } 40% { transform: translateX(-6px); opacity: 0; } 41% { transform: translateX(6px); opacity: 0; } 80% { transform: translateX(-.5px); opacity: 1; } }
@keyframes nav-cuelga { 0%, 100% { transform: rotate(0); } 18% { transform: rotate(16deg); } 40% { transform: rotate(-11deg); } 60% { transform: rotate(6deg); } 80% { transform: rotate(-2deg); } }
@keyframes nav-asiente { 0%, 100% { transform: translateY(0); } 25% { transform: translateY(-1.5px); } 45% { transform: translateY(1px); } 65% { transform: translateY(-.8px); } }
@keyframes nav-barra { 0% { transform: scaleY(0); } 70% { transform: scaleY(1.15); } 100% { transform: scaleY(1); } }
@keyframes nav-enchufa { 0%, 100% { transform: translateY(0); } 30% { transform: translateY(1.5px); } 55% { transform: translateY(-3.5px); } 70% { transform: translateY(-2.5px); } }
@keyframes nav-cable { 0%, 100% { transform: scaleY(1); } 30% { transform: scaleY(.6); } 55% { transform: scaleY(1.9); } 70% { transform: scaleY(1.6); } }
@keyframes nav-salta { 0%, 100% { transform: translateY(0) scale(1); } 15% { transform: translateY(0) scale(1.1, .85); } 40% { transform: translateY(-5px) scale(.92, 1.08) rotate(-8deg); } 62% { transform: translateY(0) scale(1.08, .9); } 80% { transform: translateY(0) scale(.98, 1.02); } }
@keyframes nav-abre { 0%, 100% { transform: scaleX(1); } 30% { transform: scaleX(.35); } 65% { transform: scaleX(1.12); } }
@keyframes nav-asa { 0%, 100% { transform: scaleY(1); } 25% { transform: scaleY(1.6); } 50% { transform: scaleY(1.3); } }
@keyframes nav-balancea { 0%, 100% { transform: rotate(0); } 25% { transform: rotate(8deg); } 50% { transform: rotate(-6deg); } 72% { transform: rotate(3deg); } }
@keyframes nav-sube-baja { 0%, 100% { transform: translateY(0); } 30%, 55% { transform: translateY(-2px); } 75% { transform: translateY(.5px) scale(1.05, .95); } }
@keyframes nav-solapa { 0%, 100% { transform: scaleY(1); } 30%, 55% { transform: scaleY(-1); } 72% { transform: scaleY(1.15); } }
@keyframes nav-entra { 0% { transform: translateX(0); opacity: 1; } 35% { transform: translateX(3px); opacity: 0; } 36% { transform: translateX(-4px); opacity: 0; } 70%, 100% { transform: translateX(0); opacity: 1; } }
@keyframes nav-procesa { 0%, 25%, 100% { transform: rotate(0) scale(1); } 55% { transform: rotate(90deg) scale(1.2); } 75% { transform: rotate(90deg) scale(.95); } }
@keyframes nav-sale { 0%, 45% { transform: translateX(0); } 65% { transform: translateX(3px); } 100% { transform: translateX(0); } }
@keyframes nav-vuelta { 0% { transform: rotate(0); } 15% { transform: rotate(-25deg); } 80% { transform: rotate(375deg); } 100% { transform: rotate(360deg); } }
@keyframes nav-aprieta { 0%, 100% { transform: scale(1); } 25% { transform: scale(.82); } 60% { transform: scale(1.1); } }
@keyframes nav-tubo { 0%, 100% { transform: scale(1); opacity: 1; } 25% { transform: scale(1.05, .08); } 40% { transform: scale(.05, .08); opacity: .3; } 55% { transform: scale(.05, .08); opacity: 0; } 70% { transform: scale(1.05, .08); opacity: 1; } 85% { transform: scale(1, 1.05); } }
@keyframes nav-voltea { 0%, 100% { transform: scaleX(1); } 50% { transform: scaleX(-1); } }
@keyframes nav-aparece { 0% { transform: scale(0); } 70% { transform: scale(1.25); } 100% { transform: scale(1); } }
@keyframes nav-toldo { 0%, 100% { transform: scaleY(1) skewX(0); } 25% { transform: scaleY(.7) skewX(8deg); } 50% { transform: scaleY(1.12) skewX(-6deg); } 75% { transform: scaleY(.95) skewX(3deg); } }
@keyframes nav-pin { 0%, 100% { transform: translateY(0) scaleX(1); } 20% { transform: translateY(-5px) scaleX(1); } 45% { transform: translateY(-5px) scaleX(-1); } 60% { transform: translateY(-5px) scaleX(1); } 78% { transform: translateY(0) scale(1.1, .88); } 90% { transform: translateY(-1px) scale(1); } }
@keyframes nav-llega { 0% { transform: translateX(4px); opacity: 0; } 70% { transform: translateX(-.8px); opacity: 1; } 100% { transform: translateX(0); } }
@keyframes nav-respira { 0%, 100% { transform: scaleY(1); } 45% { transform: scaleY(.9); } }
.nav-label { flex: 1; min-width: 0; }
.nav-badge {
    flex-shrink: 0; min-width: 20px; padding: 1px var(--space-075);
    font-size: var(--fs-meta); font-weight: 700; text-align: center;
    color: #fff; background: var(--primary); border-radius: 99px;
}
.nav-item.active .nav-badge { background: rgba(255, 255, 255, .28); }
.nav-badge.tone-warn   { background: var(--warn-text); }
.nav-badge.tone-danger { background: var(--danger-text); }

/* ── El grupo del menú: una entrada con las pantallas que cuelgan de ella ──
   El `<summary>` no se pinta: quien lleva el aspecto es el `.nav-item` de
   adentro, que es EL MISMO enlace que en una entrada sin hijas. Así un grupo
   no puede verse distinto del resto del menú, que es lo que pasa cuando el
   contenedor se pinta aparte. */
/* Sin summary, sin flecha: el padre es un ENLACE PLANO y el acordeón lo
   sirve el servidor — apretar al padre navega, y en su página se pinta SU
   submenú y ninguno más. Decisión del 2026-08-02. */
/* La sangría NO es la del rótulo del padre (39px = relleno 12 + icono 17 +
   separación 10). Alinear la hija bajo el rótulo del padre se lee bien con un
   solo nivel y se rompe con dos: el nieto caería en 78px y a la mitad del
   ancho del menú no queda rótulo que leer. 36px es lo que deja pasar el
   conector —la línea baja por 20px y la punta termina en 33.5—, y deja
   escalones de 12px para lo que viene: 36 → 48 → 60, con 157px de rótulo
   todavía en el tercer nivel. */
.nav-sublist {
    --nav-sub: 36px;
    /* Los dos huecos que el conector tiene que cruzar, escritos UNA vez: el
       aire entre hijas y el que el ítem del padre deja debajo (su
       `margin-bottom`). Con el número suelto en cada regla, mover el aire
       parte la línea en rayitas y nadie relaciona una cosa con la otra. */
    --nav-gap: 1px; --nav-lead: 2px;
    /* El riel del conector cae en el centro del icono del padre: 12 de relleno
       del ítem más la mitad de sus 17px. Movido de ahí, la línea deja de salir
       del icono y pasa a ser una raya al lado del menú. */
    --nav-rail: 20px; --nav-line: 2px;
    list-style: none; margin: 0 0 var(--space-075); padding: 0;
}
/* La hija NO se escribe más chica que el padre. El tamaño no es lo que dice
   que algo cuelga de otra cosa —eso lo dicen la sangría y el conector—; lo
   único que consigue es una lista de nombres que cuesta leer. La sangría vive
   en el enlace y no en la lista para que la pastilla de hover ocupe el ancho
   del menú, como la del padre. */
.nav-subitem {
    /* Flex y no block desde que la hija puede llevar badge (avisos,
       2026-09-10): la ranura se empuja a la derecha con `margin-left: auto`,
       que en un bloque no hace nada. */
    display: flex; align-items: center;
    padding: var(--space-100) var(--space-150) var(--space-100) var(--nav-sub); margin-bottom: var(--nav-gap);
    /* Más claro que --text-light a propósito (pedido de Renzo, 2026-08-02):
       la hija pesa menos que su padre, y el gris del texto normal las dejaba
       compitiendo. */
    color: #9b96af; font-size: var(--fs-body); font-weight: 500;
    border-radius: var(--radius); text-decoration: none;
}
.nav-subitem:hover { background: var(--bg); color: var(--text); text-decoration: none; }
/* La hija activa se marca con COLOR y no con la pastilla del padre: dos
   pastillas moradas seguidas compiten, y la de arriba es la que dice en qué
   módulo estás. */
.nav-subitem.active { color: var(--primary); font-weight: 600; }
.nav-subitem:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-soft); }

/* ── El conector: del icono del padre hasta la hija abierta ────────────────
   La misma flecha de tiendapp. Baja desde el CENTRO DEL ICONO del padre
   (12 de relleno + 17/2 ≈ 20px), recorre las hijas que quedan por encima y
   dobla con una punta sobre la que está abierta. Dice dos cosas que la
   sangría sola no dice: de quién cuelga esta lista y en cuál de sus hijas
   estás parado.

   Sin una línea de JavaScript —tiendapp la marca con un `forEach` que reparte
   clases y mide el codo—: `:has()` pregunta por el hermano siguiente, que es
   exactamente la pregunta que allá se contestaba recorriendo el DOM. Donde
   `:has()` no exista no hay flecha y el menú sigue navegándose igual.

   El codo mide `50%` —el centro de la fila— en vez de un alto fijo: así no hay
   que reescribirlo cuando cambie el relleno del enlace. Le SUMA medio trazo
   porque con `border-box` el borde de abajo se dibuja DENTRO del alto, así que
   su centro cae en `alto - 1`: sin la suma la línea entra dos píxeles por
   encima de la punta y el doblez se ve quebrado. Lo mismo vale para la punta,
   que se centra sola en `50%` con su `translateY(-50%)`. El día que haya un
   tercer nivel, el `li` de en medio deja de ser una fila y esa regla necesita
   su propio alto. */
.nav-sublist > li { position: relative; }
/* El tramo: las hijas por encima de la abierta. Baja `--nav-gap` de más para
   entrar en el `li` siguiente; si termina en su propio borde, la línea sale
   punteada. */
.nav-sublist > li:has(~ li > .nav-subitem.active)::before {
    content: ''; position: absolute; left: var(--nav-rail); top: 0;
    bottom: calc(var(--nav-gap) * -1);
    width: var(--nav-line); background: var(--primary);
}
/* El codo y su punta, sobre la hija abierta. */
.nav-sublist > li:has(> .nav-subitem.active)::before {
    content: ''; position: absolute; left: var(--nav-rail); top: 0;
    width: 12px; height: calc(50% + var(--nav-line) / 2);
    border-left: var(--nav-line) solid var(--primary);
    border-bottom: var(--nav-line) solid var(--primary);
    border-bottom-left-radius: 4px;
}
.nav-sublist > li:has(> .nav-subitem.active)::after {
    content: ''; position: absolute; top: 50%;
    left: calc(var(--nav-rail) + 5px); width: 7px; height: 7px;
    border-top: var(--nav-line) solid var(--primary);
    border-right: var(--nav-line) solid var(--primary);
    transform: translateY(-50%) rotate(45deg);
}
/* La primera hija arranca la línea DENTRO de la pastilla del padre. Naciendo
   en el borde de la lista queda flotando: se lee como una raya al lado del
   menú y no como algo que sale del icono. */
.nav-sublist > li:first-child:has(~ li > .nav-subitem.active)::before {
    top: calc(var(--nav-lead) * -1);
}
.nav-sublist > li:first-child:has(> .nav-subitem.active)::before {
    top: calc(var(--nav-lead) * -1);
    height: calc(50% + var(--nav-lead) + var(--nav-line) / 2);
}
/* El conector va ENCIMA del gris de la hija. Al cargar la página entera la
   cascada de `menu-entra` deja cada enlace con su animación terminada
   (`both`), y eso lo vuelve una capa propia que se pinta después del
   `::before` del `li` y le corta la línea; navegando sin recarga no pasaba,
   y por eso solo se veía en algunos menús (Santiago, 2026-11-03, en
   Compras › Recepciones). */
.nav-sublist > li::before, .nav-sublist > li::after { pointer-events: none; z-index: 1; }

.content {
    flex: 1; min-width: 0;
    padding: var(--space-300) var(--content-pad);
    background: var(--bg);
    border-radius: var(--radius-shell) var(--radius-shell) 0 0;
    box-shadow: var(--shadow-shell);
    /* El recorte que antes hacía `.shell`: los orbes de la portada van de
       borde a borde y sin esto se salen de la tarjeta. `clip` y no `hidden`
       para no volverla contenedor de desplazamiento — los `sticky` de
       adentro se siguen pegando a la ventana. */
    overflow: clip;
    /* Contenedor de consulta: lo que vive adentro puede responder al ANCHO
       DEL CONTENIDO y no al de la ventana. Con el panel lateral abierto el
       contenido pierde 380px sin que la ventana cambie, y un `@media` no
       se entera. */
    container-type: inline-size; container-name: content;
}

/* De borde a borde: la pantalla que lo pide (`{% block content_attrs %}
   data-borde-a-borde`) se queda sin el relleno de la tarjeta y pinta hasta
   sus orillas; las esquinas las sigue recortando `overflow: clip`. Nació en
   el chat del equipo (Renzo, 2026-10-08: «aprovechar al máximo esa área»).

   SIN FONDO, y no por gusto: lo de adentro tapa la tarjeta entera, y con el
   fondo claro puesto, el recorte redondo suavizaba la esquina de la lista
   oscura contra ese claro — quedaba un filo blanco en la punta (Renzo, el
   mismo día). Sin fondo, la esquina se suaviza contra el shell, que es lo
   que de verdad está detrás. */
.content[data-borde-a-borde] { padding: 0; background: transparent; }

/* ── En escritorio se desplaza la TARJETA, no la página ─────────────────────
   El shell mide lo que la ventana bajo la topbar y no se mueve: las dos
   tarjetas se quedan quietas con sus cuatro puntas a la vista, y lo que se
   desplaza es el interior del contenido. Antes bajaba la tarjeta entera y
   las esquinas de arriba se perdían bajo la topbar.

   Solo desde 768px. En el celular el desplazamiento sigue siendo el de la
   página: uno interno le quita al navegador la barra que se esconde y el
   gesto de recargar.

   Lo que antes se pegaba a la ventana con `top: var(--topbar)` ahora se
   pega al techo de la tarjeta, así que su tope baja a cero acá.

   `screen`: el papel no tiene ventana, y con el alto fijo la hoja impresa
   saldría cortada en la primera página. */
@media screen and (min-width: 768px) {
    .shell {
        height: calc(100dvh - var(--topbar));
        min-height: 0;
        padding-bottom: var(--shell-gap);
    }
    .sidebar, .menu-lateral, .side-panel, .content {
        position: static; height: auto;
        border-radius: var(--radius-shell);
    }
    .content {
        overflow: hidden auto;
        overscroll-behavior: contain;
        scrollbar-width: thin;
        scrollbar-color: var(--border-strong) transparent;
    }
    /* La banda es una PÍLDORA de vidrio que flota arriba (Renzo,
       2026-10-04, sobre su referente del buscador de vidrio): pegada, a 12px
       del techo y de los lados de la tarjeta, y la pantalla corre por debajo
       y se ve a través. Antes (2026-09-29) era el techo de la tarjeta, de
       borde a borde y sin radio. El vidrio, el canto y la tinta los pone la
       regla de la banda; acá solo dónde va.

       Los márgenes negativos deshacen el relleno de `.content` (24px arriba,
       `--content-pad` a los lados) hasta dejar esos 12px; el `top` negativo,
       lo mismo para el pegado, que se mide desde dentro de ese relleno.

       Solo aquí, donde la tarjeta es la que se desplaza; en el celular una
       banda pegada taparía media pantalla.

       `.content .page-banner` y no suelta: este bloque va ANTES que la regla
       del banner en la hoja, y a igual peso la de abajo ganaría por orden. */
    .content .page-banner {
        position: sticky; top: -12px; z-index: 20;
        margin: calc(-1 * var(--space-150)) calc(var(--space-150) - var(--content-pad)) var(--space-250);
    }
    /* Lo que salta a un ancla cae debajo de la banda, no detrás. */
    .content [id] { scroll-margin-top: 80px; }
    .content:has(:target) > .messages { top: var(--space-100); }
}

/* Colapsado: riel de iconos. La etiqueta se va, el icono queda centrado. */
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar { width: var(--rail); }
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .nav-label,
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .nav-badge,
/* El submenú se va con el rótulo: colapsado el sidebar mide un icono, y una
   lista de nombres debajo de un icono no cabe ni se entiende. */
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .nav-sublist,
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .sidebar-label,
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .sidebar-label-row { display: none; }
body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .nav-item { justify-content: center; padding: var(--space-100) 0; }
/* La RANURA del contador también: con la pastilla escondida quedaba vacía
   pero viva, y el `gap` del ítem le hacía sitio y corría el ícono 4px a la
   izquierda en cada ítem con contador (POS, Pedidos…). Desde 768px: debajo
   el menú es el cajón, que muestra los contadores aunque el riel no. */
@media (min-width: 768px) {
    body.sidebar-collapsed:not(.sidebar-asoma) .sidebar .badge-slot { display: none; }
}

/* El riel que se ASOMA (Renzo, 2026-10-31): el menú es siempre el riel, y
   al pasar el ratón por encima se abre a su ancho entero y la tarjeta se
   corre para hacerle sitio —igual que cuando se abre el panel del agente,
   con su misma curva—. La clase la pone y la quita `sidebar.js`: entra al
   riel, abre; entra a la tarjeta, cierra. */
.sidebar { transition: width .22s ease; }
/* Mientras crece, el menú pasa por anchos donde «Tienda en línea» no cabe:
   partido en dos renglones, cada fila de abajo saltaba durante la
   animación. En un renglón y recortado, el texto aparece a medida que el
   menú se abre. */
.sidebar .nav-label, .sidebar .nav-subitem, .sidebar .sidebar-label { white-space: nowrap; overflow: hidden; }

/* La tarjeta de segundo nivel también se asoma (Renzo, 2026-11-01: «pasa lo
   mismo con el segundo nivel de menú»): con el menú suelto queda en sus
   íconos, al pasar el ratón se abre empujando el contenido y al entrar a la
   tarjeta de contenido se cierra; sobre el menú se abre con él. La chincheta
   la fija abierta junto con el menú. La clase `lateral-asoma` la pone `sidebar.js`. Solo la del shell
   (`.shell > .menu-lateral`): la muestra de la Guía va abierta, y en el
   teléfono la tarjeta es la lista a lo ancho de su puerta. */
@media (min-width: 768px) {
    .shell > .menu-lateral { transition: width .22s ease; }
    .shell > .menu-lateral .nav-label,
    .shell > .menu-lateral .sidebar-label { white-space: nowrap; overflow: hidden; }
    body.sidebar-collapsed:not(.lateral-asoma) .shell > .menu-lateral { width: var(--rail); }
    body.sidebar-collapsed:not(.lateral-asoma) .shell > .menu-lateral .nav-label,
    body.sidebar-collapsed:not(.lateral-asoma) .shell > .menu-lateral .badge-slot,
    body.sidebar-collapsed:not(.lateral-asoma) .shell > .menu-lateral .sidebar-label { display: none; }
    body.sidebar-collapsed:not(.lateral-asoma) .shell > .menu-lateral .nav-item { justify-content: center; padding: var(--space-100) 0; }
}

/* El cajón de móvil: un checkbox, sin una línea de JS.
   El checkbox ES el botón —se pinta como la hamburguesa— para que se pueda
   accionar con Espacio. Una <label> recibe foco y no se activa con teclado. */
.drawer-toggle, .drawer-overlay { display: none; }
.menu-buscar, .menu-cierre { display: none; }

/* LA HAMBURGUESA (Renzo, 2026-10-01: «mucho más lindo, que se anime cuando
   pase al estado cerrar, sin fondo ese botón», y después «al principio se ve
   como las barras de diferente tamaño, no me gusta»). Tres rayas IGUALES de
   20px y nada de caja: ni fondo al pasar por encima ni el recuadro que
   Chrome de Android pinta al tocar. 44px de área para el dedo, corrida 12px
   a la izquierda para que las rayas arranquen donde arranca el relleno de la
   barra. Sin efecto de `:hover`: en un teléfono el hover se queda pegado
   después del toque y estropeaba la animación de cierre.

   Un `<input>` no tiene hijos y solo trae dos pseudoelementos, así que la
   raya del MEDIO es el fondo del propio control —una píldora en SVG, con las
   mismas puntas redondas que las otras dos— y las de afuera son `::before` y
   `::after`.

   Al abrir: la del medio se recoge hacia su centro, las de afuera se juntan
   en el medio y DESPUÉS giran hasta cruzarse, con una curva que se pasa y
   vuelve —ese rebote es el gesto—. Al cerrar, al revés: primero se
   descruzan y después se separan. El orden lo ponen los `transition-delay`
   de cada estado: la transición que corre es la del estado AL QUE se llega. */
.drawer-toggle {
    appearance: none; -webkit-appearance: none;
    -webkit-tap-highlight-color: transparent;
    margin: 0; padding: 0; font: inherit;
    position: fixed; z-index: 101;
    top: calc(env(safe-area-inset-top) + (var(--topbar-franja) - 44px) / 2);
    left: calc(clamp(14px, 3vw, 32px) - 12px);
    width: 44px; height: 44px;
    color: #fff; border: 0; border-radius: 12px; cursor: pointer;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 2' preserveAspectRatio='none'%3E%3Crect width='20' height='2' rx='1' fill='%23fff'/%3E%3C/svg%3E")
                no-repeat 50% 50% / 20px 2px;
    transition: background-size .2s ease .18s;
}
.drawer-toggle::before, .drawer-toggle::after {
    content: ''; position: absolute; left: 12px;
    width: 20px; height: 2px; border-radius: 1px; background: currentColor;
    transition: transform .22s ease, top .18s ease .2s;
}
.drawer-toggle::before { top: 15px; }
.drawer-toggle::after { top: 27px; }
/* Hacia adentro: a 390px la caja está a 2px del borde y un aro hacia afuera
   se cortaba por la izquierda. */
.drawer-toggle:focus-visible { outline: 2px solid #fff; outline-offset: -2px; }
.drawer-toggle:checked { background-size: 0 2px; transition: background-size .14s ease; }
.drawer-toggle:checked::before, .drawer-toggle:checked::after {
    top: 21px;
    transition: top .16s ease, transform .34s cubic-bezier(.34, 1.7, .5, 1) .16s;
}
.drawer-toggle:checked::before { transform: rotate(45deg); }
.drawer-toggle:checked::after { transform: rotate(-45deg); }

/* ── El escritorio ─────────────────────────────────────────────────────────
   Portado de tiendapp (style.css:1976-2135). Los orbes viven DENTRO del
   escenario, con la máscara radial que los desvanece antes de que el
   contenedor los recorte — sin ella el difuminado se corta en seco contra
   el borde. No son fondo de la aplicación: eso es lo que hace `base.html`
   en las pantallas de autenticación, y ahí se queda.
   ------------------------------------------------------------------------ */

.home-stage {
    /* 62/46 de tiendapp: la respiración del escenario es parte de la cara. */
    position: relative; padding: var(--space-800) 0 var(--space-600); text-align: center;
    /* SIN recorte propio (pedido de Renzo, 2026-08-04): el `clip` de acá
       cortaba los orbes en seco contra los bordes de la columna — se veía
       la costura en los laterales. Quien impide el scroll lateral del
       `body` es el `overflow: clip` que `.shell` ya tiene, así que los
       orbes sangran hasta el borde de la aplicación y la máscara los
       desvanece antes de que ninguna costura se vea. */
}

/* De borde a borde de la aplicación: `inset` a cero en X y el padding del
   contenido cancelado con margen negativo, así el lienzo llega hasta donde
   llega la pantalla en vez de terminar 28px antes (Renzo, 2026-08-04 — se
   veía la costura en los laterales). Y el radio horizontal de la máscara es
   50% y no 58% JUSTAMENTE por eso: con 58% el desvanecido todavía valía un
   20% de opacidad al llegar al borde, y ahí es donde se cortaba en seco. */
.home-orbs {
    position: absolute; inset: -60px 0 -180px; z-index: 0;
    margin-inline: calc(var(--content-pad) * -1);
    pointer-events: none; overflow: hidden;
    -webkit-mask-image: radial-gradient(50% 52% at 50% 38%, #000 30%, transparent 100%);
    mask-image: radial-gradient(50% 52% at 50% 38%, #000 30%, transparent 100%);
}
.home-orb {
    position: absolute; border-radius: 50%;
    filter: blur(58px); mix-blend-mode: multiply; will-change: transform;
}
.home-orb.orb-lavender {
    width: 30vmax; height: 30vmax; top: 46%; left: 24%; opacity: .26;
    animation: orb-drift-a 16s ease-in-out infinite;
}
.home-orb.orb-cyan {
    width: 26vmax; height: 26vmax; top: 6%; right: 4%; opacity: .42;
    animation: orb-drift-b 19s ease-in-out infinite;
}
.home-orb.orb-yellow {
    width: 20vmax; height: 20vmax; top: -12%; left: 6%; opacity: .36;
    animation: orb-drift-c 22s ease-in-out infinite;
}
.home-orb.orb-violet {
    width: 24vmax; height: 24vmax; top: 26%; right: 26%; bottom: auto;
    opacity: .32; animation: orb-drift-d 17s ease-in-out infinite;
}
.home-orb.orb-glow {
    width: 34vmax; height: 34vmax; top: 2%; left: 50%;
    transform: translateX(-50%); opacity: .30;
    filter: blur(70px); mix-blend-mode: normal;
    animation: orb-pulse 9s ease-in-out infinite;
}

/* ── La tira de métricas, portada de tiendapp ────────────────────────────────
   Las casillas las define la vista (HOME_SLOTS); los números los publican los
   módulos por `metrics_for()`. Los tamaños vienen de la escala de la casa y
   no de los px de tiendapp: la tira es una pantalla más, no una embajada. */
/* El radio de una TARJETA y no una píldora (pedido de Renzo, 2026-08-12). Con
   `999px` la tira era la única pieza del ERP con esa forma: al lado de las
   tarjetas cuadradas de abajo se leía como un control —una barra de búsqueda,
   un conmutador— y no como el bloque de datos que es. */
.home-metrics {
    display: flex; align-items: stretch; gap: 0; flex-wrap: wrap;
    margin-bottom: var(--space-250); padding: var(--space-075);
    /* De VIDRIO (Renzo, 2026-10-04), el mismo de la banda —sus tokens
       `--bar-glass-*` y su canto—: detrás van los orbes de la portada, y la
       tira blanca los tapaba. Sigue con forma de tarjeta, solo que más
       redonda: el canto de luz en un radio de 12px se leía como un marco. */
    position: relative; z-index: 5;
}
/* EL VIDRIO de las tarjetas (2026-10-09): el material y el canto de la
   banda, con su esmerilado. Era la misma receta copiada en la tira de Inicio
   y en las tarjetas de Analíticas; se juntó acá el 2026-10-09 (la barra de
   búsqueda del chat la probó ese día y volvió a la de siempre). Una pieza
   nueva usa la clase `.vidrio`; esas dos siguen por su selector, con el
   mismo resultado. Necesita algo detrás que dejar pasar:
   `ui/_orbes_pantalla.html`. La sombra es la de la banda desde el
   2026-10-11 (Renzo, en el inicio): suave alrededor y sin el canto. La
   píldora de período de Analíticas entró ese día: repetía la receta. El
   radio por `--vidrio-radio`, para que quien lo quiera distinto cambie la
   variable y no vuelva a escribir la propiedad. */
.vidrio,
.home-metrics,
.analytics-period,
.analytics-grid-item > .block {
    background: linear-gradient(180deg, var(--bar-glass-top), var(--bar-glass-bottom));
    border: 1px solid var(--bar-glass-edge); border-radius: var(--vidrio-radio, 22px);
    box-shadow: var(--bar-glass-shadow);
    -webkit-backdrop-filter: blur(14px) saturate(170%);
    backdrop-filter: blur(14px) saturate(170%);
}
/* Sin esmerilado no hay vidrio: el fondo se vuelve sólido. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
    .vidrio, .home-metrics, .analytics-period, .analytics-grid-item > .block { background: var(--card-bg); }
}
.home-metrics-head {
    display: flex; flex-direction: column; justify-content: center; gap: var(--space-025);
    padding: var(--space-075) var(--space-200) var(--space-075) var(--space-150);
}
.home-metrics-scope {
    font-size: var(--fs-badge); font-weight: 700; letter-spacing: .08em;
    text-transform: uppercase; color: var(--text-light); opacity: .75;
}
/* ── El selector de período (`ui/_period.html`) ─────────────────────────────
   Dos disparadores, uno por pregunta —qué fechas y contra qué— y cada uno
   abre su panel. Son `<details class="menu">` como cualquier menú de la casa:
   `menu.js` los cierra al clic de afuera y con Esc.

   El disparador va SIN caja (pedido de Renzo, 2026-08-03, sobre el select de
   antes): dentro de la píldora de la tira, un borde dentro de otro borde era
   una caja dentro de una caja. El fondo aparece al pasar y mientras está
   abierto, que es cuando hace falta saber qué se tocó. */
.period { display: flex; align-items: center; gap: var(--space-025); flex-wrap: wrap; }
.period-menu { position: relative; }
.period-trigger {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-050) var(--space-100); border-radius: var(--radius-sm);
    list-style: none; cursor: pointer; user-select: none; color: var(--text);
}
.period-trigger::-webkit-details-marker { display: none; }
.period-trigger:hover,
.period-menu[open] > .period-trigger { background: var(--primary-soft); }
.period-trigger:focus-visible {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
.period-trigger-glyph { width: 18px; height: 18px; flex: none; color: var(--primary); }
.period-compare .period-trigger-glyph { color: var(--text-light); }
.period-trigger-text {
    display: flex; flex-direction: column; min-width: 0; line-height: 1.25;
}
.period-trigger-label { font-size: var(--fs-body); font-weight: 600; white-space: nowrap; }
.period-trigger-dates { font-size: var(--fs-small); color: var(--text-light); white-space: nowrap; }
.period-trigger-chevron {
    width: 15px; height: 15px; flex: none; color: var(--text-light);
    transition: transform .16s;
}
.period-menu[open] .period-trigger-chevron { transform: rotate(180deg); }
/* El panel abre hacia la DERECHA de su disparador —el selector vive a la
   izquierda de la tira y del tablero—, al revés del menú de tres puntos.
   `.period` delante para ganarle a `.menu-pop`, que está más abajo. */
.period .menu-pop {
    left: 0; right: auto; white-space: normal; max-width: calc(100vw - 32px);
}
/* `max-content`: un absoluto se encoge al ancho de su bloque contenedor —el
   `<details>` del disparador, 230px— y el calendario de dos meses quedaba
   desbordando el panel por la derecha. */
.period .period-pop { display: flex; width: max-content; padding: 0; }
.period .period-compare-pop { min-width: 260px; }
.period-presets {
    display: flex; flex-direction: column; gap: 1px;
    margin: 0; padding: var(--space-100); border: 0; border-right: 1px solid var(--border);
    min-width: 200px;
    /* El alto lo pone `period-picker.js` en el panel según el espacio que
       queda en la ventana; cada columna lo hereda y se desplaza por dentro
       en vez de cortarse contra el borde. */
    max-height: inherit; overflow-y: auto;
}
/* Cada opción es una fila con su radio ESCONDIDO adentro: viaja en el GET
   como cualquier campo, y la fila marcada se pinta con `:has`. */
.period-option {
    position: relative;
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-150);
    padding: var(--space-100) var(--space-100); border-radius: var(--radius-sm);
    font-size: var(--fs-body); white-space: nowrap; cursor: pointer;
}
.period-option:hover { background: var(--bg); }
.period-option:has(input:checked) {
    background: var(--primary-soft); color: var(--primary-dark); font-weight: 600;
}
.period-option:has(input:focus-visible) {
    outline: 2px solid var(--primary); outline-offset: -2px;
}
/* La marca de la elegida, dibujada: dos bordes de una caja girada. */
.period-option:has(input:checked)::after {
    content: ""; flex: none; width: 5px; height: 10px; margin: calc(-1 * var(--space-050)) var(--space-050) 0 0;
    border: solid currentColor; border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
.period-sep { height: 1px; margin: var(--space-050) var(--space-075); background: var(--border); }
.period-body {
    display: flex; flex-direction: column; gap: var(--space-150);
    padding: var(--space-200) var(--space-200) 0; max-height: inherit; overflow-y: auto;
}
.period-fields { display: flex; align-items: center; gap: var(--space-100); }
.period-field { flex: 1 1 0; min-width: 0; }
.period-field input { width: 100%; }
.period-field input[aria-invalid="true"] { border-color: var(--danger-vivid); }
.period-arrow { width: 16px; height: 16px; flex: none; color: var(--text-light); }
/* Alto reservado: el calendario se arma al abrir el panel por primera vez,
   y sin piso el panel saltaría de tamaño debajo del cursor. */
.period-calendar { display: flex; justify-content: center; min-height: 290px; }
/* La casilla a la que flatpickr se engancha: en línea no muestra nada, pero
   el vendor la vuelve de texto y quedaba pintada al lado de los meses. */
.period-calendar > input { display: none; }
/* El pie va ABAJO del panel aunque el calendario sea más bajo que la
   lista de presets, y PEGADO: con el panel recortado por la ventana, lo que
   se desplaza es el calendario y no los botones (Renzo, 2026-09-15: con el
   selector abajo en la pantalla, «Aplicar» quedaba fuera de vista). */
.period-foot {
    position: sticky; bottom: 0;
    display: flex; justify-content: flex-end; gap: var(--space-100);
    margin-top: auto; padding: var(--space-150) 0;
    border-top: 1px solid var(--border); background: var(--card-bg);
}
/* «Aplicar» sin nada nuevo que aplicar se ve apagado: la casa todavía no
   tiene un `.btn:disabled` general, y un morado lleno que no hace nada se
   lee como un botón roto. */
.period-foot .btn:disabled { opacity: .45; cursor: not-allowed; }
/* Fuera del teléfono no hay pasos: el calendario va al lado de los atajos y
   «Atajos» no tiene a dónde volver. Tampoco la cabeza rica ni la baldosa y
   las fechas de cada atajo: son de la hoja (el escritorio no cambia). */
.period-volver { display: none; }
.hoja-cabeza, .period-option-glifo, .period-option-pista { display: none; }
/* En el teléfono el panel es la hoja de todo menú (ver `.menu-pop` en el
   teléfono, más abajo), en DOS PASOS (spec hoja del celular, 2026-10-08):
   los atajos, y «Personalizado» con su calendario de un mes. */
@media (max-width: 640px) {
    /* El relleno de abajo de la hoja, otra vez: `.period .period-pop
       { padding: 0 }` le gana al de `.menu-pop`, y «Aplicar» quedaba encima
       de la barra de inicio del iPhone (revisión final, 2026-10-08). */
    .period .period-pop {
        flex-direction: column; width: auto;
        padding-bottom: calc(var(--space-100) + env(safe-area-inset-bottom));
    }
    .period-body { max-height: none; overflow: visible; padding-top: var(--space-050); }
    /* Los atajos, en DOS columnas de botones grandes (Renzo, 2026-10-08:
       «los items se ven como muy chiquitos»): todos a la vista desplazando
       la hoja, ninguno escondido en una fila de lado. */
    .period-presets {
        display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: var(--space-100); min-width: 0; max-height: none; overflow: visible;
        padding: var(--space-050) var(--space-150) var(--space-200); border: 0;
    }
    /* Cada atajo, como el referente de Renzo (2026-10-08): su baldosa con
       ícono, el nombre y, debajo, sus fechas; el elegido en morado con un
       círculo con ✓. */
    /* Medido a 390px: con letra de 15px y baldosa de 36 el nombre se partía
       («Últimos 30 / días»); así cabe en una tarjeta de media pantalla. */
    .period-presets .period-option {
        min-height: 58px; padding: var(--space-100); gap: var(--space-100); letter-spacing: -.01em;
        justify-content: flex-start;
        border: 1px solid var(--border); border-radius: 14px;
        font-size: calc(var(--fs-body) * var(--hoja)); white-space: normal;
    }
    .period-option:has(input:checked) {
        border-color: color-mix(in srgb, var(--primary) 35%, var(--border));
    }
    .period-option-glifo {
        display: grid; place-items: center; position: relative; flex: none;
        width: 34px; height: 34px; border-radius: 10px;
        background: var(--primary-soft); color: var(--primary);
    }
    .period-option-glifo svg { width: 24px; height: 24px; }
    /* El número de «los últimos N días», escrito dentro del calendario: más
       chico que el paso más chico de la escala y apretado, o «365» se salía
       de la caja (Renzo, 2026-10-08: «un poquitín más pequeños para que se
       puedan leer mejor»). Centrado en el cuerpo del calendario, debajo de
       su raya. */
    .period-option-numero {
        position: absolute; left: 0; right: 0; top: 17px;
        text-align: center; line-height: 1; letter-spacing: -.04em;
        font-size: calc(var(--fs-badge) * .78); font-weight: 700;
    }
    .period-option-texto {
        display: flex; flex-direction: column; gap: var(--space-025); flex: 1; min-width: 0;
    }
    .period-option-pista {
        display: block; font-weight: 400; color: var(--text-light);
        font-size: var(--fs-small);
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .period-presets .period-option:has(input:checked)::after {
        content: ""; flex: none; width: 22px; height: 22px; margin: 0;
        border: 0; border-radius: 50%; transform: none;
        background: var(--primary) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center / 14px no-repeat;
    }
    /* Los separadores de grupo: una raya fina a todo el ancho. */
    .period-sep { grid-column: 1 / -1; height: 1px; margin: var(--space-050) 0; background: var(--border); }
    /* «Personalizado», el último: a todo el ancho y con su chevrón. */
    .period-presets .period-option:last-child { grid-column: 1 / -1; }
    .period-presets .period-option:last-child:not(:has(input:checked))::after {
        content: ""; flex: none; width: 7px; height: 7px; margin-right: var(--space-050);
        border: solid currentColor; border-width: 2px 2px 0 0;
        transform: rotate(45deg); opacity: .5;
    }
    /* Los PASOS (`data-paso` en el `<details>`, lo cambia
       `period-picker.js`): o los atajos, o el calendario. */
    .period-menu[data-paso="atajos"] .period-body { display: none; }
    .period-menu[data-paso="personalizado"] .period-presets { display: none; }
    .period-volver {
        display: flex; align-items: center; gap: var(--space-075); align-self: flex-start;
        min-height: 44px; padding: 0 var(--space-050); border: 0; background: none;
        color: var(--primary-dark); font: inherit;
        font-size: calc(var(--fs-body) * var(--hoja)); font-weight: 600; cursor: pointer;
    }
    .period-volver svg { width: 20px; height: 20px; }
    .period-field input { min-height: 44px; font-size: calc(var(--fs-body) * var(--hoja)); }
    /* El pie: SOLO «Aplicar», grande y a todo el ancho, con el rango que
       aplica. «Cancelar» lo hacen «Atajos», el velo y el arrastre. */
    .period-foot { padding: var(--space-150) 0 0; border-top: 0; }
    #period-cancel { display: none; }
    #period-apply { flex: 1; min-height: 50px; font-size: calc(var(--fs-body) * var(--hoja)); }
    .period-compare-pop .period-option {
        min-height: 52px; padding: 0 var(--space-200); border-radius: 0;
        border-top: 1px solid var(--border);
        font-size: calc(var(--fs-body) * var(--hoja));
    }
}
/* En un teléfono angosto (375px) «Últimos 12 meses» se partía en dos
   renglones: el nombre del atajo baja un poco. */
@media (max-width: 380px) {
    .period-presets .period-option { font-size: calc(var(--fs-body) * var(--hoja)); }
}
/* La división entre casillas es una RAYA RECTA, dibujada aparte.

   Antes el `border-left` y un `border-radius: 14px` vivían en el mismo
   elemento, y un borde se curva con el radio de su caja: la línea salía
   arqueada en las dos puntas y las casillas parecían pegadas con una costura
   redonda. Los dos eran correctos por separado y juntos no.

   Separados, cada uno hace lo suyo: el radio redondea el FONDO del hover, y la
   raya la pinta un `::before` que no sabe nada de radios. Va sangrada 7px
   arriba y abajo para que no toque el relleno de la tira — una raya que llega
   al filo se lee como que la tarjeta está partida en dos. */
.home-metric {
    position: relative;
    display: flex; flex-direction: column; justify-content: center; gap: var(--space-025);
    padding: var(--space-075) var(--space-150); border-radius: 16px;
    transition: background .15s ease;
}
.home-metric::before {
    content: ''; position: absolute; left: 0; top: 7px; bottom: 7px;
    width: 1px; background: color-mix(in srgb, var(--text) 12%, transparent);
}
.home-metric:hover { background: color-mix(in srgb, var(--card-bg) 55%, transparent); }
.period-trigger-dates--corta { display: none; }
.home-metric-label {
    font-size: var(--fs-badge); font-weight: 600; color: var(--text-light);
    text-transform: uppercase; letter-spacing: .07em; white-space: nowrap;
}
.home-metric-row { display: flex; align-items: center; gap: var(--space-100); }
.home-metric-value {
    font-size: var(--fs-stat); font-weight: 700; color: var(--text);
    letter-spacing: -.01em; line-height: 1.1;
    font-variant-numeric: tabular-nums;
}
/* «En vivo» va al FINAL de la fila, pegado a la derecha, sin importar dónde
   lo dejó el Escritorio: «Gasto en IA» llegó después de él (`analytics/0004`
   la sembró de última) y quedaba a su derecha. */
.home-metric-live {
    order: 1; margin-left: auto; padding-right: var(--space-200); text-align: right;
}
.home-metric-live .home-metric-row { justify-content: flex-end; }
.home-metric-live:hover { background: transparent; }

.home-delta {
    display: inline-flex; align-items: center; gap: var(--space-025);
    font-size: var(--fs-badge); font-weight: 700; line-height: 1;
    padding: var(--space-025) var(--space-075) var(--space-025) var(--space-050); border-radius: 999px;
    font-variant-numeric: tabular-nums;
}
.home-delta svg { width: 11px; height: 11px; }
.home-delta.up { color: var(--ok-text); background: var(--ok-bg); }
.home-delta.down { color: var(--danger-text); background: var(--danger-bg); }

/* El punto que dice «esto se está moviendo». Solo aparece cuando hay dato:
   un pulso junto a un guion prometería una medición que no existe. */
.live-dot {
    position: relative; display: inline-block;
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--ok-text);
}
.live-dot::after {
    content: ''; position: absolute; inset: -4px;
    border-radius: 50%; background: var(--ok-text); opacity: .28;
    animation: live-ping 2s cubic-bezier(0, 0, .2, 1) infinite;
}
@keyframes live-ping {
    0% { transform: scale(.6); opacity: .5; }
    70%, 100% { transform: scale(1.5); opacity: 0; }
}

.home-greeting-title {
    position: relative; z-index: 1;
    margin: 0 0 var(--space-100);
    /* El paso `hero` son los 34px de tiendapp: al ladear las capturas, los
       30px de `--fs-number` se notaban. */
    font-size: var(--fs-hero); font-weight: 700;
    letter-spacing: -.025em; line-height: 1.2;
    animation: home-rise .5s ease both;
}

/* La frase de gratitud, portada con sus diez frases reales. Un saludo.
   `home-rise` es la entrada escalonada de tiendapp: la frase primero, los
   chips después. El bloque universal de movimiento reducido la apaga. */
.home-gratitude {
    position: relative; z-index: 1;
    margin: 0 0 var(--space-400);
    font-size: var(--fs-section); color: var(--text-light);
    animation: home-rise .5s ease .06s both;
}
@keyframes home-rise {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: none; }
}

.home-search {
    position: relative; z-index: 2; max-width: 620px; margin: 0 auto;
    /* Tercer paso de la entrada escalonada: título, frase, buscador,
       chips, feed. */
    animation: home-rise .5s ease .12s both;
}
.home-search-icon {
    position: absolute; left: 20px; top: 27px; transform: translateY(-50%);
    width: 18px; height: 18px; color: var(--text-light);
    transition: color .18s ease; pointer-events: none;
}
/* Con `[type="search"]` escrito: la regla genérica de campos —más abajo,
   (0,1,1)— empataba con `.home-search input` y ganaba por orden. La píldora
   salía con el padding de un campo cualquiera y el icono encima del texto.
   Lo mismo su `:focus` contra el `:focus` genérico. */
.home-search input[type="search"] {
    width: 100%; padding: var(--space-200) var(--space-250) var(--space-200) var(--space-600);
    font: inherit; font-size: var(--fs-body);
    border: 1px solid var(--border); border-radius: 999px;
    background: var(--card-bg); color: var(--text);
    box-shadow: var(--shadow); outline: none;
    transition: border-color .18s ease, box-shadow .18s ease;
}
.home-search input::placeholder { color: var(--text-light); opacity: .8; }
.home-search input[type="search"]:focus {
    border-color: var(--primary); box-shadow: 0 0 0 4px var(--primary-soft);
}
.home-search:focus-within .home-search-icon { color: var(--primary); }

/* `:empty` y no una clase: el fragmento que devuelve el servidor viene vacío
   cuando no hay término, así que la caja se abre sola cuando hay algo que
   mostrar y se cierra sola cuando no. Sin JS de por medio. */
.home-search-results {
    position: absolute; top: calc(100% + 10px); left: 0; right: 0; z-index: 40;
    max-height: 380px; overflow-y: auto; padding: var(--space-075); text-align: left;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
}
/* Sin término el fragmento llega con solo espacios y comentarios, y eso ya
   no es `:empty`: quedaba una franja blanca debajo de la casilla al borrar
   la búsqueda o al mandarle la pregunta al panel. */
.home-search-results:empty,
.home-search-results:not(:has(*)) { display: none; }
.search-list { list-style: none; margin: 0; padding: 0; }
/* La fila de un resultado: el icono de la pantalla, su nombre, de qué se
   trata y la flecha de que se entra. La baldosa es la misma de las listas
   (`tile-sm`), y a propósito: una pantalla se reconoce por el mismo icono en
   la barra de la izquierda, en el encabezado de su tabla y acá. */
.search-list a {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-100); border-radius: 10px;
    color: var(--text); font-size: var(--fs-body); text-decoration: none;
}
.search-list a:hover { background: var(--primary-soft); color: var(--primary-dark); }
/* El nombre puede ser largo —«Distribuidora de Café del Sur SAS»— y la pista
   de la derecha no puede quedar empujada fuera de la caja: encoge el nombre,
   que es lo que se entiende a medias sin perder el renglón. */
.search-label {
    flex: 0 1 auto; min-width: 0; font-weight: 500;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Qué es y de dónde cuelga, en UNA pista a la derecha: separadas eran dos
   grises en el mismo renglón, que se leen como uno partido en dos. */
.search-meta {
    margin-left: auto; flex: none;
    color: var(--text-light); font-size: var(--fs-small); white-space: nowrap;
}
.search-meta-sep { color: var(--text-faint); }
.search-go { width: 16px; height: 16px; flex: none; color: var(--text-faint); }
/* Buscar y no encontrar TAMBIÉN es una respuesta. El fragmento se quedaba
   mudo con un término sin resultados, y una caja que se abre vacía al
   teclear se lee como un buscador roto. */
.search-none {
    margin: 0; padding: var(--space-100) var(--space-150);
    color: var(--text-light); font-size: var(--fs-body);
}

/* ── La paleta de ⌘K ─────────────────────────────────────────────────────────
   Un `<dialog>` anclado ARRIBA y no al centro: la pantalla de la que uno
   viene se sigue viendo debajo, y el ojo ya estaba arriba —es donde vive la
   barra—. Mide lo mismo que el buscador del Inicio, que es la otra caja de
   buscar de Agrupa.

   `display` solo con `[open]`: un `<dialog>` cerrado es `display: none`, y
   ponerle `flex` a secas lo deja pintado encima de toda pantalla del ERP. */
.paleta {
    width: min(620px, calc(100vw - 2 * var(--space-200)));
    max-height: min(70vh, 560px);
    margin: 12vh auto auto; padding: 0;
    border: 1px solid var(--glass-line); border-radius: var(--radius-lg);
    /* VIDRIO: el fondo translúcido y el desenfoque de lo que hay detrás.
       `backdrop-filter` no es decoración acá — sin él, el translúcido deja
       leer el texto de la pantalla de atrás entre los renglones. */
    background: var(--glass-bg); color: var(--glass-text);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    backdrop-filter: blur(20px) saturate(140%);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}
.paleta[open] { display: flex; flex-direction: column; }
.paleta::backdrop { background: rgb(12 10 24 / 55%); }
.paleta-caja {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-200);
    border-bottom: 1px solid var(--glass-line);
}
.paleta-glifo { width: 18px; height: 18px; flex: none; color: var(--glass-text-soft); }
/* El campo sin caja propia: la caja es la paleta entera. Con el borde de
   `input` puesto quedaba una caja dentro de otra, que es lo mismo que le
   pasaba a la barra de filtros dentro de su tarjeta. */
.paleta-caja input[type="search"] {
    flex: 1 1 auto; min-width: 0;
    padding: var(--space-050) 0; border: 0; background: none; box-shadow: none; outline: none;
    font: inherit; font-size: var(--fs-section); color: var(--glass-text);
}
.paleta-caja input[type="search"]::placeholder { color: var(--glass-text-soft); }
/* SIN anillo de foco acá adentro. La regla general de los campos lo pinta
   al enfocar —y este nace enfocado, que es lo que hace la paleta—, así que
   dibujaba un óvalo pegado al texto por los cuatro lados (Renzo,
   2026-09-15: «mejorar un tri el padding del buscador»). El campo no es una
   casilla sobre una tarjeta: la boca de la paleta es la caja entera, y el
   aire lo pone ella. */
.paleta-caja input[type="search"]:focus { border: 0; box-shadow: none; }
/* La equis para borrar es la de la casa (junto a los campos); sobre el
   vidrio va del color de su texto (Renzo, 2026-10-06). */
.paleta-caja input[type="search"]::-webkit-search-cancel-button { background: var(--glass-text); }
.paleta-kbd {
    flex: none; padding: 1px var(--space-075);
    border: 1px solid var(--glass-line); border-radius: var(--radius-sm);
    background: var(--glass-soft);
    color: var(--glass-text-soft); font: inherit; font-size: var(--fs-meta);
}
.paleta-resultados { padding: var(--space-100); overflow-y: auto; }
/* Las filas, sobre vidrio: el texto se aclara, la baldosa del icono deja de
   ser morada clara —sobre oscuro se lee como un borrón— y la flecha vive en
   su propio cuadro, que es lo que la vuelve un botón a la vista. */
.paleta .search-list a { color: var(--glass-text); }
.paleta .tile { background: var(--glass-soft); color: var(--glass-text); }
.paleta .search-meta { color: var(--glass-text-soft); }
.paleta .search-meta-sep { color: var(--glass-text-soft); }
.paleta .search-go {
    width: 26px; height: 26px; padding: var(--space-050); border-radius: var(--radius-sm);
    background: var(--glass-soft); color: var(--glass-text-soft);
}
.paleta .search-list a:hover { background: var(--glass-hover); color: var(--glass-text); }
.paleta .search-list a.is-activa {
    background: var(--glass-activa); color: #fff;
    box-shadow: inset 0 0 0 1px var(--glass-line);
}
.paleta .search-list a.is-activa .search-go { background: var(--primary); color: #fff; }
.paleta .search-none, .paleta .search-more { color: var(--glass-text-soft); }
/* La fila que Enter abre. Se marca con una clase y no con `:hover` porque
   acá manda el teclado: el ratón sigue a la marca, no al revés. */
.search-list a.is-activa { background: var(--primary-soft); color: var(--primary-dark); }
/* Y su flecha se llena: en una lista de diez filas iguales, el fondo suave
   solo dice «esta»; la flecha llena dice «y Enter entra acá». */
.search-list a.is-activa .search-go {
    width: 24px; height: 24px; padding: var(--space-050); border-radius: var(--radius-sm);
    background: var(--primary); color: #fff;
}
/* El `hover` lo pone la regla de vidrio de arriba (`.paleta .search-list
   a:hover`). Acá vivía el gris claro del tema, con la MISMA especificidad y
   más abajo: le ganaba, y sobre el vidrio oscuro dejaba una barra blanca
   con el texto blanco encima — ilegible (Renzo, 2026-09-15: «el over blanco
   no se lee»). Dos reglas para el mismo estado es cómo se llega a eso. */
/* Las teclas, escritas al pie: un atajo que no se anuncia lo conocen los
   tres que estaban cuando se hizo. */
.paleta-pie {
    display: flex; align-items: center; gap: var(--space-200); flex-wrap: wrap;
    padding: var(--space-100) var(--space-200);
    border-top: 1px solid var(--glass-line); background: var(--glass-hover);
    color: var(--glass-text-soft); font-size: var(--fs-meta);
}
.paleta-pista { display: inline-flex; align-items: center; gap: var(--space-050); }
.paleta-pie kbd {
    padding: 1px var(--space-075); border: 1px solid var(--glass-line); border-radius: 6px;
    background: var(--glass-soft); color: var(--glass-text); font: inherit;
}
.search-label { overflow-wrap: anywhere; }
.search-more {
    margin: var(--space-075) 0 var(--space-025); padding: 0 var(--space-150) var(--space-075);
    color: var(--text-light); font-size: var(--fs-small);
}

.home-chips {
    position: relative; z-index: 1;
    display: flex; gap: var(--space-100); justify-content: center; flex-wrap: wrap;
    margin-top: var(--space-300);
    animation: home-rise .5s ease .18s both;
}
.home-chip {
    display: inline-flex; align-items: center; gap: var(--space-100);
    padding: var(--space-100) var(--space-100) var(--space-100) var(--space-200);
    border: 1px solid var(--border); border-radius: 999px;
    background: var(--card-bg); color: var(--text);
    font-size: var(--fs-body); font-weight: 500; text-decoration: none;
    box-shadow: var(--shadow);
    transition: transform .15s ease, border-color .15s ease;
}
.home-chip:hover {
    border-color: var(--primary); transform: translateY(-1px);
    color: var(--text); text-decoration: none;
}
.home-chip:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--primary-soft); }
.home-chip-count {
    padding: var(--space-050) var(--space-100); border-radius: 999px;
    background: var(--primary-soft); color: var(--primary-dark);
    font-size: var(--fs-meta); font-weight: 700; font-variant-numeric: tabular-nums;
}
.home-chip.tone-warn .home-chip-count   { background: var(--warn-bg);   color: var(--warn-text); }
.home-chip.tone-danger .home-chip-count { background: var(--danger-bg); color: var(--danger-text); }

/* ── El home angosto ────────────────────────────────────────────────────────
   La píldora de la tira solo funciona en UNA fila: en cuanto envuelve, el
   radio de 999px y los separadores internos se leen como celdas rotas
   (Renzo lo vio redimensionando, 2026-08-04). Las seis casillas piden unos
   730px, y el ancho que les queda es el de la ventana MENOS el sidebar
   —250px— y el respiro del contenido; por eso el corte va en 1100 y no en
   los 900 de tiendapp, que no tenía sidebar fijo. Debajo de ahí la tira es
   una tarjeta: radio normal, sin separadores, el rango en su propia línea.

   El saludo baja reasignando el paso `hero`. La escala sigue siendo el
   único camino: un tamaño se renombra por pantalla, no se teclea.

   La tira NO va acá: sus dos pisos los decide `tira.js` midiendo si cabe
   (abajo). Estaba copiada en los dos. */
@media (max-width: 1100px) {
    :root { --fs-hero: 28px; }
    .home-stage { padding: var(--space-600) 0 var(--space-400); }
}

/* La tira en dos pisos CUANDO NO CABE en una fila. Lo decide
   `assets/js/tira.js`, que mide y pone `.home-metrics-dos-pisos`: el corte
   era un ancho fijo del contenido (1040 px, el de cinco casillas) y la sexta
   —«Gasto en IA», 2026-10-26— se partió sola al renglón de abajo en una
   pantalla ancha. Cuánto pide la fila depende de cuántas casillas puso el
   Escritorio y de cuántas cifras trae cada una, y eso no lo sabe un ancho.
   Sin script la tira se parte como cualquier flex; nada se pierde.

   Los selectores van con `:where()` para pesar lo mismo que los de la tira:
   el bloque del teléfono, más abajo, los deshace por orden y no por peso.

   Arriba se elige, abajo se lee (Renzo, 2026-10-11, con el Asistente
   abierto: «se ve raro»). Partida a secas, «RANGO» quedaba suelto con el
   mismo aspecto de los rótulos de las cifras —se leía como una casilla sin
   número—, los disparadores pegados a los números sin nada entre los dos,
   y cada piso con su relleno, sin una columna en común. Ahora:

   - el rótulo se va, como en el teléfono: el glifo dice qué es;
   - una raya separa los dos pisos, sangrada como las de las casillas;
   - lo que está EN VIVO sube a la derecha del encabezado: no depende del
     rango, y las cifras que sí dependen se reparten el piso de abajo en
     columnas parejas. `order` y el corte forzado del `::after` lo hacen
     sin tocar la plantilla; el número de casillas lo decide el Escritorio.

   El primer glifo del rango cae en la misma columna que el primer rótulo:
   4px de la cabeza + 8px del disparador = 12px de la casilla. */
.home-metrics:where(.home-metrics-dos-pisos) { border-radius: var(--radius-lg); }
.home-metrics:where(.home-metrics-dos-pisos)::after { content: ''; order: 2; flex-basis: 100%; }
:where(.home-metrics-dos-pisos) .home-metrics-head {
    order: 0; flex: 1 1 auto; flex-direction: row; align-items: center;
    justify-content: flex-start; gap: var(--space-100); padding: var(--space-025) var(--space-050) var(--space-075);
}
:where(.home-metrics-dos-pisos) .home-metrics-scope { display: none; }
:where(.home-metrics-dos-pisos) .home-metrics-head,
:where(.home-metrics-dos-pisos) .home-metric-live {
    border-bottom: 1px solid color-mix(in srgb, var(--text) 12%, transparent);
}
:where(.home-metrics-dos-pisos) .home-metric { order: 3; flex: 1 1 0; padding: var(--space-100) var(--space-150) var(--space-075); }
:where(.home-metrics-dos-pisos) .home-metric-live { order: 1; flex: none; padding: var(--space-025) var(--space-150) var(--space-075); border-radius: 0; }
:where(.home-metrics-dos-pisos) .home-metric-live::before { display: none; }
/* El primero no lleva raya: colgaba sola contra el borde (Renzo,
   2026-10-03, con el panel abierto). */
:where(.home-metrics-dos-pisos) .home-metrics-head + .home-metric::before { display: none; }

/* ── El escritorio en el teléfono ───────────────────────────────────────────
   Renzo, 2026-09-15, con la portada abierta en su iPhone: «se ve todo como
   muy grave». Lo que pasaba, medido a 390px: el saludo y su frase se comían
   la primera pantalla —el buscador y los pendientes quedaban debajo del
   pliegue—, la tira de números se partía en 2 y luego 3 casillas con la
   rayita de cada una arrancando a media pantalla, y los pendientes eran
   pastillas centradas de anchos distintos. */
@media (max-width: 640px) {
    :root { --fs-hero: 24px; }
    .home-stage { padding: var(--space-200) 0 var(--space-200); }
    .home-greeting-title { margin-bottom: var(--space-050); }
    .home-gratitude { margin-bottom: var(--space-200); font-size: var(--fs-small); }
    /* La tira se vuelve REJILLA de dos columnas. Las líneas son el `gap` de
       1px sobre el fondo del contenedor y no un borde por casilla: así valen
       para CUALQUIER número de casillas, y son las que el Escritorio decide
       desde /analiticas/t/escritorio/. */
    .home-metrics {
        display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1px; padding: 1px; overflow: hidden;
        background: var(--border);
    }
    .home-metrics-head, .home-metric { background: var(--card-bg); }
    .home-metrics-head { grid-column: 1 / -1; padding: var(--space-100) var(--space-150); }
    /* «RANGO» se va: el glifo del calendario ya dice qué es, y su rótulo le
       robaba el renglón a los dos disparadores, que caían uno debajo del
       otro. */
    .home-metrics-scope { display: none; }
    .home-metric { padding: var(--space-100) var(--space-150); border-radius: 0; }
    .home-metric::before { display: none; }
    /* En media columna el rótulo largo se PARTE en vez de cortarse: «Personas
       (módulo más usado)» de la Flota salía «PERSONAS (MÓDULO M» (2026-10-09).
       Los cortos no cambian. */
    .home-metric-label { white-space: normal; }
    /* La última casilla, cuando queda sola en su renglón, ocupa el ancho:
       media casilla vacía deja ver el fondo de las líneas como un bloque
       gris. Es `even` porque el primer hijo de la rejilla es el rango. */
    .home-metric:last-child:nth-child(even) { grid-column: 1 / -1; }
    .home-metric:hover, .home-metric-live:hover { background: var(--card-bg); }
    /* Los dos pisos de `tira.js` no van en la rejilla: el
       `::after` sería una celda vacía, el `order` sacaría lo en vivo de
       último —y la regla de arriba cuenta por el orden del documento— y
       la raya se sumaría a las líneas del `gap`. */
    .home-metrics::after { content: none; }
    .home-metrics-head, .home-metric-live { border-bottom: 0; }
    .home-metric, .home-metric-live { order: 0; }
    .home-metric-live { margin-left: 0; text-align: left; }
    .home-metric-live .home-metric-row { justify-content: flex-start; }
    /* Los pendientes, en columna y a todo el ancho, con el conteo a la
       derecha: se leen como una lista de lo que falta y se tocan con el
       pulgar. */
    .home-chips {
        flex-direction: column; align-items: stretch;
        gap: var(--space-100); margin-top: var(--space-200);
    }
    .home-chip { justify-content: space-between; padding: var(--space-100) var(--space-150) var(--space-100) var(--space-200); }
    /* La hora baja debajo del texto y alineada con él: al lado le dejaba al
       texto una columna de dos palabras. */
    .home-feed-item { align-items: flex-start; gap: var(--space-100); flex-wrap: wrap; }
    .home-feed-glyph { width: 28px; height: 28px; }
    .home-feed-when { flex-basis: 100%; padding-left: var(--space-500); }
}

/* ── Encabezado de pantalla ────────────────────────────────────────────── */

/* ── El banner de sección ──────────────────────────────────────────────────

   Lo pinta el shell, una vez por vista. Antes cada plantilla escribía su
   propio `<div class="page-header">`: veinticuatro copias del mismo
   contenedor, y solo cinco sabían dónde poner una acción.

   El resplandor va en el FONDO y no en un elemento: un `<div>` decorativo
   sería un nodo más que un lector de pantalla recorre y que hay que
   acordarse de esconder. Un degradado no existe para el árbol de
   accesibilidad, y hay una prueba que lo fija.

   `flex-wrap` y no un `grid` de dos columnas: a 390px el título y sus dos
   botones no caben en una línea, y con el grid los botones se comprimían
   hasta partir la palabra «Guardar». Envolviendo, bajan enteros. */
.page-banner {
    display: flex; justify-content: space-between;
    /* CENTRADO, no arriba: con `flex-start` los botones quedaban colgando del
       borde superior mientras el título ocupaba dos líneas. */
    align-items: center;
    gap: var(--space-200); flex-wrap: wrap;
    /* BAJA (Renzo, 2026-09-29): de 24px de relleno vertical a 12. Ahora se
       queda pegada arriba mientras la pantalla corre por debajo, y una banda
       alta pegada se come la mitad de la ventana. */
    margin-bottom: var(--space-250); padding: var(--space-100) var(--space-150) var(--space-100) var(--space-200);
    /* La banda como el listón de la landing (Renzo, 2026-09-29): vidrio
       CLARO en píldora, con los tres ingredientes de `.site-nav` en
       `site.css` —el esmerilado con saturación, un degradado vertical con
       más luz arriba y los `inset` del canto—. La refracción
       `url(#vidrio-liquido)` va más abajo, de prueba.

       La tinta se voltea reasignando los dos tokens de la banda y no regla
       por regla: el botón fantasma, los tres puntos y el título ya se pintan
       con ellos, así que pasan a oscuro solos. */
    --banner-text: var(--text);
    --banner-sub: var(--text-light);
    border-radius: 999px;
    color: var(--banner-text);
    /* Vidrio claro y nada más (Renzo, 2026-10-04): se probó teñirlo con
       los colores de la mesa del equipo —en manchas y en un barrido— y no
       se veía bien. El círculo morado de la esquina también salió. */
    background: linear-gradient(180deg, var(--bar-glass-top), var(--bar-glass-bottom));
    border: 1px solid var(--bar-glass-edge);
    /* Una sombra suave alrededor y sin el canto (`--glass-rim`) (Renzo,
       2026-10-10, desde el inspector). */
    box-shadow: var(--bar-glass-shadow);
    -webkit-backdrop-filter: blur(14px) saturate(170%);
    backdrop-filter: blur(14px) saturate(170%);
    /* De prueba (Renzo, 2026-10-04): la refracción —lo que pasa por debajo
       se deforma, como en el vidrio de Apple—. El filtro es la pieza
       `ui/_vidrio_liquido.html`. Va en una SEGUNDA declaración a propósito:
       el navegador que no sepa parsearla (Safari, Firefox) se queda con la
       de arriba, esmerilado sin refracción. Con menos desenfoque: a 14px el
       fondo ya es una pasta y no queda forma que deformar; 10px (Renzo,
       2026-10-10, desde el inspector; antes 4px). */
    backdrop-filter: blur(10px) saturate(170%) url(#vidrio-liquido);
    position: relative; overflow: hidden;
}
/* El filtro vive en el documento pero no ocupa lugar. */
.vidrio-defs { position: absolute; width: 0; height: 0; }
/* Sin esmerilado no hay vidrio: el fondo se vuelve sólido. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
    .page-banner { background: var(--card-bg); }
}

/* El contenido por encima del fondo. */
.page-banner > * { position: relative; z-index: 1; }
.page-banner h1 {
    margin: 0; font-size: var(--fs-page); font-weight: 700;
    letter-spacing: -.02em; color: var(--banner-text);
}
/* Una pastilla al lado del título («IA» en la mesa del equipo) va centrada
   con las mayúsculas, no sentada en la línea base: ahí quedaba caída. */
.page-banner h1 .pill { vertical-align: middle; margin: -.3em 0 0 var(--space-050); letter-spacing: 0; }
/* `min-width: 0` para que el texto largo se rompa en vez de estirar la
   columna y empujar los botones fuera de la banda. */
/* `flex: 1` para que el texto se coma el sobrante y el avatar quede PEGADO a
   él. Sin esto, con tres hijos el `space-between` del banner reparte el hueco y
   la inicial se va al borde izquierdo, a un palmo del nombre al que pertenece.
   Con dos hijos —el caso de las demás pantallas— el resultado no cambia. */
/* La BASE va en cero y no en `auto`, y esa palabra es la que sostiene el
   banner de tres hijos.

   Con `flex-wrap: wrap` el navegador parte la línea ANTES de encoger: reparte
   por el tamaño hipotético de cada hijo —que con base `auto` es el ancho de su
   contenido— y sólo después aplica `flex-shrink` dentro de cada línea. El
   párrafo de esta pantalla mide 1.010px de contenido, y con la baldosa
   delante los tres pedían 1.168 contra 1.152 de banda: se pasaba por
   dieciséis píxeles, la banda partía, y el botón «Volver» caía debajo del
   título alineado a la izquierda. `min-width: 0` no lo salvaba — permite
   encoger, pero para entonces ya se había partido.

   Con la base en cero los tres caben siempre, y el texto crece hasta llenar
   lo que sobra, que es lo que decía el párrafo de abajo y ahora es verdad
   también con tres hijos. */
/* Y el PISO, que es la otra mitad de esa misma trampa: con la base en cero
   el tamaño hipotético del texto también es cero, así que los tres hijos
   «caben» SIEMPRE y la banda no envuelve nunca — ni a 390px, donde el texto
   quedaba en una columna de seis píxeles y el título se desbordaba por debajo
   de los botones. Renzo lo vio en la ficha de un integrante y en la pantalla
   de su PIN el 2026-09-03.

   El piso le da al texto un tamaño hipotético de verdad para el reparto: si
   la baldosa, el piso y los botones no caben en una línea, los botones bajan
   enteros y el título se queda con la suya, que después crece hasta llenarla.
   `14rem` cabe con holgura en cualquier banda de escritorio junto a la
   baldosa y las acciones —así que ahí nada cambia—, y el `60%` es para un
   teléfono angosto, donde 14rem más la baldosa ya no entran. */
.page-banner-text { min-width: min(14rem, 60%); flex: 1 1 0; }
/* Sin subtítulo (Renzo, 2026-09-29): la banda lleva el título y nada más.
   Se esconde todo lo que no sea el `h1` y no la clase `.page-banner-meta`,
   porque no todas las pantallas la usan para lo que ponen debajo. El bloque
   `page_meta` se sigue llenando; si vuelve, es quitar esta línea. */
.page-banner-text > :not(h1) { display: none; }
.page-banner-meta {
    margin: 1px 0 0; color: var(--banner-sub); font-size: var(--fs-body);
}
/* `.mono` dentro del banner HEREDA el color de la banda, no el suyo.

   `.mono` fija `--text-light`, que es un gris pensado para fondo claro: sobre
   la banda oscura daba 3.11:1 —por debajo del 4.5 de la WCAG— y el NIT del
   tercero salía casi ilegible justo en el sitio donde se coteja contra una
   factura. El resto de la meta da 9.83, así que el defecto era del contexto y
   no del color de la banda.

   Viene de antes de la ficha: el formulario ya pintaba el documento con
   `class="page-banner-meta mono"`. Se arregla acá, en el banner, y no
   cambiándole el color a `.mono`, que sobre blanco está bien. */
.page-banner-meta.mono, .page-banner-meta .mono { color: inherit; }
/* Un enlace en la meta —«Se usa en Descuento PARCEROSVIP»— también hereda:
   el color de enlace es para fondo claro y sobre la banda no contrasta. Lo
   que lo distingue es el subrayado. */
.page-banner-meta a { color: inherit; text-decoration: underline; }
/* Las acciones NO se estiran y se mantienen juntas: envueltas a 390px caen
   como un bloque debajo del título, en el orden en que se leen. */
.page-banner-actions {
    display: flex; align-items: center; gap: var(--space-100);
    flex-wrap: wrap; flex-shrink: 0;
}
/* Un `<form>` no anida, así que las acciones de la banda que van en POST
   —empezar la preparación, deshacerla— traen cada una el suyo y quedan como
   hermanas de los enlaces. Sin esto, el margen por omisión del formulario las
   desalinea contra los botones de al lado. */
.page-banner-actions .banner-form { margin: 0; display: contents; }
/* Sobre la banda, el botón fantasma se dibuja con el borde claro: el de
   `--border` desaparece contra el negro y quedaba un texto flotando. */
.page-banner .btn-ghost {
    color: var(--banner-text);
    border-color: color-mix(in srgb, var(--banner-text) 28%, transparent);
    background: transparent;
}
.page-banner .btn-ghost:hover {
    background: color-mix(in srgb, var(--banner-text) 12%, transparent);
    color: var(--banner-text);
}
/* Y lo DESTRUCTIVO sobre la banda, que es la otra mitad que faltaba.

   `.btn-danger-ghost` se pinta con `--text-light` y sólo se vuelve rojo al
   pasar por encima: correcto sobre una tarjeta blanca —lo destructivo no
   grita hasta que lo tocas— y un defecto sobre el negro, donde ese gris da
   2:1 y el botón se lee como DESHABILITADO. Renzo lo vio así en la ficha de
   una variante el 2026-08-17: «Eliminar» parecía apagado al lado de
   «Cancelar», que sí tenía su regla de banda desde siempre.

   Es la misma forma de defecto que el pie de tabla de esta misma sesión: una
   regla escrita para una variante y no para su hermana.

   RELLENO y no fantasma, con el par de `badge-soft-danger` —que es el que la
   banda ya usa para decir peligro—: sobre el negro un contorno rojo compite
   con el borde claro de «Cancelar» y las dos acciones se leen igual de
   pesadas. Rellena, se ve cuál es la que borra sin leerla. */
.page-banner .btn-danger-ghost,
.page-banner .btn-danger {
    background: var(--danger-bg);
    color: var(--danger-text);
    border-color: var(--danger-bg);
}
.page-banner .btn-danger-ghost:hover,
.page-banner .btn-danger:hover {
    background: var(--danger-vivid);
    color: var(--danger-vivid-text);
    border-color: var(--danger-vivid);
}

/* ── Los tres puntos de la banda ──────────────────────────────────────────
   TODA acción de la banda vive dentro del menú, aunque sea una sola (Renzo,
   2026-09-13). El shell envuelve `page_actions` en `.page-banner-menu`; acá
   los botones que las pantallas ya escriben se visten de filas del menú, y
   las reglas de banda de arriba —fantasma claro, peligro relleno— se apagan
   porque el desplegable es una tarjeta clara y no la banda oscura. Van con
   TRES clases (0,3,0) a propósito: `.page-banner .btn-ghost` pesa (0,2,0) y
   está más arriba, y con el mismo peso ganaría por orden. */
.page-banner-menu { flex-shrink: 0; }
/* Sin acciones, sin menú: un botón de tres puntos que no ofrece nada es una
   promesa rota. `:has` mira lo que el bloque trajo. */
.page-banner-menu:not(:has(:is(.btn, button, a, label):not(.banner-filtrar))) { display: none; }
body:has(#filtros-panel) .page-banner-menu { display: revert; }
/* El disparador se dibuja como el botón fantasma de la banda, con su borde
   claro: los 32px grises del menú de una fila desaparecen contra el negro. */
.page-banner .page-banner-menu > .menu-trigger {
    height: var(--btn-h); padding: 0 var(--space-200) 0 var(--space-100); font-size: var(--fs-body);
    color: var(--banner-text);
    border: 1px solid color-mix(in srgb, var(--banner-text) 28%, transparent);
    border-radius: 999px;
}
.page-banner .page-banner-menu > .menu-trigger:hover {
    background: color-mix(in srgb, var(--banner-text) 12%, transparent);
    color: var(--banner-text);
}
/* Abierto se ACLARA, con un toque de morado (Renzo, 2026-10-04): con el gris
   del hover, sobre la banda de vidrio se leía oscuro, como apagado. */
.page-banner .page-banner-menu[open] > .menu-trigger {
    background: color-mix(in srgb, var(--primary) 12%, var(--card-bg));
    border-color: color-mix(in srgb, var(--primary) 40%, transparent);
    color: var(--banner-text);
}
/* El envoltorio que las pantallas ya traen deja de ser una fila: sus hijos
   pasan a ser filas del menú, una debajo de otra. */
.page-banner .menu-pop .page-banner-actions { display: contents; }
/* Sin relleno arriba: la primera fila ya trae su `margin-top`, y sumarle el
   del desplegable dejaba más aire arriba que abajo. */
.page-banner .menu-pop { min-width: 240px; padding-top: 0; }
/* Cada botón, del tono que sea, es una fila del menú: sin borde ni relleno de
   color, a la izquierda y al ancho. El glifo que `_btn.html` ya trae queda
   como el de `.menu-item`. */
.page-banner .menu-pop .btn {
    display: flex; width: 100%; height: auto; min-height: 0;
    justify-content: flex-start; gap: var(--space-100);
    padding: var(--space-100) var(--space-100); border: 0; border-radius: var(--radius-sm);
    box-shadow: none; background: transparent;
    color: var(--text); font-weight: 500; font-size: var(--fs-body);
}
/* Aire entre filas (Renzo, 2026-09-13): con 1px el fondo del hover pegaba
   contra la raya. La raya va al medio del hueco. */
.page-banner .menu-pop .btn { margin-top: var(--space-100); position: relative; }
/* Una raya entre fila y fila (Renzo, 2026-09-13). Va como pseudoelemento y
   no como borde porque las filas viven dentro de `<form>` y `<div>` con
   `display: contents`, y un borde en un elemento que no pinta caja no se
   ve. La primera fila no la lleva: la raya separa, no encabeza. */
.page-banner .menu-pop .btn::before {
    content: ''; position: absolute; left: 10px; right: 10px;
    top: calc(var(--space-050) * -1); height: 1px; background: var(--border);
}
/* Cuatro formas de «primera fila»: el botón suelto, el botón dentro de su
   `<form>`, y las dos mismas dentro del envoltorio `.page-banner-actions`.
   El `:not(.page-banner-actions)` importa: sin él, «primer hijo > .btn»
   casaba con TODOS los botones del envoltorio y no salía ni una raya. */
.page-banner .menu-pop > :first-child:not(.page-banner-actions)::before,
.page-banner .menu-pop > :first-child:not(.page-banner-actions) > .btn::before,
.page-banner .menu-pop > .page-banner-actions > :first-child::before,
.page-banner .menu-pop > .page-banner-actions > :first-child > .btn::before { display: none; }
/* «FILTRAR» (Renzo, 2026-10-09): el shell lo pinta en toda banda y se ve
   solo donde hay panel de Filtros. Con el peso de las filas (0,3,0): un
   `.banner-filtrar` suelto perdía contra `display: flex` de arriba. Escondido,
   la fila que le sigue pasa a ser la primera y no lleva raya. */
.page-banner .menu-pop .banner-filtrar { display: none; }
body:has(#filtros-panel) .page-banner .menu-pop .banner-filtrar { display: flex; }
body:not(:has(#filtros-panel)) .page-banner .menu-pop > .banner-filtrar + :not(.page-banner-actions)::before,
body:not(:has(#filtros-panel)) .page-banner .menu-pop > .banner-filtrar + :not(.page-banner-actions) > .btn::before,
body:not(:has(#filtros-panel)) .page-banner .menu-pop > .banner-filtrar + .page-banner-actions > :first-child::before,
body:not(:has(#filtros-panel)) .page-banner .menu-pop > .banner-filtrar + .page-banner-actions > :first-child > .btn::before { display: none; }
/* Con Filtrar a la vista, ÉL es la primera fila, sin raya. */
.page-banner .menu-pop > .banner-filtrar::before { display: none; }
/* …y la primera acción del envoltorio pasa a ser la SEGUNDA fila: recupera
   la raya que la regla de «primera fila» de arriba le quita (revisión
   final, 2026-10-09; eran 33 pantallas). */
body:has(#filtros-panel) .page-banner .menu-pop > .banner-filtrar + .page-banner-actions > :first-child::before,
body:has(#filtros-panel) .page-banner .menu-pop > .banner-filtrar + .page-banner-actions > :first-child > .btn::before { display: block; }
.btn-cuenta:not(:empty) {
    margin-left: auto; min-width: 20px; padding: 0 var(--space-075); border-radius: 999px;
    font-size: var(--fs-small); font-weight: 600; text-align: center;
    background: var(--primary-soft); color: var(--primary);
}
/* Y una quinta (Renzo, 2026-10-04): la fila que viene justo después de un
   formulario SIN botones —el escondido que el botón manda con `form=`, como
   «Crear pedido»—. Ese formulario no pinta nada, así que la fila es la
   primera aunque no sea el primer hijo, y con una sola opción salía una
   raya suelta encima. Por lo que tiene adentro y no por su clase:
   `hidden-form` es una marca sin estilo, y así debe seguir. */
.page-banner .menu-pop > form:first-child:not(:has(button)) + .btn::before,
.page-banner .menu-pop > .page-banner-actions > form:first-child:not(:has(button)) + .btn::before { display: none; }
.page-banner .menu-pop .btn:where(:not(:disabled)):hover {
    background: var(--primary-soft); color: var(--primary-dark);
    border: 0; box-shadow: none;
}
/* Lo que BORRA se pone rojo al pasar por encima y no en reposo, como
   `.menu-item-danger`: una lista con un ítem rojo permanente se lee como una
   alarma abierta. */
.page-banner .menu-pop .btn-danger:where(:not(:disabled)):hover,
.page-banner .menu-pop .btn-danger-ghost:where(:not(:disabled)):hover {
    background: var(--danger-bg); color: var(--danger-text);
}

/* ── La banda en el teléfono (Renzo, 2026-10-01) ───────────────────────────
   Era la píldora flotante de antes —sombra, radio de píldora, «Opciones» en
   su propio renglón— y gastaba ~230px antes del primer número del Resumen.
   Aquí toma la forma de la banda del escritorio —de borde a borde, raya
   abajo— pero NO pegada: en el teléfono una banda pegada se come la
   pantalla. Los márgenes negativos deshacen el relleno de `.content` en
   este ancho (16px arriba, 14px a los lados; ver el bloque del shell).

   Sin `backdrop-filter`: un elemento con filtro es el bloque contenedor de
   sus descendientes `fixed`, y la hoja del ⋮ quedaría pegada al fondo de la
   banda y recortada por su `overflow`. Sin pegarse no hay nada debajo que
   esmerilar, así que el vidrio no se ve distinto. Los colores no cambian.

   El ⋮ pierde el rótulo «Opciones»: el nombre lo sigue dando el
   `aria-label`, y con él puesto la baldosa, el título y el botón no cabían
   en un renglón. Con los 44px del dedo, sí. */
@media (max-width: 640px) {
    .content .page-banner {
        margin: calc(-1 * var(--space-200)) calc(-1 * var(--space-200)) var(--space-200);
        padding: var(--space-100) var(--space-100) var(--space-100) var(--space-200);
        gap: var(--space-150);
        border-radius: 0; border: 0;
        border-bottom: 1px solid var(--border);
        box-shadow: none;
        -webkit-backdrop-filter: none; backdrop-filter: none;
    }
    .page-banner .page-banner-menu > .menu-trigger {
        width: 44px; height: 44px; padding: 0;
        justify-content: center; border-radius: 50%;
    }
    .page-banner .page-banner-menu > .menu-trigger .menu-trigger-text { display: none; }
}

.page-search { display: flex; gap: var(--space-100); margin-bottom: var(--space-200); max-width: 560px; }
.page-search input {
    flex: 1; min-width: 0; padding: var(--space-150) var(--space-200);
    font: inherit; font-size: var(--fs-body); color: var(--text);
    background: var(--card-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    outline: none;
}
.page-search input:focus {
    border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft);
}

/* ── La tabla ──────────────────────────────────────────────────────────────
   El scroll horizontal vive ACÁ, nunca en el `body`. Es lo que todas las
   pantallas con tabla necesitan igual, y por eso es lo que el componente
   aporta; las celdas las escribe cada pantalla.
   ------------------------------------------------------------------------ */

.table-wrap {
    /* NADA DE SCROLL VERTICAL PROPIO (Renzo, 2026-08-14). La página es la
       que se desplaza, y la tabla mide lo que midan sus filas.

       Tuvo un `max-height` para tener un scrollport contra el cual pegar el
       `thead`. Funcionaba, y el precio era una caja que se desplaza dentro
       de otra que se desplaza: la rueda del ratón hace una cosa distinta
       según dónde esté el puntero, la barra de la tabla y la de la página
       conviven a dos centímetros, y llegar al pie de una lista de cincuenta
       filas obliga a desplazar dos veces. Un ERP con una sola barra se
       opera sin pensar en dónde estás parado.

       EL PRECIO, dicho: el `thead` pegajoso se pierde. `overflow-x: auto`
       hace que el otro eje compute a `auto`, así que esto SIGUE siendo un
       contenedor de scroll —hace falta para el desplazamiento lateral— y un
       `position: sticky` de adentro se pega a ESTA caja, que ya no se
       desplaza en vertical. Recuperarlo pidiendo que se pegue a la página
       exige que la tabla deje de ser contenedor de scroll, y con eso el
       `body` se desplazaría de lado, que es lo que §6.1 prohíbe. Son
       excluyentes: se eligió la barra única. */
    overflow-x: auto;
    background: var(--card-bg);
    /* SIN borde propio. Lo tuvo mientras una tabla podía vivir suelta sobre el
       fondo; ahora las once listas van dentro de una tarjeta que ya trae el
       suyo, así que el de acá era un segundo contorno a un píxel del primero.
       El radio se queda: es el que redondea las esquinas de abajo cuando la
       tabla es lo último de la tarjeta.

       Solo ABAJO. Arriba de una tabla está SIEMPRE su encabezado, y con el
       radio en las cuatro esquinas la banda gris salía curva en toda tabla
       que no arrancara pegada al borde de su tarjeta —la matriz de un rol,
       las tablas dentro de un formulario—. «Ninguna debe de ser así»
       (Renzo, 2026-09-29). */
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    /* Que la barra de scroll se vea siempre en macOS: una tabla que se
       desplaza sin señal de que se desplaza es una tabla a la que le faltan
       columnas. */
    scrollbar-width: thin;
}
.table {
    width: 100%; border-collapse: collapse;
    font-size: var(--fs-body); white-space: nowrap;
}
/* El encabezado, en FRASE NORMAL sobre una banda.

   Era versalitas de 11px en negrita con `letter-spacing`. Cinco rasgos
   tipográficos —caja alta, tamaño chico, peso 700, espaciado, color
   apagado— para decir «esto es un rótulo», cuando la posición ya lo dice:
   está arriba de la columna. El resultado era un rótulo más difícil de leer
   que el dato que rotula, y con «CONCILIABLE» al lado de «ESTADO» la caja
   alta además borra la frontera entre dos palabras.

   Ahora lo que separa el encabezado de los datos es el FONDO, que es una
   señal que no compite con la lectura. Y en frase normal el rótulo largo
   —«Método de pago»— se lee de un golpe en vez de deletrearse.

   El peso 500 y no 400: sobre la banda, a 400 el rótulo se hunde. */
/* Sin `position: sticky`. Lo llevaba, y desde que la tabla dejó de tener
   scrollport propio no se pegaba a nada: se quedaba escrito prometiendo algo
   que no ocurre, que es como llegó a estar roto la primera vez. Ver el
   comentario de `.table-wrap` para por qué las dos cosas son excluyentes. */
.table thead th {
    padding: var(--space-150) var(--space-200); text-align: left;
    font-size: var(--fs-small); font-weight: 500;
    color: var(--text-light);
    background: var(--table-head-bg); border-bottom: 1px solid var(--border);
}
.table tbody td {
    /* RELLENO A LOS LADOS, que es lo que cambió el 2026-08-14. No lo tuvo
       nunca: la separación salía del ancho del contenido, con el argumento
       de que en tablas de texto sobra. Sobra hasta que no: la lista de
       productos mostraba «Accesorios4» —el tipo pegado a la cantidad de
       variantes— y el plan de cuentas leía «CONCILIABLEESTADO». Las dos son
       la misma falla, y el parche era una clase (`.cell-pad`) que cada
       celda corta tenía que acordarse de llevar. Acordarse no escala; el
       relleno de la casa sí.

       Y POCO relleno arriba y abajo (Renzo, 2026-08-14). Decía «filas que
       respiran» y llevaba `--sp-3`, con el argumento de que a 11px la
       primera celda de dos líneas quedaba apretada. Ese argumento envejeció
       con la piel nueva: ahora hay una línea entre fila y fila, así que la
       separación ya no la tiene que dar el aire. Con `--sp-3` la fila medía
       60px y en una pantalla cabían nueve productos de once; con `--sp-1`
       mide 44 y caben todos, que es lo que se le pide a una lista.

       La ALTURA de la fila no la fija esto: la fija la miniatura de 36px,
       que es más alta que el texto. Bajar más el relleno no compacta nada
       —tocaría bajar `--cell-thumb`— y sí dejaría el texto de las celdas
       sin miniatura pegado a las líneas de arriba y abajo. */
    padding: var(--space-050) var(--space-200);
    /* Y el resto de la fila es SECUNDARIO. Lo que identifica una fila es su
       primera celda: el tipo, la cantidad de variantes y el contacto están
       para consultarse, no para leerse de corrido. En el mismo negro que el
       nombre compiten con él, y quinientas filas de texto negro parejo son un
       muro. El nombre vuelve a `--text` en la regla del enlace. */
    color: var(--text-light);
    vertical-align: middle;
    /* La LÍNEA entre filas. No la tuvo nunca: lo único que separaba una fila
       de la siguiente era el hover, o sea nada mientras el puntero está en
       otra parte. En una tabla de seis columnas eso obliga a seguir el
       renglón con la vista sin nada que lo sostenga, y a la cuarta columna
       ya se está leyendo la fila de abajo.

       Va en `--table-line` y no en `--border`: quinientas líneas del color
       del borde de una tarjeta convierten la lista en una reja. */
    border-bottom: 1px solid var(--table-line);
}
/* La última no lleva línea: debajo no hay fila que separar, y contra el
   filo de la tarjeta se lee como un borde de más. */
.table tbody tr:last-child > td { border-bottom: 0; }
/* Los dos EXTREMOS respiran más. Con el mismo relleno que el resto, la
   primera celda arranca a 16px del filo de la tarjeta, que trae los suyos:
   la columna se lee metida hacia adentro respecto del título de la tarjeta
   que la encabeza. */
/* El PIE va incluido desde el 2026-08-17 (Renzo: «este footer se ve raro»).
   Estaba escrito solo para `thead` y `tbody`, así que la fila de totales
   perdía los 20px de los dos extremos y sobresalía por los dos lados de su
   propia tabla: «Total valorizado» arrancaba a la izquierda del rótulo de su
   columna y la cifra terminaba pasada del borde. Alcanza a las tres tablas
   del ERP que llevan pie —existencias, valorización y los costos de un
   producto—, que tenían el mismo defecto sin que nadie lo hubiera nombrado. */
.table thead th:first-child,
.table tbody td:first-child,
.table tfoot td:first-child { padding-left: var(--space-250); }
.table thead th:last-child,
.table tbody td:last-child,
.table tfoot td:last-child { padding-right: var(--space-250); }

/* El rótulo de una columna que ABRE CON MINIATURA se corre hasta el nombre.

   La celda mide miniatura + hueco + texto, así que el rótulo pegado al filo
   queda encima de la foto: rotula lo que no nombra, y la columna se lee
   corrida respecto de su propio encabezado. Es la última pieza que separaba
   esta tabla de la referencia (Renzo, 2026-08-14).

   El corrimiento se DERIVA de la miniatura y su hueco —los mismos tokens
   que pinta `.cell-thumb` y el `gap` de `.cell-media`—, no se escribe. Con
   un `48px` a mano, agrandar la foto desalinea el encabezado y nada lo
   avisa: quedaría casi bien, que es la peor forma de estar mal.

   Va DESPUÉS de la regla del `:first-child` a propósito: las dos pesan
   (0,2,1) y a igual especificidad manda el orden del archivo. La lección de
   `.cell-pad` y de `.field-check`, que estaban escritas y no hacían nada. */
.table thead th.th-lead {
    padding-left: calc(var(--space-250) + var(--cell-thumb) + var(--space-150));
}

/* La canaleta de la casilla de selección.

   Ancho FIJO y no «lo que ocupe»: la casilla mide 20px y el rótulo de la
   columna de al lado empezaría a 20px del filo, o sea encima de ella. Con
   la canaleta declarada, la primera columna de datos arranca siempre en el
   mismo sitio esté o no encendida la selección — que es lo que deja que
   una lista con verbos y otra sin ellos se lean como la misma tabla.

   `line-height: 0` porque el `<input>` es `inline` y la caja de línea le
   agrega unos píxeles debajo que descuadran la fila contra sus vecinas.

   Cuando esta columna existe, es ella la primera y por lo tanto la que
   toma el relleno de borde: la regla de `:first-child` ya la alcanza sola.
   Lo que sí hay que quitarle es el corrimiento de `.th-lead`, que se
   calculó suponiendo que la columna de la foto era la primera. */
.cell-check {
    width: calc(var(--space-250) + 20px);
    line-height: 0;
}
/* SIN relleno a la derecha. Lo heredaba de `.table tbody td` y se sumaba al
   de la izquierda de la celda vecina: 16 + 16 = 32px entre la casilla y la
   miniatura, que a esa distancia dejan de leerse como parte de la misma
   fila. La separación entre dos columnas la pone UNA de las dos, y acá le
   toca a la vecina, que es la que tiene contenido que separar.

   VA NOMBRADO en el selector, y ésta es la CUARTA vez que la misma cascada
   muerde en este archivo —`.cell-pad`, `.field-check`, `.num` y ahora
   ésta—. Escrito `.cell-check { padding-right: 0 }` no hace nada: pesa
   (0,1,0) contra los (0,1,2) de `.table tbody td`, la regla queda escrita,
   se lee bien y el hueco sigue en 32px. Si vuelves a tocar el relleno de
   una celda concreta, nombra la tabla y el elemento o no pasará nada. */
.table thead th.cell-check,
.table tbody td.cell-check { padding-right: 0; }

/* ── La barra de la selección múltiple ────────────────────────────────────
   Encima de la tabla y dentro de la tarjeta, no flotando sobre la pantalla.
   Flotante taparía filas —justo las que se están mirando para decidir— y en
   una tabla larga hay que poder desplazarse con la selección puesta.

   Se pinta VISIBLE y la esconde el script: sin él, unas casillas sin barra
   son casillas que no llevan a ninguna parte. Ver `assets/js/bulk.js`.
   ------------------------------------------------------------------------ */
/* La barra ES el encabezado de la tarjeta con otro contenido, no una franja
   nueva (Renzo, 2026-08-14).

   Primer intento: una banda morada que aparecía ENCIMA del encabezado.
   Empujaba la tabla 47px hacia abajo, así que la fila que se iba a marcar
   se movía justo antes del clic siguiente. Segundo intento: turnarse con el
   encabezado, pero pintada de morado seguía leyéndose como otra cosa que
   aparece, que es exactamente lo que no se quería.

   Lo que va es UNA banda que cambia de contenido: «14 PRODUCTOS» cuando no
   hay nada marcado, «3 seleccionados» y los verbos cuando sí. Por eso copia
   al encabezado en todo lo que se ve —fondo, relleno, borde— y el conteo
   usa `.card-count`, la misma versalita del número que reemplaza. Lo único
   que cambia entre los dos estados son las palabras y los botones.

   `bulk.js` las turna, y por eso miden lo mismo: mismo relleno y un solo
   borde abajo. Un píxel de diferencia sería un píxel de salto en cada clic.

   Sin margen propio: al filo la lleva el `<form>`, que ya lo hace por ella
   y por la tabla. */
.bulkbar {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-150) var(--space-200);
    padding: var(--space-150) var(--space-250);
    /* SIN fondo, igual que el encabezado que reemplaza. Lo llevaba en
       `--card-bg` para «copiarlo», y el encabezado no tiene ninguno: es
       transparente sobre el blanco de la tarjeta. La diferencia importa
       porque esta banda llega a los filos, así que un fondo propio se pinta
       POR ENCIMA del radio de 12px de la tarjeta y le cuadra las esquinas
       de arriba — «al seleccionar se eliminan los bordes con radius».
       Transparente, la esquina la sigue dibujando la tarjeta. */
    border-bottom: 1px solid var(--border);
    font-size: var(--fs-body);
}
/* `hidden` TIENE que ganarle al `display` de arriba.

   La hoja del navegador trae `[hidden] { display: none }` con especificidad
   (0,1,0), y `.bulkbar` con su `display: flex` vale lo mismo pero va
   después: gana el nuestro, y el elemento queda con el atributo puesto y a
   la vista. Se vio exactamente así —`bar.hidden === true` y
   `getComputedStyle(bar).display === 'flex'`—, con la barra anunciando «0
   seleccionados» encima de la tabla.

   Vale para CUALQUIER componente de esta hoja que declare `display` y se
   apague con `hidden`: los `<p>` de aquí abajo se ocultan solos porque no
   declaran ninguno, y por eso el defecto salió en uno y no en los otros. */
.bulkbar[hidden] { display: none; }
/* Y el mismo margen superior NEGATIVO que el encabezado que reemplaza.

   `.card-header` lleva `margin-top: -16px` para meterse dentro del relleno
   de la tarjeta y empezar en su borde. La barra vive un nivel más adentro
   —dentro del `<form>`— y sin ese margen empezaba 16px más abajo: medido,
   la tabla bajaba 17px al marcar la primera casilla (16 del relleno más el
   píxel de diferencia de altura). Un salto pequeño y en cada clic, que es
   peor que uno grande y una vez.

   Va sobre la barra y no sobre el formulario: el formulario también
   contiene la tabla, y subirlo entero la metería debajo del encabezado
   cuando NO hay selección. */
.card-table > [data-bulk-form] > .bulkbar {
    margin-top: calc(var(--space-200) * -1);
}
/* AQUÍ VIVIÓ una excepción al tamaño de los botones, y duró un día.

   Decía «el botón NO manda la altura de la banda» y le bajaba el relleno
   para que midiera 20 en vez de 22, porque el título medía 20,9 y esa
   diferencia era un píxel de salto al seleccionar. Funcionaba, y era un
   parche local a un problema general: dos piezas que tienen que medir lo
   mismo, igualadas ajustando una de las dos a mano.

   Se retira con el sistema de tamaños: ahora la banda declara su alto
   (`--band-h`) y ese alto se deriva del botón que tiene que poder llevar.
   El contenido ya no negocia la altura del contenedor.

   (Y no era la sombra: el botón destructivo lleva una, pero `box-shadow` se
   pinta fuera del flujo y no ocupa maquetación.) */
.bulkbar-all { margin: 0; color: var(--text-light); font-size: var(--fs-small); }
/* A la derecha del todo: los verbos son el final de la frase que empieza en
   «3 seleccionados». `margin-left: auto` y no `justify-content` porque las
   dos líneas de la izquierda aparecen y desaparecen. */
.bulkbar-actions {
    display: flex; flex-wrap: wrap; gap: var(--space-100);
    margin-left: auto;
}
/* El tope, debajo del conteo y en el sitio de la pista del encabezado que
   la barra reemplaza: es una advertencia, no un dato de la selección, y
   metida entre el contador y los botones se leería como parte de lo que
   está pasando ahora. Antes iba en un renglón propio a lo ancho, debajo de
   los botones, y hacía la banda más alta que el encabezado. */
.bulkbar-cap {
    margin: 0; font-size: var(--fs-meta); color: var(--text-light);
}
/* Las líneas bajo el conteo llevan el mismo aire que la pista bajo el
   título (`.card-head-text .card-hint`). */
.bulkbar .card-head-text > p { margin-top: var(--space-050); }
.table thead th.cell-check + th.th-lead,
.table thead th.cell-check ~ th.th-lead {
    padding-left: calc(var(--cell-thumb) + var(--space-150) + var(--space-200));
}
.table tbody tr:hover { background: var(--bg); }
/* La fila que abre una ficha (`data-row-href`, `js/rows.js`): la mano dice
   que toda la fila es el enlace, no solo el nombre. */
html.js .table tbody tr[data-row-href] { cursor: pointer; }
/* La fila que se está editando desde el formulario de arriba de la tabla.
   Con la tabla desplazada, el campo y la fila no se ven a la vez: esto es lo
   único que las relaciona. `background-color` y no el atajo `background`, que
   le borraría la flecha a cualquier `select` que caiga dentro. */
.row-editing td { background-color: var(--primary-soft); }
/* ── El pie que totaliza ───────────────────────────────────────────────────
   Lo pinta `ui/_total.html`. RESPIRA MÁS que una fila del cuerpo (Renzo,
   2026-08-17): una fila se recorre y el total se lee, y con el mismo alto que
   sus cincuenta vecinas se lee como la fila cincuenta y uno. Los 11px de
   antes eran los de una fila.

   El relleno horizontal NO se declara acá. Con el atajo `padding` se borraba
   el que la regla de los extremos le acababa de dar —va después y gana— y la
   fila de totales sobresalía por los dos lados de su propia tabla. Es la misma
   forma de defecto que ya costó tres veces en esta hoja, ahora por un atajo y
   no por especificidad. */
/* FILAS DE SECCIÓN Y DE SUBTOTAL dentro del cuerpo (2026-09-17). Los estados
   financieros agrupan cuentas por sección y cierran cada sección con su
   total, y escribían esas filas con `<th>`: la hoja solo viste el `thead`,
   así que el navegador las centraba y las ponía en negrita a su manera
   (Renzo, capturas del flujo de efectivo y el balance). La sección se lee
   como un encabezado menor, en la tinta apagada del `thead`; el subtotal
   cierra la sección con una raya fina y el total general con la raya doble
   del pie. Ver «Tabla» en docs/marco-ui.md. */
.table tbody tr.row-group > td {
    padding-top: var(--space-200); padding-bottom: var(--space-100);
    font-size: var(--fs-small); font-weight: 600; letter-spacing: .04em;
    text-transform: uppercase; color: var(--text-light);
    background: var(--table-head-bg);
}
.table tbody tr.row-total > td { font-weight: 600; border-top: 1px solid var(--border); }
.table tbody tr.row-total-strong > td { font-weight: 700; border-top: 2px solid var(--border); }
/* El encabezado de DOS NIVELES de una planilla (nómina, 2026-10-16): arriba
   los grupos —Devengos, Deducciones— sobre sus columnas, abajo las columnas.
   El renglón de grupos no lleva la línea de abajo salvo bajo cada grupo:
   así la raya dice qué columnas abarca, y las celdas vacías de los lados no
   dibujan una reja sobre Integrante ni sobre el neto. */
.table thead tr.th-groups > th { padding-bottom: 0; border-bottom: 0; }
.table thead tr.th-groups > th.th-group {
    text-align: center; color: var(--text);
    border-bottom: 1px solid var(--border);
}
/* La PRIMERA COLUMNA que se queda al desplazar de lado (`table-pin-first`,
   nómina, 2026-10-16). Una planilla de trece columnas se desplaza dentro de
   su tarjeta y, sin esto, el nombre de la persona sale por la izquierda y
   las cifras quedan huérfanas. Se fija la celda MARCADA —`th.th-lead` en el
   encabezado, `.cell-pin` en el resto—, no `:first-child`: si mañana la
   primera celda fuera otra, no se pegaría una columna por accidente.

   `sticky` necesita un fondo OPACO o las cifras se leen por debajo. Fuera de
   una lista, el de la tarjeta (y el del encabezado en el `thead`). Dentro de
   `.content > .card-table` y `#results` la tarjeta es un degradado
   translúcido y el encabezado va sin fondo (más abajo), así que el relleno
   es ese mismo degradado PUESTO SOBRE `--bg`: varios fondos apilados dan un
   color opaco que se ve igual que la tarjeta, en claro y en oscuro. Esas
   reglas llevan el alcance de la lista porque, si no, `.content >
   .card-table .table thead th { background: none }` pesa más y las deja
   transparentes; `tests/test_stylesheet.py` lo vigila. */
.table-pin-first th.th-lead,
.table-pin-first .cell-pin {
    position: sticky; left: 0; z-index: 1;
}
.table-pin-first tbody td.cell-pin,
.table-pin-first tfoot td.cell-pin { background: var(--card-bg); }
.table-pin-first thead th.th-lead,
.table-pin-first thead th.cell-pin { background: var(--table-head-bg); }
.table-pin-first tbody tr:hover td.cell-pin { background: var(--bg); }
#results .card-table .table-pin-first thead th.th-lead,
#results .card-table .table-pin-first thead th.cell-pin,
.content > .card-table .table-pin-first thead th.th-lead,
.content > .card-table .table-pin-first thead th.cell-pin {
    background: linear-gradient(var(--lista-arriba), var(--lista-arriba)), var(--bg);
}
#results .card-table .table-pin-first tbody td.cell-pin,
.content > .card-table .table-pin-first tbody td.cell-pin {
    background: linear-gradient(var(--lista-abajo), var(--lista-abajo)), var(--bg);
}
#results .card-table .table-pin-first tbody tr:hover td.cell-pin,
.content > .card-table .table-pin-first tbody tr:hover td.cell-pin {
    background:
        linear-gradient(color-mix(in srgb, var(--primary) 4%, transparent),
                        color-mix(in srgb, var(--primary) 4%, transparent)),
        linear-gradient(var(--lista-abajo), var(--lista-abajo)), var(--bg);
}

/* La casilla que se escribe DENTRO de una celda (la vista «Novedades» de
   la planilla de nómina, 2026-10-16): angosta, para que quepan once por
   fila, y con la cifra a la derecha como la columna que la lleva. Debajo,
   en `cell-sub`, el valor que el sistema calculó. */
.table .cell-input {
    width: 7rem;
    text-align: right;
}
/* La tabla de lo que un pago aplica (recibo de caja y comprobante de
   egreso, Santiago 2026-11-03: «las columnas no están uniformes»). Ancho
   FIJO en porcentajes, como Reabastecer: el documento se queda con lo que
   sobra, las dos cifras miden igual y «Completo» va al centro. La casilla
   llena su columna. */
.table-aplicar { table-layout: fixed; min-width: 36rem; }
.table-aplicar .col-doc { width: auto; }
.table-aplicar .col-fecha { width: 16%; }
.table-aplicar .col-plata { width: 20%; }
.table-aplicar .col-marca { width: 13%; }
.table-aplicar .cell-center { text-align: center; }
.table-aplicar .cell-input { width: 100%; }

/* La que lleva TEXTO y no cifra (el SKU, el código de barras del editor
   masivo, 2026-10-27): más ancha y a la izquierda, que es como se lee. */
.table .cell-input-texto {
    width: 11rem;
    text-align: left;
}

/* Las mismas casillas de UNA persona, en vertical (su nómina, 2026-10-17):
   una fila por casilla. Desde 2026-11-06 llevan cuatro columnas —la
   novedad, el valor de la hora, a cuánto se paga y la casilla con su
   total— y van a todo el ancho de la tarjeta: a su medida dejaban media
   tarjeta en blanco (Santiago lo vio en la nómina de Angela). */
.table.table-casillas { width: 100%; }
.table.table-casillas .cell-rotulo {
    padding: var(--space-050) var(--space-200) var(--space-050) var(--space-250);
    text-align: left; font-weight: 500; color: var(--text);
    vertical-align: middle;
    border-bottom: 1px solid var(--table-line);
}
.table.table-casillas tbody tr:last-child > .cell-rotulo { border-bottom: 0; }

.table tfoot td {
    /* EL MISMO relleno lateral que una celda del cuerpo, y por eso escrito y
       no heredado: la regla base es `.table tbody td` y nunca alcanzó al pie,
       así que sus celdas se quedaban con el `padding: 1px` que trae el
       navegador. La cifra caía 15px a la izquierda de la columna que
       totaliza, y a simple vista se lee como un número puesto en el aire
       (Renzo, 2026-08-17: «nada que está alineado»). Medido en el navegador,
       que es la única forma de cazar esto: el `<td>` estaba en su sitio y lo
       corrido era su contenido. */
    padding: var(--space-200) var(--space-200);
    border-top: 2px solid var(--border);
    font-weight: 700;
}
/* El rótulo, con el peso de un nombre de fila y en la tinta del texto: el
   `700` de la regla de arriba lo heredaba también el segundo renglón, y una
   aclaración en negrita deja de leerse como aclaración. */
.total-label { font-weight: 600; color: var(--text); }
.table tfoot .cell-sub { font-weight: 400; }
/* La CIFRA es lo que se viene a leer: en tinta de marca y con cifras de ancho
   fijo para que cuadre con la columna que cierra.

   En `--fs-stat`, que es el token de «una cifra de un vistazo» —el de la tira
   del escritorio— y no `--fs-section`: aquél mide 13.5px contra los 13 del
   rótulo de al lado, o sea medio píxel de diferencia, que no es una jerarquía,
   es una casualidad. `--fs-number` es la cifra que ES el contenido de una
   pantalla entera, y ésta cierra una tabla. */
.total-value {
    font-size: var(--fs-stat); color: var(--primary-dark);
    font-variant-numeric: tabular-nums;
}
/* La cifra en su propio elemento, para que el rótulo chico de encima no
   herede su tamaño ni su tinta. */
.total-amount { display: block; }
/* El rótulo chico ENCIMA de la cifra («Total sin IVA (por unidad)»): dice de
   qué es el número sin alargar el rótulo de la izquierda. En gris y en el
   cuerpo de siempre, para que la cifra siga siendo lo que se lee. */
.total-caption {
    display: block; font-size: var(--fs-small); font-weight: 400;
    color: var(--text-light); font-variant-numeric: normal; margin-bottom: var(--space-025);
}
/* El pie como FRANJA (Renzo, 2026-09-24): fondo suave de marca de lado a
   lado, más aire, y un filete vertical que separa el rótulo de la cifra. Las
   esquinas de abajo siguen las de la tarjeta, que es donde termina. */
/* El fondo va en la FILA y no en las celdas: el pie tiene dos celdas y la
   tabla puede tener más columnas (la de los botones), y con el fondo en las
   celdas la franja se cortaba antes del borde. La fila se pinta de lado a
   lado aunque le falten celdas. */
.total-panel { background: var(--primary-soft); }
.total-panel td { background: transparent; padding-top: var(--space-200); padding-bottom: var(--space-200); }
.total-panel .total-value { border-left: 1px solid var(--primary-soft-border); padding-left: var(--space-250); }
/* Lo que se suma ANTES del total, en la columna de la cifra (`parts_aside`):
   rótulo a la izquierda y monto a la derecha, en peso normal, y una raya que
   lo separa del total. Se lee de arriba abajo como una factura. */
.total-partes-lado { display: grid; gap: var(--space-050); margin: 0 0 var(--space-150);
  padding-bottom: var(--space-150); border-bottom: 1px solid var(--primary-soft-border);
  font-size: var(--fs-body); font-weight: 400; }
/* Como un RECIBO (Santiago, 2026-11-05: «los totales de abajo están muy
   lejos de los valores»): dos columnas que miden lo justo, pegadas a la
   derecha; cada nombre se alinea contra su cifra y las cifras contra el
   borde, el mismo del total de abajo. Antes cada renglón era un flex de
   lado a lado y el nombre quedaba en la otra punta. */
.total-partes-lado { grid-template-columns: auto auto; justify-content: end; column-gap: var(--space-300); }
.total-partes-lado > div { display: contents; }
.total-partes-lado dt { color: var(--text-light); text-align: right; }
.total-partes-lado dd { margin: 0; color: var(--text); white-space: nowrap; text-align: right; }
.total-panel.total-danger { background: var(--danger-bg); }
/* Una suma PARCIAL. El fondo entero y no solo la cifra: lo que está
   incompleto es el renglón, no el número — y teñir solo la cifra deja el
   rótulo diciendo «Total» en negro, que es la mitad que se lee primero. */
.total-danger td { color: var(--danger-text); background: var(--danger-bg); }
.total-danger .total-value, .total-danger .total-label { color: var(--danger-text); }
/* Los renglones que se suman ANTES del total (`parts` de `ui/_total.html`):
   subtotal e impuestos, como en una factura. En la letra del cuerpo, con el
   rótulo pegado a su cifra; el filete grueso lo lleva el primero, y el total
   que los cierra baja a uno fino para no partir el pie en dos. */
.table tfoot .total-parte td {
    border-top: 0; padding-top: var(--space-100); padding-bottom: var(--space-100);
    font-weight: 400; color: var(--text-light); font-variant-numeric: tabular-nums;
}
.table tfoot .total-parte:first-child td { border-top: 2px solid var(--border); padding-top: var(--space-200); }
.table tfoot .total-parte .num { color: var(--text); }
.total-parte-label { text-align: right; }
.table tfoot .total-parte + tr:not(.total-parte) td { border-top: 1px solid var(--border); }
/* Una celda de PLATA. A la derecha para que las cifras cuadren por su unidad:
   alineadas a la izquierda, «$1.400» y «$14.537» arrancan juntas y terminan en
   dos columnas distintas, y comparar dos números obliga a leerlos enteros. */
.table .cell-num { text-align: right; }

/* `.cell-pad` VIVIÓ AQUÍ, y se retiró el 2026-08-14.

   Existió porque las celdas no llevaban relleno horizontal: la separación
   salía del ancho del contenido, que alcanza hasta que el contenido es más
   angosto que su rótulo. Un interruptor bajo «CONCILIABLE» y una pastilla
   bajo «ESTADO» dejaban leer «CONCILIABLEESTADO» en el plan de cuentas, y
   la clase se le ponía a la celda corta para separarla de su vecina.

   Se retira porque el arreglo estaba en el lugar equivocado. La misma falla
   apareció en productos —«Accesorios4», el tipo pegado a las variantes— en
   una celda donde nadie se acordó de ponerla, y ése es el punto: un defecto
   que se corrige celda por celda vuelve en la próxima tabla que se escriba.
   Ahora el relleno lo pone `.table tbody td`, y ochenta y seis usos de la
   clase se quitaron con ella.

   No la vuelvas a escribir: `tests/test_marco_ui.py` lo caza, y una celda
   con las dos cosas queda con el doble de relleno que sus vecinas. */

/* La celda de TEXTO LARGO no manda el ancho de la tabla.

   `.table` fija `white-space: nowrap`, y con eso un solo nombre de 149
   caracteres —13551510, «Anticipo de retencion en la fuente en Compra de
   bienes…»— le fija el ancho a toda su columna y empuja el resto de la
   tabla fuera de la pantalla: 1.534 filas pagan el scroll horizontal de
   una. La celda que puede crecer declara que PARTE línea, con tope, y el
   tope va en `ch` porque lo que se está limitando es texto. */
.cell-wrap {
    white-space: normal;
    max-width: 56ch;
}
/* La GEMELA de `.cell-wrap`: la celda cuyo texto largo no vale la pena leer
   entero en la lista —el nombre de un archivo subido, «social_renzo.biffa_
   httpss.mj.run…_354cf540-6555-….mp4»— corta con «…» en vez de partir línea.
   Partido, un nombre así ocupaba tres renglones y la fila triplicaba su alto;
   entero, empujaba Tipo y Peso fuera de la tarjeta (Renzo, 2026-11-02). El
   tope va en el bloque de texto de la celda y no en el `td`, que con la tabla
   en `auto` no lo respeta; el nombre entero queda en el `title`. */
.cell-clip .cell-text { max-width: 40ch; }
.cell-clip .cell-text > * {
    display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Sin miniatura, el bloque de texto va directo en el `td`: en línea no
   respetaría el tope. Y `--corto` para cuando dos columnas de texto van lado
   a lado —Nombre y Contacto en la lista de terceros, Renzo 2026-11-03:
   «apretar todo para que se pueda leer en una sola pantalla»—. */
.cell-clip > .cell-text { display: block; }
.cell-clip--corto .cell-text { max-width: 28ch; }
.nowrap { white-space: nowrap; }

/* La celda de una CANTIDAD, y el rótulo que la encabeza.

   A la derecha porque es lo que alinea las unidades bajo las unidades: en una
   columna centrada o a la izquierda, 8 y 1.240 empiezan en el mismo punto y
   comparar dos sedes de un vistazo deja de ser posible. Y con cifras
   tabulares porque la letra del ERP es proporcional —el 1 mide menos que el
   8—, así que sin esto las columnas de la pantalla de existencias quedan
   dentadas aunque todas terminen alineadas.

   El `th` lleva la misma clase: un encabezado a la izquierda sobre una columna
   a la derecha se lee como si rotulara la columna de al lado. Y va NOMBRADO en
   el selector porque `.table thead th` fija `text-align: left` y le gana por
   especificidad —(0,1,1) contra (0,1,0)—: es la misma cascada que ya dejó sin
   efecto a `.field-check` y al botón destructivo dentro de una tabla, con la
   regla escrita y sin hacer nada. */
.num,
.table thead th.num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Las columnas de CIFRAS del mismo ancho (`table-cifras` en el `<table>`).

   Con el ancho automático cada columna mide su rótulo: «Comprometido» y «No
   disponible» salían anchas, «Valor» angosta, y la fila de números se leía
   dentada aunque cada cifra estuviera a la derecha (Santiago, 2026-10-27,
   Existencias). El mismo PORCENTAJE en todas las reparte por igual a
   cualquier ancho —un `rem` fijo sacaba la tabla de la tarjeta en un portátil—;
   una cifra que no cabe la estira igual, y lo que sobra se lo queda la columna
   de texto. Nació en Existencias y en las líneas de un movimiento. */
.table-cifras th.num,
.table-cifras td.num { width: 9%; }
/* Las tablas de Impuestos (Santiago, 2026-11-03: «el tipo debería estar
   alineado en todas partes»): con el ancho automático cada tarjeta medía sus
   columnas por su contenido y Tipo, Base y Valor caían en otra vertical en
   cada una. Fijas, las cuatro columnas son las mismas en todas. */
.tabla-impuestos { table-layout: fixed; }
.tabla-impuestos th:nth-child(2) { width: 16%; }
.tabla-impuestos th.num { width: 17%; }
.tabla-impuestos td:first-child { overflow-wrap: anywhere; }
/* La cartera por cliente (Santiago, 2026-11-06: «las tablas no respetan
   dónde van las columnas»): una tabla por cliente, y cada una medía sus
   columnas por lo suyo. Fijas, Documento, Vence y las siete cifras caen en
   la misma vertical en todas; la del total junta Documento y Vence en su
   primera columna. */
.tabla-cartera { table-layout: fixed; min-width: 56rem; }
.tabla-cartera th:nth-child(1) { width: 22%; }
.tabla-cartera th:nth-child(2) { width: 12%; }
.tabla-cartera th.num { width: 9.4%; }
.tabla-cartera--total th:nth-child(1) { width: 34%; }
.tabla-cartera td:first-child { overflow-wrap: anywhere; }
/* El nombre largo de una cuenta parte en renglones en vez de montarse sobre
   la columna Tipo (`.link-next` no parte, a propósito, en las demás). */
.tabla-impuestos td:first-child .link-next { white-space: normal; }
/* La casilla de una cifra en la fila (lo contado, lo tecleado) mide una cifra
   y no la caja de un formulario: a su ancho de siempre, la columna se comía
   el doble y la tabla de un ajuste se salía de la tarjeta. */
.table-cifras td.num input[type="number"] { width: 6rem; }
/* Con su unidad ADENTRO (`input-unit`, la tabla de un traslado, 2026-10-30):
   la misma medida que «Pedir» en Reabastecer, y la cifra a la derecha. */
.table-cifras td.num .input-unit { display: inline-flex; width: 8.5rem; }
.table-cifras td.num .input-unit > input[type="number"] { width: auto; text-align: right; }
/* El motivo de una baja, en su columna de la tabla de un movimiento: un
   desplegable chico y no las tarjetas de la captura, que en la fila ocupaban
   media pantalla. Sin motivo va en ámbar: es lo que falta para confirmar. */
.table .cell-motivo-col { width: 11rem; }
.table .cell-motivo { display: block; }
.table .cell-motivo select {
    width: 100%; text-align: left; padding: var(--space-050) var(--space-400) var(--space-050) var(--space-100); font-size: var(--fs-small);
    background-position: right 10px center;
}
.table .cell-motivo select[aria-invalid="true"] {
    border-color: var(--warn-text); background-color: var(--warn-bg); color: var(--warn-text);
}
.table .cell-error { margin-top: var(--space-050); white-space: normal; text-align: right; }

/* El encabezado que ordena.

   Hereda el color apagado del `th` y solo se enciende al activarse: dieciocho
   encabezados clicables pintados con énfasis compiten con los datos, que es lo
   que la tabla existe para mostrar.

   El `inherit` de `color` y `font` no es adorno: sin él el `<a>` toma el azul
   de enlace del navegador y la fila de encabezados se lee como una barra de
   navegación en vez de como los rótulos de las columnas. */
.table thead th .th-sort {
    display: inline-flex; align-items: center; gap: var(--space-050);
    color: inherit; font: inherit; letter-spacing: inherit;
    text-decoration: none; cursor: pointer;
}
.table thead th .th-sort:hover { color: var(--text); }
.table thead th .th-sorted-asc,
.table thead th .th-sorted-desc { color: var(--text); }

/* La flecha. `chevron` apunta abajo, o sea descendente —lo más reciente
   primero, que es como abre toda tabla del ERP—; ascendente lo gira. Un solo
   glifo para los dos estados evita que el set crezca por una rotación. */
.th-arrow {
    display: inline-flex;
    /* El glifo del set viene a 24px y acá es una marca al lado de un rótulo
       de 11px: sin el tope tapa la palabra que califica. */
    width: 14px; height: 14px;
}
.th-arrow svg { width: 100%; height: 100%; }
.th-sorted-asc .th-arrow { transform: rotate(180deg); }

/* El renglón del total de un desglose, en sus dos estados.

   Los DOS existen, y el segundo es el que importa: cuando a un costo le falta
   una línea porque un proveedor no contestó, pintar la suma con el mismo
   énfasis que un total completo es exactamente el «número limpio que miente»
   que §11 prohíbe. Quien lo lee lo usa para poner un precio.

   Se distingue por color y además por la PALABRA —«Suma parcial» en vez de
   «Total», que la pone la plantilla—: el color solo no le dice nada a quien
   no distingue el rojo del gris.

   Se retiró el 2026-08-17: el pie de esa tabla pasó a `ui/_total.html`, y el
   tono degradado es ahora `.total-danger` —el `danger` del vocabulario de
   siempre— en vez de una clase con el nombre de su única pantalla. Las dos
   reglas que vivían acá se fueron con ella. */

/* La fila de algo YA HECHO: el registro de DNS que la comprobación encontró
   puesto. Existía en el marcado sin una sola regla —la clase estaba, el color
   no—, así que la tabla de registros se leía toda igual y había que ir pastilla
   por pastilla para saber cuáles faltaban, que es justo lo que la pantalla
   viene a contestar de un vistazo.

   En el fondo de la celda y no en el de la `<tr>`: `.table tbody tr:hover`
   pinta la fila, y un fondo en la fila lo tapa el del hover. Es la misma forma
   que `.total-danger` de la fila de totales. */
.row-done td { background: var(--ok-bg); }

/* El VALOR de un registro de DNS. Una clave DKIM son 200 y pico de caracteres
   sin un solo espacio donde partir, así que sin esto la celda estira la tabla
   entera y las otras tres columnas se aplastan. `anywhere` y no `break-all`:
   parte sólo cuando hace falta, y un valor corto —`ltdc.agrupa.co`— se queda
   entero en su renglón.

   Es la misma forma que ya tenía la referencia de la pasarela en la ficha del
   pago; acá la clase estaba puesta y la regla no existía. */
.record-value { word-break: break-word; overflow-wrap: anywhere; }

/* El renglón de NOTA que cuelga del registro de arriba: es una continuación de
   esa fila, no una fila más. Sin borde por encima —el que la tabla le pone a
   toda fila la separaría justo de aquello a lo que pertenece— y sin el relleno
   de arriba, para que se lea pegada. */
.row-note td { border-top: 0; padding-top: 0; }

/* El FONDO va en la clase base y no solo en los modificadores. Sin él, un
   `class="pill"` a secas —los papeles de un tercero, el estado de una
   ubicación— salía como texto en negrita al lado de una pastilla de verdad.
   Una clase llamada «pill» que no se ve como una pastilla es un defecto de la
   clase base, no de quien la usa. */
/* La pastilla, en FRASE NORMAL desde el 2026-08-14.

   Era versalitas de 10px. A ese tamaño la caja alta no es énfasis, es una
   multa de legibilidad: «SIN VARIANTES» y «CONCILIABLE» se deletrean, y en
   una fila de tabla la pastilla compite con el nombre del producto que sí
   hay que leer. Lo que hace que un estado se lea como estado es el FONDO de
   color y la forma redondeada, no la caja de la letra.

   Alcanza a TODO el ERP —fichas, banners, el POS, el KDS—, no solo a las
   tablas, y eso se decidió a sabiendas (Renzo, 2026-08-14): dos formas de
   la misma pastilla según la pantalla es peor que una sola que cambia.

   El texto lo escriben las plantillas ya en frase normal («Activo»,
   «Borrador»), así que quitar el `text-transform` no deja ninguna en
   minúsculas: era la CSS la que las gritaba. */
.pill {
    display: inline-block; padding: var(--space-025) var(--space-150); border-radius: 999px;
    font-size: var(--fs-small); font-weight: 500;
    background: var(--pill-bg); color: var(--pill-text);
    white-space: nowrap;
}
/* La chica (`ui/_pill.html` con `size="small"`, 2026-11-05): la marca que va
   dentro de una celda, bajo el texto —«Sin producto» en la línea de una
   factura—. Más baja y angosta, con la letra de una nota. */
.pill.pill-small { padding: 0 var(--space-100); font-size: var(--fs-badge); line-height: 16px; }
/* Los cuatro estados, con la familia VIVA. La pastilla es lo único que la
   usa: es la pieza del tamaño de una palabra, donde el tono pálido no
   alcanza a leerse como estado. Los avisos y las filas teñidas siguen con la
   familia pálida — ver el comentario de los tokens. */
.pill-ok     { background: var(--ok-vivid);     color: var(--ok-vivid-text); }
.pill-danger { background: var(--danger-vivid); color: var(--danger-vivid-text); }
.pill-warn   { background: var(--warn-vivid);   color: var(--warn-vivid-text); }
.pill-muted  { background: var(--bg);        color: var(--text-light); }
/* Lo que está PASANDO, que no es ni un logro ni una alarma: el pedido que
   alguien está empacando. En verde se leería como despachado y en ámbar como
   un problema. */
.pill-info   { background: var(--info-vivid); color: var(--info-vivid-text); }

/* La pastilla CON glifo, que pinta `ui/_pill.html`. Es un modificador y no la
   clase base porque `.pill` es `inline-block` en más de cien sitios y pasarla
   entera a `inline-flex` le cambiaría la línea base a todos por un caso.

   El relleno de la izquierda baja: el glifo ya trae su propio aire y con el
   relleno completo la pastilla queda descentrada hacia la derecha. */
.pill-icon {
    display: inline-flex; align-items: center; gap: var(--space-050);
    padding-left: var(--space-075); vertical-align: middle;
}
/* 11px y no `--fs-badge`: el glifo tiene que leerse a la altura de una
   versalita de 10px, y a 10px un trazo de 2.4 se empasta. */
.pill-icon svg { width: 11px; height: 11px; flex: none; }

/* La celda que ABRE una fila: una baldosa y el nombre, en la misma celda.
   `inline-flex` y no `flex` para que la celda pueda seguir alineando su
   contenido como cualquier otra —una columna de plata a la derecha, por
   ejemplo— en vez de que el envoltorio se coma el ancho entero.

   Va como envoltorio DENTRO de la celda y no como una columna aparte: una
   columna propia para el icono se lleva un ancho de tabla sin decir nada, y
   al reordenar columnas el icono se separa de lo que acompaña. */
/* `gap` de `--sp-3` y no de `--sp-2`, o sea el MISMO que `.cell-media`.

   Las dos abren una fila con algo cuadrado y su nombre al lado, y estaban a
   8px una y a 12 la otra. Con los dos tamaños ya iguales, el hueco distinto
   era lo último que hacía que dos listas del mismo ERP se leyeran con otro
   ritmo. Y no es solo estética: `.th-lead` corre el rótulo de esa columna
   `--cell-thumb + --sp-3`, así que con 8px el encabezado de estas treinta y
   cinco caería 4px a la derecha del nombre que rotula. */
.cell-lead { display: inline-flex; align-items: center; gap: var(--space-150); min-width: 0; }

/* La flecha que abre las variantes de un producto en la lista de etiquetas.

   Un BOTÓN y no un enlace: no lleva a ninguna parte, despliega lo que ya
   está en la página. Va del tamaño de una celda para que la columna no baile
   entre una fila con flecha y una sin ella — 226 de los 476 productos de LTDC
   tienen una sola variante y no la llevan, así que la mitad de las filas
   alternarían de sangría si esto no ocupara su sitio siempre. */
/* En su RECUADRO punteado (Renzo, 2026-10-14): suelta, la flecha se perdía
   junto a la casilla y la miniatura. El hueco de la fila que no se
   despliega (`--vacio`) mide lo mismo, así que la columna sigue alineada. */
.row-toggle {
    flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
    width: 24px; height: 24px; padding: 0;
    border: 1px dashed var(--border-strong); border-radius: 8px; background: none;
    color: var(--text-light); font-size: var(--fs-small); line-height: 1;
    cursor: pointer;
}
.row-toggle:hover { color: var(--primary); border-color: var(--primary); background: var(--primary-soft); }
.row-toggle-on { color: var(--primary); border-color: var(--primary); }
/* El hueco de la flecha en la fila que no se despliega: mide lo mismo y no
   se ve, para que todas las filas arranquen en la misma columna. */
.row-toggle--vacio { visibility: hidden; }
/* El chevron apunta a la derecha cerrado y abajo abierto: es el mismo trazo
   de `{% icon 'chevron' %}` que ya usan los encabezados ordenables, girado. */
.row-toggle svg { width: 14px; height: 14px; transform: rotate(-90deg); transition: transform .12s ease; }
.row-toggle-on svg { transform: rotate(0deg); }

/* LA LÍNEA DEL ÁRBOL. La dibuja el CSS y no un carácter en el marcado: un
   «└» escrito en la celda se lo lee un lector de pantalla como contenido, y
   lo que quiere decir —«esto cuelga de lo de arriba»— ya lo dice la sangría
   para quien ve y el orden del DOM para quien escucha.

   El codo cuelga desde arriba de la fila hasta su centro y gira: sin el
   giro, catorce variantes seguidas se leen como una lista suelta y no como
   las hijas de la familia que quedó arriba. */
/* LA SANGRÍA DE UNA FILA CON ÁRBOL es mayor que la de una fila sangrada a
   secas, y no es capricho: el codo arranca en `--sp-5` (20) y mide `--sp-3`
   (12), o sea que termina en 32. Con los 24 de `cell-indent` el texto empezaba
   ANTES de que la línea acabara y la línea lo cruzaba —«Grano / 340g» salía
   tachado (Renzo, 2026-09-09)—. Los 40 dejan ocho de aire después del codo.

   Va sobre `.cell-tree` y no cambiando `.cell-indent`: esa la comparte el
   menú de la tienda, que sangra sin dibujar ningún codo y a la que los 24 le
   sientan bien.

   Y la celda del árbol NO lleva además `cell-indent`, aunque la sangría sea
   lo que las dos hacen: con las dos clases puestas ganaba `.cell-indent` por
   ir más abajo en la hoja —misma especificidad (0,2,1)— y la sangría se
   quedaba en 24. Es la cuarta vez en dos días que la cascada de este archivo
   muerde por el orden y no por el peso. */
/* `--tree-level` es EN CUÁL ESCALÓN va esta fila: 1 la hija, 2 la nieta. Lo
   escribe quien pinta la tabla, en línea sobre la celda, porque es un dato de
   la fila y no de la pantalla —igual que `--callout-tint` en el bloque de
   aviso—. Sin él vale 1, así que la lista de etiquetas, que solo tiene un
   nivel, sigue pintando exactamente igual que antes de que esto existiera.

   Cada escalón corre el codo y el texto `--sp-5` (20) más a la derecha: es lo
   que ya separa al primer escalón del margen, así que el ojo lee el mismo
   paso dos veces en vez de dos pasos distintos. Lo estrenó el árbol de
   Ubicaciones (Sede Central con sus bodegas adentro), donde una nieta y su
   madre sangradas igual se leen como hermanas. */
/* DOS SELECTORES para la misma declaración, y es la cascada otra vez.

   `.table tbody td:first-child { padding-left: var(--sp-5) }` —los 20px que
   separan la primera columna del filo— pesa (0,2,2) contra los (0,2,1) de
   `.table td.cell-tree`: gana el filo y la sangría no existe. En la lista de
   etiquetas nunca se notó porque ahí la celda del árbol NO es la primera —va
   detrás de la casilla de marcar—, así que la pieza llevaba el defecto
   dormido desde el 2026-09-09. Ubicaciones, que no tiene casillas, lo
   despertó: las cuatro bodegas salían a ras de su sede.

   La variante `:first-child` sube a (0,3,2) y gana siempre, esté la celda
   donde esté. No se arregla al revés —excluyendo `.cell-tree` del selector
   del filo— porque esa regla también la usan el `thead` y el pie, y tocarla
   mueve tablas que no tienen nada que ver con esto. */
.table td.cell-tree,
.table tbody td.cell-tree:first-child {
    position: relative;
    padding-left: calc(var(--space-500) + (var(--tree-level, 1) - 1) * var(--space-250));
}
.table td.cell-tree::before {
    content: ""; position: absolute; top: 0; bottom: 50%;
    left: calc(var(--space-250) + (var(--tree-level, 1) - 1) * var(--space-250));
    width: 1px; background: var(--border);
}
.table td.cell-tree::after {
    content: ""; position: absolute; top: 50%;
    left: calc(var(--space-250) + (var(--tree-level, 1) - 1) * var(--space-250));
    width: var(--space-150); height: 1px; background: var(--border);
}
/* La última hija corta el tramo vertical en el codo: si sigue de largo,
   parece que abajo viene otra hermana que no está. */
.table tr.row-child:last-of-type td.cell-tree::before { bottom: 50%; }
/* La hija de una tabla con la primera columna fija (el editor masivo, las
   variantes, 2026-11-01): `.table td.cell-tree` le ponía `position: relative`
   y le ganaba a `.cell-pin`, así que el producto se quedaba y su variante se
   iba con el desplazamiento. `sticky` también sirve de base para la rama
   (`::before`/`::after`), así que no se pierde nada. */
.table.table-pin-first tbody td.cell-pin.cell-tree { position: sticky; }

/* La captura de un conteo se REPINTA ENTERA con cada escaneo (`#count-lines`,
   swap `outerHTML`). Sin esto el navegador ancla el desplazamiento a algo de
   adentro y, con la tabla apilada —diez mil píxeles de tarjetas—, cada
   escaneo mandaba la pantalla al final: la casilla y su verde o ámbar
   quedaban ocho mil píxeles arriba, con la pistola en la mano (2026-10-29).
   Fuera del ancla, el navegador se ancla a lo de arriba, que no cambia. */
#count-lines { overflow-anchor: none; }
/* Lo mismo con la captura de Movimientos (`#lines`, 2026-11-05): marcar un
   lote, «−», «+» o el motivo en una hoja larga mandaban la pantalla al
   final. El salto hacia arriba —el `autofocus` de la casilla— lo devuelve
   `assets/js/quieto.js`. */
#lines { overflow-anchor: none; }

/* ── La tabla que en ANGOSTO se apila (`.table-apilada`) ──────────────────
   En el teléfono, o en una ventana partida, cada fila es una TARJETA: la
   primera celda —lo que nombra la fila— a todo el ancho, y las demás debajo,
   en renglón, envolviendo, cada una con su rótulo (`data-label` en la celda,
   porque el `thead` sale de la vista: sigue en el árbol para el lector de
   pantalla). Una celda VACÍA no se pinta, así que la plantilla la escribe sin
   espacios adentro (`<td>{% if … %}…{% endif %}</td>`): con un salto de
   línea ya no es `:empty` y se queda con su hueco.

   Por el ancho del CONTENIDO y no de la ventana (`@container content`): con
   el panel lateral abierto o la ventana partida, la tabla se angosta sin que
   la ventana cambie. 800 y no los 640 del teléfono: en una ventana partida
   el contenido mide unos 700 y la tabla de cinco columnas del conteo pide
   877, así que también ahí se iba de lado. Nació en la captura de un conteo
   (2026-10-26), donde a 375 px «Contado», «Diferencia» y «No hay» quedaban
   fuera, detrás de un desplazamiento lateral. Recibir un envío tenía la
   misma idea escrita solo para ella (`.table-recibir`, 2026-10-16) y en la
   misma rama pasó a usar ésta: de allá salieron `.cell-ancha` y el
   `.stepper` a lo ancho con botones de dedo. */
@container content (max-width: 800px) {
    .table.table-apilada, .table.table-apilada tbody { display: block; }
    .table.table-apilada thead {
        position: absolute; width: 1px; height: 1px; margin: -1px;
        overflow: hidden; clip-path: inset(50%);
    }
    .table.table-apilada tbody tr {
        display: flex; flex-wrap: wrap; align-items: center;
        gap: var(--space-050) var(--space-200);
        padding: var(--space-150) var(--space-200);
        border-bottom: 1px solid var(--table-line);
    }
    .table.table-apilada tbody tr:last-child { border-bottom: 0; }
    .table.table-apilada tbody td,
    .table.table-apilada tbody td:first-child,
    .table.table-apilada tbody td:last-child {
        display: block; padding: 0; border: 0; background: none;
        text-align: left; white-space: normal; min-width: 0;
    }
    .table.table-apilada tbody td:first-child { flex: 1 0 100%; color: var(--text); }
    /* `.cell-ancha`: la celda del CONTROL —el «Llegó» de recibir— va sola en
       su renglón, al final de la tarjeta, con el rótulo encima; los datos
       cortos quedan juntos arriba de ella. */
    .table.table-apilada tbody td.cell-ancha { flex: 1 0 100%; order: 1; }
    .table.table-apilada tbody td.cell-ancha[data-label]::before {
        display: block; margin: 0 0 var(--space-050);
    }
    .table.table-apilada td.cell-ancha .stepper { width: 100%; }
    .table.table-apilada .stepper-btn { width: 56px; min-height: 44px; }
    .table.table-apilada tbody td:empty { display: none; }
    .table.table-apilada tbody td[data-label]::before {
        content: attr(data-label); margin-right: var(--space-100);
        color: var(--text-light); font-size: var(--fs-small);
    }
    /* El fondo va en la FILA: en celdas, el hueco entre ellas queda blanco. */
    .table.table-apilada tr.row-editing { background-color: var(--primary-soft); }
    .table.table-apilada tr.row-child { background-color: var(--bg); }
    /* La hija conserva su sangría; el codo se queda en la celda, que ahora
       mide solo su texto, y sin la raya larga se lee como sangría y punto. */
    .table.table-apilada tbody td.cell-tree,
    .table.table-apilada tbody td.cell-tree:first-child {
        padding-left: calc(var(--space-250) + (var(--tree-level, 1) - 1) * var(--space-250));
    }
    .table.table-apilada td.cell-tree::before,
    .table.table-apilada td.cell-tree::after { left: calc((var(--tree-level, 1) - 1) * var(--space-250)); }
    /* Un botón de la fila (`.row-actions`) va al lado de su dato, no debajo. */
    .table.table-apilada tbody td .row-actions {
        display: inline-flex; vertical-align: middle; margin-left: var(--space-100);
    }
}

/* ── Piezas sueltas de cabecera de lista ──────────────────────────────────
   Las estrenó la lista de etiquetas, que hasta el 2026-10-20 tenía su propio
   buscador, sus pestañas y su pie. Hoy usa la barra de filtros de la casa;
   quedan las que siguen usando otras pantallas. */
/* `.card-header .list-title` y no `.list-title` a secas: dentro del
   encabezado manda `.card-header h2`, que con (0,1,1) le gana a una clase
   suelta con (0,1,0). Es la MISMA trampa que este archivo ya documenta unas
   líneas más arriba para `.card-count`, y volví a caer en ella. */
.card-header .list-title {
    margin: 0; font-size: var(--fs-section); font-weight: 600;
    letter-spacing: 0; text-transform: none;
}
.list-head-tools { display: flex; align-items: center; gap: var(--space-150); flex-wrap: wrap; }
.list-search { position: relative; display: flex; align-items: center; }
/* `input[type="search"]` en el selector y no `input` a secas, y el PADDING
   COMPLETO en una sola declaración.
   
   La regla base de los campos —`textarea, input[type="date"],
   input[type="search"]…`— fija `padding: 11px 13px` con el atajo, tiene la
   misma especificidad (0,1,1) que `.list-search input` y va más abajo: le
   pisaba los dos lados. El síntoma era la lupa encima de la «B» de «Buscar»
   y el ⌘K recortando el texto. Con el tipo en el selector son (0,2,1) y
   gana; con el atajo completo, ningún lado se queda sin poner.
   
   Los 34 y los 44 son el hueco de la lupa y el de la × que limpia: si
   cambian de tamaño, cambian acá. Hasta el 2026-09-15 el de la derecha era
   el de la pista `⌘K`, que se fue con el atajo a la paleta. */
.list-search input[type="search"] { min-width: 280px; padding: var(--space-150) var(--space-500) var(--space-150) var(--space-400); }

/* La línea de avisos de una lista: los datos sucios dichos de un vistazo,
   con el porqué en la ayuda. Va DENTRO de la tarjeta y en una sola línea
   porque un aviso no puede tapar el trabajo. */
.list-note { margin: 0; color: var(--text-light); font-size: var(--fs-small); }
.list-note-item { display: inline-flex; align-items: center; gap: var(--space-050); }
.list-note-item + .list-note-item::before { content: "·"; margin: 0 var(--space-100) 0 0; }

/* El buscador con su lupa y su ×. Las dos van DENTRO del campo: un icono
   fuera obliga a mirar dos sitios para entender un control. */
.list-search-icon {
    position: absolute; left: var(--space-150); display: flex;
    pointer-events: none; color: var(--text-light);
}
.list-search-icon svg { width: 16px; height: 16px; }
/* La celda del kebab: angosta y a la derecha, para que no le robe ancho a
   los datos. */
.table td.cell-actions, .table th.cell-actions { width: 1%; text-align: right; }

/* Un pie DENTRO de una tarjeta de tabla.

   `.card-foot` trae `padding-top` y nada más, porque nació dentro de una
   `.card` con relleno propio: ahí los lados y el fondo se los pone la
   tarjeta. Una `.card-table` no tiene relleno abajo —la tabla llega a los
   bordes a propósito— así que el pie quedaba pegado al borde de abajo. Se
   veía apeñuscado, que es exactamente la palabra que usó Renzo.

   Y a los LADOS llega al filo, como la banda y la tabla: con solo el relleno
   quedaba doble sangría —la raya empezaba 20px adentro del borde y el texto
   40px, corrido contra la baldosa del encabezado—. Medido en la cola de
   Envíos el 2026-09-15 («está muy chueca»): tarjeta 275–999, pie 296–978.

   Va acotado a la combinación y no arreglando `.card-foot`: los otros cinco
   pies del ERP viven en tarjetas con relleno y ahí la regla actual es
   correcta. */
.card-table > .card-foot {
    margin: 0 calc(var(--space-250) * -1); padding: var(--space-200) var(--space-250);
}

/* El segundo renglón de una celda: la razón social debajo del nombre. Sin
   `display: block` salía pegada en la misma línea —«Café del Sur SAS
   Comercializadora Café del Sur SAS»— y parecía un error de datos.

   Pegado al nombre: con el interlineado de la tabla (1,55) y 2px encima, la
   variante quedaba a un renglón de su producto y se leía como otra cosa
   (Renzo, 2026-10-03). Interlineado propio y sin margen. */
.cell-sub {
    display: block; margin-top: 0; line-height: 1.35;
    font-size: var(--fs-meta); color: var(--text-light);
}
/* Su `display: block` le gana al atributo `hidden` (esta hoja no trae un
   `[hidden]` global): el aviso «Sin precio con este proveedor» de la fila
   plantilla de la OC salía en toda línea nueva aunque el script no lo
   encendiera (hallazgo A, 2026-10-27). */
.cell-sub[hidden] { display: none; }
/* Debajo de una PASTILLA sí lleva aire: la pastilla tiene su fondo, y sin
   separación la nota rozaba su borde («Sin elegir» y el porqué en las
   afiliaciones del contrato, Santiago 2026-11-05). */
.pill + .cell-sub { margin-top: var(--space-075); }
/* Las tablas que `columnas-iguales.js` alineó (`data-columnas-fijas`, solo
   después de medir): lo que no cabe en su columna se corta con «…», y el
   script deja el texto entero en su `title`. */
.table[data-columnas-fijas] td,
.table[data-columnas-fijas] td > a,
.table[data-columnas-fijas] td .cell-sub { overflow: hidden; text-overflow: ellipsis; }
.table[data-columnas-fijas] td > a { display: inline-block; max-width: 100%; vertical-align: bottom; }

/* LOS PAGOS DE LA FICHA, que NO son una tabla y por eso viven acá.

   `.table thead th` no lleva relleno horizontal —en una tabla ancha las
   columnas se separan con el espacio sobrante—, así que dentro de la tarjeta
   angosta de la lateral los encabezados se tocaban: «MÉTODOCOMPROBANTEQUIÉN».
   Y `.table-wrap` no salvaba nada: su scroll horizontal esconde la última
   columna, que es la del monto.

   La fila resuelve las dos: cuándo y cuánto en el renglón que se lee, el resto
   apagado debajo, y cabe en cualquier ancho sin desbordar. */
.pay-rows { list-style: none; margin: 0; padding: 0; }
.pay-row { padding: var(--space-150) 0; border-top: 1px solid var(--border); }
.pay-row:first-child { border-top: none; padding-top: 0; }
.pay-row-head { display: flex; align-items: baseline; gap: var(--space-150); }
.pay-row-when { font-size: var(--fs-small); color: var(--text-light); }
/* El monto a la derecha y con cifras de ancho fijo: es la columna que el ojo
   recorre de arriba abajo para sumar, y con cifras proporcionales los miles no
   se alinean. `margin-left: auto` en vez de `justify-content: space-between`
   para que la fecha no se estire cuando el monto es corto. */
.pay-row-amount { margin-left: auto; font-variant-numeric: tabular-nums; }
.pay-row-sub {
    margin: var(--space-025) 0 0; font-size: var(--fs-meta); color: var(--text-light);
    /* Envuelve, y tiene que hacerlo: el método, quién y la referencia sumados
       pasan del ancho de la tarjeta. Lo único que NO se parte es la
       referencia, que va en su propio span. */
    overflow-wrap: anywhere;
}
/* La referencia de la pasarela: 40 caracteres sin un espacio donde partir. El
   corte lo hace el servidor con `truncatechars` —el valor entero viaja en el
   `title`, que es donde alguien la va a buscar para pegarla en el panel de
   Wompi—; acá solo se le prohíbe partirse a mitad. */
.pay-row-ref { white-space: nowrap; }
/* «Pagada … 06 Ago 2026 · 14:18», el renglón de tiendapp: el rótulo apagado a
   la izquierda y el cuándo a la derecha. Va como par y no como `.facts`
   porque es UNO solo — un `dl` de una fila trae la rejilla de dos columnas
   para nada. */
.pay-settled {
    display: flex; justify-content: space-between; gap: var(--space-150);
    margin: var(--space-200) 0 var(--space-150);
    font-size: var(--fs-small); color: var(--text-light);
}

/* Una celda que dice que no hay nada —«Sin productos»— frente a otras que sí
   dicen algo. Va apagada porque es la AUSENCIA del dato: en su columna las
   demás celdas son enlaces, y con el mismo color el ojo se detiene en la única
   que no lleva a ninguna parte. */
.cell-muted { color: var(--text-light); }

.pager {
    display: flex; align-items: center; justify-content: center; gap: var(--space-150);
    margin-top: var(--space-200);
}
.pager-status {
    color: var(--text-light); font-size: var(--fs-body);
    font-variant-numeric: tabular-nums;
}
.pager .btn { padding: var(--space-100) var(--space-200); }

/* ── Tarjeta y feed ────────────────────────────────────────────────────── */

.card {
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius-card); box-shadow: var(--shadow);
    padding: var(--space-200) var(--space-250);
}
/* Una PILA de tarjetas: las separa el contenedor, y por eso hay un solo sitio
   donde está escrito cuánto.

   El primer intento fue `.card + .card { margin-top }`, por hermano adyacente,
   y se pisó con lo que ya existía: `.product-main` y `.product-rail` ya
   separaban con `gap`, así que las tarjetas de la ficha del producto pasaron a
   32px —16 de gap más 16 de margen— sin que nadie lo pidiera. Un margen en el
   hijo y un `gap` en el padre resuelven lo mismo y se suman, que es la forma de
   equivocarse propia de esta pareja.

   `.product-main` y `.product-rail` son esta misma idea con dos nombres más
   viejos; se dejan porque además fijan columna y `min-width`. */
.card-stack { display: flex; flex-direction: column; gap: var(--space-200); min-width: 0; }
/* El aviso trae su `margin-top` para cuando va suelto; dentro de una pila el
   `gap` ya lo separa y los dos se sumaban: el doble de aire entre las cifras
   y el aviso de Por pagar (2026-11-04). */
.card-stack > .tip { margin-top: 0; }

/* Y la separación POR OMISIÓN, para la pantalla que no envuelve nada.
   `.card-stack` es opt-in: hay que acordarse de ponerlo, y con 27 usos contra
   154 tarjetas la mayoría de las pantallas no lo tenía. Sus tarjetas no salían
   «un poco juntas» — salían PEGADAS, con el borde de una contra el de la
   siguiente, y lo que parecía un hueco era la sombra. Dominios, medido el
   2026-08-11: `bottom: 464` y `top: 464`.

   Va sobre los HIJOS DIRECTOS de `.content`, y ahí está el porqué de que no se
   sume con lo de arriba: una tarjeta dentro de una pila es `.card-stack > .card`
   y no `.content > .card`, así que esta regla no la alcanza. Es la trampa que
   se documentó al escribir `.card-stack` —un margen en el hijo y un `gap` en el
   padre resuelven lo mismo y se suman— evitada por selector en vez de por
   acordarse.

   El hermano adyacente y no `margin-bottom`: la última tarjeta no cuelga un
   margen del que después alguien tenga que defenderse.

   Alcanza también a `.card-stack` y a `.table-wrap` sueltos porque el problema
   no es «la tarjeta»: es que dos bloques de contenido seguidos se toquen.

   Lo que sigue a una tarjeta es CUALQUIER COSA y no otra tarjeta, y esa
   generalización es la que costó una regresión. El modelo de lista del ERP no
   pone la tabla como hija de `.content`: la pone dentro de `#results`, que es
   el destino del intercambio de htmx. Con la regla escrita como
   `.card + .card` la barra de filtros y su tabla quedaban PEGADAS en las
   cincuenta y una listas —medido en Productos: `bottom: 390`, `top: 390`—
   porque en medio hay un `<div id="results">` que no es una tarjeta.

   Antes las separaba el `margin-bottom` de `.filters-card`, el mismo que se
   quitó arriba por sumarse dentro de una pila. Quitarlo sin generalizar esto
   fue cambiar un defecto por otro. */
.content > .card + *,
.content > .card-stack + *,
.content > .table-wrap + *,
.content > .ficha-seg + *,
.content > #results + * { margin-top: var(--space-200); }
/* Y DENTRO del destino del intercambio: Solicitudes pinta dos tarjetas
   —«Para resolver» y «Las mías»— dentro de `#results`, y ahí ninguna de las
   reglas de arriba alcanza (no son hijas de `.content`). Salían pegadas
   (Renzo, 2026-09-14). Solo tarjeta con tarjeta: una tabla con su paginador
   se separan solos. */
#results > .card + .card,
#results > .tip + .card { margin-top: var(--space-200); }   /* el aviso de arriba y su tarjeta (Conciliación sin mapeo, 2026-11-01) */

/* Al aterrizar en un ancla (`/ajustes/perfil/empresa/#seccion-marca`), la
   barra de arriba es fija y tapaba el encabezado de la tarjeta: se llegaba
   «más abajo» y no se sabía dónde (Renzo, 2026-09-14). El margen de
   desplazamiento deja la tarjeta entera a la vista, con su aire. */
.content [id] { scroll-margin-top: calc(var(--topbar) + var(--space-200)); }

/* ── El banner de una ficha: avatar, pastillas y cifras ────────────────────
   Lo estrena el tercero y lo va a querer igual el empleado de la F2, así que
   nada de esto se llama `party-*`. */

/* La inicial sobre el tinte de marca, como la primera celda de una tabla. Un
   cuadro gris vacío se lee como algo que falta. */
.banner-avatar {
    flex: none; display: grid; place-items: center;
    width: 46px; height: 46px; border-radius: 50%;
    background: var(--primary); color: #fff;
    font-size: var(--fs-section); font-weight: 600; text-transform: uppercase;
}
/* En LÍNEA con el nombre, dentro del `h1`. `inline-flex` con `vertical-align`
   y no `flex`: como bloque rompería el renglón del título y volvería a quedar
   debajo, que es de donde se la trajo. */
.banner-badges {
    display: inline-flex; flex-wrap: wrap; gap: var(--space-075);
    margin-left: var(--space-150); vertical-align: middle;
}
/* Pastilla del banner: fondo propio y no transparente, porque sobre la banda
   oscura una pastilla sin relleno es un borde flotando.

   La FORMA la llevan también las variantes de color: media docena de
   pantallas escriben `badge-soft-muted` o `badge-soft-info` a secas, y sin
   esto salían como texto resaltado sin redondear (Renzo, 2026-09-12, en
   la ficha de una serie de tareas). */
.badge-soft, .badge-soft-danger, .badge-soft-warn, .badge-soft-info,
.badge-soft-muted {
    display: inline-block; padding: var(--space-050) var(--space-150); border-radius: 999px;
    font-size: var(--fs-small); font-weight: 600; line-height: 1.4;
}
.badge-soft { background: var(--ok-bg); color: var(--ok-text); }
.badge-soft-danger { background: var(--danger-bg); color: var(--danger-text); }
/* Las dos que la banda del pedido necesita para contar en qué va: el ámbar de
   lo que quedó a medias —pago parcial, envío parcial— y el azul de lo que
   está pasando. `preparing` NO puede ir en verde: el verde de `.badge-soft`
   se lee como «listo», y un pedido que se está empacando todavía no salió. */
.badge-soft-warn { background: var(--warn-bg); color: var(--warn-text); }
.badge-soft-info { background: var(--info-bg); color: var(--info-text); }
/* El cargo NO es un estado. Con el verde de `.badge-soft` las dos pastillas del
   encabezado se leían como dos estados y el ojo no sabía cuál mirar; el color
   se lo queda el que dice si la persona sigue trabajando acá. */
.badge-soft-muted { background: var(--pill-bg); color: var(--pill-text); }

/* Las cifras. `dd` ARRIBA y `dt` abajo en el marcado, que es el orden en que se
   leen —el número primero y el rótulo debajo—; el orden semántico del `dl` se
   respeta porque cada par va en su propio `div`. */
.banner-stats {
    display: flex; flex-wrap: wrap; gap: var(--space-150) var(--space-300);
    margin: 0; flex: none;
}
.banner-stats > div { display: flex; flex-direction: column; gap: var(--space-025); }
.banner-stats dd {
    margin: 0; color: #fff;
    font-size: var(--fs-page); font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.banner-stats dt {
    color: var(--banner-sub);
    font-size: var(--fs-badge); font-weight: 600;
    text-transform: uppercase; letter-spacing: .04em;
}

/* Una casilla y su botón en una fila. La casilla se estira; el botón no, o a
   390px se parte en dos renglones con el texto cortado.

   Se llamaba `.comment-row` cuando su único uso era el comentario del tercero.
   El segundo consumidor —los tipos de producto, que se crean y se renombran
   inline— no es un comentario, y una clase con el nombre de su primer caso es
   la que después nadie reusa: se copia la regla con otro nombre y quedan dos
   formas distintas de la misma fila. */
.inline-row {
    display: flex; gap: var(--space-150); align-items: center;
    /* Con `nowrap` la casilla paga TODO lo que no cabe: medido a 434px, con el
       rótulo «Renombrar «Prueba F1e»» y los dos botones de esa fila, quedaba de
       26 píxeles — un campo de texto en el que no se lee lo que se escribe. */
    flex-wrap: wrap;
}
/* La casilla se estira, y `220px` es su base: es lo que la baja a su propio
   renglón antes de dejarse aplastar. `min-width: 0` para que ese renglón pueda
   ser más angosto que la base cuando la pantalla lo es. */
.inline-row input { flex: 1 1 220px; min-width: 0; }
.inline-row .btn { flex: none; }
/* El «cuánto» de una regla de descuento en un renglón: «2,65 % + $700 por
   venta» (recaudadores, 2026-10-22). Las casillas son de un número corto, no
   de una frase: con la base de 220px de `.inline-row` cada una ocupaba media
   tabla y el renglón se leía como dos campos sueltos. */
.tarifa { display: flex; align-items: center; gap: var(--space-100); white-space: nowrap; }
.tarifa input { width: 5.5rem; flex: none; }
/* Un campo CON RÓTULO dentro de la fila: `.field` trae el rótulo arriba y 17px
   de margen abajo, y con `align-items: center` el botón quedaba a media altura
   entre el rótulo y la casilla («Asignar» junto a «Repartidor» en la ficha de
   la ruta, 2026-09-14). El campo pierde el margen y el botón baja al borde,
   que es donde está la casilla. Y dos campos en la fila se alinean por
   ARRIBA: con una pista debajo de uno solo («Clave» en Repartidores),
   centrarlos dejaba el rótulo del otro flotando a media altura. */
.inline-row > .field { margin-bottom: 0; align-self: flex-start; }
/* Menos la CASILLA: no tiene rótulo arriba, así que alineada por arriba
   la pastilla quedaba a la altura de los rótulos de al lado y no de sus
   controles («Obligatorio» junto a «Máximo» en la lista de un tipo de
   mantenimiento, 2026-10-23). Va al piso, como en `.form-grid`; y sin el
   margen de la pastilla, que en la fila lo pone el `gap`. */
.inline-row > .field-toggle { align-self: flex-end; }
.inline-row > .field-toggle > .field-check { margin-bottom: 0; }
/* Y mide lo que mide el control de al lado: el botón de la casa trae 11px de
   relleno y `line-height: 1` (39px) y la casilla 36px; alineados por abajo,
   los 3px de más asomaban por arriba (Renzo, 2026-09-14, «Asignar» junto a
   «Repartidor»). Una sola altura para los dos, escrita una vez. */
.inline-row > .field select,
.inline-row > .field input:not([type="checkbox"]):not([type="radio"]) { height: 36px; }
.inline-row > .field ~ .btn {
    align-self: flex-end; height: 36px; padding-top: 0; padding-bottom: 0;
}
/* Lo mismo con la casilla SUELTA, sin `.field` alrededor: el campo de
   orígenes de una app medía 34px de casilla contra 41 de «Agregar» y el botón
   asomaba por arriba y por abajo (Renzo, 2026-11-01). El botón chico se queda
   en lo suyo: es el de una fila de tabla, junto a una casilla de PIN. */
.inline-row > input:not([type="checkbox"]):not([type="radio"]) { height: 36px; }
.inline-row > input ~ .btn:not(.btn-small) {
    height: 36px; padding-top: 0; padding-bottom: 0;
}
/* La misma fila DENTRO de una celda de acciones (la clave nueva del
   repartidor con su Guardar): ahí no se parte, porque a dos renglones la
   fila mide más que la tabla y se lee como dos controles sueltos. La
   casilla se acorta a lo que cabe: es un PIN, no una frase. */
.row-actions .inline-row { flex-wrap: nowrap; }
.row-actions .inline-row input { flex: 0 1 150px; min-width: 110px; }
/* La misma fila cuando vive entre el encabezado de una tarjeta y su tabla.
   Relleno POR LOS CUATRO LADOS y no solo abajo: la fila es un bloque de
   trabajo —se escribe y se aprieta Agregar— y pegada al filo del relleno de la
   tarjeta se leía como parte del encabezado de arriba en vez de como algo
   aparte. Los 12px de más a los lados son a propósito: es lo que la separa de
   la tabla que viene debajo, que sí llega al borde.

   Alcanza a las seis filas de este tipo —las dos de líneas de costo, las dos de
   canales y las dos de colecciones—, que es justamente lo que se quiere: una
   sola forma para la misma fila. */
.inline-row-card { padding: var(--space-150) 0; }
/* La fila con la que se AGREGA una condición: al pie de la tarjeta y en su
   propio panel. Puesta al final sin fondo se lee como una condición más —una
   a medio llenar—, y el panel es lo que dice «esto todavía no es una».

   El margen de abajo lo pone ella y no la tarjeta: `.card-table` lleva
   `padding-bottom: 0` para que una tabla llegue al filo, y un panel pegado al
   borde inferior se ve cortado. */
.rule-builder {
    margin-bottom: var(--space-200); padding: var(--space-150) var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--bg);
}
/* Una barra con su rótulo (`.meter`) ENTRE el encabezado de una tarjeta de
   tabla y la tabla: «¿En qué se le va el servidor?» de la Flota (2026-10-09).
   `.card-table` le quita el margen al encabezado para pegarlo al `thead`;
   la barra lo devuelve arriba y se separa de la tabla abajo. */
.card-table > .meter { margin: var(--space-150) 0 var(--space-200); }
/* EL ALTA PLEGADA: «＋ Agregar regla de IVA» al pie de la tabla, y el
   formulario recién al abrirla. La Configuración contable tiene once altas, y
   abiertas todas a la vez la pantalla era once formularios vacíos con una
   tabla encima de cada uno: lo que se venía a LEER —qué reglas hay— pesaba
   menos que lo que casi nunca se usa (Renzo, 2026-09-20: «todavía lo veo muy
   raro»). Es el plegable de la casa (`details.fold` + `ui/_fold_head.html`),
   no uno nuevo; acá solo va lo que cambia por vivir dentro de una tarjeta.

   Una tarjeta con DOS altas —ICA y autorretención, una tasa a mano y un rango
   oficial— lleva dos plegables, y el rótulo de cada uno dice cuál es.

   El margen de abajo lo pone el último y no la tarjeta, por lo mismo que en
   `.rule-builder`: `.card-table` lleva `padding-bottom: 0`. */
.card-table > .rule-add { margin-top: var(--space-150); }
.card-table > .rule-add:last-child { margin-bottom: var(--space-200); }
.rule-add > .fold-body { padding: var(--space-200); background: var(--bg); }
/* Y el formulario es UNA FILA: cada casilla con su rótulo y el botón al
   final, a la altura de las casillas (eso ya lo hace `.inline-row > .field`).
   Tres anchos y no una rejilla pareja: en tres columnas iguales el «%» medía
   lo mismo que la cuenta, sobraba media fila y el botón caía solo en un
   tercer renglón. `field-short` para lo que son cuatro dígitos, `field-grow`
   para la cuenta, que es la que trae nombre. Cuando no cabe, envuelve. Y con
   TOPE: un alta de dos casillas en una tarjeta de 1.200px estiraba la cuenta
   a lo ancho de la pantalla y el botón quedaba a un metro de lo que envía. */
.rule-row { align-items: flex-start; }
.rule-row > .field { flex: 1 1 170px; min-width: 0; max-width: 280px; }
.rule-row > .field-short { flex: 0 1 112px; }
.rule-row > .field-grow { flex: 2 1 260px; max-width: 460px; }
@media (max-width: 720px) {
    .rule-row > .field, .rule-row > .field-grow { max-width: none; }
}
/* Afiliaciones del contrato (Santiago, 2026-11-05): una fila por afiliación,
   con la entidad y la fecha del cambio lado a lado, y aire entre una y otra.
   La fecha dice «Fecha del cambio» adentro: con los 112px de `field-short`
   se cortaba. Lo del contrato, separado de lo de la persona. */
.afiliacion-fila { margin-bottom: var(--space-250); }
.afiliacion-fila > .rule-row { flex-wrap: nowrap; }
.afiliacion-fila > .rule-row > .field-short { flex: 0 0 152px; }
.afiliacion-contrato { margin-top: var(--space-400); }
/* Un rótulo en dos renglones baja SU casilla y la fila deja de ser una fila. */
.rule-row > .field > label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Un monto tampoco necesita el ancho de un concepto: son seis dígitos. Sin el
   tope, la casilla de la plata mide lo mismo que la del texto y la fila se lee
   como dos campos iguales cuando no lo son.

   Va con `.inline-row` DELANTE por la cascada, y es la tercera vez que esta
   forma aparece en el proyecto: `.inline-row input` tiene una clase y un
   elemento, así que le ganaba a `.input-amount`, que tiene una clase sola. La
   regla estaba escrita y no hacía nada — medido en el navegador: 255px, el
   mismo ancho que el concepto. Igual que `.field-check` contra `.field label`
   y que el botón destructivo dentro de una tabla. */
.inline-row .input-amount { flex: 0 1 150px; }

/* Los TRES controles de una condición, tratados como uno.

   Campo, operador y valor son una sola idea —«tipo de producto es Café»— y
   el botón «Agregar» es otra. Sin el envoltorio, la fila reparte su espacio
   entre cuatro cosas y al angostarse el botón se va solo a su renglón
   dejando los selects arriba; con él, la condición se parte por dentro y el
   botón la sigue.

   La clase existía en el marcado desde `de8d0a0` SIN una sola regla, o sea
   que el `<div>` era de bloque y los tres selects se apilaban uno debajo de
   otro. La guarda de huérfanas lo cazó: es exactamente para esto.

   `min-width: 0` en los selects porque «Todavía no has creado ninguno» mide
   más que la columna que le toca, y sin eso ese `<option>` le fija el ancho
   a la fila entera. */
.inline-row-controls {
    display: flex; gap: var(--space-150); align-items: center; flex-wrap: wrap;
    flex: 1 1 auto; min-width: 0;
}
.inline-row-controls select { min-width: 0; }

/* ── El editor de valores de una opción ────────────────────────────────────
   Filas que se arrastran para reordenar. El orden es información —es el que
   van a tener las opciones en la variante— y con un `textarea` cambiarlo era
   cortar y pegar texto. */
.value-editor { margin-bottom: var(--space-200); }
.value-rows { list-style: none; margin: 0 0 var(--space-150); padding: 0; }
.value-row {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-100) 0;
}
/* La casilla del valor es la que crece; los botones no se encogen ni se
   estiran, o a 390px «Quitar» se parte en dos renglones. */
.value-row input { flex: 1 1 auto; min-width: 0; }
.value-row .btn { flex: none; }
/* El asa dice «esto se arrastra». `grab` y no `move`: el cursor de mover
   promete que ya lo estás moviendo. */
.value-grip {
    flex: none; cursor: grab; user-select: none;
    padding: 0 var(--space-050); color: var(--text-faint);
    font-size: var(--fs-body); line-height: 1;
}
/* El botón deshabilitado —la flecha de la primera o la última fila— se apaga
   en vez de desaparecer: una fila con tres botones y la de al lado con dos
   baila cada vez que se reordena. */
.value-row .btn:disabled { opacity: .35; cursor: default; }

/* ── El campo de fichas ────────────────────────────────────────────────────
   Lo elegido es lo único que se ve. Con 22 tipos en casillas, saber cuáles
   están marcados obliga a barrer los veintidós.

   Se llaman `token` y no `chip` porque `.chip` YA existe: es la pastilla de
   solo lectura que pinta los valores de un eje en la tabla de variantes.
   Reusar el nombre habría dejado dos reglas para la misma clase, con la de
   más abajo ganando en las dos pantallas — y hay una prueba que lo caza. */
/* `relative` porque la lista cuelga del campo: dentro del flujo empujaría
   todo lo de abajo cada vez que alguien pone el cursor en la caja, y un
   formulario que salta mientras se usa se lee como una pantalla rota. */
.token-field { position: relative; margin-bottom: var(--space-200); }
/* Al final de su tarjeta el margen se sumaba al relleno de la tarjeta: 32px
   de blanco bajo los alcances de una app. La separación la pone quien viene
   después, no el último. */
.token-field:last-child { margin-bottom: 0; }
/* El campo de ORÍGENES (`ui/_origins.html`): la caja de fichas y, debajo, su
   fila de agregar. Pegadas a 4px se leían como un solo control; el aire lo
   pone el contenedor. */
.token-field-stack { display: flex; flex-direction: column; gap: var(--space-150); }
/* La caja ENTERA es el control desde el 2026-09-09 —las fichas y el buscador
   viven adentro—, así que se pinta como se pinta un campo: mismo borde,
   mismo radio, y el foco se dibuja acá y no en el `input` de adentro, que no
   tiene borde propio. `cursor: text` porque eso es lo que promete. */
.tokens {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100);
    min-height: 44px; padding: var(--space-100) var(--space-150);
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card-bg); cursor: text;
}
.tokens.is-open { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
/* Sin borde ni fondo: el que se ve es el de la caja. `flex: 1` con una base
   chica para que quepa junto a las fichas y crezca al renglón siguiente
   cuando ya no. */
/* Con `.token-field` delante por la cascada: la regla genérica de los
   campos es `input[type="text"]` —una clase y un atributo— y le ganaba a
   una clase sola, así que el buscador salía con su propio borde y su
   propio fondo DENTRO de la caja de fichas: dos cajas, una adentro de la
   otra. Es la quinta vez que esta forma aparece en el proyecto. */
.token-field .token-input {
    flex: 1 1 90px; min-width: 90px; width: auto; padding: var(--space-050) 0;
    border: 0; border-radius: 0; background: transparent; color: var(--text);
    font: inherit; font-size: var(--fs-small);
}
.token-field .token-input:focus { outline: none; box-shadow: none; }
/* CERRADO por omisión y abierto por una clase atada. Ver la nota del
   parcial: `x-show` no aplica su estado inicial sobre un nodo que htmx
   inyecta, y así el fallo cae del lado bueno — un menú que no se abre se
   ve; tres menús abiertos encima del formulario, también, pero tarde. */
.token-menu {
    display: none;
    position: absolute; z-index: 20; left: 0; right: 0; top: 100%;
    max-height: 260px; overflow-y: auto; margin-top: var(--space-050); padding: var(--space-050);
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card-bg); box-shadow: var(--shadow-lg);
}
/* Botones y no filas con un `click`: se tabulan, se accionan con Enter, y el
   lector de pantalla las anuncia como lo que son. */
.token-menu.is-open { display: block; }
.token-option {
    display: block; width: 100%; padding: var(--space-100) var(--space-150);
    border: 0; border-radius: var(--radius-sm);
    background: transparent; color: var(--text);
    font-size: var(--fs-small); text-align: left; cursor: pointer;
}
.token-option.is-active { background: var(--primary-soft); color: var(--primary-dark); }
/* «Crear «…»» del campo con alta libre (`create`): escondida hasta que lo
   escrito no existe. La prende `pintar()`, como a `.token-none`. */
.token-create:not(.is-shown) { display: none; }
.token-create { font-weight: 600; color: var(--primary-dark); }
.token-none {
    display: none; margin: 0; padding: var(--space-100) var(--space-150);
    font-size: var(--fs-small); color: var(--text-light);
}
.token-none.is-shown { display: block; }
/* Con `faces` (personas, 2026-10-08): la fila es la del buscador de la
   casa —cara, nombre y detalle— y la ficha lleva la cara en chico, la
   misma de `.choice-face`: dos tamaños de la misma cara se leen como la
   misma persona. */
.token-option-persona { display: flex; align-items: center; gap: var(--space-150); }
.token-con-cara { padding-left: var(--space-050); }
/* `.token .token-cara` y no `.token-cara`: `.finder-thumb` (38 px) viene más
   abajo en esta hoja y con la misma fuerza le ganaba, y la cara de la ficha
   salía del tamaño de la del buscador (visto el 2026-10-18 en la calculadora). */
.token .token-cara {
    width: 22px; height: 22px; border-radius: 6px;
    font-size: var(--fs-badge);
}
.token-preset { display: none; margin-top: var(--space-100); }
.token-preset.is-shown { display: inline-block; }
.token {
    display: inline-flex; align-items: center; gap: var(--space-075);
    padding: var(--space-050) var(--space-075) var(--space-050) var(--space-150);
    font-size: var(--fs-small); font-weight: 600; color: var(--text);
    background: var(--primary-soft); border-radius: 999px;
}
/* La equis es un BOTÓN y no un aspa dibujada: se tabula, se acciona con
   Enter, y su objetivo de clic mide lo que mide un dedo. */
.token-remove {
    display: inline-flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; padding: 0;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--primary-dark);
    font-size: var(--fs-body); line-height: 1; cursor: pointer;
}
.token-remove:hover { background: rgba(255, 255, 255, .7); }
.token-remove:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* «y 231 más» del campo plegado (2026-10-27): del alto de una ficha para que
   se lea como la que sigue, pero con borde y sin relleno, para que se lea
   como algo que se toca y no como un cliente más. Escondido hasta que
   `pintar()` lo prende, como `.token-preset`. */
.token-more {
    display: none; align-items: center;
    padding: var(--space-050) var(--space-150);
    border: 1px solid var(--border); border-radius: 999px;
    background: transparent; color: var(--primary-dark);
    font-size: var(--fs-small); font-weight: 600; cursor: pointer;
}
.token-more.is-shown { display: inline-flex; }
.token-more:hover { background: var(--primary-soft); }
.token-more:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.tokens-empty { font-size: var(--fs-small); color: var(--text-light); }

/* Una lista de casillas en columna: cada una es una decisión, y repartidas en
   dos columnas hay que barrer de lado a lado para saber si falta alguna.
   `align-items: start` o cada pastilla se estira al ancho de la más larga. */
.check-list {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: var(--space-100); margin-bottom: var(--space-200);
}
/* Dentro de la lista el aire lo pone `gap`: la pastilla trae margen propio
   para cuando va suelta, y sumados los dos la lista respira el doble — las
   tres pastillas de Apps se leían como tres tarjetas separadas. */
.check-list .field-check { margin-bottom: 0; }

/* Una casilla con un prefijo pegado: lo que se teclea es un pedazo de una
   dirección, y sin ver el resto no se sabe qué se está escribiendo. El prefijo
   es TEXTO —no un control— y por eso no lleva borde propio ni recibe foco. */
.prefixed { display: flex; align-items: center; gap: 0; }
.field-prefix {
    flex: none; padding-right: var(--space-100);
    font-size: var(--fs-small); color: var(--text-light);
    white-space: nowrap;
}
/* Con `.prefixed` delante por la cascada: la regla genérica del `input` de una
   fila tiene una clase y un elemento y le ganaría a una clase sola. Es la misma
   forma que ya costó tres defectos. */
.prefixed input { flex: 1 1 auto; min-width: 0; }

/* Los dos radios de la coincidencia, en fila y dentro de la tarjeta.

   Con relleno ARRIBA (Renzo, 2026-08-16, con la pantalla enfrente). Iba en
   cero, que estaba bien mientras las pastillas eran dos casillas de texto
   plano: una casilla no tiene borde propio y el aire se lo daba su renglón.
   Las pastillas SÍ lo tienen, y pegadas al filo del encabezado se leen como
   parte de la banda de arriba en vez de como el primer control de la tarjeta.
   Es el mismo argumento por el que `.inline-row-card` lleva relleno por
   arriba.

   Y a los lados CERO (Renzo, con la pantalla enfrente): el relleno lateral lo
   pone `.card`, y repetir `--sp-5` acá lo sumaba — las pastillas arrancaban
   21px más adentro que las condiciones de abajo y que el panel de agregar, o
   sea la única fila de la tarjeta que no alineaba con las demás. Es la misma
   trampa que `.card-lines`, que sí lo repite porque su fila va sangrada a
   propósito. */
.match-row {
    display: flex; flex-wrap: wrap; gap: var(--space-150);
    padding: var(--space-200) 0;
}

/* ── Las líneas de una tarjeta ─────────────────────────────────────────────
   Una fila por cosa, con su borde arriba, dentro de una tarjeta: las líneas
   de costo de un producto y las condiciones de una colección automática. En
   las de costo cada fila son DOS formularios —el que edita y el que borra—
   que tienen que quedar en el mismo renglón.

   Se llamaba `.cost-lines` mientras su único consumidor eran los costos. El
   segundo —las condiciones, 2026-08-16— no es un costo, y una clase con el
   nombre de su primer caso es la que después nadie reusa: se copia la regla
   con otro nombre y quedan dos formas de la misma fila. Es la misma lección
   que ya movió `.comment-row` a `.inline-row`. */
/* ── La tarjeta Costo (2026-09-23) ──────────────────────────────────────
   Cinco columnas que tienen que CABER en la tarjeta sin scroll: el concepto
   se estira y las otras cuatro miden lo que mide su contenido —un nombre de
   proveedor, «Impoconsumo 8 %», una cifra, dos íconos—. Con los `select` al
   100 % de la celda el monto y los botones se caían fuera de la pantalla
   (Renzo, 2026-09-23: «la columna de impuestos está gigante»). Las celdas
   llevan menos aire vertical que una tabla de lectura: son controles. */
/* SIN anchos fijos (Renzo, 2026-09-24: «mira cómo se ve», la tarjeta cortada
   a la derecha): en lectura cada columna mide lo que mide su texto y la
   tabla cabe en la tarjeta. Al editar, las casillas llenan la celda que ya
   tienen, con un mínimo para que un nombre de proveedor se lea. */
.cost-table .col-concept input[type="text"] { width: 100%; min-width: 6rem; }
.cost-table .col-supplier select,
.cost-table .col-tax select { width: 100%; min-width: 7rem; }
.cost-table .col-amount .input-amount { width: 6.5rem; }
.cost-row-actions { display: inline-flex; gap: var(--space-050); }
/* En lectura la fila es texto y respira lo mismo que cualquier tabla; en
   edición las casillas ocupan la celda. */
.cost-table tr.is-editing td { padding-top: var(--space-075); padding-bottom: var(--space-075); }
/* La raya del total va de lado a lado. El pie de `ui/_total.html` tiene
   cuatro celdas (colspan=3 + el valor bajo «Monto sin IVA») y la tabla
   cinco: la quinta no existe, y la raya que pone cada celda se cortaba
   antes de los botones (Renzo, 2026-09-23). La raya la pone la última fila
   del cuerpo, que sí tiene las cinco. */
.cost-table tbody tr:last-child td { border-bottom: 1px solid var(--border); }
.cost-table tfoot td { border-top: 0; }
/* El renglón de alta de la misma tarjeta, en UNA línea: el concepto se
   estira y los demás miden lo suyo. Se parte solo cuando no cabe (teléfono). */
.cost-new { flex-wrap: nowrap; }
.cost-new input[type="text"] { flex: 1 1 8rem; }
.cost-new .cost-new-supplier { flex: 0 0 12rem; min-width: 0; }
.cost-new .cost-new-tax { flex: 0 0 9.5rem; min-width: 0; }
.cost-new .input-amount { flex: 0 0 7rem; }
/* Por debajo de un portátil ancho el concepto quedaba en «Nueva lí»: el
   renglón se parte en dos antes de aplastar la casilla que dice qué va. */
@media (max-width: 1100px) {
    .cost-new { flex-wrap: wrap; }
    .cost-new input[type="text"] { flex: 1 1 100%; }
    .cost-new .cost-new-supplier, .cost-new .cost-new-tax { flex: 1 1 10rem; }
}

.card-lines { list-style: none; margin: 0; padding: 0; }
.card-lines li {
    display: flex; align-items: center; gap: var(--space-150);
    flex-wrap: wrap;
    padding: var(--space-100) var(--space-250);
    border-top: 1px solid var(--border);
}
/* La fila de la línea NO envuelve: concepto, monto y «Guardar» son una sola
   cosa y partidos en tres renglones dejan de leerse como una fila. Quien baja
   de renglón cuando no cabe es «Eliminar», que es el `li` el que envuelve. Las
   casillas pueden encogerse porque las dos llevan `min-width: 0`.

   El selector era `form.inline-row` y dejó de acertar cuando la fila pasó a
   ser un `<div>` con `form="…"` —el `<form>` se mudó afuera para poder vivir
   dentro del formulario del producto—, así que las dos reglas quedaron
   escritas sin hacer nada. Es la misma forma de defecto de siempre, ahora por
   un elemento que cambió y no por especificidad. */
.card-lines .inline-row { flex-wrap: nowrap; }
/* La fila de edición se estira; «Eliminar» no. Sin esto queda pegado al monto
   y el renglón se lee como un solo control. */
.card-lines .inline-row { flex: 1 1 auto; min-width: 0; }

/* ── Una condición ya puesta ───────────────────────────────────────────────
   Leída como la frase que es —«Tipo de producto es igual a accesorios»— y no
   como cuatro columnas. Era una `<table>` con encabezado, y con dos
   condiciones eso es más encabezado que dato: la pieza más pesada del marco
   puesta al servicio de la lista más corta. La caja es `.boxed-row`; acá van
   solo las partes de la frase.

   `margin-bottom` en la lista porque el panel de agregar viene justo debajo y
   sin él las dos cosas se tocan. */
.rule-list { margin-bottom: var(--space-200); }
/* Las tres partes se ALINEAN entre filas, que es lo que deja leer cinco
   condiciones de un vistazo en vez de cinco frases de largos distintos. Por
   ancho mínimo y no por una rejilla: la fila es la `.boxed-row` del marco,
   que es flexible y la comparte con el editor de examen — meterle una
   rejilla acá le cambiaría la forma a la otra pantalla. Un campo más largo
   que su mínimo desalinea SU fila y nada más, que es el modo correcto de
   fallar. */
.rule-field { flex: none; min-width: 150px; font-weight: 600; color: var(--text); }
/* El operador en tinta de marca y apagado de peso: es la juntura de la frase,
   no el dato, y en gris de texto competía con el campo. */
.rule-op {
    flex: none; min-width: 96px;
    color: var(--primary-dark); font-size: var(--fs-small);
}
/* El valor empuja al botón contra el filo derecho: es el margen automático y
   no un `flex-grow` en el valor, porque el valor es una PASTILLA y estirarla
   pintaría un óvalo del ancho de la tarjeta para decir «origen». */
.rule-value { margin-right: auto; }
/* El botón, contra el filo y detrás de un separador: sin la raya se lee como
   la última palabra de la frase. `flex: none` para que no lo aplaste el
   operador que se estira. */
.rule-remove {
    flex: none; padding-left: var(--space-150);
    border-left: 1px solid var(--border);
}
/* En ANGOSTO la frase envuelve. Los mínimos de campo y operador alinean
   cinco condiciones en una tarjeta ancha, y en un celular son justo lo que no
   cabe: sumados a una pastilla que no parte («$500.000 · últimos 90 días»),
   la fila pedía 624px dentro de 305 y la página entera se corría 285px de
   lado (medido en la ficha del segmento, 2026-09-12). Por el ancho del
   CONTENIDO y no de la ventana, como la tira del escritorio: con el panel
   lateral abierto el contenido se angosta sin que la ventana cambie. Vale
   igual para las condiciones de una colección, que son esta misma fila. */
@container content (max-width: 640px) {
    .rule-list .boxed-row { flex-wrap: wrap; row-gap: var(--space-100); }
    .rule-field, .rule-op { min-width: 0; }
    .rule-value { min-width: 0; white-space: normal; }
}

/* La nota de una tarjeta que viene pegada a una lista en caja necesita el
   mismo aire que `.field` le deja en las tarjetas de formulario: sin él se lee
   como una fila más de la lista. Salió midiendo la tarjeta de «Desde qué
   bodega sale» contra la de ajustes que tiene debajo — 0px contra 17px. Va por
   hermandad y no con una clase para no pedirle nada al marcado: cualquier
   tarjeta que ponga su nota bajo un `.boxed-list` la hereda. */
.boxed-list + .card-note { margin-top: var(--space-200); }

/* Una nota dentro de una tarjeta: dice algo del contenido que hay debajo y no
   es un error. Apagada porque no es una alarma —lo reemplazado es una decisión
   de quien escribió la línea, no un problema— pero presente, porque un total
   al que le falta una línea sin explicación es un número que miente. */
.card-note {
    /* Flexible desde que lleva un botón al lado: el ofrecimiento de traer el
       logo del tema, en Marca. Sin esto el botón caía al renglón siguiente,
       alineado a la izquierda debajo de su frase, y se leía como otra cosa.
       Una nota sin botón se ve igual que siempre — un solo hijo en una caja
       flexible no cambia de sitio. */
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100);
    margin: 0 0 var(--space-200);
    font-size: var(--fs-small); line-height: 1.5; color: var(--text-light);
}

/* Las viñetas del Resumen (la ficha del descuento, calcada de la de
   Shopify): condiciones y rendimiento como lista corta de hechos. Con sus
   puntos y su sangría propia porque el reset global de listas los quita, y
   una lista de condiciones sin viñetas se lee como un párrafo cortado. */
.summary-list {
    margin: 0 0 var(--space-200); padding-left: var(--space-250);
    list-style: disc;
    font-size: var(--fs-small); line-height: 1.6; color: var(--text);
}
.summary-list li { margin: 0 0 var(--space-050); }

/* Los papeles que explican un documento —hoy la factura de un pedido, mañana
   lo que aporte otro módulo por el mismo hook—. Con caja y borde y no como
   enlace suelto: es el destino más pesado de la tarjeta y compite con una
   tabla de abonos que ocupa diez veces más alto.

   `a:not(.btn)` no hace falta acá porque no está dentro de una tabla, pero el
   ancho SÍ: sin `display: flex` el icono se sienta en la línea base y queda
   dos píxeles bajo el texto. */
.doc-links { list-style: none; margin: 0 0 var(--space-200); padding: 0; display: grid; gap: var(--space-075); }
.doc-link {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-100) var(--space-150);
    border: 1px solid var(--primary-soft-border);
    border-radius: var(--radius);
    background: var(--primary-soft);
    color: var(--primary-dark); font-weight: 600; text-decoration: none;
}
.doc-link svg { width: 15px; height: 15px; flex: none; }
.doc-link:hover { background: var(--bg); }

/* Un identificador NO necesita el ancho de un nombre: son dos o tres palabras
   con guiones. Sin el tope, dos casillas en la misma fila se reparten el
   espacio a la mitad y la que se lee —el nombre— queda igual de angosta que la
   que casi nunca se toca. `flex-grow: 0` o el reparto se lo lleva igual. */
.inline-row .input-handle { flex: 0 1 200px; }
/* El rótulo de la fila: al lado del campo y no encima, porque la fila entera
   mide un renglón. `flex: none` o se estira y le come el ancho a la casilla, que
   es la única que tiene que crecer. `margin-bottom: 0` para ganarle al margen de
   `.field label`, que lo empujaría medio renglón por debajo del campo. */
.inline-row-label {
    flex: none; margin-bottom: 0;
    font-size: var(--fs-small); font-weight: 600; color: var(--text-light);
}

/* La lista del riel: un enlace, su pastilla y una línea de detalle debajo. */
.rail-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.rail-list li {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-075);
    padding: var(--space-100) 0; border-bottom: 1px solid var(--border);
}
.rail-list li:last-child { border-bottom: 0; padding-bottom: 0; }
/* Ocupa el renglón entero: es el detalle de la línea de arriba, no otra cosa
   al lado. */
.rail-list-sub {
    flex: 1 0 100%;
    font-size: var(--fs-meta); color: var(--text-light);
}

/* ── La ficha de lectura: hechos y actividad ───────────────────────────────
   Dos formas que la ficha de un tercero estrena y que van a repetirse en cuanto
   haya una de un empleado o de un pedido: la lista de HECHOS del riel y la
   ACTIVIDAD leída de la bitácora. */

/* `dl` de verdad y no dos columnas de `div`: rótulo y valor son un par, y un
   lector de pantalla lo anuncia como tal. La grilla los alinea sin tabla. */
/* `minmax(0, 1fr)` y no `1fr` en el valor, y `minmax(0, auto)` en el
   rótulo: `1fr` es `minmax(auto, 1fr)`, y ese mínimo `auto` es el
   min-content de la celda. Un dato de una sola palabra larga —un canal
   importado como «Subscription_Contract_Checkout_One_Time», una URL, un
   correo— imponía un mínimo más ancho que la tarjeta y se salía por el
   borde derecho (visto en la ficha de un tercero, 2026-09-15). Con el
   mínimo en cero la columna puede encogerse, y `overflow-wrap` es lo que
   parte esa palabra en vez de estirarla. */
.facts { display: grid; grid-template-columns: minmax(0, auto) minmax(0, 1fr);
         gap: var(--space-100) var(--space-150); margin: 0; }
.facts dt { font-size: var(--fs-label); font-weight: 600; color: var(--text-light);
            overflow-wrap: anywhere; }
/* El valor a la DERECHA, que es como se leen los pares de una ficha: el ojo
   baja por una sola columna en vez de perseguir el final de cada rótulo. */
.facts dd { margin: 0; font-size: var(--fs-small); color: var(--text);
            text-align: right; overflow-wrap: anywhere; }
.facts-text { margin: 0; font-size: var(--fs-small); line-height: 1.5; color: var(--text-light); }
/* La fecha y el vencimiento al final de la tarjeta del total, separados de
   lo que se le aplica (Santiago, 2026-11-05); en el borrador, casillas. */
/* Centradas en su fila: la pastilla «Vencida hace…» o la casilla hacen el
   valor más alto que el rótulo, y arriba el rótulo quedaba corrido
   (Santiago, 2026-11-05). */
.facts-fechas { margin-top: var(--space-150); padding-top: var(--space-150); border-top: 1px solid var(--border);
                align-items: center; }
.facts-fechas dd { display: flex; justify-content: flex-end; align-items: center; gap: var(--space-100); flex-wrap: wrap; }
.facts-fechas input { max-width: 9.5rem; text-align: right; }
/* LA FILA DE UN DATO CON SU BALDOSA (`.fact-row`): la baldosa a la
   izquierda, el rótulo arriba y el valor debajo, a lo ancho. Para la tarjeta
   de pocos datos donde cada uno pesa —el plazo y las observaciones de la
   factura (Renzo, 2026-10-07)— y donde un valor puede ser un PÁRRAFO: a la
   derecha de `.facts` se partía en renglones desparejos contra el borde.
   `.fact-row-big` es la cifra que se lee primero; `.fact-row-aside`, su
   complemento al lado, separado por un filete. */
.fact-row { display: flex; gap: var(--space-150); align-items: flex-start;
            padding: var(--space-150) 0; }
.fact-row + .fact-row { border-top: 1px solid var(--border); }
.tip + .fact-row { margin-top: var(--space-100); }
.fact-row-body { flex: 1; min-width: 0; }
.fact-row-label { margin: 0 0 var(--space-025); font-size: var(--fs-label); font-weight: 600;
                  color: var(--text-light); }
.fact-row-value { margin: 0; font-size: var(--fs-small); line-height: 1.5;
                  color: var(--text); overflow-wrap: anywhere; }
.fact-row-big { font-size: var(--fs-section); font-weight: 700; }
/* Un botón debajo del valor —«Descargar PDF» bajo el número de la factura
   (Santiago, 2026-11-05: «¿no está muy pegado al texto de arriba?»)— lleva
   el aire de un bloque, no el de un renglón. */
.fact-row-value + .btn { margin-top: var(--space-100); }
.fact-row-aside { margin-left: var(--space-150); padding-left: var(--space-150);
                  border-left: 1px solid var(--border); color: var(--text-light);
                  white-space: nowrap; }
/* EN UN RENGLÓN (`.fact-row-inline`): la baldosa y el rótulo a la izquierda,
   el valor a la derecha, que es como se lee una lista de pares cortos —el
   comprador del pedido—. Va como `div` que agrupa un `dt` y su `dd` dentro de
   un `dl` (`.fact-rows`), así que la baldosa vive DENTRO del rótulo. */
.fact-rows { margin: 0; }
/* Apretada a propósito (Renzo, 2026-10-07): son pares de una línea, con la
   baldosa chica (`size="sm"`), y la tarjeta no puede crecer más que la lista
   de hechos que reemplaza. */
.fact-row-inline { align-items: center; justify-content: space-between;
                   padding: var(--space-050) 0; }
.fact-row-inline .fact-row-label { display: flex; align-items: center;
                                   gap: var(--space-150); margin: 0; flex: none;
                                   font-size: var(--fs-small); font-weight: 400; }
.fact-row-inline .fact-row-value { min-width: 0; text-align: right; }
/* CADA FILA EN SU FONDO SUAVE (`.fact-rows-suaves`), todas iguales y con aire
   entre ellas, sin filete —«Quién es» del tercero, Renzo 2026-11-03: «que no
   se vea tan apretado, no intercalemos los colores de fondo, todos
   claritos»; y apretada: «que el card no se vea tan grande»—. La baldosa
   es la de marca, más chica que la de una celda. */
.fact-rows-suaves { display: flex; flex-direction: column; gap: var(--space-050); }
.fact-rows-suaves > .fact-row { padding: var(--space-050) var(--space-100);
                                 border-radius: var(--radius-sm); background: var(--bg); }
.fact-rows-suaves > .fact-row + .fact-row { border-top: 0; }
.fact-rows-suaves .fact-row-label { gap: var(--space-100); }
.fact-rows-suaves .fact-row-label .tile { width: 24px; height: 24px; border-radius: 7px; }
.fact-rows-suaves .fact-row-label .tile svg { width: 14px; height: 14px; }
.fact-rows-suaves .fact-row-value { overflow-wrap: anywhere; }
/* EN FRANJA (`.fact-rows-franja`): los datos uno al lado del otro en su
   recuadro, cada uno con su baldosa, el rótulo arriba y el valor debajo, y
   un filete entre ellos. El pie de La idea de una campaña: formatos, cuándo
   sale y lo que cuesta (Renzo, 2026-10-31). Angosta, vuelven a apilarse. */
.fact-rows-franja { display: flex; padding: var(--space-150) 0;
                    border: 1px solid var(--border); border-radius: var(--radius); }
.fact-rows-franja > .fact-row { flex: 1 1 auto; min-width: 0; align-items: center;
                                padding: var(--space-050) var(--space-200); }
.fact-rows-franja > .fact-row + .fact-row { border-top: 0; border-left: 1px solid var(--border); }
.fact-rows-franja .fact-row-label { font-weight: 400; }
.fact-rows-franja .fact-row-value { font-size: var(--fs-body); font-weight: 500; overflow-wrap: normal; }
/* Sin ancho para todos en un renglón (la pantalla mide su `.content`), de a
   dos; en el celular, uno debajo del otro. */
@container (max-width: 1100px) {
    .fact-rows-franja { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: var(--space-150); }
    .fact-rows-franja > .fact-row:nth-child(odd) { border-left: 0; }
}
@container (max-width: 600px) {
    .fact-rows-franja { grid-template-columns: minmax(0, 1fr); row-gap: 0; padding: 0 var(--space-200); }
    .fact-rows-franja > .fact-row { padding: var(--space-150) 0; }
    .fact-rows-franja > .fact-row + .fact-row { border-left: 0; border-top: 1px solid var(--border); }
}
/* Un valor que no cabe: UNA línea con elipsis y la salida al lado
   (`ui/_valor_largo.html`). El texto es lo que se encoge —`min-width: 0`, la
   misma razón de siempre— y el botón nunca: cortarle el rótulo a la salida
   sería el chiste de la pantalla. Alineado a la derecha porque el valor de
   una ficha se lee por su final. */
.valor-largo { display: flex; align-items: center; justify-content: flex-end;
               gap: var(--space-100); max-width: 100%; min-width: 0; }
.valor-largo-texto { min-width: 0; overflow: hidden;
                     text-overflow: ellipsis; white-space: nowrap; }
/* La salida no se encoge nunca: es 18 px y es la única forma de leer lo que
   la elipsis se comió. */
.valor-largo .help { flex: none; }
/* `valor-largo.js` esconde la salida donde el texto cabía, con `hidden`; el
   `display: inline-flex` de `.help` le ganaba a la hoja del navegador y el
   ojito salía en TODOS los renglones (visto en la ficha del chat,
   2026-10-10). */
.valor-largo .help[hidden] { display: none; }
/* EN UNA CELDA de tabla (2026-10-17, «Aplica a» de Descuentos). `.table` no
   parte línea y una celda crece hasta su texto: sin un tope, la elipsis no
   llega nunca y la lista empuja el resto de las columnas fuera de la
   tarjeta. El tope va en la pieza y no en la `<td>`: el `max-width` de una
   celda no lo respeta el algoritmo de la tabla, el de lo que va adentro sí.
   Y a la izquierda, como se lee una columna de texto. */
.table .valor-largo { justify-content: flex-start; max-width: 28ch; }
.table .valor-largo--corto { max-width: 18ch; }
/* El disparador con rótulo escrito al lado del glifo, para donde sí hay
   sitio; en una ficha no lo hay, y ahí va el ícono pelado. */
.help-icon-rotulo { display: inline-flex; align-items: center; gap: var(--space-050);
                    white-space: nowrap; font-size: var(--fs-label);
                    font-weight: 600; }
/* Un dato de la ficha que se CAMBIA en su sitio —la estación de una orden o
   de una receta—: el select y su botón, alineados a la derecha como el
   valor que reemplazan, sin JavaScript. */
.facts-form { display: inline-flex; gap: var(--space-100); align-items: center; margin: 0; }
.facts-form select { width: auto; }

/* El botón que acaba de copiar (`copy.js`): el check en el verde de «hecho»,
   un momento, sin cambiar de tamaño. */
.btn.is-copied { color: var(--ok-text); border-color: color-mix(in srgb, var(--ok-text) 40%, var(--border)); }

/* ── La tarjeta de un panel (`ui/_panel.html`) ─────────────────────────────
   La cara del referente de Renzo para la ficha del cliente (2026-10-10), que
   comparten las tres fichas que pintan paneles —pedido, tercero y chat—: la
   baldosa con su glifo y el título en versalitas en el encabezado, el dato
   destacado en un bloque con degradado, cada cosa como tarjetita con sus
   pastillas, y renglones con glifo. Todo opcional: un panel de solo
   renglones se ve como siempre, con el encabezado más limpio. */
.panel-tarjeta .card-header { justify-content: flex-start; gap: var(--space-100); flex-wrap: nowrap; }
.panel-tarjeta .card-header h2 {
    flex: 1; min-width: 0; margin: 0; font-size: var(--fs-label); font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase; color: var(--text);
}
.panel-destacado {
    display: flex; flex-direction: column; gap: var(--space-025);
    margin-bottom: var(--space-150); padding: var(--space-150) var(--space-200);
    border-radius: var(--radius);
    background: linear-gradient(135deg, var(--primary-soft) 0%, color-mix(in srgb, var(--primary-soft) 40%, var(--card-bg)) 100%);
}
.panel-destacado-rotulo { font-size: var(--fs-label); color: var(--text-light); }
.panel-destacado-valor { font-size: var(--fs-section); font-weight: 700; color: var(--text); }
.panel-cosa { display: flex; align-items: center; gap: var(--space-150); }
.panel-cosa-texto { display: flex; flex: 1; flex-direction: column; gap: var(--space-075); min-width: 0; }
.panel-cosa-foto {
    flex: none; width: 52px; height: 52px; border-radius: 12px;
    object-fit: cover; background: var(--card-bg);
}
.panel-cosa-flecha {
    display: inline-flex; flex: none; align-items: center; justify-content: center;
    width: 28px; height: 28px; border-radius: 50%;
    background: var(--primary-soft); color: var(--text);
}
.panel-cosa-flecha svg { width: 15px; height: 15px; transform: rotate(-90deg); }
.panel-cosa-flecha:hover { background: var(--primary); color: #fff; }
.panel-cosa + .panel-cosa {
    margin-top: var(--space-150); padding-top: var(--space-150);
    border-top: 1px solid var(--border);
}
.panel-cosa + .panel-filas { margin-top: var(--space-150); }
.panel-cosa-cabeza { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-100); }
.panel-cosa-titulo {
    min-width: 0; font-size: var(--fs-small); font-weight: 600;
    color: var(--primary-dark); overflow-wrap: anywhere;
}
.panel-cosa-monto { flex: none; font-size: var(--fs-small); font-weight: 700; color: var(--text); }
.panel-cosa-meta { margin: 0; font-size: var(--fs-label); color: var(--text-light); }
.panel-cosa-pastillas { display: flex; flex-wrap: wrap; gap: var(--space-075); }
/* Una pastilla más larga que la columna se CORTA con «…» en vez de salirse
   de la tarjeta y montarse sobre la flecha: `.pill` no parte línea, y un
   canal importado como «Subscription_Contract_Checkout_One» no tiene un
   solo espacio por donde partir (ficha del tercero, 2026-10-13). */
.panel-cosa-pastillas .pill {
    min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.panel-filas dt { display: flex; align-items: center; gap: var(--space-075); }
/* El enlace de la tarjeta, al PIE y a lo ancho, como un renglón más con su
   flecha (referente de Renzo, 2026-10-10): en la columna de una ficha un
   botón en el encabezado se montaba sobre el título. */
.panel-pie {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-100); margin-top: var(--space-150); padding: var(--space-100) var(--space-150);
    border-radius: var(--radius); background: var(--bg);
    font-size: var(--fs-small); font-weight: 600; color: var(--primary-dark);
}
.panel-pie:hover { background: var(--primary-soft); }
.panel-pie > span { flex: 1; }
.panel-pie svg { flex: none; width: 16px; height: 16px; }
.panel-pie-flecha { transform: rotate(-90deg); }
.panel-pie-lista { color: var(--primary); }
/* La caja interior: lo de la tarjeta junto, con su borde (referente). */
.panel-cuerpo {
    padding: var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--card-bg);
}
/* Sin dibujo grande al lado (el regalo de Puntos): «en ningún lugar de
   Agrupa lo tenemos así» (Renzo, 2026-10-29). */
/* «● Activa»: el punto del estado delante del valor. */
.panel-filas dd { display: flex; align-items: center; justify-content: flex-end; gap: var(--space-075); }
.panel-punto { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--text-faint); }
.panel-punto--ok { background: var(--ok-vivid); }
.panel-punto--warn { background: var(--warn-text); }
.panel-punto--danger { background: var(--danger-text); }
.panel-fila-icono { flex: none; width: 15px; height: 15px; color: var(--text-light); }

/* ── Filas de dato con glifo ─────────────────────────────────────────────────
   El recuadro del referente de la factura ante la DIAN (Renzo, 2026-10-02):
   glifo, rótulo, valor y acción en COLUMNAS. El valor es el que se parte
   (`minmax(0, 1fr)` + `overflow-wrap`): un CUFE de 96 caracteres se montaba
   encima de «Copiar» cuando era una celda más de una tabla. */
.datos { border: 1px solid var(--border); border-radius: var(--radius); background: var(--card-bg); }
.dato {
    display: grid; grid-template-columns: 24px 120px minmax(0, 1fr) auto;
    align-items: center; gap: var(--space-150); padding: var(--space-150) var(--space-200);
}
.dato + .dato { border-top: 1px solid var(--border); }
.dato-glifo { display: flex; color: var(--text-light); }
.dato-glifo svg { width: 20px; height: 20px; }
.dato-rotulo { font-size: var(--fs-small); font-weight: 600; color: var(--text); }
.dato-valor { min-width: 0; font-size: var(--fs-small); color: var(--text); overflow-wrap: anywhere; }
.dato-accion { display: flex; justify-content: flex-end; }
@media (max-width: 640px) {
    .dato { grid-template-columns: 24px minmax(0, 1fr) auto; }
    .dato-valor { grid-column: 2; }
}
/* En un riel angosto: el rótulo a su medida y el valor en UN renglón con
   «…» (el botón de copiar lleva el valor entero). */
.datos-compactos .dato { grid-template-columns: 20px auto minmax(0, 1fr) auto; }
.datos-compactos .dato-valor { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* LLANAS: las filas DENTRO de una tarjeta, sin recuadro propio —la tarjeta
   ya es el recuadro— y con la raya entre filas nada más. El riel de la ficha
   del integrante (Renzo, 2026-10-08). Mide su propio ancho: en los 270 px del
   riel el rótulo va ARRIBA del valor, chico y apagado, porque en una sola
   fila la dirección quedaba de a dos palabras por renglón; desde 300 px
   —el teléfono, donde el riel ocupa la pantalla— vuelven las columnas.
   Una fila sin rótulo (`.dato-solo`: un rol) pone el valor en su lugar. */
.datos-llanas { container-type: inline-size; }
.datos-llanas .dato {
    grid-template-columns: 20px minmax(0, 1fr) auto;
    row-gap: 1px; column-gap: var(--space-150); padding: var(--space-150) 0;
}
/* Cada pieza en su celda, escrita: colocadas solas, la acción caía en la
   segunda fila y estiraba una tercera. */
.datos-llanas .dato-glifo  { grid-column: 1; grid-row: 1 / span 2; }
.datos-llanas .dato-rotulo { grid-column: 2; grid-row: 1; }
.datos-llanas .dato-valor  { grid-column: 2; grid-row: 2; }
.datos-llanas .dato-accion { grid-column: 3; grid-row: 1 / span 2; }
.datos-llanas .dato-debajo { grid-column: 2 / -1; grid-row: 3; }
.datos-llanas .dato-solo > * { grid-row: 1; }
/* …menos lo que cuelga debajo: el dato que se copia en un paso de «Cómo
   hacerlo» (ficha de un pendiente, 2026-10-23). */
.datos-llanas .dato-solo > .dato-debajo { grid-row: 2; }
.datos-llanas .dato-debajo { min-width: 0; overflow-wrap: anywhere; }
.datos-llanas .url-box.dato-debajo { margin: var(--space-100) 0 0; }
.datos-llanas > .dato:first-child { padding-top: 0; }
.datos-llanas > .dato:last-child { padding-bottom: 0; }
.datos-llanas .dato-glifo { color: var(--primary); }
.datos-llanas .dato-glifo svg { width: 19px; height: 19px; }
.datos-llanas .dato-rotulo { font-size: var(--fs-label); font-weight: 500; color: var(--text-light); }
.datos-llanas .dato-valor { font-size: var(--fs-body); line-height: 1.4; }
.datos-llanas .dato-valor .pill { vertical-align: middle; }
.datos-llanas .dato-accion { gap: var(--space-100); }
.datos-llanas .dato-accion form { display: contents; }
/* `.dato-debajo` cuelga de la fila a todo su ancho: el enlace de invitación. */
@container (min-width: 300px) {
    .datos-llanas .dato { grid-template-columns: 20px minmax(0, 7.5em) minmax(0, 1fr) auto; }
    .datos-llanas .dato > * { grid-row: 1; }
    .datos-llanas .dato-valor  { grid-column: 3; }
    .datos-llanas .dato-accion { grid-column: 4; }
    .datos-llanas .dato-debajo { grid-column: 2 / -1; grid-row: 2; }
    .datos-llanas .dato-solo .dato-valor { grid-column: 2 / 4; }
    .datos-llanas .dato-rotulo { font-size: var(--fs-body); font-weight: 400; }
}
/* Los pasos de «Cómo hacerlo» (ficha de un pendiente, 2026-10-23): el dato
   que se copia va en la fila 2 y su «Copiar» en la columna de los botones,
   a su altura, para que todos queden alineados con «Abrir» y del mismo
   ancho (Renzo: «que los botones queden alineados»). Después del
   `@container` de arriba, que pone toda la fila en la 1. */
.datos-llanas.datos-pasos .dato-debajo { grid-column: 2 / -2; }
.datos-llanas.datos-pasos .dato-accion-debajo {
    grid-column: -2; grid-row: 2; align-self: center; margin-top: var(--space-100);
}
.datos-llanas.datos-pasos .dato-accion .btn { min-width: 7rem; justify-content: center; }
/* La descripción de una línea de documento: parte renglón, pero no se
   aplasta a una palabra por renglón cuando la tabla tiene diez columnas. */
.cell-descripcion { min-width: 24ch; }
/* Las líneas de una factura caben en su tarjeta (Santiago, 2026-11-05: «que
   la tabla quepa en el ancho de la pantalla»). Son doce columnas, tres de
   ellas casillas, y con el relleno de la casa (16 px por lado) medían 1.542
   px en una tarjeta de 918: el ⋮ de la línea quedaba fuera. `.doc-lineas`
   (`accounting/_document_lines_table.html`) junta las celdas, deja partir
   los rótulos largos («Precio unitario») y angosta las casillas: la de la
   cuenta muestra el código y el nombre queda en su `title`. Los dos
   extremos guardan su aire (`:first-child` / `:last-child` pesan más). */
.doc-lineas > thead > tr > th,
.doc-lineas > tbody > tr > td { padding-left: var(--space-075); padding-right: var(--space-075); }
.doc-lineas > thead > tr > th { white-space: normal; }
.doc-lineas .cell-descripcion { min-width: 14ch; }
.table.doc-lineas td .combo { min-width: 6.5rem; }
.doc-lineas select[name^="cost_center_"] { width: 7.5em; }
.doc-lineas .celda-sugerida { flex-wrap: wrap; }
.doc-lineas .celda-sugerida select { width: 7.5em; }
/* El pie termina donde termina la columna Total (Santiago, 2026-11-05: «se
   corrió los totales»). El pie es otra tabla (`.tabla-totales`) que llega al
   filo de la tarjeta; con el ⋮ de la línea, la columna Total queda un ⋮ más
   adentro. El ⋮ mide fijo —su relleno, el botón de la casa (`.menu-trigger`:
   18 px de glifo y 8 a cada lado) y el aire del extremo— y el pie se corre
   eso más el relleno de la celda Total. Sin ⋮ no cambia nada. */
.card-table:has(.doc-lineas) { --doc-menu-col: calc(var(--space-075) + var(--space-100) * 2 + 18px + var(--space-250)); }
.doc-lineas > thead > tr > th.cell-actions,
.doc-lineas > tbody > tr > td.cell-actions { width: var(--doc-menu-col); }
.card-table:has(.doc-lineas > tbody .cell-actions) .tabla-totales td.total-value {
    padding-right: calc(var(--doc-menu-col) + var(--space-075));
}
/* El trámite: las filas y, al lado, el panel que dice cómo quedó. */
.tramite {
    display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(240px, 1fr);
    gap: var(--space-200); align-items: stretch; margin-top: var(--space-200);
}
@media (max-width: 1000px) { .tramite { grid-template-columns: 1fr; } }
/* El rótulo pequeño encima del título de una tarjeta («TRÁMITE»). */
.card-kicker {
    margin: 0 0 var(--space-025); font-size: var(--fs-label); font-weight: 600; letter-spacing: .08em;
    text-transform: uppercase; color: var(--text-faint);
}
/* La pastilla de estado de la cabecera con su renglón debajo. */
.card-estado { display: flex; flex-direction: column; align-items: center; gap: var(--space-050); }

/* ── La ficha del documento (referente de Renzo, 2026-10-02) ──────────────── */
/* El nombre del tercero, grande, debajo de su rótulo. */
.card-name { margin: 0; font-size: var(--fs-stat); font-weight: 700; color: var(--text); }
/* La cifra que ES la tarjeta: el total a pagar. */
.total-cifra {
    display: flex; align-items: center; gap: var(--space-100); margin: var(--space-100) 0 0;
    font-size: var(--fs-number); font-weight: 700; color: var(--text); letter-spacing: -.01em;
}
/* El asiento por rol: cada grupo con su cabecera tenue en el tono de su rol. */
.asiento-grupo {
    margin-top: var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius-sm); overflow: hidden;
}
.asiento-grupo-cabeza { padding: var(--space-100) var(--space-150); background: var(--bg); }
.asiento-grupo-ok .asiento-grupo-cabeza   { background: var(--ok-bg); }
.asiento-grupo-info .asiento-grupo-cabeza { background: var(--info-bg); }
.asiento-grupo-warn .asiento-grupo-cabeza { background: var(--warn-bg); }
.asiento-grupo-titulo { margin: 0; font-size: var(--fs-small); font-weight: 700; color: var(--text); }

/* ── El panel de estado ilustrado (`ui/_estado_panel.html`) ────────────────── */
.estado-panel {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--space-100); padding: var(--space-300) var(--space-250); box-sizing: border-box; height: 100%;
    border-radius: var(--radius); text-align: center;
    background: var(--bg); --ep-tono: var(--info-text);
}
.estado-panel-ok     { background: color-mix(in srgb, var(--ok-bg) 70%, var(--card-bg));     --ep-tono: var(--ok-text); }
.estado-panel-danger { background: color-mix(in srgb, var(--danger-bg) 70%, var(--card-bg)); --ep-tono: var(--danger-text); }
.estado-panel-info   { background: color-mix(in srgb, var(--info-bg) 70%, var(--card-bg));   --ep-tono: var(--info-text); }
.estado-panel-warn   { background: color-mix(in srgb, var(--warn-bg) 70%, var(--card-bg));   --ep-tono: var(--warn-text); }
.estado-panel-ilustracion { width: 190px; height: 154px; margin-bottom: var(--space-100); overflow: visible; }
.estado-panel-titulo { margin: 0; font-size: var(--fs-body); font-weight: 700; color: var(--text); }
.estado-panel-texto { margin: 0; max-width: 32ch; font-size: var(--fs-small); color: var(--text-light); line-height: 1.5; }
.ep-doc { animation: ep-sube .5s ease-out both; }
.ep-hoja { fill: var(--card-bg); filter: drop-shadow(0 8px 16px rgba(16, 18, 22, .10)); }
.ep-linea {
    fill: var(--border-strong); transform-box: fill-box; transform-origin: left center;
    animation: ep-linea .45s ease-out both;
}
.ep-linea:nth-of-type(2) { animation-delay: .15s; }
.ep-linea:nth-of-type(3) { animation-delay: .22s; }
.ep-linea:nth-of-type(4) { animation-delay: .29s; }
.ep-linea:nth-of-type(5) { animation-delay: .36s; }
.ep-linea:nth-of-type(6) { animation-delay: .43s; }
.ep-sello {
    transform-box: fill-box; transform-origin: center;
    animation: ep-pop .45s cubic-bezier(.34, 1.56, .64, 1) .6s both;
}
.ep-circulo { fill: var(--ep-tono); }
.ep-glifo {
    fill: none; stroke: #fff; stroke-width: 4; stroke-linecap: round; stroke-linejoin: round;
    stroke-dasharray: 48; stroke-dashoffset: 0; animation: ep-trazo .4s ease-out .95s both;
}
.ep-destello {
    fill: none; stroke: var(--ep-tono); stroke-width: 3.5; stroke-linecap: round;
    animation: ep-destello .5s ease-out 1.2s both;
}
.ep-destello-2 { animation-delay: 1.3s; }
@keyframes ep-sube { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes ep-linea { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes ep-pop { from { transform: scale(0); } to { transform: scale(1); } }
@keyframes ep-trazo { from { stroke-dashoffset: 48; } to { stroke-dashoffset: 0; } }
@keyframes ep-destello { from { opacity: 0; transform: translate(-3px, 3px); } to { opacity: 1; transform: none; } }
/* Con «reducir movimiento» el apagado general de la casa deja todo en su
   estado final: por eso cada animación sale DESDE lo oculto (`both`) y no
   hay un bloque propio acá. */

/* ── La ficha en dos columnas ──────────────────────────────────────────────
   La forma de la ficha de pedidos de `tiendapp` (su `.o-layout`): el
   documento a la izquierda —ancho, porque lleva la tabla— y el contexto a
   la derecha. `minmax(0, …)` en la principal por la razón de siempre: sin
   él, una tabla ancha estira la columna en vez de desbordar con su scroll.
   La estrena el pedido confirmado; el borrador NO la usa — su columna única
   es la pantalla de captura, y partir el flujo del lector en dos columnas
   sería mover la vista a mitad de escaneo. */
/* La ficha clara de un pendiente (2026-10-25): una columna de lectura que
   termina en la decisión. A 1500 px un párrafo a todo el ancho no se lee. */
/* A todo el ancho, como la lista de Pendientes (Renzo, 2026-10-25). */
.ficha-una { display: flex; flex-direction: column; gap: var(--space-200); }
.ficha-una > .tip { margin-top: 0; }
.ficha-quien { display: flex; align-items: center; gap: var(--space-100);
               flex-wrap: wrap; margin: 0; font-size: var(--fs-small);
               color: var(--text-light); }
.ficha-porque { margin: 0; line-height: 1.55; }
.ficha-nota { margin: 0; white-space: pre-line; line-height: 1.55; }
.ficha-quieta { margin: var(--space-100) 0 0; font-size: var(--fs-small);
                color: var(--text-light); }
.ficha-revisado { display: flex; align-items: center; gap: var(--space-100);
                  margin: 0; }
.ficha-botones { display: flex; flex-wrap: wrap; gap: var(--space-100); }
.ficha-decision .fold { margin-top: var(--space-150); }
/* La ficha paso a paso (Renzo, 2026-10-25): un paso a la vez, y al pie
   «Atrás» a la izquierda y «Sigue: …» a la derecha. En el celular los
   rótulos de la miga se esconden menos el del paso en el que vas. */
.ficha-paso { display: flex; flex-direction: column; gap: var(--space-200); }
.ficha-paso > .tip { margin-top: 0; }
.ficha-que, .ficha-cerrada { display: flex; flex-direction: column; gap: var(--space-150); }
.ficha-pregunta { display: flex; align-items: center; justify-content: space-between;
                  flex-wrap: wrap; gap: var(--space-150); }
.ficha-paso-pie { display: flex; flex-wrap: wrap; gap: var(--space-100); }
.ficha-bloque { display: flex; flex-direction: column; gap: var(--space-050); }
.ficha-bloque-titulo { margin: 0; font-size: var(--fs-small); font-weight: 600;
                       color: var(--text-light); }
.ficha-paso-pie > :last-child { margin-left: auto; }
@media (max-width: 600px) {
    .ficha-miga li:not(.is-activo) .pasos-miga__rotulo { display: none; }
    .ficha-paso-pie { flex-direction: column-reverse; }
    .ficha-paso-pie > * { width: 100%; justify-content: center; margin-left: 0; }
}

.ficha-cols {
    display: grid; grid-template-columns: minmax(0, 2fr) minmax(280px, 1fr);
    gap: var(--space-200); align-items: start;
}
.ficha-main, .ficha-side {
    display: flex; flex-direction: column; gap: var(--space-200); min-width: 0;
}
/* El punto es el de tiendapp (1000px): más angosto, la lateral cae debajo. */
@media (max-width: 1000px) {
    .ficha-cols { grid-template-columns: 1fr; }
}

/* El seguimiento de la orden de compra: tarjetas sueltas debajo de las
   líneas, en dos columnas y una en angosta (mismo punto que `.ficha-cols`). */
.ficha-seg {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-200); align-items: stretch; margin-top: var(--space-200);
}
/* Todas del mismo alto (2026-10-27): un resumen corto y el botón al pie,
   pegado abajo aunque la vecina tenga más renglones. */
.ficha-seg .seg-card { display: flex; flex-direction: column; margin: 0; }
.seg-pie {
    margin-top: auto; display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); padding: var(--space-150) var(--space-200) var(--space-200);
}
.seg-pie .card-note { margin: 0; padding: 0; }
/* En cada renglón el texto toma el ancho y lo demás —«Ver», la pastilla,
   «Ver PDF»— cae a la derecha, en la misma vertical en todas las tarjetas. */
.seg-card .card-lines li > .cell-text { flex: 1 1 auto; min-width: 0; }
.seg-card .card-lines li > .inline-row { flex: 0 0 auto; margin: 0; }
@media (max-width: 1000px) {
    .ficha-seg { grid-template-columns: 1fr; }
}
.ficha-seg .url-box { margin: 0 var(--space-200) var(--space-150); }
/* Las facturas de la orden en tabla (Santiago, 2026-11-05): seis columnas no
   caben en media ficha; la tarjeta toma el ancho de las dos. */
.ficha-seg > .seg-card-ancha { grid-column: 1 / -1; }
.ficha-seg .url-box-valor { overflow-wrap: anywhere; min-width: 0; }

/* Una FICHA EN PESTAÑAS (`.ficha-pestanas`, con `pestanas.js`): las pestañas
   a la izquierda y el riel de la casa a la derecha (`product-layout`). Nació
   en la orden de compra y la usa también la ficha del tercero (2026-11-02).
   Cada pestaña (`.pestana-panel`) es una pila con el hueco de la columna;
   el seguimiento de adentro (`.ficha-seg`) ya no pide su margen de arriba. */
.ficha-pestanas > .product-main > .tabs { margin-bottom: 0; }
/* Cinco o seis pestañas no caben en el teléfono: la fila se desliza con el
   dedo y cada pestaña conserva su rótulo entero, en vez de «Gen…», «Prod…». */
@container content (max-width: 640px) {
    .ficha-pestanas > .product-main > .tabs { overflow-x: auto; scrollbar-width: none; }
    .ficha-pestanas > .product-main > .tabs > .tab { flex: none; margin-bottom: 0; padding-inline: var(--space-150); }
}
.pestana-panel { display: flex; flex-direction: column; gap: var(--space-200); min-width: 0; }
.pestana-panel > .ficha-seg { margin-top: 0; }
/* Dentro de una pestaña cada tarjeta va a todo el ancho, una debajo de otra
   (Renzo, 2026-11-02): en dos columnas una tarjeta sola dejaba media
   pestaña vacía. */
.pestana-panel > .ficha-seg { grid-template-columns: minmax(0, 1fr); }
/* El tercero de un documento EN UN RIEL (`ui/_tercero_cabeza.html`): a 312px
   el nombre y su botón no caben en un renglón, y el nombre se partía en
   tres. En el riel el botón baja bajo los datos, y cada dato va en su
   renglón. */
.product-rail .tercero-cabeza .card-header { flex-wrap: wrap; }
.product-rail .tercero-cabeza .card-actions { flex-basis: 100%; }
.product-rail .tercero-cabeza .card-hint { overflow-wrap: anywhere; }
/* La línea de estados del riel, con el aire de la tarjeta. */
.oc-estado { padding: var(--space-100) var(--space-200) var(--space-200); }
/* Lo que le toca a la orden ahora, a lo ancho bajo la línea: la tarjeta dice
   en qué va y qué sigue. */
.oc-siguiente { padding: 0 var(--space-200) var(--space-200); }
.oc-siguiente > .btn { width: 100%; justify-content: center; }
/* La prioridad de pago dentro del resumen, separada de las cifras. */
.oc-prioridad { margin: 0; padding: var(--space-150) var(--space-200) var(--space-200);
  border-top: 1px solid var(--border); }
.oc-prioridad .field { margin: 0; }
/* El resumen financiero: rótulo a la izquierda, cifra a la derecha y el
   total en su franja teñida, como el pie de la tabla. Las cifras las sigue
   `orden-compra.js` por `data-suma` mientras se edita. */
.oc-resumen { display: flex; flex-direction: column; gap: var(--space-075);
  margin: 0; padding: var(--space-050) var(--space-200) var(--space-200); }
.oc-resumen > div { display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-150); font-size: var(--fs-body); }
.oc-resumen dt { color: var(--text-light); }
.oc-resumen dd { margin: 0; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text); }
.oc-resumen > .oc-resumen-total { margin-top: var(--space-075); padding: var(--space-100) var(--space-150);
  border-radius: var(--radius-sm); background: var(--primary-soft);
  font-size: var(--fs-section); font-weight: 700; }
.oc-resumen > .oc-resumen-total dt { color: var(--text); }

/* Un valor CON su acción al lado —«Puesto · Cambiar», «Sin invitar · Invitar»—
   sigue siendo un valor de la ficha: se alinea a la derecha como los demás, y
   el botón viaja en la misma línea. `flex-wrap` porque el enlace de invitación
   trae un input de ancho completo debajo. */
.fact-action {
    display: flex; align-items: center; justify-content: flex-end;
    gap: var(--space-100); flex-wrap: wrap;
}
.fact-action form { display: contents; }

/* La CIFRA de la tarjeta de salario. Es el dato por el que alguien abre la
   tarjeta, así que se lee como cifra y no como un renglón más de la rejilla. */
.fact-figure {
    margin: 0; font-size: var(--fs-number); font-weight: 700;
    font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}

/* El historial debajo de la cifra, separado por la misma línea que separa
   cualquier pie de tarjeta. Las razones van en su propio renglón chico: en 312px
   «$2.000.000 · Salario al migrar desde tiendapp» no cabe en una línea y
   partirse a mitad de la cifra es peor que apilarse. */
.facts-history {
    margin-top: var(--space-150); padding-top: var(--space-150);
    border-top: 1px solid var(--border);
}
.fact-note {
    display: block; font-size: var(--fs-label); color: var(--text-light);
}

/* La LÍNEA la dibuja la lista con un `::before`, no un borde por entrada: con
   el borde queda cortada en el espacio entre entradas y se ve punteada. */
/* `margin-top` para separarse de la casilla de comentario: sin él la primera
   tarjeta nacía pegada al input y las dos se leían como un solo bloque. */
.timeline {
    position: relative; list-style: none;
    margin: var(--space-250) 0 0; padding: 0 0 0 var(--space-300);
    display: flex; flex-direction: column; gap: var(--space-200);
}
/* La línea arranca y termina EN las bolitas de los extremos, no en el borde de
   la lista: sobresalir por arriba y por abajo se ve como un cable suelto. */
.timeline::before {
    content: ''; position: absolute; top: 10px; bottom: 10px; left: 5px;
    width: 1.5px; background: var(--border);
}
.timeline-item {
    position: relative;
    display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-050) var(--space-150);
}
/* La bolita tapa la línea a su altura: sin fondo propio se ve el cable pasando
   por debajo y deja de leerse como un hito. */
/* Alineada con el RENGLÓN, no con el borde de la entrada: una bolita a la altura
   del espacio entre líneas no marca nada. En una entrada suelta el renglón
   empieza arriba; en un comentario empieza después del relleno de la tarjeta, y
   por eso los dos desplazamientos son distintos. */
.timeline-dot {
    position: absolute; left: calc(var(--space-300) * -1 + 1px); top: 5px;
    width: 9px; height: 9px; border-radius: 50%;
    background: var(--border-strong);
    box-shadow: 0 0 0 3px var(--card-bg);
}
/* De marca, y a la altura del nombre de quien escribió: lo que dijo una persona
   es lo que se busca al recorrer la lista. */
.timeline-comment > .timeline-dot { background: var(--primary); top: 23px; }

/* La fecha en tabular para que las columnas de dígitos se alineen entre filas;
   con cifras proporcionales una lista de veinte marcas de tiempo baila. */
.timeline-when {
    flex: none;
    font-size: var(--fs-meta); color: var(--text-light);
    font-variant-numeric: tabular-nums;
}
.timeline-what { flex: 1 1 auto; font-size: var(--fs-small); color: var(--text); }
.timeline-who { color: var(--text-light); }

/* El historial de precios (ficha del producto, `catalog/_price_row.html`): lo
   que veía el cliente antes y ahora, con el precio de comparación tachado como
   en la tienda. Una rejilla de dos columnas para que «Antes» y «Ahora» alineen
   sus cifras, y en tabular para que los dígitos no bailen entre renglones. */
.price-step-variant { flex-basis: 100%; font-size: var(--fs-small); font-weight: 600; color: var(--text); }
.price-step {
    flex-basis: 100%; margin: 0;
    display: grid; grid-template-columns: auto 1fr; gap: var(--space-025) var(--space-150);
    align-items: baseline;
    font-size: var(--fs-small); font-variant-numeric: tabular-nums;
}
.price-step dt { color: var(--text-light); }
.price-step dd { margin: 0; color: var(--text); }
.price-step s { color: var(--text-light); }

/* El comentario, en tarjeta. */
.timeline-card {
    flex: 1 1 100%; min-width: 0;
    padding: var(--space-200) var(--space-200) var(--space-200) var(--space-250);
    background: var(--card-bg);
    border: 1px solid var(--border); border-radius: var(--radius);
}
/* Lo MÍO en un hilo con alguien de afuera (`ui/_hilo.html`): la tarjeta de
   quien mira, con el tinte de la marca; la del otro lado queda en blanco. */
.timeline-card[data-mine] { background: var(--primary-soft); }
/* El cuerpo respira respecto al encabezado: con 4px el comentario se leía
   colgado del nombre en vez de debajo de él. */
.timeline-head {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-150);
    margin-bottom: var(--space-100);
}
.timeline-author { font-size: var(--fs-small); font-weight: 600; color: var(--text); }
/* Las acciones se van a la derecha del encabezado, y ahí termina el renglón. */
.timeline-actions { margin-left: auto; display: flex; align-items: center; gap: var(--space-075); }
.timeline-body { margin: 0; font-size: var(--fs-small); line-height: 1.5; color: var(--text); }

/* El `<details>` que revela el formulario de edición en su sitio. El marcador
   nativo se quita: el `<summary>` se pinta como botón y la flechita al lado de
   «Editar» lo delataría como algo que se despliega y no como algo que se
   aprieta. */
.inline-edit { position: relative; }
.inline-edit > summary { list-style: none; cursor: pointer; }
.inline-edit > summary::-webkit-details-marker { display: none; }
.inline-edit[open] > summary { border-color: var(--primary); color: var(--primary); }
/* ABSOLUTO y anclado a la derecha del botón. Dentro del flujo caía adentro de
   `.timeline-actions`, que es una fila alineada a la derecha, así que la casilla
   quedaba de dos centímetros. Y `min()` con `vw` para que a 390px no se salga
   por el borde. */
.inline-edit > form {
    position: absolute; top: calc(100% + 6px); right: 0; z-index: 5;
    width: min(420px, 78vw);
    padding: var(--space-150);
    background: var(--card-bg);
    border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}

/* ── La ayuda de un campo: el `(i)` y su modal ─────────────────────────────
   Ver `ui/_help.html` para por qué es una casilla y no un `<details>`. */
/* El envoltorio mide EXACTAMENTE el ícono, y de ahí sale el centrado.

   Sin la altura fija su caja calculaba 22px con un ícono de 15 dentro, así que
   estiraba la fila del rótulo y el `(i)` salía tres píxeles y medio por encima
   del texto — que es la diferencia entre un ícono alineado y uno montado.
   `align-self: center` no lo arreglaba, porque no era estiramiento: era la
   altura del envoltorio.

   Un solo número para los dos: si el ícono crece y el envoltorio no, vuelve el
   mismo defecto por el otro lado. */
.help {
    --help-size: 15px;
    position: relative; display: inline-flex; flex: none;
    width: var(--help-size); height: var(--help-size);
}
/* Con rótulo (`trigger`) la caja mide lo que mide el rótulo: con los 15px del
   `(i)` el botón «Agregar productos» se salía de su fila (2026-09-29). */
.help:has(.help-icon-rotulo) { width: auto; height: auto; }
/* `boton=1`: el disparador como un botón con borde («Rechazar» de la ficha
   de la campaña y de la propuesta de correo). Mide lo que un `.btn` normal:
   va al lado de «Aprobar», y chico se veía de segunda (Renzo, 2026-10-31;
   antes medía lo que `.btn-small`). */
.help-icon.help-icon-boton {
    display: inline-flex; align-items: center; white-space: nowrap;
    width: auto; height: auto; gap: var(--space-100); padding: var(--space-150) var(--space-250); line-height: 1;
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    background: var(--card-bg); color: var(--text); font-size: var(--fs-body); font-weight: 600;
}
.help-icon.help-icon-boton svg { width: 15px; height: 15px; }
.help-icon.help-icon-boton:hover { background: var(--bg); color: var(--text); }
/* Sin disparador (`headless`): lo abre otro script. No ocupa nada en la fila. */
.help-headless { display: block; width: 0; height: 0; }
/* Oculta pero enfocable. NO se le agrega a la lista de excepciones del
   selector de casillas: esa lista es para las que no deben LLEVAR nuestro
   dibujo —el cajón se pinta como hamburguesa—, y ésta simplemente no se ve.
   Que herede el dibujo en 1px invisible no le hace daño a nadie. */
.help-toggle { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
/* El círculo lo dibuja el SVG. Antes era un borde CSS con una «i» de texto
   adentro, y no había forma de centrarla: la itálica la corría a la derecha y
   la métrica de la fuente la subía sobre el rótulo. Un glifo trazado cae donde
   se pone, y a 15px eso es la diferencia entre un ícono y una errata.

   `display: block` en el SVG y no `inline`: como inline se sienta en la línea
   base y arrastra el descendente, que es el píxel y medio que lo dejaba alto
   respecto al rótulo de al lado. */
.help-icon {
    display: block; width: var(--help-size); height: var(--help-size);
    color: var(--text-light); cursor: pointer;
    transition: color .16s;
}
.help-icon svg { display: block; width: 100%; height: 100%; }
.help-icon:hover { color: var(--primary); }
/* El foco se pinta en el ÍCONO porque la casilla mide un píxel: sin esto, el
   tabulador llega a la ayuda y no se ve que llegó. El círculo es solo para el
   `(i)` pelado: con rótulo o con cara de botón el anillo sigue la forma del
   disparador. Con el `50%` para todos, «Agregar productos» con el foco salía
   como un óvalo morado (el traslado, 2026-11-03: de «Código» a «Cantidad» se
   pasa con el tabulador, y el foco se para en él). */
.help-toggle:focus-visible + .help-icon {
    outline: 2px solid var(--primary); outline-offset: 2px;
    color: var(--primary);
}
.help-toggle:focus-visible + .help-icon:not(.help-icon-rotulo):not(.help-icon-boton) { border-radius: 50%; }
.help-sheet { display: none; }
.help-toggle:checked ~ .help-sheet {
    /* 150: encima de la barra de arriba (100) y de los cajones (110), debajo
       del velo de carga (200). Con 80 la barra le tapaba el título a la hoja
       alta del `(i)` de un módulo en Roles (2026-09-29). */
    display: flex; position: fixed; inset: 0; z-index: 150;
    align-items: center; justify-content: center; padding: var(--space-250);
}
.help-backdrop { position: absolute; inset: 0; background: rgba(17, 16, 29, .44); cursor: pointer; }
.help-panel {
    position: relative; display: flex; flex-direction: column; gap: var(--space-150);
    width: 100%; max-width: 420px;
    padding: var(--space-250);
    background: var(--card-bg); border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
    text-align: left;
    /* La hoja vive DENTRO de quien la llama, y hereda su texto: en una celda
       de tabla (`nowrap`) o en un encabezado en mayúsculas, el párrafo salía
       en una sola línea que cruzaba la pantalla (Roles, 2026-09-29). */
    white-space: normal; text-transform: none; letter-spacing: normal;
    font-weight: 400;
}
.help-panel-title { font-size: var(--fs-section); font-weight: 600; color: var(--text); }
.help-panel-head { display: flex; gap: var(--space-150); align-items: center; }
.help-panel-heading { display: flex; flex-direction: column; gap: var(--space-025); min-width: 0; }
.help-panel-subtitle { font-size: var(--fs-body); color: var(--text-light); }
/* La hoja ANCHA: una lista, no una frase. Con tope de alto y su propio
   scroll, porque un módulo con nueve acciones no cabe en un celular. */
.help-panel-wide {
    max-width: 560px; max-height: calc(100dvh - 2 * var(--space-250));
    overflow-y: auto; padding-right: var(--space-300);
}
.help-panel-wide .help-panel-title { font-size: var(--fs-page); }
/* La cabeza de la hoja que se desplaza se queda arriba, y la × con ella
   (Renzo, 2026-10-31: «para poder cerrar cuando quiera»). Sale hasta los
   bordes de la hoja con márgenes negativos del tamaño del relleno, con el
   fondo de la tarjeta para tapar lo que pasa por debajo. `top` negativo del
   mismo relleno: lo pegajoso se mide desde adentro del relleno, y con 0
   asomaba una franja del cuerpo encima. */
.help-panel-wide > .help-panel-head {
    position: sticky; top: calc(-1 * var(--space-250)); z-index: 2;
    margin: calc(-1 * var(--space-250)) calc(-1 * var(--space-300)) 0 calc(-1 * var(--space-250));
    padding: var(--space-250) var(--space-500) var(--space-150) var(--space-250);
    background: var(--card-bg);
}
/* La hoja COMPLETA: casi toda la pantalla, para lo que necesita alto (el
   selector de productos). El cuerpo se desplaza; la cabeza y el pie no. */
.help-panel-full {
    max-width: 960px; height: calc(100dvh - 2 * var(--space-250));
    max-height: none; overflow: hidden; padding-right: var(--space-300);
}
.help-foot {
    display: flex; align-items: center; gap: var(--space-150);
    padding-top: var(--space-150); border-top: 1px solid var(--border);
}
/* El selector de productos (`ui/_selector_productos.html`). La tabla es de
   spans y no `<table>`: vive dentro de la hoja del modal, que puede estar
   dentro de un `<label>`. */
.add-zone {
    display: flex; justify-content: center; padding: var(--space-250);
    border: 1px dashed var(--border-strong); border-radius: var(--radius-card);
}
/* El disparador es el de `ui/_help.html`; acá lleva cara de botón pastilla,
   no de `(i)`: es la acción principal de la tarjeta. */
/* `margin` y `font-size` otra vez acá: el disparador es un `<label>`, y dentro
   de un `.field` —la casilla «Código» de la captura del traslado— `.field
   label` (0,1,1) le ganaba a `.help-icon-rotulo` (0,1,0): el botón salía con
   el rótulo de 12px y 8px de margen abajo, medio renglón más alto que la
   casilla de al lado (2026-11-03). Con (0,2,0) manda la pieza en cualquier
   contenedor. */
.selector .help-icon {
    display: inline-flex; align-items: center; gap: var(--space-075);
    width: auto; height: auto; padding: var(--space-100) var(--space-200); border-radius: 999px;
    margin: 0; font-size: var(--fs-label);
    background-color: var(--primary-soft); color: var(--primary);
    font-weight: 600;
}
.selector .help-icon svg { width: 16px; height: 16px; }
.selector-bar { display: flex; flex-wrap: wrap; gap: var(--space-100); }
.selector-search { position: relative; flex: 1 1 260px; display: flex; }
.selector-search svg {
    position: absolute; left: 11px; top: 50%; translate: 0 -50%;
    width: 16px; height: 16px; color: var(--text-light); pointer-events: none;
}
.selector .selector-search input { flex: 1; padding-left: var(--space-400); }
.selector-table {
    display: flex; flex-direction: column; flex: 1; min-height: 0;
    overflow-y: auto; border: 1px solid var(--border);
    border-radius: 0 0 var(--radius-field) var(--radius-field);
}
.selector-head, .selector-product, .selector-variant {
    display: grid; gap: var(--space-150); align-items: center;
    padding: var(--space-100) var(--space-150);
}
.selector-head {
    grid-template-columns: minmax(0, 1fr) 90px 120px;
    position: sticky; top: 0; z-index: 1;
    background-color: var(--table-head-bg); color: var(--text-light);
    font-weight: 500; border-bottom: 1px solid var(--border);
}
.selector-product { grid-template-columns: auto auto minmax(0, 1fr) 90px 120px; }
.selector-variant {
    grid-template-columns: auto minmax(0, 1fr) 90px 120px;
    padding-left: calc(var(--space-150) + var(--space-600));
}
.selector-rows { display: block; }
/* En una `inline-row` el selector es un botón: mide lo que mide. */
.inline-row .selector { flex: none; }
/* La fila de agregar productos del pedido, y su vacío. */
.editor-add { margin-bottom: var(--space-200); }
.editor-add .variant-picker { flex: 1 1 260px; min-width: 0; }
.editor-add .variant-picker input { width: 100%; }
.editor-empty { color: var(--text-light); margin: 0; }
.editor-empty[hidden], .table-wrap[hidden] { display: none; }
.selector-group { display: block; border-bottom: 1px solid var(--border); }
.selector-name { display: flex; flex-direction: column; min-width: 0; }
.selector-title { color: var(--text); }
.selector-sub { font-size: var(--fs-small); color: var(--text-light); }
.selector-stock.is-warn { color: var(--warn-text); font-weight: 600; }
.selector-stock.is-warn::before { content: '⚠ '; }
/* Sin Inventario instalado no hay columna de disponible: se va entera. */
.is-sin-stock .selector-stock,
.is-sin-stock [data-selector-col-stock] { display: none; }
.is-sin-stock .selector-head { grid-template-columns: minmax(0, 1fr) 120px; }
.is-sin-stock .selector-product { grid-template-columns: auto auto minmax(0, 1fr) 120px; }
.is-sin-stock .selector-variant { grid-template-columns: auto minmax(0, 1fr) 120px; }
/* «Pendiente · Todo» de la lista de un traslado (Santiago, 2026-11-06): en
   «Pendiente» se esconde lo que salió completo (`is-salio`), que vive en sus
   envíos; si salió todo, la tabla da paso a «Todo despachado». Lo decide el
   radio marcado del interruptor (`ui/_segmented.html`), sin JavaScript. */
.lineas-todo-despachado { display: none; }
.lineas-vistas:has(input[name="vista_lineas"][value="pendiente"]:checked) tr.is-salio { display: none; }
.lineas-vistas.is-todo-salio:has(input[name="vista_lineas"][value="pendiente"]:checked) .table-wrap { display: none; }
.lineas-vistas.is-todo-salio:has(input[name="vista_lineas"][value="pendiente"]:checked) .lineas-todo-despachado { display: block; }
.lineas-vistas.is-todo-salio .lineas-salieron { display: none; }
.lineas-vistas:has(input[name="vista_lineas"][value="todo"]:checked) .lineas-salieron { display: none; }
/* Sin precio (un traslado): la columna se va y el disponible queda al borde. */
.is-sin-precio [data-selector-col-precio] { display: none; }
.is-sin-precio .selector-head { grid-template-columns: minmax(0, 1fr) 90px; }
.is-sin-precio .selector-product { grid-template-columns: auto auto minmax(0, 1fr) 90px; }
.is-sin-precio .selector-variant { grid-template-columns: auto minmax(0, 1fr) 90px; }
.selector-empty { display: block; padding: var(--space-250); text-align: center; color: var(--text-light); }
.selector-more { align-self: center; margin: var(--space-150) 0; }
.selector-count { color: var(--text-light); }
.selector-foot-actions { margin-left: auto; display: flex; gap: var(--space-100); }
/* `hidden` tiene que ganarle al `display` propio de estas piezas, igual que
   en `.bulkbar`: la hoja del navegador pierde por especificidad. */
.selector-empty[hidden], .selector-more[hidden], .selector [hidden] { display: none; }
/* «Agregar» con nada elegido se ve apagado: la casa todavía no tiene un
   `.btn:disabled` general (ver `.period-foot`). */
.selector .btn:disabled { opacity: .45; cursor: not-allowed; }
/* En un celular la fila no cabe a lo ancho: disponible y precio bajan
   debajo del nombre. */
@media (max-width: 640px) {
    .selector-bar select { flex: 1 1 40%; min-width: 0; }
    .selector-head { display: none; }
    /* Tres columnas: la casilla, el nombre (dos renglones) y, apilados a la
       derecha, disponible y precio. La miniatura sobra a este ancho. */
    .selector-product, .selector-variant,
    .is-sin-stock .selector-product, .is-sin-stock .selector-variant {
        grid-template-columns: auto minmax(0, 1fr) auto;
        row-gap: var(--space-025);
    }
    .selector-product .finder-thumb { display: none; }
    .selector-row > input, .selector-name { grid-row: span 2; }
    .selector-stock, .selector-price { grid-column: 3; text-align: right; }
    .selector-variant { padding-left: var(--space-300); }
}
.help-x {
    position: absolute; top: var(--space-200); right: var(--space-200);
    width: 20px; height: 20px; color: var(--text-light); cursor: pointer;
}
.help-x svg { width: 100%; height: 100%; }
/* El `(i)` de un módulo en Roles (`perms/_permisos_modulo.html`): una fila
   por lo que el permiso deja hacer, con su baldosa y la pastilla del nivel
   que lo trae. `span` en bloque y no `div`: la hoja vive dentro de un
   `<label>` en las ayudas de campo, y la misma regla vale acá. */
.perm-list { display: flex; flex-direction: column; }
.perm-list-title { display: block; margin-top: var(--space-100); font-weight: 600; color: var(--text); }
.perm-item {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--space-050) var(--space-150); align-items: center;
    padding: var(--space-150) 0; border-top: 1px solid var(--border);
}
.perm-item:first-of-type { border-top: 0; }
.perm-item-body { display: flex; flex-direction: column; gap: var(--space-025); }
.perm-item-label { font-weight: 600; color: var(--text); }
.perm-item-help { font-size: var(--fs-small); color: var(--text-light); line-height: 1.4; }
/* En un celular la pastilla al lado aprieta la frase a tres palabras por
   renglón: baja debajo del texto, en la misma columna. */
@media (max-width: 520px) {
    .perm-item { grid-template-columns: auto minmax(0, 1fr); }
    .perm-item .pill { grid-column: 2; justify-self: start; }
}
.help-panel-text { font-size: var(--fs-body); line-height: 1.5; color: var(--text-light); }
/* `align-self` y no `width: 100%`: un botón que cruza el modal entero se lee
   como la acción de una pantalla y no como el cierre de un aviso. */
.help-close { align-self: flex-start; cursor: pointer; }
/* La × del modal CHICO solo en el teléfono: en escritorio cabe entero y
   «Entendido» está a la vista. */
@media (min-width: 641px) {
    .help-panel:not(.help-panel-wide, .help-panel-full) .help-x { display: none; }
}
/* En el teléfono el modal ocupa la pantalla (Renzo, 2026-10-01): la caja de
   420px con margen dejaba el texto en una columna angosta y la hoja ancha se
   cortaba. La × arriba a 44px, y el título le deja su sitio. El completo
   conserva su `overflow: hidden`: el que se desplaza es su cuerpo. */
@media (max-width: 640px) {
    .help-toggle:checked ~ .help-sheet { padding: 0; align-items: stretch; }
    .help-panel {
        max-width: none; height: 100dvh; max-height: none; border-radius: 0;
        padding: calc(var(--space-250) + env(safe-area-inset-top)) var(--space-200)
                 calc(var(--space-250) + env(safe-area-inset-bottom));
    }
    .help-panel:not(.help-panel-full) { overflow-y: auto; }
    /* Toda hoja se desplaza aquí: la cabeza con la × se queda arriba, como
       la de la ancha en escritorio (2026-10-31). */
    .help-panel:not(.help-panel-full) > .help-panel-head {
        position: sticky; top: calc(-1 * (var(--space-250) + env(safe-area-inset-top))); z-index: 2;
        margin: calc(-1 * (var(--space-250) + env(safe-area-inset-top))) calc(-1 * var(--space-200)) 0;
        padding: calc(var(--space-250) + env(safe-area-inset-top)) var(--space-500) var(--space-150) var(--space-200);
        background: var(--card-bg);
    }
    .help-panel .help-x {
        top: 6px; right: 6px; width: 44px; height: 44px;
        padding: var(--space-150); box-sizing: border-box;
    }
    .help-panel-head { padding-right: var(--space-500); }
}
.home-feed-card {
    max-width: 720px; margin: 0 auto;
    /* Encima de los orbes SIEMPRE: el escenario sangra 180px hacia abajo a
       propósito (el desvanecido), y sin contexto de apilamiento propio esa
       decoración quedaba pintada sobre la tarjeta de actividad. */
    position: relative; z-index: 1;
    /* Última en entrar: cierra la escalera que abre el saludo. */
    animation: home-rise .5s ease .24s both;
}
/* El encabezado DENTRO de una tarjeta: título a la izquierda, y a la derecha
   una pista gris o un botón.

   El margen negativo lleva la divisoria hasta los bordes de la tarjeta. Sin
   él la línea queda flotando con veintidós píxeles de aire a cada lado, que
   se lee como una raya y no como el borde de una banda. Los números salen
   del `padding` de `.card` — si aquél cambia, éste cambia con él. */
.card-header {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-200); flex-wrap: wrap;
    margin: calc(var(--space-200) * -1) calc(var(--space-250) * -1) var(--space-200);
    padding: var(--space-150) var(--space-250);
    border-bottom: 1px solid var(--border);
}
/* `hidden` tiene que ganarle al `display: flex` de arriba, igual que en
   `.bulkbar` y por lo mismo: la hoja del navegador trae `[hidden]` con la
   misma especificidad y nuestra regla va después. Lo apaga `bulk.js`
   mientras hay filas seleccionadas, para que la barra ocupe este sitio en
   vez de empujar la tabla. */
.card-header[hidden] { display: none; }
/* Una tarjeta que es SOLO su encabezado —la del emisor en la configuración
   contable: título, pista, pastilla y «Configurar», y nada debajo—. El
   margen de abajo de la banda y el relleno de la tarjeta dejaban una franja
   vacía bajo el filete (Renzo, 2026-09-19); la banda última se come ese
   relleno y no pinta filete, porque no separa nada de nada. */
.card > .card-header:last-child { margin-bottom: calc(var(--space-200) * -1); border-bottom: 0; }
/* En MAYÚSCULAS como tiendapp — decisión de Renzo del 2026-08-04, cerrando
   la pendiente anotada en F1-e. Vale para TODAS las tarjetas del ERP, los
   conteos-encabezado de las listas incluidos. */
/* `.bulkbar h2` va EN ESTA REGLA y no imitándola desde su propio bloque.

   La barra de selección ocupa el sitio de esta banda, así que su conteo
   tiene que pintarse exactamente igual. El primer intento fue ponerle la
   clase `.card-count` —la misma que lleva el `<h2>` de acá— y no alcanzó:
   dentro del encabezado manda `.card-header h2`, que con (0,1,1) le gana a
   `.card-count` con (0,1,0). O sea que el número del encabezado NUNCA se
   pintó con `.card-count`, y copiar la clase copiaba la etiqueta pero no
   la apariencia: 13,5px/600 contra 11,5px/700, y el texto cambiaba de
   tamaño al seleccionar.

   Compartir el selector es lo único que garantiza que sigan iguales cuando
   alguien toque uno de los dos. */
/* Las DOS bandas miden lo mismo, y lo que fija ese alto es el botón que
   tienen que poder llevar (`--band-h`).

   Antes se igualaban por coincidencia: el título medía 20,9 y el botón 22,
   así que la banda con verbos salía un píxel más alta y la tabla brincaba
   ese píxel. Se arregló bajándole el relleno al botón —una excepción al
   sistema de tamaños, escondida dentro de la barra— y esa excepción se
   retira acá: el alto lo declara la banda, no lo negocia su contenido.

   Alcanza a TODAS las tarjetas del ERP y no solo a las listas, a sabiendas
   (Renzo, 2026-08-14): una banda que puede llevar un botón se dimensiona
   para llevarlo, tenga uno o no, o dos tarjetas de la misma pantalla se
   leen con dos alturas según lo que les hayan puesto adentro. */
.card-header,
.bulkbar { min-height: var(--band-h); }

.card-header h2,
.bulkbar h2 {
    margin: 0; font-size: var(--fs-section); font-weight: 600;
    text-transform: uppercase; letter-spacing: .02em;
}
/* Texto, no acción. Es «primera = principal» o «lo que se vende son las
   variantes»: dice algo del contenido de la tarjeta y no se puede tocar. */
.card-hint { margin: 0; color: var(--text-light); font-size: var(--fs-small); }

/* El conteo COMO encabezado de la tabla: «475 productos». Es lo que unifica las
   listas del ERP —la barra de filtros en su tarjeta arriba, la tabla en la suya
   con su número— y vive DENTRO del fragmento que htmx intercambia, así que
   filtrar lo actualiza. En versalitas porque es un rótulo de sección y no un
   título: compite con el nombre de la pantalla si se pinta como un `h2` normal.

   `tabular-nums` para que el número no baile de ancho al cambiar de cuatro
   dígitos a tres mientras se escribe en el buscador. */
.card-count {
    margin: 0;
    font-size: var(--fs-label); font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase;
    color: var(--text); font-variant-numeric: tabular-nums;
}

/* EL PANEL DE COLUMNAS, en la cabecera de la tarjeta.

   `position: relative` sobre el `<details>` y absoluto el panel: en flujo,
   abrirlo empujaba la tabla entera hacia abajo y la fila que estabas
   mirando se iba de la pantalla. Un menú que mueve lo que venías leyendo
   se siente como que la pantalla se rompió.

   El marcador del `<summary>` se quita por partida doble —`list-style` y
   el pseudo-elemento de WebKit— porque cada navegador lo dibuja por su
   lado, y con uno solo quedaba el triángulo encima del glifo en Safari. */
.colmenu { position: relative; }
.colmenu > summary { list-style: none; cursor: pointer; }
.colmenu > summary::-webkit-details-marker { display: none; }
.colmenu-panel {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
    min-width: 210px; padding: var(--space-150);
    /* `--card-bg` y no un `--surface` inventado: ese token NO EXISTE en esta
       hoja, y un `background` que no resuelve deja el panel TRANSPARENTE —
       las filas de la tabla se leen a través de las casillas. No lo caza
       ninguna prueba: la guarda de `test_stylesheet.py` comprueba que cada
       clase tenga regla, no que cada variable esté definida. Solo apareció
       mirando la pantalla. */
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: 0 10px 30px rgba(16, 15, 40, .16);
}
.colmenu-list { list-style: none; margin: 0 0 var(--space-150); padding: 0; }
/* El área de clic es la ETIQUETA entera y no solo la casilla: doce píxeles
   cuadrados es un blanco que se falla, y fallarlo acá cierra el panel. */
.colmenu-item {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-075) var(--space-050); cursor: pointer;
    font-size: var(--fs-body); color: var(--text);
}
.colmenu-item:hover { background: var(--bg); border-radius: var(--radius-field); }
.colmenu-foot { margin-bottom: var(--space-075); }
/* En angosto el panel se ANCLA A LA IZQUIERDA del botón, no a la derecha.

   Medido a 375px: la cabecera de la tarjeta envuelve y deja el botón cerca
   del borde izquierdo, así que un panel de 210px colgando hacia la izquierda
   empezaba en -61px — las casillas quedaban fuera de la pantalla, y como el
   desborde se recorta en vez de generar scroll, no había forma de llegar a
   ellas. El `max-width` es el cinturón: con una etiqueta larga, `left: 0` no
   alcanza para impedir que se salga por el otro lado. */
@media (max-width: 767px) {
    .colmenu-panel { right: auto; left: 0; max-width: calc(100vw - 32px); }
}

/* ── El interior del encabezado: baldosa, título y pista ───────────────────
   Lo que `ui/_card_head.html` pinta DENTRO de `.card-header`. La banda no
   cambia —sus márgenes negativos, su borde, su altura— así que una tarjeta
   con baldosa y una sin ella siguen midiendo igual y pegando igual contra el
   `thead` de su tabla.

   Nació cerrando un empate: `.card-header` manda la pista a la DERECHA del
   título, y una pista que explica de qué va la tarjeta —«Estos los administra
   el sistema»— se lee debajo, no al lado. La pantalla que lo necesitó primero
   —pasarelas— se inventó `gateway-name`/`gateway-hint` para tenerlo, y
   Dominios lo copió. Dos encabezados de tarjeta con dos nombres es la deriva;
   esto es el nombre único. */
.card-head-lead { display: flex; align-items: center; gap: var(--space-150); min-width: 0; }
/* `min-width: 0` en los dos: sin él un título largo estira la banda y se lleva
   por delante lo que haya a la derecha en vez de romperse. */
.card-head-text { min-width: 0; }
/* El `<h2>` lo pinta `.card-header h2` — versalitas, 13.5px — y no se repite
   acá: el título de una tarjeta se ve igual la traiga o no la baldosa. Lo
   único que cambia es que ahora tiene una pista DEBAJO, y eso es un margen. */
/* Título y pista PEGADOS (Renzo, 2026-11-02, la tarjeta de líneas de la orden
   de compra: «disminuir el espacio entre título y subtítulo»): medio margen y
   el interlineado justo, o entre los dos quedaba un renglón de aire. Vale
   para toda tarjeta de la casa. */
.card-head-text h2 { line-height: 1.3; }
.card-head-text .card-hint { margin-top: var(--space-025); line-height: 1.35; }
/* La pieza trae su banda, y once tarjetas la meten DENTRO de la suya porque
   llevan algo a la derecha (las columnas, un botón): la de adentro pintaba un
   segundo filete, corto y descolgado bajo el título (Santiago, 2026-10-30).
   Adentro de otra banda es solo el interior. */
.card-header .card-header { margin: 0; padding: 0; border-bottom: 0; }

/* ── Tus campos (`ui/_tus_campos.html`, 2026-10-29) ─────────────────────
   Las secciones de los campos personalizados de una ficha. Lo que creó la
   empresa va tintado y con borde punteado —«esto lo armaste tú»—; lo que
   trae un módulo, en blanco. Adentro, cada campo en su mini-card.
   `--bloque`: dentro de la tarjeta de un formulario, sin sombra propia. */
.campos--tuyos { background: var(--campos-tinte); border: 1px dashed var(--campos-borde); }
.campos--tuyos > .card-header,
.campos--tuyos > .campos-cabeza { border-bottom: 1px dashed var(--campos-borde); }
.campos--bloque {
    border-radius: var(--radius); padding: var(--space-150) var(--space-200);
    margin: var(--space-200) 0;
}
.campos--bloque:not(.campos--tuyos) { border: 1px solid var(--border); }
.campos-cabeza {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap;
    margin: 0 0 var(--space-150); padding-bottom: var(--space-150);
    border-bottom: 1px solid var(--border);
}
.campos-cabeza h2 { margin: 0; font-size: var(--fs-section); }
.campos-etiqueta {
    display: inline-flex; align-items: center; gap: var(--space-050);
    padding: var(--space-050) var(--space-100); border: 1px solid var(--primary-soft-border);
    border-radius: 999px; background: var(--card-bg); color: var(--primary-dark);
    font-size: var(--fs-small); font-weight: 600; white-space: nowrap;
    text-decoration: none;
}
a.campos-etiqueta:hover { background: var(--primary-soft); }
/* UNA columna, siempre: «debe de quedar un campo debajo del otro» (Renzo,
   2026-10-29). */
/* `container-type`: el rótulo y la casilla van lado a lado según lo que
   mida la TARJETA, no la ventana (ver la regla de `@container` de abajo). */
.campos-rejilla {
    display: grid; gap: var(--space-150);
    grid-template-columns: minmax(0, 1fr);
    container-type: inline-size;
}
.campo-mini {
    display: flex; flex-direction: column; gap: var(--space-100); min-width: 0;
    padding: var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--card-bg);
}
.campo-mini--ancho { grid-column: 1 / -1; }
/* El rótulo al medio de su baldosa (Renzo, 2026-10-29). */
.campo-mini__cabeza { display: flex; align-items: center; gap: var(--space-100); }
.campo-mini__cabeza .tile { flex: none; }
.campo-mini__texto { min-width: 0; }
.campo-mini__texto > label {
    display: block; margin: 0; color: var(--text);
    font-size: var(--fs-body); font-weight: 600;
}
.campo-mini__desc { margin: var(--space-025) 0 0; color: var(--text-light); font-size: var(--fs-small); }
.campo-mini .field { margin-bottom: 0; }
/* EL RÓTULO A LA IZQUIERDA Y LA CASILLA A LA DERECHA (Renzo, 2026-11-04,
   con un referente): la baldosa, el rótulo y su descripción en la primera
   columna; el control —y el «Quitar» de un archivo— en la segunda. Sigue
   siendo un campo debajo del otro. Donde no cabe —una tarjeta angosta, el
   teléfono— vuelve a ir la casilla debajo. El campo ancho (texto largo,
   texto rico) se queda debajo siempre: en media tarjeta no se escribe un
   párrafo, y la casilla sola (`:only-child`) ocupa la fila entera. */
@container (min-width: 520px) {
    .campo-mini:not(.campo-mini--ancho) {
        display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
        align-items: center; column-gap: var(--space-300);
    }
    .campo-mini:not(.campo-mini--ancho) > :not(.campo-mini__cabeza) { grid-column: 2; }
    .campo-mini > .campo-mini__cabeza:only-child { grid-column: 1 / -1; }
}
.campos-pie,
.campos-vacio { margin: var(--space-150) 0 0; color: var(--text-light); font-size: var(--fs-small); }
.campos-pie { padding-top: var(--space-150); border-top: 1px dashed var(--campos-borde); }
/* Lo que la tarjeta quiere decir de sí misma, al otro extremo de la banda: hoy
   qué significa la coincidencia elegida en las condiciones de una colección.

   En su caja y no como texto suelto: la banda ya tiene un texto apagado —la
   pista, debajo del título— y dos grises en el mismo renglón se leen como uno
   partido en dos. `min-width: 0` y `flex-wrap` heredados de `.card-header`
   hacen que baje a su propio renglón cuando la tarjeta se angosta, en vez de
   aplastar el título. */
.card-head-note {
    display: flex; align-items: center; gap: var(--space-100);
    margin: 0; padding: var(--space-075) var(--space-150);
    border-radius: var(--radius-field); background: var(--bg);
    font-size: var(--fs-small); color: var(--text-light);
}

/* ── La baldosa ────────────────────────────────────────────────────────────
   Un icono dentro de un cuadro redondeado con tinte. Es la pieza que le pone
   cara a una sección sin gastarle una ilustración: el encabezado de una
   tarjeta, la primera celda de una fila, el banner de una pantalla.

   El TINTE dice de qué se trata y reusa los pares de la semántica —los mismos
   que las pastillas—, así que un ámbar significa lo mismo en los dos sitios.
   `brand` es el neutro: «esto es una sección», sin carga.

   El `<svg>` se dimensiona ACÁ y no en cada plantilla, igual que en `.btn`:
   con el tamaño escrito en el marcado, la tercera baldosa sale de 18px porque
   alguien la copió de otro sitio. */
/* ── La tarjeta de un archivo (`ui/_archivo.html`, 2026-10-11) ─────────
   La piel de las tarjetas de Tareas (`--lista-*`): degradado blanco, filo
   y sombra larga lila. La baldosa lleva la sigla del tipo con el color de
   las baldosas de la casa —PDF peligro, hoja de cálculo ok, CSV info, lo
   demás la marca—, que ya traen su noche. «Descargar» es el botón puesto
   del conmutador, pero es rótulo: el enlace es la tarjeta entera. */
.archivo {
    display: flex; align-items: center; gap: var(--space-150);
    max-width: 440px; padding: var(--space-100) var(--space-150) var(--space-100) var(--space-100);
    border-radius: 18px; color: var(--text); text-decoration: none;
    background: linear-gradient(180deg, var(--lista-arriba), var(--lista-abajo));
    border: 1px solid var(--lista-filo);
    box-shadow: inset 0 1px 0 var(--lista-brillo), var(--lista-sombra);
    transition: transform .2s ease;
}
.archivo:hover { color: var(--text); transform: translateY(-1px); }
.archivo-sigla {
    flex: none; display: grid; place-items: center;
    width: 44px; height: 52px; border-radius: 10px;
    font-size: var(--fs-meta); font-weight: 700; letter-spacing: .04em;
    background: var(--primary-soft); color: var(--primary-dark);
}
.archivo-sigla svg { width: 22px; height: 22px; }
.archivo[data-tipo="pdf"] .archivo-sigla { background: var(--danger-bg); color: var(--danger-text); }
.archivo[data-tipo="xlsx"] .archivo-sigla,
.archivo[data-tipo="xls"] .archivo-sigla { background: var(--ok-bg); color: var(--ok-text); }
.archivo[data-tipo="csv"] .archivo-sigla { background: var(--info-bg); color: var(--info-text); }
.archivo-datos { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-025); }
.archivo-nombre { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-body); font-weight: 600; }
.archivo-meta { font-size: var(--fs-meta); color: var(--text-light); }
.archivo-bajar {
    flex: none; display: inline-flex; align-items: center; gap: var(--space-075);
    height: 34px; padding: 0 var(--space-150); border-radius: 12px;
    font-size: var(--fs-small); font-weight: 600; color: var(--primary);
    background: var(--lista-puesto); border: 1px solid var(--lista-vidrio-filo);
    box-shadow: var(--lista-puesto-sombra);
}
.archivo-bajar svg { width: 16px; height: 16px; }

/* EL LOGO DE UNA MARCA AJENA (`ui/_logo_marca.html`, 2026-10-18): una
   baldosa BLANCA en los dos temas con la SILUETA del logo —el SVG como
   máscara—, o la inicial mientras la marca no tenga su archivo, o un glifo
   de la casa para lo que no es marca. Todo en una sola tinta,
   `--logo-tinta`: morado por omisión, y quien la usa la cambia
   (Integraciones: gris claro lo que no está conectado). */
.logo-marca {
    --logo-tinta: var(--primary);
    flex: none; display: grid; place-items: center;
    width: 56px; height: 56px; border-radius: 16px;
    background: #fff;
    border: 1px solid color-mix(in srgb, var(--border) 75%, transparent);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 8px 18px -12px rgba(40, 30, 90, .28);
}
/* Un logo ANCHO (PayU, FedEx: palabra, no símbolo) en un cuadro de 30 se
   ve como una raya: la caja es de 40 × 30 y `contain` mete la máscara
   entera en su proporción — el cuadrado sale de 30, la palabra llega a 40. */
.logo-marca__dibujo {
    display: block; width: 40px; height: 30px; background: var(--logo-tinta);
    -webkit-mask: center / contain no-repeat; mask: center / contain no-repeat;
}
.logo-marca svg { width: 26px; height: 26px; color: var(--logo-tinta); }
.logo-marca__inicial {
    display: grid; place-items: center; width: 100%; height: 100%;
    border-radius: inherit; font-size: var(--fs-page); font-weight: 800;
    letter-spacing: -.02em; line-height: 1;
    background: color-mix(in srgb, var(--logo-tinta) 11%, #fff);
    color: var(--logo-tinta);
}
.logo-marca--sm { width: 36px; height: 36px; border-radius: 11px; }
.logo-marca--sm .logo-marca__dibujo { width: 26px; height: 20px; }
.logo-marca--sm svg { width: 18px; height: 18px; }
.logo-marca--sm .logo-marca__inicial { font-size: var(--fs-columna); }

.tile {
    flex: none; display: grid; place-items: center;
    width: 38px; height: 38px; border-radius: var(--radius-sm);
    background: var(--primary-soft); color: var(--primary-dark);
}
.tile svg { width: 19px; height: 19px; }
.tile-brand  { background: var(--primary-soft); color: var(--primary-dark); }
.tile-ok     { background: var(--ok-bg);     color: var(--ok-text); }
.tile-warn   { background: var(--warn-bg);   color: var(--warn-text); }
.tile-danger { background: var(--danger-bg); color: var(--danger-text); }
.tile-info   { background: var(--info-bg);   color: var(--info-text); }
/* El gris de «esto no lo decides tú»: los dominios que da el sistema, un
   registro que sólo se lee. */
.tile-muted  { background: var(--bg); color: var(--text-faint); }
/* La de una CELDA, y del MISMO tamaño que la miniatura de una foto.

   Decía «más chica y más cerrada: en una fila de 40px la grande manda el
   alto, y veinte filas crecen por un adorno», y era cierto mientras fuera
   la única forma de abrir una fila. Dejó de serlo: `.cell-thumb` abre las
   listas que llevan foto —productos, terceros, archivos, usuarios— a 35px,
   y ésta abría las otras treinta y cinco a 26. Navegando entre listas, las
   filas cambiaban de altura sin que la diferencia dijera nada.

   El argumento del alto sigue valiendo y por eso el tamaño común es 35 y no
   38: lo que se igualó es el de CELDA, no el de pantalla.

   El radio y el glifo van derivados del tamaño, no escritos: a 26px un
   radio de 7 y un glifo de 14 eran proporción, y copiarlos tal cual a 35
   los dejaría de serlo. */
.tile-sm {
    width: var(--cell-thumb); height: var(--cell-thumb);
    border-radius: 9px;
}
.tile-sm svg { width: 18px; height: 18px; }
/* En círculo (`redonda=1`). */
.tile-redonda { border-radius: 50%; }
/* Lo que falta (`punteada=1`): sin fondo, borde y glifo punteados y tenues,
   para que se distinga de lo hecho por la forma y no solo por el tono. */
.tile-punteada {
    background: transparent;
    border: 1.5px dashed color-mix(in srgb, var(--text-faint) 32%, transparent);
    color: color-mix(in srgb, var(--text-faint) 48%, transparent);
}
.tile-punteada svg { stroke-dasharray: 2.4 2; }
/* La del BANNER, sobre el negro. El tinte claro de las otras se ve como un
   parche encendido ahí, así que ésta es un vidrio: el mismo fondo, un poco
   más claro.

   Va ANIDADA bajo `.page-banner` y no suelta, y no es por especificidad: es
   la única de las baldosas que pinta con `--banner-text`, que es blanco puro.
   Suelta, quien la copiara a una tarjeta se llevaría un glifo blanco sobre
   fondo blanco — el contenido entero, invisible. Anidada, fuera del banner
   simplemente no aplica y cae al tinte de marca de `.tile`, que se ve.
   `test_the_banner_text_colour_is_only_used_on_dark_blocks` lo verifica, y es
   quien lo cazó al escribir esto. */
.page-banner .tile-banner {
    width: 38px; height: 38px; border-radius: 999px;
    background: var(--primary-soft); color: var(--primary);
}
.page-banner .tile-banner svg { width: 20px; height: 20px; }



/* En una tarjeta que ES una tabla, el encabezado pega con el `thead` y no deja
   un renglón en blanco entre los dos. `.card-header` trae `margin-bottom` para
   separarse del contenido normal de una tarjeta —un formulario, un párrafo—,
   pero acá lo que sigue es la cabecera de la tabla, que tiene su propio borde:
   con el hueco en medio se leen DOS líneas separadas por una franja vacía, que
   es exactamente lo que se ve en la captura. */
.card-table > .card-header { margin-bottom: 0; }

/* Y cuando la paginación va DENTRO de la tarjeta —debajo de la tabla— la tabla
   deja de ser lo último: sus esquinas redondeadas caen a media tarjeta, con el
   paginador colgando debajo y pegado al filo, porque `.card-table` le quita a
   la tarjeta el relleno de abajo justamente para que la tabla llegue al borde.

   Se cuadran las esquinas cuando hay algo después, y el relleno se lo pone ese
   algo. `:not(:last-child)` y no una clase nueva: quien decide es la posición,
   y una clase habría que acordarse de ponerla en las tres listas paginadas. */
.card-table > .table-wrap:not(:last-child),
.card-table > [data-bulk-form]:not(:last-child) > .table-wrap { border-radius: 0; }
.card-table > .pager { margin-top: 0; padding: var(--space-200) var(--space-250); }

/* La última fila NO va contra el filo de la tarjeta (Renzo, 2026-11-02:
   «no puede quedar tan apretado en el borde»). `.card-table` le quita a la
   tarjeta el relleno de abajo para que la tabla llegue a los bordes, y
   cuando no hay paginador ni «Agregar fila» debajo, la última fila quedaba
   a un píxel de la esquina redondeada. El aire lo pone la tabla que es lo
   último de su tarjeta —sola, o dentro del formulario que la envuelve—; si
   hay algo después, el relleno se lo pone ese algo, como arriba. */
.card-table > .table-wrap:last-child,
.card-table > form:last-child > .table-wrap:last-child,
.card-table > [data-bulk-form]:last-child > .table-wrap:last-child { padding-bottom: var(--space-150); }

/* Y lo mismo para «Agregar fila», que es lo otro que puede ir DEBAJO de una
   tabla: el relleno se lo pone él, igual que el paginador, porque la tarjeta
   se lo quitó para que la tabla llegara al filo.

   Al ras de la izquierda y no centrado: la fila que va a crear empieza ahí,
   y un botón centrado bajo una tabla se lee como el pie del formulario —el
   sitio de guardar—, que es justo lo que NO es. */
.table-add { padding: var(--space-200) var(--space-250); }

/* La tabla llega a los BORDES de la tarjeta (Renzo, 2026-08-14).

   Se quedaba dentro del relleno, y mientras el encabezado fue blanco no se
   notaba: no había nada que dibujara dónde terminaba la tabla. Con la banda
   gris sí — arranca a 21px del filo y deja una franja blanca a cada lado,
   que se lee como una tabla despegada de su propia tarjeta.

   El margen negativo estuvo antes y se quitó por un argumento correcto: con
   la tabla de variantes compartiendo tarjeta con la caja de ejes, la sangría
   dejaba dos anchos distintos y el ojo perdía la columna. Ese argumento se
   respeta y por eso vuelve DE OTRA FORMA: la caja se ensancha hasta el filo,
   pero el TEXTO no se mueve, porque la primera y la última celda llevan
   exactamente el relleno que la tarjeta acaba de ceder (`--sp-5` las dos).
   El nombre de la primera fila cae en la misma vertical que el título de la
   tarjeta y que cualquier otro contenido; lo único que se estira es el
   fondo. Es la diferencia entre sangrar la tabla y sangrar sus datos.

   `--sp-5` va escrito y no derivado de `.card`: son el mismo valor y tienen
   que seguir siéndolo. Si algún día el relleno de la tarjeta cambia, esto
   deja de cuadrar — y ahí es donde hay que mirar. */
.card-table { padding-bottom: 0; }
/* En el celular, la tarjeta de resultados de una LISTA no se encoge a una
   fila: con un solo resultado quedaba una tira suelta flotando arriba
   (Renzo, 2026-10-09). Llega hasta más o menos el pie de la pantalla; las
   tablas de las fichas no entran, por eso va atada a `#results`. */
@media (max-width: 767px) {
    #results > .card-table { min-height: 60dvh; }
}
/* El `<form>` de la selección múltiple se metió EN MEDIO, y con él la tabla
   dejó de ser hija directa de la tarjeta: la regla de arriba dejó de
   acertar, la sangría volvió y el radio de 20px de `.table-wrap` reapareció
   —«se separó otra vez y quedó curvo el encabezado»—.

   La salida no es repetir el selector con un nivel más, sino que el que
   llegue al filo sea el FORMULARIO: así la barra y la tabla van a lo ancho
   por estar dentro de él, sin que ninguna de las dos lleve margen propio, y
   el día que algo más entre en ese formulario no hay una tercera regla que
   acordarse de escribir. */
/* Lo mismo el formulario de la barra de guardar que envuelve su tabla (los
   pagos fijos del contrato, 2026-11-02): sin él, la tabla quedaba metida
   con el relleno de la tarjeta y su `thead` no llegaba a los filos. */
.card-table > [data-bulk-form],
.card-table > [data-savebar-form] {
    margin: 0 calc(var(--space-250) * -1);
    /* El ancho no lo da `width: auto`: con márgenes negativos a los dos
       lados, un contenedor de scroll sigue midiendo el ancho de su padre y
       se desbordaría por la derecha. */
    width: auto;
}
/* La tabla SIN selección: es ella la que llega al filo, porque no hay
   formulario que lo haga por ella. */
.card-table > .table-wrap {
    margin: 0 calc(var(--space-250) * -1);
    width: auto;
    border-radius: 0 0 var(--radius-card) var(--radius-card);
}
/* Y la tabla DENTRO del formulario no lleva margen: el filo ya lo alcanzó
   su padre, y repetirlo la sacaría otros 20px por cada lado.
   Las dos declaraciones van en UN bloque por selector, no repartidas entre
   dos: `test_stylesheet` prohíbe que el mismo selector fije la misma
   propiedad en dos sitios, y con razón — gana el de más abajo, que es un
   orden que nadie ve al leer el de arriba. */
.card-table > [data-bulk-form] > .table-wrap,
.card-table > [data-savebar-form] > .table-wrap {
    margin: 0;
    border-radius: 0 0 var(--radius-card) var(--radius-card);
}

/* LA LISTA (Renzo, 2026-10-11: «las tablas … con ese look and feel de
   tableros de tareas»). La tarjeta de una lista lleva la piel de las
   tarjetas del tablero: degradado blanco, filo blanco por dentro, sombra
   larga lila y 24px de radio. El radio va por `--radius-card` y no por
   `border-radius`, para que las esquinas de abajo de la tabla (que lo leen)
   sigan cuadrando con las de la tarjeta. SIN `overflow: hidden`: recortaría
   el «⋯» de las filas.

   Solo en las LISTAS: la tabla que htmx pinta en `#results`, y la que una
   pantalla pone suelta en la página (`.content > .card-table`: Informes,
   Temas; Renzo, 2026-10-11, «veo varias así»). En una ficha las tarjetas con
   tabla van dentro de su rejilla y no entran: quedaban de 24px junto a las
   de 12 de la columna.

   El encabezado pierde la banda gris del 2026-08-14: con la tarjeta en
   degradado la banda se leía como un parche; lo separa la raya de abajo y
   el gris lila. La fila al pasar se tiñe del primario, apenas. */
#results .card.card-table, .content > .card.card-table {
    --radius-card: 24px;
    background: linear-gradient(180deg, var(--lista-arriba), var(--lista-abajo));
    border-color: var(--lista-filo);
    box-shadow: inset 0 1px 0 var(--lista-brillo), var(--lista-sombra);
}
#results .card-table .table-wrap, .content > .card-table .table-wrap { background: none; }
#results .card-table .table thead th,
.content > .card-table .table thead th { background: none; color: var(--lista-encabezado); }
/* El buscador y «Columnas», con la cápsula del conmutador de Tareas
   (Renzo, 2026-10-11, con la captura enfrente).

   El buscador es una cápsula de vidrio que flota sobre la página: la fila
   de filtros deja de ser tarjeta, porque vidrio blanco sobre tarjeta blanca
   no se ve. El foco sigue sin halo (2026-10-09): solo el filo se tiñe del
   primario.

   «Columnas» va dentro de la tarjeta blanca, así que no es vidrio sino el
   botón PUESTO del conmutador: blanco con sombra lila; abierto, la tinta en
   el primario. */
.card.filtros-fila { background: none; border: 0; box-shadow: none; }
.filtros-fila .filtros-caja {
    min-height: 46px; padding: 0 var(--space-200); gap: var(--space-100); border-radius: 18px;
    background: linear-gradient(180deg, var(--lista-vidrio-arriba), var(--lista-vidrio-abajo));
    border-color: var(--lista-vidrio-filo);
    box-shadow: inset 0 1px 0 var(--lista-vidrio-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--lista-vidrio-sombra);
}
.filtros-fila .filtros-caja:focus-within {
    border-color: color-mix(in srgb, var(--primary) 45%, var(--lista-vidrio-filo));
    box-shadow: inset 0 1px 0 var(--lista-vidrio-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--lista-vidrio-sombra);
}
.filtros-fila .filtros-lupa { width: 17px; height: 17px; color: var(--lista-encabezado); }
#results .colmenu > summary.btn, .content > .card-table .colmenu > summary.btn {
    height: 38px; padding: 0 var(--space-200); gap: var(--space-100); border-radius: 13px;
    font-size: var(--fs-small); font-weight: 500; color: var(--lista-encabezado);
    background: var(--lista-puesto); border: 1px solid var(--lista-vidrio-filo);
    box-shadow: var(--lista-puesto-sombra);
    transition: color .2s, box-shadow .2s;
}
#results .colmenu > summary.btn:hover, .content > .card-table .colmenu > summary.btn:hover { color: var(--text); background: var(--lista-puesto); }
#results .colmenu[open] > summary.btn, .content > .card-table .colmenu[open] > summary.btn { color: var(--primary); font-weight: 600; }
#results .colmenu > summary.btn svg, .content > .card-table .colmenu > summary.btn svg { width: 17px; height: 17px; }
#results .card-table .table tbody tr:hover,
.content > .card-table .table tbody tr:hover { background: color-mix(in srgb, var(--primary) 4%, transparent); }

/* ── Ficha en dos columnas ────────────────────────────────────────────── */

/* Lo que se escribe leyendo a la izquierda; lo que se elige de una lista y no
   se relee, a la derecha. Un producto de LTDC tiene trece variantes y cinco
   campos de su app, y en una sola columna eso son tres pantallas de scroll con
   el estado enterrado en la mitad.

   `align-items: start` o el riel estira sus tarjetas hasta el alto de la
   columna izquierda, y una tarjeta de un `select` mide media pantalla.

   El riel es una MEDIDA FIJA y la principal es la que cede: al revés, en un
   monitor ancho el riel se estira y un desplegable de 500px de ancho se lee
   como un error. `minmax(0, 1fr)` y no `1fr` porque una tabla ancha adentro
   —la de variantes lo es— infla la columna por encima de la rejilla. */
.product-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 312px;
    gap: var(--space-200); align-items: start;
}
.product-main, .product-rail {
    display: flex; flex-direction: column; gap: var(--space-200); min-width: 0;
}
/* Las tarjetas de la ficha NO llevan el tope de lectura de `.form-card`: ya
   están acotadas por la columna, y sumarle 760px encima dejaba una franja
   vacía a la derecha de cada una. */
.product-main > .card { width: 100%; }

/* Un solo eje cuando el riel dejaría a la columna principal más angosta que
   una tabla de seis columnas. Debajo de eso el riel va ARRIBA: el estado y el
   tipo son lo primero que alguien mira al abrir un producto, y mandarlos al
   final de tres pantallas de scroll es esconderlos. */
@media (max-width: 1100px) {
    .product-layout { grid-template-columns: minmax(0, 1fr); }
    .product-rail { order: -1; }
}

/* ── La ficha de la plantilla de etiqueta ─────────────────────────────────
   El riel es MÁS ANCHO que el de un producto: lo que lleva es un dibujo del
   papel, y a 312px un rollo de tres columnas se lee con letra de hormiga.
   Se pega arriba al bajar, porque es lo que se mira mientras se teclean las
   medidas de la columna de al lado. */
.product-layout-preview {
    grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
}
/* `top` libra la banda pegada del título (`.content .page-banner`), que mide
   lo que su botón de tres puntos: `--band-h`. */
.label-preview-rail {
    position: sticky; top: calc(var(--band-h) + var(--space-200));
}
@media (max-width: 1100px) {
    .product-layout-preview { grid-template-columns: minmax(0, 1fr); }
    .label-preview-rail { position: static; }
}
.label-preview-rail .card-header { justify-content: space-between; }
.label-preview-live {
    display: inline-flex; align-items: center; gap: var(--space-100);
    padding: var(--space-050) var(--space-100); border-radius: 999px;
    background: var(--ok-bg); color: var(--ok-text);
    font-size: var(--fs-meta); font-weight: 600;
}

/* El papel (`labels.preview`). El SVG trae su `viewBox` en milímetros y sus
   colores de PAPEL —blanco sobre soporte gris, tinta negra— en los dos
   temas: es un dibujo del papel, no de la interfaz. */
.label-preview {
    margin-bottom: var(--space-150); padding: var(--space-150);
    border-radius: var(--radius-sm); background: var(--bg);
}
.label-preview svg {
    display: block; width: 100%; height: auto;
    border-radius: var(--radius-sm);
}
/* La regla con el ancho del rollo: una línea con sus dos topes y la medida
   en el centro, como se acota un plano. */
.label-preview-ruler {
    position: relative; margin-bottom: var(--space-100); text-align: center;
    border-bottom: 1px solid var(--text-light);
    font-size: var(--fs-meta); color: var(--text-light);
}
.label-preview-ruler::before, .label-preview-ruler::after {
    content: ''; position: absolute; bottom: -4px;
    width: 1px; height: 7px; background: var(--text-light);
}
.label-preview-ruler::before { left: 0; }
.label-preview-ruler::after { right: 0; }
.label-preview-ruler span { display: inline-block; padding-bottom: var(--space-025); }

.label-preview-facts, .label-preview-printer {
    display: flex; align-items: center; gap: var(--space-150);
    margin-bottom: var(--space-150); padding: var(--space-150);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.label-preview-fact {
    flex: 1 1 0; display: flex; align-items: center; gap: var(--space-100);
}
.label-preview-fact + .label-preview-fact {
    padding-left: var(--space-150); border-left: 1px solid var(--border);
}
/* Por su clase y no `span` a secas: la baldosa TAMBIÉN es un `span`, y la
   columna flexible le corría el icono a la esquina. */
.label-preview-text { display: flex; flex-direction: column; }
.label-preview-fact small, .label-preview-printer small {
    color: var(--text-light); font-size: var(--fs-meta);
}

/* «Contenido de la etiqueta»: una fila por campo, que se arrastra por su
   agarre (`drag-sort.js`). La del código de barras va fija al final. */
.label-fields { display: flex; flex-direction: column; gap: var(--space-100); }
.label-field-row {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-150);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--card-bg);
}
.label-field-row[hidden] { display: none; }
.label-field-row.is-dragging { opacity: .45; }
.label-field-row.is-over { border-color: var(--primary); }
.label-field-grip { display: grid; cursor: grab; color: var(--text-faint); }
.label-field-grip svg { width: 16px; height: 16px; }
.label-field-name {
    flex: 1 1 auto; display: flex; flex-direction: column;
    font-weight: 500;
}
.label-field-fixed { margin-top: var(--space-150); background: var(--bg); }
.label-fields-add { margin-top: var(--space-100); }
.label-field-fixed + .field { margin-top: var(--space-200); }
.label-paper-note {
    display: flex; flex-wrap: wrap; justify-content: space-between;
    gap: var(--space-100); margin: var(--space-150) 0 0; padding: var(--space-100) var(--space-150);
    border-radius: var(--radius-sm);
    background: var(--primary-soft); color: var(--primary-dark);
    font-size: var(--fs-meta);
}

/* La baldosa con el NÚMERO del paso (`ui/_card_head.html` con `step`). */
.tile-step {
    font-size: var(--fs-small); font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* ── Campo con unidad ─────────────────────────────────────────────────────
   Un `<input>` con su unidad pegada a la derecha («mm»), dentro de UN solo
   borde. La unidad no viaja: es un `<span>`. El foco se pinta en el marco y
   no en la casilla, igual que la pastilla de la casilla de verificación.
       <span class="input-unit"><input …><span class="input-unit-label">mm</span></span> */
.input-unit {
    display: flex; align-items: stretch; overflow: hidden;
    background: var(--field-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    transition: border-color .16s, box-shadow .16s;
}
.input-unit:focus-within {
    border-color: var(--primary); box-shadow: 0 0 0 3.5px var(--primary-soft);
}
.input-unit > input, .input-unit > input:focus {
    flex: 1 1 auto; min-width: 0;
    border: 0; border-radius: 0; box-shadow: none;
}
.input-unit-label {
    display: grid; place-items: center; padding: 0 var(--space-150);
    border-left: 1px solid var(--border); background: var(--bg);
    color: var(--text-light); font-size: var(--fs-small);
}
/* La unidad ANTES de la cifra: el «$» de `ui/_money_input.html` con
   `prefijo` (2026-10-19). Dentro de una tabla, a la medida de su casilla
   y no a todo el ancho de la celda, que alinea la cifra a la derecha. */
.input-unit-antes > .input-unit-label {
    border-left: 0; border-right: 1px solid var(--border);
    padding: 0 var(--space-100);
}
.table .input-unit-antes { display: inline-flex; vertical-align: middle; }

/* ── Contador ─────────────────────────────────────────────────────────────
   `ui/_stepper.html`: − y + a los lados de un `<input type="number">`, en
   un solo borde. Las flechitas del navegador se apagan: con los dos botones
   serían cuatro formas de hacer lo mismo. */
.stepper {
    display: flex; align-items: stretch; overflow: hidden;
    background: var(--field-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
}
.stepper:focus-within { border-color: var(--primary); }
/* En una celda de `.table` mide lo mismo en todas las filas: sin ancho, la
   celda lo encoge a su contenido y cada fila sale de otro tamaño. Salió de
   la tabla de recibir un envío (2026-10-16) al mudarse a `.table-apilada`. */
.table td .stepper { width: 9.5rem; }
/* En una columna de cifras (`td.num`) el control va pegado a la derecha,
   como el número y el encabezado: pegado a la izquierda, con el encabezado
   a la derecha, se leía como una columna sin título y «Contado» vacía
   (conteo de estantería, Santiago, 2026-10-30). */
.table td.num .stepper { margin-left: auto; }
.table td.num .inline-row { justify-content: flex-end; }
.stepper > input[type="number"], .stepper > input[type="number"]:focus {
    flex: 1 1 auto; min-width: 0; text-align: center;
    border: 0; border-radius: 0; box-shadow: none;
    -moz-appearance: textfield; appearance: textfield;
}
.stepper > input::-webkit-inner-spin-button,
.stepper > input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.stepper-btn {
    flex: none; width: 40px; border: 0; cursor: pointer;
    background: var(--bg); color: var(--text);
    font: inherit; font-size: var(--fs-body);
}
.stepper-btn:first-child { border-right: 1px solid var(--border); }
.stepper-btn:last-child { border-left: 1px solid var(--border); }
/* Solo botones (2026-10-18): el número es un `<output>`, no un campo. */
.stepper-numero {
    flex: 1 1 auto; min-width: 2.25em; display: grid; place-items: center;
    padding: 0 var(--space-075); font-weight: 600; font-variant-numeric: tabular-nums;
}
.stepper-btn:hover { background: var(--primary-soft); color: var(--primary-dark); }

/* ── Ejes de opción ───────────────────────────────────────────────────── */

/* Los ejes no son un modelo: son lo que dicen las variantes, agrupado. Trece
   filas con «Presentación» y «Molienda» son en realidad dos ejes de pocos
   valores, y verlos así es la diferencia entre entender un producto de un
   vistazo y deducirlo leyendo la tabla. */
/* ── Los ejes de opción, y su orden ────────────────────────────────────────
   Cada eje es un RENGLÓN con su nombre arriba y sus valores debajo, separados
   por una línea. Antes iban en una sola fila —nombre a la izquierda, valores a
   la derecha— y con tres ejes de seis valores eso es una pared: el nombre
   quedaba huérfano contra una hilera que envolvía tres veces.

   El renglón es además lo que hace agarrable el orden: se arrastra o se sube
   con la flecha, y las dos cosas necesitan una fila con altura, no una línea
   de texto. */
.option-axes {
    display: flex; flex-direction: column;
    /* Aire contra el encabezado de la tarjeta. `--sp-4` son 16px y no los 15
       del boceto: el proyecto tiene escala, y un número suelto acá es el
       primero de los que después nadie sabe si eran a propósito. */
    margin-top: var(--space-200);
    margin-bottom: var(--space-100);
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card-bg); overflow: hidden;
}
/* Las tres piezas van con la celda ESCRITA, ninguna colocada sola. La versión
   anterior dejaba el nombre en automático y salía en la TERCERA columna, con
   el bloque de flechas ocupando la segunda encima de los valores: la
   colocación automática ubica primero los ítems que declaran fila —la manija
   la declaraba— y el que no declara nada se va al hueco que quede. Medido en
   la ficha: el nombre en x=828 y los valores en x=335, o sea el título a la
   derecha de sus propios valores. */
.option-axis {
    display: grid; align-items: center; gap: var(--space-050) var(--space-150);
    grid-template-columns: auto minmax(0, 1fr);
    padding: var(--space-150) var(--space-200);
}
.option-axis + .option-axis { border-top: 1px solid var(--border); }
.option-axis.is-dragging { opacity: .45; }
.option-axis.is-over { background: var(--primary-soft); }
/* La manija dice «esto se agarra», y es lo ÚNICO que mueve: sin JavaScript no
   hay orden que cambiar. */
.option-axis-grip {
    grid-column: 1; grid-row: 1 / 3;
    color: var(--text-faint); font-size: var(--fs-body);
    line-height: 1; cursor: grab; user-select: none;
}
.option-axes[data-axis-sort] .option-axis-grip:active { cursor: grabbing; }
.option-axis-name {
    grid-column: 2; grid-row: 1;
    color: var(--text); font-size: var(--fs-body); font-weight: 600;
}
.option-axis-values {
    grid-column: 2; grid-row: 2; display: flex; gap: var(--space-075); flex-wrap: wrap;
}
.option-axes-hint {
    margin: 0 0 var(--space-200); color: var(--text-light); font-size: var(--fs-small);
}
/* «Agrupar por» de la tabla de variantes (Santiago, 2026-11-05): el rótulo
   y el interruptor en un renglón, sobre la tabla y con su mismo margen. */
.variantes-agrupar {
    display: flex; align-items: center; gap: var(--space-150);
    margin: 0 0 var(--space-150);
}
.variantes-agrupar .field-label { margin: 0; }
/* Agrupada, la tabla no se mueve al abrir o cerrar un grupo: anchos fijos. */
.variantes-agrupadas { table-layout: fixed; }
.variantes-agrupadas .col-agarre { width: 3rem; }
.variantes-agrupadas .col-precio { width: 24%; }
.variantes-agrupadas .col-costo { width: 14%; }
.variantes-agrupadas .col-disponible { width: 13%; }
.variantes-agrupadas .col-acciones { width: 9rem; }
/* Rellenas y sin borde: son una MUESTRA de los valores que hay, no controles.
   Con borde competían con los campos del formulario de arriba. */
.chip {
    padding: var(--space-050) var(--space-100); border-radius: 8px;
    background: var(--bg); border: 1px solid transparent;
    color: var(--text); font-size: var(--fs-small); white-space: nowrap;
}
/* Con la tinta de la marca cuando la pastilla es el DATO de la frase y no una
   muestra: el valor de una condición es lo que alguien escribió, y en el gris
   de fondo se perdía entre el campo y el operador. Es un modificador de
   `.chip` y no otra clase: sigue siendo la misma pastilla de solo lectura. */
.chip-brand { background: var(--primary-soft); color: var(--primary-dark); font-weight: 600; }
/* El tope, dicho. Sin borde y sin fondo: no es un valor del eje, es la cuenta
   de los que no caben — pintarla igual que a los demás la haría parecer uno. */
.chip-more {
    background: none; border-color: transparent;
    color: var(--text-faint); font-style: italic;
}

/* ── El feed con la piel de tiendapp ─────────────────────────────────────────
   «En vivo» dice cómo se lee la lista, no que haya un socket. El glifo por
   fila viene de `icons.py`; la denegación se tiñe de peligro porque lo que se
   guarda es que NO pasó nada, y eso se tiene que ver distinto. */
.home-feed-header { display: flex; align-items: center; justify-content: space-between; }
.home-feed-live {
    display: inline-flex; align-items: center; gap: var(--space-075);
    font-size: var(--fs-meta); font-weight: 600; color: var(--text-light);
    text-transform: uppercase; letter-spacing: .06em;
}
.home-feed-live::before {
    content: ''; width: 6px; height: 6px; border-radius: 50%;
    background: var(--ok-text); animation: live-pulse 1.8s ease-in-out infinite;
}
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }

.home-feed { list-style: none; margin: 0; padding: 0; }
.home-feed-item {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-150) var(--space-075); border-bottom: 1px solid var(--border);
    font-size: var(--fs-body); border-radius: 10px;
    transition: background .15s ease;
}
.home-feed-item:hover { background: var(--bg); }
.home-feed-item:last-child { border-bottom: none; }
.home-feed-glyph {
    flex: 0 0 auto; display: inline-flex; align-items: center;
    justify-content: center; width: 32px; height: 32px;
    border-radius: 10px; background: var(--bg); color: var(--text-light);
}
.home-feed-glyph svg { width: 16px; height: 16px; }
/* El tinte dice QUÉ pasó sin leer: verde es plata que entró, morado es
   alguien que llegó, rojo es un cobro que no pasó. La plantilla solo mira
   `kind` — el texto es de quien lo publica. */
.home-feed-item.kind-order .home-feed-glyph { background: var(--ok-bg); color: var(--ok-text); }
.home-feed-item.kind-charge .home-feed-glyph { background: var(--ok-bg); color: var(--ok-text); }
.home-feed-item.kind-customer .home-feed-glyph { background: var(--primary-soft); color: var(--primary-dark); }
.home-feed-item.kind-charge_failed .home-feed-glyph { background: var(--danger-bg); color: var(--danger-text); }
.home-feed-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.home-feed-when {
    flex: 0 0 auto; color: var(--text-light); font-size: var(--fs-small);
    white-space: nowrap; font-variant-numeric: tabular-nums;
}
.mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--fs-small); color: var(--text-light);
}
/* La casilla de un JSON o un arreglo. Hereda la letra de `.mono` pero NO su
   gris: acá el texto no es un dato secundario, es lo que se está editando.
   `tab-size` para que un JSON pegado con tabulaciones no salga escalonado a
   ocho espacios, y `nowrap` + scroll horizontal porque una línea larga
   partida a la mitad deja de ser JSON legible. */
textarea.mono {
    color: var(--text); line-height: 1.5; tab-size: 2;
    white-space: pre; overflow-wrap: normal; overflow-x: auto;
}

.empty { padding: var(--space-300) var(--space-250); text-align: center; }
.empty-compact { padding: var(--space-200) 0; text-align: left; }
.empty-icono {
    display: block; width: 36px; height: 36px; margin: 0 auto var(--space-100);
    padding: var(--space-100); box-sizing: border-box; border-radius: 50%;
    background: var(--bg); color: var(--text-light);
}
.empty-compact .empty-icono { margin-left: 0; }
.empty-title { margin: 0 0 var(--space-050); font-weight: 600; font-size: var(--fs-body); }
.empty-text { margin: 0 0 var(--space-200); color: var(--text-light); font-size: var(--fs-body); }
/* Arte y tarjetas (2026-10-08, el chat del equipo): la ilustración en vez
   del glifo, y una fila de tarjetas que cuentan o hacen algo. */
.empty-arte { display: grid; place-items: center; margin-bottom: var(--space-100); }
.empty-arte svg { max-width: 100%; height: auto; }
.empty-tarjetas { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-150); margin-top: var(--space-300); }
.empty-tarjeta { display: flex; align-items: center; gap: var(--space-150); padding: var(--space-150) var(--space-200); min-width: 170px; border: 1px solid var(--border); border-radius: var(--radius); background-color: var(--card-bg); color: var(--text-light); font-size: var(--fs-small); text-align: left; text-decoration: none; }
a.empty-tarjeta:hover { border-color: var(--primary); color: var(--text); }
.empty-tarjeta--info { cursor: default; }
/* Las tarjetas como fila de puntos separados por una línea (2026-10-09,
   el chat sin conversación): ícono arriba, rótulo debajo, sin caja. */
.empty-tarjetas--separadas { gap: 0; }
.empty-tarjetas--separadas .empty-tarjeta { flex-direction: column; justify-content: flex-start; gap: var(--space-100); min-width: 0; width: 170px; padding: var(--space-050) var(--space-250); border: 0; border-radius: 0; background: none; text-align: center; }
.empty-tarjetas--separadas .empty-tarjeta + .empty-tarjeta { border-left: 1px solid var(--border); }
.empty-tarjeta__rotulo { line-height: 1.3; }

/* El aviso de la llamada entrante (spec chat llamadas §3.2): arriba a la
   derecha, encima de todo, en cualquier pantalla del shell. Lo pinta
   `llamar.js`. */
.llamada-entrante { position: fixed; top: 16px; right: 16px; z-index: 1000; display: flex; align-items: center; gap: var(--space-150); width: min(380px, calc(100vw - 32px)); padding: var(--space-200) var(--space-200); border: 1px solid var(--border); border-radius: var(--radius-lg); background-color: var(--card-bg); color: var(--text); box-shadow: var(--shadow-lg); animation: llamada-entra .25s ease-out; }
.llamada-entrante__cara { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; flex: none; }
.llamada-entrante__cara--inicial { display: grid; place-items: center; background-color: var(--primary-soft); color: var(--primary); font-weight: 600; }
.llamada-entrante__quien { flex: 1; min-width: 0; display: grid; }
.llamada-entrante__quien span { color: var(--text-light); font-size: var(--fs-small); }
.llamada-entrante__texto { margin: 0; }
.llamada-entrante__botones { display: flex; gap: var(--space-100); }
.llamada-entrante__boton { padding: var(--space-100) var(--space-200); border: 0; border-radius: 999px; font: inherit; font-weight: 600; cursor: pointer; }
.llamada-entrante__boton--no { background-color: var(--danger-vivid); color: var(--danger-vivid-text); }
.llamada-entrante__boton--si { background-color: var(--ok-vivid); color: var(--ok-vivid-text); }
@keyframes llamada-entra { from { transform: translateY(-8px); opacity: 0; } to { transform: none; opacity: 1; } }
@media (max-width: 480px) {
  .llamada-entrante { flex-wrap: wrap; }
  .llamada-entrante__botones { width: 100%; justify-content: flex-end; }
}
.empty-tarjeta__icono { display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%; background-color: var(--primary-soft); color: var(--primary); flex: none; }
.empty-tarjeta__icono svg { width: 20px; height: 20px; }
@media (max-width: 640px) { .empty-tarjeta { min-width: calc(50% - 6px); } }

@media (max-width: 1023px) {
    body:not(.sidebar-collapsed) .sidebar { width: var(--rail); }
    body:not(.sidebar-collapsed) .sidebar .nav-label,
    body:not(.sidebar-collapsed) .sidebar .nav-badge,
    body:not(.sidebar-collapsed) .sidebar .nav-sublist,
    body:not(.sidebar-collapsed) .sidebar .sidebar-label,
    body:not(.sidebar-collapsed) .sidebar .sidebar-label-row { display: none; }
    body:not(.sidebar-collapsed) .sidebar .nav-item { justify-content: center; padding: var(--space-100) 0; }
}

@media (max-width: 767px) {
    .drawer-toggle { display: block; }
    /* La tarjeta de Configuraciones, en el teléfono, solo en su puerta
       (Módulos): una lista a lo ancho encima del contenido. En las demás
       pantallas se vuelve por el menú, que tiene el renglón. */
    .menu-lateral { display: none; }
    .shell:has(> .menu-lateral--puerta) { flex-direction: column; }
    .menu-lateral--puerta {
        display: block; width: auto; height: auto; position: static;
        margin: var(--shell-gap) var(--shell-gap) 0;
        border-radius: var(--radius-shell);
    }
    /* Le hace sitio a la hamburguesa, que es fija sobre la topbar: 44px de
       caja menos los 12 que se corre a la izquierda, más 12 de aire. */
    .topbar-left { padding-left: var(--space-500); }
    .topbar-crumb { display: none; }
    /* En el celular el menú es el cajón: no hay riel que fijar. */
    .menu-fijar { display: none; }
    /* EL ISOTIPO SOLO. El lockup mide 105px fijos —el `<svg>` no encoge— y
       `.topbar-left` sí se encoge por debajo de eso, así que el wordmark se
       salía de su caja y quedaba DEBAJO de la campana (Renzo, 2026-09-15,
       desde su iPhone; medido a 390px: la marca llega a 159 y la campana
       empieza en 149). Se recorta por ancho y no se esconde nada por dentro
       porque el isotipo es el primer cuadrado del `viewBox`: 30px de ancho
       son exactamente los siete puntos, sin tocar el archivo de la marca. */
    .topbar-brand { max-width: 34px; overflow: hidden; }
    /* `flex: none` es lo que hace que esto RECORTE en vez de encoger: el
       `<svg>` es hijo de un flex y por omisión se achica hasta caber, así
       que el lockup entero salía dibujado dentro de los 34px —«lo veo
       diminuto», Renzo, 2026-09-15—. Con la medida propia puesta, el ancho
       sobrante lo corta el `overflow` del enlace y queda el isotipo. La
       altura sube de 30 a 34 para igualar a la campana y al logo de la
       empresa, que es con quienes comparte renglón. */
    .topbar-brand .brand-logo { flex: none; height: 34px; }
    /* A LA DERECHA, SOLO LOS AVATARES (Renzo, 2026-09-15). El nombre de la
       empresa gastaba media barra —hasta 22 caracteres— y está repetido
       dentro del menú que abre ese mismo logo. Quedan la campana, la
       empresa y el compi, tres cosas redondas del mismo tamaño. */
    .user-menu-name { display: none; }
    .user-menu summary { gap: 0; padding: var(--space-050); }
    /* EL DESPLEGABLE DE LA CAMPANITA A TODO LO ANCHO. Colgado del
       disparador con `right: 0` se salía 47px por la izquierda a 390px
       —la campana está a 77px del borde y la caja mide 360—, y la inicial
       y el arranque del título quedaban fuera de la ventana. Fijo contra
       la ventana, bajo la topbar, con el mismo margen a los dos lados. */
    .notices .notices-pop {
        position: fixed; left: 12px; right: 12px; width: auto;
        top: calc(var(--topbar) + 6px);
    }
    /* `clip` recorta también a los descendientes fijos, y en este ancho el
       sidebar ES fijo: sin esto el cajón sale recortado por su propio
       contenedor. */
    .shell { margin-top: var(--topbar); padding: 0; }
    .sidebar {
        position: fixed; top: var(--topbar); left: 0; z-index: 90;
        margin-right: 0; border-radius: 0 18px 0 0;
        width: min(280px, 84vw) !important;
        transform: translateX(-102%); transition: transform .22s ease;
        background: var(--card-bg);
    }
    /* El checkbox manda: sin JS, el cajón abre y cierra igual. */
    .drawer-toggle:checked ~ .shell .sidebar { transform: translateX(0); }
    .drawer-toggle:checked ~ .drawer-overlay {
        display: block; position: fixed; inset: var(--topbar) 0 0 0;
        z-index: 89; background: rgba(13, 11, 22, .45);
    }
    .sidebar .nav-label, .sidebar .nav-badge,
    .sidebar .nav-sublist, .sidebar .sidebar-label, .sidebar .sidebar-label-row {
        display: revert !important;
    }
    .sidebar .nav-item { justify-content: flex-start !important; padding: var(--space-100) var(--space-150) !important; }
    /* El radio se pinta acá y no en `.shell` porque en este ancho el shell
       no recorta —tiene que dejar salir el cajón—, así que las esquinas
       cuadradas del contenido taparían las suyas. */
    .content { padding: var(--space-200) var(--space-200); border-radius: 18px 18px 0 0; }
    /* En el celular el panel tapa la página entera, como el menú. Entra
       DESLIZÁNDOSE (`transform`) y no creciendo: animar `width` relayouteaba
       el hilo, el compositor y cada personaje en cada cuadro, y en el
       teléfono se trababa (Renzo, 2026-10-03). Cerrado queda afuera y
       `hidden`, para que el tabulador no entre. */
    .side-panel, body.panel-open .side-panel {
        position: fixed; inset: var(--topbar) 0 0 0; z-index: 95;
        width: 100vw; height: auto; margin-left: 0; border-radius: 0;
    }
    .side-panel {
        transform: translateX(100%); visibility: hidden;
        transition: transform .22s ease, visibility 0s linear .22s;
    }
    body.panel-open .side-panel {
        transform: none; visibility: visible;
        transition: transform .22s ease, visibility 0s;
    }
    .side-panel-body { width: 100%; }
    body.panel-filtros .filtros-panel { width: 100%; }
}

/* ── Accesibilidad y pantallas chicas ──────────────────────────────────── */

@media (max-width: 480px) {
    .auth-card { padding: var(--space-400) var(--space-300) var(--space-300); border-radius: var(--radius); }
    .auth-title { font-size: var(--fs-page); }
}

@media (prefers-reduced-motion: reduce) {
    .orb, .auth-card, .auth-card > * { animation: none; }
    /* `!important` y no más especificidad: las animaciones del escritorio
       viven en selectores compuestos (`.home-orb.orb-cyan`) para poder pisar
       las de página completa, así que un `.home-orb` a secas acá pierde por
       especificidad AUNQUE venga después — y los cinco orbes seguían
       derivando con `prefers-reduced-motion` puesto. Enumerar los cinco
       compuestos arreglaría este caso y no el del sexto que llegue. */
    .home-orb { animation: none !important; }
    .sidebar { transition: none; }
    .side-panel, body.panel-open .side-panel { transition: none; }
    /* El personaje llega formado y quieto; sin respirar ni girar. */
    .compi *, .is-thinking .compi * { animation: none !important; }
    /* El panel llega puesto en vez de deslizándose. `drawer.js` no depende de
       `transitionend` para cerrar —tiene un plazo de respaldo— justo porque
       acá el evento no llega nunca. */
    dialog.drawer:modal { transition: none; transform: none; opacity: 1; }
    .drawer::backdrop { transition: none; }
    /* Y el sexto llegó: `.pw-rules li` y `.pw-ready` animan `rule-in` y no
       los alcanzaba NINGUNO de los selectores de arriba —el `li` es nieto de
       la tarjeta, y `animation` no se hereda del padre—, así que la
       contraseña se escribía con un deslizamiento por tecla, porque HTMX
       reemplaza ese fragmento en cada una. La lista no es el mecanismo: el
       `*` sí, y por eso ahora apaga las dos cosas y no solo las
       transiciones.

       `iteration-count` además de `duration`: los orbes son `infinite`, y una
       animación infinita de .01ms no se detiene, se repite miles de veces por
       segundo. Apagar el movimiento no puede costar más que dejarlo. */
    * {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
    }
    /* `*` no alcanza a `::before` ni a `::after`, y la duración no se hereda:
       la ✕ de la hamburguesa —dos pseudoelementos que giran— se seguía
       animando. */
    *::before, *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
    }
}

/* ── El buscador ───────────────────────────────────────────────────────────
   WebKit les pone `appearance: searchfield` a los `input[type="search"]` y
   con ella IGNORA el padding declarado: en Safari el buscador del home salía
   bajo y el icono —posicionado para un campo alto— caía encima del
   placeholder. Va por selector de elemento, como la casilla: los cuatro
   buscadores del ERP declaran su propio padding y a todos les pasa lo mismo.
   ------------------------------------------------------------------------ */

input[type="search"] {
    appearance: none; -webkit-appearance: none;
}

/* ── La casilla ────────────────────────────────────────────────────────────
   `appearance: none` y control propio. `accent-color` solo TIÑE la del
   sistema operativo: en macOS sigue siendo un cuadrado con las esquinas de
   macOS, en Windows otra cosa, y en ninguna de las dos se parece al ERP.

   Va por SELECTOR DE ELEMENTO y no por clase. Con una clase, «todas las
   casillas» dura hasta que alguien escriba un `<input type="checkbox">` sin
   acordarse de ponerla, y nadie lo nota: la del sistema también se ve como
   una casilla. La única excepción viaja escrita en el propio selector.
   ------------------------------------------------------------------------ */

input[type="checkbox"]:not(.drawer-toggle):not(.help-toggle) {
    appearance: none; -webkit-appearance: none;
    flex: none;
    width: 20px; height: 20px; margin: 0;
    /* 1px desde el 2026-08-17 (Renzo). Era 1.5px, y con el borde además más
       claro el trazo grueso era lo que quedaba gritando. EL RADIO BAJÓ CON
       ELLA: los dos controles conviven en la misma tarjeta —«Activo» al lado
       de «Todas las condiciones»— y uno a 1px junto a otro a 1.5px se lee
       como que uno de los dos está en otro estado. */
    border: 1px solid var(--checkbox-border);
    /* CUADRADA con las esquinas redondeadas, no un círculo (Renzo,
       2026-08-14). Fue círculo hasta que la selección múltiple puso
       cincuenta casillas juntas en una tabla: en columna, el círculo se lee
       como un radio —«elige una»— y lo que hacen es «elige las que
       quieras». El radio del ERP sigue siendo redondo y ahora se distinguen
       de un vistazo, que antes no.

       6px sobre 20: bastante para que no sea una caja dura, poco para que
       no se confunda con el círculo. No usa `--radius-sm` (8px) porque ése
       es de una tarjeta pequeña y a esta escala ya redondea de más. */
    border-radius: 6px;
    background: var(--card-bg) center / 11px 8px no-repeat;
    cursor: pointer;
    transition: background-color .14s ease, border-color .14s ease;
}
/* El palomito va DIBUJADO. Con `appearance: none` el del sistema desaparece,
   y una casilla marcada que solo cambia de color no dice que está marcada:
   dice que es de otro color. El SVG va en línea —no hay petición— y el trazo
   es blanco sobre el morado de la marca, que da 4.5:1. */
input[type="checkbox"]:not(.drawer-toggle):not(.help-toggle):checked {
    background-color: var(--primary);
    border-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 9'%3E%3Cpath d='M1.2 4.6 4.4 7.8 10.8 1.3' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
input[type="checkbox"]:not(.drawer-toggle):not(.help-toggle):hover:not(:checked) {
    border-color: var(--primary);
}
/* ALGUNAS, ni todas ni ninguna: la casilla del encabezado de una tabla con
   parte de sus filas marcadas. No existía porque hasta ahora ninguna casilla
   mandaba sobre otras.

   Una RAYA y no un palomito, que es lo universal y además lo único honesto:
   con el palomito, «tres de cincuenta» y «cincuenta de cincuenta» se ven
   igual, y quien apriete «Archivar» creyendo lo segundo archiva lo primero
   —o al revés—. El estado lo pone el JavaScript (`indeterminate` no es un
   atributo del marcado, es una propiedad del elemento), así que sin script
   la casilla solo tiene dos estados y ninguno miente. */
input[type="checkbox"]:not(.drawer-toggle):not(.help-toggle):indeterminate {
    background-color: var(--primary);
    border-color: var(--primary);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 9'%3E%3Cpath d='M2 4.5h8' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
}

/* El INTERRUPTOR: la misma casilla con `class="interruptor"`, para lo que se
   prende y se apaga AL INSTANTE, sin «Guardar» (2026-10-09, mostrar u ocultar
   una tarjeta del inicio del chat). Una casilla dice «marca las que quieras»
   para mandar después; un interruptor dice «esto queda así ya». La bolita va
   dibujada con un degradado, así no hace falta un elemento más.
   También vive DENTRO de un formulario con la barra de guardar: el «Hecho»
   de cada actividad en la lista de un mantenimiento (2026-10-19), donde se
   lee como «hecho / sin hacer» y viaja con lo demás al guardar. */
input[type="checkbox"].interruptor:not(.drawer-toggle):not(.help-toggle) {
    width: 32px; height: 18px; border: 0; border-radius: 999px;
    background: var(--checkbox-border) radial-gradient(circle, #fff 0 6px, transparent 6.5px) 2px 50% / 14px 14px no-repeat;
    transition: background-color .14s ease, background-position .14s ease;
}
input[type="checkbox"].interruptor:not(.drawer-toggle):not(.help-toggle):checked {
    background-color: var(--primary);
    background-image: radial-gradient(circle, #fff 0 6px, transparent 6.5px);
    background-position: calc(100% - 2px) 50%;
}

/* El RADIO, con el mismo trato que la casilla y por la misma razón que estaba
   escrita arriba: el del sistema es azul en macOS y cuadrado-gris en Windows,
   y al lado de una interfaz morada se lee como un control de otro programa.

   Se agrega en F2-e porque hasta acá no había ninguno: el primero es el de
   «cuál opción es la correcta» del examen, y ahí la diferencia saltaba —cuatro
   radios azules del sistema debajo de campos con foco morado—.

   El punto va DIBUJADO en el borde y no con `background-image`: un círculo
   sólido se dibuja con `box-shadow` hacia adentro y así no hay que codificar
   un SVG para una sola forma. */
input[type="radio"] {
    appearance: none; -webkit-appearance: none;
    flex: none;
    width: 20px; height: 20px; margin: 0;
    border: 1px solid var(--checkbox-border);
    border-radius: 50%;
    background: var(--card-bg);
    cursor: pointer;
    transition: border-color .14s ease, box-shadow .14s ease;
}
input[type="radio"]:checked {
    border-color: var(--primary);
    box-shadow: inset 0 0 0 5px var(--primary);
}
input[type="radio"]:hover:not(:checked) { border-color: var(--primary); }
input[type="radio"]:focus-visible {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
input[type="checkbox"]:not(.drawer-toggle):not(.help-toggle):focus-visible {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
input[type="checkbox"]:not(.drawer-toggle):not(.help-toggle):disabled {
    cursor: not-allowed; opacity: .5;
}

/* ── El deslizador ─────────────────────────────────────────────────────────
   Como la casilla: por SELECTOR DE ELEMENTO, para que ningún deslizador salga
   con el azul del sistema (Renzo lo vio en el customizer el 2026-10-01). La
   barra es delgada y el botón la desborda, blanco por fuera y morado por
   dentro, como la manija de algo que se agarra.

   El tramo lleno lo escribe `assets/js/deslizador.js` como `--range-fill`
   —WebKit no tiene pseudo-elemento para él—; Firefox usa el suyo. Sin el
   script la barra sale vacía y el control funciona igual. */
input[type="range"] {
    appearance: none; -webkit-appearance: none;
    width: 100%; height: 22px; margin: 0;
    background: transparent; cursor: pointer;
    --range-track: var(--border-strong);
}
input[type="range"]::-webkit-slider-runnable-track {
    height: 6px; border-radius: 999px;
    background: linear-gradient(to right,
        var(--primary) var(--range-fill, 0%),
        var(--range-track) var(--range-fill, 0%));
}
input[type="range"]::-moz-range-track {
    height: 6px; border-radius: 999px; background: var(--range-track);
}
input[type="range"]::-moz-range-progress {
    height: 6px; border-radius: 999px; background: var(--primary);
}
input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none; appearance: none;
    width: 18px; height: 18px; margin-top: calc(-1 * var(--space-075));
    border-radius: 50%; background: var(--primary);
    border: 3px solid var(--card-bg);
    box-shadow: 0 1px 4px rgba(23, 20, 31, .3);
    transition: transform .12s ease;
}
input[type="range"]::-moz-range-thumb {
    width: 12px; height: 12px;
    border-radius: 50%; background: var(--primary);
    border: 3px solid var(--card-bg);
    box-shadow: 0 1px 4px rgba(23, 20, 31, .3);
}
input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.12); }
input[type="range"]:focus-visible { outline: none; }
input[type="range"]:focus-visible::-webkit-slider-thumb {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
input[type="range"]:focus-visible::-moz-range-thumb {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
input[type="range"]:disabled { cursor: not-allowed; opacity: .5; }
/* Los extremos debajo de la barra: «8px … 64px» dice cuánto queda por mover
   sin tener que arrastrar hasta el tope para averiguarlo. */
.range-scale {
    display: flex; justify-content: space-between; margin-top: var(--space-025);
    font-size: var(--fs-small); color: var(--text-faint);
    font-variant-numeric: tabular-nums;
}

/* La pastilla: casilla y rótulo adentro de un mismo contorno. El objetivo de
   clic pasa a ser la frase entera y no un cuadradito de 20px, que es la
   diferencia entre marcarla al primer intento y al segundo.

   `width: fit-content` porque vive dentro de `.form-grid`, cuyas columnas
   miden 260px: sin eso la pastilla se estira hasta el borde y deja el rótulo
   flotando en medio de la nada. */
/* Los DOS selectores hacen falta. Dentro de `ui/_field.html` la pastilla es
   un `<label>` anidado en un `div.field`, y `.field label` —que existe desde
   P0 y fija `display: block`— le ganaba por especificidad: (0,1,1) contra
   (0,1,0). La pastilla salía en bloque, estirada a lo ancho de la columna y
   con la casilla encima del rótulo. El marcado estaba bien y la regla
   escrita: lo que fallaba era la cascada, que ninguna prueba de texto ve. */
.field .field-check,
.field-check {
    display: inline-flex; align-items: center; gap: var(--space-100);
    width: fit-content; max-width: 100%;
    margin-bottom: var(--space-200); padding: var(--space-100) var(--space-200) var(--space-100) var(--space-100);
    border: 1px solid var(--border); border-radius: 999px;
    background: var(--card-bg);
    cursor: pointer;
    transition: border-color .14s ease, box-shadow .14s ease;
}
/* La explicación de una casilla va PEGADA a ella (2026-10-22) porque las dos
   viven en un mismo envoltorio, `ui/_check_field.html`: adentro de `.field`
   la pastilla no lleva margen (abajo) y la ayuda queda a los 6px de
   cualquier `.field-hint`; el aire hasta la casilla siguiente lo pone el
   envoltorio. Sueltas, «Apagada: …» quedaba a 23px de su casilla y a 0 de
   la de abajo, y se leía como de ésa (lo vio Santiago en «Se vende» /
   «Lleva existencias»). La pieza reemplazó el parche de márgenes negativos
   que hubo acá (95ad7999e). */
/* Dentro de `.field` el margen lo pone el envoltorio: `_field.html` anida la
   pastilla dentro de un `div.field`, y con los dos márgenes la casilla
   quedaba al doble de separación que cualquier otro campo. En terceros las
   dos clases van en la MISMA etiqueta, así que ahí no se suman. */
.field > .field-check { margin-bottom: 0; }
/* Varias pastillas del mismo grupo van juntas y se acomodan solas. Puestas
   en `.form-grid` caía una por columna: «Cliente» a la izquierda,
   «Proveedor» a medio metro y «Aliado» en el renglón de abajo, que es tres
   opciones de lo mismo leídas como tres campos distintos. */
.check-row {
    display: flex; flex-wrap: wrap; gap: var(--space-100);
    margin-bottom: var(--space-200);
}
.check-row .field-check { margin-bottom: 0; }
/* ── Una casilla como celda de la grilla ─────────────────────────────────
   Cuando los campos los declara una EMPRESA, el orden lo eligió ella y no se
   puede reagrupar: `.check-row` junta las casillas de un mismo grupo, pero acá
   vienen intercaladas —caja, país, calidad, exclusiva, código— y moverlas
   sería reescribir una decisión de quien administra.

   Así que la celda se arregla en su sitio, con dos cosas:

   Un rótulo VACÍO arriba (`::before`, con la letra y el margen de `.field
   label`) — la pastilla se alinea con los CONTROLES de al lado y no con sus
   rótulos. Sin esto flota a la altura de la palabra «País de compra», con el
   control de al lado un renglón más abajo, y la fila entera se lee torcida.
   Hasta el 2026-10-28 eso lo hacía `justify-content: flex-end`, que manda la
   pastilla al PISO de la fila: con vecinos de un renglón da lo mismo, pero
   junto a una lista alta —«Colecciones redimibles» en la ficha del plan, que
   es un `select` múltiple de cinco renglones— la casilla «vip» caía al fondo,
   a la altura de nada (lo vio Renzo). El rótulo vacío la deja en el renglón
   del primer control, mida lo que mida la fila.

   `width: 100%` — la pastilla llena su columna. Con `fit-content` medía lo que
   midiera su texto, así que dos casillas seguidas salían de dos anchos
   distintos y con medio metro de aire al lado: eso es lo que se lee como
   campos sueltos en un patio y no como una grilla. */
.form-grid > .field-toggle::before {
    content: "\00a0"; display: block;
    font-size: var(--fs-small); font-weight: 600; margin-bottom: var(--space-100);
}
.form-grid > .field-toggle > .field-check { width: 100%; }
/* En UNA columna no hay vecino con quien alinear: el rótulo vacío solo dejaba
   un hueco sobre la casilla (Santiago, 2026-11-06, el tipo de mantenimiento). */
.form-grid-one > .field-toggle::before { content: none; }
.field-check:hover { border-color: var(--border-strong); }
/* El foco se pinta en la PASTILLA y no en la casilla: el contorno de 20px
   dentro de otro contorno se lee como un error de dibujo. */
.field-check:focus-within {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
.field-check:focus-within input[type="checkbox"] { outline: none; }
/* El rótulo NO hereda de `.field label`, que es en bloque y va arriba. */
.field-check span, .field-check label {
    display: inline; margin: 0;
    font-size: var(--fs-small); font-weight: 500; color: var(--text);
    text-transform: none; letter-spacing: normal;
}

/* ── El selector de color ──────────────────────────────────────────────────
   Por el mismo argumento que la casilla y la flecha del `select`: el control
   del sistema es un rectángulo gris en Windows, un botón redondo con borde
   azul en macOS y una barra en Linux. Estaba SIN UNA SOLA REGLA en toda la
   hoja, así que los ocho sitios que lo usan —los ajustes del tema, el
   customizer, el bloque de aviso, el examen, las láminas de una pantalla—
   salían cada uno con la cara de su sistema operativo, dentro de un
   formulario donde todo lo demás lo dibujamos nosotros.

   Va al ELEMENTO y no a una clase: eran ocho llamadas escritas a mano en
   cinco plantillas, y una clase obliga a acordarse de ponerla en la novena.
   `.mail-swatch-free` sigue ganando donde ya decidía su tamaño —es una clase,
   le gana por especificidad—, así que la fila de colores del editor de correo
   no cambia.

   Los tres pseudoelementos hacen falta y no son intercambiables: el color
   pintado vive dentro de un nodo del navegador, y sin quitarle su borde queda
   una línea gris de 1px entre nuestro borde y el color. Firefox lo llama de
   otro modo que WebKit; lo que no exista se ignora solo. */
input[type="color"] {
    appearance: none; -webkit-appearance: none;
    width: 56px; height: 40px; padding: var(--space-050);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-field);
    background-color: var(--card-bg);
    cursor: pointer;
    transition: border-color .14s ease;
}
input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
input[type="color"]::-webkit-color-swatch {
    border: 0; border-radius: 6px;
}
input[type="color"]::-moz-color-swatch {
    border: 0; border-radius: 6px;
}
input[type="color"]:hover { border-color: var(--primary); }
input[type="color"]:focus-visible {
    outline: 2px solid var(--primary); outline-offset: 2px;
}

/* ── La flecha de un `select` ──────────────────────────────────────────────
   Va DIBUJADA, por lo mismo que la casilla: la del sistema es azul en macOS,
   un triángulo en Windows, y en las dos vive pegada al borde derecho del
   control — a medio palmo del texto en cuanto el `select` es ancho.

   Va en TODOS y no en `.field select`: los del ERP viven en tres sitios —el
   formulario, la barra de filtros y la tabla de roles— y con la regla en uno
   solo, los otros dos seguían con la del sistema al lado de los nuestros. La
   nuestra es además más angosta, que es lo que le quita ancho a la columna de
   acciones de la tabla de roles.

   `padding-right` le deja sitio: sin eso, una opción larga pasa por debajo.
   ------------------------------------------------------------------------ */

select {
    appearance: none; -webkit-appearance: none;
    /* La CAJA también va acá, y no una vez por contexto. Estaba definida tres
       veces —formulario, filtros, ajustes— y los `select` de la tabla de
       roles no caían en ninguna: salían con la caja por omisión del navegador
       y nuestra flecha pegada encima, dos dedos debajo del «Alcance» de la
       misma pantalla, que sí se veía bien. */
    padding: var(--space-100) var(--space-400) var(--space-100) var(--space-150);
    font-family: inherit; font-size: var(--fs-body); color: var(--text);
    background-color: var(--card-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    outline: none;
    cursor: pointer;
    transition: border-color .16s, box-shadow .16s;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%235d5873' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    background-size: 10px 6px;
}

select:focus-visible {
    border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft);
}

/* El select MÚLTIPLE no es un desplegable: la flecha de arriba encima de una
   caja de varias filas anuncia un menú que no existe, y el relleno del
   desplegable deja las opciones descolgadas del borde. Sin flecha, con un
   alto que muestra ocho filas y cada opción con su aire — lo estrenan los
   canales y los terceros del alta de listas de precios. `background-image`
   explícito y no el atajo `background`, por la guarda de la flecha. */
select[multiple] {
    background-image: none;
    padding: var(--space-075); height: 172px; cursor: default;
}
select[multiple] option { padding: var(--space-100) var(--space-100); border-radius: 8px; }
.field select[multiple] { width: 100%; }
/* La casilla que FILTRA un select múltiple, encima de él: sin este aire las
   dos cajas se tocan y se leen como un solo control roto. El ancho ya lo da
   la regla de `input[type="text"]` dentro de `.field`. */
.field .field-filter { margin-bottom: var(--space-075); }

/* ── El buscador de variantes ──────────────────────────────────────────────
   El panel que reemplaza al `datalist` nativo (que sale AL LADO y no se
   puede vestir): DEBAJO del campo, con la foto del producto, el título y el
   SKU en mono. Lo estrena la carga de precios; el día que la captura del
   pedido lo quiera, estas clases ya no se llaman `price-*`.

   `finder-*` y NO `picker-*`: el selector de iconos ya se llama así, y la
   guarda de propiedades duplicadas cazó el choque antes de que las dos
   pantallas se vistieran mutuamente. */
.variant-picker { position: relative; }
/* Dentro de una `inline-row` el que se estira es EL ENVOLTORIO y no la
   casilla: el panel se posiciona contra él, y con el envoltorio al ancho de su
   contenido los resultados salían de 0 píxeles. */
.inline-row .variant-picker { flex: 1 1 220px; min-width: 0; }
.inline-row .variant-picker input { width: 100%; }
/* Y cuando la casilla llega con rótulo (`ChoiceWidget` pintado por
   `ui/_field.html`), el que se estira es el `.field`: sin esto quedaba del
   ancho natural de un `<input>` —174px en «Habilitar a alguien» de
   Repartidores— y el panel, recortado a ese ancho, cortaba los nombres. */
.inline-row > .field:has(> [data-choice-field]) { flex: 1 1 260px; min-width: 0; }
/* El producto de una celda de tabla: miniatura y título en una línea. Reusa
   `finder-thumb` para que lo que se eligió en el panel se vea igual en la
   lista — dos tamaños de la misma foto se leen como dos productos. */
.cell-prod {
    display: flex; align-items: center; gap: var(--space-100); min-width: 0;
}
.finder-panel {
    position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 30;
    background: var(--card-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    box-shadow: 0 12px 32px rgba(0, 0, 0, .12);
    max-height: 340px; overflow-y: auto;
    padding: var(--space-075);
}
/* Abierto hacia ARRIBA. Un panel absoluto no le suma alto al documento: con
   el campo cerca del fondo —el buscador de la colección vive DEBAJO de la
   ficha—, lo que sobresale queda bajo el borde de la página y no hay scroll
   que lo alcance (visto en /productos/colecciones/, 2026-08-24). El JS mide
   y voltea; esta clase solo dice hacia dónde. */
.finder-panel-up {
    top: auto; bottom: calc(100% + 6px);
    box-shadow: 0 -12px 32px rgba(0, 0, 0, .12);
}
/* Despegado de su tabla (`finder.js`, 2026-10-04): fijo sobre la página,
   con `left`, `top` y `width` puestos por el script. */
.finder-panel-fijo { position: fixed; right: auto; bottom: auto; }
/* La fila es un `<button type="button">`: el clic elige, el TAB no entra —
   la lista se recorre con las flechas, no robándole tabulaciones al precio
   que viene después. */
.finder-row {
    display: flex; align-items: center; gap: var(--space-150);
    width: 100%; padding: var(--space-100) var(--space-100); border: none; border-radius: 10px;
    background: transparent; cursor: pointer; text-align: left;
    font-family: inherit;
}
.finder-row-active { background: var(--primary-soft); }
/* Una variante que no se puede elegir —sin precio en la lista en otra
   moneda del pedido—: se ve, pero apagada y con su motivo en el title. */
.finder-row[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.finder-thumb {
    flex: none; width: 38px; height: 38px; border-radius: 9px;
    object-fit: cover; background: var(--bg);
    border: 1px solid var(--border);
}
/* Sin foto, la INICIAL sobre el tinte de marca — el mismo criterio del
   `banner-avatar`: un cuadro gris vacío se lee como algo que falta. */
.finder-thumb-letter {
    display: grid; place-items: center;
    background: var(--primary); color: #fff; border-color: var(--primary);
    font-size: var(--fs-body); font-weight: 600; text-transform: uppercase;
}
.finder-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.finder-title {
    font-size: var(--fs-small); font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.finder-sku, .finder-detail { font-size: var(--fs-label); color: var(--text-light); }
.finder-detail { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* El resto que NO se pinta, dicho: un panel de ocho sobre mil setecientas
   variantes que se calla el recorte se lee como «no está». */
.finder-more {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap;
    margin: var(--space-050) 0 var(--space-025); padding: var(--space-075) var(--space-100) var(--space-025);
    border-top: 1px solid var(--border);
    font-size: var(--fs-label); color: var(--text-light);
    /* Pegado al fondo del panel: con las 59 a la vista, «Agregar las 59»
       quedaba debajo de todas y había que bajar hasta el final a buscarlo.
       −6px es el `padding` del panel, para que no asome fila por debajo. */
    position: sticky; bottom: -6px; background: var(--card-bg);
}
/* «Mostrar 6 más» y «Agregar las 14» (`expand` y `pickAll` de `finder.js`,
   2026-10-25): enlaces de texto y no botones llenos, porque viven DEBAJO de
   la lista y no compiten con las filas, que son lo que se elige. */
.finder-more-btn {
    padding: var(--space-050) 0; border: none; background: none; cursor: pointer;
    font: inherit; font-weight: 600; color: var(--primary);
}
.finder-more-btn:hover { text-decoration: underline; }
.finder-more-all { margin-left: auto; }

/* ── El desplegable de una lista larga: la flecha adentro ─────────────────
   La flecha que el `<datalist>` nativo pintaba a la derecha, dibujada por
   la casa: sin ella la casilla se lee como un campo de texto y nadie va a
   hacerle clic esperando una lista. Mismo criterio —y misma trampa— que la
   lupa del buscador de artículos: `pointer-events` apagados o la flecha se
   come el clic que debía enfocar la casilla. El `mousedown` que la abre lo
   cuelga `combo.js`, que sí la escucha. */
.combo-caret {
    position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
    width: 18px; height: 18px; color: var(--text-faint);
    pointer-events: auto; cursor: pointer;
}
.combo input { width: 100%; padding-right: var(--space-400); }
/* En una celda, el combo no baja de 11rem: la tabla de las líneas de una
   factura le dejaba 85 px y no cabía ni el código de la cuenta. */
.table td .combo { min-width: 11rem; }
/* EL NOMBRE de lo elegido, al lado del código. La casilla guarda «240805» y
   eso es lo que mostraba: veintiocho números de seis cifras en la
   Configuración contable, y para saber qué cuenta era cada uno había que
   abrir el desplegable. El nombre va ENCIMA de la casilla y no dentro de su
   valor —lo que viaja en el POST sigue siendo el código—; `left` lo pone
   `combo.js` midiendo el código, y se esconde mientras se escribe. */
.combo-name {
    position: absolute; top: 50%; right: 34px; transform: translateY(-50%);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    color: var(--text-light); pointer-events: none;
}
.combo-name[hidden] { display: none; }
/* La opción del desplegable con su nombre ENTERO, en dos renglones si hace
   falta: en la columna angosta la casilla corta en «143505 M…» (2026-11-05). */
.finder-title.combo-opcion { white-space: normal; }
/* Las sugeridas arriba, con su porqué de color y una raya que las separa
   del resto (la matriz de las cuentas de una compra, 2026-11-05). */
.combo-porque { color: var(--primary); font-weight: 600; }
.finder-row.combo-sugerida:has(+ .finder-row:not(.combo-sugerida)) {
    border-bottom: 1px solid var(--border); border-radius: 10px 10px 0 0; margin-bottom: var(--space-050);
}

/* ── El campo de un artículo: la lupa adentro ──────────────────────────────
   La lupa DENTRO de la casilla y no un rótulo al lado que diga «buscar»: lo
   que tiene que anunciar el campo es que no hay que saberse el SKU de
   memoria, y eso lo dice el glifo en el sitio donde va a caer el cursor.

   Absoluta contra `.variant-picker`, que ya era `position: relative` porque
   el panel se posiciona contra él. `pointer-events: none` o la lupa se come
   el clic que debería enfocar la casilla — el defecto clásico de este
   patrón. */
.sku-field-glyph {
    position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
    width: 16px; height: 16px; color: var(--text-faint);
    pointer-events: none;
}
.sku-field input[type="text"] { width: 100%; padding-left: var(--space-400); }
/* La CARA de lo elegido, dentro de la casilla (elegir un integrante,
   2026-10-03): se ve QUIÉN quedó, no solo cómo se escribe su nombre. Misma
   anatomía y misma trampa que la lupa de arriba —absoluta contra
   `.variant-picker`, `pointer-events: none` o se come el clic que enfoca—,
   y la foto es la miniatura del panel en chico: dos tamaños de la misma
   cara se leen como la misma persona. */
.choice-face {
    position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
    width: 24px; height: 24px; border-radius: 7px;
    font-size: var(--fs-label);
    pointer-events: none;
}
.choice-with-face input[type="text"] { padding-left: var(--space-500); }
.sku-selections:not(:empty) { display: grid; gap: var(--space-100); margin-top: var(--space-150); }
.sku-selection { padding: var(--space-100) var(--space-100); border: 1px solid var(--border); border-radius: var(--radius-field); }
.sku-selection .finder-info { flex: 1; }
.sku-selection [data-sku-thumb] { display: flex; flex: none; }

/* ── Las filas de movimiento de producción ─────────────────────────────────
   La celda del código es una FILA y no un bloque: el buscador se estira y el
   rótulo de la salida principal se le pega al lado, que es donde lo lee
   quien está tecleando ahí. */
.mov-c-code { display: flex; align-items: center; gap: var(--space-100); }
.mov-c-code .variant-picker { flex: 1 1 auto; min-width: 0; }
/* La papelera no manda el ancho de la tabla: `1%` es el truco de siempre
   para una columna que mide lo que mide su contenido. */
.mov-c-x { width: 1%; text-align: right; white-space: nowrap; }

/* ── El editor del borrador ────────────────────────────────────────────────
   La piel del `order_editor` de tiendapp sobre los componentes de acá: los
   buscadores reusan `finder-*`, la grilla reusa `ficha-cols`, y esto es lo
   que el editor trae de propio — las celdas editables de la tabla de
   líneas, el panel de pago y la pila de campos laterales. */
.editor-cell {
    width: 100%; box-sizing: border-box; padding: var(--space-100) var(--space-100);
    border: 1px solid var(--border-strong); border-radius: 8px;
    font-size: var(--fs-small); font-family: inherit; text-align: right;
    color: var(--text); background: var(--card-bg);
}
.editor-cell:focus {
    outline: none; border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}
/* Angostas y pegadas a su columna: una celda editable al ancho completo de
   una columna de tabla se lee como un campo de formulario perdido. */
.editor-lines td.num .editor-cell { max-width: 110px; margin-left: auto; }
/* El aire horizontal que las tablas del sistema no llevan: acá CUATRO
   columnas numéricas viven pegadas, y sin esto los encabezados se leen
   «CANT.PRECIODCTO %» — la pasada visual del editor lo leyó tal cual. Por
   selector propio: la regla general de `.table` no se toca. */
.editor-lines thead th, .editor-lines tbody td {
    padding-left: var(--space-075); padding-right: var(--space-075);
}
/* Mínimos POR CAMPO y no por gancho `js-*` — esos son markers y la guarda
   exige que sigan sin regla. El precio es el ancho que manda: seis cifras
   y centavos no caben en lo que la columna del producto deje. */
.editor-lines input[name="line_quantity"],
.editor-lines input[name="line_discount_pct"] { min-width: 64px; }
.editor-lines input[name="line_unit_price"] { min-width: 108px; }
/* La unidad de la fila de la OC en su propia columna (2026-10-27): fija, en
   la letra de la tabla; desplegable solo si hay caja, a su ancho. */
.editor-lines td[data-unidad] { white-space: nowrap; }
/* La unidad fija de la fila (sin desplegable): en una línea y en la tinta de
   la tabla, como el texto de la celda vecina. */
.cell-unidad { white-space: nowrap; color: var(--text); }
.editor-lines td[data-unidad] select { width: auto; max-width: 11em; }
/* El impuesto, del ancho de lo elegido («IVA 19 %») y no del rótulo más
   largo de la lista: con el riel al lado la tabla no cabía (2026-11-02). */
.editor-lines select[name="line_tax"] { width: auto; max-width: 9em; }
/* El nombre de una línea libre es lo que más se lee de la fila: ancho para
   que «JARRA ARTE LATTE NEGRA MATE 600 ML» no quede en «JARRA ARTE LA…». */
.editor-lines input[name="line_description"] { min-width: 18em; }
/* La línea libre se LEE como el nombre de un producto: sin borde ni fondo
   hasta que se apunta o se escribe en ella. Sigue siendo una casilla. */
.editor-lines input[name="line_description"]:not([type="hidden"]) {
    border-color: transparent; background: transparent; font-weight: inherit; color: inherit;
    /* El texto cae en la MISMA vertical que el nombre de un producto: la
       casilla se corre a la izquierda lo que miden su relleno y su borde, y
       el borde que aparece al escribir queda por fuera (Renzo, 2026-11-02). */
    margin-left: calc(-1 * (var(--space-150) + 1px));
}
.editor-lines input[name="line_description"]:hover,
.editor-lines input[name="line_description"]:focus { border-color: var(--border); background: var(--card-bg); }
/* LAS LÍNEAS DE UN DOCUMENTO (`ui/_lineas.html`, la pieza de la casa que
   nació en la orden de compra, 2026-11-02). Todo `lineas-*` es de ella.

   La tabla APRETADA (Renzo, 2026-11-02: que se vea toda sin desplazarse de
   lado, y que cada encabezado diga claro de qué control es). Una sola regla
   de alineación: el encabezado arranca donde arranca su control, a la
   izquierda; la única columna que se LEE como cifra —el subtotal— va a la
   derecha, con su encabezado. Cada casilla, del ancho de lo que lleva. Los
   nombres (`line_quantity`…) son los de `.editor-lines`, la casa de toda
   fila de líneas. Solo `.lineas-apretadas`, que pone `ui/_lineas.html`: una
   tabla de líneas que todavía no es la pieza no cambia. */
.lineas-apretadas thead th, .lineas-apretadas thead th.num, .lineas-apretadas tbody td.num { text-align: left; }
.lineas-apretadas tr:has(> .cell-actions) > .num:nth-last-child(2),
.lineas-apretadas tr:not(:has(> .cell-actions)) > .num:last-child { text-align: right; }
.lineas-apretadas td.num .editor-cell { max-width: none; margin-left: 0; }
.lineas-apretadas input[name="line_quantity"] { width: 4.5em; min-width: 0; }
.lineas-apretadas input[name="line_unit_price"] { width: 7.5em; min-width: 0; }
.lineas-apretadas td[data-unidad] select { max-width: 7.5em; }
/* Los desplegables de la cuenta, la retención y el centro de costo: anchos
   como su opción más larga («Compras de café pergamino o cereza 0,5 %»)
   empujaban la retención fuera de la vista (2026-11-05). */
.lineas-apretadas .celda-sugerida select { width: 8.5rem; max-width: 8.5rem; }
.lineas-apretadas select[name^="cost_center_"] { max-width: 11rem; }
.lineas-apretadas .combo:has(> input[name^="account_"]) { width: 8rem; min-width: 0; }
.lineas-apretadas input[name^="account_"] { width: 100%; min-width: 0; }
.lineas-apretadas .celda-sugerida { flex-direction: column; align-items: flex-start; }
.lineas-apretadas select[name="line_tax"] { max-width: 7.5em; }
.lineas-apretadas input[name="line_description"] { min-width: 13em; }
.lineas-apretadas .cell-text { white-space: normal; }
.lineas-apretadas .cell-sub .codigo-chip { margin-top: var(--space-025); }
/* Lo recibido en la ficha de la recepción (Santiago, 2026-11-04): una
   columna por dato con anchos FIJOS, para que al pasar de R1 a R2 a R3
   ninguna salte aunque falte el lote o el nombre mida otra cosa. El
   producto se queda con lo que sobra; en una ventana angosta la tabla se
   desliza de lado en vez de montar una celda sobre otra. */
.recibido-lineas { table-layout: fixed; width: 100%; min-width: 62em; }
.recibido-lineas .recibido-lote { width: 8em; }
.recibido-lineas .recibido-fecha { width: 7.5em; }
.recibido-lineas .recibido-cifra { width: 7em; }
.recibido-lineas th { white-space: normal; vertical-align: bottom; }
.recibido-lineas td { overflow-wrap: anywhere; }
/* LOS PAGOS FIJOS DEL CONTRATO (2026-11-02): el nombre se lleva la fila y
   lo demás mide lo que lleva —el monto, una cifra de millones con su «$»;
   el Sí / No y la papelera, su ancho—. Cada encabezado arranca donde su
   control, como en la orden de compra. */
.pagos-lineas td:first-child input { width: 100%; min-width: 12em; }
.pagos-lineas td:nth-child(2), .pagos-lineas td:nth-child(3),
.pagos-lineas th:nth-child(2), .pagos-lineas th:nth-child(3) { width: 1%; white-space: nowrap; }
.pagos-lineas td:nth-child(2) input { width: 10em; }
/* «Distinto a la lista», como alerta: la pastilla roja con su glifo y el
   enlace para actualizar la lista al lado, en la misma línea si cabe. */
.oc-distinto { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-050) var(--space-075); margin-top: var(--space-050); }
.oc-distinto .badge-soft-danger { display: inline-flex; align-items: center; gap: var(--space-050); white-space: nowrap; }
.oc-distinto .badge-soft-danger svg { width: 12px; height: 12px; flex: none; }
.lineas-apretadas .cell-sub.oc-distinto a:not(.btn) { color: var(--danger-text); font-weight: 600; }
/* El total de las líneas, FIJO al pie de la tarjeta (2026-11-02): en su
   propia tabla, fuera de la que se desliza de lado, a todo el ancho. De la
   casa desde el 2026-11-03 (`.tabla-totales`, antes `.oc-totales`): la
   orden de compra y la ficha de los documentos contables lo comparten. */
.tabla-totales { overflow: visible; }
.tabla-totales > .table { width: 100%; margin: 0; }
/* La franja del total no lleva raya encima: pegada a la última fila, la
   raya de pie de tabla se cortaba antes de la columna de la cifra. */
.tabla-totales tfoot td { border-top: 0; }
/* El total como franja (`panel`) con subtotal e impuestos EN SU COLUMNA
   (`parts_aside`): todo lo de plata en una vertical a la derecha del filete
   (Renzo, 2026-11-02). La columna mide lo que necesita el resumen. */
.tabla-totales td.total-value { width: 1%; min-width: calc(var(--space-400) * 8); }
/* «Total» y su baldosa a tamaño de encabezado de tarjeta: chicos se perdían
   en la franja. La baldosa en blanco, o sobre el tinte de la franja no se
   veía el cuadro. Y el menor aire vertical posible: la franja era más alta
   que lo que dice (Renzo, 2026-11-02). */
.tabla-totales .total-panel .cell-lead > .tile { width: 38px; height: 38px; border-radius: var(--radius-sm); background: var(--card-bg); }
.tabla-totales .total-panel .cell-lead > .tile svg { width: 20px; height: 20px; }
.tabla-totales .total-panel .total-label { font-size: var(--fs-stat); font-weight: 700; }
.tabla-totales .total-panel td { padding-top: var(--space-100); padding-bottom: var(--space-100); }
.tabla-totales .total-partes-lado { row-gap: 0; margin-bottom: var(--space-075); padding-bottom: var(--space-075); }
.tabla-totales .total-caption { margin-bottom: 0; }
/* AGREGAR, en la última fila de la tabla (2026-11-02): sin la cara de pie de
   tabla (raya gruesa, negrita). La casilla única toma el ancho de la fila
   desde la columna del producto, y «Ver catálogo» va a su lado como enlace. */
.lineas-apretadas .lineas-fila-agregar td { padding: var(--space-200) var(--space-075);
  border-top: 1px solid var(--border); font-weight: 400; }
/* La casilla arranca en la vertical de las baldosas de producto: el mismo
   relleno lateral que las filas de arriba, no el del pie de tabla. */
.lineas-agregar { display: flex; align-items: center; gap: var(--space-200); }
.lineas-agregar .sku-field { flex: 1 1 auto; min-width: 0; }
.lineas-agregar .sku-field input[type="text"] { width: 100%; min-height: 40px; font-size: var(--fs-body); }
.lineas-agregar .sku-field-glyph { width: 18px; height: 18px; }
.lineas-agregar .lineas-camara > .btn svg { width: 22px; height: 22px; }
/* «Ver catálogo», botón sólido de marca y del alto de la casilla (Renzo,
   2026-11-02). */
.lineas-agregar .help-icon-rotulo { min-height: 40px; padding-inline: var(--space-200);
  border-color: var(--primary); background: var(--primary); box-shadow: none;
  color: #fff; font-size: var(--fs-body); font-weight: 600; white-space: nowrap; }
.lineas-agregar .help-icon-rotulo:hover { background: var(--primary-dark); border-color: var(--primary-dark); }
/* La última opción de la lista: lo escrito como línea sin producto, con su
   «+» en la baldosa de la inicial y separada de los productos. */
.finder-panel .lineas-opcion-libre { border-top: 1px solid var(--border); }
.finder-panel .lineas-opcion-libre .finder-thumb-letter { font-size: var(--fs-section); }
/* La cámara, adentro del buscador a la derecha, como la lupa a la izquierda. */
.lineas-camara { position: absolute; right: var(--space-050); top: 50%; transform: translateY(-50%); }
.lineas-camara[hidden] { display: none; }
/* La cámara es el glifo solo, sin cara de botón: se lee como parte de la
   casilla, igual que la lupa. */
.lineas-camara > .btn { border-color: transparent; background: transparent; box-shadow: none; color: var(--text-light); }
.lineas-camara > .btn:hover { color: var(--primary-dark); background: var(--primary-soft); }
/* Agregar va al pie de la tarjeta, separado por una raya y sin el recuadro
   punteado: es la última fila de la tarjeta, no una caja aparte
   (referente de Renzo, 2026-11-02). La casilla, del alto del botón. */
.sku-field:has(.lineas-camara:not([hidden])) input[type="text"] { padding-right: var(--space-500); }
.editor-prod {
    display: flex; align-items: center; gap: var(--space-100); min-width: 0;
    font-weight: 600; color: var(--text);
}
/* La celda del producto se queda con el sobrante: sin esto, las cuatro
   columnas numéricas se reparten el ancho y el título termina partido. */
.editor-c-prod { width: 100%; min-width: 220px; }
.editor-vt { color: var(--text-light); font-weight: 500; }
/* «Sale de» vive DEBAJO del título, dentro de la celda del producto, y no
   en columna propia: como columna, el editor pasaba de 757 a 908px de
   tabla dentro de una tarjeta de 703, y a 1440px —la resolución de
   escritorio más común— el total de la línea y el botón de quitar quedaban
   detrás de un desplazamiento horizontal. `.table-wrap` está para el
   celular, no para que el escritorio pierda la columna de plata. Es el
   mismo patrón que `.editor-vt` y `.cell-sub` ya usan en esta misma celda
   para la variante. */
.editor-prod-name {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: var(--space-050); min-width: 0;
}
.editor-origin {
    display: inline-flex; align-items: center; gap: var(--space-075);
    font-size: var(--fs-meta); font-weight: 500; color: var(--text-light);
}
/* El desplegable, a la medida de un subtítulo. La caja base del `select`
   es de formulario —13px y 9px de alto— y debajo del título se lee como
   otro campo del formulario en vez de como el dato de apoyo que es. Se
   ajusta el TAMAÑO, no la caja: el borde y la flecha siguen siendo los de
   la hoja, y los 30px de la derecha son los que la flecha necesita para
   que una opción larga no se le meta por debajo. */
.editor-origin select { padding: var(--space-050) var(--space-400) var(--space-050) var(--space-100); font-size: var(--fs-small); }
/* En la fila de la casa (`orders/_order_line_row.html`, 2026-11-02) el
   desplegable es `ui/_cell_select.html`: a la medida de la opción, no los
   11rem de una celda de texto. */
.editor-origin .cell-input { width: auto; max-width: 14em; }
/* El % de descuento del Pedido, angosto como la cantidad: en la tabla
   apretada, sin ancho propio se quedaba con el sobrante de la fila. */
.lineas-apretadas input[name="line_discount_pct"] { width: 4.5em; min-width: 0; }
/* El envío del Pedido, tecleable en el resumen del total (`casilla` de una
   parte de `ui/_total.html`, 2026-11-02): la casilla a la medida de una
   cifra y alineada con las de arriba. */
.total-partes-lado > div > .total-parte-casilla { align-self: center; }
.total-parte-casilla .input-unit { display: inline-flex; width: 9rem; vertical-align: middle; }
.total-parte-casilla .input-unit > input { text-align: right; padding-block: var(--space-050); }
/* El renglón ELEGIBLE del resumen (`desplegable` en `ui/_total.html`): el
   desplegable y la pastilla a la izquierda de la cifra, en la misma línea
   que los demás renglones (la retención de IVA de la factura, 2026-11-05). */
.total-parte-elegible > dt { display: inline-flex; align-items: center; justify-content: flex-end; gap: var(--space-100); }
.total-parte-elegible > dd { align-self: center; }
.total-parte-elegible select { width: auto; max-width: 14rem; padding-block: var(--space-025); }
.total-parte-interruptor { display: inline-flex; align-items: center; gap: var(--space-100); cursor: pointer; }
/* La pastilla «Sugerida» se va al elegir otra, y un renglón en cero se esconde (2026-11-05). */
.sugerida-marca { display: inline-flex; }
.sugerida-marca[hidden], .total-partes-lado > div[hidden], .facts > [hidden] { display: none; }
.total-parte-cifra { min-width: 6rem; text-align: right; }
.editor-c-total { font-weight: 700; color: var(--text); }
.editor-c-x { width: 30px; text-align: center; }
.editor-x {
    border: none; background: none; color: var(--text-light);
    font-size: var(--fs-page); line-height: 1; cursor: pointer;
    padding: 0 var(--space-050); border-radius: 6px;
}
.editor-x:hover { color: var(--danger-text); background: var(--danger-bg); }
/* El precio en el panel de resultados, a la derecha y en tinta de marca —
   es EL dato por el que este buscador existe. */
.editor-hit-price {
    margin-left: auto; color: var(--primary); font-weight: 700;
    white-space: nowrap;
}
/* El precio de lista que quedó ATRÁS, tachado y apagado al lado del
   cobrado: un descuento que no se ve aplicado se lee como un descuento
   que no corrió — Renzo vio 41.895 sin saber que la base era 52.369. */
.editor-hit-was {
    color: var(--text-light); font-weight: 500;
    font-size: var(--fs-label);
}
/* DE QUÉ LISTA sale ese precio, en su propio renglón bajo la cifra: el
   tachado dice que el precio cambió y esto dice quién lo cambió. Sin él,
   un pedido sin cliente cotizado por la lista del canal se lee como un
   descuento que nadie aplicó (Santiago, 2026-09-28). */
.editor-hit-list {
    display: block; text-align: right;
    color: var(--text-light); font-weight: 500;
    font-size: var(--fs-label);
}

.editor-pay { display: flex; flex-direction: column; gap: var(--space-025); }
.editor-pay-row {
    display: flex; justify-content: space-between; align-items: center;
    padding: var(--space-100) var(--space-025); font-size: var(--fs-small);
}
.editor-pay-row span { color: var(--text-light); }
.editor-pay-row b { font-weight: 600; color: var(--text); }
.editor-pay-muted b, .editor-pay-muted span {
    color: var(--text-light); font-size: var(--fs-label);
}
.editor-pay-total {
    border-top: 1px solid var(--border); margin-top: var(--space-050); padding-top: var(--space-150);
}
.editor-pay-total span {
    color: var(--text); font-weight: 700; font-size: var(--fs-body);
}
.editor-pay-total b { font-size: var(--fs-section); font-weight: 800; }
.editor-ship {
    color: var(--text-light); display: inline-flex; align-items: center;
    gap: var(--space-050);
}
.editor-ship .editor-cell { max-width: 100px; }

/* La pila de campos de las tarjetas laterales: mismo `.field` del sistema,
   apilado con su aire. */
.editor-fields { display: flex; flex-direction: column; gap: var(--space-150); }
.editor-fields .field { margin: 0; }
/* El buscador de una tarjeta de campos (el Cliente del pedido) es un campo
   más: todo el ancho, no el del texto de ayuda. */
.editor-fields .variant-picker input { width: 100%; }

/* La fila de un beneficio del plan (F8): clave y valor LADO A LADO con su
   quitar al final — pedido por Renzo con la fila de la referencia
   enfrente. Flex y no `form-grid-two`: la fila es 1fr + 1fr + auto, y el
   botón de quitar no puede quedarse con media columna.

   `.benefit-row input` por CONTEXTO y con el flex explícito: los anchos
   por elemento del formulario (`.field input[...]`) traen `width: 100%`,
   y sin esta regla cada casilla ocupa el renglón entero — que es
   exactamente cómo la fila salió apilada la primera vez. */
.benefit-row { display: flex; gap: var(--space-150); align-items: center; margin-bottom: var(--space-150); }
.benefit-row input { flex: 1; width: auto; min-width: 0; }
.benefit-row .btn { flex-shrink: 0; }

/* ── Filtros ───────────────────────────────────────────────────────────────
   Un `<form>` GET. La URL queda compartible, así que una bitácora filtrada
   se pega en un chat y el otro ve lo mismo.

   Desde el 2026-10-09 (Renzo) la barra vive en dos lugares: la FILA encima
   de la tabla, con el buscador y una ficha por filtro puesto, y el PANEL de
   Filtros en la columna derecha, la del Asistente, con los desplegables.
   ------------------------------------------------------------------------ */
/* Sin relleno propio desde el 2026-10-11: la caja es la cápsula de vidrio
   y la fila ya no es tarjeta (ver «El buscador y Columnas», más arriba). */
.filtros-fila { padding: 0; }
.filtros-fila[hidden] { display: none; }
/* LA CAJA (Renzo, 2026-10-09, segunda vuelta): la lupa, las fichas y el
   cursor en UNA línea que no baja nunca a un segundo renglón; si no cabe, se
   desliza de lado —con el dedo en el celular, con el trackpad en el
   escritorio— y la barra de desplazamiento no se pinta.

   Ella y los desplegables del panel comparten UNA regla: viven en lugares
   distintos y son los controles de la misma barra. Con dos, el próximo
   ajuste se haría en una y se verían distintos. */
.filtros-caja, .filtros-campo select {
    width: 100%; padding: var(--space-100) var(--space-150);
    font: inherit; font-size: var(--fs-small); color: var(--text);
    background-color: var(--card-bg); border: 1px solid var(--border-strong);
    border-radius: var(--radius-field); outline: none;
    transition: border-color .16s, box-shadow .16s;
}
/* El foco es el borde morado, SIN el anillo de 3 px de los campos de la casa:
   «se pone como un halo sutil moradito, quitémoslo» (Renzo, 2026-10-09). */
.filtros-caja:focus-within, .filtros-campo select:focus {
    border-color: var(--primary); box-shadow: none;
}
/* El sitio de la flecha: el relleno de arriba es el de la casilla de texto y
   le dejaba 12px, así que una opción larga pasaba por debajo de la flecha
   (salió a la luz el 2026-10-30, cuando la prueba aprendió a leer los pasos). */
.filtros-campo select { cursor: pointer; padding-right: var(--space-400); }
.filtros-caja { display: flex; align-items: center; gap: var(--space-100); padding-block: var(--space-050); cursor: text; }
.filtros-lupa { flex: none; width: 16px; height: 16px; color: var(--text-light); }
/* La franja que se desliza: las fichas y la casilla, sin bajar nunca a un
   segundo renglón, y sin barra de desplazamiento pintada. */
.filtros-desliza {
    flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--space-100);
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none;
}
.filtros-desliza::-webkit-scrollbar { display: none; }
/* La casilla no lleva caja propia: la caja es el control. Crece hasta llenar
   lo que las fichas dejan, y nunca baja de un ancho donde se pueda escribir. */
.filtros-caja .filtros-escribe {
    flex: none; width: 16em; max-width: none; padding: var(--space-025) 0; border: 0;
    background: transparent; box-shadow: none; outline: none;
    font: inherit; color: var(--text);
}
/* El «+» pegado a lo escrito (2026-10-09): un círculo chico, del gris de la
   lupa, que se tiñe al pasar por encima. `filtros.js` le da la medida a la
   casilla, y por eso el «+» queda justo después del texto. */
.filtros-agregar {
    flex: none; display: inline-grid; place-items: center;
    width: 20px; height: 20px; padding: 0; border-radius: 999px;
    border: 1.5px solid currentColor; background: transparent;
    color: var(--text-light); cursor: pointer;
}
.filtros-agregar svg { width: 12px; height: 12px; }
.filtros-agregar:hover { color: var(--primary); }
.filtros-agregar[hidden] { display: none; }
.filtros-caja .filtros-escribe::-webkit-search-cancel-button { display: none; }
.filtros-caja .filtros-escribe:focus,
.filtros-caja .filtros-escribe:focus-visible { box-shadow: none; outline: none; background: transparent; }
/* Las fichas de lo filtrado: la etiqueta apagada y el valor en tinta, con
   la × que lo quita. Son botones: la ficha entera quita el filtro. */
.filtros-fichas { display: flex; flex: none; flex-wrap: nowrap; gap: var(--space-100); }
.filtros-fichas[hidden] { display: none; }
.filtro-ficha {
    display: inline-flex; flex: none; align-items: center; gap: var(--space-075);
    padding: var(--space-050) var(--space-100); border-radius: 999px; white-space: nowrap;
    border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
    background: var(--primary-soft); color: var(--text);
    font: inherit; font-size: var(--fs-small); cursor: pointer;
}
.filtro-ficha:hover { border-color: var(--primary); }
.filtro-ficha-rotulo { color: var(--text-light); }

/* El PANEL. Un control por renglón con su rótulo arriba —en la columna sobra
   el ancho que en la fila faltaba— y «Limpiar filtros» al pie. La cabeza y
   el pie quedan quietos; lo de en medio se desplaza. */
.filtros-panel-cabeza {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-200) var(--space-250); border-bottom: 1px solid var(--border);
}
/* La «×» del panel: `.icon-btn` es un icono de la barra oscura, y en esta
   cabeza blanca quedaba invisible. Lo mismo que el drawer, acotado acá. */
.filtros-panel-cabeza .icon-btn { color: var(--text-light); }
.filtros-panel-cabeza .icon-btn:hover { color: var(--text); background: var(--bg); }
.filtros-panel-cabeza h2 {
    margin: 0; font-size: var(--fs-section); font-weight: 600;
    text-transform: uppercase; letter-spacing: .02em;
}
.filtros-panel-cuerpo {
    flex: 1; min-height: 0; overflow-y: auto; padding: var(--space-200) var(--space-250);
    display: flex; flex-direction: column; gap: var(--space-200);
}
.filtros-campo { display: flex; flex-direction: column; gap: var(--space-100); min-width: 0; }
.filtros-campo > label { font-size: var(--fs-small); font-weight: 600; color: var(--text-light); }
.filtros-campo .token-field { margin-bottom: 0; }
.filtros-panel-pie { padding: var(--space-150) var(--space-250); border-top: 1px solid var(--border); }

/* ── Drawer ────────────────────────────────────────────────────────────────
   `<dialog open>` se pinta en el flujo aunque Alpine no cargue; con Alpine
   se asciende a modal y gana trampa de foco, Esc y capa de fondo.
   ------------------------------------------------------------------------ */

.drawer {
    /* Lateral y de alto completo: es un cajón, no un globo. Pegado a la
       derecha con `margin: auto` a la izquierda, que es como se empuja un
       diálogo modal sin sacarlo del centrado nativo. */
    width: min(560px, 100%); max-width: 100%;
    height: 100dvh; max-height: 100dvh;
    margin: 0 0 0 auto;
    padding: 0; border: 0;
    border-radius: var(--radius-lg) 0 0 var(--radius-lg);
    background: var(--card-bg); color: var(--text);
    box-shadow: var(--shadow-lg);
    display: flex; flex-direction: column;
    /* Nace AFUERA y `drawer.js` lo trae con `.is-open` al cuadro siguiente.
       Es lo que hace que se vea entrar en vez de aparecer.

       Sin script se queda así —desplazado y transparente— y por eso el
       `:not([open])` de abajo: el `<dialog open>` que el servidor pinta se ve
       igual que siempre, y solo el que ya se ascendió a modal empieza afuera.
       Un `@starting-style` sería lo moderno, pero Safari 17 —la versión de
       los iPad del mostrador— lo ignora y el panel entraría de golpe ahí. */
    transition: transform .22s ease-out, opacity .22s ease-out;
}
/* `drawer_class` en `ui/_drawer.html`: el panel ANCHO, para lo que no cabe
   en 560px sin apretarse (el cierre del mes de los agentes, 2026-10-03). */
.drawer.drawer-ancho { width: min(820px, 100%); }
dialog.drawer:modal { transform: translateX(100%); opacity: 0; }
/* Adentro `none` y no `translateX(0)`: cualquier `transform` vuelve al cajón
   el marco de lo `fixed` que lleve, y una hoja de `ui/_help.html` abierta
   desde el cajón (el selector «Elegir productos» de un kit, 2026-10-23)
   quedaba del ancho del cajón en vez de la pantalla. Entrar y salir siguen
   igual: la animación y la transición van de `none` a `translateX(100%)`. */
dialog.drawer:modal.is-open { transform: none; opacity: 1; }
/* La ENTRADA va con una animación y no con la transición (2026-10-12). La
   transición necesita haber pintado el panel afuera antes de traerlo, y el
   panel recién ascendido a modal no tiene ese «antes»: medido en Chrome,
   `getAnimations()` daba 0 y el cajón aparecía de golpe. Una animación
   arranca de su `from` sin preguntar dónde estaba, y Safari 17 la entiende
   (a `@starting-style` no). La SALIDA sigue siendo la transición: al
   quitar `.is-open` el panel ya está pintado y se va deslizando. */
dialog.drawer:modal.is-open { animation: drawer-entra .22s ease-out; }
@keyframes drawer-entra { from { transform: translateX(100%); opacity: 0; } }
/* Lo que htmx deja en el hueco viene con `open` y se pintaba un par de
   cuadros quieto, sin modal, antes de que `drawer.js` lo ascendiera. */
#drawer-host dialog.drawer:not(:modal) { visibility: hidden; }
/* La capa de fondo entra con él. `::backdrop` no hereda de nadie, así que
   repite su propia transición y su propia animación. */
.drawer::backdrop { opacity: 0; transition: opacity .22s ease-out; }
.drawer.is-open::backdrop { opacity: 1; animation: drawer-fondo .22s ease-out; }
@keyframes drawer-fondo { from { opacity: 0; } }
/* Aquí y no en el bloque general de movimiento reducido: ese va antes en la
   hoja y, con la misma especificidad, la animación de arriba le ganaría. */
@media (prefers-reduced-motion: reduce) {
    dialog.drawer:modal.is-open, .drawer.is-open::backdrop { animation: none; }
}
.drawer-body { flex: 1; overflow-y: auto; }
/* No hace falta un `.drawer[open] { display: block }`: `dialog:not([open])`
   solo aplica cuando NO está abierto. Y sí hacía daño — con (0,2,0) le ganaba
   a `.drawer` y anulaba el `display: flex`, así que el encabezado no quedaba
   fijo y el cuerpo no hacía scroll propio. */
.drawer::backdrop { background: rgba(13, 11, 22, .45); }
.drawer-head {
    flex: none;
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); padding: var(--space-200) var(--space-250);
    background: #ffffff; color: #000000;
    border-radius: var(--radius-lg) 0 0 0;
    border-bottom: solid 1px #bfbfbf;
}
.drawer-head h2 { margin: 0; font-size: var(--fs-section); font-weight: 600; }
.drawer-head-text { min-width: 0; }
.drawer-hint { margin: var(--space-025) 0 0; font-size: var(--fs-small); color: var(--text-faint); }
.drawer-hint:empty { display: none; }
/* El encabezado del drawer pasó a fondo BLANCO, y `.icon-btn-dark` es la
   clase de un icono sobre fondo oscuro: la «×» quedaba blanca sobre blanco,
   invisible pero clicable. Se acota al drawer en vez de cambiar la clase
   —la usa también la barra superior, que sigue siendo oscura— y así ninguna
   de las dos superficies decide por la otra. */
.drawer-head .icon-btn-dark { color: var(--text-light); }
.drawer-head .icon-btn-dark:hover { color: var(--text); background: var(--bg); }
.icon-btn-dark { color: rgba(255, 255, 255, .8); text-decoration: none; }
.icon-btn-dark:hover { text-decoration: none; }
.drawer-body { padding: var(--space-200) var(--space-250) var(--space-300); }
.drawer-meta {
    display: grid; grid-template-columns: auto 1fr; gap: var(--space-075) var(--space-200);
    margin: 0 0 var(--space-200); font-size: var(--fs-body);
}
.drawer-meta dt { color: var(--text-faint); font-weight: 600; }
.drawer-meta dd { margin: 0; }
.drawer-note { margin: 0; color: var(--text-light); font-size: var(--fs-body); }
/* La lista de formularios del cohete: una fila por formulario, asignado o no.
   Es una lista de DECISIÓN, no de lectura, así que cada fila lleva su acción a
   la derecha y el estado a la izquierda — y la asignada se distingue por el
   fondo, no solo por el rótulo del botón: con veinte filas, leer «Quitar» en
   cada una para saber cuáles tiene es trabajo que el color hace de un vistazo. */
.forms-list { list-style: none; margin: 0; padding: 0; }
.forms-row {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius-sm); margin-bottom: var(--space-100);
}
.forms-row.is-assigned { background: var(--bg); border-color: var(--border-strong); }
/* `min-width: 0` para que el nombre largo se acorte en vez de empujar el botón
   fuera del diálogo, que a 390px ocupa todo el ancho. */
.forms-row-main { flex: 1; min-width: 0; }
.forms-row-name {
    display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap;
    margin: 0; font-weight: 600; font-size: var(--fs-body);
}
.forms-row-meta {
    margin: var(--space-025) 0 0; color: var(--text-light); font-size: var(--fs-small);
}
.forms-row-action { flex: none; margin: 0; }

.diff { white-space: normal; }
.diff td { overflow-wrap: anywhere; }
.diff .diff-before { color: var(--danger-text); text-decoration: line-through; }
.diff .diff-after  { color: var(--ok-text); font-weight: 500; }

@media (max-width: 767px) {
    .drawer { width: 100%; border-radius: 0; }
    .drawer-head { border-radius: 0; }
    .filters select, .filters input { width: 100%; }
}

/* ── Casillas de formulario del ERP ───────────────────────────────────────
   `.field` ya existe para las pantallas de autenticación; acá se agregan los
   controles que aquellas no usan y el estado degradado.
   ------------------------------------------------------------------------ */

/* La APARIENCIA de estos controles NO depende de `.field`, y ese es el
   arreglo. Estaba toda ahí adentro, así que un `<textarea>` o un
   `input[type=number]` fuera de un `.field` —los editores de bloque, que usan
   `.block-edit`— salía con el borde crudo del navegador al lado de campos bien
   pintados. Se veía como un formulario a medio hacer, y lo era: la mitad de
   los controles no tenía estilo.

   Solo el ANCHO se queda en `.field`, y por la razón que ya estaba escrita
   arriba: adentro de un campo el control llena su columna, pero en una fila es
   el `flex` quien decide y un `width: 100%` ahí lo empuja fuera. */
/* `select` NO está en este grupo y `number` tampoco: los dos ya se estilan a
   nivel de elemento más arriba —el `select` con su flecha y sus 34px a la
   derecha, el `number` en el grupo de texto—. Ponerlos acá los pisaría con un
   padding sin sitio para el caret, que es el defecto que la regla de arriba
   vino a cerrar. Hay un test que lo vigila. */
textarea,
input[type="date"],
input[type="search"],
input[type="url"],
input[type="tel"] {
    padding: var(--space-150) var(--space-150);
    font-family: inherit; font-size: var(--fs-body); color: var(--text);
    /* `background-color` y NO el atajo `background`: el atajo resetea
       `background-image` a `none`, y esta regla es más específica que la que
       le dibuja la flecha al `select`. Se la borraba. */
    background-color: var(--field-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    outline: none;
    transition: border-color .16s, box-shadow .16s;
}
.field select, .field textarea, .field input[type="date"],
.field input[type="search"], .field input[type="number"],
.field input[type="url"], .field input[type="tel"] { width: 100%; }
textarea:focus, input[type="date"]:focus,
input[type="search"]:focus,
input[type="url"]:focus, input[type="tel"]:focus {
    border-color: var(--primary); box-shadow: 0 0 0 3.5px var(--primary-soft);
}
textarea { resize: vertical; min-height: 76px; }

/* En una pantalla TÁCTIL ningún campo baja de `1rem`.

   iOS acerca la página al tocar un campo de menos de 16px —para que se lea
   lo que uno escribe— y no la vuelve a alejar: la pantalla queda más ancha
   que el teléfono y hay que pellizcar para volver. Renzo lo vio el
   2026-09-03 en la pantalla del PIN de un integrante, con el campo a 13px.

   `pointer: coarse` y no un ancho: el iPad también acerca, y un portátil
   angosto no. `1rem` y no un paso de la escala: no es un tamaño que se
   eligió, es el umbral del navegador, y por eso se escribe como lo que es.

   `!important` a propósito, como en el bloque de `prefers-reduced-motion`:
   hay once reglas que le ponen tamaño a un campo por su contexto —los
   filtros, el buscador, los ajustes— y las once tienen más especificidad
   que el selector del control. Repetirlas acá sería una lista que se queda
   atrás con el siguiente campo que alguien escriba; esto es una exigencia
   sobre TODOS, y así se escribe una exigencia. */
@media (pointer: coarse) {
    /* Sin zoom en lo táctil (Renzo, 2026-11-06: es una app, no una página).
       `user-scalable=no` del marco no alcanza: Safari lo ignora. Esto apaga
       el pellizco y el doble toque en el navegador y deja desplazar. */
    html { touch-action: pan-x pan-y; }

    input[type="email"], input[type="text"], input[type="number"],
    input[type="password"], input[type="datetime-local"], input:not([type]),
    input[type="date"], input[type="month"], input[type="search"], input[type="url"],
    input[type="tel"], select, textarea {
        font-size: max(var(--fs-body), 1rem) !important;
    }
}

/* Ámbar y no rojo: no es un error de quien está escribiendo, es un dato
   viejo que hay que reemplazar. El rojo acusaría a la persona equivocada. */
.field-degraded input, .field-degraded select, .field-degraded textarea {
    border-color: var(--warn-text);
}
.field-warn {
    margin: var(--space-100) 0 0; padding: var(--space-100) var(--space-100);
    background: var(--warn-bg); color: var(--warn-text);
    border-radius: var(--radius); font-size: var(--fs-small); line-height: 1.5;
}
.field-warn code {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--fs-small); overflow-wrap: anywhere;
}

.form-section { margin-bottom: var(--space-300); }
.form-section-title {
    margin: 0 0 var(--space-150); padding-bottom: var(--space-100);
    font-size: var(--fs-meta); font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-faint);
    border-bottom: 1px solid var(--border);
}
/* El tope de 420px es lo que impide que a 1440px una columna mida 600 y el
   `select` de «Tipo» —tres opciones cortas— se estire hasta allá, dejando la
   flecha a medio palmo del texto. También le pone un largo de línea legible
   a los campos de texto: nadie escribe un teléfono en 600px. */
/* Las columnas LLENAN la tarjeta, y el tope se mueve al CAMPO.

   Estaba en la columna —`minmax(260px, 420px)`— y eso dejaba una franja vacía
   a la derecha de cada campo ancho: el título y la descripción de un producto
   ocupaban media tarjeta y el resto era aire. Se veía como un formulario a
   medio armar.
   
   Pero quitarlo del todo tampoco: a 1440px una columna suelta mide 600, y un
   `select` de tres opciones cortas estirado hasta allá deja la flecha a medio
   palmo del texto — y nadie escribe un teléfono en 600px. Así que el tope
   sigue, sobre lo que de verdad lo necesita: el campo NORMAL. El que se
   declara ancho lo dice, y llena. */
/* 320px y no 240 en el `minmax`: con la tarjeta llenando el ancho, `auto-fit`
   decide cuántas columnas caben, y en 240 salían CUATRO de unos 250px a
   1440px — el nombre de un producto no se lee en 250px. En 320 salen tres de
   unos 350, que es el ancho que ya tenían las dos de antes. Se aprovecha el
   ancho con más columnas, no con campos más largos.

   El `min(…, 100%)` no es adorno y se pagó mirándolo: un `minmax` con un
   mínimo FIJO no baja de ese mínimo, así que a 355px de viewport la grilla
   medía 250 y su columna 320, y los campos salían 49px POR FUERA de la
   tarjeta. Con `min(320px, 100%)` el mínimo cede cuando el contenedor es más
   angosto que él, que es justo el caso del teléfono. */
.form-grid {
    display: grid; gap: 0 var(--space-200);
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
}
/* La misma grilla en UNA columna, para las fichas de pocos controles cortos —el
   tipo de producto, la opción—: en varias columnas quedan pares desparejos, con
   media fila en blanco al lado del último campo, y el ojo tiene que decidir por
   dónde sigue leyendo.

   Va DESPUÉS de `.form-grid` y no antes: las dos son una clase sola, así que
   entre ellas no decide la especificidad sino el orden. Escrita arriba, la
   regla estaba puesta y la ficha seguía saliendo en tres columnas. */
.form-grid-one { grid-template-columns: minmax(0, 1fr); }
/* Y en TRES, para medidas cortas que van juntas —los tres espacios de una
   etiqueta, el rollo con sus columnas—: a 320px por columna nunca entraban. */
.form-grid-3 {
    grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr));
}
/* Y en DOS fijas, para el alta que EMPAREJA campos: el nombre con su bandera
   de IVA, los canales con los terceros. El `auto-fit` de la base en una
   tarjeta ancha reparte en tres o cuatro columnas y el formulario se lee
   regado — lo pagó el alta de listas de precios, con el nombre en una
   esquina y la casilla del IVA flotando a media tarjeta (2026-08-02). */
.form-grid-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 720px) {
    .form-grid-two { grid-template-columns: minmax(0, 1fr); }
}
/* Un campo SIN rótulo dentro de la grilla —una casilla suelta— se alinea con
   los controles de al lado y no con sus rótulos. Sin esto la pastilla se pega
   al techo de su celda, a la altura de la palabra «Nombre», y la fila entera se
   lee torcida. */
.field-bottom { display: flex; flex-direction: column; justify-content: flex-end; }
.form-grid > .field { max-width: 420px; }
/* La pieza del municipio (`ui/_place_field.html`) son cuatro campos que
   tienen que caer en la grilla del formulario como si fueran suyos: país,
   departamento, municipio y la ciudad de afuera. El envoltorio solo existe
   para que `municipio.js` sepa cuáles van juntos. */
.place-field { display: contents; }
.form-grid > .place-field > .field { max-width: 420px; }
/* Ocupa la fila entera de la grilla. Sin esto —y estuvo sin esto desde la
   F1-a— la dirección de una sede quedaba en una columna de 260px al lado de
   un hueco vacío, que es justo lo contrario de lo que el nombre promete. */
.form-grid .field-wide { grid-column: 1 / -1; max-width: none; }
/* En el cajón, la columna ES el cajón: los campos llenan el mismo ancho que la
   zona de fotos y los botones, y no se quedan a 420px con la zona a 520. */
.drawer-body .form-grid-one > .field { max-width: none; }
.form-actions { display: flex; gap: var(--space-100); align-items: center; margin-top: var(--space-200); }

/* ── Formularios de pantalla y confirmación ───────────────────────────── */

/* `.page-header`, `.page-header-actions` y `.page-sub` se BORRARON al pasar
   el encabezado al shell. No se dejan como alias: mientras tengan regla,
   `test_every_class_a_template_uses_has_a_rule` deja pasar una plantilla que
   vuelva a la forma vieja, y la guarda de la migración desaparece.
   `.page-banner .btn-primary` hace lo que hacía la última línea. */
.page-banner .btn-primary { flex-shrink: 0; }

/* La tarjeta NO topa, y el tope de lectura vive en el campo.

   Topaba en 760px «por ancho de lectura», pero lo que hay que proteger de
   estirarse es el CAMPO, y eso ya lo hace `.form-grid > .field`. Sobre la
   tarjeta el tope hacía otra cosa: a 1440px dejaba 374px de aire muerto a la
   derecha del formulario, con la tarjeta de abajo —la del costo de llegada, que
   es una tabla y nunca topó— midiendo 1134. Dos tarjetas de la misma pantalla
   con anchos distintos se leen como una pantalla a medio armar, y ése era el
   síntoma.

   Y necesitaba excepciones, que es la señal de que era el default equivocado:
   la matriz de roles ya se había salido con `form-card-wide`. Mismo argumento
   que el `width: 100%` de `.btn-primary`. Ahora el default llena y quien quiera
   angostarse lo pide con `form-card-narrow` — que es UNA plantilla, la de
   invitar a alguien. */
.form-card { max-width: none; }
.form-card-narrow { max-width: 460px; }
/* El tope de LECTURA, como opt-in y no como default — el default lleno ya
   se litigó arriba. Lo piden las fichas de catálogo contable (cuenta,
   diario): campos cortos en una columna, y a pantalla completa la tarjeta
   era un río de aire con un formulario en la esquina. 760px es el ancho de
   la referencia de tiendapp que la revisión pidió copiar. */
.form-card-reading { max-width: 760px; }

/* EL DESTRUCTIVO NO ES ROJO EN REPOSO (Renzo, 2026-08-14).

   Era rojo relleno con sombra roja, y se leía como un botón de plantilla:
   la pieza más gritona de la pantalla puesta sobre la acción que casi nunca
   se aprieta. Un rojo permanente además se gasta — cuando todo lo que puede
   destruir está en rojo, el rojo deja de avisar.

   Ahora es el mismo contorno neutro que cualquier secundario, y lo que dice
   qué hace es el GLIFO: el bote de basura en «Eliminar», la caja con tapa
   en «Archivar». El icono distingue las dos cosas, que el color no
   distinguía.

   El rojo aparece AL PASAR POR ENCIMA, que es el instante en que importa:
   el puntero ya está sobre el botón y todavía no se ha hecho clic. Es la
   última señal antes de la confirmación, no la decoración de la pantalla.

   Y NO es lo único que avisa, por lo mismo que está escrito en las
   pastillas: quien no distingue el rojo del gris se entera por la palabra
   —«Eliminar»—, por el glifo y por la página de confirmación, que es la que
   de verdad gatea. El color es la cuarta señal, no la primera.

   `.btn-danger` y `.btn-danger-ghost` quedan IGUALES y comparten regla. Eran
   dos tonos para la misma idea —4 usos y 50— y ahora que el relleno rojo se
   fue no hay nada que los distinga. El nombre viejo se conserva para no
   tocar cincuenta plantillas por un renombre; la migración al componente se
   lo lleva. */
.btn-danger,
.btn-danger-ghost {
    color: var(--text-light);
    background: transparent;
    border-color: var(--border-strong);
}
.btn-danger:hover,
.btn-danger-ghost:hover {
    background: var(--danger-bg);
    color: var(--danger-text);
    border-color: var(--danger-bg);
}

.confirm-text { margin: 0 0 var(--space-200); color: var(--text-light); font-size: var(--fs-body); }

/* `stretch` y no `flex-start`: la zona punteada ocupa el ancho de su tarjeta,
   como la del producto. Con `flex-start` se encogía al ancho de su texto y una
   zona de arrastre del tamaño de una frase no se lee como una zona. */
.upload { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-100); }
/* La previa sí se queda de su tamaño, y redonda solo para una persona: la de
   un producto es cuadrada porque una etiqueta recortada en círculo se pierde. */
.upload-preview { width: 72px; height: 72px; object-fit: cover; align-self: flex-start;
    border-radius: 10px; border: 1px solid var(--border); }
/* Sin Alpine el atributo nunca se quita, así que la previa vacía no ocupa
   sitio en vez de dejar un cuadro roto. */
[x-cloak] { display: none !important; }

/* ── Mensajes ─────────────────────────────────────────────────────────── */

.messages { list-style: none; margin: 0 0 var(--space-200); padding: 0; }
.messages li {
    padding: var(--space-150) var(--space-200); margin-bottom: var(--space-100);
    border-radius: var(--radius); font-size: var(--fs-body); font-weight: 500;
}
.messages .success { background: var(--ok-bg);     color: var(--ok-text); }
.messages .error   { background: var(--danger-bg); color: var(--danger-text); }
.messages .warning { background: var(--warn-bg);   color: var(--warn-text); }
.messages .info    { background: var(--info-bg);   color: var(--info-text); }
/* Cuando la página vuelve a UN PUNTO —un formulario que postea a `#su-tarjeta`
   para no devolver a quien agregó una regla al tope de una pantalla de cuatro
   mil píxeles— el aviso no puede quedarse arriba, fuera de vista: «Regla
   creada» o, peor, el error, no los leería nadie. Se pega bajo la barra y
   acompaña. Sin fragmento en la URL no cambia nada, y un navegador sin
   `:has()` deja el aviso donde siempre estuvo. */
.content:has(:target) > .messages {
    position: sticky; top: calc(var(--topbar) + var(--space-100)); z-index: 5;
}
.content:has(:target) > .messages li { box-shadow: var(--shadow); }
/* Y el destino baja lo que mide el aviso: pegado a 8px de la barra, tapaba el
   título de la tarjeta a la que se acababa de volver (medido: 72 contra 80). */
.content:has(> .messages:not([hidden])) :target { scroll-margin-top: calc(var(--topbar) + var(--space-200) + 64px); }

/* Dentro del drawer la tabla no compite con la altura de la pantalla: el
   drawer ya hace scroll, y un tope acá dejaría dos barras anidadas. */
.table-wrap-inline { max-height: none; min-height: 0; }

/* ── Listas de tarjetas y controles menores ───────────────────────────── */

.card-list { list-style: none; margin: 0 0 var(--space-250); padding: 0; display: grid; gap: var(--space-100); }
.card-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-200); padding: var(--space-200) var(--space-200);
}
.card-title { font-weight: 600; font-size: var(--fs-section); }
.card-sub { margin: var(--space-050) 0 0; color: var(--text-light); font-size: var(--fs-small); }

.inline-form { display: inline; margin: 0; }
.icon-btn-light {
    color: var(--text-light); width: 26px; height: 26px; font-size: var(--fs-body);
}
.icon-btn-light:hover { background: var(--bg); color: var(--text); }
.icon-btn-light:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* Baja CON `.btn`, o deja de ser pequeño. Cuando el botón normal pasó a 12px,
   éste se quedaba en 12.5 y quedaba más grande que él — un nombre que miente
   en la única dimensión que lo justifica. Hay una prueba que compara los dos. */
/* El de una FILA, una banda o una tarjeta. 30px, no 22.

   Medía `--sp-1 --sp-3` con `--fs-small`, o sea 22px de alto, y ahí no cabe
   un glifo legible: a 14px el icono ocupaba casi todo el botón y a 11 no se
   distinguía de un punto. Con los CTA llevando icono, 22px dejó de ser un
   tamaño posible.

   Sigue siendo claramente más chico que el de pantalla —30 contra 38— que
   es lo que tiene que decir: esta acción es de esta fila, no de la
   pantalla. */
.btn-small {
    /* El alto va DECLARADO y no se deja al relleno: es el número del que
       cuelga `--band-h`, y derivarlo de padding + interlínea + bordes lo
       dejaría cambiando solo cada vez que alguien toque la tipografía. */
    min-height: var(--btn-small-h);
    padding: 0 var(--space-200);
    font-size: var(--fs-small);
    /* Un renglón siempre: es un botón de fila o de tarjeta, y en una
       columna angosta el flex lo encogía hasta partir «Ver y actuar» en
       tres renglones (la emisión electrónica de la factura, 2026-11-02). */
    white-space: nowrap;
}
.btn-small svg { width: 14px; height: 14px; }

/* SOLO EL GLIFO, cuadrado. Es el «⋯» de una fila, el «✕» de un panel: los
   que hoy se resuelven caso por caso con una clase distinta en cada
   pantalla —`.icon-btn` sobre oscuro, `.forms-row-action`, y varios `.btn`
   con el texto quitado a mano—.

   Cuadrado y del alto de `.btn-small` para que en una fila de acciones
   mezclada —«Editar» y un «⋯»— los dos midan igual de alto y el cuadrado
   no se lea como un botón a medio hacer.

   Quien lo use TIENE que poner `aria-label`: un botón cuyo nombre accesible
   es la cadena vacía no existe para quien navega con lector de pantalla. */
.btn-icon {
    width: 30px; height: 30px;
    padding: 0;
    gap: 0;
}
.btn-icon svg { width: 15px; height: 15px; }

.preview-card { margin-top: var(--space-300); }

/* ── Ajustes ───────────────────────────────────────────────────────────── */

.setting { padding: var(--space-200) 0; border-bottom: 1px solid var(--border); }
.setting:last-of-type { border-bottom: none; }
.setting-head {
    display: flex; align-items: center; gap: var(--space-100); margin-bottom: var(--space-100);
}
.setting-label { font-size: var(--fs-body); font-weight: 600; }
.setting input[type="text"], .setting select {
    width: 100%; max-width: 380px; padding: var(--space-150) var(--space-150);
    font-family: inherit; font-size: var(--fs-body); color: var(--text);
    /* Longhand: el atajo `background` resetea `background-image`, y esta
       regla es más específica que la que le dibuja la flecha al `select`. */
    background-color: var(--field-bg);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    outline: none;
}
.setting input:focus, .setting select:focus {
    border-color: var(--primary); box-shadow: 0 0 0 3.5px var(--primary-soft);
}

/* Los tres contextos fijan un `padding` propio —para igualar el alto de sus
   vecinos— y con eso pisan el sitio que la flecha necesita a la derecha. Se
   lo devuelven acá, en una sola línea.

   Y va DESPUÉS de los tres a propósito: entre selectores idénticos manda el
   orden, y puesta más arriba, la regla de ajustes —que vive al final— se la
   volvía a pisar. Hay una prueba que resuelve ese orden y exige 30px. */
.field select, .filters select, .setting select { padding-right: var(--space-400); }
.setting-default {
    display: flex; gap: var(--space-100); flex-wrap: wrap; align-items: center;
    margin: var(--space-100) 0 0; color: var(--text-light); font-size: var(--fs-small);
}
/* La línea de estado y el campo que la sigue (la casilla «Geocodificar con
   Google» debajo de la pastilla «Conectado») no se tocan: el aire entre las
   dos es el de un campo con el siguiente (Renzo, 2026-09-14). */
.setting-default + .field { margin-top: var(--space-150); }
/* La URL del webhook de un proveedor de datáfono: para copiarla entera de
   un toque (`user-select: all`) y que no reviente el ancho de la tarjeta. */
.webhook-url {
    display: block; margin-top: var(--space-100); padding: var(--space-100) var(--space-100);
    border-radius: var(--radius-sm); background: var(--bg);
    font-size: var(--fs-small); word-break: break-all; user-select: all;
}
.setting-reset { margin-top: var(--space-100); }
.setting.field-degraded input { border-color: var(--warn-text); }

/* Los ajustes por bloques (`ui/_settings_table.html`, 2026-10-21): una
   tabla por bloque, «Ajuste · Qué hace · Valor».

   Las tres columnas MIDEN LO MISMO en todos los bloques (2026-10-23): con el
   ancho automático cada tabla repartía según su texto más largo, y en una
   pantalla de siete tablas la columna «Valor» saltaba de lugar de un bloque
   al siguiente. `table-layout: fixed` + el `<colgroup>` de la pieza: el
   rótulo un cuarto, el valor un ancho fijo —un control de formulario no
   necesita media pantalla, y estirado hasta el filo se lee como un campo de
   texto largo— y lo que hace se queda con el resto.

   La fila se alinea arriba —el control y su línea de estado son más altos
   que el rótulo— y el campo no lleva su margen de formulario: la fila ya
   separa. El aire de la fila es uno solo, el de la celda. */
.settings-table { table-layout: fixed; white-space: normal; }
.settings-table-col-label { width: 26%; }
.settings-table-col-help { width: auto; }
.settings-table-col-value { width: 360px; }
.settings-table tbody td {
    vertical-align: top; padding-top: var(--space-150); padding-bottom: var(--space-150);
}
.settings-table-label { font-weight: 600; color: var(--text); }
/* El rótulo y lo que hace arrancan a la altura del TEXTO del control, no de
   su borde: con el mismo `padding-top` el rótulo quedaba 9px más arriba que
   «OC» y la fila se leía torcida. */
.settings-table-label, .settings-table-help { padding-top: calc(var(--space-150) + var(--space-100)); }
.settings-table-help { color: var(--text-light); line-height: 1.45; }
.settings-table-value .field { margin-bottom: 0; max-width: none; }
/* Un control dentro de una celda (`ui/_field.html` con `nolabel`, el rótulo en
   la cabecera de la columna): el aire entre filas lo pone la fila, no la casilla. */
.table td > .field { margin-bottom: 0; }
.settings-table-value .field input:not([type="checkbox"]):not([type="radio"]),
.settings-table-value .field select,
.settings-table-value .field textarea { width: 100%; }
/* El subtítulo dentro de un bloque (`groups`): una banda chica que abarca las
   tres columnas, el título en negrita chica y su frase al lado. Las
   filas que siguen se leen bajo él, sin otra tarjeta. */
.settings-table tbody tr.settings-table-group > th {
    padding: var(--space-150) var(--space-250) var(--space-100); text-align: left;
    background: var(--table-head-bg); border-bottom: 1px solid var(--table-line);
    font-weight: 400;
}
.settings-table-group-title {
    margin-right: var(--space-100); color: var(--text);
    font-size: var(--fs-small); font-weight: 600;
}
.settings-table-group-hint { color: var(--text-light); font-size: var(--fs-small); }
/* UNA línea callada bajo el control: el estado, el valor por defecto y, si
   hay qué devolver, el enlace para volver. Antes eran una pastilla gris, el
   texto y un botón entero en su propio renglón: tres pisos por fila para
   decir «nadie lo tocó». */
.settings-table-meta {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-025) var(--space-100);
    margin: var(--space-075) 0 0; color: var(--text-faint);
    font-size: var(--fs-meta); line-height: 1.5;
}
/* El valor por defecto en la letra de la línea y no en monoespaciada: «Proponer
   descontarlo» en mono ocupaba el doble y partía la línea en dos. */
.settings-table-state, .settings-table-default { white-space: nowrap; }
.settings-table-meta .mono { font-family: inherit; font-weight: 500; color: var(--text-light); }
/* «Modificado» es una pastilla CHICA: es el único estado que hay que ver de
   lejos, y aun así no puede pesar más que el valor que acompaña. */
.settings-table-meta .pill {
    padding: 0 var(--space-100); font-size: var(--fs-meta); line-height: 18px;
}
.settings-table-meta .pill svg { width: 10px; height: 10px; }
.settings-table-meta > * + *::before {
    content: "·"; margin-right: var(--space-100); color: var(--text-faint);
}
/* Sin punto después de la pastilla, que ya separa, ni antes del enlace:
   cuando la línea no cabe, el enlace baja solo y un «·» colgando al inicio
   del renglón se leía como basura. */
.settings-table-meta > .pill + *::before,
.settings-table-meta > .settings-table-reset::before { content: none; }
.settings-table-meta > .settings-table-reset { margin-left: var(--space-050); }
/* «Volver al valor por defecto» es un enlace, no un botón: devolver es lo
   raro, y como botón competía con el control de arriba. Sigue siendo el
   `<button form=…>` de la casa, así que viaja sin JavaScript. */
.settings-table-meta .settings-table-reset {
    height: auto; min-height: 0; padding: 0; border: 0; background: none;
    box-shadow: none; color: var(--primary); font-size: var(--fs-meta);
    font-weight: 500; line-height: 1.5;
}
.settings-table-meta .settings-table-reset:hover { background: none; text-decoration: underline; }
/* Compras → Motivos (2026-10-23): seis plegables de la casa, cerrados y con
   su conteo, uno debajo del otro con el aire de una lista y no pegados. El
   alta va al pie de cada uno: la casilla crece y «Agregar motivo» se para a
   la altura de la casilla, no de su rótulo. */
.reason-lists > .reason-list + .reason-list { margin-top: var(--space-100); }
.reason-lists > .reason-list:first-of-type { margin-top: 0; }
.reason-list > .fold-body { padding: var(--space-150) var(--space-200) var(--space-200); }
.reason-add {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-150);
    margin-top: var(--space-200);
}
.reason-add > .field { flex: 1 1 240px; margin-bottom: 0; }
.reason-add > .btn { flex: none; }
/* «Apagar»/«Prender» al filo derecho de su renglón, todos en la misma
   columna: pegados al nombre, cada uno caía a otra altura. */
.reason-list .boxed-row > .inline-form { margin-left: auto; }
/* En el teléfono la fila se apila: rótulo, qué hace y el valor, uno debajo
   del otro. Los encabezados sobran: cada renglón ya dice qué es. */
@media (max-width: 720px) {
    .settings-table { table-layout: auto; }
    .settings-table colgroup, .settings-table thead { display: none; }
    .settings-table tbody tr {
        display: block; padding: var(--space-150) var(--space-200);
        border-bottom: 1px solid var(--table-line);
    }
    .settings-table tbody tr:last-child { border-bottom: 0; }
    .settings-table tbody tr.settings-table-group > th { display: block; padding: var(--space-150) var(--space-200) var(--space-100); }
    .settings-table-group-hint { display: block; margin-top: var(--space-025); }
    .settings-table tbody td,
    .settings-table tbody td:first-child,
    .settings-table tbody td:last-child {
        display: block; width: auto; max-width: none; padding: 0; border: 0;
    }
    .settings-table-help { margin: var(--space-025) 0 var(--space-100); }
}

/* Medios de pago: una tarjeta por pasarela, sobre el kit de Ajustes. */
.gateway-card { margin-bottom: var(--space-200); }
/* `gateway-head`, `gateway-name` y `gateway-hint` se retiraron el 2026-08-11.
   Eran `.card-header` con otro nombre: la pantalla de pasarelas los escribió
   porque necesitaba la pista DEBAJO del título y `.card-header` la manda a la
   derecha, y Dominios los copió. Eso ahora lo hace `.card-head-lead` —una sola
   forma, con su parcial— y los diecisiete usos migraron. Ver docs/marco-ui.md. */
/* ── Marketplace ──────────────────────────────────────────────────────── */

.section-title {
    margin: var(--space-300) 0 var(--space-150); font-size: var(--fs-meta); font-weight: 700;
    letter-spacing: .08em; text-transform: uppercase; color: var(--text-faint);
}
.section-title:first-of-type { margin-top: 0; }
.card-actions { display: flex; align-items: center; gap: var(--space-100); flex-shrink: 0; }
/* Perfil: los botones de cada tarjeta van abajo, con aire, y pueden partir
   en dos líneas en una pantalla angosta. `.card-actions` a secas vive en la
   cabecera y no se parte. */
.profile-actions { margin-top: var(--space-200); flex-wrap: wrap; }
/* Un dato que se muestra y no se edita (el correo, que es la llave). */
.field-static { margin: 0; padding: var(--space-100) 0; color: var(--text-light); }
/* «Tu cuenta» como tarjeta de identidad (Renzo, 2026-10-10): la cara, el
   nombre y el correo en una fila, y los botones a la derecha. En el
   teléfono se apila, centrado. */
.cuenta {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center; gap: var(--space-200);
}
.cuenta-datos { min-width: 0; }
.cuenta-nombre { margin: 0; font-size: var(--fs-section); font-weight: 700; color: var(--text); }
.cuenta-correo { margin: var(--space-025) 0 var(--space-075); color: var(--text-light); overflow-wrap: anywhere; }
.cuenta-datos .field-hint { margin: 0; max-width: 52ch; }
.cuenta-acciones { display: flex; flex-direction: column; align-items: stretch; gap: var(--space-100); }
@media (max-width: 640px) {
    .cuenta { grid-template-columns: 1fr; justify-items: center; text-align: center; }
    .cuenta-datos .field-hint { margin-inline: auto; }
    .cuenta-acciones { flex-direction: row; flex-wrap: wrap; justify-content: center; }
}
/* La zona de peligro y sus confirmaciones: el mismo borde que el aviso rojo. */
/* La zona de peligro con cara de peligro (Renzo, 2026-09-14): tinte rojo
   de fondo, borde rojo, el título en rojo. Antes era una tarjeta blanca con
   un borde fino que se leía como cualquier otra. */
.card-danger {
    border: 1px solid color-mix(in srgb, var(--danger-text) 35%, white);
    background: color-mix(in srgb, var(--danger-bg) 60%, white);
    box-shadow: none;
}
.card-danger .card-header { border-bottom-color: color-mix(in srgb, var(--danger-text) 25%, white); }
.card-danger .card-header h2 { color: var(--danger-text); }
.card-danger-text { margin: 0 0 var(--space-150); color: var(--danger-text); font-size: var(--fs-body); }
/* Los avisos que llegan DENTRO de una tarjeta repintada por la barra. */
.messages-inline { margin: 0 0 var(--space-150); }
/* El botón de una tarjeta que la barra de guardar esconde
   (`data-savebar-fallback`): `hidden` tiene que ganarle al `display: flex`
   de `.card-actions`, igual que en `.card-header[hidden]` y por lo mismo. */
.card-actions[hidden] { display: none; }
.alert-danger ul, .alert-warn ul { margin: var(--space-100) 0 0; padding-left: var(--space-200); }
.alert p { margin: 0 0 var(--space-075); }
.alert p:last-child { margin-bottom: 0; }

.fill-list { list-style: none; margin: var(--space-100) 0 0; padding: 0; display: grid; gap: var(--space-100); }
.fill-list label { display: block; margin-bottom: var(--space-050); font-size: var(--fs-body); }
.fill-list input {
    width: 100%; padding: var(--space-100) var(--space-150); font: inherit; font-size: var(--fs-body);
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    background: var(--field-bg); outline: none;
}
.fill-list input:focus { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }

.table td .card-sub { display: block; }

/* Una acción y su regla, apiladas: en columnas la tabla no cabía. */
.action-rule {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-100); padding: var(--space-050) 0; font-size: var(--fs-body);
}
.action-rule + .action-rule { border-top: 1px solid var(--border); }
.action-rule select { min-width: 132px; }

/* La matriz de un rol. La fila «Todos los módulos» va teñida: no es un
   módulo, es la llave que abre todos, y tiene que leerse distinta. */
.role-matrix-all > td { background: var(--primary-soft); }
/* Con aire: cada fila es un módulo con su baldosa, su nombre y su clave, y
   al relleno de una lista (pensado para una línea de texto) le faltaba
   respiro entre módulo y módulo (Renzo, 2026-09-29). */
.role-matrix tbody td { vertical-align: middle; padding-block: var(--space-200); }
.role-matrix .cell-lead .card-sub { margin-top: var(--space-025); }
.role-matrix .action-rule { padding-block: var(--space-075); }
.role-matrix-empty { padding: var(--space-200); text-align: center; }
/* Un buscador en la banda de una tarjeta, a la derecha —donde las listas
   ponen «Columnas»—. En un celular baja a su propio renglón. */
.card-head-search { flex: 0 1 260px; min-width: 0; }
@media (max-width: 640px) { .card-head-search { flex-basis: 100%; } }
/* Un `<select>` con su glifo adentro, a la izquierda (`select-glifo.js`
   cambia cuál se ve). El glifo no recibe clics: el que abre es el select. */
.select-glifo { position: relative; display: inline-flex; align-items: center; }
.select-glifo svg {
    position: absolute; left: 11px; width: 16px; height: 16px;
    color: var(--text-light); pointer-events: none;
}
.select-glifo svg[hidden] { display: none; }
.select-glifo select { padding-left: var(--space-400); }

/* El párrafo que explica de qué va un formulario, dentro de su tarjeta.

   Antes esta prosa vivía en el subtítulo del encabezado. En el banner oscuro
   dos renglones de explicación pesan el doble que sobre blanco, y además
   compiten con los datos de la fila —el identificador, el correo, cuántas
   variantes— que es lo que uno va a mirar ahí. Baja acá, que es donde alguien
   la lee una vez y no en cada visita. */
.form-lead {
    margin: 0 0 var(--space-200); color: var(--text-light); font-size: var(--fs-body);
    max-width: 62ch;
}

/* El conteo de una recepción (`inventory/_conteo.html`): modos, casilla del
   código, avisos, pista y tabla son hermanos sueltos dentro de la tarjeta, y
   sin separación la casilla quedaba pegada a los botones y la tabla a la
   pista. Un `gap` en la RAÍZ del fragmento —que htmx repinta entero— y no un
   margen en cada pieza: lo que se agregue mañana ya viene separado. */
#recibir-conteo { display: flex; flex-direction: column; gap: var(--space-150); }
#recibir-conteo > .card-hint, #recibir-conteo > .alert { margin: 0; }

/* ── Zona de arrastre de archivos ─────────────────────────────────────── */

/* Y funciona SIN una línea de JavaScript. El `<input type="file">` se estira
   invisible sobre toda el área: los navegadores aceptan que se le suelte un
   archivo encima, así que arrastrar sale gratis y no hay estado que mantener
   ni evento que capturar. El `<label>` de al lado solo dibuja.

   Un dropzone de JavaScript sería más lucido y dejaría la pantalla sin subir
   imágenes en cuanto el script falle, que es la razón por la que el protocolo
   de esta plataforma pide que todo sirva sin JS. */
.dropzone {
    position: relative; display: block;
    padding: var(--space-300) var(--space-250);
    border: 1.5px dashed var(--border-strong); border-radius: var(--radius);
    background: var(--bg);
    color: var(--text-light); font-size: var(--fs-body); text-align: center;
    cursor: pointer;
    transition: border-color .16s, background .16s, color .16s;
}
/* El archivo YA ELEGIDO, dentro de la zona. En negrita y con su glifo: es la
   diferencia entre una zona que invita y una que confirma, y sin confirmación
   nadie sabe si el clic hizo algo — el `Choose File` del navegador al menos
   escribía el nombre al lado. */
.dropzone-chosen {
    display: inline-flex; align-items: center; gap: var(--space-100);
    color: var(--text); font-weight: 600;
}
.dropzone-chosen svg { width: 16px; height: 16px; color: var(--primary); flex: none; }
.dropzone:hover, .dropzone:focus-within {
    border-color: var(--primary); background: var(--primary-soft);
    color: var(--text);
}
/* Invisible pero PRESENTE: sigue siendo el control que recibe el archivo, el
   que abre el selector al hacer clic y el que el teclado enfoca. `display:
   none` lo sacaría de los tres. */
.dropzone input[type="file"] {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; cursor: pointer;
}

/* La miniatura de lo que ya está guardado. */
.thumb-row { display: flex; flex-wrap: wrap; gap: var(--space-150); margin-bottom: var(--space-150); }
.thumb {
    position: relative; width: 84px; height: 84px; flex-shrink: 0;
    border: 1px solid var(--border); border-radius: 10px;
    background: #fff center/cover no-repeat;
}
.thumb-badge {
    position: absolute; bottom: 4px; left: 50%; transform: translateX(-50%);
    padding: var(--space-025) var(--space-075); border-radius: 999px;
    background: rgba(0, 0, 0, .55); color: #fff;
    font-size: var(--fs-badge); font-weight: 700; letter-spacing: .04em;
    white-space: nowrap;
}
/* La zona COMPACTA (`compact=1`, 2026-10-19): un botón punteado con su
   cámara, y la foto guardada en miniatura chica a su lado. */
.upload-compact { flex-direction: row; align-items: center; gap: var(--space-100); }
.upload-compact .thumb-row { margin: 0; }
.upload-compact .thumb { width: 32px; height: 32px; border-radius: 6px; }
.upload-compact .upload-preview { width: 32px; height: 32px; border-radius: 6px; align-self: center; }
.upload-compact .dropzone {
    display: inline-flex; align-items: center; gap: var(--space-050);
    padding: var(--space-050) var(--space-150); border-radius: var(--radius-field);
    font-size: var(--fs-small); white-space: nowrap;
}
.upload-compact-icon { width: 16px; height: 16px; flex: none; }
.upload-compact .dropzone-chosen span { max-width: 10rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Etiquetas ─────────────────────────────────────────────────────────── */

/* Las guardadas se PINTAN como fichas; el campo de abajo es el que las edita.
   Un editor de fichas de verdad —escribir, Enter, borrar con backspace— es
   JavaScript, y sin él la pantalla se quedaría sin poder etiquetar. Así se ve
   lo que hay y se sigue pudiendo cambiar con el teclado y nada más. */
.tag-row { display: flex; flex-wrap: wrap; gap: var(--space-075); margin-bottom: var(--space-100); }
/* Las pastillas de un panel (`Panel.pills`: los segmentos del tercero,
   2026-11-02): en el riel de 312px un nombre largo se corta con «…» en vez
   de empujar la tarjeta; la que es enlace se oscurece al pasar. */
.panel-pastillas { margin-bottom: 0; }
.panel-pastillas > .tag { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
.panel-pastillas > a.tag { text-decoration: none; }
.panel-pastillas > a.tag:hover { background: color-mix(in srgb, var(--primary) 18%, var(--primary-soft)); }
.tag {
    padding: var(--space-050) var(--space-100); border-radius: 999px;
    background: var(--primary-soft); color: var(--primary-dark);
    border: 1px solid color-mix(in srgb, var(--primary) 22%, transparent);
    font-size: var(--fs-meta); font-weight: 600; white-space: nowrap;
}

/* ── La primera celda de una tabla ────────────────────────────────────── */

/* Miniatura + nombre + subtítulo. Es la forma que se repite en seis de las
   nueve tablas del ERP —la medición de §9.2 del spec de la F1 la nombra como
   lo primero que convendría extraer— y acá deja de ser tres copias parecidas.

   La miniatura no es decoración: en una lista de quinientos productos es lo
   que deja reconocer uno de un vistazo, sin leer. */
.cell-media { display: flex; align-items: center; gap: var(--space-150); }
/* El nombre con su subtítulo forman UN bloque: el interlineado de la tabla
   los separaba como dos renglones sueltos (2026-10-03, ver `.cell-sub`). */
.cell-text { min-width: 0; line-height: 1.35; }
.cell-thumb {
    width: var(--cell-thumb); height: var(--cell-thumb); flex-shrink: 0;
    border: 1px solid var(--border); border-radius: 9px;
    background: #fff center/cover no-repeat;
}
/* Sin imagen, la inicial sobre el tinte de marca. Un cuadro gris vacío se lee
   como algo que falta; una inicial se lee como lo que es —todavía no tiene
   foto— y encima ayuda a distinguir la fila. */
.cell-thumb-empty {
    display: flex; align-items: center; justify-content: center;
    border-color: transparent; background: var(--primary-soft);
    color: var(--primary-dark);
    font-size: var(--fs-small); font-weight: 700; text-transform: uppercase;
}
/* Sin foto y sin inicial que decir (una línea libre de la orden de compra):
   el glifo de la caja, al tamaño de una baldosa chica. */
.cell-thumb-empty > svg { width: 55%; height: 55%; }
/* UN CÓDIGO —un SKU, una referencia— en su ficha gris de letra fija: se lee
   como un código y no como texto, y no se confunde con el nombre de al
   lado. Nació en las líneas de la orden de compra (2026-11-02). */
.codigo-chip {
    display: inline-block; padding: var(--space-025) var(--space-075);
    border-radius: var(--radius-sm); background: var(--bg); border: 1px solid var(--border);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: var(--fs-small); color: var(--text-light);
    white-space: nowrap;
}
/* El nombre pesa; el enlace no se pinta de color. En una tabla de quinientas
   filas, quinientos enlaces morados son ruido: lo que distingue la fila es el
   nombre, y el subrayado al pasar por encima ya dice que se puede abrir.

   `:not(.btn)` porque un BOTÓN también puede ser un `<a>`, y esta regla —dos
   clases y dos elementos— le ganaba por especificidad a la suya. El efecto se
   veía en tres pantallas a la vez: «Desactivar» en ubicaciones, «Eliminar» en
   las direcciones del tercero y «Eliminar» en los tipos de producto salían con
   el borde rosado de `btn-danger-ghost` y el texto NEGRO, que es un botón
   destructivo que no se lee como destructivo. Es la misma forma del error de la
   pastilla contra `.field label`: la regla del botón estaba escrita y perdía en
   la cascada. Excluirlo es mejor que subir la especificidad del botón, porque
   así el que manda sigue siendo el botón en cualquier contenedor futuro. */
.cell-media a:not(.btn), .table tbody a:not(.btn) {
    color: var(--text); font-weight: 600;
}
.cell-media a:not(.btn):hover, .table tbody a:not(.btn):hover {
    color: var(--primary-dark);
}

/* Una celda que enumera cosas: no crece, se corta y lo dice. Sin esto, cuatro
   etiquetas de veinte caracteres empujan las columnas de la derecha fuera de
   la pantalla y la fila deja de poder barrerse. */
.cell-tags { max-width: 260px; }
.cell-more { color: var(--text-faint); font-size: var(--fs-meta); }

/* ───────────────────────────────────────────────────────────────────────────
   INTEGRANTES: la grilla de tarjetas y las barras de progreso.

   Es la ÚNICA lista del ERP que no usa la tarjeta-tabla, y es una excepción
   decidida: cada integrante lleva el medidor de su ficha más una barra por
   inducción —cuatro en el caso de LTDC—, y en una tabla eso son cuatro
   columnas que solo significan algo leídas juntas. A 1440px la fila se vuelve
   ilegible antes de la tercera. Cargos y Tareas siguen con la tabla.
   ─────────────────────────────────────────────────────────────────────────── */

/* El rótulo del grupo lleva el conteo pegado, no en otra columna: «Barista 10»
   se lee como una sola cosa. La línea que sigue al texto lo separa del
   siguiente grupo sin gastar un margen entero. */
.people-group { margin-bottom: var(--space-250); }
.people-group-title {
    display: flex; align-items: center; gap: var(--space-100);
    margin: 0 0 var(--space-150); font-size: var(--fs-body); font-weight: 700;
    color: var(--text);
}
.people-group-title::after {
    content: ""; flex: 1; height: 1px; background: var(--border);
}
.people-group-count { font-weight: 600; color: var(--text-light); }

/* `min(340px, 100%)` y no un mínimo fijo, por lo mismo que `.form-grid`: con el
   mínimo fijo, a 360px de viewport la columna mide más que la grilla y las
   tarjetas salen por fuera. */
.people-grid {
    display: grid; gap: var(--space-150);
    grid-template-columns: repeat(auto-fill, minmax(min(340px, 100%), 1fr));
}
.people-card {
    display: flex; flex-direction: column; gap: var(--space-150);
    padding: var(--space-200); background: var(--card-bg);
    border-radius: var(--radius-lg);
    /* Sin borde y con la sombra suave: el referente (2026-09-13) flota
       sobre el fondo en vez de recortarse con una línea. */
    box-shadow: var(--shadow);
}
/* Rejilla y no fila: la foto, el nombre y los botones arriba, y los anillos
   en su propio renglón desde la columna del nombre hasta el borde. */
.people-card-head {
    display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: start; column-gap: var(--space-150);
}
.people-avatar {
    flex-shrink: 0; width: 48px; height: 48px; border-radius: 50%;
    object-fit: cover;
}
.people-avatar-empty {
    display: grid; place-items: center; background: var(--primary-soft);
    color: var(--primary); font-weight: 700; font-size: var(--fs-stat);
}
/* `min-width: 0` es lo que permite que el correo se recorte: sin él, un flex
   item no baja de su contenido y un correo largo estira la tarjeta entera. */
.people-card-who {
    flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-025);
    padding-top: var(--space-025);
}
.people-card-name {
    font-weight: 700; font-size: var(--fs-body);
    color: var(--text); text-decoration: none;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.people-card-name:hover { color: var(--primary-dark); }
.people-card-mail {
    font-size: var(--fs-small); color: var(--text-light);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* El estado y el acceso, bajo el correo: pastillas con punto de color. */
.people-card-pills { display: flex; flex-wrap: wrap; gap: var(--space-075); margin-top: var(--space-050); }
.pill-dot {
    display: inline-flex; align-items: center; gap: var(--space-075);
    padding: var(--space-025) var(--space-100) var(--space-025) var(--space-100); font-weight: 600;
}
.pill-dot::before {
    content: ''; width: 6px; height: 6px; border-radius: 50%;
    background: currentColor; flex: none;
}
.people-card-tools { display: flex; gap: var(--space-075); flex-shrink: 0; }
/* `.icon-btn` está calibrado contra el banner oscuro —hover blanco—, así que en
   una tarjeta clara desaparecía. Éste es su par para fondo claro. */
.card-icon-btn {
    display: grid; place-items: center; width: 32px; height: 32px;
    border: 1px solid var(--border); border-radius: var(--radius-field);
    color: var(--text-light); background: var(--card-bg);
}
.card-icon-btn:hover {
    color: var(--primary-dark); border-color: var(--primary);
}
.card-icon-btn:focus-visible {
    outline: 2px solid var(--primary); outline-offset: 1px;
}
.card-icon-btn svg { width: 16px; height: 16px; }
/* Los formularios de la tarjeta, en baldosas (`people/_card_form.html`,
   2026-11-01): una fila bajo las pastillas, alineada con el nombre. Antes
   eran un renglón con rótulo y barra cada uno, y cuatro de ellos medían
   más que el resto de la tarjeta. Si alguien tiene muchos, la fila baja. */
/* De borde a borde bajo una línea suave (el referente de Renzo, 2026-11-01). */
.people-card-forms {
    grid-column: 1 / -1;
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-050);
    margin-top: var(--space-150); padding-top: var(--space-150);
    border-top: 1px solid var(--border);
}
/* El ancla del globo: se enfoca con el teclado y al tocarla. */
.people-card-form { display: inline-flex; border-radius: 50%; cursor: default; }
.people-card-form:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* Los medidores de la ficha: la baldosa del ícono a la izquierda y, a su
   derecha, el rótulo con la cifra y la barra. Sin panel —ni fondo ni borde ni
   relleno— desde el 2026-09-14. Completo = verde lima en la baldosa, la cifra
   y la barra. */
.meter-panel { display: flex; align-items: center; gap: var(--space-150); padding: 0; }
.meter-tile {
    flex: none; display: grid; place-items: center; width: 34px; height: 34px;
    border-radius: var(--radius-sm); background: var(--primary-soft);
    color: var(--primary);
}
.meter-tile svg { width: 16px; height: 16px; }
.meter-panel-done .meter-tile {
    background: color-mix(in srgb, var(--done) 16%, white);
    color: var(--done-text);
}
.meter-panel .meter { flex: 1; min-width: 0; gap: var(--space-050); }
.meter-panel .meter-label { color: var(--text); }
.meter-panel .meter-track { height: 5px; background: var(--primary-soft); }
.meter-panel-done .meter-track { background: color-mix(in srgb, var(--done) 16%, white); }
.people-card-none {
    margin: 0; font-size: var(--fs-small); font-style: italic; color: var(--text-light);
}
/* La tarjeta Acceso: el enlace de invitación cuelga de la fila del estado. */
.access-card .input-copy { margin-top: var(--space-100); }
/* El riel de la ficha (2026-10-08): lo que no es una fila de dato —las
   notas, el vacío de Roles— va en un recuadro suave, para que se lea como
   contenido de la tarjeta y no como un texto suelto bajo la banda. */
.ficha-recuadro {
    margin: 0; padding: var(--space-150) var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--bg);
}
.ficha-recuadro { container-type: inline-size; }
.ficha-recuadro .empty-compact { padding: var(--space-050) 0; }
.ficha-recuadro .empty-text:last-child { margin-bottom: 0; }
/* Con ancho, el vacío va en UNA fila: el glifo, lo que dice y el botón. */
@container (min-width: 360px) {
    .ficha-recuadro .empty-compact {
        display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
        align-items: center; column-gap: var(--space-150);
    }
    .ficha-recuadro .empty-icono { grid-row: 1 / span 2; margin: 0; }
    .ficha-recuadro .empty-title { grid-column: 2; }
    .ficha-recuadro .empty-text { grid-column: 2; margin-bottom: 0; }
    .ficha-recuadro .empty-compact > .btn { grid-column: 3; grid-row: 1 / span 2; }
}
.ficha-recuadro.facts-text { color: var(--text); }
.ficha-sedes { display: flex; flex-wrap: wrap; gap: var(--space-050); }
/* En los 312 px del riel la banda no baja el botón a otro renglón: se
   parte la pista, y «Editar» se queda al lado de su título. */
/* ── «Dónde aparece» de un sugerido (2026-10-29) ───────────────────────────
   Arriba los lugares (`.option-cards--fila`): los tres en un renglón cuando
   caben y, si no, uno debajo del otro —nunca dos y uno huérfano—. Debajo, si
   sale en el cajón, un PANEL que junta lo del cajón: Encima / Debajo a la
   izquierda y una muestra a la derecha que pone el sugerido donde quedó
   elegido. La muestra no tiene script: la lee el CSS del radio `drawer` con
   `:has`, y el sugerido cambia de `order`. Con la tarjeta angosta la muestra
   baja debajo del interruptor. */
.sugerido-donde { container-type: inline-size; }
.sugerido-donde__cuerpo { display: grid; gap: var(--space-200); }
.sugerido-donde .option-cards--fila .option-cards-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-100); }
@container (min-width: 760px) {
    .sugerido-donde .option-cards--fila .option-cards-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-150); }
}
.sugerido-donde__panel {
    display: grid; gap: var(--space-200); align-items: center;
    padding: var(--space-200); border-radius: var(--radius); background: var(--bg);
}
@container (min-width: 520px) {
    .sugerido-donde__panel { grid-template-columns: minmax(0, 1fr) 270px; gap: var(--space-250); padding: var(--space-200) var(--space-250); }
}
.sugerido-donde__cajon { margin: 0; min-width: 0; }
.sugerido-donde__cajon > label { font-size: var(--fs-body); font-weight: 600; color: var(--text); margin-bottom: var(--space-025); }
.sugerido-donde__cajon .field-hint { margin: 0 0 var(--space-150); }
.sugerido-donde__cajon .tip { margin: var(--space-200) 0 0; }
.sugerido-donde__lugares { min-width: 0; }
.sugerido-vista { min-width: 0; max-width: 320px; }
.sugerido-vista__cabeza {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-150) var(--space-150) var(--space-100); font-size: var(--fs-small); color: var(--text);
    border: 1px solid var(--border); border-bottom: 0; border-radius: var(--radius) var(--radius) 0 0;
    background: var(--card-bg);
}
.sugerido-vista__cabeza svg { width: 14px; height: 14px; color: var(--text-light); }
.sugerido-vista__lineas {
    display: flex; flex-direction: column; gap: var(--space-100); padding: 0 var(--space-150) var(--space-150);
    border: 1px solid var(--border); border-top: 0; border-radius: 0 0 var(--radius) var(--radius);
    background: var(--card-bg);
}
.sugerido-vista__sugerido, .sugerido-vista__linea { display: flex; align-items: center; gap: var(--space-100); min-width: 0; }
.sugerido-vista__sugerido {
    order: 0; padding: var(--space-100); border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
    border-radius: var(--radius-field); background: var(--primary-soft);
    box-shadow: 0 4px 14px color-mix(in srgb, var(--primary) 14%, transparent);
}
.sugerido-vista__linea { order: 1; padding: var(--space-025) 0; }
.sugerido-donde:has(input[name="drawer"][value="abajo"]:checked) .sugerido-vista__sugerido { order: 2; }
.sugerido-vista__foto {
    display: grid; place-items: center; flex: none; width: 36px; height: 36px;
    border-radius: 8px; background: var(--bg) center / cover no-repeat; color: var(--text-faint);
}
.sugerido-vista__foto svg { width: 18px; height: 18px; }
.sugerido-vista__texto { display: flex; flex-direction: column; gap: var(--space-050); flex: 1; min-width: 0; }
.sugerido-vista__titulo {
    font-size: var(--fs-meta); font-weight: 600; color: var(--primary);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sugerido-vista__raya { display: block; height: 5px; width: 85%; border-radius: 3px; background: color-mix(in srgb, var(--text-faint) 30%, transparent); }
.sugerido-vista__raya--corta { width: 50%; }
.sugerido-vista__mas {
    display: grid; place-items: center; flex: none; width: 22px; height: 22px;
    border-radius: 50%; background: var(--primary); color: #fff;
}
.sugerido-vista__mas svg { width: 12px; height: 12px; }
.sugerido-vista__cantidad {
    display: inline-flex; align-items: center; gap: var(--space-075); flex: none; padding: 1px var(--space-075);
    border: 1px solid var(--border); border-radius: 6px;
    font-size: var(--fs-meta); color: var(--text-faint);
}
.sugerido-vista__cantidad b { font-weight: 500; color: var(--text-light); }
.sugerido-vista__basura { width: 14px; height: 14px; flex: none; color: var(--text-faint); }

.product-rail .card-header { flex-wrap: nowrap; }
.product-rail .card-head-lead { flex: 1 1 auto; }
.product-rail .card-header > .btn { flex: none; }

/* La barra. `--primary` mientras falta algo y verde cuando llega al final: el
   verde es la señal de «esto ya está» y usarlo siempre lo dejaría sin decir
   nada. */
.meter { display: flex; flex-direction: column; gap: var(--space-050); }
.meter-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-100);
}
.meter-label {
    display: inline-flex; align-items: center; gap: var(--space-050);
    font-size: var(--fs-label); font-weight: 600; letter-spacing: .02em;
    text-transform: uppercase; color: var(--text-light);
}
.meter-icon { display: inline-flex; color: var(--text-light); }
.meter-icon svg { width: 13px; height: 13px; }
.meter-value {
    font-size: var(--fs-small); font-weight: 700; color: var(--primary-dark);
    white-space: nowrap;
}
.meter-value-done { color: var(--done-text); }
.meter-track {
    height: 5px; border-radius: 999px; background: var(--warn-bg);
    overflow: hidden;
}
.meter-fill {
    display: block; height: 100%; border-radius: 999px;
    background: var(--primary);
}
.meter-fill-done { background: var(--done); }

/* La tarjeta de una inducción. Reusa `.people-grid` y `.people-card`: son la
   misma forma —una tarjeta con cabecera, cuerpo y pie— y darles dos nombres
   sería mantener dos veces el mismo ancho de columna. */
.onboarding-card-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-100);
}
.onboarding-card-name {
    margin: 0; font-size: var(--fs-body); font-weight: 700; color: var(--text);
}
.onboarding-card-badges {
    display: flex; flex-wrap: wrap; gap: var(--space-050); justify-content: flex-end;
}
.onboarding-card-text {
    margin: 0; font-size: var(--fs-small); line-height: 1.5;
    color: var(--text-light);
}
/* Los pasos como pastillas y no como lista numerada: lo que se busca acá es
   «¿qué cubre esta inducción?», y una lista vertical de cinco renglones hace la
   tarjeta el doble de alta para decir lo mismo. */
.onboarding-steps { display: flex; flex-wrap: wrap; gap: var(--space-075); }
/* En MAYÚSCULAS y con su ícono, como la referencia. La versión anterior las
   pintaba en caja normal y del tamaño del texto de la tarjeta, así que un
   título largo —«Día 1 — Cultura y presentación personal»— competía con la
   descripción en vez de leerse como una etiqueta. */
.onboarding-step {
    display: inline-flex; align-items: center; gap: var(--space-075);
    padding: var(--space-050) var(--space-100); border-radius: 999px;
    background: var(--primary-soft); color: var(--primary-dark);
    font-size: var(--fs-badge); font-weight: 700;
    text-transform: uppercase; letter-spacing: .03em;
}
.onboarding-step-icon { width: 13px; height: 13px; flex-shrink: 0; }
.onboarding-card-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-100); margin-top: auto;
    padding-top: var(--space-150); border-top: 1px solid var(--border);
}
.onboarding-card-counts {
    font-size: var(--fs-small); color: var(--text-light);
}
/* El tacho va rojo DESDE EL REPOSO, con fondo tenue.

   Decía lo contrario —«tres tachos rojos en una grilla de cinco tarjetas
   gritan más que el contenido»— y el argumento no se sostuvo al verlo: en gris
   el botón no se leía como botón, y al lado de «Editar» con su rótulo parecía
   un recuadro vacío. El rojo tenue lo identifica sin gritar; el fuerte queda
   para el hover. */
.card-icon-btn-danger {
    color: var(--danger-text); border-color: var(--danger-bg);
    background: var(--danger-bg);
}
.card-icon-btn-danger:hover {
    color: var(--danger-text); border-color: var(--danger-text);
}
.form-note { margin-top: var(--space-150); }

/* ── El kiosco de marcaje ──────────────────────────────────────────────────
   Reusa `.auth-stage` y `.auth-card`, que ya son la caja centrada a pantalla
   completa del login: es la misma forma y darle otro nombre sería mantener dos
   veces el mismo ancho.

   Lo que cambia es la ESCALA. Esta pantalla se opera de pie, a un brazo de
   distancia y muchas veces con las manos ocupadas, así que el PIN y el botón
   son deliberadamente más grandes que en cualquier otro formulario del ERP. */
.kiosk-card { max-width: 460px; }
/* La sede, ARRIBA del título y en versalitas: quien llega al mostrador tiene
   que poder confirmar de un vistazo que está marcando en la tienda correcta, y
   un kiosco abierto en la sede equivocada registra el turno de otra gente. */
.kiosk-site {
    margin: 0 0 var(--space-050); text-transform: uppercase; letter-spacing: .08em;
    font-size: var(--fs-badge); font-weight: 700; color: var(--primary);
}
/* Ancho completo, cifras grandes y espaciadas. `letter-spacing` porque son
   dígitos tapados por puntos: sin separación, cuatro puntos pegados no dejan
   contar cuántos se tecleó. */
.kiosk-pin {
    width: 100%; padding: var(--space-200); text-align: center;
    font-size: var(--fs-number); letter-spacing: .5em; text-indent: .5em;
    border: 1px solid var(--border-strong); border-radius: var(--radius-field);
    background: var(--card-bg); color: var(--text);
}
.kiosk-pin:focus { outline: 2px solid var(--primary); outline-offset: 1px; }
/* La confirmación del toque. En verde y con el nombre completo: el barista
   necesita saber que quedó registrado ÉL y no la persona de antes. */
.kiosk-result {
    margin: 0 0 var(--space-150); padding: var(--space-150);
    border-radius: var(--radius-field);
    background: var(--ok-bg); color: var(--ok-text);
    font-size: var(--fs-body); font-weight: 600; text-align: center;
}
/* Quién está adentro. Es lo único que le dice al barista que su toque quedó
   registrado después de que el mensaje se va, y a quien cierra la tienda,
   quién se olvidó de marcar salida. */
.kiosk-inside {
    margin-top: var(--space-250); padding-top: var(--space-200);
    border-top: 1px solid var(--border);
}
.kiosk-inside-row {
    display: flex; justify-content: space-between; gap: var(--space-100);
    margin: 0 0 var(--space-100); font-size: var(--fs-body); color: var(--text);
}
/* Salir del kiosco va abajo, chico y sin color: cierra la sesión, así que un
   botón llamativo sobre un mostrador es un accidente esperando. */
.kiosk-exit { margin-top: var(--space-200); text-align: center; }

/* ── El planificador semanal ───────────────────────────────────────────────
   La grilla es sede × día, sobre `.table`: es una tabla de verdad —encabezado
   de columna y encabezado de fila— y montarla con `grid` le quitaría la
   semántica sin comprar nada.

   El ancho lo reparte el navegador con `table-layout: fixed`: sin eso, la
   columna del día con más turnos se come a las otras seis y la semana deja de
   leerse de un vistazo. */
.week-nav {
    display: flex; align-items: center; justify-content: center;
    gap: var(--space-150); margin-bottom: var(--space-200);
}
.week-label {
    font-size: var(--fs-small); font-weight: 700; color: var(--text-light);
    letter-spacing: .04em;
}
.week-grid { table-layout: fixed; }
/* La abreviatura del día arriba y el número debajo: quien planifica busca por
   día de la semana, y confirma por fecha. */
.week-day { text-align: center; text-transform: capitalize; }
.week-daynum {
    display: block; font-size: var(--fs-meta); font-weight: 400;
    color: var(--text-light);
}
/* El nombre de la sede abre la fila: era un `<th>` en el cuerpo, que la
   hoja no viste (regla 4 de la guía). `td` y con más peso que la celda base. */
.table tbody .week-site {
    text-align: left; font-weight: 600; color: var(--text);
    white-space: nowrap;
}
.week-cell { vertical-align: top; padding: var(--space-100); }
/* Cada turno es un enlace-bloque: el objetivo de toque es la tarjeta entera y
   no un texto de 12px, que sobre una tablet no se acierta. */
.week-shift {
    display: block; margin-bottom: var(--space-050); padding: var(--space-050) var(--space-100);
    border-radius: var(--radius-field);
    background: var(--primary-soft); color: var(--primary-dark);
    text-decoration: none;
}
.week-shift:hover { background: var(--primary); color: #fff; }
.week-shift-who {
    display: block; font-size: var(--fs-small); font-weight: 600;
}
.week-shift-when { display: block; font-size: var(--fs-meta); opacity: .85; }
/* ── El indicador de carga ────────────────────────────────────────────────
   Uno solo, fijo y centrado, para todo pedido de htmx. Lo enciende
   `js/loader.js` con la clase `is-on` — y con 250ms de retardo, que es lo que
   evita que los filtros de las listas, que salen en cada tecla, pinten y
   despinten un velo sobre la pantalla entera cuatro veces por palabra.

   `visibility` ADEMÁS de `opacity`: con opacidad sola el velo sigue estando y
   se come los clics de toda la página mientras es invisible. Y la transición
   la incluye para que apagarse no sea un corte seco.
   ------------------------------------------------------------------------ */
.loader {
    position: fixed; inset: 0; z-index: 200;
    display: grid; place-items: center;
    background: rgba(13, 11, 22, .38);
    backdrop-filter: blur(2px);
    opacity: 0; visibility: hidden;
    transition: opacity .18s ease, visibility 0s linear .18s;
}
.loader.is-on {
    opacity: 1; visibility: visible;
    transition: opacity .18s ease, visibility 0s;
}
/* 72px: un 40% menos que los 120 con los que viene dibujado. Se puede escalar
   sin perder nada porque es vectorial y su desenfoque está en unidades del
   `viewBox` —el filtro encoge con el dibujo—, así que el tamaño es una
   decisión de pantalla y no una restricción del archivo. */
.loader-art { display: block; width: 72px; height: 72px; }
.loader-art-svg { display: block; width: 100%; height: 100%; }

/* Para lo que se anuncia y no se ve. `clip-path` y no `display: none`: lo
   escondido con `display` los lectores de pantalla tampoco lo leen. */
/* ANCLADO arriba a la izquierda (`top`/`left`): sin eso queda donde caería
   en el flujo, y si ningún ancestro está posicionado se ubica contra `body`
   — o sea FUERA de la tarjeta que se desplaza. En Etiquetas, el texto de la
   flecha de cada fila llegaba a 2.469px y le daba scroll a la ventana
   entera: al cargar quedaba corrida, con la banda cortada arriba, y la rueda
   sobre la tabla no la devolvía (Renzo, 2026-10-20). */
.visually-hidden {
    position: absolute; top: 0; left: 0; width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
    .loader { transition: none; }
}

/* ── La barra de guardado ─────────────────────────────────────────────────
   Pegada al techo del contenido, debajo de la topbar fija. Aparece cuando hay
   algo sin guardar y se va cuando no — pero NACE VISIBLE en el HTML y la
   esconde `js/savebar.js`. Esa inversión es lo único que la hace degradar: sin
   JavaScript se queda puesta y la ficha se guarda igual.

   `position: sticky` y no `fixed`: fija taparía el contenido de las pantallas
   que no la tienen, y obligaría a reservarle alto a todas. Pegajosa ocupa su
   renglón solo donde existe.
   ------------------------------------------------------------------------ */
.savebar {
    /* FIJA y centrada, dentro de la franja de la topbar. Ahí el medio está
       vacío —a la izquierda la marca, a la derecha la empresa— y es donde el
       ojo ya está cuando algo cambia arriba.

       Fija y no pegajosa: pegajosa vive dentro del contenido, así que se
       despega al llegar al final de la ficha y desaparece justo cuando alguien
       terminó de escribir y va a guardar.

       `z-index` por encima de la topbar, que es 100: la barra la TAPA a
       propósito mientras hay algo pendiente. */
    /* Centrada en la franja a lo ALTO también, mida lo que mida la barra: la
       mitad de la topbar, corrida media altura propia. Con `top: 12px`
       quedaban 12 px arriba y 4 abajo (Renzo, 2026-10-08). */
    position: fixed; top: calc(env(safe-area-inset-top) + var(--topbar-franja) / 2); left: 50%;
    transform: translate(-50%, -50%);
    z-index: 110;
    /* Se encoge con la pantalla y no se desborda: `min()` la deja crecer hasta
       560px y después la ata al ancho disponible menos el aire de los lados. */
    width: min(560px, calc(100vw - 2 * var(--space-200)));
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150);
    padding: var(--space-100) var(--space-100) var(--space-100) var(--space-200);
    /* Un punto más clara que la topbar: del mismo color se leería como un
       hueco recortado en ella en vez de como algo que se posó encima. */
    background: #1b1830; color: #fff;
    border: 1px solid rgba(255, 255, 255, .1);
    border-radius: 999px;
    box-shadow: 0 8px 28px rgba(0, 0, 0, .45);
}
.savebar-text {
    display: flex; align-items: center; gap: var(--space-100);
    font-size: var(--fs-body); font-weight: 600;
}
/* Un punto y no un icono: lo que dice es «hay algo pendiente», y para eso una
   forma de seis píxeles alcanza. Un triángulo de alerta diría que algo está
   mal, y no lo está — todavía no se guardó, nada más. */
.savebar-dot {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--warn-text); flex: none;
}
.savebar-actions { display: flex; align-items: center; gap: var(--space-075); flex: none; }
/* El texto cede primero: con la pantalla angosta lo que no puede faltar son
   los botones, y «Cambios sin guardar» se recorta antes que ellos. */
.savebar-text { min-width: 0; overflow: hidden; text-overflow: ellipsis;
                white-space: nowrap; }
/* El fantasma sobre fondo oscuro: el de la hoja es para fondo claro y acá
   quedaba texto gris sobre casi negro. */
.savebar .btn-ghost {
    color: #fff; background: rgba(255, 255, 255, .12);
    border-color: transparent;
}
.savebar .btn-ghost:hover { background: rgba(255, 255, 255, .2); }

/* Con JavaScript: escondida hasta que algo cambia. `js/savebar.js` marca el
   `<html>` para que la regla exista solo cuando hay quien la encienda — sin
   eso, un JS que no cargue dejaría la barra escondida y la ficha sin guardar
   por ninguna puerta. */
html.js .savebar { display: none; }
html.js .savebar.is-dirty { display: flex; }
/* `visible=1`: el formulario que se manda tal como llega (el plan con sus
   sugerencias). La barra se queda, y sin cambios calla lo que no aplica. */
html.js .savebar.savebar-visible { display: flex; }
.savebar.savebar-visible:not(.is-dirty) [data-savebar-text],
.savebar.savebar-visible:not(.is-dirty) .btn[type="reset"],
.savebar.savebar-visible:not(.is-dirty) [data-savebar-reset] { visibility: hidden; }
/* Falló el guardado: la barra se queda —hay cambios sin guardar— y lo dice
   con el borde. El detalle del error lo pinta la ficha; la barra solo avisa
   que no entró. La marcan los modos grupo y dirigido de `savebar.js`. */
.savebar.is-error { border-color: var(--danger-vivid); }
.savebar.is-error .savebar-text { color: var(--danger-vivid); }
/* La celda de una tabla que se edita entera (`data-savebar-marcar`): cambiada
   y sin guardar. */
.table tbody tr > td.is-changed { background: var(--warn-bg); }

/* La barra de ACCIÓN: la misma barrita como puro feedback de viaje —
   «Publicando…» mientras el POST navega—. Escondida SIN condición de JS, al
   revés de la de guardar: no gobierna nada, así que sin JavaScript no hay
   nada que encender ni puerta que perder. */
.savebar.actionbar { display: none; }
html.js .savebar.actionbar.is-active { display: flex; }

/* Angosto: la barra se va ABAJO. Arriba no cabe —la topbar ya lleva la
   hamburguesa, la marca y el nombre de la empresa, así que el medio deja de
   estar libre— y puesta debajo de ella tapaba el título de la ficha, que es lo
   primero que alguien mira para saber qué está editando.

   Abajo no tapa nada que se lea, queda donde llega el pulgar, y es donde
   vivían los botones antes de esta pasada. */
@media (max-width: 767px) {
    .savebar {
        /* Abajo, solo centrada a lo ancho: el `-50%` vertical de arriba la
           subiría media altura. */
        top: auto; bottom: var(--space-150); transform: translateX(-50%);
        width: calc(100vw - 2 * var(--space-150));
        border-radius: var(--radius);
    }
}
/* Y en una pantalla de verdad chica el rótulo sobra: los dos botones dicen lo
   mismo. `340px` y no `380`: un iPhone mini mide 375 y ahí el rótulo TODAVÍA
   cabe — cortarlo en 380 lo escondía en un teléfono corriente. */
@media (max-width: 340px) {
    .savebar-text { display: none; }
    .savebar { justify-content: flex-end; }
}

/* ── El dibujo del loader ─────────────────────────────────────────────────
   Las animaciones que venían dentro del SVG, movidas acá. Incrustado y con sus
   reglas afuera, la página puede PAUSARLO — que es lo que hace falta: quieto
   en la barra de guardado, girando solo mientras se guarda.

   Corren únicamente bajo `.is-spinning`. Al revés —animando siempre y pausando
   con una clase— un dibujo que aparece en una barra que casi siempre está
   quieta estaría gastando cuadros todo el tiempo para nada.
   ------------------------------------------------------------------------ */
.loader-bg { fill: var(--loader-bg, #0d0d14); }
.ring, .arm, .core, .au, .white { transform-box: fill-box; transform-origin: center; }
.white { fill: var(--loader-rest, #fff); }

.is-spinning .ring  { animation: loader-spin calc(var(--loader-pulse, 2s) * 6) linear infinite; }
.is-spinning .arm   { animation: loader-merge var(--loader-pulse, 2s) cubic-bezier(.45,0,.55,1) infinite; }
.is-spinning .core  { animation: loader-core var(--loader-pulse, 2s) cubic-bezier(.45,0,.55,1) infinite; }
.is-spinning .white { animation: loader-whiteout var(--loader-pulse, 2s) cubic-bezier(.45,0,.55,1) infinite; }
.is-spinning .au1 { animation: loader-drift1 calc(var(--loader-pulse, 2s) * 3.5) ease-in-out infinite; }
.is-spinning .au2 { animation: loader-drift2 calc(var(--loader-pulse, 2s) * 4.5) ease-in-out infinite; }
.is-spinning .au3 { animation: loader-drift3 calc(var(--loader-pulse, 2s) * 5.5) ease-in-out infinite; }
/* El velo de toda la app trae el dibujo con `is-spinning` fijo (ui/_loader.html):
   escondido seguía girando en cada página, y un `transform` de SVG corre en el
   hilo principal, el de las teclas (Renzo, 2026-10-12: el chat escribía con
   atraso). Escondido queda en pausa; `.is-on` lo suelta donde iba. */
.loader:not(.is-on) .loader-art * { animation-play-state: paused; }

@keyframes loader-spin  { to { transform: rotate(360deg); } }
@keyframes loader-merge { 0%, 100% { transform: translateX(0) scale(1); }
                          50%      { transform: translateX(-70px) scale(1.08); } }
@keyframes loader-core  { 0%, 100% { transform: scale(.94); }
                          50%      { transform: scale(1.14); } }
@keyframes loader-whiteout { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
@keyframes loader-drift1 { 0%, 100% { transform: translate(-40px, 30px); }
                           50%      { transform: translate(110px, -80px); } }
@keyframes loader-drift2 { 0%, 100% { transform: translate(80px, 60px); }
                           50%      { transform: translate(-70px, -40px); } }
@keyframes loader-drift3 { 0%, 100% { transform: translate(20px, -90px); }
                           50%      { transform: translate(-60px, 90px); } }

/* La marca de la barra: el mismo dibujo, del tamaño de un punto. Reemplaza al
   punto ámbar que había — dice lo mismo cuando está quieto y además puede
   decir «estoy guardando» sin cambiar de forma. */
.savebar-mark { width: 20px; height: 20px; flex: none; display: block; }

@media (prefers-reduced-motion: reduce) {
    .is-spinning .ring, .is-spinning .arm, .is-spinning .core,
    .is-spinning .white, .is-spinning .au { animation: none; }
    .is-spinning .white { opacity: 1; }
}

/* ── Los bloques de una inducción ────────────────────────────────────────────
   Nueve tipos, y el contenedor los separa a todos igual: el ritmo vertical de
   un paso lo pone el CONTENEDOR y no cada tipo, porque con nueve márgenes
   propios el espacio entre un texto y un aviso dependería de cuál vino antes.

   `.block-prose` es el único sitio donde se pinta marcado que no escribimos
   nosotros —lo que `|rich` deja pasar—, así que sus reglas van por descendencia
   y no por clase: el HTML migrado no trae ninguna de las nuestras. */
.block { margin-bottom: var(--space-250); }
.block:last-child { margin-bottom: 0; }

.block-title {
    margin: 0 0 var(--space-100); font-size: var(--fs-section); font-weight: 700;
    color: var(--text);
}

.block-prose { font-size: var(--fs-body); color: var(--text); line-height: 1.65; }
.block-prose > *:first-child { margin-top: 0; }
.block-prose > *:last-child { margin-bottom: 0; }
.block-prose p { margin: 0 0 var(--space-150); }
.block-prose ul, .block-prose ol { margin: 0 0 var(--space-150); padding-left: var(--space-250); }
.block-prose li { margin-bottom: var(--space-050); }
.block-prose h2, .block-prose h3, .block-prose h4 {
    margin: var(--space-200) 0 var(--space-100); font-size: var(--fs-section);
    font-weight: 700;
}
.block-prose a { color: var(--primary-dark); text-decoration: underline; }
.block-prose blockquote {
    margin: 0 0 var(--space-150); padding-left: var(--space-150);
    border-left: 3px solid var(--border-strong); color: var(--text-light);
}
.block-prose code {
    padding: 1px var(--space-050); border-radius: var(--radius-field);
    background: var(--bg); font-size: var(--fs-small);
}
/* Las tablas del recetario tienen seis columnas y no caben en un teléfono. El
   desbordamiento se scrollea DENTRO del bloque: sin esto, la página entera se
   corre de lado y la barra lateral queda a media pantalla. */
.block-prose table {
    display: block; overflow-x: auto; width: 100%;
    border-collapse: collapse; margin: 0 0 var(--space-150);
    font-size: var(--fs-small);
}
.block-prose th, .block-prose td {
    padding: var(--space-075) var(--space-100); border: 1px solid var(--border); text-align: left;
    vertical-align: top;
}
.block-prose hr { margin: var(--space-200) 0; border: 0; border-top: 1px solid var(--border); }

/* El tinte llega en una variable que el validador ya comprobó contra un
   hexadecimal. Por eso `sanitize` puede sacar `style` de su lista blanca sin
   perder el color del aviso: acá entra por un canal con forma conocida. */
.block-callout {
    display: flex; gap: var(--space-150); padding: var(--space-200);
    border-radius: var(--radius-card);
    background: var(--callout-tint, var(--primary-soft));
    border: 1px solid rgba(0, 0, 0, .05);
}
.block-callout-emoji { font-size: 1.4em; line-height: 1.4; }
.block-callout-body { min-width: 0; flex: 1; }

.block-divider { margin: var(--space-250) 0; border: 0; border-top: 1px solid var(--border); }

.block-image-one {
    display: block; max-width: 100%; height: auto;
    border-radius: var(--radius-card); margin-bottom: var(--space-150);
}
.block-image-one:last-child { margin-bottom: 0; }

.block-video-player {
    width: 100%; border-radius: var(--radius-card); background: var(--canvas);
    margin-bottom: var(--space-150);
}

/* ── La checklist de una inducción, compacta ──────────────────────────────
   `.check-row` la comparten DOS componentes: esta lista y la grilla de campos
   de empresa, que le pone `margin-bottom: 17px`. Ese margen era el aire que
   estiraba una tarjeta de veintidós ítems a tres pantallas de scroll. Se acota
   a `.block-checklist` en vez de renombrar la clase: la grilla también la usa
   y renombrarla movería una decisión que no es de acá.
   ------------------------------------------------------------------------ */
.block-checklist .check-list { margin-top: var(--space-075); margin-bottom: var(--space-150); gap: 0; }
.check-head {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-100);
}
.check-head .block-title { margin: 0; font-size: var(--fs-body); }
.check-count {
    flex: none; font-size: var(--fs-small); font-weight: 600;
    color: var(--text-light); font-variant-numeric: tabular-nums;
}
.block-checklist .check-row {
    display: flex; align-items: center; gap: var(--space-100); flex-wrap: nowrap;
    margin: 0; padding: var(--space-050) 0; font-size: var(--fs-body);
}
/* Tachado Y apagado. Es la misma información que la casilla puesta, a
   propósito: con veintidós ítems «qué falta» se lee por el tachado de un
   vistazo, y contando casillas marcadas no. */
.block-checklist .check-row.is-done span {
    text-decoration: line-through; color: var(--text-faint);
}

/* La verificación (`ui/blocks/verificacion.html`): un renglón por ítem, el
   rótulo a la izquierda y el control a la derecha; en el teléfono, uno
   debajo del otro. */
.block-verificacion .verif-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.verif-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr); gap: var(--space-150);
             align-items: start; padding: var(--space-100) 0; }
.verif-row + .verif-row { border-top: 1px solid var(--border); }
.verif-row.is-done .verif-label { color: var(--text-light); }
.verif-req { color: var(--danger-text, var(--warn-text)); }
.verif-foto, .verif-rango { font-size: var(--fs-small); color: var(--text-light); }
.verif-control { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.verif-nota { font-size: var(--fs-small); }
.verif-edit-row { flex-wrap: wrap; }
.verif-edit-row input[name="item_unit"], .verif-edit-row input[name="item_min"],
.verif-edit-row input[name="item_max"] { width: 6rem; }
@media (max-width: 640px) { .verif-row { grid-template-columns: 1fr; } }

/* La lista en la ficha de mantenimiento («01 · La lista»): un ítem por caja
   (`.boxed-row`), todos dentro de UN formulario que guarda la barra de la
   casa. La caja va en columna —nombre, controles, nota, foto, quién y cuándo—
   y su borde izquierdo, más grueso, dice de un vistazo si pasa o falla. */
.run-list-progress { margin: 0 0 var(--space-150); }
/* Cajas altas (controles, nota, foto): un poco más de aire que la lista corta. */
/* Es un `<ul>`: sin esto heredaba los 40px de sangría del navegador. */
.run-list .boxed-list { gap: var(--space-100); list-style: none; margin: 0; padding: 0; }
/* CADA ÍTEM SE ABRE (L1, 2026-10-19): el plegable de la casa (`.fold`).
   Cerrado, una línea: nombre | estado | flecha. El borde izquierdo, más
   grueso, dice si pasa (verde), falla (rojo) o no se hizo (ámbar). Abierto,
   la fila de antes (D1): respuesta | «No se hizo» | nota, y lo que se abre
   (motivo, descripción, error) en su propio renglón. */
.run-item > .run-item-fold { margin-top: 0; }
.run-item > .run-item-fold > summary { flex-wrap: wrap; gap: var(--space-100); border-left-width: 3px; }
.run-item > .run-item-fold > .fold-body { border-left-width: 3px; }
.run-item.is-falla .run-item-fold > summary, .run-item.is-falla .run-item-fold > .fold-body { border-left-color: var(--danger-text); }
.run-item.is-skipped .run-item-fold > summary, .run-item.is-skipped .run-item-fold > .fold-body { border-left-color: var(--warn-text); }
.run-item.is-ok .run-item-fold > summary, .run-item.is-ok .run-item-fold > .fold-body { border-left-color: var(--ok-text); }
/* El nombre baja de renglón antes que empujar el estado fuera de la línea. */
.run-item-fold .run-item-name { flex: 1 1 9rem; min-width: 0; overflow-wrap: break-word; }
.run-item-help { margin-left: var(--space-075); vertical-align: -2px; }
.run-item-fold > summary .pill { flex: none; }
/* La hoja de la ayuda vive en el `<li>` (fuera del plegable): no ocupa
   sitio en la fila. Su casilla está en el tabulador (`keys`), y el foco se
   pinta en el `(i)` de la línea. */
.run-item > .help-headless { position: absolute; }
.run-item:has(> .help .help-toggle:focus-visible) .run-item-help .help-icon {
    outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 50%; color: var(--primary);
}
/* El interruptor «Hecho» de una actividad, a la IZQUIERDA de la línea y
   fuera del `<summary>`: se marca sin abrir, como el visto de una lista de
   pendientes, y no queda escondido dentro del botón del plegable. Alto de
   la línea cerrada, para quedar centrado con ella. */
.run-item.has-switch { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: var(--space-100); align-items: start; }
.run-item-switch { display: flex; align-items: center; min-height: 44px; cursor: pointer; }
.run-item-body {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-100) var(--space-150);
}
.run-item-desc, .run-item-error, .run-item-reason { flex: 1 1 100%; }
.run-item-reason input { width: 100%; }
/* «Qué se debe hacer» en la ayuda conserva sus renglones. */
.run-item .help-panel-text { white-space: pre-line; }
.run-item-desc { margin: 0; color: var(--text-light); font-size: var(--fs-small); white-space: pre-line; }
.run-item-error { margin: 0; }
.run-item-ctl { display: flex; align-items: center; gap: var(--space-100); min-width: 0; }
.run-item-ctl:has(.run-item-text) { flex: 1 1 14rem; }
.run-item-text { width: 100%; }
.run-item-num { width: 7rem; }
.run-item-unit { font-size: var(--fs-small); color: var(--text-light); }
/* «Sin responder» es la salida, no una respuesta: discreto, y marcado no se
   rellena del color de la marca. */
.choice-segmented--compact .choice-pill.run-item-blank { color: var(--text-faint); font-weight: 400; }
.choice-segmented--compact .choice-pill.run-item-blank:has(input:checked) { color: var(--text-light); }
/* El segmentado desliza una ficha (ver `.choice-segmented--compact::before`):
   sobre «Sin responder» la ficha se apaga al gris en vez de pintar la pastilla. */
.choice-segmented--compact:has(.run-item-blank input:checked)::before { background: var(--bg); box-shadow: none; }
.run-item-skip { display: inline-flex; align-items: center; gap: var(--space-075); cursor: pointer;
    font-size: var(--fs-small); color: var(--text-light); white-space: nowrap; }
.run-item-note { display: flex; align-items: center; gap: var(--space-100); min-width: 0; }
.run-item-note:has(.is-open) { flex: 1 1 14rem; }
.run-item-note-field input { width: 100%; }
.run-item-note-field.is-open { flex: 1; }
/* «Nota» es un enlace: la casilla que lo vuelve interruptor no se ve, y el
   foco se dibuja en el rótulo. Sin `:has()` el rótulo no aparece y el
   campo queda siempre a la vista. */
.run-item-note-toggle { display: none; align-items: center; gap: var(--space-050); cursor: pointer;
    font-size: var(--fs-small); color: var(--primary); white-space: nowrap; }
.run-item-note-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
/* Más peso que la casilla de la casa (`input[type="checkbox"]:not()…`), que le daba tamaño. */
.run-item-note-toggle input[type="checkbox"].run-item-note-open { width: 0; height: 0; min-width: 0; padding: 0; border: 0; }
.run-item-note-toggle svg { width: 14px; height: 14px; }
.run-item-note-toggle:focus-within { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px; }
.run-item-foto { flex: none; }
/* Sin JS, la casilla muestra el motivo: «No se hizo» lo exige, y mientras no
   esté marcada la casilla el campo sobra. Solo donde `:has()` existe: un
   navegador sin él nunca lo volvería a mostrar, y el motivo es obligatorio. */
@supports selector(:has(*)) {
  .run-item-reason { display: none; }
  .run-item:has(.run-item-skip input:checked) .run-item-reason { display: block; }
  .run-item-note-toggle { display: inline-flex; }
  .run-item-note-field:not(.is-open) { display: none; }
  .run-item-note:has(.run-item-note-open:checked) { flex: 1 1 14rem; }
  .run-item-note:has(.run-item-note-open:checked) .run-item-note-field { display: block; flex: 1; }
  .run-item-note:has(.run-item-note-open:checked) .run-item-note-toggle { color: var(--text-light); }
}
/* La nota de un ítem que no pasa: es la que dice qué pasó y qué falta. */
.run-item-note-falla { color: var(--danger-text); border-color: var(--danger-text); }
.run-item-answer { padding: var(--space-050) 0; }
.run-item-meta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100); margin-left: auto; }
.run-item-photo { max-width: 96px; max-height: 96px; border-radius: var(--radius-sm); display: block; }
/* Lo que impide dar la lista por llena, al pie de la tarjeta. */
.run-list-missing { margin-top: var(--space-150); font-size: var(--fs-small); }
/* La lista en pasos: la barra de avance «n de total» separada del ítem. */
.run-paso .progreso { margin-bottom: var(--space-150); }
/* El resumen de la lista en pasos: el botón que sigue donde se quedó. */
.run-list-next { margin-top: var(--space-150); }
.run-photo-grid { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: var(--space-100); }
/* Una fila del historial de la hoja de vida: la `.boxed-row` de siempre,
   pero en columna, porque debajo de la línea van lo encontrado y las fotos.
   Sus `.card-lines` van sin el sangrado de tarjeta: ya están dentro de una caja. */
.historial-fila { flex-direction: column; align-items: stretch; gap: var(--space-100); }
.historial-fila .card-lines li { padding: var(--space-050) 0; }
/* LA LISTA DE LA FICHA (Santiago, 2026-11-06, con su referencia): cada
   ítem es un plegable de la casa (`.fold`) con aire de tarjeta: la baldosa
   de su estado, el nombre con su rango o lo que se hace, y la respuesta en
   pastilla. El abierto se marca con el filo de la marca. */
.chk-avance { display: flex; align-items: center; gap: var(--space-150); margin-left: auto; min-width: min(100%, 22rem); }
.chk-avance .progreso { flex: 1; min-width: 6rem; }
.chk-avance-texto { font-size: var(--fs-small); color: var(--text-light); white-space: nowrap; }
.chk { display: grid; gap: var(--space-100); }
.chk > .fold { margin-top: 0; }
.chk .chk-item > summary { gap: var(--space-150); padding: var(--space-150) var(--space-200); background: var(--card-bg); }
.chk .chk-item[open] > summary, .chk .chk-item[open] > .fold-body { border-color: var(--primary); }
.chk .chk-item[open] > summary { background: var(--primary-soft); }
.chk-texto { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-025); }
.chk-nombre { font-weight: 600; }
.chk-desc-corta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chk-pastillas { flex: none; display: inline-flex; align-items: center; gap: var(--space-100); }
.chk-item > .chk-cuerpo { display: grid; gap: var(--space-200); padding: var(--space-200) var(--space-200) var(--space-200) calc(var(--space-200) + var(--space-500) + var(--space-100)); background: var(--card-bg); }
.chk-cuerpo > .field, .chk-cuerpo > .option-cards { margin: 0; }
.chk-cuerpo .option-cards-grid { grid-template-columns: repeat(auto-fill, minmax(12rem, 18rem)); }
.chk-desc { margin: 0; color: var(--text-light); white-space: pre-line; }
.chk-hecho { display: inline-flex; align-items: center; gap: var(--space-075); cursor: pointer; }
.chk-num { display: inline-flex; align-items: center; gap: var(--space-100); }
.chk-num input { width: 8rem; }
.chk-medida { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-150); }
.chk-unidad { font-weight: 600; color: var(--text-light); }
.chk-no-se-hizo { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-150); }
.chk-motivo { flex: 1 1 16rem; }
.chk-motivo input { width: 100%; }
@supports selector(:has(*)) {
  .chk-motivo { display: none; }
  .chk-no-se-hizo:has(input[type="checkbox"]:checked) .chk-motivo { display: block; }
}
.chk-acciones { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-150); padding-top: var(--space-150); border-top: 1px solid var(--border); }
.chk-acciones-fin { margin-left: auto; display: inline-flex; gap: var(--space-100); }
.chk-leida { margin: 0; display: flex; gap: var(--space-100); align-items: baseline; }
.chk-quien { margin: 0; color: var(--text-faint); }
/* En pasos, lo que espera su turno: se ve, no se abre. */
.chk-bloqueado > summary { pointer-events: none; opacity: .6; }
.chk-bloqueado .fold-chevron { visibility: hidden; }
.chk--espera .chk-item > summary { opacity: .75; }
@media (max-width: 640px) {
    .chk-item > summary { flex-wrap: wrap; padding: var(--space-150); }
    .chk-pastillas { flex-basis: 100%; padding-left: calc(var(--space-500) + var(--space-100)); }
    .chk-item > .chk-cuerpo { padding-left: var(--space-150); }
    .chk-avance { min-width: 100%; }
}
/* Los soportes: cada archivo con su clase y su «⋯». */
.mant-soportes { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-100); }
.mant-soporte { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-075) var(--space-100); }
.mant-soporte-clase { grid-column: 1 / -1; }
.mant-subir { display: grid; gap: var(--space-150); margin-top: var(--space-200); }
.mant-siguiente { display: grid; gap: var(--space-100); }
.mant-siguiente .btn { width: 100%; justify-content: center; }
.mant-equipo-foto img { width: 100%; max-height: 160px; object-fit: cover; border-radius: var(--radius-sm); display: block; margin-bottom: var(--space-100); }
/* La lista de un tipo, como las variantes: lo que pide cada ítem en
   pastillas juntas, y el «Editar» sin partirse. */
.tipo-pide { display: inline-flex; flex-wrap: wrap; gap: var(--space-050); }
.tipo-items .cell-actions .inline-row { flex-wrap: nowrap; }
.tipo-items .cell-text .cell-sub { white-space: normal; max-width: 22rem; }
.tipo-items td { padding-top: var(--space-150); padding-bottom: var(--space-150); }
/* Lo que se encontró, bajo el nombre del mantenimiento en la hoja de vida. */
.mant-hallazgo { color: var(--danger-text); }
/* El riel del tipo va dentro de su formulario: el formulario no cuenta
   como caja, así sus tarjetas guardan el mismo espacio entre ellas que en
   cualquier riel. */
.riel-formulario { display: contents; }
/* En el teléfono la fila se apila: el nombre arriba y lo demás debajo, el
   segmentado a lo ancho. */
@media (max-width: 640px) {
    .run-item-ctl, .run-item-note:has(.is-open) { flex: 1 1 100%; }
    /* La línea cerrada en rejilla: nombre | estado | flecha, sin que la
       flecha quede sola abajo. */
    .run-item > .run-item-fold > summary { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; }
    /* Con el interruptor al lado queda menos ancho: el estado baja debajo
       del nombre, a la derecha, y el nombre se lee entero. */
    .run-item.has-switch > .run-item-fold > summary {
        grid-template-columns: minmax(0, 1fr) auto;
        grid-template-areas: "name chev" "pill pill";
    }
    .run-item.has-switch .run-item-fold .run-item-name { grid-area: name; }
    .run-item.has-switch .run-item-fold > summary .pill { grid-area: pill; justify-self: end; }
    .run-item.has-switch .run-item-fold .fold-chevron { grid-area: chev; }
    .run-item-text { width: 100%; }
}
/* La lista de un tipo: el nombre con su «Qué se debe hacer» recortado
   debajo, y el agregar con la descripción a lo ancho bajo la fila. */
.type-item-name { flex: 1 1 12rem; min-width: 0; display: flex; flex-direction: column; }
.type-item-desc { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.type-item-add { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-150); }
.type-item-add > .inline-row, .type-item-add > .field { width: 100%; }

.block-quiz-rule, .block-upload-rule {
    margin: 0 0 var(--space-150); font-size: var(--fs-small); color: var(--text-light);
}
.block-quiz-card {
    margin: 0 0 var(--space-200); padding: var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-card);
}
.block-quiz-question {
    padding: 0; margin-bottom: var(--space-150); font-size: var(--fs-body);
    font-weight: 600; color: var(--text);
}
.block-quiz-image {
    display: block; max-width: 100%; height: auto;
    border-radius: var(--radius-field); margin-bottom: var(--space-150);
}
.block-quiz-score {
    margin: 0; padding: var(--space-150) var(--space-200);
    border-radius: var(--radius-card); font-size: var(--fs-body);
    font-weight: 600;
}
.block-quiz-score.is-passed { background: var(--ok-bg); color: var(--ok-text); }
.block-quiz-score.is-failed { background: var(--danger-bg); color: var(--danger-text); }

.block-upload-list {
    margin: 0 0 var(--space-150); padding-left: var(--space-250);
    font-size: var(--fs-small);
}
.block-survey .field { margin-bottom: var(--space-150); }

/* ── El runner de la inducción ───────────────────────────────────────────────
   Los pasos son tarjetas apiladas y no un acordeón: quien hace la inducción la
   lee de arriba abajo una vez, y esconder lo ya hecho detrás de un clic
   obligaría a abrir cinco cosas para encontrar dónde iba. */
.run-meter { margin-bottom: var(--space-250); }
.run-steps { display: flex; flex-direction: column; gap: var(--space-200); }
.run-step { padding: var(--space-250); }
.run-step-body { margin-top: var(--space-200); }
/* El paso cerrado se apaga, no se esconde: tiene que verse que existe y que
   falta, o la inducción parece más corta de lo que es. */
.run-step.is-locked { opacity: .72; }
.run-step-locked {
    margin: var(--space-150) 0 0; font-size: var(--fs-small);
    color: var(--text-light);
}
.run-step.is-done { border-color: var(--ok-text); }
/* Varias inducciones a la vez es el caso normal: en LTDC hay 35 para 32
   personas, porque el pre-ingreso y la de barista conviven. */
.run-block { margin-bottom: var(--space-400); }
.run-block:last-child { margin-bottom: 0; }

/* ── Las inducciones DENTRO de la ficha ──────────────────────────────────────
   Una tarjeta por inducción, y la audiencia decide la forma: la de la persona
   sale compacta y la de administración sale abierta y marcable. */
.induccion-card .card-header h2 {
    display: flex; align-items: center; gap: var(--space-100); min-width: 0;
}
.card-title-icon {
    width: 18px; height: 18px; flex: none; color: var(--text-light);
}

/* El paso NO vuelve a ser una tarjeta adentro de la tarjeta de la inducción.
   Una caja dentro de otra a un píxel de distancia es el defecto que ya se
   corrigió en la barra de filtros y en `.table-wrap`: acá el paso es una
   franja, separada por una línea y no por un segundo contorno. */
.induccion-card .run-steps { gap: 0; }
.induccion-card .run-step {
    border: 0; border-radius: 0; box-shadow: none; background: none;
    padding: var(--space-200) 0; border-top: 1px solid var(--border);
}
/* `is-done` pinta el borde del paso, y acá ese borde es la línea que lo separa
   del anterior: dejarla verde marcaría la separación y no el paso. */
.induccion-card .run-step.is-done { border-top-color: var(--border); }

/* La forma compacta: una línea por paso, con lo que la persona entregó. */
.step-list { list-style: none; margin: 0; padding: 0; }
.step-line {
    display: flex; align-items: flex-start; gap: var(--space-150);
    padding: var(--space-150) 0; border-top: 1px solid var(--border);
}
.step-line:first-child { border-top: 0; padding-top: 0; }
.step-line-mark { color: var(--text-light); line-height: 1.4; }
.step-line.is-done .step-line-mark { color: var(--ok-text); }
.step-line-text {
    display: flex; flex-direction: column; gap: var(--space-100); min-width: 0;
}
.step-line-title { font-weight: 600; }
.step-line-when { font-size: var(--fs-small); color: var(--text-light); }
/* La nota, al lado de la fecha. Verde solo cuando APROBÓ: un 60% en verde se
   lee como que pasó, y la nota mínima la pone quien arma el examen. */
.step-line-mark-score { color: var(--text); }
.step-line-mark-score.is-passed { color: var(--ok-text); }
/* Los archivos envuelven: un paso migrado de `tiendapp` trae hasta ocho, y en
   una sola línea el octavo se sale de la tarjeta. */
.step-line-files { display: flex; flex-wrap: wrap; gap: var(--space-100); }

/* La acción que no pertenece a ninguna de las tarjetas de la lista. */
.section-action { margin: 0; }

/* ── Los roles del integrante, en el riel ────────────────────────────────────
   Nombre a la izquierda y «Quitar» a la derecha, en una fila por rol: en dos
   líneas el botón se lee como si fuera de la tarjeta y no del rol. */
.role-list { list-style: none; margin: 0 0 var(--space-150); padding: 0; }
.role-line {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); padding: var(--space-100) 0;
    border-top: 1px solid var(--border);
}
.role-line:first-child { border-top: 0; }
.role-line-name { font-weight: 600; min-width: 0; }
/* El desplegable cede y el botón no: con 312px de riel, un nombre de rol largo
   empujaba «Asignar» fuera de la tarjeta. */
.role-assign { display: flex; gap: var(--space-100); align-items: center; margin-top: var(--space-150); }
.role-assign select { min-width: 0; flex: 1; }
.role-assign .btn { flex: none; }

/* ── El editor del integrante ────────────────────────────────────────────────
   Desde el 2026-09-23 es la única pantalla que escribe sobre una persona: la
   ficha se lee. La columna ancha es UN formulario y, debajo, las tarjetas
   que actúan al instante con su propio `<form>`; el riel solo resume: el
   medidor y la tarjeta Resumen (2026-10-22). */

/* El medidor arriba del riel: la barra de la tarjeta de la lista, contando
   la ficha entera. Lo que la lista no puede decir —QUÉ falta, cada dato como
   una pastilla que lleva a su casilla o su tarjeta— es la primera tarjeta de
   la columna, «Lo que falta» (2026-10-22). */
.ficha-medidor { display: flex; flex-direction: column; gap: var(--space-150); }
.ficha-faltan {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-075); margin: 0;
}
.ficha-faltan .pill { text-decoration: none; }
.ficha-faltan .pill:hover { filter: brightness(.96); }

/* Quién es: la foto como una TIRA arriba de los campos —la cara, y al lado
   qué hacer con ella— y la rejilla de campos debajo a todo el ancho. Como
   columna al lado, los campos se quedaban con menos de 640px y la rejilla
   caía a una sola columna con medio tarjetón vacío. */
.ficha-persona { display: flex; flex-direction: column; gap: var(--space-200); }
.ficha-foto {
    display: flex; align-items: center; gap: var(--space-200);
    padding-bottom: var(--space-200); border-bottom: 1px solid var(--border);
}
.ficha-foto .upload { flex: none; align-items: center; }
.ficha-foto-texto { display: flex; flex-direction: column; gap: var(--space-075); min-width: 0; }
.ficha-foto-texto .field-hint { margin: 0; }
.ficha-foto-titulo { font-weight: 600; color: var(--text); cursor: pointer; }
.ficha-foto-quitar { margin: 0; align-self: flex-start; }
/* La cara cuando el servidor no conserva archivos y no hay zona de subida. */
.ficha-avatar {
    display: grid; place-items: center; width: 84px; height: 84px; flex: none;
    border-radius: 50%; object-fit: cover;
    background: var(--primary-soft); color: var(--primary);
    font-weight: 700; font-size: var(--fs-stat);
}

/* La zona de subida en modo avatar (`ui/_upload.html` con `avatar=1`): la
   foto ES la zona. Un círculo de 96px, la cámara en la esquina, y al pasar
   el mouse el borde de marca de cualquier dropzone. */
.dropzone-avatar {
    width: 96px; height: 96px; padding: 0; border-radius: 50%;
    border: 2px solid var(--border); background: var(--primary-soft);
    overflow: visible; display: grid; place-items: center;
}
.dropzone-avatar:hover, .dropzone-avatar:focus-within {
    border-color: var(--primary); background: var(--primary-soft);
}
.dropzone-avatar-img {
    width: 100%; height: 100%; border-radius: 50%; object-fit: cover;
    grid-area: 1 / 1;
}
.dropzone-avatar-initial {
    grid-area: 1 / 1; font-size: var(--fs-stat); font-weight: 700;
    color: var(--primary); text-transform: uppercase;
}
.dropzone-avatar-badge {
    position: absolute; right: -2px; bottom: -2px;
    display: grid; place-items: center; width: 30px; height: 30px;
    border-radius: 50%; background: var(--primary); color: #fff;
    border: 2px solid #fff; box-shadow: var(--shadow);
}
.dropzone-avatar-badge svg { width: 15px; height: 15px; }
.dropzone-avatar input[type="file"] { border-radius: 50%; }

/* «Ya tiene PIN» y el botón de quitarlo, en la misma línea. */
.ficha-pin-hint { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-100); }

/* Un formulario dentro de una tarjeta que ya muestra algo —registrar
   salario, subir un documento—, separado de lo de arriba por el pie de
   siempre. Se llamaba `.rail-form` cuando esas tarjetas vivían en el riel;
   desde el 2026-10-22 el riel del editor solo resume y ellas van en la
   columna, con sus campos en `.form-grid`. */
.card-form {
    display: flex; flex-direction: column; gap: var(--space-150);
    margin-top: var(--space-200); padding-top: var(--space-200);
    border-top: 1px solid var(--border);
}
.card-form .field, .card-form .form-lead { margin: 0; }
.card-form > .btn { align-self: flex-start; }

/* En el teléfono el riel del editor sube, como el de toda ficha de la casa:
   hasta el 2026-10-22 bajaba porque traía cinco tarjetas con formularios, y
   ahora es el medidor —lo primero que se mira— y un resumen corto. */

/* El enlace de invitación se copia a mano —Agrupa no manda correo—, así que el
   control existe para SELECCIONARLO, no para escribir en él. */
.input-copy {
    width: 100%; font-size: var(--fs-small);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
/* Un formulario POR BLOQUE: un paso de tiendapp tiene hasta 21, y con uno solo
   marcar una casilla mandaría también el examen a medio contestar. */
.block-form { margin-bottom: var(--space-250); }
.block-form:last-child { margin-bottom: 0; }
.block-form .block { margin-bottom: var(--space-150); }
.block-form-foot { display: flex; justify-content: flex-end; }

/* ── El editor de pasos de un flujo ──────────────────────────────────────────
   Cada paso es una fila con su información a la izquierda y sus controles a la
   derecha, y los controles son BOTONES y no arrastre: reordenar tiene que
   funcionar con teclado. */
.step-row { display: flex; align-items: center; gap: var(--space-150); }
.step-row-main { flex: 1; min-width: 0; }
.step-row-tools { display: flex; align-items: center; gap: var(--space-050); }
/* Un paso inactivo se ve apagado y no se esconde: tiene que notarse que existe
   y que no cuenta, o nadie entiende por qué el total no cuadra. */
.step-row.is-off { opacity: .6; }
/* Los pasos del asistente del cambio de unidad: un `<fieldset>` sin marco que,
   mientras algo espera en el paso 00, no se deja tocar. Sin `opacity`: lavaba
   las tarjetas enteras y no se leía qué iba a pasar (2026-10-29); los
   controles deshabilitados ya se ven deshabilitados. */
.unit-change-steps { border: 0; margin: 0; padding: 0; min-width: 0; }
.step-add { margin-top: var(--space-200); }
.step-add-row { display: flex; gap: var(--space-100); }
.step-add-row input { flex: 1; }
/* Los formularios de las flechas son de un botón: sin esto cada uno ocupa su
   propia línea y los controles se apilan en vertical. */
.step-row-tools form { display: inline; }

/* ── El selector de bloques ──────────────────────────────────────────────────
   Sin una línea de JavaScript, con el mismo truco que `ui/_help.html`: una
   casilla oculta como interruptor. Un selector que no abre deja el editor
   inutilizable —no degradado, inutilizable— y el protocolo de la plataforma
   pide que todo sirva sin script. */
.picker { display: inline-block; }
.picker-toggle { position: absolute; opacity: 0; width: 0; height: 0; }
.picker-open { cursor: pointer; }
.picker-sheet { display: none; }
.picker-toggle:checked ~ .picker-sheet { display: block; }
/* La casilla está OCULTA pero presente: la recibe el tabulador y la barra
   espaciadora la acciona, así que el selector se abre con teclado. El foco se
   dibuja sobre el rótulo, que es lo que se ve. */
.picker-toggle:focus-visible + .picker-open {
    outline: 2px solid var(--primary); outline-offset: 2px;
}
.picker-backdrop {
    position: fixed; inset: 0; z-index: 40; background: rgba(13, 11, 22, .45);
}
.picker-panel {
    position: fixed; z-index: 41; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: min(520px, calc(100vw - var(--space-300)));
    max-height: calc(100vh - var(--space-300)); overflow-y: auto;
    background: var(--card-bg); border-radius: var(--radius-lg);
    padding: var(--space-250); box-shadow: 0 24px 60px rgba(13, 11, 22, .28);
}
.picker-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: var(--space-200);
}
.picker-title { font-size: var(--fs-section); font-weight: 700; }
.picker-close {
    cursor: pointer; font-size: var(--fs-page); line-height: 1;
    color: var(--text-light); padding: 0 var(--space-075);
}
/* Tres columnas, como la referencia. En un teléfono son dos: con tres, cada
   tarjeta queda de 90px y el rótulo se parte en dos líneas. */
.picker-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-150); }
@media (max-width: 520px) { .picker-grid { grid-template-columns: repeat(2, 1fr); } }
.picker-card {
    display: flex; flex-direction: column; align-items: center; gap: var(--space-100);
    padding: var(--space-200) var(--space-100); border: 1px solid var(--border);
    border-radius: var(--radius-card); background: var(--card-bg);
    cursor: pointer; color: var(--text); font-size: var(--fs-body);
    font-weight: 600; font-family: inherit;
}
.picker-card:hover {
    border-color: var(--primary); background: var(--primary-soft);
    color: var(--primary-dark);
}
.picker-card-icon { width: 24px; height: 24px; color: var(--primary); }
.picker-card-name { text-align: center; }

/* ── El pie de una tarjeta de formulario ─────────────────────────────────────
   La casilla a la izquierda y el guardar a la derecha, separados por una línea
   del resto: es lo último que se decide antes de guardar. Arriba, entre los
   campos, «Formulario activo» se leía como un dato más de la ficha. */
.card-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap;
    margin-top: var(--space-200); padding-top: var(--space-200);
    border-top: 1px solid var(--border);
}
.card-foot .field-check { margin: 0; }
.field-required { color: var(--danger-text); }

/* ── El desbloqueo de un paso ────────────────────────────────────────────────
   Una FILA y no una tarjeta con dos controles sueltos: es una sola decisión
   —cuándo abre— y partida en dos campos se leía como dos. */
.unlock-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap; padding: var(--space-200) var(--space-250);
}
.unlock-label {
    display: inline-flex; align-items: center; gap: var(--space-100);
    font-weight: 600; color: var(--text);
}
.unlock-label label { margin: 0; }
.unlock-icon { width: 18px; height: 18px; color: var(--primary); flex-shrink: 0; }
.unlock-controls { display: inline-flex; align-items: center; gap: var(--space-100); }
/* El rótulo mas largo es «A las N horas de arrancar la inducción», y a 200px
   se cortaba. `max-content` deja que el `<select>` mida lo que su opción mas
   larga necesita: es un conjunto cerrado de tres, no una lista que crezca. */
.unlock-controls select { width: max-content; min-width: 200px; }
.unlock-unit { font-size: var(--fs-small); color: var(--text-light); }

/* Las horas SOLO se teclean en el modo que las usa. Las otras dos —«al
   completar el paso anterior» y «al día siguiente»— no llevan número, y el
   campo encendido al lado invitaba a escribir algo que el servidor descarta.

   El apagado se ve en las DOS piezas, el campo y su unidad: apagar solo el
   campo dejaba un «horas» a plena tinta al lado de una casilla gris, que se
   lee como que la unidad sigue contando. */
.unlock-controls input:disabled {
    background: var(--bg); color: var(--text-faint);
    border-color: var(--border); cursor: not-allowed;
}
/* La unidad se apaga con su campo, por hermano adyacente. Sin esto quedaba un
   «horas» a plena tinta al lado de una casilla gris, que se lee como que la
   unidad sigue contando. Por CSS y no con un `:class` de Alpine: el estado ya
   lo dice el `disabled`, y un segundo binding es una copia que se
   desincroniza. */
.unlock-controls input:disabled + .unlock-unit { color: var(--text-faint); }

/* ── Las pestañas de los pasos ───────────────────────────────────────────────
   Cada pestaña es un ENLACE y no un interruptor de JavaScript, por la misma
   razón que el guardado es por bloque: el contenido de cada paso lo trae el
   servidor. Pintar los cinco a la vez para que el cambio fuera instantáneo
   serían ~100 editores de texto en el DOM — «Inducción Barista» tiene 30+
   bloques ella sola.

   La tira desborda a lo ancho y no se parte en dos filas: con cinco pasos de
   título largo, envolver mueve el editor hacia abajo cada vez que se renombra
   uno. */
.step-tabs {
    display: flex; align-items: center; gap: var(--space-075);
    margin: var(--space-250) 0 var(--space-200); padding-bottom: var(--space-100);
    overflow-x: auto; scrollbar-width: thin;
}
.step-tab {
    display: inline-flex; align-items: center; gap: var(--space-100);
    flex-shrink: 0; max-width: 260px; padding: var(--space-100) var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--card-bg); color: var(--text-light);
    font-size: var(--fs-body); font-weight: 600; text-decoration: none;
}
.step-tab:hover { background: var(--bg); color: var(--text); }
/* Las pestañas DENTRO del panel del editor de bloques: envuelven en vez
   de correrse a un lado.

   `overflow-x: auto` es lo correcto en la pantalla de integrantes, donde
   la tira ocupa el ancho entero; acá el panel mide ~315px y con tres
   pasos el «+ Paso» queda detrás de un scroll horizontal que nadie
   descubre — la acción principal de la tira, escondida. */
.vento-customizer__panel .step-tabs {
    flex-wrap: wrap; overflow-x: visible;
}
.vento-customizer__panel .step-tab { max-width: 100%; }
.step-tab.is-active {
    background: var(--primary); border-color: var(--primary); color: #fff;
}
/* El número va en su propia pastilla: con cinco pasos que empiezan todos por
   «Día», el número es lo único que los distingue de un vistazo. */
.step-tab-num {
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0; width: 22px; height: 22px; border-radius: 8px;
    background: var(--bg); color: var(--text-light);
    font-size: var(--fs-small); font-weight: 700;
}
.step-tab.is-active .step-tab-num {
    background: rgba(255, 255, 255, .25); color: #fff;
}
/* El título se recorta y no se parte: una pestaña de dos líneas empuja toda la
   tira, y el nombre completo ya está en la cabecera del paso. */
.step-tab-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* La × vive PEGADA a la pestaña activa y encima de ella, no en una columna
   propia: en una columna, la tira crecería y encogería al cambiar de pestaña y
   los títulos bailarían de sitio. */
.step-tab-slot { position: relative; display: inline-flex; flex-shrink: 0; }
.step-tab-slot.is-active .step-tab { padding-right: var(--space-300); }
.step-tab-close {
    position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
    display: inline-flex; align-items: center; justify-content: center;
    width: 20px; height: 20px; border-radius: 6px;
    color: rgba(255, 255, 255, .7); font-size: var(--fs-body); line-height: 1;
    text-decoration: none;
}
.step-tab-close:hover { background: rgba(255, 255, 255, .2); color: #fff; }
.step-tab-add-form { display: inline-flex; flex-shrink: 0; }
.step-tab-add {
    padding: var(--space-100) var(--space-200); border: 1px dashed var(--border);
    border-radius: var(--radius-field); background: transparent; cursor: pointer;
    color: var(--primary); font-size: var(--fs-body); font-weight: 600;
    font-family: inherit;
}
.step-tab-add:hover { background: var(--primary-soft); border-color: var(--primary); }

/* ── El bloque dentro del editor ─────────────────────────────────────────── */
.block-editor-head { margin: var(--space-300) 0 var(--space-150); }
/* El bloque va sobre un fondo TENUE y no sobre la tarjeta blanca: un paso de
   `tiendapp` tiene hasta 21, y en blanco sobre blanco no se ve dónde termina
   uno y empieza el siguiente. El borde aparece al pasar por encima, que es lo
   que dice cuál se está por tocar. */
/* BLANCA, y con aire arriba además de abajo (pedido de Renzo, 2026-08-12).
   Era gris —`--bg`, el del lienzo— y sobre el lienzo gris de la pantalla los
   bloques se leían como huecos en la tarjeta en vez de como piezas encima de
   ella. `--card-bg` y no `#ffffff`: es la misma variable que usa `.card`, así
   que el día que el tema cambie, cambian juntas.

   El margen va en los DOS lados y no solo abajo. Acá sí es margen del hijo y
   no `gap` del padre —al revés que las tarjetas— porque entre dos bloques va
   `.block-between`, que es un elemento con su propia altura: con `gap` la
   separación se sumaría a la de él. */
.block-card {
    margin-top: var(--space-150); margin-bottom: var(--space-150);
    border: 1px solid transparent;
    background: var(--card-bg);
}
.block-card:hover { border-color: var(--border); }
/* Solo el borde: el fondo ya es blanco, así que la línea que cambiaba a
   `--card-bg` no hacía nada y hacía dudar de si el foco pintaba algo más. */
.block-card:focus-within { border-color: var(--primary); }
.block-card-head {
    display: flex; align-items: center; justify-content: space-between;
    margin-bottom: var(--space-150);
}
/* El chip lleva el color PRIMARIO y no el del `badge-soft` genérico: ahí es
   verde, que en esta plataforma significa «completo», y un bloque recién
   agregado no está completo de nada. */
.block-card-kind {
    background: var(--primary-soft); color: var(--primary-dark);
    text-transform: uppercase; letter-spacing: .04em; font-size: var(--fs-small);
    font-weight: 700;
}
.block-card-tools { display: flex; align-items: center; gap: var(--space-050); }
.block-card-tools form { display: inline; }
.block-edit { display: flex; flex-direction: column; gap: var(--space-100); }
/* Vacío se ve PUNTEADO y atenuado; escrito, sólido. El título del bloque es
   opcional en los nueve tipos, y un campo vacío con borde sólido se lee como
   uno que falta llenar — de los 87 bloques reales, la mayoría no lo usa. */
.block-edit-title { font-weight: 600; }
.block-edit-title:placeholder-shown { border-style: dashed; opacity: .65; }
.block-edit-title:focus { border-style: solid; opacity: 1; }
.block-edit-html { font-family: ui-monospace, SFMono-Regular, monospace; }
.block-edit-row { display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap; }
.block-edit-row .field-label { margin-bottom: 0; }
.block-edit-foot { display: flex; justify-content: flex-end; }
.block-edit-none { margin: 0; font-size: var(--fs-small); color: var(--text-light); }
/* El «+» entre dos bloques: la línea lo cruza, así que se lee como un punto de
   inserción y no como un botón suelto. */
/* El carril entre dos bloques, donde vive el «+». Era de 20px y con los
   bloques ya blancos y con su propio margen quedaba apretado: el botón tocaba
   las dos tarjetas a la vez y la línea punteada no se leía como un carril.

   48px y no los 50 que salieron de medir en el navegador: la rejilla de
   espaciado es de 4, la diferencia de dos píxeles no se ve, y una rejilla con
   una excepción deja de serlo al tercer componente — está escrito arriba de
   `--sp-1`, y es de donde salieron los 14 y los 18 de tiendapp. */
.block-between {
    display: flex; justify-content: center; align-items: center;
    height: calc(var(--space-300) * 2); position: relative;
}
.block-between::before {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    border-top: 1px dashed var(--border);
}
.block-between-btn {
    position: relative; z-index: 1; width: 26px; height: 26px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%; border: 1px solid var(--border);
    background: var(--card-bg); color: var(--text-light);
    font-size: var(--fs-body); line-height: 1;
}
.block-between-btn:hover { border-color: var(--primary); color: var(--primary); }
/* Los dos botones del pie van en COLUMNA y no centrados en una fila: son
   «Agregar bloque» y «Reordenar», los dos punteados y del mismo ancho, como la
   referencia. Antes había uno solo y centrado tenía sentido. */
.block-add-foot {
    display: flex; flex-direction: column; gap: var(--space-150);
    margin-top: var(--space-200);
}

/* ── El editor de texto rico ─────────────────────────────────────────────────
   Degrada a un textarea: sin JavaScript queda el HTML crudo, feo y guardable.
   Con `richtext.js` aparecen la barra y el área de TipTap, y el textarea se
   esconde — pero sigue siendo él quien viaja en el POST, así que el guardado es
   el mismo camino en los dos casos.

   `.richtext-raw` se esconde con una CLASE que agrega el script, no con
   `display:none` de entrada: sin script tiene que verse, y una regla estática
   dejaría el campo invisible justo cuando es lo único que hay. */
.richtext {
    position: relative;   /* el ancla de la barrita del renglón vacío */
    border: 1px solid var(--border); border-radius: var(--radius-field);
}
.richtext-bar {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-025);
    padding: var(--space-075); border-bottom: 1px solid var(--border); background: var(--bg);
    border-radius: var(--radius-field) var(--radius-field) 0 0;
}
.richtext-btn {
    min-width: 28px; height: 28px; padding: 0 var(--space-075); border: 0; cursor: pointer;
    background: transparent; border-radius: var(--radius-field);
    color: var(--text); font-size: var(--fs-body); font-family: inherit;
}
.richtext-btn:hover { background: var(--primary-soft); color: var(--primary-dark); }
.richtext-btn.is-active { background: var(--primary-soft); color: var(--primary-dark); }
.richtext-sep { width: 1px; height: 18px; margin: 0 var(--space-050); background: var(--border); }
/* `background-color` y NO el atajo, y 30px a la derecha: la flecha del `select`
   la dibuja una regla de elemento —la menos específica de la hoja— así que el
   atajo se la borra, y sin el sitio una opción larga se le mete por debajo.
   Las dos cosas las vigila `tests/test_stylesheet.py`. */
/* `width: auto` y no heredado: el editor vive dentro de un `.field`, y
   `.field select { width: 100% }` estiraba este selector a lo ancho de la
   columna. La barra terminaba pareciendo un formulario en vez de una barra —
   se vio en la pantalla, no en una prueba. */
.richtext-select {
    width: auto; height: 28px; padding: 0 var(--space-400) 0 var(--space-100);
    border: 1px solid var(--border);
    border-radius: var(--radius-field); background-color: var(--card-bg);
    font-size: var(--fs-small); font-family: inherit; color: var(--text);
}
/* El HTML crudo va a la DERECHA y separado: no es una opción de formato, es
   cambiar de modo. Mezclarlo con las negritas invita a tocarlo sin querer. */
.richtext-html {
    margin-left: auto; padding: 0 var(--space-150); border: 1px solid var(--border);
    font-size: var(--fs-small); font-weight: 600; color: var(--text-light);
}
.richtext-area {
    padding: var(--space-150); min-height: 120px; font-size: var(--fs-body);
    line-height: 1.65; color: var(--text);
}
.richtext-area:focus, .richtext-area .ProseMirror:focus { outline: none; }
.richtext:focus-within { border-color: var(--primary); }
.richtext-area p { margin: 0 0 var(--space-150); }
.richtext-area ul, .richtext-area ol { margin: 0 0 var(--space-150); padding-left: var(--space-250); }
.richtext-area blockquote,
.richtext-lectura blockquote {
    margin: var(--space-150) 0; padding-left: var(--space-150);
    border-left: 3px solid var(--primary); color: var(--text-light);
}
.richtext-area img { max-width: 100%; height: auto; border-radius: var(--radius-field); }
/* Las tablas del recetario tienen seis columnas y no caben en un teléfono, así
   que el área desborda a lo ancho en vez de romper la página. */
.richtext-area table { border-collapse: collapse; width: 100%; margin: var(--space-150) 0; }
.richtext-area th, .richtext-area td {
    border: 1px solid var(--border); padding: var(--space-100) var(--space-150);
    font-size: var(--fs-small); vertical-align: top;
}
.richtext-area th { background: var(--bg); font-weight: 600; }
.richtext-area .selectedCell { background-color: var(--primary-soft); }
.richtext-raw {
    width: 100%; border: 0; border-radius: 0 0 var(--radius-field) var(--radius-field);
    font-family: ui-monospace, SFMono-Regular, monospace; font-size: var(--fs-small);
}
.richtext-raw-hidden { display: none; }
.richtext-raw-hidden.richtext-raw-open { display: block; }
/* Montado el editor, el textarea crudo se esconde también por el estado del
   CONTENEDOR (`data-richtext-ready`, que no tiene id). htmx, al asentar un
   reemplazo, le devuelve al textarea los atributos del molde cuando su id
   coincide con el de antes, y le borraba `richtext-raw-hidden`: la caja del
   Asistente mostraba el crudo encima del editor tras el primer mensaje
   (2026-10-11). */
[data-richtext-ready] > .richtext-raw:not(.richtext-raw-open) { display: none; }

/* El selector de emoji. Va pegado al botón y NO es un modal: interrumpir la
   escritura para poner una carita es peor que no tener el botón. */
/* UN panel anclado para el emoji y para los cinco formatos. Antes eran cinco
   `<select>`: dentro de un `.field` del formulario se estiran a lo ancho, y la
   barra terminaba más alta que el área de escritura.

   Columna por omisión —una opción por renglón, legible— y las dos variantes
   que necesitan rejilla la piden por su clase. */
.richtext-pop {
    position: absolute; z-index: 60; display: flex; flex-direction: column;
    /* 340 y no 260: con la sección de marca, la paleta de veintitrés y el
       selector libre, el tope viejo dejaba «Quitar el color» abajo del
       borde. Una opción que no se ve es una opción que no existe. */
    gap: 1px; min-width: 132px; max-height: 340px; overflow-y: auto;
    padding: var(--space-100); background: var(--card-bg);
    border: 1px solid var(--border); border-radius: var(--radius-card);
    box-shadow: var(--shadow-lg);
}
.richtext-pop.is-emoji { flex-flow: row wrap; gap: var(--space-025); max-width: 264px; }

/* El panel de color va por SECCIONES —los de la marca primero, después la
   paleta— porque un correo de la empresa se escribe en los colores de la
   empresa, y buscarlos entre veintitrés genéricos es no tenerlos. */
.richtext-pop-title {
    margin: var(--space-050) 0 var(--space-025); padding: 0 var(--space-050);
    font-size: var(--fs-label); font-weight: 600; color: var(--text-light);
}
.richtext-pop-title:first-child { margin-top: 0; }
.richtext-swatches {
    display: grid; grid-template-columns: repeat(6, 1fr); gap: var(--space-050);
    max-width: 216px;
}

/* El selector libre del navegador: la etiqueta ocupa la fila y el campo es el
   cuadro de color a la derecha. Se estila como una opción más para que no se
   lea como un control ajeno metido en el panel. */
.richtext-free { display: flex; align-items: center; justify-content: space-between; gap: var(--space-100); cursor: pointer; }
.richtext-free-input {
    width: 26px; height: 22px; padding: 0; border: 1px solid var(--border);
    border-radius: var(--radius-field); background: none; cursor: pointer;
}

/* Cada opción SE MUESTRA A SÍ MISMA: el tamaño a su tamaño, la tipografía en
   su tipografía. Por eso el estilo en línea lo pone el script — es el dato,
   no decoración, y una clase por cada valor sería una hoja imposible. */
.richtext-pick {
    padding: var(--space-050) var(--space-100); border: 0; cursor: pointer;
    background: transparent; border-radius: var(--radius-field);
    color: var(--text); font-size: var(--fs-small); font-family: inherit;
    text-align: left; line-height: 1.4;
}
.richtext-pick:hover { background: var(--bg); }
.richtext-pop.is-emoji .richtext-pick {
    width: 32px; height: 32px; padding: 0; text-align: center;
    font-size: var(--fs-glyph);
}

/* Un color se elige VIÉNDOLO. El borde va por dentro para que un color claro
   siga teniendo forma sobre el fondo de la tarjeta. */
.richtext-swatch {
    width: 30px; height: 30px; border: 0; cursor: pointer;
    border-radius: var(--radius-field); box-shadow: inset 0 0 0 1px rgb(0 0 0 / .12);
}
.richtext-swatch:hover { box-shadow: inset 0 0 0 1px rgb(0 0 0 / .35); }

/* La «A» del color lleva debajo la raya de lo que aplica. */
.richtext-color { font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }

/* ── El editor de la casa, perfil documento (spec editor-casa §4) ─────────
   La cara de la segunda vuelta (2026-10-07: «absurdamente simple» junto a
   Slack): un documento con aire, menús con íconos de la casa y secciones, la
   barra al seleccionar como píldora, la barrita del renglón vacío y el
   bloque iluminado mientras se le abre el menú. */
.richtext--documento .richtext-area {
    min-height: 220px; padding: var(--space-200) var(--space-250) var(--space-200) var(--space-500);
}
/* El margen de la izquierda es DEL documento (Renzo, 2026-10-08): el agarre
   ⠿ sale con pasar el mouse por la franja al lado de un bloque, no solo
   encima de su texto. El editor solo oye el mouse dentro de su caja, así
   que la caja se estira sobre el margen —sin verse—. `--richtext-margen` lo
   ajusta quien cambie el margen del área (el canvas del chat). */
.richtext--documento .richtext-area > .ProseMirror {
    margin-left: calc(-1 * var(--richtext-margen, var(--space-500)));
    padding-left: var(--richtext-margen, var(--space-500));
}
/* La LECTURA de lo que escribió el perfil documento (el canvas del chat,
   2026-10-07): las MISMAS reglas que el editor, para que lo que se ve al
   editar sea lo que se lee al guardar. Casillas, secciones y columnas ya
   se pintan en cualquier parte (abajo).

   La ESCALA del documento son variables `--doc-…` (2026-10-08: el canvas se
   veía «muy Google Docs» al lado del de Slack y de Evernote). Sin nada que
   las ponga valen lo de siempre —onboarding y notas fiscales no cambian—, y
   quien quiera un documento con aire las sube en su contenedor (el canvas
   del chat, en `chat.css`):

     --doc-texto, --doc-interlineado   el cuerpo
     --doc-parrafo                     el aire debajo de cada párrafo
     --doc-sangria, --doc-item         la sangría de las listas y el aire
                                       entre sus renglones
     --doc-h2/h3/h4                    el tamaño de cada título, y
     --doc-h2/h3/h4-arriba             el aire que lo separa de lo anterior
     --doc-seccion                     el relleno de la sección destacada
     --doc-codigo                      la letra de un bloque de código */
/* Sin ligaduras, como las pinta ProseMirror al editar: con ellas el renglón
   cortaba en otra palabra al guardar. */
.richtext-lectura { overflow-wrap: anywhere; font-variant-ligatures: none; }
.richtext--documento .richtext-area,
.richtext-lectura {
    font-size: var(--doc-texto, var(--fs-body));
    line-height: var(--doc-interlineado, 1.7);
}
.richtext-lectura img { max-width: 100%; height: auto; }
/* `:where` deja este párrafo con el peso de `.richtext-area p`: el de una
   casilla (`ul[data-type="taskList"] … > p`, abajo) tiene que seguir
   ganando, o cada renglón de casillas carga el aire de un párrafo. */
:where(.richtext--documento) .richtext-area p,
.richtext-lectura p { margin: 0 0 var(--doc-parrafo, var(--space-150)); }
.richtext--documento .richtext-area ul:not([data-type="taskList"]),
.richtext--documento .richtext-area ol,
.richtext-lectura ul:not([data-type="taskList"]),
.richtext-lectura ol {
    margin: 0 0 var(--doc-parrafo, var(--space-150)); padding-left: var(--doc-sangria, var(--space-250));
}
/* El párrafo de un renglón de lista no lleva el aire de un párrafo suelto:
   los renglones van juntos y la lista entera se separa de lo que sigue. */
.richtext--documento .richtext-area li > p,
.richtext-lectura li > p { margin-bottom: var(--doc-item, var(--space-150)); }
.richtext--documento .richtext-area h2,
.richtext-lectura h2 { margin: var(--doc-h2-arriba, var(--space-250)) 0 var(--space-100); font-size: var(--doc-h2, var(--fs-section)); line-height: 1.3; }
.richtext--documento .richtext-area h3,
.richtext-lectura h3 { margin: var(--doc-h3-arriba, var(--space-200)) 0 var(--space-100); font-size: var(--doc-h3, var(--fs-body)); line-height: 1.35; font-weight: 700; }
.richtext--documento .richtext-area h4,
.richtext-lectura h4 { margin: var(--doc-h4-arriba, var(--space-150)) 0 var(--space-050); font-size: var(--doc-h4, var(--fs-small)); text-transform: uppercase; letter-spacing: .04em; color: var(--text-light); }
/* Lo primero del documento no empuja hacia abajo: el aire de arriba de un
   título separa secciones, no al documento de su cabecera. */
.richtext--documento .richtext-area > .ProseMirror > :first-child,
.richtext-lectura > :first-child { margin-top: 0; }
.richtext-area .is-empty::before {
    content: attr(data-placeholder); float: left; height: 0;
    color: var(--text-faint); pointer-events: none;
}
/* Solo con el foco ADENTRO —el texto o el agarre, que vive en el área—:
   ProseMirror conserva la selección del bloque al perder el foco, y el
   bloque quedaba iluminado después de hacer clic afuera (Renzo,
   2026-10-08). Con el menú del bloque abierto el foco sigue en el agarre,
   así que mientras se elige, se ve. */
.richtext-area:focus-within .ProseMirror-selectednode {
    /* Con FONDO y no con borde (Renzo, 2026-10-08: el borde apretaba el
       contenido): el morado suave de la marca, extendido 8 px alrededor con
       una sombra del mismo color para que respire sin mover el texto. */
    background-color: var(--primary-soft);
    box-shadow: 0 0 0 8px var(--primary-soft);
    border-radius: 2px;
}
/* Detrás de una foto el fondo no se ve: ahí, un contorno fino. */
.richtext-area:focus-within img.ProseMirror-selectednode {
    background-color: transparent;
    box-shadow: 0 0 0 3px var(--primary);
    border-radius: var(--radius-field);
}
.richtext-area pre,
.richtext-lectura pre {
    margin: var(--space-150) 0; padding: var(--space-150); border-radius: var(--radius-field);
    background: var(--bg); font-family: ui-monospace, SFMono-Regular, monospace;
    font-size: var(--doc-codigo, var(--fs-small));
    white-space: pre-wrap;
}

/* El menú de la casa: el del agarre, el «¶ ⌄», el «Aa» y las sugerencias del
   «/» y del «:» comparten este vestido. */
.richtext-menu {
    position: absolute; z-index: 70; min-width: 240px; max-height: 380px; overflow-y: auto;
    padding: var(--space-075); background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius-card); box-shadow: var(--shadow-lg);
}
.richtext-menu-titulo {
    margin: var(--space-075) var(--space-100) var(--space-025); font-size: var(--fs-label); font-weight: 600;
    color: var(--text-light); text-transform: uppercase; letter-spacing: .04em;
}
.richtext-menu-titulo:first-child { margin-top: var(--space-025); }
.richtext-menu-sep { margin: var(--space-075) 0; border: 0; border-top: 1px solid var(--border); }
.richtext-menu-item {
    display: flex; align-items: center; gap: var(--space-100); width: 100%;
    padding: var(--space-100) var(--space-100); border: 0; cursor: pointer; background: transparent;
    border-radius: var(--radius-field); color: var(--text);
    font-size: var(--fs-small); font-family: inherit; text-align: left;
}
.richtext-menu-item:hover { background: var(--primary-soft); color: var(--primary-dark); }
.richtext-menu-item.is-marcado { font-weight: 600; }
.richtext-menu-item.is-peligro { color: var(--danger-text); }
.richtext-menu-item.is-peligro:hover { background: var(--danger-bg); color: var(--danger-text); }
.richtext-menu-icono { display: inline-flex; align-items: center; justify-content: center; width: 20px; flex: none; }
.richtext-menu-icono svg, .richtext-menu-flecha svg { width: 18px; height: 18px; }
.richtext-menu-texto { flex: 1; white-space: nowrap; }
.richtext-menu-flecha { display: inline-flex; margin-left: auto; color: var(--text-light); }
.richtext-menu-flecha svg[data-icono="chevron"] { transform: rotate(-90deg); }
.richtext-muestra { display: inline-block; width: 14px; height: 14px; border-radius: 50%; border: 1px solid var(--border); }
.richtext-muestra.is-info { background: var(--info-vivid); }
.richtext-muestra.is-ok { background: var(--ok-vivid); }
.richtext-muestra.is-warn { background: var(--warn-vivid); }
.richtext-muestra.is-danger { background: var(--danger-vivid); }
.richtext-muestra.is-neutral { background: var(--bg); }
.richtext-menu-emojis { display: grid; grid-template-columns: repeat(6, 34px); gap: var(--space-025); padding: var(--space-025); }
.richtext-menu-emojis button {
    width: 34px; height: 34px; border: 0; cursor: pointer; background: transparent;
    border-radius: var(--radius-field); font-size: var(--fs-glyph);
}
.richtext-menu-emojis button:hover, .richtext-menu-emojis button.is-marcado { background: var(--primary-soft); }

/* Las sugerencias: baldosa con el ícono, el nombre y la ayuda en gris. */
.richtext-sugs { min-width: 280px; }
.richtext-sug {
    display: flex; align-items: center; gap: var(--space-100); width: 100%;
    padding: var(--space-075) var(--space-100); border: 0; cursor: pointer; background: transparent;
    border-radius: var(--radius-field); color: var(--text);
    font-family: inherit; text-align: left;
}
.richtext-sug.is-activa { background: var(--primary-soft); }
.richtext-sug-glifo {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; flex: none; border: 1px solid var(--border);
    border-radius: var(--radius-field); background: var(--card-bg); font-size: var(--fs-glyph);
    color: var(--text);
}
.richtext-sug-glifo svg { width: 18px; height: 18px; }
.richtext-sug-cuerpo { display: flex; flex-direction: column; min-width: 0; }
.richtext-sug-nombre { font-size: var(--fs-small); font-weight: 600; }
.richtext-sug-ayuda { font-size: var(--fs-meta); color: var(--text-light); }
.richtext-sug.is-activa .richtext-sug-nombre { color: var(--primary-dark); }

/* ── El índice lateral de un documento (`assets/js/indice.js`) ───────────
   Rayitas a la izquierda, una por título y más cortas cuanto más adentro;
   al pasar el mouse (o con el foco) se abre la lista. Pegado arriba
   mientras se baja por el documento, y sin ocupar lugar en el flujo. */
/* Por encima del documento y de la barra al seleccionar (60 la de flotar,
   58 acá no la tapa: está debajo del texto, no a su izquierda) y por
   debajo de los menús (70). */
.indice { position: sticky; top: 12px; z-index: 58; height: 0; }
.indice[hidden] { display: none; }
.indice__rayas {
    /* A cuánto del contenido van las rayitas: `--indice-lado`, que pone
       quien sabe cuánto margen tiene (el canvas del chat, 64 px). */
    position: absolute; top: 4px; left: calc(-1 * var(--indice-lado, 44px));
    display: flex; flex-direction: column; gap: var(--space-100); padding: var(--space-075) var(--space-050);
    cursor: pointer;
}
.indice__raya { display: block; height: 2px; border-radius: 2px; background: var(--border-strong); }
.indice__raya--1, .indice__raya--2 { width: 20px; }
.indice__raya--3 { width: 14px; }
.indice__raya--4 { width: 9px; }
.indice__raya.is-activa { background: var(--primary); }
.indice__lista {
    position: absolute; top: 0; left: calc(-4px - var(--indice-lado, 44px)); z-index: 1;
    display: none; min-width: 240px; max-width: 320px; max-height: 60vh;
    overflow-y: auto; margin: 0; padding: var(--space-100); list-style: none;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius-card); box-shadow: var(--shadow-lg);
}
.indice:hover .indice__lista, .indice:focus-within .indice__lista { display: block; }
.indice__item {
    display: block; width: 100%; padding: var(--space-050) var(--space-100); border: 0; cursor: pointer;
    background: none; border-radius: var(--radius-field); text-align: left;
    font: inherit; font-size: var(--fs-small); color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.indice__item:hover { background: var(--bg); }
.indice__item--1 { font-weight: 700; }
.indice__item--3 { padding-left: var(--space-300); }
.indice__item--4 { padding-left: var(--space-400); color: var(--text-light); }
.indice__item.is-activa { color: var(--primary-dark); }

/* ── El perfil MENSAJE (spec chat cara §2–3) ─────────────────────────────
   Lo que se escribe en un chat: sin borde propio (lo pone quien lo usa, el
   redactor), renglón a renglón y con tope de alto. La barrita de formato
   nace escondida y la muestra «Aa». */
.richtext--mensaje { border: 0; border-radius: 0; }
.richtext--mensaje:focus-within { border-color: transparent; }
.richtext--mensaje .richtext-area {
    min-height: 0; max-height: 12em; overflow-y: auto;
    padding: var(--space-100) var(--space-200) var(--space-050); line-height: 1.5;
}
.richtext--mensaje .richtext-area p { margin: 0; }
.richtext--mensaje .richtext-area ul,
.richtext--mensaje .richtext-area ol { margin: var(--space-025) 0; }
.richtext-mensaje-barra {
    display: flex; flex-wrap: wrap; gap: var(--space-025); padding: var(--space-075) var(--space-100) 0;
}
.richtext-mensaje-barra[hidden] { display: none; }
.richtext-mensaje-barra .richtext-btn {
    display: inline-flex; align-items: center; justify-content: center;
}
.richtext-mensaje-barra .richtext-btn svg { width: 16px; height: 16px; }
/* Los emojis mientras se escribe (`extensionEmojis`, 2026-10-12): en una
   frase, un poco más grandes que la letra; si el mensaje es solo emojis,
   grandes. Lo mismo que el mensaje enviado, así lo que se escribe es lo
   que se manda. Relativo a la letra del editor: quien lo usa la pone. */
.richtext--mensaje .richtext-emoji { font-size: 1.4em; line-height: 1; vertical-align: -.12em; }
.richtext--mensaje .richtext-solo-emojis { font-size: 2.15em; line-height: 1.25; }
.richtext--mensaje .richtext-solo-emojis .richtext-emoji { font-size: inherit; vertical-align: baseline; }

/* La ficha de mención: dentro de un mensaje (la pinta el servidor o, si
   llegó por el socket, `chat-equipo.js`) y dentro del editor. Una persona
   es texto de color; un objeto es una pastilla con su ícono, que abre. Sin
   acceso, gris y sin enlace: «Factura · no tienes acceso». */
.mencion {
    position: relative;    /* el ancla de su tarjeta */
    /* ALINEADA AL TEXTO (Renzo, 2026-10-08): el ícono va centrado y el
       nombre en la línea base (`align-self: baseline` en `__nombre`). Así la
       línea base de la ficha es la de su nombre y no el borde de abajo del
       ícono, y con `vertical-align: baseline` el nombre cae en el mismo
       renglón exacto que el texto de alrededor. */
    display: inline-flex; align-items: center; gap: var(--space-050); max-width: 22em;
    padding: 1px var(--space-100) 1px var(--space-075); border-radius: 8px; vertical-align: baseline;
    background: var(--primary-soft); color: var(--primary-dark);
    border: 1px solid color-mix(in srgb, var(--primary) 18%, transparent);
    font-weight: 600; text-decoration: none; line-height: 1.45;
    transition: background-color .12s ease, border-color .12s ease;
}
a.mencion:hover,
a.mencion:focus-visible {
    background-color: color-mix(in srgb, var(--primary) 16%, var(--card-bg));
    border-color: color-mix(in srgb, var(--primary) 35%, transparent);
    outline: none;
}
.mencion > svg { width: 14px; height: 14px; flex: none; color: var(--primary); }

/* La TARJETA de una mención (2026-10-08, Renzo: «si pongo el mouse encima
   que me muestre más detalles»): el tipo con su ícono, el nombre, la pista
   del módulo —el estado de una tarea, el cliente de un pedido— y «Abrir».
   Vive dentro de la ficha y sale al pasar el mouse o al llegar con el
   teclado; sin JavaScript. Arriba de la ficha; `mencion--abajo` (la pone
   quien sabe que arriba no cabe) la voltea. Escondida no se copia. */
.mencion__tarjeta {
    position: absolute; z-index: 30; left: -6px; bottom: calc(100% + 8px);
    display: flex; flex-direction: column; gap: var(--space-050);
    visibility: hidden; opacity: 0;
    transition: opacity .12s ease, visibility 0s linear .18s;
    width: max-content; min-width: 200px; max-width: 280px;
    padding: var(--space-150) var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius);
    background-color: var(--card-bg); color: var(--text);
    box-shadow: var(--shadow-lg);
    font-weight: 400; line-height: 1.4; text-align: left; white-space: normal;
}
/* El mouse puede IR a la tarjeta (Renzo, 2026-10-08: «cuando intento ir al
   botón abrir, se oculta»): la tarjeta es parte del enlace, así que estar
   encima de ella sigue siendo estar encima de la ficha; un puente
   invisible cubre los 8 px de aire, y al salir espera un instante antes de
   irse. Escondida con `visibility`, que no recibe el mouse ni se copia. */
.mencion__tarjeta::before {
    content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 10px;
}
.mencion--abajo .mencion__tarjeta { bottom: auto; top: calc(100% + 8px); }
.mencion--abajo .mencion__tarjeta::before { top: auto; bottom: 100%; }
a.mencion:hover .mencion__tarjeta,
a.mencion:focus-visible .mencion__tarjeta {
    visibility: visible; opacity: 1;
    transition: opacity .12s ease, visibility 0s;
}
a.mencion .mencion__tarjeta:hover .mencion__tarjeta-abrir { text-decoration: underline; }
.mencion__tarjeta-cabeza { display: flex; align-items: center; gap: var(--space-100); }
.mencion__tarjeta-glifo {
    display: grid; place-items: center; flex: none;
    width: 28px; height: 28px; border-radius: 8px;
    background-color: var(--primary-soft); color: var(--primary-dark);
}
.mencion__tarjeta-glifo svg { width: 16px; height: 16px; }
.mencion__tarjeta-tipo {
    font-size: var(--fs-badge); font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-light);
}
.mencion__tarjeta-nombre { margin-top: var(--space-025); font-size: var(--fs-body); font-weight: 700; color: var(--text); }
.mencion__tarjeta-pista { font-size: var(--fs-small); color: var(--text-light); }
.mencion__tarjeta-abrir {
    margin-top: var(--space-075); padding-top: var(--space-100); border-top: 1px solid var(--border);
    font-size: var(--fs-small); font-weight: 600; color: var(--primary);
}
.mencion__nombre { align-self: baseline; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Una persona: su carita y el nombre, sin pastilla de objeto. */
.mencion--persona { gap: var(--space-050); padding: 1px var(--space-075) 1px var(--space-025); border-radius: 999px; }
.mencion__cara {
    width: 16px; height: 16px; flex: none; border-radius: 50%; object-fit: cover;
}
.mencion__cara--letra {
    display: inline-grid; place-items: center;
    background-color: var(--primary); color: #fff;
    font-size: var(--fs-badge); font-weight: 700; line-height: 1;
}
.mencion--sin-acceso, .mencion--pendiente {
    background: var(--bg); color: var(--text-light); font-weight: 500;
}

/* El menú de la «@»: pestañas arriba, resultados debajo. */
.mencion-menu { width: 360px; max-width: calc(100vw - 16px); padding: 0; }
.mencion-pestanas {
    display: flex; gap: var(--space-025); overflow-x: auto; scrollbar-width: none;
    padding: var(--space-075) var(--space-075) 0; border-bottom: 1px solid var(--border);
}
.mencion-pestana {
    flex: none; padding: var(--space-075) var(--space-100); margin-bottom: -1px; border: 0; cursor: pointer;
    background: none; border-bottom: 2px solid transparent;
    color: var(--text-light); font: inherit; font-size: var(--fs-small); font-weight: 600;
}
.mencion-pestana:hover { color: var(--text); }
.mencion-pestana.is-activa { color: var(--primary-dark); border-bottom-color: var(--primary); }
.mencion-lista { max-height: 300px; overflow-y: auto; padding: var(--space-075); }
.mencion-vacio { margin: var(--space-100) var(--space-100); font-size: var(--fs-small); color: var(--text-light); }
.mencion-menu .richtext-sug-glifo { border-radius: 50%; overflow: hidden; }
.mencion-menu .richtext-sug-glifo img { width: 100%; height: 100%; object-fit: cover; }

/* La barra al seleccionar: una píldora con íconos. Nace escondida y
   absoluta: TipTap le pone su estilo en línea recién la primera vez que la
   muestra, y antes de eso quedaba en el flujo, visible (se vio en pantalla). */
.richtext-flotante {
    position: absolute; visibility: hidden;
    display: flex; align-items: center; gap: var(--space-025); padding: var(--space-050); z-index: 60;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: 12px; box-shadow: var(--shadow-lg);
}
.richtext-btn-icono { display: inline-flex; align-items: center; justify-content: center; }
.richtext-btn-icono svg { width: 18px; height: 18px; }
.richtext-tipo {
    display: inline-flex; align-items: center; gap: var(--space-075); padding: 0 var(--space-100);
    font-size: var(--fs-small); font-weight: 600; white-space: nowrap;
}
.richtext-tipo svg { width: 16px; height: 16px; }
.richtext-tipo .richtext-tipo-flecha { width: 14px; height: 14px; color: var(--text-light); }

/* La barrita del renglón vacío: insertar sin teclear «/». A la derecha del
   renglón; la mueve el editor, no un reloj. */
.richtext-barrita {
    position: absolute; right: 12px; transform: translateY(-50%); z-index: 50;
    display: flex; align-items: center; gap: var(--space-025); padding: var(--space-050);
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: 10px; box-shadow: var(--shadow);
}
.richtext-barrita[hidden] { display: none; }
.richtext-barrita .is-principal {
    width: 30px; height: 30px; border-radius: 50%;
    background: var(--primary); color: var(--card-bg);
}
.richtext-barrita .is-principal:hover { background: var(--primary-dark); color: var(--card-bg); }

/* El agarre ⠿, con el glifo de la casa. */
.richtext-agarre {
    display: inline-flex; align-items: center; justify-content: center;
    /* Separado del bloque: fuera de los 8 px del fondo del seleccionado, y
       con aire. TipTap lo pega al borde izquierdo del bloque y mide la caja
       SIN márgenes (un `margin` no lo movía): se corre con `translate`. */
    translate: -14px 0;
    width: 22px; height: 26px; padding: 0; border: 0; cursor: grab;
    background: transparent; border-radius: var(--radius-field);
    color: var(--text-light); font-size: var(--fs-glyph); line-height: 1;
}
.richtext-agarre svg { width: 18px; height: 18px; }
.richtext-agarre:hover { background: var(--bg); color: var(--text); }

/* ── Casillas, sección destacada y columnas, al LEER y al editar ──────────
   Por atributo y no por clase: es lo que el editor escribe y lo único que la
   lista blanca deja pasar (`kernel/ui/html.py`, `DOCUMENT_ATTRIBUTES`), así
   que pintan igual en `.block-prose`, en las notas fiscales y dentro del
   editor. */
ul[data-type="taskList"] { list-style: none; margin: 0 0 var(--space-150); padding-left: 0; }
/* De la LISTA y no del `li`: dentro del editor TipTap pinta el `li` sin su
   `data-type` (solo lo pone al guardar), y la casilla quedaba encima del
   texto. */
ul[data-type="taskList"] > li { display: flex; align-items: flex-start; gap: var(--space-100); }
/* Entre renglón y renglón, `--doc-item` del documento que la contiene (el
   canvas lo sube); sin él, pegados como siempre. */
ul[data-type="taskList"] > li + li { margin-top: var(--doc-item, 0); }
/* La casilla, centrada en el PRIMER renglón de su texto a cualquier tamaño
   de letra: el rótulo mide un renglón (`1lh`) y la centra adentro. */
ul[data-type="taskList"] > li > label { flex: none; display: flex; align-items: center; height: 1lh; }
ul[data-type="taskList"] > li > div { flex: 1; min-width: 0; }
ul[data-type="taskList"] > li > div > p { margin: 0; }
/* Leyendo, la casilla no se marca: el clic no se guardaría en ningún lado
   (marcar desde la lectura llega con el canvas, spec editor-casa §8). Dentro
   del editor, sí. */
ul[data-type="taskList"] input[type="checkbox"] { pointer-events: none; }
.richtext-area ul[data-type="taskList"] input[type="checkbox"] { pointer-events: auto; }
li[data-checked="true"] > div { color: var(--text-light); text-decoration: line-through; }
div[data-type="callout"] {
    position: relative; margin: var(--space-150) 0;
    /* A la izquierda, el sitio del emoji: el relleno, una letra y un aire. */
    padding: var(--doc-seccion, var(--space-150));
    padding-left: calc(var(--doc-seccion, var(--space-150)) * 2 + var(--doc-texto, var(--fs-body)));
    border: 1px solid var(--info-bg); border-radius: var(--radius-card);
    background: var(--info-bg);
}
div[data-type="callout"]::before {
    content: attr(data-emoji); position: absolute;
    top: var(--doc-seccion, var(--space-150)); left: var(--doc-seccion, var(--space-150));
    font-size: var(--doc-texto, var(--fs-body)); line-height: var(--doc-interlineado, 1.65);
}
div[data-type="callout"] > :last-child { margin-bottom: 0; }
div[data-tono="ok"] { border-color: var(--ok-bg); background: var(--ok-bg); }
div[data-tono="warn"] { border-color: var(--warn-bg); background: var(--warn-bg); }
div[data-tono="danger"] { border-color: var(--danger-bg); background: var(--danger-bg); }
div[data-tono="neutral"] { border-color: var(--border); background: var(--bg); }
div[data-type="columns"] {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-200); margin: var(--space-150) 0;
}
div[data-cols="3"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
div[data-type="column"] > :last-child { margin-bottom: 0; }
/* En el editor, la columna muestra su borde solo al pasar o con el cursor
   adentro: limpio al leer, ubicable al escribir (como Slack). */
.richtext-area div[data-type="column"] {
    padding: var(--space-100); border: 1px dashed transparent; border-radius: var(--radius-field);
}
.richtext-area div[data-type="columns"]:hover > div[data-type="column"],
.richtext-area div[data-type="columns"]:focus-within > div[data-type="column"] { border-color: var(--border); }
@media (max-width: 640px) {
    div[data-type="columns"], div[data-cols="3"] { grid-template-columns: 1fr; }
}

/* ── Las filas repetibles de un editor ───────────────────────────────────────
   Una fila vacía al final es la que ofrece agregar la siguiente: agregar es
   escribir y guardar, sin un botón aparte y sin JavaScript. */
.rows { display: flex; flex-direction: column; gap: var(--space-100); }
/* Cada fila es una TARJETA y no dos controles sueltos en una línea. Con doce
   ítems —el checklist de presentación personal tiene doce— sin el recuadro no
   se ve dónde termina un ítem y empieza el siguiente, que es el mismo problema
   que `.block-card` resuelve un nivel más arriba. */
.rows-row {
    display: flex; align-items: center; gap: var(--space-150); flex-wrap: wrap;
    padding: var(--space-100) var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius-field); background: var(--card-bg);
}
.rows-row:focus-within { border-color: var(--primary); }
.rows-row > input[name$="label"], .rows-row > input[name$="text"],
.rows-row > input[name$="url"], .rows-row > input[name$="alt"] {
    flex: 1; min-width: 180px;
}
/* La pastilla de `.field-check` está calibrada para un formulario, donde hay
   una o dos. Acá hay UNA POR FILA —doce en el checklist más largo— y a ese
   ritmo la pastilla pesa más que el ítem que califica. Adentro de `.rows` se
   desarma y queda la casilla con su rótulo. */
.rows-row .field-check {
    margin: 0; padding: 0; gap: var(--space-075); border: 0; background: transparent;
    white-space: nowrap;
}
.rows-row .field-check:hover { border-color: transparent; }
.rows-row .field-check span { font-size: var(--fs-small); color: var(--text-light); }

/* Los controles de un editor de bloque no viven adentro de un `.field`, así
   que no heredan su ancho. Sin esto, un textarea de instrucciones queda del
   tamaño que el navegador decida —20 columnas— al lado de campos que llenan
   la tarjeta. */
.block-edit > input, .block-edit > textarea, .block-edit > select { width: 100%; }
.block-edit textarea { min-height: 60px; resize: vertical; font-family: inherit; }
/* Los tres números del bloque de carga no llenan la fila: un campo de ancho
   completo para un «10» se lee como si esperara un párrafo. */
.block-edit .input-narrow { width: 88px; flex: none; }

/* ── La galería del bloque imagen ────────────────────────────────────────────
   Las que ya están, con su texto alternativo y su casilla de conservar. Quitar
   una es DESMARCARLA: la URL de un archivo subido no la escribió nadie a mano,
   y pedir que se borre a mano es pedir que se edite una cadena de sesenta
   caracteres sin equivocarse. */
.gallery {
    display: grid; gap: var(--space-150);
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}
.gallery-item {
    display: flex; flex-direction: column; gap: var(--space-100); margin: 0;
    padding: var(--space-100); border: 1px solid var(--border);
    border-radius: var(--radius-field); background: var(--card-bg);
}
.gallery-thumb {
    width: 100%; height: 100px; object-fit: cover;
    border-radius: var(--radius-field); background: var(--bg);
}
.gallery-alt { font-size: var(--fs-small); }
.gallery-keep {
    display: inline-flex; align-items: center; gap: var(--space-075); cursor: pointer;
    font-size: var(--fs-small); color: var(--text-light);
}
/* Desmarcada, la tarjeta se apaga: es lo que dice que al guardar se va, y sin
   la señal la casilla es un cambio sin consecuencia visible. */
.gallery-item:has(.gallery-keep input:not(:checked)) { opacity: .45; }

/* ── La previa del bloque video ──────────────────────────────────────────── */
.video-preview {
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 16 / 9; max-width: 320px; overflow: hidden;
    border-radius: var(--radius-field); background: var(--canvas);
}
.video-preview-frame { width: 100%; height: 100%; }
.video-preview-link {
    padding: var(--space-150); color: #fff; font-size: var(--fs-small);
    text-align: center; word-break: break-all;
}

/* ── La pastilla que se ELIGE ────────────────────────────────────────────────
   Cada una es una CASILLA con su rótulo y no un botón con estado en
   JavaScript: así se marca con teclado, viaja en el formulario y no hace falta
   una línea de script. La casilla se esconde y el rótulo entero es el control.

   Se llama `choice-pill` y no `pill` a secas, y la lección vale más que el
   nombre: nació como `.pill` y colisionó con la pastilla de ESTADO que el ERP
   usa en catálogo, ubicaciones y la lista de formularios. El resultado fue que
   «ACTIVO» pasó a verse como un botón enorme con borde en pantallas que esta
   fase no tocó. Un nombre genérico en una hoja global no es un nombre: es una
   apuesta a que nadie más lo haya pensado. */
.choice-pills { display: flex; flex-wrap: wrap; gap: var(--space-100); }
/* `position: relative` ancla la casilla escondida DENTRO de su pastilla. Sin
   él, la casilla `absolute` caía junto al primer ancestro posicionado —lejos,
   arriba de la tarjeta— y al tocar el rótulo el navegador le daba el foco y
   desplazaba la página hasta ella: el brinco que se vio en «En el cajón del
   carrito» de un sugerido (Renzo, 2026-10-29). */
.choice-pill {
    position: relative;
    display: inline-flex; align-items: center; cursor: pointer;
    padding: var(--space-075) var(--space-200); border: 1px solid var(--border);
    border-radius: 999px; background: var(--card-bg);
    font-size: var(--fs-small); font-weight: 500; color: var(--text-light);
}
.choice-pill input { position: absolute; opacity: 0; width: 0; height: 0; }
/* El glifo de la pastilla, cuando la elección se reconoce mirando. Hereda el
   color del rótulo —`currentColor`— y por eso se vuelve blanco solo cuando la
   pastilla se rellena: pintarlo aparte habría dejado dos sitios donde decir de
   qué color está lo elegido. `gap` acá y no en `.choice-pill` porque una
   pastilla SIN glifo no tiene nada que separar. */
.choice-pill:has(.choice-pill-icon) { gap: var(--space-100); }
.choice-pill-icon { width: 16px; height: 16px; flex: none; }
.choice-pill:hover { border-color: var(--primary); color: var(--primary-dark); }
.choice-pill:has(input:checked) {
    background: var(--primary); border-color: var(--primary); color: #fff;
}
/* El foco se dibuja en la PASTILLA porque la casilla es invisible: sin esto,
   quien navega con teclado no ve dónde está. Con `:focus-visible` y no
   `:focus-within`: el clic con el mouse no deja el aro puesto encima de la
   elegida (se veía doble en el segmentado, 2026-10-29). */
.choice-pill:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }
.quiz-card { display: flex; flex-direction: column; gap: var(--space-100); }
.quiz-card-text { flex: 1; min-width: 200px; }

/* ── El editor de examen ─────────────────────────────────────────────────────
   Dos interfaces sobre el mismo contrato: la lista con modal cuando hay
   script, y el `fieldset` de respaldo cuando no. Alpine DESHABILITA el
   respaldo, y eso es lo que impide que el servidor reciba cada pregunta dos
   veces — un campo deshabilitado no viaja en el POST, uno escondido sí. */
.quiz-fallback { margin: 0; padding: 0; border: 0; }
.quiz-fallback[disabled] { display: none; }
.quiz-settings { display: flex; align-items: center; gap: var(--space-100); }
/* La nota mínima con su «%» (`ui/_percent_input.html`): angosta como la casilla que era. */
.quiz-settings .input-unit { width: 112px; flex: none; }

/* Autoría de cuestionarios: el contenido se agrupa por intención, con los
   controles compartidos. El fieldset conserva disabled sin el borde nativo. */
.questionnaire-fields { border: 0; padding: 0; margin: 0; min-width: 0; }
.questionnaire-section {
    display: grid; grid-template-columns: minmax(160px, 220px) minmax(0, 1fr);
    gap: var(--space-250); padding: var(--space-250) 0;
    border-bottom: 1px solid var(--border);
}
.questionnaire-section:first-child { padding-top: 0; }
.questionnaire-section:last-child { border-bottom: 0; padding-bottom: 0; }
.questionnaire-section-heading h3 { margin: 0 0 var(--space-100); font-size: var(--fs-body); }
.questionnaire-section-heading .field-hint { margin: 0; line-height: 1.5; }
.questionnaire-field { min-width: 0; }
.questionnaire-field:has(> .field-toggle) { align-self: end; }
.questionnaire-field textarea { min-height: 0; }
.questionnaire-priorities { display: grid; gap: var(--space-150); }
.questionnaire-priorities > .field {
    display: grid; grid-template-columns: minmax(0, 1fr) 80px;
    align-items: center; gap: var(--space-150); margin: 0;
    padding: var(--space-100) 0; border-bottom: 1px solid var(--border);
}
.questionnaire-priorities .field-error { grid-column: 1 / -1; }
@media (max-width: 900px) {
    .questionnaire-section { grid-template-columns: minmax(0, 1fr); gap: var(--space-150); }
}

/* ── Una lista cuyas filas son CAJAS ───────────────────────────────────────
   Cada fila con su borde y su radio, separadas por un hueco: es lo que hace
   que una fila se lea como una cosa entera y no como un renglón de una tabla.
   Va donde la fila TIENE PARTES —un glifo, un rótulo, un valor, sus botones—;
   donde es un dato plano por columna, la pieza es `.card-lines`.

   Se llamaba `.quiz-list`/`.quiz-row` mientras su único consumidor era el
   editor de examen. El segundo —las condiciones de una colección, 2026-08-16—
   no es un examen, y una clase con el nombre de su primer caso es la que
   después nadie reusa: se copia la regla con otro nombre y quedan dos formas
   de la misma fila. Los hijos `quiz-row-*` NO se promueven: el número de la
   pregunta y su asa de arrastre sí son del examen. */
.boxed-list { display: flex; flex-direction: column; gap: var(--space-075); }
.boxed-row {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-150); border: 1px solid var(--border);
    border-radius: var(--radius-field); background: var(--card-bg);
}
/* Una fila de casillas que se reparten la caja a partes iguales: el
   departamento y su caja en Entidades de Nómina (2026-11-01). */
.boxed-row > .field { flex: 1; min-width: 0; margin: 0; }
.quiz-row-num {
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0; width: 24px; height: 24px; border-radius: 8px;
    background: var(--primary); color: #fff;
    font-size: var(--fs-small); font-weight: 700;
}
/* El ícono del TIPO, antes del chip. El chip obliga a leer para saber de cuál
   se trata, y una lista de 25 se recorre mirando. */
.quiz-row-icon { width: 18px; height: 18px; flex-shrink: 0; color: var(--primary); }
.quiz-row-kind {
    flex-shrink: 0; padding: var(--space-050) var(--space-150); border-radius: 999px;
    background: var(--bg); color: var(--text-light);
    font-size: var(--fs-small); font-weight: 600;
}
/* El texto se recorta en UNA línea: una pregunta de 140 caracteres en una
   lista de 25 convierte la lista en un muro y se pierde el orden, que es lo
   único que esta pantalla sirve para ver. */
.quiz-row-text {
    flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; font-size: var(--fs-body);
}
.quiz-row-tools { display: flex; gap: var(--space-050); flex-shrink: 0; }
.quiz-add {
    align-self: flex-start; padding: var(--space-100) var(--space-150); border: 0;
    background: transparent; cursor: pointer; color: var(--primary);
    font-size: var(--fs-small); font-weight: 600; font-family: inherit;
    border-radius: var(--radius-field);
}
.quiz-add:hover { background: var(--primary-soft); }

/* El modal es un `<dialog>`: el navegador le da el fondo, el atrapado del foco
   y el cierre con Escape. Es el mismo elemento que `ui/_confirm.html` usa. */
.quiz-modal {
    width: min(560px, calc(100vw - var(--space-300))); max-height: 88vh;
    padding: 0; border: 1px solid var(--border);
    border-radius: var(--radius-lg); background: var(--card-bg);
    color: var(--text); box-shadow: var(--shadow-lg);
}
.quiz-modal::backdrop { background: rgba(13, 11, 22, .45); }
/* El cuerpo NO usa `gap`: el ritmo lo dan los rótulos. Con un gap uniforme, la
   distancia entre un rótulo y SU campo era la misma que entre ese campo y el
   rótulo siguiente, así que las ocho secciones se leían como una lista de
   dieciséis cosas sueltas. */
.quiz-modal-body {
    display: flex; flex-direction: column;
    max-height: 88vh; overflow-y: auto; padding: 0 var(--space-250) var(--space-250);
}
/* La cabecera se queda arriba al desplazar: con ocho secciones el modal
   desborda, y sin esto el «cerrar» se va de pantalla. */
.quiz-modal-head {
    position: sticky; top: 0; z-index: 1;
    display: flex; align-items: center; justify-content: space-between;
    margin: 0 calc(var(--space-250) * -1) var(--space-250);
    padding: var(--space-250); border-bottom: 1px solid var(--border);
    background: var(--card-bg);
}
.quiz-modal-head h3 { margin: 0; font-size: var(--fs-section); font-weight: 700; }
/* La baldosa y el título, juntos y a la izquierda; la × se queda a la derecha
   por el `space-between` del encabezado. */
.quiz-modal-head-lead { display: flex; align-items: center; gap: var(--space-150); }

/* La pista bajo un RÓTULO del modal: qué es esta sección y para qué sirve. Va
   pegada hacia arriba porque explica al rótulo que tiene encima.

   La que va debajo de un CONTROL —«Pega la URL de una imagen pública»— NO usa
   esta clase sino `.field-hint`, que es la de la casa para eso. Usaban la
   misma y con el margen negativo la de abajo quedaba montada sobre su input:
   una clase con dos papeles opuestos no puede acertar en los dos. */
.quiz-modal-hint {
    margin: calc(-1 * var(--space-025)) 0 var(--space-150);
    font-size: var(--fs-small); color: var(--text-light);
}

/* ── La raya entre secciones del modal ─────────────────────────────────────
   El modal tiene ocho secciones y ninguna frontera: «Explicación» arrancaba
   pegada a «Respuesta correcta» y las dos se leían como una sola.

   El aire va en la RAYA y no en los rótulos, y por eso es una etiqueta y no un
   `border-top`: puesta como borde de la sección siguiente, cada una tendría
   que acordarse de llevarlo —y las que se pintan condicionalmente estrenarían
   una raya colgando cuando su tipo de tarjeta no las muestra—. Como `<hr>`
   dentro del bloque condicional, aparece y desaparece con él.

   El margen es asimétrico a propósito: más arriba que abajo, para que la raya
   quede más cerca del rótulo que abre que del contenido que cierra. Es lo que
   hace que se lea como «empieza acá» y no como «terminó allá». */
.modal-rule {
    height: 0; border: 0; border-top: 1px solid var(--border);
    margin: var(--space-250) 0 var(--space-200);
}
/* Sin raya arriba del todo: la primera sección ya la separa el encabezado. */
.quiz-modal-body > .modal-rule:first-of-type { margin-top: var(--space-200); }

/* El motivo por el que no se pudo guardar, pegado al pie. `.field-error` ya lo
   pinta en rojo; acá sólo se le da el aire que necesita separado del último
   campo y del botón. */
.quiz-modal-error { margin: var(--space-150) 0 0; }

/* El aviso de una subida fallida en el editor de texto, debajo de su barra.
   Lo crea `richtext.js`, así que no aparece en ninguna plantilla: la guarda de
   la hoja mira plantilla→CSS y por eso no lo reclamaría, pero sin regla se
   pintaría con el tamaño y el color del cuerpo — que es exactamente el defecto
   que `field-hint` y `cell-sub` tuvieron antes. Lo rojo lo pone `.field-error`
   en la misma etiqueta; acá va sólo su sitio. */
.richtext-error { margin: var(--space-075) 0 0; }

/* ── El contador de caracteres, dentro de su caja ──────────────────────────
   `maxlength` impide pasarse pero no avisa de cuánto queda: quien escribe
   descubre el tope cuando el teclado deja de responder, y eso se lee como que
   la pantalla se colgó.

   DENTRO y no debajo: debajo empuja el campo siguiente y en un modal de ocho
   secciones eso son ocho renglones de más. El relleno de abajo del control le
   hace sitio, así que el texto nunca pasa por encima del número. */
.field-counted { position: relative; }
.field-counted textarea { padding-bottom: var(--space-300); }
.field-counted input { padding-right: var(--space-800); }
.field-count {
    position: absolute; right: 10px; bottom: 8px;
    font-size: var(--fs-meta); color: var(--text-faint);
    font-variant-numeric: tabular-nums; pointer-events: none;
}
/* En una línea el número va centrado en vertical: ahí no hay «abajo». */
.field-counted:has(input) .field-count { top: 50%; bottom: auto; transform: translateY(-50%); }

/* ── Las dos mitades de una elección binaria ───────────────────────────────
   Verdadera y Falsa reparten el ancho, como la referencia. Es un modificador
   de `.choice-pills` y no otra clase: el control sigue siendo el mismo par de
   radios —con teclado se recorren igual— y lo único que cambia es el reparto.

   El radio de píldora se cierra al de un control: dos mitades de 999px pegadas
   dejan un hueco con forma de lente en la costura. */
/* Tantas partes iguales como opciones (2026-10-19): con dos es lo de antes;
   la lista de Mantenimiento trae tres («Cumple · No cumple · Sin responder»). */
.choice-segmented { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
/* `display` y `margin` otra vez acá: dentro de un `.field`, `.field label`
   (0,1,1) le gana a `.choice-pill` y la volvía bloque, con el glifo pegado
   al rótulo y medio renglón de margen debajo (2026-10-29). */
.choice-segmented .choice-pill {
    display: flex; margin: 0;
    justify-content: center; gap: var(--space-100);
    padding: var(--space-100) var(--space-200); border-radius: var(--radius-field);
    font-size: var(--fs-body); font-weight: 600;
}
/* El visto de la elegida. El `<svg>` está SIEMPRE en el marcado y quien decide
   si se ve es el CSS, leyendo el mismo `input:checked` que pinta el fondo: una
   sola fuente de verdad, y un glifo de verdad en vez del aro que dibujaba un
   `::after` —que a este tamaño se leía como un radio sin marcar, justo lo
   contrario de lo que quiere decir. */
.choice-check { display: none; width: 16px; height: 16px; flex: none; }
.choice-segmented .choice-pill:has(input:checked) .choice-check { display: block; }
/* El segmentado CHICO (2026-10-19, una fila por ítem en la lista de
   Mantenimiento): del ancho de sus rótulos y del alto de una casilla, las
   partes pegadas dentro de un solo contorno, como `.bc-seg`. El modal
   angosto sigue con el grande. */
/* El aire de la ficha (2026-11-02): 2 px contra un borde de 1 px se leía
   como ficha pegada al contorno y al vecino («¿Es salarial?» de los pagos
   fijos). `--seg-aire` la separa del borde y `--seg-hueco` de la otra
   parte; la ficha de abajo los lee, así que pista y ficha no se desfasan. */
.choice-segmented--compact {
    --seg-aire: 3px; --seg-hueco: 4px;
    display: inline-grid; gap: var(--seg-hueco); padding: var(--seg-aire);
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--card-bg);
}
.choice-segmented--compact .choice-pill {
    padding: var(--space-050) var(--space-150); border: 0; border-radius: calc(var(--radius-field) - 2px);
    font-size: var(--fs-small); font-weight: 500; white-space: nowrap;
}
/* El segmentado chico es un INTERRUPTOR de una u otra (Renzo, 2026-10-29): el
   relleno es una sola ficha, el `::before` de la pista, que se desliza a la
   elegida en vez de apagarse en una y prenderse en otra. Sigue sin script:
   cuántas partes hay (`--seg-n`) y cuál está marcada (`--seg-i`) los lee el
   CSS con `:has`, igual que antes pintaba el fondo. Las partes miden lo mismo
   (`1fr`), por eso la ficha corre de a «una parte más el hueco». Hasta tres
   partes, que es lo que hay (la lista de Mantenimiento); sin nada marcado,
   no hay ficha. */
.choice-segmented--compact { position: relative; --seg-n: 2; --seg-i: 0; }
.choice-segmented--compact:has(> .choice-pill:nth-child(3)) { --seg-n: 3; }
.choice-segmented--compact:has(> .choice-pill:nth-child(2) input:checked) { --seg-i: 1; }
.choice-segmented--compact:has(> .choice-pill:nth-child(3) input:checked) { --seg-i: 2; }
.choice-segmented--compact::before {
    content: ""; position: absolute; top: var(--seg-aire); bottom: var(--seg-aire); left: var(--seg-aire);
    width: calc((100% - 2 * var(--seg-aire) - (var(--seg-n) - 1) * var(--seg-hueco)) / var(--seg-n));
    border-radius: calc(var(--radius-field) - 2px); background: var(--primary);
    box-shadow: 0 2px 8px color-mix(in srgb, var(--primary) 35%, transparent);
    transform: translateX(calc(var(--seg-i) * (100% + var(--seg-hueco))));
    opacity: 0; transition: transform .22s cubic-bezier(.2, .8, .2, 1), background-color .22s, opacity .15s;
}
.choice-segmented--compact:has(input:checked)::before { opacity: 1; }
/* El rótulo va encima de la ficha y la pastilla queda transparente: el color
   lo pone la ficha, el rótulo solo cambia de tinta. */
.choice-segmented--compact .choice-pill { z-index: 1; transition: color .22s; }
.choice-segmented--compact .choice-pill:has(input:checked) { background: transparent; }
@media (prefers-reduced-motion: reduce) {
    .choice-segmented--compact::before, .choice-segmented--compact .choice-pill { transition: none; }
}
/* Al ALTO DE UN BOTÓN de la casa (`--boton`, Renzo, 2026-10-29): el mismo
   interruptor cuando es la decisión de la tarjeta y no una fila de una lista
   (Encima / Debajo de un sugerido). La pastilla mide `--btn-h` menos el
   relleno y el borde de la pista, así la pista entera mide lo que un botón. */
.choice-segmented--compact.choice-segmented--boton .choice-pill {
    min-height: calc(var(--btn-h) - 2 * var(--seg-aire) - 2px); padding-block: 0; padding-inline: var(--space-200);
    align-items: center; font-size: var(--fs-body); font-weight: 600;
}
/* En el teléfono, el segmentado chico va a lo ancho, en partes que se pueden
   partir en dos renglones: del ancho de sus rótulos se salía de la tarjeta.
   Nació para la fila de Mantenimiento y se mudó a la pieza cuando el de un
   sugerido se salió igual (2026-10-29). Va DESPUÉS de las reglas del
   segmentado: con el mismo peso, ganaban ellas. */
@media (max-width: 640px) {
    .choice-segmented.choice-segmented--compact { display: grid; width: 100%; grid-auto-columns: minmax(0, 1fr); }
    .choice-segmented.choice-segmented--compact .choice-pill {
        white-space: normal; text-align: center; justify-content: center; padding-inline: var(--space-100);
    }
}

/* ── Los dos caminos de la imagen: pegar una dirección o subir un archivo ── */
.quiz-modal-url { flex: 1; min-width: 0; }
/* La «o» que los separa, en su disco. Sin el disco es una letra suelta entre
   dos controles y se lee como parte del de al lado. */
.quiz-modal-or {
    flex: none; display: grid; place-items: center;
    width: 26px; height: 26px; border-radius: 50%;
    background: var(--bg); border: 1px solid var(--border);
    font-size: var(--fs-small); color: var(--text-light);
}
.quiz-modal-drop-title { font-weight: 600; color: var(--text); }
.quiz-modal-drop-hint { font-size: var(--fs-meta); color: var(--text-light); }
/* El ancho y el respiro valen a CUALQUIER profundidad, no solo en los hijos
   directos. Los campos de cada tipo viven dentro de un `<template x-if>`, que
   agrega un `<div>` en el medio: con el selector limitado a `>`, «URL del
   video» y «Epígrafe» salían del ancho que el navegador les diera —unos
   200px— al lado de campos que sí llenaban la fila. Se veía como maquetación
   descuidada y era un selector demasiado estrecho. */
.quiz-modal-body input:not([type="radio"]):not([type="color"]),
.quiz-modal-body select,
.quiz-modal-body textarea { width: 100%; }
.quiz-modal-body select, .quiz-modal-body textarea,
.quiz-modal-body .quiz-modal-image-row,
.quiz-modal-body > input, .quiz-modal-body > div > input {
    margin-bottom: var(--space-250);
}
/* El de una pareja o una opción respira menos: son una lista, y con el espacio
   grande se leen como campos sueltos. */
.quiz-modal-body .quiz-option input,
.quiz-modal-body .quiz-pair input { margin-bottom: 0; }
.quiz-modal-body .input-narrow { width: 120px; }
.quiz-modal-body .quiz-modal-image-row > input { margin-bottom: 0; }
.quiz-modal-body .quiz-modal-image { margin-bottom: var(--space-250); }
.quiz-modal-image {
    max-width: 100%; max-height: 160px; align-self: flex-start;
    border-radius: var(--radius-field);
}
.quiz-modal-foot {
    display: flex; justify-content: flex-end; gap: var(--space-100);
    margin-top: var(--space-200); padding-top: var(--space-200);
    border-top: 1px solid var(--border);
}
/* Los rótulos del modal pesan más que los de un formulario normal: son ocho en
   una columna angosta, y en gris claro la pantalla se lee como un borrador. */
/* Aire ARRIBA además de abajo. Los rótulos que no abren sección —Emoji,
   Subtítulo, Cuerpo, URL del video— no llevan raya delante, y sin margen
   propio quedaban pegados al control de encima.

   Y se cancela justo detrás de una raya, que ya puso el suyo: sumados daban
   el doble de aire en las cuatro secciones que sí la tienen, y una pantalla
   con dos ritmos distintos se lee como un error de alineación. */
.quiz-modal-label {
    display: block; margin: var(--space-200) 0 var(--space-100);
    font-size: var(--fs-body); font-weight: 700; color: var(--text);
}
.modal-rule + .quiz-modal-label,
.modal-rule + .field-label .quiz-modal-label { margin-top: 0; }

/* La dirección a la izquierda y el cuadro punteado a la derecha, como la
   referencia. El cuadro es un BOTÓN y no una zona de arrastre: `_upload.html`
   trae su propio input de archivo, y adentro de un `<dialog>` que vive dentro
   del formulario del bloque ese input viajaría en el POST del examen. */
/* El campo se alinea ARRIBA y el cuadro es cuadrado. Estirado a la altura del
   input se veía como un botón raro; cuadrado se lee como zona de imagen, que
   es lo que la referencia hace. */
/* CENTRADO en vertical, no arriba: los tres —la dirección, la «o» y la zona—
   son tres caminos del mismo paso, y alineados arriba la «o» quedaba a la
   altura del rótulo del primero en vez de entre los dos. */
.quiz-modal-image-row {
    display: flex; align-items: center; gap: var(--space-150);
}
/* La zona deja de ser un cuadro de 104 con un icono solo y pasa a DECIR qué
   es y qué acepta, como la referencia. En columna y más ancha: tres líneas
   apiladas en 104px de lado se parten en seis. */
.quiz-modal-drop {
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: var(--space-050); flex-shrink: 0;
    width: 190px; padding: var(--space-200) var(--space-150);
    border: 2px dashed var(--border-strong);
    border-radius: var(--radius-card); background: transparent; cursor: pointer;
    color: var(--text-light); text-align: center;
}
.quiz-modal-drop:hover {
    border-color: var(--primary); color: var(--primary);
    background: var(--primary-soft);
}
.quiz-modal-drop svg { width: 20px; height: 20px; }

.quiz-option {
    display: flex; align-items: center; gap: var(--space-150);
    margin-bottom: var(--space-100);
}
.quiz-option > input[type="radio"] { flex-shrink: 0; }
.quiz-option > input:not([type]) { flex: 1; min-width: 0; }
/* La × de quitar una opción va ROJA y sin recuadro, como la referencia: dentro
   del modal es la única acción destructiva, y con borde competía visualmente
   con el campo que está al lado. */
.quiz-option-drop {
    display: grid; place-items: center; flex-shrink: 0;
    width: 28px; height: 28px; padding: 0; border: 0; cursor: pointer;
    background: transparent; color: var(--danger-text);
    border-radius: var(--radius-field);
}
.quiz-option-drop:hover { background: var(--danger-bg); }
.quiz-option-drop svg { width: 18px; height: 18px; }

/* Una pareja del emparejar: dos campos y el signo que los une. Se guardan como
   «izquierda = derecha» en una línea —que es lo que `read_form` lee— y se
   editan como dos, porque escribir el signo a mano es un formato que se
   equivoca a la tercera fila. */
.quiz-pair {
    display: flex; align-items: center; gap: var(--space-100);
    margin-bottom: var(--space-100);
}
.quiz-pair > input { flex: 1; min-width: 0; }
.quiz-pair-sign {
    flex-shrink: 0; color: var(--text-light); font-weight: 700;
}

/* ── Las tarjetas de contenido de un examen ──────────────────────────────────
   Portada, información, video, producto y cierre. Se ven distintas de una
   pregunta A PROPÓSITO: quien está presentando un examen tiene que poder
   distinguir de un vistazo lo que le piden contestar de lo que solo le
   muestran, y con la misma caja las cinco se leen como preguntas sin opciones. */
.quiz-cover {
    padding: var(--space-300) var(--space-250); margin-bottom: var(--space-200);
    border-radius: var(--radius-lg); background: var(--primary-soft);
    text-align: center;
}
.quiz-cover.is-finish { background: var(--bg); }
/* El emoji de una portada usa `--fs-number`: ahí el emoji ES el contenido de
   la tarjeta, igual que una cifra en un tablero, y la escala está cerrada en
   nueve pasos a propósito —dos pasos nuevos para esto serían dos números
   sueltos con otro nombre—. */
.quiz-cover-emoji { font-size: var(--fs-number); display: block; margin-bottom: var(--space-100); }
.quiz-cover-title { margin: 0; font-size: var(--fs-page); font-weight: 700; }
.quiz-cover-sub { margin: var(--space-100) 0 0; color: var(--text-light); }

.quiz-fact, .quiz-product {
    display: flex; gap: var(--space-200); align-items: flex-start;
    padding: var(--space-200) var(--space-250); margin-bottom: var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-card);
    background: var(--card-bg);
}
.quiz-fact-emoji { font-size: var(--fs-page); flex-shrink: 0; }
.quiz-fact-title { margin: 0 0 var(--space-100); font-size: var(--fs-section); font-weight: 700; }
.quiz-media { width: 100%; max-width: 420px; border-radius: var(--radius-field); }
.quiz-media-link { color: var(--primary); font-weight: 600; }
.quiz-product-image {
    width: 120px; height: 120px; object-fit: cover; flex-shrink: 0;
    border-radius: var(--radius-field);
}
.quiz-product-price { margin: 0 0 var(--space-100); font-weight: 700; color: var(--primary-dark); }
.quiz-product-features { margin: var(--space-100) 0 0; padding-left: var(--space-250); color: var(--text-light); }

/* Una fila del emparejar: la izquierda fija y un desplegable con las derechas
   revueltas. Con `<select>` y no arrastrando, por lo mismo que reordenar un
   bloque tiene flechas: arrastrar no funciona con teclado. */
.quiz-match-row {
    display: flex; align-items: center; gap: var(--space-150);
    margin-bottom: var(--space-100); flex-wrap: wrap;
}
.quiz-match-left { flex: 1; min-width: 140px; font-weight: 600; }
.quiz-match-row select { flex: 1; min-width: 160px; }

/* ── Reordenar los bloques arrastrando ───────────────────────────────────────
   Es un MODO y no un estado permanente: las asas al lado de cada bloque, todo
   el tiempo, compiten con el contenido — que es lo que alguien vino a editar.
   Y las flechas ↑↓ no se retiran: arrastrar no funciona con teclado, y un
   formulario de 21 bloques tiene que poder reordenarse sin ratón. */
.block-drag { display: none; }
.is-reordering .block-drag {
    display: inline-flex; align-items: center; cursor: grab;
    color: var(--text-light); font-size: var(--fs-glyph); line-height: 1;
    padding-right: var(--space-100);
}
.is-reordering .block-drag:active { cursor: grabbing; }
/* En modo reordenar el contenido del bloque se pliega: lo que se está
   ordenando es la SECUENCIA, y con los editores abiertos no entran cinco
   bloques en una pantalla. */
.is-reordering .block-edit, .is-reordering .block-edit-none { display: none; }
.is-reordering .block-card { margin-bottom: var(--space-075); }
.is-reordering .block-between { display: none; }
.block-card.is-dragging { opacity: .4; }

/* Los dos botones del pie son PUNTEADOS y del mismo ancho, como la referencia:
   son las dos acciones sobre la lista entera, y con uno relleno y otro no se
   leen como una principal y una secundaria que no lo son. */
.block-foot-btn {
    display: flex; align-items: center; justify-content: center; gap: var(--space-100);
    width: 100%; padding: var(--space-200); cursor: pointer;
    border: 2px dashed var(--border); border-radius: var(--radius-card);
    background: transparent; color: var(--text-light);
    font-size: var(--fs-body); font-weight: 600; font-family: inherit;
}
.block-foot-btn:hover {
    border-color: var(--primary); color: var(--primary);
    background: var(--primary-soft);
}
.block-foot-btn[aria-pressed="true"] {
    border-style: solid; border-color: var(--primary);
    background: var(--primary-soft); color: var(--primary-dark);
}

/* El asa de una tarjeta del examen. Va SIEMPRE visible, a diferencia de la de
   los bloques: una fila de examen es una línea de texto y el asa no compite
   con nada, mientras que la de un bloque se pone al lado de su editor. */
.quiz-row-drag {
    flex-shrink: 0; cursor: grab; color: var(--text-light);
    font-size: var(--fs-glyph); line-height: 1;
}
.quiz-row-drag:active { cursor: grabbing; }
.boxed-row[data-dragging="true"] { opacity: .4; }

/* El «Editar» de una tarjeta de formulario: ícono y rótulo juntos. Un ícono
   solo obliga a pasar por encima para saber qué hace, y en el pie de la
   tarjeta hay lugar para decirlo. */
.onboarding-card-edit {
    display: inline-flex; align-items: center; gap: var(--space-075);
}
.onboarding-card-edit svg { width: 15px; height: 15px; }

/* La pastilla de audiencia: GRIS, no verde. Al lado de «ACTIVA» —que sí es
   verde porque es un estado— dos pastillas verdes se leen como una sola cosa
   partida en dos, y son cosas distintas: quién la llena y si está encendida. */
.badge-muted {
    padding: var(--space-025) var(--space-100); border-radius: 999px;
    background: var(--bg); color: var(--text-light);
    font-size: var(--fs-badge); font-weight: 700;
    text-transform: uppercase; letter-spacing: .03em;
}

/* ── Tienda: una fila DESACTIVADA ──────────────────────────────────────────
   Un filtro inactivo sigue en la lista —desactivar no es borrar— y tiene que
   leerse como apagado sin desaparecer. La pastilla sola no alcanza: con diez
   filas, distinguir cuál está apagada obliga a leer la tercera columna de cada
   una. */
.table tr.row-off > td { opacity: .55; }

/* ── Menú de la tienda: la segunda fila es una HIJA ────────────────────────
   Sin la sangría, un menú de dos niveles se lee como una lista plana y no hay
   forma de saber qué cuelga de qué. El fondo apenas más oscuro es lo que hace
   que la sangría se lea como pertenencia y no como un error de alineación. */
.table tr.row-child > td { background: var(--bg); }
.table td.cell-indent { padding-left: var(--space-300); }


/* ── Editor de código del tema ─────────────────────────────────────────────
   Dos columnas: el árbol a la izquierda y el archivo a la derecha. En una sola
   columna, elegir un archivo empuja el editor fuera de la pantalla y hay que
   subir para cambiar de archivo — con 152, eso es todo el trabajo. */
.editor-split { display: grid; grid-template-columns: 260px 1fr;
                gap: var(--space-200); align-items: start; }
@media (max-width: 900px) { .editor-split { grid-template-columns: 1fr; } }

/* El árbol hace scroll propio: 152 archivos no caben, y sin esto el scroll es
   de la página entera y el editor se va con él. */
.editor-tree { max-height: 70vh; overflow-y: auto; }
.editor-folder { font-size: var(--fs-label); color: var(--pill-text);
                 text-transform: uppercase; letter-spacing: .04em;
                 margin: var(--space-150) 0 var(--space-050); }
.editor-files { list-style: none; margin: 0; padding: 0; }
/* `--text` y NO `--banner-text`. El segundo es blanco puro: es el color del
   texto sobre el banner OSCURO, y acá el fondo es una tarjeta blanca. Con él,
   el árbol de archivos del editor se pintaba blanco sobre blanco — estaba
   entero y no se veía. */
.editor-file { display: block; padding: var(--space-050) var(--space-100);
               border-radius: var(--radius-field); font-size: var(--fs-small);
               color: var(--text); text-decoration: none;
               word-break: break-all; }
.editor-file:hover { background: var(--bg); }
.editor-file.is-open { background: var(--primary-soft); font-weight: 600; }

/* El archivo ROTO. Un punto y no solo color: el color solo no lo distingue
   quien no ve la diferencia entre rojo y gris. */
.editor-file.file-broken::before { content: '● '; color: var(--primary-dark); }

.editor-pane { min-width: 0; }
/* CodeMirror montado sobre el textarea. Clases generadas por JS: no las ve
   el barrido de plantillas. La altura replica la del textarea de respaldo. */
.editor-pane .CodeMirror { height: 65vh; border: 1px solid var(--border);
                           border-radius: var(--radius-field);
                           font-size: var(--fs-small); }
.editor-errors { margin-top: var(--space-100); }
.editor-area { width: 100%; font-family: ui-monospace, monospace;
               font-size: var(--fs-small); line-height: 1.5;
               white-space: pre; overflow-x: auto; tab-size: 2; }

/* La lista de errores del tema, en el editor y en la importación. */
.error-list { margin: var(--space-100) 0; padding-left: var(--space-200);
              font-size: var(--fs-small); color: var(--text); }

/* Un título dentro del formulario de ajustes del tema. `header` y `paragraph`
   no guardan valor: organizan el panel, y sin distinguirse del rótulo de un
   campo no organizan nada. */
.setting-header { grid-column: 1 / -1; font-weight: 600;
                  margin: var(--space-200) 0 0; }
.setting-note { grid-column: 1 / -1; }

/* ── El customizer ───────────────────────────────────────────────────────── */
/* El layout es el del customizer de tiendapp, PORTADO: un panel angosto y la
   tienda ocupando todo lo demás, dentro de un marco con borde propio. El
   panel lo pinta customizer.js; lo que sigue en clases `customizer-*` es el
   RESPALDO server-rendered para operar sin JavaScript. */
.vento-customizer { display: flex; height: calc(100vh - 230px);
                    min-height: 520px; border: 1px solid var(--border);
                    border-radius: var(--radius-card); overflow: hidden;
                    background: var(--card-bg); box-shadow: var(--shadow); }
.vento-customizer__panel { width: 380px; flex-shrink: 0; overflow-y: auto;
                           display: flex; flex-direction: column;
                           border-right: 1px solid var(--border);
                           background: var(--bg); }
.vento-customizer__panel-head { padding: var(--space-100) var(--space-150);
                                border-bottom: 1px solid var(--border); }
.vento-customizer__pages { display: flex; gap: var(--space-050); }
.vento-customizer__pages select { flex: 1; min-width: 0; }
.vento-customizer__panel-body { padding: var(--space-100) var(--space-150) var(--space-200);
                                flex: 1; }
.vento-customizer__preview { flex: 1; position: relative;
                             background: var(--bg); }
.vento-customizer__preview iframe { width: 100%; height: 100%; border: 0;
                                    background: #fff; }
/* Dentro de una FICHA nadie le da alto (el customizer y el editor la meten
   en una columna que llena la tarjeta): `is-ficha` le pone el suyo. Nació
   con el correo del equipo para aprobar (2026-10-14). */
.vento-customizer__preview.is-ficha { flex: none; height: min(80vh, 900px);
                                      min-height: 480px;
                                      border-radius: var(--radius);
                                      overflow: hidden; }
.vento-customizer__nodomain { padding: var(--space-250); }
/* En escritorio el alto es el que le queda a la TARJETA, no una resta a mano.
   La tarjeta de contenido ya mide la ventana (ver «En escritorio se desplaza
   la TARJETA»), así que el customizer la llena en columna y llega hasta su
   relleno de abajo. El `100vh - 230px` de antes era de cuando la página
   entera se desplazaba, y con la banda nueva dejaba ~50px muertos debajo
   (Renzo, 2026-10-01). El `min-height` se queda: en una ventana bajita la
   tarjeta se desplaza en vez de aplastar el editor. */
@media screen and (min-width: 901px) {
  .content:has(> .vento-customizer) { display: flex; flex-direction: column; }
  .content > .vento-customizer { flex: 1 1 0; height: auto; }
}
@media (max-width: 900px) {
  /* Apilado: el panel arriba con su propio tope de alto, la tienda abajo. */
  .vento-customizer { flex-direction: column; height: auto; }
  .vento-customizer__panel { width: 100%; max-height: 45vh;
                             border-right: 0;
                             border-bottom: 1px solid var(--border); }
  .vento-customizer__preview { min-height: 60vh; }
}

/* El aviso de que ninguna página publicada usa la plantilla elegida. */
.bc-template-notice { position: absolute; top: 12px; left: 50%;
  transform: translateX(-50%); max-width: 85%; padding: var(--space-100) var(--space-200);
  background: #fdf6df; border: 1px solid #e8d48a; border-radius: var(--radius-field);
  color: #7a6410; font-size: var(--fs-small); font-weight: 600;
  text-align: center; box-shadow: var(--shadow); z-index: 5; }
.bc-template-notice[hidden] { display: none; }

/* La fila de una sección en el panel. */
.bc-section { display: flex; align-items: center; gap: var(--space-100); padding: var(--space-100) var(--space-150);
  border: 1px solid var(--border); border-radius: var(--radius-field);
  margin-bottom: var(--space-075); background: var(--card-bg); cursor: pointer;
  font-size: var(--fs-small); color: var(--text); transition: border-color .15s; }
.bc-section:hover { border-color: var(--primary); }
.bc-section.hidden-section { opacity: .45; }
.bc-section .drag { cursor: grab; color: var(--text-faint); }
.bc-section .name { flex: 1; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bc-section .actions { display: flex; gap: var(--space-025); }
.bc-section .actions button { border: 0; background: none;
  color: var(--text-faint); padding: var(--space-025) var(--space-050); cursor: pointer;
  font-family: inherit; font-size: var(--fs-small); }
.bc-section .actions button:hover { color: var(--primary); }
.bc-add-section { width: 100%; margin-top: var(--space-050); }

/* Campos generados desde el schema. */
/* Los campos del panel son los de la casa (`.field`, 2026-09-28): el
   rótulo en mayúsculas y letra espaciada que tenía el customizer era de
   otro sistema. Acá solo va lo que el panel angosto necesita: los controles
   a todo el ancho. */
.vento-customizer__panel .field input[type=text],
.vento-customizer__panel .field input[type=number],
.vento-customizer__panel .field textarea,
.vento-customizer__panel .field select { width: 100%; }
/* El valor de un deslizador, a la derecha de su rótulo. */
.range-value { margin-left: auto; font-size: var(--fs-small);
  color: var(--text-light); font-variant-numeric: tabular-nums; }
/* La cabecera del formulario abierto: la baldosa de la fila de donde vino
   y su nombre. */
.customizer-form-head { display: flex; align-items: center;
  gap: var(--space-100); margin: var(--space-100) 0 var(--space-200);
  padding-bottom: var(--space-150); border-bottom: 1px solid var(--border); }
.customizer-form-head h3 { margin: 0; font-size: var(--fs-body);
  font-weight: 600; color: var(--text); }
/* La casilla del panel es la pastilla de la casa (`.field-check`): la frase
   entera es el objetivo de clic, y no un cuadradito suelto debajo de su
   rótulo como salía hasta el 2026-10-01. */
.vento-customizer__panel .field > .field-check { max-width: 100%; }
/* Dentro de un pliegue de Diseño, el primer subtítulo del tema no necesita
   el aire de arriba: ya lo da el pliegue. */
.vento-customizer__panel .fold-body > .bc-heading:first-child { margin-top: 0; }
.bc-help a { color: var(--primary); }
/* El selector de página con su rótulo, y aire antes de la estructura. */
.vento-customizer__pages-field { margin-bottom: var(--space-200); }
.bc-back { font-size: var(--fs-small); color: var(--primary); cursor: pointer;
  margin-bottom: var(--space-100); display: inline-block; font-weight: 600; }
/* El subtítulo del tema parte el formulario en grupos: la raya va ARRIBA,
   cerrando el grupo anterior, y el título abre el suyo con la letra de un
   rótulo de sección, no la de un campo más (2026-10-01). */
.bc-heading { font-weight: 600; font-size: var(--fs-body);
  color: var(--text); margin: var(--space-250) 0 var(--space-150);
  padding-top: var(--space-200); border-top: 1px solid var(--border); }
.bc-help { font-size: var(--fs-small); color: var(--text-light);
  margin: var(--space-050) 0 var(--space-100); line-height: 1.5; }
.bc-img-thumb { width: 100%; height: 96px; object-fit: cover;
  border-radius: var(--radius-field); border: 1px solid var(--border);
  margin-bottom: var(--space-075); background: var(--bg); display: block; }

/* Los radios del tema: pocos y cortos en el control segmentado de la
   alineación; los largos, tarjetas en lista (`.option-cards--lista`). */
/* `.bc-seg .bc-align` y no suelta: cada opción es un `<label>` dentro de
   `.field`, y `.field label` (0,1,1) le ganaba a `.bc-align` (0,1,0) con su
   `display: block` — los íconos salían pegados a la izquierda de cada tercio
   (2026-10-01). La elegida es una pastilla DENTRO del contorno, con aire
   alrededor, no un tercio pintado de borde a borde. */
.bc-seg { display: flex; gap: var(--space-050); padding: var(--space-050);
  border: 1px solid var(--border); border-radius: var(--radius-field);
  background: var(--card-bg); }
.bc-seg .bc-align { flex: 1; display: flex; align-items: center;
  justify-content: center; gap: var(--space-100); min-width: 0;
  padding: var(--space-100) var(--space-075); margin: 0; border-radius: calc(var(--radius-field) - 3px);
  cursor: pointer; color: var(--text-light);
  font-size: var(--fs-small); font-weight: 500;
  transition: background .15s, color .15s; position: relative; }
.bc-align input { position: absolute; opacity: 0; pointer-events: none; }
.bc-seg .bc-align:hover { color: var(--text); background: var(--bg); }
.bc-seg .bc-align:has(input:checked) { background: var(--primary-soft);
  color: var(--primary); font-weight: 600; }
.bc-seg .bc-align:has(input:focus-visible) {
  outline: 2px solid var(--primary); outline-offset: 1px; }
.bc-align svg { display: block; flex: none; }
/* Un rótulo que no cabe se corta con «…» en vez de partir el segmento en dos
   renglones; el `title` del tema no existe, así que el corte es el último
   recurso: `radioSeg` ya manda a la lista lo que no cabe. */
.bc-seg .bc-align span { min-width: 0; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; }

/* Bloques dentro de una sección. */
.bc-blocks { list-style: none; margin: 0 0 var(--space-100); padding: 0; }
.bc-blocks li { display: flex; align-items: center; gap: var(--space-100); padding: var(--space-100) var(--space-100);
  border: 1px solid var(--border); border-radius: var(--radius-field);
  margin-bottom: var(--space-050); background: var(--card-bg); font-size: var(--fs-small); }
.bc-blocks li a { cursor: pointer; color: var(--text); text-decoration: none; }
.bc-blocks li a[data-act="block-edit"] { flex: 1; font-weight: 600;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bc-blocks li a:hover { color: var(--primary); }
.bc-blocks .bc-drag { cursor: grab; color: var(--text-faint); }
.bc-off { opacity: .45; }
/* El modal de agregar —secciones y bloques—: lista con búsqueda, nunca un
   prompt(). */
.bc-modal-overlay { position: fixed; inset: 0; z-index: 10000;
  background: rgba(13, 11, 22, 0.45); display: flex;
  align-items: flex-start; justify-content: center; padding: 9vh var(--space-200) var(--space-200); }
.bc-modal { background: var(--card-bg); border-radius: var(--radius-card);
  width: 420px; max-width: 100%; max-height: 74vh; display: flex;
  flex-direction: column; box-shadow: 0 18px 50px rgba(0, 0, 0, 0.22);
  border: 1px solid var(--border); }
.bc-modal__head { display: flex; align-items: center;
  justify-content: space-between; padding: var(--space-200) var(--space-200) var(--space-100); }
.bc-modal__head h3 { margin: 0; font-size: var(--fs-section); font-weight: 700; }
.bc-modal__close { background: none; border: none; font-size: var(--fs-page);
  line-height: 1; cursor: pointer; color: var(--text-faint); padding: var(--space-025) var(--space-075);
  border-radius: var(--radius-field); }
.bc-modal__close:hover { background: var(--bg); color: var(--text); }
.bc-modal__search { margin: 0 var(--space-200) var(--space-100); }
.bc-modal__list { overflow-y: auto; padding: 0 var(--space-100) var(--space-150); }
.bc-modal__item { display: flex; flex-direction: column; gap: 1px; width: 100%;
  text-align: left; background: none; border: none; cursor: pointer;
  padding: var(--space-100) var(--space-100); border-radius: var(--radius-field); font-family: inherit; }
.bc-modal__item:hover { background: var(--primary-soft); }
.bc-modal__item-label { font-size: var(--fs-small); font-weight: 600;
  color: var(--text); }
.bc-modal__item-type { font-size: var(--fs-label); color: var(--text-faint); }
.bc-modal__empty { padding: var(--space-200) var(--space-100); font-size: var(--fs-small);
  color: var(--text-faint); }

/* ── El respaldo sin JavaScript del customizer ──────────────────────────── */
.customizer-panel { max-height: 78vh; overflow-y: auto; }
.customizer-form { max-height: 78vh; overflow-y: auto; min-width: 0; }

/* Las tres secciones del panel del editor de bloques —Pasos, Bloques,
   Ajustes— como capítulos: rótulo protagonista y una raya entre uno y otro
   (Renzo, 2026-09-24). Por hermano adyacente y no `:first-child`: el
   `<template>` de Alpine que decide si hay Pasos es un elemento más, y con
   él delante Bloques nunca sería el primero. El rótulo chico en versalitas
   (`.customizer-title`) se queda para las sub-secciones de un bloque. */
/* LAS PESTAÑAS de un panel (`ui/_tabs.html`, 2026-09-24): fila con glifo y
   rótulo, la activa subrayada con el acento. Nacieron en el editor de
   bloques —Contenido · Diseño · SEO · Ajustes— y son de la casa. */
.tabs { display: flex; gap: var(--space-025); margin: 0 0 var(--space-200);
        border-bottom: 1px solid var(--border); }
.tab { display: inline-flex; align-items: center; justify-content: center;
       gap: var(--space-050); flex: 1 1 0; min-width: 0;
       padding: var(--space-100) var(--space-050); margin-bottom: -1px; border: 0;
       border-bottom: 2px solid transparent; background: none; font: inherit;
       /* 13 px, el texto de la casa (Renzo, 2026-11-04): a 12 las pestañas
          de una ficha se leían como notas al pie. */
       font-size: var(--fs-body); font-weight: 600; color: var(--text-light);
       cursor: pointer; }
.tab svg { width: 16px; height: 16px; flex: none; }
.tab span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tab:hover { color: var(--text); }
.tab.is-active { color: var(--primary-dark); border-bottom-color: var(--primary); }
.tab:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px;
                     border-radius: var(--radius-sm); }

/* ── Elegir en tarjeta (`ui/_option_cards.html`, 2026-10-08) ────────────────
   Cada opción es una tarjeta que ES el `<label>` de su radio o su casilla.
   En FILA (Renzo, 2026-10-30, «Dónde aparece» de Sugeridos): la marca a la
   izquierda, la baldosa y, a su lado, el rótulo con la consecuencia debajo.
   Igual para elegir una (radio) y elegir varias (casilla). Con grilla y no
   con un envoltorio, para que `customizer.js` siga pintando el mismo marcado.
   Las de IMAGEN (portadas, colores) siguen en columna: la miniatura manda. */
.option-cards { margin: 0; padding: 0; border: 0; min-width: 0; }
.option-cards-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-150); }
.option-card {
    position: relative; display: grid; align-items: center;
    grid-template-columns: auto auto minmax(0, 1fr) auto;
    grid-template-areas: "marca baldosa rotulo extra" "marca baldosa detalle extra";
    column-gap: var(--space-150); row-gap: var(--space-025);
    --oc-pad: var(--space-200); --oc-pad-der: var(--space-200);
    padding: var(--oc-pad);
    border: 1px solid var(--border); border-radius: var(--radius);
    background-color: var(--card-bg); cursor: pointer;
    transition: border-color .14s ease, background-color .14s ease, box-shadow .14s ease;
}
.option-card:not(:has(> .tile)) {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "marca rotulo extra" "marca detalle extra";
}
.option-card:not(:has(> .option-card-detail)) { grid-template-areas: "marca baldosa rotulo extra"; }
.option-card:not(:has(> .tile)):not(:has(> .option-card-detail)) { grid-template-areas: "marca rotulo extra"; }
.option-card > input { grid-area: marca; margin: 0; }
.option-card > .tile { grid-area: baldosa; }
.option-card > .option-card-label { grid-area: rotulo; align-self: end; }
.option-card > .option-card-detail { grid-area: detalle; align-self: start; }
.option-card:not(:has(> .option-card-detail)) > .option-card-label { align-self: center; }
.option-card > .option-card-audio { grid-area: extra; margin-top: 0; }
/* Con imagen: en columna, la miniatura arriba y la marca encima de ella. */
.option-card:has(> .option-card-imagen) {
    display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-050);
    --oc-pad-der: calc(var(--space-200) + 24px); padding-right: var(--oc-pad-der);
}
.option-card:has(> .option-card-imagen) > input { position: absolute; top: var(--space-200); right: var(--space-200); z-index: 1; }
.option-card:has(> .option-card-imagen) > .option-card-audio { margin-top: var(--space-100); }
/* Dentro de un `.field` —que es como va en una grilla de formulario, para
   que traiga el margen de los campos de al lado (el checkout de la tienda,
   el tipo de mantenimiento)— la tarjeta sigue siendo tarjeta. Es un
   `<label>`, y `.field label` (0,1,1) le ganaba a `.option-card` (0,1,0):
   bloque, letra chica y gris, y el rótulo y su frase en un mismo renglón
   (2026-10-23). */
.field .option-card {
    display: grid; margin-bottom: 0;
    font-size: inherit; font-weight: inherit; color: inherit;
}
.field .option-card:has(> .option-card-imagen) { display: flex; }
.option-card:hover { border-color: var(--border-strong); }
.option-card.is-disabled { opacity: .55; cursor: not-allowed; }
.option-card .tile { background-color: var(--primary-soft); color: var(--text); }
.option-card:has(> .option-card-imagen) .tile { margin-bottom: var(--space-100); }
.option-card-label { font-size: var(--fs-body); font-weight: 600; color: var(--text); }
.option-card-detail { font-size: var(--fs-small); line-height: 1.4; color: var(--text-light); }
/* La marcada no se tiñe: la marca ya dice cuál, y la baldosa toma el color
   (la cara de las de Sugeridos que salió el 2026-10-29). */
.option-card:has(input:checked) { border-color: var(--border-strong); }
.option-card:has(input:checked) .tile { color: var(--primary); }
.option-card:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 2px; }
/* Con imagen (2026-10-10, las portadas del perfil): la miniatura encabeza la
   tarjeta, de borde a borde, y el radio queda encima de ella. */
.option-card-imagen {
    display: block; width: calc(100% + var(--oc-pad) + var(--oc-pad-der)); aspect-ratio: 21 / 9;
    margin: calc(-1 * var(--oc-pad)) calc(-1 * var(--oc-pad-der)) var(--space-100) calc(-1 * var(--oc-pad));
    border-radius: var(--radius) var(--radius) 0 0; object-fit: cover; background: var(--bg);
}
.option-cards--compactas .option-cards-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
/* Con baldosa, la tarjeta va en un renglón —marca, baldosa y texto (2026-10-30)—
   y en 140px el texto quedaba de una palabra por línea («Cuándo sale» de Nueva
   campaña, Renzo 2026-10-31). Las de foto y las de solo texto siguen en 140. */
.option-cards--compactas .option-cards-grid:has(> .option-card > .tile) { grid-template-columns: repeat(auto-fill, minmax(min(100%, 240px), 1fr)); }
.option-cards--compactas .option-card { --oc-pad: var(--space-150); padding: var(--oc-pad); column-gap: var(--space-100); }
.option-cards--compactas .option-card:has(> .option-card-imagen) { --oc-pad-der: var(--space-150); padding-right: var(--oc-pad-der); }
.option-cards--compactas .option-card:has(> .option-card-imagen) > input { top: var(--space-100); right: var(--space-100); }
/* En LISTA (2026-10-19, los radios del customizer): una columna apretada para
   un panel angosto, opciones de una línea sin consecuencia que explicar. La
   marca a la izquierda, como en toda tarjeta (2026-10-30). Con dos clases
   para ganarle a `.field label` (0,1,1) cuando la lista va dentro de un
   `.field`: le ponía `display: block`, negrita y medio renglón de margen. */
.option-cards--lista .option-cards-grid { grid-template-columns: 1fr; gap: var(--space-075); }
.option-cards--lista .option-card {
    --oc-pad: 10px; column-gap: var(--space-100);
    margin: 0; padding: var(--oc-pad) var(--space-150);
    border-radius: var(--radius-field);
    font-size: var(--fs-small); font-weight: 500; color: var(--text);
}
.option-cards--lista .option-card-label { font-size: var(--fs-small); font-weight: 500; line-height: 1.35; }
.option-cards--lista .option-card:has(input:checked) .option-card-label { font-weight: 600; color: var(--primary); }
/* El ▶ de una opción que es un sonido (2026-10-12, el timbre de la barra):
   `timbre.js` le pone `is-sonando` mientras suena. */
.option-card-audio {
    display: inline-grid; place-items: center; width: 30px; height: 30px; margin-top: var(--space-100);
    padding: 0; border: 1px solid var(--border); border-radius: 50%; cursor: pointer;
    background: var(--card-bg); color: var(--text); transition: transform .12s ease;
}
.option-card-audio:hover { transform: scale(1.06); }
.option-card-audio svg { width: 12px; height: 12px; }
.option-card-audio__off { display: none; }
.option-card-audio.is-sonando { border-color: var(--primary); background: var(--primary); color: #fff; }
.option-card-audio.is-sonando .option-card-audio__on { display: none; }
.option-card-audio.is-sonando .option-card-audio__off { display: block; }

/* MODO LATERAL de `ui/_tabs.html` (2026-10-08, «Nueva conversación» del
   chat): una columna de pestañas con su baldosa y una línea que dice qué
   es cada una. La activa con el fondo de la marca, no subrayada: en
   columna el subrayado no se lee como «esta». En el teléfono vuelve a ser
   fila, sin la línea de detalle, y se desplaza a lo ancho si no cabe. */
/* Forma de FICHAS (2026-10-09, el buscador del chat): pastillas con borde
   que toman el ancho de su rótulo; la elegida, rellena del acento suave. */
.tabs--fichas { flex-wrap: wrap; gap: var(--space-075); margin: 0; border-bottom: 0; }
.tabs--fichas .tab { flex: none; gap: var(--space-075); padding: var(--space-050) var(--space-150); margin: 0;
                     border: 1px solid var(--border); border-radius: 999px;
                     background-color: var(--card-bg); font-size: var(--fs-body);
                     font-weight: 500; color: var(--text); }
.tabs--fichas .tab svg { width: 15px; height: 15px; color: var(--text-light); }
.tabs--fichas .tab:hover { border-color: var(--primary-soft-border); }
/* También por `aria-selected`: htmx, al intercambiar un fragmento, le
   devuelve la `class` a los elementos del mismo `id` y pisa el `is-active`
   que puso Alpine; el atributo sí sobrevive. */
.tabs--fichas .tab.is-active,
.tabs--fichas .tab[aria-selected="true"] { border-color: transparent; background-color: var(--primary-soft);
                                           color: var(--primary-dark); font-weight: 600; }
.tabs--fichas .tab.is-active svg,
.tabs--fichas .tab[aria-selected="true"] svg { color: var(--primary); }
.tabs--fichas .tab:focus-visible { border-radius: 999px; outline-offset: 2px; }
.tabs--lateral { flex-direction: column; gap: var(--space-050); margin: 0; border-bottom: 0; }
.tabs--lateral .tab {
    flex: none; justify-content: flex-start; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-150); margin: 0;
    border: 0; border-radius: var(--radius); text-align: left;
}
.tabs--lateral .tab .tile { background-color: var(--card-bg); color: var(--text);
                            box-shadow: inset 0 0 0 1px var(--border); }
.tabs--lateral .tab:hover { background-color: var(--bg); }
.tabs--lateral .tab.is-active { background-color: var(--primary-soft); border: 0; }
.tabs--lateral .tab.is-active .tile { background-color: var(--card-bg); color: var(--primary);
                                      box-shadow: none; }
.tab-texto { display: flex; flex-direction: column; gap: var(--space-025); min-width: 0; }
/* En fila (2026-10-18): la segunda línea, chica y apagada, como en la columna. */
.tabs:not(.tabs--lateral) .tab-texto { align-items: flex-start; text-align: left; }
.tabs:not(.tabs--lateral) .tab-detalle { font-size: var(--fs-small); font-weight: 400; color: var(--text-light); }
.tabs--lateral .tab-rotulo { font-size: var(--fs-body); font-weight: 600; color: var(--text); }
.tabs--lateral .tab-detalle {
    font-size: var(--fs-small); font-weight: 400; line-height: 1.35;
    color: var(--text-light); white-space: normal;
}
@media (max-width: 640px) {
    .tabs--lateral { flex-direction: row; overflow-x: auto; }
    .tabs--lateral .tab-detalle { display: none; }
}

/* MODO ENLACES de `ui/_tabs.html`: pestañas que navegan (el canvas del chat,
   2026-10-07). Toman el ancho de su rótulo y no se reparten la fila; si no
   caben, bajan de renglón —nunca `overflow`, que recortaría el «⋯»—. El
   subrayado va en el `.tab-item`, que abraza la pestaña y su menú. */
.tabs--enlaces { flex-wrap: wrap; align-items: stretch; gap: 0 var(--space-050); }
.tabs--enlaces .tab-item { display: inline-flex; align-items: center; flex: none;
                           max-width: 240px; margin-bottom: -1px;
                           border-bottom: 2px solid transparent; }
.tabs--enlaces .tab-item.is-active { border-bottom-color: var(--primary); }
.tabs--enlaces .tab { flex: 0 1 auto; padding: var(--space-100) var(--space-100); margin: 0; border: 0;
                      text-decoration: none; }
.tabs--enlaces .tab.is-active { border: 0; }
/* El «⋯» de cada pestaña: chico, y solo a la vista en la activa o al pasar
   por encima (con teclado, al recibir el foco). */
.tabs--enlaces .tab-item .menu-trigger { padding: var(--space-025); opacity: 0; }
.tabs--enlaces .tab-item.is-active .menu-trigger,
.tabs--enlaces .tab-item:hover .menu-trigger,
.tabs--enlaces .tab-item .menu-trigger:focus-visible,
.tabs--enlaces .tab-item .menu[open] .menu-trigger { opacity: 1; }
.tabs--enlaces .tab-item .menu-trigger svg { width: 15px; height: 15px; }
/* El «+» del final es un `<summary>` con cara de pestaña. */
.tabs--enlaces > .menu { display: inline-flex; align-items: center; }
.tabs--enlaces summary.tab { list-style: none; padding: var(--space-100); }
.tabs--enlaces summary.tab::-webkit-details-marker { display: none; }

/* El TÍTULO del editor: el nombre de la plantilla, grande y con lápiz.
   El lápiz lo vuelve casilla en su sitio; Enter, Esc o salir lo cierran. */
.editor-title { display: flex; align-items: center; gap: var(--space-100);
                margin: 0 0 var(--space-050); }
.editor-title-name { flex: 1; min-width: 0; margin: 0;
                     font-size: var(--fs-section); font-weight: 700;
                     overflow-wrap: anywhere; }
.editor-title-input { flex: 1; min-width: 0; font-size: var(--fs-section);
                      font-weight: 700; }
.editor-title-hint { margin: 0 0 var(--space-100); font-size: var(--fs-small);
                     color: var(--pill-text); }
.editor-open-preview { margin-bottom: var(--space-200); }

/* La TARJETA DE CONSEJO (`ui/_tip.html`): glifo, título, texto y ×. */
.tip { display: flex; gap: var(--space-100); align-items: flex-start;
       margin-top: var(--space-200); padding: var(--space-150);
       border-radius: var(--radius-field); background: var(--primary-soft);
       border: 1px solid var(--primary-soft-border); }
.tip-icon { width: 20px; height: 20px; flex: none; color: var(--primary);
            margin-top: var(--space-025); }
.tip-body { flex: 1; min-width: 0; }
.tip-title { margin: 0 0 var(--space-025); font-weight: 600; font-size: var(--fs-small); }
.tip-text { margin: 0; font-size: var(--fs-small); color: var(--text-light);
            line-height: 1.45; }
/* El consejo en color de advertencia (`tone="aviso"`): lo que hay que mirar
   antes de firmar un pendiente (2026-10-25). */
.tip-aviso { background: var(--warn-bg); border-color: transparent; }
.tip-aviso .tip-icon, .tip-aviso .tip-title { color: var(--warn-text); }
.tip-aviso .tip-text { color: var(--text); }
/* La nota que dice alguien (`cara=1`): el glifo en su círculo, como la
   cara de quien habla, y el texto a tamaño de lectura (El Jefe en La idea,
   Renzo 2026-10-31). */
.tip-cara { gap: var(--space-200); align-items: center; padding: var(--space-200); }
.tip-cara .tip-icon { width: 44px; height: 44px; padding: var(--space-150); margin: 0;
                      border-radius: 999px; background: var(--primary-soft-border); }
.tip-cara .tip-title { font-size: var(--fs-section); color: var(--text); }
.tip-cara .tip-text { font-size: var(--fs-body); }
/* Con botón (`href` + `cta`): el botón a la derecha, centrado con el texto;
   si la columna no le deja 240px al texto, baja debajo. */
.tip-con-accion { flex-wrap: wrap; align-items: center; }
.tip-con-accion .tip-icon { align-self: flex-start; }
.tip-con-accion .tip-body { flex-basis: 240px; }
/* Varios avisos fijos seguidos (`.tips`): pegados entre sí, sin el margen
   de arriba de cada uno. Nació con lo pendiente de Nómina (2026-10-30). */
.tips { display: flex; flex-direction: column; gap: var(--space-100); }
.tips > .tip { margin: 0; }

/* `ui/_antes_despues.html`: «Hoy» → «Si apruebas», una fila por campo. */
.antes-despues { display: flex; flex-direction: column; gap: var(--space-200); }
.antes-despues-fila { min-width: 0; }
.antes-despues-rotulo { margin: 0 0 var(--space-100); font-size: var(--fs-small);
                        font-weight: 600; color: var(--text-light); }
.antes-despues-par { display: grid; align-items: stretch; gap: var(--space-100);
                     grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); }
.antes-despues-caja { display: flex; flex-direction: column; gap: var(--space-025);
                      padding: var(--space-150); border-radius: var(--radius-field);
                      background: var(--bg); }
.antes-despues-caja--nuevo { background: var(--ok-bg); }
.antes-despues-caja--nuevo .antes-despues-cuando { color: var(--ok-text); }
.antes-despues-cuando { font-size: var(--fs-small); color: var(--text-light); }
.antes-despues-valor { font-weight: 600; white-space: pre-line;
                       overflow-wrap: anywhere; }
.antes-despues-flecha { width: 20px; height: 20px; align-self: center;
                        color: var(--text-light); }
@media (max-width: 600px) {
    .antes-despues-par { grid-template-columns: minmax(0, 1fr); }
    .antes-despues-flecha { justify-self: center; transform: rotate(90deg); }
}

.editor-section + .editor-section {
    margin-top: var(--space-250); padding-top: var(--space-250);
    border-top: 1px solid var(--border);
}
.editor-section-title {
    margin: 0 0 var(--space-150); font-size: var(--fs-body); font-weight: 700;
    color: var(--text); letter-spacing: -.01em;
}

.customizer-title { font-size: var(--fs-label); color: var(--pill-text);
                    text-transform: uppercase; letter-spacing: .04em;
                    margin: var(--space-150) 0 var(--space-050); }

.customizer-tree { list-style: none; margin: 0; padding: 0; }
.customizer-node { display: flex; align-items: center; gap: var(--space-050);
                   border-radius: var(--radius-field); }
.customizer-node:hover { background: var(--bg); }
.customizer-node.is-open { background: var(--primary-soft); }
/* Oculta ≠ borrada: el trabajo de configurarla sigue ahí. Se atenúa, no se
   quita, o no habría cómo volver a mostrarla. */
.customizer-node.is-hidden .customizer-node-name { opacity: .55; }
.customizer-node-name { flex: 1; min-width: 0; display: block;
                        padding: var(--space-050) var(--space-100);
                        font-size: var(--fs-small); color: var(--text);
                        text-decoration: none; word-break: break-word; }
/* Los botones de la fila viven cada uno en su `<form>`: sin esto, cada uno
   sería un bloque y la fila se partiría en cuatro líneas. */
.customizer-node-actions { display: flex; align-items: center; gap: var(--space-025); }
.customizer-node-actions form { display: inline; }

.customizer-empty { font-size: var(--fs-small); color: var(--pill-text);
                    padding: var(--space-100); }

.customizer-blocks { list-style: none; margin: 0; padding: 0; }
.customizer-block { border: 1px solid var(--border);
                    border-radius: var(--radius-field); padding: var(--space-100);
                    margin-bottom: var(--space-100); }
.customizer-block.is-hidden { opacity: .55; }
.customizer-block-name { font-weight: 600; font-size: var(--fs-small);
                         margin: 0 0 var(--space-050); }
.customizer-block-form { margin-bottom: var(--space-100); }
.customizer-block-add { display: flex; gap: var(--space-100); align-items: end;
                        flex-wrap: wrap; }

/* El rechazo que el panel muestra sin recargar. Arriba de todo porque abajo,
   con el árbol de por medio, no se ve. */
.customizer-error { grid-column: 1 / -1; margin: 0 0 var(--space-100);
                    padding: var(--space-100) var(--space-150);
                    border-radius: var(--radius-field);
                    background: var(--primary-soft);
                    color: var(--text); font-size: var(--fs-small); }

/* El modal de agregar: una LISTA con búsqueda. Con 44 secciones, un `prompt()`
   del navegador obliga a saberse el nombre exacto de memoria. */
.section-picker { margin-top: var(--space-150); }
.section-picker-search { width: 100%; margin: var(--space-100) 0; }
.section-picker-list { list-style: none; margin: 0; padding: 0;
                       max-height: 40vh; overflow-y: auto; }
.section-picker-item { margin: 0; }
.section-picker-add { display: block; width: 100%; text-align: left;
                      padding: var(--space-050) var(--space-100); border: 0;
                      background: none; cursor: pointer;
                      border-radius: var(--radius-field);
                      font-size: var(--fs-small); color: var(--text); }
.section-picker-add:hover { background: var(--primary-soft); }

/* ── La pantalla de temas ────────────────────────────────────────────────── */
/* Las acciones de la fila van en `.cell-actions` + `.row-actions`, las de
   toda tabla. Tuvieron regla propia con `flex-wrap: wrap`, que con el error
   del tema ensanchando la primera columna apilaba los cinco botones. */

/* El PRIMER error del tema, debajo de su nombre. Con su archivo: un «tiene
   errores» sin el archivo obliga a abrir el editor y buscar entre 152. */
.theme-error { margin: var(--space-050) 0 0; }

/* La casilla que solo existe para copiar de ella: monoespaciada porque lo que
   lleva es un token, y a un token hay que poder distinguirle un 1 de una l. */
.copy-field {
    width: 100%; margin-top: var(--space-100); padding: var(--space-150) var(--space-150);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    /* El token y no `13px`: los siete pasos se nombran por PAPEL, y hay una
       prueba que rechaza un tamaño en píxeles crudos. Un número suelto acá es
       cómo la hoja volvía a tener catorce tamaños elegidos de a uno. */
    font-size: var(--fs-body);
    color: var(--text);
    background: var(--bg);
    border: 1px solid var(--border); border-radius: 10px;
}

/* ── Campo de medio del tema, y la biblioteca ─────────────────────────── */

/* El campo vive en dos anchos muy distintos: el panel del customizer tiene
   ~320px y la pantalla de ajustes del tema es una columna de formulario
   normal. Todo se mide en relativo por eso — nada de anchos fijos. */
.media-field { display: flex; flex-direction: column; gap: var(--space-075); }

.media-field__preview {
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--bg); overflow: hidden; line-height: 0;
}
.media-field__preview[hidden] { display: none; }
.media-field__preview img, .media-field__preview video {
    width: 100%; max-height: 160px; object-fit: contain; display: block;
}

/* La misma zona de `.dropzone` pero más baja: acá va dentro de un panel
   angosto con veinte campos, y la altura de la ficha de un producto dejaría
   la mitad de los ajustes debajo del pliegue. */
.media-field__zone {
    position: relative; display: block; padding: var(--space-100) var(--space-150);
    border: 1.5px dashed var(--border-strong); border-radius: var(--radius-field);
    background: var(--bg); color: var(--text-light);
    font-size: var(--fs-small); text-align: center; cursor: pointer;
    transition: border-color .16s, background .16s, color .16s;
}
.media-field__zone:hover, .media-field__zone:focus-within {
    border-color: var(--primary); background: var(--primary-soft);
    color: var(--text);
}
/* Invisible pero PRESENTE, igual que en `.dropzone`: sigue siendo el control
   que recibe el archivo soltado, el que abre el diálogo al hacer clic y el que
   el teclado enfoca. `display: none` lo sacaría de los tres. */
.media-field__zone input[type="file"] {
    position: absolute; inset: 0; width: 100%; height: 100%;
    opacity: 0; cursor: pointer;
}
/* El rótulo de la zona. Sin regla propia heredaría el tamaño del formulario,
   que en el panel del customizer parte el texto en tres líneas. */
.media-field__hint { display: block; line-height: 1.4; }
.media-field.is-uploading .media-field__zone {
    border-style: solid; border-color: var(--primary); opacity: .6;
}

/* La casilla con la ruta: chica y gris. Es el respaldo —se puede pegar una
   dirección a mano— pero no es lo que uno viene a hacer, así que no compite
   con la zona por la atención. */
.media-field__value {
    font-size: var(--fs-small); color: var(--text-light);
    font-family: var(--font-mono, monospace);
}
.media-field__actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-075); margin: 0;
}
.media-field__error { font-size: var(--fs-small); color: var(--danger-text); }
.media-field__error[hidden] { display: none; }

/* ── Marca: las cuatro ranuras de logo ───────────────────────────────────
   Cada ranura es una TARJETA, y la previa y la zona de arrastre ocupan LA
   MISMA celda. Apiladas —que es como salían— una ranura llena mide el doble
   que una vacía, las filas de la rejilla dejan de alinearse y las cuatro se
   leen como una pila desordenada en vez de como cuatro cosas del mismo tipo
   (Renzo, 2026-08-25, con la pantalla enfrente).

   Con logo puesto la zona se CALLA —borde y fondo transparentes— y deja ver
   la previa; al pasar por encima vuelve, con su fondo, y ahí es el botón de
   reemplazar. Sin logo se ve exactamente como en el resto del ERP, que es lo
   que Renzo pidió conservar. */
/* El encabezado de un GRUPO dentro de una tarjeta —baldosa, título y pista—
   para la tarjeta que reúne dos cosas (los logos y los colores de Marca).
   Reusa `.form-section-title` y `.field-hint`; lo suyo es la baldosa al lado
   y el aire de abajo. */
.form-section-head {
    display: flex; align-items: flex-start; gap: var(--space-150);
    margin: 0 0 var(--space-200);
}
.form-section-head + .form-section-head,
.form-grid + .form-section-head { margin-top: var(--space-100); }
.form-section-head .tile { flex: none; }
.form-section-text { min-width: 0; }
.form-section-head .form-section-title { margin: 0 0 var(--space-025); }
.form-section-head .field-hint { margin: 0; }

.brand-slot, .brand-color {
    border: 1px solid var(--border); border-radius: var(--radius-card);
    padding: var(--space-150);
    /* El aire de ABAJO lo pone la tarjeta y no la rejilla: `.form-grid` trae
       `gap: 0 var(--space-200)` —solo de columna— así que las dos filas se tocaban
       y las cuatro ranuras volvían a leerse como un bloque. */
    margin: 0 0 var(--space-200);
}
/* La cabecera de la ranura: nombre y para qué sirve, y la pastilla
   «Recomendado» a la derecha en la horizontal clara. */
.brand-slot-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-100); margin-bottom: var(--space-150);
}
.brand-slot-text { display: flex; flex-direction: column; gap: var(--space-025); min-width: 0; }
.brand-slot-title { margin: 0; font-weight: 600; color: var(--text); }
.brand-slot-hint { font-size: var(--fs-small); color: var(--text-light); line-height: 1.4; }
.brand-slot .field-hint { margin-bottom: 0; }

.brand-slot .media-field { display: grid; grid-template-columns: 1fr; gap: var(--space-100); }
/* La misma celda para las dos. */
.brand-slot .media-field__preview,
.brand-slot .media-field__zone { grid-row: 1; grid-column: 1; }

.brand-slot .media-field__preview {
    height: 116px; padding: var(--space-150); line-height: normal;
    display: flex; align-items: center; justify-content: center;
    /* Transparente al puntero: el control que recibe el clic y el archivo
       soltado es la ZONA, que está debajo. Sin esto, la mitad llena de la
       pantalla deja de aceptar un arrastre y no hay nada que lo explique. */
    pointer-events: none;
}
.brand-slot .media-field__preview img {
    width: auto; max-width: 100%; max-height: 100%;
}
.brand-slot .media-field__zone {
    display: flex; align-items: center; justify-content: center;
    min-height: 116px;
}
.brand-slot .media-field__hint { transition: opacity .16s; }
.brand-slot .media-field__preview:not([hidden]) ~ .media-field__zone {
    border-color: transparent; background: transparent;
}
.brand-slot .media-field__preview:not([hidden]) ~ .media-field__zone .media-field__hint {
    opacity: 0;
}
.brand-slot .media-field__preview:not([hidden]) ~ .media-field__zone:hover,
.brand-slot .media-field__preview:not([hidden]) ~ .media-field__zone:focus-within {
    border-color: var(--primary); background: rgba(247, 246, 251, .93);
}
.brand-slot .media-field__preview:not([hidden]) ~ .media-field__zone:hover .media-field__hint,
.brand-slot .media-field__preview:not([hidden]) ~ .media-field__zone:focus-within .media-field__hint {
    opacity: 1;
}

/* La ranura de fondo oscuro se juzga SOBRE fondo oscuro: un logo negro se ve
   perfecto contra el blanco del editor y desaparece en el correo, que es
   exactamente lo que estas dos ranuras existen para evitar.

   Solo la PREVIA. La zona VACÍA se queda con la piel de arrastre de la casa
   —fondo claro y borde punteado, la misma de todo el ERP—: pintarla de negro
   la convertía en un panel raro que no se parece a ningún otro sitio donde se
   sueltan archivos, y la convención es lo que hace que se entienda sin leer
   (Renzo, 2026-08-25). El fondo oscuro es para juzgar un logo, no para pedir
   uno. */
.brand-slot--oscuro .media-field__preview { background: var(--banner-from); }
.brand-slot--oscuro .media-field__preview:not([hidden]) ~ .media-field__zone:hover,
.brand-slot--oscuro .media-field__preview:not([hidden]) ~ .media-field__zone:focus-within {
    background: rgba(22, 19, 42, .93); color: var(--banner-sub);
    border-color: var(--banner-sub);
}
/* Sin la clase la ranura clara no se distingue de la oscura en el marcado, y
   un `--claro` que no pinta nada es una clase que miente. Pinta su lienzo
   EXPLÍCITAMENTE, así el par se lee como un par. */
/* Un tablero de ajedrez suave bajo el logo claro: un PNG con fondo
   transparente se ve como transparente, y un logo blanco no desaparece
   contra el lienzo (el referente de Renzo, 2026-09-14). */
.brand-slot--claro .media-field__preview {
    background-color: #fff;
    background-image:
        linear-gradient(45deg, var(--bg) 25%, transparent 25%, transparent 75%, var(--bg) 75%),
        linear-gradient(45deg, var(--bg) 25%, transparent 25%, transparent 75%, var(--bg) 75%);
    background-size: 16px 16px; background-position: 0 0, 8px 8px;
}

/* El aviso de una ranura que solo tiene SVG: es una advertencia, no un error
   —el archivo sirve para la tienda—, así que va en ámbar y no en rojo. */
.field-hint--warn { color: var(--warn-text); }

/* La tarjeta de un color. Mismo marco que la de un logo —lo comparte arriba—
   porque son lo mismo para quien mira: una pieza de la marca con su nombre,
   su control y su explicación. Sin el marco, la mitad de abajo de la pantalla
   parecía un formulario pegado debajo de una rejilla de tarjetas. */
.brand-color > label { display: block; margin-bottom: var(--space-100); }
.brand-color .field-hint { margin-bottom: 0; }

/* El par selector + hexadecimal. El nativo no deja teclear el código que
   alguien tiene copiado de su manual de marca, y la casilla sola obliga a
   adivinar el color a ojo: van juntos o ninguno de los dos sirve. */
.color-field { display: flex; align-items: center; gap: var(--space-100); }
.color-field input[type="color"] {
    width: 48px; height: 38px; flex: none; padding: var(--space-025); cursor: pointer;
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--card-bg);
}
/* El chip lleva su propio filete POR DENTRO. Sin él, «Tarjeta» en #ffffff se
   pinta blanco sobre la tarjeta blanca y se lee como una casilla VACÍA —o sea
   como «no elegí ningún color»— que es lo contrario de lo que dice. Los dos
   seudoelementos son el mismo control en los dos motores. */
.color-field input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.color-field input[type="color"]::-webkit-color-swatch {
    border: 1px solid rgba(28, 24, 48, .14); border-radius: 7px;
}
.color-field input[type="color"]::-moz-color-swatch {
    border: 1px solid rgba(28, 24, 48, .14); border-radius: 7px;
}
.color-field__hex {
    flex: 1; min-width: 0; text-transform: lowercase;
    font-family: var(--font-mono, monospace);
}

/* El selector de la biblioteca. */
.media-picker {
    position: fixed; inset: 0; z-index: 1200;
    display: flex; align-items: center; justify-content: center;
    background: rgba(17, 15, 22, .5);
}
/* `display: flex` le gana al atributo `hidden` del navegador — hay que
   restaurarlo explícito o el modal nace abierto.

   Y no solo el modal: TODO lo que adentro se apaga con `hidden` necesita lo
   mismo, porque `.btn` también trae `display`. Sin esta segunda regla, el «Ver
   más» se veía con la grilla vacía — un botón que ofrece más de nada. Va como
   regla de descendencia y no una por control: con dos que ya cayeron, la
   tercera cae igual. */
.media-picker[hidden] { display: none; }
.media-picker [hidden] { display: none; }

/* El ✕ del modal. `.icon-btn` está pintado para la barra oscura —blanco al
   80%— y acá quedaba blanco sobre blanco: el botón existía, medía sus 32px y
   no se veía. */
.media-picker .icon-btn { color: var(--text-light); }
.media-picker .icon-btn:hover { color: var(--text); }
.media-picker__box {
    display: flex; flex-direction: column; gap: var(--space-150);
    width: min(680px, 92vw); max-height: 82vh; padding: var(--space-200);
    background: var(--card-bg); border-radius: var(--radius);
    box-shadow: 0 18px 50px rgba(17, 15, 22, .28); overflow: hidden;
}
.media-picker__head {
    display: flex; align-items: center; justify-content: space-between;
}
/* Los ejes del picker: Todos · Imágenes · Videos. Botones y no un `<select>`
   porque son tres y se alternan de un clic — un desplegable serían dos.
   `flex: none` sobre la fila para que no la encoja el `overflow-y` de la
   grilla que va debajo. */
.media-picker__axes { display: flex; gap: var(--space-075); flex: none; }
.media-picker__axis {
    padding: var(--space-050) var(--space-150); border-radius: 999px; cursor: pointer;
    border: 1px solid var(--border);
    background: transparent; color: var(--text-light);
    font-family: inherit; font-size: var(--fs-small); font-weight: 600;
}
.media-picker__axis:hover { color: var(--text); }
.media-picker__axis.is-on {
    background: var(--primary-soft); border-color: var(--primary-soft-border);
    color: var(--primary-dark);
}
.media-picker__grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: var(--space-100); margin: 0; padding: 0; list-style: none; overflow-y: auto;
}
.media-picker__cell {
    display: flex; flex-direction: column; gap: var(--space-050); width: 100%;
    padding: var(--space-050); border: 1.5px solid transparent;
    border-radius: var(--radius-field); background: none; cursor: pointer;
}
.media-picker__cell:hover, .media-picker__cell:focus-visible {
    border-color: var(--primary); background: var(--primary-soft);
}
.media-picker__thumb {
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 1; border-radius: 6px;
    background: var(--bg) center/cover no-repeat;
    font-size: var(--fs-small); color: var(--text-faint);
}
/* El nombre en UNA línea con puntos suspensivos: los nombres del CDN de
   Shopify traen el prompt de la imagen adentro —hubo uno de 118 caracteres— y
   sin esto una ficha empuja a las otras tres de su fila. */
.media-picker__name {
    font-size: var(--fs-small); color: var(--text-light);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.media-picker__empty { font-size: var(--fs-small); color: var(--text-light); }
.media-picker__empty[hidden] { display: none; }

/* El conmutador tabla / íconos. Vive en el encabezado de la tarjeta, al lado
   del conteo: cambiar de forma no puede dejarlo marcando la otra. */
.view-switch { display: inline-flex; gap: var(--space-025); margin-left: auto; }
.view-switch__btn {
    display: grid; place-items: center; width: 32px; height: 32px;
    border-radius: var(--radius-field); color: var(--text-faint);
    transition: color .16s, background .16s;
}
.view-switch__btn:hover { color: var(--text); background: var(--bg); }
.view-switch__btn.is-active { color: var(--primary); background: var(--primary-soft); }
.view-switch__btn svg { width: 18px; height: 18px; }

/* La grilla de la pantalla de Archivos. Grilla y no tabla porque a una imagen
   se la reconoce MIRÁNDOLA: una columna «Nombre» con `IMG_4471.jpg` no
   reconoce nada. */
.media-grid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: var(--space-200); margin: 0; padding: var(--space-200); list-style: none;
}
.media-cell { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.media-cell a { display: flex; flex-direction: column; gap: var(--space-075); min-width: 0; }
.media-thumb {
    display: flex; align-items: center; justify-content: center;
    aspect-ratio: 4 / 3; border: 1px solid var(--border); border-radius: 8px;
    background: var(--bg) center/cover no-repeat;
}
.media-thumb-empty {
    font-size: var(--fs-small); color: var(--text-faint);
    text-transform: uppercase; letter-spacing: .04em;
}
.media-name {
    font-size: var(--fs-small); color: var(--text); font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.media-meta { font-size: var(--fs-small); color: var(--text-faint); }

/* ── La galería del producto ──────────────────────────────────────────── */

/* La celda se arrastra entera (media-sort.js) y la estrella marca la
   principal. `position: relative` porque la estrella se ancla a la esquina
   superior derecha de la MINIATURA, que es la primera hija. */
/* La grilla dentro de la tarjeta no repite el respiro de abajo: lo pone la
   zona de subida que la sigue. */
.galeria-grid { padding-bottom: 0; }
.galeria-cell { position: relative; cursor: grab; }
/* La previa es el primer cuadro del video; los eventos apagados para que el
   drag agarre la celda y no los controles del reproductor. */
.galeria-video-preview { object-fit: cover; pointer-events: none; }
.galeria-cell:active { cursor: grabbing; }
.galeria-cell.is-dragging { opacity: .4; }
.galeria-cell.is-over .media-thumb {
    border-color: var(--primary);
    box-shadow: 0 0 0 2px var(--primary-soft);
}
.media-star {
    position: absolute; top: 6px; right: 6px; width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 999px; background: white;
    color: var(--primary);
    box-shadow: 0 1px 4px rgba(15, 18, 34, .18);
    pointer-events: none;
}
.media-star svg { width: 15px; height: 15px; }

/* La zona de arrastre y su botón son dos cosas: la zona recibe, el botón
   confirma. Pegados se leen como un solo control con dos bordes. */
.galeria-upload { margin-top: var(--space-200); }
.galeria-upload-foot {
    margin-top: var(--space-150); display: flex; justify-content: flex-end;
}

/* ── Filas arrastrables (menús, colecciones) y editables ─────────────── */

/* El agarre de punticos: es EL único sitio por donde la fila se arrastra —
   la fila entera pelearía con seleccionar el texto de sus inputs. */
.drag-cell { width: 28px; padding-right: 0; }
/* El NÚMERO de cada fila, puesto por la hoja con un contador y no por la
   plantilla: al arrastrar una fila (`drag-sort.js` la mueve en el DOM sin
   repintar) los números se reacomodan solos, y el 7 sigue siendo el
   séptimo de la vitrina (Renzo, 2026-10-26, la colección «menu-barra»). */
.table-numbered tbody { counter-reset: fila; }
.table-numbered .num-cell {
    width: 1%; padding-left: var(--space-050); padding-right: 0;
    text-align: right; white-space: nowrap;
    color: var(--text-light); font-variant-numeric: tabular-nums;
}
/* El número va PEGADO al agarre (Renzo, 2026-10-26): con el relleno de la
   casa a los dos lados quedaban 32 px de aire entre los punticos y el «1».
   El `padding-right: 0` de `.drag-cell` no alcanzaba: `.table tbody td`
   pesa más y le devolvía los 16 px. */
.table-numbered .drag-cell { padding-right: 0; }
/* Y pegado a la casilla de selección cuando la hay: el agarre ya trae su
   propio aire dentro de sus 22 px. */
.table-numbered .cell-check + .drag-cell { padding-left: var(--space-050); }
.table-numbered tbody .num-cell::before { counter-increment: fila; content: counter(fila); }
.drag-handle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 26px; cursor: grab; color: var(--text-faint);
    border-radius: 6px;
}
.drag-handle:hover { color: var(--text); background: var(--bg); }
.drag-handle:active { cursor: grabbing; }
.drag-handle svg { width: 14px; height: 14px; }

/* La tabla de variantes de la ficha del producto (Renzo, 2026-10-14): filas
   con AIRE —el relleno base de 4px las dejaba apretadas contra el borde— y
   el nombre pegado a su agarre: la columna del agarre ya trae su propio
   filo de 20px, y el relleno normal de la celda siguiente abría un hueco
   que no separaba nada. Por id, porque es esa tabla y no todas las que se
   arrastran. */
#variantes tbody td { padding-top: var(--space-150); padding-bottom: var(--space-150); }
#variantes td.drag-cell + td,
#variantes th.drag-cell + th { padding-left: var(--space-050); }
/* En una lista de CAJAS (`boxed-list`, las reglas de «Desde qué bodega sale»)
   el agarre no es una celda con ancho sino un hijo flex de la fila, y el
   navegador lo ENCOGE antes que dejar envolver el texto de al lado: medido a
   375px, 22px se volvían 14 —el ancho pelado de su glifo— y el área de toque
   se perdía justo en el aparato donde más falta hace. Es la misma trampa del
   `<svg>` de `.setting-default` (v409). Solo muerde donde el agarre es hijo
   directo de una `.boxed-row`: en una `<table>` vive dentro de su `<td>` y
   en el tablero dentro de `.grid-tools`, y ninguno de los dos pasa por acá. */
.boxed-row > .drag-handle { flex: none; }
.drag-row.is-dragging { opacity: .4; }
/* La marca de destino va ARRIBA de la fila: es donde la pieza va a caer. */
.drag-row.is-over td { border-top: 2px solid var(--primary); }
/* Los inputs de la fila no gritan: se leen como texto hasta que se tocan.
   El margen negativo compensa el padding propio del input (--sp-3): sin él,
   «Cafés» arranca un padding a la derecha de su cabecera «Texto». */
.drag-row input[type="text"] {
    border-color: transparent; background: transparent;
    margin-left: calc(var(--space-150) * -1);
    width: calc(100% + var(--space-150));
}
.drag-row input[type="text"]:hover { border-color: var(--border); }
.drag-row input[type="text"]:focus {
    border-color: var(--primary); background: var(--card-bg);
}

.plain-list { margin: 0 0 var(--space-150); padding-left: var(--space-250); }
.plain-list li { font-size: var(--fs-body); color: var(--text); padding: var(--space-025) 0; }

/* ── La ficha de un archivo ───────────────────────────────────────────── */

/* DOS columnas, y el reparto es la decisión: la previa manda porque a un
   archivo se lo reconoce mirándolo, y los datos son de consulta. Apiladas
   —como estaba— la previa empujaba el nombre y el alt debajo del pliegue, y
   esta pantalla existe justamente para renombrar y describir.

   `minmax(0, …)` en la primera pista: sin el mínimo cero, una imagen ancha
   estira la columna y el `grid` desborda a la derecha en vez de encogerla. */
.file-detail {
    display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    gap: var(--space-200); align-items: start;
}
/* Un umbral y no un `md`: debajo de ~900px la columna de datos queda tan
   angosta que «Dimensiones» parte en dos renglones. */
@media (max-width: 900px) {
    .file-detail { grid-template-columns: minmax(0, 1fr); }
}

.file-detail__media {
    display: flex; align-items: center; justify-content: center;
    padding: var(--space-250); min-height: 240px;
}
.file-detail__media img, .file-detail__media video {
    max-width: 100%; max-height: 60vh; object-fit: contain;
    border-radius: var(--radius-field);
}
.file-detail__nopreview {
    margin: 0; font-size: var(--fs-section); font-weight: 700;
    color: var(--text-faint); text-transform: uppercase; letter-spacing: .06em;
}

.file-detail__side { display: flex; flex-direction: column; gap: var(--space-200); }

/* Etiqueta y dato en dos columnas. `dl` y no una tabla: son pares
   nombre-valor, que es literalmente para lo que existe. */
.file-detail__facts {
    display: grid; grid-template-columns: auto 1fr;
    gap: var(--space-050) var(--space-150); margin: var(--space-150) 0;
}
.file-detail__facts dt {
    font-size: var(--fs-small); color: var(--text-faint); font-weight: 600;
}
.file-detail__facts dd {
    margin: 0; font-size: var(--fs-small); color: var(--text);
}

/* ── Reseñas ──────────────────────────────────────────────────────────────
   La pantalla de moderación. Prefijo `rsn-` y no nombres genéricos —`stars`,
   `hint`— a propósito: son cinco reglas de UNA pantalla, y un `.stars` suelto
   en una hoja de 5.200 líneas es un nombre que el próximo que necesite
   estrellas va a reusar sin mirar qué le hace a esta tabla.

   El sello «Compra verificada» NO tiene regla propia: usa `.badge-soft`, que
   ya existe. Una clase nueva para la misma pastilla es cómo se termina con
   tres verdes distintos en el mismo ERP. */
/* `.table` lleva `white-space: nowrap` para toda la casa, y con un cuerpo de
   reseña de 600 caracteres eso hace una columna de miles de píxels: la tabla
   sale con scroll horizontal y el autor, el estado y los botones quedan fuera
   de la pantalla. Se corrige por CELDA y no tocando la regla global — el
   nowrap está bien para las once listas que muestran datos cortos.

   `max-width` en `ch` y no en píxels: lo que hay que acotar es cuántos
   CARACTERES entran por renglón, que es lo que decide si un párrafo se lee. */
.rsn-body { white-space: normal; line-height: 1.45; }
/* El ancho lo da la COLUMNA (ver `.rsn-table .rsn-body`), pero el texto no
   puede correr los ~90 caracteres de la celda entera: un párrafo se lee hasta
   ~65. El tope va en el TEXTO y no en la celda para que la fila siga ocupando
   el ancho completo y las otras columnas queden alineadas. */
.rsn-clamp { max-width: 62ch; display: block; }
/* La reseña es la columna ANCHA a propósito: `width: 100%` en una tabla
   automática le da a esta celda todo lo que las otras no reclamen, y las
   otras tres quedan del ancho de su contenido — es lo que mata el hueco
   muerto que había entre el texto y el autor cuando cada columna tenía su
   ancho rígido. */
/* Las PROPORCIONES van declaradas y no al azar del contenido: con la reseña
   en «lo que sobre», cuatro renglones cortos dejaban un desierto entre el
   texto y el autor, y todo lo demás apretado contra el borde derecho. La
   reseña se lleva la mitad, el autor un cuarto, y estado y acciones el
   resto — medido contra el referente de Renzo. */
.rsn-table .rsn-body { width: 50%; }
.rsn-table .rsn-author { width: 26%; white-space: normal; }
.rsn-meta { margin-top: var(--space-100); }

/* Los filtros en la cabecera estándar de la tarjeta, como el referente. */
.rsn-cardfilters { display: flex; align-items: center; gap: var(--space-100); }

/* ── Las estrellas dibujadas, con su nota ───────────────────────────── */
.rsn-stars-row {
    margin: 0 0 var(--space-050);
    display: flex; align-items: center; gap: var(--space-100);
}
.rsn-star { width: 17px; height: 17px; }
.rsn-star path { fill: var(--border); }
.rsn-star--on path { fill: var(--text); }
.rsn-score {
    font-size: var(--fs-meta); font-weight: 600; color: var(--text-light);
    background: var(--bg); border-radius: 4px; padding: .15em .5em;
}
.rsn-title { display: block; margin-bottom: var(--space-025); }

/* ── El autor: avatar con iniciales, y el correo debajo ─────────────── */
.rsn-person { display: flex; gap: var(--space-100); align-items: center; }
/* El tono viene de la vista (`--rsn-h`, estable por nombre): claro de fondo y
   oscuro de letra, del MISMO tono, para que cualquier pareja combine. */
.rsn-avatar {
    display: grid; place-items: center; flex: none;
    width: 28px; height: 28px; border-radius: 50%;
    font-size: var(--fs-badge); font-weight: 600;
    background: hsl(var(--rsn-h, 220) 45% 92%);
    color: hsl(var(--rsn-h, 220) 30% 40%);
}
.rsn-person-info { min-width: 0; font-size: var(--fs-body); }
/* La RESEÑA arriba —es el texto que se lee— y las columnas cortas (autor,
   estado, acciones) al CENTRO de su fila, como en el referente: arriba
   quedaban flotando con un vacío debajo en cada fila alta, y esa masa
   despareja era lo que Renzo leía como columnas «pesadas». */
.rsn-table td { vertical-align: middle; }
.rsn-table .rsn-body { vertical-align: top; }
/* El correo es la línea más larga de la celda: si no corta, ÉL decide el ancho
   de la columna entera. `anywhere` y no `word-break`: corta solo cuando no hay
   más remedio. Y TENUE, no en negrita: `.table tbody a` pinta los enlaces
   oscuros y en 600, pero este es la segunda línea de la celda — si pesa más
   que el nombre, la columna se lee al revés. */
.rsn-email a { overflow-wrap: anywhere; }
.table tbody .rsn-email a:not(.btn) {
    color: var(--text-light); font-weight: 400;
}
/* ESTA tabla no vive en un contenedor de scroll: sus celdas envuelven —nada
   desborda a lo ancho— y el tope de alto solo servía para el thead pegajoso.
   A cambio, el menú de acciones puede FLOTAR: dentro del scrollport se
   cortaba contra el borde, y abrirlo en flujo empujaba la fila — el «salto
   raro» que Renzo reportó el 2026-08-08. */
.rsn-wrap { overflow: visible; max-height: none; }
/* El menú de la fila: los tres punticos. Flota anclado al botón y no mueve
   NADA al abrir — la tarjeta no recorta (borde redondeado sin `overflow`),
   así que hasta el de la última fila se ve entero. */
.rsn-actions { text-align: right; }
.rsn-menu { display: inline-block; text-align: left; position: relative; }
/* El summary ya se estiliza por `.rsn-menu > summary`; la clase propia
   existe para que la guarda de la hoja tenga una regla que encontrar — una
   clase usada sin regla es exactamente lo que esa prueba caza. */
.rsn-menu-toggle { user-select: none; }
.rsn-menu > summary {
    list-style: none; cursor: pointer;
    font-size: var(--fs-body); line-height: 1; padding: .55rem .9rem;
}
.rsn-menu > summary::-webkit-details-marker { display: none; }
.rsn-menu[open] > summary { background: var(--bg); }
.rsn-menu-list {
    position: absolute; right: 0; top: calc(100% + 4px); z-index: 30;
    width: max-content; min-width: 13rem;
    padding: var(--space-050);
    display: flex; flex-direction: column; gap: var(--space-025);
    background: var(--card-bg);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
}
.rsn-menu-list .inline-form { display: block; }
/* Ítems de menú, no botones apilados: sin borde, y el hover es el fondo —
   cuatro pastillas con borde una sobre otra se leen como un formulario. */
.rsn-menu-list .btn {
    display: block; width: 100%; text-align: left; border: 0;
}
.rsn-menu-list .btn:hover { background: var(--bg); }
.rsn-menu-list .rsn-reply { display: block; }
.rsn-menu-list .rsn-reply > summary { display: block; text-align: left; }
.rsn-stars { white-space: nowrap; display: inline-flex; gap: var(--space-025); }
/* La cabecera «Acciones» a la derecha, sobre su columna de kebabs. */
.rsn-table thead .rsn-th-actions { text-align: right; }

/* ── El pie: «Mostrando X a Y» y los números de página ──────────────── */
.rsn-foot {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap;
    padding: var(--space-150) var(--space-200);
    border-top: 1px solid var(--border);
}
.rsn-foot-status { font-size: var(--fs-small); color: var(--text-light); }
.rsn-pages { display: flex; align-items: center; gap: var(--space-050); }
.rsn-page {
    display: grid; place-items: center; min-width: 32px; height: 32px;
    padding: 0 .6rem; border-radius: var(--radius-sm);
    border: 1px solid var(--border); text-decoration: none; color: inherit;
    font-size: var(--fs-small);
}
.rsn-page:hover { border-color: var(--text-light); }
.rsn-page--current {
    background: var(--text); border-color: var(--text); color: #fff;
    font-weight: 600;
}
.rsn-page-gap { color: var(--text-faint); padding: 0 .3rem; }
.rsn-thumbs { display: flex; gap: var(--space-050); margin-top: var(--space-100); }
.rsn-thumbs img {
    width: 56px; height: 56px; object-fit: cover;
    border-radius: var(--radius-sm);
}
/* El `<details>` de responder. `inline-block` para que el resumen no empuje a
   los botones de estado a otra línea: en una lista de doscientos renglones,
   una fila más alta que las demás por un desplegable cerrado se lee como si
   esa reseña fuera distinta. */
.rsn-reply { display: inline-block; }
.rsn-reply textarea {
    display: block; width: 260px; margin: var(--space-100) 0;
}

/* ============================================================
   AUTOMATIZACIONES — la lista, la barra del builder y la bitácora.
   El lienzo de nodos trae su propia hoja (`automations/builder.css`,
   compilada con el bundle); acá vive solo lo que pinta el shell.
   ============================================================ */

/* La barra del builder reemplaza al banner del shell, con la cara de la
   referencia de Renzo (2026-10-08): volver en su círculo, el nombre y la
   descripción editables donde se leen, el estado, y a la derecha Probar,
   Guardar, el interruptor y el ⋯. Guardar sigue la regla de la barra de
   guardado: apagado sin cambios. */
.atm-toolbar {
    display: flex; justify-content: space-between; align-items: center;
    gap: var(--space-150); flex-wrap: wrap;
    padding: var(--space-150) var(--space-200);
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: var(--radius-card) var(--radius-card) 0 0; border-bottom: none;
}
.atm-toolbar-left { display: flex; align-items: center; gap: var(--space-150); min-width: 0; }
.atm-toolbar-actions { display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap; }
.atm-back {
    flex: none; display: grid; place-items: center;
    width: 38px; height: 38px; border-radius: 50%;
    background: var(--bg); color: var(--text-light); text-decoration: none;
}
.atm-back:hover { color: var(--text); background: var(--primary-soft); }
.atm-back svg { width: 18px; height: 18px; }
.atm-titles { min-width: 0; display: flex; flex-direction: column; gap: var(--space-025); }
.atm-title-row { display: flex; align-items: center; gap: var(--space-100); min-width: 0; }
/* El nombre y la descripción se editan donde se leen: un borde que solo
   aparece al entrar. */
.atm-toolbar h1,
.atm-description {
    margin: 0; padding: 1px var(--space-075); border-radius: var(--radius-field);
    border: 1px solid transparent; cursor: text;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.atm-toolbar h1 { font-size: var(--fs-page); font-weight: 700; min-width: 40px; max-width: 46ch; }
.atm-description { font-size: var(--fs-small); color: var(--text-light); max-width: 64ch; }
.atm-description:empty::before { content: attr(data-placeholder); color: var(--text-faint); }
.atm-toolbar h1:hover, .atm-description:hover { background: var(--bg); }
.atm-toolbar h1:focus, .atm-description:focus {
    outline: none; background: var(--card-bg); border-color: var(--primary);
    white-space: normal;
}
.atm-dirty {
    display: inline-flex; align-items: center; gap: var(--space-075);
    font-size: var(--fs-label); color: var(--text-light);
}
.atm-dirty::before {
    content: ''; width: 8px; height: 8px; border-radius: 50%;
    background: var(--warn-text);
}
.atm-active.field-check { margin: 0; }
/* El `display` de arriba le gana al `[hidden]` del navegador. */
.atm-dirty[hidden] { display: none; }
/* Apagado se tiene que VER apagado: un morado lleno que no hace nada se lee
   como roto. La casa no tiene un `.btn:disabled` general; cada contexto pone
   el suyo, igual que `.period-foot`. */
.atm-toolbar .btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }

/* El lienzo: alto de pantalla menos el shell — el flujo se recorre
   con el canvas, no con el scroll de la página. */
.atm-canvas {
    height: calc(100vh - 180px); min-height: 420px;
    background: var(--card-bg); border: 1px solid var(--border);
    border-radius: 0 0 var(--radius) var(--radius); overflow: hidden;
}
/* De borde a borde (`data-borde-a-borde` en el `.content`): la barra y el
   lienzo pierden su marco propio —las esquinas las recorta la tarjeta— y en
   escritorio, donde la tarjeta tiene alto fijo, el lienzo llena lo que la
   barra deja. En el celular sigue el alto calculado de arriba. */
.content[data-borde-a-borde] .atm-toolbar {
    border: 0; border-bottom: 1px solid var(--border); border-radius: 0;
}
.content[data-borde-a-borde] .atm-canvas { border: 0; border-radius: 0; }
@media screen and (min-width: 768px) {
    .content[data-borde-a-borde] .atm-builder {
        display: flex; flex-direction: column; height: 100%;
    }
    .content[data-borde-a-borde] .atm-toolbar { flex: none; }
    .content[data-borde-a-borde] .atm-canvas { flex: 1; height: auto; min-height: 0; }
}

/* La bitácora de corridas. Cada corrida es un `<tbody>`: su renglón dice qué
   viajó en UNA línea, y la franja de abajo —a todo el ancho— trae el paso a
   paso cuando se abre. Abrir es el `<details>` de la celda «Qué viajó»; su
   `summary::before` cubre la fila entera, así que se toca en cualquier
   parte. Sin JavaScript: `:has()` muestra la franja. */
.atm-runs td.atm-nw { white-space: nowrap; width: 1%; }
.atm-runs .atm-run > tr:first-child { position: relative; }
.atm-runs td.atm-viaje { max-width: 0; width: 100%; }
.atm-viaje details > summary { list-style: none; cursor: pointer; }
.atm-viaje details > summary::-webkit-details-marker { display: none; }
.atm-viaje details > summary::before { content: ''; position: absolute; inset: 0; }
/* El enlace al flujo (la bitácora de todas) queda por encima del velo. */
.atm-runs .atm-run > tr:first-child a { position: relative; z-index: 1; }
.atm-viaje-linea {
    display: flex; align-items: center; gap: var(--space-150);
    white-space: nowrap; min-width: 0;
}
/* Lo que viajó, en texto y no en fichas: el pedido en negrita, el cliente
   en gris y el total. Si no cabe se acorta el cliente con «…», nunca hasta
   una letra (`min-width`), y antes que eso se recorta la línea entera. */
.atm-que { display: flex; align-items: baseline; gap: var(--space-100); min-width: 0; overflow: hidden; }
.atm-que > b { font-weight: 600; flex: none; }
.atm-que-sep { color: var(--text-faint); flex: none; }
.atm-que-nombre { color: var(--text-light); overflow: hidden; text-overflow: ellipsis; min-width: 7ch; }
.atm-que-total { flex: none; font-variant-numeric: tabular-nums; }
/* Cómo terminó: UNA pastilla corta (`resumen.desenlace`) y la flechita,
   siempre al final y del mismo tamaño en cada fila. */
.atm-viaje-fin { margin-left: auto; flex: none; display: flex; align-items: center; gap: var(--space-150); }
.atm-viaje-chev {
    width: 24px; height: 24px; display: grid; place-items: center; flex: none;
    border: 1px solid var(--border); border-radius: 8px; color: var(--text-faint);
    transition: transform .15s;
}
.atm-viaje details[open] .atm-viaje-chev { transform: rotate(180deg); }
.atm-runs td.atm-flujo { font-weight: 600; max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.atm-runs .atm-franja { display: none; }
.atm-runs .atm-run:has(details[open]) .atm-franja { display: table-row; }
.atm-runs .atm-run:has(details[open]) td { border-bottom-color: transparent; }
.atm-runs .atm-franja > td { padding-top: 0; }

.atm-tono-ok { color: var(--ok-text); }
.atm-tono-danger { color: var(--danger-text); }
.atm-tono-warn { color: var(--warn-text); }
.atm-tono-muted { color: var(--text-faint); }

/* El paso a paso: una tarjeta por paso (`_run_detail.html`). */
.atm-pasos { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-100); }
.atm-paso {
    border: 1px solid var(--border); border-radius: var(--radius-card);
    padding: var(--space-150) var(--space-200); background: var(--card-bg);
}
.atm-paso-cabeza { display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap; }
.atm-paso-n {
    width: 24px; height: 24px; border-radius: 50%; display: grid; place-items: center;
    background: var(--primary-soft); color: var(--primary);
    font-size: var(--fs-label); font-weight: 600; flex: none;
}
.atm-paso-nombre { font-weight: 600; }
.atm-paso-meta { margin-left: auto; display: flex; gap: var(--space-100); font-size: var(--fs-small); }
.atm-paso-frase, .atm-paso-pares, .atm-paso-condiciones, .atm-crudo, .atm-paso .atm-step-error {
    margin: var(--space-100) 0 0 var(--space-400);
}
.atm-paso-pares {
    display: grid; grid-template-columns: max-content 1fr; gap: var(--space-025) var(--space-200);
    font-size: var(--fs-small);
}
.atm-paso-pares dt { color: var(--text-light); }
.atm-paso-pares dd { margin: 0; overflow-wrap: anywhere; }
.atm-paso-condiciones { list-style: none; padding: 0; font-size: var(--fs-small); }
.atm-crudo > summary {
    display: inline-flex; cursor: pointer; list-style: none; font-size: var(--fs-small);
    color: var(--primary); font-weight: 500;
}
.atm-crudo > summary::-webkit-details-marker { display: none; }
.atm-crudo-caja { position: relative; margin-top: var(--space-050); }
.atm-crudo-caja > .btn { position: absolute; top: var(--space-050); right: var(--space-050); }
.atm-crudo-caja pre {
    margin: 0; padding: var(--space-100) var(--space-150); max-height: 320px; overflow: auto;
    background: var(--bg); border: 1px solid var(--border);
    border-radius: var(--radius-field); font-size: var(--fs-label); white-space: pre;
}
@media (max-width: 767px) {
    .atm-paso-frase, .atm-paso-pares, .atm-paso-condiciones, .atm-crudo, .atm-paso .atm-step-error {
        margin-left: 0;
    }
    .atm-paso-pares { grid-template-columns: 1fr; }
    .atm-paso-pares dd { margin-bottom: var(--space-050); }
}
.atm-step-error {
    margin-top: var(--space-025); padding: var(--space-050) var(--space-100);
    background: var(--danger-bg); color: var(--danger-text);
    border-radius: var(--radius-field); font-size: var(--fs-label);
    white-space: pre-wrap;
}

/* ============================================================
   INTEGRACIONES — el directorio tipo apps: una tarjeta por servicio
   conectable, con su logo y su estado. Las tarjetas las contribuyen
   los módulos dueños por hook; esta pantalla solo agrega.
   ============================================================ */
/* La cara de vidrio (Renzo, 2026-10-18: «con los logos, más pro y más
   glass, como Alegra»): las fichas que filtran arriba, un `.panel-vidrio`
   por clase con su conteo, y cada servicio una `.tarjeta-vidrio` ENTERA
   que es el enlace: logo, nombre con su estado, para qué sirve y la
   flecha. Dos por fila en escritorio, una en el teléfono. */
.directorio { display: flex; flex-direction: column; gap: var(--space-200); }
.directorio > .tabs--fichas { margin: 0 var(--space-025); }
.directorio__conteo { font-size: var(--fs-small); color: var(--text-light); white-space: nowrap; }
.directorio__rejilla {
    list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-200);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr));
}
.directorio__rejilla > li { display: flex; min-width: 0; }
.card.tarjeta-vidrio.tarjeta-directorio {
    flex: 1; min-width: 0; flex-direction: row; align-items: center; gap: var(--space-200);
    padding: var(--space-200) var(--space-200) var(--space-200) var(--space-200); color: inherit; text-decoration: none;
}
.card.tarjeta-vidrio.tarjeta-directorio--enlace { transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease; }
.card.tarjeta-vidrio.tarjeta-directorio--enlace:hover {
    transform: translateY(-2px);
    border-color: var(--primary-soft-border);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 22px 36px -24px var(--vidrio-sombra), 0 1px 3px rgba(16, 18, 22, .05);
}
.card.tarjeta-vidrio.tarjeta-directorio--enlace:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
/* El sello verde en la esquina del logo: «esto ya está conectado» se lee
   de un vistazo en una rejilla de quince, sin leer pastillas. */
.tarjeta-directorio__logo { position: relative; flex: none; }
.tarjeta-directorio__sello {
    position: absolute; right: -5px; bottom: -5px;
    display: grid; place-items: center; width: 22px; height: 22px;
    border-radius: 50%; background: var(--ok-text); color: #fff;
    box-shadow: 0 0 0 3px var(--card-bg);
}
.tarjeta-directorio__sello svg { width: 12px; height: 12px; }
.tarjeta-directorio__texto { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-050); }
.tarjeta-directorio__nombre { display: flex; align-items: center; gap: var(--space-100); min-width: 0; }
.tarjeta-directorio__titulo {
    min-width: 0; font-size: var(--fs-columna); font-weight: 700; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tarjeta-directorio__nombre .pill { flex: none; }
/* Lo que impide algo («Lo necesitan: Nómina»), con su candado, debajo de
   para qué sirve (Módulos, 2026-10-31). */
.tarjeta-directorio__aviso {
    display: flex; align-items: center; gap: var(--space-050);
    color: var(--warn-text); font-size: var(--fs-small);
}
.tarjeta-directorio__aviso svg { width: 14px; height: 14px; flex: none; }
/* Sin enlace, lo que se puede hacer va a la derecha: el engranaje,
   «Instalar», el ⋮. */
.tarjeta-directorio__botones { flex: none; display: inline-flex; align-items: center; gap: var(--space-075); }
.tarjeta-directorio__desc {
    color: var(--text-light); font-size: var(--fs-small); line-height: 1.45;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.tarjeta-directorio__accion {
    flex: none; display: inline-flex; align-items: center; gap: var(--space-075);
    color: var(--text-faint); font-size: var(--fs-small); font-weight: 600;
}
.tarjeta-directorio__accion svg { width: 18px; height: 18px; transition: transform .18s ease; }
.tarjeta-directorio--enlace:hover .tarjeta-directorio__accion { color: var(--primary); }
.tarjeta-directorio--enlace:hover .tarjeta-directorio__accion svg { transform: translateX(3px); }
/* «Conectar» solo aparece al pasar el mouse: en reposo la rejilla es de
   logos y nombres, no de quince botones; en el teléfono no hay hover y la
   flecha basta. */
.tarjeta-directorio__asoma { opacity: 0; transition: opacity .18s ease; }
.tarjeta-directorio--enlace:hover .tarjeta-directorio__asoma, .tarjeta-directorio--enlace:focus-visible .tarjeta-directorio__asoma { opacity: 1; }
/* Lo que todavía no existe se apaga un poco: sigue ahí para que se sepa
   que viene, pero no compite con lo que sí se puede conectar hoy. */
/* Morado lo conectado, gris claro lo demás (Renzo, 2026-10-18): la rejilla
   dice qué tienes sin leer una pastilla. El gris es fijo y no un token del
   tema porque la baldosa es blanca en los dos. */
.tarjeta-directorio .logo-marca { --logo-tinta: #c3bfd0; }
.tarjeta-directorio--activa .logo-marca { --logo-tinta: var(--primary); }
.tarjeta-directorio--pendiente .tarjeta-directorio__titulo { color: var(--text-light); }
@media (max-width: 720px) {
    .panel-vidrio.directorio__grupo { padding: var(--space-200) var(--space-100) var(--space-100); border-radius: 22px; }
    .card.tarjeta-vidrio.tarjeta-directorio { padding: var(--space-200) var(--space-150) var(--space-200) var(--space-200); gap: var(--space-150); }
    .tarjeta-directorio__nombre { flex-wrap: wrap; row-gap: var(--space-025); }
    .tarjeta-directorio__titulo { white-space: normal; }
    .tarjeta-directorio .logo-marca { width: 48px; height: 48px; border-radius: 14px; }
    .tarjeta-directorio .logo-marca__dibujo { width: 34px; height: 26px; }
    .tarjeta-directorio__asoma { display: none; }
}

/* ── Plantillas: el editor de correos ──────────────────────────────────
   La fila hereda la anatomía del árbol del customizer; lo propio del
   correo es la agarradera SIEMPRE visible y el resumen bajo el nombre —
   un correo se lee de arriba a abajo y el orden es contenido. El preview
   lleva la barra de un cliente de correo: el asunto rendido es parte de
   lo que se está diseñando, no un adorno. */
.mail-editor-drag { cursor: grab; color: var(--pill-text); flex-shrink: 0;
                    padding: 0 var(--space-025); user-select: none; }
.mail-editor-drag:active { cursor: grabbing; }
.customizer-node.is-dragging { opacity: .45; }
.mail-editor-sub { display: block; font-size: var(--fs-small);
                   color: var(--pill-text); white-space: nowrap;
                   overflow: hidden; text-overflow: ellipsis;
                   font-weight: 400; }
.mail-editor-formbar { display: flex; align-items: center;
                       justify-content: space-between;
                       margin-bottom: var(--space-100); }
.mail-preview-chrome { padding: var(--space-100) var(--space-150);
                       border-bottom: 1px solid var(--border);
                       background: var(--card-bg); }
/* La barra de la previa: la frase, el selector de datos y «Enviar prueba»
   en una fila que se PARTE en pantalla angosta, con separación vertical —
   apilados y pegados se leen como un solo control. El `gap` de dos valores
   es justo eso: menos aire entre filas que entre columnas. */
.mail-preview-meta { display: flex; flex-wrap: wrap; align-items: center;
                     gap: var(--space-050) var(--space-100);
                     margin: 0; font-size: var(--fs-small);
                     color: var(--pill-text); }
/* La frase va en UN solo `<span>` y no suelta: en un contenedor flex cada
   nodo de texto es un item aparte, y «De:», el remitente y «— rendido con»
   se partirían en tres renglones distintos.

   Y acá NO va el `flex: 1 1 0` que manda `docs/marco-ui.md` para el banner.
   Allá el problema era que la línea se partía cuando debía ENCOGER; acá
   partirse es justo lo que se quiere en pantalla angosta, y con `1 1 0` la
   frase se estira y empuja el selector al otro extremo — lejos del
   «rendido con» del que es continuación. Queda el `flex` por omisión, y
   `min-width: 0` para que un remitente largo encoja en vez de desbordar. */
.mail-preview-from { min-width: 0; }
/* El select se alinea al BOTÓN y no al revés: el botón es el de la casa y
   su alto es el mismo en todo el ERP, mientras que este select es uno suelto
   dentro de una frase. Con el relleno por omisión quedaban 35 contra 39 —el
   glifo de 15px del botón manda sobre la interlínea de 13—, y cuatro píxeles
   de diferencia entre dos controles lado a lado se ven torcidos.

   Los dos números son los que declara `.btn`, no calibrados a ojo: el
   relleno de 11px es el suyo, y la interlínea de 15px es el alto de su
   glifo (`.btn svg`), que es lo que le fija el alto del contenido. Con
   `line-height: normal` el select daba 39,5 contra 39 — medio píxel, que
   según el zoom se ve como uno. Si `.btn` cambia, acá hay que cambiarlo, y
   por eso está dicho. */
.mail-preview-meta > select { flex: none; padding-block: var(--space-150);
                              line-height: 15px; }

/* «Enviar prueba» usa EL botón de la casa (`ui/_btn.html`, tono ghost,
   tamaño small): trae su borde, su alto y su glifo —que sale del verbo
   «Enviar», no de esta pantalla—. Ghost y no `primary` porque compite con
   «Guardar», que es la acción de la pantalla: dos botones sólidos al lado
   convierten una decisión en dos. Acá solo va el acuse, que no es un botón. */
.mail-preview-test-acuse { color: var(--pill-text); }

/* LA BARRA DEL NAVEGADOR de la vista previa (2026-09-24): dispositivos a
   la izquierda, la dirección en el centro como una barra de URL, y a la
   derecha deshacer, rehacer y pantalla completa. */
/* La pieza (`ui/_preview_bar.html`): la barra arriba y el escenario abajo,
   a todo el alto de la columna. */
.preview-frame { display: flex; flex-direction: column; height: 100%; }
/* `hidden` sobre un `.btn` o sobre la dirección no esconde nada: los dos
   declaran su `display`. «Salir» y la dirección sin datos se esconden así. */
.preview-actions [hidden], .preview-url[hidden] { display: none; }
.preview-bar { display: flex; align-items: center; gap: var(--space-100);
               padding: var(--space-100) var(--space-150);
               border-bottom: 1px solid var(--border);
               background: var(--card-bg); }
.preview-devices { display: flex; gap: var(--space-025); flex: none; }
.preview-devices .btn.is-active { background: var(--primary-soft);
                                  color: var(--primary-dark); }
.preview-url { flex: 1; min-width: 0; display: flex; align-items: center;
               gap: var(--space-075); padding: var(--space-100) var(--space-150); border-radius: 999px;
               background: var(--bg); border: 1px solid var(--border);
               font-size: var(--fs-small); color: var(--text-light);
               text-decoration: none; }
.preview-url svg { width: 14px; height: 14px; flex: none; }
.preview-url-text { overflow: hidden; text-overflow: ellipsis;
                    white-space: nowrap; }
.preview-url[href]:hover { color: var(--primary-dark);
                           border-color: var(--primary-soft-border); }
.preview-actions { display: flex; gap: var(--space-025); flex: none; align-items: center; }
/* El ESCENARIO: el marco centrado sobre el fondo del panel, con el ancho
   del dispositivo elegido (`--marco`, que pone el editor). */
.preview-stage { flex: 1; min-height: 0; position: relative;
                 overflow: hidden; background: var(--bg); }
.preview-stage iframe { position: absolute; top: 0; left: 0;
                        transform-origin: 0 0; border: 0;
                        background: #fff; }
/* Pantalla completa: la columna de la vista previa fija a la ventana. */
.vento-customizer__preview.is-fullscreen { position: fixed; inset: 0;
                                           z-index: 1000; }
@media (max-width: 900px) {
  .preview-url { display: none; }
}

/* El selector de bloques: tarjetas alargadas con su glifo, dos por fila —
   el molde del selector de bloques de tiendapp, con la factura del kit. */
.mail-picker { display: grid; grid-template-columns: 1fr 1fr;
               gap: var(--space-050); margin-top: var(--space-050); }
.mail-picker-item { display: flex; align-items: center; gap: var(--space-050);
                    padding: var(--space-100) var(--space-150); background: var(--card-bg);
                    border: 1px solid var(--border);
                    border-radius: var(--radius-field); cursor: pointer;
                    font: inherit; font-size: var(--fs-small);
                    font-weight: 500; color: inherit; text-align: left;
                    width: 100%; }
.mail-picker-item:hover { border-color: var(--primary-soft-border);
                          background: var(--primary-soft);
                          color: var(--primary-dark); }
.mail-picker-item svg { width: 18px; height: 18px; flex-shrink: 0;
                        color: var(--primary); }

/* El botón que abre el modal de agregar: una fila entera, porque es la
   única acción del pie de la lista de bloques. */
.mail-picker-open { margin-top: var(--space-100); }
.mail-picker-open .btn { width: 100%; justify-content: center; }

/* EL modal de agregar bloque: el buscador del customizer con las tarjetas
   del selector adentro. Mismo molde que `.quiz-modal` —el `<dialog>` de la
   casa—, más angosto porque su contenido son dos columnas de tarjetas y no
   un formulario de ocho campos. */
.block-picker {
    width: min(480px, calc(100vw - var(--space-300))); max-height: 82vh;
    padding: 0; border: 1px solid var(--border);
    border-radius: var(--radius-lg); background-color: var(--card-bg);
    color: var(--text); box-shadow: var(--shadow-lg);
}
.block-picker::backdrop { background-color: rgba(13, 11, 22, .45); }
.block-picker-body { max-height: 82vh; overflow-y: auto;
                     padding: 0 var(--space-250) var(--space-250); }
/* La cabecera se queda arriba al desplazar, como la del modal del examen:
   con la lista larga, el «cerrar» se iba de pantalla. */
.block-picker-head {
    position: sticky; top: 0; z-index: 1;
    display: flex; align-items: center; justify-content: space-between;
    margin: 0 calc(var(--space-250) * -1) var(--space-200);
    padding: var(--space-200) var(--space-250); border-bottom: 1px solid var(--border);
    background-color: var(--card-bg);
}
.block-picker-head h3 { margin: 0; font-size: var(--fs-section);
                        font-weight: 700; }
.block-picker-search { width: 100%; margin-bottom: var(--space-100); }

/* Los ajustes generales de la página: una fila por lado, con la escala de
   pasos a la derecha. Rótulo y escala en la misma línea porque son quince
   controles en un panel angosto — uno debajo del otro, la sección sola
   ocupaba la pantalla entera. */
.design-panel { margin-top: var(--space-150); }

/* Los invitados de una página «solo con invitación», dentro del panel de
   ajustes del editor (313px): la casilla con su botón en una línea, y una
   fila por correo con sus dos acciones en icono. Una tabla de tres columnas
   se salía del panel por la derecha. */
.page-guests-add { display: flex; gap: var(--space-100); align-items: center; }
.page-guests-add input { flex: 1; min-width: 0; }
.page-guests-list { list-style: none; margin: var(--space-150) 0 0; padding: 0; }
.page-guests-row {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-100) 0; border-top: 1px solid var(--border);
}
.page-guests-who { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.page-guests-who .field-hint { margin-top: var(--space-025); }
.page-guests-email {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500;
}
.page-guests-actions { display: flex; gap: var(--space-050); flex: none; }
.design-row { display: flex; align-items: center; gap: var(--space-100);
              margin-bottom: var(--space-075); }
.design-row-label { flex: 1; color: var(--text-light);
                    font-size: var(--fs-small); }
.design-scale { display: inline-flex; gap: var(--space-025); flex-shrink: 0; }
/* Los pasos son un segmentado: mismos anchos, para que la escala se lea
   como una escala y no como cinco botones sueltos de tamaños distintos. */
.design-step {
    min-width: 30px; padding: var(--space-050) var(--space-050); cursor: pointer;
    border: 1px solid var(--border); background-color: var(--card-bg);
    color: var(--text-light); font: inherit; font-size: var(--fs-small);
    font-weight: 600; border-radius: var(--radius-field);
}
.design-step:hover:not(.is-active) { border-color: var(--primary-soft-border);
                                     background-color: var(--primary-soft);
                                     color: var(--primary-dark); }
.design-step.is-active { border-color: var(--primary);
                         background-color: var(--primary);
                         color: #fff; }
.design-step:disabled { cursor: default; opacity: .55; }
.design-bg { display: flex; align-items: center; gap: var(--space-100); }
.design-check { margin: var(--space-100) 0 var(--space-050); }

/* El menú de la barra `/` del editor de plantillas: la piel del finder,
   flotando bajo el campo activo — fijo porque el campo puede vivir dentro
   de un panel con su propio scroll. */
.mail-var-menu { position: fixed; z-index: 60; max-height: 280px;
                 overflow-y: auto; }

/* La tarjeta de un valor de captura: Guarda/Botón arriba, el icono y su
   color abajo — una fila apretada de cinco controles no se entendía. */
.mail-value-card { border: 1px solid var(--border);
                   border-radius: var(--radius-field);
                   padding: var(--space-100); margin-bottom: var(--space-050);
                   background: var(--card-bg); }

/* El selector de icono y el picker de color de un valor de captura. */
.mail-icon-row { display: flex; align-items: center; gap: var(--space-050);
                 margin-bottom: var(--space-050); flex-wrap: wrap; }
.mail-icon-choice { width: 34px; height: 34px; display: inline-flex;
                    align-items: center; justify-content: center;
                    background: var(--card-bg);
                    border: 1px solid var(--border);
                    border-radius: var(--radius-field); cursor: pointer;
                    color: var(--pill-text); padding: 0; }
.mail-icon-choice svg { width: 18px; height: 18px; }
.mail-icon-choice:hover { border-color: var(--primary-soft-border);
                          color: var(--primary); }
.mail-icon-choice.is-active { border-color: var(--primary);
                              background: var(--primary-soft);
                              color: var(--primary); }
.mail-swatch-row { display: inline-flex; align-items: center; gap: var(--space-050); }
.mail-swatch { width: 22px; height: 22px; border-radius: 50%;
               border: 1px solid var(--border); cursor: pointer;
               padding: 0; }
.mail-swatch.is-active { outline: 2px solid var(--primary);
                         outline-offset: 1px; }
.mail-swatch-free { width: 34px; height: 26px; padding: 0;
                    border: 1px solid var(--border);
                    border-radius: var(--radius-field);
                    background: var(--card-bg); cursor: pointer; }

/* «Volver a generar con IA» del bloque Imagen (2026-11-01): el despliegue
   va en el panel, debajo del campo, con los productos como filas cortas. */
.mail-ia { display: grid; gap: var(--space-100); }
.mail-ia__form { display: grid; gap: var(--space-100);
                 padding: var(--space-150);
                 border: 1px solid var(--border);
                 border-radius: var(--radius-field);
                 background: var(--card-bg); }
.mail-ia__productos { list-style: none; margin: 0; padding: 0;
                      display: grid; gap: var(--space-050); }
.mail-ia__productos li { display: flex; align-items: center;
                         justify-content: space-between;
                         gap: var(--space-100); }
.mail-ia__acciones { display: flex; gap: var(--space-100); }

/* El conmutador del preview de plantillas: Correo / Después del clic. */
.mail-preview-toprow { display: flex; align-items: center;
                       justify-content: space-between; gap: var(--space-100); }
.mail-seg { display: inline-flex; border: 1px solid var(--border);
            border-radius: var(--radius-field); overflow: hidden;
            flex-shrink: 0; }
.mail-seg button { border: 0; background: var(--card-bg); cursor: pointer;
                   font: inherit; font-size: var(--fs-small);
                   padding: var(--space-075) var(--space-150); color: var(--pill-text); }
.mail-seg button + button { border-left: 1px solid var(--border); }
.mail-seg button.is-active { background: var(--primary-soft);
                             color: var(--primary-dark);
                             font-weight: 600; }

/* Las filas de bloques del editor de plantillas como TARJETAS — el mismo
   lenguaje del picker de agregar: borde, radio, glifo morado. */
.mail-blocks .customizer-node { background: var(--card-bg);
                                border: 1px solid var(--border);
                                border-radius: var(--radius-field);
                                padding: var(--space-100) var(--space-100);
                                margin-bottom: var(--space-050); }
.mail-blocks .customizer-node:hover { background: var(--card-bg);
                                      border-color:
                                        var(--primary-soft-border); }
.mail-blocks .customizer-node.is-open { background: var(--primary-soft); }
.mail-block-ico { flex-shrink: 0; width: 18px; height: 18px;
                  color: var(--primary); }
.mail-block-ico svg { width: 18px; height: 18px; display: block; }
/* En la estructura, el glifo va en BALDOSA (2026-09-24, la maqueta de
   Renzo): un cuadrado suave que separa el icono del nombre. En el picker
   sigue suelto, que ahí la tarjeta entera es la baldosa. */
.mail-blocks .mail-block-ico { width: 30px; height: 30px; display: grid;
                               place-items: center;
                               border-radius: var(--radius-sm);
                               background: var(--primary-soft); }
.mail-blocks .mail-block-ico svg { width: 16px; height: 16px; }
.mail-blocks .customizer-node-actions .menu-pop { min-width: 160px; }

/* Las pestañas de variante de una página: «lo que ve el que respondió
   5» — chips arriba de la lista de bloques. */
.mail-variants { display: flex; flex-wrap: wrap; gap: var(--space-050);
                 margin-bottom: var(--space-050); align-items: center; }
.mail-variant-chip { border: 1px solid var(--border);
                     background: var(--card-bg); cursor: pointer;
                     font: inherit; font-size: var(--fs-small);
                     padding: var(--space-050) var(--space-100); border-radius: 999px;
                     color: var(--pill-text); }
.mail-variant-chip.is-active { background: var(--primary-soft);
                               border-color: var(--primary-soft-border);
                               color: var(--primary-dark);
                               font-weight: 600; }
.mail-variant-new { border: 1px dashed var(--border);
                    border-radius: 999px; padding: var(--space-050) var(--space-100);
                    font: inherit; font-size: var(--fs-small);
                    background: transparent; width: 7.5em; }

/* ── El asistente «Nueva pregunta por correo» ─────────────────────────
   Dos columnas: los cuatro pasos a la izquierda, el preview del motor
   real a la derecha. En angosto, el preview cae abajo. */
.asistente { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
             gap: var(--space-100); align-items: start; }
@media (max-width: 1000px) { .asistente { grid-template-columns: 1fr; } }
.asistente__pasos { padding: var(--space-150); }
.asistente__pasos h2 { margin: 0 0 var(--space-100); }
.asistente__recetas { display: flex; gap: var(--space-050); flex-wrap: wrap; }
.asistente__chips { display: flex; flex-direction: column; gap: var(--space-075);
                    margin-bottom: var(--space-100); }
.asistente__chip { display: flex; align-items: center; gap: var(--space-100);
                   border: 1px solid var(--border); border-radius: 10px;
                   padding: var(--space-075) var(--space-100); background: var(--card-bg); }
.asistente__chip input[type="text"] { flex: 1; min-width: 0; }
.asistente__chip-glifo { font-size: var(--fs-glyph); line-height: 1;
                         min-width: 22px;
                         text-align: center; }
.asistente__tabs { display: flex; flex-wrap: wrap; gap: var(--space-050);
                   margin-bottom: var(--space-100); }
.asistente__tab { display: inline-flex; align-items: center; gap: var(--space-075);
                  border: 1px solid var(--border); background: var(--card-bg);
                  cursor: pointer; font: inherit;
                  font-size: var(--fs-small); padding: var(--space-050) var(--space-100);
                  border-radius: 999px; color: var(--pill-text); }
.asistente__tab.is-activa { background: var(--primary-soft);
                            border-color: var(--primary-soft-border);
                            color: var(--primary-dark); font-weight: 600; }
.asistente__check { display: flex; align-items: center; gap: var(--space-100);
                    margin: var(--space-050) 0; cursor: pointer; }
.asistente__disparadores { display: flex; flex-direction: column;
                           gap: var(--space-100); margin-bottom: var(--space-100); }
.asistente__disparador { display: grid;
                         grid-template-columns: auto 1fr; gap: var(--space-025) var(--space-100);
                         border: 1px solid var(--border);
                         border-radius: 10px; padding: var(--space-100) var(--space-150);
                         cursor: pointer; }
.asistente__disparador.is-activo { border-color: var(--primary-soft-border);
                                   background: var(--primary-soft); }
.asistente__disparador input { grid-row: span 2; align-self: center; }
.asistente__disparador-rotulo { font-weight: 600; }
.asistente__disparador-desc { grid-column: 2; font-size: var(--fs-small);
                              color: var(--text-light); }
.asistente__errores { border: 1px solid #fca5a5; background: #fef2f2;
                      color: #b91c1c; border-radius: 10px;
                      padding: var(--space-100) var(--space-150); margin-bottom: var(--space-100); }
.asistente__errores p { margin: var(--space-025) 0; }
.asistente__pie { display: flex; gap: var(--space-050); align-items: center;
                  margin-top: var(--space-100); }
.asistente__preview { padding: 0; overflow: hidden; position: sticky;
                      top: var(--space-100); }
.asistente__preview-cabeza { padding: var(--space-100) var(--space-200);
                             border-bottom: 1px solid var(--border);
                             font-size: var(--fs-small);
                             color: var(--text-light); }
.asistente__preview iframe { width: 100%; height: 70vh; border: 0;
                             display: block; background: #f6f6f7; }

/* El × de eliminar en la lista de plantillas: `.icon-btn` nació blanco
   para la topbar oscura — dentro de una celda necesita su propio tinte,
   y en rojo al pasar: borrar se anuncia. */
.cell-actions { width: 1%; white-space: nowrap; }
/* Los controles del final de una fila, juntos y pegados a la derecha.
   NO es `.inline-row`: aquélla es de FORMULARIO y envuelve a propósito —está
   escrito en su regla, y es lo correcto cuando un campo compite con dos
   botones—, pero `.cell-actions` mide `width: 1%`, así que ahí envolver
   significa apilar cada control en su propio renglón. Se vio con «Abrir» y el
   menú de Dominios, uno debajo del otro.

   El hueco es de 6px y no de `--sp-3`: son controles de la misma acción, no
   dos campos de un formulario. */
.row-actions {
    display: flex; align-items: center; justify-content: flex-end;
    gap: var(--space-075); flex-wrap: nowrap;
}
.cell-actions .icon-btn { color: var(--text-light); }
.cell-actions .icon-btn:hover { background: #fee2e2; color: #b91c1c; }

/* ── El menú de más acciones ───────────────────────────────────────────────
   Un `<details class="menu">` con su disparador y su desplegable. Lo pinta
   `ui/_menu_button.html`; lo que lleva adentro lo escribe cada pantalla.

   Es el TERCER menú que se escribe en esta hoja y el único que queda. Antes
   hubo `.user-menu` —el de la topbar, que se queda porque es oscuro y vive en
   otra superficie, pero ya comparte la clase para heredar el cierre— y
   `.crear-menu`, el de Plantillas, que se borró acá: sus 26 líneas tenían
   CERO usos en plantillas el 2026-08-11. La guarda de la hoja mira
   plantilla→CSS y no al revés, así que el CSS muerto no lo caza nadie; se vio
   contando a mano al escribir esto.

   Lo que hace que sea uno y no cuatro es que el disparador sea un parcial. La
   parte que se copia mal —apagar el marcador en Safari, poner el
   `aria-label`— es justo la que no se ve al mirar la pantalla.

   El cierre al clic de afuera y con Esc lo pone `menu.js`, en `document`. */
.menu { position: relative; }
.menu-trigger {
    list-style: none; cursor: pointer;
    display: inline-flex; align-items: center; gap: var(--space-050);
    height: 32px; padding: 0 var(--space-100) 0 var(--space-100); border-radius: var(--radius-sm);
    color: var(--text-faint);
    font-size: var(--fs-small); font-weight: 500; white-space: nowrap;
}
/* Safari pinta su propio triángulo y lo hace por `::-webkit-details-marker`,
   no por `list-style`. Las dos líneas hacen falta. */
.menu-trigger::-webkit-details-marker { display: none; }
.menu-trigger svg { width: 18px; height: 18px; }
/* «Opciones», junto a los puntos. */
.menu-trigger-text { line-height: 1; }
.menu-trigger:hover { background: var(--bg); color: var(--text); }
.menu-trigger:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.menu[open] .menu-trigger { background: var(--bg); color: var(--text); }
/* A la DERECHA porque el menú vive al final de una fila: anclado a la
   izquierda, el desplegable de la última columna se sale de la tabla. */
/* EL MENÚ DE LA ÚLTIMA FILA SE CORTABA (Renzo, 2026-09-08, mirando las
   direcciones de un tercero). `.table-wrap` lleva `overflow-x: auto` para el
   desplazamiento lateral, y su propio comentario ya lo advierte: eso hace que
   el OTRO eje compute a `auto`, así que la tabla también recorta en vertical.
   El menú de la última fila caía 94px por debajo del borde y ahí se perdía
   —no se puede desplazar hasta él, porque un absoluto recortado no alarga
   nada—.

   **Se abre hacia arriba**, que es lo que hace todo el mundo y no mueve un
   píxel de la página. Con TRES filas o más, porque lo que recorta no es la
   ventana sino la tabla: encima de la última fila tiene que caber el menú, y
   eso son 97px contra una fila de 52 más el encabezado. Medido, no estimado.

   Con una o dos filas no cabe ni arriba ni abajo DENTRO de la tabla, y eso no
   lo arregla la hoja: lo hace `menu.js`, que en ese caso pasa el menú a
   `fixed` y lo decide contra la ventana. Acá vivió un `padding-bottom` que
   hacía crecer la tabla para darle sitio; se quitó porque empujaba media
   pantalla hacia abajo cada vez que alguien abría el menú de una ficha con
   una sola dirección —«lo veo todo mal», Renzo, el mismo día—.

   Sin `overflow: visible`: quitarle el recorte a la tabla mientras el menú
   está abierto le devolvería el scroll lateral a cero y dejaría que una
   tabla ancha se pintara fuera de la tarjeta, que es lo que §6.1 prohíbe. */
.table tbody:has(tr:nth-child(3)) tr:nth-last-child(-n+2) .menu-pop {
    top: auto; bottom: calc(100% + 6px);
}
.menu-pop {
    position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
    min-width: 220px; padding: var(--space-075);
    background: var(--card-bg); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    text-align: left; white-space: nowrap;
}
/* Un `<form>` dentro del menú es el camino de lo destructivo —POST, nunca un
   GET que borra— y no tiene por qué separar de sus vecinos: la separación la
   pone el relleno del ítem. */
.menu-pop form { margin: 0; }
/* Sirve a un `<a>` y a un `<button>` con la misma regla: el ítem que navega y
   el que envía un formulario se ven igual porque para quien lee son lo
   mismo. `font: inherit` y `text-align` existen sólo por el `<button>`, que
   trae los suyos del navegador. */
.menu-item {
    display: flex; align-items: center; gap: var(--space-100); width: 100%;
    padding: var(--space-100) var(--space-100); border: 0; border-radius: var(--radius-sm);
    background: transparent; cursor: pointer;
    font: inherit; font-size: var(--fs-body); text-align: left;
    color: var(--text); text-decoration: none;
}
.menu-item:hover { background: var(--primary-soft); color: var(--primary-dark); }
.menu-item svg { width: 15px; height: 15px; flex: none; }
/* Lo que BORRA, en rojo y al final de la lista. El tinte llega en el hover y
   no en reposo: una lista con un ítem rojo permanente se lee como una alarma
   abierta, y esto es sólo una opción más hasta que se apunta a ella. */
.menu-item-danger:hover { background: var(--danger-bg); color: var(--danger-text); }
/* La raya entre lo que navega y lo que borra. Un `<hr>` traería sus márgenes
   de fábrica y habría que deshacerlos. */
.menu-sep { height: 1px; margin: var(--space-050) var(--space-075); background: var(--border); }
/* El rótulo de un grupo de ítems, cuando el menú lista COSAS y no acciones
   —los hilos del Asistente— y hace falta decir de qué son. Se lee como el
   encabezado de una tarjeta, que es el mismo gesto en otro tamaño. */
.menu-titulo { margin: var(--space-050) var(--space-100) var(--space-075); font-size: var(--fs-label); font-weight: 600;
               letter-spacing: .04em; text-transform: uppercase;
               color: var(--text-faint); }
/* El ítem VIGENTE: el orden que ya está aplicado, el hilo que estás leyendo.
   Nació en el ⋮ del tablero de Alistamiento y lo necesitó el menú de hilos:
   por la regla de la casa, sube acá en vez de copiarse. */
.menu-item[aria-current="true"] { font-weight: 600; color: var(--primary-dark); }
/* «Ordenar por: Nombre ▾», el de la casa (`ui/_ordenar.html`, 2026-10-31).
   Un botón fantasma que dice el orden vigente y abre el menú de la casa con
   todos: escala a los órdenes que haga falta, cosa que el segmentado no.
   El rótulo va en gris y el vigente en negrita, como se lee: «por… esto». */
.ordenar__boton { list-style: none; gap: var(--space-050); }
.ordenar__boton::-webkit-details-marker { display: none; }
.ordenar__boton svg { width: 16px; height: 16px; flex: none; }
.ordenar__rotulo { color: var(--text-light); font-weight: 400; }
.ordenar__flecha { transition: transform .15s ease; }
.ordenar[open] .ordenar__flecha { transform: rotate(180deg); }
/* La hoja del teléfono sube desde el borde y el velo aparece (Renzo,
   2026-10-08). La curva es la de las hojas de iOS: arranca rápido y se
   posa. El movimiento reducido lo apaga la regla universal de la casa. */
@keyframes hoja-sube { from { transform: translateY(100%); } }
@keyframes velo-aparece { from { opacity: 0; } }
/* ── En el teléfono, todo menú es una HOJA desde abajo (Renzo, 2026-10-01) ─
   Colgado de su disparador, el ⋮ de la banda se abría hacia la izquierda y
   quedaba cortado fuera de la pantalla: `right: 0` está pensado para el
   final de una fila, y en el teléfono el botón vive pegado a la izquierda.
   Pegado abajo y de lado a lado no se sale nunca, queda donde llega el
   pulgar, y es lo que ya hacía la hoja del selector de período.

   La geometría va con `!important`, como la regla táctil de los campos y
   por lo mismo: media docena de reglas de contexto la pisarían
   (`.period .menu-pop`, las últimas filas de una tabla que abren hacia
   arriba, la campanita, el menú de hilos) y `menu.js` y `period-picker.js`
   escriben `style` en línea. Enumerarlas arreglaría estas y no la séptima.

   El velo es el `::before` del `<summary>` del menú abierto: tocarlo ES
   tocar el disparador, así que el `<details>` se cierra solo —sin
   `menu.js`, y también en Safari de iOS, que no despacha `click` sobre un
   nodo que no es clicable—. El panel de Columnas (`ui/_columns.html`) es
   otro `<details>` y entra a la misma regla. */
@media (max-width: 640px) {
    /* De borde a borde y apoyada abajo, como una hoja de app (2026-10-08):
       con 8px de margen se leía como una tarjeta flotando. El relleno de
       abajo respeta la barra del iPhone. */
    .menu-pop, .colmenu-panel {
        position: fixed !important;
        top: auto !important; left: 0 !important; right: 0 !important;
        bottom: 0 !important;
        width: auto !important; min-width: 0 !important; max-width: none !important;
        max-height: 85dvh !important;
        overflow-y: auto; overscroll-behavior: contain;
        z-index: 160; padding: 0 0 calc(var(--space-100) + env(safe-area-inset-bottom));
        border: 0; border-radius: 20px 20px 0 0;
        white-space: normal;
        /* La letra de la hoja: la escala de la casa por un factor. Fue 1.25
           mientras el teléfono usaba la escala del escritorio; con la suya
           (al final de la hoja) ya no hay que agrandar nada. */
        --hoja: 1;
        animation: hoja-sube .25s cubic-bezier(.32, .72, 0, 1);
    }
    /* La CABEZA: el agarre dibujado arriba y el título, pegados al
       desplazar. El título es `data-hoja-titulo` de la propia hoja (lo pone
       la plantilla o `menu.js` al abrir); sin él, solo el agarre. */
    .menu-pop::before, .colmenu-panel::before {
        content: attr(data-hoja-titulo);
        display: block; position: sticky; top: 0; z-index: 1;
        padding: var(--space-300) var(--space-200) var(--space-100); text-align: center;
        font-size: calc(var(--fs-section) * var(--hoja)); font-weight: 600;
        color: var(--text);
        background:
            linear-gradient(var(--border-strong), var(--border-strong))
                center 9px / 36px 5px no-repeat,
            var(--card-bg);
    }
    .menu[open] > summary::before, .colmenu[open] > summary::before {
        content: ''; position: fixed; inset: 0; z-index: 159;
        background: rgba(17, 16, 29, .44);
        animation: velo-aparece .2s ease-out;
        transition: opacity .25s ease-out;
    }
    /* Cerrando (`menu.js` pone `data-cerrando` mientras la hoja baja): el
       velo se apaga con ella. */
    .menu[data-cerrando] > summary::before,
    .colmenu[data-cerrando] > summary::before { opacity: 0; }
    /* Y que ningún VIDRIO la atrape: un `backdrop-filter` o un `filter`
       vuelve al elemento el bloque contenedor de sus descendientes `fixed`,
       y la hoja se pegaba a él y no a la ventana. Pasó con la tira del
       Inicio y la banda de Analíticas cuando se volvieron de vidrio
       (2026-10-04): la hoja del período salía arriba, fuera de la
       pantalla, con el velo recortado en la forma de la tira (Renzo,
       2026-10-08). Mientras el menú está abierto ningún ancestro esmerila;
       el velo tapa ese vidrio de todos modos. Así sirve para el vidrio que
       venga, sin anotarlo uno por uno. */
    *:has(.menu[open]),
    *:has(.colmenu[open]) {
        -webkit-backdrop-filter: none !important;
        backdrop-filter: none !important;
        filter: none !important;
    }
    /* El fondo quieto: detrás del velo no se desplaza nada. */
    html:has(.menu[open]),
    html:has(.colmenu[open]) { overflow: hidden; }
    /* Los renglones, para el pulgar: 52px, letra de la hoja, ícono de 22px
       y una raya fina entre uno y otro. `.menu-pop` delante para ganarle a
       `.menu-pop .btn.menu-item` (los `_btn.html` del menú). */
    .menu-pop .menu-item,
    .menu-pop .btn.menu-item {
        min-height: 52px; padding: 0 var(--space-200); gap: var(--space-200);
        border-radius: 0; border-top: 1px solid var(--border);
        font-size: calc(var(--fs-body) * var(--hoja));
    }
    .menu-pop .menu-item svg { width: 22px; height: 22px; }
    /* Lo que borra, rojo desde ya: en el teléfono no hay hover que lo
       avise. */
    .menu-pop .menu-item-danger { color: var(--danger-text); }
    /* La CABEZA RICA (`ui/_hoja_cabeza.html`): baldosa, título, bajada y
       ✕. Con ella la hoja no pinta su título simple, y el agarre se dibuja
       acá. */
    .menu-pop:has(> .hoja-cabeza)::before { display: none; }
    .hoja-cabeza {
        display: flex; align-items: center; gap: var(--space-200);
        position: sticky; top: 0; z-index: 1;
        padding: var(--space-300) var(--space-200) var(--space-200);
        background:
            linear-gradient(var(--border-strong), var(--border-strong))
                center 9px / 36px 5px no-repeat,
            var(--card-bg);
    }
    .hoja-cabeza .tile { width: 52px; height: 52px; border-radius: 14px; }
    .hoja-cabeza .tile svg { width: 26px; height: 26px; }
    .hoja-cabeza-texto { flex: 1; min-width: 0; }
    .hoja-cabeza-titulo {
        margin: 0; font-size: var(--fs-page); font-weight: 700;
        letter-spacing: -.01em; color: var(--text);
    }
    .hoja-cabeza-bajada {
        margin: var(--space-025) 0 0; color: var(--text-light);
        font-size: calc(var(--fs-body) * var(--hoja));
    }
    .hoja-cerrar {
        display: grid; place-items: center; flex: none;
        width: 40px; height: 40px; border: 0; border-radius: 12px;
        background: var(--bg); color: var(--text-light); cursor: pointer;
    }
    .hoja-cerrar svg { width: 20px; height: 20px; }
    /* El panel de Columnas no son renglones `.menu-item` sino casillas con
       su pie: aire a los lados (la hoja va de borde a borde) y casillas
       para el pulgar (revisión final, 2026-10-08). */
    .colmenu-panel { padding-inline: var(--space-200); }
    .colmenu-item {
        min-height: 52px; gap: var(--space-200); border-top: 1px solid var(--border);
        font-size: calc(var(--fs-body) * var(--hoja));
    }
}

/* Con un menú abierto, el banner deja de recortar — su `overflow: hidden`
   existe para el círculo decorativo y se comería el desplegable; el círculo
   se esconde ese instante para no sangrar por las esquinas. */
.page-banner:has(.menu[open]) { overflow: visible; z-index: 30; }
/* Y la tabla tampoco: `.table-wrap` hace scroll propio —es lo que evita que
   el `body` se desplace de lado— y ese scroll RECORTA. Sin esto, el menú de
   la última fila sale cortado por el filo de la tarjeta, que es un defecto
   que sólo aparece con datos y nunca en una pantalla de ejemplo con dos.

   SOLO SIN `menu.js` (2026-10-22). Con el script, todo menú dentro de una
   tabla se ubica `fixed` contra la ventana (`place()`), que ya no lo recorta
   nada; y volver `visible` el `overflow` de la tabla al abrir tiraba su
   desplazamiento lateral a cero —en Existencias con muchas sedes la celda
   que se tocó se iba de la pantalla—. `menu.js` marca `<html
   data-menu-js>` al cargar; sin él queda este respaldo, como antes. */
html:not([data-menu-js]) .table-wrap:has(.menu[open]) { overflow: visible; }
/* El panel del buscador NO va acá (2026-10-04): quitarle el scroll a la
   tabla mientras estaba abierto la devolvía de golpe a su inicio y la montaba
   sobre la columna de al lado. Dentro de una tabla, `finder.js` lo pone fijo
   (`.finder-panel-fijo`) y ya no lo recorta ningún filo. */

/* Los ajustes de la superficie en el editor de bloques: plegados al pie
   del panel. El tope es de los bloques — la anatomía del customizer,
   donde los ajustes del tema viven en su propio botón. */
/* Los ajustes de la superficie (correo, página) plegados al pie del panel.
   Una TARJETA y no una línea gris con «⚙»: Renzo (2026-09-07) la veía
   perdida —glifo chiquito, rótulo chiquito—. El summary es un botón ancho
   con el engranaje a 20px, el rótulo al cuerpo y la flecha que gira. */
.fold { margin-top: var(--space-150); }
.fold > summary {
    list-style: none; cursor: pointer;
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-150);
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--bg); color: var(--text);
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:hover { border-color: var(--text-faint); }
.fold[open] > summary { border-bottom-left-radius: 0; border-bottom-right-radius: 0; background: var(--card-bg); }
.fold-icon { flex: none; width: 20px; height: 20px; color: var(--primary); }
.fold-label { flex: 1; font-size: var(--fs-body); font-weight: 600; }
/* Cuántas cosas hay adentro, para decidir si vale la pena abrir. */
.fold-count {
    flex: none; padding: 1px var(--space-100); border-radius: 999px;
    background: var(--primary-soft); color: var(--primary-dark);
    font-size: var(--fs-meta); font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.fold-chevron { flex: none; width: 16px; height: 16px; color: var(--text-faint); transition: transform .15s ease; }
.fold[open] .fold-chevron { transform: rotate(180deg); }
.fold-body {
    padding: var(--space-150) var(--space-150) var(--space-100);
    border: 1px solid var(--border); border-top: 0;
    border-radius: 0 0 var(--radius) var(--radius);
}
/* Un plegable cuyo cuerpo es una TABLA (las provisiones del mes por
   cuenta, con la fila de cada persona): la tabla va de borde a borde, sin
   el relleno del cuerpo, y el scroll de lado es el de `.table-wrap`. */
.fold-tabla > .fold-body { padding: 0; overflow: hidden; }
/* El total y la alerta de la cabecera (`ui/_fold_head.html`, 2026-10-30, el
   flujo de pagos): la cifra a la derecha y el filo rojo de lo vencido. */
.fold-total { flex: none; font-size: var(--fs-body); font-weight: 700; font-variant-numeric: tabular-nums; }
.fold > summary.fold-alerta { box-shadow: inset 4px 0 0 var(--danger-text); }
/* Encima de una lista de plegables: cómo agrupar a la izquierda y, a la
   derecha, «Colapsar todos» y las columnas (el flujo de pagos, 2026-10-30). */
.lista-herramientas { display: flex; align-items: center; justify-content: flex-start; gap: var(--space-100); flex-wrap: wrap; }
.lista-herramientas-fin { margin-left: auto; display: flex; align-items: center; gap: var(--space-100); }
/* «Seleccionar lo que se puede pagar» de un plegable, arriba de su tabla. */
.fold-acciones { margin: 0; padding: var(--space-100) var(--space-200); }
/* Por aprobar: al pie de cada proveedor, los avisos en una línea callada a la
   izquierda y los botones a la derecha (Santiago, 2026-10-31). */
.aprobar-pie { display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-150) var(--space-200); flex-wrap: wrap; padding: var(--space-200);
  border-top: 1px solid var(--border); }
.aprobar-pie .card-hint { margin: 0; }
/* Las facturas de Por aprobar respiran más que una lista (Santiago,
   2026-10-31: «las filas aún están muy apretadas»). La lista de la casa va
   apretada para que quepan quinientas; aquí son pocas y cada una se firma. */
.aprobar-lista .fold-tabla .table tbody td { padding-top: var(--space-150); padding-bottom: var(--space-150); }
.aprobar-pie .aprobar-acciones { padding: 0; margin-left: auto; }
.aprobar-acciones .btn { white-space: nowrap; }
.aprobar-acciones { flex-wrap: wrap; }
/* Por aprobar (flujo de pagos, 2026-10-30): los botones del grupo a la
   derecha, bajo su tabla; en el teléfono, una tarjeta por proveedor en
   vez de los plegables. */
.aprobar-acciones { justify-content: flex-end; padding: var(--space-150) var(--space-200); margin: 0; }
.aprobar-tarjetas { display: none; }
.aprobar-tarjeta { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-100); }
.aprobar-tarjeta .equipo-cifra, .aprobar-tarjeta .card-hint { margin: 0; }
.aprobar-tarjeta-cabeza { display: flex; justify-content: space-between; align-items: center; gap: var(--space-100); }
@media (max-width: 640px) {
    .aprobar-tarjetas { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-150); }
    .aprobar-lista { display: none; }
}

/* ── Comandas: la barra ────────────────────────────────────────────────────
   La única pantalla del ERP que se lee DE REOJO, a un metro de distancia y
   con las manos ocupadas. Todo lo de acá sale de esa restricción y no del
   gusto: el número grande porque es lo que se grita, la grilla de tarjetas
   porque una tabla obliga a seguir una fila con el dedo, y los botones
   anchos porque se tocan con el pulgar y no con un cursor.

   `auto-fill` con mínimo de 260px: la misma pantalla sirve en la tablet del
   mostrador (dos tarjetas) y en el monitor de la barra (cinco o seis), sin
   una consulta de medios por aparato. */
.kds-board {
    display: grid; gap: var(--space-150);
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    align-items: start;
}
.kds-card { padding: var(--space-150); display: flex; flex-direction: column;
            gap: var(--space-100); }
/* La demorada, en rojo y con borde: el color solo no alcanza a un metro de
   distancia, y el borde es lo que la separa del resto de un vistazo. Solo
   mientras sigue abierta — ver el comentario de la plantilla. */
.kds-card-late { background: var(--danger-bg); border-color: var(--danger-text); }
.kds-card-head { display: flex; align-items: baseline;
                 justify-content: space-between; gap: var(--space-100); }
/* El número ES el contenido de la tarjeta: es lo que el barista grita y lo
   que el cliente mira en su tiquete. Enlaza a la ficha, y por eso se
   subraya al pasar por encima como cualquier enlace del ERP. */
.kds-number { font-size: var(--fs-number); font-weight: 700; line-height: 1;
              color: var(--text); text-decoration: none; }
.kds-number:hover { text-decoration: underline; }
/* Los minutos esperando. En versalitas y apagado: informa, no compite con
   el número. En la demorada hereda el rojo de la tarjeta. */
.kds-clock { font-size: var(--fs-badge); font-weight: 700; color: var(--text-light);
             text-transform: uppercase; letter-spacing: .06em; }
.kds-card-late .kds-clock { color: var(--danger-text); }
.kds-customer { margin: 0; font-size: var(--fs-small); color: var(--text-light); }
.kds-lines { margin: 0; padding: 0; list-style: none; display: flex;
             flex-direction: column; gap: var(--space-050); }
.kds-line { display: flex; gap: var(--space-100); font-size: var(--fs-body);
            color: var(--text); }
/* La cantidad en su propia columna y tabular: «2 un» y «250 g» tienen que
   alinearse, o la lista se lee como un párrafo. */
.kds-line-qty { flex: 0 0 4.5rem; font-weight: 700;
                font-variant-numeric: tabular-nums; }
.kds-note { margin: 0; padding: var(--space-075) var(--space-100); border-radius: var(--radius-field);
            background: var(--warn-bg); color: var(--warn-text);
            font-size: var(--fs-small); }
/* El avance ocupa el ancho; cancelar va debajo, chico y sin color. Es la
   misma jerarquía que el «Salir» del kiosco: lo destructivo sobre una
   pantalla que se toca todo el día no puede verse como lo frecuente. */
.kds-actions { margin-top: auto; display: flex; flex-direction: column;
               gap: var(--space-075); align-items: stretch; }
.kds-actions form { margin: 0; }
.kds-actions .btn-small { align-self: center; }

/* ── La dirección que alguien va a teclear en un aparato ───────────────────
   La URL que se copia y se pega en la tablet del mostrador, o que se teclea
   con el control remoto de un televisor. En monoespaciada y en su propia caja
   porque es un dato que se LEE carácter por carácter, no una frase.

   Nació como `.kds-url` y se renombró al llegar la segunda pantalla que la
   necesitaba —el editor de Pantallas, con la misma tarea: copiar una
   dirección y llevarla a un aparato—. Un `.kds-url` dentro de `signage` es
   cómo empiezan los primos que `docs/marco-ui.md` viene a evitar; el nombre
   dice qué ES y no de qué módulo salió. */
.url-box {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-100) var(--space-150); margin-bottom: var(--space-100);
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--bg);
}
/* Dentro de una celda el margen de abajo sobra: la celda centra el bloque
   entero, y con el margen la caja quedaba unos píxeles más arriba que el
   nombre de al lado (Repartidores, 2026-09-14). */
.table td .url-box { margin-bottom: 0; }
/* `break-all` y no `nowrap`: en una pantalla angosta la URL con un UUID no
   cabe, y cortarla con puntos suspensivos esconde justo la parte que hay que
   copiar. */
.url-box-valor { flex: 1; font-size: var(--fs-small); color: var(--text);
                 word-break: break-all; }
.url-box-acciones { display: flex; gap: var(--space-100); }
/* El botón de copiar. Existía el marcado —`data-copy` en la pantalla de
   dominios— y no existían ni la regla ni el JavaScript: el botón se veía
   como texto suelto y no copiaba nada. `assets/js/copy.js` es la otra
   mitad. */
.btn-copy { white-space: nowrap; }

/* ── El televisor del editor de Pantallas ──────────────────────────────────
   El marco donde vive el reproductor de verdad, en un `<iframe>`. La
   proporción es la del APARATO —16/9, la de cualquier televisor de sala— y se
   mantiene aunque la cartelera de adentro esté girada: lo que gira es el
   contenido, no el aparato colgado de la pared.

   `aspect-ratio` y no el truco del `padding-top`: el truco obliga a
   posicionar el hijo en absoluto y a que el ALTO sea una consecuencia del
   ancho, así que no hay forma de ponerle un techo sin romperlo. Acá el techo
   es una línea (`max-height`) y hace falta: a 1600px de ventana la columna
   principal mide 900 y un 16/9 sin tope son 506px de televisor, que empuja el
   guion —la tabla de láminas— debajo del pliegue. */
.tv-marco {
    position: relative; width: 100%;
    aspect-ratio: 16 / 9; max-height: 60vh;
    /* Negro literal y no un token: es lo que se ve entre dos láminas y lo que
       muestra una pantalla apagada, o sea el color del APARATO. Un gris de la
       hoja lo pintaría de ERP y ya no se parecería al televisor. */
    background: #000;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-field);
    overflow: hidden;
}
.tv-marco iframe {
    position: absolute; inset: 0; width: 100%; height: 100%; border: 0;
    display: block;
}
/* El rótulo de por qué está negro.
   Un rectángulo negro no distingue «apagada» de «sin láminas» de «se está
   cargando», y las tres se arreglan de forma distinta. Sin esto, quien mira
   el editor cree que el reproductor está roto — que es justamente lo que la
   vista previa venía a descartar antes de subirse a la silla. */
.tv-aviso {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    padding: var(--space-100) var(--space-200);
    border-radius: 999px;
    background: rgba(255, 255, 255, .10);
    color: #fff; font-size: var(--fs-small); font-weight: 600;
    text-align: center; pointer-events: none;
}

/* La banda de abajo del marco: el pie con lo que el televisor está haciendo.
   Va PEGADA al marco y no en el encabezado de la tarjeta porque describe lo
   que se está mirando —«5 láminas · 40 s de vuelta»—, y a media pantalla de
   distancia de la imagen eso deja de leerse como su pie. */
.tv-pie {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-050) var(--space-150);
    margin-top: var(--space-100);
    color: var(--text-light); font-size: var(--fs-small);
}

/* ── Reparto ─────────────────────────────────────────────────────────────
   El tablero del día: una columna por zona, más «Sin ubicar» y «Sin zona».
   Es una tira que SE DESPLAZA de lado en su propio contenedor y no en el
   body — la regla de la casa para lo ancho, la misma de `table-wrap`: con
   seis zonas y una pantalla de portátil, envolver las columnas en dos filas
   rompe la metáfora de las pilas de la bodega. */
.reparto-tablero {
    display: flex; gap: var(--space-200);
    overflow-x: auto; padding-bottom: var(--space-100);
}
.reparto-columna { flex: 0 0 320px; max-width: 320px; }

/* El color de la zona, el mismo del polígono en el mapa: es lo que hace que
   una columna y su dibujo se reconozcan como la misma cosa de un vistazo. */
.reparto-color {
    display: inline-block; width: 14px; height: 14px; border-radius: 4px;
    flex: 0 0 auto;
}

.reparto-pila { list-style: none; margin: 0; padding: 0; }
.reparto-pila > li {
    padding: var(--space-100) 0;
    border-bottom: 1px solid var(--border);
}
.reparto-pila > li:last-child { border-bottom: 0; }
.reparto-pila-mas { margin: var(--space-100) 0 0; color: var(--text-light); font-size: var(--fs-small); }

/* El desplegable de corregir un pin a mano: cerrado por omisión porque son
   dos clics al año, y abierto ocupa el alto de un mapa por cada pedido sin
   ubicar. */
.reparto-pin > summary { list-style: none; cursor: pointer; }
.reparto-pin > summary::-webkit-details-marker { display: none; }


/* **El mapa se le montaba a la banda del ERP.** Leaflet pinta sus paneles en
   `z-index: 400` y sus controles en `1000`, y la escala de esta hoja es
   topbar 100, sidebar 101, savebar 110, drawer 200: le gana a todas.

   La salida NO es subir la topbar —eso es una carrera que se pierde con el
   siguiente widget— sino encerrar el mapa en su propio contexto de
   apilamiento: un elemento posicionado con `z-index` distinto de `auto`
   contiene adentro toda la escala de sus hijos, así que el 1000 de Leaflet
   pasa a valer 1000 DENTRO del cuadro y 0 fuera de él. */
.reparto-mapa { position: relative; z-index: 0; }

.reparto-mapa {
    height: 260px; margin: var(--space-100) 0;
    border: 1px solid var(--border); border-radius: var(--radius);
    overflow: hidden;
}
.reparto-mapa-alto { height: 420px; }

/* La hoja de ruta impresa: papel, porque el teléfono se descarga y la moto
   no tiene cargador. */
.reparto-hoja { padding: var(--space-250); color: #000; background: #fff; }
.reparto-hoja h1 { margin: 0 0 var(--space-100); }

/* El pin numerado que pinta Leaflet. Vive en la hoja del ERP y también en la
   del reparto: son dos superficies distintas que dibujan el mismo mapa, y un
   pin sin regla se ve como un cuadro azul sin número. */
.mapa-pin span {
    display: grid; place-items: center;
    width: 26px; height: 26px; border-radius: 50%;
    color: #fff; font-size: var(--fs-small); font-weight: 700;
    box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}

/* El enlace de un repartidor dentro de su fila. Se trunca porque es un UUID
   —nadie lo lee, se copia— y a lo ancho empujaba el menú de acciones fuera
   de la tarjeta. */
.reparto-enlace { max-width: 320px; }
.reparto-enlace .url-box-valor {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Los avisos del reparto: «Sin ubicar» y «Sin zona», ARRIBA del tablero.
   Envuelven en vez de desplazarse —al revés que la tira de zonas— porque son
   lo que alguien tiene que atender hoy, y lo que se atiende no puede quedar
   fuera de la pantalla. */
.reparto-avisos {
    display: flex; flex-wrap: wrap; gap: var(--space-200);
    margin-bottom: var(--space-200);
}
.reparto-aviso { flex: 1 1 320px; min-width: 300px; }

/* AQUÍ VIVIÓ el aire de la columna que sigue a una cifra, retirado el
   2026-08-14 junto con `.cell-pad`.

   Decía: «una columna `num` está pegada a su borde derecho por definición,
   así que la siguiente empieza justo ahí» — «POR COBRARESTADO» en el
   encabezado, el monto besando la pastilla de estado en cada fila. Su
   premisa era que `.table thead th` no lleva relleno horizontal, y desde
   hoy sí lo lleva: la regla pedía exactamente `var(--space-200)`, que es el
   valor por omisión, o sea que ya no hacía nada.

   Vale la pena dejar dicho el patrón, porque es el argumento del cambio
   entero: el MISMO defecto se parchó TRES veces por separado —`.cell-pad`
   en contabilidad, esta regla en pedidos, y «Accesorios4» en productos, que
   no se parchó nunca— antes de que alguien lo arreglara donde nace. Tres
   parches locales de una falla global son la señal de que el arreglo está
   en el lugar equivocado. */

/* ── El tablero del reparto, segunda pasada ──────────────────────────────
   Lo que cambió y por qué: la columna de una zona era un encabezado de
   tarjeta con la cuenta en letra pequeña, y la pregunta que se hace parado
   frente al tablero es «¿cuántos hay en el norte?». Ahora la cifra es lo
   grande y el nombre lo acompaña — al revés de como estaba. */
.reparto-zona-head {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-200) var(--space-200) var(--space-150);
}
/* La baldosa con el COLOR de la zona: el mismo del polígono en el mapa, que
   es lo que hace que la columna y su dibujo se reconozcan como la misma
   cosa. El color entra por `--zona` desde la fila, porque lo elige la
   empresa y una hoja no puede tener una regla por zona de cada cliente. */
.reparto-tile { --zona: var(--primary); }
.reparto-tile .tile { background: var(--zona); color: #fff; }
.reparto-zona-nombre {
    margin: 0; font-size: var(--fs-small); font-weight: 700;
    letter-spacing: .04em; text-transform: uppercase;
    color: var(--text-light);
}
.reparto-zona-cuenta { margin: var(--space-025) 0 0; color: var(--text-light); }
.reparto-zona-cuenta strong {
    font-size: var(--fs-section); color: var(--text); margin-right: var(--space-050);
}
.reparto-armar { padding: var(--space-150) var(--space-200) var(--space-200); }

/* El enlace que continúa una frase: «…una zona que los cubra. Dibuja una
   zona ›». La flecha va en `::after` y no en el texto para que no se pueda
   copiar ni la lea un lector de pantalla como un carácter suelto. */
.link-next { font-weight: 600; white-space: nowrap; }
.link-next::after { content: " ›"; }

/* El avance de una ruta: una longitud, no dos números que hay que dividir.
   El texto se queda al lado porque una barra sola no dice cuántas paradas
   son — y «0 de 3» y «0 de 30» se ven igual de vacías. */
.reparto-avance { display: flex; align-items: center; gap: var(--space-150); }
.reparto-barra {
    display: block; width: 120px; height: 6px; border-radius: 999px;
    background: var(--border); overflow: hidden; flex: 0 0 auto;
}
.reparto-barra > span {
    display: block; height: 100%; border-radius: 999px;
    background: var(--primary);
}
.reparto-avance-texto {
    color: var(--text-light); font-size: var(--fs-small);
    font-variant-numeric: tabular-nums;
}

/* El estado con punto. NO es una pastilla: en una tabla de veinte rutas
   veinte pastillas pintan más que las filas. El punto da el color y la
   palabra da el significado a quien no distingue los colores. */
.reparto-estado { display: inline-flex; align-items: center; gap: var(--space-100); }
.reparto-estado::before {
    content: ""; width: 7px; height: 7px; border-radius: 50%;
    background: var(--text-light); flex: 0 0 auto;
}
.reparto-estado-draft::before { background: var(--text-light); }
.reparto-estado-assigned::before { background: var(--primary); }
.reparto-estado-in_progress::before { background: var(--ok-text); }
.reparto-estado-closed::before { background: var(--border); }
.reparto-estado-cancelled::before { background: var(--danger-text); }

/* ── El árbol de trazabilidad ──────────────────────────────────────────────
   Los dos árboles de la ficha del lote: de dónde vino la tanda y a dónde
   fue. La sangría con borde a la izquierda y no con viñetas: lo que hay que
   seguir con el ojo es la PROFUNDIDAD —cuántos pasos hay entre el saco del
   proveedor y la bolsa que se vendió—, y una viñeta por nivel compite con
   el enlace del lote, que es lo único en lo que se hace clic.

   Sin `list-style` y sin números por la misma razón: los hermanos de un
   nivel no tienen orden que importe —dos componentes de una receta entraron
   juntos— y numerarlos sugeriría una secuencia que el libro no afirma. */
.trace-branch { list-style: none; margin: 0; padding: 0 0 0 var(--space-150); }
.trace-branch .trace-branch {
    border-left: 1px solid var(--border); margin-top: var(--space-100);
}
.trace-node { padding: var(--space-100) 0 0 var(--space-150); font-size: var(--fs-small); }
/* La hoja —una entrada o una venta— apagada contra el lote: el lote es el
   nodo que se sigue, y esto es el dato que lo explica. */
.trace-leaf {
    margin: var(--space-025) 0 0; font-size: var(--fs-small); color: var(--text-light);
}

/* La sub-fila de una tabla: el reparto por lote de una línea de despacho.
   Apagada y con sangría contra su fila madre — es el detalle de la fila de
   arriba, no una fila más: sin la distinción, una línea con tres lotes se
   lee como cuatro líneas despachadas. */
.row-sub > td { background: var(--bg); font-size: var(--fs-small); }
.row-sub > td:first-child { padding-left: var(--space-200); }

/* Paquetes: la hija de un paquete se sangra bajo su contenedora en la
   tabla de líneas del pedido y en la captura. Solo un margen y el tono
   apagado — no es otra tabla. */
.table .line-child td:first-child { padding-left: 2.25rem; color: var(--text-light); }
.line-child-mark { margin-right: .35rem; }

/* ── Analíticas ──────────────────────────────────────────────────────────
   Los bloques de un informe, en cualquier superficie: el tablero, la
   biblioteca, la vista previa del constructor y —desde el plan d— la ficha
   de otro módulo.

   `--fs-stat` y `tabular-nums` son los mismos de `.home-metric-value`, y eso
   es a propósito: la tira del escritorio va a PASAR a ser un bloque de
   éstos, y si los dos números no se ven idénticos la migración se nota. Las
   reglas de `.home-metric*` se van con ella. */
/* El título de una tarjeta que se puede abrir. Va en la familia
   `card-head-*` y no se llama `.block-title`: ese nombre ya es del editor
   de bloques de pasos, y dos cosas distintas con el mismo nombre es cómo
   empezaron los tres menús desplegables.

   Es un enlace pero no se pinta como uno: en una rejilla de seis, seis
   títulos subrayados son ruido. Se delata al pasar el mouse, que es cuando
   alguien está por hacer clic. */
.card-head-link { color: inherit; text-decoration: none; }
.card-head-link:hover { color: var(--primary); text-decoration: underline; }

.block-value {
    font-size: var(--fs-stat); font-weight: 700; color: var(--text);
    letter-spacing: -.01em; line-height: 1.1; margin: 0;
    font-variant-numeric: tabular-nums;
}
.block-delta {
    display: inline-flex; align-items: center; gap: var(--space-025); margin: var(--space-050) 0 0;
    font-size: var(--fs-badge); font-weight: 700; line-height: 1;
}
.block-delta-up { color: var(--ok-text); }
.block-delta-down { color: var(--danger-text); }
/* La nota del pie: lo que el número NO dice. «Sin período anterior con qué
   comparar» vive acá, y es la razón por la que un delta ausente no se pinta
   como 0%. */
.block-note {
    margin: var(--space-075) 0 0; font-size: var(--fs-small); color: var(--text-light);
}
/* El bloque que no se pudo calcular. NO es un error de sistema: es una
   pregunta que ya no se puede hacer porque su fuente cambió, y se lee como
   tal — tono de aviso, no de alarma. */
.block-error {
    margin: 0; padding: var(--space-150); border-radius: var(--radius);
    background: var(--warn-bg); color: var(--warn-text);
    font-size: var(--fs-small);
}
/* Alto FIJO y no automático: Chart.js mide su contenedor al arrancar, y con
   alto automático el lienzo nace en 0 y la gráfica no se ve nunca. */
.block-chart { position: relative; height: 260px; margin-top: var(--space-100); }

/* La serie con DOS líneas: cada una con su punto, su nombre y su total en
   una fila. Los colores son los mismos tokens que `analiticas.js` lee para
   las líneas —`--primary` y `--chart-muted`—, así el punto y la línea no se
   pueden separar. */
.series-legend {
    list-style: none; margin: 0; padding: 0;
    display: flex; flex-wrap: wrap; gap: var(--space-100) var(--space-250);
}
.series-legend-item { display: flex; align-items: baseline; gap: var(--space-100); }
.series-legend-label { font-size: var(--fs-small); color: var(--text-light); }
.series-dot {
    flex: none; width: 10px; height: 10px; border-radius: 3px;
    align-self: center; background: var(--primary);
}
.series-dot-1 { background: var(--chart-muted); }
.series-legend .block-delta { margin: 0; color: var(--text-light); }
/* La leyenda del ranking que compara: va encima de las barras. */
.ranking-legend { margin-top: var(--space-100); }

/* El embudo: barras propias, sin librería. Cuatro divs no justifican una. */
.funnel { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-100); }
.funnel-step {
    display: grid; grid-template-columns: 12rem 1fr auto; gap: var(--space-150);
    align-items: center;
}
.funnel-label { font-weight: 600; }
.funnel-bar {
    height: 22px; border-radius: var(--radius);
    background: var(--primary); width: var(--ancho, 0%); min-width: 2px;
}
.funnel-rate {
    font-size: var(--fs-small); color: var(--text-light);
    font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* EL CONSTRUCTOR.

   El informe ocupa el lienzo y las decisiones viven en un riel a la
   derecha. Hasta el 2026-09-09 eran dos tarjetas de formulario arriba y la
   vista previa de tercera, debajo del pliegue: se armaba a ciegas. Un
   informe se lee mirándolo mientras se cambia una cosa a la vez, y para eso
   las dos tienen que caber en la misma pantalla.

   El riel tiene ancho FIJO y el lienzo se queda con el resto: con dos
   fracciones, una tabla de ocho columnas empujaba el riel hasta dejar los
   rótulos en dos renglones. */
.builder {
    display: grid; gap: var(--space-250);
    grid-template-columns: minmax(0, 1fr) 300px;
    align-items: start;
}
.builder-canvas { min-width: 0; }
/* Pegajoso: el riel es más corto que un informe con tabla larga, y bajar a
   ver una fila no debería costar el control que se estaba tocando. El tope
   lo pone la topbar del shell. */
.builder-controls {
    position: sticky; top: var(--space-200);
    max-height: calc(100vh - var(--space-400)); overflow-y: auto;
    padding: var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card-bg);
}

/* La banda: de dónde sale y de cuándo. Las tres decisiones que cambian
   TODO lo demás, en una fila y separadas de las que solo cambian el
   dibujo. El nombre se estira porque es el único texto libre. */
.builder-band {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--space-150);
    margin-bottom: var(--space-100);
}
.band-field { display: flex; flex-direction: column; gap: var(--space-075); min-width: 0; }
.band-field:first-child { flex: 1 1 260px; }
.band-label {
    font-size: var(--fs-label); font-weight: 600; color: var(--text-light);
}
.builder-band select, .builder-band input { width: auto; min-width: 160px; }
.band-field:first-child input { width: 100%; }
/* Cada decisión de la banda es un `ui/_field.html`: el aire lo pone el `gap`. */
.band-field > .field { margin-bottom: 0; }
.builder-note {
    margin: 0 0 var(--space-200); font-size: var(--fs-small);
    color: var(--text-light);
}

.control-group { margin-bottom: var(--space-250); }
.control-group:last-child { margin-bottom: 0; }
.control-title {
    margin: 0 0 var(--space-150);
    font-size: var(--fs-small); font-weight: 600; color: var(--text);
}
.control-note {
    margin: 0; font-size: var(--fs-small); color: var(--text-light);
}

/* Las medidas y las agrupaciones se ven COMPLETAS y no dentro de un
   desplegable: cada módulo declara qué mide, y esconder esa lista detrás de
   un clic es esconder justo lo que la fuente sabe hacer. Radios de verdad,
   así que el cambio viaja sin JavaScript. */
.pick { display: flex; flex-direction: column; gap: var(--space-025); }
.pick-row {
    display: flex; align-items: center; gap: var(--space-150);
    padding: var(--space-100) var(--space-100); border-radius: var(--radius-sm);
    font-size: var(--fs-small); cursor: pointer;
}
.pick-row:hover { background: var(--bg); }
.pick-row:has(input:checked) { background: var(--primary-soft); }
.pick-row input[type="radio"] { width: 16px; height: 16px; }
.pick-row input:checked { box-shadow: inset 0 0 0 4px var(--primary); }
.pick-name { flex: 1; min-width: 0; }
.pick-note { color: var(--text-light); }

/* Las formas de pintar, en baldosas: son ocho y se distinguen por su
   glifo antes que por su nombre. El radio se esconde acá —y SOLO acá— y la
   baldosa entera es el control; el foco se dibuja en la baldosa. */
.viz-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-050); }
.viz-tile {
    display: flex; flex-direction: column; align-items: center; gap: var(--space-050);
    padding: var(--space-100) var(--space-025); border-radius: var(--radius-sm);
    color: var(--text-light); cursor: pointer; text-align: center;
}
.viz-tile:hover { background: var(--bg); color: var(--text); }
.viz-tile input[type="radio"] { position: absolute; opacity: 0; pointer-events: none; }
.viz-tile:has(input:checked) {
    background: var(--primary-soft); color: var(--primary-dark);
}
.viz-tile:has(input:focus-visible) { outline: 2px solid var(--primary); outline-offset: 1px; }
.viz-glyph { width: 18px; height: 18px; }
.viz-name { font-size: var(--fs-meta); line-height: 1.2; }

/* El informe pintado. Es lo que ocupa el lienzo, y por eso acá —y solo
   acá— crece: en un tablero el anillo comparte fila con otros dos bloques y
   200px es lo que le toca; en el constructor tiene el lienzo entero y ese
   mismo anillo se lee como una miniatura de sí mismo. La gráfica también
   respira más alto por la misma razón. */
.analytics-preview { margin-top: var(--space-100); }
/* En el lienzo la tarjeta no lleva encabezado: el nombre del informe ya
   está en el banner de la pantalla Y en el campo «Nombre» a dos dedos de
   ahí, y la ventana la dice la banda. Tres veces lo mismo en cien píxeles.
   Lo que queda es el informe, que es de lo que va la pantalla. En un
   tablero el encabezado SÍ hace falta: ahí la tarjeta es una entre varias
   y su nombre es lo único que la distingue. */
.builder-canvas .block > .card-header { display: none; }
.builder-canvas .ring { grid-template-columns: minmax(0, 320px) minmax(0, 1fr); }
.builder-canvas .block-chart { min-height: 320px; }

/* La sección de filtros es un plegable de la casa —`.fold`, el mismo de
   «Ajustes del correo»— y acá solo se le quita el aire de más: dentro del
   riel arranca pegada al grupo anterior y su cuerpo no necesita el sangrado
   de un panel ancho. */
.control-fold { margin: 0; }
.control-fold > .fold-body { padding: var(--space-150) var(--space-100) var(--space-100); }
/* Lo puesto arriba, lo demás como lista de nombres. Sin JavaScript: un
   `<details>` se abre solo, y el filtro que se llena sube al repintar. */
.filters-set .field:last-child { margin-bottom: var(--space-150); }
/* Cada filtro libre es UNA línea que se abre sola. Abrir «Agregar un
   filtro» y encontrar siete controles era la misma pared de campos que la
   sección venía a evitar, solo que detrás de una puerta. El marcador se
   quita por partida doble: `list-style` para todos y el pseudoelemento de
   WebKit, que no lo obedece. */
/* El renglón arranca DEBAJO de la línea, no encima: resaltado y pegado al
   separador se leen como una sola pieza (Renzo, 2026-09-09). Poco cerrado
   —la lista es de ocho— y más cuando está abierto, que es cuando el
   resaltado tiene un control debajo y necesita marcar dónde empieza. */
.filter-slot + .filter-slot { border-top: 1px solid var(--border); }
.filter-slot { padding-top: var(--space-050); }
.filter-name {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-100) var(--space-100); border-radius: var(--radius-sm);
    font-size: var(--fs-small); color: var(--text); cursor: pointer;
    list-style: none;
    /* CONSTANTE, no solo al abrir. Cerrado separa el renglón de la línea
       de abajo; abierto es el aire hasta el control. Con un valor por
       estado el renglón se corría cuatro píxeles al abrirse y se veía
       como un brinco (Renzo, 2026-09-09). */
    margin-bottom: var(--space-050);
}
.filter-name::-webkit-details-marker { display: none; }
.filter-name::before {
    content: '+'; width: 14px;
    color: var(--text-light); font-weight: 600; text-align: center;
}
/* Abierto, el renglón se separa del control: resaltado y pegado al campo
   se leían como una sola pieza —el rótulo pintado del mismo color que la
   caja de abajo— (Renzo, 2026-09-09). El mismo aire que lleva por debajo. */
.filter-slot[open] > .filter-name { font-weight: 600; }
.filter-slot[open] > .filter-name::before { content: '−'; }
.filter-name:hover { background: var(--bg); }
/* El aire de abajo lo pone el RENGLÓN con relleno, no el campo con
   margen: el margen del campo se colapsa hacia afuera del `<details>`
   —que no tiene ni borde ni relleno que lo detenga— y el control quedaba
   pegado a la línea de separación de abajo (Renzo, 2026-09-09). El
   relleno no colapsa nunca. */
/* Solo por DEBAJO: lo que se agregue arriba mueve el renglón al abrirlo, y
   eso es el brinco. El aire de arriba lo pone `.filter-slot`, siempre. */
.filter-slot[open] { padding-bottom: var(--space-150); }
/* El campo, ya sin rótulo: lo dice el renglón que se acaba de abrir. */
.field-desnudo { margin: 0 var(--space-100); }
.filters-list { border-top: 1px solid var(--border); }
.filters-list .field:last-child { margin-bottom: 0; }

/* La rejilla del tablero: doce columnas, y cada bloque dice cuántas ocupa
   con `--ancho-bloque`. Doceavos y no porcentajes porque es el vocabulario
   que el shell ya usa, y dos rejillas con dos vocabularios es cómo se llega
   a que un bloque de «media pantalla» mida distinto en dos sitios. */
.analytics-grid {
    display: grid; gap: var(--space-200);
    grid-template-columns: repeat(12, minmax(0, 1fr));
}
/* Cada tarjeta de una fila mide lo que la más alta.

   La rejilla ya estira la CELDA —`align-items: stretch` es el defecto de
   grid—, pero la tarjeta de adentro conservaba su alto natural y quedaba
   flotando arriba con un hueco debajo. Tres reglas: la celda es un flex,
   la tarjeta crece hasta llenarla, y lo que crece adentro es la GRÁFICA —
   no el título ni la nota del pie, que miden lo que miden.

   Así una fila con un anillo, una serie y una tabla de una fila termina
   pareja, y las tres gráficas quedan con el mismo alto útil. */
.analytics-grid-item { grid-column: span var(--ancho-bloque, 6); display: flex; }
.analytics-grid-item > .block {
    flex: 1; display: flex; flex-direction: column; min-width: 0;
    /* De vidrio (Renzo, 2026-10-04): la receta es `.vidrio`, más arriba;
       detrás rebotan los orbes de `.orbs-pantalla`. */
}
.analytics-grid-item > .block > .block-chart,
.analytics-grid-item > .block > .ring,
.analytics-grid-item > .block > .funnel {
    flex: 1; min-height: 180px;
}
/* La tabla no se estira: una tabla de dos filas estirada a 400px es una
   fila de datos y trescientos píxeles de nada. Lo que crece es el hueco
   debajo, que es honesto. */
.analytics-grid-item > .block > .table { margin-bottom: auto; }
/* La portada de Reportes contables, en estas mismas tarjetas y una por
   fila. La que trae resumen (`.reporte`) pasa de columna a rejilla: el
   encabezado y los botones a la izquierda, y el resumen del mes a la
   derecha, de arriba abajo. El hueco entre columnas es MAYOR que lo que el
   `.card-header` se sale hacia el filo (`--sp-5`), así su raya no toca el
   resumen. Las cifras llegan después de la página (`hx-swap-oob`);
   mientras, `.is-cargando` las deja en gris. En angosto, el resumen baja
   debajo de los botones. */
.analytics-grid-item > .block.reporte {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 340px);
    grid-template-rows: auto 1fr; column-gap: var(--space-400);
}
.reporte > .card-actions { grid-column: 1; align-self: start; }
.reporte > .reporte-cifras { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
.analytics-grid-item > .block > .card-actions { flex-wrap: wrap; }
.reporte-cifras {
    margin: 0; padding: var(--space-050) var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius);
    background: color-mix(in srgb, var(--card-bg) 70%, transparent);
}
.reporte-cifra {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); padding: var(--space-100) 0;
}
.reporte-cifra + .reporte-cifra { border-top: 1px solid var(--border); }
.reporte-cifra dt { font-size: var(--fs-small); color: var(--text-light); }
.reporte-cifra dd {
    margin: 0; font-size: var(--fs-body); font-weight: 600; color: var(--text);
    font-variant-numeric: tabular-nums; text-align: right;
}
.reporte-cifra .reporte-cifra-ok { color: var(--ok-text); }
.reporte-cifra .reporte-cifra-danger { color: var(--danger-text); }
/* El saldo negativo en la tabla del flujo de caja proyectado: el mismo
   rojo que en el resumen, sobre una celda `.num`. */
.table .num.reporte-cifra-danger { color: var(--danger-text); }
.reporte-cifras.is-cargando dd { color: var(--text-faint); }
@media (max-width: 900px) {
    .analytics-grid-item > .block.reporte { grid-template-columns: minmax(0, 1fr); }
    .reporte > .reporte-cifras { grid-column: 1; grid-row: auto; margin-top: var(--space-200); }
}
@media (max-width: 900px) {
    .analytics-grid-item { grid-column: 1 / -1; }
}
@media (max-width: 1100px) {
    /* El riel baja debajo del informe: a menos de eso, 300px de controles
       dejan la gráfica en una tira. Y deja de ser pegajoso, o taparía lo
       que se está mirando. */
    .builder { grid-template-columns: minmax(0, 1fr); }
    .builder-controls { position: static; max-height: none; }
}
@media (max-width: 900px) {
    .funnel-step { grid-template-columns: 1fr; gap: var(--space-025); }
}

/* El anillo: el total en el centro y la leyenda al lado, como el referente
   de Shopify que Renzo pasó el 2026-09-08. La leyenda es HTML y no la de
   Chart.js —que solo pinta rótulos— porque quien mira un anillo quiere el
   número al lado, no pasar el mouse por cada gajo. */
.ring {
    display: grid; gap: var(--space-200); align-items: center;
    grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
    margin-top: var(--space-100);
}
.ring-chart { position: relative; height: 200px; }
/* El centro NO intercepta el mouse: encima del lienzo, Chart.js dejaría de
   recibir el hover y se perderían los tooltips de cada gajo. */
.ring-center {
    position: absolute; inset: 0; pointer-events: none;
    display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: var(--space-025); text-align: center;
}
.ring-total {
    font-size: var(--fs-stat); font-weight: 700; color: var(--text);
    line-height: 1.05; letter-spacing: -.01em;
    font-variant-numeric: tabular-nums;
}
.ring-dash { color: var(--text-light); font-size: var(--fs-small); }
.ring-legend { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-075); }
/* El rótulo primero y el valor debajo cuando no cabe: en una tarjeta de un
   tercio de pantalla, «Bodega Itagüí · $256.168.130» en una línea recortaba
   el NOMBRE a «B.» — que es la mitad inútil de la fila. */
.ring-legend-item {
    display: grid; grid-template-columns: 10px minmax(0, 1fr);
    gap: var(--space-025) var(--space-100); align-items: baseline;
    font-size: var(--fs-small);
}
.ring-legend-value { grid-column: 2; }
.ring-dot { width: 10px; height: 10px; border-radius: 3px; background: var(--border); }
/* La variación de cada gajo, al lado de su valor y sin color (la regla de
   la serie con dos líneas: el anillo no sabe si crecer es bueno). */
.ring-legend-value .block-delta { margin: 0 0 0 var(--space-050); color: var(--text-light); }
.ring-legend-label {
    color: var(--text); overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; font-weight: 600;
}
.ring-legend-value {
    color: var(--text-light); font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* La banda de período del tablero: el mismo control del escritorio, en su
   propia píldora sobre la rejilla. El vidrio es el de la regla `.vidrio`
   (2026-10-11); acá solo el acomodo y su radio, más chico. */
.analytics-period {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-075) var(--space-200);
    --vidrio-radio: 18px;
    width: fit-content;
}
/* La fila de arriba del tablero: el período a la izquierda y, editando, la
   barra de edición a la derecha. El margen de abajo es de la FILA y no de
   la píldora: así las dos piezas bajan parejas. */
.analytics-toolbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap; margin-bottom: var(--space-200);
}

/* ── El tablero en modo edición ──────────────────────────────────────────
   Se edita ENCIMA del tablero, con los datos puestos: lo que se acomoda es
   lo que se va a ver. El panel de la izquierda ofrece lo que falta; cada
   tarjeta trae su agarre, su × y el tirador de la esquina. Los asideros de
   JavaScript son atributos `data-`; las clases de acá solo visten. */
/* Primera y a todo lo ancho: es lo primero que hay que saber al entrar, y
   una nota angosta al lado del rango se leía como parte del rango. */
.tablero-editbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); flex-wrap: wrap;
    margin-bottom: var(--space-200); padding: var(--space-075) var(--space-075) var(--space-075) var(--space-200);
    background: var(--primary-soft); border: 1px solid var(--border);
    border-radius: var(--radius-card);
}
.tablero-editbar-text { margin: 0; font-size: var(--fs-small); color: var(--text); }
.tablero-editbar-actions { display: flex; gap: var(--space-100); }

.tablero-editando {
    display: grid; grid-template-columns: 300px minmax(0, 1fr);
    gap: var(--space-200); align-items: start;
}
/* El panel se queda a la vista mientras la rejilla hace scroll: se agrega
   mirando el tablero, no volviendo arriba cada vez. */
.tablero-panel { position: sticky; top: var(--space-200); }
.tablero-panel-search { margin-bottom: var(--space-150); }
.tablero-panel-search input[type="search"] { min-width: 0; width: 100%; }
.tablero-panel-group {
    margin: var(--space-200) 0 var(--space-100); font-size: var(--fs-small);
    font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
    color: var(--text-light);
}
.tablero-panel-list { list-style: none; margin: 0; padding: 0; }
.tablero-panel-row {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); padding: var(--space-100) 0; border-top: 1px solid var(--border);
}
/* `hidden` es «ya está en el tablero». El `display: flex` de arriba le gana
   a la regla del navegador para `[hidden]`, así que sin esto la fila se
   veía igual con el atributo puesto. */
.tablero-panel-row[hidden] { display: none; }
.tablero-panel-lead { display: flex; flex-direction: column; min-width: 0; }
.tablero-panel-name {
    font-size: var(--fs-body); color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tablero-panel-hint { font-size: var(--fs-small); color: var(--text-light); }
.tablero-panel-empty { padding: var(--space-100) 0; font-size: var(--fs-small); color: var(--text-light); }
/* Lo que el buscador dejó afuera. Aparte de `hidden` —que es «ya está en el
   tablero»— para que borrar la búsqueda no devuelva lo que ya está puesto. */
.tablero-panel-row.is-filtered { display: none; }

/* Las tarjetas mientras se edita: la rejilla arrastrable es la marca. La
   banda de la tarjeta deja sitio al agarre y a la × en vez de taparlos. */
.analytics-grid[data-drag-sort] .analytics-grid-item { position: relative; }
.analytics-grid[data-drag-sort] .analytics-grid-item > .block > .card-header {
    padding-left: calc(var(--space-250) + var(--space-300)); padding-right: calc(var(--space-250) + var(--space-400));
}
.analytics-grid[data-drag-sort] .analytics-grid-item:hover > .block {
    outline: 2px dashed var(--primary); outline-offset: -2px;
}
.grid-tools {
    position: absolute; top: 9px; right: 9px; left: 10px; z-index: 2;
    display: flex; align-items: center; justify-content: space-between;
    pointer-events: none;
}
.grid-tools > * { pointer-events: auto; }
.grid-tools .drag-handle { background: var(--card-bg); border: 1px solid var(--border); }
/* El tirador: la esquina inferior derecha, en L. Se ve tenue hasta que se
   pasa por encima o se está estirando. */
.grid-resize {
    position: absolute; right: 5px; bottom: 5px; z-index: 2;
    width: 14px; height: 14px; cursor: nwse-resize; opacity: .5;
    border-right: 3px solid var(--primary); border-bottom: 3px solid var(--primary);
    border-radius: 0 0 5px 0;
}
.grid-resize:hover, .analytics-grid-item.is-resizing .grid-resize { opacity: 1; }
.analytics-grid-item.is-resizing > .block,
.analytics-grid-item.is-over > .block {
    outline: 2px solid var(--primary); outline-offset: -2px;
}
.analytics-grid-item.is-dragging { opacity: .4; }
.analytics-grid-empty { grid-column: 1 / -1; }
@media (max-width: 1100px) {
    /* El panel baja arriba de la rejilla y deja de ser pegajoso, como el
       riel del constructor: a menos de eso, 300px se comen el tablero. */
    .tablero-editando { grid-template-columns: minmax(0, 1fr); }
    .tablero-panel { position: static; }
}
@media (max-width: 900px) {
    /* Cada bloque ya ocupa el ancho entero: no hay nada que estirar. */
    .grid-resize { display: none; }
}

@media (max-width: 700px) {
    .ring { grid-template-columns: minmax(0, 1fr); }
}

/* ── SEO: la tarjeta de Buscadores y los patrones ─────────────────────────
   La PREVIA imita el resultado de Google: título, descripción y nada más.
   No copia sus colores exactos —sería una promesa que Google rompe cuando
   cambie su diseño— pero sí su jerarquía, que es lo que se está juzgando.

   `.is-derived` es lo que NO escribió una persona: sale del patrón de la
   tienda o del contenido. Va en gris para que se lea «esto ya está resuelto»
   y no «esto está vacío», que es la diferencia que la tarjeta existe para
   comunicar. */
.seo-preview {
    display: flex; flex-direction: column; gap: var(--space-050);
    margin: 0 0 var(--space-200); padding: var(--space-200) var(--space-200);
    background: var(--bg); border: 1px solid var(--border);
    border-radius: var(--radius-sm);
}
.seo-preview-title {
    font-size: var(--fs-body); font-weight: 600; color: var(--primary-dark);
    /* Se corta como lo corta un buscador, no se envuelve en tres renglones:
       ver el corte ES la información que la previa da. */
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.seo-preview-desc {
    font-size: var(--fs-small); line-height: 1.45; color: var(--text);
}
.seo-preview-title.is-derived, .seo-preview-desc.is-derived {
    color: var(--text-light); font-style: italic;
}
.seo-preview-note {
    margin-top: var(--space-075); font-size: var(--fs-small); color: var(--text-light);
}

/* El renglón de fichas debajo de un patrón. Envuelve: son tres hoy y el día
   que sean seis no puede empujar el ancho del formulario. */
.field-tokens {
    display: flex; flex-wrap: wrap; gap: var(--space-075); margin: var(--space-100) 0 0;
}
.field-tokens .chip { cursor: pointer; border: 0; font: inherit; }

/* El encabezado del grupo de patrones, dentro del formulario de ajustes. */
.seo-patterns { margin-top: var(--space-100); }
.seo-patterns h3 {
    margin: 0 0 var(--space-050); font-size: var(--fs-body); font-weight: 600;
}

/* La previa de la imagen para compartir, en Ajustes. */
.setting-preview {
    display: block; height: auto; margin: 0 0 var(--space-100);
    border: 1px solid var(--border); border-radius: var(--radius-sm);
}

/* ── Tareas: tablero, calendario y tarjetas ─────────────────────────── */
.tareas-head { display: flex; align-items: center; gap: var(--space-150); }
/* ── Tareas en vidrio: la maqueta de Renzo (`tablero-tareas.html`,
   2026-10-10) ─────────────────────────────────────────────────────────
   El panel de vidrio (`.tareas-shell`) con el conmutador y la vista, columnas
   de vidrio con su tinte por estado y tarjetas blancas con sombra larga. La
   banda de arriba es la de la casa y no cambia; la fila de herramientas de
   la maqueta no va (Renzo, el mismo día). Los valores son los de la
   maqueta; en oscuro, los mismos papeles con la tinta de la noche.

   SIN `backdrop-filter` en ninguna capa: crea el bloque contenedor de lo
   `fixed`, y el «⋯» de tarjetas y columnas, que `menu.js` saca a `fixed`
   cuando la lista recorta, quedaba atrapado (`docs/marco-ui.md`). Los
   orbes de la casa detrás se probaron y salieron (Renzo, el mismo día). */
.tareas-vidrio, .tablero--vidrio {
    --tv-tinta: #1c1733; --tv-tinta-2: #3b3655; --tv-gris: #77728f;
    --tv-raya: rgba(120, 110, 170, .16); --tv-acento: #6c4cf5;
    --tv-filo: rgba(255, 255, 255, .78);
    --tv-vidrio-arriba: rgba(255, 255, 255, .62); --tv-vidrio-abajo: rgba(255, 255, 255, .36);
    --tv-brillo: rgba(255, 255, 255, .95);
    --tv-sombra: 0 22px 48px -26px rgba(70, 50, 150, .32), 0 2px 8px -3px rgba(70, 50, 150, .08);
    --tv-tarjeta-arriba: rgba(255, 255, 255, .9); --tv-tarjeta-abajo: rgba(255, 255, 255, .68);
    --tv-tarjeta-sombra: 0 1px 2px rgba(60, 40, 120, .05), 0 10px 22px -16px rgba(60, 40, 120, .32);
    --tv-tarjeta-sombra-alta: 0 2px 4px rgba(60, 40, 120, .05), 0 16px 30px -16px rgba(60, 40, 120, .4);
    --tv-rojo-fondo: rgba(255, 226, 231, .85); --tv-rojo: #d3344b;
    --tv-ambar-fondo: rgba(255, 240, 206, .9); --tv-ambar: #b8740c; --tv-ambar-glifo: #eda21a;
    --tv-neutro-fondo: rgba(238, 235, 249, .85); --tv-neutro: #5d5878;
    --tv-col-abierta: rgba(243, 240, 255, .7); --tv-col-curso: rgba(236, 229, 255, .75);
    --tv-col-revision: rgba(255, 234, 244, .75); --tv-col-hecha: rgba(226, 240, 255, .78);
    --tv-col-pie: rgba(255, 255, 255, .32);
    color: var(--tv-tinta);
}
:root[data-theme="dark"] .tareas-vidrio, :root[data-theme="dark"] .tablero--vidrio {
    --tv-tinta: #f1eefc; --tv-tinta-2: #d6d1ec; --tv-gris: #a19bbd;
    --tv-raya: rgba(255, 255, 255, .08); --tv-acento: #a48bff;
    --tv-filo: rgba(255, 255, 255, .1);
    --tv-vidrio-arriba: rgba(52, 46, 78, .62); --tv-vidrio-abajo: rgba(36, 32, 56, .5);
    --tv-brillo: rgba(255, 255, 255, .1);
    --tv-sombra: 0 22px 48px -26px rgba(0, 0, 0, .7), 0 2px 8px -3px rgba(0, 0, 0, .3);
    --tv-tarjeta-arriba: rgba(48, 43, 70, .95); --tv-tarjeta-abajo: rgba(38, 34, 58, .9);
    --tv-tarjeta-sombra: 0 1px 2px rgba(0, 0, 0, .3), 0 10px 22px -16px rgba(0, 0, 0, .7);
    --tv-tarjeta-sombra-alta: 0 2px 4px rgba(0, 0, 0, .3), 0 16px 30px -16px rgba(0, 0, 0, .8);
    --tv-rojo-fondo: rgba(211, 52, 75, .2); --tv-rojo: #ff8a9b;
    --tv-ambar-fondo: rgba(237, 162, 26, .18); --tv-ambar: #f3c35c;
    --tv-neutro-fondo: rgba(255, 255, 255, .08); --tv-neutro: #c9c3e2;
    --tv-col-abierta: rgba(60, 54, 90, .55); --tv-col-curso: rgba(70, 56, 120, .5);
    --tv-col-revision: rgba(100, 50, 85, .45); --tv-col-hecha: rgba(45, 70, 110, .5);
    --tv-col-pie: rgba(30, 27, 45, .3);
}
/* El vidrio de la columna: degradado claro, filo y brillo. */
.tablero--vidrio .tablero-columna {
    background: linear-gradient(180deg, var(--tv-vidrio-arriba, rgba(255, 255, 255, .62)), var(--tv-vidrio-abajo, rgba(255, 255, 255, .36)));
    border: 1px solid var(--tv-filo, rgba(255, 255, 255, .78));
    box-shadow: inset 0 1px 0 var(--tv-brillo, rgba(255, 255, 255, .95)), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--tv-sombra, 0 22px 48px -26px rgba(70, 50, 150, .32));
}

/* El panel de la maqueta (`.shell.glass`): 36px, degradado blanco
   translúcido, filo blanco y sombra larga. Envuelve el conmutador y la
   vista. SIN esmerilado: el «⋯» de cada fila de la tabla pasa a `fixed`
   (la tarjeta recorta) y un `backdrop-filter` en el panel lo mandaba fuera
   de la pantalla. */
.tareas-shell {
    display: flex; flex-direction: column; gap: var(--space-100);
    padding: var(--space-100) var(--space-200) var(--space-200); border-radius: 36px;
    background: linear-gradient(180deg, var(--panel-vidrio-arriba), var(--panel-vidrio-abajo));
    border: 1px solid var(--tv-filo);
    box-shadow: inset 0 1px 0 var(--tv-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--tv-sombra); }
/* La cabecera del panel: el título a la izquierda, la cápsula a la derecha
   y la raya debajo, como `.head` en la maqueta. */
/* Sin título: la cápsula sola a la derecha y lo más baja posible (Renzo,
   el mismo día: «reducir lo más que podamos ese header»). */
.tareas-shell > .card-header { margin: 0; padding: 0; border-bottom: 0; justify-content: flex-end; }
.tareas-shell > .card-header h2 { color: var(--tv-tinta); }
/* El conmutador Tablero / Tabla / Calendario, el `.seg` de la maqueta:
   una cápsula de vidrio y la vista puesta en blanco con su sombra lila. */
.tareas-vidrio .view-switch--texto { padding: var(--space-050); gap: var(--space-050); border-radius: 18px;
    background: linear-gradient(180deg, var(--tv-vidrio-arriba), var(--tv-vidrio-abajo));
    border: 1px solid var(--tv-filo);
    box-shadow: inset 0 1px 0 var(--tv-brillo), inset 0 -1px 0 rgba(255, 255, 255, .3), var(--tv-sombra); }
.tareas-vidrio .view-switch--texto .view-switch__btn { height: 35px; padding: 0 var(--space-200); gap: var(--space-100); border-radius: 13px;
    font-size: var(--fs-small); font-weight: 500; color: var(--tv-gris); transition: background .2s, color .2s; }
.tareas-vidrio .view-switch--texto .view-switch__btn:hover { color: var(--tv-tinta); background: none; }
.tareas-vidrio .view-switch--texto .view-switch__btn.is-active { color: var(--tv-acento); font-weight: 600;
    background: rgba(255, 255, 255, .92); box-shadow: inset 0 1px 0 #fff, 0 4px 14px -6px rgba(108, 76, 245, .4); }
:root[data-theme="dark"] .tareas-vidrio .view-switch--texto .view-switch__btn.is-active {
    background: rgba(255, 255, 255, .1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 4px 14px -6px rgba(0, 0, 0, .6); }
.tareas-vidrio .view-switch--texto .view-switch__btn svg { width: 17px; height: 17px; }
/* El tablero suelto dentro del panel: sin relleno propio. Con `.tareas-vidrio`
   delante, porque `.tablero` va más abajo en la hoja y ganaba. */
.tablero.tablero--vidrio { padding: 0; }
.tablero--vidrio .tablero-columnas { gap: var(--space-200); }
/* La columna: vidrio de 28px con el tinte de su estado arriba. */
.tablero--vidrio .tablero-columna { border-radius: 28px; padding: var(--space-200) var(--space-100) var(--space-100); }
/* El tinte arriba es el del TONO de la columna, no el de su llave: el
   mismo de Tareas (Abierta, En curso, En revisión, Hecha) para cualquier
   tablero que lo pida. */
.tablero--vidrio .tablero-columna--neutral { background: linear-gradient(180deg, var(--tv-col-abierta), var(--tv-col-pie)); }
.tablero--vidrio .tablero-columna--info { background: linear-gradient(180deg, var(--tv-col-curso), var(--tv-col-pie)); }
.tablero--vidrio .tablero-columna--warn { background: linear-gradient(180deg, var(--tv-col-revision), var(--tv-col-pie)); }
.tablero--vidrio .tablero-columna--ok { background: linear-gradient(180deg, var(--tv-col-hecha), var(--tv-col-pie)); }
.tablero--vidrio .tablero-columna--danger { background: linear-gradient(180deg, var(--tv-rojo-fondo), var(--tv-col-pie)); }
.tablero--vidrio .tablero-columna.is-over { outline: 0; box-shadow: inset 0 1px 0 var(--tv-brillo), 0 0 0 2px color-mix(in srgb, var(--tv-acento) 28%, transparent), var(--tv-sombra); }
.tablero--vidrio .tablero-columna-head { gap: var(--space-100); padding: var(--space-025) var(--space-025) 0 var(--space-050); margin-bottom: var(--space-150); }
/* Cuatro columnas al lado del menú lateral miden ~270px: la baldosa y los
   botones van un poco más chicos que en la maqueta para que «En revisión»
   quepa entero. */
.tablero--vidrio .tablero-columna-acciones { gap: 0; margin-right: calc(-1 * var(--space-025)); }
.tablero--vidrio .tablero-columna-estado { width: 32px; height: 32px; border-radius: 50%;
    background: rgba(255, 255, 255, .75); box-shadow: inset 0 1px 0 #fff, 0 3px 10px -4px rgba(80, 60, 160, .25); }
:root[data-theme="dark"] .tablero--vidrio .tablero-columna-estado { background: rgba(255, 255, 255, .08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); }
.tablero--vidrio .tablero-columna-estado.is-ok svg { width: 22px; height: 22px; }
/* Los colores de los estados, los de la maqueta: anillo gris, avance
   morado sobre su pista lila, anillo ámbar relleno de crema y chulo verde. */
.tablero--vidrio .tablero-columna-estado.is-neutral { color: #bdb8cd; }
.tablero--vidrio .tablero-columna-estado.is-info { color: #6c4cf5; }
.tablero--vidrio .tablero-estado-pista { stroke: #e3dcfb; }
.tablero--vidrio .tablero-columna-estado.is-warn { color: #f3b62a; }
.tablero--vidrio .tablero-estado-relleno { fill: #fff4d1; }
.tablero--vidrio .tablero-columna-estado.is-ok { color: #2fbf71; }
:root[data-theme="dark"] .tablero--vidrio .tablero-estado-pista { stroke: rgba(164, 139, 255, .25); }
:root[data-theme="dark"] .tablero--vidrio .tablero-estado-relleno { fill: rgba(243, 182, 42, .2); }
.tablero--vidrio .tablero-columna-nombre { font-size: var(--fs-columna); font-weight: 600; letter-spacing: -.01em; color: var(--tv-tinta); }
.tablero--vidrio .tablero-columna-count { min-width: 26px; height: 24px; padding: 0 var(--space-100); font-size: var(--fs-body); font-weight: 500;
    color: var(--tv-gris); background: rgba(255, 255, 255, .6); }
:root[data-theme="dark"] .tablero--vidrio .tablero-columna-count { background: rgba(255, 255, 255, .08); }
.tablero--vidrio .tablero-columna-acciones .btn.tablero-columna-mas,
.tablero--vidrio .tablero-columna-menu > .menu-trigger { width: 28px; height: 28px; padding: 0; justify-content: center; border-radius: 10px; color: var(--tv-gris); }
.tablero--vidrio .tablero-columna-acciones .btn.tablero-columna-mas:hover,
.tablero--vidrio .tablero-columna-menu > .menu-trigger:hover { background: rgba(255, 255, 255, .8); color: var(--tv-tinta); }
.tablero--vidrio .tablero-lista { gap: var(--space-100); }
.tablero--vidrio .tablero-vacio { border-color: var(--tv-raya); color: var(--tv-gris); }
.tablero--vidrio .tablero-agregar { height: 46px; margin-top: var(--space-075); padding: 0; border-radius: 16px;
    font-size: var(--fs-body); font-weight: 500; color: var(--tv-tinta-2);
    background: rgba(255, 255, 255, .42); border: 1px solid rgba(255, 255, 255, .7); }
.tablero--vidrio .tablero-agregar:hover { background: rgba(255, 255, 255, .78); color: var(--tv-tinta); }
:root[data-theme="dark"] .tablero--vidrio .tablero-agregar { background: rgba(255, 255, 255, .05); border-color: rgba(255, 255, 255, .08); }
.tablero--vidrio .tablero-agregar svg { width: 18px; height: 18px; }

/* La tarjeta: blanca con su degradado, filo blanco y sombra larga; se
   levanta al pasar. COMPACTA (Renzo, el mismo día): el título arriba, sin
   «⋯», y un solo renglón abajo con la cara y luego las pastillas. */
.tablero--vidrio .tablero-tarjeta { border-radius: 18px; padding: var(--space-150) var(--space-100) var(--space-100) var(--space-150);
    background: linear-gradient(180deg, var(--tv-tarjeta-arriba), var(--tv-tarjeta-abajo));
    border: 1px solid rgba(255, 255, 255, .95);
    box-shadow: inset 0 1px 0 #fff, var(--tv-tarjeta-sombra);
    transition: transform .18s ease, box-shadow .18s ease, opacity .18s; }
:root[data-theme="dark"] .tablero--vidrio .tablero-tarjeta { border-color: rgba(255, 255, 255, .08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), var(--tv-tarjeta-sombra); }
.tablero--vidrio .tablero-tarjeta:hover { transform: translateY(-2px); box-shadow: inset 0 1px 0 #fff, var(--tv-tarjeta-sombra-alta); }
.tablero--vidrio .tablero-tarjeta:active { cursor: grabbing; }
.tablero--vidrio .tablero-tarjeta.is-dragging { opacity: .45; transform: scale(.98); }
.tablero--vidrio .tablero-tarjeta-title { font-size: var(--fs-body); font-weight: 600; line-height: 1.3; letter-spacing: -.01em;
    color: var(--tv-tinta); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    padding-right: 0; }
.tablero--vidrio .tablero-tarjeta-meta { font-size: var(--fs-body); color: var(--tv-gris); margin-top: var(--space-025); }
.tablero-tarjeta-meta:empty { display: none; }
.tablero-tarjeta-meta > span + span::before { content: " · "; }
.tablero--vidrio .tablero-tarjeta .tablero-tarjeta-pie { flex-wrap: nowrap; gap: var(--space-050); margin-top: var(--space-100); }
.tablero--vidrio .tablero-tarjeta .tablero-tarjeta-pills { flex: 1 1 auto; gap: var(--space-050); }
/* Las pastillas de la maqueta, un punto más bajas: con su glifo y el tono
   suave. Con alto fijo van en `inline-flex` centrado: `.pill` es
   `inline-block` y, sin glifo, el texto se quedaba arriba. */
.tablero--vidrio .tablero-tarjeta .pill { display: inline-flex; align-items: center; height: 24px; padding: 0 var(--space-100); gap: var(--space-050); border-radius: 999px;
    font-size: var(--fs-badge); font-weight: 500; white-space: nowrap; }
.tablero--vidrio .tablero-tarjeta .pill svg { width: 13px; height: 13px; }
.tareas-vidrio .pill-danger, .tablero--vidrio .pill-danger { background: var(--tv-rojo-fondo); color: var(--tv-rojo); }
.tareas-vidrio .pill-warn, .tablero--vidrio .pill-warn { background: var(--tv-ambar-fondo); color: var(--tv-ambar); }
/* Las barras de «Alta» van rellenas y en su ámbar vivo; el reloj de «Vence
   hoy» sigue de trazo (`:has(rect)` distingue un glifo del otro). */
.tareas-vidrio .pill-warn svg:has(rect) { color: var(--tv-ambar-glifo); fill: currentColor; stroke: none; }
.tareas-vidrio .pill-muted, .tareas-vidrio .pill-info, .tablero--vidrio .pill-muted, .tablero--vidrio .pill-info { background: var(--tv-neutro-fondo); color: var(--tv-neutro); }
/* La tabla y el calendario: una tarjeta blanca dentro del panel. La piel
   es la de toda lista de la casa (`.card-table`, 2026-10-11, que nació
   acá); lo propio son las mismas pastillas, un punto más grandes porque la
   fila tiene aire. */
.tareas-vidrio .card.tareas-hoja { margin: 0; }
.tareas-hoja .cell-text { color: var(--tv-tinta); font-weight: 600; }
.tareas-hoja .pill { display: inline-flex; align-items: center; height: 26px; padding: 0 var(--space-100); gap: var(--space-050); border-radius: 999px; font-size: var(--fs-small); font-weight: 500; white-space: nowrap; }
.tareas-hoja .pill svg { width: 14px; height: 14px; }
.tareas-vidrio .tareas-tarjeta .tareas-tarjeta-quien { flex: none; margin-left: 0; }
.tareas-vidrio .tareas-tarjeta .tareas-avatar { width: 24px; height: 24px; font-size: var(--fs-meta); font-weight: 600; }
.tareas-vidrio .tareas-tarjeta-quien.is-sede { display: inline-grid; place-items: center; width: 24px; height: 24px;
    border-radius: 50%; background: var(--tv-neutro-fondo); color: var(--tv-gris); }
.tareas-vidrio .tareas-tarjeta-quien.is-sede svg { width: 14px; height: 14px; }

/* ── El tablero de la casa (ui/_tablero.html + tablero.js) ─────────────
   Nació como el tablero de Tareas (Renzo, 2026-09-12, con el boceto
   enfrente) y subió a la casa con el CRM (2026-10-01): columnas tintadas
   cuando tienen tarjetas, la vacía con su recuadro punteado que dice qué
   llega ahí, y el destino iluminado mientras se arrastra. */
.tablero { display: flex; flex-direction: column; gap: var(--space-150); padding: var(--space-150);
           container-type: inline-size; }
/* Las columnas se reparten el ancho (`minmax(0, 1fr)`) y no se desbordan:
   un `overflow` acá recortaba el menú «⋯» que se abre desde una tarjeta. */
.tablero-columnas { display: grid; gap: var(--space-150);
                    grid-template-columns: repeat(var(--tablero-columnas, 4), minmax(0, 1fr)); }
/* Cuatro columnas angostas no caben una tarjeta: según el ANCHO DEL TABLERO
   (no el de la pantalla, que no sabe del menú lateral) pasan a dos. */
@container (max-width: 860px) {
    .tablero-columnas { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) { .tablero-columnas { grid-template-columns: 1fr; } }
/* La columna (Renzo, 2026-10-10): una caja gris muy suave con filo, entre
   la página y la tarjeta blanca, tenga o no tarjetas. Antes se tintaba de
   morado cuando tenía, y el tablero lleno quedaba lila entero. */
.tablero-columna { background: color-mix(in srgb, var(--card-bg) 50%, var(--bg));
                   border: 1px solid var(--border); border-radius: var(--radius-card);
                   padding: var(--space-150); min-height: 200px;
                   display: flex; flex-direction: column; }
.tablero-columna.is-over {
    outline: 2px dashed var(--primary); outline-offset: -2px; }
/* La cabecera: el glifo del estado, el nombre, el número y, a la derecha,
   «+» y «⋯». El glifo dice el estado por la FORMA —vacío, a medias, anillo,
   chulo— y no solo por el color. */
.tablero-columna-head { display: flex; align-items: center; gap: var(--space-100);
                        min-height: 30px; margin: 0 0 var(--space-150);
                        padding-left: var(--space-025); }
.tablero-columna-nombre { flex: 1; min-width: 0; margin: 0;
                          font-size: var(--fs-body); font-weight: 600;
                          overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tablero-columna-estado { display: inline-grid; place-items: center; flex: none; }
.tablero-columna-estado svg { width: 20px; height: 20px; }
.tablero-columna-estado.is-neutral { color: var(--border-strong); }
.tablero-columna-estado.is-info { color: var(--primary); }
.tablero-columna-estado.is-warn { color: var(--warn-vivid); }
/* El anillo de «En curso» con su pista pálida, y el de «En revisión»
   relleno de su ámbar suave. */
.tablero-estado-pista { stroke: color-mix(in srgb, var(--primary) 20%, transparent); }
.tablero-estado-relleno { fill: var(--warn-bg); }
.tablero-columna-estado.is-ok { color: var(--ok-vivid); }
.tablero-columna-estado.is-danger { color: var(--danger-vivid); }
.tablero-columna-acciones { display: inline-flex; align-items: center; gap: var(--space-025);
                            margin-right: calc(-1 * var(--space-050)); }
.tablero-columna-acciones .btn.tablero-columna-mas {
    width: 28px; height: 28px; min-height: 0; border: 0; background: none;
    box-shadow: none; color: var(--text-light); }
.tablero-columna-acciones .btn.tablero-columna-mas:hover {
    background: var(--card-bg); color: var(--primary); }
.tablero-columna-acciones .btn.tablero-columna-mas svg { width: 16px; height: 16px; }
.tablero-columna-count { min-width: 24px; height: 24px; padding: 0 var(--space-100);
                         border-radius: 999px; display: inline-grid;
                         place-items: center; font-size: var(--fs-small);
                         font-weight: 600; background: var(--card-bg);
                         color: var(--text-light); }
.tablero-columna-pie { margin: calc(-1 * var(--space-100)) 0 var(--space-150);
                       font-size: var(--fs-small); color: var(--text-light); }
.tablero-lista { list-style: none; margin: 0; padding: 0; display: flex;
                 flex-direction: column; gap: var(--space-100); flex: 1; }
.tablero-vacio { flex: 1; display: flex; flex-direction: column;
                 align-items: center; justify-content: center; gap: var(--space-050);
                 padding: var(--space-200) var(--space-150); text-align: center;
                 border: 1px dashed var(--border-strong);
                 border-radius: var(--radius-field); color: var(--text-light); }
.tablero-vacio svg { width: 28px; height: 28px; margin-bottom: var(--space-050);
                     color: var(--text-faint); }
.tablero-vacio strong { color: var(--text); font-weight: 600; }
.tablero-vacio small { font-size: var(--fs-small); }
[data-tablero-tarjeta].is-dragging { opacity: .5; }
/* `llenar`: las columnas llegan al pie de la pantalla (Renzo, 2026-10-02).
   `--tablero-alto` lo mide tablero.js —lo que queda desde las columnas hasta
   abajo—. Es un MÍNIMO: con más tarjetas la columna crece y la página baja,
   porque un scroll adentro de la columna recortaría el menú «⋯». Solo con las
   columnas lado a lado; apiladas (celular) cada una mide su contenido. */
@media (min-width: 901px) {
    [data-tablero-llenar] .tablero-columna { min-height: var(--tablero-alto, 200px); }
    /* `fijo` (Renzo, 2026-10-09): el alto es EXACTO y la lista se desplaza
       por dentro, sin barra. El «⋯» no se pierde: `menu.js` lo saca a
       `fixed` cuando un ancestro recorta, y lo cierra al desplazar. Sin
       `--tablero-alto` (columnas en dos filas) el alto vuelve a `auto`. */
    [data-tablero-fijo] .tablero-columna { height: var(--tablero-alto); }
    [data-tablero-fijo] .tablero-lista {
        min-height: 0; overflow-y: auto; scrollbar-width: none;
        /* Aire para la sombra y el borde de la tarjeta, que el recorte
           del desplazamiento se comería. */
        margin: calc(-1 * var(--space-050)); padding: var(--space-050);
    }
    [data-tablero-fijo] .tablero-lista::-webkit-scrollbar { display: none; }
}

/* ── Compras: el tablero de recepción ─────────────────────────────────
   La tarjeta (`_reception_card.html`) lleva la piel de la casa,
   `.tablero-tarjeta`, en el tablero `vidrio` (2026-11-02): la cara de
   Tareas. El gancho con `tablero.js` sigue siendo `data-tablero-tarjeta`.
   Lo único propio: la tarjeta que no se arrastra no ofrece la mano. */
.recepcion-tarjeta:not([draggable="true"]) { cursor: default; }

/* ── CRM: la tarjeta de oportunidad ─────────────────────────────────── */
.crm-tarjeta { background: var(--card-bg); border: 1px solid var(--border);
               border-radius: var(--radius-field); padding: var(--space-100) var(--space-150);
               display: flex; flex-direction: column; gap: var(--space-050);
               box-shadow: var(--shadow); cursor: grab; }
.crm-tarjeta:hover { border-color: var(--border-strong); }
.crm-tarjeta.is-vencida { border-color: var(--danger-text); }
.crm-tarjeta-title { font-weight: 600; color: var(--text); text-decoration: none; }
.crm-tarjeta-head { display: flex; align-items: flex-start; justify-content: space-between;
                    gap: var(--space-100); }
.crm-tarjeta-menu { flex: none; }
.crm-tarjeta-meta { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-100);
                    font-size: var(--fs-small); color: var(--text-light); }
.crm-tarjeta-meta strong { color: var(--text); }
.crm-tarjeta-pie { display: flex; align-items: center;
                   justify-content: space-between; gap: var(--space-100); }
/* El próximo paso en la tarjeta: glifo del tipo, el título recortado y la
   fecha siempre entera; en rojo si venció. */
.crm-tarjeta-paso { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--space-075);
                    font-size: var(--fs-small); color: var(--text-light); }
.crm-tarjeta-paso svg { width: 14px; height: 14px; flex: none; color: var(--primary); }
.crm-tarjeta-paso-texto { min-width: 0; overflow: hidden; text-overflow: ellipsis;
                          white-space: nowrap; color: var(--text); }
.crm-tarjeta-paso-fecha { flex: none; }
.crm-tarjeta-paso.is-vencido, .crm-tarjeta-paso.is-vencido svg,
.crm-tarjeta-paso.is-vencido .crm-tarjeta-paso-texto { color: var(--danger-text); }
.crm-tarjeta-pie .avatar { flex: none; }
.crm-tarjeta-pie .avatar { width: 24px; height: 24px; font-size: var(--fs-meta); }
/* La ficha: los pasos del embudo arriba (flechas que se oprimen para mover),
   la actividad a la izquierda y los datos al lado. */
.crm-pasos { list-style: none; display: flex; gap: var(--space-050); margin: 0 0 var(--space-200);
             padding: 0; overflow-x: auto; }
.crm-pasos li { flex: 1; min-width: 110px; }
.crm-pasos li > span, .crm-pasos button {
    display: block; width: 100%; padding: var(--space-100) var(--space-150); border: 0; text-align: center;
    background: var(--bg); color: var(--text-light); font: inherit;
    clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 50%, calc(100% - 10px) 100%, 0 100%, 10px 50%); }
.crm-pasos button { cursor: pointer; }
.crm-pasos button:hover { color: var(--text); }
.crm-pasos li.is-hecho > span, .crm-pasos li.is-hecho button {
    background: var(--primary-soft); color: var(--text); }
.crm-pasos li.is-actual > span { background: var(--primary); color: #fff; font-weight: 600; }
/* Cada próximo paso en la ficha: el glifo del tipo, qué y quién, y cuándo a
   la derecha (o «Venció…» en rojo). */
.crm-paso-fila { text-decoration: none; color: inherit; }
.crm-paso-fila:hover { border-color: var(--border-strong); }
.crm-paso-fila .tile { width: 32px; height: 32px; }
.crm-paso-fila .tile svg { width: 16px; height: 16px; }
.crm-paso-fila-texto { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-025); }
.crm-paso-fila-texto strong { color: var(--text); }
.crm-paso-fila-cuando { flex: none; font-size: var(--fs-small); color: var(--text-light); }
.crm-ficha-accion { margin-top: var(--space-150); }
.crm-desenlace { margin: 0 0 var(--space-200); display: flex; align-items: center; gap: var(--space-100); }
.crm-ficha { display: grid; grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
             gap: var(--space-200); align-items: start; }
.crm-ficha-main, .crm-ficha-side { display: flex; flex-direction: column; gap: var(--space-200);
                                   min-width: 0; }
@media (max-width: 900px) { .crm-ficha { grid-template-columns: 1fr; } }
.crm-previa { width: 100%; min-height: 420px; border: 1px solid var(--border);
              border-radius: var(--radius-field); }

/* El tablero de Alistamiento (rediseño del 2026-09-14, con el referente de
   Renzo). Clases propias y no las de Tareas: son dos tableros con reglas
   distintas y compartir clases los ataría. Cada columna tiene TONO —el de su
   baldosa— y el tono repite lo que dice el rótulo, nunca lo reemplaza. */

.al-tablero-wrap { display: flex; flex-direction: column; gap: var(--space-150);
                   margin-top: var(--space-150); }
/* La barra de selección está SIEMPRE a la vista (Renzo, 2026-09-14):
   aparecer al marcar empujaba el tablero. Se queda pegada arriba para que
   quien baja por una columna larga no pierda el «Mover a…»; lo que cambia
   es si hay algo marcado. Tinte de la marca, como el referente. */
.al-seleccion { position: sticky; top: var(--space-150); z-index: 5; display: flex;
                flex-wrap: wrap; align-items: center; gap: var(--space-150);
                min-height: 64px; padding: var(--space-150) var(--space-200);
                background: color-mix(in srgb, var(--primary-soft) 60%, var(--card-bg));
                border: 1px solid var(--primary-soft);
                border-radius: var(--radius-card); box-shadow: var(--shadow); }
.al-seleccion-marca { width: 34px; height: 34px; flex: none; display: inline-grid;
                      place-items: center; border-radius: var(--radius-field);
                      background: var(--primary); color: #fff; }
.al-seleccion-marca svg { width: 18px; height: 18px; }
.al-seleccion.is-vacia .al-seleccion-marca { background: var(--card-bg);
                                             color: var(--text-faint);
                                             border: 1px solid var(--border); }
.al-seleccion-cuenta { flex: 1; min-width: 0; margin: 0;
                       font-size: var(--fs-section); font-weight: 600;
                       color: var(--primary-dark); }
.al-seleccion.is-vacia .al-seleccion-cuenta { font-weight: 400;
                                              font-size: var(--fs-body);
                                              color: var(--text-light); }
/* El aviso de por qué se rechazó la marca (2026-10-01): en el sitio de la
   cuenta, en el tono de advertencia, hasta la siguiente marca. */
.al-seleccion .al-seleccion-cuenta.is-aviso { color: var(--warn-vivid); }
.al-seleccion-acciones { display: flex; align-items: center; gap: var(--space-150); }
.al-seleccion-sep { width: 1px; height: 28px; background: var(--border-strong); }
/* El disparador del menú con rótulo: mide y se lee como un botón de la
   casa, porque la casa solo trae el de tres puntos. */
.al-mover-trigger { list-style: none; cursor: pointer; display: inline-flex;
                    align-items: center; gap: var(--space-100); min-width: 190px;
                    padding: var(--space-100) var(--space-150); background: var(--card-bg);
                    border: 1px solid var(--border-strong);
                    border-radius: var(--radius-field);
                    font-size: var(--fs-body); font-weight: 600;
                    color: var(--primary-dark); }
.al-mover-trigger::-webkit-details-marker { display: none; }
.al-mover-trigger span { flex: 1; }
.al-mover-trigger svg { width: 16px; height: 16px; flex: none; }
.al-mover-trigger:hover { border-color: var(--primary); }
.al-mover-trigger:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.al-mover[open] .al-mover-chevron { transform: rotate(180deg); }
/* Sin nada marcado el menú no abre (lo impide el script): se tiene que VER
   apagado. La casa no trae aún un apagado general (rama `boton-apagado`
   sin mezclar), así que la barra trae el suyo. */
.al-seleccion.is-vacia .al-mover-trigger { opacity: .5; cursor: not-allowed; }
.al-seleccion .menu-item:disabled { opacity: .45; cursor: not-allowed;
                                    background: transparent; color: var(--text-light); }
.al-seleccion .btn:disabled { opacity: .45; cursor: not-allowed;
                              box-shadow: none; }

/* LAS CUATRO COLUMNAS MIDEN LO MISMO Y SIEMPRE LO MISMO (Renzo,
   2026-09-15): la que se llena de pedidos estiraba la página entera y
   buscar en Empacando obligaba a bajar por Por alistar. El alto sale de
   la ventana —lo que queda bajo la banda, la sede y la barra— acotado
   arriba y abajo para que ni en una pantalla corta quede un buzón ni en
   un monitor grande una columna interminable. Lo que se desplaza es la
   lista de tarjetas; la cabecera y el pie se quedan.

   Sin techo desde el 2026-09-30: la banda de sección bajó y la tarjeta del
   contenido ya no se mueve, así que las columnas llegan hasta el tip, a
   ras del borde de abajo, en cualquier alto de pantalla. Los 25rem son lo
   que va encima (topbar, banda, sede, barra de selección) más el tip y el
   aire de abajo, medido. */
.al-tablero { display: grid; gap: var(--space-150);
              --al-alto-columna: max(340px, calc(100dvh - 25rem));
              grid-template-columns: repeat(4, minmax(230px, 1fr)); }
@media (max-width: 1100px) {
    .al-tablero { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 700px) { .al-tablero { grid-template-columns: 1fr; } }
/* La estación de alistamiento (2026-09-30): arriba no hay menú, banda ni
   selector de sede —solo la barra de la estación—, así que las columnas
   reservan mucho menos (barra de la estación + barra de selección + aire,
   medido) y bajan casi hasta el borde. La tarjeta del contenido deja aire a
   los lados y abajo en vez de ir de borde a borde. */
.al-estacion .content { margin: 0 var(--space-200) var(--space-200);
                        border-radius: var(--radius-shell);
                        /* `terminal.css` pinta el texto claro para su fondo
                           oscuro; adentro de la tarjeta va el del kit (visto
                           en la estación de recepción, 2026-10-29: títulos de
                           columna y proveedores blancos sobre claro). */
                        color: var(--text); }
.al-estacion .al-tablero { --al-alto-columna: max(340px, calc(100dvh - 17rem)); }
/* La estación de recepción es el shell sin la barra de arriba ni el menú
   (`purchasing/station/frame.html`, 2026-10-30): el contenido no deja el
   hueco de la barra fija ni el ancho máximo del ERP. */
.al-estacion .shell { margin-top: 0; max-width: none; min-height: 0; padding: 0; }
.al-columna { display: flex; flex-direction: column; gap: var(--space-150);
              height: var(--al-alto-columna); min-height: 0;
              padding: var(--space-200);
              background: var(--card-bg); border: 1px solid var(--border);
              border-radius: var(--radius-card); box-shadow: var(--shadow); }
.al-columna.is-over { outline: 2px dashed var(--primary); outline-offset: -2px; }
/* Una fila: la baldosa, el nombre con su contador y, a la derecha, los tres
   puntos sin rótulo, con «Seleccionar todos» adentro (Renzo, 2026-09-30). */
.al-columna-head { display: flex; align-items: center; gap: var(--space-150); }
.al-columna-titulo { display: flex; align-items: center; gap: var(--space-100);
                     min-width: 0; }
.al-columna-nombre { margin: 0; min-width: 0; font-size: var(--fs-section);
                     font-weight: 700; color: var(--text);
                     white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.al-columna-count { flex: none; min-width: 24px; height: 22px; padding: 0 var(--space-100);
                    border-radius: 999px; display: inline-grid;
                    place-items: center; font-size: var(--fs-small);
                    font-weight: 700; }
.al-columna.is-brand .al-columna-count { background: var(--primary-soft);
                                        color: var(--primary-dark); }
.al-columna.is-info .al-columna-count { background: var(--info-bg);
                                       color: var(--info-text); }
.al-columna.is-warn .al-columna-count { background: var(--warn-bg);
                                       color: var(--warn-text); }
.al-columna.is-ok .al-columna-count { background: var(--ok-bg);
                                     color: var(--ok-text); }
/* «Seleccionar todos», como fila del menú: la casilla en lugar del glifo. */
.al-todos { cursor: pointer; }
.al-todos input { margin: 0; }
/* El ⋮ de cada columna: sus opciones según su naturaleza (ordenar y tomar,
   seguir con el más antiguo, ver lo despachado). Pegado a la esquina, junto
   al contador; la opción vigente se marca con su glifo y su peso. */
.al-columna-menu { margin: 0 calc(-1 * var(--space-075)) 0 auto; }

/* `min-height: 0` es lo que deja encogerse a un hijo flexible: sin eso la
   lista mide lo que miden sus tarjetas y desborda la columna en vez de
   desplazarse. La sangría de la derecha es para que la barra no se pare
   encima del borde de la tarjeta ni lo mueva al aparecer. */
.al-lista { list-style: none; margin: 0 calc(var(--space-100) * -1) 0 0; padding: 0;
            padding-right: var(--space-100); display: flex;
            flex-direction: column; gap: var(--space-100); flex: 1;
            min-height: 0; overflow-y: auto; overscroll-behavior: contain;
            scrollbar-width: thin; scrollbar-gutter: stable; }
/* `center`: el agarre, la casilla, el código y la flecha van en el mismo eje,
   tenga o no correo debajo el código (Renzo, 2026-09-30). */
.al-tarjeta { position: relative; display: flex; flex-wrap: wrap;
              align-items: center; gap: var(--space-100); padding: var(--space-100) var(--space-150);
              background: var(--card-bg); border: 1px solid var(--border);
              border-radius: var(--radius-field);
              transition: border-color .15s, box-shadow .15s; }
.al-tarjeta:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.al-tarjeta.is-marcada { border-color: var(--primary);
                         box-shadow: 0 0 0 1px var(--primary); }
.al-tarjeta.is-dragging { opacity: .45; }
/* El candado (2026-09-14): lo que tomó otra persona se ve, pero no se
   agarra. Borde punteado y el candado donde iría el agarre. */
.al-tarjeta.is-bloqueada { border-style: dashed; }
.al-tarjeta-candado { display: flex; align-items: flex-start; padding-top: var(--space-050);
                      color: var(--text-light); }
.al-tarjeta-candado svg { width: 16px; height: 16px; }
/* La que llega al tablero aparece; las que cambian de columna se deslizan
   con `transform` desde alistamiento-tablero.js. */
@keyframes al-llega { from { opacity: 0; transform: scale(.96); }
                      to { opacity: 1; transform: none; } }
[data-tablero-tarjeta].is-llegando { animation: al-llega .3s ease; }
@media (prefers-reduced-motion: reduce) {
    [data-tablero-tarjeta].is-llegando { animation: none; }
}
/* Agarre y casilla EN FILA y no apilados: apilados le daban a la tarjeta
   compacta un alto mínimo que el código solo no necesita. */
.al-tarjeta-asas { display: flex; align-items: center; gap: var(--space-050); }
.al-tarjeta-check { margin: 0; }
.al-tarjeta-cuerpo { flex: 1; min-width: 0; display: flex;
                     flex-direction: column; gap: 1px; color: var(--text); }
/* El correo del cliente, a la vista en la tarjeta compacta: es lo que se
   busca de un vistazo. Un renglón, cortado si no cabe. */
.al-tarjeta-correo { font-size: var(--fs-small); color: var(--text-light);
                     white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.al-tarjeta-folio { font-size: var(--fs-section); font-weight: 700;
                    color: var(--text); text-decoration: none; }
.al-tarjeta-folio:hover { color: var(--primary-dark); }
/* El ícono de lote (2026-10-01): antes del código, del tamaño de la letra,
   en el gris de los datos; el título dice qué pedidos van juntos. */
.al-tarjeta-lote { display: inline-flex; vertical-align: -2px; margin-right: var(--space-050);
                   color: var(--text-light); }
.al-tarjeta-lote svg { width: 15px; height: 15px; }
/* El detalle ocupa TODO el ancho de la tarjeta, bajo una raya, y no la
   columna sangrada del código (Renzo, 2026-09-30). `[hidden]` se repite
   porque el `display: flex` de acá le ganaría al atributo.

   `min-width: 0` (traslado #19054, 2026-10-15): sin él el detalle mide lo
   que mide su renglón más largo que no se parte —la pastilla «Juan Manuel
   Saenz Herrera + Harold …»— y se sale por la derecha de la tarjeta, que lo
   recorta junto con el subtítulo. */
.al-tarjeta-detalle { flex-basis: 100%; min-width: 0;
                      display: flex; flex-direction: column;
                      gap: var(--space-050); padding-top: var(--space-100);
                      border-top: 1px solid var(--border); }
.al-tarjeta-detalle[hidden] { display: none; }
/* «Ver más», abajo y a lo ancho, con cara de botón: morado sutil y borde
   punteado (Renzo, 2026-09-30), para que se lea como «hay más adentro» y no
   compita con la flecha, que es la acción principal. */
.al-tarjeta .btn.al-tarjeta-mas {
    flex-basis: 100%; height: 26px;
    color: var(--primary-dark);
    background: color-mix(in srgb, var(--primary-soft) 55%, transparent);
    border: 1px dashed var(--primary-soft-border);
}
.al-tarjeta .btn.al-tarjeta-mas:hover {
    background: var(--primary-soft); border-color: var(--primary);
    color: var(--primary-dark);
}
.al-tarjeta-cliente { font-size: var(--fs-body); color: var(--text); }
.al-tarjeta-dato { display: inline-flex; align-items: center; gap: var(--space-075);
                   font-size: var(--fs-small); color: var(--text-light); }
.al-tarjeta-dato svg { width: 14px; height: 14px; flex: none; }
.al-tarjeta-pills { display: flex; flex-wrap: wrap; gap: var(--space-050); margin-top: var(--space-025); }
/* Una pastilla más ancha que la tarjeta —quienes recogen, con dos nombres
   completos— se parte en dos renglones en vez de salirse: es justo el dato
   que hay que leer entero para no pisar el trabajo de otro. */
.al-tarjeta-pills .pill { max-width: 100%; white-space: normal; }
/* El glifo es un chevrón hacia abajo; girado, apunta a la ficha. */
/* La flecha es la acción PRINCIPAL —abrir la ficha—: botón ancho, con más
   área de clic que un glifo suelto. El chevrón apunta abajo; girado,
   apunta a la ficha. */
.al-tarjeta .btn.al-tarjeta-ir { width: 40px; flex: none; }
.al-tarjeta .btn.al-tarjeta-ir svg { transform: rotate(-90deg); }
/* En «Pendientes» la flecha toma (2026-10-06) y va dentro de su propio
   formulario: el formulario no ocupa lugar y el botón queda donde siempre. */
.al-tarjeta-tomar { display: contents; }
.al-tarjeta-nota { flex-basis: 100%; }
/* «Despachar» en Listos (2026-10-29): a lo ancho, encima de «Ver más»; sin
   esto el formulario se metía en la fila del título y lo apretaba. */
.al-tarjeta-accion { flex-basis: 100%; }

.al-vacio { flex: 1; min-height: 0; display: flex; flex-direction: column;
            align-items: center; justify-content: center; gap: var(--space-050);
            padding: var(--space-200) var(--space-150); text-align: center;
            border: 1px dashed var(--border-strong);
            border-radius: var(--radius-field); color: var(--text-light); }
.al-vacio svg { width: 40px; height: 40px; margin-bottom: var(--space-100);
                color: var(--text-faint); }
.al-vacio strong { color: var(--text); font-weight: 600; }
.al-vacio small { font-size: var(--fs-small); }
/* «y 74 más · Mostrar todos»: el pie de una columna recortada por el tope
   (Santiago, 2026-10-02). Un enlace y no un botón: pide el mismo fragmento
   con `todo=1`, y sin script es la misma pantalla. */
.al-mas { display: block; padding: var(--space-150); text-align: center;
          font-size: var(--fs-small); color: var(--primary);
          text-decoration: none; }
.al-mas:hover { text-decoration: underline; }


.tablero-tarjeta { position: relative; background: var(--card-bg);
                  border: 1px solid var(--border); border-radius: var(--radius-field);
                  padding: var(--space-150); cursor: grab; box-shadow: var(--shadow); }
.tablero-tarjeta:hover { border-color: var(--border-strong); }
.tablero-tarjeta.is-dragging { opacity: .5; }
/* Una tarjeta que maneja otro módulo (Mantenimiento): no se arrastra, así
   que no ofrece la mano; el filo izquierdo la distingue de las sueltas. */
.tareas-tarjeta.is-gestionada { cursor: default; border-left: 3px solid var(--primary); }
.tareas-gestionada { display: flex; flex-direction: column; gap: var(--space-100);
                     align-items: flex-start; margin-bottom: var(--space-150); }
.tablero-tarjeta-title { font-weight: 600; text-decoration: none; color: var(--text);
                        display: block; padding-right: var(--space-300); }
.tablero-tarjeta-title:hover { color: var(--primary); }
.tablero-tarjeta-meta { font-size: var(--fs-small); color: var(--text-light);
                       margin-top: var(--space-025); }
.tablero-tarjeta-pills { display: flex; gap: var(--space-050); flex-wrap: wrap;
                        margin-top: var(--space-100); }
/* El piso de abajo: pastillas a la izquierda, quién a la derecha. */
.tablero-tarjeta-pie { display: flex; align-items: center; flex-wrap: wrap;
                      gap: var(--space-100); margin-top: var(--space-150); }
/* Si no caben en un renglón, quién baja al siguiente, a la derecha. */
.tablero-tarjeta-pie .tablero-tarjeta-pills { flex: 0 1 auto; min-width: 0; margin-top: 0; }
.tareas-tarjeta-quien { display: inline-flex; align-items: center; gap: var(--space-075);
                        margin-left: auto; font-size: var(--fs-small);
                        color: var(--text-light); white-space: nowrap; }
.tareas-tarjeta-quien.is-sede { color: var(--text-faint); }
/* Flotando en la esquina SOLO dentro de una tarjeta: en la tabla el «⋯» va
   en su celda. Suelto, con `#results` posicionado (los orbes le ponen
   `position: relative`), todos los de la tabla se apilaban arriba. */
.tareas-tarjeta > .tareas-menu { position: absolute; top: var(--space-100); right: var(--space-100); }
/* La cara del responsable (`{% avatar %}`): abajo a la derecha de la
   tarjeta con su primer nombre, y delante del nombre en la tabla. */
.tareas-avatar { width: 24px; height: 24px; font-size: var(--fs-meta); }
.tareas-responsable { display: inline-flex; align-items: center; gap: var(--space-100); }
/* El calendario de la casa (`ui/_calendario.html`): mes, semana y lista.
   Salió de Tareas el 2026-10-08. Con la cara de Agrupa desde el 2026-11-06
   (Santiago, con el boceto: «el calendario es súper feo»): ‹ Hoy › en una
   cápsula, el mes grande, una hoja blanca de rayas finas sin la cuadrícula
   gris, hoy en lila, fines de semana y días de otro mes más callados, y cada
   cosa como pastilla blanca con el punto de su tono. */
.cal-nav { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-150);
           padding: var(--space-150) var(--space-200) 0; }
.cal-pasos { display: inline-flex; align-items: center; height: 32px; padding: var(--space-025);
             border: 1px solid var(--border); border-radius: 999px; background: var(--card-bg); }
.cal-paso { display: inline-flex; align-items: center; justify-content: center; height: 100%;
            min-width: 28px; padding: 0 var(--space-100); border-radius: 999px;
            color: var(--text-light); font-size: var(--fs-small); font-weight: 500; text-decoration: none; }
.cal-paso svg { width: 16px; height: 16px; }
.cal-paso:hover { background: var(--primary-soft); color: var(--primary-dark); }
.cal-titulo { font-size: var(--fs-stat); font-weight: 600; text-transform: capitalize; }
.cal-vistas { margin-left: auto; }
.cal-grilla { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr));
              margin: var(--space-150) var(--space-200) var(--space-200);
              border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden;
              background: var(--card-bg); }
.cal-head { padding: var(--space-100) var(--space-150); font-size: var(--fs-meta); font-weight: 600;
            color: var(--text-faint); border-bottom: 1px solid var(--border); }
.cal-dia { min-height: 104px; padding: var(--space-100); min-width: 0;
           display: flex; flex-direction: column; gap: var(--space-050);
           border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.cal-dia:nth-child(7n) { border-right: 0; }
.cal-grilla > .cal-dia:nth-last-child(-n + 7) { border-bottom: 0; }
.cal-grilla.is-semana .cal-dia { min-height: 240px; }
.cal-dia.is-finde, .cal-head.is-finde { background: var(--bg); }
.cal-dia.is-otro-mes .cal-dia-num { color: var(--text-faint); }
.cal-dia.is-otro-mes .cal-item { opacity: .7; }
.cal-dia.is-hoy { background: var(--primary-soft); }
.cal-dia-num { font-size: var(--fs-small); font-weight: 500; color: var(--text-light);
               min-width: 24px; height: 24px; padding: 0 var(--space-075); align-self: flex-start;
               display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; }
.cal-dia.is-hoy .cal-dia-num { background: var(--primary); color: #fff; }
.cal-grilla.is-semana .cal-dia-num { text-transform: capitalize; }
/* La pastilla de cada cosa: blanca, con el punto de su tono. */
.cal-item { display: flex; align-items: center; gap: var(--space-075); width: 100%; min-width: 0;
            padding: var(--space-025) var(--space-100) var(--space-025) var(--space-075); border: 1px solid var(--border);
            border-radius: 999px; background: var(--card-bg); color: var(--text);
            font: inherit; font-size: var(--fs-meta); font-weight: 500; text-align: left;
            text-decoration: none; cursor: pointer; }
.cal-item:hover { border-color: var(--primary); color: var(--primary-dark); }
.cal-item-texto { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.cal-punto { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--primary); }
.cal-item.is-warn .cal-punto { background: var(--warn-text); }
.cal-item.is-danger .cal-punto { background: var(--danger-text); }
.cal-item.is-ok .cal-punto { background: var(--ok-text); }
.cal-item.is-neutral .cal-punto { background: var(--text-faint); }
.cal-item.is-planned { border-style: dashed; background: transparent; color: var(--text-light); }
.cal-item.is-planned .cal-punto { background: transparent; box-shadow: inset 0 0 0 1.5px var(--text-faint); }
.cal-mas { font-size: var(--fs-meta); font-weight: 600; color: var(--primary); text-decoration: none;
           padding-left: var(--space-075); }
.cal-mas:hover { text-decoration: underline; }
/* La lista: cada día con su baldosa —el número grande y el día— y sus pastillas. */
.cal-lista { list-style: none; margin: var(--space-150) var(--space-200) var(--space-200); padding: 0;
             display: flex; flex-direction: column; border: 1px solid var(--border);
             border-radius: var(--radius); background: var(--card-bg); overflow: hidden; }
.cal-lista-dia { display: grid; grid-template-columns: 56px minmax(0, 1fr); align-items: center;
                 gap: var(--space-150); padding: var(--space-150) var(--space-200); }
.cal-lista-dia + .cal-lista-dia { border-top: 1px solid var(--border); }
.cal-lista-fecha { display: flex; flex-direction: column; align-items: center; padding: var(--space-050) 0;
                   border-radius: var(--radius-sm); background: var(--bg); color: var(--text-light);
                   font-size: var(--fs-meta); text-transform: lowercase; }
.cal-lista-fecha b { font-size: var(--fs-stat); font-weight: 600; color: var(--text); line-height: 1.2; }
.cal-lista-dia.is-hoy .cal-lista-fecha { background: var(--primary); color: #fff; }
.cal-lista-dia.is-hoy .cal-lista-fecha b { color: #fff; }
.cal-lista-items { display: flex; flex-wrap: wrap; gap: var(--space-075); min-width: 0; }
.cal-lista-items .cal-item { width: auto; max-width: 100%; }
@media (max-width: 768px) {
  .cal-grilla { grid-template-columns: 1fr; }
  .cal-head { display: none; }
  .cal-dia.is-otro-mes { display: none; }
  .cal-dia, .cal-grilla.is-semana .cal-dia { min-height: 0; flex-direction: row; flex-wrap: wrap;
                                             align-items: center; gap: var(--space-050); border-right: 0; }
  .cal-dia .cal-item { width: auto; max-width: 100%; }
  /* En la agenda del celular solo los días con algo, y hoy. */
  .cal-dia:not(:has(.cal-item)):not(.is-hoy) { display: none; }
  .cal-lista-dia { padding: var(--space-100) var(--space-150); }
}
/* El conmutador de Tareas es de PALABRAS, no de glifos: tres rótulos no
   caben en los 32px del botón de íconos y se encimaban. */
.view-switch--texto .view-switch__btn { width: auto; padding: 0 var(--space-150);
                                        font-size: var(--fs-small);
                                        font-weight: 500; text-decoration: none;
                                        display: inline-flex; align-items: center;
                                        gap: var(--space-075); }
.view-switch--texto .view-switch__btn svg { width: 16px; height: 16px; }
/* La ficha en el drawer (Renzo, 2026-09-12, con el boceto enfrente):
   cabecera con baldosa y menú, datos con glifo, botones de estado a lo
   ancho, títulos de sección con filo, campos con glifo adentro, opciones
   como tarjetas explicadas, alertas que se rellenan al marcarse, y el pie
   con Cancelar / Guardar. El historial cierra. */
/* El `h2` del drawer mide lo que mide su texto; con la cabecera adentro
   tiene que ocupar todo el ancho para que el menú «…» caiga junto a la ×. */
.drawer-head h2:has(> .tareas-cabecera) { flex: 1; min-width: 0; }
.tareas-cabecera { display: flex; align-items: flex-start; gap: var(--space-150);
                   min-width: 0; }
.tareas-cabecera-texto { display: flex; flex-direction: column; gap: var(--space-075);
                         min-width: 0; }
.tareas-titulo { font-size: var(--fs-section); font-weight: 600;
                 line-height: 1.25; overflow-wrap: anywhere; }
.tareas-estado { display: flex; gap: var(--space-050); flex-wrap: wrap; }
.tareas-menu-ficha { margin-left: auto; align-self: center; }
.tareas-glifo { width: 16px; height: 16px; flex: none; }
.tareas-resumen { display: flex; flex-wrap: wrap; gap: var(--space-100) var(--space-200);
                  font-size: var(--fs-body); color: var(--text-light);
                  margin: 0 0 var(--space-200); }
.tareas-resumen > * { display: inline-flex; align-items: center; gap: var(--space-075); }
.tareas-resumen a { color: var(--primary); text-decoration: none; }
.tareas-resumen .is-vencida { color: var(--danger-text); font-weight: 600; }
.tareas-acciones { display: grid; gap: var(--space-100);
                   grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                   margin-bottom: var(--space-250); }
.tareas-acciones form { display: contents; }
.tareas-acciones .btn { width: 100%; justify-content: center; }
.tareas-seccion { display: flex; align-items: center; gap: var(--space-150);
                  font-size: var(--fs-body); font-weight: 600;
                  color: var(--text); margin: var(--space-250) 0 var(--space-150); }
.tareas-seccion::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.form-grid--drawer { grid-template-columns: 1fr 1fr; gap: 0 var(--space-150); }
.form-grid--drawer > .field-wide { grid-column: 1 / -1; }
.form-grid--drawer .field { margin-bottom: var(--space-150); }
.form-grid--drawer label.is-required::after { content: " *"; color: var(--danger-text); }
/* Y en cualquier rejilla, no solo en la del drawer: `required_css_class =
   'is-required'` lo declaran ya varios formularios (Tareas, Solicitudes) y
   fuera del drawer el asterisco no salía. */
.form-grid label.is-required::after { content: " *"; color: var(--danger-text); }
/* El glifo va ADENTRO de la casilla: el envoltorio se posiciona y la
   casilla —o la que flatpickr pone en su lugar— le deja el hueco. */
.tareas-con-glifo { position: relative; }
/* `bottom` cuenta el margen inferior del campo (el envoltorio es ítem de
   grilla y lo contiene), y `z-index` porque flatpickr envuelve su casilla
   en un `position: relative` que de otro modo la pinta encima del glifo. */
.tareas-con-glifo > svg { position: absolute; left: 13px;
                          bottom: calc(var(--space-150) + 13px); z-index: 1;
                          width: 16px; height: 16px; color: var(--text-light);
                          pointer-events: none; }
/* Altura FIJA del control para que el glifo quede centrado de verdad: con
   la altura natural (padding + línea) el centro se corría dos píxeles. */
.tareas-con-glifo input, .tareas-con-glifo select { padding-left: var(--space-500);
                                                     height: 42px; }
/* Con alguien elegido en «Responsable», su CARA ocupa el lugar del glifo
   (`.choice-face`, la casilla de elegir un integrante): las dos juntas se
   montaban y del glifo asomaba un pedazo detrás de la foto. */
.tareas-con-glifo:has(.choice-with-face) > svg { visibility: hidden; }
.tareas-descripcion { position: relative; }
.tareas-contador { display: block; text-align: right; margin-top: calc(-1 * var(--space-100));
                   font-size: var(--fs-meta); color: var(--text-light); }
.check-list--fila { flex-direction: row; flex-wrap: wrap; gap: var(--space-100);
                    margin-bottom: 0; }
.check-list--fila .field-check:has(input:checked) {
    background: var(--primary-soft); border-color: var(--primary);
    color: var(--primary-dark); font-weight: 600;
}
.tareas-sono { display: block; font-size: var(--fs-meta); color: var(--text-light);
               font-weight: 400; }
.tareas-guardar { display: flex; justify-content: flex-end; gap: var(--space-100);
                  margin-top: var(--space-250); padding-top: var(--space-200);
                  border-top: 1px solid var(--border); }
.tareas-tarjeta-title { cursor: pointer; }

/* ── Tareas: los tableros ─────────────────────────────────────────────── */
.tableros-grid { display: grid; gap: var(--space-150);
                 grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.tablero-card { display: flex; gap: var(--space-150); padding: var(--space-200);
                background: var(--card-bg); border: 1px solid var(--border);
                border-radius: var(--radius-card); text-decoration: none;
                color: var(--text); box-shadow: var(--shadow);
                transition: border-color .14s, transform .14s; }
.tablero-card:hover { border-color: var(--primary); transform: translateY(-1px); }
.tablero-card--nuevo { border-style: dashed; box-shadow: none;
                       background: transparent; color: var(--text-light); }
.tablero-card.is-archivado { opacity: .7; }
.tablero-card-color { width: 6px; border-radius: 999px; flex: none;
                      background: var(--primary); }
.tablero-card-color.is-morado { background: var(--primary); }
.tablero-card-color.is-azul { background: var(--info-vivid); }
.tablero-card-color.is-verde { background: var(--ok-vivid); }
.tablero-card-color.is-ambar { background: var(--warn-vivid); }
.tablero-card-color.is-rojo { background: var(--danger-vivid); }
.tablero-card-color.is-gris { background: var(--border-strong); }
.tablero-card-body { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.tablero-card-name { font-weight: 600; font-size: var(--fs-body); }
.tablero-card-desc { color: var(--text-light); font-size: var(--fs-small); }
.tablero-card-stats { display: flex; gap: var(--space-150); margin-top: var(--space-100);
                      font-size: var(--fs-small); color: var(--text-light); }
.tablero-card-stats .is-vencida { color: var(--danger-text); font-weight: 600; }
.tableros-archivados { margin-top: var(--space-250); }
.tableros-archivados > summary { cursor: pointer; color: var(--text-light);
                                 font-size: var(--fs-small); font-weight: 600;
                                 margin-bottom: var(--space-150); }
/* La lista con la cara del tablero por dentro (Renzo, 2026-10-10): el
   panel de vidrio sin cápsula arriba, cada tablero con la piel de la
   tarjeta de tarea —degradado blanco, filo, sombra larga, se levanta al
   pasar—, su color en la franja de siempre (Renzo, el mismo día: se
   probó un punto en baldosa y la franja se queda), y «Nuevo tablero» como
   el «Agregar» de la columna. */
.tareas-shell.tableros-shell { padding: var(--space-200); gap: var(--space-200); }
.tareas-vidrio .tableros-grid { gap: var(--space-200); }
.tareas-vidrio .tablero-card { gap: var(--space-150); padding: var(--space-200) var(--space-200) var(--space-200); border-radius: 22px; color: var(--tv-tinta);
    background: linear-gradient(180deg, var(--tv-tarjeta-arriba), var(--tv-tarjeta-abajo));
    border: 1px solid rgba(255, 255, 255, .95);
    box-shadow: inset 0 1px 0 #fff, var(--tv-tarjeta-sombra);
    transition: transform .18s ease, box-shadow .18s ease; }
:root[data-theme="dark"] .tareas-vidrio .tablero-card { border-color: rgba(255, 255, 255, .08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06), var(--tv-tarjeta-sombra); }
.tareas-vidrio .tablero-card:hover { transform: translateY(-2px); border-color: rgba(255, 255, 255, .95);
    box-shadow: inset 0 1px 0 #fff, var(--tv-tarjeta-sombra-alta); }
:root[data-theme="dark"] .tareas-vidrio .tablero-card:hover { border-color: rgba(255, 255, 255, .12); }
.tareas-vidrio .tablero-card-body { gap: var(--space-050); }
.tareas-vidrio .tablero-card-name { font-size: var(--fs-columna); letter-spacing: -.01em; color: var(--tv-tinta); }
.tareas-vidrio .tablero-card-desc { color: var(--tv-gris); }
.tareas-vidrio .tablero-card-stats { flex-wrap: wrap; gap: var(--space-050); margin-top: var(--space-100); }
.tareas-vidrio .tablero-card .pill { display: inline-flex; align-items: center; height: 24px; padding: 0 var(--space-100); gap: var(--space-050); border-radius: 999px;
    font-size: var(--fs-badge); font-weight: 500; white-space: nowrap; }
.tareas-vidrio .tablero-card .pill svg { width: 13px; height: 13px; }
.tareas-vidrio .tablero-card--nuevo { color: var(--tv-tinta-2); box-shadow: none;
    background: rgba(255, 255, 255, .42); border: 1px dashed rgba(120, 110, 170, .28); }
.tareas-vidrio .tablero-card--nuevo:hover { background: rgba(255, 255, 255, .78); box-shadow: none; }
:root[data-theme="dark"] .tareas-vidrio .tablero-card--nuevo { background: rgba(255, 255, 255, .05); border-color: rgba(255, 255, 255, .12); }
.tareas-vidrio .tableros-archivados { margin: 0; }
.tareas-vidrio .tableros-archivados > summary { color: var(--tv-gris); padding-left: var(--space-050); }

/* EL PANEL Y LA TARJETA DE VIDRIO (2026-10-12): la cara de «Lo de la
   empresa» del Inicio, mudada a la casa cuando Marketing la necesitó para la
   campaña enviada. `.panel-vidrio` es el panel translúcido con su luz lila y
   azul; `.tarjeta-vidrio` (sobre una `.card`) es la tarjeta blanca de 18px
   con la banda de baldosa redonda y título en versalitas. SIN menús adentro
   del panel: el `backdrop-filter` atrapa lo `fixed` (`docs/marco-ui.md`). */
.panel-vidrio {
    padding: var(--space-200) var(--space-250) var(--space-200); border-radius: 28px;
    border: 1px solid var(--vidrio-borde);
    background:
        radial-gradient(70% 120% at 0% 0%, color-mix(in srgb, var(--primary-soft) var(--vidrio-brillo), transparent) 0%, transparent 70%),
        radial-gradient(60% 120% at 100% 100%, color-mix(in srgb, #bfdbfe var(--vidrio-eco), transparent) 0%, transparent 70%),
        var(--vidrio-caja-tenue);
    -webkit-backdrop-filter: blur(18px) saturate(1.3); backdrop-filter: blur(18px) saturate(1.3);
    box-shadow: inset 0 1.5px 0 var(--vidrio-luz), 0 28px 50px -34px var(--vidrio-sombra);
}
/* Un `backdrop-filter` vuelve al panel el marco de todo lo `fixed` que lleve
   adentro: la hoja de `ui/_help.html` —el selector «Elegir productos» de los
   componentes de un kit, 2026-10-23— se abría metida en el panel, del alto
   del panel. Mientras una hoja está abierta adentro, el vidrio no difumina. */
.panel-vidrio:has(.help-toggle:checked) { -webkit-backdrop-filter: none; backdrop-filter: none; }
.card.tarjeta-vidrio {
    margin: 0; padding: 0; display: flex; flex-direction: column; text-align: left;
    border-radius: 18px; border: 1px solid var(--vidrio-borde);
    background: color-mix(in srgb, var(--card-bg) 94%, transparent);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 16px 30px -24px var(--vidrio-sombra), 0 1px 3px rgba(16, 18, 22, .04);
}
.tarjeta-vidrio .card-header { margin: 0; padding: var(--space-150) var(--space-100) var(--space-150) var(--space-200); gap: var(--space-050); flex-wrap: nowrap; border-bottom: 1px solid color-mix(in srgb, var(--border) 70%, transparent); background: color-mix(in srgb, var(--primary-soft) 22%, transparent); }
.tarjeta-vidrio .card-head-lead { gap: var(--space-100); min-width: 0; flex: 1; }
.tarjeta-vidrio .tile { width: 34px; height: 34px; border-radius: 50%; flex: none; background: var(--primary-soft); color: var(--primary); box-shadow: inset 0 1px 0 var(--vidrio-luz); }
.tarjeta-vidrio .tile svg { width: 17px; height: 17px; }
/* Una tabla adentro va de filo a filo de la tarjeta, sin el margen
   negativo de `.card-table` (que supone una tarjeta con relleno), y con
   las esquinas de abajo de la tarjeta. */
.tarjeta-vidrio > .table-wrap { margin: 0; border-radius: 0 0 18px 18px; }
/* El cuerpo de la tarjeta, debajo de su banda (`ui/_card_head.html`): la
   tarjeta no trae relleno para que la banda y la tabla lleguen al borde.
   Nació como `.campana-tarjeta__cuerpo` en la campaña enviada; se mudó a la
   casa con las estadísticas del chat (2026-10-16). */
.tarjeta-vidrio__cuerpo { padding: var(--space-200) var(--space-200) var(--space-200); }
.tarjeta-vidrio .card-head-text { min-width: 0; }
.tarjeta-vidrio .card-head-text h2 { font-size: var(--fs-small); font-weight: 700; letter-spacing: .01em; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* La cabecera del panel: el rótulo («Lo de **la empresa**», «Instalados»)
   a la izquierda y lo que lo acompañe —un conteo, una pastilla— a la
   derecha. Nació en el Inicio y la campaña enviada la copió; se mudó acá
   cuando Módulos la necesitó también (2026-10-12). */
.panel-vidrio__cabeza { display: flex; align-items: center; gap: var(--space-100); margin: 0 var(--space-050) var(--space-200); color: var(--text-light); }
.panel-vidrio__rotulo { flex: 1; min-width: 0; margin: 0; font-size: var(--fs-columna); font-weight: 500; line-height: 1.3; color: var(--text-light); }
.panel-vidrio__rotulo strong { color: var(--text); font-weight: 700; }

/* Los componentes de un kit, en el drawer «Armar kit» (Renzo, 2026-10-14:
   «nuestro nuevo look and feel» es el del tablero de Tareas). «Elegir
   productos» siempre arriba —el selector de la casa, 2026-10-23— y cada
   artículo agregado como una LÍNEA-tarjeta abajo, con su foto, su cantidad
   y su unidad: es `ui/_product_picker.html` dentro del panel de vidrio, y
   esto le da a cada línea la piel de la tarjeta de vidrio y pone sus campos
   a la derecha. La hoja del selector es `fixed`: mientras está abierta el
   panel suelta su `backdrop-filter` (ver `.panel-vidrio`). */
.componentes { margin-bottom: var(--space-200); }
.componentes .sku-selections:not(:empty) { gap: var(--space-100); margin-top: var(--space-200); }
.componentes .sku-selection {
    display: flex; align-items: center; gap: var(--space-150); padding: var(--space-100) var(--space-100) var(--space-100) var(--space-150);
    border-radius: 18px; border: 1px solid var(--vidrio-borde);
    background-color: color-mix(in srgb, var(--card-bg) 94%, transparent);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 16px 30px -24px var(--vidrio-sombra), 0 1px 3px rgba(16, 18, 22, .04);
}
.componentes .sku-selection .finder-info { min-width: 0; }
.componente__datos { display: flex; align-items: center; gap: var(--space-100); flex: none; }
.componente__cantidad { width: 6.5rem; }
.componente__unidad { width: 10rem; }
/* En el teléfono la cantidad y la unidad bajan a su propio renglón, debajo
   del nombre: a la derecha no cabían y el nombre quedaba de una letra por
   línea. La × se queda arriba, al lado del nombre. */
@media (max-width: 560px) {
    .componentes .sku-selection { flex-wrap: wrap; row-gap: var(--space-100); }
    .componentes .sku-selection > .btn { order: 2; }
    .componente__datos { order: 3; flex: 1 1 100%; min-width: 0; box-sizing: border-box; padding-left: var(--space-600); }
    .componente__cantidad { width: 6rem; flex: none; }
    .componente__unidad { width: auto; flex: 1 1 auto; min-width: 0; }
}
.componentes__vacio { margin: var(--space-200) var(--space-050) 0; }
.card.tarjeta-vidrio.componentes__copiar { gap: var(--space-075); padding: var(--space-200) var(--space-200); margin-bottom: var(--space-200); }
.componentes__copiar-fila { display: flex; align-items: center; gap: var(--space-100); }
.componentes__copiar-fila select { flex: 1 1 auto; min-width: 0; }

/* Módulos con la cara de Integraciones (Renzo, 2026-10-31): el directorio
   de la casa (`.directorio`, `ui/_tarjeta_directorio.html`); acá solo la
   fila de arriba —las fichas a la izquierda, «Ordenar por» a la derecha—
   y el engranaje de los ajustes de cada módulo. */
.modulos { display: flex; flex-direction: column; gap: var(--space-250); }
.modulos-barra { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-150); }
.modulos-controles { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100); }
.modulo__engranaje { flex: none; }

/* Marketing (2026-10-12): la ficha con la cara de Tareas (la lista pasó a la
   tabla de la casa el 2026-10-18). El panel de vidrio y las pastillas son los
   de arriba; esto es solo el acomodo propio: los cuatro pasos de la ficha, el «cuándo» que muestra el día y la hora solo al programar, y el
   resumen fijo con su lista de pasos. */
/* La tabla (2026-10-17): las pastillas de cada campaña en su celda, en fila
   y sin partirse. */
.pendiente-pastillas { display: flex; flex-wrap: wrap; gap: var(--space-050); }
/* En el teléfono la tabla se desplaza de lado: el nombre no se aprieta a
   una palabra por renglón para que quepa todo. */
.pendiente-nombre { min-width: 30ch; }
.campana-ficha { display: flex; flex-direction: column; gap: var(--space-200); }
.campana-pasos .card { display: flex; flex-direction: column; gap: var(--space-150); margin: 0; }
.campana-pasos .card > .card-header { margin-bottom: 0; }
.campana-cuando:not(:has(input[name="when"][value="later"]:checked)) .campana-fecha { display: none; }
/* Marketing › Nueva, una sola pantalla (spec marketing-nueva, 2026-10-16):
   el radio «¿Quién la crea?» decide qué camino se ve —«Tú» o «El Jefe»—; en
   el del Jefe, «A quién le llega» solo con correo o WhatsApp, los productos
   solo con Instagram y el día y la hora solo al programar. La barra de
   guardar es de «Tú»: el Jefe tiene su botón en el resumen. */
.nueva-dos:not(:has(input[name="quien"][value="jefe"]:checked)) .nueva-jefe,
.nueva-dos:has(input[name="quien"][value="jefe"]:checked) .nueva-tu { display: none; }
html.js body:has(.nueva-dos input[name="quien"][value="jefe"]:checked) .savebar { display: none; }
/* Las tarjetas de cada camino van en su envoltura, no sueltas en el form:
   la envoltura repite el espacio de la columna o quedan pegadas. */
.nueva-jefe, .campana-pasos > .nueva-tu { display: flex; flex-direction: column; gap: var(--space-200); }
.nueva-jefe:not(:has(input[name="formato"][value="correo"]:checked, input[name="formato"][value="whatsapp"]:checked)) .nueva-solo-envio,
.nueva-jefe:not(:has(input[name="formato"][value="post"]:checked, input[name="formato"][value="carrusel"]:checked, input[name="formato"][value="reel"]:checked, input[name="formato"][value="historia"]:checked)) .nueva-solo-redes,
.nueva-cuando:not(:has(input[name="cuando"][value="programar"]:checked)) .campana-fecha { display: none; }
.campana-alcance { padding: var(--space-150) var(--space-200); border-radius: 14px;
    background: var(--tv-neutro-fondo); color: var(--tv-tinta); }
.campana-alcance-cifra { margin: 0; }
.campana-frecuencia { display: flex; flex-direction: column; gap: var(--space-100); }
.campana-frecuencia-fila { display: flex; align-items: center; gap: var(--space-150); }
.campana-frecuencia-fila > :first-child { width: 150px; flex: none; }
.campana-frecuencia-unidad { font-size: var(--fs-small); color: var(--tv-gris); }
.campana-alcance-motivos { margin: var(--space-025) 0 0; font-size: var(--fs-small); color: var(--tv-gris); }
/* «Enviar prueba a» del correo del equipo, debajo del alcance (2026-10-14). */
.correo-prueba { margin-top: var(--space-150); padding-top: var(--space-150);
                 border-top: 1px solid var(--border);
                 display: flex; flex-direction: column; gap: var(--space-100);
                 align-items: flex-start; }
.correo-prueba .field { width: 100%; margin: 0; }
/* «Antes de soltarla» acompaña al bajar. El `sticky` va en la COLUMNA y no
   en la tarjeta: `.ficha-cols` alinea arriba (`align-items: start`), así que
   la lateral mide lo que su única tarjeta y una tarjeta pegada adentro no
   tiene por dónde moverse; la columna sí, porque la rejilla mide lo que el
   formulario. `top` libra la banda pegada del título (`--band-h`), como el
   riel de la plantilla de etiqueta, pero con `--sp-3` y no `--sp-4`: la banda
   real mide 60px y no los 55 de la variable, y con 16 la tarjeta saltaba 3px
   al cargar, antes de desplazar nada (medido a 1.524px). Debajo del punto de
   `.ficha-cols` cae bajo el formulario y se suelta. */
.campana-ficha .ficha-side { position: sticky; top: calc(var(--band-h) + var(--space-150)); }
@media (max-width: 1000px) {
    .campana-ficha .ficha-side { position: static; }
}
.campana-resumen-cifra { margin: 0 0 var(--space-150); font-size: var(--fs-columna); font-weight: 600;
    color: var(--tv-tinta); }
.campana-checks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-100); }
.campana-checks li { display: flex; gap: var(--space-100); align-items: flex-start; }
.campana-check-marca { flex: none; width: 24px; height: 24px; border-radius: 999px;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--fs-badge); font-weight: 600; background: var(--tv-neutro-fondo); color: var(--tv-neutro); }
.campana-check-marca svg { width: 14px; height: 14px; }
.campana-checks li.is-hecho .campana-check-marca { background: var(--primary); color: #fff; }
.campana-check-texto { display: flex; flex-direction: column; min-width: 0; }
.campana-check-titulo { font-weight: 600; color: var(--tv-tinta); }
.campana-check-valor { font-size: var(--fs-small); color: var(--tv-gris); overflow-wrap: anywhere; }
.campana-acciones { display: flex; flex-direction: column; gap: var(--space-100); margin-top: var(--space-200); }
.campana-acciones .btn { width: 100%; justify-content: center; }
.campana-resumen-nota { margin: var(--space-200) 0 0; font-size: var(--fs-small); color: var(--tv-gris); }
/* La campaña enviada: el panel y las tarjetas de vidrio de la casa. */
.campana-enviada { display: flex; flex-direction: column; gap: var(--space-250); }
/* El propósito de la que ya salió: un `.field` (su aire hasta las tarjetas)
   del ancho de sus opciones, no de todo el panel (Renzo, 2026-11-03). */
.campana-proposito { max-width: 320px; margin-inline: var(--space-050); }
.campana-tarjetas { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-200); }
/* Igual que `.tarjeta-vidrio__cuerpo`, la pieza de la casa. */
.campana-tarjeta__cuerpo { padding: var(--space-200) var(--space-200) var(--space-200); }
.campana-cifra { margin: 0; font-size: var(--fs-page); font-weight: 700; line-height: 1.15; color: var(--text); }
.campana-cifra-de { font-size: var(--fs-body); font-weight: 500; color: var(--text-light); }
.campana-cifra-sub { margin: var(--space-025) 0 0; color: var(--text-light); }
.campana-nota { margin: var(--space-150) 0 0; font-size: var(--fs-small); color: var(--text-faint); }
.campana-filas { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-100); }
.campana-filas li { display: flex; justify-content: space-between; gap: var(--space-150); color: var(--text-light); }
.campana-filas__valor { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.campana-filas__valor small { font-weight: 500; color: var(--text-light); }
/* Lo que vendió, con su tinte (verde), como Gratitud e Hitos en el Inicio. */
.card.campana-tarjeta--ventas { background: linear-gradient(165deg, color-mix(in srgb, #bbf7d0 var(--vidrio-brillo), var(--card-bg)) 0%, var(--card-bg) 80%); }
.campana-tarjeta--ventas .card-header { background: none; }
.campana-tarjeta--ventas .tile { background: color-mix(in srgb, #86efac 32%, var(--card-bg)); color: #15803d; }
.campana-destinatarios .card-header { padding: var(--space-150) var(--space-200); }
.campana-panel__tabla { margin-top: var(--space-200); }
/* Un correo largo o un motivo largo parten línea: si no, empujan la tabla
   más allá de su tarjeta. */
.campana-destinatarios td.campana-parte { white-space: normal; overflow-wrap: anywhere; }

/* El color se elige con bolitas, no con un desplegable de nombres. */
.tablero-colores { display: flex; gap: var(--space-100); }
/* `.field label` es más específica y pone `display: block`; por eso el
   selector va con `.field` adelante, o la bolita mide cero. */
.field .tablero-color-opcion { position: relative; width: 32px; height: 32px;
                               border-radius: 999px; cursor: pointer;
                               border: 2px solid transparent; display: grid;
                               place-items: center; margin: 0; }
.tablero-color-opcion input { position: absolute; opacity: 0; inset: 0;
                              cursor: pointer; }
.tablero-color-opcion span { display: block; width: 22px; height: 22px;
                             border-radius: 999px; background: var(--primary); }
.tablero-color-opcion.is-azul span { background: var(--info-vivid); }
.tablero-color-opcion.is-verde span { background: var(--ok-vivid); }
.tablero-color-opcion.is-ambar span { background: var(--warn-vivid); }
.tablero-color-opcion.is-rojo span { background: var(--danger-vivid); }
.tablero-color-opcion.is-gris span { background: var(--border-strong); }
.tablero-color-opcion:has(input:checked) { border-color: var(--text); }
.tablero-archivar { display: flex; align-items: center; gap: var(--space-150);
                    margin-top: var(--space-200); padding-top: var(--space-200);
                    border-top: 1px solid var(--border); }
/* «+ Nueva tarea» al pie de cada columna, y el bloque plegable de
   recurrencia del drawer de creación. */
.tablero-agregar { display: flex; align-items: center; justify-content: center;
                  gap: var(--space-075); margin-top: var(--space-100); padding: var(--space-100);
                  border-radius: var(--radius-field);
                  background: color-mix(in srgb, var(--text) 4%, transparent);
                  font-size: var(--fs-small); font-weight: 500;
                  color: var(--text-light); text-decoration: none;
                  transition: background .16s, color .16s; }
.tablero-agregar svg { width: 15px; height: 15px; flex: none; }
.tablero-agregar:hover { background: var(--card-bg); color: var(--primary); }
.tareas-repite > summary { list-style: none; cursor: pointer; }
.tareas-repite > summary::-webkit-details-marker { display: none; }
.tareas-repite > summary::before { content: "▸"; margin-right: var(--space-100);
                                   color: var(--text-light); }
.tareas-repite[open] > summary::before { content: "▾"; }

/* ── Segmentos ─────────────────────────────────────────────────────────────
   La ficha usa las piezas del editor de condiciones de las colecciones
   (`match-row`, `boxed-list`, `rule-*`, `rule-builder`). Lo que sigue es solo
   lo que el segmento tiene y la colección no. */

/* Los ajustes opcionales de una condición («En los últimos N días», «Canal»)
   en su PROPIA línea del panel: puestos al lado del valor partían la fila en
   un lugar distinto según el ancho, y se leían como otro valor más. */
.rule-builder-extra {
    flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center;
    gap: var(--space-150); margin: 0;
}
.rule-param { display: inline-flex; align-items: center; gap: var(--space-100); }
/* Las etiquetas del borrador son el campo de fichas del marco, y dentro de
   una fila no llevan su margen de abajo: lo pone el panel. */
.rule-builder .token-field { flex: 1 1 240px; min-width: 0; margin-bottom: 0; }

/* Cuántos entran, en la tarjeta de la vista previa: la cifra es lo que se
   mira, la palabra la acompaña. */
.segment-count {
    display: flex; align-items: baseline; gap: var(--space-100);
    margin: 0 0 var(--space-150); color: var(--text-light);
}
.segment-count strong {
    font-size: 2rem; line-height: 1; color: var(--text);
    font-variant-numeric: tabular-nums;
}

/* ── Bins: las columnas del Finder ────────────────────────────────────── */
/* Sede › zona › pasillo › estante › nivel › bin como columnas, una al lado
   de la otra: el clic en un elemento abre la siguiente (Renzo, 2026-09-12,
   después de probar las carpetas de a una pantalla). El contenedor se
   desplaza en horizontal cuando la ruta es más ancha que la tarjeta. */
.cols-browser { display: flex; overflow-x: auto; align-items: stretch;
                margin: 0 calc(-1 * var(--space-200)); }
.cols-col { flex: 0 0 260px; min-width: 260px; display: flex;
            flex-direction: column; gap: var(--space-025); padding: var(--space-100);
            border-right: 1px solid var(--border); }
.cols-col:last-child { border-right: 0; }
.cols-head { margin: 0; padding: var(--space-075) var(--space-100) var(--space-100); font-size: var(--fs-label);
             font-weight: 600; color: var(--text-light);
             text-transform: uppercase; letter-spacing: .04em; }
.cols-item { display: flex; align-items: center; justify-content: space-between;
             gap: var(--space-100); padding: var(--space-100) var(--space-100); border-radius: 10px;
             color: inherit; text-decoration: none; }
.cols-item:hover { background-color: var(--primary-soft); }
.cols-item-selected { background-color: var(--primary-soft); color: var(--primary-dark);
                      font-weight: 600; }
.cols-item-off { opacity: .55; }
.cols-item-text { display: flex; flex-direction: column; min-width: 0; }
.cols-item-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cols-item-code { font-size: var(--fs-label); color: var(--text-light); }
/* El conteo: gris cuando es cero, de marca cuando hay algo adentro. */
.cols-count { flex-shrink: 0; min-width: 28px; text-align: center; padding: var(--space-025) var(--space-100);
              border-radius: 999px; background-color: var(--border);
              font-size: var(--fs-label); font-weight: 600; font-variant-numeric: tabular-nums; }
.cols-count-on { background-color: var(--primary); color: #fff; }
.cols-empty { margin: 0; padding: var(--space-100); color: var(--text-light); font-size: var(--fs-label); }
.cols-create { padding: var(--space-075) var(--space-050) var(--space-025); margin-top: auto; }
/* El buscador de artículos (`.sku-field`) dentro de una fila con su botón:
   es él, y no su `<input>`, quien reparte el ancho con el botón. */
.inline-row .sku-field { flex: 1 1 220px; min-width: 0; }

/* ── Bins: navegación y referencia espacial compactas ────────────────── */
/* La ficha de un nivel o de un bin (maqueta de Renzo, 2026-09-13): tres
   columnas debajo de la banda —galería, cifras y datos, pistas— que en
   angosto se apilan. Las fotos son del estante tomadas con el celular,
   apaisadas o verticales, y lo que importa es reconocer el lugar, no verlas
   enteras: `object-fit: cover` en la grande y en las miniaturas. */
.place-card { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.5fr) minmax(0, 1fr); gap: var(--space-200); padding-top: var(--space-200); align-items: start; }
/* Sin pistas —la ficha de una zona, un estante o un nivel— no hay tercera
   columna: las cifras se quedan con el resto del ancho. */
.place-card:not(:has(.place-hints)) { grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); }
.place-slider { min-width: 0; }
.galeria { max-width: 100%; }
.place-slider .splide__track { border-radius: var(--radius-field); }
.place-photo { display: block; aspect-ratio: 4 / 3; border: 1px solid var(--border); border-radius: var(--radius-field); background-color: var(--bg); overflow: hidden; line-height: 0; }
.place-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.place-photo-none { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-100); color: var(--text-faint); font-size: var(--fs-small); line-height: 1.3; }
.place-photo-none svg { width: 28px; height: 28px; }
/* Sin script, la lista del slider se apila con aire entre fotos. */
.place-slider:not(.is-initialized) .splide__list { display: flex; flex-direction: column; gap: var(--space-100); }
/* Las flechas y los puntos de Splide con la piel del ERP: el `core` trae
   solo la mecánica, sin colores, y así no hay dos estilos peleando. Nació
   en Bins (`.place-slider`); desde 2026-10-12 cualquier carrusel de la
   receta `fotos` la toma con `.carrusel-fotos` (el visor de una
   publicación de Marketing), en vez de escribir otra. */
.place-slider .splide__arrow, .carrusel-fotos .splide__arrow { position: absolute; top: 50%; transform: translateY(-50%); z-index: 1; width: 30px; height: 30px; border: 0; border-radius: 999px; background-color: rgba(255, 255, 255, .92); color: var(--text); box-shadow: 0 1px 4px rgba(0, 0, 0, .18); cursor: pointer; display: grid; place-items: center; opacity: .9; }
.place-slider .splide__arrow:hover, .carrusel-fotos .splide__arrow:hover { opacity: 1; color: var(--primary-dark); }
.place-slider .splide__arrow:disabled, .carrusel-fotos .splide__arrow:disabled { opacity: .3; cursor: default; }
.place-slider .splide__arrow svg, .carrusel-fotos .splide__arrow svg { width: 14px; height: 14px; fill: currentColor; }
.place-slider .splide__arrow--prev, .carrusel-fotos .splide__arrow--prev { left: 8px; }
.place-slider .splide__arrow--prev svg, .carrusel-fotos .splide__arrow--prev svg { transform: scaleX(-1); }
.place-slider .splide__arrow--next, .carrusel-fotos .splide__arrow--next { right: 8px; }
.place-slider .splide__pagination, .carrusel-fotos .splide__pagination { display: flex; justify-content: center; gap: var(--space-075); margin: var(--space-100) 0 0; padding: 0; list-style: none; }
.place-slider .splide__pagination__page, .carrusel-fotos .splide__pagination__page { width: 8px; height: 8px; padding: 0; border: 0; border-radius: 999px; background-color: var(--border-strong); cursor: pointer; }
.place-slider .splide__pagination__page.is-active, .carrusel-fotos .splide__pagination__page.is-active { background-color: var(--primary); }
.place-facts { display: flex; flex-direction: column; gap: var(--space-150); min-width: 0; }
.place-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: var(--space-150); }
.place-tile, .place-fact { display: flex; gap: var(--space-150); padding: var(--space-150); border-radius: var(--radius-card); background-color: var(--bg); min-width: 0; }
.place-fact { align-items: center; }
.place-tile { flex-direction: column; align-items: flex-start; }
.place-tile-text, .place-fact-text { display: flex; flex-direction: column; gap: var(--space-025); min-width: 0; flex: 1; }
.place-tile-label, .place-fact-label { font-size: var(--fs-label); color: var(--text-light); }
.place-tile-value { font-size: var(--fs-stat); font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.place-fact-value { font-size: var(--fs-body); overflow-wrap: anywhere; }
.place-text { margin: 0; color: var(--text); line-height: 1.5; }
.place-hints { display: flex; flex-direction: column; gap: var(--space-150); min-width: 0; }
.place-hint { display: flex; align-items: flex-start; gap: var(--space-150); padding: var(--space-150); border-radius: var(--radius-card); background-color: var(--primary-soft); }
.place-hint-text { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.place-hint-title { font-weight: 600; color: var(--primary-dark); }
.place-hint-body { font-size: var(--fs-small); color: var(--text-light); line-height: 1.5; }
@media (max-width: 960px) { .place-card { grid-template-columns: 1fr; } }

/* Las fotos que ya hay, en el drawer de editar: cada una con su ×, que es
   la casilla «remove_photos» vestida. Marcada, la foto se atenúa y lo dice. */
.place-edit-photos { display: flex; flex-wrap: wrap; gap: var(--space-150); margin-bottom: var(--space-150); }
.place-edit-photo { position: relative; display: flex; flex-direction: column; gap: var(--space-050); width: 96px; cursor: pointer; }
.place-edit-photo img { width: 96px; height: 72px; object-fit: cover; border: 1px solid var(--border); border-radius: var(--radius-sm); display: block; transition: opacity .16s; }
.place-edit-x, .upload-file-x { position: absolute; top: -6px; right: -6px; width: 22px; height: 22px; border: 0; border-radius: 999px; background-color: var(--text); color: #fff; font-size: var(--fs-glyph); line-height: 22px; text-align: center; cursor: pointer; padding: 0; box-shadow: 0 1px 3px rgba(0, 0, 0, .25); }
.place-edit-check { position: absolute; inset: 0; opacity: 0; cursor: pointer; margin: 0; }
.place-edit-check:focus-visible ~ .place-edit-x { outline: 2px solid var(--primary); outline-offset: 1px; }
.place-edit-state { font-size: var(--fs-label); color: var(--danger-text); visibility: hidden; }
.place-edit-photo:has(.place-edit-check:checked) img { opacity: .35; }
.place-edit-photo:has(.place-edit-check:checked) .place-edit-x { background-color: var(--danger-text); }
.place-edit-photo:has(.place-edit-check:checked) .place-edit-state { visibility: visible; }
.field-note { font-weight: 400; color: var(--text-faint); }

/* Lo elegido en `ui/_upload.html` con `multiple`: una miniatura por archivo
   con su ×, antes de guardar. Un archivo que no es imagen muestra su nombre. */
.upload-files { display: flex; flex-wrap: wrap; gap: var(--space-150); }
.upload-file { position: relative; width: 72px; height: 72px; border: 1px solid var(--border); border-radius: 10px; background-color: var(--bg); overflow: visible; }
.upload-file img { width: 100%; height: 100%; object-fit: cover; border-radius: 10px; display: block; }
.upload-file-name { display: block; padding: var(--space-075); font-size: var(--fs-label); color: var(--text-light); overflow: hidden; text-overflow: ellipsis; word-break: break-all; max-height: 100%; }

.bins-top-row { --bins-card-min-height: 242px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-200); align-items: stretch; }
.bins-top-row > .card { min-height: var(--bins-card-min-height); min-width: 0; }
.bins-search-card .inline-row { flex-wrap: wrap; }
.bins-search-card .inline-row input { min-width: 0; flex: 1 1 140px; }
.bins-search-card .bins-scan-row { margin-top: var(--space-200); line-height: 1.8; }
.bin-contents-card { scroll-margin-top: calc(var(--topbar, 64px) + 20px); }
.bin-contents-card > .form-card { padding-top: var(--space-200); }
.bin-contents-card > .form-card > .form-lead { margin-top: var(--space-200); line-height: 1.8; }
.rack-card { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); padding: 0; overflow: hidden; }
.rack-stage { position: relative; min-width: 0; min-height: calc(var(--bins-card-min-height, 242px) - 2px); background: var(--bg); grid-column: 1; grid-row: 1 / 3; }
.rack-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; touch-action: none; cursor: grab; }
.rack-canvas:active { cursor: grabbing; }
.rack-toolbar { position: absolute; top: 10px; right: 10px; }
.rack-gestures { position: absolute; bottom: 10px; inset-inline: 10px; display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-050) var(--space-100); pointer-events: none; font-size: var(--fs-meta); color: var(--text-light); }
.rack-error { position: absolute; inset: 30px; display: grid; place-items: center; text-align: center; color: var(--text-light); font-size: var(--fs-label); }
.rack-error[hidden] { display: none; }
.rack-hover { position: absolute; top: 42px; left: 10px; max-width: 80%; padding: var(--space-050) var(--space-100); border-radius: 6px; background: var(--surface, white); color: var(--text); font-size: var(--fs-label); pointer-events: none; }
.rack-header { grid-column: 2; grid-row: 1; align-self: end; padding: var(--space-250) var(--space-200) var(--space-100); }
.rack-title { margin: 0 0 var(--space-100); font-size: var(--fs-page); line-height: 1.25; letter-spacing: -.4px; font-weight: 650; }
.rack-trail { margin: 0; font-size: var(--fs-label); color: var(--text-light); line-height: 1.7; }
.rack-current { display: inline-flex; align-items: center; gap: var(--space-100); color: var(--primary); font-size: var(--fs-label); margin-top: var(--space-150); }
.rack-dot { width: 7px; height: 7px; border-radius: 2px; background: var(--primary); display: inline-block; flex-shrink: 0; }
.rack-caption { grid-column: 2; grid-row: 2; align-self: start; margin: 0; padding: 0 var(--space-200) var(--space-250); font-size: var(--fs-label); color: var(--text-light); line-height: 1.6; overflow-wrap: anywhere; }
.rack-guide { grid-column: 1 / -1; align-self: center; padding: var(--space-300) var(--space-300); }
.rack-guide-icon { width: 34px; height: 34px; color: var(--primary); float: left; margin: 0 var(--space-200) var(--space-100) 0; }
.rack-guide-context { margin: 0 0 var(--space-100); color: var(--text-light); font-size: var(--fs-label); }
.rack-hint { color: var(--text-light); font-size: var(--fs-label); line-height: 1.7; margin: var(--space-100) 0 0; }
.rack-card-guide { background: var(--bg); }
.content:has([data-bins-workspace]) .page-banner { padding-block: var(--space-200); }
@media (max-width: 1000px) {
  .bins-top-row { grid-template-columns: 1fr; }
}
@media (max-width: 480px) {
  .rack-card { grid-template-columns: 1fr; }
  .rack-header { grid-column: 1; grid-row: 1; padding: var(--space-200) var(--space-200) var(--space-100); }
  .rack-stage { grid-column: 1; grid-row: 2; }
  .rack-caption { grid-column: 1; grid-row: 3; padding: var(--space-100) var(--space-200) var(--space-200); }
}


/* ── Plan y pago (kernel/billing) ─────────────────────────────────────────
   Las tarjetas de plan son etiquetas con un radio adentro: elegir es tocar
   la tarjeta entera, y la elegida se marca por `:has(input:checked)` (y por
   `.is-selected`, que pone plan-pago.js para quien no tenga `:has`). */
.plan-alert {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-200);
    padding: var(--space-200) var(--space-250); border-radius: var(--radius); border: 1px solid var(--border);
}
.plan-alert p { margin: var(--space-050) 0 0; color: var(--text-light); font-size: var(--fs-small); }
.plan-alert-danger { background: var(--danger-bg); border-color: var(--danger-text); }
.plan-alert-warn { background: var(--warn-bg); border-color: var(--warn-text); }
.plan-alert-ok { background: var(--primary-soft); border-color: var(--primary-soft-border); }

.plan-period-toggle { display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap; }
.plan-period-option {
    display: inline-flex; align-items: center; gap: var(--space-075); cursor: pointer;
    padding: var(--space-075) var(--space-200); border: 1px solid var(--border); border-radius: 999px;
    font-size: var(--fs-small); font-weight: 600;
    background: var(--card-bg); color: inherit; font-family: inherit;
}
.plan-period-option[aria-pressed="true"] { border-color: var(--primary); color: var(--primary-dark); background: var(--primary-soft); }

.plan-pick { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-200); margin-top: var(--space-200); }
.plan-pick-card {
    position: relative; display: flex; flex-direction: column; gap: var(--space-075);
    padding: var(--space-250); border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--card-bg); cursor: pointer;
}
.plan-pick-card input[type="radio"] { position: absolute; top: 18px; right: 18px; width: 18px; height: 18px; accent-color: var(--primary); }
.plan-pick-card:has(input:checked), .plan-pick-card.is-selected { border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
.plan-pick-quote { cursor: default; }
.plan-pick-name { font-size: var(--fs-stat); font-weight: 700; margin-top: var(--space-100); }
.plan-pick-lead { color: var(--text-light); font-size: var(--fs-small); }
.plan-pick-price { font-size: var(--fs-number); font-weight: 800; letter-spacing: -.02em; margin-top: var(--space-100); }
.plan-pick-per { color: var(--text-light); font-size: var(--fs-small); }
.plan-pick-list { list-style: none; margin: var(--space-150) 0 0; padding: var(--space-150) 0 0; border-top: 1px solid var(--border); display: grid; gap: var(--space-100); font-size: var(--fs-small); }
.plan-pick-list li { display: flex; gap: var(--space-100); align-items: flex-start; }
.plan-pick-list li::before { content: '✓'; flex: none; width: 18px; height: 18px; border-radius: 6px; display: grid; place-items: center; font-size: var(--fs-meta); background: var(--primary-soft); color: var(--primary-dark); }
.plan-pick-quote .btn { margin-top: auto; justify-content: center; }

.pay-grid { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: var(--space-300); margin-top: var(--space-200); }
.pay-grid .field-wide { grid-column: 1 / -1; }
.pay-summary { padding: var(--space-250); border-radius: var(--radius); background: var(--primary-soft); display: grid; gap: var(--space-100); align-content: start; }
.pay-summary h3 { margin: 0 0 var(--space-075); font-size: var(--fs-section); }
.pay-summary-row { display: flex; justify-content: space-between; gap: var(--space-150); font-size: var(--fs-small); color: var(--text-light); }
.pay-summary-row strong { color: inherit; }
.pay-summary-total { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-150); padding-top: var(--space-100); border-top: 1px solid var(--border); }
.pay-summary-total strong { font-size: var(--fs-page); }
.pay-summary .btn { justify-content: center; }

@media (max-width: 820px) {
    .plan-pick { grid-template-columns: 1fr; }
    .pay-grid { grid-template-columns: 1fr; }
    .plan-alert { flex-direction: column; align-items: flex-start; }
}

/* La sección de tarjeta de Plan y pago: glifo dentro de la casilla, la
   marca detectada a la derecha del número, y «Pago seguro · SSL» arriba. */
.pay-secure { display: flex; align-items: center; gap: var(--space-100); color: var(--text-light); font-size: var(--fs-small); }
.pay-secure svg { width: 16px; height: 16px; }
.pay-field { position: relative; display: flex; align-items: center; }
.pay-field > svg { position: absolute; left: 12px; width: 16px; height: 16px; color: var(--text-light); pointer-events: none; }
.pay-field input { padding-left: var(--space-500); width: 100%; }
/* Las franquicias (ui/_franquicias.html): apagadas hasta que el número
   diga cuál es; en Plan y pago van dentro de la casilla, a la derecha. */
.franquicias { display: inline-flex; gap: var(--space-075); align-items: center; }
.franquicia { width: 30px; height: 20px; opacity: .35; filter: grayscale(1); transition: opacity .15s, filter .15s; }
.franquicia.is-on { opacity: 1; filter: none; }
.franquicias:not(:has(.is-on)) .franquicia { opacity: .6; filter: none; }
.pay-field .franquicias { position: absolute; right: 10px; }
.pay-field input { padding-right: 156px; }

/* La cuenta regresiva de Plan y pago: cuántos días quedan de prueba o de
   cortesía. Cabeza con glifo, título y pastilla; pie con el número y la
   fecha; la barra ancha, y lo lleno es lo que queda. El tono lo pone
   .plan-alert-*. */
.plan-countdown { display: block; }
.plan-countdown-head { display: flex; align-items: flex-start; gap: var(--space-200); }
.plan-countdown-tile { background: var(--card-bg); }
.plan-alert-ok .plan-countdown-tile { color: var(--primary-dark); }
.plan-alert-warn .plan-countdown-tile { color: var(--warn-text); }
.plan-alert-danger .plan-countdown-tile { color: var(--danger-text); }
.plan-countdown-text { flex: 1 1 auto; min-width: 0; }
.plan-countdown-title { margin: 0 0 var(--space-075); font-size: var(--fs-page); font-weight: 800; letter-spacing: -.02em; }
.plan-countdown-text p { margin: 0; line-height: 1.5; }
.plan-countdown-foot { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-150); margin-top: var(--space-200); font-size: var(--fs-small); color: var(--text-light); }
.plan-countdown-foot strong { font-size: var(--fs-stat); color: var(--text); }
.plan-progress { margin-top: var(--space-100); height: 12px; border-radius: 999px; background: var(--card-bg); overflow: hidden; }
.plan-progress i { display: block; height: 100%; border-radius: 999px; }
.plan-alert-ok .plan-progress i { background: var(--primary); }
.plan-alert-warn .plan-progress i { background: var(--warn-text); }
.plan-alert-danger .plan-progress i { background: var(--danger-text); }
@media (max-width: 820px) {
    .plan-countdown-head { flex-wrap: wrap; }
}

/* La etiqueta de estado de la cuenta regresiva, en el tono de la tarjeta.
   No es una pastilla de la casa: ese vocabulario (ok/warn/danger/info/
   muted) no se amplía, y acá el tono tranquilo es el morado de la marca. */
.plan-countdown-state { padding: var(--space-075) var(--space-200); border-radius: 999px; font-size: var(--fs-small); font-weight: 600; white-space: nowrap; background: var(--card-bg); }
.plan-alert-ok .plan-countdown-state { color: var(--primary-dark); }
.plan-alert-warn .plan-countdown-state { color: var(--warn-text); }
.plan-alert-danger .plan-countdown-state { color: var(--danger-text); }

/* El gancho «Ahorra 2 meses» del interruptor, en el morado de la marca:
   Renzo no quiso verdes en Plan y pago (2026-09-19). */
.plan-save-tag { padding: var(--space-075) var(--space-150); border-radius: 999px; font-size: var(--fs-small); font-weight: 700; white-space: nowrap; background: var(--primary); color: #fff; }

/* La ficha de una empresa en la Flota: los datos de la cuenta como una
   fila de hechos, y las acciones como tarjetas iguales con el botón al
   pie. */
.flota-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-200); margin: 0; }
.flota-facts div { display: grid; gap: var(--space-050); align-content: start; }
.flota-facts dt { margin: 0; font-size: var(--fs-meta); text-transform: uppercase; letter-spacing: .06em; color: var(--text-light); }
.flota-facts dd { margin: 0; display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-075); }
.flota-facts dd strong { font-size: var(--fs-stat); }
.flota-actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--space-200); }
.flota-action { display: flex; flex-direction: column; gap: var(--space-100); padding: var(--space-200); border: 1px solid var(--border); border-radius: var(--radius); background: var(--card-bg); }
.flota-action h3 { margin: 0; font-size: var(--fs-section); }
.flota-action .card-actions { margin-top: auto; padding-top: var(--space-075); }
.flota-action-danger { border-color: var(--danger-vivid); background: var(--danger-bg); }
/* ── La hoja que se imprime ────────────────────────────────────────────────
   Nació con el pedido (`orders/order_print.html`), y las clases son genéricas
   —`.hoja`, no `.hoja-pedido`— a propósito: el documento que se mete en una
   caja tiene la misma forma lo imprima quien lo imprima, y la hoja de ruta
   del reparto o un traslado piden exactamente esto.

   SOLO TINTA. El único color de la hoja es el LOGO de la tienda. El morado
   de Agrupa no entra: este documento lo recibe el cliente de un comerciante,
   y ponerle la marca del ERP es firmar con el nombre equivocado. La
   jerarquía la cargan el tamaño, el peso, una regla y el aire — que además
   es lo único que sobrevive a una impresora en blanco y negro, que es la que
   hay. Un diseño que depende de un acento de color se imprime gris.

   El negro es CÁLIDO (`#17140f`) y no un `#111`: sobre papel, un negro
   azulado se ve sucio al lado de la tinta de una impresora láser.

   LA HOJA SE VE IGUAL EN PANTALLA Y EN PAPEL. No hay una maqueta para cada
   una: lo único que hace `@media print` es APAGAR lo que envuelve. Dos
   maquetas del mismo documento divergen, y la que se descubre rota es
   siempre la del papel, porque nadie la mira hasta que ya salió.
   ------------------------------------------------------------------------ */

.hoja {
    --hoja-tinta: #17140f;
    --hoja-tinta-2: #6a625a;
    --hoja-linea: #ddd7ce;
    max-width: 760px;
    padding: var(--space-400) var(--space-400) var(--space-400);
    background: var(--card-bg);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    color: var(--hoja-tinta);
    font-size: var(--fs-body);
    line-height: 1.45;
}

/* La cabecera: el folio grande a la izquierda, el logo enfrente. Es el único
   sitio donde esta hoja levanta la voz — todo lo demás se lee agachándose y
   esto se lee de lejos, que es lo que hace falta cuando la hoja aparece
   suelta en una mesa de empaque. */
.hoja-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-300);
    padding-bottom: var(--space-200); margin-bottom: var(--space-300);
    border-bottom: 1.5px solid var(--hoja-tinta);
}
.hoja-head-text { min-width: 0; }
.hoja-folio {
    margin: 0; font-size: var(--fs-number); font-weight: 700;
    letter-spacing: -.8px; line-height: 1.05;
    font-variant-numeric: tabular-nums;
}
.hoja-fecha { margin: var(--space-050) 0 0; font-size: var(--fs-meta); color: var(--hoja-tinta-2); }
.hoja-codigo { margin: var(--space-100) 0 0; }
.hoja-codigo svg { height: 3.5rem; width: auto; }
.hoja-cite { margin: var(--space-050) 0 0; font-size: var(--fs-meta); color: var(--hoja-tinta-2); }

/* El degradado es de la marca GENÉRICA —la inicial cuando la empresa no subió
   logo—, y por eso cuelga del `span` y no de la clase: colgado de la clase se
   lo comía también el `<img>`, y un logo de fondo transparente —que es como
   se sube un logo— salía como un cuadrado morado. */
span.hoja-marca {
    width: 38px; height: 38px; flex-shrink: 0;
    display: grid; place-items: center; border-radius: var(--radius-sm);
    font-size: var(--fs-small); font-weight: 700; color: #fff;
    background: linear-gradient(135deg, var(--primary) 0%, #a78bfa 100%);
}
/* Alto fijo y ancho libre: un logo puede ser cuadrado o un lockup
   horizontal, y encajarlo en un cuadrado lo recorta o lo encoge hasta que no
   se lee. Un `<img>` SÍ se imprime; un fondo, casi nunca — por eso el logo
   es una imagen y no una regla de CSS. */
img.hoja-marca {
    height: 42px; width: auto; max-width: 190px; flex-shrink: 0;
    object-fit: contain; object-position: right center;
}
/* La empresa que compra (la orden de compra, el acta): su logo encima de su
   razón social y su NIT, todo a la derecha, enfrente del folio. */
.hoja-empresa {
    display: flex; flex-direction: column; align-items: flex-end;
    gap: var(--space-050); text-align: right;
}

/* Quién y adónde, en DOS COLUMNAS: una columna sin datos no existe, y lo que
   no existe no deja hueco. Es lo que mata las tres rayas sueltas que salían
   en una venta de mostrador. */
.hoja-quien {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-100) var(--space-500); margin-bottom: var(--space-250);
}
.hoja-parte { min-width: 0; }
/* El rótulo en caja normal y NO en versalitas: cinco rasgos tipográficos
   —caja alta, tamaño chico, peso, espaciado, color apagado— para decir «esto
   es un rótulo» cuando la posición ya lo dice. Con el color y el tamaño
   alcanza. */
.hoja-rotulo {
    margin: 0 0 var(--space-050); font-size: var(--fs-meta); font-weight: 600;
    color: var(--hoja-tinta-2);
}
.hoja-nombre { margin: 0; font-size: var(--fs-section); font-weight: 650; line-height: 1.3; }
.hoja-dato { margin: 1px 0 0; }

/* Lo que la hoja de la orden de compra le dice a quien la abre por el enlace
   del proveedor (`purchasing/order_sheet.html`): el aviso de cancelada, con
   el mismo recuadro de la nota de entrega, y el enlace para bajar el PDF, que
   en papel sobra. */
.hoja-aviso { margin: 0 0 var(--space-250); padding: var(--space-100) var(--space-150); border: 1px solid var(--hoja-linea);
    border-left: 3px solid var(--hoja-tinta); border-radius: 3px; font-weight: 600; }
.hoja-pdf { margin: 0 0 var(--space-200); }
.hoja-pdf a { color: var(--hoja-tinta); font-weight: 600; }

/* La nota de entrega a ancho completo: «portería hasta las 6pm» decide si el
   paquete entra o se devuelve. Recuadrada y no con un fondo de color: los
   navegadores no imprimen fondos por omisión y el aviso se perdería
   justamente en el papel, que es donde hace falta. El borde sí se imprime. */
.hoja-nota {
    margin: 0 0 var(--space-250); padding: var(--space-100) var(--space-150);
    border: 1px solid var(--hoja-linea);
    border-left: 3px solid var(--hoja-tinta);
    border-radius: 3px;
    line-height: 1.4;
}

/* La tabla, con la piel del documento y no la del ERP: la banda gris de
   `.table thead` es de una pantalla con veinte filas, y acá son cuatro
   renglones en papel. `white-space: normal` porque `.table` viene con
   `nowrap`: en pantalla una columna larga se resuelve con scroll horizontal
   y en papel no hay scroll — el nombre del producto se saldría de la hoja. */
/* El envoltorio de la casa (2026-10-31) sin su piel: el papel no tiene
   tarjeta debajo, y en la impresión el `overflow-x` cortaría la tabla. */
.hoja .table-wrap { background: none; border-radius: 0; }
.hoja-tabla thead th {
    background: none; padding: 0 var(--space-100) var(--space-075);
    font-size: var(--fs-meta); font-weight: 600; color: var(--hoja-tinta-2);
    border-bottom: 1px solid var(--hoja-tinta);
    white-space: normal;
}
.hoja-tabla td {
    padding: var(--space-100); white-space: normal; vertical-align: top;
    border-bottom: 1px solid var(--hoja-linea);
    color: var(--hoja-tinta);
}
.hoja-tabla tr:last-child td { border-bottom: 0; }
.hoja-tabla th:first-child, .hoja-tabla td:first-child { padding-left: 0; }
.hoja-tabla th:last-child, .hoja-tabla td:last-child { padding-right: 0; }
.hoja-tabla .num { font-variant-numeric: tabular-nums; }
.hoja-producto { font-weight: 550; }
.hoja-vt { display: block; font-size: var(--fs-meta); color: var(--hoja-tinta-2); }

/* Los montos alineados bajo la columna de totales, que es donde el ojo ya
   está cuando termina de leer la tabla. */
.hoja-montos {
    display: grid; grid-template-columns: auto minmax(110px, auto);
    justify-content: end; align-items: baseline;
    gap: var(--space-050) 0; margin: var(--space-200) 0 0;
}
.hoja-montos dt { color: var(--hoja-tinta-2); }
.hoja-montos dd {
    margin: 0; padding-left: var(--space-300);
    text-align: right; font-variant-numeric: tabular-nums;
}
.hoja-montos .hoja-total {
    margin-top: var(--space-050); padding-top: var(--space-100);
    border-top: 1.5px solid var(--hoja-tinta);
    font-size: var(--fs-stat); font-weight: 700; color: var(--hoja-tinta);
}

/* Las dos firmas del acta, lado a lado: quien entrega y quien recoge. Una
   línea para firmar es un `border-top`, no un campo de formulario — nadie
   firma un `<input>` en la hoja impresa. */
.hoja-firmas {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-100) var(--space-500); margin-top: var(--space-300);
}
.hoja-firmas > div { padding-top: var(--space-300); border-top: 1px solid var(--hoja-linea); }
@media (max-width: 480px) {
    .hoja-firmas { grid-template-columns: 1fr; gap: var(--space-300); }
}

/* El margen del papel. Va en `@page` y no como relleno de la hoja: el relleno
   se repite en CADA página que el documento ocupe y el margen lo pone la
   impresora una vez, que es la diferencia entre un pedido largo bien paginado
   y uno con una franja en blanco a media hoja. */
@page { margin: 16mm; }

@media print {
    /* Apagar el envoltorio por CASCADA y no nombrando cada pieza: la topbar,
       el menú, la banda y los botones se esconden porque no son la hoja, no
       porque estén en una lista que hay que acordarse de actualizar. El día
       que el shell gane una barra más, esta regla ya la apaga. */
    body.app { background: #fff; }
    body.app > * { display: none; }
    body.app > .shell { display: block; background: transparent;
        box-shadow: none; }
    .shell > * { display: none; }
    /* `background: transparent` y no solo `display`: el lienzo del ERP es un
       gris (`--bg`) que en pantalla separa las tarjetas del fondo, y detrás
       de la hoja es una mancha. Casi ningún navegador imprime fondos por
       omisión, pero el que tenga «gráficos de fondo» marcado —que es lo que
       alguien marca para que salga el logo— se lo llevaría entero. */
    .shell > .content { display: block; margin: 0; padding: 0;
        max-width: none; min-height: 0; background: transparent;
        box-shadow: none; border: 0; border-radius: 0; }
    .content > * { display: none; }
    .content > .hoja { display: block; }

    /* En papel la hoja ES la página: ni borde, ni relleno, ni fondo. El
       margen lo pone `@page`. */
    .hoja { max-width: none; padding: 0; border: 0; border-radius: 0;
        background: transparent; }
    /* Que una línea no quede partida entre dos hojas. */
    .hoja-tabla tr { break-inside: avoid; }
    .hoja .table-wrap { overflow: visible; }
    .hoja-montos { break-inside: avoid; }
    .hoja-quien { break-inside: avoid; }
    .hoja-pdf { display: none; }
}

/* ── El campo de horario de la casa (`ui/_schedule_field.html`) ── */
.schedule-field { display: flex; flex-direction: column; }
/* Lo que el script esconde (`hidden`) no puede volver por un `display` de
 * otra regla: `.btn { display: inline-flex }` le ganaba al `[hidden]` del
 * navegador (la trampa de 3151). */
.schedule-field [hidden] { display: none; }
/* «Copiar el lunes a todos» con aire: pegado a la primera fila se leía
 * como parte de ella (Renzo, 2026-10-01). */
.schedule-field__tools { display: flex; justify-content: flex-end; margin-bottom: var(--space-200); }
.schedule-field__row {
    display: grid; align-items: center; gap: var(--space-100) var(--space-150);
    grid-template-columns: 6.5rem auto minmax(0, 1fr);
    padding: var(--space-100) 0; border-top: 1px solid var(--border);
}
.schedule-field__row:first-of-type { border-top: 0; }
.schedule-field__day { font-weight: 600; }
/* «Abre / Cerrado»: dos radios vestidos de pastilla partida. El radio
 * queda en el DOM —es lo que viaja— y se ve su etiqueta. */
.schedule-field__switch {
    display: inline-flex; border: 1px solid var(--border);
    border-radius: 999px; overflow: hidden;
}
.schedule-field__switch label { position: relative; margin: 0; cursor: pointer; }
.schedule-field__switch input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
.schedule-field__switch span {
    display: block; padding: var(--space-050) var(--space-150); font-size: var(--fs-small);
    color: var(--text-light);
}
.schedule-field__switch input:checked + span { background: var(--primary-soft); color: var(--primary-dark); font-weight: 600; }
.schedule-field__switch input:focus-visible + span { outline: 2px solid var(--primary); outline-offset: -2px; }
.schedule-field__ranges { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100) var(--space-150); }
.schedule-field__range { display: inline-flex; align-items: center; gap: var(--space-075); }
.schedule-field__range input { width: 7.5rem; text-align: center; }
.schedule-field__a { color: var(--text-light); font-size: var(--fs-small); }
.schedule-field__note { color: var(--text-light); font-size: var(--fs-small); }
.schedule-field__link {
    border: 0; background: none; padding: 0; cursor: pointer;
    color: var(--primary); font-size: var(--fs-small); font-weight: 600;
}
.schedule-field__drop {
    border: 0; background: none; cursor: pointer; padding: 0 var(--space-050);
    color: var(--text-light); font-size: 1.1rem; line-height: 1;
}
@media (max-width: 640px) {
    .schedule-field__row { grid-template-columns: 1fr auto; }
    .schedule-field__ranges, .schedule-field__note { grid-column: 1 / -1; }
}

/* El pin de la ficha de la ubicación (`ui/_mapa_assets.html`). */
.mapa-campo {
    position: relative; z-index: 0; height: 280px; margin: var(--space-100) 0;
    border: 1px solid var(--border); border-radius: var(--radius);
    overflow: hidden;
}

/* Las fotos de un campo `media` (`ui/_media_list_widget.html`): baldosas
 * de las guardadas y la casilla de la casa para la siguiente. */
.media-list {
    display: grid; gap: var(--space-150);
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    align-items: start;
}
.media-list__tile {
    position: relative; margin: 0; aspect-ratio: 4 / 3; overflow: hidden;
    border: 1px solid var(--border); border-radius: var(--radius-field);
    background: var(--bg);
}
.media-list__tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-list__quitar {
    position: absolute; top: 6px; right: 6px; width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%; background: var(--card-bg); color: var(--text);
    border: 1px solid var(--border); cursor: pointer; font-size: 1rem;
}
.media-list__quitar input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }
/* Marcada para quitar: se apaga, y se va al guardar. */
.media-list__tile:has(.media-list__quitar input:checked) img { opacity: .3; }
.media-list__tile:has(.media-list__quitar input:checked) .media-list__quitar { background: var(--danger-text); color: #fff; border-color: transparent; }
/* La casilla para agregar: la zona punteada y «Elegir de la biblioteca».
 * La ruta tecleable se esconde a la vista —sigue en el DOM y viaja— y el
 * «Quitar» de una casilla vacía no tiene qué quitar. */
/* `input.` y no la clase sola: `.field input[type="text"] { width: 100% }`
 * es más específica, y la casilla escondida medía la tarjeta entera y
 * desbordaba la página en pantallas angostas. */
.media-list__add input.media-field__value {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
}
.media-list__add [data-media-clear] { display: none; }
.media-list__add .media-field__zone {
    min-height: 110px; display: flex; align-items: center; justify-content: center;
}

/* Una ficha larga, con cada sección separada por un divisor (Renzo,
 * 2026-10-01: «para que se entienda mejor cada sección»). Variante de la
 * tarjeta de formulario y no regla de `.form-section`: la usan catorce
 * pantallas, y cada una decide si la quiere. */
.form-card--secciones > .form-section {
    margin-top: var(--space-250); padding-top: var(--space-250);
    border-top: 1px solid var(--border);
}

/* ── Buscadores e IA (`storefront/buscadores.html`) ── */
.buscadores-panel { display: flex; flex-direction: column; gap: var(--space-200); margin-top: var(--space-200); }
/* El Resumen del referente (2026-10-01): el anillo a la izquierda, el
 * avance por área a la derecha, separados por una línea. */
.buscadores-resumen {
    /* `stretch` y no `start`: la línea que separa el anillo de las áreas
     * va a todo el alto de la tarjeta, no hasta donde llega el texto. */
    display: grid; gap: var(--space-250); align-items: stretch;
    grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
}
.buscadores-resumen__total {
    display: flex; flex-direction: column; align-items: center; gap: var(--space-150);
    text-align: center; padding-right: var(--space-250); border-right: 1px solid var(--border);
}
.buscadores-resumen__areas h2 { margin: 0 0 var(--space-150); }
.buscadores-rotulo {
    align-self: flex-start; margin: 0; font-size: var(--fs-small); font-weight: 600;
    letter-spacing: .06em; text-transform: uppercase; color: var(--text-light);
}
.buscadores-areas {
    display: grid; gap: var(--space-150);
    grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
}
.buscadores-area {
    display: flex; flex-direction: column; gap: var(--space-100); padding: var(--space-150) var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-card);
    text-decoration: none; color: inherit; transition: border-color .15s;
}
.buscadores-area:hover { border-color: var(--primary); }
.buscadores-area__cabeza { display: flex; align-items: center; gap: var(--space-100); }
.buscadores-area__label { font-weight: 600; }
.buscadores-area__numero { font-size: 1.5rem; font-weight: 700; line-height: 1.1; }
.buscadores-area__ver { color: var(--primary); font-size: var(--fs-small); font-weight: 600; }
.buscadores-urgente__cabeza { display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-150); }
.buscadores-urgente__cabeza h2 { margin: 0; }
.buscadores-urgente__todas { color: var(--primary); font-size: var(--fs-small); font-weight: 600; white-space: nowrap; }
/* Las pastillas con el mismo margen que el título de la tarjeta y aire
 * arriba: pegadas a la línea de la cabecera se leían como parte de ella. */
.buscadores-chips { display: flex; flex-wrap: wrap; gap: var(--space-100); padding: var(--space-150) 0; }
.buscadores-chip {
    border: 1px solid var(--border); background: var(--card-bg); color: var(--text);
    border-radius: 999px; padding: var(--space-050) var(--space-200); font-size: var(--fs-small); cursor: pointer;
}
.buscadores-chip.is-active { background: var(--primary-soft); color: var(--primary-dark); border-color: transparent; font-weight: 600; }
.buscadores-mejora { display: flex; align-items: center; gap: var(--space-150); }
.buscadores-mejora .cell-sub { display: block; }
.buscadores-urgente td:last-child { white-space: nowrap; }
@media (max-width: 760px) {
    .buscadores-resumen { grid-template-columns: 1fr; }
    .buscadores-resumen__total { padding-right: 0; border-right: 0; padding-bottom: var(--space-200); border-bottom: 1px solid var(--border); }
}
.buscadores-fila {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--space-150); padding: var(--space-100) 0; border-top: 1px solid var(--border);
}
.buscadores-fila:first-of-type,
.card-header + .buscadores-fila { border-top: 0; }
.buscadores-fila .field-hint { display: block; margin: var(--space-025) 0 0; }
.buscadores-llms {
    font-family: var(--font-mono, monospace); font-size: var(--fs-small);
    white-space: pre-wrap; max-height: 420px; overflow: auto;
    background: var(--bg); border: 1px solid var(--border);
    border-radius: var(--radius-field); padding: var(--space-150); margin: 0;
}

/* Una fila informativa de una tabla («Fuera de buscadores»): se lista para
 * que se sepa, pero no es un problema, y no puede pesar como uno. */
.table tr.is-muted td { color: var(--text-light); }

/* ── El anillo de un porcentaje (`ui/_anillo.html`) ── */
.anillo { position: relative; width: 190px; aspect-ratio: 1; margin: 0 auto; }
/* `>`: solo el círculo arranca arriba. El glifo del centro (y el de la
   pastilla) no se gira. `block`: en línea, el svg deja debajo el hueco de
   las letras que bajan, la caja mide 45 de alto con 40 de círculo y el
   centro queda 2,6 px más abajo que el anillo. */
.anillo > svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); }
.anillo circle { fill: none; stroke-width: 10; }
.anillo__fondo { stroke: var(--primary-soft); }
.anillo__lleno { stroke: var(--primary); stroke-linecap: round; transition: stroke-dasharray .4s ease; }
.anillo__centro {
    position: absolute; inset: 0; display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: var(--space-075);
}
.anillo__numero { font-size: 2rem; font-weight: 700; line-height: 1; }

/* ── El globo al pasar el mouse (`ui/_globo.html`) ──
   Encima del ancla, centrado y con piquito. Sale con `:hover` y con el foco:
   en el celular, tocar un ancla con `tabindex` la enfoca, y tocar fuera lo
   quita. Oscuro sobre claro y claro sobre oscuro: `--text` y `--card-bg`
   cambian solos con el tema. */
.con-globo { position: relative; }
.globo {
    position: absolute; bottom: 100%; left: 50%; z-index: 40;
    transform: translate(-50%, -6px);
    display: flex; flex-direction: column; gap: var(--space-025);
    width: max-content; max-width: 240px;
    padding: var(--space-100) var(--space-150);
    border-radius: var(--radius-field); background: var(--text);
    color: var(--card-bg); font-size: var(--fs-small); line-height: 1.35;
    text-align: left; white-space: normal; box-shadow: var(--shadow);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .12s ease, visibility .12s;
}
.globo::after {
    content: ''; position: absolute; top: 100%; left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent; border-top-color: var(--text);
}
.con-globo:hover > .globo,
.con-globo:focus > .globo { opacity: 1; visibility: visible; }
.globo__titulo { font-weight: 700; }
.globo__nota { opacity: .7; }
/* La barrita (`avance`): se llena desde cero cada vez que el globo sale, y
   verde en 100. La pista, un velo claro sobre el fondo oscuro del globo. */
.globo__avance { display: block; width: 160px; margin: var(--space-050) 0 var(--space-025); }
.globo__avance .progreso { height: 6px; background: color-mix(in srgb, var(--card-bg) 22%, transparent); }
.globo__avance--completo .progreso__lleno { background: var(--done); }
@keyframes globo-llenar { from { width: 0; } }
.con-globo:hover > .globo .progreso__lleno,
.con-globo:focus > .globo .progreso__lleno { animation: globo-llenar .6s ease-out .08s both; }

/* En cero no hay trazo: la punta redonda de un trazo de largo 0 se pinta
   igual, como un punto suelto arriba. */
.anillo--vacio .anillo__lleno { stroke-linecap: butt; }

/* ── La barra de avance (`ui/_progreso.html`) ── */
.progreso {
    height: 8px; border-radius: 999px; background: var(--border);
    overflow: hidden;
}
.progreso__lleno {
    display: block; height: 100%; border-radius: inherit;
    background: var(--primary); transition: width .4s ease;
}
/* La pista PARTIDA (`tramos`, 2026-10-09): las partes de un todo una al lado
   de la otra, con un filo de pista entre ellas. Solo la pista redondea sus
   puntas; un tramo redondo por dentro se leía como pastillas sueltas. Si los
   tramos suman más de 100 se encogen en proporción (`flex-shrink`), y si
   suman menos queda pista a la vista: es lo que falta.
   El tono de cada tramo —y el de su punto en la leyenda, `.series-dot-tramo`
   con el mismo `--tramo`— es la paleta de series de la casa: `--primary` que
   se aclara 16 % por tramo hasta un piso de 22 %, la misma regla que
   `analiticas.js` aplica a los gajos del anillo. Un solo sitio para la barra
   y el punto: así no se pueden separar. */
.progreso--tramos { display: flex; gap: var(--space-025); }
.progreso--tramos .progreso__lleno { border-radius: 0; }
.progreso--tramos .progreso__lleno,
.series-dot-tramo { opacity: max(.22, calc(1 - var(--tramo, 0) * .16)); }
/* Sin total (`indeterminado`, 2026-10-17): una franja que recorre la pista.
   Con movimiento reducido se queda quieta y tenue: la pastilla de estado ya
   dice «Trayendo». */
.progreso--indeterminado { position: relative; }
.progreso--indeterminado .progreso__lleno {
    position: absolute; left: 0; width: 40%;
    animation: progreso-recorre 1.4s ease-in-out infinite;
}
@keyframes progreso-recorre {
    0% { transform: translateX(-100%); }
    100% { transform: translateX(250%); }
}
@media (prefers-reduced-motion: reduce) {
    .progreso--indeterminado .progreso__lleno {
        animation: none; width: 100%; opacity: .35;
    }
}

/* La barra de «abriendo» de `cambiar-sin-recargar.js` (2026-10-22): la
   pista de arriba de la pantalla mientras llega lo que se tocó. La raíz
   lleva `data-cambiando` solo si tarda; antes no se ve nada. */
:has(> .cambio-cargando) { position: relative; }
.cambio-cargando {
    position: absolute; inset: 0 0 auto; z-index: 5; pointer-events: none;
    opacity: 0; visibility: hidden; transition: opacity .15s ease;
}
.cambio-cargando .progreso { height: 3px; border-radius: 0; background: transparent; }
[data-cambiando] > .cambio-cargando { opacity: 1; visibility: visible; }
[data-cambiando] { cursor: progress; }
[data-cambiando-pieza] { transition: opacity .2s ease; }
[data-cambiando] [data-cambiando-pieza] { opacity: .55; }
/* La del selector de período (`ui/_period.html`, 2026-10-29): la misma
   pista, al PIE de los dos disparadores y no arriba de la pantalla, porque
   es donde estaba el dedo. */
.cambio-cargando.period-cargando { inset: auto 8px -5px; }
.period-cargando .progreso { border-radius: 999px; }

/* ── La fila de avance (`ui/_fila_avance.html`, 2026-10-17) ──
   Nombre, conteo y estado en una línea; la barra debajo; las acciones y los
   avisos plegados al pie. Es la fila del tablero de Importar de Shopify y
   de cualquier carga que avance por partes. */
.fila-avance {
    display: grid; gap: var(--space-100); padding: var(--space-150) var(--space-200);
    background: var(--card-bg, #fff); border: 1px solid var(--border);
    border-radius: var(--radius, 12px);
}
.fila-avance__cabeza {
    display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap;
}
.fila-avance__nombre { font-weight: 600; }
.fila-avance__conteo {
    margin-left: auto; color: var(--text-light); font-variant-numeric: tabular-nums;
}
.fila-avance__acciones { display: flex; gap: var(--space-100); flex-wrap: wrap; }
.fila-avance__avisos { margin-top: var(--space-025); }
.fila-avance__lista { margin: 0; padding-left: var(--space-200); display: grid; gap: var(--space-050); }
.fila-avance__lista code { font-size: .85em; }
.fila-avance__nota { color: var(--text-light); }

/* ── La barra de un asistente por pasos (`ui/_pasos_miga.html`) ──
   Nació como la miga del asistente de Correo; en la casa desde el
   2026-10-18. Los pasos en fila, unidos por una raya; el activo pesa, los
   hechos llevan ✓ y son enlace si se puede volver a ellos. */
.pasos-miga { display: flex; align-items: center; gap: var(--space-100); list-style: none;
              margin: 0 0 var(--space-150); padding: 0; flex-wrap: wrap; }
.pasos-miga li { display: flex; align-items: center; gap: var(--space-075);
                 font-size: var(--fs-small); color: var(--text-light); }
.pasos-miga li + li::before { content: ''; width: 24px; height: 1px; background: var(--border); margin-right: var(--space-050); }
.pasos-miga a { display: inline-flex; align-items: center; gap: var(--space-075); color: inherit; text-decoration: none; }
.pasos-miga li.is-hecho { cursor: pointer; color: var(--primary-dark); }
.pasos-miga li.is-activo { color: var(--text); font-weight: 600; }
.pasos-miga__num { display: inline-flex; align-items: center; justify-content: center;
                   width: 22px; height: 22px; border-radius: 50%;
                   border: 1px solid var(--border); font-size: var(--fs-small); }
.pasos-miga__rotulo { white-space: nowrap; }
.pasos-miga li.is-hecho .pasos-miga__num {
  background: var(--primary-soft); border-color: var(--primary-soft-border); color: var(--primary-dark); }
.pasos-miga li.is-activo .pasos-miga__num { background: var(--primary); border-color: var(--primary); color: #fff; }
/* La línea de estados (`linea=1`): el recorrido de una cosa, no un
   asistente. Cada paso es una columna con su círculo arriba y el rótulo y la
   fecha debajo; la raya va de círculo a círculo y se tiñe hasta el último
   hecho. Lo pendiente es un círculo vacío. Nació en el riel de la orden de
   compra (2026-11-02). */
.pasos-miga--linea {
  --paso-circulo: 22px;
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  gap: 0; align-items: start; margin: 0;
}
.pasos-miga--linea li {
  position: relative; flex-direction: column; gap: var(--space-075);
  text-align: center; cursor: default;
}
.pasos-miga--linea li + li::before {
  position: absolute; top: calc(var(--paso-circulo) / 2 - 1px);
  left: calc(-50% + var(--paso-circulo) / 2 + var(--space-050));
  right: calc(50% + var(--paso-circulo) / 2 + var(--space-050));
  width: auto; height: 2px; margin: 0; border-radius: 1px;
}
.pasos-miga--linea li.is-hecho::before,
.pasos-miga--linea li.is-activo::before { background: var(--primary); }
.pasos-miga--linea .pasos-miga__num { width: var(--paso-circulo); height: var(--paso-circulo);
  border-width: 2px; background: var(--card-bg); }
.pasos-miga--linea li.is-hecho { color: var(--text); }
.pasos-miga--linea li.is-hecho .pasos-miga__num { background: var(--primary); border-color: var(--primary); color: #fff; }
.pasos-miga--linea li.is-activo .pasos-miga__num { background: var(--card-bg); border-color: var(--primary); color: var(--primary); }
.pasos-miga--linea li { font-size: var(--fs-meta); }
.pasos-miga--linea .pasos-miga__rotulo { white-space: normal; line-height: 1.25; }
/* En la línea el hecho NO pesa más: «Confirmada» en negrita no cabe en un
   cuarto de riel. Se distingue por el círculo lleno. */
.pasos-miga--linea li.is-hecho, .pasos-miga--linea li.is-activo { font-weight: 500; }
.pasos-miga__fecha, .pasos-miga__nota { display: block; margin-top: var(--space-025); font-size: var(--fs-meta);
  font-weight: 400; color: var(--text-light); }
/* El paso SALTADO (2026-11-05, la orden que se mandó por fuera): no pasó por
   aquí ni falta. Círculo punteado y gris, con su nota («No se envió por
   Agrupa»). */
.pasos-miga--linea li.is-saltado { color: var(--text-faint); }
.pasos-miga--linea li.is-saltado .pasos-miga__num { border-style: dashed; color: var(--text-faint); }
/* En columna (`columna=1`): un renglón por paso, la fecha a la derecha y la
   raya bajando entre los círculos. Para el riel, donde la fila no cabe. */
.pasos-miga--columna { grid-auto-flow: row; grid-auto-columns: auto; gap: var(--space-150); }
.pasos-miga--columna li { flex-direction: row; align-items: center; gap: var(--space-100);
  text-align: left; font-size: var(--fs-small); }
.pasos-miga--columna li + li::before {
  top: auto; bottom: calc(100% + var(--space-025)); left: calc(var(--paso-circulo) / 2 - 1px); right: auto;
  width: 2px; height: calc(var(--space-150) - var(--space-050));
}
.pasos-miga--columna .pasos-miga__rotulo { display: flex; flex: 1 1 auto; align-items: baseline;
  justify-content: space-between; gap: var(--space-100); }
.pasos-miga--columna .pasos-miga__fecha, .pasos-miga--columna .pasos-miga__nota { margin-top: 0; }

/* ── La pantalla de Shopify (`apps/shopify_import`, 2026-10-17) ──
   El paso a paso de la app personalizada, el tablero y el error de una fila.
   Lo demás son piezas de la casa. */
/* Escribir un nombre para confirmar (`ui/_confirmar_escribiendo.html`). */
.confirmar-escribiendo { margin-top: var(--space-150); max-width: 420px; }
.faltan { display: flex; align-items: center; justify-content: space-between; gap: var(--space-150); flex-wrap: wrap; padding: var(--space-150) var(--space-200); border: 1px solid var(--border); border-radius: var(--radius); background: var(--card-bg); }
.faltan__texto { margin: 0; flex: 1 1 280px; }
.importar-a-donde { margin-top: var(--space-200); padding: var(--space-200); border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.importar-a-donde__frase { margin: 0 0 var(--space-050); font-size: var(--fs-body); }
.asistente-shopify__pie { display: flex; gap: var(--space-100); justify-content: flex-end; margin-top: var(--space-200); }
.paso-a-paso { margin: 0; padding-left: var(--space-300); display: grid; gap: var(--space-100); }
.paso-a-paso__alcances { margin: var(--space-075) 0 0; padding-left: var(--space-200); display: grid; gap: var(--space-050); }
.paso-a-paso__alcances code { font-size: .85em; }
.tablero-importacion { display: grid; gap: var(--space-100); margin-bottom: var(--space-200); }
.tablero-importacion__error {
    margin: calc(-1 * var(--space-075)) 0 0; padding: 0 var(--space-200); color: var(--danger, #b42318); font-size: .92em;
}
.form-inline { display: flex; gap: var(--space-100); align-items: center; margin: var(--space-100) 0; }

/* ── Traer de otro sistema (2026-10-18) ────────────────────────────────────
   El panel de `apps/traer`: los tres orígenes, la fecha de corte, un renglón
   por cosa que se trae con sus problemas (rojo frena, ámbar avisa) y las
   decisiones de bodegas e impuestos. El tablero de la corrida es el de
   arriba (`.tablero-importacion`). */
.traer-conexion-estado { display: flex; gap: var(--space-100); align-items: center; margin: var(--space-150) 0; }
.traer-corte, .traer-decisiones { display: flex; flex-wrap: wrap; gap: var(--space-100) var(--space-200); align-items: end; }
.traer-decisiones label, .traer-dudas label { display: grid; gap: var(--space-050); min-width: 220px; }
.traer-renglon__cabeza { display: flex; justify-content: space-between; gap: var(--space-150); align-items: start; }
.traer-renglon__cabeza p { margin: var(--space-025) 0 0; }
.traer-renglon__acciones { display: flex; flex-wrap: wrap; gap: var(--space-100); align-items: center; margin-top: var(--space-100); }
.traer-subir { display: flex; flex-wrap: wrap; gap: var(--space-100); align-items: center; }
.traer-dudas { display: flex; flex-wrap: wrap; gap: var(--space-100) var(--space-200); align-items: end; margin-top: var(--space-100); }
.traer-problema { margin: var(--space-075) 0 0; padding: var(--space-075) var(--space-100); border-radius: 8px; font-size: .92em; }
.traer-problema--frena { background: var(--danger-bg); color: var(--danger-text); }
.traer-problema--avisa { background: var(--warn-bg); color: var(--warn-text); }

/* ── El gorrito de cumpleaños del avatar (Renzo, 2026-10-09) ─────────────
   Lo pone `ui/_avatar.html` solo a quien cumple años hoy. El gorrito va
   ladeado arriba a la IZQUIERDA, hacia afuera: a la derecha quedaba entre la
   cara y el nombre (Renzo: «del otro lado»). Al pasar el mouse —por la cara
   o por TODA la fila que la lleva: el botón, el enlace o el renglón con el
   nombre— se endereza con un saltico y salen ocho chispas de confetti en
   los colores de Agrupa. La envoltura no cambia el tamaño de la cara.
   Todo va en proporción a la cara: el gorrito y su sitio, en %; las chispas,
   con un paso que es 1px hasta caras de 48 y crece de ahí en adelante. Con el
   gorrito topado en 30px, en la ficha del chat (76px) quedaba chico y flotando
   afuera del círculo (Renzo, 2026-10-10: «cuadrar el gorrito»). Si la cara va
   en una grilla, la envoltura tiene que ocupar la celda de la cara: estirada,
   el gorrito crece con ella. */
.avatar-cumple { position: relative; display: inline-flex; flex: none; vertical-align: middle; }
.avatar-gorrito {
    position: absolute; top: -50%; left: 10%;
    width: 62%; height: auto; min-width: 12px;
    transform: rotate(-18deg); transform-origin: 50% 100%;
    transition: transform .3s cubic-bezier(.3, 1.6, .5, 1);
    pointer-events: none;
    filter: drop-shadow(0 1px 1px rgba(40, 30, 80, .18));
}
.avatar-cumple:hover .avatar-gorrito,
:is(a, button, label, li, tr):hover .avatar-cumple .avatar-gorrito { transform: rotate(-6deg) translateY(-2px); }
.avatar-chispa {
    position: absolute; left: 50%; top: 45%;
    width: 5px; height: 5px; border-radius: 1px;
    --paso: max(1px, 100% / 48);
    opacity: 0; pointer-events: none;
    background: var(--chispa, #ffd166);
}
.avatar-chispa:nth-of-type(1) { --chispa: #ffd166; --x: -30; --y: -26; --r: 200deg; }
.avatar-chispa:nth-of-type(2) { --chispa: #ff8fab; --x: 26; --y: -30; --r: -160deg; }
.avatar-chispa:nth-of-type(3) { --chispa: #7c5cf0; --x: 32; --y: 6; --r: 240deg; border-radius: 50%; }
.avatar-chispa:nth-of-type(4) { --chispa: #5dcaa5; --x: -32; --y: 8; --r: -220deg; }
.avatar-chispa:nth-of-type(5) { --chispa: #ffd166; --x: -10; --y: -38; --r: 300deg; border-radius: 50%; }
.avatar-chispa:nth-of-type(6) { --chispa: #85b7eb; --x: 12; --y: -36; --r: -260deg; }
.avatar-chispa:nth-of-type(7) { --chispa: #ff8fab; --x: -22; --y: 26; --r: 180deg; border-radius: 50%; }
.avatar-chispa:nth-of-type(8) { --chispa: #5dcaa5; --x: 20; --y: 28; --r: -200deg; }
.avatar-cumple:hover .avatar-chispa,
:is(a, button, label, li, tr):hover .avatar-cumple .avatar-chispa { animation: avatar-chispa .9s ease-out forwards; }
.avatar-cumple:hover .avatar-chispa:nth-of-type(even),
:is(a, button, label, li, tr):hover .avatar-cumple .avatar-chispa:nth-of-type(even) { animation-delay: .04s; }
@keyframes avatar-chispa {
    0%   { opacity: 1; transform: translate(-50%, -50%) rotate(0) scale(.6); }
    100% { opacity: 0; left: calc(50% + var(--x) * var(--paso)); top: calc(45% + var(--y) * var(--paso));
           transform: translate(-50%, -50%) rotate(var(--r)) scale(1); }
}
/* La banda de la página es una píldora con `overflow: hidden`, y en la ficha
   del integrante el gorrito se cortaba (Renzo, 2026-10-09). Solo cuando
   adentro hay un cumpleañero la banda deja asomar lo de afuera: el fondo y el
   esmerilado siguen la forma redonda con o sin recorte. */
.page-banner:has(.avatar-cumple) { overflow: visible; }
/* Quien pidió menos movimiento conserva el gorrito y se queda sin confetti. */
@media (prefers-reduced-motion: reduce) {
    .avatar-gorrito { transition: none; }
    .avatar-cumple:hover .avatar-gorrito,
    :is(a, button, label, li, tr):hover .avatar-cumple .avatar-gorrito { transform: rotate(-18deg); }
    .avatar-cumple:hover .avatar-chispa,
    :is(a, button, label, li, tr):hover .avatar-cumple .avatar-chispa { animation: none; }
}

/* ── La portada del día del cumpleaños (Renzo, 2026-10-09) ───────────────
   La torta entre el saludo y el buscador (`templates/home_cumple.html`):
   las llamas titilan con su halo; al soplar pasa el viento, las llamas se
   van y sale el humito. El confetti lo suelta `cumple.js` sobre toda la
   pantalla, una vez al día. Con «reducir movimiento», todo quieto. */
.cumple { position: relative; z-index: 1; display: grid; justify-items: center; gap: var(--space-075); margin: var(--space-050) auto var(--space-300); }
.cumple[hidden] { display: none; }
.cumple-escena { position: relative; width: min(270px, 70vw); aspect-ratio: 34 / 27; }
.cumple-torta { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; filter: drop-shadow(0 10px 18px rgba(60, 40, 140, .16)); visibility: hidden; }
/* El dibujo es el respaldo: se ve solo cuando `cumple.js` decide que no hay
   3D (`cumple--2d`). Mientras tanto la escena guarda su lugar vacía. */
.cumple--2d .cumple-torta { visibility: visible; }
/* Con 3D (`cumple-torta3d.js`) el dibujo se esconde entero —el 3D trae su
   propia ráfaga de aire—; el lienzo se arrastra para girar la torta. */
.cumple-lienzo { position: absolute; inset: 0; width: 100%; height: 100%; cursor: grab; touch-action: pan-y; animation: cumple-entra .6s ease-out; }
@keyframes cumple-entra { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.cumple-lienzo:active { cursor: grabbing; }
.cumple--3d .cumple-torta { display: none; }
.cumple-llama { transform-box: fill-box; transform-origin: 50% 100%; animation: cumple-titila .45s ease-in-out infinite alternate; transition: transform .45s ease-in, opacity .45s ease-in; }
.cumple-velas g:nth-child(2) .cumple-llama { animation-delay: .15s; }
.cumple-velas g:nth-child(3) .cumple-llama { animation-delay: .3s; }
@keyframes cumple-titila { from { transform: scale(1, 1) rotate(-4deg); } to { transform: scale(.88, 1.12) rotate(4deg); } }
.cumple-halo { transform-box: fill-box; transform-origin: center; animation: cumple-halo 1.2s ease-in-out infinite alternate; transition: opacity .4s; }
@keyframes cumple-halo { from { opacity: .55; transform: scale(.9); } to { opacity: 1; transform: scale(1.1); } }
.cumple-humo, .cumple-viento { opacity: 0; }
.cumple--soplando .cumple-viento { animation: cumple-viento .8s ease-out forwards; }
@keyframes cumple-viento { 0% { opacity: 0; transform: translateX(-70px); } 35% { opacity: .9; } 100% { opacity: 0; transform: translateX(90px); } }
.cumple--apagada .cumple-llama { animation: none; transform: scale(.1) translateX(26px); opacity: 0; }
.cumple--apagada .cumple-halo { animation: none; opacity: 0; }
.cumple--apagada .cumple-humo { animation: cumple-humo 1.8s ease-out .3s forwards; }
@keyframes cumple-humo { 0% { opacity: .75; transform: translateY(0); } 100% { opacity: 0; transform: translate(10px, -40px); } }
.cumple-deseo { margin: 0; min-height: 1.3em; color: var(--text-light); font-size: var(--fs-body); }
/* Por el atributo y no por clase: el botón sale de `ui/_btn.html`. */
[data-cumple-soplar] { border-radius: 999px; padding-inline: var(--space-250); }
[data-cumple-soplar]:disabled { opacity: 1; cursor: default; background: var(--primary-soft); border-color: var(--primary-soft); color: var(--primary); }
/* Soplada, la torta se va (Renzo, 2026-10-10): se desvanece y su hueco se
   cierra; `cumple.js` le pone el alto de arranque y después la esconde. */
.cumple--fin { overflow: hidden; opacity: 0; margin-block: 0; transform: scale(.96); transition: opacity .45s ease, transform .45s ease, max-height .6s ease .35s, margin .6s ease .35s; }
/* El confeti de la casa (`assets/js/confeti.js`): el cumpleaños y la sala
   de espera del alta. */
.confeti { position: fixed; inset: 0; z-index: 9999; overflow: hidden; pointer-events: none; }
.confeti i { position: absolute; top: -16px; width: 8px; height: 10px; border-radius: 2px; opacity: .95; animation: confeti-cae 3.5s cubic-bezier(.25, .6, .4, 1) forwards; }
@keyframes confeti-cae { to { transform: translate(var(--deriva, 0), 105vh) rotate(var(--giro, 720deg)); opacity: .8; } }
@media (prefers-reduced-motion: reduce) {
    .cumple-llama, .cumple-halo { animation: none; }
    .cumple--soplando .cumple-viento, .cumple--apagada .cumple-humo { animation: none; }
    .cumple--apagada .cumple-llama { transition: none; }
    .cumple-lienzo { animation: none; }
}

/* ══ El carrusel de TARJETAS de la casa (`carrusel.js`, receta `tarjetas`) ══
   Nació en la fila del inicio del chat (2026-10-09) y subió acá el
   2026-10-10, cuando el perfil con personalidad pidió el mismo para
   películas y libros. `.carrusel-tarjetas` va en el `.splide`;
   `.carrusel-flechas` en el `.splide__arrows` que la pantalla pone donde
   quiera (Splide usa ese en vez de crear el suyo). Botones redondos de
   vidrio claro: toman los tokens `--vidrio-*` si la pantalla los trae. */
.carrusel-flechas.splide__arrows { display: flex; gap: var(--space-100); }
.carrusel-flechas .splide__arrow {
    position: static; transform: none; width: 32px; height: 32px; padding: 0;
    border-radius: 50%; display: grid; place-items: center; opacity: 1; cursor: pointer;
    border: 1px solid var(--vidrio-borde, var(--border)); background: var(--card-bg); color: var(--text);
    box-shadow: inset 0 1px 0 var(--vidrio-luz, transparent), 0 6px 14px -8px var(--vidrio-sombra, rgba(0, 0, 0, .18));
    transition: box-shadow .15s ease;
}
.carrusel-flechas .splide__arrow svg { width: 11px; height: 11px; fill: currentColor; }
.carrusel-flechas .splide__arrow--prev svg { transform: scaleX(-1); }
.carrusel-flechas .splide__arrow:disabled { opacity: .45; cursor: default; }
.carrusel-flechas .splide__arrow:hover:not(:disabled) { box-shadow: inset 0 1px 0 var(--vidrio-luz, transparent), 0 8px 18px -8px var(--vidrio-sombra, rgba(0, 0, 0, .22)); }
/* El riel deja aire abajo y a los lados para la sombra de las tarjetas. */
.carrusel-tarjetas .splide__track { overflow: hidden; padding: var(--space-025) var(--space-025) var(--space-150); margin: calc(-1 * var(--space-025)) calc(-1 * var(--space-025)) calc(-1 * var(--space-150)); }
.carrusel-tarjetas:not(.is-initialized) .splide__list { display: flex; gap: var(--space-100); overflow-x: auto; }
.carrusel-tarjetas .splide__pagination { position: static; margin-top: var(--space-200); gap: var(--space-075); }
.carrusel-tarjetas .splide__pagination__page { width: 7px; height: 7px; border-radius: 4px; background: color-mix(in srgb, var(--primary) 20%, transparent); border: 0; padding: 0; transition: width .2s ease, background .2s ease; }
.carrusel-tarjetas .splide__pagination__page.is-active { width: 20px; background: var(--primary); transform: none; }
/* Si caben todas, ni flechas ni puntos: no hay a dónde ir. */
.carrusel-tarjetas:not(.is-overflow) .splide__arrows,
.carrusel-tarjetas:not(.is-overflow) .splide__pagination { visibility: hidden; }

/* ══ El perfil con personalidad (Integrantes, spec 2026-10-09) ══════════════
   El editor vive en la página personal; el perfil de otro, en el cajón que
   se abre desde el chat. Todo con los tokens de la casa: claro y oscuro
   salen solos. */

/* ── el editor ── */
.perfil-editor .tabs { margin-bottom: var(--space-200); }
.perfil-panel { display: grid; gap: var(--space-250); }
.perfil-bloque { display: grid; gap: var(--space-150); }
.perfil-bloque + .perfil-bloque { border-top: 1px solid var(--border); padding-top: var(--space-250); }
.perfil-bloque__cabeza { display: flex; align-items: center; justify-content: space-between; gap: var(--space-150); }
.perfil-bloque__cabeza h3 { margin: 0; font-size: var(--fs-section); font-weight: 600; }
.perfil-audiencia select { min-width: 9.5rem; }
.perfil-error { margin: 0; color: var(--danger-vivid); font-size: var(--fs-small); }
.perfil-vacio { list-style: none; color: var(--text-light); font-size: var(--fs-small); padding: var(--space-100) 0; }
.perfil-opcional { color: var(--text-faint); font-weight: 400; }
.perfil-quitar { flex: none; }
.perfil-cara .perfil-quitar { width: 24px; height: 24px; min-height: 0; }
.perfil-buscar { max-width: 32rem; }
.perfil-buscar input[type="search"] { width: 100%; }
.perfil-lleno { margin: var(--space-050) 0 0; }

/* Los amigos: caras con su ✕. */
.perfil-caras { display: flex; flex-wrap: wrap; gap: var(--space-100); margin: 0; padding: 0; list-style: none; }
.perfil-cara { display: inline-flex; align-items: center; gap: var(--space-100); padding: var(--space-050) var(--space-150) var(--space-050) var(--space-050);
    border: 1px solid var(--border); border-radius: 999px; background: var(--card-bg); }
.perfil-cara .avatar { width: 26px; height: 26px; font-size: var(--fs-meta); }
.perfil-cara__nombre { font-size: var(--fs-small); }

/* Las redes: un renglón por red. */
.perfil-redes { display: grid; gap: var(--space-100); margin: 0; padding: 0; list-style: none; }
.perfil-red { display: grid; grid-template-columns: minmax(8rem, 10rem) 1fr auto auto; gap: var(--space-100); align-items: center; }
@media (max-width: 640px) {
    .perfil-red { grid-template-columns: 1fr auto; }
}

/* Los favoritos: hasta cinco filas con carátula; la primera, destacada. */
.perfil-favoritos { display: grid; gap: var(--space-100); margin: 0; padding: 0; list-style: none; counter-reset: favorito; }
.perfil-favorito { display: flex; align-items: center; gap: var(--space-150); padding: var(--space-100); border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--card-bg); cursor: grab; }
.perfil-favorito:first-child { border-color: var(--primary-soft-border); background: var(--primary-soft); }
.perfil-favorito__agarre { display: inline-flex; color: var(--text-faint); }
.perfil-favorito__agarre svg { width: 16px; height: 16px; }
.perfil-favorito__caratula { width: 48px; height: 48px; flex: none; border-radius: var(--radius-sm); object-fit: cover; background: var(--bg); }
.perfil-favoritos--pelicula .perfil-favorito__caratula,
.perfil-favoritos--libro .perfil-favorito__caratula { width: 36px; height: 54px; }
.perfil-favoritos--cancion .perfil-favorito__caratula { border-radius: var(--radius-sm); }
.perfil-favorito__caratula--sin { display: inline-grid; place-items: center; color: var(--text-light); font-weight: 600; }
.perfil-favorito__texto { display: grid; min-width: 0; flex: 1; }
.perfil-favorito__titulo { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perfil-favorito__sub { color: var(--text-light); font-size: var(--fs-small); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perfil-favorito__likes { font-size: var(--fs-small); color: var(--text-light); white-space: nowrap; }

/* El muro en el editor. */
.perfil-muro { gap: var(--space-200); }
.perfil-publicar { display: grid; gap: var(--space-150); }
.perfil-publicar textarea { min-height: 5.5rem; }
.perfil-muro__titulo { margin: var(--space-150) 0 0; font-size: var(--fs-section); font-weight: 600; }

/* ── una publicación (editor y cajón) ── */
.perfil-publicaciones { display: grid; gap: var(--space-150); margin: 0; padding: 0; list-style: none; }
.perfil-publicacion { display: grid; gap: var(--space-100); padding: var(--space-150) var(--space-200); border: 1px solid var(--border);
    border-radius: var(--radius-card); background: var(--card-bg); }
.perfil-publicacion__cabeza { display: flex; align-items: center; gap: var(--space-100); color: var(--text-light); font-size: var(--fs-small); }
.perfil-publicacion__cara { width: 32px; height: 32px; flex: none; border-radius: 50%; object-fit: cover; display: inline-grid; place-items: center;
    background: var(--primary-soft); color: var(--primary-dark); font-size: var(--fs-small); font-weight: 600; }
.perfil-publicacion__autor { color: var(--text); font-size: var(--fs-body); }
.perfil-publicacion__cabeza .menu { margin-left: auto; }
.perfil-publicacion__audiencia { padding: 0 var(--space-100); border-radius: 999px; background: var(--bg); font-size: var(--fs-meta); }
.perfil-publicacion__texto { margin: 0; white-space: normal; overflow-wrap: anywhere; }
.perfil-publicacion__enlace { font-size: var(--fs-small); overflow-wrap: anywhere; }
.perfil-publicacion__pie { display: flex; align-items: center; }
.perfil-fotos { display: grid; gap: var(--space-050); border-radius: var(--radius); overflow: hidden; }
.perfil-fotos img { display: block; width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3; }
/* UNA sola foto se ve ENTERA, en su proporción y nunca más grande que ella
   misma: con `width: 100%` y `cover` una foto en una tarjeta ancha se
   recortaba y se ampliaba hasta verse borrosa (Renzo, 2026-10-10). Las de
   dos a cuatro sí se recortan: son una grilla. */
.perfil-fotos--1 { display: flex; justify-content: center; background: var(--bg); }
.perfil-fotos--1 a { display: block; max-width: 100%; }
.perfil-fotos--1 img { width: auto; height: auto; max-width: 100%; max-height: 28rem; aspect-ratio: auto; object-fit: contain; }
.perfil-fotos--2, .perfil-fotos--4 { grid-template-columns: 1fr 1fr; }
.perfil-fotos--3 { grid-template-columns: 2fr 1fr; }
.perfil-fotos--3 a:first-child { grid-row: span 2; }
.perfil-mas { list-style: none; text-align: center; }

/* ── el ❤️ ── */
.perfil-like { display: inline-flex; align-items: center; gap: var(--space-050); color: var(--text-light); font-size: var(--fs-small); }
.perfil-like__boton { display: inline-grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 50%;
    background: transparent; color: inherit; cursor: pointer; transition: transform .12s ease, color .12s ease; }
.perfil-like__boton svg { width: 18px; height: 18px; }
.perfil-like__boton:hover { color: #e0245e; background: var(--bg); }
.perfil-like__boton:active { transform: scale(.88); }
.perfil-like.is-mio { color: #e0245e; }
.perfil-like__solo { font-size: var(--fs-small); }
.perfil-like__quienes { position: relative; }
.perfil-like__quienes summary { cursor: pointer; list-style: none; font-weight: 600; }
.perfil-like__quienes summary::-webkit-details-marker { display: none; }
.perfil-like__lista { position: absolute; z-index: 5; left: 0; top: calc(100% + 4px); min-width: 12rem; max-height: 16rem; overflow: auto;
    padding: var(--space-100); border: 1px solid var(--border); border-radius: var(--radius); background: var(--card-bg); box-shadow: var(--shadow-lg); }
.perfil-quienes { display: grid; gap: var(--space-100); margin: 0; padding: 0; list-style: none; color: var(--text); }
.perfil-quienes li { display: flex; align-items: center; gap: var(--space-100); }
.perfil-quienes__cara { width: 24px; height: 24px; font-size: var(--fs-meta); }
@media (prefers-reduced-motion: reduce) {
    .perfil-like__boton { transition: none; }
    .perfil-like__boton:active { transform: none; }
}

/* ── el cajón (2026-10-10, sobre el referente de Renzo) ──
   SIN `backdrop-filter` (Renzo, 2026-10-10): los orbes se mueven sin parar
   y cada esmerilado encima se recalculaba en cada cuadro —nueve, uno con la
   refracción SVG en la portada pegada—; al desplazar, Chrome no alcanzaba y
   el cajón parpadeaba. Los orbes ya vienen borrosos: el vidrio son fondos
   translúcidos, cantos de luz y sombras. Y los orbes, QUIETOS: detrás de
   cajas que se desplazan no se les notaba la deriva y costaba cada cuadro.
   El mismo vidrio que la fila del inicio del chat: los tokens `--vidrio-*`
   llegan con `.equipo-vidrio` en el artículo, los orbes de la casa van
   detrás y cada parte es un panel de vidrio con tarjetas blancas encima. La
   portada tiñe su cielo con el color de la persona. */
.perfil-cajon .drawer-head {
    /* DENTRO de la portada (Renzo, 2026-10-10): a la altura de los «⋯» —el
       relleno del cuerpo (16) más el de la portada (16)— y 16px adentro de
       su borde derecho. */
    position: absolute; top: calc(2 * var(--space-200)); right: calc(2 * var(--space-200)); z-index: 4;
    padding: 0; background: transparent; border: 0; border-radius: 0;
}
.perfil-cajon .drawer-head-text {
    position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap;
}
.perfil-cajon .drawer-head .icon-btn-dark {
    width: 32px; height: 32px; border-radius: 50%;
    background: color-mix(in srgb, var(--card-bg) 85%, transparent); color: var(--text);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .9), 0 6px 14px -8px rgba(40, 30, 90, .3);
}
.perfil-cajon .drawer-head .icon-btn-dark:hover { background: var(--card-bg); color: var(--text); }
.perfil-cajon.drawer.drawer-ancho { width: min(940px, 100%); }
.perfil-cajon .drawer-body {
    position: relative; padding: var(--space-200) var(--space-200) var(--space-400); background: color-mix(in srgb, var(--primary-soft) 30%, var(--bg));
    /* Sin barra de scroll a la vista (Renzo, 2026-10-10): se sigue
       desplazando con la rueda, el trackpad, el dedo y el teclado. */
    scrollbar-width: none;
}
.perfil-cajon .drawer-body::-webkit-scrollbar { display: none; }

.perfil-vista { --perfil-color: var(--primary); position: relative; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-200); }
/* Sin esto un carrusel con más tarjetas que ancho estiraba la grilla —el
   mínimo automático de un hijo de grid es su contenido— y todo el cajón se
   salía por la derecha. */
.perfil-caja, .perfil-carrusel { min-width: 0; }
.perfil-vista .orb { animation: none; }

/* Los paneles: vidrio tenue con su filo de luz, como `.inicio` del chat. */
.perfil-caja, .perfil-heroe { position: relative; border-radius: 24px; border: 1px solid var(--vidrio-borde); }
.perfil-caja {
    box-shadow: inset 0 1.5px 0 var(--vidrio-luz), 0 28px 50px -34px var(--vidrio-sombra);
    display: grid; gap: var(--space-200); padding: var(--space-200) var(--space-250) var(--space-250);
    background:
        radial-gradient(70% 120% at 0% 0%, color-mix(in srgb, var(--primary-soft) var(--vidrio-brillo), transparent) 0%, transparent 70%),
        var(--vidrio-caja);
}
.perfil-caja__cabeza { display: flex; align-items: center; justify-content: space-between; gap: var(--space-150); }
.perfil-seccion__titulo { display: flex; align-items: center; gap: var(--space-100); margin: 0; font-size: var(--fs-section); font-weight: 700; color: var(--text); }
.perfil-seccion__titulo svg { width: 20px; height: 20px; color: var(--primary); }
.perfil-seccion__cuenta { color: var(--text-faint); font-weight: 500; }
.perfil-caja__accion.btn { border-radius: 999px; border-color: var(--vidrio-borde); background: var(--card-bg); box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 6px 14px -8px var(--vidrio-sombra); }
.perfil-caja__accion.btn svg { color: var(--primary); }
/* Películas y libros en el carrusel de tarjetas de la casa: la cabecera
   (con las flechas) va adentro del `.splide`, y sin desborde los puntos ni
   ocupan lugar. */
.perfil-carrusel .perfil-caja__cabeza { margin-bottom: var(--space-200); }
.perfil-carrusel.carrusel-tarjetas:not(.is-overflow) .splide__pagination { display: none; }

/* Las tarjetas sobre el vidrio: blancas, 18px, sombra suave (la de
   `.inicio-tarjeta`). */
.perfil-vista :is(.perfil-pista, .perfil-libro, .perfil-publicacion) {
    border-radius: 18px; border: 1px solid var(--vidrio-borde);
    background: color-mix(in srgb, var(--card-bg) 94%, transparent);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 16px 30px -24px var(--vidrio-sombra), 0 1px 3px rgba(16, 18, 22, .04);
}

/* ── la portada ── */
.perfil-heroe {
    isolation: isolate; padding: var(--space-250); min-height: 13rem; display: grid; align-content: end;
    box-shadow: var(--glass-rim), 0 18px 34px -24px var(--vidrio-sombra);
}
/* El fondo: su capa recorta. Desplegada es sólida (la tarjeta, la imagen o
   el cielo); recogida, la imagen se va y queda la tarjeta casi opaca, sin
   esmerilado (ver la cabecera del cajón). */
.perfil-heroe__fondo {
    position: absolute; inset: 0; z-index: 0; border-radius: inherit; overflow: hidden;
    background-color: var(--card-bg);
}
.perfil-heroe__brillo { position: absolute; inset: 0; opacity: 0; background: linear-gradient(180deg, var(--bar-glass-top), var(--bar-glass-bottom)); }
.perfil-heroe__cielo {
    position: absolute; inset: 0; z-index: 0;
    background:
        radial-gradient(60% 70% at 18% 8%, color-mix(in srgb, var(--perfil-color) 45%, #fff) 0%, transparent 70%),
        radial-gradient(55% 80% at 85% 0%, color-mix(in srgb, #bfdbfe 70%, #fff) 0%, transparent 70%),
        radial-gradient(70% 60% at 60% 45%, color-mix(in srgb, #fbcfe8 55%, #fff) 0%, transparent 75%),
        linear-gradient(180deg, color-mix(in srgb, var(--perfil-color) 22%, #f6f4ff) 0%, color-mix(in srgb, var(--card-bg) 92%, transparent) 78%);
}
/* Las nubes: tres velos blancos, borrosos, a media altura. */
.perfil-heroe__cielo::before, .perfil-heroe__cielo::after {
    content: ''; position: absolute; left: -10%; right: -10%; height: 46%;
    background: radial-gradient(40% 60% at 25% 60%, rgba(255, 255, 255, .85), transparent 70%),
                radial-gradient(35% 55% at 70% 50%, rgba(255, 255, 255, .7), transparent 70%);
    filter: blur(14px);
}
.perfil-heroe__cielo::before { top: 18%; }
.perfil-heroe__cielo::after { top: 34%; opacity: .7; transform: scaleX(-1); }
:root[data-theme="dark"] .perfil-heroe__cielo {
    background:
        radial-gradient(60% 70% at 18% 8%, color-mix(in srgb, var(--perfil-color) 40%, transparent) 0%, transparent 70%),
        radial-gradient(55% 80% at 85% 0%, rgba(96, 165, 250, .18) 0%, transparent 70%),
        linear-gradient(180deg, color-mix(in srgb, var(--perfil-color) 18%, var(--card-bg)) 0%, var(--card-bg) 80%);
}
:root[data-theme="dark"] .perfil-heroe__cielo::before, :root[data-theme="dark"] .perfil-heroe__cielo::after { opacity: .08; }
/* La portada elegida (una de las de Gemini): de fondo, y fundida hacia el
   color de la tarjeta abajo, para que el nombre y las fichas se lean
   siempre. En oscuro se funde hacia oscuro. */
.perfil-heroe__foto { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; object-fit: cover; object-position: center 35%; }
.perfil-heroe__velo {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, transparent 22%, color-mix(in srgb, var(--card-bg) 55%, transparent) 55%, color-mix(in srgb, var(--card-bg) 88%, transparent) 100%);
}
:root[data-theme="dark"] .perfil-heroe__foto { opacity: .55; }
.perfil-heroe__acciones {
    position: absolute; z-index: 2; top: var(--space-200); right: calc(32px + var(--space-200) + var(--space-150));
    display: flex; align-items: center; gap: var(--space-100);
}
.perfil-heroe__acciones .btn { border-radius: 999px; }
.perfil-heroe__acciones .btn-ghost { background: color-mix(in srgb, var(--card-bg) 88%, transparent); border-color: var(--vidrio-borde); box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 6px 14px -8px var(--vidrio-sombra); }
.perfil-heroe__acciones .menu-trigger {
    /* Redondo y con los puntos al CENTRO: el disparador de la casa trae
       relleno desparejo (7 y 10) porque al lado va «Opciones». */
    width: 32px; height: 32px; padding: 0; gap: 0; justify-content: center; border-radius: 50%;
    border: 1px solid var(--vidrio-borde); color: var(--text);
    background: color-mix(in srgb, var(--card-bg) 88%, transparent);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 6px 14px -8px var(--vidrio-sombra);
}
.perfil-amigo { display: contents; }
.perfil-amigo .is-amigo { color: var(--primary-dark); border-color: var(--primary-soft-border); background: var(--primary-soft); }
.perfil-heroe__cuerpo {
    position: relative; z-index: 1; display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(0, 15rem);
    gap: var(--space-250); align-items: center; margin-top: var(--space-400);
}
.perfil-heroe__cara { position: relative; display: inline-flex; }
.perfil-cara-grande {
    display: inline-grid; place-items: center; width: 7.5rem; height: 7.5rem; flex: none;
    border-radius: 50%; overflow: hidden; object-fit: cover;
    border: 5px solid var(--card-bg); box-shadow: 0 18px 30px -18px rgba(40, 30, 90, .45);
    background: var(--perfil-color); color: #fff;
    font-size: var(--fs-hero); font-weight: 700; letter-spacing: .02em;
}
.perfil-heroe__punto { position: absolute; right: 8px; bottom: 8px; width: 20px; height: 20px; border-radius: 50%; background: #22c55e; border: 3px solid var(--card-bg); }
.perfil-heroe__quien { display: grid; gap: var(--space-050); min-width: 0; }
.perfil-heroe__nombre { margin: 0; font-size: var(--fs-page); font-weight: 800; letter-spacing: -.01em; line-height: 1.15; }
.perfil-heroe__cargo { margin: 0; color: var(--text-light); }
.perfil-heroe__datos { display: flex; flex-wrap: wrap; gap: var(--space-100); margin: var(--space-100) 0 0; padding: 0; list-style: none; }
.perfil-dato {
    display: inline-flex; align-items: center; gap: var(--space-100);
    padding: var(--space-050) var(--space-150); border: 1px solid var(--vidrio-borde); border-radius: 999px;
    background: color-mix(in srgb, var(--card-bg) 82%, transparent); color: var(--text); font-size: var(--fs-small);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 6px 12px -10px var(--vidrio-sombra);
}
.perfil-dato svg { width: 14px; height: 14px; color: var(--text-light); }
.perfil-dato__muestra { width: 14px; height: 14px; border-radius: 50%; background: var(--perfil-color); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12); }
.perfil-dato--red { text-decoration: none; }
.perfil-dato--red:hover { color: var(--text); text-decoration: none; border-color: var(--perfil-color); }
.perfil-heroe__cita {
    margin: 0; padding: var(--space-200); border-radius: 18px; border: 1px solid var(--vidrio-borde);
    background: color-mix(in srgb, var(--card-bg) 86%, transparent);
    box-shadow: inset 0 1.5px 0 var(--vidrio-luz), 0 16px 30px -24px var(--vidrio-sombra);
}
.perfil-heroe__comillas { display: block; height: 1.4rem; font-family: Georgia, serif; font-size: var(--fs-hero); line-height: 1; color: var(--primary); opacity: .6; }
.perfil-heroe__cita blockquote { margin: 0; font-size: var(--fs-body); line-height: 1.5; color: var(--text); overflow-wrap: anywhere; }

/* ── la música ── */
.perfil-musica { display: grid; grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); gap: var(--space-150); align-items: start; }
.perfil-musica--sola { grid-template-columns: minmax(0, 1fr); }
.perfil-sonando {
    position: relative; isolation: isolate; overflow: hidden;
    display: grid; grid-template-columns: 9rem minmax(0, 1fr); gap: var(--space-200); align-items: center;
    padding: var(--space-200); border-radius: 18px; background: #5b6b8f; color: #fff;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35), 0 18px 34px -24px rgba(30, 40, 80, .6);
}
.perfil-sonando__fondo { position: absolute; z-index: -2; inset: -25%; width: 150%; height: 150%; object-fit: cover; filter: blur(30px) saturate(1.3); opacity: .9; }
.perfil-sonando::after { content: ''; position: absolute; inset: 0; z-index: -1; background: linear-gradient(100deg, rgba(20, 24, 48, .18), rgba(20, 24, 48, .5)); }
.perfil-sonando__arte { aspect-ratio: 1; border-radius: 14px; overflow: hidden; background: rgba(255, 255, 255, .15); box-shadow: 0 14px 28px -12px rgba(0, 0, 0, .45); }
.perfil-sonando__arte img { display: block; width: 100%; height: 100%; object-fit: cover; }
.perfil-sonando__sin { display: grid; place-items: center; height: 100%; font-size: var(--fs-hero); color: rgba(255, 255, 255, .75); }
.perfil-sonando__texto { display: grid; gap: var(--space-050); min-width: 0; }
.perfil-sonando__titulo { margin: 0; font-size: var(--fs-page); font-weight: 700; line-height: 1.2; overflow-wrap: anywhere; }
.perfil-sonando__artista { margin: 0; color: rgba(255, 255, 255, .8); }
.perfil-sonando__reproductor { display: flex; align-items: center; gap: var(--space-150); margin-top: var(--space-150); }
.perfil-sonando__duracion { font-size: var(--fs-small); color: rgba(255, 255, 255, .85); font-variant-numeric: tabular-nums; }
.perfil-sonando .perfil-like { color: rgba(255, 255, 255, .88); }
.perfil-sonando .perfil-like__boton:hover { background: rgba(255, 255, 255, .16); color: #ffd1e1; }
.perfil-sonando .perfil-like.is-mio { color: #ffc2d6; }
/* La onda: barras quietas; cuando la canción suena, bailan. */
.perfil-onda { flex: 1; min-width: 0; height: 28px; display: flex; align-items: center; gap: var(--space-025); overflow: hidden; }
.perfil-onda span { --alto: 50%; --i: 0; flex: 1; min-width: 2px; max-width: 3px; height: var(--alto); border-radius: 2px; background: rgba(255, 255, 255, .7); transform-origin: center; }
.perfil-sonando:has(.is-sonando) .perfil-onda span { animation: perfil-onda .9s ease-in-out infinite alternate; animation-delay: calc(var(--i) * -.07s); }
@keyframes perfil-onda { from { transform: scaleY(.35); } to { transform: scaleY(1); } }

.perfil-pistas { display: grid; gap: var(--space-100); margin: 0; padding: 0; list-style: none; }
.perfil-pista { display: grid; grid-template-columns: 3.5rem minmax(0, 1fr) auto; gap: var(--space-150); align-items: center; padding: var(--space-100); }
.perfil-pista__arte { width: 3.5rem; height: 3.5rem; border-radius: 12px; object-fit: cover; background: var(--bg); }
.perfil-pista__texto { display: grid; min-width: 0; }
.perfil-pista__titulo { font-weight: 700; color: var(--text); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.perfil-pista__titulo:hover { text-decoration: underline; }
.perfil-pista__artista { color: var(--text-light); font-size: var(--fs-small); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.perfil-pista .perfil-like { margin-top: var(--space-025); }

/* El ▶ de los 30 segundos (`timbre.js`, `data-fragmento-audio`). */
.perfil-play {
    display: inline-grid; place-items: center; flex: none; width: 36px; height: 36px;
    padding: 0; border: 1px solid var(--vidrio-borde); border-radius: 50%; cursor: pointer;
    background: var(--card-bg); color: var(--text);
    box-shadow: inset 0 1px 0 var(--vidrio-luz), 0 6px 14px -8px var(--vidrio-sombra);
    transition: transform .12s ease;
}
.perfil-play:hover { transform: scale(1.06); }
.perfil-play svg { width: 14px; height: 14px; }
.perfil-play--grande { width: 48px; height: 48px; border: 0; background: #fff; color: #1d1b26; }
.perfil-play--grande svg { width: 18px; height: 18px; }
.perfil-play--fila { justify-self: end; }
.perfil-play__off { display: none; }
.perfil-play.is-sonando { background: var(--primary); color: #fff; }
.perfil-play.is-sonando .perfil-play__on { display: none; }
.perfil-play.is-sonando .perfil-play__off { display: block; }

/* ── libros y películas: cada uno en su tarjeta, la portada 2:3 con su lomo ── */
.perfil-libro { display: grid; gap: var(--space-050); align-content: start; width: 9.5rem; padding: var(--space-100) var(--space-100) var(--space-150); }
.perfil-libro__tapa {
    position: relative; display: block; aspect-ratio: 2 / 3; margin-bottom: var(--space-100);
    border-radius: 4px 10px 10px 4px; overflow: hidden; background: var(--bg);
    box-shadow: 0 1px 2px rgba(0, 0, 0, .1), 0 10px 20px -10px rgba(20, 16, 40, .35);
    transition: transform .15s ease;
}
.perfil-libro__tapa::after { content: ''; position: absolute; inset: 0 auto 0 0; width: 7px; background: linear-gradient(90deg, rgba(0, 0, 0, .2), rgba(255, 255, 255, .08) 60%, transparent); }
a.perfil-libro__tapa:hover { transform: translateY(-3px); }
.perfil-libro__tapa img { display: block; width: 100%; height: 100%; object-fit: cover; }
.perfil-libro__generada {
    --tono: 260;
    display: flex; flex-direction: column; justify-content: space-between; height: 100%;
    padding: var(--space-150) var(--space-150) var(--space-150) calc(var(--space-150) + var(--space-050));
    background: linear-gradient(transparent 62%, rgba(0, 0, 0, .18)), linear-gradient(160deg, hsl(var(--tono) 40% 40%), hsl(var(--tono) 46% 22%));
    color: #fbf6ec; font-family: Georgia, 'Iowan Old Style', 'Times New Roman', serif; text-align: center;
}
.perfil-libro__generada-titulo { font-size: var(--fs-section); font-weight: 700; line-height: 1.2; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 6; -webkit-box-orient: vertical; overflow: hidden; }
.perfil-libro__generada-autor { font-size: var(--fs-meta); letter-spacing: .03em; opacity: .85; }
.perfil-libro__titulo { margin: 0; font-size: var(--fs-small); font-weight: 700; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.perfil-libro__autor { margin: 0; font-size: var(--fs-meta); color: var(--text-light); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* La portada se queda ARRIBA al bajar (Renzo, 2026-10-10), y para no
   comerse el cajón se compacta con el scroll: la cara se achica, las fichas
   y la frase se van, y quedan nombre, cargo y acciones. La compactación es
   una animación ligada al scroll del cajón (`animation-timeline`): donde el
   navegador no la tiene, la portada se queda fija entera. En el celular no
   se fija: ahí ocupa media pantalla. */
@media (min-width: 721px) {
    /* 8px de aire arriba, como la banda de cualquier pantalla. */
    .perfil-heroe { position: sticky; top: calc(var(--space-100) - var(--space-200)); z-index: 3; }
}
@supports (animation-timeline: scroll()) {
    /* La línea de tiempo con NOMBRE, la del scroll del cajón: con
       `scroll(nearest)` los hijos de la portada miraban a la portada misma,
       que recorta y no se desplaza. */
    .perfil-cajon .drawer-body { scroll-timeline: --perfil-scroll block; }
    /* La ✕ vive fuera del cuerpo (en la cabeza del cajón): `timeline-scope`
       le presta la misma línea de tiempo para que baje con la barra. */
    .perfil-cajon { timeline-scope: --perfil-scroll; }
    @media (min-width: 721px) and (prefers-reduced-motion: no-preference) {
        .perfil-heroe, .perfil-heroe__cuerpo, .perfil-heroe .perfil-cara-grande, .perfil-heroe__quien,
        .perfil-heroe__cita, .perfil-heroe__datos, .perfil-heroe__punto {
            animation-timing-function: linear; animation-fill-mode: both;
            animation-timeline: --perfil-scroll; animation-range: 0 220px;
        }
        .perfil-heroe { animation-name: perfil-heroe-pega; }
        .perfil-heroe__cuerpo { animation-name: perfil-cuerpo-pega; }
        .perfil-heroe .perfil-cara-grande { animation-name: perfil-cara-pega; }
        .perfil-heroe__cita, .perfil-heroe__datos { overflow: hidden; animation-name: perfil-se-va; }
        .perfil-heroe__quien { animation-name: perfil-quien-pega; }
        .perfil-heroe__punto { animation-name: perfil-punto-pega; }
        .perfil-heroe__fondo, .perfil-heroe__foto, .perfil-heroe__cielo, .perfil-heroe__velo, .perfil-heroe__brillo {
            animation-timing-function: linear; animation-fill-mode: both;
            animation-timeline: --perfil-scroll; animation-range: 0 220px;
        }
        .perfil-heroe__fondo { animation-name: perfil-fondo-pega; }
        .perfil-heroe__foto, .perfil-heroe__cielo { animation-name: perfil-foto-pega; }
        .perfil-heroe__velo { animation-name: perfil-se-apaga; }
        .perfil-heroe__brillo { animation-name: perfil-brillo-pega; }
        .perfil-heroe__acciones { animation: perfil-acciones-pega linear both; animation-timeline: --perfil-scroll; animation-range: 0 220px; }
        .perfil-cajon .drawer-head {
            animation: perfil-cerrar-pega linear both;
            animation-timeline: --perfil-scroll; animation-range: 0 220px;
        }
    }
}
/* Recogida, la portada termina con la forma de la banda de los módulos
   (`.page-banner`): píldora, 10px de relleno y la cara del tamaño de su
   baldosa (44px), con el fondo que la persona eligió. */
/* Recogida es la banda de los módulos: la imagen se va y queda solo su
   vidrio (Renzo, 2026-10-10: «que desaparezca sutilmente el fondo y quede
   solo glass igual a los headers»). */
@keyframes perfil-heroe-pega { to { min-height: 0; padding: var(--space-100) var(--space-200); border-radius: 40px; border-color: var(--bar-glass-edge); } }
@keyframes perfil-fondo-pega { to { background-color: color-mix(in srgb, var(--card-bg) 90%, transparent); } }
@keyframes perfil-foto-pega { to { opacity: 0; } }
@keyframes perfil-se-apaga { to { opacity: 0; } }
@keyframes perfil-brillo-pega { to { opacity: 1; } }
@keyframes perfil-cuerpo-pega { to { margin-top: 0; gap: var(--space-150); } }
/* Recogida, el nombre y el cargo van juntos, como título y bajada. */
@keyframes perfil-quien-pega { to { gap: 0; line-height: 1.1; } }
@keyframes perfil-cara-pega { to { width: 2.75rem; height: 2.75rem; border-width: 2px; font-size: var(--fs-small); box-shadow: none; } }
@keyframes perfil-punto-pega { to { right: -1px; bottom: -1px; width: 12px; height: 12px; border-width: 2px; } }
/* La ✕ y las acciones van al centro de la barra recogida (66px, con el
   nombre y el cargo juntos): 8px de aire + 33 − 16 del botón. La ✕ se mide
   desde el cajón; las acciones, desde la portada. */
@keyframes perfil-cerrar-pega { to { top: 25px; } }
@keyframes perfil-acciones-pega { to { top: 17px; } }
@keyframes perfil-se-va { to { opacity: 0; max-height: 0; margin-top: 0; padding-top: 0; padding-bottom: 0; border-width: 0; } }
.perfil-heroe__cita { max-height: 14rem; }
.perfil-heroe__datos { max-height: 8rem; }
@media (max-width: 720px) {
    .perfil-heroe__cuerpo { grid-template-columns: auto minmax(0, 1fr); }
    .perfil-heroe__cita { grid-column: 1 / -1; }
    .perfil-musica { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 520px) {
    .perfil-cajon .drawer-body { padding: var(--space-100) var(--space-100) var(--space-300); }
    .perfil-caja { padding: var(--space-150) var(--space-150) var(--space-200); }
    .perfil-heroe { padding: var(--space-200); }
    .perfil-heroe__acciones { left: var(--space-200); right: calc(32px + var(--space-200)); flex-wrap: wrap; }
    .perfil-heroe__cuerpo { grid-template-columns: minmax(0, 1fr); margin-top: calc(2 * var(--space-400) + var(--space-200)); gap: var(--space-150); }
    .perfil-cara-grande { width: 6rem; height: 6rem; }
    .perfil-sonando { grid-template-columns: 6rem minmax(0, 1fr); gap: var(--space-150); padding: var(--space-150); }
    .perfil-libro { width: 8rem; }
}
@media (prefers-reduced-motion: reduce) {
    .perfil-play, .perfil-libro__tapa { transition: none; }
    .perfil-play:hover, a.perfil-libro__tapa:hover { transform: none; }
    .perfil-sonando:has(.is-sonando) .perfil-onda span { animation: none; }
}

/* En el chat, la cara y el nombre abren el perfil. */
.chat-perfil { color: inherit; text-decoration: none; border-radius: 50%; }
.chat-perfil:hover { text-decoration: underline; }

/* ── Una publicación como se verá en redes (ui/_publicacion.html) ─────────
   El marco de Instagram para ver una pieza antes de firmarla: la cuenta,
   el medio y el texto. Nació con el equipo de contenido (2026-10-12). El
   medio guarda la proporción del formato (4:5 el feed, 9:16 reel e
   historia) para que lo que se ve sea lo que sale. */
/* La burbuja de WhatsApp (ui/_mensaje_whatsapp.html), 2026-10-14: los tokens de .publicacion, que está al lado. */
.wa-mensaje { max-width: 340px; width: 100%; padding: var(--space-150); border: 1px solid var(--border); border-radius: 14px; background: var(--bg); display: flex; flex-direction: column; gap: var(--space-050); }
.wa-mensaje__cuenta { font-size: var(--fs-body); color: var(--text-faint); }
.wa-mensaje__burbuja { background: var(--card-bg); border-radius: 10px; padding: var(--space-050); box-shadow: 0 1px 1px rgb(0 0 0 / .08); }
.wa-mensaje__imagen { display: block; width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: 8px; }
.wa-mensaje__imagen--vacia { display: grid; place-items: center; background: var(--bg); color: var(--text-faint); font-size: var(--fs-body); }
.wa-mensaje__texto { margin: var(--space-075) var(--space-075) var(--space-025); font-size: var(--fs-body); line-height: 1.45; color: var(--text); overflow-wrap: anywhere; }
.wa-mensaje__pie { margin: 0 var(--space-075) var(--space-050); font-size: var(--fs-body); color: var(--text-faint); }
.wa-mensaje__boton { display: block; text-align: center; padding: var(--space-100); background: var(--card-bg); border-radius: 10px; color: var(--primary); font-weight: 600; }
/* EL CELULAR (`ui/_marco_celular.html`): un teléfono dibujado alrededor de
   una vista previa. Colores fijos y no tokens: es el dibujo de un aparato,
   y en el tema oscuro un teléfono sigue siendo negro con la pantalla clara.
   `--chat` pone el fondo de una conversación y la casilla de escribir. */
.celular {
    width: 100%; max-width: 360px; margin: 0 auto; padding: var(--space-100);
    border-radius: 46px; background: #1c1c1e;
    box-shadow: inset 0 0 0 2px #48484a, 0 12px 32px rgb(20 16 40 / .14);
}
.celular__pantalla { display: flex; flex-direction: column; min-height: 620px; overflow: hidden; border-radius: 38px; background: #fff; }
.celular__estado {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--space-150) var(--space-300) var(--space-100); font-size: var(--fs-small); font-weight: 600; color: #000;
}
.celular__isla { width: 80px; height: 22px; border-radius: 12px; background: #000; }
.celular__senal { display: inline-flex; align-items: flex-end; gap: var(--space-025); height: 10px; }
.celular__senal i { width: 3px; border-radius: 1px; background: #000; }
.celular__senal i:nth-child(1) { height: 4px; }
.celular__senal i:nth-child(2) { height: 6px; }
.celular__senal i:nth-child(3) { height: 8px; }
.celular__senal i:nth-child(4) { height: 10px; }
.celular__cabeza {
    display: flex; align-items: center; gap: var(--space-100);
    padding: var(--space-075) var(--space-150) var(--space-100); border-bottom: 1px solid #ececec; color: #111;
}
.celular__atras, .celular__mas { flex: none; width: 20px; height: 20px; }
.celular__cabeza .company-mark { flex: none; width: 30px; height: 30px; border-radius: 50%; }
.celular__nombre { flex: 1; min-width: 0; overflow: hidden; font-size: var(--fs-body); text-overflow: ellipsis; white-space: nowrap; }
.celular__cuerpo { flex: 1; min-height: 0; overflow: hidden; }
.celular--chat .celular__cuerpo { padding: var(--space-150); background: #efeae2; }
.celular__cuerpo .wa-mensaje { max-width: none; padding: 0; border: 0; background: transparent; }
.celular__cuerpo .publicacion { max-width: none; border: 0; border-radius: 0; }
.celular__cuerpo .formato__correo { display: block; width: 100%; height: 560px; border: 0; background: #fff; }
.celular__escribir { display: flex; align-items: center; gap: var(--space-100); padding: var(--space-100) var(--space-150) var(--space-250); background: #f6f6f6; color: #666; }
.celular__escribir svg { flex: none; width: 20px; height: 20px; }
.celular__casilla { flex: 1; padding: var(--space-075) var(--space-150); border-radius: 18px; background: #fff; font-size: var(--fs-body); color: #999; }
/* Una acción encima de una imagen (`ui/_encima.html`, 2026-11-01): con el
   mouse, la imagen se oscurece y el botón aparece en el centro; solo el botón
   recibe el clic, lo demás deja pasar las flechas del carrusel y el scroll
   del correo. En un teléfono no hay mouse: el botón se queda en una esquina. */
.con-encima { position: relative; }
.encima { position: absolute; inset: 0; z-index: 1; display: grid; place-items: center; background: rgb(0 0 0 / .28); opacity: 0; pointer-events: none; transition: opacity .15s ease-out; }
.encima .btn { pointer-events: auto; white-space: nowrap; } /* «Editar en vivo» en un renglón */
.con-encima:hover > .encima, .con-encima:focus-within > .encima { opacity: 1; }
@media (hover: none) {
    .encima { inset: auto var(--space-150) var(--space-150) auto; background: none; opacity: 1; }
}
.publicacion { max-width: 380px; width: 100%; border: 1px solid var(--border); border-radius: 14px; background: var(--card-bg); overflow: hidden; }
.publicacion__cabeza { display: flex; align-items: center; gap: var(--space-100); padding: var(--space-100) var(--space-150); font-size: var(--fs-body); color: var(--text); }
.publicacion__avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--primary-soft); }
.publicacion__medio { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; background: var(--bg); }
.publicacion--post, .publicacion--carrusel { --publicacion-aspecto: 4 / 5; }
.publicacion--reel, .publicacion--historia { --publicacion-aspecto: 9 / 16; }
.publicacion .publicacion__medio { aspect-ratio: var(--publicacion-aspecto, 4 / 5); }
.publicacion__vacio { display: grid; place-items: center; color: var(--text-faint); font-size: var(--fs-body); }
.publicacion .carrusel-fotos { aspect-ratio: auto; }
.publicacion__medio .splide__slide img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; display: block; }
.publicacion__texto { margin: 0; padding: var(--space-100) var(--space-150) var(--space-200); font-size: var(--fs-body); line-height: 1.45; color: var(--text); }
.publicacion__tags { color: var(--primary); }

/* ── La tarjeta de un formato del paquete (marketing/conceptos/_hijo.html) ─
   La pieza como se verá a la izquierda, a ancho de celular —`.publicacion`
   mide hasta 380px—, y a la derecha el estado, cuándo sale y los botones
   (Renzo, 2026-10-17). Se parte por el ANCHO DE LA TARJETA y no por el de la
   ventana: con `flex-wrap` y un mínimo para los datos, cuando a la derecha no
   le caben 280px se bajan debajo de la pieza. Con un `@media` de ventana, una
   ventana de 1.300px con el riel al lado dejaba a los datos en 90px, una
   palabra por renglón (Renzo, 2026-10-17, «muy mal responsive»). */
.formato__cuerpo { display: flex; flex-wrap: wrap; gap: var(--space-250); align-items: flex-start; }
.formato__vista {
    flex: 0 1 480px; min-width: 0;
    display: flex; flex-direction: column; gap: var(--space-200);
    padding: var(--space-200); border: 1px solid var(--border);
    border-radius: var(--radius-card); background: var(--bg);
}
.formato__datos { flex: 1 1 340px; min-width: 0; display: flex; flex-direction: column; gap: var(--space-150); }
/* La pestaña con la forma del referente de Renzo (2026-10-21): a la
   izquierda la vista previa en su tarjeta, con el logo del canal y el
   celular; a la derecha una pila de tarjetas. */
.formato__vista-cabeza { display: flex; align-items: center; gap: var(--space-150); }
.formato__vista-cabeza h3 { margin: 0; font-size: var(--fs-section); font-weight: 600; }
.formato__vista-cabeza .card-hint { margin: 0; }
.formato__animar { display: flex; flex-direction: column; gap: var(--space-100); margin: 0; }
.formato__animar .card-hint { margin: 0; text-align: center; }
.formato__vista-cabeza[data-canal="whatsapp"] .logo-marca { --logo-tinta: #25d366; }
.formato__vista-cabeza[data-canal="instagram"] .logo-marca { --logo-tinta: #e1306c; }
/* La tarjeta del estado: la pastilla y «Opciones» en una línea; debajo,
   «Sale» y «Prueba» como filas de ícono, rótulo, casilla y acción. */
.formato__estado { display: flex; flex-direction: column; gap: var(--space-150); }
.formato__cabeza { display: flex; align-items: center; justify-content: space-between; gap: var(--space-150); }
.formato__fila {
    display: grid; grid-template-columns: 18px auto minmax(0, 1fr) auto;
    align-items: center; gap: var(--space-100); margin: 0;
}
.formato__fila > svg { width: 18px; height: 18px; color: var(--text-light); }
.formato__fila-rotulo { min-width: 48px; font-size: var(--fs-body); font-weight: 600; color: var(--text); }
.formato__fila-valor, .formato__fila input {
    min-width: 0; min-height: 36px; padding: 0 var(--space-100);
    display: flex; align-items: center; border: 1px solid var(--border);
    border-radius: var(--radius-field); font-size: var(--fs-body); color: var(--text);
}
.formato__fila-valor { overflow: hidden; background: var(--card-bg); white-space: nowrap; text-overflow: ellipsis; }
/* Al tocar «Cambiar», la fecha, la hora y Guardar toman el lugar del valor
   y su botón (las dos últimas columnas). */
.formato__hora { grid-column: 3 / -1; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; align-items: center; gap: var(--space-100); margin: 0; }
.formato__hora input { width: 100%; min-width: 0; }
.formato__nota { display: flex; align-items: flex-start; gap: var(--space-100); margin: 0; font-size: var(--fs-small); color: var(--text-light); }
.formato__nota svg { flex: none; width: 16px; height: 16px; margin-top: 1px; }
.formato__asunto { margin: 0; font-weight: 600; color: var(--text); }
/* Al final, «Pedir cambios» y el botón que guarda, mitad y mitad. */
.formato__acciones { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--space-150); margin: 0; }
.formato__acciones .btn { justify-content: center; white-space: nowrap; }

/* ── La ficha de la campaña rehecha (spec ficha-campana, 2026-10-17) ──────
   Los formatos en UNA tarjeta con pestañas (`ui/_tabs.html`); cada panel es
   un `.formato` con la pieza y sus datos. La descripción se edita en el
   sitio; la dirección creativa va en pastillas; la oferta trae su producto
   con foto. Las ventanas de Editar, Rechazar y Devolver son la de la casa. */
.formatos { display: flex; flex-direction: column; gap: var(--space-200); }
.formato { display: flex; flex-direction: column; gap: var(--space-150); }
.formato__texto { display: flex; flex-direction: column; gap: var(--space-150); }
.formato__texto .card { display: flex; flex-direction: column; gap: var(--space-150); }
.formato__texto .card-header { margin-bottom: 0; }
.formato__texto textarea, .formato__texto input:not([type="hidden"]), .formato__texto select { width: 100%; }
.formato__texto textarea { resize: vertical; }
/* Los dos botones de la IA, mitad y mitad (el referente). */
.formato__ia { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--space-100); }
.formato__ia .btn { justify-content: center; white-space: nowrap; }
.campana-descripcion { display: flex; flex-direction: column; gap: var(--space-150); }
.campana-descripcion textarea { width: 100%; resize: vertical; }
/* «La idea» (marketing/conceptos/_cuerpo.html, Renzo 2026-10-18): la idea
   central es el titular del cuerpo, en su caja y a su tamaño —en la banda
   salía en versalitas, una frase entera gritada—; debajo, la descripción a
   renglón legible. La dirección creativa cierra la tarjeta en su franja, de
   filo a filo como la banda (los números salen del relleno de `.card`): el
   carácter en pastilla y los tonos como texto, porque los tonos son una
   frase y en pastillas sin quiebre se salían por el borde. */
/* La tarjeta en la rejilla de la ficha: sin esto su titular largo la
   ensanchaba más allá de su columna. */
.campana-idea { min-width: 0; }
/* La banda de La idea lleva Editar y, mientras el paquete espera, Rechazar
   y Aprobar: siempre a la vista desde que el rail se fue a un panel (Renzo,
   2026-10-21). El costo va al pie de la tarjeta, en gris. */
.campana-idea__banda, .campana-idea__aprobar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100); }
.campana-idea__aprobar form { margin: 0; }
.campana-idea__costo { margin: var(--space-150) 0 0; }
/* La franja del equipo trabajando (`_trabajando.html`): el personaje
   pensando a la izquierda; qué hace, la barra de todo el trabajo y qué
   sigue a la derecha. Va arriba de La idea, donde salen los avisos. */
.concepto-trabajando { display: flex; align-items: center; gap: var(--space-200); }
.concepto-trabajando__cara { flex: none; }
/* Un 20 % más grande que el de una fila (56 → 68px; Renzo, 2026-10-21). */
.concepto-trabajando__cara .compi { width: 68px; height: 68px; }
.concepto-trabajando__texto { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--space-050); }
.concepto-trabajando__texto > p { margin: 0; }
.concepto-trabajando__que { font-weight: 600; color: var(--text); }
.concepto-trabajando__cuenta { font-weight: 400; color: var(--text-light); }
.concepto-trabajando__barra { margin-block: var(--space-050); }
/* Un brillo recorre lo lleno: se nota que el equipo sigue trabajando
   aunque el número no se mueva (lo apaga «reducir movimiento»). */
.concepto-trabajando__barra .progreso__lleno {
    background-image: linear-gradient(90deg, transparent 0%, rgb(255 255 255 / .45) 50%, transparent 100%);
    background-size: 50% 100%; background-repeat: no-repeat;
    animation: trabajando-brillo 1.8s linear infinite;
}
@keyframes trabajando-brillo { from { background-position: -100% 0; } to { background-position: 200% 0; } }
.campana-idea__cuerpo { display: flex; flex-direction: column; gap: var(--space-150); }
.campana-idea__titular {
    margin: 0; max-width: 60ch;
    font-size: var(--fs-page); font-weight: 700; line-height: 1.25;
    letter-spacing: -.01em; color: var(--text); text-wrap: balance;
}
.campana-idea__mensaje { margin: 0; font-size: var(--fs-body); line-height: 1.65; color: var(--text-light); }
/* La franja de filo a filo con su rótulo en versalitas: lo que decidió el
   equipo en la pieza suelta y el acuerdo con Finanzas en el correo. */
.campana-idea__direccion {
    display: flex; flex-direction: column; gap: var(--space-100);
    margin: var(--space-200) calc(var(--space-250) * -1) 0;
    padding: var(--space-200) var(--space-250) 0;
    border-top: 1px solid var(--border);
}
.campana-idea__rotulo {
    margin: 0; font-size: var(--fs-label); font-weight: 700;
    text-transform: uppercase; letter-spacing: .04em; color: var(--text-faint);
}
/* La dirección creativa y la observación del Jefe, lado a lado bajo un
   filete de filo a filo (Renzo, 2026-10-31); sin espacio para los dos, uno
   debajo del otro. Con uno solo, ese ocupa el ancho. */
.campana-idea__notas {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
    gap: var(--space-200); margin: var(--space-200) calc(var(--space-250) * -1) 0;
    padding: var(--space-200) var(--space-250) 0; border-top: 1px solid var(--border);
}
.campana-idea__notas > .tip { margin: 0; }
/* La caja de la dirección, con la cara de la nota del Jefe (`.tip-cara`)
   pero en blanco: su glifo en un círculo, el rótulo, los tonos en palabras
   y, tras un filete, sus colores en muestras (`tonos.colores`). */
.campana-idea__paleta {
    display: flex; align-items: center; gap: var(--space-200); padding: var(--space-200);
    border: 1px solid var(--border); border-radius: var(--radius-field); background: var(--card-bg);
}
.campana-idea__glifo { flex: none; width: 44px; height: 44px; padding: var(--space-150);
                       border-radius: 999px; background: var(--primary-soft); color: var(--primary); }
.campana-idea__paleta-rotulo { margin: 0; font-size: var(--fs-section); font-weight: 600; color: var(--text); }
.campana-idea__estilo { flex: 1; display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.campana-idea__estilo .pill { flex: none; }
.campana-idea__tonos { margin: 0; font-size: var(--fs-body); line-height: 1.55; color: var(--text-light); }
.campana-idea__muestras { flex: none; display: flex; align-items: center; align-self: stretch; gap: var(--space-150);
                          padding-left: var(--space-250); border-left: 1px solid var(--border); }
/* `--muestra` lo pone cada círculo en su `style` con el color que leyó
   `tonos.colores`; aquí va el de por omisión. */
.campana-idea__muestra { --muestra: transparent; width: 36px; height: 36px; border-radius: 999px; background: var(--muestra);
                         box-shadow: inset 0 0 0 1px rgb(0 0 0 / .14); }
/* Angosta, las muestras bajan a su renglón, alineadas con el texto: al
   lado le dejaban al texto una palabra por línea. */
@container (max-width: 600px) {
    .campana-idea__paleta { display: grid; grid-template-columns: auto minmax(0, 1fr); row-gap: var(--space-150); }
    .campana-idea__muestras { grid-column: 2; padding-left: 0; border-left: 0; }
}
/* La idea con la forma del referente de Renzo (2026-10-21): la dirección
   creativa con su baldosa y su carácter junto al rótulo; al pie, los datos
   en su franja (`.fact-rows-franja`). Sin la imagen de la campaña al lado
   (Renzo, 2026-10-31): comía alto, y el texto va a lo ancho para que la
   tarjeta sea baja —«mucho espacio muerto»—. */
/* Lo que pidió la persona, como cita: la raya a la izquierda dice que son
   SUS palabras y no las del equipo. */
.campana-idea__pedido { margin: 0; max-width: 120ch; padding: var(--space-025) 0 var(--space-025) var(--space-200);
                        border-left: 4px solid var(--primary-soft-border);
                        font-size: var(--fs-body); line-height: 1.6; color: var(--text-light); }
.campana-idea__pedido strong { color: var(--text); }
.campana-idea__estilo-cabeza { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-100) var(--space-150); }
.campana-idea__datos { margin-top: var(--space-200); }
.campana-acciones-ficha { display: flex; flex-direction: column; gap: var(--space-150); }
.campana-acciones-ficha form { margin: 0; }
.campana-modal-form { display: flex; flex-direction: column; gap: var(--space-150); margin: 0; }
.campana-modal-form .field { display: flex; flex-direction: column; gap: var(--space-050); margin: 0; }
.campana-modal-form .btn { align-self: flex-start; }

/* El prompt a la vista (spec prompt-a-la-vista, 2026-10-29): el texto que
   recibió el generador, entero y con sus saltos; las versiones del arte en
   miniatura, una columna por versión. */
.prompts { display: flex; flex-direction: column; gap: var(--space-200); }
.prompt__rotulo { margin: 0 0 var(--space-050); font-size: var(--fs-small); color: var(--text-light); }
.prompt__texto {
    margin: 0; padding: var(--space-150); border-radius: var(--radius-field);
    background: var(--bg); border: 1px solid var(--border);
    font-family: var(--font-mono, monospace); font-size: var(--fs-small);
    white-space: pre-wrap; max-height: 320px; overflow: auto;
}
.prompt-caja { font-family: var(--font-mono, monospace); font-size: var(--fs-small); min-height: 220px; }
.arte-versiones { display: flex; flex-wrap: wrap; gap: var(--space-200); }
.arte-version { display: flex; flex-direction: column; gap: var(--space-100); width: 120px; }
.arte-version__tira { display: flex; flex-direction: column; border-radius: var(--radius-field); overflow: hidden; border: 1px solid var(--border); }
.arte-version__tira img { display: block; width: 100%; height: auto; }
.arte-version form { margin: 0; }
/* Una tarjeta del panel de ajustes: la casilla y su botón, y debajo lo que
   se calcula de ella (el alcance, el producto). */
.campana-ajuste { display: flex; flex-direction: column; gap: var(--space-150); }
.campana-ajuste .card-header { margin-bottom: 0; }
.campana-producto { display: flex; gap: var(--space-200); align-items: center; }
.campana-producto__foto {
    flex: none; width: 88px; height: 88px; border-radius: 14px;
    object-fit: cover; background: var(--bg);
}
.campana-producto__datos { min-width: 0; display: flex; flex-direction: column; gap: var(--space-025); }
.campana-producto__datos p { margin: 0; }
.campana-producto__nombre { font-size: var(--fs-body); color: var(--text); }
.campana-producto__precio { font-size: var(--fs-columna); font-weight: 700; color: var(--text); }

/* La plantilla en su panel lateral (proceso creativo, 2026-10-13): entera,
   del ancho del panel, con la esquina de las tarjetas. */
.creativo-plantilla { display: block; max-width: 100%; height: auto;
    border-radius: var(--radius-card); margin-bottom: var(--space-200); }
/* La definición de cada categoría se lee entera: es lo que leen la IA y el
   Creativo, y una casilla de 20 caracteres la cortaba (2026-10-13). */
.creativo-definicion { width: 100%; min-width: 24ch; }
/* Los generadores de Marketing, uno por caja (2026-10-13): nombre, estado,
   la llave, «Probar» y «Quitar» en una línea; en el celular se parte y la
   casilla de la llave baja a ocupar el ancho. */
.generador-fila { flex-wrap: wrap; }
.generador-nombre { display: flex; flex-direction: column; flex: 0 0 170px; }
.generador-estado { flex: none; }
.generador-fila > input[type="password"] { flex: 1 1 220px; min-width: 0; }
.generador-quitar {
    display: inline-flex; align-items: center; gap: var(--space-075); flex: none;
    font-size: var(--fs-small); color: var(--text-light); cursor: pointer;
}
/* La pastilla de algo que está pasando AHORA (`ui/_pill.html` con `busy`):
   un aro que gira en lugar del glifo, del color de la pastilla, y la
   pastilla respira apenas (Renzo, 2026-10-13: «el badge de trabajando
   debería tener algo más, un spinner»). Con movimiento reducido, quieto. */
.pill-giro {
    width: 11px; height: 11px; flex: none; border-radius: 50%;
    border: 2px solid currentColor; border-right-color: transparent;
    animation: pill-giro .8s linear infinite;
}
.pill.is-busy { animation: pill-respira 1.6s ease-in-out infinite; }
/* El botón pensando (`ui/_btn.html` con `busy`, o un script que pone
   `aria-busy="true"`): el giro de la pastilla en vez del glifo, y quieto. */
.btn[aria-busy="true"] { pointer-events: none; }
.btn[aria-busy="true"] > svg { display: none; }
.btn[aria-busy="true"]::before { content: ''; width: 14px; height: 14px; flex: none; border-radius: 50%;
    border: 2px solid currentColor; border-right-color: transparent; animation: pill-giro .8s linear infinite; }
@keyframes pill-giro { to { transform: rotate(360deg); } }
@keyframes pill-respira { 50% { opacity: .72; } }
@media (prefers-reduced-motion: reduce) {
    .pill-giro, .pill.is-busy, .btn[aria-busy="true"]::before { animation: none; }
}

/* El campo heredado (`ui/_inherited_field.html`): el botón de «Usar el
   heredado» cae debajo de la ayuda, sin pegarse a ella. */
.inherited-field > .btn { margin-top: var(--space-100); }
/* El gris ES la señal de «esto es lo heredado»: no se deja al navegador. */
.inherited-field input::placeholder { color: var(--text-faint); opacity: 1; }

/* La ventana del reabastecimiento (`inventory/_replenishment_fields.html`,
   2026-10-23): una pregunta por desplegable y, debajo de «¿Cómo se repone?»,
   solo lo que ese modo usa, apenas metido hacia adentro con una línea a la
   izquierda para que se lea como LA RESPUESTA de esa pregunta y no como otra
   pregunta. Un solo ritmo vertical —el `gap`—, así los campos no llevan su
   margen de formulario y nada se pega a nada. */
.replenishment-fields { display: flex; flex-direction: column; gap: var(--space-250); }
.replenishment-fields .field { margin-bottom: 0; }
.replenishment-fields > .form-lead { margin: 0; }
.replenishment-question { display: flex; flex-direction: column; gap: var(--space-200); }
.replenishment-question select { width: 100%; }
.replenishment-answer {
    display: flex; flex-direction: column; gap: var(--space-200);
    padding-left: var(--space-200); border-left: 2px solid var(--border);
}
.replenishment-answer > .field-hint { margin: 0; }
/* Un número de días o una cantidad no necesita el ancho del cajón. */
.replenishment-answer .inherited-field input,
.replenishment-answer .input-unit { max-width: 240px; }
.replenishment-today { margin: 0; }

/* ── Gasto en IA (2026-10-17, maqueta de Renzo) ──────────────────────────────
   La tarjeta de cifras con la gráfica al lado y, en el panel de vidrio, una
   fila por departamento. Lo que no es de acá —la baldosa, la pastilla, la
   barra de avance, el delta del Inicio, la chispa— son piezas de la casa. */
.ia-cifras { display: flex; flex-direction: column; gap: var(--space-200); }
.ia-cifras__cabeza { display: flex; align-items: center; justify-content: space-between; gap: var(--space-150); }
.ia-cifras__cabeza .home-metrics-head { padding: 0; flex-direction: row; align-items: center; gap: var(--space-100); }
.ia-cifras__cuerpo {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(0, 1.6fr);
    gap: var(--space-200); align-items: center;
}
.ia-cifra { display: flex; align-items: flex-start; gap: var(--space-150); min-width: 0; }
.ia-cifra + .ia-cifra { border-left: 1px solid var(--border); padding-left: var(--space-200); }
.ia-cifra__texto { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.ia-cifra__rotulo {
    font-size: var(--fs-badge); font-weight: 600; color: var(--text-light);
    text-transform: uppercase; letter-spacing: .07em; white-space: nowrap;
}
.ia-cifra__valor {
    font-size: var(--fs-stat); font-weight: 700; color: var(--text);
    letter-spacing: -.01em; line-height: 1.1; font-variant-numeric: tabular-nums;
}
.ia-cifra__pie {
    display: flex; align-items: center; gap: var(--space-075); flex-wrap: wrap;
    font-size: var(--fs-small); color: var(--text-light);
}
.ia-cifra__pie .progreso { flex: 1 1 60px; min-width: 60px; }
.ia-cifras__grafica { min-width: 0; }
.ia-grafica { height: 150px; }

/* El orden de los departamentos vive en la cabeza del panel. */

/* La fila de un departamento: quién, el gasto, el porcentaje, la tendencia y
   la flecha que abre su cajón. Es un enlace entero: toda la fila lleva al
   detalle, sin un botón suelto por celda. */
/* `.card.ia-depto` y no `.ia-depto`: la fila es también `.card.tarjeta-vidrio`,
   y esa regla ganaría por especificidad a una clase sola. */
.card.ia-depto {
    display: grid;
    grid-template-columns: minmax(180px, 1.3fr) minmax(0, 1fr) minmax(0, 1fr) minmax(120px, 1fr) 24px;
    gap: var(--space-200); align-items: center;
    padding: var(--space-200) var(--space-250);
    color: inherit; text-decoration: none;
    transition: transform .15s ease, box-shadow .15s ease;
}
.card.ia-depto:hover, .card.ia-depto:focus-visible { transform: translateY(-1px); box-shadow: var(--shadow); }
.ia-depto__quien { display: flex; align-items: center; gap: var(--space-150); min-width: 0; }
.ia-depto__nombre {
    margin: 0; font-size: var(--fs-section); font-weight: 700; color: var(--text);
    display: flex; align-items: center; gap: var(--space-100); flex-wrap: wrap;
}
.ia-depto__sub { margin: var(--space-025) 0 0; font-size: var(--fs-small); color: var(--text-light); }
.ia-depto__celda { display: flex; flex-direction: column; gap: var(--space-050); min-width: 0; }
.ia-depto__valor {
    font-size: var(--fs-number); font-weight: 700; color: var(--text);
    letter-spacing: -.01em; line-height: 1.1; font-variant-numeric: tabular-nums;
}
.ia-depto__tendencia .chispa { margin-top: var(--space-025); }
.ia-depto__flecha { color: var(--text-faint); display: grid; place-items: center; }
.ia-depto__flecha svg { width: 18px; height: 18px; transform: rotate(-90deg); }
.ia-depto__nota {
    grid-column: 1 / -1; margin: 0; padding-top: var(--space-150);
    border-top: 1px solid var(--border);
    font-size: var(--fs-small); color: var(--text-light);
}
.ia-nota { margin: var(--space-150) 0 0; }
.ia-cajon-cifra { display: flex; flex-direction: column; gap: var(--space-075); margin-bottom: var(--space-200); }
.ia-cajon-sujetos { margin-bottom: var(--space-200); }

@media (max-width: 900px) {
    .ia-cifras__cuerpo { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ia-cifra + .ia-cifra { border-left: 0; padding-left: 0; }
    .ia-cifras__grafica { grid-column: 1 / -1; }
    .card.ia-depto { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
    .ia-depto__quien { grid-column: 1 / -1; }
    .ia-depto__tendencia { grid-column: 1 / -1; }
    .ia-depto__flecha { display: none; }
}

/* ── La chispa (`ui/_chispa.html`) ─────────────────────────────────────────
   Barras diminutas, una por valor, con la más alta llena y las demás en el
   tono suave de la serie. Solo CSS: no pide Chart.js para decir «así se
   movió este mes». */
.chispa {
    display: flex; align-items: flex-end; gap: var(--space-025);
    height: 28px; width: 100%; max-width: 220px;
}
.chispa__barra {
    flex: 1 1 0; min-height: 2px; border-radius: 2px 2px 0 0;
    background: color-mix(in srgb, var(--primary) 35%, transparent);
}
.chispa__barra--alta { background: var(--primary); }
@media (max-width: 600px) {
    .ia-cifras__cuerpo { grid-template-columns: minmax(0, 1fr); }
    .ia-cifra + .ia-cifra { border-top: 1px solid var(--border); padding-top: var(--space-150); }
    .ia-cifras__cabeza, #departamentos .panel-vidrio__cabeza { flex-wrap: wrap; }
}

/* Las variantes que propone la IA en la ficha de la campaña
   (marketing/conceptos/_variantes_lista.html): cada una es un botón que
   la pone en la caja. Se leen enteras, con su salto de línea. */
.variantes { display: flex; flex-direction: column; gap: var(--space-150); }
.variante {
    display: block; width: 100%; text-align: left; cursor: pointer;
    padding: var(--space-150) var(--space-200); border: 1px solid var(--border);
    border-radius: var(--radius); background: var(--card-bg); color: var(--text);
    font: inherit; font-size: var(--fs-body); line-height: 1.45;
}
.variante:hover, .variante:focus-visible { border-color: var(--primary); background: var(--primary-soft); }

/* ── Pantalla completa en el teléfono (2026-10-18, el chat del Equipo) ────
   `data-pantalla-completa` en el `.content` le pide a la casa la pantalla
   entera a ≤720 px: la banda se va y su `--topbar` baja a 0, así lo que
   descuenta su alto (la cuenta de `.chat-inbox`, lo pegajoso) se corre solo.
   Se combina con `data-borde-a-borde`, no lo reemplaza. La pantalla lo
   prende y lo apaga sin recargar (el chat: con una conversación abierta). */
@media (max-width: 720px) {
    html:has(.content[data-pantalla-completa]) { --topbar: 0px; }
    html:has(.content[data-pantalla-completa]) .topbar,
    html:has(.content[data-pantalla-completa]) .drawer-toggle { display: none; }
}

/* ── Mantener presionado (`sostener.js`, 2026-10-18) ───────────────────────
   Sin esto iOS abre su globo de copiar y selecciona el texto encima de lo
   que el gesto abre. Copiar lo ofrece la pantalla. */
@media (pointer: coarse) and (max-width: 720px) {
    [data-sostener] { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
    /* Lo que se escribe adentro (editar un mensaje) sí se selecciona. */
    [data-sostener] :is(input, textarea, [contenteditable]) { -webkit-touch-callout: default; -webkit-user-select: text; user-select: text; }
}

/* ── Las pestañas de abajo del teléfono (`ui/_pestanas_abajo.html`) ────────
   Como Slack en el celular: las secciones de una pantalla a un toque del
   pulgar, fijas abajo y apartadas del gesto de inicio del iPhone. Solo a
   ≤720 px. `--pestanas-abajo-h` es lo que mide sin el área segura: la
   pantalla lo usa para que su último renglón no quede debajo. */
:root { --pestanas-abajo-h: 58px; }
.pestanas-abajo { display: none; }
@media (max-width: 720px) {
    .pestanas-abajo {
        position: fixed; left: 0; right: 0; bottom: 0; z-index: 60;
        display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
        height: calc(var(--pestanas-abajo-h) + env(safe-area-inset-bottom));
        padding-bottom: env(safe-area-inset-bottom);
        border-top: 1px solid var(--border); background: var(--card-bg);
    }
    .pestanas-abajo__una {
        display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--space-050);
        color: var(--text-light); text-decoration: none;
        font-size: var(--fs-meta); font-weight: 600;
        -webkit-tap-highlight-color: transparent;
    }
    .pestanas-abajo__una[aria-current="page"] { color: var(--primary); }
    .pestanas-abajo__icono { position: relative; display: grid; place-items: center; width: 44px; height: 28px; border-radius: 999px; }
    .pestanas-abajo__una[aria-current="page"] .pestanas-abajo__icono { background: var(--primary-soft); }
    .pestanas-abajo__icono svg { width: 22px; height: 22px; }
    .pestanas-abajo__rotulo { line-height: 1; white-space: nowrap; }
    .pestanas-abajo__cuenta {
        position: absolute; top: -4px; left: 26px; min-width: 18px; height: 18px; padding: 0 var(--space-050);
        display: grid; place-items: center; box-sizing: border-box;
        border: 2px solid var(--card-bg); border-radius: 999px;
        background: var(--primary); color: var(--card-bg);
        font-size: var(--fs-badge); font-weight: 700; line-height: 1;
    }
    .pestanas-abajo__cuenta[hidden] { display: none; }
}

/* ── Novedades (`novedades/lista.html`, 2026-10-18) ────────────────────────
   Lo que cambió en Agrupa, una tarjeta por día. Cada renglón: la baldosa del
   módulo, de dónde es (en versalitas, como un rótulo), el título y el texto
   que escribió la rama. Los arreglos por dentro no se muestran. */
.novedades__lista { list-style: none; margin: 0; padding: 0; }
.novedades__fila {
    display: flex; align-items: flex-start; gap: var(--space-150);
    padding: var(--space-150) 0;
    border-top: 1px solid var(--border);
}
.novedades__fila:first-child { border-top: 0; }
.novedades__texto { display: flex; flex-direction: column; gap: var(--space-025); min-width: 0; }
.novedades__donde {
    font-size: var(--fs-meta); font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--text-faint);
}
.novedades__titulo { font-size: var(--fs-body); color: var(--text); }
.novedades__cuerpo { font-size: var(--fs-body); line-height: 1.5; color: var(--text-light); }
.novedades__cuerpo p { margin: var(--space-050) 0 0; }

/* La franja del pedido retenido por posible fraude (spec 2026-10-27,
   `orders/_retenido.html`): las señales en lista, alineadas a la izquierda
   bajo su rótulo. En `dl.facts` el valor iba a la derecha, lejos del rótulo,
   y una frase entera se leía como un dato suelto. */
.hold-label { margin: var(--space-200) 0 var(--space-100); font-size: var(--fs-label);
    font-weight: 600; color: var(--text-light); }
.hold-reasons { margin: 0 0 var(--space-150); padding-left: 1.2em; display: grid;
    gap: var(--space-050); font-size: var(--fs-small); color: var(--text); }

/* La casilla de la razón dentro de la ventana de la OC (`orden-compra.js` la
   muda ahí): a lo ancho del panel, con su aire. */
.razon-hueco { display: block; margin-top: var(--space-150); }
.razon-hueco .field { margin-bottom: 0; }
.razon-hueco textarea { width: 100%; }
/* El rótulo de la casilla repite el título de la ventana: se queda para el
   lector de pantalla y sale de la vista (lo mismo que `.visually-hidden`). */
.razon-hueco .field > label {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%); overflow: hidden; white-space: nowrap;
}

/* El bloque de la razón de la OC se apaga con `hidden` desde el script, y
   `.card` declara `display`. */
[data-razon][hidden] { display: none; }

/* Sin anclaje de desplazamiento, como `#count-lines` del conteo de
   estantería (2026-10-29): con la tabla apilada (800 px o menos) cada
   repintado `outerHTML` hacía que el navegador se anclara a algo de adentro y
   la pantalla saltaba (medido: 288 px al tocar el «+» de una tarjeta del
   medio). Va con `focus-scroll:false` en los formularios de la tabla
   (`inventory/_conteo.html`): sin él saltaba hasta la casilla del código. */
#recibir-conteo { overflow-anchor: none; }

/* RECIBIR Y TRASLADOS (`inventory/_conteo.html`, 2026-10-27): un solo control
   por renglón. El número y lo que cuenta («cajas») van pegados, a la derecha
   como toda cifra; el selector «Cajas | Bolsas» arriba de ellos; lo suelto
   y la equivalencia, debajo en chico. */
.conteo-cuanto { display: inline-flex; align-items: center; gap: var(--space-100); }
.conteo-unidad { text-align: left; color: var(--text-light); font-size: var(--fs-small); }
/* «Llegó» es solo el número, centrado en su columna: el «−»/«+» y la casilla
   de lo que se pesa miden lo mismo, y todos caen en la misma vertical. La
   unidad va en la columna de al lado (2026-10-27, Santiago). */
#recibir-conteo th.conteo-llego-celda, #recibir-conteo td.conteo-llego-celda { text-align: center; }
.conteo-llego { display: flex; align-items: center; justify-content: center; }
#recibir-conteo .conteo-peso { width: 8rem; text-align: center; }
.conteo-unidad-celda { white-space: nowrap; color: var(--text-light); font-size: var(--fs-small); }
/* La unidad y su equivalencia en UNA línea: abajo empujaban la casilla. */
.conteo-unidad-fila { display: flex; align-items: center; gap: var(--space-100); }
.conteo-unidad-fila > .cell-sub { margin: 0; }
/* Un poco más de aire entre productos. */
#recibir-conteo .table tbody td { padding-top: var(--space-150); padding-bottom: var(--space-150); }
.conteo-en { display: flex; margin: 0; }
.conteo-en select { width: auto; min-width: 0; }
/* Lote y Vence en sus columnas: casillas angostas, el «+»/«×» pegado al
   vencimiento; el renglón hijo (otro lote de la misma línea) se corre. */
.conteo-lote-casilla { width: 6em; }
/* La fecha COMPACTA del calendario de la casa (`ui/_datepicker.html`,
   2026-10-30): la clase `fecha-compacta` en la casilla con `data-datepicker`,
   para varias fechas en el renglón de una tabla (recibir: Vence y
   Fabricación). flatpickr le pasa la clase a la casilla que se ve
   (`altInput`); el «dd/mm/aaaa» entra entero con letra un punto más chica y
   menos relleno. */
input.fecha-compacta.fecha-compacta { width: 6.5em; font-size: var(--fs-small); padding-left: var(--space-050); padding-right: var(--space-050); }
.conteo-vence { display: inline-flex; align-items: center; gap: var(--space-100); }
.conteo-vence form { margin: 0; }
.conteo-hijo { padding-left: var(--space-300); }
/* El nombre no se aprieta a una palabra por renglón cuando la fila trae
   Lote y Vence: lo que cede es el espacio entre columnas. */
#recibir-conteo td.cell-wrap .cell-text { min-width: 7.5em; }
/* El aire entre columnas, menos que el de las tablas editables: con Lote,
   Vence y Fabricación la fila cabe en una línea a 1440 px con el menú
   abierto (2026-10-30). */
#recibir-conteo .table thead th, #recibir-conteo .table tbody td { padding-left: var(--space-075); padding-right: var(--space-075); }
.cell-raya { color: var(--text-light); }
/* Lo que no lleva lote: las casillas apagadas (el gris de la casa, regla de
   `input:disabled`) y el hueco del botón «+», para que Vence quede en la
   misma vertical en todas las filas. */
.conteo-hueco-boton { display: inline-block; width: 34px; flex: 0 0 34px; }
/* Lo que se pesa, con el ancho del número de un «−»/«+»: todas las cifras de
   «Llegó» en la misma vertical. */
.conteo-peso { width: 7em; text-align: center; }
/* El SKU de recibir en la letra chica de los datos: es largo y monoespaciado,
   y es la columna que más ancho le quitaba a la fila. */
#recibir-conteo td.mono { font-size: var(--fs-meta); }
/* LA TABLET ACOSTADA (`inventory/_conteo.html` con `lot_heads`,
   2026-10-30): la estación de recepción es una tablet de 1024 px, y con
   Lote, Vence y Fabricación el renglón no cabe (pide unos 1030 y la tabla
   mide 900). Cuando la TABLA mide 1040 o menos —a 1440 con el menú mide
   1062 y sigue en una línea—, las celdas `conteo-abajo` bajan a una segunda
   línea del mismo renglón, debajo del producto. Por el ancho de la tabla y
   no de la ventana (`@container conteo-tabla`), y solo con el contenido de
   más de 800: debajo la tabla se apila (`.table-apilada`).
   La tabla pasa a rejilla y cada renglón toma prestadas sus columnas
   (`subgrid`), así todo sigue en la misma vertical de renglón a renglón. El
   producto ocupa las tres primeras —que miden lo que miden Lote, Vence y
   Fabricación, la última con lo que sobre— y las demás celdas siguen en la
   primera línea; las columnas que nadie usa miden cero. La línea entre
   renglones y la banda del encabezado pasan de la celda a la fila: en la
   segunda línea hay huecos sin celda. */
.conteo-tabla { container-type: inline-size; container-name: conteo-tabla; }
@container content (min-width: 801px) {
    @container conteo-tabla (max-width: 1040px) {
        .table.conteo-lote-abajo {
            display: grid;
            grid-template-columns: auto auto minmax(0, 1fr) repeat(12, auto);
        }
        .table.conteo-lote-abajo > thead,
        .table.conteo-lote-abajo > tbody { display: contents; }
        .table.conteo-lote-abajo tr {
            display: grid; grid-column: 1 / -1; grid-template-columns: subgrid;
            align-items: center;
        }
        .table.conteo-lote-abajo tr > * { grid-row: 1; }
        .table.conteo-lote-abajo tr > :first-child { grid-column: 1 / span 3; }
        .table.conteo-lote-abajo tr > .conteo-abajo { grid-row: 2; }
        .table.conteo-lote-abajo thead tr {
            background: var(--table-head-bg); border-bottom: 1px solid var(--border);
        }
        .table.conteo-lote-abajo thead th { background: none; border-bottom: 0; }
        .table.conteo-lote-abajo thead th.conteo-abajo { padding-top: 0; }
        .table.conteo-lote-abajo tbody tr { border-bottom: 1px solid var(--table-line); }
        .table.conteo-lote-abajo tbody tr:last-child { border-bottom: 0; }
        .table.conteo-lote-abajo tbody td { border-bottom: 0; }
        .table.conteo-lote-abajo tr.row-editing { background-color: var(--primary-soft); }
        #recibir-conteo .table.conteo-lote-abajo tbody td { padding-bottom: var(--space-050); }
        #recibir-conteo .table.conteo-lote-abajo tbody td.conteo-abajo {
            padding-top: 0; padding-bottom: var(--space-150);
        }
    }
}
/* Y el «−»/«+» un poco más angosto que el de la casa: cuatro cifras caben. */
#recibir-conteo .table td .stepper { width: 8rem; }
/* Sin el relleno lateral de una casilla suelta: en el teléfono el «−»/«+»
   crece y al número le quedaban 24px, todos de relleno —se veía vacío—. */
#recibir-conteo .stepper > input[type="number"] { padding-left: var(--space-025); padding-right: var(--space-025); min-width: 2.5em; }
/* El panel «Cargar factura» de una recepción: el QR, el buzón y lo escrito a
   mano, cada parte con su aire. */
.doc-proveedor > * + * { margin-top: var(--space-200); }
.doc-proveedor .card-hint { margin-bottom: var(--space-100); }

/* Reabastecer en una página (2026-10-30): nueve columnas en la tarjeta de
   cada proveedor. Todas las tablas de Reabastecer con las MISMAS columnas,
   una debajo de la otra (Santiago, 2026-11-01: «cuando bajo, una columna
   está más a la derecha y otra a la izquierda»): ancho FIJO.

   COMPACTA (Santiago, 2026-11-05: «demasiado extensos los espacios en cada
   columna»): cada cifra mide lo que lleva, con medio relleno a los lados, y
   el producto se queda con lo que sobra. Antes eran porcentajes, y en una
   pantalla ancha cada cifra quedaba con un hueco de 100px. La casilla de
   «Pedir» llena su columna —cinco cifras y su unidad—, así el rótulo arranca
   justo sobre ella (estaba corrido a la derecha de la casilla). Más angosta que 800, la tabla se apila
   (`.table-apilada`). En «Sin proveedor», «¿A quién se le compra?» ocupa lo
   de Pedir, Costo y Subtotal. */
.table-reabastecer { table-layout: fixed; }
.table-reabastecer > thead > tr > th:not(:first-child),
.table-reabastecer > tbody > tr > td:not(:first-child),
.table-reabastecer > tfoot > tr > td:not(:first-child) {
    padding-left: var(--space-100); padding-right: var(--space-100);
}
.table-reabastecer > thead > tr > th:last-child,
.table-reabastecer > tbody > tr > td:last-child,
.table-reabastecer > tfoot > tr > td:last-child { padding-right: var(--space-250); }
/* La MISMA distribución (Santiago, 2026-11-05: «las columnas luego del
   producto siguen midiendo diferente»): las ocho columnas de datos, del
   mismo ancho exacto, y el producto con lo que sobra. En porcentaje para que
   la proporción se sostenga en cualquier pantalla. Cada título, alineado a
   su contenido: las cifras —con la casilla de Pedir— a la derecha, Estado a
   la izquierda (Santiago eligió «cifras a la derecha» el mismo día). El SKU
   va pegado al producto, en letra de código, como en la orden de compra; se
   esconde en «Columnas». En «Sin proveedor», «¿A quién se le compra?» ocupa
   lo de Pedir, Costo y Subtotal. */
.table-reabastecer > thead > tr > th:not(:first-child) { width: 8%; }
.table-reabastecer > thead > tr > th.reab-col-sku { width: 8.5rem; }
.table-reabastecer > thead > tr > th.reab-col-elegir { width: 24%; }
/* Un SKU largo se corta con «…» en vez de montarse sobre «Vende al mes»
   (Santiago, 2026-11-05: «¿no debería como cortarse?»); entero, al pasar el
   mouse (`title`) y al esconder la columna, en la ficha bajo el nombre. */
.table-reabastecer td.reab-col-sku { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* «Columnas», una sola vez para todas las tablas, al final de la fila de
   pestañas, contra el borde derecho. */
.reab-columnas { margin-left: auto; align-self: center; }
/* La casilla de «Pedir», a lo justo para cinco cifras y su unidad
   (Santiago, 2026-11-05: «el espacio de pedir está muy grande»), contra el
   borde derecho de su columna, donde termina su título. */
.table-reabastecer td.num .input-unit { display: inline-flex; width: 6.25rem; max-width: 100%; }
.table-reabastecer td.num .input-unit > input { min-width: 0; padding-left: var(--space-075); padding-right: var(--space-100); }
/* El renglón chico («2 kg sin confirmar», «35 días de cobertura») y el
   rótulo de la columna («Vende al mes») bajan de línea: sin eso cada uno
   se montaba sobre la columna vecina. */
.table-reabastecer td .cell-sub,
.table-reabastecer > thead > tr > th { white-space: normal; }
/* Con el ancho fijo, un nombre largo ya no ensancha su columna: baja de
   línea en vez de montarse sobre «Vende al mes». */
.table-reabastecer > tbody > tr > td:first-child,
.table-reabastecer > tbody > tr > td:first-child * { white-space: normal; overflow-wrap: anywhere; }
.table-reabastecer .input-unit > input { text-align: right; }
/* «Trasladar» (Santiago, 2026-11-04: «esas columnas están demasiado a la
   izquierda… si pone dos dígitos se corta»): sus cinco columnas no son las
   nueve de una tarjeta de proveedor, así que no heredan sus porcentajes —ahí
   la quinta medía el 8,5 % y a la casilla le cabía un dígito—. Las tres
   cifras a la derecha con el mismo ancho, la casilla con espacio para cuatro
   dígitos y su unidad, y el producto con lo que sobra. */
.table-trasladar { table-layout: fixed; }
.table-trasladar > thead > tr > th.num { width: 14%; white-space: normal; }
.table-trasladar > thead > tr > th.num:last-child { width: 10.5rem; }
.table-trasladar td.num .input-unit { display: inline-flex; width: 9rem; max-width: 100%; }
.table-trasladar .input-unit > input { text-align: right; }
.table-trasladar > tbody > tr > td:first-child,
.table-trasladar > tbody > tr > td:first-child * { white-space: normal; overflow-wrap: anywhere; }
/* Apilada, cada celda mide su dato: el 9 % de `.table-cifras` la dejaba en
   una palabra por línea y la casilla de «Pedir» sin ancho. */
@container content (max-width: 800px) {
    .table-reabastecer.table-apilada td.num { width: auto; }
    .table-reabastecer.table-apilada td.num .input-unit { width: 7.5rem; }
}
/* Tocar un producto abre sus sedes debajo, en las MISMAS columnas (boceto que
   aprobó Santiago, 2026-10-30). La flechita dice que se abre; la sede va
   corrida y sobre un velo lila, para leerse como parte del producto. */
.reab-abre { cursor: pointer; }
/* La flechita cuelga a la izquierda del nombre, en el margen: el nombre y
   su variante arrancan al mismo nivel, y en la misma línea que los productos
   sin sedes (Santiago, 2026-11-05: «que la variante empiece en el mismo
   nivel que el nombre»). */
.reab-abre td:first-child strong { position: relative; }
.reab-abre td:first-child strong::before { content: '▸'; color: var(--primary); position: absolute; right: 100%; padding-right: .35em; }
.reab-abre.is-abierta td:first-child strong::before { content: '▾'; }
.reab-sede td { background: color-mix(in srgb, var(--primary) 5%, transparent); font-size: var(--fs-small); }
.reab-sede td:first-child { padding-left: var(--space-300); }
.reab-antes { margin: 0; }
.reab-mover { color: var(--text); font-weight: 600; }
/* «Por sede» en la ventana de reabastecimiento de la variante. */
.replenishment-sites { margin-top: var(--space-200); }
.replenishment-sites .input-unit { display: inline-flex; width: 7.5rem; }
.replenishment-sites .input-unit > input { text-align: right; }
/* El editor masivo: la casilla del modo que no rige queda gris y no se
   escribe (Santiago, 2026-10-30). Lo escrito sigue ahí y viaja. */
.cell-input[data-reab][readonly] { opacity: .45; cursor: not-allowed; }
.reab-seguro { margin-top: var(--space-100); max-width: 22rem; white-space: normal; }

/* ── Las dos opciones del correo (marketing/conceptos/_opciones_correo.html,
   spec correo-dos-opciones) ─ Con espacio, una al lado de la otra; el
   selector solo aparece donde no caben, y se ve una a la vez. Mide su propio
   ancho y no la pantalla: en la ventana del ojito de un paso de flujo
   (2026-10-31) tampoco caben. */
.correo-opciones { flex: 1 1 100%; display: flex; flex-wrap: wrap; gap: var(--space-250); align-items: flex-start; container-type: inline-size; }
.correo-opciones__ver { display: none; flex: 1 1 100%; }
.correo-opcion { flex: 1 1 380px; min-width: 0; display: flex; flex-direction: column; gap: var(--space-150); }
.correo-opcion__rotulo { margin: 0; font-size: var(--fs-section); font-weight: 600; }
.correo-opcion .formato__vista { flex: none; }
@container (max-width: 720px) {
    /* En grid y en partes iguales: la ficha del interruptor corre de a una
       parte (`.choice-segmented--compact::before`); en flex no le atinaba. */
    .correo-opciones__ver { display: grid; grid-auto-columns: minmax(0, 1fr); }
    .correo-opciones__ver .choice-pill { justify-content: center; }
    .correo-opcion--oculta { display: none; }
}

/* ── La Guía de UI (`/guia/`, en la casa) ─────────────────────────────────
   El índice a la izquierda, pegado; la entrada a la derecha. Cada entrada
   es una URL, así que el índice son enlaces y no pestañas. Spec
   2026-10-29. */
.guia {
    display: grid; grid-template-columns: 220px minmax(0, 1fr);
    gap: var(--space-250); align-items: start;
}
.guia-indice {
    position: sticky; top: calc(var(--topbar) + var(--space-200));
    padding: var(--space-150); border-radius: var(--radius-card);
    background: var(--card-bg); border: 1px solid var(--border);
}
.guia-indice__grupo {
    margin: var(--space-150) var(--space-100) var(--space-050); font-size: var(--fs-meta);
    font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
    color: var(--text-light);
}
.guia-indice__grupo:first-child { margin-top: 0; }
.guia-indice__lista { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-025); }
.guia-indice__lista a {
    display: block; padding: var(--space-075) var(--space-100); border-radius: var(--radius-sm);
    color: var(--text); text-decoration: none; font-size: var(--fs-body);
}
.guia-indice__lista a:hover { background: var(--bg); }
.guia-indice__lista a[aria-current="page"] { background: var(--bg); font-weight: 600; }
.guia-entrada { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-200); min-width: 0; }
/* `.tip` trae su margen de arriba; acá el aire lo pone el `gap`. */
.guia-entrada > .tip { margin-top: 0; }
.guia-codigo {
    margin: 0; padding: var(--space-150) var(--space-200); overflow-x: auto;
    border-radius: var(--radius-field); background: var(--bg);
    border: 1px solid var(--border); font-size: var(--fs-small);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    white-space: pre;
}
.guia-vigila { margin: 0 0 var(--space-150); padding: 0; list-style: none;
    display: grid; gap: var(--space-100); font-size: var(--fs-small); }
/* El nombre de una prueba es una sola «palabra» larga: se parte donde sea,
   o fija el ancho de la columna y la tarjeta se sale en el teléfono. */
.guia-vigila code { font-size: var(--fs-meta); overflow-wrap: anywhere; }
@media (max-width: 900px) {
    .guia { grid-template-columns: minmax(0, 1fr); }
    /* En el teléfono el índice es una franja que se desliza de lado DENTRO
       de su caja: el `body` nunca se desplaza de lado. */
    .guia-indice { position: static; display: flex; gap: var(--space-100);
        overflow-x: auto; align-items: center; }
    .guia-indice__grupo { margin: 0; white-space: nowrap; }
    .guia-indice__lista { display: flex; }
    .guia-indice__lista a { white-space: nowrap; }
}

/* «Así sí / así no» (`ui/_asi_si_no.html`): el bueno y el malo lado a lado.
   El malo, con el borde punteado del tono de alarma, para que nadie lo
   copie creyendo que es el bueno. */
.asi-si-no { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-200); }
.asi-si-no--solo { grid-template-columns: minmax(0, 1fr); }
.asi-si-no__lado {
    margin: 0; padding: var(--space-150); border-radius: var(--radius-card);
    border: 1px solid var(--border); background: var(--card-bg); min-width: 0;
}
.asi-si-no__lado--si { border-color: var(--ok-bg); }
.asi-si-no__lado--no { border: 2px dashed var(--danger-text); }
.asi-si-no__rotulo {
    margin-bottom: var(--space-100); font-size: var(--fs-meta); font-weight: 700;
    letter-spacing: .04em; text-transform: uppercase; color: var(--ok-text);
}
.asi-si-no__lado--no .asi-si-no__rotulo { color: var(--danger-text); }
/* El malo puede venir sin `table-wrap` a propósito: el marco se desplaza
   solo, para que el ejemplo no rompa la página en el teléfono. */
.asi-si-no__ejemplo { min-width: 0; overflow-x: auto; }
.asi-si-no__nota { margin: var(--space-100) 0 0; font-size: var(--fs-small); color: var(--text-light); }
@media (max-width: 640px) {
    .asi-si-no { grid-template-columns: minmax(0, 1fr); }
}
/* Las muestras de los fundamentos: el tamaño o el radio sale del token que
   la plantilla pone en `style`, así cambian solas cuando cambia la hoja. */
.guia-regla-espacio { display: block; height: var(--space-150); border-radius: 2px;
    background: var(--primary-soft-border); }
/* «Espacios» de la guía: los tres rangos y los cinco principios, en una
   grilla de tarjetitas; y el ejemplo de proximidad, con el rótulo pegado a su
   campo (así sí) o a la misma distancia de todo (así no). */
.guia-rangos { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-150); }
.guia-rango { padding: var(--space-150) var(--space-200); border: 1px solid var(--border); border-radius: var(--radius); }
.guia-rango__titulo { margin: 0 0 var(--space-050); font-weight: 600; color: var(--text); }
.guia-rango__texto { margin: 0; font-size: var(--fs-small); line-height: 1.45; color: var(--text-light); }
.guia-rangos + .asi-si-no { margin-top: var(--space-300); }
.guia-espacio-ejemplo { display: flex; flex-direction: column; }
.guia-espacio-ejemplo > div { display: flex; flex-direction: column; }
.guia-espacio-rotulo { font-size: var(--fs-label); font-weight: 600; color: var(--text-light); }
.guia-espacio-caja {
    padding: var(--space-100) var(--space-150); border: 1px solid var(--border-strong);
    border-radius: var(--radius-field); background: var(--card-bg); font-size: var(--fs-body);
}
.guia-espacio-ejemplo--si { gap: var(--space-200); }
.guia-espacio-ejemplo--si > div { gap: var(--space-050); }
.guia-espacio-ejemplo--no { gap: var(--space-150); }
.guia-espacio-ejemplo--no > div { gap: var(--space-150); }
.guia-muestra-radio { display: block; width: 72px; height: 40px;
    border: 2px solid var(--primary-soft-border); background: var(--primary-soft); }
/* El estado como se ve EN LA BANDA: sobre su vidrio, no sobre la tabla. */
.guia-banda-muestra { display: inline-flex; padding: var(--space-050) var(--space-100);
    border-radius: 999px; background: linear-gradient(180deg, var(--bar-glass-top), var(--bar-glass-bottom));
    border: 1px solid var(--bar-glass-edge); }
/* Una banda de ejemplo dentro de una tarjeta: sin el margen de la de verdad,
   y con su título en un `<p>`, porque la página ya tiene su `h1`. */
.guia-banda-ejemplo { margin: 0; }
.guia-banda-ejemplo .page-banner-text > .guia-banda-ejemplo__titulo {
    display: block; margin: 0; font-size: var(--fs-section); font-weight: 600; }
/* La barra de guardar de ejemplo, quieta: `driven=1` hace que `savebar.js`
   no la toque, y aquí se pinta en su sitio y no fija arriba. */
.guia-marco-barra { margin-bottom: var(--space-150); padding: var(--space-100);
    border-radius: 999px; background: var(--canvas); }
html.js .guia-marco-barra .savebar, .guia-marco-barra .savebar {
    position: static; transform: none; display: flex; width: 100%; box-shadow: none; }
/* Una fila de ejemplos (botones, pastillas, baldosas) y una lista de reglas. */
.guia-fila { display: flex; flex-wrap: wrap; gap: var(--space-100); align-items: center;
    margin-bottom: var(--space-150); }
.guia-lista { margin: 0 0 var(--space-200); padding-left: 1.2em; display: grid;
    gap: var(--space-050); font-size: var(--fs-body); }
/* La imagen de muestra de «Encima de una imagen» (`ui/_encima.html`). */
.guia-encima { max-width: 280px; overflow: hidden; border-radius: var(--radius-lg); }
.guia-encima img { display: block; width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }
/* La pantalla modelo dentro de su entrada: alta, porque es una pantalla
   entera; «Abrir sola» la muestra sin marco. */
.guia-modelo-marco { display: block; width: 100%; height: 760px; border: 1px solid var(--border);
    border-radius: var(--radius-field); background: var(--canvas); }
/* «Lo que falta arreglar»: el módulo abre su grupo de pantallas. */
.guia-falta__modulo td { font-weight: 600; }

/* ── El editor de capas (marketing/contenido/editor.html + assets/js/editor-capas.js,
   spec editor-de-capas, 2026-11-01). Canvas y vista previa lado a lado del mismo
   tamaño, el panel de capas a la derecha. El lienzo es 1,5× el encuadre: lo de
   afuera existe y se ve atenuado. Las capas (.capa) las pone el motor por JS. ── */
.editor-capas { display: block; }
.editor-capas-espera { display: grid; gap: var(--space-150); justify-items: start; padding: var(--space-300);
    border: 1px solid var(--border); border-radius: var(--radius-card); background: var(--card-bg); }
/* canvas y previa del MISMO tamaño, los dos PEQUEÑOS: ninguno pasa de 50vh (Renzo 2026-11-01: «que los dos sean del
   mismo tamaño») — `--ar` (ancho/alto del encuadre) lo pone la pantalla */
.editor-capas-cuerpo { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 320px;
    gap: var(--space-200); align-items: stretch; }
/* el panel de capas no desborda la pantalla: tiene su propio desplazamiento, a la altura de la tarjeta
   del canvas (cabecera + 50vh + la barra de la IA) */
.editor-capas-panel { overflow-y: auto; min-height: 0; max-height: calc(50vh + 160px); overscroll-behavior: contain; }
/* la vista previa: mientras klein trabaja, el giro de las capas en el centro de la imagen */
.editor-capas-previa-marco { position: relative; }
/* por el ancho del CONTENIDO y no de la ventana: con el menú fijo y el Asistente abierto, a 1024 px de ventana
   quedan 305 px y un `@media` no se entera (2026-11-06: el canvas se quedaba en 0×0) */
@container content (max-width: 1040px) { .editor-capas-cuerpo { grid-template-columns: minmax(0, 1fr) 320px; }
    .editor-capas-panel { grid-column: 2; grid-row: 1 / 3; } }
@container content (max-width: 640px) { .editor-capas-cuerpo { grid-template-columns: minmax(0, 1fr); }
    .editor-capas-panel { grid-column: 1; grid-row: auto; } }
.editor-capas-lienzo, .editor-capas-previa, .editor-capas-panel { border: 1px solid var(--border);
    border-radius: var(--radius-card); background: var(--card-bg); padding: var(--space-150); }
.editor-capas-lienzo h2, .editor-capas-previa h2, .editor-capas-panel h2, .editor-capas-panel h3 {
    font-size: var(--fs-meta); text-transform: uppercase; letter-spacing: .05em; margin: 0 0 var(--space-100);
    display: flex; justify-content: space-between; align-items: center; }
/* el lienzo ES el encuadre (sin margen, Renzo 2026-11-01): su proporción es la del diseño; dentro, las capas absolutas.
   Hasta que cargan las capas, 4:5: sin una proporción, el canvas mide cero y la barra de IA se le monta a las herramientas */
[data-editor-capas] { position: relative; width: min(100%, calc(50vh * var(--ar, .8))); aspect-ratio: 4 / 5; margin-inline: auto;
    border-radius: var(--radius-field); overflow: hidden; background: var(--canvas); user-select: none;
    touch-action: none; }
[data-editor-capas] .capa { position: absolute; cursor: grab; transform-origin: center; }
[data-editor-capas] .capa canvas { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; }
[data-editor-capas] canvas.original { position: absolute; pointer-events: none; }
[data-editor-capas] .capa .borde { position: absolute; inset: 0; border: 1.5px dashed transparent; }
[data-editor-capas] .capa:hover .borde { border-color: rgba(255, 255, 255, .55); }
[data-editor-capas] .capa.sel .borde { border: 2px solid var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); }
[data-editor-capas] .capa.sel.mal .borde, [data-editor-capas] .capa.tapado .borde { border: 2px solid #e5484d; }
/* los puntos de acción, cada uno con su ícono: agrandar (esquina), girar (perilla arriba), giro 3D (al lado) */
[data-editor-capas] { perspective: 1200px; }
/* Las manijas, en un marco propio encima de todas las capas, que sigue a la
   elegida: dentro de cada capa, una capa de adelante las tapaba (2026-11-02). */
[data-editor-capas] .marco { position: absolute; transform-origin: center; pointer-events: none; z-index: 40; }
[data-editor-capas] .marco[hidden] { display: none; }
[data-editor-capas] .marco .h { position: absolute; pointer-events: auto; width: 20px; height: 20px; background: #fff; color: var(--primary);
    border: 2px solid var(--primary); border-radius: 4px; display: flex; align-items: center; justify-content: center;
    z-index: 60; box-sizing: border-box; }
[data-editor-capas] .marco .h svg { width: 12px; height: 12px; display: block; }
[data-editor-capas] .marco .h.esq { right: -10px; bottom: -10px; cursor: nwse-resize; }
/* medio adentro: el lienzo recorta lo que se sale (overflow hidden) y una capa pegada al borde no puede perder sus puntos */
[data-editor-capas] .marco .h.rot { left: calc(50% - 10px); top: -10px; border-radius: 50%; cursor: grab; }
[data-editor-capas] .marco .h.giro { right: -10px; top: calc(50% - 10px); border-radius: 50%; cursor: ew-resize; }
[data-editor-capas] .marco .h.inclina { left: calc(50% - 10px); bottom: -10px; border-radius: 50%; cursor: ns-resize; }
/* mientras GPT redibuja un texto: el original atenuado con un loader encima */
/* …y mientras Meshy arma su malla 3D (`.modelando`), la capa atenuada con la barra de avance encima */
[data-editor-capas] .capa.redibujando canvas, [data-editor-capas] .capa.modelando canvas { opacity: .45; }
.editor-capas-avance { position: absolute; left: 50%; top: 50%; z-index: 5; width: min(70%, 160px); padding: var(--space-075); transform: translate(-50%, -50%);
    border-radius: var(--radius-field); background: var(--card-bg); box-shadow: 0 1px 4px rgba(0, 0, 0, .3); pointer-events: none; }
/* la barra de lo que hace GPT va por el tiempo: 90 % en lo que suele tardar (`--espera`, medido el 2026-11-06) */
.editor-capas-avance.estimada .progreso__lleno { transition: width var(--espera) linear; }
[data-editor-capas] .capa.redibujando::before, .editor-capas-previa-marco[aria-busy="true"]::before { content: ''; position: absolute; left: 50%; top: 50%; width: 24px;
    height: 24px; margin: calc(-1 * var(--space-150)) 0 0 calc(-1 * var(--space-150)); border: 3px solid rgba(255, 255, 255, .25); border-top-color: #fff;
    border-radius: 50%; animation: editor-capas-gira .8s linear infinite; z-index: 5; }
@keyframes editor-capas-gira { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { [data-editor-capas] .capa.redibujando::before, .editor-capas-previa-marco[aria-busy="true"]::before { animation: none; } }
/* la barra «Editar con IA» bajo el canvas, siempre a la vista: aplica a la capa elegida.
   Como la maqueta de Renzo (2026-11-01): ficha blanca, el rayo en su chip, el rótulo en negrita,
   la casilla en píldora y el botón primario al lado */
/* «Editar con IA» bajo el canvas y «Imagen HD» bajo la vista previa: dos tarjetas hermanas (Renzo 2026-11-03) */
.editor-capas-pedir, .editor-capas-hd { display: flex; align-items: center; gap: var(--space-150); margin-top: var(--space-150);
    padding: var(--space-150) var(--space-200); border: 1px solid var(--border); border-radius: 16px;
    background: var(--card-bg); box-shadow: 0 1px 2px rgba(17, 17, 26, .04); }
.editor-capas-pedir { flex-wrap: wrap; } /* la casilla baja si no cabe; la tarjeta HD nunca se parte */
.editor-capas-pedir > .compi-mark { flex: none; }
/* las dos tarjetas, compactas y de un renglón (Renzo 2026-11-03): antes de doblarse, el rótulo se recorta con «…»
   y la casilla se encoge; las dos miden lo mismo (el cuadro de 44 px y lo de la derecha de 44 px de alto) */
.editor-capas-pedir-rotulo { font-weight: 700; color: var(--text); white-space: nowrap; display: flex; flex-direction: column; line-height: 1.2; flex: 1 1 0; min-width: 72px; }
.editor-capas-pedir-quien { font-size: var(--fs-small); font-weight: 400; color: var(--text-light); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* el aviso de lo que pasa reemplaza a «qué capa», en el mismo renglón; nunca crece la tarjeta */
.editor-capas-pedir-rotulo:has([data-pedir-estado]:not(:empty)) [data-pedir-quien] { display: none; }
/* la tarjeta de la HD: «Generar HD» a la derecha; cuando llega, «Ampliar», «Rechazar» y «Aceptar» en su lugar */
.editor-capas-hd > .tile { flex: none; margin: var(--space-025); } /* casi el cuadro de 44 px de la cara del Asistente */
/* la tarjeta HD nunca se parte (Renzo 2026-11-03): con Ampliar, Regenerar, Rechazar y Aceptar, se recorta el rótulo */
.editor-capas-hd .editor-capas-pedir-rotulo { min-width: 0; }
/* lo de la derecha parte como la casilla de «Editar con IA» (260 px) y con su mismo alto: las dos tarjetas se doblan juntas y miden igual */
.editor-capas-generar { display: flex; flex: 0 0 auto; justify-content: flex-end; align-items: center; min-height: 44px; }
.editor-capas-generar:has(> [hidden]) { display: none; }
.editor-capas-propuesta-acciones { display: flex; flex: 0 0 auto; flex-wrap: nowrap; justify-content: flex-end; align-items: center; gap: var(--space-075); min-height: 44px; }
.editor-capas-propuesta-acciones[hidden] { display: none; }
/* «Ampliar» es el disparador de la ventana de la casa: aquí, cuadrado y del alto de los botones de ícono */
.editor-capas-propuesta-acciones .help { width: auto; height: auto; }
.editor-capas-propuesta-acciones .help-icon.help-icon-boton { width: 30px; height: 30px; padding: 0; justify-content: center; }
.editor-capas-hd-despues, .editor-capas-hd:has([data-propuesta]:not([hidden])) .editor-capas-hd-antes { display: none; }
.editor-capas-hd:has([data-propuesta]:not([hidden])) .editor-capas-hd-despues { display: block; }
/* la cabecera del canvas: el título, la barra de herramientas en íconos (como la de Photoshop) y la medida.
   Del mismo alto que la de la vista previa, para que los dos lienzos y sus tarjetas queden parejos */
.editor-capas-cabeza { display: flex; align-items: center; gap: var(--space-100); margin-bottom: var(--space-100); min-height: 36px; }
.editor-capas-cabeza h2 { margin: 0; }
.editor-capas-cabeza > .pill { margin-left: auto; }
.editor-capas-cabeza > .pill + .pill { margin-left: 0; }
.editor-capas-previa h2 { min-height: 36px; }
/* el grosor del pincel, al lado de la barra, solo con el borrador encendido */
.editor-capas-pincel { display: flex; align-items: center; gap: var(--space-075); font-size: var(--fs-small); color: var(--text-light); }
.editor-capas-pincel[hidden] { display: none; }
.editor-capas-pincel input { width: 96px; }
/* la Luz: en el canvas, el ícono de su tipo (cono, sol, flecha) en un círculo tenue; gira con la capa */
[data-editor-capas] .capa.luz { border-radius: 50%; background: rgba(255, 190, 110, .28); box-shadow: 0 0 0 1px rgba(255, 255, 255, .55); }
/* la luz solo lleva la perilla de girar */
[data-editor-capas] .marco.solo-girar .h:not(.rot) { display: none; }
[data-editor-capas] .capa.luz .icono-luz { position: absolute; inset: 18%; color: #fff; pointer-events: none; filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .6)); }
[data-editor-capas] .capa.luz .icono-luz svg { display: block; width: 100%; height: 100%; }
.editor-capas-luz { display: flex; flex-direction: column; gap: var(--space-075); }
.editor-capas-luz[hidden] { display: none; }
/* el borrador: el cursor es la goma, con la punta donde cae el trazo, y los trazos se ven rojos translúcidos encima de la capa */
[data-editor-capas].con-borrador, [data-editor-capas].con-borrador .capa { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cg transform='rotate(45 12 12)'%3E%3Cpath d='M7.5 5.5Q12 2.5 16.5 5.5V15a4.5 4.5 0 0 1-9 0Z' stroke='%23fff' stroke-width='4'/%3E%3Cpath d='M7.5 5.5Q12 2.5 16.5 5.5V15a4.5 4.5 0 0 1-9 0Z' fill='%23fff' stroke='%23111' stroke-width='1.75'/%3E%3Cpath d='M10.5 4.4V14M7.5 13.2Q12 16.8 16.5 13.2' stroke='%23111' stroke-width='1.75'/%3E%3C/g%3E%3C/svg%3E") 9 23, crosshair; }
/* el pincel de las Formas: el cursor es el pincel, con la punta donde cae el trazo; nada se agarra mientras tanto */
[data-editor-capas].con-pincel, [data-editor-capas].con-pincel .capa { cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 24 24' fill='none' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18.4 3.6a2 2 0 0 1 2.9 2.8L12.5 15l-3-3ZM9 12.5c-2.2 0-3.6 1.4-3.8 3.3-.1 1.2-.8 2-2.2 2.7 1.6 1.3 3.7 1.9 5.8 1.5 2.7-.5 4.3-2.6 4.2-4.7' stroke='%23fff' stroke-width='4'/%3E%3Cpath d='M18.4 3.6a2 2 0 0 1 2.9 2.8L12.5 15l-3-3Z' fill='%23fff' stroke='%23111' stroke-width='1.75'/%3E%3Cpath d='M9 12.5c-2.2 0-3.6 1.4-3.8 3.3-.1 1.2-.8 2-2.2 2.7 1.6 1.3 3.7 1.9 5.8 1.5 2.7-.5 4.3-2.6 4.2-4.7' fill='%23fff' stroke='%23111' stroke-width='1.75'/%3E%3C/svg%3E") 4 25, crosshair; }
[data-editor-capas] .capa canvas.trazos { opacity: .5; }
/* con el borrador, la tarjeta de IA cambia la casilla por «Limpiar trazos» y «Borrar lo pintado» */
.editor-capas-borrar { display: flex; flex: 0 0 auto; justify-content: flex-end; align-items: center; gap: var(--space-100); min-height: 44px; }
.editor-capas-borrar[hidden] { display: none; }
.editor-capas-pedir:has([data-borrar-acciones]:not([hidden])) .editor-capas-pedir-campo { display: none; }
/* la mesa del canvas: la barra de herramientas vertical, en el costado izquierdo, dentro de un margen
   que la vista previa también deja; así los dos lienzos miden lo mismo (Renzo 2026-11-03) */
.editor-capas-mesa { position: relative; padding-inline: var(--space-600); }
.editor-capas-herramientas { position: absolute; left: 0; top: 0; display: flex; flex-direction: column; gap: var(--space-025); padding: var(--space-025);
    border: 1px solid var(--border); border-radius: var(--radius-field); background: var(--card-bg); }
/* las Formas: el disparador del menú con la cara de los botones de la barra */
.editor-capas-herramientas .menu-trigger { justify-content: center; }
/* la herramienta elegida se ve morada; con el Pincel, la que se enciende es Formas */
.editor-capas-herramientas .btn[aria-pressed="true"], .editor-capas-herramientas .encendida > .menu-trigger { border-color: var(--primary); color: var(--primary-dark); background: var(--primary-soft); }
/* «Ampliar»: la HD entera dentro de la hoja completa, sin recortar */
.editor-capas-hd-grande { display: block; width: 100%; max-height: calc(100dvh - 9rem); object-fit: contain; }
.editor-capas-pedir-campo { flex: 1 1 100%; min-width: 150px; display: flex; align-items: center; gap: var(--space-100); min-height: 44px; }
.editor-capas-pedir-campo > .btn { flex: none; }
.editor-capas-pedir > select { flex: 0 1 auto; width: auto; max-width: 55%; min-width: 0; }
.editor-capas-pedir:has([data-borrar-acciones]:not([hidden])) > select { display: none; }
/* las dos tarjetas de abajo miden lo mismo: Editar con IA lleva dos renglones */
.editor-capas-pedir, .editor-capas-hd { min-height: 126px; }
.editor-capas-pedir input[type="text"] { flex: 1; min-width: 0; border-radius: 999px; padding: var(--space-100) var(--space-200); }
.editor-capas-pedir [data-pedir-estado]:empty { display: none; }
/* la previa: el encuadre a la misma escala que en el canvas */
.editor-capas-previa canvas { display: block; width: min(100% - 2 * var(--space-600), calc(50vh * var(--ar, .8))); aspect-ratio: 4 / 5; margin-inline: auto;
    border-radius: var(--radius-field); background: var(--canvas); }
/* el correo compuesto es más largo que la pantalla: canvas y previa a todo el ancho, en un rollo del alto de siempre;
   se le quita ancho a las capas y al margen de la derecha, para que se vea a una escala con la que se pueda trabajar */
.con-rollo .editor-capas-rollo { max-height: 50vh; overflow-y: auto; overscroll-behavior: contain; padding-inline: var(--space-100); }
.con-rollo [data-editor-capas], .con-rollo .editor-capas-previa canvas { width: 100%; }
.con-rollo .editor-capas-mesa { padding-inline: var(--space-600) 0; }
/* la mitad del margen de cada lado: mide lo mismo que el canvas, y centrada en su tarjeta */
.con-rollo .editor-capas-previa .editor-capas-rollo { margin-inline: var(--space-300); }
@container content (min-width: 1041px) { .editor-capas-cuerpo.con-rollo { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 260px; } }
/* sin ancho para el margen de la barra: la barra va encima, y canvas y previa se quedan con todo, iguales */
@container content (max-width: 480px) { .editor-capas-mesa { padding-inline: 0; }
    .editor-capas-herramientas { position: static; flex-direction: row; width: fit-content; margin: 0 auto var(--space-100); }
    .editor-capas-previa canvas { width: min(100%, calc(50vh * var(--ar, .8))); }
    .con-rollo .editor-capas-mesa { padding-inline: 0; }
    .con-rollo .editor-capas-previa .editor-capas-rollo { margin-inline: 0; } }
/* el panel: las capas como filas —ojo, miniatura, nombre, menú de la casa—, de adelante hacia atrás;
   se reordenan arrastrando (drag-sort.js), que es la profundidad (Renzo 2026-11-01, su maqueta) */
/* Se ven cinco capas; las demás, con scroll dentro de la lista (Renzo 2026-11-02). Cada fila mide
   la miniatura (32px) más su relleno; el menú de cada fila sale por encima (menu.js lo pasa a fixed). */
.editor-capas-lista { list-style: none; margin: 0; padding: 0; --fila-capa: calc(32px + 2 * var(--space-050));
    max-height: calc(5 * var(--fila-capa)); overflow-y: auto; overscroll-behavior: contain; }
.editor-capas-lista li { display: grid; grid-template-columns: auto 32px minmax(0, 1fr) auto; align-items: center;
    gap: var(--space-100); padding: var(--space-050) var(--space-050) var(--space-050) 0; border-radius: var(--radius-field);
    cursor: grab; }
.editor-capas-lista li:hover { background: var(--bg); }
.editor-capas-lista li.on { background: var(--primary-soft); font-weight: 600; }
.editor-capas-lista li.off .nombre, .editor-capas-lista li.off .mini { opacity: .45; }
.editor-capas-lista li.is-over { outline: 2px dashed var(--primary); outline-offset: -2px; }
.editor-capas-lista .mini { width: 32px; height: 32px; border-radius: 6px; background: var(--canvas); object-fit: contain; }
/* el nombre en dos renglones: «Molino…» no dice cuál de los dos molinos de un correo compuesto es */
.editor-capas-lista .nombre { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; line-height: 1.2; }
.editor-capas-lista li .menu-pop { right: 0; left: auto; }
/* la capa elegida: texto, tipografía, color y tamaño, un campo debajo del otro */
.editor-capas-sel { margin: var(--space-150) 0; padding-top: var(--space-150); border-top: 1px solid var(--border); }
.editor-capas-sel h3 { margin: 0 0 var(--space-100); }
.editor-capas-sel label:not(.choice-pill) { display: block; font-size: var(--fs-meta); margin: var(--space-100) 0 var(--space-050); } /* las pastillas del segmentado son de la casa */
.editor-capas-sel input[type="text"], .editor-capas-sel select { width: 100%; }
.editor-capas-sel input[type="range"] { width: 100%; }

/* Las retenciones que practicó el cliente (ficha de la venta, 2026-11-03):
   cada casilla a la derecha y del MISMO ancho, la fecha en su fila y el
   botón al pie, en la columna de las casillas. */
.tabla-retenciones td.num { width: 1%; min-width: calc(var(--space-400) * 6); }
.tabla-retenciones td.num input { width: 100%; text-align: right; }
.tabla-retenciones tfoot td { border-top: 0; padding-top: var(--space-150); }
/* La retención de cada línea de compra: la casilla del ancho de la de la
   cuenta y la etiqueta «Sugerida» a su derecha, en el mismo renglón. */
.celda-sugerida { display: flex; align-items: center; gap: var(--space-100); flex-wrap: nowrap; }
.celda-sugerida select { flex: none; width: calc(var(--space-400) * 5); }
.celda-sugerida .pill { flex: none; white-space: nowrap; }

/* LOS RENGLONES-ENLACE (`.fact-rows-enlaces`, 2026-11-06): una franja de
   `.fact-row` donde cada renglón entero lleva a otra pantalla —Contabilidad
   del período de nómina—. Sin subrayado; al pasar, el nombre toma la marca. */
.fact-rows-enlaces > a.fact-row { color: inherit; text-decoration: none; }
/* Todos del mismo ancho (Santiago, 2026-11-06: «deben ser proporcionales»):
   el ancho no lo decide lo largo de la frase. */
.fact-rows-franja.fact-rows-enlaces > .fact-row { flex: 1 1 0; }
.fact-rows-enlaces > a.fact-row:hover .fact-row-label,
.fact-rows-enlaces > a.fact-row:focus-visible .fact-row-label { color: var(--primary); }

/* SOLO MES (`.period--meses`, 2026-11-06): las opciones son enlaces y no
   radios; la elegida lleva `.is-active` y se pinta como la marcada. Sin
   columna del calendario, la lista no lleva filete a la derecha y se
   desplaza por dentro si hay muchos meses. */
.period--meses .period-presets { border-right: 0; max-height: 360px; }
.period--meses a.period-option { color: inherit; text-decoration: none; }
.period--meses a.period-option:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.period--meses .period-option.is-active { background: var(--primary-soft); color: var(--primary-dark); font-weight: 600; }
.period--meses .period-option.is-active::after {
    content: ""; flex: none; width: 5px; height: 10px; margin: calc(-1 * var(--space-050)) var(--space-050) 0 0;
    border: solid currentColor; border-width: 0 2px 2px 0; transform: rotate(45deg);
}

/* ── La escala del teléfono (2026-11-06) ─────────────────────────────────
   «Todo se ve diminuto»: el teléfono leía los tamaños del escritorio, 13 px de
   cuerpo pensados para mouse y pantalla grande. Se reasignan los pasos y crece
   toda la app desde aquí; una pantalla NUNCA pone tamaños propios para el
   teléfono. `--fs-hero` no está: es solo el saludo y tiene sus cortes.

   `--toque` es el otro lado de lo mismo: lo que se toca no baja de 48 px. Va
   en las piezas de la casa. Los `--space-*` no crecen: se comerían el ancho. */
@media (max-width: 720px) {
    :root {
        --fs-number: 34px;
        --fs-page: 26px;
        --fs-stat: 22px;
        --fs-glyph: 20px;
        --fs-columna: 18px;
        --fs-section: 17px;
        --fs-body: 16px;
        --fs-small: 15px;
        --fs-label: 14px;
        --fs-meta: 13px;
        --fs-badge: 12px;
    }
    .btn, .nav-item, .menu-item, .home-chip,
    .menu > summary, .colmenu > summary,
    input:not([type="checkbox"], [type="radio"], [type="range"], [type="file"], [type="color"]),
    select { min-height: var(--toque); }
    /* El de un glifo crece parejo: si no, el mínimo lo vuelve un óvalo. */
    .btn.btn-icon { min-width: 44px; min-height: 44px; }
}

/* ── Las pestañas del marco (2026-11-06) ─────────────────────────────────
   En el teléfono se navega con el pulgar: la pieza de siempre, incluida por
   el marco con `--casa`. Una cápsula de íconos entre dos burbujas, Buscar y el
   Asistente. La hamburguesa abre el mismo menú, oscuro como la barra de arriba:
   la pantalla se corre al lado y las pestañas se van. Entre 721 y 767 px no hay
   pestañas y sigue la de arriba. */
@media (max-width: 720px) {
    .pestanas-abajo__una { cursor: pointer; }
    .pestanas-abajo__capsula { display: contents; }
    /* La paleta en el teléfono: sin teclas que anunciar, y al quedar corto el
       renglón cede la pista y no el nombre, que es lo que se busca. */
    .paleta-kbd, .paleta-pie { display: none; }
    .search-label { flex: none; max-width: 70%; }
    .search-meta { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    .drawer-toggle { display: none; }
    .topbar-left { padding-left: 0; }
    /* El Asistente se abre desde su pestaña. Su panel no nace abierto aunque
       la cookie lo diga: tapaba la pantalla al entrar. Esto lo esconde antes
       del primer cuadro; `panel.js` hace el resto y pone `panel-tocado`
       cuando la persona pregunta. */
    .side-panel-form, .side-panel-form + .topbar-sep { display: none; }
    body.panel-open:not(.panel-tocado):not(.panel-filtros) .side-panel {
        transform: translateX(100%); visibility: hidden;
    }
    /* Abierto tapa las pestañas, y este botón es lo único que lo cierra. */
    body.panel-open.panel-tocado:not(.panel-filtros) .side-panel-form { display: inline-flex; }

    /* El menú: los mismos renglones y permisos, oscuros en cualquier tema. */
    .sidebar {
        color-scheme: dark;
        --text: #ece9f7; --text-light: #aaa5c0; --text-faint: #7d7894;
        --bg: #1b1827; --border: #2d2940; --primary-soft: #2a2248; --primary-dark: #b7a4ff;
        width: var(--cajon) !important; border-radius: 0;
        bottom: 0; height: auto; max-height: none;
        padding-top: 0; padding-bottom: 0;
        background: var(--canvas); box-shadow: none;
    }
    .sidebar .sidebar-label-row, .nav-fila-config { display: none !important; }
    .menu-buscar {
        position: sticky; top: 0; z-index: 1;
        display: flex; align-items: center; gap: var(--space-100);
        min-height: var(--toque); margin-bottom: var(--space-150); padding: 0 var(--space-150);
        border-radius: var(--radius);
        background: var(--bg); color: var(--text-light);
        box-shadow: 0 0 0 var(--space-100) var(--canvas);
        font-size: var(--fs-body); text-decoration: none;
    }
    .menu-buscar svg { width: var(--space-250); height: var(--space-250); }
    /* Configuraciones y la X flotan al pie, como las pestañas que reemplazan. */
    .menu-cierre {
        position: sticky; z-index: 1;
        bottom: calc(var(--space-100) + env(safe-area-inset-bottom));
        display: flex; justify-content: center; gap: var(--space-100);
        margin-top: var(--space-300); padding-bottom: calc(var(--space-100) + env(safe-area-inset-bottom));
        pointer-events: none;
    }
    .menu-cierre__ajustes, .menu-cierre__x {
        display: inline-flex; align-items: center; justify-content: center; gap: var(--space-100);
        height: var(--toque);
        border: 1px solid color-mix(in srgb, #fff 14%, transparent); border-radius: 999px;
        background: color-mix(in srgb, var(--bg) 70%, transparent);
        -webkit-backdrop-filter: blur(22px) saturate(160%);
        backdrop-filter: blur(22px) saturate(160%);
        box-shadow: 0 10px 24px rgb(0 0 0 / 40%);
        color: var(--text); font-size: var(--fs-body); font-weight: 500; text-decoration: none;
        pointer-events: auto; cursor: pointer; -webkit-tap-highlight-color: transparent;
    }
    .menu-cierre__ajustes { padding: 0 var(--space-250); }
    .menu-cierre__x { flex: none; box-sizing: border-box; width: var(--toque); }
    .menu-cierre svg { width: var(--space-250); height: var(--space-250); }
    /* La pantalla se corre lo que mide el menú y queda asomada: tocarla cierra. */
    .content { transition: transform .22s ease; }
    html:has(.drawer-toggle:checked) { overflow: hidden; }
    html:has(.drawer-toggle:checked) .content { transform: translateX(var(--cajon)); }
    html:has(.drawer-toggle:checked) .pestanas-abajo--casa { display: none; }

    /* La barra no se ve: reparte una cápsula y sus dos burbujas, y entre ellas
       se toca la pantalla. */
    .pestanas-abajo--casa {
        left: var(--space-150); right: var(--space-150);
        bottom: calc(var(--space-100) + env(safe-area-inset-bottom));
        display: flex; gap: var(--space-100);
        height: var(--pestanas-casa-h); padding: 0;
        border: 0; background: none;
        pointer-events: none;
    }
    /* Vidrio líquido: flota y deja ver lo que pasa debajo. El `backdrop-filter`
       aquí no atrapa ningún `fixed`: adentro no hay. */
    .pestanas-abajo--casa .pestanas-abajo__capsula,
    .pestanas-abajo--casa .pestanas-abajo__una--suelta {
        pointer-events: auto;
        border: 1px solid color-mix(in srgb, #fff 55%, transparent);
        border-radius: 999px;
        background: color-mix(in srgb, var(--card-bg) 42%, transparent);
        -webkit-backdrop-filter: blur(22px) saturate(190%);
        backdrop-filter: blur(22px) saturate(190%);
        box-shadow: 0 12px 32px rgb(20 16 40 / 18%),
                    inset 0 1px 0 color-mix(in srgb, #fff 70%, transparent),
                    inset 0 -1px 0 color-mix(in srgb, #fff 25%, transparent);
    }
    .pestanas-abajo--casa .pestanas-abajo__capsula {
        flex: 1; min-width: 0; display: flex; padding: var(--space-050);
    }
    .pestanas-abajo--casa .pestanas-abajo__una--suelta {
        flex: none; width: var(--pestanas-casa-h); box-sizing: border-box;
    }
    /* Solo íconos: el nombre queda para el lector de pantalla. */
    .pestanas-abajo--casa .pestanas-abajo__rotulo {
        position: absolute; width: 1px; height: 1px; overflow: hidden;
        clip-path: inset(50%); white-space: nowrap;
    }
    .pestanas-abajo--casa .pestanas-abajo__una {
        border-radius: 999px; color: var(--text-light); flex: 1; min-width: 0;
    }
    .pestanas-abajo--casa .pestanas-abajo__una--suelta { flex: none; }
    .pestanas-abajo--casa .pestanas-abajo__una[aria-current="page"] {
        background: color-mix(in srgb, var(--primary) 14%, transparent);
        color: var(--primary);
    }
    .pestanas-abajo--casa .pestanas-abajo__icono,
    .pestanas-abajo--casa .pestanas-abajo__una[aria-current="page"] .pestanas-abajo__icono {
        width: auto; height: auto; background: none;
    }
    .pestanas-abajo--casa .pestanas-abajo__icono svg {
        width: var(--space-300); height: var(--space-300); stroke-width: 2;
    }
    /* La encendida se rellena; `.relleno` y `.detalle` son de los dibujos de
       la barra (`pestana-*` en `icons.py`). */
    .pestanas-abajo--casa [aria-current="page"] svg .relleno { fill: currentColor; }
    .pestanas-abajo--casa [aria-current="page"] svg .detalle { stroke: var(--card-bg); }
    .pestanas-abajo--casa .pestanas-abajo__cuenta { top: calc(-1 * var(--space-100)); left: var(--space-150); }
    /* El personaje del Asistente, vivo cuando la empresa tiene 3D. Sin 3D es
       un dibujo blanco: sobre vidrio no se vería, y por eso lleva su fondo. */
    .pestanas-abajo__icono .pestanas-abajo__compi { flex: none; width: var(--space-500); height: var(--space-500); }
    html:not([data-personajes="3d"]) .pestanas-abajo__icono svg.pestanas-abajo__compi { border-radius: 50%; background: var(--primary); }

    /* Que el último renglón no quede debajo. */
    html:has(.pestanas-abajo--casa) .content {
        padding-bottom: calc(var(--pestanas-casa-h) + var(--space-400) + env(safe-area-inset-bottom));
    }
    html:has(.pestanas-abajo--casa) .hilo-pagina .hilo {
        height: calc(100dvh - var(--topbar) - var(--pestanas-casa-h) - var(--space-800) - env(safe-area-inset-bottom));
    }
    /* Lo de borde a borde pinta hasta abajo: con el relleno, el `.content`
       transparente dejaba una franja del lienzo negro. */
    html:has(.pestanas-abajo--casa) .content[data-borde-a-borde] { padding-bottom: 0; }
    /* El Asistente a todo el ancho: el relleno del contenido, el de la tarjeta
       y el del hilo se sumaban y dejaban 270 px para escribir. */
    html:has(.pestanas-abajo--casa) .content:has(> .hilo-ancho) { padding: 0; }
    .card.hilo-pagina.hilo-ancho { padding: 0; }
    html:has(.pestanas-abajo--casa) .hilo-pagina.hilo-ancho .hilo {
        height: calc(100dvh - var(--topbar) - var(--pestanas-casa-h) - var(--space-200) - env(safe-area-inset-bottom));
    }
    .hilo-ancho .hilo-cabecera { margin: var(--space-100) var(--space-100) 0; }
    .hilo-ancho .hilo-mensajes { padding-inline: var(--space-150); }
    .hilo-ancho .hilo-composer { padding: 0 var(--space-100) var(--space-100); }
    /* `.content` abre un contexto de apilamiento (`container-type`): una hoja,
       con todo su `z-index`, nunca pasa por encima de la barra. Se va la barra
       mientras la hoja está abierta. */
    html:has(.menu[open]) .pestanas-abajo--casa,
    html:has(.colmenu[open]) .pestanas-abajo--casa { display: none; }

    /* El título del calendario traía 34 px fijos, y con la letra del teléfono
       se cortaba el mes. */
    html .flatpickr-months .flatpickr-month { height: var(--toque); overflow: visible; }
    html .flatpickr-months .flatpickr-prev-month,
    html .flatpickr-months .flatpickr-next-month {
        display: flex; align-items: center; height: var(--toque); padding-top: 0; padding-bottom: 0;
    }
    html .flatpickr-current-month {
        display: flex; align-items: center; justify-content: center; gap: var(--space-050);
        height: var(--toque); padding: 0; font-size: var(--fs-section);
    }

    html:has(.content[data-pantalla-completa]) .pestanas-abajo--casa { display: none; }

    /* Las tarjetas de cifras: baldosa y rótulo en un renglón, el número debajo
       y la nota al pie. Sin la baldosa encima y el rótulo partido dejan de ser
       torres, y `tira.js` baja la cifra que no cabe. Con `--desliza` son una
       fila que se pasa con el dedo: dos a la vista y la tercera asoma. */
    .equipo-cifras { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-100); }
    .equipo-cifras--desliza { padding: var(--space-025); }
    .equipo-cifras--desliza > .card { flex: 0 0 46%; }
    .panel-vidrio:has(> .equipo-cifras) { padding: var(--space-100); }
    .equipo-cifras > .card {
        display: grid; grid-template-columns: auto minmax(0, 1fr); align-content: start; align-items: center;
        gap: var(--space-050) var(--space-100); padding: var(--space-150);
    }
    .equipo-cifras:not(.equipo-cifras--desliza) > .card:last-child:nth-child(odd) { grid-column: 1 / -1; }
    .equipo-cifras > .card > .tile { align-self: center; width: var(--space-300); height: var(--space-300); border-radius: var(--radius-sm); }
    .equipo-cifras > .card > .tile svg { width: var(--space-200); height: var(--space-200); }
    .equipo-cifras > .card > div:last-child { display: contents; }
    .equipo-cifras > .card > div:last-child > * { grid-column: 1 / -1; }
    .equipo-cifras > .card > div:last-child > .card-hint:first-child {
        grid-column: 2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-label);
    }
    .equipo-cifras > .card .equipo-cifra { font-size: var(--fs-stat); white-space: nowrap; }
    .equipo-cifras > .card .equipo-cifra[data-larga="1"] { font-size: var(--fs-section); }
    .equipo-cifras > .card .equipo-cifra[data-larga="2"] { font-size: var(--fs-small); }
    .equipo-cifras > .card > div:last-child > span.card-hint {
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--fs-meta);
    }

    /* Los títulos: un nombre largo va en dos renglones como mucho, y entero en
       la ficha. La cabecera de tarjeta es un rótulo, no otro título: a 17 px en
       versalitas gritaba tanto como el de la pantalla. */
    .page-banner h1 {
        display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
        overflow: hidden; font-size: var(--fs-columna); line-height: 1.25;
    }
    /* La pastilla baja a su renglón, a plomo con el título. Con ella no hay
       corte: el texto va suelto en el `h1`, y el corte se llevaría la pastilla. */
    .page-banner h1:has(.banner-badges) { display: block; overflow: visible; }
    .page-banner h1 .banner-badges {
        display: flex; margin: var(--space-050) 0 0 calc(-1 * var(--space-100));
    }
    /* El relleno de la primera sale hacia la izquierda: lo que queda a plomo con
       el título es su texto, no su borde. */
    .page-banner h1 .banner-badges > * {
        padding: var(--space-025) var(--space-100);
        font-size: var(--fs-meta); line-height: 1.3;
    }
    .page-banner .banner-avatar, .page-banner .tile-banner {
        flex: none; width: var(--space-500); height: var(--space-500);
    }
    /* Los tres puntos: el mínimo de `.menu > summary` los estiraba a lo alto. */
    .menu > .menu-trigger { min-width: 44px; min-height: 44px; }
    .card-header h2, .bulkbar h2 { font-size: var(--fs-label); font-weight: 700; letter-spacing: .06em; }

    /* El período y su comparación, en una línea y dos columnas iguales: el
       rótulo con su flecha arriba, y abajo la fecha corta a todo el ancho. Sin
       la baldosa del glifo cabe «Últimos 365 días» sin cortarse. */
    .period { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-050); }
    .period-menu { min-width: 0; }
    .period-trigger {
        display: grid; grid-template-columns: minmax(0, 1fr) auto;
        column-gap: var(--space-050); padding-inline: var(--space-075);
    }
    .period-trigger-glyph, .period-trigger-dates { display: none; }
    .period-trigger-text { display: contents; }
    .period-trigger-label { grid-area: 1 / 1; overflow: hidden; text-overflow: ellipsis; font-size: var(--fs-small); }
    .period-trigger-chevron { grid-area: 1 / 2; width: 14px; height: 14px; }
    .period-trigger-dates--corta {
        display: block; grid-column: 1 / -1;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
        color: var(--text-light); font-size: var(--fs-meta);
    }
    /* Las cifras: el rótulo en minúscula cabe en una línea, todas arrancan a la
       misma altura, y la cifra larga baja de tamaño con `tira.js`. */
    .home-metric { justify-content: flex-start; }
    .home-metric-label { font-size: var(--fs-meta); letter-spacing: 0; text-transform: none; }
    .home-metric-row { flex-wrap: wrap; row-gap: var(--space-025); }
    .home-metric-value { white-space: nowrap; }
    .home-metric-value[data-larga="1"] { font-size: var(--fs-section); }
    .home-metric-value[data-larga="2"] { font-size: var(--fs-small); }

    /* Las tarjetas de un vacío, una debajo de la otra y del mismo ancho. */
    .empty-tarjetas:not(.empty-tarjetas--separadas) {
        flex-direction: column; align-items: stretch;
        width: min(100%, 300px); margin-inline: auto;
    }

    /* Las pestañas laterales en fila caben a lo ancho, sin deslizarse. */
    .tabs--lateral .tab { flex: 1 1 0; min-width: 0; justify-content: center; gap: var(--space-075); padding-inline: var(--space-050); }
    .tabs--lateral .tab .tile { flex: none; width: var(--space-300); height: var(--space-300); }

    /* La tarjeta de segundo nivel: una fila que se desliza bajo la barra de
       arriba y sobre su negro, en todas sus pantallas. En tarjeta llenaba el
       teléfono y dejaba la página debajo. `sidebar.js` centra la encendida. */
    .shell:has(> .menu-lateral) { flex-direction: column; }
    .shell > .menu-lateral {
        flex: none; display: flex; gap: var(--space-100);
        width: auto; height: auto; position: static; margin: 0;
        padding: var(--space-050) var(--space-150) var(--space-150);
        border-radius: 0; background: none; box-shadow: none;
        overflow-x: auto; overscroll-behavior-x: contain;
    }
    .shell > .menu-lateral .sidebar-label { display: none; }
    .shell > .menu-lateral .nav-list,
    .shell > .menu-lateral .nav-list > li { display: contents; }
    .shell > .menu-lateral .nav-item {
        flex: none; min-height: var(--space-500); margin: 0; padding: 0 var(--space-200);
        border-radius: 999px; white-space: nowrap;
        background: color-mix(in srgb, #fff 9%, transparent); color: var(--banner-sub);
    }
    .shell > .menu-lateral .nav-item.active { background: var(--primary); color: #fff; }

    /* Las secciones de una pantalla: la misma pieza en su sitio, un segmentado
       que se queda arriba al desplazar. La barra de abajo es una, la del marco. */
    .pestanas-abajo--en-pantalla {
        position: sticky; top: var(--topbar); bottom: auto; z-index: 6;
        display: block; height: auto;
        padding: var(--space-100) var(--space-150);
        border: 0; background: var(--card-bg);
    }
    .pestanas-abajo--en-pantalla .pestanas-abajo__capsula {
        display: flex; padding: var(--space-050);
        border-radius: 999px; background: var(--bg);
    }
    .pestanas-abajo--en-pantalla .pestanas-abajo__una {
        flex: 1; min-width: 0; min-height: var(--space-500);
        flex-direction: row; gap: var(--space-050);
        border-radius: 999px; font-size: var(--fs-small);
    }
    .pestanas-abajo--en-pantalla .pestanas-abajo__una[aria-current="page"] {
        background: var(--card-bg); color: var(--text); box-shadow: var(--shadow);
    }
    .pestanas-abajo--en-pantalla .pestanas-abajo__icono {
        order: 1; width: auto; height: auto; background: none !important;
    }
    .pestanas-abajo--en-pantalla .pestanas-abajo__icono svg { display: none; }
    .pestanas-abajo--en-pantalla .pestanas-abajo__cuenta { position: static; border: 0; }
}
/* ── La tabla que en el teléfono son tarjetas (`.table-tarjetas`) ─────────
   Una lista en el celular no se lee desplazándose de lado (Renzo, 2026-11-06).
   Cada fila es una tarjeta que se toca entera, y cada celda dice qué papel
   juega con `data-celda`: `fecha`, `titulo`, `total`, `pastilla`, `quien` o
   `dato`. Es de quien tiene columnas por usuario: la que está escondida no
   sale y las demás se acomodan. A diferencia de `.table-apilada`, que muestra
   todo con su rótulo, ésta decide qué pesa.

   Pedidos los escribe a mano. Las demás listas los reciben de
   `tabla-tarjetas.js`, que suma `accion`, `vacia`, `suelta` y `data-label`. */
@media (max-width: 720px) {
    .table.table-tarjetas, .table.table-tarjetas tbody { display: block; }
    .table.table-tarjetas thead {
        position: absolute; width: 1px; height: 1px; margin: -1px;
        overflow: hidden; clip-path: inset(50%);
    }
    .table.table-tarjetas tbody tr {
        position: relative; display: flex; flex-wrap: wrap; align-items: center;
        gap: var(--space-075) var(--space-100);
        padding: var(--space-200);
        border-bottom: 1px solid var(--table-line);
    }
    .table.table-tarjetas tbody tr:last-child { border-bottom: 0; }
    .table.table-tarjetas tbody tr:has(> .cell-check) { padding-left: var(--space-600); }
    .table.table-tarjetas tbody td,
    .table.table-tarjetas tbody td:first-child,
    .table.table-tarjetas tbody td:last-child {
        order: 6; flex: 0 0 100%; display: block; min-width: 0; max-width: none;
        padding: 0; border: 0; background: none;
        text-align: left; white-space: normal;
    }
    .table.table-tarjetas tbody td.cell-check {
        position: absolute; z-index: 1; top: var(--space-200); left: var(--space-200);
        flex: none; width: auto;
    }
    .table.table-tarjetas tbody td[data-celda="fecha"] {
        order: 1; color: var(--text-light); font-size: var(--fs-small);
    }
    .table.table-tarjetas tbody td[data-celda="titulo"] {
        order: 2; flex: 1 1 0; font-size: var(--fs-section); font-weight: 700;
    }
    .table.table-tarjetas td[data-celda="titulo"] .tile { display: none; }
    /* Toda la tarjeta lleva a la ficha. */
    .table.table-tarjetas td[data-celda="titulo"] a::after {
        content: ''; position: absolute; inset: 0;
    }
    .table.table-tarjetas tbody td[data-celda="total"] {
        order: 3; flex: none; color: var(--text); font-size: var(--fs-section); font-weight: 700;
    }
    /* El corte de renglón: lo que sigue baja, quepa o no junto al total. */
    .table.table-tarjetas tbody tr::after { content: ''; order: 3; flex: 0 0 100%; height: 0; }
    .table.table-tarjetas tbody td[data-celda="pastilla"] { order: 4; flex: none; }
    .table.table-tarjetas tbody td[data-celda="quien"] { order: 5; color: var(--text); }
    .table.table-tarjetas tbody td[data-celda="dato"] {
        flex: none; color: var(--text-light); font-size: var(--fs-small);
    }
    .table.table-tarjetas tbody td:has(> .cell-raya),
    .table.table-tarjetas tbody td[data-celda="vacia"] { display: none; }
    /* Un dato con su rótulo: el rótulo a la izquierda y el valor a la derecha.
       `float` y no `flex`: el valor puede ser varias cosas y se queda junto. */
    .table.table-tarjetas tbody td[data-celda="dato"][data-label] {
        flex: 0 0 100%; color: var(--text); text-align: right;
    }
    .table.table-tarjetas tbody td[data-label]::before {
        content: attr(data-label); float: left;
        margin-right: var(--space-150); color: var(--text-light);
    }
    .table.table-tarjetas tbody td[data-celda="suelta"] { order: 2; }
    .table.table-tarjetas tbody td[data-celda="accion"] {
        order: 7; display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-100);
    }
    /* Lo que se toca adentro queda encima del enlace que cubre la tarjeta. */
    .table.table-tarjetas tbody td:not([data-celda="titulo"]) :is(a, button, summary, label, input) {
        position: relative; z-index: 1;
    }
    .table.table-tarjetas tfoot { display: block; }
    .table.table-tarjetas tfoot tr {
        display: flex; justify-content: space-between; gap: var(--space-150);
        padding: var(--space-150) var(--space-200);
    }
    .table.table-tarjetas tfoot td { padding: 0; border: 0; }
    /* Encima de la tarjeta, una línea: cuántos y Columnas. */
    .card-table:has(.table-tarjetas) > .card-header { flex-wrap: nowrap; }
    .card-table:has(.table-tarjetas) > .card-header .card-hint,
    .card-table:has(.table-tarjetas) > .card-header .tile { display: none; }
}

/* ── El Inicio en el teléfono (2026-11-06) ───────────────────────────────
   Primero quién eres y qué buscas; las cifras y lo pendiente vienen después.
   Todo a punta de `order`: el escritorio no se entera. `display: contents`
   suelta los hijos del escenario para ordenarlos junto a sus hermanos, y por
   eso sobran las esferas, que necesitan esa caja. */
@media (max-width: 720px) {
    .content:has(> .home-stage) { display: flex; flex-direction: column; gap: var(--space-200); }
    .home-stage { display: contents; }
    .home-orbs { display: none; }
    .content:has(> .home-stage) > .messages { order: -3; }
    .home-greeting-title {
        order: -2; margin: 0;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }
    .home-gratitude {
        order: -1; margin: calc(-1 * var(--space-150)) 0 0;
        display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
    }
    .home-search { order: 1; width: 100%; margin: 0; }
    .home-metrics { order: 2; }
    .home-chips { order: 3; margin: 0; }
    .home-feed-card { order: 4; }
    /* Toda la casilla lleva a su cifra: el enlace del número medía 22 px. */
    .home-metric { position: relative; }
    .home-metric-value a::after { content: ''; position: absolute; inset: 0; }
    /* El glifo a la izquierda de las dos líneas, y la fecha bajo el texto. */
    .home-feed-item {
        display: grid; grid-template-columns: auto 1fr;
        column-gap: var(--space-150); row-gap: var(--space-025);
    }
    .home-feed-glyph { grid-row: span 2; }
    .home-feed-when { padding-left: 0; }
}

/* Con el teclado abierto, las pestañas estorban: flotan sobre él en Android
   y le roban alto a lo que se escribe. Basta el foco, sin escuchar nada, y
   solo el de lo que abre teclado: un botón o una lista enfocados no lo hacen
   y las pestañas se irían hasta tocar otra cosa. */
@media (max-width: 720px) and (pointer: coarse) {
    html:has(:is(input:not([type="checkbox"], [type="radio"], [type="file"], [type="submit"], [type="button"], [type="range"], [type="color"]), textarea, [contenteditable="true"]):focus) .pestanas-abajo--casa { display: none; }
    html:has(:is(input:not([type="checkbox"], [type="radio"], [type="file"], [type="submit"], [type="button"], [type="range"], [type="color"]), textarea, [contenteditable="true"]):focus) .hilo-pagina .hilo {
        height: calc(100dvh - var(--topbar) - 48px);
    }
}
