/* ============================================================
   base.css — Componentes A.4 del VISUALES v3 (las 4 interfaces)
   Requiere tokens.css. Todo usa propiedades lógicas → RTL (dir="rtl")
   espeja solo. Estados universales: foco, hover, activo, presionado,
   deshabilitado. Zona táctil mínima 44×44 en todo lo pulsable.
   ============================================================ */

/* ---------- Reset mínimo y base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font);
  font-size: var(--fs-body); line-height: var(--lh-body);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
h1 { font-size: var(--fs-h1); line-height: var(--lh-h1); font-weight: var(--fw-h1); margin: 0; }
h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: var(--fw-h2); margin: 0; }
h3 { font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-h3); margin: 0; }
p  { margin: 0 0 var(--sp-3); }
a  { color: var(--brand-texto); }
small, .texto-small { font-size: var(--fs-small); line-height: var(--lh-small); }
.texto-2 { color: var(--text-2); }
.texto-caption { font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption); }
img, svg { max-width: 100%; }
[hidden] { display: none !important; }

/* Solo para lectores de pantalla */
.solo-lector {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
  /* REPARADOR 31/08/2026 · `width` es solo un MÍNIMO para una tabla: con el
     dibujo automático, una <table> se estira hasta que cabe su contenido y se
     salía de este cuadradito de 1×1 (`clip` recorta lo que se PINTA, no lo que
     OCUPA). La tabla invisible que acompaña a cada gráfica del panel —la que
     lee en voz alta la misma serie— medía 344 px dentro de su caja de 1 px y
     empujaba la página 16 px a lo ancho: en un móvil de 360 px el panel del
     admin obligaba a arrastrar de lado (CP-520: ninguna pantalla debe pedir
     desplazamiento horizontal). Con el tope y el dibujo fijo, la tabla se queda
     dentro y el lector de pantalla la sigue leyendo igual. */
  max-width: 1px;
  /* MÓVIL · 06/09/2026 · EL PÍXEL QUE SOBRABA. Es `position: absolute` pero sin
     coordenadas, así que se queda donde le tocaría estar: al final del <body>.
     Ese 1 px cae JUSTO DEBAJO del último elemento, y en una pantalla que mide
     exactamente lo que la ventana el documento pasa a medir 1 px más y el
     teléfono la da por desplazable (MEDIDO en la bandeja del operador después
     de acotarla: documento 813 px, ventana 812, y el único elemento que
     asomaba era este párrafo). Con el margen negativo el píxel se solapa con
     lo que hay encima y deja de contar. No cambia nada de lo que se ve: la
     caja sigue recortada a 1×1 y el lector de pantalla la sigue leyendo. */
  margin: -1px;
}
table.solo-lector { table-layout: fixed; }

/* ---------- B12 · «Saltar al contenido» (04/09/2026) ----------
   El PRIMER tabulador de cualquier pantalla. Invisible hasta que se enfoca:
   entonces aparece arriba del todo, dentro del área visible, con el mismo
   anillo de foco que todo lo demás. Quien navega con teclado o con lector
   no tiene que recorrer la cabecera entera en cada pantalla.
   Contraste: texto --brand-contrast sobre --brand (5.10:1 teal · 6.94:1 azul). */
.saltar-contenido {
  position: fixed;
  inset-block-start: var(--sp-2);
  inset-inline-start: var(--sp-2);
  z-index: var(--z-avisos);
  display: inline-flex; align-items: center;
  min-block-size: var(--tactil);
  padding: 0 var(--sp-4);
  background: var(--brand); color: var(--brand-contrast);
  border-radius: var(--r-campo);
  font-size: var(--fs-body); font-weight: var(--fw-strong);
  line-height: var(--lh-body); text-decoration: none;
  /* (02/11/2026 · N-9) la sombra, solo cuando se ve (abajo): escondido, su y=-52..-8 dejaba asomar 20 px de sombra sobre la barra. */
  /* Fuera de la vista, pero SIN display:none (si no, no se puede enfocar). */
  transform: translateY(calc(-100% - var(--sp-4)));
  transition: transform var(--dur-rapida) var(--ease);
}
.saltar-contenido:focus, .saltar-contenido:focus-visible { transform: translateY(0); box-shadow: var(--sombra-flotante); }
/* El destino del salto lleva tabindex="-1" puesto por ui.js: no debe dibujar
   un anillo permanente, solo el del teclado. */
main:focus:not(:focus-visible), #pantalla:focus:not(:focus-visible) { outline: none; }

/* ---------- Estados universales (A.4) ---------- */
/* Foco de teclado: anillo 2px --focus, separado 2px del control. Nunca se quita. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-campo);
}
/* 07/10/2026 · CP-541 · lo que va de lado a lado DENTRO de algo que recorta (la barra lateral, las listas y el chat
   con desplazamiento, la fila de pestañas): su anillo va POR DENTRO, separado 2 px de su borde. Por fuera, el
   contenedor le cortaba dos o cuatro lados. MEDIDO ANTES (Chrome, Tab de verdad): .lateral-item, .panel-nav-item,
   .op-perfilbar-yo, .tab, la fila de la cola y la burbuja enfocada perdían el anillo por los lados (recortado por
   aside.panel-lateral, .op-cola-lista, .tabs, #mensajes); .nav-grupo-btn lo llevaba pegado a su borde (-2 px). */
.lateral-item:focus-visible, .panel-nav-item:focus-visible, .op-perfilbar-yo:focus-visible, .tabs .tab:focus-visible,
.fila-chat:focus-visible, .chat-mensajes .burbuja:focus-visible { outline-offset: -4px; }
/* 30/09/2026 · OLEADA 26 · CP-541 · y lo mismo en las pantallas que el recorrido de 4 pantallas no visitaba. MEDIDO
   ANTES (Chrome, cada clase de control de CADA pantalla enfocada como con el teclado): la fila de ajustes del
   cliente (Ajustes, Mis pagos) perdía el anillo por los lados (recortado por .pantalla-scroll), y en la tabla con
   desplazamiento del Saldo del operador la fila enfocable y el botón de la última columna lo perdían contra el borde
   de .tabla-envoltura. AHORA: anillo por dentro, 2 px separado de su borde. */
.fila-ajuste:focus-visible, .tabla-envoltura tr:focus-visible, .tabla-envoltura td .btn:focus-visible { outline-offset: -4px; }
/* Deshabilitado: 40% opacidad, sin sombra, texto --text-3 (su único uso) */
:disabled, [aria-disabled="true"] {
  opacity: .4; box-shadow: none !important; cursor: not-allowed;
  color: var(--text-3);
}

/* Zona táctil ampliada: dibujo pequeño, toque de 44×44 (A.3) */
.tactil { position: relative; }
/* FALLO (OP-1) · 05/09/2026 — LA CAPA NO SE PONE SI DENTRO HAY UN BOTÓN O UN
   ENLACE PROPIO. Es la MISMA salvaguarda que el `::after` general de más abajo
   ya lleva (`:not(:has(button, a[href], input, select, textarea))`) y que
   allí está explicada: «evita ponerlo en contenedores pulsables que llevan
   dentro otro botón (si no, la capa taparía el botón de dentro)». A `.tactil`
   se le había olvidado.
   QUÉ ROMPÍA, medido con `document.elementFromPoint` sobre el centro exacto del
   botón: las filas de la cola del operador se pintan
   `class="fila-chat op-fila tactil"` (operador/pantallas/bandeja.js:669) y las
   que están sin dueño llevan DENTRO el botón «Tomar». La capa de la fila
   —absoluta, medida en 361×126 px, todo el ancho y el alto de la fila— quedaba
   ENCIMA del botón (el botón es `position: relative` pero con `z-index: auto`,
   así que gana la capa, que va después). Resultado: el dedo, o el ratón, caía
   en la fila y ABRÍA la conversación en vez de tomarla. Nadie podía tomar una
   conversación desde la cola.
   Se deja fuera SOLO `button` y `a[href]`: los `<label class="tactil">` que
   envuelven una casilla (admin/pantallas/horarios.js:264 y :430) conservan sus
   44×44, que es justo para lo que se hizo esto. */
.tactil:not(:has(button, a[href]))::after {
  content: ""; position: absolute; inset: 50% auto auto 50%;
  translate: -50% -50%;
  min-width: var(--tactil); min-height: var(--tactil);
  width: 100%; height: 100%;
}

/* ---------- Botones (rework 10/08/2026 · PULIDO.md §3) ----------
   Tres variantes con estados completos (reposo/hover/activo/foco/
   deshabilitado): primario (relleno marca), secundario (borde) y
   fantasma (.btn-texto/.btn-fantasma, solo para acciones terciarias). */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--alto-boton);
  padding: 0 calc(var(--sp-4) * 1.35); /* holgura 35% (A.2): el control crece, no recorta */
  border: none; border-radius: var(--r-campo);
  background: var(--brand); color: var(--brand-contrast);
  font-family: inherit; font-size: var(--fs-body); font-weight: var(--fw-strong);
  line-height: var(--lh-body);
  cursor: pointer; text-decoration: none; user-select: none;
  box-shadow: var(--sombra-tarjeta);
  transition: background var(--dur-rapida) var(--ease),
              box-shadow var(--dur-rapida) var(--ease),
              transform var(--dur-rapida) var(--ease);
}
.btn:hover { background: var(--brand-dark); }
.btn:active { background: var(--brand-dark); transform: translateY(1px); box-shadow: none; }
/* Red de seguridad: variantes usadas SIN la clase base .btn (patrón
   frecuente en pantallas). Reciben el mismo esqueleto que .btn para no
   pintarse como botones por defecto del navegador (borde outset, radio 0,
   fuente del sistema). :where() = especificidad 0: .btn, las utilidades
   (.texto-caption, .tactil…) y los estilos en línea siguen mandando. */
:where(.btn-texto:not(.btn), .btn-fantasma:not(.btn), .btn-peligro:not(.btn), .btn-peligro-relleno:not(.btn)) {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
  min-height: var(--alto-boton);
  border: none; border-radius: var(--r-campo);
  font-family: inherit; font-size: var(--fs-body); font-weight: var(--fw-strong);
  line-height: var(--lh-body);
  cursor: pointer; text-decoration: none; user-select: none;
  transition: background var(--dur-rapida) var(--ease);
}
.btn-secundario {
  background: var(--surface); color: var(--brand-texto);
  border: var(--borde-grosor) solid var(--borde-control);  /* B12 · contorno de control: 3:1 */
  box-shadow: none;
}
.btn-secundario:hover { background: var(--surface-2); border-color: var(--brand-texto); }
.btn-secundario:active { background: var(--surface-2); }
.btn-texto, .btn-fantasma { background: transparent; color: var(--brand-texto); padding: 0 var(--sp-3); box-shadow: none; }
.btn-texto:hover, .btn-fantasma:hover { background: var(--surface-2); }
.btn-texto:active, .btn-fantasma:active { background: var(--surface-2); }
.btn-peligro { background: transparent; color: var(--danger); padding: 0 var(--sp-3); box-shadow: none; }
.btn-peligro:hover { background: var(--danger-tint); }
.btn-peligro-relleno { background: var(--danger); color: var(--danger-contrast); }
.btn-peligro-relleno:hover { filter: brightness(.94); }
.btn--compacto { min-height: 36px; padding: 0 var(--sp-3); font-size: var(--fs-small); }
.btn-icono {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tactil); height: var(--tactil);
  background: transparent; border: none; border-radius: var(--r-redondo);
  color: var(--text-2); cursor: pointer;
}
.btn-icono:hover { background: var(--surface-2); }

/* ---------- Campo de entrada (input) ---------- */
.campo { display: block; margin-block-end: var(--sp-4); }
.campo-etiqueta {
  display: block; font-size: var(--fs-small); line-height: var(--lh-small);
  margin-block-end: var(--sp-1); color: var(--text);
}
.campo-control {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: var(--alto-campo);
  padding-inline: var(--sp-3);
  background: var(--surface);
  border: var(--borde-grosor) solid var(--borde-control);  /* B12 · 1.4.11: el contorno ES el campo */
  border-radius: var(--r-campo);
}
.campo-control:focus-within { outline: 2px solid var(--focus); outline-offset: 2px; }
.campo-control input, .campo-control select, .campo-control textarea {
  flex: 1; min-width: 0; border: none; outline: none; background: transparent;
  font: inherit; color: var(--text); padding: 0;
}
.campo-control ::placeholder { color: var(--text-place); opacity: 1; }
.campo-ayuda { font-size: var(--fs-small); color: var(--text-2); margin-block-start: var(--sp-1); }
.campo.con-error .campo-control { border-color: var(--danger); }
.campo-error { /* icono + texto: nunca solo color */
  display: flex; align-items: center; gap: var(--sp-1);
  font-size: var(--fs-small); color: var(--danger); margin-block-start: var(--sp-1);
}

/* Selector / desplegable [▼]: mismo estilo que el input */
.selector select { appearance: none; cursor: pointer; padding-inline-end: var(--sp-6); }
.selector .campo-control { position: relative; }
.selector .campo-control::after {
  content: ""; position: absolute; inset-inline-end: var(--sp-3);
  width: 8px; height: 8px; pointer-events: none;
  border-inline-end: 1.5px solid var(--text-2);
  border-block-end: 1.5px solid var(--text-2);
  transform: rotate(45deg) translateY(-2px);
}
/* El valor elegido no se pega a la flecha ni la tapa (RTL incluido). */
.selector select { text-overflow: ellipsis; }

/* ---------- Barra de filtros (admin y operador · fallo B, 27/08/2026) -------
   Antes vivía solo en admin.css y el operador no tenía ninguna. Es compartida:
   etiqueta visible ENCIMA de cada desplegable, valor actual legible dentro,
   contador de resultados al final y, debajo, los chips de «filtros activos».
   En móvil los filtros se reparten a lo ancho y bajan de línea: nunca se
   cortan ni obligan a desplazarse en horizontal. */
.filtros-barra {
  display: flex; align-items: flex-end; gap: var(--sp-3);
  flex-wrap: wrap; margin-block-end: var(--sp-3);
}
.filtros-barra .campo, .filtros-barra .filtro-campo { margin-block-end: 0; }
.filtros-barra .campo-control { min-height: 40px; }
.filtros-barra .btn { min-height: 40px; }
/* Cada filtro crece hasta ocupar su hueco y nunca baja de 12rem: con 4 filtros
   caben 2 o 3 por línea en escritorio y 1 en un móvil de 390px. */
.filtros-barra .filtro-campo { flex: 1 1 12rem; min-inline-size: 0; max-inline-size: 22rem; }
/* 07/10/2026 · CP-528 · un campo suelto de la barra (el buscador) tampoco puede ser más ancho que la barra: con la
   letra al 200 % su caja de búsqueda (20 letras de ancho natural) medía 393 px en una barra de 358 y la PÁGINA
   se desplazaba 71 px a lo ancho (admin/clientes a 390 px, MEDIDO). AHORA se encoge hasta caber. */
.filtros-barra > .campo { min-inline-size: 0; max-inline-size: 100%; }
.filtros-barra .filtro-campo .campo-etiqueta { color: var(--text-2); }
/* El contador se va al final de la barra y se lee como un dato, no como un botón.
   Nunca se parte ni empuja a los desplegables: si no cabe, baja de línea. */
.filtros-cuenta {
  flex: 0 0 auto; white-space: nowrap;
  margin: 0 0 var(--sp-2); margin-inline-start: auto;
}

/* Chips de «filtros activos»: qué está puesto y cómo quitarlo de un toque. */
.filtros-activos {
  display: flex; align-items: center; gap: var(--sp-2);
  flex-wrap: wrap; margin-block-end: var(--sp-4);
}
.filtros-activos-titulo { font-size: var(--fs-small); color: var(--text-2); }
.chip-filtro { max-inline-size: 100%; }
.chip-filtro .chip-filtro-clave { color: var(--text-2); }
.chip-filtro .chip-filtro-clave::after { content: ":"; }
.chip-filtro .chip-filtro-valor {
  max-inline-size: 14rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Panel plegable de filtros: para columnas estrechas (la cola del operador
   mide 280–340px y cuatro desplegables a la vista se comían la lista).
   Si hay alguno puesto viene abierto y el número sale en la pestaña. */
.filtros-panel { margin-block-end: 0; }
.filtros-panel > summary {
  display: flex; align-items: center; gap: var(--sp-2);
  min-block-size: var(--tactil);
  font-size: var(--fs-small); color: var(--text);
}
.filtros-panel > summary:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.filtros-panel > summary .contador {
  min-inline-size: 20px; block-size: 20px; padding-inline: 6px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand); color: var(--brand-contrast);
  font-size: var(--fs-caption); font-weight: var(--fw-strong);
}
/* Dos por línea si la columna da de sí, uno si no: el panel abierto no se come
   la lista de conversaciones que hay debajo. */
.filtros-panel-campos {
  display: grid; gap: var(--sp-2) var(--sp-3); margin-block-start: var(--sp-2);
  grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
}
.filtros-panel-campos .filtro-campo { max-inline-size: none; }

@media (max-width: 767px) {
  .filtros-barra .filtro-campo { flex: 1 1 100%; max-inline-size: none; }
  .filtros-cuenta { margin-inline-start: 0; }
}

/* ---------- Interruptor (toggle): dibujo 44×26, toque 44×44 ---------- */
.toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tactil); height: var(--tactil);
  background: none; border: none; padding: 0; cursor: pointer;
}
.toggle-pista {
  width: 44px; height: 26px; border-radius: 13px;
  background: var(--toggle-off);
  position: relative; transition: background var(--dur) var(--ease);
}
.toggle-pista::after {  /* la bolita: cambia de lado — segunda señal, no solo color */
  content: ""; position: absolute; inset-block-start: 3px; inset-inline-start: 3px;
  width: 20px; height: 20px; border-radius: var(--r-redondo);
  background: var(--toggle-bola); box-shadow: 0 1px 2px rgba(0,0,0,.25);
  transition: inset-inline-start var(--dur) var(--ease);
}
.toggle[aria-checked="true"] .toggle-pista { background: var(--brand); }
.toggle[aria-checked="true"] .toggle-pista::after { inset-inline-start: 21px; }

/* Casilla y radio: marca --brand, toque 44 */
/* B12 · 24×24 (mínimo de WCAG 2.2 · 2.5.8) y `flex-shrink:0`: dentro de una
   fila flexible apretada la casilla se quedaba en 13 px de ancho y había que
   apuntar con el ratón. Donde la fila entera es la etiqueta (.opcion-fila,
   label.tactil) el blanco real siguen siendo los 44 px de la fila. */
input[type="checkbox"], input[type="radio"] {
  accent-color: var(--brand-texto);
  inline-size: 24px; block-size: 24px;
  min-inline-size: 24px; min-block-size: 24px;
  flex-shrink: 0;
  margin: 0;
}
/* ---- M-7 (06/09/2026 · tanda 3 de móvil) · LA CASILLA DE UNA TABLA, CON SUS
   44 px DE VERDAD ----
   ANTES: la casilla medía 24×24 con 10 px de margen alrededor. Los 24×24
   cumplen el mínimo de WCAG 2.2 (2.5.8) y el margen daba la separación, pero
   la regla del proyecto es 44×44 de zona de toque en TODO lo pulsable, y aquí
   no se cumplía: MEDIDO con `document.elementFromPoint` sobre la lista de
   Clientes a 375 px de ancho, el centro de la casilla devolvía
   `INPUT.cli-sel`, pero 14 px por encima, por debajo, a la izquierda o a la
   derecha devolvía `TD.cli-celda-sel` — la celda, que no marca nada (su único
   oyente detiene el clic para que marcar no abra la ficha). Hay 34 casillas
   así, y es justo la pantalla de las acciones sobre varios clientes a la vez.

   POR QUÉ NO VALÍA «AÑADIR `input` A LA LISTA DE LA CAPA ::after», que es lo
   que proponía el expediente: COMPROBADO en el navegador, un `<input>` es un
   elemento reemplazado y NO pinta `::before` ni `::after` mientras conserva su
   dibujo nativo; y probando a mano sobre la casilla real, ni `padding: 10px`
   ni `border: 10px solid transparent` la hacen crecer (medida antes y después:
   24×24 en los dos casos). Lo único que crece la caja es el tamaño explícito,
   y con el dibujo nativo eso agranda también el dibujo.

   ASÍ QUE SE DIBUJA. Con `appearance: none` la casilla pasa a ser una caja
   normal: se le dan los 44×44 de toque y el cuadrito de 24×24 que se ve lo
   pinta `::before` (y la marca ✓, `::after`). El dibujo se mantiene igual de
   grande que antes y con los mismos colores del sistema: contorno
   `--borde-control` (el token de contorno de control, 3:1, WCAG 1.4.11) y,
   marcada, el mismo par verificado que usa el botón principal —fondo `--brand`
   con la marca en `--brand-contrast`—, así que claro, oscuro y mix salen solos
   y no entra ni un color nuevo.
   Se mantiene el margen de 10 px: la separación entre dos casillas seguidas es
   la misma que había, y las zonas de 44 px no se solapan. */
td > input[type="checkbox"], th > input[type="checkbox"],
td > input[type="radio"], th > input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  /* La zona de toque, 44×44 de verdad. El margen negativo la deja ocupando el
     mismo hueco de 44 px que ocupaba antes la casilla con sus 10 de margen
     (24 + 10 + 10 = 44), así que la tabla no se mueve ni un píxel. */
  inline-size: var(--tactil); block-size: var(--tactil);
  min-inline-size: var(--tactil); min-block-size: var(--tactil);
  margin: 0; padding: 0; border: 0; background: none;
  cursor: pointer;
}
/* El cuadrito que se ve: los mismos 24×24 de antes. */
td > input[type="checkbox"]::before, th > input[type="checkbox"]::before,
td > input[type="radio"]::before, th > input[type="radio"]::before {
  content: "";
  inline-size: 24px; block-size: 24px;
  border: var(--borde-grosor) solid var(--borde-control);
  border-radius: var(--r-ancla);
  background: var(--surface);
}
td > input[type="radio"]::before, th > input[type="radio"]::before {
  border-radius: var(--r-redondo);
}
td > input[type="checkbox"]:checked::before,
th > input[type="checkbox"]:checked::before,
td > input[type="checkbox"]:indeterminate::before,
th > input[type="checkbox"]:indeterminate::before,
td > input[type="radio"]:checked::before, th > input[type="radio"]:checked::before {
  background: var(--brand); border-color: var(--brand);
}
/* La marca ✓ y la rayita del «algunos sí, algunos no».
   AQUÍ SÍ VAN PROPIEDADES FÍSICAS, y es a propósito: un ✓ no se lee de
   izquierda a derecha, es un dibujo. Con propiedades lógicas se espejaría en
   árabe y saldría al revés. */
td > input[type="checkbox"]::after, th > input[type="checkbox"]::after,
td > input[type="radio"]::after, th > input[type="radio"]::after {
  content: ""; position: absolute; display: none;
}
td > input[type="checkbox"]:checked::after, th > input[type="checkbox"]:checked::after {
  display: block;
  width: 5px; height: 10px; margin-bottom: 2px;
  border: 2px solid var(--brand-contrast);
  border-top: 0; border-left: 0;
  transform: rotate(45deg);
}
td > input[type="checkbox"]:indeterminate::after, th > input[type="checkbox"]:indeterminate::after {
  display: block;
  width: 12px; height: 0;
  border-top: 2px solid var(--brand-contrast);
}
td > input[type="radio"]:checked::after, th > input[type="radio"]:checked::after {
  display: block;
  width: 10px; height: 10px; border-radius: var(--r-redondo);
  background: var(--brand-contrast);
}
/* B12 · «El anillo de foco no se quita nunca»: si el control lleva
   `outline:none` porque el dibujo es otro recuadro, ESE recuadro pinta el
   anillo. Sin esto, el anillo abrazaría los 44 px invisibles y se vería un
   rectángulo enorme alrededor de un cuadrito de 24. */
td > input[type="checkbox"]:focus-visible, th > input[type="checkbox"]:focus-visible,
td > input[type="radio"]:focus-visible, th > input[type="radio"]:focus-visible {
  outline: none;
}
td > input[type="checkbox"]:focus-visible::before, th > input[type="checkbox"]:focus-visible::before,
td > input[type="radio"]:focus-visible::before, th > input[type="radio"]:focus-visible::before {
  outline: 2px solid var(--focus); outline-offset: 2px;
}
.opcion-fila { /* en listas de ajustes la fila ENTERA es pulsable */
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tactil); padding: var(--sp-2) var(--sp-4); cursor: pointer;
}
.opcion-fila:hover { background: var(--surface-2); }

/* ---------- Tarjeta ---------- */
.tarjeta {
  background: var(--surface); border-radius: var(--r-tarjeta);
  box-shadow: var(--sombra-tarjeta); padding: var(--sp-4);
}

/* ---------- Insignia (badge): SIEMPRE pareja tinte+texto de A.1 ---------- */
.insignia {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: 2px var(--sp-2); border-radius: 999px;
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption);
  white-space: nowrap;
  background: var(--brand-tint); color: var(--brand-tint-text);
}
.insignia--exito  { background: var(--success-tint); color: var(--success-tint-text); }
.insignia--aviso  { background: var(--warning-tint); color: var(--warning-tint-text); }
.insignia--peligro{ background: var(--danger-tint);  color: var(--danger-tint-text); }
.insignia--info   { background: var(--info-tint);    color: var(--info-tint-text); }
.insignia--ia     { background: var(--ai-tint);      color: var(--ai-tint-text); }

/* ---------- Avatar: 32/40/48 (96 en perfil) ---------- */
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: var(--r-redondo);
  overflow: hidden; background: var(--surface-2); flex-shrink: 0;
  color: var(--avatar-texto); font-weight: var(--fw-strong);
}
.avatar img, .avatar svg { width: 100%; height: 100%; display: block; }
.avatar--32 { width: 32px; height: 32px; font-size: var(--fs-small); }
.avatar--48 { width: 48px; height: 48px; }
.avatar--96 { width: 96px; height: 96px; font-size: var(--fs-h1); }
.avatar--elegido { outline: 2px solid var(--brand-texto); outline-offset: 2px; } /* + check: dos señales */

/* ---------- Menú flotante (⋮) ---------- */
.menu-flotante {
  position: absolute; z-index: var(--z-flotantes);
  min-width: 220px; padding: var(--sp-2) 0;
  background: var(--surface); border-radius: var(--r-tarjeta);
  box-shadow: var(--sombra-flotante);
}
.menu-flotante [role="menuitem"] {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tactil); padding-inline: var(--sp-4);
  width: 100%; border: none; background: none; font: inherit;
  color: var(--text); cursor: pointer; text-align: start;
}
.menu-flotante [role="menuitem"]:hover { background: var(--surface-2); }
.menu-flotante [role="menuitem"].peligro { color: var(--danger); }

/* Elemento activo del menú lateral: tinte + barra 3px (dos señales) */
.menu-item-activo {
  background: var(--brand-tint); color: var(--brand-tint-text);
  border-inline-start: 3px solid var(--brand-texto);
}

/* ---------- Popup / diálogo (decisión titular 09/08/2026) ----------
   UNA sola a la vez. Cierre por: ✕ (44×44), tocar fuera, tecla Esc.
   Variantes: aviso (1 botón), confirmación (◆/■), explicación.      */
.popup-fondo {
  position: fixed; inset: 0; z-index: var(--z-capas);
  background: var(--velo);
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-4);
}
/* REPARACIÓN 04/09/2026 · Una ventana NUNCA puede ser más alta que la pantalla.
   Antes medía lo que midiera su contenido (la de crear etiqueta, 918 px) y en
   un portátil de 1366×768 se salía por arriba y por abajo a la vez: los botones
   de guardar y cancelar quedaban fuera y nada se desplazaba. Ahora el alto está
   topado a la pantalla, el CUERPO se desplaza, y la cabecera (con su ✕) y los
   botones se quedan pegados arriba y abajo, siempre a la vista y siempre a un
   clic. El relleno de arriba y de abajo vive ahora en la cabecera y en los
   botones, que son los que se quedan fijos; si no, se desplazaría con ellos. */
.popup {
  width: min(440px, 100%);
  background: var(--surface); border-radius: var(--r-tarjeta);
  box-shadow: var(--sombra-flotante);
  padding: 0 var(--sp-6);
  max-block-size: calc(100vh - var(--sp-8));
  max-block-size: calc(100dvh - var(--sp-8));   /* móvil: el alto REAL, sin la barra */
  overflow-y: auto; overscroll-behavior: contain;
  animation: popup-entra var(--dur) var(--ease);
}
@keyframes popup-entra { from { opacity: 0; transform: scale(.97); } to { opacity: 1; transform: none; } }
.popup-cabecera {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  position: sticky; inset-block-start: 0; z-index: 1;
  background: var(--surface);
  padding-block: var(--sp-6) var(--sp-3);
}
.popup-cabecera h2 { flex: 1; }
.popup-cerrar { /* ✕ dibujado 24px, toque 44×44 */
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--tactil); height: var(--tactil);
  margin: calc(-1 * var(--sp-2)); margin-inline-start: 0;
  border: none; background: none; border-radius: var(--r-redondo);
  font-size: 1.5rem; line-height: 1; color: var(--text-2); cursor: pointer; /* CP-528 · 24px */
}
.popup-cerrar:hover { background: var(--surface-2); }
.popup-acciones {
  display: flex; flex-direction: column; gap: var(--sp-2);
  position: sticky; inset-block-end: 0; z-index: 1;
  background: var(--surface);
  padding-block: var(--sp-4) var(--sp-6);
}
.popup-acciones .btn { width: 100%; }

/* ---------- Toast: 4 segundos, pausa con el ratón encima ---------- */
.toast-zona {
  position: fixed; inset-block-end: var(--sp-6); inset-inline: 0;
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  z-index: var(--z-avisos); pointer-events: none;
}
.toast {
  pointer-events: auto;
  display: flex; align-items: center; gap: var(--sp-2);
  max-width: min(480px, calc(100vw - 32px));
  padding: var(--sp-3) var(--sp-4);
  background: var(--text); color: var(--bg);
  border-radius: var(--r-campo); box-shadow: var(--sombra-flotante);
  font-size: var(--fs-small);
  animation: toast-entra var(--dur) var(--ease);
}
@keyframes toast-entra { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ---------- Banner: fijo bajo la cabecera, NO desaparece solo ---------- */
.banner {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-small);
  background: var(--info-tint); color: var(--info-tint-text);
}
.banner--aviso   { background: var(--warning-tint); color: var(--warning-tint-text); }
.banner--peligro { background: var(--danger-tint);  color: var(--danger-tint-text); }
.banner--exito   { background: var(--success-tint); color: var(--success-tint-text); }
.banner .banner-accion {
  margin-inline-start: auto; font-weight: var(--fw-strong); color: inherit;
  min-height: 36px; font-size: inherit; /* compacto dentro del banner (como .btn--compacto) */
}

/* ---------- Pestañas / segmento: activa = texto brand + subrayado 2px ---------- */
.tabs { display: flex; border-block-end: var(--borde-grosor) solid var(--border); overflow-x: auto; }
.tab {
  min-height: var(--tactil);
  padding: var(--sp-2) calc(var(--sp-4) * 1.35); /* holgura 35%, sin cortar palabras */
  border: none; background: none; font: inherit; font-weight: var(--fw-strong);
  color: var(--text-2); cursor: pointer; white-space: nowrap;
  border-block-end: 2px solid transparent;
}
.tab[aria-selected="true"] { color: var(--brand-texto); border-block-end-color: var(--brand-texto); }
.tab:hover { background: var(--surface-2); }

/* Segmento de 3 opciones (temas A.8) */
.segmento {
  display: inline-flex; padding: var(--sp-1); gap: var(--sp-1);
  background: var(--surface-2); border-radius: var(--r-campo);
}
.segmento button {
  min-height: 36px; padding: 0 var(--sp-4);
  border: none; border-radius: calc(var(--r-campo) - 4px);
  background: transparent; font: inherit; color: var(--text-2); cursor: pointer;
}
.segmento button[aria-pressed="true"] {
  background: var(--surface); color: var(--text);
  font-weight: var(--fw-strong); box-shadow: var(--sombra-tarjeta);
}

/* ---------- Tabla de datos (el componente más usado del Admin) ----------
   CABECERA PEGAJOSA · por qué está resuelto así:
   `overflow-x:auto` obliga al navegador a convertir también el eje vertical en
   `auto`, así que la envoltura SIEMPRE es un contenedor de desplazamiento. Con
   la altura libre no le sobra nada por arriba y la cabecera `sticky` no llegaba
   a pegarse nunca. La solución limpia es aceptar que la envoltura es EL
   contenedor de desplazamiento (los dos ejes) y darle un alto máximo: entonces
   la tabla larga se recorre dentro de su propia caja, la cabecera se queda
   arriba y el pie («Mostrando 1–25 de 214») se queda abajo. En horizontal no
   cambia nada: sigue desplazándose igual en móvil.
   El tope se calcula con la unidad `dvh` (alto real de la ventana en móvil,
   ya descontada la barra del navegador) menos la cabecera y el aire de página.
   Si la tabla es corta, el tope no se alcanza y todo se ve como antes. */
.tabla-envoltura {
  overflow: auto;
  max-block-size: calc(100dvh - var(--alto-cabecera-chat) - var(--sp-8));
  border-radius: var(--r-tarjeta); background: var(--surface); box-shadow: var(--sombra-tarjeta);
}
.tabla { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--fs-body); }
.tabla th {
  position: sticky; top: 0; z-index: 2;
  height: var(--tactil); padding-inline: var(--sp-4);
  background: var(--surface); border-block-end: var(--borde-grosor) solid var(--border);
  font-size: var(--fs-small); font-weight: var(--fw-strong); color: var(--text-2);
  text-align: start; white-space: nowrap; cursor: pointer;
}
.tabla td {
  height: var(--fila-tabla); padding-inline: var(--sp-4);
  border-block-end: var(--borde-grosor) solid var(--border);
}
.tabla--compacta td { height: var(--fila-tabla-compacta); }
.tabla tbody tr:hover { background: var(--surface-2); }
.tabla tbody tr.pulsable { cursor: pointer; }
.tabla-pie { /* «Mostrando 1–25 de 214» + ← → */
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--fs-small); color: var(--text-2);
  /* Gemelo de la cabecera: si la tabla se recorre por dentro, el pie con el
     «Mostrando…» y las flechas se queda siempre a la vista, abajo del todo. */
  position: sticky; bottom: 0; z-index: 2;
  background: var(--surface);
}
.tabla-pie .paginas { margin-inline-start: auto; display: flex; gap: var(--sp-2); }

/* ---------- Campo OTP: UN campo real, aspecto de 6 casillas ---------- */
/* (24/10/2026 · responsive) LA FILA DE 6 CASILLAS CABE EN CUALQUIER TELÉFONO. MEDIDO (lectura): 6×44 + 5×8 = 304 px
   FIJOS; el hueco es la pantalla menos 64 (operador, tarjeta 2FA compartida) u 80 (afiliado, admin): 296/280 a
   360 px (Galaxy S/A), 256/240 a 320 px (iPhone SE 1.ª, «Zoom de pantalla», Z Fold cerrado). Con `fit-content` la
   fila nunca encogía: se descentraba a 360/375 y a 320 la sexta casilla quedaba FUERA de la pantalla (html lleva
   overflow-x: clip, así que ni desplazando). AHORA la caja se topa al hueco (`max-inline-size`) y las casillas, con
   `min-inline-size: 0`, encogen por igual (≈40 px a 360, ≈33-36 a 320: el dígito de 20 px sigue cabiendo); por
   debajo de 384 px el hueco entre casillas baja a 4 px para que a 360/375 sigan midiendo 44. Con 304 px o más
   disponibles no cambia ni un píxel. El input invisible cubre la misma caja.
   OJO: NO pasar las casillas a `flex: 1 1 0`: con `fit-content` la fila se encogería al ancho de los dígitos. */
.otp { position: relative; display: block; width: fit-content; max-inline-size: 100%; margin-inline: auto; }
.otp input {
  position: absolute; inset: 0; opacity: 0;
  width: 100%; height: 100%;
  font: inherit; letter-spacing: 2.4em;
}
/* `direction: ltr` en la FILA entera: un código de 6 cifras se lee de izquierda a derecha también en árabe (G.2).
   Sin esto el flex heredaba rtl y la primera cifra se pintaba a la derecha. */
.otp-casillas { display: flex; gap: var(--sp-2); pointer-events: none; direction: ltr; }
.otp-casilla {
  width: 44px; height: 52px; min-inline-size: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.25rem; font-weight: var(--fw-strong); /* CP-528 · 20px */
  background: var(--surface); border: var(--borde-grosor) solid var(--borde-control);  /* B12 */
  border-radius: var(--r-campo);
  /* Los dígitos son SIEMPRE occidentales 0-9, también en árabe (G.2) */
  direction: ltr;
}
.otp:focus-within .otp-casilla { border-color: var(--brand-texto); }
.otp.con-error .otp-casilla { border-color: var(--danger); }
@media (max-width: 383px) { .otp-casillas { gap: var(--sp-1); } }

/* ---------- Tarjeta KPI (rework 10/08/2026 · PULIDO.md §2) ----------
   Etiqueta PEQUEÑA arriba (13px, gris) + valor GRANDE debajo (28px/700)
   + tendencia opcional (.kpi-variacion con .insignia--exito/--peligro).
   Nunca etiqueta y número pegados en la misma línea. */
.kpi { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 2px; min-width: 0; }
.kpi-etiqueta {
  display: flex; align-items: center; gap: var(--sp-1); width: 100%;
  font-size: var(--fs-small); line-height: var(--lh-small);
  font-weight: var(--fw-caption); color: var(--text-2);
}
.kpi-valor {
  display: block; margin: 0;
  font-size: var(--fs-kpi); line-height: var(--lh-kpi); font-weight: var(--fw-kpi);
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
  color: var(--text); overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.kpi-variacion { display: inline-flex; align-items: center; gap: var(--sp-1); margin-block-start: 2px; } /* usa .insignia--exito / --peligro */
/* 07/10/2026 · CP-528 · la insignia de la tarjeta parte su texto si no cabe (lo que ya hacía el panel del operador,
   operador.css, ahora en todos). MEDIDO ANTES: admin/pagos a 390 px con la letra al 200 %, «Ingresos − costes»
   salía 18 px por la derecha y la página se desplazaba a lo ancho. */
.kpi .kpi-variacion { flex-wrap: wrap; max-inline-size: 100%; }
.kpi .kpi-variacion .insignia { white-space: normal; max-inline-size: 100%; text-align: start; }
/* (02/11/2026 · r8-rojos · CP-521) DOS RENGLONES CON ⓘ SEGUIDOS. MEDIDO ANTES (Mi saldo a 390 px con «Firme · Esperando»): la ⓘ respondía 44,5×24, porque la capa de la de abajo y la
   tarjeta le quitaban el alto. Un renglón con ⓘ reserva el alto de su zona de toque, y ninguna pisa a otra. */
.kpi .kpi-variacion:has(> .info-btn) { min-block-size: var(--tactil); }

/* ---------- Chip editable: 32px alto, ✕ con toque 44 ---------- */
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  height: 32px; padding-inline: var(--sp-3);
  background: var(--surface-2); border: var(--borde-grosor) solid var(--borde-control);  /* B12 */
  border-radius: 999px; font-size: var(--fs-small);
}
.chip button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; margin-inline-end: -6px;
  border: none; background: none; border-radius: var(--r-redondo);
  color: var(--text-2); cursor: pointer; font-size: var(--fs-body);
}
.chip button:hover { background: var(--border); color: var(--danger); }

/* ---------- Barra de progreso: 8px + etiqueta SIEMPRE en texto ---------- */
.progreso { display: block; }
.progreso-pista { height: 8px; border-radius: 999px; background: var(--border); overflow: hidden; }
.progreso-relleno { height: 100%; border-radius: 999px; background: var(--brand); transition: width var(--dur) var(--ease); }
.progreso-texto { font-size: var(--fs-small); color: var(--text-2); margin-block-start: var(--sp-1); }

/* ---------- Miga de pan ---------- */
.miga { display: flex; flex-wrap: wrap; gap: var(--sp-1); font-size: var(--fs-small); color: var(--text-2); }
.miga a { color: var(--text-2); text-decoration: none; }
.miga a:hover { color: var(--brand-texto); text-decoration: underline; }
.miga .separador::before { content: "›"; padding-inline: var(--sp-1); }

/* ---------- Esqueletos de carga (EL estado de carga estándar) ---------- */
.esqueleto {
  background: var(--surface-2); border-radius: var(--r-campo);
  position: relative; overflow: hidden; color: transparent;
  min-height: 1em;
}
.esqueleto::after {
  content: ""; position: absolute; inset: 0;
  /* El brillo sale de un token por tema (claro / oscuro / mix+sistema oscuro) */
  background: linear-gradient(90deg, transparent, var(--esqueleto-brillo), transparent);
  animation: esqueleto-brillo 1.4s infinite;
}
@keyframes esqueleto-brillo { from { translate: -100% 0; } to { translate: 100% 0; } }
.esqueleto--fila { height: var(--fila-chat); border-radius: 0; }
.esqueleto--texto { height: 14px; margin-block: 6px; }
.esqueleto--circulo { width: 48px; height: 48px; border-radius: var(--r-redondo); }

/* ---------- Botón de información ⓘ: dibujo 20px, toque 44×44 ---------- */
.info-btn {
  /* MATRIZ DE DISPOSITIVOS (10/08/2026): la caja mide lo que se ve (20×20)
     y la zona táctil de 44×44 la pone el ::after común de más abajo. Antes
     la caja medía 44 con margin:-12px y, cuando el botón caía al final de
     una fila, se salía 12px del contenedor y desplazaba la página en
     horizontal (se veía en admin «seguridad», cliente «vincular»…). */
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 20px; block-size: 20px; margin: 0;
  border: none; background: none; cursor: pointer; flex-shrink: 0;
}
/* CP-521 (08/10/2026) · LA ZONA DE 44 DE LA ⓘ, ENCIMA DE SU VECINO. MEDIDO ANTES (Chrome a 390 px, qué recibe el
   toque a lo ancho y a lo alto de cada ⓘ): junto a un botón (cabeceras de suscripciones y moderadores del admin,
   banners, saldo del afiliado, cabecera del chat del cliente) parte de su capa ::after quedaba DEBAJO del botón de al
   lado, que va después en la página: tocar el borde de la ⓘ abría el botón. AHORA la CAPA de la ⓘ —el mando más
   pequeño— va por encima: su zona llega a 44 y el vecino, que mide más, conserva la suya. Se sube solo la capa
   (transparente), no el dibujo de la ⓘ (medido con la pasada visual: subir la ⓘ entera pintaba su círculo encima
   de la sombra del botón vecino en el tema oscuro). Y SOLO en páginas sin lienzo (<canvas>): con una gráfica en la
   página, una capa subida obliga a Chrome a componer aparte y el texto de los botones pierde el suavizado de
   subpíxel (medido: los botones del inicio del admin, 944 píxeles distintos a 1280 px). Ahí las ⓘ que lo necesitan
   llevan su zona corrida (abajo). */
body:not(:has(canvas)) .info-btn::after { z-index: 1; }
/* La ⓘ de un título de sección con una fila pulsable justo debajo (inicio del admin, «Salud del sistema»): su zona
   sube 2 px, hacia el borde de la tarjeta (que no es un mando), y la fila de abajo no se la come. */
.seccion-titulo > .info-btn::after { translate: -50% calc(-50% - 2px); }
/* CP-521 (30/09/2026) · LA ZONA DE LA ⓘ SE CORRE HACIA EL HUECO LIBRE, NO ENCIMA DEL VECINO. MEDIDO ANTES (Chrome a
   390 px, qué recibe el toque): el rótulo con ⓘ justo encima del selector de tema del perfil del afiliado le comía
   4 px a [Claro] (respondía 67 × 40), y la ⓘ a 8 px de un interruptor le comía su borde (32 de ancho en formas de
   acceso del admin). La capa es transparente: correrla no cambia nada de lo que se ve. */
.campo-etiqueta:has(+ .segmento) > .info-btn::after { translate: -50% calc(-50% - 6px); }
.toggle + .info-btn::after { translate: calc(-50% + 12px) -50%; }
[dir="rtl"] .toggle + .info-btn::after { translate: calc(-50% - 12px) -50%; }
/* la ⓘ al final de una nota con su botón justo debajo (formas de acceso del admin, «Simular cambio de IP») */
.adm-nota-demo > .info-btn::after { translate: -50% calc(-50% - 8px); }
.info-btn .info-circulo {
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: var(--r-redondo);
  border: 1.5px solid var(--info); color: var(--info);
  font-size: 0.75rem; font-weight: var(--fw-strong); font-style: italic; /* CP-528 · 12px */
  font-family: Georgia, serif;
}
.info-globo {
  /* Capa propia --z-globos (> diálogos): el globo ⓘ pulsado DENTRO de un
     popup se lee POR ENCIMA, nunca detrás del velo (arreglo 22/08/2026). */
  position: absolute; z-index: var(--z-globos);
  max-width: 300px; padding: var(--sp-3) var(--sp-4);
  background: var(--surface); border-radius: var(--r-tarjeta);
  box-shadow: var(--sombra-flotante);
  font-size: var(--fs-small); line-height: var(--lh-small); color: var(--text);
}

/* ---------- Hoja inferior (bottom sheet) con tirador ---------- */
.hoja-fondo { position: fixed; inset: 0; z-index: var(--z-capas); background: var(--velo); display: flex; align-items: flex-end; }
.hoja {
  width: 100%; max-height: 85vh; overflow-y: auto;
  background: var(--surface);
  border-start-start-radius: var(--r-tarjeta); border-start-end-radius: var(--r-tarjeta);
  padding: var(--sp-2) var(--sp-4) var(--sp-6);
  animation: hoja-sube var(--dur) var(--ease);
}
@keyframes hoja-sube { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
/* (24/10/2026 · responsive, consolidación) EL ASA DE LA HOJA. El tirador era solo la rayita de 36×4 y el resto de la franja de
   arriba la gobernaba el navegador (desplazar la hoja), así que en el móvil el dedo rara vez «cogía» la hoja. Ahora el
   tirador es una franja a TODO lo ancho (8 px de aire + la rayita) con touch-action: none: ahí el dedo arrastra la hoja,
   no la página. MISMA GEOMETRÍA AL PÍXEL: el margen de arriba pasa a relleno y la rayita se dibuja con ::after en el
   mismo sitio; el margen de abajo se queda como margen (sigue colapsando igual con lo que viene detrás). */
.hoja-tirador {
  display: flex; justify-content: center; align-items: flex-end;
  width: 100%; height: 12px; box-sizing: border-box; padding-block-start: var(--sp-2);
  margin: 0 auto var(--sp-4); background: none; touch-action: none; cursor: grab;
}
.hoja-tirador::after { content: ""; width: 36px; height: 4px; border-radius: 999px; background: var(--border); }

/* ---------- Burbujas de chat (A.6/A.7) ---------- */
.burbuja {
  max-width: var(--burbuja-max-movil);
  padding: var(--sp-2) var(--sp-3);
  border-radius: var(--r-burbuja);
  background: var(--bubble-recibida); color: var(--text);
  box-shadow: var(--sombra-tarjeta);
  width: fit-content;
}
.burbuja--propia {
  background: var(--bubble-propia); color: var(--bubble-propia-texto);
  margin-inline-start: auto;
  border-end-end-radius: var(--r-ancla); /* el ancla del emisor */
}
.burbuja--recibida { border-end-start-radius: var(--r-ancla); }
.burbuja-meta { /* hora + checks dentro de la burbuja, abajo al final */
  display: flex; align-items: center; gap: 3px; justify-content: flex-end;
  font-size: var(--fs-caption); color: var(--text-2); margin-block-start: 2px;
}
/* B12 · Dentro de la burbuja PROPIA el fondo es el color de marca, no la
   tarjeta: --text-2 encima daba 4.30:1 en el azul oscuro de los paneles
   (la hora de tu propio mensaje, 11px). Se usa el texto de la burbuja
   rebajado, que es lo mismo que ya hacían la etiqueta de traducción y el
   reproductor de voz: peor caso medido 5.23:1. */
.burbuja--propia .burbuja-meta, .burbuja--operador .burbuja-meta {
  color: var(--bubble-propia-texto); opacity: .85;
}
.checks { display: inline-flex; direction: ltr; }
.checks--entregado { color: var(--check-entregado); }
.checks--leido { color: var(--check-leido); } /* rellenos + color: dos señales (A.6) */
@media (min-width: 900px) {
  .burbuja { max-width: min(var(--burbuja-max-escritorio), var(--burbuja-tope)); }
}

/* ---------- Fila de lista de chats (~72px, A.7) ---------- */
.fila-chat {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--fila-chat); padding-inline: var(--sp-4);
  cursor: pointer; background: var(--surface);
}
.fila-chat:hover { background: var(--surface-2); }
.fila-chat.activa { background: var(--brand-tint); } /* conversación abierta (dos paneles) */
.fila-chat + .fila-chat { border-block-start: var(--borde-grosor) solid var(--border); }
.fila-chat .contador { /* pastilla ② de no leídos */
  min-width: 20px; height: 20px; padding-inline: 6px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand); color: var(--brand-contrast);
  font-size: var(--fs-caption); font-weight: var(--fw-strong);
}

/* ---------- Estados estándar (G.5): vacío / error ---------- */
.estado-vacio {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-4); text-align: center; color: var(--text-2);
}
.estado-vacio svg { width: 64px; height: 64px; opacity: .7; }

/* ---------- Iconos del set de líneas (1.5px, un solo set — A.5) ---------- */
.icono { width: var(--icono-m); height: var(--icono-m); flex-shrink: 0; }
.icono--16 { width: var(--icono-s); height: var(--icono-s); }
.icono--24 { width: var(--icono-l); height: var(--icono-l); }
.icono svg, svg.icono { fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

/* RTL: los iconos direccionales (volver, chevrons, flechas) se espejan solos
   en las CUATRO interfaces — ui.js añade .icono-espejo a los direccionales. */
[dir="rtl"] .icono-espejo { transform: scaleX(-1); }
[dir="rtl"] .espejo-rtl svg { transform: scaleX(-1); }

/* ---------- Utilidades de disposición ---------- */
.fila { display: flex; align-items: center; gap: var(--sp-3); }
.columna { display: flex; flex-direction: column; gap: var(--sp-3); }
.crece { flex: 1; min-width: 0; }
.centro { text-align: center; }

/* ============================================================
   .panel-layout — Marco de PANEL (admin/operador/afiliado) con
   barra lateral de navegación IZQUIERDA fija (rework 10/08/2026,
   PULIDO.md §4). En escritorio (≥1024px): sidebar 240px + contenido
   fluido con tope --contenido-max. En móvil (<1024px) la sidebar
   desaparece y cada interfaz conserva su navegación apilada.
   Las pantallas de puerta (login, 2FA…) añaden body.sin-marco.
   ============================================================ */
.panel-app { display: flex; min-height: 100dvh; }
.panel-cuerpo { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.panel-topbar {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--alto-cabecera-chat); padding-inline: var(--sp-4);
  background: var(--surface);
  border-block-end: var(--borde-grosor) solid var(--border);
  position: sticky; top: 0; z-index: var(--z-barras);
}
.panel-contenido { flex: 1; width: 100%; max-width: var(--contenido-max); margin-inline: auto; padding: var(--sp-4); }
.panel-lateral { display: none; }
@media (min-width: 1024px) {
  .panel-lateral {
    display: flex; flex-direction: column;
    width: var(--ancho-lateral); flex-shrink: 0;
    position: sticky; top: 0; height: 100dvh; overflow-y: auto;
    background: var(--surface);
    border-inline-end: var(--borde-grosor) solid var(--border);
    z-index: var(--z-barras);
  }
  body.sin-marco .panel-lateral { display: none; }
  .panel-contenido { padding: var(--sp-6); }
}
.panel-lateral-logo {
  display: flex; align-items: center; gap: var(--sp-3); flex-shrink: 0;
  min-height: var(--alto-cabecera-chat); padding-inline: var(--sp-4);
  border-block-end: var(--borde-grosor) solid var(--border);
  font-weight: var(--fw-strong);
}
.panel-nav { flex: 1; display: flex; flex-direction: column; padding-block: var(--sp-2); }
.panel-nav-item {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tactil); width: 100%;
  padding-inline: var(--sp-4);
  border: none; background: none; font: inherit; color: var(--text-2);
  cursor: pointer; text-decoration: none; white-space: nowrap;
  border-inline-start: 3px solid transparent;
}
.panel-nav-item:hover { background: var(--surface-2); }
.panel-nav-item.activo, .panel-nav-item[aria-current="page"] {
  background: var(--brand-tint); color: var(--brand-tint-text);
  border-inline-start-color: var(--brand-texto); font-weight: var(--fw-strong);
}
.panel-nav-pie { border-block-start: var(--borde-grosor) solid var(--border); padding-block: var(--sp-2); flex-shrink: 0; }

/* ============================================================
   BARRA LATERAL PLEGABLE — compartida por los TRES paneles
   (admin, operador y afiliado). Petición del titular 04/09/2026.
   El motor está en assets/js/core/lateral.js; aquí solo el aspecto.

   Dos plegados independientes, que se recuerdan por rol y aparato:
     · TODA la barra → `.lateral-mini` en el contenedor de la app
       (.admin-app / .panel-app): pasa a 64px de solo iconos, y cada
       icono enseña su nombre con la etiqueta flotante del sistema.
     · CADA GRUPO → `data-plegado="si"` en su `.nav-grupo-caja`.
       Al plegarse, sus entradas se van del árbol de accesibilidad
       (display:none, no opacidad) MENOS la sección donde estás, que
       sigue siempre a la vista.
   Todo con tokens y propiedades lógicas: claro, oscuro y árabe (RTL)
   salen solos, y «reducir movimiento» ya apaga --dur en tokens.css.
   ============================================================ */

/* ---------- El botón de plegar / desplegar ---------- */
.lateral-plegar { margin-inline-start: auto; flex-shrink: 0; }
/* La flecha mira al lado hacia el que se va a mover la barra. El icono
   «volver» ya se espeja solo en árabe (.icono-espejo): aquí se le suma
   el giro del modo estrecho, y en árabe los dos se anulan. */
.lateral-mini .lateral-plegar .icono-espejo { transform: scaleX(-1); }
[dir="rtl"] .lateral-mini .lateral-plegar .icono-espejo { transform: none; }

/* ---------- Los grupos del menú ---------- */
.nav-grupo-caja[hidden] { display: none; }
.nav-grupo-btn {
  display: flex; align-items: center; gap: var(--sp-2);
  inline-size: 100%;
  /* Es un botón de verdad, también con el dedo: 44px de alto como todo lo
     que se pulsa (A.3), aunque el rótulo sea pequeño. */
  min-block-size: var(--tactil);
  padding: var(--sp-3) var(--sp-4) var(--sp-1);
  border: none; background: none; font: inherit;
  color: var(--text-2); text-align: start; cursor: pointer;
}
.nav-grupo-caja:first-child .nav-grupo-btn { padding-block-start: var(--sp-1); }
.nav-grupo-texto {
  flex: 1; min-inline-size: 0;
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption);
  letter-spacing: .06em; text-transform: uppercase;
  /* F-2 (04/09/2026) · El rótulo BAJA DE LÍNEA en vez de cortarse.
     En alemán «Sicherheit und Einstellungen» pide 192 px y su hueco
     son 168: con `nowrap` + `ellipsis` se leía «SICHERHEIT UND EINST…»
     y el nombre del grupo dejaba de decir lo que es. Rozan el borde
     también el italiano del admin y el alemán y el francés del
     afiliado. Los rótulos cortos siguen en UNA línea: solo se parte el
     que no cabe, y el botón crece de alto lo justo (sus 44 px de zona
     táctil son un mínimo, no un tope). */
  overflow-wrap: break-word;
}
.nav-grupo-btn:hover { color: var(--text); }
.nav-grupo-btn:hover .nav-grupo-texto { text-decoration: underline; }
.nav-grupo-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: -4px; }   /* CP-541: 2 px por dentro de su borde */
.nav-grupo-flecha {
  display: inline-flex; flex-shrink: 0;
  transition: transform var(--dur) var(--ease);
}
/* Las entradas de un grupo se apilan igual sean del rol que sean
   (el admin usa `.nav-item`, el operador `.op-nav a`, el afiliado
   `.panel-nav-item`). */
.nav-grupo-items { display: flex; flex-direction: column; }
.nav-grupo-caja[data-plegado="si"] .nav-grupo-flecha { transform: rotate(-90deg); }
[dir="rtl"] .nav-grupo-caja[data-plegado="si"] .nav-grupo-flecha { transform: rotate(90deg); }

/* Grupo plegado: dentro solo se queda la sección donde estás. */
.nav-grupo-caja[data-plegado="si"] .nav-grupo-items > *:not(.lateral-item--activo) { display: none; }
/* Al desplegar, las entradas entran suaves. La clase la pone y la quita
   lateral.js, así que un repintado normal de la barra NO parpadea. */
.nav-grupo-abriendo .nav-grupo-items > .lateral-item {
  animation: nav-grupo-abre var(--dur) var(--ease) backwards;
}
@keyframes nav-grupo-abre { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }

/* Una entrada apagada (modo Simple del admin) se va de verdad: `.nav-item`
   declara display:flex y eso le ganaba al `hidden` del navegador. */
.lateral-item[hidden] { display: none; }

/* F-3 (04/09/2026) · El nombre de una entrada BAJA DE LÍNEA en vez de
   sacarle a la barra una barra de desplazamiento horizontal. En alemán
   «Einstellungen & White Label» pide 235 px y el hueco son 224: con
   `white-space: nowrap` (que las tres barras traen de antes) aparecía un
   scroll lateral dentro del menú y el final del nombre quedaba fuera.
   Los nombres cortos siguen en UNA línea; solo se parte el que no cabe,
   y la fila crece de alto lo justo (sus 44 px son un mínimo). En modo
   estrecho no cambia nada: allí el nombre no se pinta. */
.admin-lateral .nav-item,
.panel-lateral .panel-nav-item,
.panel-lateral .op-nav a { white-space: normal; }

/* ---------- Modo estrecho (solo iconos) ----------
   Solo donde la barra existe de verdad como columna fija. Por debajo,
   la del admin es un cajón que sale con ☰ y la del operador/afiliado
   ni se pinta: ahí el botón cierra el menú y nada se encoge.
   (24/10/2026 · responsive) `and (hover: hover)`: SOLO DONDE HAY RATÓN. En un iPad apaisado (1024-1366 px) o una
   tableta Android la barra plegada dejaba iconos cuyo nombre solo salía al pasar el ratón o al enfocar con el
   teclado, y en WebKit tocar un enlace no lo enfoca: no había forma de saber qué era cada icono antes de pulsarlo.
   Ahí la barra se queda con sus nombres (lateral.js mira lo mismo en `enEscritorio()`) y el botón de plegar se
   esconde (matriz 10.6). En el Chrome de la suite «hover» es «hover»: ninguna foto cambia. */
@media (min-width: 961px) and (hover: hover) {
  /* `width`, no `inline-size`: el ancho de la barra ya está escrito con
     `width` en admin.css y arriba, y mezclar las dos formas de decir lo
     mismo hace que gane la ÚLTIMA hoja, no la regla más precisa (con
     `inline-size` aquí, el admin se quedaba en 240px sin plegarse). No
     es un problema de árabe: `width` no tiene lado. */
  .lateral-mini .admin-lateral,
  .lateral-mini .panel-lateral { width: 64px; }

  .lateral-mini .admin-lateral .lateral-item > span,
  .lateral-mini .panel-lateral .lateral-item > span,
  .lateral-mini .nav-grupo-btn,
  .lateral-mini .lateral-logo strong,
  .lateral-mini .panel-lateral-logo strong { display: none; }

  .lateral-mini .admin-lateral .lateral-item,
  .lateral-mini .panel-lateral .lateral-item {
    justify-content: center; gap: 0;
    padding-inline: 0; border-inline-start-width: 0;
  }
  /* Sin el borde de 3px, la sección activa se reconoce por el tinte. */
  .lateral-mini .admin-lateral .lateral-item--activo,
  .lateral-mini .panel-lateral .lateral-item--activo { box-shadow: inset 0 0 0 1px var(--brand-texto); }

  /* Sin rótulos, los grupos se separan con una línea: se siguen leyendo
     como bloques y no se pierde el orden del menú. */
  .lateral-mini .nav-grupo-caja + .nav-grupo-caja {
    margin-block-start: var(--sp-2); padding-block-start: var(--sp-2);
    border-block-start: var(--borde-grosor) solid var(--border);
  }

  /* La cabecera se apila: la marca arriba y el botón debajo, los dos
     centrados y con su zona táctil de 44px intacta. */
  .lateral-mini .lateral-logo,
  .lateral-mini .panel-lateral-logo {
    flex-direction: column; gap: var(--sp-1);
    justify-content: center; padding-inline: 0; padding-block: var(--sp-2);
  }
  .lateral-mini .lateral-plegar { margin-inline-start: 0; }

  /* La transición suave del ancho (la apaga «reducir movimiento»). */
  .admin-lateral, .panel-lateral { transition: width var(--dur) var(--ease); }
  /* …menos en el primer pintado: la barra no debe «entrar» plegándose. */
  .lateral-sin-animar .admin-lateral,
  .lateral-sin-animar .panel-lateral { transition: none; }
}

/* ============================================================
   .chat-layout — CLIENTE en escritorio (≥1024px): dos paneles
   tipo WhatsApp Web (PULIDO.md §4): lista de chats a la izquierda
   (380px) + conversación a la derecha, a pantalla completa.
   El <body> recibe .cl-dos-paneles cuando la pantalla activa es
   una conversación (lo gestiona cliente/index.html).
   ============================================================ */
.chat-layout-lateral { display: none; }
@media (min-width: 1024px) {
  body.cl-dos-paneles .chat-layout {
    display: flex; align-items: stretch;
    /* + --safe-arriba (06/09/2026): la cabecera de la interfaz crece lo que mida
       la muesca, asi que los dos paneles tienen que descontarla tambien. */
    height: calc(100dvh - 57px - var(--safe-arriba)); /* bajo la cabecera de la interfaz */
    overflow: hidden;
  }
  body.cl-dos-paneles .chat-layout-lateral {
    display: flex; flex-direction: column;
    width: var(--ancho-lista-chats); flex-shrink: 0; min-width: 0;
    border-inline-end: var(--borde-grosor) solid var(--border);
    background: var(--surface);
  }
  body.cl-dos-paneles .chat-layout-principal { flex: 1; min-width: 0; display: flex; flex-direction: column; }
}

/* ---------- Indicador del tema Mix (feedback V3, 10/08/2026) ----------
   «Mix (automático — ahora: claro)» en el propio botón del selector. */
.tema-mix-ahora {
  display: block;
  font-size: var(--fs-caption); line-height: var(--lh-caption);
  font-weight: var(--fw-caption); color: var(--text-2);
  white-space: nowrap;
}
/* En móvil la frase «(automático — ahora: claro)» no cabe en una línea:
   se deja envolver para que el selector de tema no desborde a 360px. */
@media (max-width: 560px) {
  .tema-mix-ahora { white-space: normal; }
}
.segmento button[aria-pressed="true"] .tema-mix-ahora { color: var(--text-2); }

/* ============================================================
   MATRIZ DE DISPOSITIVOS (10/08/2026) — reglas comunes que hacen
   cumplir, en las 4 interfaces y en TODOS los anchos probados
   (360·390·414·430·768·1024·1280·1440·1536+ y horizontal 812×375),
   en los 6 idiomas y en RTL, las cuatro promesas del titular:
     1) cero desplazamiento horizontal de la página;
     2) nada cortado, solapado ni fuera de pantalla;
     3) toda cosa pulsable con 44×44 reales de toque;
     4) el texto no se sale de su caja (el alemán es el más largo).
   Va al final del archivo a propósito: solo añade red de seguridad,
   no cambia el dibujo de ningún componente.
   ============================================================ */

/* 1 · La página NUNCA se desplaza en horizontal.
   Se usa `clip` y no `hidden`: `clip` no crea un contenedor de scroll, así
   que las cabeceras de tabla pegajosas y los laterales `sticky` siguen
   funcionando igual. Lo único que recorta son las zonas táctiles
   transparentes que sobresalen en el borde (no se ve nada). */
html { overflow-x: clip; }
body { overflow-x: clip; }

/* 2 · Palabras muy largas (alemán, URLs, correos) nunca rompen la caja. */
body { overflow-wrap: break-word; }

/* 3 · Filas flexibles: si no cabe, se baja de línea en vez de desbordar.
   Sin esto, en alemán a 360px se salían de pantalla el botón de «invitar»
   (admin·clientes), la fila de salud (admin·inicio) y la del centro de
   control. `min-width:0` deja encoger a los hijos con texto largo. */
.fila { flex-wrap: wrap; }
.fila > * { min-width: 0; }
.extremo { flex-wrap: wrap; }
/* 3 bis · B12 · CP-528 · `flex-wrap` no basta: un `.crece` tiene
   `flex-basis: 0`, así que el navegador NO considera que la fila «no cabe» —
   la estruja hasta dejar el texto en una columna de una letra por línea y
   nunca la parte. Se vio con la letra al 200 % en un móvil de 390 px (título
   de pantalla del panel, aviso del modo práctica, etiquetas de versión).
   Con una base de 14 caracteres la fila baja de línea ANTES de estrujar; a
   tamaño normal esa base cabe de sobra y no cambia nada. */
.fila > .crece, .extremo > .crece,
.op-pagina-cabecera > .crece, .pagina-cabecera > .crece,
.af-pagina-cabecera > .crece { flex-basis: 14ch; }

/* 4 · Ningún control puede ser más ancho que su hueco. */
.btn, .btn-secundario, .btn-texto, .btn-fantasma, .btn-peligro,
.btn-peligro-relleno, .btn-icono, .chip, .segmento,
select, textarea, .campo-control {
  max-inline-size: 100%;
}
.segmento { flex-wrap: wrap; }
.segmento button { min-block-size: var(--tactil); }
/* CP-528 · Los <fieldset> traen min-inline-size:min-content del navegador: con la
   letra al 200 % un campo de teléfono (input de 20 columnas) los ensancha más que
   la tarjeta y se salen del viewport. */
fieldset { min-inline-size: 0; }

/* 5 · Desplegables y campos sueltos (fuera de `.campo-control`): mismo
   aspecto de token y altura de toque de 44px. Antes se pintaban con el
   estilo por defecto del navegador y medían 22px de alto. */
select:not(.campo-control select),
input[type="text"]:not(.campo-control input),
input[type="search"]:not(.campo-control input),
input[type="number"]:not(.campo-control input),
input[type="tel"]:not(.campo-control input),
input[type="email"]:not(.campo-control input),
input[type="date"]:not(.campo-control input),
input[type="time"]:not(.campo-control input) {
  min-block-size: var(--tactil);
  padding-inline: var(--sp-3);
  border: var(--borde-grosor) solid var(--borde-control);  /* B12 */
  border-radius: var(--r-campo);
  background: var(--surface); color: var(--text);
  font: inherit;
}
select:not(.campo-control select) { cursor: pointer; }

/* 5 bis · Campos dentro de `.campo-control`: el control ocupa TODO el alto
   del recuadro (antes el desplegable medía 22px dentro de un campo de 48 y
   media caja no respondía al toque). */
.campo-control > input, .campo-control > select, .campo-control > textarea {
  align-self: stretch;
}
/* El propio control manda su altura mínima de toque: así también valen los
   campos «compactos» con `style="min-height:36px"` escritos en pantalla
   (el recuadro crece hasta 44 en vez de dejar un desplegable de 34px). */
.campo-control > select,
.campo-control > textarea,
.campo-control > input:not([type="checkbox"]):not([type="radio"]) {
  min-block-size: var(--tactil);
}
/* Los desplegables muy cortos («1·2·3») también necesitan 44 de ancho.
   Va con la misma especificidad que `.campo-control select { min-width:0 }`
   pero más abajo, para ganar sin recurrir a !important. */
select { min-inline-size: var(--tactil); }
.campo-control > select { min-inline-size: var(--tactil); }

/* 6 · Zona táctil de 44×44 REALES en todo lo pulsable, sin cambiar el
   dibujo: un ::after transparente centrado que nunca baja de 44.
   · `:where()` = especificidad 0 ⇒ cualquier regla propia de una pantalla
     o de un componente sigue mandando.
   · `:not(:has(...))` evita ponerlo en contenedores pulsables que llevan
     dentro otro botón (si no, la capa taparía el botón de dentro).
   · `position: relative` a nivel de ETIQUETA (especificidad 0,0,1): los
     botones colocados en absoluto por clase (p. ej. `.btn-menu-msj`)
     siguen ganando y no se mueven. */
/* FALLO (B) · 27/08/2026 — la lista de aquí abajo TIENE que ser la misma que la
   del `::after`. Faltaban `.chip`, `.info-btn`, `.tab` y `.btn-icono`: cuando uno
   de ellos se pinta con un `<span>` (p. ej. el chip «AR ↔ ES» del hilo, que no
   lleva botón dentro y por eso sí recibe la capa), el elemento se queda
   `position: static`, la capa absoluta busca contenedor y no encuentra ninguno,
   así que `inline-size:100%` pasa a ser el 100% de la VENTANA (medido: 1520×673)
   y `inset:50% 50%` la centra encima de toda la pantalla. Resultado: una lámina
   invisible a pantalla completa tapando los desplegables de filtros — no se
   abrían ni con el ratón ni con el toque. Con `position: relative` la capa vuelve
   a medir lo que mide el chip. */
/* FALLO (G-11) · 06/09/2026 — EL «SALTAR AL CONTENIDO» ROBABA 44 px EN LAS
   CUATRO INTERFACES. `.saltar-contenido` se declara `position: fixed` arriba
   (base.css:53) con peso 0,1,0; esta línea lo pisaba con `a[href]`, que pesa
   0,1,1 y además está más abajo en el fichero. Al pasar a `position: relative`
   el enlace deja de estar fuera del flujo y OCUPA SITIO DE VERDAD: la barra de
   44 px que se ve desplazada hacia arriba con `translateY` sigue reservando su
   hueco, así que TODA la pantalla bajaba 44 px.
   MEDIDO ANTES DEL ARREGLO (Chrome, 375x812, banco propio, las cuatro
   interfaces): `getComputedStyle(a).position` = "relative", alto 44 px,
   `main.getBoundingClientRect().top` = 44 y `scrollHeight` 856 contra
   `innerHeight` 812 — 44 px de barrido vertical en una pantalla que cabía.
   En el chat del cliente eso dejaba la caja de escribir por debajo del borde.
   ARREGLO: se excluye este enlace de la lista. El resto no cambia; el enlace
   conserva su zona táctil porque ya mide 44 px de alto por su
   `min-block-size` y sigue recibiendo la capa `::after` de aquí abajo. */
button, a[href]:not(.saltar-contenido), [role="button"], summary,
.chip, .info-btn, .tab, .btn-icono { position: relative; }
:where(button, a[href], [role="button"], .chip, .info-btn, .tab, .btn-icono):not(:has(button, a[href], input, select, textarea))::after {
  content: ""; position: absolute;
  /* top/left físicos + translate físico: centrar es igual en LTR y en RTL
     (si se usara inset-inline-start, en árabe la capa quedaría descentrada). */
  inset: 50% auto auto 50%;
  translate: -50% -50%;
  inline-size: 100%; block-size: 100%;
  min-inline-size: var(--tactil); min-block-size: var(--tactil);
  border-radius: inherit;
}
/* Las casillas y radios viven dentro de `.opcion-fila`, que ya es una fila
   pulsable de 44px de alto: el toque real es la fila entera. */

/* (02/11/2026 · P-5) EN EL MÓVIL, LAS CASILLAS Y LOS ICONOS DE UNA VENTANA SE TOCAN CON EL DEDO. MEDIDO ANTES (360 px, Centro de Ayuda): «Lo
   ven» de 85 a 117×24, «Mostrar el pie» 280×24, iconos 74×36, y la casilla de «Oculta» sola en su línea con el texto debajo (son <label> o
   <input>: la capa ::after de arriba no los cubre). Una regla de la ventana y no de esa pantalla: cualquier casilla suelta en una ventana
   tiene el mismo problema. En escritorio, con ratón, la densidad no cambia. */
@media (max-width: 767px) {
  .popup label.fila:has(> input[type="checkbox"], > input[type="radio"]) {
    min-block-size: var(--tactil); align-items: center; flex-wrap: nowrap;
  }
  .popup label.fila:has(> input[type="checkbox"], > input[type="radio"]) > input { flex: none; }
  .popup label.btn--compacto:has(> input[type="radio"]) { min-block-size: var(--tactil); }
}

/* 7 · Diálogos y hojas: nunca más anchos que la pantalla ni pegados al borde. */
.popup, .popup-caja, .hoja, .hoja-inferior, .menu-flotante, .info-globo {
  max-inline-size: calc(100vw - var(--sp-6));
}

/* 8 · Imágenes y medios: dentro de su caja siempre. */
img, svg, video, canvas { max-inline-size: 100%; }

/* 9 · ÁREA SEGURA DEL TELÉFONO — la muesca, la barra de gestos y, con el
   teléfono girado, la muesca del costado. (06/09/2026 · tanda 3 de móvil)

   POR QUÉ HACÍA FALTA. Hasta hoy, en toda la hoja de estilos había UNA sola
   línea de área segura (`cliente.css:364`, el relleno de abajo del cajón de
   escribir) y NINGUNO de los seis `index.html` pedía ir de borde a borde, así
   que esa línea no se activaba nunca: `env(safe-area-inset-bottom)` valía 0
   siempre. Ahora los seis piden `viewport-fit=cover`, la aplicación ocupa la
   pantalla entera —que es lo que se espera de una aplicación de mensajería— y
   estas reglas son las que impiden que la muesca tape la cabecera y que la
   barra de gestos tape lo de abajo.

   CÓMO SE HA COMPROBADO SIN UN IPHONE DELANTE. En un ordenador los cuatro
   `env()` valen 0, así que el arreglo sería invisible y no se podría afirmar
   nada. Por eso van por variable (`tokens.css`): dándoles un valor a mano
   (`--safe-arriba: 44px; --safe-abajo: 34px`, que son los del iPhone) la
   pantalla se comporta igual que en el teléfono y se puede medir y fotografiar.
   Lo que NO se puede afirmar desde aquí es el aparato de verdad: eso hay que
   verlo en un teléfono con muesca.

   El relleno se SUMA al que ya había (`calc(... + var(--safe-...))`), nunca
   lo sustituye: sin muesca todo mide exactamente lo de antes. */

/* 9.1 · El «Saltar al contenido», que sale arriba a la izquierda del todo.
   Y OJO CON EL ESCONDITE: el enlace se esconde subiéndolo con `translateY`
   justo lo que mide más un poco de aire. Si se le baja el punto de partida por
   la muesca y no se le sube el escondite lo mismo, ASOMA. MEDIDO con una
   muesca de 47 px antes de corregirlo: la pastilla verde se quedaba a la vista
   con 39 de sus 44 px pintados encima de la pantalla, sin que nadie la hubiera
   enfocado. Por eso la muesca se suma también al translate. */
.saltar-contenido {
  inset-block-start: calc(var(--sp-2) + var(--safe-arriba));
  inset-inline-start: calc(var(--sp-2) + var(--safe-inicio));
  transform: translateY(calc(-100% - var(--sp-4) - var(--safe-arriba)));
}
.saltar-contenido:focus, .saltar-contenido:focus-visible { transform: translateY(0); }

/* 9.2 · Barra superior de los paneles (operador y afiliado). Se queda pegada
   arriba, así que es lo primero que se mete debajo de la muesca. */
.panel-topbar {
  padding-block-start: var(--safe-arriba);
  padding-inline: calc(var(--sp-4) + var(--safe-inicio)) calc(var(--sp-4) + var(--safe-fin));
  /* Y que la barra siga MIDIENDO sus 56 px POR DEBAJO de la muesca, no 56 en
     total: si solo se le pone el relleno, la barra crece hasta donde llegue su
     contenido y queda más estrecha de la cuenta justo donde el dedo pulsa. */
  min-block-size: calc(var(--alto-cabecera-chat) + var(--safe-arriba));
}
/* 9.3 · El contenido: aire a los lados con el teléfono girado y aire abajo
   para que el último botón no quede debajo de la barra de gestos. */
.panel-contenido {
  padding-inline: calc(var(--sp-4) + var(--safe-inicio)) calc(var(--sp-4) + var(--safe-fin));
  padding-block-end: calc(var(--sp-4) + var(--safe-abajo));
}
@media (min-width: 1024px) {
  .panel-contenido {
    padding-inline: calc(var(--sp-6) + var(--safe-inicio)) calc(var(--sp-6) + var(--safe-fin));
    padding-block-end: calc(var(--sp-6) + var(--safe-abajo));
  }
  /* La columna de navegación pega con el borde de la pantalla. */
  .panel-lateral { padding-inline-start: var(--safe-inicio); padding-block-end: var(--safe-abajo); }
}

/* 9.4 · Lo que flota abajo: el aviso de conexión y los mensajes de «Guardado».
   Sin esto se pintan justo encima de la barra de gestos y se lee la mitad. */
/* (24/10/2026 · responsive) `--teclado`: lo que tapa el teclado en pantalla del iPhone (lo publica ui.js; en Android
   y en la suite vale 0). Las píldoras de conexión llevan lo suyo en sus bloques B1/B2 y en pwa.css: la línea que
   había aquí para `.api-estado` quedaba PISADA por orden por la de su bloque (misma especificidad, más abajo), y la
   píldora caía dentro de los 34 px del indicador de inicio del iPhone. */
.toast-zona { inset-block-end: calc(var(--sp-6) + var(--safe-abajo) + var(--teclado, 0px)); }

/* 9.5 · Diálogos y hojas. La hoja inferior nace pegada al borde de abajo, que
   es justo donde vive la barra de gestos. */
.popup-fondo {
  padding: calc(var(--sp-4) + var(--safe-arriba)) calc(var(--sp-4) + var(--safe-derecha))
           calc(var(--sp-4) + var(--safe-abajo)) calc(var(--sp-4) + var(--safe-izquierda));
}
.popup {
  max-block-size: calc(100dvh - var(--sp-8) - var(--safe-arriba) - var(--safe-abajo));
}
/* OJO CON LA MANO IZQUIERDA (06/09/2026) · Aquí van `padding-block` y
   `padding-inline`, NO el `padding` de toda la vida. El atajo `padding` es
   FÍSICO (arriba, derecha, abajo, izquierda) y no sabe nada del idioma: si se
   le mete dentro `--safe-inicio`, en árabe el hueco de la muesca acaba
   reservado en el lado CONTRARIO al que tiene la muesca, y lo que se quería
   apartar sigue tapado. Con las propiedades lógicas, «inicio» es de verdad por
   donde empieza la línea y el cruce de `tokens.css` hace el resto. */
.hoja {
  max-block-size: calc(85dvh - var(--safe-abajo));
  padding-block: var(--sp-2) calc(var(--sp-6) + var(--safe-abajo));
  padding-inline: calc(var(--sp-4) + var(--safe-inicio)) calc(var(--sp-4) + var(--safe-fin));
}

/* ============================================================
   10 · TELÉFONOS DE VERDAD (24/10/2026 · auditoría responsive, zona núcleo)
   Lo que una foto de Chrome no enseña: el zoom automático del iPhone al
   enfocar un campo, el «deslizar para recargar» de Android, el rebote de la
   página en iOS, lo fijo que cae bajo la barra de gestos o bajo el teclado, y
   las etiquetas que solo salían con ratón. Reglas que en el Chrome de la suite
   (sin WebKit, sin teclado, sin muesca, con ratón) dejan cada píxel como está.
   Lo que publica ui.js en <html> y leen estas reglas:
     --teclado       px que tapa el teclado en pantalla del iPhone (0 si cerrado)
     --alto-visual   altura visible real con el teclado abierto (si no, no existe:
                     cada caja clavada a la pantalla usa var(--alto-visual, 100dvh))
     --pie-flotante  si hay barra de escribir a la vista, desde el borde hasta
                     encima de ella (ya incluye barra de gestos y teclado)
   ============================================================ */

/* 10.1 · iPHONE: NINGÚN CAMPO POR DEBAJO DE 16 px. Safari —y todo navegador de iPhone, que es WebKit— AMPLÍA la
   página al enfocar un control con letra computada menor de 16 px y NO la deshace al salir: todos los campos
   heredan los 15 px del cuerpo (`font: inherit`) y el desplegable «Escribo en» iba a 11 px (×1,45). Se arregla con
   el tamaño, nunca con `maximum-scale` (WCAG 1.4.4; Samsung lo ignora). `-webkit-touch-callout` solo existe en el
   WebKit de iOS: en Chrome, Samsung y escritorio este bloque es falso y no toca nada. `!important` a propósito:
   tiene que ganar a los `font: inherit` de igual especificidad de cliente.css / operador.css / admin.css, que
   cargan DESPUÉS de esta hoja, y al `.trad-linea select` de 11 px. `1em` es la letra del padre: respeta la letra
   ampliada del sistema (CP-528). La altura no cambia (line-height en rem, min-height 44/48). Las casillas, radios,
   deslizadores, ficheros y colores no abren teclado y no se tocan. */
@supports (-webkit-touch-callout: none) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]),
  select, textarea { font-size: max(16px, 1em) !important; }
}

/* 10.2 · SIN «DESLIZAR PARA RECARGAR» NI REBOTE DE PÁGINA. La app mide exactamente la pantalla y la raíz está
   siempre en scrollTop 0: al tirar hacia abajo al principio del hilo (o con un hilo corto, o en la puerta),
   Chrome y Samsung Internet recargaban la app en mitad de la conversación —se perdía la cita activa— y en el
   iPhone rebotaba el documento entero dejando ver una banda de fondo. Va en las DOS (Chromium lo lee de html). No
   toca el desplazamiento: solo lo que pasa al llegar al borde. Y el cinturón: los desplazadores que viven dentro
   de una caja clavada a la pantalla no encadenan a la raíz (en iOS 16+ esto es lo que corta el rebote de verdad).
   Solo esos: un desplazador anidado en una página que SÍ se desplaza (tablas, hilo del admin a 960 px) no entra,
   porque `contain` le impediría pasar el gesto a la página. */
html, body { overscroll-behavior-y: none; }
.chat-mensajes, .pantalla-scroll, .auth, #op-mensajes,
.hoja, .popup, .mm-cuerpo, .atencion-filtros { overscroll-behavior: contain; }

/* 10.3 · UNA VENTANA O UNA HOJA ABIERTA NO DESPLAZA LA PÁGINA DE DETRÁS. Arrastrar sobre el velo (o la rueda sobre
   él) movía la página de debajo —en iOS con el efecto goma de la capa fija— y al cerrar la persona estaba en otro
   sitio. `pinch-zoom` y no `none`: el zoom por pellizco sigue vivo con un diálogo abierto (WCAG 1.4.4). Lo de
   DENTRO sigue desplazándose: `.popup`, `.hoja` y `.mm-cuerpo` son contenedores de desplazamiento y en ellos el
   navegador vuelve a decidir solo. La rueda la corta ui.js (UI.popup y el diálogo multimedia). */
.popup-fondo, .hoja-fondo, .portafolio-capa { overscroll-behavior: contain; touch-action: pinch-zoom; }

/* 10.4 · LO QUE FLOTA ABAJO, SIEMPRE A LA VISTA: los avisos (9.4, `.toast-zona`) y las píldoras de conexión (B1,
   B2, pwa.css) suman la barra de gestos (`--safe-abajo`) y el teclado en pantalla del iPhone (`--teclado`), y las
   píldoras suben por encima de la barra de escribir cuando la hay (`--pie-flotante`). Ver `.api-estado`. */

/* 10.5 · PULSACIÓN LARGA = ETIQUETA. En una pantalla táctil sin ratón (iPad, tabletas) el nombre y la frase de una
   pastilla de acción (y de cualquier botón con etiqueta flotante) salían solo al pasar el ratón; ahora salen
   manteniendo el dedo (ui.js, «etiquetaConElDedo»). Sin el menú de copiar/compartir del sistema encima y sin
   seleccionar texto mientras se mantiene. Transparente: no cambia lo que se ve. */
.btn-accion, button[data-etiqueta], [role="button"][data-etiqueta] {
  -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
}

/* 10.6 · LA BARRA LATERAL NO SE PLIEGA A ICONOS DONDE NO HAY RATÓN (ver «Modo estrecho», más arriba): el botón de
   plegar se esconde en una tableta apaisada. En el Chrome de la suite «hover» es «hover»: no cambia ninguna foto. */
@media (min-width: 961px) and (hover: none) { .lateral-plegar { display: none; } }

/* ============================================================
   PULIDO VISUAL COMÚN (auditoría 12/08/2026) — C-2 … C-16.
   Reglas compartidas por las 4 interfaces, extraídas de los
   cuatro informes de auditoría y deduplicadas. Solo tokens y
   propiedades lógicas: ni un color nuevo, RTL espeja solo.
   NO añadir aquí nada específico de un rol: eso va en
   cliente.css / admin.css / operador.css / afiliado.css.
   ============================================================ */

/* C-2 · La ⓘ vuelve a ser un círculo de 20×20 (faltaba resetear el padding
   del navegador y proteger el círculo de encogerse). El toque de 44×44 lo
   sigue poniendo el ::after común de la matriz de dispositivos. */
.info-btn { padding: 0; line-height: 0; }
.info-btn .info-circulo { flex-shrink: 0; line-height: 1; }

/* C-3 · La barra de progreso se ve aunque se pinte con <span>, y su relleno
   se distingue de la pista también en tema oscuro. */
.progreso-pista, .progreso-relleno { display: block; }
.progreso-relleno { background: var(--brand-texto); }

/* C-4 · Acento de marca usado como TEXTO o TRAZO (el relleno de .btn no se toca:
   blanco sobre --brand ya cumple 6.94:1). */
a { color: var(--brand-texto); }
.btn-secundario { color: var(--brand-texto); }
.btn-secundario:hover { border-color: var(--brand-texto); }
.btn-texto, .btn-fantasma { color: var(--brand-texto); }
.tab[aria-selected="true"] { color: var(--brand-texto); border-block-end-color: var(--brand-texto); }
.miga a:hover { color: var(--brand-texto); }

/* C-5 · Un icono del set dentro de una insignia: 14px, sin empujar la línea. */
.insignia .icono, .insignia > svg { inline-size: 14px; block-size: 14px; flex-shrink: 0; }

/* C-5 bis · Estado neutro/pendiente: ni marca ni semántico (la insignia sin
   modificador usa el tinte de marca y se lee como «ya está bien»). */
.insignia--neutra {
  background: var(--surface-2); color: var(--text-2);
  box-shadow: inset 0 0 0 1px var(--border);
}

/* C-6 · Estados G.5: vacío y error hablan el mismo idioma visual. */
.estado-vacio .icono, .estado-vacio svg { inline-size: 48px; block-size: 48px; opacity: .7; color: var(--text-2); }
.estado-vacio h2 { font-size: var(--fs-h3); line-height: var(--lh-h3); color: var(--text); margin: 0; }
.estado-vacio p { max-inline-size: 46ch; margin: 0; }
.estado-vacio .btn { margin-block-start: var(--sp-1); }

.estado-error {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-4); text-align: center;
}
.estado-error .icono, .estado-error svg { inline-size: 48px; block-size: 48px; color: var(--danger); }
.estado-error h2 { font-size: var(--fs-h3); line-height: var(--lh-h3); color: var(--text); margin: 0; }
.estado-error p { max-inline-size: 46ch; margin: 0; color: var(--text-2); }

/* C-7 · El toast dice de qué habla con dos señales: icono + barra de color. */
.toast { border-inline-start: 3px solid transparent; }
.toast--exito   { border-inline-start-color: var(--success-fill); }
.toast--aviso   { border-inline-start-color: var(--warning-fill); }
.toast--peligro { border-inline-start-color: var(--danger); }
.toast--info    { border-inline-start-color: var(--info); }
.toast .icono   { flex-shrink: 0; }

/* C-9 · Compacto también cuando la pantalla lo pidió con min-height en línea. */
.btn[style*="min-height:36px"], .btn[style*="min-height: 36px"] {
  min-height: 36px; padding: 0 var(--sp-3); font-size: var(--fs-small);
}

/* C-9 bis · El híbrido .btn.btn-texto era un botón fantasma disfrazado de
   enlace azul: se le da cuerpo de secundario para que SE VEA que es un botón
   (PULIDO §3/§5). Excepción: el «cancelar» de los popups sigue siendo terciario. */
.btn.btn-texto, .btn.btn-fantasma {
  background: var(--surface);
  border: var(--borde-grosor) solid var(--border);
  color: var(--brand-texto);
}
.btn.btn-texto:hover, .btn.btn-fantasma:hover { background: var(--surface-2); border-color: var(--brand-texto); }
.popup-acciones .btn.btn-texto { background: transparent; border: none; }
.popup-acciones .btn.btn-texto:hover { background: var(--surface-2); }

/* C-10 · Tabla de datos: cifras comparables, cabecera honesta y pie visible. */
.tabla { font-variant-numeric: tabular-nums; }
.tabla .num, .tabla th.num, .tabla td.num { text-align: end; font-variant-numeric: tabular-nums; }
.tabla th { border-block-end: 2px solid var(--border); }
.tabla tbody tr:last-child td { border-block-end: none; }
.tabla-pie { border-block-start: var(--borde-grosor) solid var(--border); }

/* La cabecera solo hace la manita si de verdad ordena. */
.tabla th { cursor: default; }
.tabla th[aria-sort] { cursor: pointer; user-select: none; }
.tabla th[aria-sort]:hover { color: var(--text); }
.tabla th[aria-sort] .flecha-orden { opacity: 0; margin-inline-start: var(--sp-1); }
.tabla th[aria-sort="ascending"] .flecha-orden,
.tabla th[aria-sort="descending"] .flecha-orden { opacity: 1; }
.tabla th[aria-sort="descending"] .flecha-orden { transform: rotate(180deg); }

/* La cabecera se fija DENTRO de su envoltura (que es quien se desplaza), así que
   siempre va a `top: 0`. Antes se le restaba el alto de la barra superior porque
   se creía que el que se desplazaba era la página; ya no es el caso. Se deja el
   selector para anular cualquier resto de esa idea en los CSS de cada rol. */
.panel-contenido .tabla th, .admin-contenido .tabla th { top: 0; }

/* Solo la tabla suelta (sin envoltura) se fija respecto a la página, y entonces
   sí tiene que dejar hueco a la barra superior. */
:is(.panel-contenido, .admin-contenido) > .tabla > thead > tr > th,
:is(.panel-contenido, .admin-contenido) .tarjeta > .tabla > thead > tr > th { top: var(--alto-cabecera-chat); }

/* C-11 · Lista de filas dentro de una tarjeta: sin padding y con el radio recortado. */
.tarjeta--lista { padding: 0; overflow: hidden; }

/* Tabla o tarjeta dentro de otra tarjeta: una sola elevación. */
.tabla-envoltura--plana, .tarjeta .tabla-envoltura { box-shadow: none; border-radius: var(--r-campo); }
.tarjeta .tarjeta { box-shadow: none; border: var(--borde-grosor) solid var(--border); }

/* Banner dentro del contenido (no a ancho completo bajo la cabecera). */
.banner--tarjeta {
  border-radius: var(--r-tarjeta);
  border-inline-start: 3px solid currentColor;   /* segunda señal además del color */
}
.banner .icono { flex-shrink: 0; }

/* C-12 · Título de bloque dentro de tarjeta: 17/24/600 + aire fijo debajo. */
.seccion-titulo {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-h3);
  margin: 0 0 var(--sp-3);
}
/* CP-525 · overflow-wrap: un título de una sola palabra larga
   («Systemzustand») parte antes que empujar el viewport y dejar
   la ⓘ fuera de pantalla. */
.seccion-titulo > .crece { min-width: 0; overflow-wrap: anywhere; }
.seccion-titulo + .tabla-envoltura, .seccion-titulo + .ajuste-fila { margin-block-start: 0; }
/* Los <h2>/<h3> sueltos dentro de una tarjeta caen aquí sin tocar las pantallas.
   Se excluye .columna: esa tarjeta ya separa a sus hijos con gap y el margen se sumaría. */
.tarjeta:not(.columna) > h2, .tarjeta:not(.columna) > h3 {
  font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: var(--fw-h3);
  margin: 0 0 var(--sp-3);
}

/* C-13 · Avatares: nunca deformados, siempre despegados del fondo. */
.avatar img, .avatar svg { object-fit: cover; border-radius: inherit; }
.avatar { box-shadow: inset 0 0 0 1px var(--border); }
.avatar--64 { inline-size: 64px; block-size: 64px; }

/* Elegido = dos señales: aro (ya existe) + check. El overflow se abre solo aquí;
   el recorte circular lo mantiene el border-radius heredado por la imagen. */
.avatar--elegido { position: relative; overflow: visible; }
.avatar--elegido::after {
  content: ""; position: absolute; inset-block-end: -2px; inset-inline-end: -2px;
  inline-size: 18px; block-size: 18px; border-radius: var(--r-redondo);
  background: var(--brand) center/12px no-repeat
    url("data:image/svg+xml;utf8,<svg 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'><path d='M4 12.5l5 5L20 6.5'/></svg>");
}

/* C-14 · Nota con icono: el dibujo a la izquierda y el texto envolviendo a su lado
   (gana a la regla global .fila { flex-wrap: wrap } de la matriz de dispositivos). */
.nota-icono { display: flex; align-items: flex-start; gap: var(--sp-2); flex-wrap: nowrap; }
.nota-icono > .icono { flex-shrink: 0; margin-block-start: 2px; color: var(--text-2); }
.nota-icono > span { min-width: 0; }

/* Vista previa del último mensaje en una fila de chat: nunca rompe la fila. */
.fila-chat .previa { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Bloque plegable estándar (<details>): el chevron gira al abrirse. */
.bloque-plegable { margin-block-end: var(--sp-3); }
.bloque-plegable > summary { cursor: pointer; list-style: none; }
/* FALLO (V-03) · 05/09/2026 — el renglón que se pulsa para abrir un plegable
   medía 22 px de alto (18 el de «Vista previa» del compositor de mensajes
   programados): la mitad de los 44 que pide WCAG 2.5.5. MOTIVO: `summary` SÍ
   está en la lista de `position: relative` de la línea 1073, pero NO en el
   `:where(...)` de la 1074 que pone la capa `::after` de 44×44 — ese
   `position: relative` se quedó sin nada que anclar. Medido con
   `document.elementFromPoint` y con un clic de verdad: a 16 px del centro el
   clic NO llega al `<summary>`, cae en el vecino (`.fila-chat.op-fila`).
   SE ARREGLA CON ALTO, NO CON LA CAPA, y no es un capricho: es lo que ya hacen
   los otros cuatro plegables del proyecto (`.filtros-panel > summary`,
   `.af-acordeon summary`, `.op-faq summary`, `.datos-prueba summary`), y una
   capa de 44 sobre un renglón de 22 sobresaldría ~11 px por arriba y por abajo
   robándole el clic a lo que tenga al lado — el fallo del que avisa el
   comentario de la línea 1053.
   `:not(.tactil)` deja fuera a los que YA se estiran con su PROPIA capa
   (admin/informes: 22 px dibujados y 44 pulsables, medido): sin esa excepción
   crecerían de 22 a 44 y moverían píxeles en pantallas que hoy están bien.
   SEGUNDA MITAD DE LA REGLA (misma fecha) — un `.tactil` que lleva DENTRO un
   botón o un enlace tampoco recibe ya la capa: se la quita a propósito el
   `:not(:has(button, a[href]))` de la línea 107 (arreglo OP-1, el del botón
   «Tomar» de la cola). El `<summary>` de «Modo avanzado» de admin/ia entra justo
   ahí, porque lleva una ⓘ dentro (admin/pantallas/ia.js:211), y se quedó en
   22 px de pulsación — medido: el clic a 16 px del centro no llega a nadie. Se
   copia LA MISMA condición para darle alto a esos, y solo a esos. */
.bloque-plegable > summary:not(.tactil),
.bloque-plegable > summary.tactil:has(button, a[href]) { min-block-size: var(--tactil); }
.bloque-plegable > summary::-webkit-details-marker { display: none; }
.bloque-plegable > summary .icono:last-child { transition: transform var(--dur-rapida) var(--ease); }
.bloque-plegable[open] > summary .icono:last-child { transform: rotate(90deg); }
[dir="rtl"] .bloque-plegable[open] > summary .icono:last-child { transform: rotate(-90deg); }
.bloque-plegable-cuerpo { margin-block-start: var(--sp-3); }

/* La ⓘ viaja pegada a su texto; el hueco libre se lo queda lo que venga después.
   Se acota a los paneles (el cliente no los tiene). Admin va por .admin-contenido
   —su equivalente de .panel-contenido— porque era uno de los que lo pedía. */
.panel-contenido .fila > .crece:has(+ .info-btn),
.admin-contenido .fila > .crece:has(+ .info-btn),
.panel-contenido .kpi-etiqueta > .crece,
.admin-contenido .kpi-etiqueta > .crece { flex: 0 1 auto; }
.panel-contenido .fila > .info-btn:not(:last-child),
.admin-contenido .fila > .info-btn:not(:last-child) { margin-inline-end: auto; }

/* C-15 · Un campo bloqueado es de LECTURA, no está «apagado». */
.campo-control input:disabled, .campo-control select:disabled, .campo-control textarea:disabled {
  opacity: 1; color: var(--text-2); -webkit-text-fill-color: var(--text-2);
}

/* C-16 · El ✕ del popup es un icono del set, no un carácter. */
.popup-cerrar { font-size: 0; color: var(--text-2); }
.popup-cerrar .icono { inline-size: var(--icono-l); block-size: var(--icono-l); }

/* CP-205 · Selector de tema CON miniatura: se elige viendo cómo queda
   cada tema (los colores salen de los tokens --previa-* de A.1). */
.selector-tema { display: flex; gap: var(--sp-3); flex-wrap: wrap; }
.selector-tema button {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-1);
  background: var(--surface); border: var(--borde-grosor) solid var(--borde-control);  /* B12 */
  border-radius: var(--r-campo); padding: var(--sp-2); cursor: pointer;
  color: var(--text); font: inherit; font-size: var(--fs-small); min-width: 84px;
  min-height: var(--tactil);
}
.selector-tema button[aria-pressed="true"] { border-color: var(--brand-texto); box-shadow: 0 0 0 1px var(--brand-texto); }
.selector-tema button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.tema-mini { display: flex; width: 64px; height: 40px; border-radius: 6px; overflow: hidden; border: var(--borde-grosor) solid var(--border); }
.tema-mini-parte { position: relative; flex: 1; }
.tema-mini-burbuja { position: absolute; inset-inline-start: 4px; inset-block-start: 6px; inline-size: 24px; block-size: 8px; border-radius: 4px; }
.tema-mini-burbuja--propia { inset-inline-start: auto; inset-inline-end: 4px; inset-block-start: 20px; }
.tema-mini + span { line-height: var(--lh-small); }
.selector-tema .tema-mix-ahora { font-size: var(--fs-caption); color: var(--text-2); }

/* CP-203 · Recorte circular con zoom de la foto propia de perfil. */
.recorte-circulo {
  inline-size: 200px; block-size: 200px; border-radius: var(--r-redondo);
  overflow: hidden; margin-inline: auto; position: relative;
  border: var(--borde-grosor) solid var(--border); background: var(--surface-2);
}
.recorte-circulo img {
  /* (24/10/2026 · responsive) top/left FÍSICOS a propósito, como la ✓ de las casillas (arriba): centrar con un
     translate es un dibujo, no dirección de lectura. Con `inset-inline-start: 50%` en árabe eso era `right: 50%`
     y el translate(-50%) físico sacaba la foto ENTERA del círculo: operador, afiliado y cliente en árabe veían el
     círculo de recorte vacío y la vista previa nunca coincidía con lo que se guardaba (el lienzo recorta el centro). */
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%) scale(var(--recorte-zoom, 1));
  min-inline-size: 100%; min-block-size: 100%; object-fit: cover;
}

/* CP-241 · «Leído» se distingue por color Y por trazo (dos señales, A.6):
   el doble check leído se dibuja más grueso, no solo más verde. */
.checks--leido .icono { stroke-width: 2.5; }

/* CP-265 · Menú ▾ de la burbuja: aparece al pasar el ratón o con el foco
   dentro de la burbuja; nunca exige mantener pulsado. */
.burbuja { position: relative; }
.burbuja .burbuja-menu-btn {
  position: absolute; inset-inline-end: 4px; inset-block-start: 4px;
  /* 07/10/2026 · CP-540 · escondido con opacidad, no con display:none: así el teclado lo encuentra también al
     volver hacia atrás (Shift+Tab). MEDIDO ANTES: con display:none, Shift+Tab desde la burbuja siguiente se
     saltaba el ▾ (solo existía con el foco ya dentro de su burbuja). En reposo se ve igual: nada. */
  display: inline-flex; opacity: 0; align-items: center; justify-content: center;
  inline-size: 24px; block-size: 24px; border: none; border-radius: var(--r-campo);
  background: var(--surface); color: var(--text-2); cursor: pointer; padding: 0;
  box-shadow: var(--sombra-tarjeta);
}
.burbuja:hover .burbuja-menu-btn,
.burbuja:focus-within .burbuja-menu-btn,
.burbuja:focus .burbuja-menu-btn,
.burbuja .burbuja-menu-btn:focus-visible,
.burbuja.menu-abierto .burbuja-menu-btn { opacity: 1; }   /* CP-542: sigue ahí mientras su menú está abierto */
/* (24/10/2026 · responsive, consolidación) EN TÁCTIL NO HAY :hover: el ▾ quedaba invisible (opacidad 0) y el único
   camino al menú era mantener pulsado, justo lo que CP-265 no quiere exigir. Con (hover: none) se deja ver al 70 %
   en todos los paneles (la regla del operador en operador.css dice lo mismo para su hilo). Inerte en la suite visual
   (Chrome sin ventana responde hover: hover); :hover/:focus/.menu-abierto siguen poniéndolo al 100 %. */
@media (hover: none) { .burbuja .burbuja-menu-btn { opacity: .7; } }
.burbuja .burbuja-menu-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }

/* CP-263 · El composer avisa EN VIVO cuando el anti-robo detecta algo */
.op-composer textarea.op-texto-dlp { outline: 2px solid var(--danger); outline-offset: -2px; border-radius: var(--r-campo); }


/* ---------- B1 · Estado de la conexión con el servidor (aviso discreto) ----------
   Píldora al pie: «Sin conexión con el servidor · usando datos locales» o
   «N cambios pendientes de enviar». Solo aparece cuando hay algo que decir. */
.api-estado {
  position: fixed;
  /* (24/10/2026 · responsive) DÓNDE SE POSA (ver matriz 10.4). `--pie-pildora` lo comparten las tres píldoras (B1,
     B2 y la de pwa.css): si hay una barra de escribir a la vista, `--pie-flotante` (ui.js, en píxeles del layout:
     ya lleva la barra de gestos y el teclado) las sube por ENCIMA del cajón; si no, la barra de gestos del iPhone
     y lo que tape su teclado en pantalla. MEDIDO ANTES (lectura, 390 px): con el servidor caído, «Sin conexión con
     el servidor · usando datos locales» y «Reconectando…» cubrían el textarea y el botón de enviar enteros mientras
     duraba la caída, y la primera caía a 12 px del borde físico, dentro del indicador de inicio. En la suite (sin
     píldoras, env() y --teclado a 0) todo mide lo de siempre. */
  --pie-pildora: var(--pie-flotante, calc(var(--sp-3) + var(--safe-abajo) + var(--teclado, 0px)));
  inset-block-end: var(--pie-pildora); inset-inline-start: 50%; transform: translateX(-50%);
  z-index: var(--z-toast, 90); display: inline-flex; align-items: center; gap: var(--sp-2);
  max-width: calc(100% - var(--sp-8)); padding: var(--sp-2) var(--sp-4);
  background: var(--surface); color: var(--text-2); border: var(--borde-grosor) solid var(--border);
  border-radius: 999px; box-shadow: var(--sombra-2, 0 2px 8px rgba(0,0,0,.12));
  font-size: var(--fs-small); line-height: 1.3; pointer-events: none;
}
[dir="rtl"] .api-estado { transform: translateX(50%); }
.api-estado-punto { width: 8px; height: 8px; border-radius: 50%; background: var(--warning, var(--brand)); flex-shrink: 0; }

/* ---------- B2 · Estado del canal en vivo (tiempo real) ----------
   Píldora discreta «Reconectando…» / «Sin conexión en vivo». No tapa el
   chat; si ya está la píldora de la API, se coloca encima de ella. */
.tr-estado { inset-block-end: var(--pie-pildora); }
body:has(#api-estado) .tr-estado { inset-block-end: calc(var(--pie-pildora) + 44px); }
.tr-punto { background: var(--warning-fill, var(--warning)); }
.tr-estado--reconectando .tr-punto { animation: tr-pulso 1.2s ease-in-out infinite; }
@keyframes tr-pulso { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) { .tr-estado--reconectando .tr-punto { animation: none; } }
/* «escribiendo…» y presencia en vivo (compartidos por las cuatro interfaces) */
.tr-escribiendo { color: var(--text-2); font-style: italic; }
.tr-presencia { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--borde-control); vertical-align: middle; margin-inline-end: var(--sp-1); }
.tr-presencia--en-linea { background: var(--success-fill, var(--success)); }
.tr-presencia--conectado { background: var(--warning-fill, var(--warning)); }
.checks--bloqueado { color: var(--danger); }

/* ---------- B3 · Traducción en vivo (IA 4): etiqueta bajo la burbuja, chip de idioma, línea del composer ---------- */
.burbuja-trad { display: block; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-2); margin-block-start: 2px; }
.burbuja-trad-btn, .enlace-traduccion {
  font: inherit; font-size: var(--fs-caption); color: var(--info); background: none; border: none;
  padding: 2px 0; cursor: pointer; text-decoration: none; min-height: 24px; /* toque suficiente sin engordar la burbuja */
}
.burbuja-trad-btn:hover, .enlace-traduccion:hover { text-decoration: underline; }
.burbuja-trad-espera { color: var(--text-2); font-style: italic; }
.burbuja-trad-espera svg { animation: b3-girar 1.2s linear infinite; }
.burbuja-trad-aviso { color: var(--warning-tint-text, var(--text-2)); }
.burbuja--propia .burbuja-trad, .burbuja--propia .burbuja-trad-btn, .burbuja--operador .burbuja-trad, .burbuja--operador .burbuja-trad-btn { color: var(--bubble-propia-texto); opacity: .85; }
/* OLEADA 29 · CP-541 (30/09/2026) · dentro de la burbuja PROPIA (la del color de la marca), el anillo de foco del ▾ y de
   «Traducido · ver original» va del color del texto de esa burbuja: el color de foco del panel ES el de la burbuja
   del operador.
   MEDIDO ANTES (Chrome, n_p0_o29_reglas_b.py, operador/bandeja?id=…, Claro y Mix con el sistema claro): 1:1, el
   anillo no se veía. AHORA se distingue de la burbuja (el texto de la burbuja está hecho para leerse sobre ella). */
.burbuja--propia .burbuja-menu-btn:focus-visible, .burbuja--propia .burbuja-trad-btn:focus-visible { outline-color: var(--bubble-propia-texto); }
/* la del operador lleva el texto --brand-contrast sobre --bubble-operador (operador.css) */
.burbuja--operador .burbuja-menu-btn:focus-visible, .burbuja--operador .burbuja-trad-btn:focus-visible { outline-color: var(--brand-contrast); }
@keyframes b3-girar { to { transform: rotate(360deg); } }
.chip-idiomas, .op-chip-idioma { gap: 6px; font-weight: 500; white-space: nowrap; }
.op-chip-idioma { cursor: pointer; color: var(--text); font: inherit; font-size: var(--fs-small); }
.trad-linea { /* «Escribo en: árabe ▾ · se enviará al cliente en español» */
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3); font-size: var(--fs-caption); color: var(--text-2);
}
.trad-linea select { font: inherit; font-size: var(--fs-caption); color: var(--text); background: transparent; border: none; border-bottom: 1px dashed var(--border); padding: 0 2px; cursor: pointer; }
.trad-linea .enlace-traduccion { min-height: 0; }
.trad-aviso-motor { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-1) var(--sp-3); font-size: var(--fs-caption); color: var(--text-2); background: var(--surface-2); }
[dir="rtl"] .burbuja-texto, [dir="ltr"] .burbuja-texto { unicode-bidi: plaintext; }
.transcripcion { font-size: var(--fs-small); margin-block-start: var(--sp-1); }

/* ---------- §12-bis · Notas de «Datos de prueba» con relleno de un clic ----------
   El patrón de la tanda de acceso, ahora también en las pantallas de
   ACTIVACIÓN (fallo real del titular, 22/08/2026). No es un estilo nuevo:
   es la MISMA nota de siempre, que ahora hace sitio a su botón.
   Cada interfaz sigue poniendo su clase (.op-nota-demo, .af-nota-demo,
   .adm-nota-demo, .nota-demo); esto solo coloca las piezas. */
.nota-demo-rellena {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-2) var(--sp-3);
}
.nota-demo-rellena .nota-demo-texto { flex: 1 1 180px; min-width: 0; }
.nota-demo-rellena .btn { flex-shrink: 0; }
.nota-demo-rellena code { direction: ltr; unicode-bidi: isolate; white-space: nowrap; }

/* La nota base (la usan el popup de palabra peligrosa y quien no tenga
   clase propia). Cliente/operador/afiliado/admin la afinan en su CSS. */
.nota-demo {
  margin-block-start: var(--sp-3);
  padding: var(--sp-3);
  background: var(--info-tint); color: var(--info-tint-text);
  border-radius: var(--r-campo);
  font-size: var(--fs-small); line-height: var(--lh-small);
  text-align: start;
}

/* El aviso en llano que crea UI.avisoCampo cuando no hay .campo-error
   propio (los OTP de casillas, el popup peligroso…). */
[data-demo-aviso] { margin-block-start: var(--sp-2); }

/* ============================================================
   §15 · Portafolio — «mis mensajes predeterminados» (UI.portafolio)
   Botoncito discreto en la barra de escribir + panel lateral
   (hoja inferior en móvil). Solo tokens: claro/oscuro/RTL solos.
   ============================================================ */
.portafolio-lanzador { display: inline-flex; align-items: center; gap: 2px; }
.portafolio-lanzador .info-btn { align-self: flex-start; }

.portafolio-capa { position: fixed; inset: 0; z-index: var(--z-capas); background: transparent; }
.portafolio-panel {
  position: absolute; inset-block: 0; inset-inline-end: 0;
  width: min(400px, 100%);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-inline-start: var(--borde-grosor) solid var(--border);
  box-shadow: var(--sombra-flotante);
  animation: portafolio-entra var(--dur) var(--ease);
}
@keyframes portafolio-entra { from { transform: translateX(16px); opacity: 0; } to { transform: none; opacity: 1; } }
[dir="rtl"] .portafolio-panel { animation-name: portafolio-entra-rtl; }
@keyframes portafolio-entra-rtl { from { transform: translateX(-16px); opacity: 0; } to { transform: none; opacity: 1; } }

.portafolio-cabecera {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: var(--tactil); padding: var(--sp-2) var(--sp-3);
  border-block-end: var(--borde-grosor) solid var(--border);
}
.portafolio-cuerpo {
  flex: 1; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-3);
}
/* §15 · «los tuyos» / «los del negocio» (solo cuando quien abre es el admin) */
.portafolio-fuentes {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-block-end: var(--borde-grosor) solid var(--border);
}
.portafolio-fuentes .chip { font: inherit; font-size: var(--fs-small); color: var(--text); cursor: pointer; }
.portafolio-fuentes .chip:hover { background: var(--border); }
.portafolio-fuentes .chip[aria-pressed="true"] {
  background: var(--brand-tint); color: var(--brand-tint-text);
  border-color: transparent; font-weight: var(--fw-strong); cursor: default;
}
.portafolio-pie {
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3);
  border-block-start: var(--borde-grosor) solid var(--border);
}
.portafolio-ayuda { margin: 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-2); }

.portafolio-sugerencias {
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface-2);
  border-radius: var(--r-tarjeta);
}
.portafolio-nota {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-3);
  border: var(--borde-grosor) solid var(--border);
  border-radius: var(--r-tarjeta);
  background: var(--surface);
}
.portafolio-nota--sugerida { border-color: var(--brand-texto); }
.portafolio-num {
  flex-shrink: 0; min-width: 24px; height: 24px; padding-inline: 6px;
  display: grid; place-items: center;
  border-radius: var(--r-redondo);
  background: var(--brand-tint); color: var(--brand-tint-text);
  font-size: var(--fs-caption); font-weight: var(--fw-strong);
  font-variant-numeric: tabular-nums;
}
.portafolio-previa {
  margin: var(--sp-1) 0 0;
  font-size: var(--fs-small); line-height: var(--lh-small); color: var(--text-2);
  white-space: pre-line; overflow-wrap: anywhere;
}
.portafolio-porque { margin: var(--sp-1) 0 0; font-size: var(--fs-caption); color: var(--text-2); font-style: italic; }
.portafolio-acciones { display: flex; align-items: center; gap: var(--sp-2); margin-block-start: var(--sp-2); }

/* Móvil: hoja inferior no intrusiva (mismo gesto que las hojas A.4) */
@media (max-width: 720px) {
  .portafolio-panel {
    inset-inline: 0; inset-block: auto 0;
    width: 100%; max-height: 80vh;
    border-inline-start: none;
    border-block-start: var(--borde-grosor) solid var(--border);
    border-start-start-radius: var(--r-tarjeta); border-start-end-radius: var(--r-tarjeta);
    animation-name: portafolio-sube;
  }
  [dir="rtl"] .portafolio-panel { animation-name: portafolio-sube; }
}
@keyframes portafolio-sube { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }

/* ============================================================
   §16 · BARRA DE ACCIONES POR FILA (UI.accionesFila)
   24/08/2026 · ampliada el 03/09/2026 (punto 4 del titular)
   Fallo real del titular: «actualmente hay que rebuscar bastante las
   cosas». Fuera el menú ⋮: cada fila enseña SUS botones.
   Punto 4 (03/09/2026): «añádele a las acciones un cuadrado a cada
   acción con un color, que cuando pases el cursor por encima haya una
   breve descripción de lo que hace». Cada botón es ahora una PASTILLA
   CUADRADA de color suave con el icono en el color fuerte del mismo
   tono, y su globo lleva el NOMBRE en negrita y una frase en llano.
   Los pares de color viven en tokens.css, con sus ratios calculados.
   Únicamente tokens y propiedades lógicas: claro/oscuro y RTL salen solos.
   ============================================================ */
.acciones-fila {
  display: inline-flex; align-items: center; justify-content: flex-end;
  gap: var(--sp-1); flex-wrap: nowrap;
  /* En RTL la barra se espeja sola: `row` sigue la dirección del documento
     y aquí no hay ni una propiedad física. */
}

/* ---------- La pastilla cuadrada ----------
   La zona PULSABLE sigue midiendo 44×44 (regla táctil de A.3); el
   cuadrado que se VE mide --acc-cuadro (36px) y lo dibuja ::before,
   centrado. El tono entra por `data-tono` y solo mueve tres variables,
   así que añadir un color nuevo no toca ni una regla de aquí. */
.btn-accion {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: var(--tactil); block-size: var(--tactil); flex-shrink: 0;
  padding: 0; border: none; background: transparent;
  border-radius: var(--r-campo);
  cursor: pointer; font: inherit;
  /* Neutro de fábrica: una pantalla que no declare tono sale en gris. */
  --acc-bg: var(--acc-gris-bg);
  --acc-ico: var(--acc-gris-ico);
  --acc-borde: var(--acc-gris-borde);
  color: var(--acc-ico);
}
/* Significado del color, el mismo en TODO el sistema (ver tokens.css):
   verde = aprobar/activar/confirmar · azul = ver/abrir/escribir ·
   ámbar = suspender/pausar/avisar · rojo = eliminar/bloquear/denegar ·
   gris = neutro/secundario. */
.btn-accion[data-tono="verde"] { --acc-bg: var(--acc-verde-bg); --acc-ico: var(--acc-verde-ico); --acc-borde: transparent; }
.btn-accion[data-tono="azul"]  { --acc-bg: var(--acc-azul-bg);  --acc-ico: var(--acc-azul-ico);  --acc-borde: transparent; }
.btn-accion[data-tono="ambar"] { --acc-bg: var(--acc-ambar-bg); --acc-ico: var(--acc-ambar-ico); --acc-borde: transparent; }
.btn-accion[data-tono="rojo"]  { --acc-bg: var(--acc-rojo-bg);  --acc-ico: var(--acc-rojo-ico);  --acc-borde: transparent; }
.btn-accion[data-tono="gris"]  { --acc-bg: var(--acc-gris-bg);  --acc-ico: var(--acc-gris-ico);  --acc-borde: var(--acc-gris-borde); }
/* Red de seguridad: una peligrosa sin tono declarado sale roja igual
   (pierde ante cualquier data-tono, que es más específico). */
.btn-accion--peligro { --acc-bg: var(--acc-rojo-bg); --acc-ico: var(--acc-rojo-ico); --acc-borde: transparent; }

.btn-accion::before {
  content: ''; position: absolute;
  inset: calc((var(--tactil) - var(--acc-cuadro)) / 2);
  border-radius: var(--r-campo);
  background: var(--acc-bg);
  box-shadow: inset 0 0 0 var(--borde-grosor) var(--acc-borde);
  transition: transform var(--dur-rapida) var(--ease), box-shadow var(--dur-rapida) var(--ease);
}
.btn-accion .icono { position: relative; flex-shrink: 0; color: var(--acc-ico); }
/* Al pasar por encima (y al enfocar con el teclado) el cuadrado se marca
   con un aro de SU PROPIO tono: se ve cuál está bajo el cursor sin que el
   color cambie de significado. */
.btn-accion:hover::before,
.btn-accion:focus-visible::before {
  box-shadow: inset 0 0 0 2px var(--acc-ico);
  transform: scale(1.06);
}
.btn-accion:active::before { transform: scale(.94); }
/* El anillo de foco abraza el CUADRADO, no la zona táctil de 44, y SEPARADO de él (A.4: 2 px de anillo a 2 px).
   07/10/2026 · CP-541 · MEDIDO ANTES (Chrome, Tab de verdad en admin/clientes): el anillo empezaba justo en el borde
   del cuadrado (hueco 0) y el cuadrado, al enfocarse, crece un 6 % (scale 1.06): se montaba encima del anillo.
   AHORA: 3 px de hueco sobre el cuadrado en reposo, ~2 px con el cuadrado crecido; sigue dentro de los 44 px. */
.btn-accion:focus-visible { outline-offset: calc((var(--tactil) - var(--acc-cuadro)) / -2 + 3px); }
/* El texto corto solo aparece en el modo tarjeta (pantallas estrechas) */
.btn-accion-texto { display: none; }

/* ---------- El globo: NOMBRE en negrita + qué hace, en una frase ----------
   Etiqueta flotante propia (nunca el `title` del navegador): capa
   --z-globos, la misma que ya deja leerse por encima de diálogos y hojas.
   Se cierra con Esc, al salir el ratón y al mover el foco. */
.globo-etiqueta {
  position: absolute; z-index: var(--z-globos); pointer-events: none;
  max-inline-size: 240px; padding: var(--sp-1) var(--sp-2);
  background: var(--text); color: var(--bg);
  border-radius: var(--r-campo); box-shadow: var(--sombra-flotante);
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption);
  text-align: start;
  animation: globo-etiqueta-entra var(--dur-rapida) var(--ease);
}
/* Con descripción el globo respira: dos líneas y texto que sí puede partir. */
.globo-etiqueta--desc { max-inline-size: 260px; padding: var(--sp-2) var(--sp-3); }
.globo-etiqueta-titulo {
  display: block; font-weight: var(--fw-strong);
  font-size: var(--fs-small); line-height: var(--lh-small);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.globo-etiqueta-desc {
  display: block; margin-block-start: 2px;
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption);
  white-space: normal; opacity: .85;
}
/* Piquito apuntando al botón. `--flecha` es la distancia desde el borde
   INICIAL del globo (la calcula ui.js, ya espejada en árabe), así que
   aquí no hace falta ni un left ni un right. */
.globo-etiqueta::after {
  content: ''; position: absolute;
  inset-block-start: 100%;
  inset-inline-start: 0; margin-inline-start: calc(var(--flecha, 50%) - 5px);
  border: 5px solid transparent;
  border-block-start-color: var(--text);
}
.globo-etiqueta--abajo::after {
  inset-block-start: auto; inset-block-end: 100%;
  border-block-start-color: transparent;
  border-block-end-color: var(--text);
}
@keyframes globo-etiqueta-entra { from { opacity: 0; } to { opacity: 1; } }

/* Panel del botón «Más»: SOLO acciones secundarias (las principales jamás
   entran aquí). Hereda el aspecto de .menu-flotante y lleva las MISMAS
   pastillas de color, para que una acción se reconozca esté donde esté. */
.menu-acciones { min-inline-size: 240px; max-inline-size: 320px; }
.menu-acciones [role="menuitem"] {
  display: flex; align-items: center; gap: var(--sp-3);
  min-block-size: var(--tactil); inline-size: 100%;
  padding: var(--sp-2) var(--sp-4);
  border: none; background: none; font: inherit; color: var(--text);
  text-align: start; cursor: pointer;
  --acc-bg: var(--acc-gris-bg); --acc-ico: var(--acc-gris-ico); --acc-borde: var(--acc-gris-borde);
}
.menu-acciones [role="menuitem"][data-tono="verde"] { --acc-bg: var(--acc-verde-bg); --acc-ico: var(--acc-verde-ico); --acc-borde: transparent; }
.menu-acciones [role="menuitem"][data-tono="azul"]  { --acc-bg: var(--acc-azul-bg);  --acc-ico: var(--acc-azul-ico);  --acc-borde: transparent; }
.menu-acciones [role="menuitem"][data-tono="ambar"] { --acc-bg: var(--acc-ambar-bg); --acc-ico: var(--acc-ambar-ico); --acc-borde: transparent; }
.menu-acciones [role="menuitem"][data-tono="rojo"]  { --acc-bg: var(--acc-rojo-bg);  --acc-ico: var(--acc-rojo-ico);  --acc-borde: transparent; }
.menu-acciones [role="menuitem"][data-tono="gris"]  { --acc-bg: var(--acc-gris-bg);  --acc-ico: var(--acc-gris-ico);  --acc-borde: var(--acc-gris-borde); }
.menu-acc-cuadro {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: var(--acc-cuadro); block-size: var(--acc-cuadro); flex-shrink: 0;
  border-radius: var(--r-campo);
  background: var(--acc-bg); color: var(--acc-ico);
  box-shadow: inset 0 0 0 var(--borde-grosor) var(--acc-borde);
}
.menu-acc-texto { display: block; min-inline-size: 0; }
.menu-acc-nombre { display: block; }
.menu-acc-desc {
  display: block; color: var(--text-2);
  font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption);
}
.menu-acciones [role="menuitem"]:hover { background: var(--surface-2); }
.menu-acciones [role="menuitem"].peligro .menu-acc-nombre { color: var(--danger); }

/* ---------- Pantallas estrechas: icono + texto corto debajo ----------
   En modo tarjeta la barra ocupa su propia línea al pie de la tarjeta:
   nunca cortada, nunca desbordada, y cada botón sigue midiendo 44 de alto.
   Aquí la pastilla ES el botón entero (el color envuelve icono y texto),
   así que el cuadrado de ::before sobra. */
@media (max-width: 767px) {
  .acciones-fila {
    display: flex; inline-size: 100%; justify-content: flex-start;
    flex-wrap: wrap; gap: var(--sp-2);
  }
  .btn-accion {
    flex: 1 1 auto; flex-direction: column; gap: 2px;
    inline-size: auto; min-inline-size: var(--tactil); max-inline-size: 100%;
    block-size: auto; min-block-size: var(--tactil);
    padding: var(--sp-2) var(--sp-2);
    border-radius: var(--r-campo);
    background: var(--acc-bg);
    box-shadow: inset 0 0 0 var(--borde-grosor) var(--acc-borde);
    transition: box-shadow var(--dur-rapida) var(--ease);
  }
  .btn-accion::before { content: none; }
  .btn-accion:hover, .btn-accion:focus-visible { box-shadow: inset 0 0 0 2px var(--acc-ico); }
  .btn-accion:focus-visible { outline-offset: 2px; }
  .btn-accion-texto {
    display: block; max-inline-size: 100%; color: var(--acc-ico);
    font-size: var(--fs-caption); line-height: var(--lh-caption); font-weight: var(--fw-caption);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* La celda que contiene la barra baja a su propia línea dentro de la
     tarjeta apilada (en vez de apretujarse al final de la primera). */
  .tabla--tarjetas tbody td:has(> .acciones-fila) {
    flex: 1 0 100%; margin-inline-start: 0; margin-block-start: var(--sp-1);
  }
}

/* ============================================================
   FLUIDEZ AL CAMBIAR DE PANTALLA (queja 2 del titular · 29/08/2026)
   El router deja un CALCO de la pantalla anterior encima mientras
   construye la nueva por debajo: no se ve el hueco en blanco ni el
   esqueleto gris cuando los datos ya están en memoria. Solo si la
   espera pasa de 300 ms se suelta el calco (entonces sí se ve el
   esqueleto) y aparece la barra finísima de arriba.
   El fundido usa --dur-rapida (150 ms); «reducir movimiento» ya lo
   deja en 1 ms desde tokens.css §A.5, así que no hay que repetirlo.
   Ver assets/js/core/router.js.
   ============================================================ */
.rt-calco {
  /* La posición, el tamaño y el fondo los pone el router en línea
     (copia exacta de dónde estaba el contenido). */
  z-index: calc(var(--z-barras) - 1);   /* por debajo de menús, diálogos, ⓘ y toasts */
  pointer-events: none;
  opacity: 1;
  transition: opacity var(--dur-rapida) ease-out;
  contain: paint;
}
.rt-calco--sale { opacity: 0; }

/* Barra de progreso finísima: solo aparece cuando la espera es larga
   (>300 ms). Crece deprisa al principio y se frena, para no mentir. */
.rt-progreso {
  position: fixed;
  inset-block-start: 0;
  inset-inline-start: 0;
  block-size: 2px;
  inline-size: 0;
  z-index: var(--z-avisos);
  background: var(--brand);
  border-end-end-radius: 2px;
  opacity: 0;
  pointer-events: none;
}
.rt-progreso--activa {
  opacity: 1;
  animation: rt-progreso-avanza 8s cubic-bezier(.15, .85, .25, 1) forwards;
}
.rt-progreso--fin {
  animation: none;
  inline-size: 100%;
  opacity: 0;
  transition: inline-size 120ms ease-out, opacity 200ms ease-out 100ms;
}
@keyframes rt-progreso-avanza {
  from { inline-size: 0; }
  to   { inline-size: 92%; }
}
@media (prefers-reduced-motion: reduce) {
  /* Sin movimiento: la barra se queda quieta mientras se espera. */
  .rt-progreso--activa { animation: none; inline-size: 40%; }
}

/* ============================================================
   B6 · MULTIMEDIA — adjuntar, visor de imágenes y nota de voz
   ============================================================
   Todo con tokens: no hay un solo color suelto. Propiedades lógicas
   en todo lo que se pueda espejar, para que en árabe (RTL) salga bien
   sin escribir una sola regla aparte. Zonas pulsables de 44 px.
   ------------------------------------------------------------ */
.popup--adjuntar, .popup--voz { width: min(520px, 100%); }
.mm-cuerpo { max-block-size: min(60vh, 460px); overflow-y: auto; }
/* OLEADA 29 · CP-541 (30/09/2026) · el anillo de foco (2 px, separado 2 px) de lo de DENTRO de esta caja con
   desplazamiento cabe en ella: 4 px de margen interior. MEDIDO ANTES (Chrome, n_p0_o29_reglas_b.py): el anillo de
   cada zona de «Elegir archivo» (adjuntar) salía recortado por esta caja (overflow), en los cuatro temas. */
.mm-cuerpo { padding: 4px; }
.mm-pie { flex-direction: column; }
.mm-pie .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2); }

/* ---- la zona de elegir / arrastrar ---- */
.mm-zona {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
  padding: var(--sp-6) var(--sp-4);
  border: 2px dashed var(--borde-control); border-radius: var(--r-tarjeta);  /* B12 */
  background: var(--surface-2); color: var(--text-2);
  text-align: center; cursor: pointer;
}
.mm-zona:hover, .mm-zona:focus-visible { border-color: var(--brand-texto); color: var(--text); }
.mm-zona .icono { color: var(--brand-texto); }
.mm-zona-titulo { font-weight: var(--fw-strong); color: var(--text); margin: 0; }
.mm-zona-nota { font-size: var(--fs-small); line-height: var(--lh-small); color: var(--text-2); margin: 0; }
.mm-zona-botones { display: flex; flex-wrap: wrap; gap: var(--sp-2); justify-content: center; margin-block-start: var(--sp-2); }
.mm-zona-botones .btn { display: inline-flex; align-items: center; gap: var(--sp-2); }
/* Mientras se arrastra algo por encima, la ventana entera lo dice. */
.mm-soltando .mm-zona, .mm-soltando { outline: 2px dashed var(--brand-texto); outline-offset: -6px; }
/* OLEADA 29 · CP-541 (30/09/2026) · y si la zona tiene el foco del teclado mientras se arrastra, su anillo de foco
   (2 px sólido, separado 2 px) EN LUGAR del rayado: el contorno de foco no se quita nunca. */
.mm-soltando .mm-zona:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.mm-soltando .mm-zona { background: var(--brand-tint); color: var(--brand-tint-text); }

/* ---- la lista de lo elegido ---- */
.mm-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
.mm-item {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-2); border: var(--borde-grosor) solid var(--border);
  border-radius: var(--r-campo); background: var(--surface);
}
.mm-item--error { border-color: var(--danger); background: var(--danger-tint); }
.mm-previa {
  flex: 0 0 auto; inline-size: 56px; block-size: 56px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--r-campo); object-fit: cover;
  background: var(--surface-2); color: var(--text-2);
}
.mm-previa--revision { color: var(--warning); }
.mm-item-datos { flex: 1 1 auto; min-inline-size: 0; }
.mm-nombre {
  margin: 0; font-weight: var(--fw-strong); color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mm-meta { margin: 2px 0 0; font-size: var(--fs-small); line-height: var(--lh-small); color: var(--text-2); }
.mm-ok, .mm-error {
  display: flex; align-items: center; gap: var(--sp-1);
  margin: 2px 0 0; font-size: var(--fs-small); line-height: var(--lh-small);
}
.mm-ok { color: var(--success); }
.mm-error { color: var(--danger-tint-text); }
.mm-item-acciones { display: flex; gap: var(--sp-1); flex: 0 0 auto; }
.mm-item-acciones .btn-icono { inline-size: var(--tactil); block-size: var(--tactil); }

/* ---- la barra de progreso ---- */
.mm-barra {
  margin-block-start: var(--sp-2); block-size: 6px; border-radius: 999px;
  background: var(--surface-2); overflow: hidden;
}
.mm-barra-relleno {
  display: block; block-size: 100%; inline-size: 2%;
  background: var(--brand); border-radius: 999px;
  transition: inline-size 120ms linear;
}
/* Ya salió todo por el cable y el servidor está mirándolo (anti-robo y
   moderación): la barra late en vez de quedarse parada mintiendo. */
.mm-barra-relleno--espera { animation: mm-late 1.2s ease-in-out infinite; }
@keyframes mm-late { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* ---- el visor de imágenes ---- */
.popup-fondo--visor { padding: 0; }
.popup--visor {
  inline-size: 100%; max-inline-size: none; block-size: 100%;
  border-radius: 0; display: flex; flex-direction: column;
  padding: var(--sp-4);
  /* El visor ocupa la pantalla entera a propósito: aquí no se topa el alto ni
     se desplaza la ventana (la imagen ya tiene su propio encuadre). */
  max-block-size: none; overflow: visible;
}
.popup--visor .popup-cabecera, .popup--visor .popup-acciones { position: static; padding-block: 0; }
.popup--visor .popup-cabecera { margin-block-end: var(--sp-3); }
.popup--visor .popup-acciones { margin-block-start: var(--sp-4); }
.popup--visor .mm-cuerpo { flex: 1 1 auto; max-block-size: none; overflow: hidden; padding: 0; }
.visor-lienzo {
  block-size: 100%; min-block-size: 200px;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; background: var(--surface-2); border-radius: var(--r-tarjeta);
  touch-action: none; cursor: default;
}
.visor-lienzo--movible { cursor: grab; }
.visor-lienzo--movible:active { cursor: grabbing; }
.visor-img {
  max-inline-size: 100%; max-block-size: 100%;
  transform-origin: center; transition: transform 90ms ease-out;
  user-select: none; -webkit-user-drag: none;
}
.visor-cargando, .visor-error {
  display: flex; align-items: center; gap: var(--sp-2);
  color: var(--text-2); margin: 0; padding: var(--sp-4);
}
.visor-error { color: var(--danger-tint-text); }
.popup--visor .popup-acciones { flex-direction: row; flex-wrap: wrap; justify-content: center; }
.popup--visor .popup-acciones .btn { width: auto; min-inline-size: var(--tactil); }
.btn-icono-texto { display: inline-flex; align-items: center; gap: var(--sp-2); }

/* ---- la grabadora de voz ---- */
.voz-caja { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); text-align: center; }
.voz-estado { margin: 0; color: var(--text-2); display: flex; align-items: center; gap: var(--sp-2); }
.voz-estado--grabando { color: var(--danger); font-weight: var(--fw-strong); }
.voz-punto {
  inline-size: 10px; block-size: 10px; border-radius: var(--r-redondo);
  background: var(--danger); animation: voz-late 1.4s ease-in-out infinite;
}
@keyframes voz-late { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
.voz-reloj { margin: 0; font-size: var(--fs-kpi); line-height: var(--lh-kpi); font-weight: var(--fw-kpi); font-variant-numeric: tabular-nums; }
.voz-onda { inline-size: 100%; block-size: 56px; color: var(--brand-texto); }
.voz-audio { inline-size: 100%; margin-block-start: var(--sp-2); }
.voz-max { margin: 0; font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-2); }
.voz-aviso {
  display: flex; gap: var(--sp-3); align-items: flex-start;
  padding: var(--sp-3); border-radius: var(--r-campo);
  background: var(--warning-tint); color: var(--warning-tint-text);
}
.voz-aviso .icono { flex: 0 0 auto; }
.voz-aviso p { margin: 0 0 var(--sp-1); }
.voz-aviso-titulo { font-weight: var(--fw-strong); }

/* ---- B6 · el adjunto YA enviado, dentro de la burbuja ----
   Foto con visor, documento con descarga y NOTA DE VOZ CON REPRODUCTOR real.
   Todo con propiedades lógicas: en árabe la barra crece desde la derecha
   sola, sin una sola regla aparte. */
.adj-foto-caja { display: block; }
.adj-foto {
  display: block; position: relative; padding: 0; border: 0; cursor: pointer;
  inline-size: min(260px, 60vw); block-size: 160px; overflow: hidden;
  border-radius: var(--r-campo); background: var(--surface-2);
}
.adj-foto:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.adj-foto img { inline-size: 100%; block-size: 100%; object-fit: cover; opacity: 0; transition: opacity .18s ease; }
.adj-foto img.adj-foto-lista { opacity: 1; }
.adj-foto-espera {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--text-2);
}
.adj-doc {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2); border-radius: var(--r-campo);
  background: var(--surface-2); max-inline-size: min(300px, 70vw);
}
.adj-doc > .icono { flex: 0 0 auto; color: var(--brand-texto); }
.adj-doc-datos { flex: 1 1 auto; min-inline-size: 0; }
.adj-doc-nombre {
  display: block; font-weight: var(--fw-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.adj-doc-meta { display: block; font-size: var(--fs-small); line-height: var(--lh-small); color: var(--text-2); }
.adj-nota {
  display: flex; align-items: center; gap: var(--sp-1);
  margin-block-start: var(--sp-1);
  font-size: var(--fs-caption); line-height: var(--lh-caption);
}
.adj-nota--revision { color: var(--warning-tint-text); }
.adj-nota--retirado { color: var(--danger); }
.adj-nota .icono { flex: 0 0 auto; }

/* el reproductor */
.voz-repro {
  display: flex; align-items: center; gap: var(--sp-2);
  inline-size: min(280px, 68vw);
}
.voz-repro-btn, .voz-repro-vel {
  flex: 0 0 auto; min-inline-size: var(--tactil); min-block-size: var(--tactil);
  display: inline-flex; align-items: center; justify-content: center;
  border: var(--borde-grosor) solid var(--borde-control); background: var(--surface);  /* B12 */
  color: var(--brand-texto); border-radius: var(--r-redondo); cursor: pointer;
}
.voz-repro-vel { border-radius: var(--r-campo); font-size: var(--fs-small); font-weight: var(--fw-strong); color: var(--text-2); }
.voz-repro-btn:hover, .voz-repro-vel:hover { background: var(--surface-2); }
.voz-repro-btn:focus-visible, .voz-repro-vel:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.voz-repro-centro { flex: 1 1 auto; min-inline-size: 0; }
.voz-repro-barra {
  position: relative; display: block; block-size: 6px; border-radius: var(--r-redondo);
  background: var(--borde-control); cursor: pointer; touch-action: none;  /* B12 */
}
.voz-repro-barra:focus-visible { outline: 2px solid var(--focus); outline-offset: 4px; }
/* La barra se ve fina (6 px) pero se AGARRA con el dedo: la zona sensible se
   estira hacia arriba y hacia abajo sin tapar nada que se pueda pulsar. */
.voz-repro-barra::before {
  content: ""; position: absolute; inset-block: -14px; inset-inline: 0;
}
.voz-repro-relleno {
  position: absolute; inset-block: 0; inset-inline-start: 0; inline-size: 0;
  background: var(--brand); border-radius: var(--r-redondo);
}
.voz-repro-punto {
  position: absolute; inset-block-start: 50%; inset-inline-start: 0;
  inline-size: 12px; block-size: 12px; border-radius: var(--r-redondo);
  background: var(--brand); transform: translate(-50%, -50%);
}
[dir="rtl"] .voz-repro-punto { transform: translate(50%, -50%); }
.voz-repro-pie { display: flex; align-items: center; gap: var(--sp-2); margin-block-start: 2px; }
.voz-repro-tiempo { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--text-2); font-variant-numeric: tabular-nums; }
.voz-repro-aviso { font-size: var(--fs-caption); line-height: var(--lh-caption); color: var(--danger); }
.voz-repro--cargando .voz-repro-btn { opacity: .6; }
/* Dentro de MI burbuja (fondo de marca) todo se dibuja con el color del texto
   de esa burbuja: si no, la barra y el reloj se pierden sobre el color. */
.burbuja--operador .voz-repro-barra,
.burbuja--propia .voz-repro-barra { background: color-mix(in srgb, currentColor 35%, transparent); }
.burbuja--operador .voz-repro-barra .voz-repro-relleno,
.burbuja--propia .voz-repro-barra .voz-repro-relleno,
.burbuja--operador .voz-repro-punto,
.burbuja--propia .voz-repro-punto { background: currentColor; }
.burbuja--operador .voz-repro-tiempo,
.burbuja--propia .voz-repro-tiempo { color: currentColor; opacity: .85; }
.burbuja--operador .adj-doc,
.burbuja--propia .adj-doc { background: color-mix(in srgb, currentColor 16%, transparent); }
.burbuja--operador .adj-doc > .icono,
.burbuja--propia .adj-doc > .icono,
.burbuja--operador .adj-doc .btn-icono,
.burbuja--propia .adj-doc .btn-icono { color: currentColor; }
.burbuja--operador .adj-doc-meta,
.burbuja--propia .adj-doc-meta { color: currentColor; opacity: .85; }

@media (prefers-reduced-motion: reduce) {
  .mm-barra-relleno, .visor-img, .adj-foto img { transition: none; }
  .mm-barra-relleno--espera, .voz-punto { animation: none; }
}
@media (max-width: 480px) {
  .mm-previa { inline-size: 44px; block-size: 44px; }
  .voz-repro, .adj-foto { inline-size: min(220px, 62vw); }
  .popup--visor .popup-acciones .btn span { display: none; }   /* solo iconos: caben los 5 */
  .popup--visor .popup-acciones .btn[data-mm-boton="ajustar"] span { display: inline; }
}

/* ---------- B8 · F-3 · Banda de estado del sistema (cliente, operador, afiliado) ----------
   La Sala de Máquinas prometía «los clientes ven una pantalla amable» y no la
   veía nadie. Esta banda es esa promesa cumplida: va pegada arriba del todo,
   por encima de la cabecera, y solo existe mientras el sistema no está
   operativo. No tapa nada ni bloquea: se puede seguir leyendo lo que ya está. */
.sistema-banda {
  border-block-end: var(--borde-grosor) solid var(--warning, var(--border));
}

/* ============================================================
   B11 · RENDIMIENTO (04/09/2026) · LAS LISTAS LARGAS SOLO DIBUJAN
   LO QUE SE VE
   ============================================================
   Una lista de 500 conversaciones cabe entera en la pantalla igual que
   caben 500 personas en un ascensor: no cabe. El navegador, sin embargo,
   se empeñaba en medir y dibujar las 500, aunque solo se vieran doce.
   `content-visibility: auto` le dice: «de esta fila, si no está a la
   vista, no te molestes»; y `contain-intrinsic-size` le dice cuánto mide
   para que la barra de desplazamiento sea del tamaño correcto y no pegue
   saltos al bajar.
   Qué NO cambia:
     · el HTML es el mismo: las filas SIGUEN estando en la página, así que
       los filtros, la búsqueda de la propia pantalla, el lector de
       pantalla y «llévame a esta fila» funcionan exactamente igual;
     · un navegador que no lo entienda (Safari antiguo) lo ignora y dibuja
       como siempre: no se rompe nada, simplemente no gana.
   Se aplica SOLO a las filas de las listas largas, nunca a un hilo de
   mensajes abierto (ahí el alto de cada burbuja es muy variable y una
   medida aproximada haría saltar el desplazamiento).
   ------------------------------------------------------------ */
.op-cola-lista .op-fila,
.conv-lista .fila-chat,
.lista-larga > * {
  content-visibility: auto;
  /* Solo se le adivina el ALTO: el ancho lo manda la columna, como siempre.
     `auto` = «la primera vez que la dibujes, apúntate lo que medía de verdad»,
     así la barra de desplazamiento deja de dar saltos al bajar. */
  contain-intrinsic-block-size: auto var(--fila-chat);
}

/* ---------- Banda «sin conexión» DENTRO del panel (B7 por rol; desde el 27/09/2026 aquí, para los cuatro paneles) ----------
   La píldora de arriba flota abajo del todo y en un panel con tablas largas
   se queda fuera de la vista. Esta banda va dentro de la propia pantalla, la
   primera cosa que se lee, y no tapa nada: empuja el contenido. Solo tokens. */
.pwa-banda {
  display: flex; align-items: flex-start; gap: var(--sp-3);
  margin-block-end: var(--sp-4); padding: var(--sp-3) var(--sp-4);
  background: var(--surface); color: var(--text);
  border: var(--borde-grosor) solid var(--warning, var(--border));
  border-inline-start: 4px solid var(--warning, var(--border));
  border-radius: var(--r-tarjeta);
}
.pwa-banda .crece { flex: 1; min-inline-size: 0; }
.pwa-banda strong { display: block; line-height: 1.3; }
.pwa-banda .texto-small { color: var(--text-2); }
.pwa-banda-icono {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  inline-size: 32px; block-size: 32px; border-radius: var(--r-campo);
  background: var(--warning-tint, var(--surface-2)); color: var(--warning-tint-text, var(--warning));
}
.pwa-banda .info-btn { flex-shrink: 0; }
/* (26/09/2026) «Sin conexión con el servidor» (aparato en línea, servidor sin
   contestar): mismo sitio, mismo tamaño, otro color, para que no se confunda
   con «sin red». Solo tokens; con los de peligro ausentes cae en los de aviso. */
.pwa-banda--servidor {
  border-color: var(--danger, var(--warning, var(--border)));
  border-inline-start-color: var(--danger, var(--warning, var(--border)));
}
.pwa-banda--servidor .pwa-banda-icono {
  background: var(--danger-tint, var(--warning-tint, var(--surface-2)));
  color: var(--danger-tint-text, var(--danger, var(--warning)));
}

/* ============================================================
   P2 · OLEADA P2-5 · CP-523 (30/09/2026) · LAS TABLAS EN EL MÓVIL, EN TODAS LAS APPS.
   MEDIDO ANTES: el patrón decidido (G.1: tarjetas apiladas con 2-3 columnas y el resto al tocar; solo las
   financieras con desplazamiento lateral y la primera columna fija) vivía en admin.css: las comisiones del
   afiliado no podían usarlo. Y «el resto, al tocar» no existía: lo que llevaba .td-extra desaparecía y, si la
   fila no abría una ficha, no había forma de verlo en el móvil.
   AHORA: el mismo patrón aquí (admin.css lo repite igual: en el admin no cambia nada) y una tarjeta se abre y se
   cierra con un toque (core/ui.js, tarjetasQueSeAbren): lo escondido baja a sus líneas con el nombre de su columna.
   ============================================================ */
@media (max-width: 767px) {
  .tabla--tarjetas { display: block; }
  .tabla--tarjetas thead, .tabla--tarjetas .td-extra, .tabla--tarjetas tfoot { display: none; }
  .tabla--tarjetas tbody { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-3); }
  .tabla--tarjetas tbody tr {
    display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-2);
    border: var(--borde-grosor) solid var(--border);
    border-radius: var(--r-campo);
    padding: var(--sp-3);
    cursor: pointer;
  }
  .tabla--tarjetas tbody td { display: block; height: auto; border-block-end: none; padding: 0; }
  .tabla--tarjetas tbody td:first-child { flex: 1 1 auto; min-width: 0; }
  .tabla--tarjetas tbody td:last-child { margin-inline-start: auto; }
  /* Abierta: el resto, cada dato en su línea y con el nombre de su columna */
  .tabla--tarjetas tbody tr.tarjeta-abierta > td.td-extra { display: block; flex: 1 1 100%; order: 9; }
  .tabla--tarjetas tbody tr.tarjeta-abierta > td.td-extra[data-rotulo]::before {
    content: attr(data-rotulo) ": "; color: var(--text-2);
  }
}
/* CP-520 (30/09/2026) · UNA TABLA QUE NO CABE SE APILA (core/ui.js, tablasQueNoSeDesplazan). Solo las financieras
   (.tabla--fija-inicio) se desplazan dentro de su marco. Una tarjeta por fila y cada dato en su línea con el nombre de
   su columna (lo pone ui.js en data-rotulo). En el móvil, las de gestión (.tabla--tarjetas) como siempre: lo esencial
   y el resto al tocar. Si la cabecera lleva mandos (una ⓘ de columna, «seleccionar todos»), se queda arriba como una
   tira, para que apilar no los quite (CP-528, letra al 200 %). Nada se recorta: el texto largo baja de línea. */
.tabla--apilada { display: block; }
.tabla--apilada thead { display: none; }
.tabla--apilada:not(.tabla--tarjetas) thead:has(button, input, select, .info-btn) { display: block; }
@media (min-width: 768px) {
  .tabla--apilada thead:has(button, input, select, .info-btn) { display: block; }
}
/* (24/10/2026 · responsive, hallazgo moderador-26 · RETIRADO EN LA CONSOLIDACIÓN) La zona núcleo puso aquí
   `.tabla-envoltura:has(> .tabla--apilada:not(.tabla--tarjetas)) { max-block-size: none; }` para que una tabla
   apilada no se recorriera dentro de su caja (el equipo del moderador), creyendo que ninguna foto cambiaba. MEDIDO
   en la pasada visual completa del 24/10/2026: el equipo del moderador NO cambiaba (su tope no se alcanza), pero
   Incentivos y Papelera del administrador a 360-430 px crecían entre 580 y 800 px (perdían su marco con tope en
   los seis idiomas y los tres temas: 144 capturas). Como la propia zona quería dejar las listas del admin como
   están, la regla se retira; el desplazador anidado de las tablas apiladas queda como decisión pendiente. */
/* (02/11/2026 · r11-visual, V-2) SE REPONE, sabiendo lo que cuesta en fotos. MEDIDO (360×900): Papelera enseñaba 812 de 1459 px, Programados
   812 de 1627 e Incentivos 812 de 1242, cortados a media tarjeta y sin barra visible: el pie decía «1–6 de 6» y se veían 3 y media. Esas
   fotos crecen: es a propósito (la referencia la fija quien lleve la pasada, no este carril). Las .tabla--tarjetas no entran. */
.tabla-envoltura:has(> .tabla--apilada:not(.tabla--tarjetas)) { max-block-size: none; }
.tabla--apilada thead tr {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) var(--sp-3);
  padding: var(--sp-3) var(--sp-3) 0;
}
/* (02/11/2026 · r11-visual, V-3) De la tira solo quedan los mandos: CP-528 la conservó por ellos, y los nombres ya van en cada dato
   (data-rotulo). MEDIDO ANTES: «Para Mensaje Cuándo Estado Acción» suelto sobre las tarjetas de Programados, y lo mismo en Papelera.
   Lo que ordena al pulsarlo (aria-sort) es un mando aunque no lleve botón. */
.tabla--apilada thead th:not([aria-sort]):not(:has(button, input, select, .info-btn)) { display: none; }
.tabla--apilada thead th {
  position: static; display: inline-flex; align-items: center; gap: var(--sp-1);
  height: auto; min-height: var(--tactil); padding: 0; border: none; white-space: normal; background: none;
}
.tabla--apilada tbody, .tabla--apilada tfoot { display: flex; flex-direction: column; gap: var(--sp-3); padding: var(--sp-3); }
.tabla--apilada tbody tr, .tabla--apilada tfoot tr {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--sp-1) var(--sp-3);
  border: var(--borde-grosor) solid var(--border);
  border-radius: var(--r-campo);
  padding: var(--sp-3);
}
.tabla--apilada tbody td, .tabla--apilada tfoot td {
  display: block; height: auto; border-block-end: none; padding: 0;
  min-width: 0; max-width: 100%; overflow-wrap: anywhere; white-space: normal;
  flex: 1 1 100%;
}
/* un botón de celda con su insignia: si no caben en una línea, la insignia baja entera (el rótulo no se parte) */
.tabla.tabla--apilada td > .btn--compacto { white-space: normal; flex-wrap: wrap; max-width: 100%; padding-block: var(--sp-1); }
/* (02/11/2026 · P-4) una columna sin nombre no enseña «: » suelto. MEDIDO ANTES (360 px, es y ar): Pagos, 27 celdas con ese «:» (6 sin
   nada más en la fila); Suscripciones, 29 (27). La regla de `content: none` que había debajo perdía por peso contra esta. */
.tabla--apilada td[data-rotulo]:not([data-rotulo=""]):not(:first-child):not(:empty)::before {
  content: attr(data-rotulo) ": "; color: var(--text-2);
  /* el nombre de la columna es otra isla de texto: en árabe no le da la vuelta al dato que va detrás (un teléfono,
     un importe; CP-526) */
  unicode-bidi: isolate; direction: inherit;
}
@media (max-width: 767px) {
  .tabla.tabla--apilada.tabla--tarjetas .td-extra { display: none; }
  .tabla.tabla--apilada.tabla--tarjetas tbody td { flex: 0 1 auto; }
  .tabla.tabla--apilada.tabla--tarjetas tbody td:first-child { flex: 1 1 auto; }
  .tabla.tabla--apilada.tabla--tarjetas tbody td:last-child { margin-inline-start: auto; }
  .tabla.tabla--apilada.tabla--tarjetas td[data-rotulo]:not(.td-extra)::before { content: none; }
  .tabla.tabla--apilada.tabla--tarjetas tbody tr { cursor: pointer; }
  .tabla.tabla--apilada.tabla--tarjetas tbody tr.tarjeta-abierta > td.td-extra { display: block; flex: 1 1 100%; order: 9; }
}
.tabla--fija-inicio thead th:first-child,
.tabla--fija-inicio tbody td:first-child {
  position: sticky; inset-inline-start: 0;
  background: var(--surface); z-index: 1;
}
.tabla--fija-inicio thead th:first-child { z-index: 3; }
/* CP-520 (30/09/2026) · EN ÁRABE LA PRIMERA COLUMNA NO SE QUEDABA FIJA. MEDIDO ANTES (facturas, 360 px, árabe): al
   desplazar la tabla 80 px, el nº de factura se iba 80 px con ella. La celda lleva dir="ltr" (el código se lee de
   izquierda a derecha, CP-526) y su «inicio» pasaba a ser la IZQUIERDA: se pegaba al lado contrario. AHORA: en una
   página en árabe la columna fija se pega a la DERECHA, que es donde empieza la tabla. */
[dir="rtl"] .tabla--fija-inicio thead th:first-child,
[dir="rtl"] .tabla--fija-inicio tbody td:first-child { inset-inline-start: auto; left: auto; right: 0; }
/* P2 · OLEADA P2-5 · CP-525 (30/09/2026) · UN BOTÓN DE UNA FILA DE TABLA NO SE PARTE EN LÍNEAS.
   MEDIDO ANTES (360 px): en «Mi saldo» del operador, [No estoy de acuerdo] salía en CUATRO líneas en español y en
   dos en alemán, aplastado en su columna. AHORA: el botón compacto de una celda va en una línea; si la tabla no
   cabe, se desplaza dentro de su marco (como cualquier tabla ancha), nunca la página. */
.tabla td > .btn--compacto { white-space: nowrap; }


/* ============================================================
   CENTRO DE AYUDA DEL EQUIPO (Fase 2 · 27/10/2026)
   Estilos comunes de la pantalla «Ayuda» de operador, moderador y
   afiliado (ayuda_sim.py · */pantallas/ayuda.js). Nombres propios
   (ayuda-*) para no tocar nada de lo que ya existe. El acordeón
   imita al de las FAQ del operador (.op-faq): chevron dibujado con
   bordes que gira solo en RTL y objetivo táctil de 44 px. Los avisos
   usan los tintes de estado de tokens.css (con su versión oscura).
   ============================================================ */
.ayuda-pagina { max-width: 720px; margin-inline: auto; }
.ayuda-titulo { margin-block: var(--sp-4) var(--sp-2); }
.ayuda-cabecera-af { margin-block-end: var(--sp-4); }
.ayuda-subtitulo-af { margin-block-end: var(--sp-3); }
.ayuda-acordeon details { border-block-end: var(--borde-grosor) solid var(--border); }
.ayuda-acordeon summary {
  display: flex; align-items: center; gap: var(--sp-2);
  min-height: var(--tactil); padding-inline: var(--sp-2);
  cursor: pointer; font-weight: var(--fw-strong); list-style: none;
}
.ayuda-acordeon summary::-webkit-details-marker { display: none; }
.ayuda-acordeon summary::before {
  content: ""; flex-shrink: 0;
  inline-size: 7px; block-size: 7px;
  border-block-start: 1.5px solid var(--text-2);
  border-inline-end: 1.5px solid var(--text-2);
  transform: rotate(45deg);
  transition: transform var(--dur-rapida) var(--ease);
}
/* En RTL, border-inline-end YA es el borde izquierdo (el espejo lo hace él): cerrado apunta a la izquierda (-45°)
   y abierto hacia abajo (-135°). Revisión 27/10: con la doble corrección de .op-faq salían al revés en árabe. */
.ayuda-acordeon summary:dir(rtl)::before { transform: rotate(-45deg); }
.ayuda-acordeon details[open] summary::before { transform: rotate(135deg); }
.ayuda-acordeon details[open] summary:dir(rtl)::before { transform: rotate(-135deg); }
.ayuda-acordeon details p { margin: 0; padding: 0 var(--sp-6) var(--sp-3); color: var(--text-2); line-height: 1.6; }
.ayuda-alerta {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  padding: var(--sp-3); margin-block: var(--sp-3);
  border-radius: var(--radio-m, 12px);
  background: var(--info-tint); color: var(--info-tint-text);
}
.ayuda-alerta--aviso { background: var(--warning-tint); color: var(--warning-tint-text); }
.ayuda-alerta--peligro { background: var(--danger-tint); color: var(--danger-tint-text); }
.ayuda-alerta--ok { background: var(--success-tint); color: var(--success-tint-text); }
.ayuda-contacto {
  display: flex; align-items: center; gap: var(--sp-2);
  margin-block-start: var(--sp-4); padding-block-start: var(--sp-3);
  border-block-start: var(--borde-grosor) solid var(--border);
  color: var(--text-2);
}
/* el editor del administrador: las seis pestañas de idioma (con su ✓) caben en la ventana sin barra
   de desplazamiento, también cuando la ventana lleva la suya */
.ayuda-tabs .tab { padding-inline: var(--sp-3); }
/* tras la lista, la última fila ya hace de raya: el pie no pone otra encima */
.ayuda-acordeon ~ .ayuda-contacto, .tarjeta ~ .ayuda-contacto { border-block-start: 0; padding-block-start: 0; }

/* >>> ayuda-v3 (28/10/2026) */
/* v3 (27/10/2026) · EL FORMATO de cuerpos y respuestas (core/ayuda-formato.js): párrafos, PASOS («1.»), PUNTOS
   («- ») y CAJA de aviso («! ») van como hijos directos del <details>, sin envoltorio (la prueba de Chrome de
   o35r mide su primer <p>). En el acordeón de operador y moderador llevan el mismo margen lateral que el párrafo
   (.ayuda-acordeon details p); en el del afiliado (.af-acordeon) van a ras, como su párrafo. */
.ayuda-pasos, .ayuda-puntos {
  margin: 0 0 var(--sp-3); padding-inline-start: var(--sp-6);
  color: var(--text-2); line-height: 1.6; overflow-wrap: anywhere;
}
.ayuda-pasos > li + li, .ayuda-puntos > li + li { margin-block-start: var(--sp-1); }
.ayuda-caja {
  display: flex; align-items: flex-start; gap: var(--sp-2);
  margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-3);
  border-radius: var(--radio-m, 12px);
  background: var(--warning-tint); color: var(--warning-tint-text); line-height: 1.6;
}
.ayuda-caja > .icono { flex-shrink: 0; margin-block-start: 3px; }
.ayuda-acordeon details > .ayuda-pasos,
.ayuda-acordeon details > .ayuda-puntos,
.ayuda-acordeon details > .ayuda-caja { margin-inline: var(--sp-6); }
/* las preguntas agrupadas por su sección: el título de la sección como subtítulo (h4; h5 en la vista previa) */
.ayuda-grupo-titulo {
  margin-block: var(--sp-3) var(--sp-1);
  font-size: var(--fs-body); font-weight: var(--fw-strong); color: var(--text-2);
}
/* TEXTOS LARGOS (una dirección web o un correo sin espacios) no se salen de la pantalla a 360 px: el título de
   cada sección o pregunta, el aviso, el pie, la caja y los párrafos parten donde haga falta. min-width: 0 deja
   encoger al hijo de un flex (sin él, el texto empuja y se sale). */
.ayuda-acordeon summary, .ayuda-resumen,
.ayuda-alerta > span, .ayuda-contacto > span, .ayuda-caja > span { min-width: 0; overflow-wrap: anywhere; }
[data-ayuda-item] > p { overflow-wrap: anywhere; }
/* el editor del administrador: el nombre de cada fila tampoco se sale, y la marca «Oculta» va pegada a él */
.ayuda-ed-texto { overflow-wrap: anywhere; }
.ayuda-ed-marca { margin-inline-start: var(--sp-1); vertical-align: middle; }
/* RR-2 · CP-521 (02/11/2026) · ↑ y ↓ de cada fila miden 44 de ancho (de alto les basta la capa ::after común: su fila es
   más alta que el botón). MEDIDO ANTES (Chrome a 390 px, qué recibe el toque): el ↑ de la última fila dibujaba 32×36 y
   respondía 39,5×44,5: la capa de 44 de su vecino de la derecha, que va después en la página, le quitaba 4,5 px. */
.ayuda-ed-mover { min-inline-size: var(--tactil); }
/* M-5 (02/11/2026 · decisión del titular) · EL COLOR DE CADA SECCIÓN Y EL ÍNDICE. Una clase por color de la lista cerrada (ayuda_sim.COLORES), con
   sus dos valores: --ay-c, el color, y --ay-f, su fondo suave. Opacos, para que el contraste se pueda medir: --ay-c sobre --ay-f y sobre
   --surface da 4,5:1 o más en claro y en oscuro (pruebas/navegador/n_ayuda_colores.py lo mide en Chrome). Sin clase, todo como hasta hoy. */
.ayuda-color--azul { --ay-c: #1D5A8F; --ay-f: #DCEBF8; }
.ayuda-color--verde { --ay-c: #0F6B41; --ay-f: #D7F1E3; }
.ayuda-color--ambar { --ay-c: #80500B; --ay-f: #FBEBD0; }
.ayuda-color--rojo { --ay-c: #9C2B26; --ay-f: #FBE2E0; }
.ayuda-color--violeta { --ay-c: #5B3FB5; --ay-f: #E9E3FB; }
.ayuda-color--turquesa { --ay-c: #0B6470; --ay-f: #D3EFF2; }
.ayuda-color--rosa { --ay-c: #A12560; --ay-f: #FBE1EE; }
.ayuda-color--gris { --ay-c: #4A585C; --ay-f: #E6EBED; }
:root[data-theme="oscuro"] .ayuda-color--azul { --ay-c: #8EC2EE; --ay-f: #1C3547; }
:root[data-theme="oscuro"] .ayuda-color--verde { --ay-c: #6FD3A2; --ay-f: #17382B; }
:root[data-theme="oscuro"] .ayuda-color--ambar { --ay-c: #E9B163; --ay-f: #3D2F17; }
:root[data-theme="oscuro"] .ayuda-color--rojo { --ay-c: #F4A5A0; --ay-f: #442322; }
:root[data-theme="oscuro"] .ayuda-color--violeta { --ay-c: #BBABFB; --ay-f: #2F2A52; }
:root[data-theme="oscuro"] .ayuda-color--turquesa { --ay-c: #6FD0DC; --ay-f: #143A40; }
:root[data-theme="oscuro"] .ayuda-color--rosa { --ay-c: #F3A3CB; --ay-f: #44233A; }
:root[data-theme="oscuro"] .ayuda-color--gris { --ay-c: #B4C4CA; --ay-f: #2C383D; }
@media (prefers-color-scheme: dark) {
  :root[data-theme="mix"] .ayuda-color--azul { --ay-c: #8EC2EE; --ay-f: #1C3547; }
  :root[data-theme="mix"] .ayuda-color--verde { --ay-c: #6FD3A2; --ay-f: #17382B; }
  :root[data-theme="mix"] .ayuda-color--ambar { --ay-c: #E9B163; --ay-f: #3D2F17; }
  :root[data-theme="mix"] .ayuda-color--rojo { --ay-c: #F4A5A0; --ay-f: #442322; }
  :root[data-theme="mix"] .ayuda-color--violeta { --ay-c: #BBABFB; --ay-f: #2F2A52; }
  :root[data-theme="mix"] .ayuda-color--turquesa { --ay-c: #6FD0DC; --ay-f: #143A40; }
  :root[data-theme="mix"] .ayuda-color--rosa { --ay-c: #F3A3CB; --ay-f: #44233A; }
  :root[data-theme="mix"] .ayuda-color--gris { --ay-c: #B4C4CA; --ay-f: #2C383D; }
}
/* el icono, en su baldosa; la franja, al lado donde empieza el texto (a la derecha en árabe) */
.ayuda-color > summary > .icono, .ayuda-color > .icono {
  box-sizing: content-box; padding: 4px; border-radius: var(--radio-s, 8px);
  color: var(--ay-c); background: var(--ay-f);
}
details.ayuda-color { border-inline-start: 3px solid var(--ay-c); }
/* el acordeón del afiliado va a ras: sin este margen, su texto y su icono se pegaban a la franja (visto en la captura de 1280 px) */
details.af-acordeon.ayuda-color { padding-inline-start: var(--sp-2); }
.ayuda-grupo-titulo.ayuda-color { border-inline-start: 3px solid var(--ay-c); padding-inline-start: var(--sp-2); }
/* el índice: envuelve en vez de desbordar, con objetivo táctil de 44 px; sin color, con los del tema */
.ayuda-indice-lista {
  display: flex; flex-wrap: wrap; gap: var(--sp-2);
  margin: 0 0 var(--sp-3); padding: 0; list-style: none;
}
.ayuda-indice-lista > li { min-width: 0; max-inline-size: 100%; }
.ayuda-indice-lista > li[hidden], .ayuda-indice[hidden] { display: none; }
.ayuda-indice-enlace {
  display: inline-flex; align-items: center; min-height: var(--tactil); max-inline-size: 100%;
  padding-inline: var(--sp-3); border-radius: var(--radio-m, 12px);
  border: var(--borde-grosor) solid var(--ay-c, var(--border));
  background: var(--ay-f, var(--surface-2)); color: var(--ay-c, var(--text));
  font-size: var(--fs-small); font-weight: var(--fw-strong); text-decoration: none; overflow-wrap: anywhere;
}
.ayuda-indice-enlace:hover { text-decoration: underline; }
/* la sección a la que lleva el índice no se queda debajo de la barra fija de arriba */
[data-ayuda-item], .ayuda-fila { scroll-margin-block-start: calc(var(--alto-cabecera-chat) + var(--safe-arriba, 0px) + var(--sp-2)); }
/* el editor: la lista de colores, sin estilos en línea */
.ayuda-ed-colores { border: 0; padding: 0; margin: var(--sp-3) 0 0; }
.ayuda-ed-colores-lista { flex-wrap: wrap; gap: var(--sp-2); }
.ayuda-ed-color {
  display: inline-flex; align-items: center; gap: var(--sp-1); min-height: var(--tactil);
  padding-inline: var(--sp-2); border-radius: var(--radio-m, 12px); cursor: pointer;
  border: var(--borde-grosor) solid var(--ay-c, var(--border));
  background: var(--ay-f, var(--surface-2)); color: var(--ay-c, var(--text));
}
/* <<< ayuda-v3 */
/* ============================================================
   CRÉDITOS IBO · el «Banco» del equipo (Fase 2 · 27/10/2026)
   La pantalla del operador y del afiliado (core/creditos-ibo.js) y
   la del administrador (admin/pantallas/creditos-ibo.js). Nombres
   propios (ibo-*): no toca nada de lo que ya existe.
   ============================================================ */
.ibo-pagina { max-width: 720px; margin-inline: auto; gap: var(--sp-4); }
.ibo-bloque { gap: var(--sp-3); }
.ibo-bloque h3 { margin: 0; }
.ibo-saldo { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; }
.ibo-vias { border: 0; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
/* (02/11/2026 · P-5) «Con mi tarjeta» medía 296×27: una forma de pago no se elige a 27 px de alto. */
.ibo-via { align-items: flex-start; gap: var(--sp-2); cursor: pointer; flex-wrap: nowrap;
  min-block-size: var(--tactil); padding-block: var(--sp-2); }
.ibo-via > .columna { flex: 1 1 auto; min-inline-size: 0; }
.ibo-via input { margin-block-start: 3px; }
.ibo-ayuda--corto { color: var(--danger); }
.ibo-total { align-items: baseline; gap: var(--sp-2); padding-block: var(--sp-2);
  border-block: var(--borde-grosor) solid var(--border); }
.ibo-total strong { font-size: var(--fs-h3, 1.25rem); }
.ibo-nota { display: flex; align-items: flex-start; gap: var(--sp-2); margin: 0;
  padding: var(--sp-2) var(--sp-3); border-radius: var(--radio-m, 12px);
  background: var(--info-tint); color: var(--info-tint-text); }
.ibo-movs { list-style: none; margin: 0; padding: 0; }
.ibo-mov { gap: var(--sp-2); padding-block: var(--sp-2); border-block-end: var(--borde-grosor) solid var(--border); }
.ibo-mov-n { min-inline-size: 3ch; text-align: end; font-variant-numeric: tabular-nums; }
.ibo-filtros { margin-block-end: var(--sp-3); align-items: end; }   /* M-3 · filtro de movimientos del administrador */
.ibo-ver-mas { margin-block-start: var(--sp-3); }                   /* M-3 · «Ver más» */
/* M-4 · «Siguen valiendo como están» del editor de la ayuda (B-7: sin estilos en línea) */
.ayuda-ed-revisados { border: 0; padding: 0; margin: var(--sp-3) 0 0; }
.ayuda-ed-revisados-lista { flex-wrap: wrap; gap: var(--sp-3); }
.ayuda-ed-revisados-lista label { gap: var(--sp-1); }
.ibo-kpis { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr));
  margin-block-end: var(--sp-4); }
.ibo-ajustes { display: grid; gap: var(--sp-3); grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }
/* >>> ibo-pantallas (28/10/2026) */
/* ---- Créditos IBO · arreglos de las pantallas (27/10/2026) ----
   AÑADIR al final del bloque «CRÉDITOS IBO» de app/assets/css/base.css (tras .ibo-ajustes).
   Solo clases ibo-*: no toca nada de lo que ya existe. */
/* el círculo de la vía no se encoge al lado de un texto largo (.fila > * trae min-width:0): a 360 px el texto baja
   de línea dentro de su columna y el círculo se queda en la misma línea que su nombre */
.ibo-via > input { flex: none; }
/* el campo y su ayuda, juntos (sin los 16 px de .campo ni los 12 px de la columna entre los dos) */
.ibo-campo-grupo { display: flex; flex-direction: column; gap: var(--sp-1); }
.ibo-campo-grupo > .campo { margin: 0; }
.ibo-campo-grupo > .campo-ayuda { margin: 0; }
/* las dos líneas de un movimiento (qué · fecha e importe), juntas: la .columna trae 12 px entre ellas */
.ibo-mov > .columna { gap: 2px; }
/* «Ajustar» y «Anular» no se estrujan en la fila de una persona o de un movimiento (si no caben, bajan de línea) */
.ibo-mov > .btn { flex: none; }
/* una nota que AVISA (venta cerrada, mínimo de cobro, la venta cambiada desde otra ventana): ámbar, no azul */
.ibo-nota--aviso { background: var(--warning-tint); color: var(--warning-tint-text); }
.ibo-nota > .crece { min-inline-size: 0; }
.ibo-nota > .btn { flex: none; align-self: center; }
/* administrador: la ayuda del salario pegada a su interruptor, y «Guardar» con su «Cambios sin guardar» al lado */
.ibo-ayuda-via { margin: 0; }
.ibo-guardar { margin-block-start: var(--sp-3); }
/* <<< ibo-pantallas */
/* >>> banco-armazon */
/* ============================================================
   BANCO DEL EQUIPO · EL ARMAZÓN (03/11/2026 · P6). Todo con clases propias (banco-*): nada de lo que ya existe cambia, y sin estilos en
   línea, por la política de contenido. Colores solo por tokens: el tema oscuro sale solo. Nada de anchos fijos: a 360 px, cifra y botón
   se apilan y un texto largo baja de línea.
   ============================================================ */
.banco-importe { unicode-bidi: isolate; white-space: nowrap; font-variant-numeric: tabular-nums; }
.banco-tarjeta { display: flex; flex-direction: column; gap: var(--sp-3); max-width: 720px; margin-inline: auto; margin-block-end: var(--sp-4); }
.banco-grande { display: flex; flex-direction: column; gap: var(--sp-1); min-inline-size: 0; }
.banco-grande-rotulo { display: flex; align-items: center; gap: var(--sp-1); font-weight: 600; }
.banco-grande-fila { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-2); }
.banco-cifra { font-size: 2rem; line-height: 1.2; overflow-wrap: anywhere; }
.banco-grande--debes .banco-cifra { color: var(--danger); }
.banco-grande-frase { margin: 0; }
.banco-lineas { display: flex; flex-direction: column; border-block-start: 1px solid var(--border); }
.banco-linea { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px var(--sp-2); padding-block: var(--sp-2); border-block-end: 1px solid var(--border); }
.banco-linea:last-child { border-block-end: 0; }
.banco-linea-rotulo { display: flex; align-items: center; gap: var(--sp-1); min-inline-size: 0; }
.banco-linea-detalle { grid-column: 1 / -1; min-inline-size: 0; overflow-wrap: anywhere; }
.banco-aviso { margin: 0; }
.banco-pruebas { display: flex; align-items: flex-start; gap: var(--sp-1); margin: 0; }
.banco-pruebas > svg { flex: none; }
.banco-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.banco-fila { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-3); border-block-end: 1px solid var(--border); min-inline-size: 0; overflow-wrap: anywhere; }
.banco-fila:last-child { border-block-end: 0; }
.banco-fila-cabeza { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.banco-apagado { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); max-width: 720px; }
.banco-apagado p { margin: 0; }
.banco-apagado-lista { margin: 0; padding-inline-start: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-1); }
.banco-interruptor { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); margin: 0 0 var(--sp-3); }
.banco-puerta { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); margin-block-end: var(--sp-4); }
.banco-puerta p { margin: var(--sp-1) 0 0; }
.banco-puerta .crece { min-inline-size: 200px; }
/* <<< banco-armazon */
/* >>> banco-admin */
/* ============================================================
   BANCO DEL ADMINISTRADOR (03/11/2026 · P9). Fichas en lugar de tablas: una cola de retiros se decide también desde el teléfono, y a 360 px una tabla de ocho columnas
   obliga a desplazarse. Colores solo por tokens; márgenes lógicos, para el árabe; nada baja de 44 px.
   ============================================================ */
.banco-explica { margin: 0 0 var(--sp-3); }
.banco-barra { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-2) var(--sp-3); margin-block-end: var(--sp-3); }
.banco-barra > * { min-inline-size: 0; }
.banco-barra > .texto-small { flex: 1 1 12rem; }
.banco-barra .campo { margin-block-end: 0; }
.banco-filtro { flex: 0 1 16rem; }
.banco-fichas { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.banco-ficha { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-campo); min-inline-size: 0; overflow-wrap: anywhere; }
.banco-ficha-cabeza { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }
.banco-ficha-cabeza .crece { min-inline-size: 8rem; }
.banco-ficha-importe { font-size: 1.25rem; }
.banco-datos { display: grid; grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr)); gap: var(--sp-2) var(--sp-3); margin: 0; }
.banco-datos--columna { grid-template-columns: minmax(0, 1fr); }
.banco-dato { min-inline-size: 0; }
.banco-dato dt { font-size: var(--fs-small); color: var(--text-2); }
.banco-dato dd { margin: 0; overflow-wrap: anywhere; }
.banco-alerta { display: flex; align-items: flex-start; gap: var(--sp-1); margin: 0; color: var(--danger); font-weight: 600; }
.banco-alerta > svg, .banco-avisos svg { flex: none; }
.banco-avisos { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-1); font-size: var(--fs-small); }
.banco-avisos li { display: flex; align-items: flex-start; gap: var(--sp-1); color: var(--warning-tint-text); background: var(--warning-tint); border-radius: var(--r-campo); padding: var(--sp-1) var(--sp-2); }
.banco-acciones { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-block-start: var(--sp-1); }
.banco-acciones .btn { flex: 0 1 auto; }
.banco-ir { min-height: var(--tactil); }
.banco-rastro summary { display: list-item; min-height: var(--tactil); line-height: var(--tactil); cursor: pointer; font-size: var(--fs-small); }
.banco-rastro ul { margin: 0; padding-inline-start: var(--sp-4); font-size: var(--fs-small); }
.banco-totales { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: var(--sp-2) var(--sp-3); margin-block-end: var(--sp-3); }
.banco-total { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.banco-total span:first-child { display: flex; align-items: center; gap: var(--sp-1); font-size: var(--fs-small); color: var(--text-2); }
.banco-rejilla { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: var(--sp-2) var(--sp-3); }
.banco-campo { margin-block-end: 0; min-inline-size: 0; }
.banco-campo .campo-control { max-inline-size: 100%; }
.banco-campo input { min-inline-size: 0; inline-size: 100%; }
.banco-campo--ancho { grid-column: 1 / -1; }
.banco-area { min-height: 72px; align-items: stretch; padding-block: var(--sp-2); }
.banco-area textarea { inline-size: 100%; }
.banco-casilla { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tactil); cursor: pointer; }
.banco-casilla input { inline-size: 22px; block-size: 22px; flex: none; }
.banco-interruptor-fila { display: flex; align-items: center; gap: var(--sp-2); min-height: var(--tactil); padding-block: var(--sp-1); }
.banco-interruptor-fila .crece { min-inline-size: 0; }
.banco-interruptor-rotulo { display: block; }
.banco-regla { padding-block: var(--sp-1); overflow-wrap: anywhere; }
.banco-regla-dato { margin: var(--sp-1) 0 0; padding-inline-start: var(--sp-4); }
.banco-bloque { display: flex; flex-direction: column; gap: var(--sp-2); }
.banco-mov .banco-fila-cabeza { flex-wrap: nowrap; }
.banco-cuenta-vista { display: flex; flex-direction: column; gap: var(--sp-2); }
.banco-iban { font-size: 1.05rem; overflow-wrap: anywhere; user-select: all; }
[data-banco-cuadre="no"] { color: var(--danger); font-weight: 600; }
[data-banco-cuadre] p { margin: 0; display: flex; align-items: center; gap: var(--sp-1); flex-wrap: wrap; }
/* (04/11/2026 · revisión A-7) Los dos botones de esa tabla de Pagos llevan el estilo compacto de su vecino. MEDIDO ANTES: 36 px de alto a 360 px */
[data-srespuesta], [data-scc-responder] { min-height: var(--tactil); }
/* (04/11/2026 · revisión B-11) Un título no se sale de su ventana, se llame como se llame. MEDIDO ANTES: con un nombre sin espacios, el contenido medía 4712 px en una ventana de 328 px */
.popup-cabecera h2 { min-inline-size: 0; overflow-wrap: anywhere; }
/* <<< banco-admin */
