/* ============================================================================
   MNX :: MANOLITO MOBILE v3 — Design System (reescrito DESDE CERO)
   ----------------------------------------------------------------------------
   Android-first, premium, dark. Material 3 Expressive + física de movimiento.
   100% scopeado bajo .mnx-root -> NO contamina desktop ni la mobile-v2 vieja.
   Construido para 60fps: solo transform/opacity en animaciones, will-change
   acotado, safe-area insets, tap targets >= 48dp, ripple + haptics.
   ============================================================================ */

/* :root TAMBIÉN lleva los tokens (mnx-total): los overlays (sheets/cartbar/
   dialogs) que las páginas renderizan DESPUÉS de </MnxShell> quedan hermanos
   de .mnx-root y sin esto todos sus var(--mnx-*) resolvían a `initial` —
   posición, glass y z-index degradados. Variables solas no pintan nada:
   definirlas en :root es inocuo para el desktop. */
:root,
.mnx-root {
    /* ===================================================================
       MNX v2 — tokens Material 3 Expressive (dark, marca roja Manolito).
       Re-mapeo de VALORES de los tokens existentes (nombres preservados →
       cero cambios de markup) + tokens nuevos opt-in. Elevación tonal real:
       en dark M3 la jerarquía la da el COLOR de superficie, no la sombra.
       =================================================================== */
    /* ===== Superficies — rampa surface-container tonal (tinte rojo sutil) ===== */
    --mnx-bg-0: #0b0809;          /* surface-dim / fondo absoluto */
    --mnx-bg-1: #140f10;          /* surface-container-low  (era #0f1217) */
    --mnx-bg-2: #1d1617;          /* surface-container — cards (era #161a21) */
    --mnx-bg-3: #281e1f;          /* surface-container-high — elevada/press (era #1e242d) */
    --mnx-bg-4: #332727;          /* surface-container-highest (NUEVO) — sheets/dialogs */
    --mnx-glass: rgba(16, 11, 12, 0.84);

    --mnx-line: rgba(255, 255, 255, 0.08);   /* outline-variant */
    --mnx-line-2: rgba(255, 255, 255, 0.15); /* outline */

    --mnx-fg: #f2dfdc;           /* on-surface (blanco cálido, no gris azulado) */
    --mnx-fg-2: #d6c2bf;         /* secundario */
    --mnx-fg-3: #a08c8a;         /* on-surface-variant (era #7e8799) */

    --mnx-red: #e11d3a;
    --mnx-red-2: #ff4659;
    --mnx-red-deep: #9c0f22;
    --mnx-red-soft: rgba(225, 29, 58, 0.16);
    /* Containers M3 — fills de chips/badges/indicadores activos */
    --mnx-secondary-container: #5d3f3d; --mnx-on-secondary-container: #ffdad7;
    --mnx-tertiary: #e0c38c;            /* ámbar complementario (vuelto/avisos premium) */
    --mnx-tertiary-container: #584419;  --mnx-on-tertiary-container: #fddfa6;

    --mnx-ok: #6fdcae;            /* verde cálido/luminoso para dark */
    --mnx-warn: #f5b740;
    --mnx-info: #9ec6ff;
    --mnx-scrim: rgba(0, 0, 0, .6);

    /*  ===== Degradés =====
        El degradé de marca estaba escrito A MANO SEIS VECES (botón primario,
        FAB de la nav, hero, segmento activo, avatar, botón de instalar) y una
        de las seis iba a 135° en vez de 150°: el hero levantaba distinto que
        todo lo demás y nadie sabía si era a propósito. Un degradé repetido no
        es un detalle de estilo, es la identidad de la app copiada y pegada: con
        seis copias, cambiar el rojo de Manolito son seis ediciones y la que se
        olvide queda del color viejo para siempre. Ahora es UNO. */
    --mnx-grad-brand: linear-gradient(150deg, var(--mnx-red-2), var(--mnx-red-deep));
    --mnx-grad-danger: linear-gradient(150deg, var(--mnx-red), var(--mnx-red-deep));
    --mnx-grad-ok: linear-gradient(150deg, #3ee9a8, #169f6e);
    --mnx-grad-surface: linear-gradient(150deg, var(--mnx-bg-3), var(--mnx-bg-2));

    /* ===== Tipografía =====
       ⚠️ ESTA ESCALA ES LEY: ningún font-size literal en px vuelve al archivo.
       Antes había VEINTISIETE tamaños distintos — con 14.5 / 13.5 / 12.5 / 11.5 /
       10.5 / 15.5 px, o sea medio píxel, que es lo que queda cuando cada pantalla
       se fue tanteando sola. Medio píxel no lo distingue nadie, pero veintisiete
       tamaños sí: es exactamente la sensación de "hecho a mano y sin terminar".
       Ocho pasos alcanzan para las 55 pantallas y cualquiera puede elegir bien
       sin pensar. Si hace falta un tamaño nuevo, primero buscar cuál de estos
       ocho ya se ve igual.

       Los pasos NO son una progresión geométrica: abajo (11→12→13→15) están
       apretados porque es donde vive la UI y ahí un salto grande rompe filas;
       arriba (20→26→34) están separados porque un titular tiene que ganar de
       lejos. Hay UN literal permitido y está abajo, --mnx-fs-input. */
    --mnx-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mnx-fs-1: 11px;   /* micro — badges, contadores, unidades, deltas */
    --mnx-fs-2: 12px;   /* caption — etiquetas, segunda línea de fila, chips */
    --mnx-fs-3: 13px;   /* body chico — el tamaño más usado del sistema */
    --mnx-fs-4: 15px;   /* body — título de fila, botones, hints */
    --mnx-fs-5: 17px;   /* title chico — encabezado de sheet y de diálogo */
    --mnx-fs-6: 20px;   /* title — h2 de pantalla, números medianos */
    --mnx-fs-7: 26px;   /* headline — saludo, KPI, TOTAL del carrito */
    --mnx-fs-8: 34px;   /* display — el número grande, el que se lee de reojo */
    /*  ⚠️ 16px NO es decorativo y por eso queda afuera de la escala: iOS hace
        auto-zoom al enfocar un <input> con menos de 16px, y en el POS eso es la
        pantalla saltando en la cara del cajero a mitad de una venta. Es un piso
        funcional. NO bajarlo para "que entre mejor". */
    --mnx-fs-input: 16px;

    /*  Tamaño de GLIFO, que no es tamaño de texto. Hoy son los emoji; el lote 3
        los reemplaza por el sprite SVG y entonces estos cinco tokens son el
        único lugar donde se toca el tamaño de TODOS los íconos del celular.
        Por eso se separan ahora: para que ese cambio no sea otra vez 30 líneas. */
    --mnx-ico-xs: 13px;  /* chevrones, cerrar */
    --mnx-ico-s: 18px;   /* inline: buscador, toast, escáner, avatar chico */
    --mnx-ico-m: 22px;   /* nav, tiles, banners, FAB extendido */
    --mnx-ico-l: 28px;   /* FAB central de la nav */
    --mnx-ico-xl: 34px;  /* avatar grande, hero, estado vacío */
    --mnx-ico-2xl: 44px; /* el emoji solo y centrado: trofeo, brújula, novedades */

    --mnx-num: "tnum" 1;          /* helper font-feature-settings tabular */

    /* ===== Radios (shape scale M3 + alias preservados) ===== */
    --mnx-r-xs: 4px;
    --mnx-r-s: 8px;
    --mnx-r-sm: 12px;            /* = medium M3 (preservado) */
    --mnx-r-md: 18px;           /* preservado (cards/botones MNX) */
    --mnx-r-lg: 24px;           /* preservado (sheets) */
    --mnx-r-xl: 28px;           /* extra-large M3 (NUEVO) — dialogs/sheet hero */
    --mnx-r-pill: 999px;

    /* ===== Espaciado ===== */
    --mnx-gap: 14px;
    --mnx-pad: 16px;

    /* ===== Touch / layout ===== */
    --mnx-touch: 52px;            /* >= 48dp con margen */
    /*  ⚠️ ESTE NÚMERO MENTÍA Y LO USA TODO LO STICKY (2026-07-30).
        Decía 60 px, pero el header real mide más: 10+10 de padding vertical + el alto
        del contenido, y el contenido más alto es .mnx-icon-btn, que es --mnx-touch
        (52 px) → 72 px. Como .mnx-tabs y compañía se pegan en
        `top: calc(var(--mnx-header-h) + safe-top)`, quedaban 12 px POR DEBAJO del
        header: la primera fila de tabs se metía abajo del header translúcido y se
        leía a medias en todas las pantallas con solapas.  */
    --mnx-header-h: 72px;
    --mnx-nav-h: 66px;
    /*  Alto real de .mnx-cartbar: 12+12 de padding + 52 del botón. Está como token
        porque lo necesita el toast host para no taparla.  */
    --mnx-cartbar-h: 76px;
    --mnx-safe-top: env(safe-area-inset-top, 0px);
    --mnx-safe-bottom: env(safe-area-inset-bottom, 0px);

    /* ===== Motion — springs M3 Expressive (nombres preservados) ===== */
    /* Regla de oro: spatial (transform/tamaño) puede rebotar; effects
       (color/opacity) NUNCA. Solo transform/opacity (GPU, no toca SignalR). */
    --mnx-spring: cubic-bezier(0.30, 1.30, 0.55, 1);     /* spatial-default suavizado (era 1.56) */
    --mnx-spring-soft: cubic-bezier(0.30, 1.12, 0.60, 1);
    --mnx-ease: cubic-bezier(0.2, 0, 0, 1);
    --mnx-emph-dec: cubic-bezier(0.05, 0.7, 0.1, 1);     /* entrantes, premium */
    --mnx-ease-std: cubic-bezier(0.2, 0, 0, 1);
    --mnx-fast: 0.15s;
    --mnx-med: 0.28s;
    --mnx-dur-2: 0.20s; --mnx-dur-3: 0.30s; --mnx-dur-4: 0.45s;
    /*  Se fueron --mnx-emph-acc, --mnx-celebrate, --mnx-dur-1, --mnx-stagger,
        --mnx-primary-container, --mnx-on-primary-container, tres de los cuatro
        --mnx-st-* y las nueve --mnx-t-* del shorthand M3: DIECIOCHO tokens que
        no tenían un solo var() en todo el repo. --mnx-st-press se queda porque
        SÍ lo usa .mnx-pcard:active — es el motivo por el que esta lista se
        volvió a grepear entera en vez de confiar en la del análisis. Un token
        declarado y nunca
        usado no es inofensivo: el que viene lo lee como parte del sistema y lo
        empieza a usar, y entonces aparece un segundo sistema al lado del que
        sí anda. Las --mnx-t-* eran justo eso — una escala tipográfica paralela,
        en rem y con el shorthand `font:`, mientras las 55 pantallas escribían
        px a mano. Si un lote futuro necesita alguno, se agrega EN EL MISMO
        COMMIT que su primer uso.  */

    /* ===== Elevación tonal M3 (sombras suaves, dark) ===== */
    --mnx-shadow-1: 0 1px 2px rgba(0,0,0,.45), 0 1px 3px 1px rgba(0,0,0,.25);
    --mnx-shadow-2: 0 4px 8px 3px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.45);
    --mnx-shadow-3: 0 8px 12px 6px rgba(0,0,0,.30), 0 4px 4px rgba(0,0,0,.45);
    --mnx-shadow-red: 0 8px 24px rgba(225, 29, 58, 0.32);

    /* ===== State layers M3 (NUEVOS) ===== */
    --mnx-st-press: .10;   /* el único state layer con uso real: .mnx-pcard:active */

    --mnx-z-header: 40;
    --mnx-z-nav: 45;
    --mnx-z-fab: 44;
    --mnx-z-sheet: 60;
}

/* ===== Reset acotado ===== */
.mnx-root, .mnx-root * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
.mnx-root {
    font-family: var(--mnx-font);
    color: var(--mnx-fg);
    background: var(--mnx-bg-0);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
    overscroll-behavior-y: contain;
    /*  ⚠️ NÚMEROS TABULARES EN TODA LA MOBILE, no sólo donde alguien se acordó.
        Estaba declarado en once lugares sueltos y faltaba en el resto: un total
        que pasa de $9.990 a $10.100 con cifras proporcionales CAMBIA DE ANCHO,
        así que el número se corre solo mientras el cajero lo está leyendo. En
        una app que se refresca sola con cada venta eso se ve como que la
        pantalla tiembla. Con tabular todas las cifras miden lo mismo y la
        columna de plata queda quieta. En los nombres de producto ("Coca 2.25L")
        no molesta: alinea. Las once declaraciones sueltas quedan porque son
        este mismo valor — no hay conflicto, sólo dejaron de ser necesarias.  */
    font-variant-numeric: tabular-nums;
}
.mnx-root button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }

/* ============================================================================
   ICONOS DE LÍNEA — <MnxIco N="caja" />  (sprite en Shared/MobileX/MnxSprite)
   ============================================================================
   Las 56 pantallas usan 212 emoji distintos en 1.555 lugares. Un emoji no es
   un icono: lo dibuja la fuente del sistema operativo, viene multicolor, se ve
   distinto en cada version de Android, no toma el color de marca y no se puede
   alinear con el texto de al lado.

   Estos heredan el color (`stroke: currentColor`): el mismo icono adentro de un
   boton rojo sale rojo y adentro de un aviso sale ambar, sin una sola regla por
   caso. Y miden 1em, o sea que el tamano lo gobiernan los tokens --mnx-ico-*
   que se separaron en el lote 2 — ese fue el motivo de separarlos entonces.

   ⚠️ `flex: 0 0 auto` no es decorativo: adentro de un flex el icono es un item
   mas y sin eso se ACHICA cuando el texto de al lado es largo. Un icono que
   cambia de tamano segun lo que diga la etiqueta se ve roto, y en una lista de
   productos con nombres de largo variable pasa en cada fila.  */
.mnx-ic {
    width: 1em; height: 1em; flex: 0 0 auto;
    display: inline-block; vertical-align: -.14em;
    fill: none; stroke: currentColor; stroke-width: 2;
    stroke-linecap: round; stroke-linejoin: round;
}
/*  COMPENSACION OPTICA DEL TRAZO. El trazo esta en unidades de la grilla de 24
    y escala junto con el icono, asi que el grosor REAL cambia con el tamano:
    2 unidades son 1,08 px a 13 px (se ve lavado, casi gris) y 3,67 px a 44 px
    (se ve tosco, como un dibujo infantil). Es el mismo motivo por el que una
    tipografia de texto y una de titulo no son el mismo dibujo agrandado.
    Van como modificadores explicitos en los dos extremos — en el medio
    (18-28 px, que es donde vive casi todo) el trazo base ya se ve bien.  */
.mnx-ic.xs { stroke-width: 2.5; }   /* a 13 px */
.mnx-ic.xl { stroke-width: 1.6; }   /* a 34-44 px */
.mnx-ic.f { stroke: none; fill: currentColor; }   /* variantes macizas (el punto de estado) */

/* ============================================================================ */

/* ============================================================================
   ⚠️ EL QUE SCROLLEA TIENE QUE SER EL DOCUMENTO, NO EL <body>
   ============================================================================
   Dos reglas GLOBALES del sistema (ninguna es de MNX) se combinan y convierten
   al <body> en una caja de scroll PROPIA de alto FIJO:

     · wwwroot/css/site.css:137            → html, body { height: 100% }
     · wwwroot/css/v140-componentes.css:288 → html, body { overflow-x: hidden }

   `overflow-x:hidden` obliga a `overflow-y` a computar `auto` (regla del spec:
   si un eje deja de ser `visible`, el otro también). Con `height:100%` el body
   queda del alto del viewport y se traga el sobrante ADENTRO, así que el
   documento nunca crece. Medido en el shell (.mnx-root > .mnx-shell >
   .mnx-content con 1594 px de contenido, 360x640):
   documentElement.scrollHeight = clientHeight = 640 y window.scrollY máximo = 0.

   No es cosmético. Rompe cuatro cosas en LAS 55 PANTALLAS:
     1. `scrollerY()` de mnx.js devuelve SIEMPRE 0 (`.mnx-content` no es scroller
        propio —no tiene overflow-y— así que cae a window.scrollY, que no se
        mueve). Como el pull-to-refresh sólo se arma "si estás arriba de todo",
        y arriba de todo da true SIEMPRE, el empleado que llegó al final del
        carrito y sigue arrastrando para bajar ARMA EL REFRESH y al soltar
        recarga la pantalla en plena venta.
     2. El header nunca toma `.scrolled` y los FAB nunca se colapsan.
     3. Sin scroll de documento el navegador NUNCA retrae la barra de
        direcciones, y `height:100%` es el viewport GRANDE: los últimos ~60/100 px
        de cada pantalla viven permanentemente abajo de la barra.
     4. `.mnx-shell { min-height:100dvh }` mide contra un padre que ya se quedó
        en 100vh, así que el dvh no sirve para nada.

   La cura es la misma que se validó midiendo en /m2/login (que la tiene
   scopeada en su propio <style>). Va con `:has()` para no tocar NINGUNA
   pantalla de escritorio: sólo aplica en los documentos donde está el shell
   mobile. Si el navegador es viejo y no entiende `:has()`, descarta el bloque
   entero y queda exactamente el comportamiento de hoy — no hay regresión
   posible en la dirección mala.

   `overflow-x: clip` en vez de `hidden` es lo que permite dejar
   `overflow-y: visible` sin volver a habilitar el paneo horizontal (con
   `hidden` el spec fuerza `overflow-y:auto` de nuevo; con `clip` no). Si el
   navegador no entiende `clip` descarta ESA declaración y queda el
   `overflow-x:hidden` global, pero ya con `height:auto`: el body no puede
   desbordar, así que el scroll del documento sigue funcionando igual.
   ============================================================================ */
html:has(.mnx-root),
body:has(.mnx-root) {
    height: auto;
    min-height: 100%;
    overflow-y: visible;
    overflow-x: hidden;
    overflow-x: clip;
}

/* ============================================================================
   SHELL :: header (sticky) + content (scroll) + bottom-nav (fixed)
   ============================================================================ */
.mnx-shell {
    min-height: 100dvh;
    background:
        radial-gradient(120% 60% at 50% -10%, rgba(225,29,58,0.10), transparent 60%),
        var(--mnx-bg-0);
    display: flex;
    flex-direction: column;
}

.mnx-header {
    position: sticky; top: 0; z-index: var(--mnx-z-header);
    padding: calc(var(--mnx-safe-top) + 10px) var(--mnx-pad) 10px;
    min-height: calc(var(--mnx-header-h) + var(--mnx-safe-top));
    display: flex; align-items: center; gap: 12px;
    background: var(--mnx-glass);
    backdrop-filter: blur(18px) saturate(140%);
    -webkit-backdrop-filter: blur(18px) saturate(140%);
    border-bottom: 1px solid var(--mnx-line);
}
.mnx-header-title { display: flex; flex-direction: column; min-width: 0; flex: 1; }
/* ⚠️ ÚNICA declaración de .mnx-header-title b (mnx-17). Estaba escrita CUATRO
   veces (líneas 216/678/933/934) con TRES font-size distintos: editar el
   bloque "natural" no tenía ningún efecto, ganaba la última por orden de
   fuente. Y una de ellas animaba font-size + letter-spacing en cada scroll,
   que es re-layout del header en cada frame. */
/*  ⚠️ EL TÍTULO SE PARTÍA LETRA POR LETRA, EN VERTICAL. El subtítulo (línea de
    abajo) siempre tuvo `nowrap` + ellipsis; el TÍTULO no. `.mnx-header-title` es
    `flex:1; min-width:0`, o sea que puede achicarse hasta CERO — y en las
    pantallas con muchas acciones en el header (inventario tiene cinco: buscar,
    avisos, catálogo, menú y ＋) los botones se lo comen. Sin `nowrap`, el <b>
    envuelve por carácter: "Inventario" salía en columna, ocupando el TERCIO
    SUPERIOR de la pantalla. El dueño lo reportó como "se ve raro" — no era raro,
    estaba roto.
    Con ellipsis el título se recorta, que es lo correcto: la barra no puede
    crecer (tiene `height` fijo) y perder dos letras del título es infinitamente
    mejor que perder 500 px de contenido.  */
.mnx-header-title b { font-size: var(--mnx-fs-6); font-weight: 700; letter-spacing: -.2px; line-height: 1.15;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mnx-header-title span { font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mnx-header-actions { display: flex; align-items: center; gap: 8px; }
.mnx-icon-btn {
    width: var(--mnx-touch); height: var(--mnx-touch);
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: var(--mnx-r-pill); color: var(--mnx-fg);
    position: relative; transition: background var(--mnx-fast);
    /*  El tamaño del ícono vive ACÁ, no en cada uso. Antes cada botón del header
        se traía su propio `style="font-size:…"` inline: el de volver y el de
        buscar decían 20px y el resto quedaba en lo que heredara. Con <MnxIco>
        (que mide 1em) el botón manda, y todos los botones del header miden
        igual sin que nadie lo repita.  */
    font-size: var(--mnx-ico-m);
}
.mnx-icon-btn:active { background: var(--mnx-bg-3); }
.mnx-icon-btn .mnx-dot {
    position: absolute; top: 10px; right: 10px; width: 9px; height: 9px;
    background: var(--mnx-red); border-radius: 50%; border: 2px solid var(--mnx-bg-0);
}

.mnx-content {
    flex: 1;
    padding: var(--mnx-pad) var(--mnx-pad) calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + 24px);
    -webkit-overflow-scrolling: touch;
    animation: mnx-page-in var(--mnx-med) var(--mnx-ease);
}

/*  ⚠️ EL GLOBITO FLOTANTE SE COMÍA EL FINAL DE LA PANTALLA. Medido:
    `.mnx-content` reservaba abajo `nav-h + safe + 24px`, pero `.vzb-fab-mnx`
    arranca en `nav-h + safe + 16px` y MIDE 52px — o sea que llega hasta
    `nav-h + safe + 68px` y le pisaba los últimos ~44 px al contenido. En el home
    eso tapaba el subtítulo del último módulo: se leía "…nte IA" en vez de
    "Asistente IA". No es que el texto estuviera cortado — estaba TAPADO, que es
    peor, porque el que lo ve piensa que la app está rota.

    El lote 1 le bajó la opacidad al globito en reposo para que dejara leer lo de
    abajo. Eso ayuda pero no alcanza: un elemento translúcido encima del texto
    sigue siendo un elemento encima del texto. La cura de verdad es RESERVARLE
    EL LUGAR, que es lo que faltaba.

    Va con `:has()` para que sólo lo pague la pantalla que de verdad monta el
    globito: si el navegador es viejo y no lo entiende, descarta el bloque y
    queda exactamente el comportamiento de hoy. No hay regresión posible.  */
.mnx-shell:has(.vzb-fab-mnx) .mnx-content,
.mnx-root:has(.vzb-fab-mnx) .mnx-content {
    padding-bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + 84px);
}
@keyframes mnx-page-in {
    /* mnx-17: desde .6 y no desde 0 — el contenido nunca "aparece de la nada",
       se asienta. Y 6px en vez de 8: a 60fps la diferencia se siente más fina. */
    from { opacity: .6; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ===== Bottom navigation (3-5 destinos, siempre visible) ===== */
.mnx-nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--mnx-z-nav);
    display: flex; justify-content: space-around; align-items: stretch;
    padding: 6px 8px calc(6px + var(--mnx-safe-bottom));
    height: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom));
    background: var(--mnx-glass);
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
    border-top: 1px solid var(--mnx-line);
}
.mnx-nav-item {
    flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px; color: var(--mnx-fg-3); border-radius: var(--mnx-r-md);
    font-size: var(--mnx-fs-1); font-weight: 700; letter-spacing: .2px;
    transition: color var(--mnx-fast), transform var(--mnx-fast);
    position: relative;
}
.mnx-nav-item .mnx-nav-ico { font-size: var(--mnx-ico-m); line-height: 1; transition: transform var(--mnx-med) var(--mnx-spring); }
.mnx-nav-item:active { transform: scale(.92); }
.mnx-nav-item.active { color: var(--mnx-fg); }
/*  ⚠️ ACÁ HABÍA DOS INDICADORES DE PESTAÑA ACTIVA, NO UNO. Este bloque dibujaba
    una barrita roja de 26×3 px pegada arriba; la capa v3 del final del archivo
    redefine el MISMO `::before` como una pastilla de 60×30 detrás del ícono.
    Gana la de abajo por orden de fuente, así que la barrita no se vio nunca —
    pero seguía ahí, leyéndose como si fuera el diseño. Ídem el transform del
    ícono, declarado -2px/1.06 acá y -1px/1.04 allá.

    Es la cuarta vez en este rediseño que aparece el mismo patrón (el título del
    header, `.mnx-row-main b`, `.mnx-empty .em` y esto): dos declaraciones de lo
    mismo, la de arriba muerta. No rompe nada visible — por eso sobrevive — pero
    el que viene edita la que no manda y no entiende por qué no pasa nada.
    La versión que MANDA es la de la capa v3; ésta se va.  */

/* ===== FAB central (vender) ===== */
.mnx-nav-fab {
    flex: 0 0 auto; width: 60px; height: 60px; margin-top: -22px;
    border-radius: var(--mnx-r-pill);
    background: var(--mnx-grad-brand);
    color: #fff; display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--mnx-ico-l); box-shadow: var(--mnx-shadow-red);
    transition: transform var(--mnx-med) var(--mnx-spring);
}
/*  ⚠️ ACÁ HABÍA UN `.mnx-nav-fab:active { transform: scale(.9) }` MUERTO.
    La capa v2 del final del archivo (§2.2) declara el MISMO selector con
    scale(.92) y gana por orden de fuente: el .9 no se aplicó nunca. Regla
    entera pisada, y con un valor distinto — o sea que el que la editaba veía
    dos números contradictorios y ninguno de los dos era el que corría.  */

/* ============================================================================
   COMPONENTES
   ============================================================================ */
/* Saludo */
.mnx-greet { margin: 4px 0 18px; }
.mnx-greet h1 { font-size: var(--mnx-fs-7); font-weight: 700; letter-spacing: -.4px; line-height: 1.15; }
.mnx-greet h1 .mnx-accent { color: var(--mnx-red-2); }
.mnx-greet p { color: var(--mnx-fg-3); font-size: var(--mnx-fs-3); margin-top: 4px; }
/* ítem 68 :: la hora, tickeada por menu-reloj.js. Números tabulares para que
   HH:mm no corra el punto de al lado cada 30s (misma regla que .mnx-money). */
.mnx-hora-viva { font-variant-numeric: tabular-nums; color: var(--mnx-fg-2); font-weight: 700; }

/* Hero acción (Vender) */
.mnx-hero {
    position: relative; overflow: hidden;
    display: flex; align-items: center; gap: 14px;
    width: 100%; text-align: left;
    padding: 20px; border-radius: var(--mnx-r-lg);
    background: var(--mnx-grad-brand);
    color: #fff; box-shadow: var(--mnx-shadow-red);
    transition: transform var(--mnx-fast) var(--mnx-ease);
    margin-bottom: var(--mnx-gap);
}
/*  ⚠️ Y ACÁ UN `.mnx-hero:active { transform: scale(.98) }`, también muerto:
    §3.12 lo redeclara en .985 y explica por qué (una pieza grande se hunde, no
    se encoge). Gana la de abajo; ésta sólo confundía.  */
.mnx-hero-ico { font-size: var(--mnx-ico-xl); }
.mnx-hero-txt b { display: block; font-size: var(--mnx-fs-6); font-weight: 700; }
.mnx-hero-txt span { font-size: var(--mnx-fs-2); opacity: .9; }
.mnx-hero-arrow { margin-left: auto; font-size: var(--mnx-ico-m); opacity: .9; }
.mnx-hero::after {
    content: ""; position: absolute; top: -50%; left: -60%; width: 50%; height: 200%;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,.18), transparent);
    /* ⚠️ ANIMACIÓN BORRADA (mnx-17). Barría un brillo animando `left`, o sea LAYOUT,
       en bucle infinito sobre el botón más grande del home. En un Android de gama
       media eso es reflow permanente: el hilo principal queda ocupado y el primer
       toque del empleado llega tarde. El destello no se extraña; los frames sí.
       El brillo estático queda: da el borde de luz sin costar nada. */
    transform: skewX(-20deg); opacity: .5;
}

/* Grid de KPIs */
.mnx-kpis { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mnx-gap); margin-bottom: 20px; }
.mnx-kpi {
    background: var(--mnx-bg-2); border: 1px solid var(--mnx-line);
    border-radius: var(--mnx-r-md); padding: 14px;
}
.mnx-kpi-label { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); text-transform: uppercase; letter-spacing: .6px; font-weight: 700; }
.mnx-kpi .mnx-kpi-value { font-size: var(--mnx-fs-7); font-weight: 900; margin-top: 6px; font-variant-numeric: tabular-nums; }
/*  Un KPI que no tiene NÚMERO no puede tener peso de número. Los dos tiles de
    arriba del home dicen "Ver" y "Buscar": son links, no plata. Con 26px y 900
    le gritan más fuerte que los títulos de los 12 módulos de abajo, que es
    exactamente al revés de lo que importa. Esto no se veía hasta ahora porque
    `.mnx-tile span` le venía pisando el tamaño a los dos.  */
.mnx-kpi .mnx-kpi-value.act { font-size: var(--mnx-fs-4); font-weight: 700; color: var(--mnx-fg-2);
    display: inline-flex; align-items: center; gap: 6px; }

/* Sección título */
.mnx-section-h { font-size: var(--mnx-fs-3); font-weight: 700; color: var(--mnx-fg-2); text-transform: uppercase; letter-spacing: 1px; margin: 6px 2px 12px; }

/* Grid de módulos */
.mnx-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mnx-gap); }
.mnx-tile {
    position: relative; overflow: hidden;
    display: flex; flex-direction: column; gap: 10px;
    padding: 16px; border-radius: var(--mnx-r-md);
    background: var(--mnx-bg-2); border: 1px solid var(--mnx-line);
    text-align: left; min-height: 96px;
    transition: transform var(--mnx-fast) var(--mnx-ease), border-color var(--mnx-fast);
}
.mnx-tile:active { transform: scale(.97); border-color: var(--mnx-line-2); }
/*  ⚠️ ESTOS TRES SELECTORES LLEVAN EL PADRE A PROPÓSITO — NO ES REDUNDANCIA.
    `.mnx-tile span` es (0,1,1) y le GANA a `.mnx-tile-ico`, que es (0,1,0); y
    encima está escrito más abajo, así que gana por partida doble. Resultado
    medido en el navegador ANTES de este arreglo:

        .mnx-tile-ico  → font-size 11px  (debía ser 28) y color gris (debía
                         ser el rojo de marca)
        .mnx-kpi-value → font-size 11px  (debía ser 26)

    O sea: los 12 íconos de módulo del home y los números de los dos KPI venían
    saliendo a TAMAÑO DE ETIQUETA y sin el color de marca. No es un bug que se
    vea como un error — se ve como que la pantalla es sosa, que es peor, porque
    nadie lo reporta. Estaba así desde que existe el archivo.

    La cura NO es ponerle !important ni tocar `.mnx-tile span`: esa regla la
    usan las 56 pantallas para el subtítulo de la fila y cambiarla mueve todo.
    Se le sube la especificidad al que tiene que ganar y listo.

    ⚠️ Al agregar un hijo CON CLASE adentro de un contenedor que tiene regla de
    tag pelado (`.mnx-tile span`, `.mnx-row-main b`, `.mnx-hero-txt span`…),
    acordarse de esto: la clase del hijo NO gana sola.  */
.mnx-tile .mnx-tile-ico {
    width: 42px; height: 42px; border-radius: var(--mnx-r-sm);
    display: inline-flex; align-items: center; justify-content: center;
    /*  ⚠️ UN ÍCONO DE LÍNEA NECESITA MÁS TAMAÑO QUE UN EMOJI PARA PESAR IGUAL.
        Un emoji es una figura MACIZA: llena su caja de color. Un ícono de línea
        es contorno — al mismo tamaño nominal ocupa una fracción de la tinta y se
        lee más chico y más flojo. Acá la caja es de 42 px: con los 22 px que
        heredaban de los emoji, los íconos quedaban nadando adentro del cuadrito.
        28 px es ~2/3 de la caja, que es la proporción que usan las grillas de
        íconos que se ven bien. No es "agrandarlo porque sí": es compensar el
        cambio de relleno a contorno.  */
    font-size: var(--mnx-ico-l);
    background: var(--mnx-red-soft); color: var(--mnx-red-2);
}
.mnx-tile b { font-size: var(--mnx-fs-4); font-weight: 700; }
.mnx-tile span { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); }

/* Ripple Material — 2 fases, currentColor (no pinta blanco sobre verde/rojo).
   Auto-aplicado a los contenedores táctiles comunes.

   ⚠️ EL RIPPLE NO VIVE ADENTRO DEL ELEMENTO TOCADO. Se dibuja en una capa
   propia colgada de <body> (#mnx-rp-layer), en coordenadas de viewport, con una
   máscara que copia la caja y el radio del elemento. Motivo: Blazor lleva su
   propia lista de hijos por elemento y un <span> ajeno adentro le desordena los
   índices y le queda de basura cuando limpia el contenedor — el ripple se
   "pegaba" a las filas de una lista que se re-renderiza. La capa propia da el
   mismo resultado visual y Blazor no la toca jamás.
   z-index 90: arriba de hojas (60), toasts (70) e instalación (75); abajo del
   escáner (95) y del modal de reconexión (100000). */
.mnx-ripple, .mnx-tile, .mnx-row, .mnx-pcard, .mnx-btn, .mnx-seg, .mnx-chip { position: relative; overflow: hidden; }
.mnx-rp-layer { position: fixed; inset: 0; z-index: 90; pointer-events: none; contain: layout paint; }
.mnx-rp-box { position: fixed; overflow: hidden; pointer-events: none; }
.mnx-rp {
    position: absolute; display: block; border-radius: 50%; transform: scale(0);
    background: currentColor; opacity: .18; pointer-events: none;
    animation: mnx-rp .6s var(--mnx-emph-dec);
}
@keyframes mnx-rp { 0% { transform: scale(0); opacity: .22; } 60% { opacity: .18; } 100% { transform: scale(2.6); opacity: 0; } }
/* State-layer M3: capa de color al presionar botones (feedback crujiente). */
.mnx-btn::after { content: ""; position: absolute; inset: 0; background: currentColor; opacity: 0; transition: opacity .4s var(--mnx-ease-std); pointer-events: none; }
.mnx-btn:active::after { opacity: .12; transition: opacity 0s; }

/* Skeleton */
/* mnx-17: el shimmer animaba `background-position` → repintado constante de cada
   fila del esqueleto. Ahora el barrido es un pseudo-elemento que se traslada:
   transform puro, lo hace el compositor. Se ve igual y no cuesta nada. */
.mnx-sk { position: relative; overflow: hidden; background: var(--mnx-bg-2); border-radius: var(--mnx-r-sm); }
.mnx-sk::after { content: ""; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent, var(--mnx-bg-3), transparent);
    transform: translateX(-100%); animation: mnx-sk 1.3s ease infinite; }
@keyframes mnx-sk { to { transform: translateX(100%); } }
/* Skeleton kit (reusa el shimmer .mnx-sk). Skeleton != spinner: replica el
   layout real para que el contenido no salte cuando llega. */
.mnx-sk-line { height: 12px; border-radius: 6px; margin-bottom: 8px; }
.mnx-sk-line.w40 { width: 40%; } .mnx-sk-line.w60 { width: 60%; } .mnx-sk-line.w80 { width: 80%; }
.mnx-sk-card { height: 96px; border-radius: var(--mnx-r-md); margin-bottom: var(--mnx-gap); }
.mnx-sk-kpi { height: 78px; border-radius: var(--mnx-r-md); }

/* Stagger de entrada en listas (--i inline en el loop Razor; delay capeado). */
/* mnx-17: cap 12 → 8 y stagger 40ms → 28ms. Antes el ítem 12 esperaba 480ms
   ANTES de empezar a aparecer: la lista se sentía perezosa justo cuando el
   empleado ya apuntó el dedo. Ahora el último arranca a los 224ms. */
.mnx-stagger > * { animation: mnx-page-in var(--mnx-dur-3) var(--mnx-emph-dec) both; animation-delay: calc(min(var(--i, 0), 8) * .028s); }

/* ============================================================================
   COMPONENTES EXTENDIDOS (páginas internas)
   ============================================================================ */
/* Tabs segmentados */
.mnx-segs { display: flex; gap: 6px; background: var(--mnx-bg-1); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-pill); padding: 5px; margin-bottom: var(--mnx-gap); }
.mnx-seg { flex: 1; min-height: 40px; border-radius: var(--mnx-r-pill); font-size: var(--mnx-fs-3); font-weight: 700; color: var(--mnx-fg-3); display: inline-flex; align-items: center; justify-content: center; gap: 6px; transition: color var(--mnx-fast), background var(--mnx-fast); }
.mnx-seg.active { color: #fff; background: var(--mnx-grad-brand); box-shadow: var(--mnx-shadow-1); }

/* Card genérica */
.mnx-card { background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); padding: var(--mnx-pad); margin-bottom: var(--mnx-gap); }
.mnx-card-h { font-size: var(--mnx-fs-2); font-weight: 700; color: var(--mnx-fg-3); text-transform: uppercase; letter-spacing: .8px; margin-bottom: 10px; }

/* Hero numérico (caja neto, etc.) */
.mnx-stat-hero { background: var(--mnx-grad-surface); border: 1px solid var(--mnx-line-2); border-radius: var(--mnx-r-lg); padding: 22px; margin-bottom: var(--mnx-gap); }
.mnx-stat-hero .lbl { font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); text-transform: uppercase; letter-spacing: .8px; font-weight: 700; }
.mnx-stat-hero .val { font-size: var(--mnx-fs-8); font-weight: 900; letter-spacing: -.5px; margin-top: 6px; font-variant-numeric: tabular-nums; }
.mnx-stat-hero .val.pos { color: var(--mnx-ok); } .mnx-stat-hero .val.neg { color: var(--mnx-red-2); }

/* Dúo de stats (ingresos/egresos) */
.mnx-duo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mnx-gap); margin-bottom: var(--mnx-gap); }
.mnx-duo .mnx-card { margin: 0; }
.mnx-duo .lbl { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); font-weight: 700; text-transform: uppercase; letter-spacing: .5px; }
.mnx-duo .num { font-size: var(--mnx-fs-6); font-weight: 700; margin-top: 4px; font-variant-numeric: tabular-nums; }

/* Lista de filas */
.mnx-list { display: flex; flex-direction: column; gap: 8px; }
.mnx-row { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left; padding: 13px 14px; background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); transition: transform var(--mnx-fast), border-color var(--mnx-fast); }
.mnx-row:active { transform: scale(.985); border-color: var(--mnx-line-2); }
.mnx-row-ico { width: 40px; height: 40px; flex: 0 0 auto; border-radius: var(--mnx-r-sm); display: inline-flex; align-items: center; justify-content: center; font-size: var(--mnx-ico-s); background: var(--mnx-bg-3); }
.mnx-row-main { flex: 1; min-width: 0; }
.mnx-row-main b { display: block; font-size: var(--mnx-fs-4); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mnx-row-main span { font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); }
.mnx-row-end { text-align: right; flex: 0 0 auto; }
.mnx-row-end b { font-size: var(--mnx-fs-4); font-weight: 700; font-variant-numeric: tabular-nums; }
.mnx-row-end span { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); display: block; }

/* Fila no leída (notificaciones) — antes vivía como estilo inline por página */
.mnx-row.unread { background: rgba(255, 70, 89, .07); border-left: 3px solid var(--mnx-red-2); }

/* Badges */
.mnx-badge { display: inline-flex; align-items: center; gap: 4px; font-size: var(--mnx-fs-1); font-weight: 700; padding: 3px 9px; border-radius: var(--mnx-r-pill); text-transform: uppercase; letter-spacing: .4px; }
.mnx-badge.ok { background: rgba(52,211,153,.16); color: var(--mnx-ok); }
.mnx-badge.warn { background: rgba(245,183,64,.16); color: var(--mnx-warn); }
.mnx-badge.red { background: var(--mnx-red-soft); color: var(--mnx-red-2); }
.mnx-badge.info { background: rgba(74,163,255,.16); color: var(--mnx-info); }
.mnx-badge.muted { background: var(--mnx-bg-3); color: var(--mnx-fg-3); }

/* Banner de estado (NN/g: rojo solo error, ámbar aviso, verde ok) */
.mnx-banner { display: flex; align-items: center; gap: 12px; padding: 14px 16px; border-radius: var(--mnx-r-md); margin-bottom: var(--mnx-gap); font-size: var(--mnx-fs-3); border: 1px solid; }
.mnx-banner .ic { font-size: var(--mnx-ico-m); flex: 0 0 auto; }
.mnx-banner.ok { background: rgba(52,211,153,.10); border-color: rgba(52,211,153,.3); color: #b9f5dd; }
.mnx-banner.warn { background: rgba(245,183,64,.10); border-color: rgba(245,183,64,.3); color: #ffe6b0; }
.mnx-banner.red { background: var(--mnx-red-soft); border-color: rgba(225,29,58,.35); color: #ffc7cf; }
.mnx-banner.info { background: rgba(74,163,255,.10); border-color: rgba(74,163,255,.3); color: #cfe5ff; }
.mnx-banner b { font-weight: 700; }

/* Grilla de métricas */
.mnx-metrics { display: grid; grid-template-columns: 1fr 1fr; gap: var(--mnx-gap); margin-bottom: var(--mnx-gap); }
.mnx-metric { background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); padding: 14px; }
.mnx-metric .lbl { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); font-weight: 700; }
.mnx-metric .num { font-size: var(--mnx-fs-7); font-weight: 900; margin-top: 4px; font-variant-numeric: tabular-nums; }

/* Inputs */
.mnx-input, .mnx-textarea { width: 100%; background: var(--mnx-bg-1); border: 1.5px solid var(--mnx-line-2); border-radius: var(--mnx-r-md); color: var(--mnx-fg); font-size: var(--mnx-fs-input); padding: 14px 16px; transition: border-color var(--mnx-fast); }
.mnx-input:focus, .mnx-textarea:focus { outline: none; border-color: var(--mnx-red-2); }
.mnx-textarea { min-height: 110px; resize: vertical; font-family: inherit; }
.mnx-label { font-size: var(--mnx-fs-2); font-weight: 700; color: var(--mnx-fg-2); margin-bottom: 7px; display: block; }
.mnx-field { margin-bottom: var(--mnx-gap); }

/* Search bar */
.mnx-search { position: relative; margin-bottom: var(--mnx-gap); }
.mnx-search .ic { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); font-size: var(--mnx-ico-s); color: var(--mnx-fg-3); pointer-events: none; }
.mnx-search .mnx-input { padding-left: 46px; }

/* Botones */
.mnx-btn { width: 100%; min-height: var(--mnx-touch); border-radius: var(--mnx-r-md); font-size: var(--mnx-fs-4); font-weight: 700; display: inline-flex; align-items: center; justify-content: center; gap: 8px; transition: transform var(--mnx-fast); padding: 0 18px; }
.mnx-btn:active { transform: scale(.97); }
.mnx-btn.primary { background: var(--mnx-grad-brand); color: #fff; box-shadow: var(--mnx-shadow-red); }
.mnx-btn.ghost { background: var(--mnx-bg-2); border: 1px solid var(--mnx-line-2); color: var(--mnx-fg); }
.mnx-btn.ok { background: var(--mnx-grad-ok); color: #06281c; }
/* Acción DESTRUCTIVA (anular/forzar/cancelar OC): rojo pleno + texto claro —
   nunca texto rojo sobre fondo rojo (bug CierreDetalle "Forzar"). */
.mnx-btn.danger { background: var(--mnx-grad-danger); color: #ffd9dd; border: 1px solid rgba(255,255,255,.14); }
.mnx-btn:disabled { opacity: .5; transform: none; box-shadow: none; }
.mnx-btn-row { display: flex; gap: 10px; }

/* Extended FAB (acción primaria por pantalla) + collapse-on-scroll */
.mnx-fab-ext { position: fixed; right: var(--mnx-pad); bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + 16px); z-index: var(--mnx-z-fab); display: inline-flex; align-items: center; gap: 8px; height: 56px; padding: 0 20px; border-radius: var(--mnx-r-pill); background: linear-gradient(150deg, var(--mnx-red-2), var(--mnx-red-deep)); color: #fff; font-weight: 700; font-size: var(--mnx-fs-4); box-shadow: var(--mnx-shadow-red); /* mnx-17: animaba width y padding = layout en cada cambio. Sólo transform. */
    transition: transform var(--mnx-dur-3) var(--mnx-spring-soft), opacity var(--mnx-fast); }
.mnx-fab-ext:active { transform: scale(.95); }
.mnx-fab-ext .ico { font-size: var(--mnx-ico-m); line-height: 1; }
.mnx-fab-ext.collapsed { padding: 0; width: 56px; justify-content: center; }
.mnx-fab-ext.collapsed .lbl { display: none; }

/* Stepper (qty +/-) */
.mnx-stepper { display: inline-flex; align-items: center; gap: 0; background: var(--mnx-bg-3); border-radius: var(--mnx-r-pill); }
.mnx-stepper button { width: 38px; height: 38px; border-radius: var(--mnx-r-pill); font-size: var(--mnx-fs-6); font-weight: 700; display: inline-flex; align-items: center; justify-content: center; color: var(--mnx-fg); }
.mnx-stepper button:active { background: var(--mnx-bg-1); }
.mnx-stepper .q { min-width: 30px; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums; }

/* Bottom sheet */
.mnx-sheet-bd { position: fixed; inset: 0; z-index: var(--mnx-z-sheet); background: rgba(0,0,0,.6); backdrop-filter: blur(3px); animation: mnx-fade var(--mnx-fast) ease; display: flex; align-items: flex-end; }
@keyframes mnx-fade { from { opacity: 0; } to { opacity: 1; } }
.mnx-sheet { width: 100%; max-height: 86dvh; overflow-y: auto; background: var(--mnx-bg-1); border-radius: var(--mnx-r-lg) var(--mnx-r-lg) 0 0; border-top: 1px solid var(--mnx-line-2); padding: 10px var(--mnx-pad) calc(var(--mnx-pad) + var(--mnx-safe-bottom)); animation: mnx-sheet-up var(--mnx-med) var(--mnx-spring); }
@keyframes mnx-sheet-up { from { transform: translateY(100%); } to { transform: translateY(0); } }
.mnx-sheet-grip { width: 42px; height: 5px; border-radius: 3px; background: var(--mnx-line-2); margin: 4px auto 14px; }
.mnx-sheet h3 { font-size: var(--mnx-fs-5); font-weight: 700; margin-bottom: 4px; }

/* Estados (vacío / cargando / error) */
.mnx-empty { text-align: center; padding: 48px 24px; color: var(--mnx-fg-3); }
/*  ⚠️ SIN `margin-bottom` ACÁ: §3.4 pone `margin: 0 auto 14px` sobre el MISMO
    selector y el shorthand se come el longhand de arriba. Es el caso que no ve
    un detector ingenuo —las propiedades no se llaman igual— y por eso el 10px
    sobrevivió leyéndose como si mandara. El `font-size` sí vive: gobierna el
    tamaño del <MnxIco> de adentro, que mide 1em.  */
.mnx-empty .em { font-size: var(--mnx-ico-xl); }
.mnx-empty b { display: block; font-size: var(--mnx-fs-5); color: var(--mnx-fg-2); font-weight: 700; margin-bottom: 4px; }
.mnx-empty span { font-size: var(--mnx-fs-3); }
.mnx-empty .mnx-btn { width: auto; margin: 16px auto 0; padding: 0 22px; }
.mnx-skrow { height: 66px; border-radius: var(--mnx-r-md); margin-bottom: 8px; }

/* Donut rendimiento */
.mnx-donut { position: relative; width: 120px; height: 120px; flex: 0 0 auto; }
.mnx-donut svg { transform: rotate(-90deg); }
.mnx-donut .ctr { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.mnx-donut .ctr b { font-size: var(--mnx-fs-7); font-weight: 900; }
.mnx-donut .ctr span { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); text-transform: uppercase; letter-spacing: .5px; }
.mnx-perf { display: flex; align-items: center; gap: 18px; }
.mnx-perf-info { flex: 1; }
.mnx-perf-info .t { font-size: var(--mnx-fs-4); font-weight: 700; margin-bottom: 4px; }
.mnx-perf-info .s { font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); }

/* Stepper de pasos (cierre) */
.mnx-steps { display: flex; align-items: center; gap: 6px; margin-bottom: 18px; }
.mnx-steps .dot { flex: 1; height: 4px; border-radius: 3px; background: var(--mnx-bg-3); transition: background var(--mnx-med); }
.mnx-steps .dot.on { background: var(--mnx-red-2); }

/* Checklist */
.mnx-check { display: flex; align-items: center; gap: 12px; padding: 13px 14px; background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); margin-bottom: 8px; }
.mnx-check .box { width: 26px; height: 26px; flex: 0 0 auto; border-radius: 8px; border: 2px solid var(--mnx-line-2); display: inline-flex; align-items: center; justify-content: center; transition: background, border-color, transform var(--mnx-fast); }
.mnx-check.on .box { background: var(--mnx-ok); border-color: var(--mnx-ok); color: #06281c; }
.mnx-check .txt { flex: 1; font-size: var(--mnx-fs-4); }

/* Toggle de tarea (check circular) */
.mnx-task { display: flex; align-items: center; gap: 13px; padding: 14px; background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); margin-bottom: 8px; transition: opacity var(--mnx-med); }
.mnx-task.done { opacity: .55; }
.mnx-task .tick { width: 30px; height: 30px; flex: 0 0 auto; border-radius: 50%; border: 2.5px solid var(--mnx-line-2); display: inline-flex; align-items: center; justify-content: center; font-size: var(--mnx-ico-s); transition: background, border-color, transform var(--mnx-fast) var(--mnx-spring); }
.mnx-task.done .tick { background: var(--mnx-ok); border-color: var(--mnx-ok); color: #06281c; transform: scale(1.05); }
.mnx-task .tmain { flex: 1; min-width: 0; }
.mnx-task .tmain b { font-size: var(--mnx-fs-4); font-weight: 700; }
.mnx-task.done .tmain b { text-decoration: line-through; }
.mnx-task .tmain span { display: block; font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); margin-top: 2px; }

/* Avatar perfil */
.mnx-avatar { width: 80px; height: 80px; border-radius: 50%; background: var(--mnx-grad-brand); display: inline-flex; align-items: center; justify-content: center; font-size: var(--mnx-ico-xl); font-weight: 900; color: #fff; box-shadow: var(--mnx-shadow-red); }
.mnx-profile-top { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 18px 0 22px; }
.mnx-profile-top h2 { font-size: var(--mnx-fs-6); font-weight: 700; margin-top: 12px; }
.mnx-profile-top p { color: var(--mnx-fg-3); font-size: var(--mnx-fs-3); margin-top: 3px; }

/* Cart footer sticky (POS) */
.mnx-cartbar { position: fixed; left: 0; right: 0; bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom)); z-index: var(--mnx-z-fab); padding: 12px var(--mnx-pad); background: var(--mnx-glass); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); border-top: 1px solid var(--mnx-line-2); display: flex; align-items: center; gap: 14px; }
.mnx-cartbar .tot { flex: 1; } .mnx-cartbar .tot span { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); display: block; } .mnx-cartbar .tot b { font-size: var(--mnx-fs-7); font-weight: 900; font-variant-numeric: tabular-nums; }
.mnx-cartbar .mnx-btn { width: auto; flex: 0 0 auto; padding: 0 26px; }

/* Pago chips */
.mnx-pays { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.mnx-pay { display: flex; align-items: center; gap: 10px; padding: 14px; border-radius: var(--mnx-r-md); background: var(--mnx-bg-2); border: 1.5px solid var(--mnx-line); font-size: var(--mnx-fs-4); font-weight: 700; transition: background, border-color, transform var(--mnx-fast); }
.mnx-pay.on { border-color: var(--mnx-red-2); background: var(--mnx-red-soft); }
.mnx-pay .em { font-size: var(--mnx-ico-m); }

/* Toast host (apilados sobre la bottom-nav) */
.mnx-toast-host { position: fixed; left: 12px; right: 12px; bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + 14px); z-index: 70; display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none; }
.mnx-toast { display: flex; align-items: center; gap: 10px; pointer-events: auto; background: var(--mnx-bg-3); color: var(--mnx-fg); border: 1px solid var(--mnx-line-2); border-radius: var(--mnx-r-md); padding: 13px 18px; font-size: var(--mnx-fs-3); font-weight: 700; box-shadow: var(--mnx-shadow-2); animation: mnx-toast-in var(--mnx-med) var(--mnx-spring); max-width: 92vw; }
.mnx-toast .ic { font-size: var(--mnx-ico-s); flex: 0 0 auto; }
/*  ⚠️ EL TOAST SE DIBUJABA ENCIMA DE "COBRAR" (2026-07-30).
    El toast host se apoya sobre la barra de navegación (nav-h + 14). Pero en el POS,
    encima de la nav vive .mnx-cartbar —el total, el botón ⛶ de escanear y el botón
    COBRAR—, así que el toast le caía justo encima. Y el toast que aparece es
    "Agregado: <producto>", que dura 4 segundos: el empleado agrega el último ítem,
    va a tocar Cobrar y el botón está tapado. En el mostrador con gente esperando,
    eso son cuatro segundos mirando la pantalla sin entender.
    Cuando hay cartbar, el toast se corre por encima de ELLA.  */
body:has(.mnx-cartbar) .mnx-toast-host {
    bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + var(--mnx-cartbar-h) + 14px);
}
.mnx-toast .msg { line-height: 1.35; word-break: break-word; }
.mnx-toast.ok { border-color: rgba(52,211,153,.4); box-shadow: var(--mnx-shadow-2), 0 0 16px rgba(52,211,153,.18); }
.mnx-toast.err { border-color: rgba(225,29,58,.4); box-shadow: var(--mnx-shadow-2), 0 0 16px rgba(225,29,58,.18); }
.mnx-toast.warn { border-color: rgba(245,183,64,.45); box-shadow: var(--mnx-shadow-2), 0 0 16px rgba(245,183,64,.18); }
.mnx-toast.leaving { animation: mnx-toast-out .22s var(--mnx-ease) forwards; }
@keyframes mnx-toast-in { from { opacity: 0; transform: translateY(16px) scale(.96); } to { opacity: 1; transform: translateY(0) scale(1); } }

/* ============================================================================
   EL MOMENTO EN QUE ALGO SALE BIEN
   ============================================================================
   Confirmar una venta es el acto más repetido del sistema y el único que le
   cierra el trato a un cliente que está parado enfrente. Hasta acá no pasaba
   NADA: aparecía un cartel con borde verde, igual que cualquier otro aviso. Un
   cajero hace eso doscientas veces por turno; que se sienta bien no es adorno,
   es la diferencia entre una herramienta y un trámite.

   Va en el TOAST y no en el botón de cobrar a propósito: el toast lo emite
   `IToastService`, por donde pasa TODA confirmación del sistema — venta, cierre
   de caja, orden de compra, tarea terminada. Una regla, y el momento aparece en
   las 56 pantallas sin tocar ninguna. Enganchado al botón del POS habría sido
   una pantalla sola, y tocando el archivo más delicado del repo.

   ⚠️ Sólo transform y opacity, y corre UNA VEZ (sin `infinite`): es lo que el
   lote 1 sacó de encima, no lo que volvió a poner. El ícono ya es un <MnxIco>
   desde el lote 3, así que hay algo concreto a lo que animarle la escala; con
   el emoji de antes esto no se podía hacer.  */
@keyframes mnx-ok-pop {
    0%   { transform: scale(.4) rotate(-12deg); opacity: 0; }
    55%  { transform: scale(1.18) rotate(2deg); opacity: 1; }
    100% { transform: scale(1) rotate(0); opacity: 1; }
}
.mnx-toast.ok .mnx-ic { animation: mnx-ok-pop var(--mnx-dur-4) var(--mnx-spring) both; }

/*  El error NO rebota: un rebote es celebración y acá algo salió mal. Sacude,
    que es el gesto de "no". Misma duración para que el sistema se sienta uno
    solo, gesto opuesto para que no haya que leer el texto para saber cuál es.  */
@keyframes mnx-err-shake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-3px); }  40% { transform: translateX(3px); }
    60% { transform: translateX(-2px); }  80% { transform: translateX(2px); }
}
.mnx-toast.err .mnx-ic { animation: mnx-err-shake var(--mnx-dur-4) var(--mnx-ease) both; }
@keyframes mnx-toast-out { to { opacity: 0; transform: translateY(8px) scale(.98); } }

/* Spinner */
.mnx-spin { width: 22px; height: 22px; border: 2.5px solid rgba(255,255,255,.25); border-top-color: #fff; border-radius: 50%; animation: mnx-spin .7s linear infinite; }
@keyframes mnx-spin { to { transform: rotate(360deg); } }

/* Banner de instalación PWA (aparece cuando se puede instalar) */
.mnx-install { position: fixed; left: 10px; right: 10px; bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + 12px); z-index: 75; display: flex; align-items: center; gap: 12px; padding: 12px 12px 12px 12px; background: linear-gradient(150deg, var(--mnx-bg-3), var(--mnx-bg-2)); border: 1px solid var(--mnx-line-2); border-radius: var(--mnx-r-lg); box-shadow: var(--mnx-shadow-2), 0 0 24px rgba(225,29,58,.14); transform: translateY(140%); opacity: 0; transition: transform var(--mnx-med) var(--mnx-spring), opacity var(--mnx-med); max-width: 560px; margin: 0 auto; }
.mnx-install.show { transform: translateY(0); opacity: 1; }
.mnx-install img { width: 46px; height: 46px; border-radius: 12px; flex: 0 0 auto; box-shadow: var(--mnx-shadow-1); }
.mnx-install-t { flex: 1; min-width: 0; }
.mnx-install-t b { display: block; font-size: var(--mnx-fs-4); font-weight: 700; }
.mnx-install-t span { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); line-height: 1.3; }
.mnx-install-go { flex: 0 0 auto; background: var(--mnx-grad-brand); color: #fff; font-weight: 700; font-size: var(--mnx-fs-3); padding: 11px 18px; border-radius: var(--mnx-r-pill); box-shadow: var(--mnx-shadow-red); }
.mnx-install-go:active { transform: scale(.95); }
.mnx-install-x { flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%; color: var(--mnx-fg-3); font-size: var(--mnx-ico-xs); display: inline-flex; align-items: center; justify-content: center; }
.mnx-install-x:active { background: var(--mnx-bg-1); }

/* ============================================================================
   POSTULANTES (RRHH) :: tabs scrollables, acordeón, chips, barras, dialog
   Material 3: primary tabs scrollables (>4 destinos) + progressive disclosure.
   ============================================================================ */
/* Tabs primary scrollables (sticky bajo el header) */
.mnx-tabs { display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none; position: sticky; top: calc(var(--mnx-header-h) + var(--mnx-safe-top)); z-index: 30; background: var(--mnx-bg-0); margin: 0 calc(-1 * var(--mnx-pad)) var(--mnx-gap); padding: 8px var(--mnx-pad); border-bottom: 1px solid var(--mnx-line); }
.mnx-tabs::-webkit-scrollbar { display: none; }
.mnx-tab { flex: 0 0 auto; min-height: 40px; padding: 0 14px; border-radius: var(--mnx-r-pill); font-size: var(--mnx-fs-3); font-weight: 700; color: var(--mnx-fg-3); display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; transition: color var(--mnx-fast), background var(--mnx-fast); }
.mnx-tab.active { color: #fff; background: var(--mnx-red-soft); color: var(--mnx-red-2); position: relative; }
.mnx-tab.active::after { content: ""; position: absolute; left: 14px; right: 14px; bottom: -9px; height: 3px; border-radius: 3px; background: var(--mnx-red-2); box-shadow: 0 0 10px var(--mnx-red); }

/* Acordeón (progressive disclosure) — usa <details class="mnx-acc"> */
.mnx-acc { background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); margin-bottom: 10px; overflow: hidden; }
.mnx-acc > summary { list-style: none; cursor: pointer; padding: 14px var(--mnx-pad); display: flex; align-items: center; gap: 10px; font-size: var(--mnx-fs-4); font-weight: 700; color: var(--mnx-fg); }
.mnx-acc > summary::-webkit-details-marker { display: none; }
.mnx-acc > summary .chev { margin-left: auto; color: var(--mnx-fg-3); transition: transform var(--mnx-med) var(--mnx-spring); font-size: var(--mnx-ico-xs); }
.mnx-acc[open] > summary .chev { transform: rotate(90deg); }
.mnx-acc > summary .badge-mini { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); font-weight: 700; }
.mnx-acc-body { padding: 0 var(--mnx-pad) 14px; }

/* Key-value (datos read-only de la ficha) */
.mnx-kv { display: flex; flex-direction: column; gap: 2px; }
.mnx-kv-row { display: flex; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--mnx-line); }
.mnx-kv-row:last-child { border-bottom: none; }
.mnx-kv-row .k { flex: 0 0 42%; font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); font-weight: 500; }
.mnx-kv-row .v { flex: 1; font-size: var(--mnx-fs-3); color: var(--mnx-fg); font-weight: 700; min-width: 0; }
.mnx-kv-row .v.block { flex-basis: 100%; }
.mnx-kv-row.col { flex-direction: column; gap: 4px; }
.mnx-kv-row.col .v { white-space: pre-wrap; word-break: break-word; font-weight: 500; line-height: 1.45; }

/* Chips (filtros togglables y pills informativas en cards) */
.mnx-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.mnx-chip { display: inline-flex; align-items: center; gap: 5px; min-height: 38px; padding: 0 13px; border-radius: var(--mnx-r-pill); font-size: var(--mnx-fs-2); font-weight: 700; color: var(--mnx-fg-2); background: var(--mnx-bg-2); border: 1.5px solid var(--mnx-line); transition: background, border-color, color, transform var(--mnx-fast); }
.mnx-chip.on { color: #fff; background: var(--mnx-red-soft); border-color: var(--mnx-red-2); color: var(--mnx-red-2); }
.mnx-chip.red.on { background: var(--mnx-red-soft); border-color: var(--mnx-red-2); color: var(--mnx-red-2); }

/* Barras de progreso (scoring por categoría) */
.mnx-bar { height: 8px; border-radius: 5px; background: var(--mnx-bg-1); overflow: hidden; margin-top: 6px; }
.mnx-bar > i { display: block; height: 100%; border-radius: 5px; }

/* Avatar chico para la lista */
.mnx-avatar.sm { width: 46px; height: 46px; font-size: var(--mnx-ico-s); box-shadow: none; flex: 0 0 auto; }

/* Card de postulante (lista) — outlined, acento si completo */
.mnx-pcard { display: block; width: 100%; text-align: left; background: var(--mnx-bg-2); border: 1px solid var(--mnx-line); border-radius: var(--mnx-r-md); padding: 14px; margin-bottom: 10px; transition: transform var(--mnx-fast), border-color var(--mnx-fast); }
.mnx-pcard:active { transform: scale(.99); border-color: var(--mnx-line-2); }
.mnx-pcard.completo { border-left: 3px solid var(--mnx-ok); }

/* Dialog centrado (confirmación binaria, NN/g) */
.mnx-dialog-bd { position: fixed; inset: 0; z-index: var(--mnx-z-sheet); background: rgba(0,0,0,.6); backdrop-filter: blur(3px); animation: mnx-fade var(--mnx-fast) ease; display: flex; align-items: center; justify-content: center; padding: 24px; }
.mnx-dialog { width: 100%; max-width: 380px; background: var(--mnx-bg-2); border: 1px solid var(--mnx-line-2); border-radius: var(--mnx-r-lg); padding: 22px; animation: mnx-sheet-up var(--mnx-med) var(--mnx-spring); }
.mnx-dialog h3 { font-size: var(--mnx-fs-5); font-weight: 700; margin-bottom: 8px; }
.mnx-dialog p { font-size: var(--mnx-fs-3); color: var(--mnx-fg-2); line-height: 1.5; margin-bottom: 18px; }

/* Barra de guardado pinned al fondo del contenido de una tab */
/*  ⚠️ LA BARRA DE GUARDADO QUEDABA DEBAJO DE LA BARRA DE NAVEGACIÓN (2026-07-30).
    Estaba en `bottom: 0`, o sea pegada al borde del viewport — y ahí abajo está
    .mnx-nav, que es `position:fixed; bottom:0` con 66 px + safe-area. Resultado:
    los botones de la savebar quedaban TAPADOS por la barra de abajo.
    No es cosmético: la savebar es la que lleva "Siguiente" en el wizard del cierre
    de turno y "Guardar"/"Cerrar compra" en el armado de una OC. Con el botón
    tapado, el paso no se puede pasar desde el celular.
    (La savebar nació justamente para des-enterrar esos botones —ver el comentario
    de nucleo-operativo P2 en el cierre—: se los movió de abajo de todo a abajo de
    la nav, que es un poquito mejor y sigue siendo inusable.)  */
.mnx-savebar { position: sticky; bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom)); margin: 14px calc(-1 * var(--mnx-pad)) 0; padding: 12px var(--mnx-pad) calc(12px + var(--mnx-safe-bottom)); background: var(--mnx-glass); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); border-top: 1px solid var(--mnx-line-2); }

/* Player de audio full-width */
.mnx-audio { width: 100%; margin-top: 10px; border-radius: var(--mnx-r-md); }
.mnx-selfie { width: 100%; max-width: 280px; border-radius: var(--mnx-r-md); border: 1px solid var(--mnx-line-2); display: block; }

/* ============================================================================
   MNX v2 — UPGRADES DE COMPONENTES (Material 3 Expressive)
   Solo cambia PROPIEDADES (cero renombres de clases/tokens). Source-order: estas
   reglas van últimas → ganan a las de arriba a igual especificidad. Aditivo y
   reversible (borrar este bloque vuelve a v1).
   ============================================================================ */

/* 2.0 — State layer universal M3: overlay currentColor al presionar, en TODO
   elemento interactivo (no solo botones). El ripple JS (.mnx-rp) sigue encima. */
.mnx-row, .mnx-tile, .mnx-seg, .mnx-pay, .mnx-nav-item, .mnx-icon-btn, .mnx-pcard { position: relative; }
.mnx-row::after, .mnx-tile::after, .mnx-seg::after, .mnx-pay::after,
.mnx-nav-item::after, .mnx-icon-btn::after, .mnx-pcard::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    background: currentColor; opacity: 0; pointer-events: none;
    transition: opacity var(--mnx-dur-2) var(--mnx-ease-std);
}
.mnx-row:active::after, .mnx-tile:active::after, .mnx-seg:active::after,
.mnx-pay:active::after, .mnx-nav-item:active::after, .mnx-icon-btn:active::after,
.mnx-pcard:active::after { opacity: var(--mnx-st-press); transition: opacity 0s; }

/* 2.1 — Header / app bar: sube a surface-container al scrollear (profundidad M3) */
.mnx-header { transition: background var(--mnx-dur-3) var(--mnx-ease-std), box-shadow var(--mnx-dur-3) var(--mnx-ease-std); }
.mnx-header.scrolled { background: var(--mnx-bg-2); box-shadow: var(--mnx-shadow-1); }

/* 2.2 — Nav: active-indicator PILL detrás del ícono (firma visual M3 nav-bar).
   Reemplaza la barrita superior v1 (mismo selector, gana por source-order). */
.mnx-nav-item.active::before {
    content: ""; position: absolute; top: 7px; left: 50%; width: 60px; height: 30px;
    transform: translateX(-50%); border-radius: var(--mnx-r-pill);
    background: var(--mnx-red-soft); box-shadow: none; z-index: 0;
    animation: mnx-pill-in var(--mnx-dur-3) var(--mnx-spring-soft);
}
.mnx-nav-item.active .mnx-nav-ico, .mnx-nav-item.active span { position: relative; z-index: 1; }
.mnx-nav-item.active .mnx-nav-ico { transform: translateY(-1px) scale(1.04); }
@keyframes mnx-pill-in { from { transform: translateX(-50%) scaleX(.4); opacity: 0; } to { transform: translateX(-50%) scaleX(1); opacity: 1; } }
.mnx-nav-fab:active { transform: scale(.92); }

/* 2.3 — Rows: thumbnail de producto (reusa fotos webp del manifest del catálogo).
   Cae a .mnx-row-ico (emoji) si no hay foto. Eleva la calidad visual al instante. */
.mnx-row-thumb { width: 44px; height: 44px; flex: 0 0 auto; border-radius: var(--mnx-r-sm); object-fit: cover; background: var(--mnx-bg-3); }

/* 2.4 — Cards: variante elevada con elevación tonal (no sombra pesada) */
.mnx-card.elev { background: var(--mnx-bg-3); box-shadow: var(--mnx-shadow-1); border-color: transparent; }

/* 2.5 — Chips: fila scrollable horizontal (filtros proveedores/categorías).
   NO cambia el color del chip activo (sigue rojo, on-brand y coherente con RRHH). */
.mnx-chips-scroll { display: flex; gap: 8px; overflow-x: auto; padding: 2px 0; scrollbar-width: none; -webkit-overflow-scrolling: touch; margin-bottom: var(--mnx-gap); }
.mnx-chips-scroll::-webkit-scrollbar { display: none; }
.mnx-chips-scroll .mnx-chip { flex: 0 0 auto; }

/* 2.6 — Botones: escalón tonal (acción secundaria fuerte) + shape-morph M3 */
.mnx-btn.tonal { background: var(--mnx-secondary-container); color: var(--mnx-on-secondary-container); box-shadow: none; }
.mnx-btn.primary.morph { transition: transform var(--mnx-fast), border-radius var(--mnx-fast) var(--mnx-ease); }
.mnx-btn.primary.morph:active { border-radius: var(--mnx-r-sm); }

/* 2.7 — Sheets: surface-container-highest + radio extra-large + sombra suave M3 */
.mnx-sheet { background: var(--mnx-bg-4); border-radius: var(--mnx-r-xl) var(--mnx-r-xl) 0 0; box-shadow: var(--mnx-shadow-2); }
.mnx-sheet-bd { background: var(--mnx-scrim); }
.mnx-dialog { background: var(--mnx-bg-4); box-shadow: var(--mnx-shadow-3); }

/* 2.8 — Inputs: focus-ring M3 (anillo, no solo borde) + radio text-field */
.mnx-input, .mnx-textarea { border-radius: var(--mnx-r-sm); transition: border-color var(--mnx-fast), box-shadow var(--mnx-fast); }
.mnx-input:focus, .mnx-textarea:focus { box-shadow: 0 0 0 3px var(--mnx-red-soft); }

/* 2.9 — Snackbar (toast): surface-container-highest + acción opcional (undo no-bloqueante) */
.mnx-toast { background: var(--mnx-bg-4); border-radius: var(--mnx-r-sm); }
.mnx-toast .act { margin-left: auto; color: var(--mnx-red-2); font-weight: 700; font-size: var(--mnx-fs-3); padding: 6px 8px; border-radius: var(--mnx-r-s); white-space: nowrap; }

/* 2.10 — Skeleton de fila (perceived performance en búsqueda POS / cambio de proveedor OC) */
.mnx-sk-row { height: 68px; border-radius: var(--mnx-r-md); margin-bottom: 8px; }

/* 2.11 — Keypad numérico propio (POS cantidades / OC) — teclas grandes thumb-zone */
.mnx-keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 8px 0 calc(8px + var(--mnx-safe-bottom)); }
.mnx-keypad button { min-height: 60px; border-radius: var(--mnx-r-sm); background: var(--mnx-bg-3); color: var(--mnx-fg); font-size: var(--mnx-fs-7); font-weight: 700; font-feature-settings: var(--mnx-num); position: relative; }
.mnx-keypad button:active { background: var(--mnx-bg-4); }
.mnx-keypad .span2 { grid-column: span 2; }
.mnx-keypad .ok { background: var(--mnx-secondary-container); color: var(--mnx-on-secondary-container); }

/* 2.12 — Denominaciones AR (POS efectivo) + total con números tabulares premium */
.mnx-denoms { display: flex; gap: 8px; overflow-x: auto; padding: 2px 0 10px; scrollbar-width: none; }
.mnx-denoms::-webkit-scrollbar { display: none; }
.mnx-denom { flex: 0 0 auto; min-height: 44px; padding: 0 16px; border-radius: var(--mnx-r-pill); background: var(--mnx-bg-3); border: 1px solid var(--mnx-line-2); color: var(--mnx-fg); font-weight: 700; font-size: var(--mnx-fs-4); font-feature-settings: var(--mnx-num); }
.mnx-denom:active { background: var(--mnx-bg-4); }

/* ============================================================================
   MNX :: Lector QR + código de barras (full-screen, premium)
   ============================================================================ */
.mnx-scan { position: fixed; inset: 0; z-index: 95; background: #000; overflow: hidden; }
.mnx-scan-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.mnx-scan-overlay { position: absolute; inset: 0; display: flex; flex-direction: column; }
.mnx-scan-top { display: flex; align-items: center; gap: 12px;
    padding: calc(var(--mnx-safe-top) + 12px) 14px 12px;
    background: linear-gradient(to bottom, rgba(0,0,0,.62), transparent); }
.mnx-scan-x, .mnx-scan-torch { width: 44px; height: 44px; flex: 0 0 auto; border-radius: var(--mnx-r-pill);
    background: rgba(0,0,0,.45); color: #fff; font-size: var(--mnx-ico-s); display: inline-flex; align-items: center; justify-content: center; }
.mnx-scan-torch.on { background: var(--mnx-tertiary); color: #241a00; }
.mnx-scan-title { flex: 1; color: #fff; font-weight: 700; font-size: var(--mnx-fs-4); text-shadow: 0 1px 4px rgba(0,0,0,.6); }
/* Ventana de escaneo: recorte oscuro alrededor (box-shadow gigante) + marco */
.mnx-scan-reticle { margin: auto; width: 74%; max-width: 320px; aspect-ratio: 1 / .64; border-radius: 22px;
    position: relative; box-shadow: 0 0 0 100vmax rgba(0,0,0,.46); }
.mnx-scan-reticle::before { content: ""; position: absolute; inset: 0; border-radius: 22px;
    border: 3px solid rgba(255,255,255,.92); box-shadow: inset 0 0 0 1px rgba(0,0,0,.2); }
.mnx-scan-reticle .laser { position: absolute; left: 7%; right: 7%; top: 14%; height: 2px;
    background: var(--mnx-red-2); box-shadow: 0 0 12px 1px var(--mnx-red-2); border-radius: 2px;
    top: 14%; will-change: transform;
    animation: mnx-laser 1.9s var(--mnx-ease-std) infinite; }
/* mnx-17: era `top` (layout) corriendo sobre el preview de la cámara — lo peor
   posible. Ahora es transform: el compositor lo resuelve sin tocar el layout. */
@keyframes mnx-laser { 0% { transform: translateY(0); } 50% { transform: translateY(560%); } 100% { transform: translateY(0); } }
/*  ── LA TARJETA DE "LO QUE ACABÁS DE CARGAR" (dentro del escáner) ──────────
    Vive sobre el preview de la cámara, así que necesita fondo propio: encima de
    una imagen en movimiento, el texto sin superficie es ilegible. Va compacta —
    el escáner es una pantalla de UNA mano y lo que importa sigue siendo la
    retícula. */
.mnx-scan-ultima {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 12px; margin-bottom: 8px;
    border-radius: var(--mnx-r-md);
    background: var(--mnx-glass);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border: 1px solid var(--mnx-line-2);
}
.mnx-scan-ultima-txt { flex: 1; min-width: 0; }
.mnx-scan-ultima-txt b {
    display: block; font-size: var(--mnx-fs-4); font-weight: 700;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mnx-scan-ultima-txt span { font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); }

.mnx-scan-foot { margin-top: auto; background: linear-gradient(to top, rgba(0,0,0,.74), transparent);
    padding: 20px 14px calc(16px + var(--mnx-safe-bottom)); }
.mnx-scan-hint { text-align: center; color: #fff; font-size: var(--mnx-fs-3); font-weight: 500; margin-bottom: 12px; text-shadow: 0 1px 4px rgba(0,0,0,.7); }
/* Feedback del resultado de cada lectura, DENTRO del escáner (los toast quedan tapados
   por la cámara full-screen). ok = agregado · warn = no está en catálogo · busy = buscando. */
.mnx-scan-hint.ok, .mnx-scan-hint.warn, .mnx-scan-hint.busy {
    width: fit-content; max-width: 92%; margin: 0 auto 12px; padding: 9px 16px;
    border-radius: var(--mnx-r-pill); font-size: var(--mnx-fs-4); font-weight: 700; text-shadow: none;
}
.mnx-scan-hint.ok   { background: #16a34a; color: #fff; box-shadow: 0 6px 18px rgba(22,163,74,.45); }
.mnx-scan-hint.warn { background: #e11d2e; color: #fff; box-shadow: 0 6px 18px rgba(225,29,46,.45); }
.mnx-scan-hint.busy { background: rgba(255,255,255,.18); color: #fff; }
/* El marco refleja el resultado: verde si agregó, rojo si no está. */
.mnx-scan-reticle.ok::before   { border-color: #22c55e; box-shadow: inset 0 0 0 1px rgba(0,0,0,.2), 0 0 22px 2px rgba(34,197,94,.6); }
.mnx-scan-reticle.warn::before { border-color: #f87171; box-shadow: inset 0 0 0 1px rgba(0,0,0,.2), 0 0 22px 2px rgba(248,113,113,.6); }
/*  PONER LA CANTIDAD :: misma caja que el tipeo manual del código, para que se
    lea como parte del mismo lector y no como otro formulario. Va arriba del hint,
    o sea entre lo que acabás de leer y el mensaje de resultado. */
.mnx-scan-cant { display: flex; gap: 8px; margin: 8px 0 0; }
.mnx-scan-cant .mnx-input { flex: 1; background: rgba(0,0,0,.5); color: #fff; border-color: rgba(255,255,255,.3); }
.mnx-scan-cant .mnx-btn { width: auto; flex: 0 0 auto; padding: 0 20px; }
.mnx-scan-manual { display: flex; gap: 8px; }
.mnx-scan-manual .mnx-input { flex: 1; background: rgba(0,0,0,.5); color: #fff; border-color: rgba(255,255,255,.3); }
.mnx-scan-manual .mnx-btn { width: auto; flex: 0 0 auto; padding: 0 20px; }

/* Botón de escaneo dentro de un buscador (.mnx-search) */
.mnx-scan-btn { position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
    width: 40px; height: 40px; border-radius: var(--mnx-r-pill); display: inline-flex; align-items: center;
    justify-content: center; font-size: var(--mnx-fs-6); color: var(--mnx-red-2); background: var(--mnx-red-soft); }
.mnx-scan-btn:active { background: var(--mnx-bg-3); }
.mnx-search.has-scan .mnx-input { padding-right: 52px; }

/* ============================================================================
   MNX v3 :: PULIDO PREMIUM (rediseño visual pantalla por pantalla)
   ----------------------------------------------------------------------------
   Orden > decoración. Todo lo de acá abajo existe para una de tres razones:
     (a) ORDEN     — agrupar y jerarquizar lo que hoy es una pila plana de cards.
     (b) PREMIUM   — el detalle que separa "web en un celu" de "app nativa".
     (c) MOVIMIENTO— animación que INFORMA (de dónde vino / qué cambió), nunca
                     adorno. Solo transform/opacity → 60fps y cero carga a SignalR.

   REGLA DE SEGURIDAD (esto es un POS en producción 24/7): ninguna regla de acá
   puede DEJAR CONTENIDO INVISIBLE si el JS no corre. Todo lo que se anima al
   entrar arranca VISIBLE por CSS; es el JS el que agrega la clase que lo
   esconde para revelarlo. Si el JS falla, se ve todo — feo, pero se ve.
   ============================================================================ */

/* ── 3.0 · Pull-to-refresh ─────────────────────────────────────────────────
   El gesto que todo Android espera y que no existía. El JS sigue el dedo y
   escribe --p (0→1); pasado el umbral el spinner se "arma" y al soltar gira. */
.mnx-ptr {
    position: fixed; left: 0; right: 0; z-index: 39;
    top: calc(var(--mnx-safe-top) + var(--mnx-header-h) - 10px);
    display: flex; justify-content: center; pointer-events: none;
}
.mnx-ptr i {
    --p: 0;
    width: 40px; height: 40px; border-radius: var(--mnx-r-pill); font-style: normal;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--mnx-bg-4); border: 1px solid var(--mnx-line-2);
    box-shadow: var(--mnx-shadow-2); color: var(--mnx-fg-3); font-size: var(--mnx-ico-s);
    opacity: calc(var(--p) * 1.4);
    transform: translateY(calc(-46px + var(--p) * 62px)) scale(calc(.55 + var(--p) * .45)) rotate(calc(var(--p) * 200deg));
}
/* Armado: cruzó el umbral, soltando refresca. Se lo decimos con color, no con texto. */
.mnx-ptr.armed i { color: var(--mnx-red-2); border-color: var(--mnx-red-2); box-shadow: var(--mnx-shadow-2), 0 0 16px rgba(225,29,58,.35); }
.mnx-ptr.snap i { transition: transform var(--mnx-dur-3) var(--mnx-spring-soft), opacity var(--mnx-dur-2); }
.mnx-ptr.going i { animation: mnx-spin .7s linear infinite; color: var(--mnx-red-2); border-color: var(--mnx-red-2); }

/* ── 3.1 · GRUPO :: la pieza que ordena ────────────────────────────────────
   Hoy cada fila es una card suelta flotando en el fondo: N bordes, N sombras,
   N radios → ruido. Un grupo es UNA superficie con hairlines adentro: se lee
   como una unidad y el ojo entiende "esto va junto". Es el patrón de los
   settings de iOS/Android y la razón por la que se sienten ordenados. */
.mnx-group {
    background: var(--mnx-bg-2); border: 1px solid var(--mnx-line);
    border-radius: var(--mnx-r-md); overflow: hidden; margin-bottom: var(--mnx-gap);
}
.mnx-group > .mnx-row {
    border: none; border-radius: 0; margin: 0; background: transparent;
    border-bottom: 1px solid var(--mnx-line);
}
.mnx-group > .mnx-row:last-child { border-bottom: none; }
.mnx-group > .mnx-row:active { transform: none; }  /* dentro de un grupo la fila no "salta": se ilumina */

/* Encabezado de sección con contador y acción a la derecha.
   Reemplaza al .mnx-section-h suelto cuando la sección tiene N ítems o un "ver todo". */
.mnx-hgroup { display: flex; align-items: center; gap: 8px; margin: 22px 2px 10px; }
.mnx-hgroup:first-child { margin-top: 6px; }
.mnx-hgroup h2, .mnx-hgroup .h {
    font-size: var(--mnx-fs-3); font-weight: 700; color: var(--mnx-fg-2);
    text-transform: uppercase; letter-spacing: 1px; margin: 0;
}
.mnx-hgroup .n {
    font-size: var(--mnx-fs-1); font-weight: 700; color: var(--mnx-fg-3);
    background: var(--mnx-bg-3); border-radius: var(--mnx-r-pill); padding: 2px 8px;
    font-feature-settings: var(--mnx-num);
}
.mnx-hgroup .go {
    margin-left: auto; font-size: var(--mnx-fs-2); font-weight: 700; color: var(--mnx-red-2);
    display: inline-flex; align-items: center; gap: 4px; padding: 6px 4px; border-radius: var(--mnx-r-s);
}

/* Sección que se borra sola cuando se queda sin contenido.
   Varios widgets del home no renderizan NADA cuando no hay nada que mostrar
   (no hay encargos, no hay pedidos web). Sin esto, agrupar bajo un título deja
   encabezados colgados sobre el vacío — peor que no tener título. Si el único
   hijo es el .mnx-hgroup, la sección entera desaparece.
   Sin soporte de :has() degrada al comportamiento de antes (título visible). */
.mnx-sect { display: block; }
.mnx-sect:not(:has(> :nth-child(2))) { display: none; }

/* Separador fino para partir una lista larga por criterio (hoy / ayer / antes). */
.mnx-divider { display: flex; align-items: center; gap: 10px; margin: 18px 2px 10px; color: var(--mnx-fg-3); font-size: var(--mnx-fs-1); font-weight: 700; text-transform: uppercase; letter-spacing: .8px; }
.mnx-divider::after { content: ""; flex: 1; height: 1px; background: var(--mnx-line); }

/* ── 3.2 · Reveal al scrollear ─────────────────────────────────────────────
   El stagger de .mnx-stagger solo cubre lo que ya está en pantalla al cargar.
   En listas largas, todo lo de abajo aparece de golpe al scrollear. Esto lo
   escalona. FAIL-SAFE: el estado oculto SOLO existe bajo .mnx-ro, clase que
   agrega el JS. Sin JS → nunca hay nada oculto. */
.mnx-ro > .mnx-reveal { opacity: 0; transform: translateY(14px); }
.mnx-ro > .mnx-reveal.in {
    opacity: 1; transform: none;
    transition: opacity var(--mnx-dur-3) var(--mnx-emph-dec), transform var(--mnx-dur-4) var(--mnx-emph-dec);
}

/* ── 3.3 · Plata protagonista ──────────────────────────────────────────────
   La plata es el dato por el que se abre esta app. Merece números tabulares
   (no bailan al actualizarse), tracking apretado y un tamaño que se lea de
   reojo con el celu en la mano y un cliente enfrente. */
/*  Mismo motivo: `.mnx-row-end b` (0,1,1) le ganaba el peso a `.mnx-money`
    (0,1,0) y la plata de esas filas salía en 700 en vez de 900. El 900 de la
    plata es una regla del sistema, no una preferencia de una pantalla.  */
.mnx-root .mnx-money { font-feature-settings: var(--mnx-num); font-variant-numeric: tabular-nums; letter-spacing: -.4px; font-weight: 900; }
.mnx-money .cts { font-size: .62em; font-weight: 700; opacity: .65; margin-left: 1px; }
.mnx-money.pos { color: var(--mnx-ok); } .mnx-money.neg { color: var(--mnx-red-2); }
/* Flash cuando un número cambia en vivo (llegó una venta, se movió la caja). */
@keyframes mnx-num-bump { 0% { transform: none; } 30% { transform: scale(1.07); } 100% { transform: none; } }
.mnx-money.bump { animation: mnx-num-bump var(--mnx-dur-4) var(--mnx-spring); display: inline-block; }

/* Delta vs ayer/período: la flecha y el color cuentan la historia sin leer. */
.mnx-delta { display: inline-flex; align-items: center; gap: 3px; font-size: var(--mnx-fs-1); font-weight: 700; padding: 2px 7px; border-radius: var(--mnx-r-pill); font-feature-settings: var(--mnx-num); }
.mnx-delta.up { background: rgba(111,220,174,.14); color: var(--mnx-ok); }
.mnx-delta.down { background: var(--mnx-red-soft); color: var(--mnx-red-2); }
.mnx-delta.flat { background: var(--mnx-bg-3); color: var(--mnx-fg-3); }

/* ── 3.4 · Estados vacíos con presencia ────────────────────────────────────
   Un vacío no es un error: es una pantalla que todavía no tiene nada. Se
   merece un orbe de marca y una frase que diga qué hacer, no un gris triste. */
.mnx-empty { position: relative; }
.mnx-empty .em {
    display: inline-flex; align-items: center; justify-content: center;
    width: 84px; height: 84px; border-radius: var(--mnx-r-pill); margin: 0 auto 14px;
    background: radial-gradient(circle at 50% 40%, var(--mnx-red-soft), transparent 70%);
}
@media (prefers-reduced-motion: no-preference) {
    /* mnx-17: el orbe flotaba en bucle infinito en TODA pantalla vacía. Un ícono
       que se mueve solo, para siempre, no comunica nada y no deja dormir al GPU. */
    @keyframes mnx-orb { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }
}

/* ── 3.5 · Foco visible (accesibilidad + teclado bluetooth del mostrador) ──
   :focus-visible no dispara con el dedo, así que no ensucia el uso táctil. */
.mnx-root :focus-visible { outline: 2px solid var(--mnx-red-2); outline-offset: 2px; border-radius: var(--mnx-r-s); }

/* ── 3.6 · Badge de conteo en la nav ───────────────────────────────────────
   Tareas pendientes / notificaciones sin leer, visibles sin entrar. */
.mnx-nav-item .mnx-nav-badge {
    position: absolute; top: 4px; left: 50%; margin-left: 6px; z-index: 2;
    min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--mnx-r-pill);
    background: var(--mnx-red); color: #fff; font-size: var(--mnx-fs-1); font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
    border: 2px solid var(--mnx-bg-0); font-feature-settings: var(--mnx-num);
}

/* ── 3.7 · Header: título que respira al scrollear ─────────────────────────
   Arriba de todo el título va grande y cómodo; al scrollear se compacta y
   deja lugar al contenido. Firma de app nativa, cero markup nuevo por página. */
.mnx-header.scrolled .mnx-header-title span { opacity: .75; }
.mnx-header-title span { transition: opacity var(--mnx-dur-3) var(--mnx-ease-std); }

/* ── 3.8 · Barra de progreso de scroll ─────────────────────────────────────
   En pantallas largas (portal, órdenes de compra, inventario) decir cuánto
   falta es orientación, no adorno. Hilo de 2px bajo el header. */
.mnx-header::after {
    content: ""; position: absolute; left: 0; bottom: -1px; height: 2px;
    width: calc(var(--sp, 0) * 100%); background: linear-gradient(90deg, var(--mnx-red-deep), var(--mnx-red-2));
    border-radius: 0 2px 2px 0; pointer-events: none;
    /* Sin scroll --sp = 0 → ancho 0 → invisible sin necesitar opacity. */
}

/* ── 3.9 · Fila con miniatura + acento de estado ───────────────────────────
   Un borde izquierdo de 3px comunica estado sin gastar un badge (que ya tiene
   presupuesto). Se lee en periférica mientras scrolleás. */
.mnx-row.acc-ok { border-left: 3px solid var(--mnx-ok); }
.mnx-row.acc-warn { border-left: 3px solid var(--mnx-warn); }
.mnx-row.acc-red { border-left: 3px solid var(--mnx-red-2); }
.mnx-row.acc-info { border-left: 3px solid var(--mnx-info); }
.mnx-group > .mnx-row[class*="acc-"] { border-left-width: 3px; border-left-style: solid; }

/* ── 3.10 · Cards con jerarquía tonal real ────────────────────────────────
   En dark, la profundidad la da el color de superficie, no la sombra. Una card
   "destacada" sube un escalón tonal y gana un hilo de marca arriba. */
.mnx-card.hero {
    background: var(--mnx-grad-surface);
    border-color: var(--mnx-line-2); position: relative; overflow: hidden;
}
.mnx-card.hero::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
    background: linear-gradient(90deg, var(--mnx-red), var(--mnx-red-2), transparent);
}

/* ── 3.11 · Scroll horizontal con pistas de borde ─────────────────────────
   Un carrusel sin pista de que sigue se lee como cortado. El degradado del
   borde derecho dice "hay más" sin ocupar espacio. */
.mnx-hscroll { position: relative; }
.mnx-hscroll::after {
    content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 34px;
    background: linear-gradient(90deg, transparent, var(--mnx-bg-0));
    pointer-events: none; opacity: .9;
}

/* ── 3.12 · Toque más honesto en superficies grandes ──────────────────────
   Escalar una card entera al tocarla se siente elástico en piezas chicas y
   barato en piezas grandes. Arriba de cierto tamaño, mejor hundirla. */
.mnx-hero:active { transform: scale(.985); }
.mnx-card.tap:active { transform: scale(.99); }
.mnx-card.tap { transition: transform var(--mnx-fast) var(--mnx-ease); }

/* ============================================================================
   MNX v3.1 :: REVISIÓN DE PUNTA A PUNTA DEL SHELL
   ----------------------------------------------------------------------------
   Accesibilidad real de mostrador: el empleado usa esto con UNA mano, con el
   local lleno, muchas veces con el texto del sistema agrandado. Nada de acá es
   decoración: todo apunta a que el toque acierte y a que la barra de abajo —
   la única forma de navegar — no se rompa nunca.
   ============================================================================ */

/* ── 4.0 · Nada invisible si el JS no corre (cinturón sobre tiradores) ──────
   .mnx-ro la agrega el JS, así que sin JS ya no hay estado oculto. Igual, si el
   navegador declara que no ejecuta scripts, lo dejamos explícito: un POS que se
   ve feo se sigue usando, uno en blanco no. */
@media (scripting: none) {
    .mnx-ro > .mnx-reveal { opacity: 1 !important; transform: none !important; }
}

/* ── 4.1 · Áreas táctiles de 44px mínimo ───────────────────────────────────
   Varios controles quedaron en 30-40px. Donde agrandar la caja rompería el
   layout (chips en fila, +/- del stepper, ✕ del banner) se agranda el ÁREA con
   un ::before invisible: el dedo acierta, el dibujo no se mueve. */
/*  ⚠️ .mnx-scan-btn NO VA EN ESTA LISTA — Y ESTUVO, Y ROMPIÓ EL POS.
    Ya es `position: absolute` (línea ~1027) para colgarse a la derecha del
    buscador. Esta regla, 226 líneas MÁS ABAJO y con la misma especificidad, le
    ganaba y lo devolvía al flujo: el botón del escáner se caía DEBAJO y a la
    IZQUIERDA del campo, encimado sobre la lupa y medio afuera de la caja. El
    dueño lo vio en el POS, que es la pantalla que más se usa del sistema.

    Es la quinta vez que muerde el mismo patrón en este archivo (el título del
    header, .mnx-row-main b, .mnx-empty .em, el indicador de pestaña y esto):
    dos declaraciones de lo mismo, gana la de abajo, y la de arriba queda
    leyéndose como si mandara.

    No hace falta acá: `absolute` TAMBIÉN es un ancestro posicionado, así que el
    ::before que agranda el área táctil sigue funcionando igual.  */
.mnx-stepper button, .mnx-install-x, .mnx-hgroup .go,
.mnx-task .tick, .mnx-sheet-grip, .mnx-toast .act { position: relative; }
.mnx-stepper button::before, .mnx-scan-btn::before, .mnx-install-x::before,
.mnx-hgroup .go::before, .mnx-task .tick::before, .mnx-toast .act::before {
    content: ""; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    min-width: 44px; min-height: 44px; width: 100%; height: 100%;
}
/* El grip de la hoja es EL blanco de cierre con el pulgar: dibujo de 5px,
   área de 44 (antes se erraba y se cerraba la hoja de casualidad o nunca). */
.mnx-sheet-grip::before {
    content: ""; position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%); width: 120px; height: 44px;
}
/* Los controles de navegación del shell ahora son <a>, no <button>: sin esto
   heredan el subrayado del navegador. */
.mnx-icon-btn, .mnx-nav-item, .mnx-nav-fab { text-decoration: none; }

/* Chips, tabs y segmentos: 38-40px → 44. Es la altura mínima que Android
   recomienda y la diferencia entre filtrar de una o errarle dos veces. */
.mnx-chip { min-height: 44px; }
.mnx-tab { min-height: 44px; }
.mnx-seg { min-height: 44px; }
.mnx-denom { min-height: 44px; }

/* ── 4.2 · La barra de abajo aguanta el texto agrandado ────────────────────
   Con el tamaño de fuente del sistema al 130% la etiqueta ("Tareas") pasaba a
   dos renglones, la barra crecía y se comía el contenido — el padding inferior
   de .mnx-content está calculado sobre --mnx-nav-h, que es fijo. La barra ahora
   no puede crecer y la etiqueta se recorta antes que romper la navegación.

   ⚠️ ACÁ ESTABA `.mnx-nav { overflow: hidden }` Y LE CORTABA LA CABEZA AL FAB.
   El botón central lleva `margin-top: -22px` justamente para sobresalir de la
   barra — es su forma. Con el overflow puesto, esos 22 px se recortaban y el
   ➕ se veía como MEDIO CÍRCULO pegado al borde. El dueño lo reportó como "que
   no se corte", y tenía razón: no era un efecto de diseño, era un recorte.

   Y no hacía falta para lo que estaba escrito arriba: `.mnx-nav` ya tiene
   `height` FIJO (no min-height), así que la barra no puede crecer por más que
   la etiqueta ocupe dos renglones; y el recorte del texto lo hace la regla de
   abajo, por ítem, con su propio ellipsis. El overflow no aportaba nada que no
   estuviera ya cubierto — sólo mutilaba el botón más importante de la app. */
.mnx-nav-item { min-width: 0; padding: 0 2px; }
.mnx-nav-item > span:not(.mnx-nav-ico):not(.mnx-nav-badge) {
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* El header sí puede crecer (min-height, no height): sólo hay que evitar que el
   título empuje a las acciones fuera de la pantalla. */
.mnx-header-actions { flex: 0 0 auto; }
.mnx-header-title b { overflow-wrap: anywhere; }

/* ── 4.3 · El aviso de conexión no puede tapar la navegación ───────────────
   #blazor-error-ui lo posiciona connectivity.js sobre la barra inferior. Si
   además fuera clickeable a todo lo ancho, taparía "Vender". Se deja pasar el
   toque salvo en sus propios links. */
#blazor-error-ui { pointer-events: none; }
#blazor-error-ui a { pointer-events: auto; }

/* Respeta usuarios con motion reducido */
@media (prefers-reduced-motion: reduce) {
    /* .mnx-rp-layer y .mnx-ptr viven en <body>, fuera de .mnx-root: sin
       nombrarlos acá se les escapaba la regla. */
    .mnx-root *, .mnx-content, .mnx-rp-layer * { animation: none !important; transition: none !important; }
    .mnx-rp-layer { display: none !important; }
    .mnx-root .mnx-sk { background: var(--mnx-bg-2) !important; }
    .mnx-scan-reticle .laser { animation: none !important; top: 50% !important; }
    /* El reveal deja de ser animación y pasa a ser "siempre visible". */
    .mnx-ro > .mnx-reveal { opacity: 1 !important; transform: none !important; }
    .mnx-ptr i { transition: none !important; }
    /*  El ícono del toast se nombra APARTE aunque `.mnx-root *` debería
        alcanzarle: el host es `position: fixed` y hay markup que lo renderiza
        fuera del shell. Es exactamente lo que ya pasó con .mnx-rp-layer y
        .mnx-ptr (ver el comentario de arriba) — dos veces alcanza para dejar de
        confiar en que el ancestro es el que uno cree. Y como el pop arranca en
        `opacity: 0`, si la regla no llega el ícono no se ve: no es que se mueva
        de más, es que DESAPARECE.  */
    .mnx-toast .mnx-ic { animation: none !important; opacity: 1 !important; transform: none !important; }
}


/* ============================================================================
   mnx-17 :: LOS FLOTANTES DEJAN LEER LO QUE TAPAN
   ----------------------------------------------------------------------------
   En las capturas del dueño el globito del asistente tapaba el ícono de la fila
   "Ventas hoy", el texto de "Tareas / Del día" y el subtítulo de "Manolín". Es
   ARRASTRABLE y su posición queda guardada, así que no se le puede asignar un
   lugar fijo sin pisarle una decisión al usuario — pero sí puede dejar de
   estorbar mientras no se lo usa.

   En reposo baja a 55% y se achica un toque; al tocarlo o al pasarle el dedo
   vuelve entero. Sigue estando, sigue siendo visible, y el texto que tiene
   debajo se lee. Sólo opacity + transform: no cuesta un frame.

   ⚠️ Va scopeado con :has(.mnx-root) porque estos flotantes viven FUERA del
   shell de MNX (los monta el layout, no la pantalla). Y `transition` sobre
   opacity/transform únicamente — nada de layout. */
body:has(.mnx-root) .manolin-shell:not(.drag):not(.abierto),
body:has(.mnx-root) .vzb-fab {
    opacity: .55;
    transform: scale(.92);
    transition: opacity var(--mnx-dur-2, .2s) var(--mnx-ease-std),
                transform var(--mnx-dur-2, .2s) var(--mnx-ease-std);
}
body:has(.mnx-root) .manolin-shell:not(.drag):active,
body:has(.mnx-root) .manolin-shell:not(.drag):focus-within,
body:has(.mnx-root) .vzb-fab:active,
body:has(.mnx-root) .vzb-fab:focus-visible {
    opacity: 1;
    transform: scale(1);
}

/* Y el final de la lista nunca queda debajo de un flotante: el último ítem se
   puede scrollear hasta zona limpia. (padding, no margin: no colapsa.) */
body:has(.mnx-root) .mnx-content { padding-bottom: calc(var(--mnx-nav-h) + var(--mnx-safe-bottom) + 96px); }

/* Con el movimiento reducido, nada de escalas: sólo la opacidad. */
@media (prefers-reduced-motion: reduce) {
    body:has(.mnx-root) .manolin-shell:not(.drag):not(.abierto),
    body:has(.mnx-root) .vzb-fab { transform: none; transition: none; }
}


/* ============================================================================
   MNX v4 :: FORMA DE POS  ·  ETAPAS DE PROCESO  ·  BOTONES QUE SE EXPLICAN
   ----------------------------------------------------------------------------
   Pedido del dueño (2026-08-01): "que tenga FORMA DE POS en el celular… todo
   tiene que tener su BOTÓN, su CÓMO y su POR QUÉ, y bien claro".

   Tres piezas, y las tres nacen del mismo reclamo concreto — la captura de
   /m2/pos con el carrito vacío: un campo de búsqueda y un cartel que decía
   "Buscá un producto" sobre medio metro de nada.

     (a) ATAJOS  — el vacío de una pantalla de trabajo no es un cartel, es el
                   menú de lo que se hace todo el día. Cada atajo dice qué hace
                   ABAJO del título: el empleado nuevo no tiene que adivinar.
     (b) ETAPAS  — un proceso con etapas (borrador → enviada → en camino →
                   recibida → documentada) tiene que decir DÓNDE ESTÁS y QUÉ
                   SIGUE. Sin eso, cada pantalla es una lista de botones y el
                   que la abre elige por intuición.
     (c) POR QUÉ — un botón con consecuencia lleva la consecuencia escrita
                   ("se da de baja el pedido y no se puede reactivar"), no una
                   confirmación genérica que nadie lee.

   ⚠️ Nada de acá esconde contenido si el JS no corre: son layout y color, sin
   un solo estado inicial oculto. Misma regla de oro del bloque v3.
   ============================================================================ */

/* ── 5.1 · La barra de cobro SIEMPRE está ──────────────────────────────────
   Antes .mnx-cartbar sólo se renderizaba con el carrito lleno: el POS abría
   SIN total y SIN botón de cobrar, o sea sin las dos cosas que hacen que una
   pantalla se lea como una caja. Ahora está siempre, y con el carrito vacío se
   muestra apagada y con el motivo escrito — un botón deshabilitado que dice
   por qué enseña; un botón ausente deja al empleado buscándolo. */
.mnx-cartbar.vacia .tot b { color: var(--mnx-fg-3); }
.mnx-cartbar .tot .motivo { color: var(--mnx-warn); }

/* ── 5.2 · Atajos: el estado vacío que trabaja ─────────────────────────────
   Grilla de 2. El primero puede ocupar el ancho entero (.ancho) cuando es LA
   acción del turno — escanear, en un almacén, es el 80% de las cargas. */
.mnx-qa { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin: 4px 0 18px; }
.mnx-qa-btn {
    grid-column: span 1; display: flex; flex-direction: column; align-items: flex-start;
    gap: 2px; text-align: left; width: 100%; min-height: 92px; padding: 14px;
    border-radius: var(--mnx-r-md); background: var(--mnx-bg-2);
    border: 1px solid var(--mnx-line); color: var(--mnx-fg);
    transition: transform var(--mnx-fast), border-color var(--mnx-fast);
}
.mnx-qa-btn:active { transform: scale(.97); border-color: var(--mnx-line-2); }
.mnx-qa-btn .mnx-ic { font-size: var(--mnx-ico-l); color: var(--mnx-red-2); margin-bottom: 4px; }
.mnx-qa-btn b { font-size: var(--mnx-fs-4); font-weight: 700; }
/* El "qué hace": la línea que convierte un ícono en una instrucción. */
.mnx-qa-btn span { font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); line-height: 1.3; }
.mnx-qa-btn.ancho { grid-column: 1 / -1; min-height: 78px; }
.mnx-qa-btn.marca { background: var(--mnx-grad-surface); border-color: var(--mnx-line-2); }

/* ── 5.3 · Etapas del proceso (dónde estás / qué sigue) ────────────────────
   Riel horizontal de N pasos. El hilo que los une va DETRÁS del punto (z-index)
   y se pinta verde hasta donde el proceso ya pasó: el avance se lee sin texto.
   El paso actual es el único con el rojo de marca — dos "actuales" sería lo
   mismo que ninguno. */
.mnx-etapas { display: flex; align-items: flex-start; margin: 2px 0 12px; }
.mnx-etapa {
    flex: 1 1 0; min-width: 0; position: relative;
    display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.mnx-etapa::before {
    content: ""; position: absolute; top: 12px; right: 50%; width: 100%; height: 2px;
    background: var(--mnx-line-2);
}
.mnx-etapa:first-child::before { display: none; }
.mnx-etapa .dot {
    position: relative; z-index: 1;
    width: 26px; height: 26px; border-radius: var(--mnx-r-pill);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--mnx-fs-1); font-weight: 700;
    background: var(--mnx-bg-3); color: var(--mnx-fg-3); border: 2px solid var(--mnx-line-2);
}
.mnx-etapa .lb {
    font-size: var(--mnx-fs-1); color: var(--mnx-fg-3); text-align: center;
    line-height: 1.2; padding: 0 2px;
}
.mnx-etapa.hecha .dot { background: var(--mnx-ok); border-color: var(--mnx-ok); color: #04150a; }
.mnx-etapa.hecha::before, .mnx-etapa.aca::before { background: var(--mnx-ok); }
.mnx-etapa.aca .dot {
    background: var(--mnx-grad-brand); border-color: var(--mnx-red-2); color: #fff;
    box-shadow: var(--mnx-shadow-red);
}
.mnx-etapa.aca .lb { color: var(--mnx-fg); font-weight: 700; }
/* Cancelada: el riel entero se apaga. Un proceso muerto no puede seguir
   mostrando "lo que sigue" como si algo fuera a pasar. */
.mnx-etapas.muerta .mnx-etapa .dot { background: var(--mnx-bg-3); border-color: var(--mnx-line); color: var(--mnx-fg-3); box-shadow: none; }
.mnx-etapas.muerta .mnx-etapa::before { background: var(--mnx-line); }

/* La ficha que lo dice con palabras. El riel se lee de reojo; esto se lee
   cuando hay una duda, y contesta las dos preguntas de una. */
.mnx-donde {
    background: var(--mnx-bg-2); border: 1px solid var(--mnx-line);
    border-left: 3px solid var(--mnx-red-2); border-radius: var(--mnx-r-md);
    padding: 12px 14px; margin-bottom: var(--mnx-gap);
}
.mnx-donde.ok   { border-left-color: var(--mnx-ok); }
.mnx-donde.muda { border-left-color: var(--mnx-line-2); }
.mnx-donde b { display: block; font-size: var(--mnx-fs-3); font-weight: 700; }
.mnx-donde span { display: block; font-size: var(--mnx-fs-2); color: var(--mnx-fg-3); margin-top: 3px; line-height: 1.35; }

/* ── 5.4 · El botón que dice qué va a pasar ────────────────────────────────
   Dos renglones: la acción arriba, la consecuencia abajo. Es lo contrario de
   una confirmación genérica — el empleado lee lo que va a pasar ANTES de
   tocar, no en un "¿estás seguro?" que ya aprendió a saltear. */
.mnx-btn.expl {
    flex-direction: column; align-items: flex-start; justify-content: center;
    gap: 1px; min-height: 62px; padding: 11px 16px; text-align: left;
}
/*  ⚠️ `color: inherit` NO es de más: hay botones .expl adentro de un
    `.mnx-row-main`, y `.mnx-row-main span` (0,1,1) le pintaba el gris de
    subtítulo de fila a las dos líneas — texto gris sobre el rojo de marca. Con
    dos clases (0,2,0) esta regla gana y el botón manda su propio color.
    Es la misma trampa que dejó los 12 íconos del home en 11px y en gris. */
.mnx-btn.expl .ttl { display: inline-flex; align-items: center; gap: 8px; font-size: var(--mnx-fs-4); font-weight: 700; color: inherit; }
.mnx-btn.expl .por { display: block; font-size: var(--mnx-fs-1); font-weight: 500; color: inherit; opacity: .85; line-height: 1.3; }

/* El por qué de un botón APAGADO va abajo del botón, no adentro: un disabled
   con el motivo adentro se lee tan gris que no se lee. */
.mnx-porque {
    display: block; font-size: var(--mnx-fs-1); color: var(--mnx-fg-3);
    line-height: 1.35; margin: 6px 2px 0;
}


/* ============================================================================
   MNX v5 :: EL HOME DEL CELULAR — VITRINA DEL PORTAL + LOS DOS COPROTAGONISTAS
   ----------------------------------------------------------------------------
   Dos pedidos del dueño (2026-08-01), textuales:

     (1) "poner dos botones premium best in class animados coprotagonistas en el
          home de inventario y órdenes de compra (con todo el proceso)"
     (2) "quiero que vayan pasando fotos reales de lo del portal arriba en el
          Home como un banner animado best in class"

   El home tenía UN hero ("Nueva venta") y abajo doce baldosas de 96 px que NI
   SIQUIERA incluían inventario ni órdenes de compra: para llegar había que
   entrar a "Más" y buscarlos entre ~40 destinos. Los dos procesos que dejan
   plata parada —un conteo a medio hacer, una compra que llegó y nadie facturó—
   eran invisibles hasta que alguien se acordaba de ir a buscarlos. Ahora tienen
   su propio bloque, arriba, y DICEN EN QUÉ ESTADO ESTÁN.

   ── Las tres reglas de movimiento de este bloque ─────────────────────────────
   ⚠️ NINGUNA ANIMACIÓN INFINITA. El lote 1 del rediseño sacó cuatro (entre ellas
      el brillo que barría el hero animando `left`, o sea LAYOUT, en bucle) porque
      en un Android de gama media eso es reflow permanente: el hilo principal
      queda ocupado y el primer toque del empleado llega tarde. Todo lo de acá
      corre UNA vez y termina.
   ⚠️ SOLO `transform` Y `opacity`. La barra de progreso del conteo no anima
      `width`: anima `scaleX` sobre un `transform-origin: left`. Es la diferencia
      entre una barra que llena y una barra que hace relayout 60 veces por segundo.
   ⚠️ EL AUTO-AVANCE DE LA VITRINA NO ES CSS: es `scrollLeft` desde mnx.js sobre
      un carrusel con scroll-snap nativo. Sin JS el banner sigue siendo un carrusel
      que se pasa con el dedo — no un slide congelado con cuatro fotos escondidas
      detrás. Es la misma inversión de siempre: lo que se ve es el piso, el JS
      sólo agrega.
   ============================================================================ */

/* ── 6.1 · LA VITRINA :: las fotos del portal, arriba de todo ───────────────
   Full-bleed: los márgenes negativos compensan exactamente el --mnx-pad de
   .mnx-content, así que la foto llega a los dos bordes de la pantalla y el
   padding vuelve adentro del riel para que se asome la placa siguiente. */
.mnx-vitr { margin: 0 0 var(--mnx-gap); animation: mnxVitrEntra .5s var(--mnx-emph-dec) both; }

.mnx-vitr-track {
    position: relative;
    display: flex; gap: 10px;
    margin: 0 calc(var(--mnx-pad) * -1);
    padding: 0 var(--mnx-pad);
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;   /* el riel no arrastra la página ni el gesto de "atrás" */
    scrollbar-width: none;
}
.mnx-vitr-track::-webkit-scrollbar { display: none; }

/*  La placa es un <a href> REAL y no un botón de Blazor: con el circuito caído
    (wifi que parpadea, deploy, celu bloqueado un rato) un onclick de Blazor no hace
    absolutamente nada. Mismo criterio que toda la navegación del shell. */
.mnx-vitr-slide {
    position: relative; flex: 0 0 100%; scroll-snap-align: center;
    height: 172px; border-radius: var(--mnx-r-lg); overflow: hidden;
    display: block; text-decoration: none; color: #fff;
    background: var(--mnx-grad-surface);
    border: 1px solid var(--mnx-line-2);
    box-shadow: var(--mnx-shadow-2);
    transition: transform var(--mnx-fast) var(--mnx-ease);
}
.mnx-vitr-slide:active { transform: scale(.985); }
/*  Si la foto no llega (endpoint caído, briefing nuevo mientras la home estaba
    abierta) queda el degradé de marca de abajo: nunca el ícono de imagen rota. */
.mnx-vitr-foto {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; display: block; border: 0;
}
/*  El velo no es decoración: sin él, un titular blanco sobre una foto clara no
    se lee, y las fotos del portal vienen de veinte fuentes distintas. */
.mnx-vitr-velo {
    position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,.10) 0%, rgba(0,0,0,.20) 38%, rgba(0,0,0,.78) 100%);
}
.mnx-vitr-txt { position: absolute; left: 0; right: 0; bottom: 0; padding: 12px 14px 13px; }
.mnx-vitr-et {
    display: inline-block; font-size: var(--mnx-fs-1); font-weight: 700;
    letter-spacing: .9px; color: var(--mnx-tertiary);
}
.mnx-vitr-tit {
    display: block; font-size: var(--mnx-fs-5); font-weight: 700;
    line-height: 1.18; margin-top: 3px; color: #fff;
}
.mnx-vitr-baj {
    display: block; font-size: var(--mnx-fs-2); font-weight: 500;
    line-height: 1.3; margin-top: 4px; color: rgba(255,255,255,.82);
}

/*  Los puntitos. ⚠️ EL PRIMERO VIENE ENCENDIDO POR CSS y el JS es el que se hace
    cargo: agrega `.js` al contenedor (que resetea a todos) y mueve `.on`. Sin JS
    queda un carrusel manual con su primer punto marcado, que es la verdad. Es la
    misma inversión que `.mnx-ro > .mnx-reveal`: lo visible es el piso.
    El orden de estas tres reglas IMPORTA — las dos primeras tienen la misma
    especificidad (0,2,0) y gana la de abajo. */
.mnx-vitr-pips { display: flex; justify-content: center; align-items: center; gap: 6px; margin-top: 9px; }
.mnx-vitr-pip {
    width: 6px; height: 6px; border-radius: var(--mnx-r-pill);
    background: var(--mnx-line-2); opacity: .55;
    transition: transform var(--mnx-med) var(--mnx-spring), opacity var(--mnx-med) var(--mnx-ease), background-color var(--mnx-med) var(--mnx-ease);
}
.mnx-vitr-pip:first-child { background: var(--mnx-red-2); opacity: 1; transform: scaleX(2.6); }
.mnx-vitr-pips.js .mnx-vitr-pip { background: var(--mnx-line-2); opacity: .55; transform: none; }
.mnx-vitr-pips.js .mnx-vitr-pip.on { background: var(--mnx-red-2); opacity: 1; transform: scaleX(2.6); }

@keyframes mnxVitrEntra {
    from { opacity: 0; transform: translate3d(0, 10px, 0); }
    to   { opacity: 1; transform: none; }
}

/* ── 6.2 · LOS TRES COPROTAGONISTAS :: inventario, órdenes de compra y stock ─
   (el tercero entró el 2026-08-01 a pedido textual del dueño: el hub de stock,
   donde se modifica el producto, el precio, la foto, los combos y las
   recomendaciones. Ver Pages/m2/Menu.razor y Shared/Stock/StockHubPanel.razor.)
   Van APILADOS y a lo ancho, no en dos columnas. En 390 px, dos columnas dan
   175 px por tarjeta: entra el título y nada más — o sea vuelve a ser una
   baldosa con otro tamaño. "Con todo el proceso" necesita el ancho: el estado,
   la barra, y los pasos con su número. */
.mnx-copros { display: grid; grid-template-columns: 1fr; gap: var(--mnx-gap); margin-bottom: 20px; }
.mnx-copro {
    position: relative; overflow: hidden;
    display: block; width: 100%; text-align: left; text-decoration: none;
    padding: 15px 16px 16px; border-radius: var(--mnx-r-lg);
    background: var(--mnx-grad-surface);
    border: 1px solid var(--mnx-line-2);
    color: var(--mnx-fg);
    box-shadow: var(--mnx-shadow-2);
    transition: transform var(--mnx-fast) var(--mnx-ease), border-color var(--mnx-fast) var(--mnx-ease);
    animation: mnxCoproEntra .46s var(--mnx-emph-dec) both;
}
.mnx-copro:active { transform: scale(.978); border-color: var(--mnx-red-2); }
/*  URGENTE = hay algo parado esperando a una persona. Se marca con filete y halo
    ESTÁTICOS, no con un pulso: un cartel que late en el home de un turno de ocho
    horas se vuelve ruido a los diez minutos, y encima cuesta frames. */
.mnx-copro.urge { border-color: var(--mnx-red-2); box-shadow: var(--mnx-shadow-red); }

.mnx-copro-top { display: flex; align-items: center; gap: 12px; }
.mnx-copro .mnx-copro-ico {
    width: 46px; height: 46px; flex: 0 0 auto; border-radius: var(--mnx-r-sm);
    display: inline-flex; align-items: center; justify-content: center;
    /*  Un ícono de línea necesita ~25% más tamaño que un emoji para pesar igual:
        es contorno, no figura maciza. 28 px sobre una caja de 46 es la misma
        proporción que ya usan las baldosas de módulo. */
    font-size: var(--mnx-ico-l);
    background: var(--mnx-red-soft); color: var(--mnx-red-2);
}
.mnx-copro-hd { flex: 1 1 auto; min-width: 0; }
.mnx-copro-hd .mnx-copro-tit { display: block; font-size: var(--mnx-fs-5); font-weight: 700; line-height: 1.15; }
/*  El estado en una línea: qué está pasando AHORA con este proceso. Es la
    diferencia entre un link y un botón que informa.
    ⚠️ CLAMP A DOS LÍNEAS (y por eso es -webkit-box y no block): el estado del
    cronograma trae el título del próximo proceso adentro ("Lo próximo 14:30 ·
    Reponer la góndola de bebidas de la fiambrería") y sin tope estira la tarjeta
    cuatro renglones. Es una frase de una oreja, no un párrafo. */
.mnx-copro-hd .mnx-copro-est {
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
    font-size: var(--mnx-fs-2); font-weight: 500; color: var(--mnx-fg-3); margin-top: 3px; line-height: 1.3;
}
.mnx-copro-hd .mnx-copro-est.ok { color: var(--mnx-ok); }
.mnx-copro-hd .mnx-copro-est.avisa { color: var(--mnx-warn); }
.mnx-copro-hd .mnx-copro-est.urge { color: var(--mnx-red-2); font-weight: 700; }
.mnx-copro-ir { flex: 0 0 auto; font-size: var(--mnx-ico-m); color: var(--mnx-fg-3); }

/*  LA BARRA. `scaleX` sobre un origen a la izquierda: llena sin tocar el layout.
    El porcentaje entra por la variable --p (0..1) desde el servidor, así que la
    barra está bien dibujada incluso si el JS nunca corre. */
.mnx-copro-bar {
    /*  `display: block` NO es de más: en el markup es un <span> (vive adentro de un
        <a> junto con los demás bloques), y a un inline el navegador le ignora el
        height y el margin — la barra no se dibujaba en absoluto. */
    display: block;
    height: 7px; margin-top: 12px; border-radius: var(--mnx-r-pill);
    background: var(--mnx-bg-0); overflow: hidden;
}
.mnx-copro-bar i {
    display: block; height: 100%; width: 100%; border-radius: inherit;
    background: var(--mnx-grad-brand);
    transform-origin: left center; transform: scaleX(var(--p, 0));
    transition: transform var(--mnx-dur-4) var(--mnx-emph-dec);
}
.mnx-copro-bar.ok i { background: var(--mnx-grad-ok); }

/*  LOS PASOS DEL PROCESO. Son <span>, no botones: la tarjeta entera ya es un
    enlace y un interactivo adentro de un <a> es marcado inválido (y en Android
    se come el toque de la tarjeta). */
.mnx-copro-pasos { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.mnx-copro-paso {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 5px 10px; border-radius: var(--mnx-r-pill);
    background: var(--mnx-bg-0); border: 1px solid var(--mnx-line);
    font-size: var(--mnx-fs-1); font-weight: 500; color: var(--mnx-fg-3);
    line-height: 1.2; white-space: nowrap;
}
.mnx-copro-paso b { font-weight: 700; color: var(--mnx-fg); font-variant-numeric: tabular-nums; }
.mnx-copro-paso.vivo { border-color: var(--mnx-line-2); color: var(--mnx-fg-2); }
.mnx-copro-paso.vivo b { color: var(--mnx-red-2); }
.mnx-copro-paso.hecho b { color: var(--mnx-ok); }
/*  El "por qué" del proceso: la frase que explica qué pasa si esto queda como
    está. Un número sin consecuencia no mueve a nadie. */
.mnx-copro-por { display: block; font-size: var(--mnx-fs-1); font-weight: 500; color: var(--mnx-fg-3); line-height: 1.35; margin-top: 10px; }

/* ── 6.3 · EL SUBBOTÓN DE CREAR, PEGADO A CADA COPROTAGONISTA ──────────────
   "que el botón de nueva orden de compra y nuevo inventario se vea mejor
   cuando entras, y tenga un subbotón en el Home junto a los nuevos botones"
   (dueño, 2026-08-01).

   La tarjeta de arriba lleva a MIRAR el proceso (el conteo abierto, las
   compras en la calle); el subbotón ARRANCA uno nuevo sin pasar por la lista.
   Son dos intenciones distintas y por eso son dos targets distintos: meter el
   alta adentro de la tarjeta obliga a elegir cuál de las dos gana.

   ⚠️ ES HERMANO DE LA TARJETA, NUNCA HIJO. La tarjeta es un <a href> real: un
   <a> (o un <button>) adentro de otro <a> es marcado inválido y en Android se
   come el toque de los dos. Por eso el par vive en un <div> contenedor y las
   dos piezas se pegan sólo VISUALMENTE — la de arriba pierde el radio y el
   borde de abajo, la de abajo cierra la caja.

   ⚠️ SI EL USUARIO NO PUEDE CREAR, EL SUBBOTÓN NO SE RENDERIZA y la tarjeta
   tiene que volver a cerrar su caja sola. Eso lo decide la clase `.con-sub`
   que escribe el servidor, no un :has(): un solo lugar que decide, y se lee
   en el HTML sin abrir el CSS. */
.mnx-copro-par { display: block; }
.mnx-copro-par.con-sub .mnx-copro {
    border-bottom-left-radius: 0; border-bottom-right-radius: 0;
    border-bottom-color: transparent;
}
.mnx-copro-sub {
    display: flex; align-items: center; gap: 10px; width: 100%;
    min-height: var(--mnx-touch); padding: 10px 14px;
    text-decoration: none; text-align: left;
    border: 1px solid var(--mnx-line-2); border-top: 0;
    border-radius: 0 0 var(--mnx-r-lg) var(--mnx-r-lg);
    background: var(--mnx-bg-2); color: var(--mnx-fg);
    transition: transform var(--mnx-fast) var(--mnx-ease), background-color var(--mnx-fast) var(--mnx-ease);
    animation: mnxCoproEntra .46s var(--mnx-emph-dec) both;
}
.mnx-copro-sub:active { transform: scale(.985); background: var(--mnx-bg-3); }
/*  Los hijos van con el padre adelante (0,2,0): `.mnx-copro-sub` es a la vez un
    contenedor con <span> adentro, y hay reglas de tag pelado en este archivo
    que se los comen. Misma lección que los 12 íconos del home en 11px y gris. */
.mnx-copro-sub .mnx-copro-sub-ico {
    flex: 0 0 auto; width: 32px; height: 32px; border-radius: var(--mnx-r-s);
    display: inline-flex; align-items: center; justify-content: center;
    font-size: var(--mnx-ico-m);
    background: var(--mnx-red-soft); color: var(--mnx-red-2);
}
.mnx-copro-sub .mnx-copro-sub-txt { flex: 1 1 auto; min-width: 0; }
.mnx-copro-sub .mnx-copro-sub-tit { display: block; font-size: var(--mnx-fs-3); font-weight: 700; line-height: 1.2; }
.mnx-copro-sub .mnx-copro-sub-por { display: block; font-size: var(--mnx-fs-1); font-weight: 500; color: var(--mnx-fg-3); line-height: 1.3; margin-top: 2px; }
.mnx-copro-sub .mnx-copro-sub-ir { flex: 0 0 auto; font-size: var(--mnx-ico-s); color: var(--mnx-fg-3); }
/*  La entrada escalonada de la SEGUNDA pareja. Antes vivía en
    `.mnx-copro:nth-child(2)`, que dejó de matchear el día que la tarjeta pasó a
    tener un envoltorio: la tarjeta es hija única de su par. */
.mnx-copro-par:nth-child(2) .mnx-copro,
.mnx-copro-par:nth-child(2) .mnx-copro-sub { animation-delay: .08s; }
.mnx-copro-par:nth-child(3) .mnx-copro,
.mnx-copro-par:nth-child(3) .mnx-copro-sub { animation-delay: .16s; }
.mnx-copro-par:nth-child(4) .mnx-copro,
.mnx-copro-par:nth-child(4) .mnx-copro-sub { animation-delay: .24s; }

@keyframes mnxCoproEntra {
    from { opacity: 0; transform: translate3d(0, 14px, 0); }
    to   { opacity: 1; transform: none; }
}

/* ── 6.2b · EL TERCERO (cronograma) Y LA DENSIDAD ───────────────────────────
   "un botón animado completo para banco de procesos cronograma" (dueño,
   2026-08-01). Con el tercero, tres tarjetas expandidas suman ~520 px apiladas
   y en 390 px eso es media pantalla de scroll ANTES de "Tu día".

   ⚠️ LA CURA NO ES ACHICAR LAS TRES. Es que la tarjeta cuyo proceso NO tiene
   nada abierto se dibuje compacta: sin barra, sin pasos y sin la frase de
   consecuencia — porque cuando no hay nada pendiente no hay ninguna
   consecuencia que explicar. En un día normal (sin conteo abierto y sin compras
   sin documentar) el bloque baja a ~330 px, y crece solo cuando de verdad hay
   trabajo parado, que es exactamente cuando querés que ocupe.

   ⚠️ EL ACCESO NUNCA DESAPARECE. Lo que se achica es el DETALLE: el ícono, el
   título, el estado y la flecha están siempre. Un acceso que a veces está y a
   veces no es un acceso que nadie aprende (misma lección que el resumen diario
   del portal).

   Especificidad: `.mnx-copro.quieta .mnx-copro-ico` es (0,3,0) y le gana a
   `.mnx-copro .mnx-copro-ico` (0,2,0), que es la que fija los 46 px. */
.mnx-copro.quieta { padding: 12px 14px 13px; }
.mnx-copro.quieta .mnx-copro-ico { width: 38px; height: 38px; font-size: var(--mnx-ico-m); }

/*  Movimiento reducido: el bloque general (`.mnx-root *`) ya apaga animaciones y
    transiciones — con `animation: none` las dos entradas de arriba quedan en su
    estado final (opacidad 1), que es justo lo que tiene que pasar. Lo único que
    NO cubre es el scroll suave del riel, que no es ni animación ni transición
    (y el auto-avance de mnx.js ya no arranca con motion reducido). */
@media (prefers-reduced-motion: reduce) {
    .mnx-root .mnx-vitr-track { scroll-behavior: auto; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   MNX v6 :: LA FOTO REAL DEL PRODUCTO EN EL BUSCADOR DEL POS  (2026-08-02)
   ═══════════════════════════════════════════════════════════════════════════
   Pedido del dueño: "que cuando busquen aparezca la imagen real del producto,
   así le es fácil detectar qué producto es… que se vea bien con armonía y que
   entre bien". Y que desde ahí se pueda cambiar o borrar la foto si está mal.

   ⚠️ ARMONÍA = REUSAR LA RANURA QUE YA EXISTE, NO INVENTAR UNA MEDIDA.
   `.mnx-row-ico` ya era un cuadrado de 40 px con el radio y el fondo del
   sistema: la miniatura es ESE MISMO hueco, sólo que a 48 px. A 40 px un yogur
   y un flan son el mismo cuadradito de colores y la foto no sirve para nada
   —que es justo lo que el pedido quiere resolver—; más de 48 empieza a comerse
   el ancho del nombre en 390 px y a mostrar menos resultados por pantalla.

   ⚠️ `object-fit: contain`, NUNCA `cover`. Las fotos de góndola vienen
   verticales (botellas) y horizontales (paquetes): con `cover` se le corta el
   cuello a la botella o la marca al paquete, o sea que la foto pasa a mentir
   justo en el detalle que el cajero usa para reconocerla. Con `contain` entra
   entera y las bandas las tapa el fondo de la ranura.

   ⚠️ EL `alt` NO ES DECORATIVO: es lo que usa el handler global de errores de
   _Host.cshtml para redibujar la placa con el NOMBRE ADENTRO cuando una foto
   404ea. Sin `alt` el navegador deja el ícono de imagen rota, que no dice nada. */
.mnx-root .mnx-thumb {
    width: 48px; height: 48px; flex: 0 0 auto;
    border-radius: var(--mnx-r-sm);
    background: var(--mnx-bg-3);
    display: inline-flex; align-items: center; justify-content: center;
    overflow: hidden;
    font-size: var(--mnx-ico-s);
    color: var(--mnx-fg-3);
}
.mnx-root .mnx-thumb img {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
}

/*  LA FILA DEL BUSCADOR SE PARTE EN DOS BOTONES, Y NO ES CAPRICHO.
    ⚠️ Un <button> adentro de otro <button> es marcado inválido y EN ANDROID SE
    COME EL TOQUE DE LOS DOS (misma lección que `.mnx-copro-sub`, que por eso es
    hermano y no hijo de la tarjeta). Así que la fila pasa a ser un <div> con dos
    botones hermanos: el grande que AGREGA y el chico que abre la foto.

    ⚠️ El área grande sigue siendo "tocar = agregar", intacta. En una caja, que
    el gesto de siempre haga otra cosa cuesta ventas: por eso el de la foto es un
    blanco chico, aparte y explícito, y NO la miniatura (que es lo que el dedo
    pisa cuando va rápido a agregar). */
.mnx-root .mnx-rowsplit { display: flex; align-items: stretch; gap: 6px; }
.mnx-root .mnx-rowsplit > .mnx-row { flex: 1; min-width: 0; }
.mnx-root .mnx-rowfoto {
    flex: 0 0 auto; width: 44px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--mnx-bg-2);
    border: 1px solid var(--mnx-line);
    border-radius: var(--mnx-r-md);
    color: var(--mnx-fg-3);
    font-size: var(--mnx-ico-m);
    transition: color var(--mnx-fast), border-color var(--mnx-fast);
}
.mnx-root .mnx-rowfoto:active { color: var(--mnx-fg); border-color: var(--mnx-line-2); }
/*  Sin foto: el botón se marca, no se esconde. Un producto sin foto es
    justamente el que hay que arreglar — esconder el acceso lo deja invisible. */
.mnx-root .mnx-rowfoto.falta { color: var(--mnx-warn); border-color: var(--mnx-line-2); }

/*  FICHA-POS v2 :: la miniatura del CARRITO es un botón que abre la foto entera.
    ⚠️ Sólo en el carrito: ahí la fila no es "tocar = agregar" (tiene sus +/−), así
    que no hay ningún gesto que romper. En el buscador la miniatura NO se toca —
    es lo que el dedo pisa cuando el cajero va rápido a agregar. */
.mnx-root .mnx-thumb.zoom { position: relative; border: 0; padding: 0; cursor: zoom-in; }
.mnx-root .mnx-thumb.zoom:active { transform: scale(.94); }
/*  ⚠️ El badge va SIEMPRE. Sin él, "se puede agrandar" es una función que existe
    y que nadie descubre — en un celular ni siquiera hay hover que la revele. */
.mnx-root .mnx-thumb.zoom::after {
    content: "🔍"; position: absolute; right: -4px; bottom: -4px;
    width: 19px; height: 19px; border-radius: 999px;
    /*  El glifo va con el paso 1 de la escala (11px), no con un 9px suelto: la
        escala es ley y el guard 4 la sostiene. El círculo se agranda a 19 para
        que entre cómodo, en vez de bajar la letra por debajo del sistema. */
    font-size: var(--mnx-fs-1); line-height: 19px; text-align: center;
    background: var(--mnx-bg); border: 1px solid var(--mnx-line);
    pointer-events: none;
}
