/* ═══════════════════════════════════════════════════════════
   OMNIXA v2 — Variables globales
   Cambia aquí y cambia todo
═══════════════════════════════════════════════════════════ */
:root{
  --font:          'Plus Jakarta Sans', Arial, sans-serif;

  /* 🔴 CUÁNTO MIDE EL PIE Y A QUÉ ALTURA VA — UNA SOLA DEFINICIÓN.
     David, 2026-09-14, con la foto de RRHH: *«la rayita súbela al borde
     de arriba del footer, no dentro»*.

     Medido: la rayita (`.panel-thumb-handle`) se colocaba con
     `bottom: calc(44px + 10px - 2px)` — **44 era el alto del pie de
     antes**, y desde que el pie crece con la letra mide 58 o más. O sea
     que la rayita se quedó 16px por debajo del borde: *dentro del
     óvalo*. Es el mismo fallo que el `minmax(148px)` de la vitrina y el
     `min-height:46px` de la cinta: **un número en píxeles no sigue a lo
     que mide otra cosa.**

     🔑 Ahora las dos piezas leen de aquí, así que la rayita no se puede
     volver a quedar atrás. */
  --footer-alto:   58px;
  --pie-abajo:     10px;
  --pie-alto:      clamp(48px, calc(var(--footer-alto) * var(--fs-app, 1)), 84px);
  /* 🔴 EL NORMAL DE LAS TARJETAS ES 1,4 — David, 2026-08-31: *«el
     tamaño de la letra de las tarjetas, lo normal me gusta en 140; que
     ese sea el 100»*. Mismo movimiento que `--fs-base: 1.6` con la
     letra de la app: el 100 pasa a ser lo que se ve bien.
     ⚠️ Y VIVE AQUÍ, no en `preferencias.js`: ese archivo carga tarde, y
     con el valor de fábrica puesto allí las tarjetas se pintarían
     pequeñas y **darían un salto** al arrancar. El `:root` existe desde
     el primer byte — la misma razón por la que `--fs-app` dejó de ser
     `body.style.zoom`. */
  --fs-card-base:  1.4;
  /* Lo que mueve el control «Letra de las tarjetas». 1 = lo normal. */
  --fs-card-pref:  1;
  /* 🔴 Y AQUÍ VA `--fs-app`, QUE ES LO QUE FALTABA. Pregunta de David,
     2026-08-31: *«¿por qué el zoom no afecta al tamaño de las cards? Lo
     digo porque me inventé el aumentarle el tamaño a la letra de las
     cards, porque si le aumentaba el zoom solamente crecía el icono, y
     lo que me molestaba es que no podía leer»*.

     Su descripción era exacta y el porqué está en una línea: los 33
     sitios que pintan texto de tarjeta dicen `calc(15px *
     var(--fs-card))` — **píxeles crudos multiplicados solo por esta
     variable**. Subir el tamaño de letra de la app no los tocaba, y los
     iconos y las cajas sí crecían (van con otras variables). O sea que
     el control hacía la mitad de lo que dice, y la mitad que faltaba
     era justo la que él quería.

     Se arregla aquí y no en los 33: el que multiplica es este, así que
     con meter `--fs-app` dentro todos lo heredan a la vez. `calc()`
     dentro de `calc()` es válido y se expande solo.

     Los dos controles siguen: «Tamaño de letra» mueve TODA la app —las
     tarjetas incluidas, que era lo que fallaba— y «Letra de las
     tarjetas» sube solo estas, para quien quiera la lista más legible
     sin agrandar el resto. */
  --fs-card:       calc(var(--fs-card-base) * var(--fs-card-pref) * var(--fs-app, 1));
  --primary:       #6c35ff;
  --accent:        #3b18cc;
  --cyan:          #00cfff;
  --grad:          linear-gradient(135deg,#6c35ff,#00cfff);
  --grad-v:        linear-gradient(180deg,#3b18cc 0%,#6c35ff 45%,#0f0a2e 100%);
  --bg:            #eef1f6;
  --bg-panel:      #f7f8fc;
  --bg-white:      #ffffff;
  --bg-dark:       #0b0d1a;
  --border:        rgba(108,53,255,.10);
  --border-soft:   rgba(108,53,255,.08);
  --text:          #111827;
  --text-soft:     #6b7280;
  --text-inv:      #ffffff;
  --success:       #22c55e;
  --danger:        #ef4444;
  --warning:       #f59e0b;
  /* Derivadas para uso interno — sin hardcodear rgba() en el CSS */
  --primary-soft:  rgba(108,53,255,.07);
  --shadow-color:  rgba(108,53,255,.12);
  /* Colores de íconos sidebar */
  /* Sidebar — esquema azul claro */
  --sb-bg:         #f8fafc;
  --sb-bg-head:    #f1f5f9;
  --sb-bg-soft:    #dbeafe;
  --sb-border:     #e2e8f0;
  --sb-text:       #1e293b;
  --sb-text-soft:  #7ca3e0;
  --sb-logo-color: #3b6dd4;
  --sb-empresa:    #1e4080;
  --sb-cargo:      #7ca3e0;
  --sb-label:      #94a3b8;
  --ico-default:   #7ca3e0;
  --ico-active:    #1d4ed8;
  --ico-active-bg: #dbeafe;
  --ico-purple:    #8b5cf6;
  --ico-violet:    #a855f7;
  --ico-cyan:      #00cfff;
  --ico-magenta:   #d946ef;
  --ico-orange:    #f59e0b;
  --ico-teal:      #10d9a3;
  /* 🔴 EL HEADER CRECE CON LA LETRA. Lo cazó una foto el 2026-08-31:
     con la letra al 160% el logo OMNIXA **se cortaba por arriba**,
     porque el header medía 92px fijos y su contenido ya no cabía. Y
     ningún banco lo vio — `textContent` lee «OMNIXA» entero esté
     cortado o no.
     Va con `max()`, no con una multiplicación pelada: al 100% tiene que
     medir exactamente los 92 de siempre, o cambiaría el reparto de toda
     la app sin que nadie lo pidiera. Y como los paneles se calculan con
     `calc(100dvh - var(--header-h))`, crecer aquí los ajusta solos. */
  /* ⚠️ EL HEADER NO SE MULTIPLICA POR `--fs-base`. Al subir la base a
     1,6 el topbar se habría ido a 147px fijos — y David lo quiere al
     revés: *«el topbar, que mínimo sea un dedo y máximo como está»*.
     Crece solo con la preferencia por encima de lo normal, y **con
     tope**: `clamp` en vez de `max`, porque un header que crece sin
     freno se come la pantalla donde se trabaja. */
  /* 🔴 EL TOPBAR, GRANDE. David lo rectificó el 2026-08-31: *«el tamaño
     del topbar lo quiero grande OMNIXA… él y sus botones menú y eco es
     más grande»*. Sube de 92 a 116px de fábrica.

     🔑 Y CON TECHO, que es la otra mitad de la frase: *«solo que si se
     le coloca más zoom no le afecte si se pasa»*. Crece con la
     preferencia de letra hasta 148 y ahí para — sin freno, al 200% el
     topbar se comería un tercio de la pantalla donde se trabaja.

     `--header-alto` sigue siendo lo que el dueño deja en Ajustes (entre
     un dedo y esto), y el `clamp` lo defiende de los dos lados. */
  /* 🔴 64 el 2026-09-14 por la mañana, y 102 esa misma tarde — y las dos
     cosas son ciertas. El topbar pasó de tres renglones a uno (el logo
     fuera, los puntitos fuera) y en 116 le sobraban 91px medidos: por eso
     64. Por la tarde David rectificó dónde vive el logo —*«tiene el
     gorro que dice omnixa; el gorro y los bordes, colócalo»*— y el
     topbar volvió a tener dos pisos: la cinta de la casa (32px medidos)
     y la píldora (62). **De los 52px ganados se devuelven 38; quedan 14
     frente a los 116 de antes**, y lo que se gana a cambio es que la
     marca y el módulo ya no compiten en la misma línea.
     🔑 Y AQUÍ ES UN PISO, NO UNA ALTURA: `.topbar` lleva `min-height`, así
     que el contenido manda. Este número y el `base` del topbar en
     `estirar.js` son el mismo: si se separan, lo que se ve de fábrica
     deja de ser lo que el control cree que hay puesto. */
  /* 📏 112 desde el 2026-09-30: el gorro creció con el logo (32 → 42)
     y 102 ya no se alcanzaba — ver `estirar.js`.
     📏 Y 100 desde el 2026-10-07: los botones de la píldora bajaron a
     la medida de Recetas (se ven de 40), la píldora pasó de 62 a 50 y
     gorro + píldora + rellenos dan 100. Medido sin piso, no restado. */
  --header-alto:   100px;  /* lo que el dueño deja en Ajustes */
  --header-h:      clamp(56px, calc(var(--header-alto) * var(--fs-app, 1)), 148px);
  /* 🎩 Y CUÁNTO OCUPA **AHORA** (2026-09-17, `nucleo/topbar-recoge.js`).
     `--header-h` es lo que mide DESPLEGADO —lo que el dueño estira en
     Ajustes, y eso no se toca—; este es lo que de verdad reparte
     pantalla. Los sitios que reparten contra el topbar preguntan por el
     vivo: con el viejo, un overlay abierto con el topbar recogido
     dejaría 102px de hueco arriba enseñando la app por debajo.
     🔴 DEVUELTO EL 2026-09-29: estas tres piezas se perdieron enteras en
     el commit de los atajos del eco (`6d8ba64`), que no tocaba el
     topbar. La pieza seguía poniendo `data-topbar="recogido"` y **nadie
     lo escuchaba**: el atributo llegaba, el CSS no existía y la pantalla
     no cambiaba ni un píxel. *Un atributo que nadie lee se ve igual que
     una pieza que no se construyó.* */
  --header-vivo:   var(--header-h);
  --rail-l:        64px;
  --rail-r:        54px;
  /* El ancho del menú: 300 de siempre más lo que el dueño le dé
     arrastrando su borde derecho (tope: dedo y medio). */
  --sidebar-w:     calc(300px + var(--sidebar-extra, 0px));
  --ecopanel-w:    360px;
  --panel-radius:  20px;
  --sheet-radius:  24px;
  --t:             .2s cubic-bezier(.4,0,.2,1);
  --ts:            .32s cubic-bezier(.4,0,.2,1);
  --z-header:      200;
  --z-rail:        100;
  --z-sidebar:     1005;
  --z-backdrop:    1004;
  --z-sheet:       620;
  --z-toast:       9999;

  /* ── Paleta de los MÓDULOS (fase 1 de variables, 2026-07-30) ──
     Los valores son EXACTOS a los literales que los módulos usaban;
     por eso agregar esto no cambia ni un pixel. El día que se quiera
     otro tema, se cambian aquí y toda la app obedece. */
  --texto:         #1e293b;   /* texto principal */
  /* ══════════════════════════════════════════════════════════════
     🔴 EL GRIS QUE NO SE LEÍA — medido el 2026-08-31
     ══════════════════════════════════════════════════════════════
     David: *«lo que no veo es los subtítulos o letra pequeña en gris»*.
     Y no era una impresión suya: `--texto-suave` era `#94a3b8`, que da
     **2,56:1 contra el blanco** cuando el mínimo para leer es 4,5. O
     sea que ese texto no cumplía, con cualquier vista.

     Peor: es el color de los subtítulos, o sea el que va pegado al
     tamaño más pequeño de la app. Letra chica Y sin contraste, las dos
     a la vez, en más de mil sitios.

     🔑 SE SUBIERON LOS TRES, CONSERVANDO EL ORDEN. Si solo se arregla
     el suave queda más oscuro que el medio y la jerarquía se invierte —
     el subtítulo pesando más que el texto que acompaña. Ahora:
       suave 4,59:1  <  medio 5,83:1  <  fuerte 7,58:1  <  texto 14,63:1
     y los cuatro por encima del mínimo, sobre blanco y sobre el gris de
     fondo (4,19 el más flojo). */
  --texto-suave:   #6b7688;   /* etiquetas y secundario — 4,59:1 */
  --texto-medio:   #5b6675;   /* 5,83:1 */
  --texto-fuerte:  #475569;   /* 7,58:1 */
  --borde:         #e2e8f0;
  --fondo-suave:   #f1f5f9;
  --fondo-claro:   #f8fafc;
  --linea:         #eef2f7;
  --gris:          #cbd5e1;
  --marca:         #6c35ff;   /* el morado, igual a --primary */
  --marca-honda:   #2f6bf6;   /* el segundo tono, del MISMO lado: para degradados */
  /* El cian del degradado del logo. Vivía escrito a mano dentro de
     `#appTitle`, y el 2026-09-11 hizo falta EN OTRO SITIO —el gorro de
     la cabecera—: dos copias del mismo degradado se separan solas, y el
     día que se retoque la identidad se retocaría en uno y el otro se
     quedaría desparejo (armonía, regla 6). */
  --marca-clara:   #00cfff;
  --marca-suave:   #ede8ff;
  --marca-fondo:   #faf8ff;
  --marca-linea:   rgba(108,53,255,.15);
  /* 🔴 LA RAYA DEL PIE SE VE, Y VA COMPLETA (2026-09-29). David, con el
     pie delante: *«la raya que pasa y divide los botones no se ven»*.
     Medido: con `--marca-linea` daba **1,26 : 1** sobre el blanco del
     pie —o sea, invisible— y además llevaba `margin:8px 0`, así que ni
     siquiera llegaba a los bordes. *Una raya que no se ve y que encima
     se queda corta no separa nada: es un adorno que ocupa un píxel.*
     🔑 Variable propia y no subirle la opacidad a `--marca-linea`:
     aquella viste bordes de tarjetas y campos por toda la app, y
     subirla ahí pintaría de morado media pantalla. */
  --pie-raya:      rgba(108,53,255,.70);
  /* 🔴 Y EL PIE SE DESPEGA DEL CONTENIDO. David, el mismo día: *«a
     veces el footer no se diferencia del contenido porque es blanco…
     el brillo o sombra que lo diferencie»* · y al escoger la maqueta:
     *«es sencillo, pero con esa sombra parece que estuviera flotando,
     eso es lo que busco»*. Medido, el pie contra la lista de encima
     daba **1,06 : 1**: blanco sobre casi blanco, con una sombra que no
     llegaba a dibujar un borde. */
  --pie-sombra:    0 10px 26px rgba(108,53,255,.28), 0 3px 10px rgba(15,23,42,.14);
  --ok:            #16a34a;
  --ok-suave:      #dcfce7;
  --ok-fondo:      #f0fdf4;
  --peligro:       #dc2626;
  --peligro-vivo:  #ef4444;
  --peligro-fondo: #fef2f2;
  --ambar:         #d97706;   /* identidad de Compras */
  --ambar-vivo:    #f59e0b;
  --ambar-fondo:   #fef3c7;
  /* 🔴 PARA ESCRIBIR, NO PARA PINTAR. Medido el 2026-08-30: la palabra
     «Cotización» en --ambar-vivo daba 2,07:1 contra su propio fondo, con
     el mínimo en 4,5 — el párrafo gris de al lado (4,59:1) se leía mejor
     que el título, o sea la jerarquía invertida en píxeles. Estos dos son
     los mismos colores oscurecidos hasta que se leen: 4,51:1 el ámbar y
     5,91:1 el rojo, y aguantan también el texto blanco encima (5,02 y
     6,47). --ambar-vivo y --peligro-vivo se quedan como estaban, para
     puntos y fondos, que es donde sí sirven. */
  --ambar-texto:   #b45309;
  --peligro-texto: #b91c1c;
  /* 🟢 Y EL VERDE DE ESCRIBIR, por lo mismo: --ok (#16a34a) da 3,30:1
     contra el blanco, o sea que sirve para un punto y no para una
     palabra. Este es el mismo verde oscurecido hasta que se lee: 5,01:1
     contra blanco. Ya vivía escrito a mano en --si-tinta desde el
     2026-09-04 — ahora hay UNA definición y aquella apunta aquí: *dos
     copias del mismo color se separan solas*. */
  --ok-texto:      #15803d;

  /* ══════════════════════════════════════════════════════════════
     LOS CUATRO ESTADOS DE UN DOCUMENTO — esperando · aceptado ·
     rechazado · cumplido (`nucleo/bloque-doc.js`, 2026-09-04).
     ══════════════════════════════════════════════════════════════
     🔴 Van aquí y no dentro de la pieza por lo mismo que la letra: un
     color escrito a mano NO obedece al tema, así que en el tema oscuro
     la línea del estado seguiría con su fondo claro y el texto encima
     sin contraste — sin dar ningún error.
     Y los tintes son los que YA se midieron legibles arriba
     (--ambar-texto 4,51:1 y --peligro-texto 5,91:1): inventar dos
     nuevos habría sido repetir la pelea del contraste que costó una
     vuelta entera. */
  --espera-tinta:    var(--ambar-texto);
  --espera-fondo:    rgba(180, 83, 9, .10);
  --si-tinta:        var(--ok-texto);
  --si-fondo:        rgba(21, 128, 61, .10);
  /* «Cumplido» pesa más que «Aceptado»: es el mismo verde con más
     cuerpo. Con dos fondos iguales, los dos estados se verían el mismo
     — y eso no da error, solo deja de distinguirlos. */
  --si-fondo-fuerte: rgba(21, 128, 61, .18);
  --no-tinta:        var(--peligro-texto);
  --no-fondo:        rgba(185, 28, 28, .10);
  --info:          #1d4ed8;
  /* 🔴 EL AZUL DEL PEDIDO. Nace el 2026-08-30, cuando cada documento pasó
     a llevar su color en la barra, el pie y los totales: el pedido estaba
     en #7c3aed y la factura en #6c35ff — dos morados que **al lado no se
     distinguen**, así que el color no decía nada justo donde más falta
     hacía. El azul da 6,16:1 contra su pastel y 6,70:1 contra el blanco. */
  --info-fondo:    #eff6ff;
  --blanco:        #fff;      /* superficies y texto sobre color */
  --sombra-marca:  rgba(108,53,255,.35);  /* sombra de botones morados */
  --velo:          rgba(15,23,42,.45);    /* fondo oscuro de overlays */
  /* ══════════════════════════════════════════════════════════════
     🔴 LA LEY DEL PISO DE LETRA — dictada por David el 2026-08-31
     ══════════════════════════════════════════════════════════════
     *«Poner como ley que siempre se fabrique con la fuente más grande,
     porque lo que no veo es los subtítulos o letra pequeña en gris»*.

     Y estaba medido antes de decirlo: `--fs-11` (11px) es **el tamaño
     más usado de toda la app** —676 sitios— y `--fs-10` otros 54. Más
     de mil doscientos textos entre 10 y 12 píxeles, que es donde vive
     todo lo que él nombra: los subtítulos, las descripciones, las
     etiquetas.

     🔑 EL PISO Y EL AJUSTE SON LA MISMA LÍNEA. `max()` resuelve las dos
     cosas de una vez: por debajo de `--fs-min` no baja nada, pase lo
     que pase; y `--fs-app` multiplica todo cuando alguien quiere la
     letra más grande. Con `--fs-app:1` los tres tamaños pequeños se
     juntan en 12px — que es el piso— y de ahí para arriba cada uno
     conserva su escalón.

     ⚠️ Y ESTO NO ES EL `zoom`. El zoom vive en `body.style.zoom`, o sea
     **en un estilo que hay que aplicar cuando el body ya existe**, y es
     justo lo que David describe que se le pierde: *«mañana amanece en
     100 y no le puedo subir porque queda en 160»* — el número guardado
     bien y la pantalla sin obedecerlo. Una variable de `:root` existe
     desde el primer byte del HTML y no hay ningún momento en el que
     pueda quedarse sin aplicar.

     Al escribir CSS nuevo: **nunca un `font-size` en px directo**. Va
     una de estas variables, o el barrido `probar-letra-minima.js` lo
     caza. */
  /* 🔴 EL NUEVO «NORMAL» — dictado por David el 2026-08-31 mirando la
     app al 160%: *«me siento bien con esa fuente; quiero que sea la
     normal, y de ahí se suba y baje, o sea que sea el 100»*.

     `--fs-base` es lo que él llama normal. No se tocan los números de
     cada variable —11px sigue siendo «el once»— porque son la relación
     entre unos y otros, y esa relación es la que hace que un título
     pese más que un subtítulo. Lo que cambia es cuánto vale «uno».

     🔑 Y por eso el control sigue diciendo 100%: **el 100 es lo normal,
     y lo normal ahora es esto**. Si el número hubiera pasado a decir
     160, la pregunta de la vez pasada —«no sé qué es normal»— volvería
     al día siguiente. */
  --fs-base: 1.6;   /* lo que David llama «el tamaño normal» */
  --fs-app: 1;      /* la preferencia de quien mira (preferencias.js) */
  /* 🔴 EL SUELO SE HABÍA QUEDADO VIEJO, Y POR ESO NO MORDÍA NUNCA.
     David, 2026-09-04, mirando el panel 2: *«le hice zoom y medio se vio
     elegante; creo que lo haces muy pequeño… los frentes me hacen todo
     con fuente pequeña, tuve que mandar a hacer el zoom de letras, y
     cuando ya me gustaba normalizarlo, aún así me entregan las cosas
     pequeñas»*.

     Y era medible: el suelo decía **12px crudos** —escrito cuando la
     base era 1— y desde que el normal pasó a 1,6 **el escalón más bajo
     de la escala vale 17,6px**, o sea que 12 nunca gana el `max()`. *El
     piso estaba puesto y no tocaba el suelo.* Es el mismo fallo que el
     `minmax(148px)` de la vitrina, el mismo día: **un número escrito con
     la base en 1 que se quedó atrás cuando la base cambió.**

     🔑 Por eso ahora el suelo VA EN LA MISMA MONEDA que la escala —«el
     trece»—, y no en píxeles crudos: así sube y baja con el normal y con
     la preferencia, y **no se puede volver a quedar viejo**. Medido en
     el panel 2 de Envixa: las seis explicaciones de las herramientas
     salían a 17,6px y en gris —*«los subtítulos o letra pequeña en
     gris»*, su frase del 31 de agosto, todavía pasando— y pasan a
     20,8px.

     ⚠️ Y cuesta jerarquía a propósito: 10, 11, 12 y 13 se juntan en un
     solo escalón. Es la decisión correcta —**un texto que no se lee no
     tiene jerarquía, tiene ausencia**— y de 13 para arriba cada uno
     conserva la suya. */
  --fs-min: calc(13px * var(--fs-base) * var(--fs-app));
  --fs-10: max(var(--fs-min), calc(10px * var(--fs-base) * var(--fs-app)));
  --fs-11: max(var(--fs-min), calc(11px * var(--fs-base) * var(--fs-app)));
  --fs-12: max(var(--fs-min), calc(12px * var(--fs-base) * var(--fs-app)));
  --fs-13: max(var(--fs-min), calc(13px * var(--fs-base) * var(--fs-app)));
  --fs-14: max(var(--fs-min), calc(14px * var(--fs-base) * var(--fs-app)));
  --fs-15: max(var(--fs-min), calc(15px * var(--fs-base) * var(--fs-app)));
  --fs-16: max(var(--fs-min), calc(16px * var(--fs-base) * var(--fs-app)));
  --fs-17: max(var(--fs-min), calc(17px * var(--fs-base) * var(--fs-app)));
  --fs-18: max(var(--fs-min), calc(18px * var(--fs-base) * var(--fs-app)));
  --fs-19: max(var(--fs-min), calc(19px * var(--fs-base) * var(--fs-app)));
  --fs-20: max(var(--fs-min), calc(20px * var(--fs-base) * var(--fs-app)));
  --fs-22: max(var(--fs-min), calc(22px * var(--fs-base) * var(--fs-app)));
  --fs-24: max(var(--fs-min), calc(24px * var(--fs-base) * var(--fs-app)));
  --fs-26: max(var(--fs-min), calc(26px * var(--fs-base) * var(--fs-app)));
}

/* Reset igual al v1 */
*{
  -webkit-tap-highlight-color:transparent;
  user-select:none;
  -webkit-user-select:none;
}
input, textarea, [contenteditable]{
  user-select:text;
  -webkit-user-select:text;
}
*, *::before, *::after{ box-sizing:border-box; margin:0; padding:0; }
html{ height:100%; }
body{
  height:100dvh;
  overflow:hidden;
  font-family:var(--font);
  background:var(--bg);
  color:var(--text);
  -webkit-font-smoothing:antialiased;
}
button{ font-family:inherit; cursor:pointer; border:none; background:none; }
input, textarea{ font-family:inherit; }

/* ══════════════════════════════════════════════════════════
   HEADER — copia exacta del v1, sin cambiar nada
══════════════════════════════════════════════════════════ */
.topbar{
  /* 🎩 EL ALTO LO MANDA EL CONTENIDO, y `--header-h` es el PISO
     (2026-09-14). Con el gorro encima de la píldora, un alto fijo lo
     recortaba: la cinta de Omnixa y la píldora juntas piden lo que
     pidan, y lo que el dueño estira en Ajustes es el mínimo. */
  height:auto;
  min-height:var(--header-h);
  /* El gorro va A SANGRE: se come el relleno del topbar para llegar
     hasta el borde de arriba y fundirse con la barra del sistema. Estas
     dos son las que lee `.pil-cabecera[data-gorro="si"]`; sin ellas
     usaría las de fábrica (14 y 16) y se saldría por los lados. */
  --pil-sangrar-y:4px;
  --pil-sangrar-x:12px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  /* 🎩 EL RELLENO BAJA A 4px ARRIBA Y ABAJO (2026-09-14). Los 10 de
     antes eran el aire de una barra plana; ahora el aire lo pone la
     píldora al estar separada del borde, y los 52px del botón más los 10
     no caben en los 56px del topbar más pequeño que deja `estirar.js`. */
  padding:4px 12px;
  /* 🔴 Y EL FONDO SE VA: la superficie ahora es la píldora. Dejar los
     dos —banda gris Y píldora— es la misma decisión pintada dos veces
     (armonía, regla 4), y el borde de la píldora deja de leerse encima
     de otra superficie casi igual. */
  background:transparent;
  backdrop-filter:none;
  position:sticky;
  top:0;
  z-index:100;
  flex-shrink:0;
  /* 🎩 Y SE RECOGE AL BAJAR (2026-09-17, `nucleo/topbar-recoge.js`).
     La transición va aquí y no en el JS: así el que se recoge y el que
     vuelve son el mismo movimiento, y no hay dos duraciones que se
     separen. */
  overflow:hidden;
  transition:min-height .18s ease, height .18s ease, opacity .18s ease;
}
/* 🎩 RECOGIDO: mide cero y el panel se queda su sitio. El `min-height`
   tiene que morir también — es el PISO que `estirar.js` mueve, y con él
   puesto el topbar no baja de 56px por mucho que la altura diga 0. */
:root[data-topbar="recogido"] .topbar{
  min-height:0;
  height:0;
  opacity:0;
  padding-top:0;
  padding-bottom:0;
  /* Y deja de comerse los toques mientras no se ve: un botón invisible
     que sigue recibiendo el dedo es peor que uno que falta. */
  pointer-events:none;
}
:root[data-topbar="recogido"]{ --header-vivo:0px; }

/* ══════════════════════════════════════════════════════════
   🎩 LA PÍLDORA DEL TOPBAR — «que el topbar quede como el header»
   ══════════════════════════════════════════════════════════
   David, 2026-09-14, con dos fotos de su celular: la de «Caja» —el
   header en píldora, con su borde— y la de «Cuentas» —el topbar nuevo,
   una barra plana—. *«quiero que el topbar quede como el header»*.

   🔑 AQUÍ NO HAY NI UN COLOR NI UNA ESQUINA: solo la caja. La forma, el
   fondo y el borde los pone `nucleo/pildora-header.js` con el estilo que
   el dueño escogió en el eco (son diez), igual que a la cabecera de la
   caja y a la del carrito. Escribirlos aquí sería una copia — y el día
   que él cambie de estilo, el header cambiaría y el topbar no, que es
   exactamente el «se parecen menos que antes» que se vino a arreglar. */
.topbar-pildora{
  flex:1;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:4px;
  /* Igual que `.title-zone`: sin esto el nombre largo empuja los bordes
     de la píldora en vez de recortarse. */
  min-width:0;
  box-sizing:border-box;
}

/* En PC el topbar se separa levemente de los paneles. La raya de antes
   se va con el fondo: separar ya lo hace la píldora, y una raya más
   debajo sería una tercera línea horizontal en cuarenta píxeles. */
@media(min-width:900px){
  .topbar{
    background:transparent;
    border-bottom:none;
    backdrop-filter:none;
  }
}

.top-btn{
  /* 🔴 «Y SUS BOTONES MENÚ Y ECO ES MÁS GRANDE». Eran 44×44 con un
     dibujo de 30 dentro de un header de 92; con el header en 116 se
     quedaban flotando. Crecen con la letra y tienen techo, como todo lo
     que vive en el topbar. */
  /* 🔴 Y BAJAN A LA MEDIDA DE RECETAS (2026-10-07). David, con la
     cabecera de Recetas delante: *«me gusta mucho el tamaño y la forma…
     quiero que todo Omnixa tenga estas medidas»* y, al decirle que
     deshacía su «el doble» del 31 de agosto: *«baja el header, no
     importa»*. La píldora de arriba medía 62 por estos dos botones; la
     de Recetas, 50.
     🔑 Y MIDEN UN TOQUE (44), NO 40: la píldora con gorro tiene su alto
     fijo en el de Recetas (ver `.pil-cabecera[data-gorro="si"]`), así
     que un botón de 44 sobresale 2px dentro de su relleno en vez de
     estirarla — y se sigue agarrando entero. *Por debajo de 44 un botón
     se falla con prisa*, y estos dos son el menú y el eco de toda la app.
     ⚠️ Aquí hubo un `margin:-2px 0` y se quitó: con el alto fijado en la
     píldora no guardaba nada. */
  width:clamp(44px, calc(44px * var(--fs-app, 1)), 54px);
  height:clamp(44px, calc(44px * var(--fs-app, 1)), 54px);
  border:none;
  background:transparent;
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
}

.title-zone{
  flex:1;
  /* 🔴 `min-width:0` O EL RECORTE NO OCURRE (2026-09-14). Un hijo de flex
     no baja de su ancho de contenido salvo que se le diga, así que sin
     esta línea el nombre largo EMPUJA a la hamburguesa y al eco en vez de
     recortarse. Medido con «Recursos Humanos» al 200%: el título llegaba
     justo al borde del eco, a CERO píxeles — un nombre una letra más
     largo se lo comía. */
  min-width:0;
  text-align:center;
  cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}

/* El nombre se recorta con puntos; la flecha nunca, que es lo que dice
   que ahí se toca. */
#moduleNombre{
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  min-width:0;
}
#moduleTitle{ max-width:100%; }

.title-zone h1{
  font-family:'Plus Jakarta Sans', system-ui, sans-serif;
  /* 🔴 EL LOGO, MÁS GRANDE Y CON TOPE. David, 2026-08-31: *«me gustaba
     como estaba antes de grande OMNIXA; ponerlo más grande, pero si se
     aumenta la fuente crece — que no se desborde, que llegue así máximo
     y si sigo creciendo no crezca»*.

     Eso es un `clamp` exacto: un mínimo (nunca más chico que hoy), un
     tamaño que sigue a la letra, y un techo donde se para. Sin el techo,
     al 200% las siete letras y el espaciado de 7px se salen por los
     lados — y a diferencia de un texto normal, aquí no hay dónde partir:
     «OMNIXA» es una sola palabra. */
  /* 🔴 YA NO ES EL LOGO: ES EL NOMBRE DEL MÓDULO (2026-09-14). «OMNIXA»
     salió del topbar porque ya está en el sombrero, y lo que va aquí es
     «Ventas», «Cuentas», «Inventario» — palabras de largo distinto y
     hasta tres veces más largas que las siete letras del logo.
     Con los 38px del logo, «Manufactura» no cabía en 390px de ancho.
     Así que baja a tamaño de título y el `clamp` sigue poniendo el techo
     —que es lo que impedía que se desbordara al subir la letra— y el
     `min` respeta el piso de letra. */
  /* 🔴 MÁS GRANDE desde el 2026-09-14, dictado por David: *«hay que
     aumentarle el tamaño al nombre del topbar, y que si se sale de
     pantalla como Recursos Humanos, que se salga no mucho»* — y esa misma
     tarde, viendo el resultado: *«como está pequeño se ve feo»*.
     De 22 a **30**, con el techo en 38.
     🔑 EL NÚMERO SALE DE UNA MEDIDA, no de un gusto, y con los 21 nombres
     reales de la app: a 30 caben enteros **20 de los 21** (solo «Recursos
     Humanos» se pasa); a 32 caben 19, a 34 caben 17 y a 36 se quedan
     fuera cuatro. *El tamaño se escoge con los nombres que existen, no
     con los que caben.*
     ⚠️ Y 30 es justo lo que se pudo porque «Módulo de» se fue: al lado
     de la palabra, con el nombre a 26 y el prefijo en el piso de letra,
     caían **16 de 21**. Pedir las dos cosas era pedir que una le robara
     el sitio a la otra. */
  font-size:clamp(24px, calc(30px * var(--fs-app, 1)), 38px);
  font-weight:800;
  /* El espaciado también, o al crecer la letra las siete quedan pegadas
     y deja de leerse como el logo que es. */
  /* Y sin el espaciado del logo: separar las letras de «Ventas» no lo
     hace más legible, lo hace más ancho. */
  letter-spacing:normal;
  background:linear-gradient(90deg,var(--marca-clara),var(--marca));
  -webkit-background-clip:text;
  -webkit-text-fill-color:transparent;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  line-height:1.1;
  gap:4px;
}

#titleArrow{
  flex-shrink:0;
  transition:transform .25s ease;
  /* La flecha no hereda el gradient-clip del h1 */
  -webkit-text-fill-color:initial;
  color:var(--primary);
}

.title-zone p{
  /* ⚠️ EL SUBTÍTULO DEL HEADER NO SIGUE A `--fs-base`. Con la base en
     1,6 pasaba a 24px, «Ventas › La venta · General» se partía en dos
     líneas y **empujaba el logo fuera del header** — lo cazó una foto,
     otra vez.

     Y no se arregla haciendo crecer el header, porque David lo quiere al
     revés: *«el topbar, que mínimo sea un dedo y máximo como está»*. Lo
     que tiene que caber en 92px es el contenido, así que este texto
     lleva su propia escala con techo: crece con la preferencia de quien
     mira, no con la base.

     🔴 PERO ERA EL TEXTO MÁS PEQUEÑO DE TODA LA PANTALLA: 15px medidos,
     por debajo de los 17,6 del escalón más bajo. Y en gris. Que no siga
     a `--fs-base` explica por qué no crecía, **no por qué tenía que
     quedarse en el suelo**: el techo es lo que protege al logo, y hasta
     el techo hay sitio. Sube a 17 con techo 21, y lo comprueba la foto —
     que es lo único que puede decir si el logo se corta. */
  font-size:clamp(15px, calc(17px * var(--fs-app, 1)), 21px);
  font-weight:600;
  color:var(--text-soft);
  -webkit-text-fill-color:var(--text-soft); /* no heredar el gradient transparente del h1 */
  background:none;
  margin-top:2px;
  letter-spacing:.2px;
}

.panel-dots{
  display:flex;
  justify-content:center;
  gap:10px;
  padding:8px 0 14px;
}

.dot{
  width:6px;
  height:6px;
  border-radius:50%;
  background:#cfcfe0;
}

.dot.active{
  background:var(--marca);
}

/* Nombre del módulo al lado del logo — solo PC */
#moduleBadge{ display:none; }
@media(min-width:900px){
  #moduleBadge{
    display:inline-block;
    margin-left:14px;
    padding:5px 15px;
    background:rgba(108,53,255,.10);
    color:#4a1d9e;
    font-family:'Plus Jakarta Sans', system-ui, sans-serif;
    font-size:var(--fs-13);
    font-weight:800;
    letter-spacing:1.5px;
    text-transform:uppercase;
    border-radius:99px;
    vertical-align:middle;
  }
  /* El h1 deja de ser bloque para que el badge quede a su lado */
  .title-zone h1{ display:inline-flex; vertical-align:middle; }
  /* Si el badge no tiene texto, no mostrar el fondo (evita la mancha) */
  #moduleBadge:empty{ display:none; }
}

@media(min-width:900px){
  .title-zone p{ display:none !important; }
  .panel-dots{   display:none !important; }
}

/* ══════════════════════════════════════════════════════════
   APP — contenedor principal flex row
══════════════════════════════════════════════════════════ */
#app {
  display: flex;
  /* 🎩 Contra el topbar VIVO, no contra el de Ajustes: al recogerse, el
     panel se queda esos 102px en vez de dejar un hueco abajo. Y la
     transición es la misma que la del topbar, o el panel llegaría a su
     sitio antes o después que él y se vería un salto. */
  height: calc(100dvh - var(--header-vivo));
  transition: height .18s ease;
  overflow: hidden;
}

/* ══════════════════════════════════════════════════════════
   RAILS — copiados exactos del v1
   Móvil: ocultos | PC: position:fixed como el v1
══════════════════════════════════════════════════════════ */
#sbRail, #ecoRail { display:none; }

@media(min-width:900px){
  #sbRail{
    display:flex;
    flex-direction:column;
    align-items:center;
    width:64px;
    flex-shrink:0;
    background:var(--bg);
    border-right:2px solid rgba(108,53,255,.15);
    padding:0;
    gap:0;
    overflow:hidden;
  }
  #ecoRail{
    display:flex;
    flex-direction:column;
    align-items:center;
    width:54px;
    flex-shrink:0;
    background:var(--bg-panel);
    border-left:2px solid rgba(108,53,255,.15);
    padding:0;
    gap:0;
    overflow:hidden;
  }
  /* Header de los rails — misma altura exacta que panel-header */
  .rail-header{
    flex-shrink:0;
    width:100%;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:12px 0;
    height:52px;
    border-bottom:2px solid rgba(108,53,255,.15);
    background:var(--bg);
  }
  /* Footer de los rails — misma altura que panel-footer */
  .rail-footer{
    flex-shrink:0;
    width:100%;
    display:flex;
    align-items:center;
    justify-content:center;
    padding:8px 0;
    height:56px;
    border-top:2px solid rgba(108,53,255,.15);
    background:var(--bg);
  }
  /* Body de los rails — mismo color que panel-body */
  .rail-body{
    flex:1;
    overflow-y:auto;
    overflow-x:hidden;
    width:100%;
    display:flex;
    flex-direction:column;
    align-items:center;
    padding:8px 0;
    gap:4px;
    background:var(--bg-panel);
  }
  /* Header y footer del rail mismo fondo que panel-header */
  .rail-header,
  .rail-footer{
    background:var(--bg);
  }
  /* ecoRail mismo fondo que panel */
  #ecoRail{ background:var(--bg-panel); }
  /* sbRail mismo fondo que panel */
  #sbRail{ background:var(--bg); }
}

/* ══════════════════════════════════════════════════════════
   PANELES — los 3 paneles principales
   Móvil: uno a la vez | PC: los 3 en fila
══════════════════════════════════════════════════════════ */
.panel {
  display: none;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  position: relative;
  /* 🔴 `clip`, NO `hidden` — Y NO ES LO MISMO.
     David, 2026-08-29: *"el header del carrito donde está el buscador
     debe estar fijo, no puede subir con el scroll"*.

     Medido: el panel tiene **106px de sobra** (`scrollHeight` 858 contra
     `clientHeight` 752). Los pone la hoja del eco — `.panel-thumb-sheet`,
     que espera abajo con `translateY(100%)` y **cuenta igual para el área
     de scroll**. Con `overflow:hidden` esa área existe aunque el dedo no
     pueda usarla: **el navegador sí puede desplazarla solo** para dejar a
     la vista el campo que se acaba de enfocar, que es justo lo que hace
     un celular cuando sube el teclado. El panel rueda 106px, y como el
     overlay de la venta vive dentro con `position:absolute`, **se va con
     él**: el header con el buscador sale por arriba de la pantalla y no
     vuelve solo.

     `overflow:clip` recorta igual pero **no crea caja de scroll**: no hay
     nada que desplazar, ni con el dedo ni por foco ni por `scrollTop`.
     Se dejan las dos líneas: la de arriba para el navegador que no
     entienda `clip`, que se queda como estaba y no peor.

     ⚠️ Esto NO se ve en Chromium de escritorio: ahí el teclado no encoge
     el viewport, así que el header nunca sube. Lo que sí se mide, y es lo
     que vigila el banco, es que **ningún ancestro del buscador tenga
     sobra de scroll**. Con `hidden` la sobra es 106; con `clip`, 0. */
  overflow: hidden;
  overflow: clip;
}
.panel.active { display: flex; }

/* Divisor entre paneles — solo PC */
.panel-divider{ display:none; }

@media(min-width:900px){
  /* Los 3 paneles siempre visibles en PC */
  .panel{ display:flex; }

  /* Divisor — copia exacta del v1 */
  .panel-divider{
    display:block;
    width:6px;
    flex-shrink:0;
    background:transparent;
    cursor:col-resize;
    position:relative;
    z-index:10;
    transition:background .15s;
  }
  .panel-divider::after{
    content:'';
    position:absolute;
    top:0;bottom:0;
    left:2px;width:3px;
    border-radius:0;
    background:rgba(108,53,255,.2);
    transition:background .15s;
  }
  .panel-divider:hover::after,
  .panel-divider.dragging::after{
    background:rgba(108,53,255,.7);
  }
  .panel-divider.dragging{
    background:rgba(108,53,255,.05);
  }
  body.resizing{
    cursor:col-resize !important;
    user-select:none !important;
  }
  body.resizing *{ pointer-events:none !important; }
  body.resizing .panel-divider{ pointer-events:all !important; }
}

/* Panel collapse — copia exacta del v1 */
@media(min-width:900px){
  .panel{
    transition:flex .28s cubic-bezier(.4,0,.2,1);
    position:relative;
  }
  .panel-collapsed{
    flex:0 0 32px !important;
    min-width:32px !important;
    overflow:hidden;
    cursor:pointer;
  }
  .panel-collapsed .panel-header,
  .panel-collapsed .panel-body,
  .panel-collapsed .panel-footer{
    visibility:hidden;
    opacity:0;
    pointer-events:none;
  }
  .panel-collapsed::before{
    content:attr(data-title);
    position:absolute;
    top:50%;left:50%;
    transform:translate(-50%,-50%) rotate(-90deg);
    white-space:nowrap;
    font-size:var(--fs-15);font-weight:800;
    letter-spacing:2.5px;text-transform:uppercase;
    color:rgba(108,53,255,.7);
    pointer-events:none;
    z-index:2;
  }
  .panel-collapsed::after{
    content:none;
  }
  .panel-will-collapse .panel-header,
  .panel-will-collapse .panel-body,
  .panel-will-collapse .panel-footer{
    opacity:.4;
    transition:opacity .15s;
  }
  .panel-focused .panel-body{
    box-shadow:0 0 0 2px rgba(108,53,255,.18),0 10px 30px rgba(0,0,0,.08);
    transition:box-shadow .2s;
  }
}

/* Header del panel */
.panel-header {
  flex-shrink: 0;
  display: none;
  padding: 12px 16px;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.panel-header h2 {
  font-size:var(--fs-20);
  font-weight: 800;
  color: var(--text);
}

@media(min-width:900px) {
  .panel-header {
    display: flex;
    position: sticky;
    top: 0;
    z-index: var(--z-overlay);
    border-radius: var(--panel-radius) var(--panel-radius) 0 0;
  }
}

/* Body del panel — área de scroll */
.panel-body {
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  /* 🎨 EL FONDO DEL IXA, con el de siempre de respaldo.
     David, 2026-09-04: *«Envixa debe tener identidad y conecta todos los
     módulos»*. La ropa la pone `armonia.vestir()` desde `abrirModulo()`,
     así que aquí no hay nada que acordarse: un módulo sin paleta no tiene
     `--ixa-fondo` puesta y se queda exactamente como hoy.

     🔴 Y VA EN EL PANEL, NO EN CADA PANTALLA. Se probó primero en
     `nucleo/vitrina.js` y la foto lo dijo enseguida: el panel 2 salía
     crema y el 1 gris — **la identidad partida a la mitad del mismo
     módulo**, que se ve peor que no tenerla. Aquí visten los tres a la
     vez y ninguna pantalla tiene que enterarse. */
  background: var(--ixa-fondo, var(--bg-panel));
  padding-bottom: 80px;
}

/* Footer del panel — pill flotante en móvil, oculto en PC */
/* ── Footer pill flotante — móvil, oculto en PC ────────── */
.panel-footer {
  position: absolute;
  bottom: var(--pie-abajo);
  left: 10px;
  right: 10px;
  background: var(--bg-white);
  border-radius: 50px;
  box-shadow: var(--pie-sombra);
  display: flex;
  align-items: stretch;
  padding: 0 4px;
  /* 🔴 EL FOOTER CRECE CON LA LETRA. David, 2026-08-31: *«el footer, veo
     que en todo Omnixa no sube con el tamaño, se queda pequeño; entonces
     aumentarle, que sea más alto»*.

     Eran 44px clavados, y esa es la parte que se ve: con la letra en su
     normal nuevo un botón del pie mide casi eso él solo, así que el
     footer quedaba **del tamaño exacto de su contenido** — sin aire, y
     sin crecer al subir la letra porque el número no dependía de nada.

     🔑 `--fs-app` y no `--fs-base`: la base ya está dentro de los
     `--fs-*` del texto de los botones, que empujan por dentro. Lo que
     esto añade es que el pie **también responda a la preferencia de
     quien mira**, que es lo que él no veía moverse. Y con techo: un pie
     de 100px se come la lista que está encima. */
  /* 🔑 LO QUE MIDE EL PIE SE DECLARA UNA VEZ, en `:root` (`--pie-alto` y
     `--pie-abajo`), porque hay OTRA pieza que se coloca respecto a él:
     la rayita de subir (`.panel-thumb-handle`). Esa llevaba el número
     escrito a mano —`44px`, el alto del pie de antes— y por eso quedaba
     **dentro** del óvalo en vez de encima. *Un número en píxeles no
     sigue a lo que mide otra cosa.* */
  min-height: var(--pie-alto);
  z-index: 10;
}

.fp-btn{
  flex: 1;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 6px 14px;
  border: none;
  border-radius: 50px;
  background: transparent;
  cursor: pointer;
  transition: background var(--t);
  white-space: nowrap;
}
.fp-btn:active{ background: var(--primary-soft); }

.fp-btn svg{
  display: block;
  flex-shrink: 0;
  stroke: var(--primary);
  color: var(--primary);
  fill: none;
}

.fp-btn .fp-label{
  font-size:var(--fs-16);
  font-weight: 800;
  line-height: 1;
  color: var(--primary);
}

.fp-btn + .fp-btn{ border-left: 1px solid var(--border); }

/* ══ PIEZA 8 del creador — en PC el PIE SUBE AL HEADER ══════
   David lo dictó así: "cada panel o overlay tiene un footer, y en PC
   esas opciones del footer pasan al header de los paneles u overlay".
   Antes en PC el footer se escondía (display:none) y esos botones
   —Crear, Cobrar, lo que el módulo pusiera— simplemente NO EXISTÍAN en
   escritorio.

   Se hace con CSS a propósito: NADA se mueve en el DOM. El módulo sigue
   pintando su footer igual que siempre y sus listeners viven donde
   estaban; aquí solo se COLOCA en la franja del header. Por eso vale
   para todos los módulos de una vez, incluidos los que todavía no se
   han tocado, y ninguno tiene que resolver su versión de PC a mano.

   Ojo: las reglas de Tablixa (body.tablixa…) y las de panel colapsado
   son más específicas y siguen mandando sobre esto.               */
@media(min-width:900px){
  .panel-footer{
    display:flex;
    top:5px;   bottom:auto;         /* de abajo del panel, a la franja del header */
    left:auto; right:12px;          /* anclado a la derecha */
    width:auto; max-width:62%;      /* el título se queda con su espacio */
    /* En PC el pie vive DENTRO de la franja del header, así que su alto
       lo manda el header y no la letra: si creciera aquí, se saldría de
       la franja. Por eso conserva su número, y por eso el `min-height`
       de arriba no vale para todos los anchos. */
    min-height:34px;
    padding:0 2px;
    border-radius:12px;
    background:transparent;         /* se funde con el header */
    box-shadow:none;
    overflow-x:auto;
    z-index:6;                      /* por encima del header sticky */
  }
  .panel-footer::-webkit-scrollbar{ display:none; }
  /* En el pie los botones se reparten el ancho (flex:1); arriba van
     compactos, del tamaño de su contenido. */
  .panel-footer .fp-btn{
    flex:0 0 auto;
    padding:6px 10px;
    border-radius:10px;
  }
  /* Si el módulo pone muchos botones, el título recorta con puntos
     suspensivos en vez de meterse debajo. */
  .panel-header h2{
    overflow:hidden;
    text-overflow:ellipsis;
    white-space:nowrap;
  }
}

/* ── Sheet backdrop global ───────────────────────────── */
#sheetBackdrop{
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,.45);
  z-index: calc(var(--z-sheet) - 1);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}
#sheetBackdrop.open{ display: block; }

/* ══════════════════════════════════════════════════════════
   TOAST — notificaciones temporales
══════════════════════════════════════════════════════════ */
#toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: #1f2937;
  color: var(--text-inv);
  padding: 12px 18px;
  border-radius: 16px;
  font-size:var(--fs-14);
  font-weight: 600;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s, transform .25s;
  z-index: var(--z-toast);
  /* 🔴 SE PUEDE LEER (David, 2026-08-27: *"el toast es largo y no puedo
     leer, debería ser de dos filas"*).

     Antes tenía `white-space: nowrap` y un borde redondo de píldora: un
     aviso de una línea se veía bien, pero **cualquier frase larga se salía
     de la pantalla por los dos lados** y no se leía ni el principio ni el
     final. Justo los avisos que importan —los que explican por qué algo no
     subió— son los largos.

     Ahora se dobla en dos o tres renglones, con el ancho topado a lo que
     cabe en un celular, y centrado. La píldora se vuelve un rectángulo
     redondeado, que es lo que pide un texto de varias líneas. */
  white-space: normal;
  max-width: min(92vw, 420px);
  text-align: center;
  line-height: 1.35;
  /* Tres renglones y lo que sobre se corta con puntos: un aviso que tapa
     media pantalla estorba más de lo que explica. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
#toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Zona activa por teclado — borde morado arriba */
@media(min-width:900px){
  .zona-activa-teclado > .panel-header,
  .zona-activa-teclado > .rail-header{
    border-top: 3px solid var(--primary);
  }
}

/* ══════════════════════════════════════════════════════════
   TECLADO — navegación por .nav-item / .nav-focused
══════════════════════════════════════════════════════════ */
.nav-item{
  cursor: pointer;
  transition: outline var(--t), box-shadow var(--t);
}
.nav-focused {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--primary-soft);
  border-radius: inherit;
  position: relative;
  z-index: 2;
}
/* Módulos del sidebar con foco */
.sb-module.nav-focused {
  background: var(--ico-active-bg);
  outline: none;
  box-shadow: none;
}

/* ══════════════════════════════════════════════════════════
   SCROLLBAR — estilo Vercel (delgada, solo al hover)
══════════════════════════════════════════════════════════ */
.panel-body{
  scrollbar-width:thin;
  scrollbar-color:transparent transparent;
  transition:scrollbar-color .2s;
}
.panel-body:hover{
  scrollbar-color:rgba(108,53,255,.3) transparent;
}
.panel-body::-webkit-scrollbar{
  width:4px;
  transition:width .2s;
}
.panel-body::-webkit-scrollbar-track{
  background:transparent;
}
.panel-body::-webkit-scrollbar-thumb{
  background:transparent;
  border-radius:99px;
}
.panel-body:hover::-webkit-scrollbar-thumb{
  background:rgba(108,53,255,.3);
}
.panel-body:hover::-webkit-scrollbar-thumb:hover{
  background:rgba(108,53,255,.6);
}
.panel-body:hover::-webkit-scrollbar:hover{
  width:8px;
}

/* ══════════════════════════════════════════════════════════
   PANEL FOCUSED — hover en PC

   DESACTIVADO (2026-07-21). El efecto de "crecer al pasar el mouse"
   choca con redimensionar y colapsar paneles: para agarrar el
   divisor hay que mover el mouse hacia él, y al hacerlo se pasa
   sobre un panel que crece y desplaza el divisor justo cuando
   ibas a agarrarlo.

   Para reactivarlo, descomentar el bloque de abajo. Las exclusiones
   :not(.panel-collapsed) y :not(:hover) son necesarias — sin ellas
   los paneles comprimidos disparan el efecto, y la regla que encoge
   le gana por especificidad a la que agranda (:has() suma el peso
   de su argumento), encogiendo el panel que estás señalando.
══════════════════════════════════════════════════════════ */
@media(min-width:900px){
  /*
  #app:has(.panel:hover:not(.panel-collapsed)) .panel:not(.panel-collapsed):not(:hover){
    flex:0.85;
    transition:flex .25s cubic-bezier(.4,0,.2,1);
  }
  #app .panel:hover:not(.panel-collapsed){
    flex:1.3;
    transition:flex .25s cubic-bezier(.4,0,.2,1);
  }
  */
  .panel-collapsed{
    flex:0 0 32px !important;
  }
}

/* ══════════════════════════════════════════════════════════
   SIDEBAR — v3 azul claro
══════════════════════════════════════════════════════════ */
#sidebar{
  position:fixed;
  top:0; left:0; bottom:0;
  width:var(--sidebar-w);
  background:var(--sb-bg);
  color:var(--sb-text);
  z-index:var(--z-sidebar);
  display:flex;
  flex-direction:column;
  transform:translateX(-100%);
  transition:transform var(--ts);
  overflow:hidden;
}
#sidebar.open{ transform:translateX(0); }

#sb-scroll{
  flex:1;
  min-height:0;
  overflow-y:auto;
  display:flex;
  flex-direction:column;
  scrollbar-width:thin;
  scrollbar-color:transparent transparent;
}
#sb-scroll:hover{
  scrollbar-color:rgba(59,109,212,.2) transparent;
}

#sbBackdrop{
  display:none;
  position:fixed;
  inset:0;
  background:rgba(0,0,0,.5);
  z-index:var(--z-backdrop);
  backdrop-filter:blur(2px);
}
#sbBackdrop.open{ display:block; }

/* ── Header ──────────────────────────────────────────── */
#sb-header{
  flex-shrink:0;
  padding:20px 18px 16px;
  background:var(--sb-bg-head);
  border-bottom:1px solid var(--sb-border);
}
#sb-omnixa-logo{
  /* 🔴 MÁS GRANDE — David, 2026-08-31: *«y el OMNIXA del sidebar más
     grande»*. Estaba en 30px CRUDOS, o sea que no obedecía a `--fs-app`:
     quien subía la letra de la app la subía en todas partes menos aquí,
     y encima el nombre quedaba más chico que el «Omnixa SAS» de debajo,
     que sí crece. Los dos problemas en una línea, como dice la ley del
     piso de letra.
     Con techo, y no es adorno: el sidebar mide 300px y «OMNIXA» lleva
     seis letras con espaciado — sin `clamp` se sale por la derecha en
     cuanto se sube la letra, que es cómo se cortó el logo del topbar
     dos veces el mismo día. */
  font-size:clamp(30px, calc(40px * var(--fs-app, 1)), 54px);
  font-weight:800;
  letter-spacing:clamp(3px, calc(4px * var(--fs-app, 1)), 6px);
  color:var(--sb-logo-color);
  line-height:1;
  margin-bottom:10px;
}
#sb-empresa{
  font-size:var(--fs-18);
  font-weight:700;
  color:var(--sb-empresa);
  margin-bottom:3px;
}
#sb-rol{
  font-size:var(--fs-12);
  font-weight:500;
  color:var(--sb-cargo);
}

/* ── Buscador ───────────────────────────────────────── */
#sb-search{
  flex-shrink:0;
  display:flex;
  align-items:center;
  gap:8px;
  margin:12px 12px 4px;
  padding:10px 14px;
  background:#fff;
  border-radius:12px;
  border:1px solid var(--sb-border);
  transition:border-color var(--t);
}
#sb-search:focus-within{ border-color:rgba(29,78,216,.4); }
#sb-search svg{ flex-shrink:0; color:var(--sb-label); }
#sb-search-input{
  flex:1;
  border:none;
  background:transparent;
  font-size:var(--fs-13);
  color:var(--sb-text);
  outline:none;
}
#sb-search-input::placeholder{ color:var(--sb-label); }

/* ── Etiquetas de sección ─────────────────────────────── */
.sb-section-label{
  flex-shrink:0;
  font-size:var(--fs-10);
  font-weight:700;
  color:var(--sb-label);
  text-transform:uppercase;
  letter-spacing:1.2px;
  padding:18px 16px 4px;
}

/* ── Módulos ──────────────────────────────────────────── */
#sb-modules{
  flex-shrink:0;
  padding:2px 8px;
  display:flex;
  flex-direction:column;
  gap:1px;
}

.sb-module{
  display:flex;
  align-items:center;
  gap:12px;
  padding:9px 10px;
  border-radius:12px;
  cursor:pointer;
  transition:background var(--t);
}
.sb-module:hover{ background:rgba(29,78,216,.06); }
.sb-module.active{ background:var(--ico-active-bg); }

.sb-ico{
  flex-shrink:0;
  color:var(--ico-default);
  transition:color var(--t);
}
.sb-module.active .sb-ico{ color:var(--ico-active); }

/* Ajuste realizado por ChatGPT (2026-10-19): conservar cada nombre de módulo en una sola línea; si no cabe, mostrar puntos suspensivos. */
.sb-nombre{
  flex:1;
  min-width:0;
  font-size:var(--fs-15);
  font-weight:600;
  color:var(--sb-text-soft);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  transition:color var(--t);
}
.sb-module.active .sb-nombre{ color:var(--ico-active); font-weight:700; }

.sb-chevron{ flex-shrink:0; color:#93c5fd; }

/* ── Separador ──────────────────────────────────────── */
/*  🔴 SE VE, Y LO QUE FALLABA ERA EL COLOR — NO EL GROSOR (2026-09-30).
    David, con el menú en «solo iconos»: *«no se ven las líneas
    separadoras de módulos y ixas y herramientas»*. Medido: `--sb-border`
    es `#e2e8f0` sobre el `#f8fafc` del menú — **1,18 : 1**, que es
    exactamente la misma cifra del asa del eco de ayer, en otra pieza.
    🔑 *Engordar una raya invisible deja una raya gorda que tampoco se
    ve*, así que se pide **`--pie-raya`** —el morado al 70%, **3,39 : 1**—,
    que es la receta que ya se midió y que sale del tema: escrita a mano,
    un tema verde dejaría la raya morada. */
/*  📏 Y EL AIRE ENTRE CATEGORÍAS SE DOBLA (2026-10-01). David: *«más
    espacio entre categorías, o sea módulos e ixas, más espacios»*.
    🔑 Lo que separa dos grupos no es la raya: es el hueco. Con 6 arriba
    y 6 abajo quedaban **12px** entre el último módulo y el primer ixa —
    *menos que el hueco entre dos filas del mismo grupo*, así que la raya
    tenía que hacer sola todo el trabajo de decir «aquí empieza otra
    cosa». Con 16 son 32, y los grupos se leen sin leer.
    ⚠️ Y 6 no estaba en la escala (4, 8, 12, 16, 24, 32); 16 sí. */
.sb-sep{
  flex-shrink:0;
  height:1px;
  background:var(--pie-raya, var(--sb-border));
  margin:16px;
}

/* ── Familia ixa ──────────────────────────────────────── */
#sb-family{
  flex-shrink:0;
  padding:2px 8px;
  display:flex;
  flex-direction:column;
  gap:1px;
}
.sb-family-text{
  flex:1;
  display:flex;
  flex-direction:column;
  gap:1px;
  min-width:0;
}
.sb-family-name{
  font-size:var(--fs-15);
  font-weight:600;
  color:var(--sb-text-soft);
  transition:color var(--t);
}
.sb-module.active .sb-family-name{ color:var(--ico-active); font-weight:700; }
.sb-family-sub{
  font-size:var(--fs-11);
  color:var(--sb-label);
}

/* ── Footer ───────────────────────────────────────────── */
#sb-footer{
  flex-shrink:0;
  display:flex;
  align-items:center;
  gap:10px;
  padding:14px 16px;
  border-top:1px solid var(--sb-border);
  background:var(--sb-bg-head);
}
#sb-user-avatar{
  width:36px; height:36px;
  border-radius:50%;
  background:var(--ico-active-bg);
  color:var(--ico-active);
  font-size:var(--fs-13);
  font-weight:800;
  display:flex;
  align-items:center;
  justify-content:center;
  flex-shrink:0;
}
#sb-user-info{ flex:1; min-width:0; }
#sb-user-name{
  font-size:var(--fs-13);
  font-weight:700;
  color:var(--sb-empresa);
}
#sb-user-status{
  display:flex;
  align-items:center;
  gap:5px;
  font-size:var(--fs-11);
  font-weight:600;
  color:#22c55e;
  margin-top:2px;
}
#sb-user-status.offline-text{ color:var(--peligro-vivo); }
.sb-status-dot{
  width:6px; height:6px;
  border-radius:50%;
  flex-shrink:0;
}
.sb-status-dot.online{ background:#22c55e; }
.sb-status-dot.offline{ background:var(--peligro-vivo); }
.sb-status-dot.slow{ background:var(--ambar-vivo); }

#sb-config-btn{
  width:32px; height:32px;
  border-radius:10px;
  display:flex;
  align-items:center;
  justify-content:center;
  color:var(--sb-label);
  transition:background var(--t),color var(--t);
  flex-shrink:0;
}
#sb-config-btn:hover{
  background:rgba(29,78,216,.08);
  color:var(--ico-active);
}

/* ══════════════════════════════════════════════════════════
/* ══════════════════════════════════════════════════════════
   ECO PANEL — sidebar derecho, mismo sistema que sidebar izq
══════════════════════════════════════════════════════════ */
#ecoPanel{
  position:fixed;
  top:0; right:0; bottom:0;
  /* 🖥️ Crece hacia la izquierda arrastrando su borde (David, 2026-09-14:
     *«como el sidebar se puede agrandar arrastrando hacia la derecha, el
     eco crezca hacia la izquierda»*). Lo mueve `nucleo/estirar.js` con la
     pieza `ecoPC`; el borde lo fabrica `nucleo/pc-eco.js`. Sin nadie que
     la escriba, el respaldo deja el ancho de siempre. */
  width:calc(var(--sidebar-w) + var(--eco-pc-extra, 0px));
  background:var(--sb-bg);
  color:var(--sb-text);
  z-index:var(--z-sidebar);
  display:flex;
  flex-direction:column;
  transform:translateX(100%);
  transition:transform var(--ts);
  /* 🔴 `clip`, NO `hidden` (2026-09-14). Es la lección de
     `probar-carrito-fijo-dedo.js` repetida aquí: **`overflow:hidden`
     recorta pero deja la caja de scroll viva**, y el navegador la usa
     para dejar a la vista el campo que se enfoca. Medido: al tocar el
     buscador del eco, el panel se quedaba con `scrollLeft:18` — o sea
     **el eco entero desplazado 18px**, con su borde de estirar fuera de
     sí mismo (x=962 con el panel en 980) y el dedo cayendo en el fondo.
     Sin un solo error: lo único que se ve es que el tirador no responde.
     Con `clip` no hay caja de scroll que desplazar. */
  overflow:clip;
  border-left:1px solid var(--sb-border);
}
#ecoPanel.open{ transform:translateX(0); }

#ecoBackdrop{
  display:none;
  position:fixed;
  inset:0;
  background:rgba(0,0,0,.5);
  z-index:var(--z-backdrop);
  backdrop-filter:blur(2px);
}
#ecoBackdrop.open{ display:block; }

#eco-header{
  flex-shrink:0;
  padding:20px 18px 16px;
  background:var(--sb-bg-head);
  border-bottom:1px solid var(--sb-border);
}
#eco-omnixa-logo{
  font-size:30px;
  font-weight:800;
  letter-spacing:3px;
  color:var(--sb-logo-color);
  margin-bottom:6px;
}
#eco-title{
  font-size:var(--fs-18);
  font-weight:700;
  color:var(--sb-empresa);
  margin-bottom:3px;
}
#eco-modulo-label{
  font-size:var(--fs-12);
  font-weight:500;
  color:var(--sb-cargo);
}

#eco-search{
  flex-shrink:0;
  display:flex;
  align-items:center;
  gap:8px;
  margin:12px 12px 4px;
  padding:10px 14px;
  background:#fff;
  border-radius:12px;
  border:1px solid var(--sb-border);
  transition:border-color var(--t);
}
#eco-search:focus-within{ border-color:rgba(29,78,216,.4); }
#eco-search svg{ flex-shrink:0; color:var(--sb-label); }
#eco-search-input{
  flex:1; border:none; background:transparent;
  font-size:var(--fs-13); color:var(--sb-text); outline:none;
}
#eco-search-input::placeholder{ color:var(--sb-label); }

#eco-scroll{
  flex:1; min-height:0; overflow-y:auto;
  scrollbar-width:thin; scrollbar-color:transparent transparent;
  padding:6px 8px 12px;
}
#eco-scroll:hover{ scrollbar-color:rgba(59,109,212,.2) transparent; }

.eco-seccion{ margin-bottom:2px; }

.eco-sec-btn{
  width:100%; display:flex; align-items:center; gap:12px;
  padding:10px 10px; background:none; border:none; cursor:pointer;
  text-align:left; transition:background var(--t); border-radius:12px;
}
.eco-sec-btn:hover{ background:rgba(29,78,216,.05); }
.eco-sec-btn.activa{ background:var(--ico-active-bg); }

.eco-sec-ico{
  width:30px; height:30px; border-radius:9px;
  display:flex; align-items:center; justify-content:center;
  flex-shrink:0; background:rgba(59,109,212,.08);
  color:var(--ico-default); transition:background var(--t),color var(--t);
}
.eco-sec-btn.activa .eco-sec-ico{ background:var(--ico-active-bg); color:var(--ico-active); }

.eco-sec-nombre{
  flex:1; font-size:var(--fs-14); font-weight:700;
  color:var(--sb-text-soft); transition:color var(--t);
}
.eco-sec-btn.activa .eco-sec-nombre{ color:var(--ico-active); }

.eco-sec-badge{
  font-size:var(--fs-10); font-weight:700; padding:2px 7px;
  border-radius:99px; background:var(--ico-active-bg); color:var(--ico-active);
}
.eco-sec-chevron{
  flex-shrink:0; color:#93c5fd;
  transition:transform var(--t); font-size:var(--fs-15);
}
.eco-sec-btn.activa .eco-sec-chevron{ transform:rotate(90deg); }

.eco-opciones{
  display:none;
  padding:2px 6px 6px 46px;
}
.eco-opciones.abierta{ display:block; }

.eco-opcion{
  display:flex; align-items:center; gap:10px;
  padding:7px 8px; border-radius:10px; cursor:pointer;
  transition:background var(--t); margin-bottom:1px;
}
.eco-opcion:hover{ background:rgba(29,78,216,.05); }

.eco-op-ico{
  width:26px; height:26px; border-radius:7px;
  display:flex; align-items:center; justify-content:center;
  flex-shrink:0; color:var(--ico-default);
}
.eco-op-ico svg{ width:16px; height:16px; }

.eco-op-texto{ flex:1; min-width:0; }
.eco-op-nombre{ font-size:var(--fs-13); font-weight:600; color:var(--sb-text); line-height:1.2; }
.eco-op-desc{ font-size:var(--fs-11); color:var(--sb-label); margin-top:1px; }

.eco-toggle{
  width:34px; height:19px; border-radius:10px;
  background:var(--sb-border); position:relative;
  flex-shrink:0; cursor:pointer; transition:background var(--t); border:none;
}
.eco-toggle.on{ background:var(--ico-active); }
.eco-toggle::after{
  content:''; position:absolute;
  width:15px; height:15px; border-radius:50%;
  background:#fff; top:2px; left:2px; transition:left var(--t);
}
.eco-toggle.on::after{ left:17px; }

#eco-footer{
  flex-shrink:0; display:flex; align-items:center;
  justify-content:space-between; padding:14px 16px;
  border-top:1px solid var(--sb-border); background:var(--sb-bg-head);
}
#eco-footer-info{ font-size:var(--fs-12); color:var(--sb-label); }
#eco-restaurar-btn{
  font-size:var(--fs-12); font-weight:700; color:var(--ico-active);
  background:none; border:none; cursor:pointer;
  padding:4px 10px; border-radius:8px; transition:background var(--t);
}
#eco-restaurar-btn:hover{ background:var(--ico-active-bg); }

/* Móvil: eco panel oculto — usa sheet en su lugar */
@media(max-width:899px){
  #ecoPanel{ display:none; }
}

/* ── Eco sheet móvil ──────────────────────────────────── */
#eco-sheet-mobile{
  display:none;
  position:fixed;
  bottom:0; left:0; right:0;
  background:var(--bg-white);
  border-radius:24px 24px 0 0;
  box-shadow:0 -4px 30px rgba(108,53,255,.12);
  z-index:var(--z-sheet);
  transform:translateY(100%);
  transition:transform var(--ts);
  flex-direction:column;
  overflow:hidden;
  /* 🔴 EL ALTO LO DECIDE EL DUEÑO, entre lo de siempre y dedo y medio
     más. David, 2026-08-31: *«si presiono esa raya arriba del eco puedo
     subirlo, dándole máximo un dedo y medio más de espacio para que
     quepan más opciones; o sea que ese sea el tamaño mínimo y pueda
     rodar a mi antojo»*.

     440 sigue siendo el mínimo —lo que mide hoy— y `--eco-extra` es lo
     que se ganó arrastrando (`nucleo/estirar.js`, tope 84px). El
     `max-height` se queda: por mucho que se arrastre, la hoja no puede
     comerse la pantalla entera. */
  height:calc(440px + var(--eco-extra, 0px));
  /* 90 y no 80: con el tope en cuatro dedos (2026-09-11) el 80% topaba
     antes que el arrastre. Sigue sin comerse la pantalla entera. */
  max-height:90dvh;
}
/* 🔑 LA RAYA ES LA MANIJA, y por eso se agranda su zona de toque sin
   agrandar la rayita. Lo que se ve son 4px; lo que atrapa el dedo son
   24 — una raya de 4px es imposible de agarrar, y quien no la agarra
   concluye que no se puede mover. */
/* (Lo que declaraba esta regla —el relleno que agranda la zona de
   agarre, el `background-clip` y el cursor— vive ahora en la de abajo,
   junta con el tamaño y el color. Ver su nota.) */
#eco-sheet-mobile.open{
  display:flex;
  transform:translateY(0);
}
/* 🔴 Y LA MISMA HISTORIA AQUÍ, y peor: `var(--border)` es el morado al
   **10%** —**1,18 : 1**, la mitad de contraste que la del pie—, así que
   el asa que se arrastra para subir el eco **existía y no se veía**.
   Misma variable del tema y mismo grosor de la escala.
   ⚠️ Y ESTA REGLA ESTABA DECLARADA DOS VECES para el mismo id, separadas
   por `#eco-sheet-mobile.open` en medio: la de arriba ponía el padding y
   el `background-clip`, esta el tamaño y el margen. *Dos reglas del
   mismo id se separan solas, y manda la última sin que se vea desde la
   primera* — ahora es una sola. */
/* 🔴 Y LA ZONA DE AGARRE ES EL NODO; LA RAYA, SU `::after` — el mismo
   patrón que ya usaba la variante de color, y ahora los dos igual.
   ⚠️ Con `padding` no se puede: **hay `box-sizing:border-box` global**,
   así que la altura final es el mayor de `height` y el relleno, no la
   suma — con 8 de raya y 8+8 de aire la caja medía **16**, no 24, y el
   banco lo cazó en el primer intento. *Y estaba escrito cuatro reglas más
   abajo, en el comentario de la variante de color: «con ese border-box,
   los 24px de agarre nunca existieron».* */
#eco-sheet-handle{
  width:48px; height:24px;
  background:transparent;
  margin:8px auto 0;
  position:relative;
  touch-action:none;
  cursor:ns-resize;
  flex-shrink:0;
}
#eco-sheet-handle::after{
  /* 📏 4px desde el 2026-09-30 (ver `.panel-thumb-handle::after`), y
     ⚠️ centrada con `top:50%` en vez de un número: la zona de agarre son
     24 y la raya 4, así que un `top` escrito a mano hay que recalcularlo
     cada vez que cambie el grosor — y el día que no se recalcule, la
     raya se queda torcida sin que nada dé error. */
  content:''; position:absolute; left:0; right:0; height:4px;
  top:50%; transform:translateY(-50%);
  border-radius:99px;
  background:var(--pie-raya, rgba(108,53,255,.7));
}
#eco-sheet-header{
  flex-shrink:0;
  padding:14px 20px 12px;
  border-bottom:1px solid var(--border);
  display:flex;
  align-items:center;
  justify-content:space-between;
  flex-wrap:wrap;              /* la rejilla de atajos va debajo del título, dentro del color */
  position:relative;
}

/* ══════════════════════════════════════════════════════════════════
   EL ECO CON CURVA Y BRILLO — David, 2026-09-11, con una maqueta al
   lado: *«lo que me gusta: las esquinas con curva, un brillo, que el
   header tenga color, y los accesos directos no importa que no tengan
   color, como están dentro del color del header se ven bien; y el body
   con esos bordes ovalados y también brillo»*.

   🔑 Todo va por CLASES y no inline: `ui/eco.js` tenía 29 espacios
   fuera de la escala y 4 colores a mano (medido con `armonia.revisar`),
   y esa es la causa medible de «feo». Aquí cada medida sale de la
   escala (4·8·12·16·24·32), cada color del tema, y el brillo es UNA
   luz arriba a la izquierda — no un efecto por elemento.

   ⚠️ El header conserva su línea de abajo: `probar-eco-nuevo-dedo.js`
   mide que exista (`borderBottomWidth > 0`) y que el header tenga OTRO
   color que el cuerpo. Las dos cosas siguen siendo verdad.
══════════════════════════════════════════════════════════════════ */
/* 🔑 EL COLOR ES DE LA HOJA, NO DEL HEADER (foto de David, 2026-09-11):
   con el color solo en el header, la hoja subía blanca y la rayita
   quedaba gris sobre blanco, casi invisible. En la referencia el color
   arranca en el borde de la hoja y la rayita va blanca, dentro del color.
   Así que el degradado y el brillo los lleva `#eco-sheet-mobile`, y el
   header queda transparente encima — el banco sigue midiendo que el
   header tiene OTRO color que el cuerpo (transparente ≠ blanco). */
#eco-sheet-mobile.eco-hoja-color{
  background:linear-gradient(135deg, var(--marca) 0%, var(--marca-honda) 100%);
}
#eco-sheet-mobile.eco-hoja-color::before{
  content:''; position:absolute; inset:0; pointer-events:none;
  background:radial-gradient(120% 70% at 8% 0%, rgba(255,255,255,.30), transparent 55%);
}
/* 🔴 LA RAYA ES UNA PIEZA PROPIA, no el fondo recortado. Tres vueltas
   costó: con `background:` se pintaba la caja de agarre entera (gorda);
   con `background-clip:content-box` no se pintaba NADA, porque el
   `box-sizing:border-box` global hace que `height:4px` + `padding:10px`
   dé un contenido de cero. Y de paso: con ese `border-box`, los «24px de
   agarre» del comentario de arriba nunca existieron — la caja medía 4.
   Ahora el handle ES la zona de agarre (48×24, transparente) y la raya
   es su `::after`, 4px, blanca, centrada. Se ven 4, se agarran 24, de
   verdad. */
#eco-sheet-mobile.eco-hoja-color #eco-sheet-handle{
  width:48px; height:24px; padding:0; margin:4px auto 0;
  background:transparent; border-radius:0; position:relative;
}
/* Sobre el header de color el blanco al 70% sí se lee, así que aquí el
   color estaba bien; lo que se iguala es el grosor, para que la misma
   pieza no mida dos cosas según el tema. */
#eco-sheet-mobile.eco-hoja-color #eco-sheet-handle::after{
  content:''; position:absolute; left:0; right:0; height:4px;
  top:50%; transform:translateY(-50%);
  border-radius:99px; background:rgba(255,255,255,.7);
}
#eco-sheet-header.eco-head-color{
  background:transparent;
  border-bottom:1px solid var(--marca-linea);
  padding:8px 16px 32px;
  margin-bottom:-24px;         /* el cuerpo se monta encima con su propia curva */
  color:var(--blanco);
}
#eco-sheet-header.eco-head-color > *{ position:relative; }
#eco-sheet-header.eco-head-color #eco-sheet-titulo{ color:var(--blanco); }
#eco-sheet-header.eco-head-color #eco-sheet-sub{ color:var(--blanco); opacity:.85; }
.eco-head-chip{
  width:clamp(38px, calc(34px * var(--fs-app,1) * var(--fs-base,1)), 52px);
  height:clamp(38px, calc(34px * var(--fs-app,1) * var(--fs-base,1)), 52px);
  border-radius:12px;
  border:1px solid rgba(255,255,255,.28);
  background:rgba(255,255,255,.18);
  color:var(--blanco);
  display:flex; align-items:center; justify-content:center;
  flex-shrink:0; cursor:pointer; font-family:inherit;
  font-size:var(--fs-20); line-height:1;
}
#eco-head-rejilla{
  flex-basis:100%; width:100%;
  display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px;
  margin-top:16px;
}
#eco-head-rejilla:empty{ display:none; }
.eco-atajo{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px;
  min-height:96px; padding:12px 4px; border-radius:14px; border:none;
  background:var(--blanco); color:var(--texto);
  cursor:pointer; font-family:inherit;
  box-shadow:0 6px 16px rgba(15,23,42,.14);
}
.eco-atajo-ico{
  width:40px; height:40px; border-radius:12px; background:var(--marca-suave);
  display:flex; align-items:center; justify-content:center; flex-shrink:0; pointer-events:none;
}
.eco-atajo-nombre{
  font-size:var(--fs-11); font-weight:700; color:var(--texto); text-align:center; line-height:1.15;
  /* 🔴 `width:100%` O EL SPAN SE SALE DE SU CELDA. Medido el 2026-09-28:
     «Registradora» en una columna de 80,5px daba un span de **128px** —
     el flex lo dimensiona por su contenido, y el `overflow:hidden`
     recorta lo que se pinta pero **no achica la caja**. Se salía por
     encima de la tarjeta de al lado, y de paso hacía que la pieza que
     encoge el nombre midiera una caja que no existía y no encogiera
     nunca. *Un `overflow:hidden` no es un ancho.* */
  width:100%;
  /* 🔴 `anywhere` NO: rompía la palabra por donde fuera («Cambi/ar card»).
     `break-word` solo parte la que no cabe ni sola en la línea — medido:
     de 29 nombres partidos de 81 se pasa a 3 (Presupuesto, Registradora,
     Resultados), y esos tres se arreglan con su `corto:`, no con tijera. */
  overflow-wrap:break-word;
  pointer-events:none; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
}
/* 🎨 EL PLANO DEL MENÚ — el fondo de color PIDE tarjetas.
   Reportado por David el 2026-10-09: *«no sé si es por el fondo, lo
   demás del eco se ve bien, **este mal**»*, con la hoja del menú
   delante y el tema verde puesto.
   📏 Medido: en la lista y en el ⚙ las filas son blancas y quedan a
   **41** de distancia del fondo; en el menú van **transparentes** —su
   marco se fue el 2026-10-01 por dictado suyo— así que el fondo se les
   mete por debajo y no hay nada que las dibuje.
   🔑 Se viste con `--sb-bg`, el plano del menú de fuera: *la misma
   lista en dos sitios tiene que verse igual, o se leen como dos
   listas.* Y las dos reglas, porque la de abajo gana por
   especificidad. */
#eco-sheet-body[data-plano="menu"],
#eco-sheet-header.eco-head-color ~ #eco-sheet-body[data-plano="menu"]{
  background:var(--sb-bg);
}
/* el cuerpo se monta sobre el color con su curva */
#eco-sheet-header.eco-head-color ~ #eco-sheet-body{
  position:relative; z-index:1;
  /* 🎨 La misma mezcla de arriba: esta regla gana por especificidad, así
     que sin repetirla aquí el cuerpo volvía a blanco justo en el eco que
     lleva cabecera de color — o sea en el que David estaba mirando. */
  background:var(--marca-fondo, var(--fondo-suave));
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--marca) 13%, var(--blanco)) 0%,
    color-mix(in srgb, var(--marca) 7%, var(--blanco)) 100%);
  border-radius:24px 24px 0 0;
  padding-top:24px;
}
/* las filas: tarjetas ovaladas con brillo, y cada clase con su forma */
.eco-fila{
  width:100%; display:flex; align-items:center; gap:12px;
  padding:12px 16px; min-height:72px;
  border-radius:14px; cursor:pointer; text-align:left; margin-bottom:12px;
  font-family:inherit; border:1px solid var(--borde); background:var(--blanco);
  box-shadow:0 8px 24px rgba(108,53,255,.10);
}
.eco-fila-ico{
  width:44px; height:44px; border-radius:12px; background:var(--marca-suave);
  display:flex; align-items:center; justify-content:center; flex-shrink:0; pointer-events:none;
}
.eco-fila-tx{ flex:1; min-width:0; pointer-events:none; }
/* El nombre en el azul del menú (David, 2026-09-11: *«la letra azul cuando
   abro el sidebar quedaría mejor que el negro en los cards»*). Es el azul
   del ítem activo y del logo, 4,86:1 sobre blanco; el de los ítems sueltos
   (`--sb-text-soft`) da 2,57:1 y no pasa el piso de letra. */
/* 🎨 LOS TRES TONOS DEL CUERPO DEL ECO (David, 2026-09-30: *«quiero
   mejorar la interfaz del eco, o sea NO EL HEADER que está bien: es el
   contenido — letras, colores, organización. Y creo que se ve bien si
   usamos 3 tonalidades de azul o del color del tema, para nombre y
   opciones»*).

   📏 Y LO QUE DECIDE EL DISEÑO SALIÓ DE UNA MEDIDA, no de un gusto: el
   nombre pintaba a **22,4px** y su explicación a **20,8** — un 7% de
   diferencia, o sea ninguna jerarquía. 🔴 Y la explicación NO SE PUEDE
   BAJAR: pide `--fs-11` y **el piso de letra la sube igualmente a la
   moneda de 13**, que es la lección ya escrita —*«--fs-11, --fs-12 y
   --fs-13 miden lo mismo porque el piso ya muerde»*—. *La jerarquía por
   tamaño solo se puede hacer por arriba*, así que sube el nombre.

   🔑 Y LOS TRES TONOS SON DE LA MISMA FAMILIA, que es lo que él pidió y
   lo que de verdad arregla la fila: hoy el nombre va azul y la
   explicación **gris**, o sea dos familias de color, y el ojo las lee
   como dos cosas pegadas en vez de como una con jerarquía.
     · fuerte  → EL NOMBRE, lo que se busca
     · medio   → LO QUE HACE, el mismo color apagado
     · flojo   → la flecha, que solo acompaña
   ⚠️ Van con DOS declaraciones: quien no entienda `color-mix` se queda
   con la primera. *Un color que solo existe en los navegadores nuevos
   se ve igual que ninguno en el celular de quien lo pidió* — la lección
   de la sombra de la barra guía.
   🎨 Y el fuerte sigue siendo `--sb-logo-color`, que David escogió el
   2026-09-11 y **sale del tema** (lo escribe `apariencia.js`): 4,86:1
   sobre blanco. No se cambia por otro, se le hacen los hermanos. */
.eco-fila-nombre{ display:block; font-size:var(--fs-15); font-weight:800; color:var(--sb-logo-color); line-height:1.15; }
.eco-fila-desc{
  display:block; font-size:var(--fs-11); margin-top:3px; line-height:1.3;
  color:var(--texto-medio);
  color:color-mix(in srgb, var(--sb-logo-color) 38%, var(--texto-medio));
  /* 📏 DOS RENGLONES Y NO TRES: medido en «Tamaño del menú», la
     explicación se comía tres y la fila pedía 72px de alto — con cinco
     opciones, la hoja entera. *Lo que se recorta es la explicación,
     nunca el nombre*: un nombre cortado no dice a dónde lleva. */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.eco-fila-fin{
  flex-shrink:0; font-size:var(--fs-18); font-weight:700; pointer-events:none;
  color:var(--texto-suave);
  color:color-mix(in srgb, var(--sb-logo-color) 45%, var(--texto-suave));
}
/* el BOTÓN: ninguna flecha — se dispara al tocar. El icono va morado sobre
   lila como los demás: cuatro cuadros llenos en fila pesaban de más (foto). */
/* la VISTA: sin brillo, porque solo informa */
.eco-fila-vista{ background:var(--fondo-suave); box-shadow:none; border-color:transparent; }
.eco-fila-vista .eco-fila-ico{ background:transparent; }
/* la palanca del INTERRUPTOR: una sola, para la fila, el nivel 2 y los ajustes */
.eco-palanca{
  width:52px; height:32px; border-radius:99px; background:var(--borde);
  position:relative; flex-shrink:0; cursor:pointer; border:none; padding:0;
  transition:background .15s;
}
.eco-palanca::after{
  content:''; position:absolute; top:4px; left:4px; width:24px; height:24px;
  border-radius:50%; background:var(--blanco); transition:left .15s;
}
.eco-palanca[aria-checked="true"]{ background:var(--marca); }
.eco-palanca[aria-checked="true"]::after{ left:24px; }
/* «Más opciones»: una raya con su nombre, sin MAYÚSCULAS grises */
.eco-sep{ display:flex; align-items:center; gap:12px; margin:16px 0 12px; }
.eco-sep::before, .eco-sep::after{ content:''; flex:1; height:1px; background:var(--borde); }
.eco-sep span{ font-size:var(--fs-13); font-weight:700; color:var(--texto-medio); display:flex; align-items:center; gap:4px; }
#eco-sheet-titulo{
  /* ⚠️ CON TECHO. David, 2026-08-31: *«el eco tiene el header muy
     grande pero los iconos pequeños»*. El título llevaba `--fs-18`, que
     con la base nueva son 28,8px — casi el tamaño del logo de la app,
     para decir «Ajustes». Se acota, y los tres botones de al lado
     crecieron: el header deja de tener dos escalas peleadas. */
  font-size:clamp(18px, calc(18px * var(--fs-base,1) * var(--fs-app,1)), 26px);
  font-weight:800; color:var(--text);
}
#eco-sheet-sub{
  font-size:var(--fs-12); color:var(--text-soft); margin-top:2px;
}
#eco-sheet-close{
  width:32px; height:32px;
  border-radius:99px;
  background:var(--fondo-suave);
  display:flex;
  align-items:center;
  justify-content:center;
  color:var(--text-soft);
  flex-shrink:0;
  transition:background var(--t);
}
#eco-sheet-close:hover{ background:var(--borde); }
#eco-sheet-body{
  flex:1;
  overflow-y:auto;
  padding:8px 16px 12px;
  /* 🎨 EL CUERPO VA DE UN CLARO DEL TEMA, NO BLANCO NI GRIS. David,
     2026-10-08: *«el eco, el fondo debería tener uno claro del color del
     tema y no blanco o gris»*. Es la MISMA receta que el formulario del
     producto —`--marca` mezclada con blanco— así que sigue al tema: con
     el verde puesto, el eco sale verde clarito y no morado.
     🔑 Las filas se quedan BLANCAS (`.eco-fila` ya lo es), que es lo que
     las convierte en tarjetas sobre el fondo en vez de renglones.
     ⚠️ Van DOS declaraciones y la plana PRIMERO: el celular que no sepa
     `color-mix` se queda con el pastel de siempre — *un color que solo
     existe en los navegadores nuevos se ve igual que ninguno en el
     celular de quien lo pidió*. */
  background:var(--marca-fondo, var(--fondo-suave));
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--marca) 13%, var(--blanco)) 0%,
    color-mix(in srgb, var(--marca) 7%, var(--blanco)) 100%);
  /* 🔴 SIN ALTURA FIJA. Estaba en `height:268px; max-height:268px` dentro
     de una hoja de 440px con 76px de cabecera: 440 − 76 − 268 = **96px de
     blanco muerto al pie**, mientras arriba la última fila se partía por
     la mitad. Se veía en todas las hojas del eco, y se notó al crecer la
     lista de opciones.

     El comentario decía «exactamente 4 opciones visibles», y esa cuenta
     dejó de valer en cuanto las filas cambiaron de alto. Fijar píxeles
     para contar filas es apostar a que las filas no cambien nunca.

     Con `flex:1` y `min-height:0` el cuerpo ocupa lo que sobra —sea lo
     que sea la cabecera— y rueda cuando no cabe. Que es lo que se quería
     desde el principio. */
  min-height:0;
  scrollbar-width:none;
}
#eco-sheet-body::-webkit-scrollbar{ display:none; }

/* Tamaños ampliados para eco sheet móvil */
#eco-sheet-body .eco-sec-btn{
  padding:14px 12px;
}
#eco-sheet-body .eco-sec-ico{
  width:44px; height:44px;
  border-radius:13px;
}
#eco-sheet-body .eco-sec-ico svg{
  width:24px; height:24px;
}
#eco-sheet-body .eco-sec-nombre{
  font-size:var(--fs-18);
}
#eco-sheet-body .eco-sec-chevron{
  font-size:var(--fs-20);
}
#eco-sheet-body .eco-opcion{
  padding:11px 10px;
}
#eco-sheet-body .eco-op-ico{
  width:40px; height:40px;
  border-radius:12px;
}
#eco-sheet-body .eco-op-ico svg{
  width:22px; height:22px;
}
#eco-sheet-body .eco-op-nombre{
  font-size:var(--fs-17);
}
#eco-sheet-body .eco-op-desc{
  font-size:var(--fs-13);
  margin-top:2px;
}
#eco-sheet-body .eco-toggle{
  width:38px; height:22px;
  border-radius:11px;
}
#eco-sheet-body .eco-toggle::after{
  width:16px; height:16px;
  top:3px; left:3px;
}
#eco-sheet-body .eco-toggle.on::after{
  left:19px;
}

/* ══════════════════════════════════════════════════════════
   CENTRO OMNIXA — overlay de búsqueda global
══════════════════════════════════════════════════════════ */
#centroBackdrop{
  display:none;
  position:fixed;
  inset:0;
  background:rgba(0,0,0,.4);
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
  z-index:calc(var(--z-sidebar) + 10);
}
#centroBackdrop.open{ display:block; }

#centroOverlay{
  position:fixed;
  top:0; left:0; right:0;
  z-index:calc(var(--z-sidebar) + 11);
  background:var(--bg-white);
  border-radius:0 0 24px 24px;
  box-shadow:0 8px 32px rgba(108,53,255,.15);
  display:flex;
  flex-direction:column;
  max-height:92dvh;
  transform:translateY(-100%);
  opacity:0;
  transition:transform .3s cubic-bezier(.4,0,.2,1), opacity .25s ease;
  pointer-events:none;
}
#centroOverlay.open{
  transform:translateY(0);
  opacity:1;
  pointer-events:all;
}

/* ── Topbar buscador ────────────────────────────────────── */
#centro-topbar{
  display:flex;
  align-items:center;
  gap:10px;
  padding:14px 16px 10px;
  flex-shrink:0;
}
#centro-search-wrap{
  flex:1;
  display:flex;
  align-items:center;
  gap:10px;
  background:var(--fondo-claro);
  border:1.5px solid rgba(108,53,255,.3);
  border-radius:14px;
  padding:10px 14px;
  transition:border-color var(--t);
}
#centro-search-wrap:focus-within{
  border-color:var(--primary);
  background:#fff;
}
#centro-input{
  flex:1;
  border:none;
  background:transparent;
  font-size:var(--fs-16);
  font-weight:500;
  color:var(--text);
  outline:none;
  font-family:var(--font);
}
#centro-input::placeholder{ color:var(--texto-suave); }
#centro-clear{
  color:var(--texto-suave);
  padding:0;
  transition:color var(--t);
}
#centro-clear:hover{ color:var(--primary); }
#centro-close{
  width:36px; height:36px;
  border-radius:10px;
  display:flex;
  align-items:center;
  justify-content:center;
  color:var(--text-soft);
  flex-shrink:0;
  transition:background var(--t);
}
#centro-close:hover{ background:var(--bg); }

/* ── Chips ──────────────────────────────────────────────── */
#centro-chips{
  display:flex;
  gap:8px;
  padding:2px 16px 12px;
  flex-shrink:0;
  overflow-x:auto;
  scrollbar-width:none;
}
#centro-chips::-webkit-scrollbar{ display:none; }

.centro-chip{
  padding:7px 18px;
  border-radius:99px;
  font-size:var(--fs-13);
  font-weight:700;
  border:none;
  cursor:pointer;
  white-space:nowrap;
  transition:background var(--t), color var(--t), transform .15s ease;
  background:var(--fondo-suave);
  color:var(--texto-medio);
}
.centro-chip.active{
  background:var(--primary);
  color:#fff;
  transform:scale(1.05);
}

/* ── Body ────────────────────────────────────────────────── */
#centro-body{
  flex:1;
  min-height:0;
  overflow-y:auto;
  padding:0 16px 20px;
  scrollbar-width:thin;
}

.centro-vista{ display:none; }
.centro-vista.active{ display:block; }

.centro-sec-label{
  font-size:var(--fs-11);
  font-weight:700;
  color:var(--texto-suave);
  text-transform:uppercase;
  letter-spacing:1px;
  margin-bottom:8px;
}

/* ── Grilla de módulos ───────────────────────────────────── */
#centro-modulos-grid,
#centro-familia-grid{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:8px;
  margin-bottom:4px;
}
.centro-tile{
  border-radius:16px;
  padding:14px 8px 10px;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:6px;
  cursor:pointer;
  border:1.5px solid var(--borde);
  background:var(--fondo-claro);
  transition:background var(--t), border-color var(--t), transform .15s ease;
}
.centro-tile:active{ transform:scale(.96); }
.centro-tile.activo{
  background:var(--marca-suave);
  border-color:var(--primary);
}
.centro-tile-ico{
  width:36px; height:36px;
  border-radius:10px;
  display:flex;
  align-items:center;
  justify-content:center;
}
.centro-tile-n{
  font-size:var(--fs-11);
  font-weight:700;
  color:var(--texto);
  text-align:center;
  line-height:1.3;
}
.centro-tile.activo .centro-tile-n{ color:var(--primary); }

/* ── Lista productos ──────────────────────────────────────── */
.centro-prod{
  display:flex;
  align-items:center;
  gap:10px;
  padding:10px 0;
  border-bottom:1px solid var(--fondo-suave);
  cursor:pointer;
}
.centro-prod-ico{
  width:36px; height:36px;
  border-radius:10px;
  background:var(--fondo-suave);
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:var(--fs-18);
  flex-shrink:0;
}
.centro-prod-info{ flex:1; min-width:0; }
.centro-prod-n{ font-size:var(--fs-14); font-weight:600; color:var(--text); }
.centro-prod-s{ font-size:var(--fs-11); color:var(--texto-suave); margin-top:1px; }
.centro-prod-p{ font-size:var(--fs-14); font-weight:700; color:var(--primary); flex-shrink:0; }

/* ── IA sugerencias ───────────────────────────────────────── */
#centro-ia-sugg{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
  margin-bottom:4px;
}
.centro-ia-chip{
  background:var(--marca-suave);
  color:var(--primary);
  font-size:var(--fs-12);
  font-weight:600;
  padding:7px 14px;
  border-radius:99px;
  border:none;
  cursor:pointer;
  transition:background var(--t);
}
.centro-ia-chip:hover{ background:#ddd6fe; }

#centro-ia-respuesta{
  background:var(--fondo-claro);
  border-radius:14px;
  padding:16px;
  min-height:80px;
  font-size:var(--fs-14);
  color:var(--text);
  line-height:1.6;
}

/* ── Empty state ──────────────────────────────────────────── */
.centro-empty{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:8px;
  padding:32px 16px;
  color:var(--texto-suave);
  font-size:var(--fs-13);
  font-weight:500;
}

/* ══════════════════════════════════════════════════════════
   THUMB BAR — handle + mini sheet por panel (móvil)
══════════════════════════════════════════════════════════ */
.panel-thumb-handle{
  position:absolute;
  /* 🔴 JUSTO ENCIMA DEL PIE, no dentro (David, 2026-09-14). Antes era
     `calc(44px + 10px - 2px)`, con el 44 del pie de hace dos semanas:
     el pie creció con la letra y la rayita se quedó **dentro del
     óvalo**. Ahora sale de lo que el pie mide de verdad, así que
     acompaña también a quien suba el tamaño de letra. */
  bottom:calc(var(--pie-abajo) + var(--pie-alto));
  left:50%;
  transform:translateX(-50%);
  width:96px;
  height:14px;
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  z-index:11;
  touch-action:none;
}
/* 🔴 LA RAYITA SE VE, Y NO ERA EL GROSOR: ERA EL COLOR (2026-09-30).
   David: *«la raya arriba de los footer no se ve casi»* — y medido, daba
   **1,36 : 1** contra el fondo de la pantalla: `rgba(108,53,255,.2)` es
   el morado al **20%**, o sea casi el fondo. *Engordar una raya
   invisible deja una raya gorda que tampoco se ve.*

   🔑 Y la receta ya existía en esta casa: `--pie-raya` es ese mismo
   morado al **70%** y da **3,39 : 1** — es lo que se arregló el 29 para
   la raya que separa los botones DENTRO del pie. Se pide esa, no se
   escribe otra: *dos definiciones del mismo gris se separan solas, y un
   color a mano deja el pie morado en un tema verde.*

   📏 Y de 3px a 8, que es de la escala de la armonía (4, 8, 12, 16). El
   ancho acompaña (36 → 48) o una raya gorda y corta se lee como un
   botón. */
.panel-thumb-handle::after{
  content:'';
  /* 📏 De 48 a 80 de largo el 2026-10-02 (David: *«la raya más grande en
     los footer»*). Se alarga y NO se engorda: el 30 de septiembre él
     mismo la pidió delgada —*«la raya sí más delgada»*— después de verla
     con su color bueno. *Lo que se agranda es lo que se ve de lejos, no
     lo que pesa.* La zona de agarre acompaña (60 → 96). */
  width:80px;
  /* 📏 De 8 a 4 el 2026-09-30, pedido por David —«la raya sí más
     delgada»— DESPUÉS de verla ya con su color bueno. 🔑 Y eso confirma
     lo del día anterior: lo que no se veía era el color, no el grosor.
     Gorda y visible era demasiado; delgada y visible es lo que pedía.
     4 sigue siendo de la escala (4, 8, 12, 16). */
  height:4px;
  border-radius:99px;
  background:var(--pie-raya, rgba(108,53,255,.7));
}

/* Sheet que sube */
.panel-thumb-sheet{
  position:absolute;
  bottom:0;
  left:0; right:0;
  /*  🌙 `--blanco` y no `#fff` (2026-10-02): era el ÚNICO nodo de toda
      la app que se quedaba blanco con el tema de noche puesto —medido
      barriendo tres pantallas—, o sea una mancha de 45.240px² encima de
      una pantalla oscura. Es la regla 2 de la armonía literal: *el color
      sale del tema, nunca de un `#`*. Con el respaldo puesto, los seis
      temas claros no cambian ni un píxel. */
  background:var(--blanco, #fff);
  border-radius:20px 20px 0 0;
  box-shadow:0 -4px 24px rgba(108,53,255,.12);
  z-index:12;
  transform:translateY(100%);
  transition:transform .3s cubic-bezier(.4,0,.2,1);
  padding-bottom:env(safe-area-inset-bottom, 0px);
}
.panel-thumb-sheet.open{
  transform:translateY(0);
}

/* Handle bar dentro del sheet */
.pts-handle-bar{
  width:36px; height:4px;
  background:var(--borde);
  border-radius:99px;
  margin:10px auto 14px;
}

/* 3 acciones principales */
.pts-acciones{
  display:flex;
  gap:8px;
  padding:0 12px 12px;
}
.pts-btn{
  flex:1;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:6px;
  padding:12px 8px 10px;
  border-radius:16px;
  background:var(--fondo-claro);
  border:1.5px solid var(--borde);
  cursor:pointer;
  transition:background var(--t), border-color var(--t);
  color:var(--texto);
}
.pts-btn:active{ background:var(--marca-suave); border-color:var(--primary); }
.pts-btn span{
  font-size:var(--fs-12);
  font-weight:700;
  color:var(--texto);
}
.pts-eco{ }
.pts-eco span{ color:var(--primary); }
.pts-eco svg{ stroke:var(--primary); }

/* Solo móvil */
@media(min-width:900px){
  .panel-thumb-handle,
  .panel-thumb-sheet{ display:none; }
}

/* ══════════════════════════════════════════════════════════
   TABLIXA — LA TABLA A PANTALLA COMPLETA
   Se activa con la clase body.tablixa (la pone tablixa-puerta.js).
   La ley de Tablixa: toda la pantalla es de la tabla — se esconden el
   topbar y los footers, las herramientas viven en los railes. Quitar
   la clase lo revierte por completo — no toca nada más.
   ══════════════════════════════════════════════════════════ */
body.tablixa .topbar { display: none !important; }
body.tablixa .panel-footer { display: none !important; }
body.tablixa .panel-body { padding-bottom: 8px !important; }
/* Sin topbar, la app ocupa toda la altura */
body.tablixa #app { height: 100dvh !important; }

/* UN SOLO PANEL: el activo manda toda la pantalla. Los railes flotan
   encima y no le quitan espacio. */
body.tablixa .panel:not(.active){ display:none !important; }
body.tablixa .panel.active{ flex:1 !important; }
/* La manija del pulgar y su hoja tampoco: en este modo no hay nada abajo, la
   pantalla entera es para la tabla. (Van absolutas, así que no le quitan sitio
   a la rejilla de la barra de arriba — pero se ven, y aquí estorban.) */
body.tablixa .panel-thumb-handle,
body.tablixa .panel-thumb-sheet{ display:none !important; }

/* El giro ya NO se hace por CSS: se le pide al navegador que se
   ponga horizontal de verdad (screen.orientation.lock), desde
   preferencias.js. Aquí solo queda esconder el cromo. */
body.tablixa{ overflow:hidden !important; }

/* ══════════════════════════════════════════════════════════
   MODO TABLA HORIZONTAL — railes laterales flotantes (Pieza A)
   En este modo los railes NO empujan la tabla: flotan sobre ella,
   angostos (dedo y medio), pegados a los bordes. Se muestran/ocultan
   con el FAB. La tabla queda de fondo, siempre visible.
   ══════════════════════════════════════════════════════════ */
body.tablixa #sbRail,
body.tablixa #ecoRail{
  display:flex !important;
  flex-direction:column;
  align-items:center;
  position:fixed;
  top:0;
  bottom:0;
  width:52px;
  z-index:450;                 /* POR ENCIMA de los overlays (400): si no, el carrito los tapa */
  background:var(--bg-panel);
  overflow-y:auto;
  overflow-x:hidden;
  transition:transform .25s cubic-bezier(.4,0,.2,1);
  box-shadow:0 0 20px rgba(15,23,42,.12);
}
body.tablixa #sbRail{
  left:0;
  border-right:2px solid rgba(108,53,255,.18);
  transform:translateX(0);
}
body.tablixa #ecoRail{
  right:0;
  border-left:2px solid rgba(108,53,255,.18);
  transform:translateX(0);
}
/* Railes escondidos (el FAB los alterna) */
/* En este modo los railes arrancan ESCONDIDOS: salen con el swipe.
   Arrastrar a la derecha saca el izquierdo; a la izquierda el derecho. */
body.tablixa #sbRail{ transform:translateX(-100%); }
body.tablixa #ecoRail{ transform:translateX(100%); }
body.tablixa.rail-izq-visible #sbRail{ transform:translateX(0); }
body.tablixa.rail-der-visible #ecoRail{ transform:translateX(0); }

/* FAB en Tablixa: barras + SALIR (salvavidas siempre visible) */
#fab-railes-wrap{ display:none; }
/* DOS FAB laterales, semitransparentes: cada uno abre SU rail.
   Van pegados a los bordes, a media altura, para que se alcancen con
   el pulgar sin pelear con los gestos del sistema. */
#fab-railes-wrap{ display:none; }
body.tablixa #fab-railes-wrap{ display:block; }

#fab-op-izq, #fab-op-der{
  position:fixed;
  /* PEGADOS A LA ESQUINA de abajo: ese espacio casi no se usa */
  bottom:0;
  top:auto;
  transform:none;
  width:30px;
  height:42px;
  border:none;
  background:rgba(30,41,59,.10);      /* casi invisible */
  color:rgba(255,255,255,.6);
  font-size:var(--fs-13);
  cursor:pointer;
  z-index:500;
  backdrop-filter:blur(2px);
  transition:background .2s, opacity .2s;
}
#fab-op-izq:active, #fab-op-der:active{ background:rgba(30,41,59,.45); }
#fab-op-izq{ left:0;  border-radius:0 10px 0 0; }
#fab-op-der{ right:0; border-radius:10px 0 0 0; }
/* Cuando el rail ya está abierto, su FAB se quita de en medio */
body.tablixa.rail-izq-visible #fab-op-izq{ opacity:0; pointer-events:none; }
body.tablixa.rail-der-visible #fab-op-der{ opacity:0; pointer-events:none; }

/* El botón de salir va al PIE del rail izquierdo */
#fab-railes{ display:none !important; }
body.tablixa #fab-salir{
  position:fixed;
  left:5px;
  bottom:10px;
  z-index:501;
  transform:translateX(-140%);
  transition:transform .25s cubic-bezier(.4,0,.2,1);
}
body.tablixa.rail-izq-visible #fab-salir{ transform:translateX(0); }
/* Y otro igual al pie del rail DERECHO */
body.tablixa #fab-salir-der{
  position:fixed;
  right:5px;
  bottom:10px;
  z-index:501;
  transform:translateX(140%);
  transition:transform .25s cubic-bezier(.4,0,.2,1);
}
body.tablixa.rail-der-visible #fab-salir-der{ transform:translateX(0); }
#fab-railes, #fab-salir{
  display:flex;
  align-items:center;
  justify-content:center;
  width:46px;
  height:46px;
  border:none;
  border-radius:50%;
  cursor:pointer;
  box-shadow:0 4px 16px rgba(15,23,42,.3);
}
#fab-railes{ background:var(--grad, linear-gradient(135deg,var(--marca),#38bdf8)); color:#fff; }
#fab-salir{ background:var(--peligro); color:#fff; }

/* ══════════════════════════════════════════════════════════
   TABLIXA — buscador y mini-catálogo a MEDIA PANTALLA
   La tabla con el foco (carrito o historial) es la protagonista y
   ocupa todo. El buscador + su mini-catálogo aparecen encima, solo
   en la mitad izquierda, y desaparecen al no usarse. Es el MISMO
   overlay del buscador; el CSS solo lo achica a media pantalla.
   ══════════════════════════════════════════════════════════ */
/* La cabecera del buscador de la venta, a la mitad izquierda */
body.tablixa #ven-buscar-head{
  position:fixed !important;
  top:8px;
  left:12px;                    /* a la MITAD IZQUIERDA */
  right:50%;                    /* hasta la mitad exacta, sin usar vw */
  width:auto;
  max-width:none;
  z-index:355;
  background:#fff;
  border-radius:14px;
  box-shadow:0 6px 24px rgba(15,23,42,.16);
}
/* El mini-catálogo, justo debajo, también media pantalla */
body.tablixa #ven-mini{
  position:fixed !important;
  left:12px !important;
  right:50% !important;
  width:auto !important;
  max-width:none !important;
  z-index:356;
}
/* El carrito (la tabla protagonista) ocupa toda la pantalla,
   sin dejar hueco arriba porque el buscador ahora flota */
body.tablixa #ven-carrito-lista{
  padding-top:8px !important;
}

/* ══════════════════════════════════════════════════════════
   LA TABLA DENTRO DE TABLIXA
   Al rotar, el "ancho" del contenedor es el alto real de la pantalla,
   y la tabla con width:100% estiraba las columnas de más. Aquí se le
   deja tomar su ancho natural: las columnas quedan del tamaño de su
   contenido, cabe lo que quepa y lo demás hace scroll.
   ══════════════════════════════════════════════════════════ */
body.tablixa .vista-tabla{
  width:auto !important;
  table-layout:auto !important;
}
/* SIN min-width:100%. Ese era el que estiraba: con pocas columnas las
   ensanchaba para llenar la pantalla, y la tabla se veia MAS GRANDE en vez de
   mostrar MAS. Ahora cada columna mide lo suyo y el ancho de mas lo gasta
   vista.js trayendo mas columnas (ver colsEfectivas). */
/* Las celdas no se estiran: solo lo que necesita su contenido */
body.tablixa .vista-tabla th,
body.tablixa .vista-tabla td{
  width:auto !important;
  white-space:nowrap;
  /* La fila se queda del MISMO alto que en vertical: el modo es para ver mas
     datos, no para verlos mas grandes. */
  padding:7px 10px !important;
  line-height:1.3;
}
/* El encabezado se queda pegado arriba mientras se rueda */
body.tablixa .vista-tabla thead th{
  position:sticky;
  top:0;
  z-index:2;
  background:var(--bg-white,#fff);
}
/* PARA QUE LA TABLA RUEDE hacen falta dos cosas: que el contenedor
   tenga overflow Y que tenga el ALTO LIMITADO. Sin lo segundo el
   contenido se sale por abajo y no hay nada que rodar. */
body.tablixa .panel{
  display:flex;
  flex-direction:column;
  min-height:0;
}
body.tablixa .panel-body{
  flex:1 1 auto;
  min-height:0;
  overflow:auto !important;
  -webkit-overflow-scrolling:touch;
  touch-action:pan-y;             /* el arrastre vertical es para rodar, siempre */
}
/* EL SCROLL: UN SOLO ELEMENTO RUEDA, y es el cuerpo del panel.
   Antes rodaba el div de dentro, que vive de `height:100%` — y eso solo
   funciona si TODOS sus padres tienen altura resuelta. En el navegador de
   escritorio salía bien (medido: 2819px de tabla en 305 de alto, rodaba); en
   el celular de David no. Encadenar porcentajes es frágil: basta con que un
   eslabón se calcule distinto (la PWA instalada, `100dvh` con la barra del
   navegador entrando y saliendo) para que la altura se resuelva en cero y no
   quede nada que rodar.
   Aquí se corta la cadena: los hijos crecen a su tamaño natural y el que rueda
   es el cuerpo, que sí tiene altura de verdad (se la da la rejilla del panel).
   El encabezado pegajoso de la tabla sigue funcionando: se pega al cuerpo. */
body.tablixa .panel-body > div{
  height:auto !important;
  max-height:none !important;
  overflow:visible !important;
}
/* La tabla de Ventas vive DENTRO del overlay, no en el panel-body:
   por eso la regla de arriba no la alcanzaba. */
body.tablixa #ven-venta-ov,
body.tablixa #ven-tot-ov{
  display:flex;
  flex-direction:column;
  min-height:0;
}
body.tablixa #ven-carrito-lista,
body.tablixa #ven-mini-lista{
  flex:1 1 auto;
  min-height:0;
  overflow:auto !important;
  -webkit-overflow-scrolling:touch;
}

/* Botón de mostrar/ocultar el encabezado de los paneles.
   Vive al pie del rail izquierdo y SIEMPRE está visible en el modo. */
#fab-header{ display:none; }
body.tablixa #fab-header{
  display:flex;
  align-items:center;
  justify-content:center;
  position:fixed;
  left:5px;
  bottom:58px;                  /* encima del botón de cerrar */
  width:42px;
  height:42px;
  border:none;
  border-radius:50%;
  background:rgba(30,41,59,.55);
  color:#fff;
  cursor:pointer;
  z-index:501;
  transform:translateX(-140%);
  transition:transform .25s cubic-bezier(.4,0,.2,1);
}
body.tablixa.rail-izq-visible #fab-header{ transform:translateX(0); }

/* El encabezado de los paneles va OCULTO por defecto en este modo.
   Solo sale si se activa con el botón del rail — ahí sirve de
   navegador entre paneles y para saber dónde estás. */
body.tablixa .panel-header{ display:none !important; }
body.tablixa.con-header-panel .panel-header{
  display:flex !important;
  position:relative;
  z-index:440;                 /* sobre los overlays, para que se vea */
}

/* (Aquí vivía la VISTA 3 PANELES. Era una prueba para decidir si el modo
   horizontal se quedaba: David decidió que no — "si uso crear algo no voy a
   usar historial, y viceversa" — y ordenó borrarla el 2026-07-31. La
   historia completa está en modo-horizontal.md.) */

/* ══════════════════════════════════════════════════════════
   EL RAIL ES LA SALIDA: va POR ENCIMA DEL TECLADO
   Los teclados se pintan en z-index 800 ("el teclado va sobre todo"), y eso
   está bien en general — pero en este modo dejaba al rail (450) enterrado.
   David: "toqué el teclado y no pude entrar al rail nuevamente". Con el
   teclado encima no había forma de volver a apagarlo.
   Aquí, y SOLO aquí, el rail y sus tiradores le ganan al teclado: son la
   única puerta de vuelta. El resto de la app no se entera.
   ══════════════════════════════════════════════════════════ */
body.tablixa #sbRail,
body.tablixa #ecoRail{ z-index:860 !important; }
body.tablixa #fab-railes-wrap,
body.tablixa #fab-op-izq,
body.tablixa #fab-op-der,
body.tablixa #fab-salir,
body.tablixa #fab-salir-der,
body.tablixa #fab-header{ z-index:870 !important; }

/* ══════════════════════════════════════════════════════════
   EL "+" DEL RAIL DERECHO — elegir qué herramientas van
   ══════════════════════════════════════════════════════════ */
.h-agregar-ov{
  position:fixed;
  inset:0;
  z-index:880;                    /* sobre el rail, que ya va sobre el teclado */
  background:var(--velo, rgba(15,23,42,.5));
  display:flex;
  align-items:center;
  justify-content:center;
  padding:16px;
}
.h-ag-caja{
  background:var(--blanco, #fff);
  border-radius:16px;
  padding:12px;
  width:min(420px, 92vw);
  max-height:86vh;
  overflow-y:auto;
  box-shadow:0 12px 40px rgba(15,23,42,.28);
}
.h-ag-tit{
  font-size:var(--fs-14);
  font-weight:800;
  color:var(--texto);
  padding:4px 6px 10px;
}
.h-ag-op{
  display:flex;
  align-items:center;
  gap:10px;
  width:100%;
  border:none;
  background:transparent;
  border-radius:11px;
  padding:9px 8px;
  cursor:pointer;
  font-family:inherit;
  font-size:var(--fs-13);
  color:var(--texto);
  text-align:left;
}
.h-ag-op.on{ background:var(--marca-suave, rgba(108,53,255,.10)); }
.h-ag-ico{
  width:32px; height:32px;
  border-radius:9px;
  display:flex; align-items:center; justify-content:center;
  background:rgba(108,53,255,.12);
  color:var(--marca);
  flex-shrink:0;
}
.h-ag-txt{ flex:1; min-width:0; }
.h-ag-si{ width:18px; text-align:center; font-weight:800; color:var(--marca); flex-shrink:0; }
.h-ag-cerrar{
  width:100%;
  margin-top:8px;
  border:none;
  border-radius:11px;
  background:var(--marca);
  color:#fff;
  font-family:inherit;
  font-size:var(--fs-14);
  font-weight:800;
  padding:11px;
  cursor:pointer;
}

/* El buscador del carrito se esconde desde la lupa del rail cuando estorba */
body.tablixa.ven-buscador-oculto #ven-buscar-head,
body.tablixa.ven-buscador-oculto #ven-mini{
  display:none !important;
}

/* ══════════════════════════════════════════════════════════
   LA BARRA DE ARRIBA DE TABLIXA (una sola fila)
   David: "el header debe ser el MISMO que tiene, no otro" y "ningún panel
   tiene footer; si hay una acción importante pasa al header". Las dos cosas
   se cumplen SIN tocar un solo módulo: el panel pasa a grid y se ponen en la
   misma fila el header del módulo y su footer. Nadie se mueve del DOM — el
   módulo sigue repintando su header y su footer cuando quiera.
   (La navegación ‹ › que iba delante se borró el 2026-07-31 con el resto de
   lo descartado: en Tablixa no se navega, se mira la tabla.)

       ┌────────────────────────────────────────────────┐
       │  Título del panel           │  acciones        │
       ├────────────────────────────────────────────────┤
       │                tabla / cards                   │
          .panel-header                .panel-footer
   ══════════════════════════════════════════════════════════ */
body.tablixa.con-header-panel .panel.active{
  display:grid !important;
  grid-template-columns:minmax(0,1fr) auto;
  grid-template-rows:auto minmax(0,1fr);
  grid-template-areas:
    "hdr ftr"
    "cuerpo cuerpo";
}
body.tablixa.con-header-panel .panel.active > .panel-header{ grid-area:hdr; }
body.tablixa.con-header-panel .panel.active > .panel-body{ grid-area:cuerpo; }
body.tablixa.con-header-panel .panel.active > .panel-footer{ grid-area:ftr; }

/* El footer SUBE a la barra de arriba.
   OJO CON ESTO — es lo que hacía que no llegara el total. El .panel-footer no
   es una fila normal: es una PÍLDORA FLOTANTE (position:absolute con
   left/right/bottom). Estando fuera del flujo NUNCA entraba en la rejilla: su
   columna medía 0px y la píldora se iba fuera de la pantalla (medido: quedaba
   en x=942 de una pantalla de 932, aplastada a 16px de ancho). Por eso David
   veía la barra pero sin sus acciones.
   Para que suba de verdad hay que devolverlo al flujo. */
body.tablixa.con-header-panel .panel.active > .panel-footer{
  display:flex !important;
  position:static !important;     /* ← LA CLAVE: vuelve al flujo, ocupa su celda */
  inset:auto !important;
  align-items:center;
  gap:6px;
  padding:2px 8px !important;
  min-height:0 !important;
  border-top:none !important;
  border-radius:0 !important;
  box-shadow:none !important;
  background:transparent !important;
  z-index:auto;
  justify-self:end;
  max-width:60vw;
  overflow-x:auto;                /* si el módulo trae muchas acciones */
  overflow-y:hidden;
  scrollbar-width:none;
}
/* Los botones del pie se estiran (flex:1) porque la píldora ocupa todo el
   ancho. Aquí, en la barra, cada uno mide lo suyo. */
body.tablixa.con-header-panel .panel.active > .panel-footer .fp-btn{
  flex:0 0 auto !important;
}
body.tablixa.con-header-panel .panel.active > .panel-footer::-webkit-scrollbar{ display:none; }
/* Los botones del footer, achicados para que quepan en la barra */
body.tablixa.con-header-panel .panel.active > .panel-footer .fp-btn{
  padding:4px 10px !important;
  font-size:var(--fs-12) !important;
  min-height:0 !important;
  white-space:nowrap;
}

/* ── La barra se esconde al bajar y vuelve al subir (como YouTube) ──
   David: "no me gustó lo del topbar que se vea; lo del header que se vea,
   pero como YouTube: si hago scroll se esconde y aparece si hago scroll hacia
   arriba". En un modo hecho para leer datos, eso es lo correcto: mientras
   lees, la barra estorba; en cuanto vuelves hacia arriba, aparece con sus
   acciones. La fila se encoge a 0 en vez de esconderse con display, para que
   la tabla gane ese alto de verdad y con una transición suave. */
body.tablixa.con-header-panel .panel.barra-escondida{
  grid-template-rows:0 minmax(0,1fr);
}
body.tablixa.con-header-panel .panel.barra-escondida > .panel-header,
body.tablixa.con-header-panel .panel.barra-escondida > .panel-footer{
  opacity:0;
  pointer-events:none;
  overflow:hidden;
}
/* La fila cambia DE GOLPE, sin animar. Animar grid-template-rows obliga al
   navegador a recalcular la tabla entera en cada cuadro — con 500 filas en un
   celular eso va a tirones, y este modo existe justo para que la tabla vaya
   fluida. Lo que sí se desvanece es el contenido (opacidad), que es barato. */
body.tablixa.con-header-panel .panel.active > .panel-header,
body.tablixa.con-header-panel .panel.active > .panel-footer{
  transition:opacity .14s;
}

/* (La navegación ‹ › que vivía aquí se borró el 2026-07-31: en Tablixa no
   se navega entre paneles.) */

/* Header de panel en Tablixa: DELGADO. Ahí el alto es lo que
   escasea, así que ocupa la mitad y la letra baja de tamaño. */
body.tablixa.con-header-panel .panel-header{
  padding:4px 8px !important;
  min-height:0;
  align-items:center;
}
body.tablixa.con-header-panel .panel-header h2{
  font-size:var(--fs-14) !important;
  margin:0;
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

/* ══════════════════════════════════════════════════════════
   LAS CARDS EN TABLIXA — DOS POR FILA
   David lo precisó probando: "en cada fila van dos card, o sea se distribuye".
   Es una rejilla de dos columnas que se llena de izquierda a derecha y luego
   baja — NO el multi-columna del CSS (ese llena toda la columna izquierda y
   solo después empieza la derecha, que era como estaba y no es lo pedido).
   Las cards NO cambian de tamaño: se reparten. Y el zoom sigue mandando, que
   es el mismo de siempre (--fs-card, desde Preferencias).
   El mosaico no entra aquí: su rejilla ya reparte sola.
   ══════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════
   LA LÍNEA DE COLOR DE CADA CARD
   ══════════════════════════════════════════════════════════════
   David, 2026-08-29: *"yo le quiero poner a cualquier card esa línea
   para decir cosas, como por ejemplo hay poquito, no hay, o el estado"*.

   Va en el borde de la mano, a la izquierda, y mide 3px: lo justo para
   contarlas de reojo sin quitarle sitio a lo que dice la card. El color
   lo pone `vista.js` como `--linea-tono` en cada elemento — no hay una
   clase por estado, porque el día que un módulo quiera decir otra cosa
   («va en camino», «vencida») bastaría con devolver su tono. */
.vista-card-item[data-linea]::before{
  content:'';
  position:absolute; left:0; top:6px; bottom:6px;
  width:3px; border-radius:0 3px 3px 0;
  background: var(--linea-tono, var(--marca));
  transition: width .15s, top .15s, bottom .15s;
}
/* Y AL SELECCIONARLA, LA LÍNEA CRECE. David lo pidió en la misma frase:
   *"que se cambie también cuando uno selecciona un card"*. Crece y llega
   de punta a punta en vez de cambiar de color — cambiarlo diría que el
   ESTADO cambió, y lo que cambió es qué card se está mirando. */
.vista-card-item[data-linea].vista-card-sel::before{
  width:5px; top:0; bottom:0; border-radius:0;
}
/* En mosaico la card es un cuadro, así que la línea va arriba: a un lado
   quedaría pegada al borde de la pantalla en la columna izquierda y en
   medio de las dos en la derecha. */
.vista-cards-mosaico .vista-card-item[data-linea]::before{
  left:6px; right:6px; top:0; bottom:auto;
  width:auto; height:3px; border-radius:0 0 3px 3px;
}
.vista-cards-mosaico .vista-card-item[data-linea].vista-card-sel::before{
  height:5px; left:0; right:0; width:auto;
}

/* ══════════════════════════════════════════════════════════════
   LA PUNTA DE COLOR — el sitio 2 de `marcas-card.md`
   ══════════════════════════════════════════════════════════════
   David, 2026-08-30: *"lo otro son las puntas de la card, de color y
   redondeadas, más gruesas"*.

   Menos sitio que una línea entera, así que es para lo que tiene que
   verse **pero no gritar** — y por eso de fábrica solo la usa «No
   queda», que es lo más grave (ver `nucleo/marcas-card.js`).

   🔴 Y lo de *"más gruesas"* NO es un capricho de estilo: una esquina
   fina no se ve en un celular a la luz del sol de Magangué, y **si la
   marca no se ve, no existe**. Por eso son 14px de lado y 4 de trazo,
   y no 6 y 1.

   El color lo pone `marcas-card.js` como `--mpunta-tono` en el propio
   nodo — no hay una clase por marca, igual que en la línea: el día que
   un módulo quiera decir otra cosa basta con que declare su tono. */
.vista-card-item[data-mpunta]{ position:relative; }
.vista-card-item[data-mpunta]::after{
  content:'';
  position:absolute; top:5px; right:5px;
  width:14px; height:14px;
  border-top:4px solid var(--mpunta-tono, var(--marca));
  border-right:4px solid var(--mpunta-tono, var(--marca));
  border-top-right-radius:8px;
  pointer-events:none;          /* no le quita el toque a la card */
}
/* En mosaico la card es un cuadro pequeño y arriba ya vive la línea, así
   que la punta baja a la esquina de abajo: arriba se pisarían las dos y
   dos marcas encimadas no son dos marcas, son una mancha. */
.vista-cards-mosaico .vista-card-item[data-mpunta]::after{
  top:auto; bottom:5px;
  border-top:none; border-right:4px solid var(--mpunta-tono, var(--marca));
  border-bottom:4px solid var(--mpunta-tono, var(--marca));
  border-top-right-radius:0; border-bottom-right-radius:8px;
}

/* LA FIGURA CUANDO NO HAY DÓNDE PEGARLA.
   Lo normal es que vaya al lado del stock —*una figura al lado del
   stock habla del stock*—, pero si esa zona no está en el reparto de la
   card cae aquí. Dice menos, y se ve: mejor eso que perderla. */
.vista-card-item .mcard-fig-esquina{
  position:absolute; left:8px; top:6px; z-index:1;
}
/* Y separada del número al que acompaña, o se lee como parte de la cifra. */
.vista-card-item .mcard-fig{ margin-left:5px; }

/* ══════════════════════════════════════════════════════════════
   EL RENGLÓN QUE SE ACABA DE AGREGAR
   ══════════════════════════════════════════════════════════════
   David, 2026-08-29: *"como todos son del mismo tamaño, no sería bueno
   que tenga un color leve o algo que sea un leve más grande mientras el
   teclado esté abierto; y si se agrega desde arriba es diferente, pero
   al revés"*.

   El problema que resuelve: agregando de a poco con el teclado puesto,
   todos los renglones se ven iguales y **no hay forma de saber cuál
   acaba de entrar** — que es justo lo que hace que alguien lo agregue
   dos veces.

   Rehecho el 2026-08-30 con los dos agentes que pidió David
   (*"que critiquen y entreguen lo mejor"*), y de lo que trajeron salieron
   tres cosas medidas que tumbaron la primera versión:

   🔴 **EL FONDO NO SE PINTA EN LA FILA: SE PINTA EN SU HIJO.** `vista.js`
   tiene `.vista-card-item:hover > div{ background:var(--marca-fondo) }` y
   `.vista-card-sel > div{ background:var(--marca-suave) !important }`. La
   marca vieja pintaba el contenedor, una capa por DEBAJO — medido con el
   dedo encima de la fila marcada, el hijo pasa a `rgb(250,248,255)` y
   **tapa la marca**. Y en un celular el `:hover` se queda pegado tras el
   toque hasta que se toca en otro sitio: no era una rareza de escritorio,
   es que **tocar el renglón marcado lo borraba**.

   De ahí sale el ORDEN DE QUIÉN MANDA, que faltaba por escribir y vale
   para toda la app:

       seleccionada  >  recién agregada  >  el dedo encima

   La selección gana por su `!important` (es lo que se está mirando). La
   marca gana al hover subiendo a (0,3,1) — `vista.js` inyecta su hoja
   DESPUÉS de esta, así que en empate ganaría el hover. El hover es el que
   menos dice: es dónde está el dedo, no lo que pasó.

   🔴 **EL GRIS NO SE VEÍA: 1,046:1.** Medido. El umbral con el que se
   considera que algo no textual se ve es 3:1 — estaba a un 3% de ahí. Con
   un velo blanco del 45% encima (la pantalla al sol) desaparecía del
   todo. Y peor: `--fondo-claro` es **el mismo color del avatar del
   producto** (`vista.js:352`), así que marcar la fila **borraba el chip
   de la inicial**. La marca quitaba información en vez de darla.

   🔴 **Y EL TAMAÑO NO DICE «NUEVO».** Los ±3px de padding cuestan 2,1×
   más que cambiar el fondo (obligan a recolocar todas las filas de
   abajo) y se leen como que ese renglón está raro, no como que acaba de
   entrar. Ninguna de las ocho referencias que se miraron —Square, Toast,
   Loyverse, Shopify POS, Lightspeed, Telegram, Todoist, WhatsApp— cambia
   la geometría de la fila. Ninguna. Fuera el padding y fuera la sombra
   (invisible, medida).

   ⚠️ NO usa `::before` — esa es la línea de estado de la card, y
   pisarla borraría lo que dice la comanda. Y NO usa la barra izquierda,
   aunque es lo que mejor se ve al sol: `vista-card-sel` ya la usa para
   decir «esta es la que se está mirando», y dos significados con el
   mismo dibujo es el fallo de las tres herramientas del eco. */

/* EL COLOR ES EL DE LA MARCA, NO UN GRIS. Un gris solo cambia el brillo;
   el morado cambia el MATIZ, y el matiz es lo que sobrevive al reflejo
   del sol, porque la luz ambiental desatura pero no borra el tono. Y en
   esta app el morado ya significa «esto es lo activo»: el precio, el
   botón de cobrar, el punto del carrusel. El vocabulario ya estaba. */
:root{
  /* 1,242:1 contra el blanco — cinco veces la diferencia que había */
  --recien-velo: rgba(108,53,255,.14);
  /* 1,512:1 — el destello del momento en que llega */
  --recien-pico: rgba(108,53,255,.26);
}

/* EL VELO: lo que dura mientras el teclado siga abierto. Dice «este es el
   último que entró», no «mírame». (0,3,1) para ganarle al hover. */
.vista-cards .vista-card-item.ven-recien > div{
  background: var(--recien-velo);
  border-radius: 10px;
}

/* EL DESTELLO DE LLEGADA — 550ms, y los primeros 121ms el pico se queda
   PLANO. Eso no es adorno: mientras se agrega, la vista está en el
   buscador, no en la lista. Un destello que empieza a caer en el
   fotograma uno ya se fue a medias cuando la mirada baja. La meseta es
   el tiempo que tarda una mirada en cambiar de sitio.

   550 y no 900 (que es lo que usa Telegram) porque aquí el ritmo es
   otro: agregando de a poco entra un producto cada segundo y pico, y con
   900 el destello seguiría vivo cuando llega el siguiente. */
@keyframes ven-destello{
  0%, 22% { background-color: var(--recien-pico); }
  100%    { background-color: var(--recien-velo); }
}
.vista-cards .vista-card-item.ven-nace > div{
  animation: ven-destello .55s cubic-bezier(.33,0,.67,1) both;
}

/* 🔴 EL APAGADO, QUE ES LO QUE PIDIÓ DAVID EXPRESAMENTE:
   *"que se quite antes de llegar el siguiente producto agregado, porque
   se le pasa al último"*.

   Y tenía un nombre técnico exacto. `Vista.render` hace `innerHTML=`, así
   que **el nodo marcado es OTRO en cada repintado** (medido:
   `mismoNodo:false`). Una `transition` sobre ese nodo no llega a correr
   nunca — la clase sale de una fila y entra en otra en el mismo
   fotograma, y eso el ojo lo lee como un objeto que SE DESPLAZA. De ahí
   su palabra: *se le pasa*.

   Se arregla con una ANIMACIÓN y no con una transición, porque una
   animación sí corre sobre un nodo recién creado. Y en COLA: la nueva no
   se enciende hasta que la vieja acabó de apagarse. Ese hueco de 180ms
   sin nadie marcado es lo que convierte un desplazamiento en dos hechos.

   `ease-in` y no `ease-out`: un apagado que empieza lento y acaba rápido
   se lee «se fue»; al revés deja un rastro que estorba con el dedo
   encima. */
@keyframes ven-apaga{
  from{ background-color: var(--recien-velo); }
  to  { background-color: transparent; }
}
.vista-cards .vista-card-item.ven-sale > div{
  animation: ven-apaga .18s cubic-bezier(.4,0,1,1) both;
}

/* Y EL EFECTO DE QUE APARECE. David: *"tiene un efecto, que el que
   estaba se sube y ese aparece"*. Entra por el lado por donde llegó:
   desde abajo cuando se agrega al final, desde arriba cuando se agrega
   arriba — que es el "al revés" que pidió. */
@keyframes ven-entra-abajo{
  from{ opacity:0; transform:translateY(10px); }
  to  { opacity:1; transform:translateY(0); }
}
@keyframes ven-entra-arriba{
  from{ opacity:0; transform:translateY(-10px); }
  to  { opacity:1; transform:translateY(0); }
}
.vista-card-item.ven-entra-abajo{  animation: ven-entra-abajo .22s cubic-bezier(.16,1,.3,1); }
.vista-card-item.ven-entra-arriba{ animation: ven-entra-arriba .22s cubic-bezier(.16,1,.3,1); }

/* 🔴 EL NÚMERO QUE SUBE — la señal que NO existía. Si el producto ya
   estaba, no entra un renglón: le sube la cantidad al que hay. Hasta hoy
   eso se marcaba igual que un renglón nuevo, así que **no había forma de
   distinguir «entró uno» de «subió a dos»** — y ese es exactamente el
   caso en que alguien lo agrega otra vez creyendo que no entró.

   Es lo que hace Shopify POS, y su razonamiento es el bueno: lo que
   cambió es el número, así que se anima el número.

   ⚠️ EL SELECTOR ES `.vista-qty .qnum`, y es un `<input>`. El primer
   informe pedía `.vista-qty-num`, **una clase que no existe**: ese CSS no
   habría fallado, habría dejado de pintar — el mismo fallo del
   `tipo:'lista'` del eco que nadie vio en meses. Se comprobó en la
   pantalla antes de escribirlo.

   260ms porque por debajo de 200 un rebote se lee como parpadeo. Escala
   1,22 porque por debajo de 1,15 no se nota en un glifo de 15px y por
   encima de 1,3 el número invade a los botones −/+. Es `transform` +
   `color`: compositor puro, cero recolocación. */
@keyframes ven-sube-num{
  0%   { transform: scale(1);    color: var(--texto); }
  40%  { transform: scale(1.22); color: var(--marca); }
  100% { transform: scale(1);    color: var(--texto); }
}
.ven-recien.ven-suma .vista-qty .qnum{
  animation: ven-sube-num .26s cubic-bezier(.34,1.56,.64,1);
  display: inline-block;   /* `transform` no aplica a un inline */
}

/* Quien pidió menos movimiento en su aparato no lo ve moverse — pero
   SIGUE VIENDO CUÁL ES. Se quitan las animaciones, se queda el velo: la
   marca es información, el movimiento es adorno. El apagado también se
   queda, en seco: sin él la marca del anterior no se iría nunca. */
@media (prefers-reduced-motion: reduce){
  .vista-card-item.ven-entra-abajo,
  .vista-card-item.ven-entra-arriba,
  .vista-cards .vista-card-item.ven-nace > div,
  .ven-recien.ven-suma .vista-qty .qnum{ animation:none; }
  .vista-cards .vista-card-item.ven-sale > div{
    animation:none; background:transparent;
  }
}

body.tablixa .vista-cards-lista{
  display:grid !important;
  grid-template-columns:1fr 1fr;
  align-content:start;
}
/* El separador de grupo cruza las DOS columnas: si no, partiría la fila */
body.tablixa .vista-cards-lista > .vista-sep{ grid-column:1/-1; }

/* ── El separador de grupo: una LÍNEA DE COLOR ──────────────────────────
   David: "las categorías por fecha se quitan de arriba y se pueden poner con
   una línea de color que, si se toca, muestra la fecha, y comienza la
   siguiente etapa". En este modo cada renglón de texto es alto que se le
   quita a la tabla — así que el grupo se marca con una raya fina y el texto
   solo aparece cuando lo pides. */
body.tablixa .vista-sep{
  padding:0 !important;
  background:transparent !important;
  border:none !important;
  font-size:0 !important;              /* el texto no ocupa hasta que se pide */
  line-height:0 !important;
  height:10px;
  display:flex;
  align-items:center;
  cursor:pointer;
  overflow:hidden;
  transition:height .16s, font-size .16s;
}
body.tablixa .vista-sep::before{
  content:'';
  display:block;
  height:3px;
  width:100%;
  border-radius:2px;
  background:var(--sep-color, var(--marca));
}
/* Tocada: se abre y muestra la fecha */
body.tablixa .vista-sep.abierta{
  height:auto;
  font-size:calc(11px * var(--fs-card,1)) !important;
  line-height:1.6 !important;
  padding:2px 10px !important;
  gap:8px;
  font-weight:800;
  color:var(--texto-medio);
  text-transform:uppercase;
  letter-spacing:.5px;
}
body.tablixa .vista-sep.abierta::before{
  width:4px;
  height:14px;
  flex-shrink:0;
}

/* ══════════════════════════════════════════════════════════
   TECLADO DE UN PULGAR — flotante y transparente
   Solo la letra y el borde son sólidos: todo lo demás deja ver la
   tabla por debajo. Por eso no roba pantalla aunque esté encima.
   ══════════════════════════════════════════════════════════ */
#tp-capa{
  position:fixed;
  inset:0;
  z-index:800;                    /* el TECLADO va sobre todo, incluidos los overlays de los modulos (700) */
}
/* Mientras pide los pulgares: azul claro sobre todo */
#tp-capa.tp-esperando{
  background:rgba(56,189,248,.18);
  backdrop-filter:blur(1px);
  display:flex;
  align-items:center;
  justify-content:center;
}
/* La X para salir mientras pide los pulgares. Sin esto, quien abre el teclado
   sin querer queda encerrado: en 'esperando' la capa atrapa todos los toques. */
#tp-cancelar{
  position:absolute;
  top:10px;
  right:10px;
  width:40px;
  height:40px;
  border:none;
  border-radius:12px;
  background:rgba(15,23,42,.82);
  color:#fff;
  font-size:var(--fs-20);
  font-weight:800;
  font-family:inherit;
  line-height:1;
  cursor:pointer;
  display:flex;
  align-items:center;
  justify-content:center;
}
#tp-aviso{
  background:rgba(15,23,42,.82);
  color:#fff;
  padding:14px 22px;
  border-radius:16px;
  font-size:var(--fs-15);
  font-weight:700;
  text-align:center;
  line-height:1.5;
}
#tp-aviso span{
  display:block;
  font-size:var(--fs-12);
  font-weight:600;
  opacity:.75;
  margin-top:4px;
}
/* Ya puesto: la capa NO tapa, solo alojan las teclas */
#tp-capa.tp-puesto{
  background:transparent;
  /* DEJA PASAR todo lo que no sea una tecla: el teclado flota y la
     tabla de abajo se sigue usando. Solo las teclas atrapan el toque. */
  pointer-events:none;
}
#tp-capa.tp-puesto .tp-tecla,
#tp-capa.tp-puesto #tp-menu{ pointer-events:auto; }
/* EL PIE DE LOS OVERLAYS ES SAGRADO (arreglo del 2026-08-02).
   El boton "Terminar" no abria 19 de 20 veces: cada tecla pinta una zona de
   toque INVISIBLE de 84px (.tp-tecla::after) y esta capa va en z-index 800,
   encima de cualquier overlay (700). El dedo caia en una tecla que no se ve.
   Mientras haya un overlay abierto, JS mide su pie y lo pone en --tp-pie:
   clip-path recorta esa franja, y al recortarla tambien la saca del
   hit-testing — o sea, ahi las zonas invisibles dejan de existir.
   El teclado sigue sirviendo arriba; el pie vuelve a ser del overlay. */
#tp-capa.tp-puesto{
  clip-path: inset(0 0 var(--tp-pie, 0px) 0);
}

/* El BOTON es la zona de toque (grande e invisible). Lo que se VE es un
   circulo mas chico dibujado por ::before y centrado. El tamaño visible
   llega por --cara desde JS (escala con el zoom). El texto se centra
   sobre el circulo. */
.tp-tecla{
  position:absolute;
  border:0;
  background:transparent;
  color:#1e293b;
  font-weight:800;
  font-family:inherit;
  cursor:pointer;
  padding:0;
  display:flex;
  align-items:center;
  justify-content:center;
  text-shadow:0 1px 3px rgba(255,255,255,.9);
}
.tp-tecla::before{
  content:"";
  position:absolute;
  left:50%;
  top:50%;
  width:var(--cara,34px);
  height:var(--cara,34px);
  transform:translate(-50%,-50%);
  border-radius:50%;
  border:2px solid rgba(108,53,255,.55);
  background:rgba(255,255,255,.05);   /* por dentro casi no se ve */
  backdrop-filter:blur(2px);
  z-index:-1;                          /* detras del texto */
}
.tp-tecla:active{ color:#fff; }
.tp-tecla:active::before{ background:rgba(108,53,255,.35); }
/* LA ZONA DE TOQUE: un area invisible que se corre hacia donde cae el
   pulgar (--zona-dx/dy vienen de la calibracion). Amplia el toque sin
   mover lo que se ve. Es parte del boton, asi que atrapa el toque igual. */
.tp-tecla::after{
  content:"";
  position:absolute;
  left:50%;
  top:50%;
  width:var(--zona,84px);
  height:var(--zona,84px);
  transform:translate(calc(-50% + var(--zona-dx,0px)), calc(-50% + var(--zona-dy,0px)));
  border-radius:50%;
}

.tp-centro{
  position:absolute;
  width:52px;
  height:52px;
  border-radius:50%;
  border:2px dashed rgba(108,53,255,.5);
  background:rgba(255,255,255,.03);
  color:rgba(108,53,255,.8);
  font-size:var(--fs-22);
  font-weight:900;
  font-family:inherit;
  cursor:pointer;
  padding:0;
  line-height:1;
}
.tp-centro:active{ background:rgba(108,53,255,.25); }

/* Overlay de TOTALES en DOS COLUMNAS en Tablixa.
   Ahí sobra ancho y falta alto: partirlo en dos evita que quede
   larguísimo y haya que rodar para ver lo de abajo. */
body.tablixa #ven-tot-ov > div:first-child{
  display:grid !important;
  grid-template-columns:1fr 1fr;
  gap:10px !important;
  align-content:start;
  overflow-y:auto;
}
/* El bloque del cliente y el del total mandan: ocupan las dos columnas */
body.tablixa #ven-tot-cli{
  grid-column:1 / -1;
}
body.tablixa #ven-tot-ov [id$="-resumen"],
body.tablixa #ven-tot-ov .ven-tot-final{
  grid-column:1 / -1;
}

/* El 5 es el APOYO: se ve distinto porque no es una letra cualquiera */
.tp-tecla.tp-apoyo::before{
  border-style:dashed;
  border-color:rgba(108,53,255,.7);
  background:rgba(108,53,255,.06);
}
/* Modo calibrar: las teclas se pueden arrastrar */
#tp-capa.tp-calibrando{
  background:rgba(56,189,248,.10);
  pointer-events:auto !important;   /* si no, el toque en el vacio no llega */
}
/* Al calibrar se ve la ZONA de cada tecla (donde puede caer el pulgar) */
#tp-capa.tp-calibrando .tp-tecla::after{
  background:rgba(56,189,248,.14);
  border:1px dashed rgba(56,189,248,.55);
}
/* La tecla ELEGIDA para recalibrar resalta, y su zona tambien */
.tp-tecla.tp-elegida::before{
  border-color:#16a34a;
  border-width:3px;
  background:rgba(22,163,74,.32);
}
.tp-tecla.tp-elegida::after{
  background:rgba(22,163,74,.22);
  border-color:#16a34a;
}
#tp-calib{
  position:fixed;
  left:50%;
  bottom:20px;
  transform:translateX(-50%);
  max-width:88vw;
  background:rgba(15,23,42,.94);
  color:#fff;
  padding:16px 24px;
  border-radius:16px;
  font-size:var(--fs-20);
  font-weight:800;
  text-align:center;
  pointer-events:none;
  line-height:1.4;
}
#tp-calib span{
  display:block;
  font-size:var(--fs-14);
  font-weight:600;
  opacity:.75;
  margin-top:5px;
}
#tp-calib-ok{
  border:none;
  border-radius:9px;
  background:#16a34a;
  color:#fff;
  padding:7px 14px;
  font-size:var(--fs-13);
  font-weight:800;
  font-family:inherit;
  cursor:pointer;
}

/* Botón "Listo" para cerrar la calibración (guarda lo ajustado) */
#tp-calib-x{
  display:block;
  margin:12px auto 0;
  border:none;
  border-radius:12px;
  background:#16a34a;
  color:#fff;
  font-size:var(--fs-16);
  font-weight:800;
  font-family:inherit;
  cursor:pointer;
  pointer-events:auto;
  padding:10px 24px;
  line-height:1;
}
#tp-calib{ position:fixed; }

/* Menú del teclado (gesto 5 → 9) */
/* Menú GRANDE, abajo al centro — entre los dos teclados */
#tp-menu{
  position:fixed;
  left:50%;
  bottom:14px;
  transform:translateX(-50%);
  width:min(460px, 78vw);
  background:rgba(15,23,42,.96);
  border-radius:20px;
  padding:12px;
  display:flex;
  flex-direction:column;
  gap:6px;
  pointer-events:auto;
  box-shadow:0 16px 50px rgba(0,0,0,.5);
  z-index:10;
}
.tp-menu-tit{
  color:rgba(255,255,255,.55);
  font-size:var(--fs-12);
  font-weight:800;
  text-transform:uppercase;
  letter-spacing:.6px;
  padding:2px 6px 6px;
}
#tp-menu button{
  border:none;
  background:rgba(255,255,255,.07);
  color:#fff;
  font-size:var(--fs-17);
  font-weight:700;
  font-family:inherit;
  padding:16px 20px;
  border-radius:14px;
  cursor:pointer;
  text-align:left;
}
#tp-menu .tp-menu-cerrar{ background:rgba(220,38,38,.35); }
#tp-menu button:active{ background:rgba(108,53,255,.4); }

/* Modo mover: se nota que el teclado está agarrado */
#tp-capa.tp-moviendo .tp-tecla::before{
  border-color:#0891b2;
  background:rgba(8,145,178,.25);
}
#tp-capa.tp-moviendo .tp-apoyo::before{
  border-style:solid;
  border-width:3px;
  background:rgba(8,145,178,.45);
}

/* Modo memoria muscular: las teclas se vuelven invisibles pero siguen
   respondiendo. Solo queda el apoyo para saber dónde estás parado. */
.tp-tecla.tp-fantasma{
  color:transparent;
  text-shadow:none;
}
.tp-tecla.tp-fantasma::before{
  border-color:transparent;
  background:transparent;
}
.tp-tecla.tp-fantasma:active{
  color:rgba(30,41,59,.6);
}
.tp-tecla.tp-fantasma:active::before{
  border-color:rgba(108,53,255,.4);
  background:rgba(108,53,255,.15);
}

/* ══════════════════════════════════════════════════════════════
   TECLADO DE DESLIZAR (teclado-desliz.js) — pantalla limpia:
   solo los dos apoyos y la letra que se va eligiendo, abajo al centro.
   ══════════════════════════════════════════════════════════════ */
#td-capa{
  position:fixed;
  inset:0;
  z-index:800;                    /* el TECLADO va sobre todo, incluidos los overlays de los modulos (700) */
  touch-action:none;              /* MATA el pinch-zoom y el scroll del sistema */
}
/* Solo VISUAL: deja pasar los toques (la app se puede usar cuando no hay
   dos pulgares). Los toques los maneja document; solo captura con 2 dedos. */
#td-capa.td-puesto{
  background:transparent;
  pointer-events:none;
}
/* Mientras se escribe (dos pulgares), el dedo no debe seleccionar texto ni
   sacar el menu del sistema. */
body.td-escribiendo{
  -webkit-user-select:none;
  user-select:none;
  -webkit-touch-callout:none;
}
/* 🔴 Y MIENTRAS HAYA UN DEDO, NADA DE LA PANTALLA SIRVE. Dictado por David
   el 2026-09-03: *«mientras se tenga uno de los dos, nada de la pantalla
   sirve, ni el footer ni card ni opciones, solo si el teclado la llama»*.

   🔑 Es mejor que una lista de qué se apaga —«que el footer no responda»—
   porque **una regla sin excepciones no se pudre**. Y arregla lo medido:
   con el teclado puesto, un toque de UN dedo llegaba a un `span` del
   footer **y cobraba**; el teclado solo se quedaba con el gesto cuando
   había dos dedos apoyados.

   ⚠️ Lo del teclado SÍ responde: su capa y su botón de micrófono se
   reactivan abajo. Y esto NO es `display:none`: la pantalla se sigue
   viendo entera — lo que se apaga es el dedo, no la vista. */
body.td-escribiendo #app,
body.td-escribiendo .topbar,
body.td-escribiendo #sidebar,
body.td-escribiendo #eco-sheet-mobile,
body.td-escribiendo #sheetBackdrop{
  pointer-events:none !important;
}
/* La capa del teclado manda: se pone después para ganar por especificidad
   aunque un ancestro esté apagado. */
body.td-escribiendo #td-capa,
body.td-escribiendo #td-micro,
body.td-escribiendo #td-menu,
body.td-escribiendo #td-variantes,
body.td-escribiendo .td-tecla{
  pointer-events:auto !important;
}
.td-apoyo, .td-dedo{
  position:absolute;
  border-radius:50%;
  transform:translate(-50%,-50%);
  pointer-events:none;
}
/* La X grande que separa las 4 zonas: bien marcada, es la frontera que hay
   que ver de un vistazo. */
.td-x{
  position:absolute;
  height:3px;
  background:rgba(108,53,255,.55);
  box-shadow:0 0 6px rgba(108,53,255,.35);
  transform-origin:center;
  pointer-events:none;
}
/* Protagonista: el lado que escribe se ve fuerte; el que reposa, atenuado */
.td-lado{ opacity:.32; transition:opacity .12s; }
.td-lado.td-lado-on{ opacity:1; }
/* El centro (apoyo FIJO): disco casi opaco que TAPA el centro de la X, para
   que la X "empiece afuera". Con ‹ (borra) a la izquierda y › (espacio) a la
   derecha. */
.td-apoyo{
  background:rgba(237,233,254,.86);
  border:2px solid rgba(108,53,255,.6);
  display:flex; align-items:center; justify-content:center;
  font-size:30px; font-weight:800; color:#6c35ff;
  transition:transform .1s, background .1s, border-color .1s;
}
/* Pulgar MODIFICADOR: sostenido en el filo -> rojo y grande. La otra mano
   pasa a la etapa 2. */
.td-apoyo-mod{
  background:rgba(239,68,68,.92);
  border-color:#b91c1c;
  color:#fff;
  transform:translate(-50%,-50%) scale(1.5);
  box-shadow:0 0 0 10px rgba(239,68,68,.22);
}
/* La mano que quedo en etapa 2 se tiñe distinto (para saber que cambio) */
.td-lado-e2 .td-zona-desliz{ background:rgba(52,211,153,.5); }   /* etapa 2 = verde */
.td-lado-e2 .td-zona-tocar{ background:rgba(45,212,191,.42); }
/* Hacia donde va el dedo ahora */
.td-dedo{
  width:38px; height:38px;
  background:rgba(108,53,255,.55);
  box-shadow:0 0 0 9px rgba(108,53,255,.13);
}
/* Las zonas del mapa: cada cuadrante da DOS letras. Interior (amarillo) = la
   letra que sale si DESLIZAS hacia ese cuadrante; exterior (rojo) = la que
   sale si TOCAS (tap) en ese cuadrante. Se ILUMINAN cuando el dedo cae. */
.td-zona{
  position:absolute;
  transform:translate(-50%,-50%);
  min-width:28px; height:28px;
  padding:0 5px;
  border-radius:14px;
  display:flex; align-items:center; justify-content:center;
  font-size:var(--fs-16); font-weight:800;
  color:#3b0764;
  pointer-events:none;
  border:2px solid transparent;
  text-shadow:0 1px 2px rgba(255,255,255,.6);
}
.td-zona-desliz{ background:rgba(250,204,21,.55); }   /* deslizar afuera = amarillo (interior) */
.td-zona-tocar{ background:rgba(248,113,113,.5); }    /* tocar          = rojo (medio)       */
.td-zona-reverso{ background:rgba(96,165,250,.5); }   /* deslizar inverso = azul (exterior)  */
/* MODO PRINCIPIANTE — el TABLERO ("picadora de masa"): por mano, un cuadrado
   partido por la X en 4 (deslizar afuera) y un marco partido en 8 (tocar e
   inverso). Las 12 piezas tienen la misma area. */
.td-tecla{ pointer-events:auto; cursor:pointer; }
#td-tablero{
  position:absolute;
  left:0; top:0;
  pointer-events:none;   /* solo las piezas reciben el toque */
}
.td-k{
  pointer-events:auto;
  stroke:rgba(255,255,255,.9);
  stroke-width:2.5;
  stroke-linejoin:round;
}
.td-k-d{ fill:rgba(250,204,21,.72); }   /* deslizar afuera = amarillo */
.td-k-t{ fill:rgba(248,113,113,.62); }  /* tocar           = rojo     */
.td-k-r{ fill:rgba(96,165,250,.62); }   /* inverso         = azul     */
.td-tablero-tocar .td-k:active{ fill:#fff; }
/* Vista de GUIA: el tablero es un MAPA, no botones. Se ve SOLIDO (para leerlo
   bien) pero no recibe toques: ahi se escribe por gesto. */
.td-tablero-guia .td-k{ pointer-events:none; }

/* ══ TEMAS ══════════════════════════════════════════════════════════
   1) claro  — el de siempre: zonas de color y letras oscuras.
   2) oscuro — teclado negro; en el de botones las letras van blancas y en el
      gestual toman el color de su gesto.
   3) neon   — vidrio oscuro con bordes y letras luminosas del color del gesto.
*/
/* ── Tema OSCURO ── */
.td-tema-oscuro .td-k{ stroke:rgba(255,255,255,.20); stroke-width:2; }
.td-tema-oscuro .td-k-d{ fill:rgba(24,24,32,.94); }
.td-tema-oscuro .td-k-t{ fill:rgba(18,18,26,.94); }
.td-tema-oscuro .td-k-r{ fill:rgba(12,12,20,.94); }
.td-tema-oscuro .td-kt{ fill:#f8fafc; }
/* en el gestual, la LETRA lleva el color del gesto */
.td-tema-oscuro .td-tablero-guia .td-kt-d{ fill:#fbbf24; }
.td-tema-oscuro .td-tablero-guia .td-kt-t{ fill:#f87171; }
.td-tema-oscuro .td-tablero-guia .td-kt-r{ fill:#60a5fa; }
.td-tema-oscuro .td-tablero-tocar .td-k:active{ fill:rgba(108,53,255,.85); }
.td-tema-oscuro .td-x{ background:rgba(255,255,255,.30); box-shadow:none; }
.td-tema-oscuro .td-apoyo{
  background:rgba(15,15,22,.95); border-color:rgba(255,255,255,.45); color:#fff;
}
.td-tema-oscuro #td-cara{ color:#e2e8f0; text-shadow:0 2px 10px rgba(0,0,0,.8); }
.td-tema-oscuro #td-hint{ color:rgba(226,232,240,.6); text-shadow:0 1px 3px rgba(0,0,0,.8); }
.td-tema-oscuro #td-manija span{ background:rgba(255,255,255,.45); }

/* ── Tema NEÓN ── */
.td-tema-neon .td-k{ fill:rgba(4,7,20,.78); stroke-width:2.5; }
.td-tema-neon .td-k-d{ stroke:#fde047; }
.td-tema-neon .td-k-t{ stroke:#fb7185; }
.td-tema-neon .td-k-r{ stroke:#38bdf8; }
.td-tema-neon .td-kt-d{ fill:#fde047; }
.td-tema-neon .td-kt-t{ fill:#fb7185; }
.td-tema-neon .td-kt-r{ fill:#38bdf8; }
.td-tema-neon #td-tablero{ filter:drop-shadow(0 0 6px rgba(56,189,248,.45)); }
.td-tema-neon .td-tablero-tocar .td-k:active{ fill:rgba(56,189,248,.55); }
.td-tema-neon .td-x{
  background:linear-gradient(90deg, rgba(253,224,71,.1), rgba(56,189,248,.85), rgba(251,113,133,.1));
  height:2px; box-shadow:0 0 10px rgba(56,189,248,.7);
}
.td-tema-neon .td-apoyo{
  background:rgba(4,7,20,.9);
  border-color:#38bdf8; color:#38bdf8;
  box-shadow:0 0 14px rgba(56,189,248,.55), inset 0 0 10px rgba(56,189,248,.25);
}
.td-tema-neon #td-cara{ color:#e0f2fe; text-shadow:0 0 14px rgba(56,189,248,.8); }
.td-tema-neon #td-hint{ color:rgba(186,230,253,.65); text-shadow:0 0 8px rgba(2,6,23,.9); }
.td-tema-neon #td-manija span{ background:#38bdf8; box-shadow:0 0 10px rgba(56,189,248,.8); }
.td-tema-neon .td-enter{ background:rgba(56,189,248,.85); box-shadow:0 0 14px rgba(56,189,248,.5); }
.td-kt{
  pointer-events:none;
  text-anchor:middle;
  dominant-baseline:central;
  font-family:inherit;
  font-weight:800;
  fill:#1a0533;
}
/* El COLOR es el que dice el gesto: amarillo = deslizar afuera, rojo = tocar,
   azul = deslizar inverso. (Se quitaron las flechitas y el punto: no se veían
   y el color ya lo dice.) */
.td-apoyo.td-tecla:active{ transform:translate(-50%,-50%) scale(.94); }
/* Enter del modo principiante: abajo a la derecha, junto al pie */
.td-enter{
  position:fixed;
  right:12px; bottom:18px;
  width:64px; height:54px;
  border-radius:14px;
  background:rgba(108,53,255,.9);
  color:#fff; font-size:26px; font-weight:800;
  display:flex; align-items:center; justify-content:center;
  box-shadow:0 3px 12px rgba(15,23,42,.3);
  z-index:5;
}
.td-enter:active{ background:#6c35ff; }
/* Manija: se arrastra para subir y bajar el teclado (debajo de los dos) */
#td-manija{
  position:fixed;
  left:50%;
  transform:translateX(-50%);
  width:150px; height:30px;
  display:flex; align-items:center; justify-content:center;
  pointer-events:auto;
  cursor:ns-resize;
  touch-action:none;
  z-index:5;
}
#td-manija span{
  display:block;
  width:110px; height:6px;
  border-radius:3px;
  background:rgba(108,53,255,.55);
  box-shadow:0 1px 4px rgba(15,23,42,.25);
}
#td-manija:active span{ background:#6c35ff; height:8px; }
.td-zona-on{
  border-color:#fff;
  box-shadow:0 0 12px 2px rgba(255,255,255,.7);
  transform:translate(-50%,-50%) scale(1.28);
  color:#1a0533;
}
/* La letra que se va eligiendo: grande, abajo al centro */
#td-guia{
  position:fixed;
  left:0; right:0;
  /* 🔴 ARRIBA DEL FOOTER, NO ENCIMA. Medido el 2026-09-03: el pie iba de
     y=692 a y=820 y el footer del panel de 776 a 834 — **se pisaban 44
     px**, y como el pie es transparente **se veía el footer por debajo
     de la letra**. David: *«que si hay footer se ponga con una base para
     verse y no confundirse»*.

     ⚠️ Y sube, no baja: al techo se alejaría del pulgar justo mientras
     se escribe, que es lo contrario de para qué existe el pie. */
  bottom:calc(24px + var(--td-pie-sube, 58px));
  text-align:center;
  pointer-events:none;
}
#td-cara{
  display:block;
  min-height:78px;
  font-size:72px;
  font-weight:800;
  line-height:1;
  color:#1e293b;
  text-shadow:0 2px 10px rgba(255,255,255,.95);
}
#td-hint{
  display:block;
  margin-top:4px;
  font-size:var(--fs-12);
  font-weight:700;
  color:rgba(30,41,59,.55);
  text-shadow:0 1px 3px rgba(255,255,255,.9);
}
/* El PIE (la letra grande) es tocable: tap = variantes (acentos/raras/mayus),
   sostener = menu. Se le da area aunque este vacio, para poder sostenerlo. */
#td-cara{ pointer-events:auto; min-width:64px; padding:0 10px; }
/* 🎨 LA BASE — CEÑIDA A CADA PIEZA, no una banda sobre la pantalla.
   ⚠️ La primera versión era un rectángulo de 145px con opacidad .82 sobre
   todo el ancho, y **lo cazó una foto**: lavaba media pantalla —«Pedidos»
   y «Devoluciones» se veían borrosos detrás— y parecía que la app se
   había atenuado sola. Un banco no lo vio: medía que la base EXISTIERA,
   no cuánto tapaba.

   Ceñida a la letra y al aviso, la base hace lo suyo —que se lean sobre
   cualquier fondo— sin apagar la pantalla de detrás. */
/* \u26a0\ufe0f VA DESPUES de las reglas de arriba a proposito: aquellas ponen
   `display:block` y `padding:0 10px`, y una regla igual de especifica
   solo gana si viene despues. Puesta antes, la base salia del ancho de
   la pantalla y el banco la daba por buena. */
#td-cara{
  display:inline-block;
  background:rgba(255,255,255,.90);
  border-radius:16px;
  padding:2px 14px;
  box-shadow:0 2px 14px rgba(15,23,42,.10);
}
#td-hint{
  display:inline-block;
  background:rgba(255,255,255,.90);
  border-radius:11px;
  padding:3px 10px;
  margin-top:6px;
}
.td-tema-oscuro #td-cara, .td-tema-neon #td-cara,
.td-tema-oscuro #td-hint, .td-tema-neon #td-hint{
  background:rgba(15,23,42,.88);
}

#td-cara.td-cara-var{ cursor:pointer; }
#td-cara.td-cara-vacio{ font-size:34px; opacity:.35; }
/* El pie cuando estas borrando o dando espacio: el simbolo y cuantos llevas */
#td-cara.td-cara-accion{ font-size:52px; color:#6c35ff; }
#td-cara.td-cara-accion b{ font-size:34px; opacity:.75; margin-left:6px; }
#td-cara.td-cara-var i{
  font-style:normal; color:#6c35ff; font-size:26px; vertical-align:super;
  margin-left:2px; opacity:.85;
}
/* El PANEL DEL PIE: sale al tocar el tercer circulo. Siempre dos filas
   (enter + 3 accesos, y las variantes de la letra); tres si se enciende la
   fila de numeros. */
#td-variantes{
  position:fixed;
  left:50%; bottom:112px;
  transform:translateX(-50%);
  display:flex; flex-direction:column; gap:8px;
  background:rgba(15,23,42,.96);
  padding:12px 14px;
  border-radius:16px;
  pointer-events:auto;
  z-index:820;
  max-width:96vw;
  box-shadow:0 8px 30px rgba(0,0,0,.35);
}
#td-variantes .td-fila{ display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
#td-variantes button{
  border:none; border-radius:12px;
  background:rgba(108,53,255,.32);
  color:#fff; font-family:inherit;
  font-size:28px; font-weight:800;
  min-width:52px; padding:9px 12px;
  cursor:pointer;
}
#td-variantes button:active{ background:rgba(108,53,255,.6); }
/* Fila de arriba: enter y los 3 accesos que uno se arma */
#td-variantes .td-fila-rapida button{ background:rgba(148,163,184,.26); }
#td-variantes .td-fila-rapida button:first-child{ background:rgba(108,53,255,.45); }
#td-variantes .td-slot{ color:rgba(255,255,255,.5); font-weight:700; }
/* Fila de numeros (opcional) */
#td-variantes .td-fila-num button{
  background:rgba(56,189,248,.28); font-size:24px; min-width:44px; padding:7px 9px;
}
/* Fila de variantes: la base primero, luego acentos */
#td-variantes .td-fila-var button:first-child{ background:rgba(148,163,184,.3); }
#td-variantes .td-fila-nota{
  color:rgba(255,255,255,.5); font-size:var(--fs-13); font-weight:600; padding:6px 2px;
}
/* Menu del deslizar (juntar los dos pulgares) */
#td-menu{
  position:fixed;
  left:50%; top:50%;
  transform:translate(-50%,-50%);
  display:flex;
  flex-direction:column;
  gap:12px;
  background:rgba(15,23,42,.96);
  padding:22px;
  border-radius:20px;
  pointer-events:auto;
  min-width:300px;
  max-width:88vw;
}
#td-menu .td-menu-tit{
  color:#fff; font-size:var(--fs-15); font-weight:800;
  text-align:center; opacity:.75; margin-bottom:6px;
}
#td-menu .td-menu-sub{
  color:#c4b5fd; font-size:var(--fs-12); font-weight:800;
  text-transform:uppercase; letter-spacing:.5px;
  margin:4px 0 -4px 4px; opacity:.8;
}
#td-menu .td-menu-fila{ display:flex; gap:8px; }
#td-menu .td-menu-fila button{ flex:1; padding:14px 8px; font-size:var(--fs-16); }
#td-menu .td-menu-fila button.on{ background:rgba(108,53,255,.75); }
/* Accesos directos del menu: enter, espacio, borrar y el punto */
#td-menu .td-menu-rapido{ margin-bottom:2px; }
#td-menu .td-menu-rapido button{
  padding:16px 6px; font-size:24px;
  background:rgba(148,163,184,.22);
}
#td-menu .td-menu-rapido button:active{ background:rgba(108,53,255,.6); }
#td-menu button{
  border:none;
  border-radius:13px;
  background:rgba(108,53,255,.30);
  color:#fff;
  padding:18px 20px;
  font-size:var(--fs-18);
  font-weight:700;
  font-family:inherit;
  cursor:pointer;
}
#td-menu button:active{ background:rgba(108,53,255,.5); }

/* ── Módulo Mecanografía ──────────────────────────────────
   Panel 1 = historial y estado · Panel 2 = herramientas ·
   lo que se abre va en overlay encima del panel 2. */
/* Panel 1: estado */
.mec-estado{ padding:14px 14px 6px; }
.mec-nivel{ display:flex; align-items:center; gap:14px; margin-bottom:14px; }
.mec-nivel-n{
  width:52px; height:52px; border-radius:16px; flex-shrink:0;
  background:linear-gradient(135deg,#6c35ff,#a855f7);
  color:#fff; font-size:24px; font-weight:800;
  display:flex; align-items:center; justify-content:center;
}
.mec-nivel-t{ font-size:var(--fs-16); font-weight:800; color:#1e293b; }
.mec-nivel-d{ font-size:var(--fs-13); color:#64748b; }
.mec-cifras{ display:flex; gap:10px; }
.mec-cifra{
  flex:1; background:#f8fafc; border:2px solid #e2e8f0; border-radius:14px;
  padding:10px; text-align:center;
}
.mec-cifra b{ display:block; font-size:var(--fs-20); font-weight:800; color:#6c35ff; }
.mec-cifra span{ font-size:var(--fs-11); color:#64748b; font-weight:700; }
.mec-vacio{ padding:40px 24px; text-align:center; }
.mec-vacio-ico{ font-size:44px; opacity:.5; }
.mec-vacio-t{ font-size:var(--fs-16); font-weight:800; color:#1e293b; margin-top:8px; }
.mec-vacio-d{ font-size:var(--fs-13); color:#94a3b8; margin-top:4px; }
.mec-lista{ padding:8px 12px 16px; display:flex; flex-direction:column; gap:8px; }
.mec-item{ display:flex; align-items:center; gap:12px; padding:10px 12px;
  background:#fff; border:1px solid #e2e8f0; border-radius:14px; }
.mec-item-ico{ width:38px; height:38px; border-radius:11px; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; font-size:var(--fs-18); background:#f1f5f9; }
.mec-item-ico.ok{ background:#dcfce7; }
.mec-item-ico.medio{ background:#fef3c7; }
.mec-item-ico.mal{ background:#fee2e2; }
.mec-item-txt{ flex:1; min-width:0; }
.mec-item-t{ font-size:var(--fs-14); font-weight:700; color:#1e293b; }
.mec-item-d{ font-size:var(--fs-12); color:#94a3b8; }
.mec-item-pts{ font-size:var(--fs-14); font-weight:800; color:#16a34a; }
/* Panel 2: herramientas */
.mec-p2{ padding:20px 16px 8px; }
.mec-p2-t{ font-size:var(--fs-22); font-weight:800; color:#1e293b; margin-bottom:4px; }
.mec-p2-d{ font-size:var(--fs-14); color:#64748b; margin-bottom:18px; }
.mec-tools{ display:flex; flex-direction:column; gap:10px; }
.mec-tool{ display:flex; align-items:center; gap:14px; padding:16px;
  border-radius:16px; border:2px solid #e2e8f0; background:#f8fafc;
  cursor:pointer; text-align:left; width:100%; font-family:inherit; }
.mec-tool:active{ background:#eef2ff; }
.mec-tool-ico{ width:48px; height:48px; border-radius:14px; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; }
.mec-tool-t{ font-size:var(--fs-15); font-weight:800; color:#1e293b; }
.mec-tool-d{ font-size:var(--fs-12); color:#64748b; }
/* Overlay (encima del panel 2). En el CELULAR va a PANTALLA COMPLETA: el
   teclado ocupa media pantalla y no cabe nada mas. Las acciones viven ARRIBA,
   nunca debajo del teclado. */
.mec-ov{
  position:absolute; inset:0; background:#fff; z-index:25;
  display:flex; flex-direction:column; overflow:hidden;
  transform:translateX(100%); transition:transform .3s cubic-bezier(.4,0,.2,1);
}
@media (max-width:899px){
  .mec-ov{ position:fixed; inset:0; z-index:700; }
}
.mec-ov.on{ transform:translateX(0); }
.mec-ov-top{ display:flex; align-items:center; gap:10px; padding:12px 14px;
  border-bottom:1px solid #e2e8f0; flex-shrink:0; }
.mec-ov-back{ width:36px; height:36px; border-radius:10px; background:#f1f5f9;
  display:flex; align-items:center; justify-content:center; border:none; cursor:pointer; flex-shrink:0; }
.mec-ov-t{ font-size:var(--fs-16); font-weight:800; color:#1e293b; flex:1; min-width:0;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mec-ov-acc{ display:flex; gap:8px; flex-shrink:0; }
.mec-btn.mini{ padding:8px 12px; font-size:var(--fs-13); border-radius:10px; }
.mec-ov-body{ flex:1; overflow-y:auto; }
/* Practicar */
.mec-pr{ padding:14px 16px; display:flex; flex-direction:column; gap:12px; }
.mec-pr-cfg{ display:flex; flex-direction:column; gap:8px; }
.mec-pr-nivel{ font-size:var(--fs-12); color:#64748b; }
.mec-chips{ display:flex; gap:8px; flex-wrap:wrap; }
.mec-chip{ border:2px solid rgba(108,53,255,.25); background:#fff; color:#3b0764;
  border-radius:12px; padding:8px 14px; font-size:var(--fs-14); font-weight:700;
  font-family:inherit; cursor:pointer; }
.mec-chip.on{ background:#6c35ff; border-color:#6c35ff; color:#fff; }
.mec-chip sub{ font-size:var(--fs-10); opacity:.7; vertical-align:super; }
.mec-pr-texto{ font-size:var(--fs-20); line-height:1.7; font-weight:600; color:#94a3b8;
  background:#f8fafc; border-radius:14px; padding:14px; letter-spacing:.5px; }
.mec-pr-texto .bien{ color:#16a34a; }
.mec-pr-texto .mal{ color:#dc2626; background:#fee2e2; border-radius:4px; }
.mec-pr-texto .aqui{ color:#1e293b; border-bottom:3px solid #6c35ff; }
.mec-pr-input{ width:100%; font-size:var(--fs-18); padding:12px; border-radius:12px;
  border:2px solid #e2e8f0; font-family:inherit; resize:none; }
.mec-pr-marcador{ display:flex; gap:10px; }
.mec-pr-marcador div{ flex:1; text-align:center; background:#f8fafc;
  border:2px solid #e2e8f0; border-radius:12px; padding:8px; }
.mec-pr-marcador b{ display:block; font-size:var(--fs-19); font-weight:800; color:#6c35ff; }
.mec-pr-marcador span{ font-size:var(--fs-11); color:#64748b; font-weight:700; }
.mec-pr-acciones{ display:flex; gap:10px; flex-wrap:wrap; }
.mec-btn{ border:2px solid rgba(108,53,255,.3); background:#fff; color:#6c35ff;
  border-radius:11px; padding:11px 16px; font-size:var(--fs-15); font-weight:700;
  font-family:inherit; cursor:pointer; }
.mec-btn:active{ background:rgba(108,53,255,.1); }
.mec-btn-ok{ background:#6c35ff; border-color:#6c35ff; color:#fff; }
.mec-si{ border-color:#16a34a; color:#16a34a; }
.mec-no{ border-color:#dc2626; color:#dc2626; }
/* Juego */
.mec-jg{ padding:14px 16px; display:flex; flex-direction:column; gap:12px; }
.mec-jg-marcador{ display:flex; gap:10px; }
.mec-jg-marcador div{ flex:1; text-align:center; background:#f8fafc;
  border:2px solid #e2e8f0; border-radius:12px; padding:8px; }
.mec-jg-marcador b{ display:block; font-size:var(--fs-19); font-weight:800; color:#be185d; }
.mec-jg-marcador span{ font-size:var(--fs-11); color:#64748b; font-weight:700; }
.mec-jg-pista{ position:relative; height:220px; border-radius:14px;
  background:linear-gradient(#faf5ff,#f1f5f9); border:2px solid #e2e8f0; overflow:hidden; }
.mec-jg-letra{ position:absolute; font-size:26px; font-weight:800; color:#6c35ff;
  transform:translate(-50%,-50%); transition:top .25s linear; }
/* Dibujar */
.mec-db{ padding:14px 16px; display:flex; flex-direction:column; align-items:center; gap:12px; }
.mec-db-hint{ font-size:var(--fs-13); color:#475569; text-align:center; max-width:340px; }
.mec-db-letras{ display:flex; flex-wrap:wrap; gap:6px; justify-content:center; max-width:360px; }
.mec-db-letras .mec-chip{ padding:6px 10px; min-width:34px; font-size:var(--fs-14); }
.mec-db-padwrap{ position:relative; }
#mec-db-pad{ width:300px; max-width:82vw; height:auto; aspect-ratio:1/1;
  border-radius:50%; touch-action:none; background:#fff;
  box-shadow:0 4px 20px rgba(108,53,255,.12); }
.mec-db-res{ position:absolute; inset:0; display:none; flex-direction:column;
  align-items:center; justify-content:center; pointer-events:none; gap:8px; }
.mec-db-res.on{ display:flex; }
.mec-db-res .mec-db-jueza, .mec-db-res .mec-btn{ pointer-events:auto; }
.mec-db-big{ font-size:80px; font-weight:800; color:#6c35ff; line-height:1;
  text-shadow:0 2px 12px rgba(255,255,255,.9); }
.mec-db-tops{ display:flex; gap:8px; }
.mec-db-cand{ font-size:var(--fs-13); font-weight:700; color:#64748b;
  background:rgba(255,255,255,.85); padding:3px 8px; border-radius:8px; }
.mec-db-cand.uno{ color:#6c35ff; }
.mec-db-ok{ font-size:36px; font-weight:800; color:#16a34a; }
.mec-db-mal{ font-size:var(--fs-20); font-weight:800; color:#dc2626; }
.mec-db-jueza{ display:flex; gap:10px; margin-top:6px; }
/* Canjes */
.mec-cj{ padding:14px 16px; display:flex; flex-direction:column; gap:12px; }
.mec-cj-saldo{ font-size:var(--fs-15); color:#475569; }
.mec-cj-saldo b{ font-size:var(--fs-22); color:#6c35ff; }
.mec-cj-lista{ display:flex; flex-direction:column; gap:8px; }
.mec-premio{ display:flex; align-items:center; justify-content:space-between;
  padding:14px 16px; border-radius:14px; border:2px solid #dcfce7;
  background:#f0fdf4; cursor:pointer; font-family:inherit; width:100%; }
.mec-premio.no{ opacity:.5; border-color:#e2e8f0; background:#f8fafc; }
.mec-premio-t{ font-size:var(--fs-15); font-weight:800; color:#1e293b; }
.mec-premio-c{ font-size:var(--fs-14); font-weight:800; color:#16a34a; }
.mec-cj-hist-t{ font-size:var(--fs-12); font-weight:800; color:#94a3b8;
  text-transform:uppercase; letter-spacing:.5px; margin-top:6px; }
.mec-cj-item{ font-size:var(--fs-13); color:#475569; padding:6px 0; border-bottom:1px solid #f1f5f9; }
.mec-cj-item.vacio{ color:#cbd5e1; }
/* Panel 3 */
.mec-p3{ padding:18px 16px; color:#334155; font-size:var(--fs-14); line-height:1.6; }
.mec-p3 h3{ margin:0 0 8px; color:#6c35ff; font-size:var(--fs-15); }
.mec-p3 h3:not(:first-child){ margin-top:18px; }
.mec-p3-niv{ margin:0; padding-left:18px; }
.mec-p3-niv li{ margin-bottom:6px; }
.mec-p3-niv li span{ display:block; font-size:var(--fs-12); color:#94a3b8; }
.mec-p3-niv li.aqui b{ color:#6c35ff; }

/* Juego RELÁMPAGO: una letra a la vez, con su reloj de reacción */
.mec-rl{ padding:14px 16px; display:flex; flex-direction:column; gap:12px; }
.mec-rl-marcador{ display:flex; gap:10px; }
.mec-rl-marcador div{ flex:1; text-align:center; background:#f8fafc;
  border:2px solid #e2e8f0; border-radius:12px; padding:8px; }
.mec-rl-marcador b{ display:block; font-size:var(--fs-19); font-weight:800; color:#7c3aed; }
.mec-rl-marcador span{ font-size:var(--fs-11); color:#64748b; font-weight:700; }
.mec-rl-caja{ background:linear-gradient(#faf5ff,#f5f3ff); border:2px solid #ddd6fe;
  border-radius:18px; padding:18px 14px 14px; text-align:center; }
.mec-rl-letra{ font-size:84px; line-height:1; font-weight:800; color:#7c3aed;
  transition:transform .12s, color .12s; }
.mec-rl-letra.entra{ animation:mec-rl-pop .18s ease-out; }
.mec-rl-letra.bien{ color:#16a34a; transform:scale(1.08); }
.mec-rl-letra.mal{ color:#dc2626; animation:mec-rl-no .2s; }
@keyframes mec-rl-pop{ from{ transform:scale(.7); opacity:.3 } to{ transform:scale(1); opacity:1 } }
@keyframes mec-rl-no{ 25%{ transform:translateX(-7px) } 75%{ transform:translateX(7px) } }
.mec-rl-barra{ height:8px; border-radius:4px; background:#ede9fe; overflow:hidden; margin-top:12px; }
.mec-rl-barra i{ display:block; height:100%; width:0; background:#22c55e; transition:width .06s linear; }
.mec-rl-barra i.medio{ background:#f59e0b; }
.mec-rl-barra i.lento{ background:#ef4444; }
.mec-rl-flojas{ font-size:var(--fs-13); color:#64748b; text-align:center; }
.mec-rl-flojas b{ display:inline-block; background:#fee2e2; color:#b91c1c;
  border-radius:8px; padding:2px 9px; font-size:var(--fs-15); margin:0 2px; }

/* ── MODO APP APARTE (/?solo=modulo) ──────────────────────
   El mismo contenedor de OMNIXA, pero mostrando un solo modulo: se esconde
   todo lo que habla del resto de la app. Misma sesion y misma base de datos. */
body.omnixa-solo #menuBtn,
body.omnixa-solo #sidebar,
body.omnixa-solo #sbBackdrop,
body.omnixa-solo #titleArrow,
body.omnixa-solo #sbRail,
body.omnixa-solo #fab-railes-wrap{ display:none !important; }
/* Boton discreto para descubrir el resto (la app chica es la puerta) */
#solo-salir{
  position:fixed;
  right:12px; bottom:12px;
  z-index:660;
  border:none; border-radius:999px;
  background:rgba(15,23,42,.86); color:#fff;
  font-family:inherit; font-size:var(--fs-12); font-weight:700;
  padding:9px 14px;
  box-shadow:0 3px 14px rgba(15,23,42,.3);
  cursor:pointer;
}
#solo-salir span{ opacity:.65; font-weight:600; margin-right:4px; }
#solo-salir:active{ background:#0f172a; }
body.td-escribiendo #solo-salir{ display:none; }   /* si escribes, estorba */

/* Juego CARRERA DE LETRAS (el carro) */
.mec-cr{ padding:14px 16px; display:flex; flex-direction:column; gap:10px; }
.mec-cr-marcador{ display:flex; gap:10px; }
.mec-cr-marcador div{ flex:1; text-align:center; background:#f8fafc;
  border:2px solid #e2e8f0; border-radius:12px; padding:8px; }
.mec-cr-marcador b{ display:block; font-size:var(--fs-19); font-weight:800; color:#0e7490; }
.mec-cr-marcador span{ font-size:var(--fs-11); color:#64748b; font-weight:700; }
.mec-cr-lienzo{ position:relative; border-radius:16px; overflow:hidden;
  border:2px solid #e2e8f0; background:#0f172a; }
#mec-cr-cv{ display:block; width:100%; height:auto; max-height:42vh; object-fit:contain; }
.mec-cr-pausa{
  position:absolute; inset:0; display:none;
  align-items:center; justify-content:center;
  background:rgba(15,23,42,.65); color:#fff;
  font-size:26px; font-weight:800; letter-spacing:3px;
}
.mec-cr-pausa.on{ display:flex; }
.mec-cr-mandos{ font-size:var(--fs-13); color:#64748b; text-align:center; }
.mec-cr-mandos b{ display:inline-block; background:#cffafe; color:#0e7490;
  border-radius:8px; padding:2px 9px; font-size:var(--fs-15); margin:0 2px; }

/* Juego FIEBRE: el tiempo se acaba y cada palabra te devuelve un poco */
.mec-fb{ padding:14px 16px; display:flex; flex-direction:column; gap:12px;
  border-radius:16px; transition:background .3s; }
.mec-fb.fiebre{ background:linear-gradient(#fff7ed,#ffedd5); }
.mec-fb.urgente .mec-fb-barra i{ animation:mec-fb-late .5s infinite; }
@keyframes mec-fb-late{ 50%{ opacity:.55 } }
.mec-fb-top{ display:flex; align-items:center; justify-content:space-between; gap:10px; }
.mec-fb-pts b{ font-size:26px; font-weight:800; color:#dc2626; }
.mec-fb-pts span{ font-size:var(--fs-11); color:#64748b; font-weight:700; margin-left:4px; }
.mec-fb-rec{ font-size:var(--fs-12); color:#94a3b8; font-weight:700; }
.mec-fb-rec b{ color:#475569; font-size:var(--fs-15); }
.mec-fb-combo{ font-size:var(--fs-20); font-weight:800; color:#f59e0b; opacity:0; transition:opacity .15s, transform .15s; }
.mec-fb-combo.on{ opacity:1; }
.mec-fb-combo.fuego{ color:#dc2626; transform:scale(1.25); text-shadow:0 0 12px rgba(220,38,38,.5); }
.mec-fb-barra{ height:14px; border-radius:7px; background:#e2e8f0; overflow:hidden; }
.mec-fb-barra i{ display:block; height:100%; width:100%; border-radius:7px;
  background:linear-gradient(90deg,#22c55e,#84cc16); transition:width .12s linear; }
.mec-fb-barra i.medio{ background:linear-gradient(90deg,#f59e0b,#fbbf24); }
.mec-fb-barra i.poco{ background:linear-gradient(90deg,#dc2626,#f87171); }
.mec-fb-pista{ position:relative; text-align:center; padding:18px 8px; min-height:110px;
  display:flex; align-items:center; justify-content:center; }
.mec-fb-palabra{ font-size:42px; font-weight:800; letter-spacing:3px; color:#cbd5e1; }
.mec-fb-palabra .bien{ color:#16a34a; }
.mec-fb-palabra .mal{ color:#dc2626; }
.mec-fb-palabra .aqui{ color:#1e293b; border-bottom:4px solid #dc2626; }
.mec-fb-aviso{ position:absolute; top:2px; left:0; right:0; text-align:center;
  font-size:var(--fs-22); font-weight:800; opacity:0; transform:translateY(6px);
  transition:opacity .15s, transform .15s; pointer-events:none; }
.mec-fb-aviso.on{ opacity:1; transform:translateY(-6px); }
.mec-fb-aviso.bien{ color:#16a34a; }
.mec-fb-aviso.mal{ color:#dc2626; }
.mec-fb-aviso.fuego{ color:#ea580c; font-size:28px; text-shadow:0 0 16px rgba(234,88,12,.6); }

/* Juego GLOBOS: caen operaciones y se revientan escribiendo el resultado */
.mec-gl{ padding:14px 16px; display:flex; flex-direction:column; gap:10px; }
.mec-gl-top{ display:flex; align-items:center; justify-content:space-between; gap:8px; flex-wrap:wrap; }
.mec-gl-pts b{ font-size:24px; font-weight:800; color:#0284c7; }
.mec-gl-pts span{ font-size:var(--fs-11); color:#64748b; font-weight:700; margin-left:4px; }
.mec-gl-vidas{ font-size:var(--fs-16); color:#ef4444; letter-spacing:2px; }
.mec-gl-rec{ font-size:var(--fs-12); color:#94a3b8; font-weight:700; }
.mec-gl-rec b{ color:#475569; font-size:var(--fs-15); }
.mec-gl-combo{ font-size:var(--fs-19); font-weight:800; color:#f59e0b; opacity:0; transition:opacity .15s, transform .15s; }
.mec-gl-combo.on{ opacity:1; }
.mec-gl-combo.fuego{ color:#dc2626; transform:scale(1.2); text-shadow:0 0 12px rgba(220,38,38,.45); }
.mec-gl-cielo{ position:relative; border-radius:16px; overflow:hidden; border:2px solid #e0f2fe; }
#mec-gl-cv{ display:block; width:100%; height:auto; max-height:42vh; object-fit:contain; }
.mec-gl-pausa{ position:absolute; inset:0; display:none; align-items:center; justify-content:center;
  background:rgba(15,23,42,.6); color:#fff; font-size:26px; font-weight:800; letter-spacing:3px; }
.mec-gl-pausa.on{ display:flex; }
.mec-gl-aviso{ position:absolute; top:8px; left:0; right:0; text-align:center;
  font-size:var(--fs-22); font-weight:800; opacity:0; transform:translateY(6px);
  transition:opacity .15s, transform .15s; pointer-events:none; }
.mec-gl-aviso.on{ opacity:1; transform:translateY(0); }
.mec-gl-aviso.bien{ color:#16a34a; }
.mec-gl-aviso.mal{ color:#dc2626; }
.mec-gl-aviso.fuego{ color:#ea580c; font-size:26px; text-shadow:0 0 14px rgba(234,88,12,.5); }

/* ═══════════════════════════════════════════════════════════════
   LOS BLOQUES «MOCHOS» — el contenido que se corta sin avisar

   David, 2026-08-27: *"cuando toco un producto hay campos que se ven
   mocho los bloques… y ese bloque no tiene scroll interno… y algunos
   otros bloques quedan mochos cuando aparecen más opciones"*.

   La medición, que es de donde sale todo esto:

       #cdet-body → pide 586px, tiene 586px   (cabe: no hay scroll)
       bloque 2   → pide 122px, tiene 101px   🔴 lo aplastaron
       bloque 3   → pide 169px, tiene 139px   🔴 lo aplastaron

   O sea: el contenedor no rueda **porque sus hijos se encogieron** para
   que todo cupiera. Y como esos bloques llevan `overflow:hidden` (para
   redondear las esquinas de sus filas), lo que sobra **se corta**. Eso
   es el «mocho».

   LA CAUSA FINA, que es la parte que casi nadie sabe: un flex item está
   protegido por `min-height:auto` («no te encojas por debajo de tu
   contenido»), **pero esa protección desaparece en cuanto el item tiene
   `overflow` distinto de `visible`** — el spec la resuelve a cero. Así
   que un `border-radius:16px;overflow:hidden`, escrito solo para las
   esquinas, es lo que abre la puerta al aplastamiento.

   LA CURA: en una lista que rueda, ningún hijo se encoge. Para eso está
   el scroll: si no cabe, se rueda — no se aprieta.

   ⚠️ HUBO AQUÍ UNA SEGUNDA REGLA, `min-height:0` SOBRE EL CONTENEDOR,
   Y SE QUITÓ. La escribió esta misma sesión creyendo que curaba «99
   sitios», y medida en Chromium **no curaba ninguno**: por la misma
   regla del spec de arriba, un elemento con `overflow-y:auto` YA tiene
   su mínimo automático en cero, así que rueda igual con o sin ella
   (100px de alto y scroll en los dos casos). Apuntaba justo a donde no
   hacía falta. Y no era inofensiva: un selector de atributo pesa lo
   mismo que una clase y este estaba al final del archivo, así que
   ganaba a cualquier clase anterior — medido, `.pista{min-height:110px}`
   con `overflow-y:auto` en línea quedaba **en 0px de alto**. Una regla
   que no cura y que puede aplastar no se deja «por si acaso».

   ⚠️ POR QUÉ UNA REGLA Y NO 26 EDICIONES. Arreglarlos a mano deja el
   problema vivo: el archivo siguiente vuelve a nacer mocho y nadie se
   acuerda de por qué.

   ⚠️ Y POR QUÉ PIDE `flex-direction:column`. Sin eso, la regla también
   alcanzaría una TIRA horizontal que rueda —chips, categorías— y ahí
   `flex-shrink:0` es lo contrario de lo que se quiere: mata la elipsis
   y saca scroll de lado (medido: tres chips que caben en 100px se van a
   177px). Hoy las dos tiras así del proyecto usan `overflow-x`, que
   esto no toca; exigir la columna hace que tampoco lo toque la que
   escriba alguien mañana.

   La excepción respetada: un hijo con `flex:1` o `flex-grow:1` es un
   scroll ANIDADO y sí tiene que repartirse el alto. Se escriben las
   cuatro grafías porque el navegador SERIALIZA el atributo: lo pintado
   con `innerHTML` conserva lo que escribió el autor, pero lo puesto por
   `el.style.cssText` se relee como `flex: 1 1 0%` — con espacio.
═══════════════════════════════════════════════════════════════ */
[style*="flex-direction:column"][style*="overflow-y:auto"]   > *,
[style*="flex-direction:column"][style*="overflow-y: auto"]  > *,
[style*="flex-direction:column"][style*="overflow:auto"]     > *,
[style*="flex-direction:column"][style*="overflow: auto"]    > *,
[style*="flex-direction:column"][style*="overflow-y:scroll"] > *,
[style*="flex-direction: column"][style*="overflow-y: auto"] > *{ flex-shrink:0; }

[style*="flex-direction:column"][style*="overflow-y:auto"]   > *[style*="flex:1"],
[style*="flex-direction:column"][style*="overflow-y:auto"]   > *[style*="flex: 1"],
[style*="flex-direction:column"][style*="overflow-y:auto"]   > *[style*="flex-grow:1"],
[style*="flex-direction:column"][style*="overflow-y:auto"]   > *[style*="flex-grow: 1"],
[style*="flex-direction:column"][style*="overflow:auto"]     > *[style*="flex:1"],
[style*="flex-direction:column"][style*="overflow:auto"]     > *[style*="flex: 1"],
[style*="flex-direction: column"][style*="overflow-y: auto"] > *[style*="flex: 1"]{ flex-shrink:1; }

/* ══════════════════════════════════════════════════════════════════
   🎩 EL GORRO DE LA CABECERA — de quién es esta pantalla
   ══════════════════════════════════════════════════════════════════
   David, 2026-09-11, con una foto de referencia al lado: *«ese nombre
   Omnixa es como el gorro del header; es como darle identidad a los
   módulos de app de otra parte»*.

   🔑 No es el logo de la app repetido: es **la marca de procedencia**
   del módulo. La pregunta «¿quién hizo esta pantalla?» solo tiene
   sentido cuando la respuesta puede ser otra — y el día que un módulo
   lo escriba alguien más, esa pantalla se abre dentro de Omnixa con la
   cara de Omnixa y quien la mira no tiene forma de saberlo.

   ⚠️ EL TAMAÑO DE LETRA VA CON `clamp()` PROPIO, Y NO ES UNA EXCEPCIÓN
   NUEVA. Es la misma de `#appTitle` y `#panelSubtitle`, con la misma
   razón escrita: *una cinta de identidad tiene techo*. Sigue a
   `--fs-app` —quien sube la letra la sube también aquí— pero no puede
   crecer sin límite, porque entonces el gorro se comería la pantalla
   que viene a presentar. La ley del piso protege lo que hay que LEER;
   esto no se lee, se reconoce.

   🔴 Y medido antes de construirlo: topbar 116px + header de la caja
   141px = 257 de 740, o sea **el 35% de la pantalla ya era cabecera**
   antes de añadir nada, y al 150% de letra el 43%. Por eso el gorro es
   una cinta de una línea y no una segunda barra: 24px con la letra
   normal. */
/* 🔑 SIN GORRO ES `display:contents` — o sea, NO EXISTE para el
   reparto. Es lo que hace que envolver la píldora siempre no cambie ni
   un píxel en los diez estilos de antes: el envoltorio está en el DOM
   desde el primer día (así `revestir()` puede encender el gorro sin
   rehacer el HTML y perder los listeners) y no estorba hasta que se
   enciende. */
.pil-cabecera{ display:contents; }
.pil-cabecera[data-gorro="si"]{
  display:block;
  position:relative;
  /* ⚠️ El aire de abajo NO es decoración: sin él la píldora toca el
     borde de la cinta y el gorro deja de leerse como una cosa que la
     envuelve — se ve como dos bandas pegadas. Y el margen lateral es
     0: con margen negativo la cinta salía más ancha que su padre y le
     robaba ancho a la píldora, que es donde viven los botones. */
  /* 🔴 A SANGRE Y CON LAS PAREDES FINAS. David, 2026-09-11, con el
     gorro ya en su celular: *«el header tiene que funcionar con la barra
     de notificaciones porque aspiro que los overlay tomen toda la
     pantalla… y la línea de las paredes y debajo son más finas»*. El
     gorro se come el padding del header que lo contiene (14px 16px en
     las cabeceras de Cuentas; otra pantalla lo declara con las dos
     variables) y llega hasta el borde de arriba, pegado a la barra del
     sistema. Sin esto quedaba una franja blanca alrededor, como una
     tarjeta puesta encima, y no como el techo de la pantalla. */
  /* «La línea debajo del header es delgada: quitarle el 70% de grosor».
     De 12px a 4, igual que los lados: una línea que abraza, no una
     franja. */
  padding:0 0 4px;
  margin:calc(-1 * var(--pil-sangrar-y, 14px)) calc(-1 * var(--pil-sangrar-x, 16px)) 0;
  border-radius:0 0 10px 10px;
  overflow:hidden;
  /* ⚠️ Con gorro el envoltorio SÍ tiene caja, así que es él quien cede
     el ancho cuando `eco-header.js` mete su botón al lado. Sin esto la
     cinta se queda con su `min-width:auto` y el eco sale fuera de la
     pantalla — el fallo que ese archivo arregló el 2026-09-02. */
  flex:1 1 auto;
  min-width:0;
  /* El fondo va del claro al de la marca, con el claro aguantando hasta
     pasada la mitad: así el óvalo —del color de la marca— se distingue
     encima, como en la referencia. Con el morado dominando, óvalo y
     fondo eran la misma mancha. */
  /* Arranca ARRIBA con el color de la barra del sistema (`theme-color`
     = `--marca`) para que barra y cinta sean una sola pieza, y baja al
     claro: el degradado se lee de arriba abajo, no de lado a lado. */
  background:linear-gradient(168deg,
    var(--ixa-marca-honda, var(--marca)) 0%,
    var(--ixa-marca-honda, var(--marca)) 28%,
    var(--ixa-marca, var(--marca-clara)) 100%);
}
/*  🌙 Y DE NOCHE EL GORRO BAJA, QUE ERA LO ÚNICO QUE DESLUMBRABA
    (2026-10-02). Con el tema «De noche» puesto, toda la app gira menos
    esta franja: el final del degradado es `--marca-clara`, el cian del
    logo, y sale **a toda luz encima de una pantalla oscura** — 13% de
    la pantalla, justo arriba.
    🔴 Y `--marca-clara` NO SE TOCA, aunque sería lo corto: **de ella
    cuelga el logo** (`nucleo/figuras.js:439`, `var(--ixa-logo,
    var(--marca-clara, #00cfff))`), y David lo dictó el 2026-09-20 con
    todas sus letras: *«no debes cambiar el logo, siempre debe ser el
    mismo color»*. Cambiar la variable para apagar el gorro le cambiaría
    el color al logo en toda la app, y sin dar ningún error.
    🔑 Por eso se apaga AQUÍ y conservando `--ixa-marca` delante: con una
    cotización abierta sigue mandando el documento, que es la ley del
    marco. Lo único que cambia es a qué color se cae cuando no hay
    ninguno. */
:root[data-tema="oscuro"] .pil-cabecera[data-gorro="si"]{
  background:linear-gradient(168deg,
    var(--ixa-marca-honda, var(--marca-honda, var(--marca))) 0%,
    var(--ixa-marca-honda, var(--marca-honda, var(--marca))) 28%,
    var(--ixa-marca, #2a2150) 100%);
}
/* 🔴 Y APAGADO, LA CINTA NO SE PINTA. `display:contents` en el
   envoltorio hace que el envoltorio no exista para el reparto — pero
   **sus hijos siguen ahí**, así que sin esta línea la cinta con
   «Omnixa» salía en los otros diez estilos: 36px de franja medidos, sin
   un solo error y sin que nadie la hubiera escogido. Lo cazó el banco
   de dedo, no la lectura. */
.pil-cabecera:not([data-gorro="si"]) .pil-gorro{ display:none; }
.pil-gorro{
  position:relative;
  display:flex;
  align-items:center;
  gap:8px;
  /* «La barrita de Omnixa es la mitad de altura» (David, 2026-09-11):
     de ~60px a ~32. El aire de arriba lo pone la barra del sistema, que
     en el celular está justo encima; aquí basta con no tocarla. */
  /* «Quitarle un poco de altura a la barra Omnixa» (2026-09-11): de
     ~34px a ~28. */
  padding:5px 16px 3px;
  min-width:0;
}
/* 🎩 EL GORRO, IDÉNTICO A LA REFERENCIA (2026-09-11). Cada pieza es un
   dibujo en CSS para que siga al degradado del ixa que vista. */
/* 🌀 EL LOGO DEL GORRO — mide lo mismo que el anillo que reemplazó, y
   sigue a la letra de la app como él. El SVG va dentro, así que se
   pinta nítido a cualquier tamaño. */
.pil-gorro-logo{
  flex-shrink:0;
  display:block;
  /* 📏 LO MÁS GRANDE QUE CABE, Y ESTÁ MEDIDO. David: «arriba del header,
     hazlo lo más grande que puedas». Se midió el topbar con el logo a
     30, 34, 38, 42, 46 y 50:

         30 → 108    34 → 112    38 → 116    42 → 120 ✗

     🔴 **Y el techo NO es «116»: es MENOS de 116.** `probar-topbar-dedo.js`
     lo pide así —*«el topbar sigue midiendo menos que los 116 de
     antes»*— y cazó el 38 en el acto: dejaba el topbar clavado en 116,
     que ya es pasarse. Así que **34 es el último que cabe**, con el
     topbar en 112.
     *El tamaño se escoge con lo que cabe, no con lo que gustaría — y el
     banco fue quien dijo dónde estaba el borde.* */
  width:clamp(28px, calc(34px * var(--fs-app, 1)), 36px);
  height:clamp(28px, calc(34px * var(--fs-app, 1)), 36px);
}
.pil-gorro-logo svg{ width:100%; height:100%; display:block; }
.pil-gorro-anillo{
  flex-shrink:0;
  width:clamp(22px, calc(24px * var(--fs-app, 1)), 30px);
  height:clamp(22px, calc(24px * var(--fs-app, 1)), 30px);
  border-radius:50%;
  background:conic-gradient(from 210deg, var(--blanco), var(--ixa-marca, var(--marca-clara)) 45%, var(--blanco) 80%, var(--blanco));
  /* Hueco y con el CORTE del logo: la máscara radial hace el anillo y la
     cónica le quita un sector. */
  -webkit-mask:radial-gradient(circle, transparent 50%, #000 52%),
               conic-gradient(from 300deg, transparent 0 48deg, #000 48deg);
  -webkit-mask-composite:source-in;
          mask:radial-gradient(circle, transparent 50%, #000 52%),
               conic-gradient(from 300deg, transparent 0 48deg, #000 48deg);
          mask-composite:intersect;
}
.pil-gorro-nombre{
  font-size:clamp(17px, calc(19px * var(--fs-app, 1)), 25px);
  font-weight:800;
  letter-spacing:.1px;
  color:#fff;
  white-space:nowrap;
  flex-shrink:0;
  text-shadow:0 1px 2px rgba(0,0,0,.18);
}
.pil-gorro-lema{
  flex:1 1 auto;
  min-width:0;
  text-align:right;
  font-size:clamp(12px, calc(12px * var(--fs-app, 1)), 16px);
  color:rgba(255,255,255,.92);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.pil-gorro-lema b{ font-weight:800; color:#fff; }
/* 📵 EL AVISO DE SIN SEÑAL OCUPA EL SITIO DEL LEMA (2026-10-02, David:
   «quitamos esto y allí aparezca el aviso»). Apagado no existe; con
   `html.omx-sin-red` el lema se va y sale el aviso, en blanco y con su
   pastilla oscura: sobre el degradado, un texto rojo no se leería. */
.pil-gorro-red{
  display:none;
  flex:0 1 auto;
  min-width:0;
  margin-left:auto;
  align-items:center;
  gap:6px;
  padding:2px 10px;
  border-radius:99px;
  background:rgba(15,23,42,.30);
  color:#fff;
  font-size:clamp(12px, calc(12px * var(--fs-app, 1)), 16px);
  font-weight:800;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.pil-gorro-red-punto{
  flex-shrink:0;
  width:8px; height:8px; border-radius:99px;
  background:var(--peligro-vivo);
  box-shadow:0 0 0 2px rgba(255,255,255,.85);
}
html.omx-sin-red .pil-gorro-lema{ display:none; }
html.omx-sin-red .pil-gorro-red{ display:inline-flex; }
.pil-gorro-raya{
  flex-shrink:0;
  width:22px; height:3px; border-radius:99px;
  background:rgba(255,255,255,.85);
  margin-left:4px;
}
/* El sombreado de la referencia (David, 2026-09-11, con la línea en la
   mitad: «el sombreado de Omnixa es de la mitad, delgado; en la
   izquierda se ve feo; Omnixa está como pegado al header»): una franja
   del alto de la cinta del logo —no del gorro entero—, desde el borde
   izquierdo hasta la mitad, del color de la marca (el mismo de la barra
   del sistema, así se funde por arriba), y que cae en curva a la
   derecha hasta la píldora. */
/* (La lengüeta se fue el 2026-09-11: David, *«arriba no me molesta,
   puede ser de un solo color con la barra de notificaciones; solo que
   abrace el header con una línea de color, y ese Omnixa y la barra con
   color degradado»*. Arriba es UN bloque continuo con la barra del
   sistema; lo que envuelve a la píldora es una línea fina.) */
.pil-cabecera[data-gorro="si"] > *{ position:relative; z-index:1; }
/* El sello: el cuadro verde con el $ de la referencia. */
.pil-sello{
  flex-shrink:0;
  width:clamp(30px, calc(34px * var(--fs-app, 1)), 42px);
  height:clamp(30px, calc(34px * var(--fs-app, 1)), 42px);
  border-radius:8px;
  display:inline-flex; align-items:center; justify-content:center;
  color:#fff; font-weight:800;
  font-size:clamp(17px, calc(18px * var(--fs-app, 1)), 24px);
  box-shadow:inset 0 0 0 2px rgba(255,255,255,.35);
}
/* «● Cuenta activa», debajo del nombre. */
.pil-estado{
  display:flex; align-items:center; gap:6px;
  font-size:clamp(12px, calc(12px * var(--fs-app, 1)), 16px);
  color:var(--texto-medio);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  line-height:1.2;
}
.pil-estado i{
  width:9px; height:9px; border-radius:50%; background:var(--ok); flex-shrink:0;
}
.pil-cabecera[data-gorro="si"] [id$="-pildora"]{
  margin:0 4px;
  box-shadow:0 5px 14px rgba(0,190,255,.35), 0 1px 3px rgba(15,23,42,.12);
  /* «Le quitaría a lo blanco un 70 por ciento»: una fila y el relleno
     justo para que los botones de 52px quepan sin aire de más. */
  padding:4px 8px 4px 12px !important;
  /* 📏 Y EL ALTO ES EL DE RECETAS EN TODAS (2026-10-07). David: *«quiero
     que todo Omnixa tenga estas medidas»* · *«baja el header, no
     importa»*. Medido: arriba 62, el formulario del producto 54, Recetas
     50 — la diferencia eran los botones que cada pantalla trae (52, 44,
     40). 🔑 Se fija AQUÍ y no botón por botón: así la siguiente
     pantalla que nazca con gorro sale igual sin acordarse de nada.
     🔴 Y ES UN MÍNIMO, NO UN ALTO: hay píldoras de DOS renglones (la de
     la caja lleva la ‹ encima del nombre), y un alto fijo se las comía
     —lo cazó `probar-bolsillo-puerta-dedo.js`—. Lo que impide que un
     botón de toque (44) la estire es la regla de abajo, que le hace
     ceder 2,5px arriba y abajo: dentro del mínimo cabe entero y se
     sigue agarrando entero.
     ⚠️ No es 40 + 8 + 3 = 51: medido, la de Recetas daba 50, y esto se
     vino a igualar a ella, no a una suma. */
  min-height:clamp(48px, calc(40px * var(--fs-app, 1) + 10px), 60px) !important;
  box-sizing:border-box;
  /* ⚠️ Con gorro, la píldora es BLANCA Y SIN BORDE, gane el estilo que
     gane en el eco. Lo dijo una foto del celular de David: su píldora
     llevaba el trazo grueso morado escogido días antes, y sobre el
     degradado un borde de 3px del mismo morado se lee como un error de
     pintado. Los diez estilos son para cuando hay topbar; con gorro, el
     gorro ES el estilo. `!important` porque el estilo va inline. */
  border-radius:99px !important;
  /* El borde fino con el degradado, como la referencia: dos fondos, el
     blanco recortado al padding y el degradado al borde. */
  /* «La línea de los bordes no son iguales»: era un degradado, y de un
     lado se leía grueso y del otro fino. Un solo color, el claro, y la
     sombra azulada debajo como la referencia. */
  border:1.5px solid var(--ixa-marca, var(--marca-clara)) !important;
  background:var(--blanco) !important;
}
/* 📏 Y SUS BOTONES CEDEN 2,5px ARRIBA Y ABAJO (2026-10-07): un botón de
   toque (44) pide entonces 39, que cabe en el mínimo de la píldora, y
   uno de 40 pide 35. Sin esto, el ☰ y el eco de arriba o el ‹ y el ⚡ del
   producto volvían a estirarla a 54 — y un alto fijo, que fue la
   primera forma, recortaba las píldoras de dos renglones. */
.pil-cabecera[data-gorro="si"] [id$="-pildora"] button{
  margin-top:-2.5px; margin-bottom:-2.5px;
}
/* Dentro de la píldora con gorro: la ‹ fina y oscura, el nombre al
   tamaño de la referencia, el ＋ relleno de morado con la cruz blanca, y
   el ☰ con sus puntos de colores. */
/* 🔴 El ‹ es un dibujo de 26 en un botón de un toque (2026-10-15): ni
   relleno que lo empuje ni tamaño de letra que lo baje. */
.pil-cabecera[data-gorro="si"] [id$="-h-atras"]{ color:var(--texto) !important; }
.pil-cabecera[data-gorro="si"] [id$="-h-nombre"]{ font-size:var(--fs-18) !important; letter-spacing:0 !important; line-height:1.15 !important; }
/* Los botones dentro del gorro: un TOQUE (44px), no los 52 de la píldora
   de siempre — allí el ＋ compite con un título de 28; aquí el nombre va a
   22 y el botón grande lo aplastaba.
   🔴 Y el piso pasó de 38/40 a 44 el 2026-10-08 (recado de A): el ‹ y el ⚡
   de la misma fila ya miden 44, y *por debajo del toque un botón se falla
   al apuntar* — en la Calc el eco es la ÚNICA puerta de sus opciones.
   Medido: el título pierde 4px por botón y no se parte en el carrito, en
   Recetas ni en la Calc (su `flex:1` lo absorbe). */
.pil-cabecera[data-gorro="si"] [id$="-h-accion"],
.pil-cabecera[data-gorro="si"] [id$="-h-lupa"],
.pil-cabecera[data-gorro="si"] #ven-q-lupa,
.pil-cabecera[data-gorro="si"] #ven-q-eco,
.pil-cabecera[data-gorro="si"] #omx-eco-header{
  width:clamp(44px, calc(44px * var(--fs-app, 1)), 50px) !important;
  height:clamp(44px, calc(44px * var(--fs-app, 1)), 50px) !important; }
.pil-cabecera[data-gorro="si"] [id$="-h-accion"] svg,
.pil-cabecera[data-gorro="si"] [id$="-h-lupa"] svg,
.pil-cabecera[data-gorro="si"] #ven-q-lupa svg{ width:24px; height:24px; }
.pil-cabecera[data-gorro="si"] #ven-q-eco svg{ width:28px; height:28px; }
.pil-cabecera[data-gorro="si"] #omx-eco-header svg{ width:28px; height:28px; }
.pil-cabecera[data-gorro="si"] [id$="-h-botones"]{ gap:6px !important; }
.pil-cabecera[data-gorro="si"] [id$="-h-titulo"]{ gap:10px !important; }
/* 🔎 LA LUPA Y EL BOTÓN DE ACCIÓN VAN DEL COLOR DE LA HERRAMIENTA
   (2026-09-13). David: *«el atrás y las figuras del footer y las lupas de
   todos los header deberían ser del color de la herramienta, ya que no
   pudimos cambiar [la barra]»*. Es la compensación de lo que el sistema
   no deja pintar: si la barra de notificaciones se queda morada para
   siempre, el color del documento tiene que ganar sitio donde sí se
   puede. Por eso usan la parte que cambia y no `honda`.
   ⚠️ Y con `--ixa-tinta` —el color de ESCRIBIR del documento—, no con el
   del gorro: encima va un trazo blanco, y blanco sobre `--ambar-vivo` da
   2,15:1 cuando el mínimo de un icono es 3. Los de escribir están
   medidos desde el 2026-08-30 (5,02 · 5,01 · 6,47). */
.pil-cabecera[data-gorro="si"] [id$="-h-accion"],
.pil-cabecera[data-gorro="si"] #ven-q-lupa{ background:var(--ixa-tinta, var(--marca)) !important; }
.pil-cabecera[data-gorro="si"] [id$="-h-accion"] svg{ stroke:#fff; }
/* La lupa de Ventas pinta su trazo en el `style` del svg: hace falta el
   `!important` para que salga blanca sobre el morado. */
.pil-cabecera[data-gorro="si"] #ven-q-lupa svg{ stroke:#fff !important; }
.pil-cabecera[data-gorro="si"] #ven-q-eco svg line{ stroke:var(--texto); }
.pil-cabecera[data-gorro="si"] #ven-q-eco svg circle{ stroke:none; }
.pil-cabecera[data-gorro="si"] #ven-q-eco svg circle:nth-of-type(1){ fill:var(--ixa-marca-honda, var(--marca)); }
.pil-cabecera[data-gorro="si"] #ven-q-eco svg circle:nth-of-type(2){ fill:var(--ixa-marca, var(--marca-clara)); }
.pil-cabecera[data-gorro="si"] #ven-q-eco svg circle:nth-of-type(3){ fill:var(--ixa-marca-honda, var(--marca)); }
.pil-cabecera[data-gorro="si"] #omx-eco-header svg line{ stroke:var(--texto); }
.pil-cabecera[data-gorro="si"] #omx-eco-header svg circle{ stroke:none; }
.pil-cabecera[data-gorro="si"] #omx-eco-header svg circle:nth-of-type(1){ fill:var(--ixa-marca-honda, var(--marca)); }
.pil-cabecera[data-gorro="si"] #omx-eco-header svg circle:nth-of-type(2){ fill:var(--ixa-marca, var(--marca-clara)); }
.pil-cabecera[data-gorro="si"] #omx-eco-header svg circle:nth-of-type(3){ fill:var(--ixa-marca-honda, var(--marca)); }

/* 🔍 EL BUSCADOR COMO CHIP — David, 2026-10-16: *«el buscador debería verse
   como un chip más visible, con sombras y brillo y curvas en las esquinas»*.
   Lo piden el catálogo de Ventas y el de Compras: *dos buscadores del mismo
   catálogo con dos caras se separan solos*. La sombra lleva el color de la
   casa y el brillo es un aro de luz por dentro, arriba, que es lo que se
   leyó en sus fotos de iOS (ver «EL BRILLO SALIÓ DE SUS FOTOS» en CLAUDE.md).
   ⚠️ Dos declaraciones: quien no entienda `color-mix` se queda con la plana. */
.omx-busca-chip{
  display:flex;align-items:center;gap:10px;flex:1 1 auto;min-width:0;
  min-height:44px;padding:0 16px;border-radius:99px;cursor:pointer;
  font-family:inherit;font-size:var(--fs-14);font-weight:700;text-align:left;
  color:var(--texto-medio);background:var(--blanco);
  border:1.5px solid var(--borde);
  border:1.5px solid color-mix(in srgb, var(--marca) 28%, var(--borde));
  box-shadow:0 4px 14px rgba(15,23,42,.12), inset 0 1px 0 rgba(255,255,255,.95);
  box-shadow:0 4px 14px color-mix(in srgb, var(--marca) 22%, transparent),
             inset 0 1px 0 rgba(255,255,255,.95);
  background-image:linear-gradient(180deg, var(--blanco), var(--fondo-claro));
}
.omx-busca-chip:active{ transform:scale(.98) }
.omx-busca-chip input{ border:none;background:none;outline:none;font:inherit;font-weight:600;
  color:var(--texto);flex:1;min-width:0 }

/* 🦓 LAS CARDS DEL CATÁLOGO VAN UNA GRIS Y UNA BLANCA, Y LA ESCOGIDA VIVA
   (David, 2026-10-16: *«que el catálogo tenga entre card colores, uno gris
   del tema y uno blanco, así se diferencia, y cuando se seleccione esté
   vivo»*). El gris es `--fondo-suave`, del tema. 🔑 Se cuenta SOLO entre
   cards (`of .ven-mini-op`): la lista puede llevar un aviso arriba, y
   contado con él la alternancia cambiaría de lado según el aviso.
   ⚠️ Dos reglas: el navegador que no entienda `of` se queda con la plana.
   🔑 Y lo escogido es una CLASE, no un `style`: un fondo en línea le gana
   a todo, y la card escogida no se distinguiría de la gris.
   ⚠️ Y la clase va DOS VECES a propósito: `:nth-child(… of .x)` suma la
   clase de dentro a su fuerza (1,3,0), así que con una sola la cebra le
   ganaba a la escogida y salía gris — lo cazó el banco. */
/* 🦓 Y EL GRIS SE OSCURECE UN PUNTO (David, el mismo día, con la foto:
   *«los productos entre filas no se diferencian»*). 📏 `--fondo-suave`
   contra el blanco da una distancia de ~14: en el sol, nada. El gris
   pasa a ser el TEXTO al 8% sobre el blanco —sigue saliendo del tema, y
   de noche se oscurece con él— y cada fila lleva su raya fina abajo.
   ⚠️ La raya va con `box-shadow` por dentro, no con `border`: un borde
   le cambia el alto a la fila y descuadra los chips. */
#ven-mini-lista > .ven-mini-op,
#cmp-mini-lista > .cmp-mini-op{ box-shadow:inset 0 -1px 0 var(--borde) }
#ven-mini-lista > .ven-mini-op:nth-child(even),
#cmp-mini-lista > .cmp-mini-op:nth-child(even){ background:var(--fondo-suave);
  background:color-mix(in srgb, var(--texto) 8%, var(--blanco)) }
#ven-mini-lista > .ven-mini-op:nth-child(even of .ven-mini-op){ background:var(--fondo-suave);
  background:color-mix(in srgb, var(--texto) 8%, var(--blanco)) }
#ven-mini-lista > .ven-mini-op:nth-child(odd of .ven-mini-op){ background:var(--blanco) }
#cmp-mini-lista > .cmp-mini-op:nth-child(even of .cmp-mini-op){ background:var(--fondo-suave);
  background:color-mix(in srgb, var(--texto) 8%, var(--blanco)) }
#cmp-mini-lista > .cmp-mini-op:nth-child(odd of .cmp-mini-op){ background:var(--blanco) }
#ven-mini-lista > .ven-mini-op.ven-mini-on.ven-mini-on{
  background:var(--marca-suave);
  background:color-mix(in srgb, var(--marca) 18%, var(--blanco));
  box-shadow:inset 0 0 0 2px var(--marca) }
#cmp-mini-lista > .cmp-mini-op.cmp-mini-on.cmp-mini-on{
  background:var(--ambar-fondo);
  background:color-mix(in srgb, var(--ambar) 22%, var(--blanco));
  box-shadow:inset 0 0 0 2px var(--ambar) }

/* 📦 LOS CHIPS DE CAJA YA NO SE ROBAN LA CARD (David, 2026-10-16, con la
   foto: *«los chip de caja roban mucho la atención»*). Prendidos eran una
   píldora LLENA del color de la casa con la letra en blanco: lo más fuerte
   de la fila, más que el nombre y el precio. Ahora son una etiqueta chica
   —letra 11, gris— y prendidos solo se TIÑEN: el color en el borde y la
   letra, el fondo apenas. 🔑 El BOTÓN sigue midiendo un toque de alto
   (se escoge con prisa); lo que se achica es lo que se VE, el `<span>` de
   dentro. El margen negativo le devuelve a la fila el alto que el toque
   le pediría. `--emp-tinta` lo pone cada módulo: morado en Ventas, ámbar
   en Compras. */
.omx-emp-chip{ background:none;border:none;padding:0;margin:-7px 0;min-height:44px;
  display:inline-flex;align-items:center;cursor:pointer;font-family:inherit }
.omx-emp-chip > span{ display:inline-flex;align-items:center;gap:4px;height:26px;padding:0 9px;
  border-radius:99px;border:1px solid var(--borde);background:var(--blanco);
  color:var(--texto-suave);font-size:var(--fs-11);font-weight:600;white-space:nowrap }
.omx-emp-chip[aria-pressed="true"] > span{ border-color:var(--emp-tinta, var(--marca));
  color:var(--emp-tinta, var(--marca));background:var(--marca-suave);
  background:color-mix(in srgb, var(--emp-tinta, var(--marca)) 12%, var(--blanco)) }

/* 🗂️ Y CÓMO SE VE EL EMPAQUE SE ESCOGE A LA VISTA, debajo de las
   categorías (David, el mismo día: *«que haya chip filtro arriba o debajo
   de los de categoría para escoger si se ve con cajas o unidad o todos o
   por separados»*). Es un control SEGMENTADO, no otra tira de colores: las
   categorías ya llevan el color, y dos tiras de colores se pelean. */
.omx-cat-vista{ display:flex;gap:3px;padding:0 8px 6px;flex-shrink:0;overflow-x:auto;
  scrollbar-width:none }
.omx-cat-vista > button{ flex:1 1 auto;background:none;border:none;padding:0;min-height:44px;
  margin:-6px 0;cursor:pointer;font-family:inherit;display:flex;align-items:center;
  justify-content:center }
.omx-cat-vista > button > span{ display:block;width:100%;text-align:center;padding:5px 6px;
  border-radius:8px;background:var(--fondo-suave);color:var(--texto-medio);
  font-size:var(--fs-11);font-weight:700;white-space:nowrap }
.omx-cat-vista > button[aria-pressed="true"] > span{ background:var(--emp-tinta, var(--marca));
  color:var(--blanco) }
