/* =========================================================================
   RESUMEN DEL CARRITO — cáscara, barras de progreso, envío, cargo y campañas

   El resumen tiene UNA sola superficie: el panel lateral
   (components/cart-drawer.php), presente en categoría, ficha y Home. Ninguna
   vista lo incrusta y no existe página dedicada de carrito.

   Parte de la cadena de hojas de componentes. El ORDEN DE CASCADA lo
   garantiza la cadena de dependencias declarada en inc/assets.php: ninguna
   vista encola estas hojas directamente, todas dependen del handle
   'nvl-components', que cierra la cadena. Mover una regla de archivo puede
   cambiar qué gana; mover un archivo de sitio en la cadena, también.

   Mobile-first. Todo valor visual es token var(--nvl-*).
   ========================================================================= */

/* --- Resumen del carrito (cáscara; la Fase 8 lo hidrata) ------------------ */

/* --- Estado vacío ---------------------------------------------------------
   El resumen ya NO se oculta entero cuando el carrito está vacío. Cuando vivía
   suelto dentro de la página, esconderlo era lo correcto: el bloque
   simplemente no aparecía. Ahora su única superficie es el panel lateral
   (components/cart-drawer.php), que el usuario abre a propósito, así que el
   vacío es una vista de primera clase: si se ocultara el resumen, abrir el
   panel sin productos dejaría un panel en blanco con solo la ✕.

   El criterio es el inverso: con .nvl-is-empty se ocultan las partes que no
   aplican sin líneas —líneas, totales, barras, aviso del cargo, mensaje de
   validación y los dos botones— y queda el título más el mensaje. Sin la
   clase, se oculta el mensaje.

   El JS no interviene: renderSummaries() ya alterna .nvl-is-empty según
   totals.count (nvl-cart.js). El defecto anterior era solo de CSS — el
   párrafo del mensaje no tenía nada que lo ocultara, así que se veía junto a
   las líneas siempre que había productos. */

/* La cabecera entera, desde el 2026-10-22. Antes quedaba «el título más el
   mensaje»; con el vacío rehecho, «Tu carrito» arriba y «Tu carrito está
   vacío» en el centro decían dos veces lo mismo, y el título empujaba el
   bloque fuera del centro óptico del panel. Dentro de la cabecera solo viven
   el título y el recorrido de recompensas, y ninguno de los dos aplica sin
   líneas. El diálogo no se queda sin nombre: lo lleva en su aria-label. */
.nvl-cart-summary.nvl-is-empty .nvl-cart-summary__head,
.nvl-cart-summary.nvl-is-empty .nvl-reward,
.nvl-cart-summary.nvl-is-empty .nvl-cart-summary__items,
.nvl-cart-summary.nvl-is-empty .nvl-cart-summary__totals,
.nvl-cart-summary.nvl-is-empty .nvl-cart-summary__fee-note,
.nvl-cart-summary.nvl-is-empty .nvl-cart-summary__review,
/* El pie entero: sin líneas no hay total que fijar, y un «Total $0.00» con el
   botón bloqueado bajo el mensaje de vacío es peor que no tener barra. */
.nvl-cart-summary.nvl-is-empty .nvl-cart-summary__foot {
    display: none;
}

.nvl-cart-summary:not(.nvl-is-empty) .nvl-cart-summary__empty {
    display: none;
}

.nvl-cart-summary {
    background: var(--nvl-color-surface);
    border: var(--nvl-border-hairline) solid var(--nvl-color-border);
    border-radius: var(--nvl-radius-lg);
    box-shadow: var(--nvl-shadow-lg);
    padding: var(--nvl-space-lg);
    /* Un único ritmo entre bloques (auditoría ui-ux §5.1): el contenedor
       reparte el espacio por gap y ningún hijo declara margin-bottom. */
    display: flex;
    flex-direction: column;
    gap: var(--nvl-space-md);
}

.nvl-cart-summary__title {
    display: flex;
    align-items: center;
    gap: var(--nvl-space-2xs);
    font-size: var(--nvl-text-lg);
    /* 2026-09-22: baja de bold, en coherencia con los H2 de sección: si baja
       el título de la página y no el del panel, el panel pesa más. */
    font-weight: var(--nvl-weight-bold);
    line-height: var(--nvl-leading-heading);
}

.nvl-cart-summary__title .nvl-icon {
    color: var(--nvl-color-primary-dark);
}

/* EL BLOQUE DEL VACÍO (owner, 2026-10-22). Tres piezas en columna: qué pasa,
   por qué importa y cómo salir de ahí. */
.nvl-cart-summary__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--nvl-space-sm);
    text-align: center;
}

.nvl-cart-summary__empty-title {
    font-size: var(--nvl-text-lg);
    font-weight: var(--nvl-weight-bold);
    line-height: var(--nvl-leading-heading);
}

.nvl-cart-summary__empty-text {
    color: var(--nvl-color-text-secondary);
    font-size: var(--nvl-text-sm);
    line-height: var(--nvl-leading-relaxed);
    /* El panel mide 400px: sin tope, la frase llega a los dos filos y deja de
       leerse como un bloque centrado. */
    max-inline-size: var(--nvl-measure-narrow);
}

.nvl-cart-summary__items {
    list-style: none;
}

/* El `display` va en :not([hidden]) y NO en la regla base. Declararlo en la
   base gana sobre el `display: none` que el navegador aplica a [hidden] —una
   hoja de autor siempre vence a la del user-agent—, así que el JS ocultaba
   estas filas y no se ocultaban: la de descuento se leía como "−$0.00" sin
   ninguna campaña activa. Mismo patrón que ya usan el progreso de campaña y el
   banner más abajo. */
.nvl-cart-summary__row:not([hidden]) {
    display: flex;
    justify-content: space-between;
    gap: var(--nvl-space-sm);
    font-size: var(--nvl-text-sm);
    line-height: var(--nvl-leading-normal);
    margin-bottom: var(--nvl-space-2xs);
}

/* Total: UNA receta para el panel del carrito y para el bloque de cierre de la
   página de checkout (docs/checkout-pagina-especificacion.md §F.3). Lleva :not([hidden])
   A PROPÓSITO: con el mismo peso que .nvl-cart-summary__row:not([hidden]) de
   arriba, gana por orden. Sin él la regla genérica le ganaba tamaño e
   interlineado y el total se veía a 14px (defecto medido en el render,
   2026-10-06). El total cierra la cuenta: destaca por tamaño, no por peso ni
   por color de marca. */
.nvl-cart-summary__row--total:not([hidden]) {
    align-items: baseline;
    margin-top: var(--nvl-space-sm);
    margin-bottom: 0;
    padding-top: var(--nvl-space-sm);
    border-top: var(--nvl-border-hairline) solid var(--nvl-color-border);
    color: var(--nvl-color-text-primary);
}

/* El color va declarado aquí, no heredado: en el recibo del checkout las
   etiquetas del desglose son grises y la del total no. */
.nvl-cart-summary__row--total:not([hidden]) dt {
    min-width: 0;
    color: var(--nvl-color-text-primary);
    font-size: var(--nvl-text-base);
    font-weight: var(--nvl-weight-regular);
    line-height: var(--nvl-leading-snug);
}

/* La cifra nunca se parte; si un importe no cupiera, se parte la etiqueta. */
.nvl-cart-summary__row--total:not([hidden]) dd {
    flex-shrink: 0;
    margin: 0;
    font-size: var(--nvl-text-xl);
    font-weight: var(--nvl-weight-bold);
    line-height: var(--nvl-leading-snug);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* «MXN» del total: la única aparición de la divisa (owner, 2026-10-06). La
   escribe renderMoneyTotal() de nvl-cart.js. */
.nvl-cart-summary__currency {
    margin-inline-end: var(--nvl-space-3xs);
    font-size: var(--nvl-text-2xs);
    font-weight: var(--nvl-weight-regular);
    color: var(--nvl-color-text-secondary);
}

/* Fila de descuento por campaña escalonada (Etapa 2, Fase 2).
   Chip, no tinta: misma anatomía que .nvl-availability — un solo sistema de
   chip de estado. El color del texto sale de --nvl-color-on-success (opción
   de Configuración del sitio). */
.nvl-cart-summary__row--discount dd {
    display: inline-flex;
    align-items: center;
    padding: var(--nvl-space-3xs) var(--nvl-space-2xs);
    border-radius: var(--nvl-radius-xs);
    border: var(--nvl-border-hairline) solid transparent;
    background: var(--nvl-color-success);
    color: var(--nvl-color-on-success);
    font-weight: var(--nvl-weight-bold);
}

/* «Tu compra con descuento» (owner, 2026-10-24): el importe de los productos ya
   descontado, que es el que decide el envío gratis.

   TINTA, NO CHIP, y es la decisión de diseño de esta fila. El descuento de
   arriba y el «GRATIS» del envío ya son dos chips verdes; un tercer chip en
   medio convertiría el desglose en un semáforo. El azul de marca la distingue
   sin competir —5.93:1 sobre blanco— y es además el color con el que ya se
   pintan los importes de cada línea del carrito: el número que dice «esto
   cuestan tus productos» se lee como pariente de ellos. */
.nvl-cart-summary__row--net dd {
    color: var(--nvl-color-primary-dark);
    font-weight: var(--nvl-weight-bold);
}

.nvl-cart-summary__review {
    width: 100%;
}

/* --- EL RECORRIDO DE RECOMPENSAS (components/cart-summary.php) ------------
   Un único recorrido horizontal de objetivos: compra mínima, niveles de la
   campaña activa y envío gratis, en una sola secuencia ordenada por el importe
   que hace falta para conseguir cada uno.
   Especificación: docs/carrito-recompensas-especificacion.md.

   DOS REGLAS QUE NO SE NEGOCIAN:
   1. La línea CONECTA los hitos y no se rellena. Ni entera, ni por tramos, ni
      con una anchura derivada del subtotal. El avance lo cuentan el estado de
      los iconos y el desplazamiento de la ventana.
   2. Las posiciones son ranuras EQUIDISTANTES, nunca proporcionales al dinero.
      De ahí sale gratis la separación mínima entre discos: el paso es la mitad
      o un tercio de la línea, nunca menos de 100px a ningún ancho.

   EL MÓDULO SE DIBUJA SOBRE BANDA PROPIA (owner, 2026-10-14). Hasta entonces
   no pintaba fondo: vivía dentro de la banda gris del panel, compartiéndola
   con el título, y el owner pidió separarlo de los dos vecinos —el título
   arriba, la lista abajo— «con bordes superior e inferior y un fondo
   atractivo». Va a sangre: el relleno lo pone el panel, y el fondo llega a los
   dos filos porque el módulo es un bloque dentro de una cabecera sin relleno
   lateral.

   EL MATERIAL ES --nvl-color-surface-deep, #334155 (owner, 2026-10-15). Llegó
   aquí en dos pasos el mismo día: fue tinta --nvl-color-secondary, el owner la
   cambió al gris --nvl-color-text-secondary (#64748b) y, al pedir después la
   cifra en ámbar a su tamaño de siempre, la banda tuvo que bajar un paso —el
   ámbar sobre #64748b daba 2.22:1 y sobre #334155 da 4.82:1—. Es Slate 700, el
   escalón que le faltaba a una familia que el tema ya usaba entera.

   POR QUÉ NO SE ARREGLÓ CAMBIANDO EL AMARILLO: porque no se podía. Medidos los
   dieciocho colores de la paleta sobre #64748b a 14px, los ÚNICOS que pasaban
   AA eran el blanco (4.76:1) y #fffbeb (4.59:1), que es blanco cálido. Ninguno
   reconociblemente amarillo. El que estorbaba era el fondo, no el tono.

   LO QUE LA BANDA NUEVA SOSTIENE, medido y no estimado:

     --nvl-color-surface  #ffffff  10.35:1  frase, rótulos y filos de los discos
     --nvl-color-warning  #f59e0b   4.82:1  LA CIFRA QUE FALTA, y de relleno la pastilla
     --nvl-color-success  #10b981   4.08:1  relleno de la pastilla en estado de logro
     --nvl-color-border-hover #cbd5e1 6.97:1 la línea del eje

   Los dos únicos pares cortos son objetos que recorta un anillo BLANCO, que da
   10.35:1 contra la banda y es quien hace el trabajo: la insignia de tinta
   (1.72:1) y el anillo del disco pendiente (2.18:1 — el defecto abierto, que
   con este fondo deja de ser 1:1 pero sigue sin llegar al 3).

   NOTA PARA QUIEN VUELVA: con #334155, --nvl-color-text-inverse-muted recupera
   6.97:1 y volvería a ser legal como segundo nivel de tinta. No se ha
   reintroducido porque el owner acotó el encargo al color de la cifra; el
   módulo sigue con una sola tinta y la jerarquía sobre el PESO.

   EL VERDE SIGUE SIENDO RELLENO, PERO YA NO POR CONTRASTE. Sobre #64748b daba
   1.88:1 y no había alternativa; sobre esta banda da 4.08:1 y pasaría AA como
   letra. Se queda de relleno porque es la forma que el owner aprobó —la misma
   pastilla que cambia de color, no un segundo tratamiento— y porque la tinta
   sobre el verde da 7.04:1, más margen que el verde sobre la banda.

   Los objetos no textuales, que solo necesitan 3:1, siguen holgados contra la
   banda: la insignia de tinta 3.75:1, la línea del eje 3.21:1. Los dos discos
   de color llevan su anillo blanco (4.76:1), que es lo que los recorta: el
   verde por sí solo da 1.88:1 contra este gris y el ámbar 2.22:1.

   Y LOS FILETES SE QUEDAN EN AZUL DE MARCA, aunque contra este gris den 1.16:1
   y ya no se lean por dentro: su trabajo era separar del blanco de arriba y de
   abajo, y ahí siguen dando 4.10:1. El salto de material —blanco a gris,
   4.76:1— hace hoy la mayor parte de esa separación. */

.nvl-reward {
    display: flex;
    flex-direction: column;
    gap: var(--nvl-space-2xs);
    background: var(--nvl-color-surface-deep);
    border-block: var(--nvl-border-hairline) solid var(--nvl-color-primary);
    /* 4.76:1. La única tinta de texto que pasa AA sobre este gris. */
    color: var(--nvl-color-surface);
}

.nvl-reward[hidden] {
    display: none;
}

/* Calles a cada lado. NO las dimensiona el disco sino la ETIQUETA: el rótulo
   cuelga centrado bajo el hito de los extremos, así que la mitad de su ancho
   sale hacia afuera. Con las medias huellas de antes (24px) el primer rótulo
   empezaba a 2px del filo del panel mientras entre etiquetas sobraban 70 —
   margen nulo y calles enormes, que es lo que se vio en el hosting. Con 40px
   el margen queda en 18–24px a cualquier ancho. */
.nvl-reward__axis {
    --nvl-reward-node: 1.875rem;  /* 30px: el disco en reposo */
    /* El disco MAYOR, el del objetivo y el del estado final. Escrito una sola
       vez: lo usan la caja del nodo, el alto de la banda y los dos estados
       grandes. Antes era el mismo calc() repetido en cuatro sitios. */
    --nvl-reward-node-max: calc(var(--nvl-reward-node) + var(--nvl-space-2xs) + var(--nvl-border-mark));
    --nvl-reward-gutter: var(--nvl-space-2xl);
    padding-inline: var(--nvl-reward-gutter);
}

/* El alto de la banda es el del disco mayor. Debajo, reserva fija para la
   separación de la etiqueta (16px) más TRES líneas de 14.4px: «Envío gratis»
   y «a partir de $1,200» no caben en dos a 92px. Son 59.2px medidos, y la
   reserva es de 64: el resto es holgura para que una métrica distinta de la
   fuente no empuje el rótulo contra la frase. Fija, porque una reserva
   variable haría saltar el panel al cambiar de hito. */
/* MENOS HITOS, EJE MÁS CORTO Y CENTRADO (owner, 2026-10-14). Con los tres
   pasos el eje ocupa el ancho entero, que es lo que separa los discos. Sin
   campaña activa quedan dos —pedido mínimo y envío gratis— y ese mismo ancho
   dejaba una línea larga y vacía entre dos discos pegados a los filos. El eje
   se encoge y se centra; la cuenta la publica el JS en data-nvl-reward-count,
   que es el único que la conoce.

   15rem = 240px: descontando las dos calles de 40, deja 160px entre centros, y
   con rótulos de 92px eso son 68px de aire entre ellos. Cabe en el panel más
   estrecho (320px de pantalla) con 20px de margen a cada lado.

   Vale también para UN solo hito, que si no se quedaba solo en mitad de una
   línea de filo a filo. */
.nvl-reward[data-nvl-reward-count="1"] .nvl-reward__axis,
.nvl-reward[data-nvl-reward-count="2"] .nvl-reward__axis {
    /* inline-size: 100% OBLIGATORIO con el margen automático. El eje es un hijo
       flexible en columna, así que se estiraba por `align-self: stretch`; un
       margen automático en el eje transversal ANULA ese estirado y el elemento
       cae a su tamaño de contenido, que aquí es cero —los discos están
       posicionados— y dejaba un eje de 80px: solo sus dos calles. Medido en el
       navegador. */
    inline-size: 100%;
    max-inline-size: 15rem;
    margin-inline: auto;
}

.nvl-reward__band {
    position: relative;
    height: var(--nvl-reward-node-max);
    margin-block-end: 4rem;
}

.nvl-reward__line {
    position: absolute;
    inset-inline: 0;
    inset-block-start: 50%;
    transform: translateY(-50%);
    height: var(--nvl-border-mark);
    border-radius: var(--nvl-radius-pill);
    background: var(--nvl-color-border-hover);
}

.nvl-reward__nodes {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* --nvl-reward-pos es el ÚNICO valor en vivo del módulo: lo escribe el JS con
   style.setProperty. El transform centra el nodo sobre su ranura, y por eso el
   scale del logro va en el disco y nunca aquí.

   LA CAJA DEL NODO NO SE ENCOGE CON EL DISCO. Mide siempre lo que el disco
   mayor y lo centra dentro. Así el 100% del que cuelga la etiqueta vale lo
   mismo en los tres estados y los tres textos arrancan a la misma altura.
   Cuando la caja se ajustaba al disco (30, 34 o 40px) las etiquetas quedaban
   escalonadas y la del estado final empezaba 4px POR DENTRO de su anillo
   exterior, que es el empalme que se veía en el hosting. */
.nvl-reward__node {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: var(--nvl-reward-pos, 0%);
    display: grid;
    place-items: center;
    inline-size: var(--nvl-reward-node-max);
    block-size: var(--nvl-reward-node-max);
    transform: translate(-50%, -50%);
    transition: inset-inline-start var(--nvl-duration-base) var(--nvl-ease-standard);
}

.nvl-reward__node[hidden] {
    display: none;
}

/* El relleno blanco no es decorativo: oculta la línea que pasa por detrás. Un
   disco transparente la dejaría cruzar el icono. `relative` ancla la insignia
   de logro, que se apoya en su hombro superior.

   DEFECTO CONOCIDO Y PENDIENTE DE DECISIÓN (2026-10-15). El anillo interior usa
   --nvl-color-text-secondary, que desde hoy es EXACTAMENTE el fondo de la
   banda: 1:1. En el hito aún no conseguido —el único estado que usa este
   anillo— el filo del disco se funde con la banda y el disco se lee más
   pequeño de lo que es. El glifo de dentro no se ve afectado (4.76:1 sobre el
   relleno blanco). La corrección es de una línea —anillo y glifo a
   --nvl-color-secondary, 3.75:1 contra la banda y 16.38:1 dentro del disco—
   pero toca un elemento que el owner dejó fuera del encargo. No se aplica sin
   su visto bueno. */
.nvl-reward__disc {
    position: relative;
    display: grid;
    place-items: center;
    inline-size: var(--nvl-reward-node);
    block-size: var(--nvl-reward-node);
    border-radius: var(--nvl-radius-circle);
    background: var(--nvl-color-surface);
    color: var(--nvl-color-text-secondary);
    box-shadow: inset 0 0 0 var(--nvl-border-thick) var(--nvl-color-text-secondary);
    transition: inline-size var(--nvl-duration-base) var(--nvl-ease-standard),
                block-size var(--nvl-duration-base) var(--nvl-ease-standard),
                background-color var(--nvl-duration-base) linear,
                box-shadow var(--nvl-duration-base) linear;
}

/* El glifo ocupa la mitad del disco y crece con él. width/height y NO
   font-size: .nvl-icon se dimensiona con --nvl-icon-inline, no con el tipo
   (assets/css/nvl-icons.css), así que un font-size aquí no lo tocaría. */
.nvl-reward__disc .nvl-icon {
    width: 50%;
    height: 50%;
}

/* LA INSIGNIA DE LOGRO (owner, 2026-10-11). El glifo de dentro del disco es
   siempre el del beneficio —sustituirlo por la verificación hacía que todos
   los logros se vieran iguales y borraba QUÉ se había ganado—, así que la
   señal de «hecho» se muda aquí, al hombro del disco.

   DE TINTA, NI VERDE NI ÁMBAR. Una insignia de color por estado volvería a
   encargarle el mensaje al color, que es justo lo que aquí no funciona: ámbar
   y verde están a 1.18:1 entre sí. En tinta, insignia = conseguido es UN SOLO
   signo en los dos estados que la llevan, no añade un relleno nuevo a la
   paleta y contrasta con todo lo que puede quedar debajo (7.04:1 sobre el
   verde, 8.31:1 sobre el ámbar, 3.75:1 sobre la banda gris — por encima del
   3:1 que pide un objeto gráfico).

   El filo del color de la banda la despega del disco, y es además lo que la
   mantiene visible en forced-colors, donde el relleno se descarta.

   NO ES DECORACIÓN. Con el glifo del beneficio en los cuatro estados, la
   insignia es lo único que separa «siguiente objetivo» de «máximo alcanzado»
   en escala de grises: los dos son ámbar de 40px. No se oculta en móvil, no
   baja de 16px y no se cambia por una sombra. */
.nvl-reward__badge {
    position: absolute;
    inset-block-start: calc((var(--nvl-border-mark) + var(--nvl-space-3xs)) * -1);
    inset-inline-end: calc((var(--nvl-border-mark) + var(--nvl-space-3xs)) * -1);
    display: none;
    place-items: center;
    inline-size: var(--nvl-space-sm);
    block-size: var(--nvl-space-sm);
    /* FILO BLANCO, y no del color de la banda (owner, 2026-10-14). Al pasar la
       banda a tinta se le dio el color del fondo —que es lo que hacía sobre el
       gris— y el resultado fue una insignia de tinta con filo de tinta sobre
       tinta: se perdía justo donde tenía que verse. El filo tiene DOS trabajos,
       y solo el blanco los hace los dos: despegar la insignia del disco verde o
       ámbar al que se asoma (17.85:1 contra su propio relleno de tinta) y
       recortarla contra la banda, donde da 4.76:1. */
    border: var(--nvl-border-hairline) solid var(--nvl-color-surface);
    border-radius: var(--nvl-radius-circle);
    background: var(--nvl-color-secondary);
    color: var(--nvl-color-surface);   /* 17.85:1 */
}

.nvl-reward__node.nvl-is-won .nvl-reward__badge,
.nvl-reward__node.nvl-is-final .nvl-reward__badge {
    display: grid;
}

/* Tres clases para ganarle al 50% del glifo del disco sin depender del orden
   de las reglas en el archivo. */
.nvl-reward__disc .nvl-reward__badge .nvl-icon {
    width: 70%;
    height: 70%;
}

/* CONSEGUIDO. El filo blanco no es adorno: es el contorno que identifica el
   estado y la señal en escala de grises. Y DESDE EL GRIS DE LA BANDA SOSTIENE
   ADEMÁS EL CONTRASTE (2026-10-15): el verde por sí solo daba 7.04:1 contra la
   tinta y da 1.88:1 contra #64748b, por debajo del 3:1 que pide un objeto
   gráfico. El anillo blanco, a 4.76:1, es lo que recorta el disco; sin él la
   pastilla verde se diluiría en la banda. Distingue además «conseguido» de
   «máximo» —uno sencillo, el otro doble—. El glifo va en --nvl-color-secondary y NUNCA en
   --nvl-color-on-success, que es conmutable por el owner y puede caer a
   2.54:1: una opción de configuración no puede romper un contraste que
   sostiene un estado. */
.nvl-reward__node.nvl-is-won .nvl-reward__disc {
    inline-size: calc(var(--nvl-reward-node) + var(--nvl-space-3xs));
    block-size: calc(var(--nvl-reward-node) + var(--nvl-space-3xs));
    background: var(--nvl-color-success);
    color: var(--nvl-color-secondary);
    box-shadow: 0 0 0 var(--nvl-border-mark) var(--nvl-color-surface);
}

/* SIGUIENTE OBJETIVO. El más grande, con halo del color de la banda para
   separarlo de la línea y una sombra suave que lo levanta. Mismo criterio del
   ámbar: relleno, nunca tinta. */
.nvl-reward__node.nvl-is-active .nvl-reward__disc {
    inline-size: var(--nvl-reward-node-max);
    block-size: var(--nvl-reward-node-max);
    background: var(--nvl-color-warning);
    color: var(--nvl-color-secondary);
    /* Anillo blanco, halo DEL COLOR DE LA BANDA y aro exterior claro que lo
       separa de la línea. El halo no es un color del disco: es el hueco, y por
       eso lo arrastra cualquier cambio de fondo de la banda. */
    box-shadow:
        0 0 0 var(--nvl-border-mark) var(--nvl-color-surface),
        0 0 0 calc(var(--nvl-border-mark) + var(--nvl-space-3xs)) var(--nvl-color-surface-deep),
        0 0 0 calc(var(--nvl-border-mark) * 2 + var(--nvl-space-3xs)) var(--nvl-color-border-hover),
        var(--nvl-shadow-float);
}

/* RECORRIDO COMPLETO. ÁMBAR —no verde— del tamaño del objetivo y con el ANILLO
   DOBLE que no lleva ningún otro estado: es el techo, y no debe parecer que
   queda algo pendiente. Nunca coexiste con el objetivo, así que los dos pueden
   compartir el ámbar sin ambigüedad: el orden del recorrido pone todos los
   hitos conseguidos delante de los pendientes, de modo que si hay final no
   queda nada activo (owner, 2026-10-11).

   EL ANILLO EXTERIOR ÁMBAR da 2.22:1 contra el gris de la banda: vuelve a ser
   el refuerzo redundante que ya fue sobre la banda clara —llegó a dar 8.31:1
   mientras el fondo era tinta— y se conserva porque el doble aro es la forma,
   no el color, lo que marca el techo. Quien recorta el disco es el anillo
   INTERIOR, blanco, a 4.76:1 contra la banda, como en «conseguido».
   La regla del tema —ámbar nunca como TINTA sobre claro— sigue intacta: aquí es
   relleno y aro, nunca texto. */
.nvl-reward__node.nvl-is-final .nvl-reward__disc {
    inline-size: var(--nvl-reward-node-max);
    block-size: var(--nvl-reward-node-max);
    background: var(--nvl-color-warning);
    color: var(--nvl-color-secondary);
    box-shadow:
        0 0 0 var(--nvl-border-mark) var(--nvl-color-surface),
        0 0 0 calc(var(--nvl-border-mark) + var(--nvl-space-3xs)) var(--nvl-color-surface-deep),
        0 0 0 calc(var(--nvl-border-mark) * 2 + var(--nvl-space-3xs)) var(--nvl-color-warning);
}

/* La etiqueta cuelga del borde inferior de la CAJA del nodo, que mide siempre
   lo que el disco mayor. 16px, no 4: el anillo exterior del estado final
   sobresale 8px de esa caja —es box-shadow, no ocupa sitio—, así que con 4
   quedaba por debajo del texto. Ahora hay 8px de aire limpio en el peor
   estado y los tres rótulos arrancan a la misma altura.
   inline-size + break-word impiden la colisión por construcción, no por
   suerte en la longitud del copy. */
.nvl-reward__node-label {
    position: absolute;
    inset-block-start: calc(100% + var(--nvl-space-sm));
    inset-inline-start: 50%;
    transform: translateX(-50%);
    inline-size: 5.75rem;
    text-align: center;
    /* 4.76:1 sobre el gris de la banda. Fue --nvl-color-text-inverse-muted
       mientras la banda era tinta (12.02:1); sobre #64748b ese gris claro cae a
       3.21:1 y no llega a AA, así que el rótulo sube a blanco. */
    color: var(--nvl-color-surface);
    font-size: var(--nvl-text-2xs);
    line-height: var(--nvl-leading-snug);
    overflow-wrap: break-word;
}

/* Por debajo de 375px el panel es la pantalla entera y tres rótulos de 92px
   no caben con margen: a 320px dejarían 2px entre ellos. 80px es el ancho que
   mantiene 16px de calle y 24px hasta el filo en el peor caso. Por encima, el
   rótulo recupera su ancho y no parte «Pedido mínimo». */
@media (max-width: 374px) {
    .nvl-reward__node-label {
        inline-size: 5rem;
    }
}

/* El rótulo del hito que manda —el objetivo, o el máximo—. SU DISTINCIÓN ES
   HOY SOLO EL PESO (owner, 2026-10-15): sobre el gris de la banda el blanco es
   la única tinta que pasa AA, así que todos los rótulos son blancos y el color
   ya no puede jerarquizar. El `color` se conserva declarado aquí, redundante
   con el heredado, porque es el que vuelve a mandar si la banda cambia de
   material otra vez. */
.nvl-reward__node.nvl-is-active .nvl-reward__node-label,
.nvl-reward__node.nvl-is-final .nvl-reward__node-label {
    color: var(--nvl-color-surface);
    font-weight: var(--nvl-weight-bold);
}

.nvl-reward__node-label i {
    display: block;
    font-style: normal;
    font-weight: var(--nvl-weight-regular);
    font-variant-numeric: tabular-nums;
}

.nvl-reward__node.nvl-is-final .nvl-reward__node-label i {
    color: var(--nvl-color-surface);
}

/* La ÚNICA cifra accionable del módulo, y la única región viva del panel.

   CENTRADA (owner, 2026-10-15). Alineada a la izquierda colgaba del filo del
   panel mientras el eje, con dos hitos, se encoge a 240px y se centra: la
   frase y lo que describe apuntaban a sitios distintos. Centrada, el bloque
   entero —discos, rótulos y frase— comparte un solo eje vertical. */
.nvl-reward__phrase {
    text-align: center;
    /* Cuerpo de la frase sobre el gris de la banda: 4.76:1. */
    color: var(--nvl-color-surface);
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-regular);
    line-height: var(--nvl-leading-normal);
}

/* LA CIFRA QUE FALTA, EN ÁMBAR (owner, 2026-10-15). Pidió que lo que falta se
   vea de inmediato, resaltado SOLO con color y negrita —sin convertirlo en otra
   pastilla, que la ámbar se queda con el beneficio— y SIN tocar el tamaño: por
   eso aquí no hay `font-size` y la cifra hereda los 14px de la frase.

   ÁMBAR COMO TINTA, QUE ES LA EXCEPCIÓN A UNA REGLA DEL TEMA. La paleta declara
   --nvl-color-warning «NUNCA como tinta de texto sobre claro: va de RELLENO».
   Aquí es tinta y es legal, porque la banda NO es clara: sobre
   --nvl-color-surface-deep da 4.82:1, por encima de los 4.5 de AA. Esa banda
   existe por esto (ver su comentario, más arriba). Sobre el gris Slate 500 que
   tuvo un rato la misma cifra daba 2.22:1, y por eso la banda bajó un paso en
   vez de cambiar el tono: medidos los dieciocho colores de la paleta, ninguno
   reconociblemente amarillo pasaba sobre aquel fondo.

ESTE COLOR Y EL FONDO DE LA BANDA NO SE TOCAN POR SEPARADO. El ámbar es el
   más oscuro de los amarillos de la paleta —sobre esta misma banda,
   --nvl-color-cta-yellow da 7.29:1 y --nvl-color-promo-highlight 6.20:1—, así
   que es ÉL quien fija hasta dónde puede aclararse el fondo: con 4.82:1 queda
   a tres décimas del umbral. Cualquier aclarado de la banda saca la cifra de
   AA aunque los otros dos tonos sigan sobrando.

   Y el color no es el único canal: la frase dice «Agrega … más», así que la
   información no depende del tono (WCAG 1.4.1), y la negrita la distingue del
   cuerpo aunque el color no se perciba.

   :not() OBLIGATORIO — la etiqueta del beneficio también es un <b>, y sin
   excluirla esta regla le ganaba por especificidad (0,1,1 contra 0,1,0): se
   quedaba con el color y con el peso 700, encima de su propio fondo ámbar
   (owner, 2026-10-14). La etiqueta se pinta a sí misma, más abajo. */
.nvl-reward__phrase b:not(.nvl-reward__chip) {
    color: var(--nvl-color-warning);   /* 4.82:1 sobre --nvl-color-surface-deep */
    font-weight: var(--nvl-weight-bold);
}

/* EL BENEFICIO, dentro de la frase. Dos énfasis que no compiten: la cifra que
   hay que agregar va en BLANCO y el beneficio que se obtiene a cambio en esta
   etiqueta, de tinta sobre ámbar. Sólida y del mismo ámbar que el disco, para
   que la etiqueta y el objetivo al que apunta se lean como una pareja. (Sobre
   la banda gris el ámbar sólido da 2.22:1 y el --nvl-color-warning-bg suave
   daría 4.59:1: el motivo de preferir el sólido ya no es el contraste, es el
   emparejamiento con el disco.)
   TINTA, no blanco (owner, 2026-10-14), y sin peso (tres pesos, 2026-10-17):
   sobre un fondo que ya destaca por sí solo, la negrita recargaba, y en bold
   empataría con la cifra de al lado, que es lo que de verdad manda. El
   contraste lo da el ámbar; la tinta encima da 8.31:1.
   El [hidden] explícito hace falta: la clase gana al display:none de la UA. */
.nvl-reward__chip {
    display: inline-block;
    padding-inline: var(--nvl-space-3xs);
    border-radius: var(--nvl-radius-xs);
    background: var(--nvl-color-warning);
    color: var(--nvl-color-secondary);   /* 8.31:1 */
    font-weight: var(--nvl-weight-regular);
}

/* ÁMBAR LA ZANAHORIA, VERDE EL TROFEO (owner, 2026-10-15). La misma pastilla
   cambia de relleno cuando ya no queda ningún objetivo pendiente, y con eso el
   cliente sabe de un vistazo si tiene el beneficio o le falta para él.

   Hasta hoy esta regla del §4.2 de la especificación —«la etiqueta ámbar se
   queda con el beneficio QUE VIENE, no con el ya ganado»— estaba escrita pero no
   se cumplía: en los estados de logro la pastilla seguía ámbar sobre un
   beneficio ya conseguido. Ahora es literal.

   VERDE DE RELLENO Y NUNCA DE TINTA: sobre la banda el verde oficial da 1.88:1 y
   no llega ni al 3:1 de un objeto gráfico, así que no puede ser el color de la
   letra a ningún tamaño —a diferencia del amarillo de la cifra, que sí tiene
   salida por tamaño—. De relleno con tinta encima da 7.04:1.

   --nvl-color-secondary y NUNCA --nvl-color-on-success, que es una opción de
   Configuración del sitio y puede caer a 2.54:1: una casilla del administrador
   no puede romper el contraste de un estado.

   La clase la publica renderReward() a partir de si queda objetivo activo. */
.nvl-reward.nvl-is-win .nvl-reward__chip {
    background: var(--nvl-color-success);
    color: var(--nvl-color-secondary);   /* 7.04:1 */
}

.nvl-reward__chip[hidden] {
    display: none;
}

/* El momento del logro, y lo ÚNICO que se anima en todo el módulo. El scale va
   en el disco porque el nodo lleva el translate que lo centra: ponerlo aquí
   anularía el centrado y el disco saltaría media anchura. */
@keyframes nvl-reward-won {
    0%   { transform: scale(0.82); }
    55%  { transform: scale(1.12); }
    100% { transform: scale(1); }
}

.nvl-reward__node.nvl-is-just-won .nvl-reward__disc {
    animation: nvl-reward-won var(--nvl-duration-slow) var(--nvl-ease-out) both;
}

/* ALTO CONTRASTE. El modo descarta rellenos y sombras, y los anillos de este
   módulo son box-shadow: sin esto los cuatro discos se reducían a tamaño más
   glifo. Mientras la verificación sustituía al icono eso bastaba; con el
   icono del beneficio en los cuatro estados, «objetivo» y «máximo» quedarían
   idénticos. El filo real los devuelve a cuatro siluetas, y la insignia —que
   sobrevive por su propio borde— es la que separa ese par.

   Aditivo a propósito: solo existe dentro de esta consulta, así que el render
   normal no cambia ni un píxel. */
@media (forced-colors: active) {
    .nvl-reward__line {
        background: CanvasText;
    }

    .nvl-reward__disc {
        border: var(--nvl-border-mark) solid CanvasText;
    }
}

/* --- Fila de envío del resumen (§5.6) -------------------------------------- */

.nvl-cart-summary__row--shipping dt {
    display: inline-flex;
    align-items: center;
    gap: var(--nvl-space-2xs);
}

/* "GRATIS" pasa a chip, con el texto de --nvl-color-on-success. */
.nvl-cart-summary__free {
    display: inline-flex;
    align-items: center;
    padding: var(--nvl-space-3xs) var(--nvl-space-2xs);
    border-radius: var(--nvl-radius-xs);
    border: var(--nvl-border-hairline) solid transparent;
    background: var(--nvl-color-success);
    color: var(--nvl-color-on-success);
    font-weight: var(--nvl-weight-bold);
}

/* --- Nota del cargo por tarjeta (§5.6) ------------------------------------- */

.nvl-cart-summary__fee-note {
    display: flex;
    align-items: center;
    gap: var(--nvl-space-2xs);
    font-size: var(--nvl-text-2xs);
    font-weight: var(--nvl-weight-regular);
    line-height: var(--nvl-leading-normal);
    color: var(--nvl-color-text-secondary);
}

@media (min-width: 768px) {
    .nvl-cart-summary__fee-note {
        font-size: var(--nvl-text-xs);
    }
}

/* --- Panel lateral del carrito (components/cart-drawer.php) ---------------
   Cáscara off-canvas de alto completo. Envuelve el MISMO resumen que usan las
   demás vistas, así que aquí no se redefine ni una regla del resumen: solo el
   contenedor, el velo y el desplazamiento.

   Alto completo real: 100dvh y no 100vh, porque en móvil la barra de
   direcciones del navegador se retrae y con 100vh el pie del panel —donde
   está el CTA— queda cortado bajo el borde inferior de la pantalla.

   Ancho: min(--nvl-drawer-width, 100vw). Desde el 2026-10-10 el panel ocupa
   TODO el ancho en móvil (decisión del owner): la franja de página que se veía
   antes a los lados era espacio desaprovechado en la vista donde menos sobra.
   A partir de 420px de pantalla vuelve a medir --nvl-drawer-width y el velo
   se ve de nuevo. */

.nvl-cart-drawer {
    position: fixed;
    inset: 0;
    z-index: var(--nvl-z-drawer);
}

.nvl-cart-drawer[hidden] {
    display: none;
}

/* El velo entra y sale con opacidad. Sin él, el panel se deslizaba sobre una
   página que ya estaba oscurecida de golpe. */
.nvl-cart-drawer__veil {
    position: absolute;
    inset: 0;
    background: var(--nvl-color-overlay);
    opacity: 0;
    transition: opacity var(--nvl-duration-base) var(--nvl-ease-standard);
}

.nvl-cart-drawer.nvl-is-open .nvl-cart-drawer__veil {
    opacity: 1;
}

.nvl-cart-drawer__panel {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 0;
    width: min(var(--nvl-drawer-width), 100vw);
    height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--nvl-color-surface);
    box-shadow: var(--nvl-shadow-lg);
    /* EL PANEL YA NO SE DESPLAZA (owner, 2026-10-11): el scroll vive en
       .nvl-cart-summary__scroll, para que la cabecera y el pie queden fijos.
       Con el panel de scroller también se iba la ✕, que es absolute contra
       él. */
    overflow: hidden;

    /* ENTRA Y SALE DESLIZÁNDOSE (owner, 2026-10-14). Por defecto —móvil— sube
       desde abajo, que es lo que hace la referencia: su panel va de `top: 100vh`
       a `top: 0` en 250ms. Aquí se mueve con `transform`, que es la misma
       imagen sin tocar la geometría en cada fotograma.
       El estado de reposo es FUERA: así el panel ya está colocado cuando el JS
       le quita el `hidden`, y la clase .nvl-is-open es lo único que lo trae. */
    transform: translateY(100%);
    transition: transform var(--nvl-duration-base) var(--nvl-ease-standard);
}

.nvl-cart-drawer.nvl-is-open .nvl-cart-drawer__panel {
    transform: none;
}

/* MÓVIL: hoja a pantalla completa, sin radio y sin franja a los lados —igual
   que la referencia—. Era ya el comportamiento por debajo de 420px; el corte
   pasa a ser el del sistema, y con él la dirección del deslizamiento. */
@media (max-width: 767px) {
    .nvl-cart-drawer__panel {
        width: 100vw;
    }
}

/* ESCRITORIO: panel lateral. Entra desde la derecha y lleva las esquinas de
   ESE lado redondeadas, que es el acabado de la referencia (20px; aquí el
   token de 24 que ya usan las tarjetas). Las dos esquinas que tocan el filo de
   la ventana se quedan a escuadra: un radio ahí dejaría ver la página por un
   hueco de 24px. */
@media (min-width: 768px) {
    .nvl-cart-drawer__panel {
        border-start-start-radius: var(--nvl-radius-xl);
        border-end-start-radius: var(--nvl-radius-xl);
        transform: translateX(100%);
    }
}

/* Sin movimiento: el panel aparece y desaparece, sin recorrido. El JS lee la
   duración real del panel para saber cuándo esconderlo, así que aquí basta con
   anular la transición: el cierre sigue completándose. */
@media (prefers-reduced-motion: reduce) {
    .nvl-cart-drawer__panel,
    .nvl-cart-drawer__veil {
        transition: none;
    }
}

/* Cabecera del panel: solo el cierre. El título ya lo pone el propio resumen
   ("Resumen de tu carrito"), y repetirlo aquí sería decir dos veces lo mismo
   en la misma caja.

   COMPARTE FILA CON EL TÍTULO desde el 2026-10-10, en vez de ocupar una propia
   encima: se sale del flujo y se ancla a la esquina de la banda gris, y el
   título reserva su hueco con padding. Ahorra una fila entera en la vista
   donde el espacio vertical es más caro.

   Sigue siendo absolute, pero desde el 2026-10-11 ya NO se desplaza: el panel
   dejó de ser el contenedor de scroll, así que su origen está quieto. Antes se
   iba con el contenido y el cliente se quedaba sin cierre a media lista. */
.nvl-cart-drawer__head {
    position: absolute;
    inset-block-start: 0;
    inset-inline-end: 0;
    z-index: 1;
    display: flex;
    justify-content: flex-end;
    padding: var(--nvl-space-sm) var(--nvl-space-sm) 0;
}

/* El glifo medía 16px: .nvl-icon se dimensiona con --nvl-icon-inline, que vale
   1em, y el botón no declara tamaño de letra. El objetivo táctil de 44px ya
   era correcto — lo pequeño era el dibujo. 24px es el token que el tema usa
   para los iconos de encabezado, no una medida inventada. */
.nvl-cart-drawer__close {
    --nvl-icon-inline: var(--nvl-icon-lead);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--nvl-tap-min);
    min-height: var(--nvl-tap-min);
    border: 0;
    border-radius: var(--nvl-radius-md);
    background: transparent;
    color: var(--nvl-color-text-secondary);
    cursor: pointer;
}

.nvl-cart-drawer__close:focus-visible {
    outline: var(--nvl-focus-width) solid var(--nvl-focus-color);
    outline-offset: var(--nvl-focus-offset);
}

/* Dentro del panel el resumen ya no es una tarjeta flotante: es el panel
   entero. Se le quitan borde, sombra y radio para que no dibuje una caja
   dentro de otra, y crece hasta el fondo.

   SIN padding NI gap propios desde el 2026-10-11: cada zona pone los suyos.
   Es lo que permite que la banda gris llegue a los filos sin un solo margen
   negativo, y que el pie quede pegado al fondo sin franja. */
.nvl-cart-drawer .nvl-cart-summary {
    flex: 1;
    min-height: 0;   /* ver el bloque de las tres zonas: sin esto nada desplaza */
    margin: 0;
    padding: 0;
    gap: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    position: static;
    max-height: none;
    overflow: visible;
}

/* --- LAS TRES ZONAS DEL PANEL (owner, 2026-10-11) -------------------------
   Cabecera y pie FIJOS, un único cuerpo desplazable. El cliente recorre un
   carrito largo sin perder de vista ni su progreso hacia las recompensas ni
   el total con la acción de compra.

   `min-height: 0` EN LA CADENA ENTERA es la condición, no un detalle: un hijo
   flex se niega por omisión a encogerse por debajo de su contenido, así que
   sin él el cuerpo no desplaza nada y el pie se va fuera de la pantalla. Va en
   el panel (por su height), en el resumen y en el cuerpo. El banco lo fija con
   prueba porque es un fallo que no produce ningún error: solo un pie que no
   está.

   Fuera del panel estas tres zonas no existen como maquetación: el resumen
   suelto se sigue apilando con el padding y el gap de su propia tarjeta. */

.nvl-cart-drawer .nvl-cart-summary__head,
.nvl-cart-drawer .nvl-cart-summary__foot {
    flex: 0 0 auto;
}

.nvl-cart-drawer .nvl-cart-summary__scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* El rebote no arrastra la página de debajo. Estaba en el panel, que ya no
       desplaza; su sitio es el contenedor que sí lo hace. */
    overscroll-behavior: contain;
    display: flex;
    flex-direction: column;
    gap: var(--nvl-space-md);
    padding: var(--nvl-space-md) var(--nvl-space-lg) var(--nvl-space-lg);
}

/* EL VACÍO, CENTRADO EN EL HUECO. `margin-block: auto` sobre el bloque y NO
   `justify-content: center` sobre el cuerpo: el cuerpo es el contenedor que
   desplaza, y centrar su contenido con justify-content recorta el principio
   del scroll de forma irrecuperable el día que algo lo desborde —el aviso de
   pedido ajustado también vive aquí—. Con `auto` el centrado se reparte entre
   los dos márgenes mientras sobra sitio y se rinde solo cuando falta, que es
   exactamente lo que se quiere. */
.nvl-cart-drawer .nvl-cart-summary.nvl-is-empty .nvl-cart-summary__empty {
    margin-block: auto;
}

/* EL PIE: el total y la acción, nada más. El desglose se queda arriba con las
   líneas — bajarlo entero costaría ~100px permanentes de la altura que
   necesita la lista, y lo que se consulta de un vistazo es el total.

   GRIS Y SIN NINGÚN FILO (owner, 2026-10-11). El panel queda en tres
   materiales simétricos —gris con lo que puedes ganar, blanco con tu pedido,
   gris con lo que pagas— y la separación la hace el cambio de color, no una
   raya. Antes aquí se juntaban DOS: este filo y el que la fila del total trae
   de serie, con 16px de franja muerta entre ambos.

   El área segura evita que el botón quede bajo el indicador de inicio. */
.nvl-cart-drawer .nvl-cart-summary__foot {
    display: flex;
    flex-direction: column;
    gap: var(--nvl-space-2xs);
    padding: var(--nvl-space-sm) var(--nvl-space-lg);
    padding-block-end: calc(var(--nvl-space-sm) + env(safe-area-inset-bottom, 0px));
    background: var(--nvl-color-background);
}

/* La fila del total ya no separa nada: el desglose vive arriba con las líneas,
   así que su filo solo dibujaba una segunda raya. Se retira AQUÍ y no en la
   regla base porque la clase la comparten el recibo del checkout
   (components/checkout-summary.php) y la barra de compra
   (components/checkout-bar.php), donde el desglose sí está encima y el filo
   sigue haciendo su trabajo. */
.nvl-cart-drawer .nvl-cart-summary__foot .nvl-cart-summary__row--total:not([hidden]) {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
}

/* El «MXN» sobre el gris. --nvl-color-text-secondary da 4.76:1 sobre blanco
   pero 4.36:1 sobre la banda, por debajo de los 4.5 de AA: es el defecto de
   token que la §8 de la especificación ya documenta, y no se añade una
   instancia nueva al mover el texto a ese fondo. A 12px junto a una cifra de
   20px en negrita, la tinta no compite: la jerarquía la lleva el tamaño. */
.nvl-cart-drawer .nvl-cart-summary__foot .nvl-cart-summary__currency {
    color: var(--nvl-color-text-primary);
}

/* --- LA BANDA DEL PANEL (owner, 2026-10-10) -------------------------------
   El panel se parte en zonas de material: gris claro arriba con lo que todavía
   puedes ganar, blanco abajo con el pedido que ya tienes. Es la jerarquía de
   siempre, con el material que pidió el owner.

   A SANGRE, y desde el 2026-10-11 SIN UN SOLO MARGEN NEGATIVO. La banda es la
   cabecera, que es una caja con un fondo: como el resumen ya no pone padding
   ni gap dentro del panel, el fondo llega a los filos por construcción y el
   filo entre título y recorrido también. Antes esto eran cuatro parches —dos
   `margin-inline: -24px` con su `padding-inline` de vuelta, y un
   `margin-block-start: -20px` para anular el gap que partía la banda en dos—.

   Radio 0: una banda no es una tarjeta, y en cuanto lleva radio o margen
   lateral vuelve a leerse como una caja dentro de otra.

   Solo dentro del panel: el resumen suelto no lleva banda, y así ninguna otra
   superficie hereda un fondo que no le corresponde. */
/* Blanca, como el panel y como la referencia (owner, 2026-10-14). La banda gris
   que agrupaba título y recorrido desaparece: ahora el recorrido se separa por
   MATERIAL —va sobre tinta— y el título se queda sobre el blanco del panel. Dos
   zonas, no una con dos inquilinos. */
.nvl-cart-drawer .nvl-cart-summary__head {
    background: var(--nvl-color-surface);
}

/* CENTRADO (owner, 2026-10-14). El relleno lateral es SIMÉTRICO y del ancho del
   objetivo táctil del cierre: así el título se centra contra el panel entero y
   no contra el hueco que deja la ✕, que está fuera del flujo y solo ocupa un
   lado. Sin esa simetría el texto quedaría desplazado hacia la izquierda justo
   lo que mide el botón. */
.nvl-cart-drawer .nvl-cart-summary__title {
    justify-content: center;
    padding: var(--nvl-space-lg) calc(var(--nvl-space-lg) + var(--nvl-tap-min)) var(--nvl-space-md);
    text-align: center;
}

/* El recorrido ocupa la banda entera, de filo a filo: su relleno es el único
   margen que tiene, y los dos filetes marcan dónde empieza y dónde acaba. */
.nvl-cart-drawer .nvl-reward {
    padding: var(--nvl-space-md) var(--nvl-space-lg);
}

/* PANTALLAS BAJAS. Con la cabecera y el pie fijos, en un teléfono de 667px el
   cromo se lleva ~354px y a la lista le quedan ~313. Aquí se recorta el
   relleno vertical de las dos zonas fijas —nunca el contenido— y la lista gana
   ~24px, que es una línea más a la vista. */
@media (max-height: 700px) {
    .nvl-cart-drawer .nvl-cart-summary__title {
        padding-block: var(--nvl-space-md) var(--nvl-space-3xs);
    }

    .nvl-cart-drawer .nvl-reward {
        padding-block: var(--nvl-space-3xs) var(--nvl-space-xs);
    }

    .nvl-cart-drawer .nvl-cart-summary__foot {
        padding-block-start: var(--nvl-space-2xs);
        padding-block-end: calc(var(--nvl-space-2xs) + env(safe-area-inset-bottom, 0px));
    }
}

@media (hover: hover) and (pointer: fine) {
    .nvl-cart-drawer__close:hover {
        background: var(--nvl-color-background);
        color: var(--nvl-color-text-primary);
    }
}

/* La página no se desplaza por detrás del panel abierto. */
body.nvl-is-drawer-open {
    overflow: hidden;
}
