/* ============================================================================
   T-057 · El sistema de diseño de IQ-Panel
   ----------------------------------------------------------------------------
   Este es el ÚNICO archivo del frontend donde puede aparecer un color o una
   tipografía literal. En cualquier otro sitio de `paneles/` es un defecto: un
   color fijo es un cliente que no puede cambiar su marca, y con eso se cae la
   promesa de que cliente nuevo es configuración y no desarrollo (D-001).

   Tres capas, y la de en medio es la que hace posible el white-label:

     1. PRIMITIVOS  — espaciado, radios, sombras, escala tipográfica, duraciones.
                      Fijos. Ningún cliente los cambia.
     2. MARCA       — los NUEVE valores de `tenant_branding`, y ni uno más.
                      Los escribe `aplicar.js` en tiempo de ejecución.
     3. SEMÁNTICOS  — --texto, --superficie, --borde-control, --foco…
                      NO se escriben aquí: los calcula `derivar.js` a partir de
                      la capa 2, garantizando AA con cualquier marca.

   Los valores semánticos que sí aparecen abajo son solo RESERVA: lo que se ve
   en el medio segundo antes de que corra el JS, y lo que se ve si el JS falla.
   Una pantalla sin colores es una pantalla rota; una con los de reserva, no.
   ============================================================================ */

/* ── Capa 1 · Primitivos ─────────────────────────────────────────────────── */

:root {
  /* Espaciado — escala de 4/8 pt. Todo margen y todo relleno sale de aquí. */
  --espacio-1: 4px;
  --espacio-2: 8px;
  --espacio-3: 12px;
  --espacio-4: 16px;
  --espacio-5: 20px;
  --espacio-6: 24px;

  /* EL CANALÓN DEL PANEL, y está aquí y no en la hoja de cada panel por lo que enseñó el
     bloque 8: hay UNA pieza que tiene que salirse de él —la banda de la cabecera, que va de
     borde a borde— y el escape se escribe con el mismo valor que el relleno. Escritos por
     separado se descuadran: escrito a mano, la banda se salía 24 px mientras que en ≤479 el
     relleno es 16, y el documento se desbordaba 8 px por lado a 320 px. Lo cazó la prueba 57.
     Cada panel lo redeclara en su punto de corte; lo que no puede es haber dos números. */
  --canalon: var(--espacio-6);
  --espacio-8: 32px;
  --espacio-10: 40px;
  --espacio-12: 48px;
  --espacio-16: 64px;
  --espacio-20: 80px;

  /* Radios */
  --radio-sm: 4px;
  --radio-md: 8px;
  --radio-lg: 12px;
  --radio-xl: 16px;
  --radio-full: 9999px;

  /* Tipografía — las tres del manual de marca, pág. 5.
     --tipografia la pone el cliente (capa 2); las otras dos son de IQUORA y solo
     se usan en superficies de marca fija (landing y maestro). */
  --tipo-cuerpo: var(--tipografia, 'DM Sans'), -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  --tipo-display: 'Space Grotesk', var(--tipo-cuerpo);
  --tipo-seccion: 'Bebas Neue', var(--tipo-cuerpo);

  /* Escala Major Third (1.25) con clamp para el salto móvil→escritorio.
     Base 16px: por debajo, iOS hace auto-zoom en los campos de formulario. */
  --texto-xs: 12px;
  --texto-sm: 14px;
  --texto-base: 16px;
  --texto-lg: clamp(18px, 1.4vw, 20px);
  --texto-xl: clamp(20px, 1.8vw, 25px);
  --texto-2xl: clamp(24px, 2.4vw, 31px);
  --texto-3xl: clamp(28px, 3.2vw, 39px);
  --texto-4xl: clamp(32px, 4.4vw, 49px);

  /* ── T-209 · LA ALTERNA COMPACTA, que T-4 pide y no existía ──────────────────────────
   * `base-ux/reglas.md` T-4 da DOS escalas: la Major Third de arriba (61/49/39/31/25/20/16/
   * 14/12) y una «alterna compacta para densidad dashboard» (36/30/24/20/16/14/12). La
   * segunda nunca se escribió, y por eso el panel maestro —que por E-3 ES densidad
   * dashboard— venía usando la escala espaciosa con los paddings apretados: media regla.
   *
   * Se declara aquí y se activa con `[data-densidad='dashboard']` en la raíz. NO se enciende
   * en el maestro desde esta tarea, y eso es deliberado: T-209 construye la base, y cambiar
   * la escala tipográfica del maestro mueve cada medida de sus siete pantallas. Esa es una
   * decisión de T-215 y T-216, que son quienes las tienen delante — y además están
   * construyendo AHORA MISMO sobre este commit. Mover el suelo bajo una sesión viva es
   * exactamente lo que la Ley 8 existe para impedir.
   *
   * Sin `clamp`: una escala compacta que además encoge en móvil deja de ser una escala. El
   * maestro es una superficie de escritorio (su propio README lo dice) y su alterna no
   * necesita el salto que sí necesita el panel del cliente. */
  --alto-linea-apretado: 1.2;
  --alto-linea-titulo: 1.3;
  --alto-linea-cuerpo: 1.6;

  /* Movimiento. Solo se anima transform y opacity: lo demás provoca reflow. */
  --duracion-rapida: 150ms;
  --duracion-normal: 200ms;
  --duracion-lenta: 300ms;
  /* La duración de SALIDA: 60-70 % de la de entrada, con tope de 250 ms (§5.1). Lo que se
     va importa menos que lo que llega, y hacerle esperar al usuario lo mismo en las dos
     direcciones es cobrarle el doble por el mismo cambio. */
  --duracion-salida: 130ms;

  /* Dos curvas, y el nombre dice cuál es cuál.
     Hasta hoy había una sola variable, y su nombre (la traducción literal de `easeOut`)
     nombraba una dirección que no era la suya: es un ease-out, o sea la curva de ENTRADA,
     pero el nombre viejo se prestaba a ponerla en una salida sin que nada chillara.
     Se renombró el 2026-09-07 (§5.5, punto 1). */
  --curva-entrada: cubic-bezier(0.22, 1, 0.36, 1);
  /* M3 emphasized-accelerate: arranca despacio y se va rápido, que es como se sale. */
  --curva-de-salida: cubic-bezier(0.3, 0, 0.8, 0.15);

  /* El escalón de una lista que entra. Máximo ~8 hijos: con más, lo que era ritmo se vuelve
     espera (§2.3 de la base de animación). */
  --escalon: 40ms;

  /* Sombras de dos capas. En rgba, no en hex, para que hereden opacidad. */
  --sombra-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);
  --sombra-md: 0 2px 8px rgba(0, 0, 0, 0.06), 0 8px 20px rgba(0, 0, 0, 0.1);
  --sombra-lg: 0 4px 12px rgba(0, 0, 0, 0.08), 0 16px 40px rgba(0, 0, 0, 0.14);

  /* ── La elevación SEMÁNTICA ─────────────────────────────────────────────────────────
   * Las tres de arriba dicen CUÁNTO se levanta algo. Estas tres dicen POR QUÉ, y esa es la
   * diferencia que impide que la escala se use al azar: una tarjeta no se levanta porque
   * quede bonita, se levanta porque está rota.
   *
   * Y son ALIAS a propósito, no valores nuevos. Si mañana cambia `--sombra-md` cambian los
   * tres a la vez y la escala sigue siendo una escala; un valor suelto aquí sería una cuarta
   * sombra que nadie sabría cuándo usar — que es exactamente cómo un sistema de elevación
   * deja de serlo. */
  --elevacion-urgente: var(--sombra-md);
  --elevacion-normal: var(--sombra-sm);
  --elevacion-calma: none;

  /* ── T-174 · La identidad: lo que hace que el panel no se vea plano ──────────────────
   * «Se ve muy plano todo el diseño completo… le falta ese ponche, esa diferenciación, esos
   * elementos que no la van a ver tan AI» (Diego, 2026-09-15). Todo lo de abajo son MEZCLAS
   * de tokens que ya cambian con la marca y con el modo, así que un tenant derivado por
   * `derivar.js` lo hereda sin que `derivar.js` sepa que existe. Ninguno es un color nuevo. */

  /* Los tamaños del icono (reglas R-4: 16 / 24 / 32, tokenizados). */
  --icono-sm: 16px;
  --icono-md: 24px;
  --icono-lg: 32px;

  /* El borde de una tarjeta, teñido con un 22 % de primario. Es lo que separa «gris sobre
     gris» de una superficie que pertenece a esta marca. 22 y no más: a 35 el borde ya
     compite con el filo de estado de los avisos, que sí tiene que ganar. */
  --borde-luz: color-mix(in oklab, var(--primario) 22%, var(--borde));
  /* El brillo de la esquina de una caja de cifra: primario al 10 % sobre transparente. */
  --brillo-acento: color-mix(in oklab, var(--primario) 10%, transparent);
  /* Un punto de la rejilla del fondo: texto al 7 %. En los dos modos es «apenas». */
  --rejilla-punto: color-mix(in oklab, var(--texto) 7%, transparent);
  /* El orbe: primario y el Violeta Tech del aviso, el brillo del color que va SOBRE el
     primario, y un halo del primario a medias. */
  --orbe-a: var(--primario);
  --orbe-b: var(--aviso);
  --orbe-luz: color-mix(in oklab, var(--sobre-primario) 85%, transparent);
  --orbe-halo: color-mix(in oklab, var(--primario) 45%, transparent);

  /* Alturas de control. Son lo que MIDE un control, no lo que hace falta para poder tocarlo:
     son dos cosas, y confundirlas ya ha costado dos veces (ver el token de abajo). */
  --control-sm: 32px;
  --control-md: 40px;
  --control-lg: 48px;

  /* ── T-209 · D-F25 · EL OBJETIVO TÁCTIL, QUE NO EXISTÍA ──────────────────────────────
   *
   * A-3 pide **44 px**, y hasta hoy ese número no estaba escrito en ninguna parte de
   * `paneles/tokens/`. Estaba en un COMENTARIO —el de aquí arriba decía «44px es el mínimo
   * táctil de iOS» justo encima de tres tokens de los que **ninguno vale 44**—, y un número
   * que solo vive en un comentario se cita de memoria y se aproxima con el token de al lado.
   *
   * Se ha pagado dos veces por eso, y las dos con la misma forma:
   *   · `tokens.css:929` ya tiene escrito que la CTA primaria en 40 px «lo cazó la prueba 41».
   *   · `paginas-3` escribió `min-height: var(--control-md)` en el segmentado de Consumo con
   *     el comentario «A-3 pide 44 px de objetivo táctil». Subió de 32 a 40 **creyendo que
   *     cumplía**: `--control-md` vale 40. Se quedó a cuatro píxeles del criterio que citaba.
   *
   * POR QUÉ NO SE ARREGLA CON LOS QUE YA HAY, que es la parte que importa:
   *   · **`--control-md` no se toca.** Vale 40 y está bien: es la altura de un CONTROL y se usa
   *     en todo el panel. Moverlo para arreglar un caso mueve la interfaz entera.
   *   · **`--control-lg` (48) no vale aquí.** Cabe, pero resuelve el síntoma inventando
   *     holgura, y deja el siguiente caso igual de desprotegido.
   *
   * O sea que la altura de un control y el objetivo táctil son DOS conceptos y por eso son dos
   * tokens. Un control puede medir 40 y ser perfectamente tocable si su caja de impacto llega
   * a 44 — por eso esto se usa en `min-height`, en `min-width` y en el `::after` que agranda un
   * objetivo pequeño, no para estirar todos los botones a 44. */
  --objetivo-tactil: 44px;

  /* T-238 · EL CÓMODO, que es otro número y por eso es otro token. `--objetivo-tactil` es el
   * MÍNIMO LEGAL: 44 px es lo que piden WCAG 2.5.5 y A-3, y por debajo se reprueba. Esto es lo
   * que el lenguaje v4 fija para un control que se usa con el pulgar mientras se hace otra
   * cosa, que es el caso de un formulario en el teléfono. Lo dice su propia hoja: «44 es el
   * mínimo legal, no el cómodo».
   *
   * Son dos tokens y no uno porque contestan dos preguntas distintas: `--objetivo-tactil`
   * contesta «¿esto reprueba?» y `--v4-tactil` contesta «¿esto se usa a gusto?». Fundirlos en
   * uno obligaría a elegir entre subir el listón de conformidad de todo el panel —que es una
   * decisión de accesibilidad, no de diseño— o dejar los formularios en el mínimo.
   *
   * Y va aquí y no en `lenguaje/v4.css` por la reja 1: un `var(--x)` que no esté DECLARADO en
   * `paneles/tokens/` es un token inventado, y las pantallas lo usan. */
  --v4-tactil: 48px;

  /* T-238 · LOS TRES RADIOS DE V4, mudados aquí desde `lenguaje/v4.css` por la misma reja: un
   * `var(--x)` que no esté declarado en `paneles/tokens/` es un token inventado, y las hojas de
   * pantalla necesitan escribirlos para que un esqueleto tenga el radio de la pieza que va a
   * sustituir. Son FORMA, no color, así que aquí no hay tope (§5.3). `v4.css` los sigue usando
   * igual; lo único que cambia es dónde están declarados. */
  --v4-r-sm: 16px;
  --v4-r: 22px;
  --v4-r-lg: 28px;

  /* T-238 · CUATRO PIGMENTOS DE V4 QUE ESTABAN ESCRITOS A MANO DENTRO DE SUS REGLAS.
   * Salieron al tener que REPETIRLOS: `box-shadow` y `background` no se funden entre reglas,
   * así que una regla de desempate o un `:focus-visible` tiene que volver a declarar la sombra
   * entera — y cada repetición era otro `color-mix` fuera de `paneles/tokens/`, o sea otra
   * marca de la reja 1 por copiar algo que ya existía.
   *
   * Con nombre, la repetición deja de ser una copia: las dos reglas dicen el mismo token.
   * El `#fff` de los dos brillos es DELIBERADO y no un descuido de `--sobre-primario`: es el
   * lustre de una pieza rellena de color de marca, y `--sobre-primario` vale `#0D0D0D` en dos
   * de las siete marcas — habría pintado un brillo NEGRO en `farmacia` y en `tienda`. */
  --v4-humo-medio: color-mix(in srgb, var(--texto) 2%, transparent);
  --v4-canto-vivo: color-mix(in srgb, var(--texto) 26%, transparent);
  --v4-brillo-cta: color-mix(in srgb, #fff 28%, transparent);
  --v4-brillo-chip: color-mix(in srgb, #fff 26%, transparent);

  --ancho-contenido: 1200px;
  --ancho-lectura: 65ch;

  /* Los siete peldaños de la alterna compacta de T-4. Ver la nota de arriba. */
  --compacto-xs: 12px;
  --compacto-sm: 14px;
  --compacto-base: 16px;
  --compacto-lg: 20px;
  --compacto-xl: 24px;
  --compacto-2xl: 30px;
  --compacto-3xl: 36px;

  /* ── Capa 3 · Reserva ──
     Lo que se ve antes de que corra derivar.js. Sin marca, modo claro. */
  --fondo: #EDF6F8;
  --superficie: #E2EAEC;
  --superficie-alta: #D2E7EB;
  --texto: #1F2020;
  --texto-suave: #5B5E5E;
  --borde: #CED5D7;
  --borde-control: #747A7D;
  --primario: #0E7A8C;
  --primario-texto: #0C6675;
  --sobre-primario: #FFFFFF;
  --acento: #0E7A8C;
  --acento-texto: #0C6675;
  --sobre-acento: #FFFFFF;
  --primario-relleno: #0E7A8C;
  --acento-relleno: #0E7A8C;
  --fondo-tinte-a: #C5E0E5;
  --fondo-tinte-b: #D6D9E9;
  --cristal-opaco: #DFE5EB;
  --foco: #0E7A8C;
  --exito: #2ECC71;
  --aviso: #4B2A8C;
  --error: #C5322B;
  --sobre-exito: #0D0D0D;
  --sobre-aviso: #FFFFFF;
  --sobre-error: #FFFFFF;
  --exito-texto: #186C3C;
  --aviso-texto: #4B2A8C;
  --error-texto: #B02D26;
}
/* T-238 · y el salto de escritorio de los dos radios grandes, que venía con ellos desde
   `lenguaje/v4.css`: en una pantalla ancha la pieza puede permitirse más curva sin volverse
   una pastilla. `--v4-r-sm` NO sube: es el radio de un campo, y un campo de 16 px de radio a
   1440 ya es lo que tiene que ser. */
@media (min-width: 768px) {
  :root { --v4-r: 26px; --v4-r-lg: 34px; }
}



/* EL INTERRUPTOR DE LA ALTERNA COMPACTA (T-4, densidad dashboard de E-3).
   Una sola regla que reapunta la escala: así ninguna pantalla tiene que cambiar un
   `font-size`, que es la única forma de que esto no acabe siendo nueve escalas. */
:root[data-densidad='dashboard'] {
  --texto-lg: var(--compacto-lg);
  --texto-xl: var(--compacto-xl);
  --texto-2xl: var(--compacto-2xl);
  --texto-3xl: var(--compacto-3xl);
  --texto-4xl: var(--compacto-3xl);
}

/* ── T-209 · LO ADAPTABLE, 1 de 2 · LA PANTALLA BAJA ─────────────────────────────────────
 *
 * EL CASO QUE NADIE HA MIRADO EN TODO EL CARRIL. El checklist de entrega (§7) pide
 * «320 / 375 / 768 / 1024 / 1440, **y en horizontal**», y en `paneles/` no hay una sola
 * `@media` de `orientation` ni de altura: **treinta y seis media queries y las treinta y seis
 * miran el ANCHO**.
 *
 * Por qué eso es un defecto y no un hueco de cobertura: un teléfono en horizontal mide
 * **844 × 390**. Como 844 > 767, entra por la rama de ESCRITORIO — recibe el layout que
 * asume sitio vertical — y lo hace con **390 px de alto**, que es *menos* de la mitad de los
 * 844 que tiene de pie. Todo lo que el carril construyó para proteger el pliegue (T-170,
 * T-171) está apagado justo donde menos sitio hay. La condición que falta no es de ancho.
 *
 * QUÉ HACE ESTA REGLA, Y QUÉ NO. Reapunta la escala a la alterna compacta, que es lo mismo
 * que hace `[data-densidad='dashboard']` y con los mismos siete peldaños ya medidos por T-4.
 * **Se reutiliza a propósito**: inventar una tercera escala para el apaisado sería la novena
 * forma de lo mismo, y este archivo entero existe para que eso no pase.
 *
 * Y NO HACE NADA MÁS, tampoco a propósito. El siguiente escalón —si el pliegue necesita que
 * la cabecera deje de ser pegajosa, o que la barra de abajo cambie— **lo decide la medida y
 * no yo**: la sonda está en `pruebas/apaisado.js` y las pruebas 1125-1128 la usan. Escribir
 * más CSS antes de tener el número es exactamente la aritmética de `calc()` que ya costó
 * tres vueltas en este carril.
 *
 * 480 px de alto y no 500: es el alto de un teléfono grande en horizontal con holgura, y por
 * debajo de eso ninguna tableta cae. La `orientation` NO se pide: lo que importa es que la
 * pantalla sea baja, y una ventana de escritorio aplastada tiene el mismo problema. */
@media (max-height: 480px) {
  :root {
    --texto-lg: var(--compacto-lg);
    --texto-xl: var(--compacto-xl);
    --texto-2xl: var(--compacto-2xl);
    --texto-3xl: var(--compacto-3xl);
    --texto-4xl: var(--compacto-3xl);
  }
}

/* ── T-209 · LO ADAPTABLE, 2 de 2 · LOS CORTES, COMO TOKEN ───────────────────────────────
 *
 * Había **diez anchos distintos** repartidos por treinta y seis media queries de trece
 * archivos, y ninguno escrito en un sitio. `paginas-1` acaba de descubrir por qué eso duele:
 * movió un corte sin mover su mecanismo y la decisión se quedó muerta.
 *
 * AVISO, Y ES LO PRIMERO QUE HAY QUE SABER: **una media query no puede usar `var()`**. No es
 * una limitación de este archivo, es del lenguaje — las custom properties se resuelven en el
 * elemento y una `@media` se evalúa antes de que haya elemento. Así que estos tokens **no
 * los lee el CSS**: los leen el JS, las `@container` y, sobre todo, **la prueba 1129**, que
 * recorre los trece archivos y cae si aparece un ancho que no esté declarado aquí.
 *
 * O sea que el mecanismo no es la cascada: es que el número sea **una decisión declarada** en
 * vez de una costumbre tecleada. Quien necesite un corte nuevo lo añade aquí y lo justifica,
 * que es exactamente un peldaño más de fricción que teclear `900px` en su hoja.
 *
 * LOS CUATRO DEL PANEL. Los `max-width` van a un píxel menos, que es la otra mitad del par y
 * de donde salen los `767`, `1023` y `1199` de las hojas. */
:root {
  --bp-estrecho: 480px;   /* teléfono estrecho · el 479 de `comun.css` y `maestro/panel.css` */
  --bp-movil: 768px;      /* teléfono ↔ tableta · el corte principal, 20 de las 36 */
  --bp-tablet: 1024px;    /* tableta ↔ escritorio */
  --bp-ancho: 1200px;     /* el escritorio ancho del panel del cliente */

  /* Y LOS TRES DE LA LANDING, que son suyos y no del panel. Van declarados y no escondidos:
     una página de venta tiene otro ritmo de lectura que un panel de trabajo, y forzarla a los
     cuatro de arriba sería uniformidad por uniformidad. Lo que no vale es que existan sin que
     nadie lo sepa. */
  --bp-landing-lema: 640px;
  --bp-landing-fila: 560px;
  --bp-landing-dos: 900px;
}

/* ── T-209 · D-F20 · EL RELLENO DE UNA PIEZA · tres tokens, y la historia de por qué tres ──
 *
 * El encargo hablaba de «los saltos con `clamp()` que nadie ha tokenizado». **Medido: los
 * `clamp()` sí están tokenizados** — hay cinco en todo `paneles/` y los cinco viven en este
 * archivo, en la escala de tipografía. Ahí no había nada repartido.
 *
 * LO QUE SÍ ESTABA REPARTIDO era otra cosa, y sale OCHO veces: «en teléfono, el relleno de una
 * pieza baja a 16». Escrito a mano, con su propia media query, en `contactos.css`,
 * `conocimiento.css` (dos), `agentes.css` (dos), `dudas.css`, `integraciones.css` y
 * `grafica.css`. Y `maestro/panel.css:623` llegó por su cuenta a la forma correcta
 * —`--canalon: var(--espacio-4)` dentro de la media query— solo que local a `.maestro`.
 * Tres sesiones distintas reinventaron el mismo mecanismo porque el mecanismo faltaba aquí.
 *
 * Y LA PRIMERA VERSIÓN DE ESTE TOKEN ERA IMPOSIBLE, que es la parte que hay que dejar escrita
 * para que no se repita. Fue `--relleno-pieza`, **de un solo valor**, y se repartió a cinco
 * sesiones con la frase «quien lo adopte borra su media query y no cambia un píxel». Esa frase
 * la escribí sin abrir los seis archivos, y es falsa para las ocho piezas: **todas llevan
 * relleno ASIMÉTRICO de dos valores** (`16px 20px`, `20px 24px`) y un token de un valor no
 * puede expresar eso en absoluto. No faltaban escalones: la FORMA era la equivocada.
 *
 * Una custom property sí puede llevar una lista, así que `padding: var(--relleno-fila)`
 * reproduce el original EXACTO. Tres familias, medidas selector por selector sobre las cuatro
 * ramas:
 *
 *   `--relleno-fila`         16/20 → 16   `.contacto-fila` `.agente-fila` `.grafica`
 *   `--relleno-ficha`        20/24 → 16   `.base-fila` `.interruptor` `.duda` `.integracion`
 *   `--relleno-fila-anidada` 16/20 → 12/16 `.documento-fila`
 *
 * LA TERCERA NO ESTABA EN NINGÚN RECUENTO, ni en el mío ni en el que me llegó: `.documento-fila`
 * baja a 12/16 y no a 16, porque es una fila DENTRO de una ficha — un peldaño más abajo en la
 * misma escalera, no una excepción.
 *
 * LA CLASIFICACIÓN la decidió el orquestador (D-F20) y es la que evita el único daño real que
 * esto podía hacer: **fila es un renglón de una lista, ficha es una pieza autónoma**. Con eso
 * `.duda` e `.integracion` son FICHAS y conservan su 20/24 en escritorio. Si hubieran caído en
 * `fila`, `.duda` habría perdido 8 px de alto por pregunta (40 de relleno vertical → 32) en la
 * pantalla cuyo presupuesto T-171 espera y que a 320 ya falla por −12. Un token que impone un
 * cambio de altura no es un mecanismo: es una regresión con nombre bonito.
 *
 * DOS QUE NO LO USAN, Y SON DECISIONES:
 *   · **`.tarjeta`** se queda en 24 a todos los anchos. Nadie ha decidido que encoja, y hacerlo
 *     movería el pliegue de las dieciséis que T-170 y T-171 pelearon a mano.
 *   · **`--canalon` no es esto renombrado**, y es la pregunta que hizo `maestro-1`. `--canalon`
 *     es el margen de la página **y un contrato de escape**: `.maestro` lo usa de `padding` y
 *     `.panel__cabecera` se sale de él con `margin: calc(var(--canalon) * -1)` y se lo devuelve
 *     como relleno propio. Los dos números tienen que ser el mismo y **la prueba 57 lleva desde
 *     el bloque 2 vigilando eso**: escritos por separado, la banda se salía 24 con el relleno en
 *     16 y el documento se desbordaba 8 px por lado a 320. Un relleno de pieza no tiene socio,
 *     no tiene escape y no tiene invariante — y además **ya discrepan en el corte**: el canalón
 *     cambia en 480 y todos los rellenos de pieza en 767. Fundirlos convertiría en mecanismo el
 *     descuadre que la 57 existe para cazar. La 1130 lo fija en las dos direcciones. */
:root {
  --relleno-fila: var(--espacio-4) var(--espacio-5);
  --relleno-ficha: var(--espacio-5) var(--espacio-6);
  --relleno-fila-anidada: var(--espacio-4) var(--espacio-5);
}

@media (max-width: 767px) {
  :root {
    --relleno-fila: var(--espacio-4);
    --relleno-ficha: var(--espacio-4);
    --relleno-fila-anidada: var(--espacio-3) var(--espacio-4);
  }
}


/* ── Marca IQUORA FIJA ────────────────────────────────────────────────────
   `landing/` y `maestro/` no son white-label: su marca no cambia nunca, así que
   no pasan por derivar.js en tiempo de ejecución — llevan sus tokens escritos.
   Se activa con <html data-marca="iquora">.

   Estos valores NO se escribieron a mano: los generó la misma `derivar()` que
   usa el panel del cliente, con los nueve valores oficiales de IQUORA. Así la
   landing y el panel no pueden divergir por un dedazo.

   Los tres colores del manual que no caben en los nueve tokens viven solo aquí,
   con su escalón legible sobre superficie alta (los crudos reprueban AA: el
   Violeta Tech da 1.86:1 sobre negro). */
[data-marca='iquora'] {
  --primario: #903749;
  --acento: #C8A0B0;
  --fondo: #1F1416;
  --superficie: #2A2022;
  --superficie-alta: #33292B;
  --texto: #EDECEC;
  --texto-suave: #AAA6A6;
  --borde: #3E3537;
  --borde-control: #7A7275;
  --foco: #A84055;
  --primario-texto: #CC7A8B;
  --acento-texto: #C8A0B0;
  --sobre-primario: #FFFFFF;
  --sobre-acento: #0D0D0D;
  --primario-relleno: #BD5267;
  --acento-relleno: #C8A0B0;
  --fondo-tinte-a: #331A1F;
  --fondo-tinte-b: #251727;
  --cristal-opaco: #2D1E21;
  --exito: #2ECC71;
  --aviso: #4B2A8C;
  --error: #C5322B;
  --sobre-exito: #0D0D0D;
  --sobre-aviso: #FFFFFF;
  --sobre-error: #FFFFFF;
  --exito-texto: #2ECC71;
  --aviso-texto: #A184DA;
  --error-texto: #DF726D;
  --tipografia: 'DM Sans';

  /* Los tres colores del manual que no caben en los nueve tokens del white-label, con su
     escalón legible sobre superficie alta (los crudos reprueban AA: el Violeta Tech da
     1.86:1 sobre negro). */
  --iquora-violeta: #4B2A8C;
  --iquora-violeta-texto: #997AD7;
  --iquora-rosa: #C8A0B0;
  --iquora-rosa-texto: #B27B91;
  --iquora-azul-noche: #1A1A2E;
  --iquora-azul-noche-texto: #8585BA;
}

/* Y LA VARIANTE CLARA, que hasta el bloque 8 no existía: «tampoco me sale la opción del modo
   claro» (Diego, 2026-09-11). De IQUORA solo había la oscura, así que el conmutador no tenía
   entre qué conmutar. Generada con la MISMA `derivar()`, que es la regla de este bloque desde
   T-057: escrita a mano, la landing y el panel divergen por un dedazo. */
[data-marca='iquora'][data-modo='claro'] {
  --primario: #903749;
  --acento: #C8A0B0;
  --fondo: #F8EDEF;
  --superficie: #ECE2E4;
  --superficie-alta: #ECD7DB;
  --texto: #201F1F;
  --texto-suave: #5D595A;
  --borde: #D7CECF;
  --borde-control: #7C7374;
  --foco: #903749;
  --primario-texto: #903749;
  --acento-texto: #7E495E;
  --sobre-primario: #FFFFFF;
  --sobre-acento: #0D0D0D;
  --primario-relleno: #903749;
  --acento-relleno: #A4617C;
  --fondo-tinte-a: #E5CCD1;
  --fondo-tinte-b: #E0D2E1;
  --cristal-opaco: #EADCDF;
  --exito: #2ECC71;
  --aviso: #4B2A8C;
  --error: #C5322B;
  --sobre-exito: #0D0D0D;
  --sobre-aviso: #FFFFFF;
  --sobre-error: #FFFFFF;
  --exito-texto: #176639;
  --aviso-texto: #4B2A8C;
  --error-texto: #A82B25;
}


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

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

/* `hidden` tiene que ganar SIEMPRE.
   El navegador lo aplica como `display: none` desde su hoja de usuario-agente, así que
   cualquier `display` escrito por nosotros —`.aviso { display: flex }`,
   `.recibida { display: grid }`— le gana por especificidad y el elemento se ve igual.
   Costó un defecto real: la landing mostraba "No se pudo enviar" y "Solicitud recibida"
   nada más cargar, antes de que nadie escribiera una letra. */
[hidden] { display: none !important; }

/* EL FONDO, CON DEGRADADO. «Me gustaría un fondo mucho más animado incluso, que sea con el
   degradado de los colores del tinto, el morado» (Diego, 2026-09-11).
 *
 * TRES DECISIONES, y las tres con su porqué:
 *
 *   1. VA EN EL BODY Y ES `fixed`, así que NO se mueve al desplazar. Un degradado que viaja con
 *      el scroll es un fondo que compite: en una herramienta de trabajo, lo que se mueve al
 *      leer es exactamente lo que su propia base de conocimientos manda no hacer («no compitas
 *      por atención que no has ganado»).
 *   2. LOS COLORES SALEN DE LOS TOKENS, nunca escritos. El tinto es `--primario` y el morado es
 *      el aviso —que desde este bloque ES el Violeta Tech del manual—, así que el degradado de
 *      un cliente lleva SU color, no el de IQUORA. Es la misma regla que el fondo derivado: el
 *      panel se adapta, no se pinta.
 *   3. `color-mix` AL 18 % Y AL 14 % SOBRE EL FONDO, no el color puro, y los dos números salen
 *      de medir el píxel. El fondo derivado deja 15:1 contra el texto; con estas mezclas la
 *      esquina más cargada baja a 13.5 y la diferencia entre esquina y centro es de 11 a 22
 *      valores — se ve, y sigue sobrando contraste por un factor de tres. Un degradado que
 *      obligue a remedir las dieciséis no es un fondo: es un rediseño escondido.
 *
 * Y NO SE MUEVE SOLO. Él dijo «más animado»; un fondo que se anima en bucle en una pantalla de
 * trabajo es un loop decorativo, y su base los prohíbe por nombre para un flujo de trabajo. El
 * movimiento que este bloque le dio al panel es el que responde a lo que uno hace. */
body {
  margin: 0;
  min-height: 100dvh;
  background:
    /* T-174 · la rejilla técnica: un punto de 1 px cada 24. Es textura, no información, y
       por eso va al 7 %: se nota como grano, no se lee como dibujo.
       T-209 · el patrón sale del PRESET y ya no está escrito aquí: es una de las tres cosas
       que distinguen a un cliente de otro, y escrito a mano las dieciséis pantallas de todos
       los clientes tienen el mismo grano. El valor de `sereno` es este mismo, punto por
       punto — extraerlo no cambió nada de lo que ya estaba medido. */
    var(--preset-patron),
    /* T-209 · LAS DOS ESQUINAS SALEN DE `derivar.js`, no de dos `color-mix` escritos aquí.
       El motivo no es higiene: con cristal, estas dos esquinas SON parte del fondo compuesto
       contra el que se mide todo el contraste, y un ingrediente del compuesto tiene que ser
       calculable desde fuera del CSS. El 18 % y el 14 % siguen siendo los mismos números y
       siguen razonados arriba; lo que cambia es que ahora viven en UN sitio. */
    radial-gradient(120% 80% at 8% 0%, var(--fondo-tinte-a) 0%, var(--fondo) 58%),
    radial-gradient(100% 70% at 100% 100%, var(--fondo-tinte-b) 0%, var(--fondo) 55%),
    var(--fondo);
  background-attachment: fixed;
  color: var(--texto);
  font-family: var(--tipo-cuerpo);
  font-size: var(--texto-base);
  line-height: var(--alto-linea-cuerpo);
  -webkit-font-smoothing: antialiased;
}

/* ── T-209 · EL FONDO SE PUEDE APAGAR ────────────────────────────────────────────────────
 *
 * La aceptación de T-209 lo pide con todas sus letras: «el patrón de fondo no baja el
 * contraste del texto que lleva encima **y se puede apagar**». Y no es una concesión: un
 * fondo con textura es exactamente lo que estorba a quien lee con dificultad, y la rejilla
 * de puntos a 24 px es un patrón repetitivo, que es lo que WCAG 1.4.12 y las guías de
 * sensibilidad visual mandan poder desactivar.
 *
 * Quién lo apaga, y son dos caminos que no se pisan:
 *   · QUIEN MIRA, con el botón. La elección vive en `localStorage` junto a la del modo —las
 *     dos son de quien mira, no del negocio— y `aplicar.js` la escribe en `data-fondo`.
 *   · SU SISTEMA, solo. `prefers-contrast: more` lo apaga sin que nadie pida nada: quien ha
 *     subido el contraste del sistema no tiene por qué venir a buscar un botón.
 *
 * Se apaga la TEXTURA, no el color: `--fondo` sigue siendo el de su marca. Apagar el fondo
 * no es volverse gris. */
:root[data-fondo='plano'] body {
  background: var(--fondo);
}

@media (prefers-contrast: more) {
  :root:not([data-fondo='textura']) body {
    background: var(--fondo);
  }
}

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--tipo-display);
  line-height: var(--alto-linea-titulo);
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h1 { font-size: var(--texto-4xl); font-weight: 700; line-height: var(--alto-linea-apretado); }
h2 { font-size: var(--texto-3xl); font-weight: 700; }
h3 { font-size: var(--texto-xl); font-weight: 600; }
h4 { font-size: var(--texto-lg); font-weight: 600; }

p { margin: 0; max-width: var(--ancho-lectura); }

/* Toda cifra que se compara con otra va en cifras tabulares, o las columnas bailan. */
.cifra,
td.numero,
th.numero {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
}

/* Un solo anillo de foco para todo. 2px del color del fondo + 2px del color de
   foco: así se ve tanto sobre la superficie como sobre el propio control
   (WCAG 2.4.7 y 2.4.11 — visible y no obscurecido).

   `[tabindex]:not([tabindex="-1"])` Y NO `[tabindex]` A SECAS, y esto se ve en las capturas:
   `panel.js` le manda el foco al `<h1 tabindex="-1">` de cada sección al acabar de cargar,
   para que un lector de pantalla anuncie en qué pantalla estás. Eso está bien y se queda: es
   lo que hace que cambiar de sección se OIGA. Lo que no está bien es que se DIBUJE el anillo:
   el navegador cuenta ese foco por código como «visible» y el resultado es un recuadro
   permanente alrededor del título, sin que nadie haya tocado una tecla. Sale en la captura de
   `cliente/inicio` del 2026-09-12 y parece un bug porque lo es.

   No es quitar accesibilidad, y la diferencia está en el propio atributo: `tabindex="-1"`
   significa exactamente «a esto no se llega con Tab». Un anillo en algo que nadie puede
   tabular no le sirve a nadie y confunde a todos; en lo que SÍ se tabula sigue puesto, y hay
   una prueba que lo exige (`pruebas/severidad.spec.js:510b`). */
:where(a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--fondo), 0 0 0 4px var(--foco);
  border-radius: var(--radio-md);
}

/* Y EL ANILLO DEL NAVEGADOR SE APAGA APARTE, que es la mitad que faltaba y que solo se vio en
   móvil. Sacar el `tabindex="-1"` de la regla de arriba dejó de pintarle NUESTRO anillo — y
   destapó el del navegador, que en el proyecto `movil` computa `outline: auto 3px` sobre el
   título. En escritorio no salía, así que la prueba 510 pasaba en tres tamaños y caía en uno:
   no basta con dejar de dibujar el anillo propio, hay que apagar el de serie.
   Va en `:where()` para que no pese nada en la cascada: cualquiera que algún día SÍ quiera un
   anillo en un `tabindex="-1"` —el contenedor de un diálogo, por ejemplo— lo pone y gana. */
:where([tabindex="-1"]):focus,
:where([tabindex="-1"]):focus-visible {
  outline: none;
}

::selection {
  background: var(--primario);
  color: var(--sobre-primario);
}

/* Quien pidió menos movimiento, recibe menos movimiento. No es opcional. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    /* Los RETRASOS también, y esto no es simetría por gusto: apagar solo la duración deja
       el `animation-delay` del escalón intacto. El octavo hijo de una lista se quedaría
       invisible 320 ms y aparecería de golpe — a quien pidió menos movimiento se le habría
       dado una lista que sale a trompicones, que es peor que la animación original. */
    animation-delay: 0ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Tokens internos de componente ────────────────────────────────────────
   No son de marca —no salen de `tenant_branding`— pero el contrato §5.3 pide que TODO
   token viva declarado aquí, y con razón: un `var(--algo)` que no está en este archivo es
   indistinguible de una errata. La reja 1 de check.sh lo comprueba.

   `initial` en una propiedad personalizada la deja en el valor "garantizado inválido", así
   que `var(--cuenta-color, <reserva>)` usa su reserva. Es la forma de DECLARAR un token que
   por defecto no vale nada y que alguien pone más tarde — aquí, el color de cada negocio en
   el selector de cuenta, que llega por la respuesta del servidor y se pone en línea. */
:root {
  --cuenta-color: initial;

  /* La altura de una barra de la gráfica de consumo, en porcentaje de su propio plano. La
     escribe el JS con `style.setProperty` en cada barra; aquí vive su valor por defecto y su
     declaración, que es lo que la reja 1 exige de TODO `var(--x)`: uno que no esté en este
     archivo es indistinguible de una errata. Cero por defecto — una barra sin dato no dibuja
     nada, que es distinto de dibujar cero. */
  --alto: 0%;

  /* La altura de UN SEGMENTO dentro de una barra apilada, en porcentaje del total de esa
     barra — no del plano. Hermana de `--alto` y por el mismo motivo: la escribe el JS del
     panel maestro en cada segmento y la reja 1 exige que TODO `var(--x)` esté declarado
     aquí. Cero por defecto: un segmento sin dato no dibuja nada.

     Se declaran las dos por separado a propósito. `--alto` mide la barra contra el tope de
     la gráfica y `--parte` mide el segmento contra su propia barra: reusar una para las dos
     cosas es lo que hace que un día alguien mida un porcentaje contra el techo equivocado. */
  --parte: 0%;

  /* Los dos colores DERIVADOS del carril. No son de marca —no salen de `tenant_branding`—
     sino de rebajar dos que sí lo son, y por eso viven aquí: una pantalla no calcula color,
     lo pide (§5.3, y es lo que la reja 1 comprueba).

     Se declaran con la derivación puesta, NO con el color ya resuelto, y la diferencia es
     todo el punto: `var(--fondo)` se sustituye por su valor COMPUTADO en el elemento donde
     se use, así que el velo y el halo siguen a la marca aunque el tema los repinte después.
     Escribirlos aquí como literal pasaría la reja —esta carpeta está exenta, es donde los
     colores viven— y rompería el repintado en silencio. Lo cazan las tres pruebas de
     `landing/pruebas/solicitud.spec.js` › «los colores derivados». */

  /* El velo de una barra que flota sobre el contenido: deja leer lo de abajo sin que compita
     con lo de encima. 88 % es lo que aguanta el texto de la barra sin perder contraste. */
  --fondo-velo: color-mix(in srgb, var(--fondo) 88%, transparent);

  /* El resplandor de marca detrás del héroe. Decorativo y detrás de todo: al 34 % tiñe sin
     teñir el texto. */
  --halo-primario: color-mix(in srgb, var(--primario) 34%, transparent);

  /* ── T-209 · EL CRISTAL, y está aquí y NO en la hoja de cada tarjeta ───────────────────
   *
   * «Se siente IA y plano» (Diego, 2026-09-19). `direccion-de-diseno.md` §5.3 prohibía el
   * cristal por escrito, y D-E7 anula la prohibición **sin anular su motivo**, que era y
   * sigue siendo cierto: un `GlassCard` copiado asume fondo oscuro con degradado y se rompe
   * con la marca clara de otro cliente. Por eso lo que entra es un cristal DERIVADO, y por
   * eso vive aquí: una pantalla no calcula material, lo pide.
   *
   * Y por eso hay UNA clase y no nueve. El inventario del bloque 8 (§1.1 del plan) contó
   * nueve clases definidas en los dos paneles de las que TRES ya habían divergido. Un
   * cristal escrito por tarjeta diverge a la tercera; éste no puede, porque solo hay uno.
   *
   * EL 72 %, decidido por Diego el 2026-09-19 con las tres opciones medidas delante. Es el
   * punto donde el compuesto se mueve poco respecto de `--superficie` —o sea donde el
   * contraste no obliga a empujar los derivados lejos del color de la marca del cliente—,
   * y sigue dejando ver el degradado. El número vive en `derivar.js` (`CRISTAL`), porque
   * `--cristal-opaco` se calcula con él: escrito dos veces, diverge.
   *
   * `saturate(140%)` y no solo `blur`: sin él, desenfocar lo de detrás lo apaga, y un
   * cristal apagado es un plástico. Es la mitad que hace que se lea como material. */
  --cristal: color-mix(in srgb, var(--superficie) 72%, transparent);
  --cristal-desenfoque: 20px;
  --cristal-saturacion: 140%;

  /* El canto del cristal: el borde teñido de marca que ya tenía la tarjeta, un paso más
     luminoso. No es un borde nuevo, es el mismo un escalón arriba. */
  --cristal-borde: color-mix(in oklab, var(--cristal-luz) 14%, var(--borde-luz));

  /* LA LUZ ESPECULAR DEL CANTO, y es blanca POR FÍSICA, NO POR MARCA: un reflejo no toma el
     color del objeto que lo refleja. Es el único literal de esta sección y por eso se dice:
     no es un color de marca que se me haya colado, es la luz. Va como token para que la
     mezcla de abajo tenga de dónde salir y para que la reja 1 lo vea declarado. */
  --cristal-luz: #FFFFFF;

  /* EL CANTO DEL CRISTAL · la luz del borde de arriba, y va en el BORDE y no en una sombra.
   *
   * Dos reglas empujaban en contra y las dos tenían razón:
   *
   *   · K-3 dice «borde 1 px **o** sombra, no las dos», y la tarjeta ya tiene borde. La
   *     primera versión puso dos capas de sombra exterior encima y la prueba 966 la tumbó —
   *     con razón: T-174 ya había decidido que aquí la profundidad va por el COLOR del borde.
   *   · Y la segunda versión, que dejó solo un `inset` en `box-shadow`, la borraba el GRADO:
   *     `comun.css` pone `box-shadow: var(--elevacion-calma)` —o sea `none`— con
   *     `:is(.tarjeta, …).grado--bien`, que gana en especificidad. Una tarjeta de Salud se
   *     quedaba sin canto y no lo cazaba nada (la 966 acepta `none`, la 1102 mira una tarjeta
   *     sin grado). Lo razonó paginas-2 desde la cascada y aquí está medido: prueba 1108.
   *
   * Subir la especificidad es una guerra que se pierde a la tercera. Lo que se hace es dejar
   * de compartir propiedad: **el canto es del MATERIAL y vive en el borde; la sombra dice a
   * qué ALTURA está la pieza y se queda en `box-shadow`**, que es del grado. Y encaja solo,
   * porque las reglas del grado tocan `border-inline-start-*` y nunca `border-top-color`.
   *
   * Es blanco POR FÍSICA, no por marca: un reflejo no toma el color del objeto que lo
   * refleja. Es el único literal de esta sección y por eso se dice. */
  --cristal-canto: color-mix(in oklab, var(--cristal-luz) 38%, var(--cristal-borde));

  /* Y EL CANTO AL PASAR POR ENCIMA, porque el cristal necesita su propio ESTADO y no lo
   * tenía. La lección la dieron cuatro choques seguidos y la dijo bien otra sesión: **cada
   * vez que el canto cambia de propiedad, hereda a quien ya escribiera esa propiedad.**
   * Estaba en `box-shadow` y chocó con la severidad (1108); se mudó a `border-top-color` y
   * chocó con el hover, que usa el ATAJO `border-color` —reescribe los cuatro lados— con
   * especificidad (0,2,1) contra la (0,1,0) de `.tarjeta`. Resultado: toda tarjeta de los DOS
   * paneles perdía el canto justo con el puntero encima, y la 1108 no lo veía porque mide en
   * reposo, que es el punto ciego que ella misma documenta.
   *
   * La salida no es parchear `border-top-color` en cada `:hover` que aparezca —eso es
   * parchear en el sitio N, y van a salir más según los cinco grupos adopten el material—:
   * es que el estado VIVA AQUÍ, junto al reposo, `--flotante` y `-no`. `comun.css` lo usa por
   * su nombre en vez de escribir un color, que es la regla de siempre.
   *
   * Más luz que en reposo: al acercarse, un material de verdad brilla más en el canto. */
  --cristal-canto-vivo: color-mix(in oklab, var(--cristal-luz) 58%, var(--borde-control));

  /* Y LOS OTROS TRES LADOS AL PASAR POR ENCIMA · el que faltaba, y por eso el canto se seguía
   * perdiendo aunque su estado ya viviera aquí.
   *
   * El estado del material declaraba el canto pero NO el borde, así que cada sitio que quería
   * que su pieza contestara al puntero tenía que escribir su propio `border-color` — y
   * `border-color` es el atajo de los CUATRO lados, o sea que al escribirlo se comía el canto
   * y había que reponerlo con una segunda línea. La regla se cumplía mal en cada sitio en vez
   * de bien en uno, y divergió tal como estaba escrito que divergiría: **tres valores** para
   * lo mismo en cinco sitios — `--borde-control` en `comun.css` (T-174) y `--texto-suave` en
   * las filas de Contactos, Conversaciones, Agentes y Conocimiento.
   *
   * Así que el material declara los cuatro lados, no uno, y quien use `.cristal` no escribe
   * NADA en su hover. El valor es el de T-174 —`--borde-control`— y no el de las filas: un
   * token de TEXTO usado como borde es de dónde salió la divergencia, no la solución.
   *
   * Va como token con nombre propio, en pareja con `--cristal-borde`, para que el día que
   * cambie cambie en un sitio. */
  --cristal-borde-vivo: var(--borde-control);

  /* Y LA SOMBRA DE LO QUE FLOTA · dos capas suaves, nunca una dura (E-8). Una corta y cerrada
     que dice que la pieza está despegada, y una larga y abierta que dice cuánto. Es para la
     barra que se queda pegada arriba, el menú que se abre y el diálogo: cosas sin borde, que
     es la mitad de K-3 que esto cumple. La estrena T-205 con el header. */
  --cristal-sombra-flotante: var(--sombra-md);

  /* ── T-209 · D-E10 · EL GLOW · acento de ESTADO, nunca decoración ──────────────────────
   *
   * Diego pidió tres cosas: glow, glassmorfismo y brutalismo. El cristal ya entró (D-E7);
   * esto es la segunda, y se escribe UNA vez aquí por el mismo motivo que el cristal: ocho
   * sesiones interpretando «glow» por su cuenta son ocho dialectos, y el panel se vería
   * peor, no mejor.
   *
   * SALE DE `--acento` Y NO DE `--primario`, y ahí está la diferencia: el primario dice de
   * quién es el panel, el acento dice qué está pasando. C-3 (60-30-10) avisa de que un panel
   * donde el acento aparece en todo deja de tener acento, y un glow es la forma más rápida
   * de inundarlo. Por eso la lista de dónde va es corta y cerrada, y está en
   * `direccion-de-diseno.md`, en la NOTA D-E10 de §5.3.
   *
   * DOS CAPAS, y no es gusto: E-8 pide dos capas suaves y nunca una dura, y la medida 2 de
   * `maestro/pruebas/relieve.spec.js` exige `deDosCapas === conSombra` sobre toda sombra
   * no-`inset` de una sección. Un glow de UNA capa rompe esa igualdad el día que caiga
   * dentro de un `[data-seccion]` — que es exactamente la mina que `.orbe` tiene puesta hoy
   * con su `0 0 12px` de una sola.
   *
   * PROHIBIDO SOBRE `.tarjeta`, y es lo más importante de la decisión: aquí la sombra es el
   * PORTADOR DE LA SEVERIDAD —la escalera crítico/aviso/bien se dibuja con sombra
   * descendente (md, sm, none) más el filo izquierdo (4, 3, 2 px)— y la vigilan 13 pruebas
   * de `severidad.spec.js`. Un glow encima de una tarjeta hace que su sombra deje de
   * significar «crítico», que es justo para lo que existe. Lo caza la 1120.
   *
   * SE APAGA SOLO, y por eso es UN token redefinido a `none` en vez de una regla por sitio:
   * quien escriba `box-shadow: var(--glow)` obedece los dos apagados sin enterarse.
   *
   * Y DE AHÍ SALE LA ÚNICA REGLA DE USO QUE HAY QUE RECORDAR: `var(--glow)` va SOLO en su
   * `box-shadow`, nunca en una lista. `box-shadow: algo, none` no es CSS válido, así que
   * encadenarlo con otra capa deja la propiedad entera muerta el día que el glow se apague.
   * Quien quiera glow Y otra sombra, pide un token que las traiga juntas. */
  --glow-canto: color-mix(in srgb, var(--acento) 70%, transparent);
  --glow-halo: color-mix(in srgb, var(--acento) 42%, transparent);
  --glow: 0 0 0 1px var(--glow-canto), 0 0 16px var(--glow-halo);
}

/* LOS DOS APAGADOS DEL GLOW.
 *
 * Con `prefers-reduced-motion` porque un resplandor de estado es SEÑAL, y quien pide menos
 * señal recibe menos. Y con el fondo plano porque un glow sobre un color liso no es luz: es
 * un halo pegado alrededor de una caja.
 *
 * NO toca el anillo de foco, y es deliberado: el anillo es indicador de accesibilidad, tiene
 * que cumplir 3:1 siempre y no puede depender de una preferencia de movimiento ni de una de
 * fondo. Sigue siendo el de `:focus-visible`, sólido y medido. El glow acompaña al foco donde
 * se decida, nunca lo sustituye. */
@media (prefers-reduced-motion: reduce) {
  :root { --glow: none; }
}

:root[data-fondo='plano'] { --glow: none; }

/* ══ T-209 · LOS PRESETS DE MARCA ════════════════════════════════════════════════════════
 *
 * Un preset NO es el color de acento. El color ya lo trae `tenant_branding` y ya se deriva.
 * Lo que hace que dos clientes con marcas distintas se vean **distintos de verdad** es la
 * FORMA: cuánto se redondea una esquina, qué letra lleva un título, y qué grano tiene el
 * papel. Sin eso, siete marcas son la misma pantalla teñida de siete colores.
 *
 * TRES DIMENSIONES, y las tres ya existían como token. Un preset no inventa nada: reparte.
 *   · el radio — de canto vivo a esquina blanda
 *   · la letra de los títulos — de las TRES que ya se cargan, nunca una cuarta
 *   · el patrón del papel — puntos, líneas o liso
 *
 * LA DENSIDAD NO ESTÁ AQUÍ, Y ES DELIBERADO. `[data-densidad='dashboard']` ya existe y es de
 * E-3: distingue el panel del maestro (8-32 px) del panel del cliente (16-64). Si un preset
 * la moviera, la densidad del MAESTRO dependería de la marca de un CLIENTE, que es al revés
 * de como está construido el producto. Un preset es del negocio; la densidad es del oficio.
 *
 * LOS CUATRO NOMBRES describen la forma, no el rubro: un preset llamado «farmacia» sería una
 * promesa que el sistema no puede cumplir con el octavo cliente.
 *
 * El reparto automático lo hace `presets.js`, y **no se guarda en ningún sitio** porque no
 * hay dónde: `tenant_branding` tiene NUEVE columnas y un décimo token es una migración
 * (§4.5), y `/sesion` no trae `tenant_id` a propósito. Está anotado en FALTANTES.md. */
:root {
  /* `sereno` es el de hoy, valor por valor: extraerlo no movió un píxel de lo ya medido. */
  --preset-patron: radial-gradient(circle, var(--rejilla-punto) 1px, transparent 1.5px) 0 0 / 24px 24px;
}

/* NÍTIDO · la mitad estructural del brutalismo (D-E10): canto vivo, rejilla que SE VE y
   título contundente. Es el que cura «se siente plano» sin tocar un color. */
:root[data-preset='nitido'] {
  --radio-sm: 0px;
  --radio-md: 2px;
  --radio-lg: 4px;
  --radio-xl: 6px;
  --tipo-display: 'Bebas Neue', var(--tipo-cuerpo);
  /* Rejilla de líneas en vez de puntos: la misma tinta al 7 %, otro dibujo. */
  --preset-patron:
    repeating-linear-gradient(to right, var(--rejilla-punto) 0 1px, transparent 1px 32px),
    repeating-linear-gradient(to bottom, var(--rejilla-punto) 0 1px, transparent 1px 32px);
}

/* CÁLIDO · lo contrario: esquina blanda, papel liso y la letra del cuerpo también en los
   títulos. Para quien atiende personas y no obras. */
:root[data-preset='calido'] {
  --radio-sm: 8px;
  --radio-md: 14px;
  --radio-lg: 22px;
  --radio-xl: 28px;
  --tipo-display: var(--tipo-cuerpo);
  --preset-patron: none;
}

/* TÉCNICO · el punto medio con más rejilla: radios de hoy, grano más apretado y el display
   de siempre. Es el que menos se aleja, y existe para que «distinto» no signifique siempre
   «llamativo». */
:root[data-preset='tecnico'] {
  --preset-patron: radial-gradient(circle, var(--rejilla-punto) 1px, transparent 1.5px) 0 0 / 14px 14px;
}

/* Y CON EL FONDO PLANO NO HAY PATRÓN, venga del preset que venga. Lo dice la regla de
   arriba (`:root[data-fondo='plano'] body`), y se repite aquí como token para que quien lea
   `--preset-patron` de la raíz —una prueba, por ejemplo— vea lo mismo que pinta la pantalla
   en vez de un patrón que nadie está viendo. */
:root[data-fondo='plano'] { --preset-patron: none; }

/* ── Componente 1 · Botón ────────────────────────────────────────────────── */

.boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--espacio-2);
  height: var(--control-md);
  padding: 0 var(--espacio-4);
  border: 1px solid transparent;
  border-radius: var(--radio-md);
  background: var(--primario);
  color: var(--sobre-primario);
  font-family: inherit;
  font-size: var(--texto-sm);
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  /* Solo transform y opacity: filter y color no provocan reflow pero sí repaint;
     se aceptan porque son de una sola propiedad y muy baratos. */
  transition: filter var(--duracion-rapida) var(--curva-entrada),
              transform var(--duracion-rapida) var(--curva-entrada),
              /* T-209 · y el glow, que si no aparece de golpe. `box-shadow` no provoca
                 relayout (M-3, y ya lo dice `comun.css`), así que entra en la lista corta.
                 200 ms, que es lo que su base pide para sombra. */
              box-shadow var(--duracion-normal) var(--curva-entrada);
}

/* T-209 · D-E10 · LA CTA PRIMARIA ENCIENDE AL PASAR POR ENCIMA. Es uno de los cinco sitios
   donde el glow entra, y el único de los cinco que existe hoy además del orbe. */
.boton:hover { filter: brightness(0.92); box-shadow: var(--glow); }

/* Y LAS VARIANTES NO, porque no son la CTA primaria. La destructiva es la que más importa de
   las tres: teñir de acento el botón que borra algo es decir «adelante» con el color con el
   que el panel dice «esto está pasando». Su peso ya lo lleva su propio color. */
.boton--secundario:hover,
.boton--fantasma:hover,
.boton--destructivo:hover { box-shadow: none; }
/* El hundido confirma el toque en menos de 100 ms, que es lo que el dedo espera. */
.boton:active { transform: scale(0.97); }

.boton--secundario {
  background: transparent;
  border-color: var(--borde-control);
  color: var(--texto);
}

.boton--fantasma {
  background: transparent;
  color: var(--primario-texto);
}

/* El destructivo va en CONTORNO, no en relleno, y es deliberado.
   Para una marca como IQUORA —cuyo primario es un rojo vino— un destructivo relleno de
   rojo se lee igual que el botón principal, y aquí el destructivo es "pausa a tu agente":
   la acción que nadie debe apretar por accidente. Con uno lleno y otro en contorno no se
   confunden ni siendo del mismo tono, ni en escala de grises, ni para quien no distingue
   el rojo del verde. */
.boton--destructivo {
  background: transparent;
  border-color: var(--error-texto);
  color: var(--error-texto);
}

.boton--destructivo:hover {
  filter: none;
  background: var(--error);
  border-color: var(--error);
  color: var(--sobre-error);
}

.boton--sm { height: var(--control-sm); padding: 0 var(--espacio-3); font-size: var(--texto-xs); }

/* 44 px en móvil, igual que `.navegacion__enlace` — que llevaba esta regla desde el bloque 2
   y era el ÚNICO que la tenía. La pasada visual lo midió a 320 px: «Salir» 52×32, «Reactivar»
   82×32 y «Ver dudas» 85×32, contra los 44 que piden el checklist §7 y WCAG 2.5.5 (V-11).

   Va sobre `.boton` y no sobre `.boton--sm`: la primera versión solo subía el pequeño y dejó
   la CTA primaria del inicio en 40 px (`--control-md`) — lo cazó la prueba 41, no la vista.
   Y va con `min-height` y no con `height`: `min-height` gana sobre un `height` explícito, así
   que sube el de 32 y el de 40 sin encoger el de 48. Los tamaños siguen siendo tres en
   escritorio, que es donde la diferencia significa algo. */
@media (max-width: 767px) {
  .boton { min-height: var(--objetivo-tactil); }
}
.boton--lg { height: var(--control-lg); padding: 0 var(--espacio-6); font-size: var(--texto-base); }

/* Orden de precedencia: disabled > cargando > active > focus > hover > normal. */
.boton[aria-busy='true'] { opacity: 0.7; pointer-events: none; }
.boton:disabled,
.boton[aria-disabled='true'] {
  opacity: 0.5;
  cursor: not-allowed;
  filter: none;
  transform: none;
}

/* En un dedo, el objetivo tactil entero. D-F25: el numero vive en `--objetivo-tactil`, no
   repetido aqui — repetirlo es como se llego a que A-3 se incumpliera dos veces. */
@media (pointer: coarse) {
  .boton { min-height: var(--objetivo-tactil); }
}

/* ── Componente 2 · Campo ────────────────────────────────────────────────── */

.campo {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-2);
}

/* La etiqueta SIEMPRE visible. Un placeholder que hace de etiqueta desaparece
   justo cuando el usuario más lo necesita: mientras escribe. */
.campo__etiqueta {
  font-size: var(--texto-sm);
  font-weight: 500;
  color: var(--texto);
}

.campo__control {
  height: var(--control-md);
  padding: 0 var(--espacio-3);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-md);
  background: var(--superficie);
  color: var(--texto);
  font-family: inherit;
  /* 16px o iOS hace zoom al enfocar y descuadra la pantalla. */
  font-size: var(--texto-base);
  transition: border-color var(--duracion-rapida) var(--curva-entrada);
}

.campo__control::placeholder { color: var(--texto-suave); }
.campo__control:hover { border-color: var(--texto-suave); }

.campo__ayuda {
  font-size: var(--texto-sm);
  color: var(--texto-suave);
}

.campo__error {
  display: flex;
  align-items: center;
  gap: var(--espacio-2);
  font-size: var(--texto-sm);
  font-weight: 500;
  /* El token RESUELTO y no el crudo: `--error` es el color de relleno y como TEXTO se queda
     por debajo de 4.5 sobre una superficie oscura. Salió al pasar el aviso al Violeta Tech en
     el bloque 8 —que obligó a revisar los tres estados uno por uno— y estaba aquí desde antes.
     Lo mismo el filo del campo, que comunica estado y cae bajo 1.4.11. */
  color: var(--error-texto);
}

.campo[data-estado='error'] .campo__control { border-color: var(--error-texto); border-width: 2px; }

.campo__control:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: var(--superficie-alta);
}

/* ── Componente 3 · Tarjeta ──────────────────────────────────────────────── */

/* EL MATERIAL. Una clase, usada por todo lo que quiera ser cristal, y NADIE la reescribe:
 * la prueba 1102 cae si aparece un `backdrop-filter` fuera de `paneles/tokens/`.
 *
 * El recurso va PRIMERO y el cristal después, dentro de `@supports`. Así un navegador que no
 * sabe de `backdrop-filter` —o un teléfono al que le cueste, si la medida de frames lo dice—
 * pinta `--cristal-opaco`, que es el compuesto ya aplanado en su PEOR esquina. Peor y no
 * medio a propósito: es el color contra el que `derivar.js` resolvió el texto, así que lo que
 * se ve y lo que se midió dicen lo mismo. */
/* @tarea T-215 · LAS TRES PIEZAS COMPARTIDAS DEL MAESTRO ADOPTAN EL MATERIAL.
 *
 * Van UNIDAS al selector de `.cristal` y no en una regla propia, a propósito: `.tarjeta` de
 * aquí abajo se escribió como bloque aparte y eso ya dejó DOS copias del `backdrop-filter`
 * dentro de este archivo. Una tercera copia es como empezaron las nueve clases divergidas del
 * inventario del bloque 8. Un material, un sitio, tres selectores.
 *
 * VAN SCOPEADAS A `.maestro`, y las tres aunque solo una lo necesite:
 *   · `.filtros` lo NECESITA — `cliente/conversaciones.css:37` declara otra `.filtros` sin
 *     fondo propio, y sin el escalón esta regla le pintaría cristal a la barra de filtros de
 *     Conversaciones, que es de `paginas-1` y no de T-215.
 *   · `.ficha` no lo necesitaría hoy: las nueve del panel del cliente son `class="tarjeta
 *     ficha"` y ya son cristal por `.tarjeta`. Pero eso es una coincidencia del marcado de hoy,
 *     y el día que alguien escriba una `.ficha` sin `.tarjeta` se le pinta cristal sin haberlo
 *     pedido. Se scopea para que la regla diga lo que quiere decir.
 *   · `.tabla-caja` solo existe en el maestro (cero usos en el marcado del cliente). El escalón
 *     no cambia nada y documenta de quién es la pieza.
 *
 * LO QUE HAY QUE SABER AL LEER ESTO: estas tres piezas las ven las OCHO pantallas del maestro,
 * y CUATRO de ellas son de `maestro-2` (T-216) — Economía, Uso, Salud y Catálogo heredan este
 * material sin haberlo pedido. Está escrito así a petición de Diego, para que el material no
 * diverja entre las dos mitades del mismo panel. Detalle en `estado/progress/T-215.md` §8. */
.cristal,
.maestro :is(.filtros, .ficha, .tabla-caja, .dialogo) {
  background: var(--cristal-opaco);
  border: 1px solid var(--cristal-borde);
  border-top-color: var(--cristal-canto);
}

/* EL ESTADO DEL MATERIAL, ENTERO · los cuatro lados, no uno.
 *
 * La primera versión declaraba solo el canto, y eso dejaba la mitad del trabajo fuera: quien
 * quisiera que su pieza contestara al puntero seguía teniendo que escribir su propio
 * `border-color`, que es el atajo de los cuatro lados, y reponer el canto detrás con una
 * segunda línea. Eso es el sitio N, y ya iban cinco con tres valores distintos.
 *
 * Con los cuatro lados aquí, quien use `.cristal` NO ESCRIBE NADA en su hover. Las cuatro
 * filas que hoy lo parchean a mano (Contactos, Conversaciones, Agentes, Conocimiento) borran
 * sus dos líneas y heredan éstas.
 *
 * `border-color` no pisa el filo del grado: `:is(.tarjeta, .alerta, …).grado--*` de
 * `comun.css` toca `border-inline-start-color` con la misma especificidad (0,2,0) y va
 * DESPUÉS en la cascada, así que gana él. Y gana más que antes: el atajo de `a.tarjeta:hover`
 * en `comun.css` iba a (0,2,1) y sí se comía el filo del grado con el puntero encima. Al
 * bajar el estado aquí, el semáforo sobrevive al hover — la 1108 medía eso en reposo.
 *
 * Lo que esta regla NO puede hacer es ganarle a un `border-color` escrito más tarde con la
 * misma especificidad: `tokens.css` carga primero. Subir la especificidad es la guerra que se
 * pierde a la tercera y está descartada arriba. La red es la 1116, que recorre TODO lo que
 * lleve `.cristal` y lo caza en navegador. */
.cristal:hover,
.tarjeta:hover {
  border-color: var(--cristal-borde-vivo);
  border-top-color: var(--cristal-canto-vivo);
}

/* LO QUE FLOTA: sin borde y con las dos capas. Es la otra mitad de K-3, y existe desde ya
   para que T-205 no tenga que inventarse un cristal propio para el header. */
.cristal--flotante {
  border: none;
  box-shadow: var(--cristal-sombra-flotante);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .cristal,
  .maestro :is(.filtros, .ficha, .tabla-caja, .dialogo) {
    background: var(--cristal);
    -webkit-backdrop-filter: blur(var(--cristal-desenfoque)) saturate(var(--cristal-saturacion));
    backdrop-filter: blur(var(--cristal-desenfoque)) saturate(var(--cristal-saturacion));
  }
}

/* Y CON EL FONDO APAGADO, EL CRISTAL TAMBIÉN SE APAGA. Es la consecuencia que casi se me
 * escapa: sin degradado detrás no hay nada que dejar pasar, así que el desenfoque solo
 * cuesta GPU y el material se lee como una tarjeta translúcida sobre un color plano — que es
 * peor que una tarjeta opaca. Quien apaga la textura recibe la superficie limpia.
 *
 * EL `:where()` NO ES ADORNO: baja esta regla a (0,1,0), la misma que `.cristal` y `.tarjeta`,
 * para que en toda esta sección **mande el ORDEN y no la especificidad**. Sin él esta regla
 * va a (0,3,0) y le gana a `.cristal-no` se ponga donde se ponga, que es la mitad del defecto
 * que se arregla abajo. Sigue ganándole a la base porque va después, que es lo que se quiere.
 *
 * FUSIÓN 2026-09-20 · **D-F18**, decisión confirmada por el Líder tras proponerla el
 * integrador. Aquí chocaron T-209 y T-215 y quedarse con un lado deshacía el trabajo del otro:
 * el de T-215 añade las cuatro piezas del maestro pero escribe el selector SIN `:where()`, o sea
 * devuelve la regla a (0,3,0) y con ello el defecto que D-E10 acababa de cerrar; el de T-209
 * conserva el `:where()` pero deja al maestro fuera del fondo plano. Se combinan: el `:where()`
 * de T-209 sobre los DOS selectores de T-215. La asimetría que queda —(0,1,0) el primero,
 * (0,2,0) el segundo— es la MISMA que ya tiene la regla base de este archivo
 * (`.cristal, .maestro :is(…)`), así que no introduce nada nuevo. */
:where(:root[data-fondo='plano']) .cristal,
:where(:root[data-fondo='plano']) .maestro :is(.filtros, .ficha, .tabla-caja, .dialogo) {
  background: var(--superficie);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.tarjeta {
  padding: var(--espacio-6);
  /* T-174 · `--borde-luz` y no `--borde`: el 22 % de primario es lo que hace que la tarjeta
     sea de ESTA marca y no una caja gris. El hover sigue subiendo a `--borde-control`. */
  border: 1px solid var(--borde-luz);
  border-radius: var(--radio-lg);
  background: var(--superficie);
}

/* LA TARJETA ES DE CRISTAL. Va como regla aparte y no fundida arriba para que se lea lo que
 * de verdad pasó: la tarjeta no se reescribió, adoptó un material que ya existía. Lo mismo
 * hará el resto en T-205 y en los grupos de páginas. */
.tarjeta {
  background: var(--cristal-opaco);
  border-color: var(--cristal-borde);
  border-top-color: var(--cristal-canto);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .tarjeta {
    background: var(--cristal);
    -webkit-backdrop-filter: blur(var(--cristal-desenfoque)) saturate(var(--cristal-saturacion));
    backdrop-filter: blur(var(--cristal-desenfoque)) saturate(var(--cristal-saturacion));
  }
}

/* `:where()` por lo mismo que arriba: que en el material mande el orden. */
:where(:root[data-fondo='plano']) .tarjeta {
  background: var(--superficie);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── CÓMO SE SALE DEL CRISTAL · Y VA AL FINAL, QUE ERA EL DEFECTO ─────────────────────────
 *
 * Una `.tarjeta` que quiere pintarse hueca —`.disponible`, el módulo que todavía no se
 * contrata— pone `background: none` y con eso borra el fondo del cristal… pero NO el
 * `backdrop-filter`, que es otra propiedad. Quedaba un recuadro punteado que desenfoca lo de
 * detrás: MÁS material justo donde la clase quiere decir menos.
 *
 * ESO SE ARREGLÓ MAL LA PRIMERA VEZ. El bloque existía, pero ARRIBA, antes de las reglas que
 * pintan el material. `.cristal-no` es (0,1,0) y `.tarjeta` también: a igual especificidad
 * gana la última, así que `.disponible` se quedaba con `var(--cristal)` y el desenfoque
 * ENTEROS. El apagador estaba escrito y no apagaba nada.
 *
 * Y LA 1109 NO LO CAZABA AUNQUE NACIÓ PARA ESTO, que es la lección que hay que llevarse: esa
 * prueba busca «desenfoque SIN fondo», y aquí sobrevivían los dos. El defecto estaba girado
 * respecto de la red, y una red con la forma del defecto que ya conoces no ve el siguiente.
 * Lo midió `paginas-2` mirando la pantalla; yo lo había razonado leyendo el CSS y me salió
 * bien la regla y mal el sitio. La red nueva es la 1150, suya.
 *
 * Va al final de la sección a propósito, y las dos reglas de fondo plano de arriba llevan
 * `:where()` para que ninguna le gane por especificidad. En este bloque manda el orden:
 * quien quiera salirse del material lo dice el último y se sale. */
.cristal-no {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── D-F21 · Y EL MISMO APAGADOR PARA EL MAESTRO, QUE NO LE LLEGABA ──────────────────────
 *
 * **Es la TERCERA vez que aparece esta familia de defecto, y por eso conviene nombrarla:**
 * *un apagador escrito que no apaga porque pierde la cascada.* Van:
 *   1. el canto en `box-shadow`, que borraba el grado (1108);
 *   2. `.cristal-no` declarado ARRIBA de las reglas que pintan el material, que dejaba a
 *      `.disponible` con el cristal entero — mismo peso, gana la última;
 *   3. y ésta, que es la misma girada un cuarto de vuelta: `.cristal-no` es **(0,1,0)** y el
 *      maestro adopta el material con `.maestro :is(.filtros, .ficha, .tabla-caja, .dialogo)`,
 *      que es **(0,2,0)**. En el maestro el apagador no apagaba nada. Lo midió `maestro-1`.
 *
 * La salida es igualar el peso en el mismo ámbito, no subirlo en general: `.maestro`
 * `.cristal-no` es (0,2,0), empata con la regla que pinta, y va al final de la sección para
 * ganar por orden — que es la regla que este bloque entero sigue.
 *
 * ── Y AHORA LO QUE NO SE PUEDE «LIMPIAR», que es lo que hay que dejar escrito ────────────
 *
 * Es tentador mirar `.cristal` (0,1,0) junto a `.maestro :is(…)` (0,2,0) y pensar que la
 * asimetría sobra, sobre todo porque tres líneas más arriba este archivo dice que aquí manda
 * el orden. **No sobra: es necesaria, y `maestro-1` la midió.**
 *
 * `.tabla-caja` está declarada **noventa líneas más abajo** en este mismo archivo, con
 * `background: var(--superficie)` y especificidad **(0,1,0)**. Si el selector del maestro se
 * bajara a (0,1,0) —con `:where()`, quitando el `.maestro`, o «unificando»—, `.tabla-caja`
 * **ganaría por orden** y las tablas de Clientes y Auditoría perderían el cristal. Las dos.
 *
 * O sea que los dos criterios conviven y no se contradicen: **dentro de la sección manda el
 * orden; contra lo que se declara FUERA de la sección y después, hace falta el escalón.** El
 * `:where()` de las reglas de fondo plano baja lo que compite DENTRO; el `.maestro` sube lo
 * que tiene que sobrevivir a lo de FUERA. Quien toque una de las dos cosas, que mida las dos
 * tablas del maestro y `.disponible` de Agentes antes de commitear.
 * Lo fija la 1131. */
.maestro .cristal-no {
  background: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.tarjeta__titulo {
  margin-bottom: var(--espacio-2);
  font-size: var(--texto-lg);
  font-weight: 600;
  /* T-259 · UNA PALABRA LARGA SE PARTE CON GUION, no a lo bruto y no por fuera de la caja.
     Censadas las nueve pantallas a 320: «Procesamiento» pide 130 px en una caja de 110 y
     «Conversaciones» pide 140 en 110 — o sea que NO es un caso de Consumo, es de la clase, y
     ya se desbordaba en Inicio antes de tocar nada.

     Y NO SE PUEDE ACORTAR EL ROTULO, que era la salida que parecia: lo manda el SERVIDOR en
     `hoy.recursos[].etiqueta` y `consumo.js:73` lo dice con todas las letras. Acortarlo aqui
     seria el panel reescribiendo lo que dice el servidor. Ademas la regla 5 del CRITERIO pide
     que el rotulo siga diciendo QUE MIDE la cifra, y «Procesamiento» es lo que lo dice.

     `hyphens: auto` con `lang="es"` puesto en el `<html>`: parte por sus silabas y deja el
     guion, que es como se lee una palabra partida. `overflow-wrap` queda de red por si el
     navegador no sabe silabear esa palabra — partir feo es peor que partir bien, pero salirse
     de la caja es peor que las dos. */
  hyphens: auto;
  overflow-wrap: break-word;
  /* T-261 · Y QUE PUEDA ENCOGER. Medido a 320 en la tarjeta de «A este ritmo»: la tarjeta es
     `grid` con 12 px de padding —pista de 263 px— y este titulo es a su vez un `flex` que
     media 275 y se salia 12 px, arrastrando la tarjeta a `scrollWidth` 299 contra 286.
     La causa es la de siempre: un item de grid o flex nace con `min-width: auto`, o sea que se
     niega a encoger por debajo de su contenido. Sin esto, `hyphens` de arriba no llega a
     actuar porque la caja crece antes de que haga falta partir. */
  min-width: 0;
}

/* LA CABECERA DE UNA TARJETA: el título, su píldora y su frase, compartiendo línea base.
 *
 * Existe porque la línea base tiene que estar AISLADA. `maestro/panel.css` ponía
 * `align-items: baseline` sobre la tarjeta ENTERA, y como la tarjeta envuelve, esa alineación
 * no afectaba solo a la fila del título: regalaba 10 px por encima del primer hijo y 24 entre
 * la primera línea y la segunda. En Salud, con cinco tarjetas, son ~170 px de nada en la
 * pantalla que se abre con prisa porque un cliente llamó.
 *
 * SE PROBARON CINCO VARIANTES antes de llegar aquí, y están medidas con sus números en
 * `feature_list.json` → T-167: `align-content: start` no hace nada; `row-gap: 0` baja 12 de 36
 * y deja 24; `align-items: center` arregla los 10 de arriba pero PIERDE la línea base y no toca
 * los 24 de en medio. Los 24 son inherentes a alinear por línea base a través de líneas
 * envueltas: no hay propiedad que los quite. El arreglo no es una propiedad — es que la fila
 * con línea base sea una FILA de verdad, y que la tarjeta vuelva a ser un bloque. */
.tarjeta__cabecera {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--espacio-3);
}

/* LA MISMA TARJETA, APRETADA. No es una tarjeta distinta y conviene dejarlo escrito, porque
 * durante dos bloques pareció que sí: es la densidad que `base-ux/reglas.md` E-3 le asigna al
 * panel maestro —«dashboard denso 8-32 px», contra «estándar 16-64» del panel del cliente—.
 *
 * Estaba aplicada desde el bloque 3 y sin declarar en ningún sitio, así que el muestrario
 * enseñaba la espaciosa mientras el producto usaba otra. Eso es lo que el caso 303 llamó «la
 * tarjeta del producto es DOS tarjetas», y la conclusión que sacó —que el maestro se había
 * desviado por descuido— era la equivocada: el maestro apretó porque le toca apretar. Lo que
 * faltaba no era corregir el valor, era ponerle nombre. */
.tarjeta--densa {
  padding: var(--espacio-3) var(--espacio-4);
  border-radius: var(--radio-md);
}

.tarjeta p { color: var(--texto-suave); }

.tarjeta__pie {
  display: flex;
  gap: var(--espacio-2);
  margin-top: var(--espacio-4);
}

/* La tarjeta de una cifra: el patrón de "una pregunta central" del tablero.
   T-174 · lleva un brillo del primario en la esquina superior derecha. Es un `background-image`
   sobre el mismo `--superficie`, no una sombra: K-3 dice borde O sombra, y el borde se queda. */
.tarjeta--cifra {
  background-image: radial-gradient(160px 100px at 100% 0%, var(--brillo-acento), transparent 70%);
}

.tarjeta--cifra .cifra {
  display: block;
  font-family: var(--tipo-display);
  font-size: var(--texto-3xl);
  font-weight: 700;
  line-height: var(--alto-linea-apretado);
  color: var(--texto);
}

.tarjeta--cifra .tarjeta__titulo {
  font-size: var(--texto-sm);
  font-weight: 500;
  color: var(--texto-suave);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* ── Componente 4 · Tabla ────────────────────────────────────────────────── */

/* Envoltura obligatoria: una tabla ancha se desplaza dentro de su caja, nunca
   empuja el ancho de la página. */
.tabla-caja {
  overflow-x: auto;
  border: 1px solid var(--borde-luz);
  border-radius: var(--radio-lg);
  background: var(--superficie);
}

.tabla {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--texto-sm);
}

.tabla th,
.tabla td {
  padding: var(--espacio-3) var(--espacio-4);
  text-align: left;
  border-bottom: 1px solid var(--borde);
}

.tabla thead th {
  position: sticky;
  top: 0;
  background: var(--superficie-alta);
  color: var(--texto-suave);
  font-size: var(--texto-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

/* `th, td` y no `td` a secas: una tabla con `<th scope="row">` en su primera columna —que es
   lo correcto para un lector de pantalla— se quedaba con media raya bajo esa columna, cortada
   a media tabla y justo encima de la esquina redondeada. Parecía una tabla mal cerrada, y solo
   se veía MIRANDO: ninguna cifra lo decía. Lo encontró la pasada visual del bloque 7 de T-009,
   que lo parcheó acotado a una pantalla porque esta hoja la descargan los cuatro paneles; el
   bloque 8 lo trae aquí, que es su sitio, y borra el parche. Lo fija la 310. */
.tabla tbody tr:last-child th,
.tabla tbody tr:last-child td { border-bottom: none; }

/* Los números se alinean a la derecha para que las unidades queden en columna. */
.tabla .numero { text-align: right; }

/* ── Componente 5 · Navegación ───────────────────────────────────────────── */

.navegacion {
  display: flex;
  gap: var(--espacio-1);
  padding: var(--espacio-2);
  border-radius: var(--radio-lg);
  background: var(--superficie);
}

.navegacion__enlace {
  display: flex;
  align-items: center;
  gap: var(--espacio-2);
  padding: var(--espacio-2) var(--espacio-3);
  border-radius: var(--radio-md);
  min-height: var(--objetivo-tactil);
  color: var(--texto-suave);
  /* `--texto-base` y no `--texto-sm`, y la razón está medida: la navegación era el ÚNICO texto
     del panel POR DEBAJO del cuerpo —14 px contra 16—, y es con lo que se llega a las dieciséis
     pantallas. «Del lado izquierdo, las subpáginas están muy, muy pequeñas. Se ve muy pequeño
     en comparación con la información que viene» (Diego, 2026-09-11). No es una sensación: es
     una comparación, y estaba invertida. No se inventa un tamaño — se deja de castigar al único
     elemento que estaba por debajo. Lo fija la 313, que compara y no escribe el número.

     Y los 44 px dejan de ser solo de móvil. La regla existía desde la pasada visual de T-008
     acotada a ≤1023, así que en escritorio el objetivo medía 38. El motivo que la justificaba
     —es el control que más se usa— no depende del ancho de la ventana. */
  font-size: var(--texto-base);
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background var(--duracion-rapida) var(--curva-entrada),
              color var(--duracion-rapida) var(--curva-entrada);
}

.navegacion__enlace:hover { background: var(--superficie-alta); color: var(--texto); }


/* La sección activa no se marca solo con color: lleva peso y fondo propios.
   Nada en esta interfaz se comunica únicamente por color (WCAG 1.4.1). */
.navegacion__enlace[aria-current='page'] {
  background: var(--superficie-alta);
  color: var(--primario-texto);
  font-weight: 600;
}

/* ── T-270 · EL COLOR QUE AGRUPA EN EL RIEL ─────────────────────────────────────────────
 *
 * T-284: «el color AGRUPA SIN DOMINAR — colorimetrías que no pierdan contexto». Y la fuente de
 * Diego del 2026-09-25 es una restricción, no un permiso: *«aquí lo importante son los textos y
 * creo que el color no funciona del todo»*. Así que el grupo se lee por una MARCA de 3 px y el
 * color **no entra en el fondo de la entrada**: el fondo sigue diciendo hover y activo, que es
 * lo que ya decía. Cuatro colores de fondo en el riel serían el riel dominando la pantalla.
 *
 * El matiz lo pone `derivar.js` (`--grupo-*`), rotado +0/+40/+80/+120 desde `color_primario`.
 * Aquí solo se reparte qué clave es de qué grupo.
 *
 * ESTA TABLA ES PROVISIONAL Y SE BORRA. El árbol de tres niveles es **T-274** y trae su propia
 * tabla `GRUPOS`; cuando llegue, el contenedor llevará `data-grupo` y estas nueve líneas se van.
 * Está aquí y no en `cliente/panel.css` a propósito: el riel lo están tocando otros dos frentes
 * (tamaños y jerarquía) y dos sesiones no comparten archivo.
 *
 * POR QUÉ `::before` Y NO LAS DOS SALIDAS OBVIAS, las dos descartadas con su motivo:
 *   · **`box-shadow: inset` NO.** `paneles/pruebas/medidas.js` mide el anillo de foco leyendo los
 *     píxeles del `box-shadow` y exige que SIN foco no haya anillo (< 2 px). Una marca de 3 px
 *     por `box-shadow` la contaría como anillo permanente y pondría en rojo cinco archivos de
 *     pruebas que no tienen nada que ver con el color.
 *   · **`background-image` NO.** `:hover` y `[aria-current]` declaran `background` en forma
 *     CORTA, que resetea `background-image`: la marca desaparecería al pasar el puntero y en la
 *     entrada activa. Es la pareja rota que vigila la 1505 — me la habría fabricado yo mismo.
 * `::before` absoluto no dispara layout, así que no mueve ni un píxel de lo que el frente de
 * tamaños está midiendo. */
[data-clave='inicio'] { --grupo-color: var(--grupo-inicio); }

[data-clave='agentes'],
[data-clave='conversaciones'],
[data-clave='conocimiento'],
[data-clave='dudas'] { --grupo-color: var(--grupo-agentes); }

[data-clave='contactos'] { --grupo-color: var(--grupo-gente); }

[data-clave='negocio'],
[data-clave='integraciones'],
[data-clave='consumo'] { --grupo-color: var(--grupo-negocio); }

.navegacion__enlace { position: relative; }

.navegacion__enlace::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  /* No de borde a borde: una marca que llega a las esquinas se lee como un borde del contenedor
     y no como la señal de una entrada. */
  inset-block: 22%;
  width: 3px;
  border-radius: var(--radio-full);
  /* Sin grupo NO se pinta nada, y por eso el valor de reserva es transparente y no un color: el
     botón «Más» y cualquier clave que el panel no agrupe se quedan sin marca, que es lo correcto
     — una marca sin grupo mentiría sobre la agrupación. */
  background: var(--grupo-color, transparent);
}

.navegacion__insignia {
  /* T-174 · Late TRES veces al aparecer o al cambiar de número (T-172 la reemplaza en vivo) y
     PARA. Finita a propósito: 3 × 1.4 s = 4.2 s, por debajo de los 5 s de WCAG 2.2.2, y M-10
     prohíbe el bucle. Una señal avisa y se calla. Con `prefers-reduced-motion`, una y de 0.01 ms. */
  animation: latido 1.4s ease-in-out 3;
  min-width: 20px;
  padding: 0 var(--espacio-1);
  border-radius: var(--radio-full);
  background: var(--primario);
  color: var(--sobre-primario);
  font-size: var(--texto-xs);
  font-weight: 600;
  text-align: center;
}

/* De 1024px para arriba la navegación es una columna lateral; por debajo, una
   fila que se desplaza. El panel del cliente se usa sobre todo desde el teléfono. */
@media (min-width: 1024px) {
  .navegacion--lateral { flex-direction: column; width: 220px; }
}

/* ── Componente 6 · Aviso ────────────────────────────────────────────────── */

/* El color del estado va en el filo y en el icono; el texto se queda en el par
   texto/superficie, que siempre pasa AA. Así ningún estado depende de que el
   navegador pinte bien un fondo de color. */
.aviso {
  display: flex;
  gap: var(--espacio-3);
  padding: var(--espacio-4);
  border-radius: var(--radio-md);
  /* El filo del aviso neutro es NEUTRO, no del color de marca: si fuera --primario, un
     cliente con marca roja tendría avisos informativos idénticos a los de error. Solo los
     tres estados llevan color, y esos son del sistema. */
  border-left: 3px solid var(--borde-control);
  background: var(--superficie-alta);
  color: var(--texto);
  font-size: var(--texto-sm);
}

.aviso__icono { flex: none; color: var(--texto-suave); }
.aviso__titulo { font-weight: 600; margin-bottom: var(--espacio-1); }
.aviso__cuerpo { color: var(--texto-suave); }

/* Filo e icono usan la variante -texto, no el color crudo: un icono que comunica
   significado cae bajo WCAG 1.4.11 igual que un borde, y el rojo crudo se pierde sobre
   una superficie oscura. */
.aviso--exito { border-left-color: var(--exito-texto); }
.aviso--exito .aviso__icono { color: var(--exito-texto); }
.aviso--aviso { border-left-color: var(--aviso-texto); }
.aviso--aviso .aviso__icono { color: var(--aviso-texto); }
.aviso--error { border-left-color: var(--error-texto); }
.aviso--error .aviso__icono { color: var(--error-texto); }

/* T-174 · El estado también RESPLANDECE hacia dentro desde su filo, con el mismo color, y se
   apaga a 16 px. Es una sombra INTERIOR: no cuenta como la sombra exterior que K-3 prohíbe
   junto a un borde, y lo mide la 966. El neutro no lleva: un resplandor gris es suciedad. */
.aviso--exito { box-shadow: inset 8px 0 16px -12px var(--exito-texto); }
.aviso--aviso { box-shadow: inset 8px 0 16px -12px var(--aviso-texto); }
.aviso--error { box-shadow: inset 8px 0 16px -12px var(--error-texto); }

/* ── Componente 7 · Marca y esqueleto ────────────────────────────────────────
   Movidas aquí desde `cliente/acceso.css` en T-008 bloque 2: con el armazón, las usan DOS
   pantallas (acceso e inicio) y duplicarlas era garantizar que discrepieran. Se movieron
   TAL CUAL, sin cambiar un valor — `tokens/pruebas/movimiento.spec.js` mide el latido del
   esqueleto sobre `acceso.html` y depende de que estos números no cambien. */

.marca {
  display: flex;
  align-items: center;
  gap: var(--espacio-3);
  margin-bottom: var(--espacio-10);
}

.marca__logo {
  height: 2rem;
  max-width: 10rem;
  object-fit: contain;
}

/* El logo de reserva. Un cliente recién dado de alta no tiene ninguno, y sus iniciales se
   ven mucho mejor que un hueco o que un icono genérico de "sin imagen". */
.marca__iniciales {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radio-md);
  background: var(--primario);
  color: var(--sobre-primario);
  font-size: var(--texto-sm);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.marca__nombre {
  font-family: var(--tipo-display);
  font-size: var(--texto-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Esqueletos, no un spinner centrado: un spinner dice "espera", un esqueleto dice "espera,
   y va a aparecer algo con esta forma". Solo se anima la opacidad. */
.esqueleto {
  display: flex;
  flex-direction: column;
  gap: var(--espacio-3);
}

.esqueleto__linea {
  width: 80%;
  height: 0.75rem;
  border-radius: var(--radio-full);
  background: var(--superficie-alta);
  animation: latido 1.4s ease-in-out infinite;
}

.esqueleto__linea--larga { width: 100%; }
.esqueleto__linea--corta { width: 45%; }

@keyframes latido {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

/* Un número o una pantalla de mentira que se ve como de verdad es una promesa que el
   backend no puede cumplir, y para cuando alguien lo nota, el cliente ya la vio. */
.etiqueta-maqueta {
  margin-top: var(--espacio-6);
  padding: var(--espacio-3);
  border: 1px dashed var(--borde-control);
  border-radius: var(--radio-md);
  color: var(--texto-suave);
  font-size: var(--texto-sm);
}

/* ── Utilidades mínimas ──────────────────────────────────────────────────── */

/* `list-style: none` porque estos tres ayudantes se usan sobre `<ul>` casi siempre —la
   navegación, la lista de conversaciones, el hilo de mensajes, los agentes— y una lista
   maquetada como tarjetas con la viñeta puesta enseña un punto negro flotando fuera de la
   caja. Estaba sin escribir: la pasada visual del bloque 2 (V-9) lo midió con
   `getComputedStyle`, `list-style-type: disc` en `.navegacion`, `.pila` y `.fila`, y los
   puntos salen en las 45 capturas. Se escribe AQUÍ, una vez, y no en cada pantalla: es la
   Ley 7. `padding-inline-start: 0` va con ello — sin él el navegador deja la sangría del
   marcador aunque el marcador ya no esté; `.navegacion` no lo necesita porque ya declara su
   propio `padding`, y ponérselo aquí (que va después en el archivo) le comería el izquierdo. */
.pila,
.fila,
.navegacion { list-style: none; }

.pila,
.fila { padding-inline-start: 0; }

.pila { display: flex; flex-direction: column; gap: var(--espacio-4); }
.fila { display: flex; flex-wrap: wrap; gap: var(--espacio-3); align-items: center; }
.rejilla {
  display: grid;
  gap: var(--espacio-4);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}

/* Visible solo para lectores de pantalla. Para etiquetas que el ojo no necesita. */
.solo-lectores {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Movimiento de sistema ────────────────────────────────────────────────
   Dos piezas, y las dos son CSS puro: el JS del panel decide QUÉ se ve, nunca CÓMO
   aparece. */

/* 1 · La lista que entra escalonada. Solo transform y opacity. */
@keyframes entra-desde-abajo {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.entra-en-lista > * {
  animation: entra-desde-abajo var(--duracion-normal) var(--curva-entrada) both;
}

/* El escalón se corta en el octavo: del noveno en adelante todos entran con el octavo.
   Una lista de treinta tardaría 1.2 s en terminar de aparecer, y eso ya no es ritmo. */
.entra-en-lista > *:nth-child(1) { animation-delay: 0ms; }
.entra-en-lista > *:nth-child(2) { animation-delay: calc(var(--escalon) * 1); }
.entra-en-lista > *:nth-child(3) { animation-delay: calc(var(--escalon) * 2); }
.entra-en-lista > *:nth-child(4) { animation-delay: calc(var(--escalon) * 3); }
.entra-en-lista > *:nth-child(5) { animation-delay: calc(var(--escalon) * 4); }
.entra-en-lista > *:nth-child(6) { animation-delay: calc(var(--escalon) * 5); }
.entra-en-lista > *:nth-child(7) { animation-delay: calc(var(--escalon) * 6); }
.entra-en-lista > *:nth-child(n+8) { animation-delay: calc(var(--escalon) * 7); }

/* 2 · El cambio de sección, sin una línea de JS.
   Las secciones se ocultan con `hidden` → `display: none`, y desde `display: none` no se
   puede transicionar: por eso hoy el cambio de pantalla es un corte seco. `@starting-style`
   da el estado DESDE el que entra, y `transition-behavior: allow-discrete` deja que
   `display` participe en la transición en vez de aplicarse de golpe. Baseline 2024.

   Y no se toca `[hidden] { display: none !important }`: esa regla se ganó con un defecto
   real y sigue mandando. */
/* SALIR ES INSTANTÁNEO, Y ENTRAR SE ANIMA. La primera versión transicionaba también la
   salida, y eso tenía un precio que solo se ve navegando: `display … allow-discrete` mantiene
   a la sección que se va OCUPANDO SITIO durante toda la transición, así que el documento mide
   por un momento lo que miden las dos juntas. Medido al pasar de Salud al Catálogo con la
   página bajada a 600: el documento crecía de 1931 a 2343, el scroll saltaba a 1151, se
   desplomaba a 0 y volvía a acomodarse. «Se sube la página y después se baja y se reacomoda»
   (Diego, 2026-09-11), palabra por palabra.

   La transición de salida no comunicaba nada —la pantalla que te vas ya no te interesa— y
   costaba eso. Se queda la de entrada, que sí dice que lo que estás viendo es nuevo. */
[data-seccion] {
  opacity: 1;
  transition: none;
}

[data-seccion]:not([hidden]) {
  transition: opacity var(--duracion-normal) var(--curva-entrada),
              transform var(--duracion-normal) var(--curva-entrada),
              display var(--duracion-normal) allow-discrete;
}

@starting-style {
  [data-seccion]:not([hidden]) { opacity: 0; transform: translateY(4px); }
}


/* ── Componente 8 · Orbe (T-174) ─────────────────────────────────────────────
   La presencia del agente: una esfera con el color de la marca que dice si está trabajando,
   en pausa, o esperando a una persona. Lo pidió Diego con un reel del «Orb» delante
   (2026-09-15), y entra porque COMUNICA ESTADO — que es la prueba de M-1 que un adorno no pasa:
     · ¿qué comunica?            si el agente trabaja ahora mismo;
     · ¿es interrumpible?        se apaga con `prefers-reduced-motion` y se para al pausar;
     · ¿vive el contenido fuera? sí: la píldora con la palabra («Trabajando») sigue al lado,
                                 y el orbe lleva `aria-hidden` (C-8).
   Es el mismo argumento con el que §5.5 exime al esqueleto de carga. Gira despacio (6 s) y
   respira poco (4 s, escala 1 → 1.04): M-9, el movimiento correcto es el que no se nota hasta
   que uno lo busca. Solo `transform` y `opacity` (M-2, prueba 962). */
.orbe {
  position: relative;
  display: inline-block;
  flex: none;
  width: var(--icono-md);
  height: var(--icono-md);
  border-radius: var(--radio-full);
  vertical-align: middle;
  background:
    radial-gradient(circle at 32% 28%, var(--orbe-luz) 0%, transparent 40%),
    radial-gradient(circle at 74% 76%, var(--orbe-b) 0%, transparent 62%),
    radial-gradient(circle at 50% 50%, var(--orbe-a) 0%, var(--orbe-b) 100%);
  box-shadow: 0 0 12px var(--orbe-halo);
}

.orbe--lg {
  width: calc(var(--icono-md) * 2);
  height: calc(var(--icono-md) * 2);
  box-shadow: 0 0 24px var(--orbe-halo);
}

/* El brillo que gira: un anillo de luz recortado con una máscara, para que solo se mueva el
   borde y el centro se quede quieto. `currentColor` en la máscara es «opaco»: lo que importa
   de una máscara es su alfa, no su color. */
.orbe::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: conic-gradient(from 0deg, transparent 0% 55%, var(--orbe-luz) 72%, transparent 88% 100%);
  -webkit-mask: radial-gradient(circle, transparent 56%, currentColor 60%);
  mask: radial-gradient(circle, transparent 56%, currentColor 60%);
  animation: orbe-gira 6s linear infinite;
}

/* T-209 · D-E10 · EL ORBE QUE ESCUCHA ENCIENDE. Es el quinto sitio del glow y el más claro
   de los cinco: el orbe en reposo lleva el halo de MARCA (`--orbe-halo`, derivado de
   `--primario`) porque en reposo lo que dice es de quién es el panel; cuando está activo lo
   que dice es que está pasando algo, y eso es acento, no marca.
   Sustituye la sombra en vez de sumarse: `var(--glow)` va solo en su `box-shadow` o se lleva
   la propiedad por delante el día que se apague. Y de paso quita la mina de este sitio — el
   `0 0 12px` de una capa que rompería la medida 2 de `relieve.spec.js`; en reposo sigue ahí
   y es de T-205. */
.orbe--activo {
  animation: orbe-respira 4s ease-in-out infinite;
  box-shadow: var(--glow);
}

/* En pausa NO se mueve, y se apaga. Ésa es la diferencia entre una señal y un adorno: si
   girara igual trabajando que parado, no diría nada. */
.orbe--pausado { opacity: 0.45; box-shadow: none; }
.orbe--pausado::before { animation: none; }

/* Esperando a una persona: un anillo que se expande y se apaga. Es el «te necesita» de T-172
   hecho movimiento, y sigue siendo infinito mientras la espera dure — como el esqueleto. */
.orbe--atencion::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  border: 2px solid var(--orbe-a);
  animation: orbe-aviso 1.4s ease-out infinite;
}

@keyframes orbe-gira { to { transform: rotate(1turn); } }
@keyframes orbe-respira {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.04); }
}
@keyframes orbe-aviso {
  from { transform: scale(1); opacity: 0.7; }
  to { transform: scale(1.7); opacity: 0; }
}

/* ── Componente 9 · Icono (T-174) ────────────────────────────────────────────
   Un solo set, dibujado en `paneles/iconos.js`: trazo 1.5, `viewBox 0 0 24 24`, sin relleno,
   `currentColor` (R-4). Aquí solo el tamaño y que no se encoja en un flex. `vertical-align`
   para cuando va en línea con texto, que es siempre. */
.icono {
  flex: none;
  /* T-251 · SE MIDE CONTRA SU TEXTO, NO CONTRA SÍ MISMO (CRITERIO, regla 11).
     Estaba en `--icono-sm` (16 px) fijo, y el comentario de aquí arriba ya decía que va en
     línea con texto «siempre». Con la medida fija, la razón contra la tipografía que acompaña
     BAILABA según dónde cayera: medido, 1em en las entradas del menú (texto 16) y 1,14em en el
     conmutador de la barra y en los títulos de tarjeta (texto 14). Ese 1,14 es el «está muy
     grande y se sale» de Diego — no era el dibujo, era que no seguía a su letra.
     Con `1em` el icono es puntuación: cambia cuando cambia el texto, y no antes. */
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
}

/* ── La cifra que cuenta (T-174) ─────────────────────────────────────────────
   El número sube de 0 a su valor en 300 ms. PERO EL DOM DICE EL VALOR FINAL DESDE EL PRINCIPIO:
   el conteo es un `::before` que TAPA la cifra con un `counter()` movido por una propiedad
   registrada, y se desvanece al terminar. Así `textContent` no miente, un lector de pantalla
   no oye «3… 9… 14», la prueba 22 (`allInnerTexts` nada más entrar) sigue pasando, y con
   `prefers-reduced-motion` la animación de 0.01 ms deja el final puesto sin una línea más.
   `contar()` en `comun.js` decide QUÉ cuenta: solo enteros menores que mil, porque `counter()`
   no sabe poner el separador de miles. */
@property --cuenta { syntax: '<integer>'; inherits: false; initial-value: 0; }
@property --cuenta-final { syntax: '<integer>'; inherits: false; initial-value: 0; }

.cifra--cuenta { position: relative; }

.cifra--cuenta::before {
  content: counter(cuenta);
  counter-reset: cuenta var(--cuenta);
  position: absolute;
  inset: 0;
  background: var(--superficie);
  animation:
    cuenta var(--duracion-lenta) var(--curva-entrada) both,
    tapa-se-va var(--duracion-rapida) var(--curva-de-salida) var(--duracion-lenta) forwards;
}

@keyframes cuenta {
  from { --cuenta: 0; }
  to { --cuenta: var(--cuenta-final); }
}
@keyframes tapa-se-va { to { opacity: 0; } }


/* ══ T-231 · LOS TOKENS DEL SISTEMA DE MOVIMIENTO ════════════════════════════════════════
 *
 * Viven AQUÍ y no en `paneles/lenguaje/movimiento.css` por la reja 1 de `check.sh`: un
 * `var(--x)` que no esté declarado en esta carpeta es indistinguible de una errata, y la
 * reja lo marca. Es el mismo motivo por el que `--alto`, `--parte` y `--cuenta-color` están
 * en el bloque de «tokens internos de componente» de arriba.
 *
 * Son tokens de COMPOSICIÓN (tiempo y recorrido), no de marca: ni uno sale de
 * `tenant_branding` ni lo puede tocar un cliente. Las duraciones y las curvas de verdad ya
 * existían (`--duracion-rapida/normal/lenta/salida`, `--curva-entrada`, `--curva-de-salida`,
 * `--escalon`); esto NO las duplica, las acompaña. */
:root {
  /* El feedback de pulsación tiene que caer por debajo de 100 ms (M-6, §4.1 de la base UX),
     y la más corta que había era `--duracion-rapida: 150ms`. 90 ms es lo que falta: por
     debajo del umbral y todavía visible. No es «rápida un poco más»: es la única duración
     del sistema cuyo número lo fija una regla de accesibilidad y no el gusto. */
  --duracion-pulsacion: 90ms;

  /* El índice de entrada de una pieza dentro de un grupo escalonado. Por defecto 0 —una
     pieza sola entra sin esperar— y `movimiento.css` lo sube con `:nth-child()`, así que el
     escalonado NO NECESITA UNA LÍNEA DE JS. Un `style="--mov-i:12"` en línea gana sobre la
     hoja (regla de la cascada), que es la salida para una lista pintada por JS que sabe el
     índice de verdad. */
  --mov-i: 0;

  /* Cuánto RECORRE lo que entra o lo que se va. Son TRES tokens y el reparto importa:
     `--mov-desliz` es LA DISTANCIA EN CURSO —la única que leen los `@keyframes`—, y las otras
     dos son los dos valores del sistema, que cada clase de `movimiento.css` copia encima.
     Ese rodeo es lo que permite que un solo `@keyframes` sirva para un bloque, una fila, un
     dato y un aviso: se cambia la variable, no la animación.

     Dos distancias y no una porque un bloque de pantalla es grande y necesita recorrido para
     que se lea como «esto llegó», mientras que una fila de tabla con 10 px se lee como un
     salto. §3.5 de la base de animación: elemento grande = más recorrido y más duración.
     Nunca son `top` ni `margin`: son el argumento de un `translateY`. */
  --mov-desliz: 10px;
  --mov-desliz-bloque: 10px;
  --mov-desliz-fila: 6px;

  /* El escalón entre BLOQUES de una pantalla. `--escalon` (40 ms) es el de las filas de una
     lista; éste es mayor porque hay menos piezas y son más grandes. 50 ms es el tope del
     rango 30-50 de M-5: por encima el ritmo se vuelve espera.

     De estos dos números sale el CORTE del escalonado, y no al revés: el presupuesto es que
     el escalón acumulado de un grupo no pase de 200 ms, así que 200/50 = 4 saltos (se corta
     en el quinto bloque) y 200/40 = 5 saltos (se corta en la sexta fila). El porqué completo
     está en la cabecera de `paneles/lenguaje/movimiento.css`. El corte en el OCTAVO hijo de
     `.entra-en-lista`, unas líneas más arriba en este mismo archivo, es el anterior a esa
     cuenta y lo retira T-232 al aplicar. */
  --mov-escalon-bloque: 50ms;

  /* El solape del relevo esqueleto → dato. El dato empieza a entrar 60 ms después de que el
     esqueleto empieza a irse, no cuando termina: sin solape hay un instante de nada y ese
     hueco es justo lo que se siente barato. */
  --mov-solape: 60ms;

  /* Cuánto salta una cifra que cambió. En `em` y no en px para que siga al tamaño de la
     cifra: la misma clase va en un número de 14 px y en uno de 68 px. */
  --mov-salto-cifra: 0.35em;
}


/* ══ T-231 · LA OLA DE PUNTOS (los orbes de carga) ═══════════════════════════════════════
 * @tarea T-231
 *
 * Diego: «meterle orbes de carga… que sea como una wave, una ola de puntitos separados unos
 * de otros, y que haga la animación de esas banderas que se mueven con el viento. Que sea
 * como la forma de una bandera, con esos puntitos degradados».
 *
 * La forma vive en `paneles/lenguaje/orbes.css`; aquí viven los NÚMEROS y los PIGMENTOS,
 * porque la reja 1 no distingue entre un color inventado y un `var(--x)` que nadie declaró:
 * los dos son lo mismo — una errata que nadie puede cambiar desde `tenant_branding`. Mismo
 * reparto que el bloque `--mov-*` de arriba.
 *
 * El prefijo es `--ola-` y NO `--orbe-`: `--orbe-a/b/luz/halo` ya son del orbe de voz de
 * T-209 (la esfera que escucha), que es otra pieza y otro mensaje. Un `--orbe-viaje` al lado
 * de un `--orbe-halo` sería el error de nombre antes de escribirlo.
 *
 * Los que llevan sufijo (`-sm`, `-md`, `-lg`) son LOS VALORES DEL SISTEMA; los que no lo
 * llevan son LO QUE ESTÁ EN JUEGO — los únicos que leen los `@keyframes`. Ese rodeo es lo
 * que permite que UN SOLO `@keyframes` sirva para los tres tamaños: se cambia la variable,
 * no la animación. */
:root {
  /* La curva de una ola es un seno, no un muelle. Ésta es la «ease-in-out sine» clásica:
     rápida en el centro del recorrido y lenta en los extremos, que es como se mueve algo que
     oscila. `--curva-entrada` (que sobrepasa) aquí haría que la tela pareciera de goma. */
  --curva-onda: cubic-bezier(0.37, 0, 0.63, 1);

  /* ── LO QUE ESTÁ EN JUEGO ──────────────────────────────────────────────────────────── */
  /* El sitio del punto en la rejilla. `--ola-x` es la COLUMNA (0 = pegado al mástil) y
     `--ola-y` la FILA. Los pone `orbes.css` con `:nth-child()`, así que la ola no necesita
     una línea de JS ni un `style=` por punto. */
  --ola-x: 0;
  --ola-y: 0;
  /* El índice de la última columna y de la última fila VISIBLES. No es decoración: de estos
     dos sale la normalización a 0…1, así que cuando el móvil esconde columnas, la ola vuelve
     a repartirse sobre las que quedan en vez de quedarse a medias. */
  --ola-ultima-col: 3;
  --ola-ultima-fila: 2;
  /* 0…1 · distancia al mástil y altura dentro de la tela. Las calcula `orbes.css`. */
  --ola-k: 0;
  --ola-j: 0;
  /* Cuánto se mueve ESTE punto (0…~1.3) y cuánta luz le toca en reposo (0…1). */
  --ola-amp: 1;
  --ola-brillo: 1;
  /* La geometría y el tiempo del tamaño que se esté usando. */
  --ola-punto: 5px;
  --ola-hueco: 4px;
  --ola-viaje: 6px;
  --ola-vaiven: 0.9s;
  --ola-tinta: var(--texto);

  /* ── LOS TRES TAMAÑOS ──────────────────────────────────────────────────────────────── */
  /* Pequeño: dentro de un botón, cargando una acción. Tiene que LEERSE A 20 px, y por eso es
     el único que no baja de punto en móvil: 4×3 = 12 puntos ya es el mínimo donde una ola
     todavía se lee como ola. 4·3.4 + 3·2.1 = 19.9 px de ancho. */
  --ola-punto-sm: 3.4px;
  --ola-hueco-sm: 2.1px;
  --ola-viaje-sm: 3.4px;
  --ola-vaiven-sm: 0.62s;
  /* Mediano: una sección cargando. */
  --ola-punto-md: 5px;
  --ola-hueco-md: 4px;
  --ola-viaje-md: 6px;
  --ola-vaiven-md: 0.9s;
  /* Grande: la pantalla entera. Tela más grande = ola más lenta, igual que en el mundo. */
  --ola-punto-lg: 7px;
  --ola-hueco-lg: 6px;
  --ola-viaje-lg: 10px;
  --ola-vaiven-lg: 1.2s;

  /* ── LA FÍSICA DE LA BANDERA ───────────────────────────────────────────────────────── */
  /* EL DESFASE, Y VIENE DE LAS DOS DIMENSIONES. Si el retraso saliera solo de la columna, la
     cresta sería una línea vertical perfecta y eso es una CORTINA. Una bandera ondula en
     diagonal, así que la fila también empuja. En negativo para que la ola ya esté corriendo
     en el primer fotograma: nadie ve arrancar una bandera. */
  --ola-paso-x: -0.115s;
  --ola-paso-y: -0.05s;
  /* LA AMPLITUD CRECE CON LA DISTANCIA AL MÁSTIL, y crece al CUADRADO, no en línea recta:
     `piso + k² · vuelo`. Pegado al asta queda el 13 % (no cero: una tela clavada todavía
     tiembla), en el borde libre el 100 %. Sin esto todos los puntos se mueven igual y vuelve
     a parecer una cortina. */
  --ola-piso: 0.13;
  --ola-vuelo: 0.87;
  /* Y la parte de abajo vuela un poco más que la de arriba: 30 % extra en la última fila. */
  --ola-caida: 0.3;
  /* El bandazo horizontal, en fracción del viaje vertical. Es lo que hace que la tela
     PAREZCA girar en vez de subir y bajar. */
  --ola-deriva: 0.18;
  /* EL VOLUMEN. El punto que viene hacia ti crece y se enciende; el que se aleja encoge y se
     funde con el fondo. Escala y opacidad — las dos baratas — hacen todo el 3D. */
  --ola-cuerpo: 0.22;
  --ola-valle: 0.38;
  /* La luz viene de arriba: la fila de abajo arranca un 34 % más apagada. */
  --ola-sombra: 0.34;

  /* ── LOS PIGMENTOS ─────────────────────────────────────────────────────────────────── */
  /* La de blanco y negro que pidió Diego: `--texto`, que ya es claro sobre oscuro y oscuro
     sobre claro. Un gris fijo solo funcionaría en un modo. */
  --ola-tinta-neutra: var(--texto);
  /* La que va DENTRO de un botón de marca, donde el fondo ya es el primario. */
  --ola-tinta-sobre: var(--sobre-primario);
  /* La variante de marca: del primario en el mástil al acento en el borde libre. El paso de
     uno a otro NO es una escalera de `color-mix` por columna — es una capa del acento con
     `opacity: var(--ola-k)` encima del primario, que da el degradado continuo y gratis. */
  --ola-tinta-marca: var(--primario);
  --ola-tinta-punta: var(--acento);
}

/* ══ T-256 · LO SUYO / LO NUESTRO · un token de SIGNIFICADO ═══════════════════════════════
 *
 * Diego, 2026-09-24: «que la información que él ponga a veces se vea en verde y la que
 * nosotros ponemos sea en negro, o lo que sea, que haga contraste de color para que él
 * identifique exactamente cuál es la información relevante para él».
 *
 * EL VERDE NO SE HACE LITERAL, y no es suavizarlo: «o lo que sea» es suyo, y lo que pedía era
 * la DISTINCIÓN, no el color. Un verde escrito a mano sería un color de marca inventado —lo
 * caza la reja 1— y encima se pelearía con la marca de cada cliente: el azul y dorado de PAIS
 * hoy, el amarillo de Farmacia mañana. Así que sale de la marca, como todo lo demás.
 *
 * SE RESUELVE CON EL ACENTO Y NO CON UN COLOR NUEVO. `--acento-texto` ya existe, ya está
 * resuelto a AA contra la superficie para las siete marcas, y ya significa «esto destaca».
 * Un décimo token de color sería una migración (contrato §5.3); esto es un ALIAS con nombre
 * semántico, que es otra cosa: no añade color, le pone nombre a uno que ya está.
 *
 * Y LA JERARQUÍA VA EN EL SENTIDO QUE PIDIÓ: lo suyo destaca y lo nuestro se calla. Lo que
 * escribió el cliente lleva un canto de acento —el mismo lenguaje con el que `grado--*` marca
 * un estado, no un lavado de color encima del texto— y lo que ponemos nosotros se queda en
 * `--texto-suave`, que es el color de lo que acompaña.
 *
 * NO LLEVA LEYENDA, y es a propósito: §1.9 dice que lo que hay que explicar no funciona. Lo
 * que se lee fuerte es lo que tú dijiste; lo que se lee bajo lo pusimos nosotros. Eso se
 * entiende sin que nadie lo cuente. */
:root {
  --suyo-canto: var(--acento-texto);
  --suyo-velo: color-mix(in srgb, var(--acento) 8%, transparent);
  --nuestro-texto: var(--texto-suave);
}

/** Lo que escribió el cliente. El canto va al inicio de línea y no debajo: leyendo en
 *  columna, el ojo lo encuentra sin buscarlo. */
.lo-suyo {
  border-inline-start: 2px solid var(--suyo-canto);
  padding-inline-start: var(--espacio-3);
  background: var(--suyo-velo);
  border-radius: 0 var(--radio-sm) var(--radio-sm) 0;
}

/** Lo que ponemos nosotros: acompaña, no compite. */
.lo-nuestro { color: var(--nuestro-texto); }
