/* =========================================================================
   NVL BASE — tokens de diseño, reset mínimo y cáscara global
   Única hoja global del tema: tokens :root, base tipográfica y los estilos
   de la cáscara (header/footer de header.php/footer.php, contenedor y skip
   link). Los componentes y plantillas tienen sus propias hojas condicionales
   (ver inc/assets.php).
   Tokens portados de la lógica validada en producción y renombrados al
   estándar --nvl-*. La paleta es la del primer caso de uso; una marca nueva
   la ajusta editando únicamente este bloque.
   ========================================================================= */

/* Poppins, auto-alojada — ÚNICA familia del tema. Tres cortes estáticos:
   Poppins no tiene versión variable, así que cada peso es un archivo y cada
   archivo su propia declaración. Subconjunto latin (cubre todos los
   diacríticos del español, la raya y el símbolo de marca registrada).
   Cero peticiones a CDN o a Google Fonts en el render. Licencia SIL OFL 1.1
   en assets/fonts/LICENSE-Poppins.txt. Precarga de los tres: inc/assets.php
   (wp_head).

   TRES PESOS Y NO CINCO — decisión del owner, 2026-10-17: regular 400, bold
   700 y extrabold 800. Se retiraron medium 500, semibold 600 y black 900 para
   dejar una jerarquía más limpia y consistente. El reparto de los 92 usos que
   tenían vive en la escala editorial de la auditoría de diseño.

   Los tres declaran font-family: "Poppins" a secas. NUNCA "Poppins ExtraBold"
   ni variantes por nombre: eso rompe el eje de peso y obliga al navegador a
   falsear las negritas.

   PESOS QUE NO EXISTEN: 100, 200, 300, 500, 600 y 900. No hay archivo detrás,
   así que pedirlos hace que el navegador sustituya el más cercano en silencio
   y el diseño deja de ser determinista. Tampoco hay cursiva: un
   font-style: italic produciría una oblicua sintética. */
@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Regular-latin.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-Bold-latin.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Poppins";
    src: url("../fonts/Poppins-ExtraBold-latin.woff2") format("woff2");
    font-weight: 800;
    font-style: normal;
    font-display: swap;
}

:root {
    /* Paleta de colores */
    --nvl-color-primary: #0284c7; /* Sky 600 */
    --nvl-color-primary-dark: #0369a1; /* Sky 700 */
    --nvl-color-secondary: #0f172a; /* Slate 900 */
    --nvl-color-accent: #38bdf8; /* Sky 400 */
    --nvl-color-background: #f5f5f5; /* Neutral 100 */
    --nvl-color-surface: #ffffff; /* White */
    /* Tono alternado de sección: gris con tinte azulado que armoniza con la
       paleta primaria. Alterna con blanco para separar secciones. */
    --nvl-color-section-alt: #eef4f8;
    /* LA ÚNICA SUPERFICIE MEDIO-OSCURA DEL TEMA (owner, 2026-10-15). Slate 700,
       el paso que faltaba de la misma familia que ya usa el resto de la escala:
       900 es --nvl-color-secondary, 800 --nvl-color-text-primary, 500
       --nvl-color-text-secondary, 300 y 200 los bordes.
       Es RELLENO y nunca tinta. Existe para la banda del recorrido del carrito,
       que no quiere ser tinta —sería una segunda banda oscura pegada al panel—
       pero necesita sostener el ámbar como texto: a 4.82:1. Sobre el gris Slate
       500 que tuvo antes, ese mismo ámbar daba 2.22:1. */
    --nvl-color-surface-deep: #334155; /* Slate 700 */
    --nvl-color-text-primary: #1e293b; /* Slate 800 */
    --nvl-color-text-secondary: #64748b; /* Slate 500 — el valor oficial de la landing. Da 4.76:1 sobre --nvl-color-surface, pero solo 4.36:1 sobre --nvl-color-background:
                                            los usos que caen sobre banda se han subido a --nvl-color-text-primary uno a uno. */
    --nvl-color-text-inverse-muted: #cbd5e1; /* Slate 300 — texto secundario sobre banda oscura: línea de confianza y nota del hero, pie */
    --nvl-color-border: #e2e8f0; /* Slate 200 */
    --nvl-color-border-hover: #cbd5e1; /* Slate 300 */
    /* LÍMITE DE UN CONTROL DE FORMULARIO. Medido en la referencia que eligió el owner (skindion.mx, 2026-10-14):
       su campo es `1px solid #dedede` sobre blanco —el <select> lo dibuja en sí mismo y los <input> en su
       envoltura, que es por lo que al inspeccionar el campo se lee `border: 1px transparent solid`—. Es un gris
       NEUTRO, no el Slate 200 del resto del tema, y esa es la diferencia que se ve: 1.35:1 sobre blanco, un pelo
       más marcado que --nvl-color-border (1.22:1) y sin el tinte frío. Solo para campos; las cajas y filetes del
       tema siguen con --nvl-color-border. */
    --nvl-color-border-field: #dedede;
    --nvl-color-success: #10b981;  /* Verde oficial. NUNCA como tinta de texto sobre claro (2.54:1): va de RELLENO con --nvl-color-secondary encima (7.04:1), o como elemento no textual. */
    --nvl-color-warning: #f59e0b;  /* Ámbar oficial. NUNCA como tinta de texto sobre claro (2.15:1): va de RELLENO con --nvl-color-secondary encima (8.31:1). */
    --nvl-color-warning-bg: #fffbeb;
    --nvl-color-warning-border: #fcd34d;
    --nvl-color-danger: #ef4444;   /* Rojo oficial. Como tinta de texto no llega a AA (3.76:1): va de RELLENO con --nvl-color-secondary encima (4.74:1), o como borde/glifo, donde el umbral es 3:1. */
    --nvl-color-danger-tint: rgb(239 68 68 / 0.08); /* Fondo del bloque de errores del checkout. Derivado por alfa de --nvl-color-danger,
                                                       mismo patrón que --nvl-color-promo-tint: no introduce un tono nuevo, escala el oficial. */
    --nvl-color-success-tint: rgb(16 185 129 / 0.08); /* Fondo de la confirmación del pedido. Misma derivación por alfa del verde oficial:
                                                         no es un tono nuevo, y el verde sigue sin usarse como tinta de texto. */

    /* CTA "Agregar al carrito": amarillo suave tipo marketplace.
       Independiente de --nvl-color-warning (ámbar de avisos) para poder
       ajustar el botón sin afectar barras de progreso, modales ni reseñas. */
    --nvl-color-cta-yellow: #fed536;
    --nvl-color-cta-yellow-hover: #f7ca00;
    --nvl-color-cta-yellow-active: #eebc00;
    --nvl-color-cta-yellow-shadow: rgba(254, 213, 54, 0.45);

    /* Badge de descuento (azul vibrante tipo Indigo/Purple) */
    --nvl-color-promo-badge: #4f46e5;
    /* Ámbar del rayo y del resaltado de la barra superior. NO es un color nuevo:
       es el literal #fbbf24 (Amber 400) de la plantilla de referencia
       (landing-peces.css:71, .bbl-promo-highlight y el fa-bolt de la barra),
       registrado como token porque la referencia es la fuente de verdad. Solo
       para la barra superior. 10.7:1 sobre --nvl-color-secondary. */
    --nvl-color-promo-highlight: #fbbf24;
    --nvl-color-promo-ink: var(--nvl-color-promo-badge); /* MISMO indigo de la paleta, con nombre de rol (tinta, no relleno): precio con campaña y encabezado del progreso. 6.29:1 sobre blanco */
    /* Texto de las ETIQUETAS verdes (relleno --nvl-color-success): «Ahorras»,
       «Descuento», «DISPONIBLE», regalo, envío GRATIS y logros de la cápsula.
       Alias de rol, no un color nuevo: vale la tinta oscura (7.04:1) y pasa al
       blanco (2.54:1, el de .bbl-pay-savings de la referencia) con la clase
       body.nvl-success-text-light, que pone inc/setup.php según la opción
       «Texto de las etiquetas verdes» de Configuración del sitio. */
    --nvl-color-on-success: var(--nvl-color-secondary);
    --nvl-color-promo-tint: rgb(79 70 229 / 0.06); /* Fondo del bloque de progreso de campaña. Derivado por alfa del indigo de la paleta en vez de un hex nuevo, mismo patrón que --nvl-color-overlay */

    --nvl-color-overlay: rgb(15 23 42 / 0.65); /* velo del panel del carrito y de la galería */
    /* Gris NEUTRO casi negro, el de la cápsula «X / N» de la galería (owner,
       2026-10-25): es el tono exacto de la referencia, y no la tinta de marca
       —más azulada— que usa --nvl-color-overlay. Sobre una zona blanca de la
       foto, el peor caso, el blanco encima rinde 8.73:1 (el velo de marca al
       0.65 daba 5.57:1). */
    --nvl-color-overlay-neutral: rgb(23 24 27 / 0.78);
    /* El DESTELLO PROPIO DEL MATERIAL TINTA, y su versión bajo el cursor. No son
       "el hover del botón secundario" y nada más: ese fue su primer uso, pero el
       papel general es este —12% es el filo y el relieve que hacen que una
       superficie --nvl-color-secondary lea como objeto iluminado en vez de como
       agujero; 22% es ese mismo relieve cuando el objeto responde. Los usan el
       botón secundario sobre banda oscura, el filo y el disco de la cápsula
       flotante del carrito, y cualquier cosa futura sobre tinta. */
    --nvl-color-overlay-light: rgb(255 255 255 / 0.12);
    --nvl-color-overlay-strong: rgb(255 255 255 / 0.22);
    /* LA TERCERA SUPERPOSICIÓN DEL MATERIAL TINTA: la que QUITA luz. Las dos de
       arriba describen el destello; esta describe la cara que se aleja de él.
       Negro puro y NO --nvl-color-secondary, y aquí la excepción está razonada:
       la doctrina de «tinte secundario, nunca negro puro» vale para sombras
       PROYECTADAS sobre página clara, donde el negro enturbia un fondo azulado.
       Esta capa va DENTRO del objeto, sobre la propia tinta, y un tinte del
       mismo color sería un no-op. Sobre el relleno rinde poco (1.09:1); su
       trabajo real es apagar el FILO luminoso en la panza: #444a59 → #252931. */
    --nvl-color-overlay-shade: rgb(0 0 0 / 0.45);

    /* EL VELO DEL HÉROE INMERSIVO (owner, 2026-10-23). La variante nueva pone
       una FOTOGRAFÍA del owner debajo del titular, así que el contraste del
       texto no puede depender de lo que traiga esa foto: hoy sube un acuario
       oscuro y mañana una playa sobrexpuesta. El velo está calculado contra el
       PEOR CASO —blanco puro debajo—, igual que --nvl-gradient-category:

         blanco (H1, CTA)           12.74:1
         #cbd5e1 (subtítulo)         6.90:1
         #38bdf8 (antetítulo)        4.78:1

       POR QUÉ 0.82 Y NO EL 0.75 DE LA REFERENCIA: con 0.75 las dos primeras
       pasan de sobra (7.95 y 5.35) pero el ANTETÍTULO EN ACENTO se queda en
       3.71:1, por debajo de AA. El acento es un azul claro y es lo primero que
       sufre cuando la foto es clara. Siete centésimas más de velo cuestan muy
       poca foto y salvan la línea entera sin tocar un color de marca.
       SI SE BAJA ESTE NÚMERO, el antetítulo tiene que dejar de ser acento. */
    --nvl-color-overlay-hero: rgb(15 23 42 / 0.82);

    /* EL BRILLO DE LA BANDA DE TINTA. Misma receta de «realce sobre tinta» que
       ya usan la barra promocional (más abajo en esta hoja) y la tarjeta de
       transferencia (nvl-transfer.css) —color-mix de dos colores oficiales,
       ningún tono nuevo—, a otra amplitud: 70% y no el 85% de aquellas.
       Resuelve a #0b304e y da 1.32:1 contra la tinta plana; al 85% (#0d233c)
       son 1.12:1, que es lo correcto en una franja de 47px y desaparece
       repartido sobre los 100svh del héroe de la Home. Al 60% la banda deja de
       leer como tinta y se percibe «un degradado», que es justo lo que no debe
       pasar: esto es campo, nunca figura. Contrastes sobre el punto más claro:
       blanco 13.57:1, acento 6.34:1, text-inverse-muted 9.14:1, y la forma del
       CTA amarillo 9.56:1. */
    --nvl-color-tinta-glow: color-mix(in srgb, var(--nvl-color-secondary) 70%, var(--nvl-color-primary-dark));

    /* UNA SOLA FUENTE DE LUZ para el héroe de la Home. Radial y no lineal —la
       única diferencia con la receta de la barra—: el foco se coloca, y así el
       mismo brillo levanta el flanco del titular y el filo de la fotografía.
       El `at` es lo único que cambia por ancho.

       CUIDADO, Y ESTÁ MEDIDO (2026-10-23): redefinir --nvl-hero-glow-at en un
       descendiente NO mueve este gradiente. Una propiedad personalizada se
       calcula con sus var() ya sustituidos EN EL ELEMENTO DONDE SE DECLARA, y
       esta se declara en :root; lo que heredan los descendientes es el radial
       con el foco de :root congelado dentro. Por eso .nvl-home-hero no cambia
       de foco en ≥768 aunque su regla lo intente, y por eso la banda de la
       descripción de la ficha escribe su propio radial (ver nvl-product.css).
       Arreglarlo de raíz es declarar este token sobre `*` en vez de :root, y
       eso movería el héroe de la Home: no se hace de paso. */
    --nvl-gradient-hero: radial-gradient(118% 92% at var(--nvl-hero-glow-at),
                         var(--nvl-color-tinta-glow), var(--nvl-color-secondary) 64%);
    --nvl-hero-glow-at:      50% 82%;   /* móvil: la foto ocupa la parte alta a sangre, el brillo baja a sostener el texto */
    --nvl-hero-glow-at-wide: 44% 46%;   /* ≥768: el hueco entre las dos columnas, para que la luz explique a las dos */

    /* LA LUZ QUE RUEDA POR EL CANTO de un objeto de tinta. Lavado blanco que
       nace en el borde superior y MUERE A 20px, medida absoluta y no porcentual:
       así no depende del alto de la pieza (la cápsula mide 66, 70 u 86px según
       plantilla y ancho) y queda SIEMPRE por encima del primer píxel de tinta
       del contenido, que cae a ~24px del canto. Por eso ningún contraste de la
       cápsula se mueve. Sobre un radio de 24px esto es, literalmente, cómo la
       luz rueda por el canto de un cilindro.
       Se aplica como background-IMAGE, nunca en la forma abreviada: con la
       abreviada se perdería la transición de 120ms de background-color. */
    --nvl-gradient-relief: linear-gradient(to bottom, var(--nvl-color-overlay-light), transparent var(--nvl-space-md));

    /* EL VELO DE LA TARJETA DE CATEGORÍA. La fotografía ocupa la tarjeta entera
       y el título y su bajada van ENCIMA, así que el contraste del texto no
       puede depender de lo que traiga la foto: las sube el owner y puede subir
       una playa sobrexpuesta. Las tres paradas están calculadas con la fórmula
       WCAG contra el PEOR CASO —una fotografía blanca pura debajo— y el bloque
       de texto vive entre el 13% y el 62% de SU PROPIA caja, que es donde se
       pinta este gradiente (el cuerpo de la tarjeta, no la tarjeta entera: así
       la garantía no depende del alto de la foto, que va de 160 a 330px):
         0.88 (suelo del cuerpo)       → 10.6:1
         0.68 (techo del texto, 62%)   →  6.27:1
         0.60 (mínimo tolerable)       →  4.70:1
       Cualquier texto del bloque queda por encima de 6:1 pase lo que pase con
       la imagen. SI SE MUEVE UNA PARADA O EL padding-block-start DEL CUERPO,
       SE REHACE LA CUENTA: bajar del 0.60 en la banda que ocupa el texto lo
       saca de AA. El rgb() va literal y no por --nvl-color-secondary porque
       color-mix() con transparent interpola en premultiplicado y no da la
       misma alfa sobre el color. */
    --nvl-gradient-category: linear-gradient(to top,
                             rgb(15 23 42 / 0.88) 0%,
                             rgb(15 23 42 / 0.72) 60%,
                             rgb(15 23 42 / 0) 100%);

    /* Sombra */
    --nvl-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
    --nvl-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
    --nvl-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);
    --nvl-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.12), 0 8px 10px -6px rgb(0 0 0 / 0.08);
    --nvl-shadow-up: 0 -10px 15px -3px rgb(0 0 0 / 0.15); /* carrito fijo en móvil */
    --nvl-shadow-primary: 0 4px 6px -1px rgb(2 132 199 / 0.4); /* botón primario en reposo — deriva de --nvl-color-primary, el mismo relleno, y reproduce el rgba(2,132,199,.4) de la landing */
    --nvl-shadow-primary-h: 0 6px 10px -2px rgb(3 105 161 / 0.4); /* botón primario en hover — deriva de --nvl-color-primary-dark, que es su relleno en ese estado */
    --nvl-shadow-cta: 0 4px 6px -1px var(--nvl-color-cta-yellow-shadow);
    --nvl-shadow-cta-h: 0 6px 10px -2px var(--nvl-color-cta-yellow-shadow);
    /* El halo del CTA del héroe desde que pasó al acento (owner, 2026-10-20).
       Misma forma que las dos de arriba y que --nvl-shadow-primary, y el mismo
       criterio: el tinte sale del relleno al que acompaña, porque un halo
       amarillo bajo un botón azul se lee como suciedad. Deriva del hex de
       --nvl-color-accent, igual que --nvl-shadow-primary deriva del primario. */
    --nvl-shadow-accent: 0 4px 6px -1px rgb(56 189 248 / 0.4);
    --nvl-shadow-accent-h: 0 6px 10px -2px rgb(56 189 248 / 0.4);
    /* OBJETO OSCURO QUE FLOTA SOBRE PÁGINA CLARA (cápsula del carrito). Dos
       capas, cada una con su trabajo: la de 2px es la sombra de CONTACTO, la que
       define el filo y evita que la pieza parezca una pegatina; la de 16/28 es
       la elevación real. Ninguna de las de arriba sirve aquí — --nvl-shadow-lg
       está calibrada para separar una tarjeta BLANCA de un fondo gris (una capa,
       negro al 10%, sin contacto) y bajo un objeto de tinta no se ve, porque el
       objeto ya es más oscuro que su propia sombra; --nvl-shadow-xl tiene dos
       capas pero con offsets de modal centrado, que proyectan fuera de la
       pantalla en una pieza anclada abajo; --nvl-shadow-up es direccional para
       una barra pegada al suelo, que es justo lo que la cápsula dejó de ser.
       Tinte --nvl-color-secondary y NO negro puro: derivación por alfa del hex
       oficial, mismo patrón que --nvl-color-overlay. Un negro puro sobre estos
       fondos azulados enturbia. */
    --nvl-shadow-float:   0 2px 4px -1px rgb(15 23 42 / 0.24),
                          0 16px 28px -10px rgb(15 23 42 / 0.28);
    /* Hover: solo se abre la capa ambiental. La de contacto no cambia porque el
       contacto no cambia — la pieza se levanta 2px, no despega. */
    --nvl-shadow-float-h: 0 2px 4px -1px rgb(15 23 42 / 0.22),
                          0 22px 38px -12px rgb(15 23 42 / 0.32);

    --nvl-shadow-hero-media: drop-shadow(0 20px 30px rgb(0 0 0 / 0.5)); /* NUEVO (§8.5) — cierra el literal de .nvl-hero__image que ui-ux devolvió para tokenizar */

    /* EL FILO DE UN OBJETO CLARO SOBRE TINTA. Spread de un píxel y blur cero:
       sigue el border-radius exacto, no necesita envoltura ni overflow: hidden
       y NO crea capa de composición —que es lo que descarta un filter:
       drop-shadow sobre un elemento LCP—. Es el mismo 22% blanco que
       --nvl-color-overlay-strong ya describe arriba como «el filo y el relieve
       que hacen que una superficie lea como objeto iluminado en vez de como
       agujero», y la misma pieza que la tarjeta de transferencia resuelve con
       border. Compuesto sobre tinta da #444a59: borde, no línea.
       NO confundir con --nvl-shadow-hero-media: una sombra OSCURA sobre tinta
       es invisible (1.60:1 contra la banda) porque el objeto ya es más oscuro
       que su propia sombra. Sobre tinta se separa con luz, no con sombra. */
    --nvl-shadow-tinta-edge: 0 0 0 var(--nvl-border-hairline) var(--nvl-color-overlay-strong);

    /* EL RELIEVE DE UN OBJETO DE TINTA (cápsula del carrito). Evolución de
       --nvl-shadow-float, que NO se retira: lo siguen usando el disco activo del
       recorrido de recompensas y la tarjeta de transferencia.

       Sobre un objeto casi negro el volumen NO puede venir de la sombra: tiene
       que venir de la LUZ. Oscurecer el relleno un 45% da 1.09:1, invisible. Por
       eso las dos primeras capas son interiores: una apaga el filo en la panza
       —la pared que se aleja de la luz— y la otra es el filo luminoso cerrado,
       que sobre tinta da #444a59 (2.01:1): borde, no línea. Ese filo es el mismo
       22% que antes vivía en el `border`, metido DENTRO: fuera dejaba un hilo
       claro de 1px entre el cuerpo y su propia sombra de contacto, y era eso lo
       que aplanaba la pieza.

       CUATRO CAPAS, SIEMPRE EN ESTE ORDEN Y EN LOS TRES ESTADOS. box-shadow solo
       interpola si las listas tienen el mismo número de capas, el mismo orden y
       el mismo inset; reordenar una sola convierte la transición en un salto. */
    --nvl-shadow-relief:
        inset 0 calc(-1 * var(--nvl-space-3xs)) var(--nvl-space-2xs) calc(-1 * var(--nvl-space-3xs)) var(--nvl-color-overlay-shade),
        inset 0 0 0 var(--nvl-border-hairline) var(--nvl-color-overlay-strong),
        0 2px 4px -1px rgb(15 23 42 / 0.28),
        0 14px 34px -12px rgb(15 23 42 / 0.34);
    /* Hover: se abre SOLO la elevación. El contacto no cambia porque el contacto
       no cambia, y el filo tampoco, porque la luz es la misma: la pieza se
       levanta 2px, no despega. */
    --nvl-shadow-relief-h:
        inset 0 calc(-1 * var(--nvl-space-3xs)) var(--nvl-space-2xs) calc(-1 * var(--nvl-space-3xs)) var(--nvl-color-overlay-shade),
        inset 0 0 0 var(--nvl-border-hairline) var(--nvl-color-overlay-strong),
        0 2px 4px -1px rgb(15 23 42 / 0.28),
        0 20px 44px -14px rgb(15 23 42 / 0.38);
    /* Active: el objeto se HUNDE. Contacto cerrado, elevación colapsada y el
       filo baja de -strong a -light. Ese cambio de filo (#444a59 → #2c3344,
       1.42:1 en una línea de 1px) ES el acuse: oscurecer la tinta daría 1.09:1
       y no lo vería nadie. */
    --nvl-shadow-relief-a:
        inset 0 calc(-1 * var(--nvl-space-3xs)) var(--nvl-space-2xs) calc(-1 * var(--nvl-space-3xs)) var(--nvl-color-overlay-shade),
        inset 0 0 0 var(--nvl-border-hairline) var(--nvl-color-overlay-light),
        0 1px 2px -1px rgb(15 23 42 / 0.30),
        0 6px 14px -8px rgb(15 23 42 / 0.30);
    /* EL POZO DEL DISCO del icono. Cóncavo y no convexo: un disco en relieve
       sería un SEGUNDO objeto compitiendo con la cápsula; un hueco es
       profundidad DENTRO de ella, que es la prueba más directa de que la pieza
       tiene espesor. Pared alta en sombra, fondo del hueco captando luz. */
    --nvl-shadow-relief-chip:
        inset 0 var(--nvl-border-hairline) var(--nvl-space-3xs) 0 var(--nvl-color-overlay-shade),
        inset 0 calc(-1 * var(--nvl-border-hairline)) 0 0 var(--nvl-color-overlay-strong);

    /* Radio */
    --nvl-radius-xs: 0.375rem;   /*  6px — chips y píldoras pequeñas cuadradas */
    --nvl-radius-sm: 0.5rem;     /*  8px */
    --nvl-radius-md: 0.75rem;    /* 12px */
    --nvl-radius-lg: 1rem;       /* 16px */
    --nvl-radius-xl: 1.5rem;     /* 24px — superficies grandes: tarjeta de categoría,
                                    media del héroe y, desde el 2026-10-24, las dos
                                    tarjetas de la ficha (galería y caja de compra) */
    --nvl-radius-pill: 999px;    /* sustituye 99px y 50px */
    --nvl-radius-circle: 50%;

    /* Espaciado — escala de 4px */
    --nvl-space-3xs: 0.25rem;   /*  4px */
    --nvl-space-2xs: 0.5rem;    /*  8px */
    --nvl-space-xs:  0.75rem;   /* 12px */
    --nvl-space-sm:  1rem;      /* 16px */
    --nvl-space-md:  1.25rem;   /* 20px */
    --nvl-space-lg:  1.5rem;    /* 24px */
    --nvl-space-xl:  2rem;      /* 32px */
    --nvl-space-2xl: 2.5rem;    /* 40px */
    --nvl-space-3xl: 3.5rem;    /* 56px */
    --nvl-space-4xl: 4.5rem;    /* 72px */
    --nvl-space-5xl: 6rem;      /* 96px */

    /* Borde, capas, foco, objetivo táctil */
    --nvl-border-hairline: 1px;
    --nvl-border-thick:    1.5px;   /* botones */
    --nvl-border-accent:   4px;     /* filo de .nvl-attribute */
    --nvl-border-promo:    3px;     /* filo inferior de la barra superior, el de la referencia (#promo-bar) */
    /* Trazo de una MARCA DE ESTADO dibujada: el filo de los discos del recorrido
       de recompensas del carrito (conseguido, objetivo y recorrido completo). No
       es decoración: sobre la banda clara el verde da 2.33:1 y el ámbar 1.97:1,
       los dos por debajo del 3:1 que exige el contorno de un objeto gráfico, y
       este filo en --nvl-color-secondary (16.38:1) es lo que los hace legales.
       NO se usa para foco: el foco es --nvl-focus-width y siempre con outline. */
    --nvl-border-mark:     2px;

    --nvl-z-base:   1;
    --nvl-z-sticky: 100;   /* resumen del carrito fijo/sticky */
    --nvl-z-drawer: 150;   /* panel lateral del carrito: la capa más alta del tema (el modal de pedido, que estaba por encima, se retiró en C3) */
    --nvl-z-skip:   1000;

    --nvl-focus-width:  2px;
    --nvl-focus-offset: 2px;
    --nvl-focus-color:  var(--nvl-color-primary);  /* redefinible por contexto: bandas oscuras y CTA amarillo */

    --nvl-tap-min: 44px;

    /* Iconografía (sprite SVG, components/icon-sprite.php) — el icono nunca
       lleva font-size propio: hereda 1em salvo estas excepciones tokenizadas
       (auditoría ui-ux §2.3). */
    --nvl-icon-inline: 1em;      /* por defecto, dentro de texto o botón */
    --nvl-icon-fixed:  1.25em;   /* ancho fijo en listas alineadas (filas del resumen) */
    --nvl-icon-lead:   1.5rem;   /* encabezado de bloque de atributos y título del resumen */

    /* Barra de progreso del resumen del carrito */
    --nvl-bar-height: 0.5rem;

    /* Movimiento */
    --nvl-duration-fast:   120ms;
    --nvl-duration-base:   200ms;
    --nvl-duration-slow:   300ms;
    --nvl-duration-slower: 500ms;

    --nvl-ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
    --nvl-ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
    --nvl-ease-in:       cubic-bezier(0.4, 0, 1, 1);

    /* Permanencia de un aviso temporal. NO entra en la escala --nvl-duration-*:
       esa va de 120 a 500ms y describe TRANSICIONES; 4000ms no es una
       transición, es el tiempo que algo se queda legible en pantalla. Meterlo
       en esa escala corrompería la progresión y tentaría a usarlo como
       duración de movimiento. */
    --nvl-duration-notice: 4000ms;

    /* Layout */
    /* 1320 y no 1200 (owner, 2026-09-23): es el ancho del contenedor de la
       referencia. Global y no solo en la ficha, para que migas, cabecera,
       Home, categoría y legales sigan alineadas al píxel con el contenido. */
    --nvl-container-max:  1320px;
    --nvl-container-pad:  var(--nvl-space-md);   /* 20px — sube a --nvl-space-xl en ≥768px */
    --nvl-measure:        65ch;    /* ancho de lectura de párrafo largo */
    --nvl-measure-narrow: 46ch;    /* bajadas de sección y texto centrado */
    /* El eslogan del pie, bajo el logo. No es una medida de lectura: es el
       ancho que la columna de marca puede ocupar sin que la fila de tres
       bloques se desborde. Con --nvl-measure-narrow la marca medía 404px y no
       cabía ni a 1320 (owner, 2026-10-16). */
    --nvl-measure-footer-brand: 16rem;  /* 256px */
    /* ANCHO DEL PANEL. Sube de 420 a 480 (owner, 2026-10-14): a 420 el contenido
       se veía apretado, y la referencia que eligió mide 528 en una pantalla de
       1440. 480 es el paso intermedio: da 60px más de respiro sin que el panel
       se coma el 40% de un portátil de 1280. */
    --nvl-drawer-width:   480px;
    /* La cápsula y el aviso flotante NO siguen al panel: su ancho se midió
       aparte —estirarlos abriría un vacío entre el contador y el importe— y
       hasta hoy compartían token con él por casualidad histórica. */
    --nvl-cart-float-width: 420px;
    /* PÁGINA DE CHECKOUT (C1). Dos columnas: el formulario topa en 544 —el
       ancho que ya se midió y aceptó para una columna— y el recibo en 416.
       A 1024 la rejilla reparte 50/50 y el formulario queda en 424; desde 1440
       los dos llegan a su tope. La cabecera y el pie usan los MISMOS topes, que
       es lo que alinea la marca con el formulario. */
    --nvl-checkout-main:  34rem;   /* 544px */
    --nvl-checkout-aside: 26rem;   /* 416px */
    /* Lado de la miniatura de una línea de pedido (recibo del checkout y, desde
       C3, el panel del carrito). CUADRADA con --nvl-ratio-gallery: es la imagen
       de la ficha, que el campo ACF ya recortó 1:1 (R-OW1, owner 2026-10-07).
       64px se sirven con el derivado 'thumbnail' (150px) hasta 2.3x. */
    --nvl-line-thumb:     4rem;    /* 64px */
    /* Alto del campo con la etiqueta DENTRO: 52px = texto de 16 + la etiqueta
       flotante de 12 encima + los dos rellenos. Menos no deja sitio a la
       etiqueta sin pisar el valor. */
    --nvl-field-h:        3.25rem; /* 52px */
    /* Escala de la etiqueta cuando flota: 12/16. Es un NÚMERO y no un calc()
       entre los dos tokens porque calc() no divide una longitud por otra. Si
       cambia --nvl-text-2xs o --nvl-text-base, este valor se recalcula a mano. */
    --nvl-label-float-scale: 0.75;
    /* LA RESERVA QUE PIDE LA CÁPSULA DEL CARRITO (owner, 2026-10-16). No es
       aire: es lo que impide que la cápsula flotante tape el copyright y las
       placas de pago al llegar al final de la página. 140px = los 122 de
       ALCANCE REAL de la cápsula —98px de alto en escritorio, 106 en móvil,
       más 24 de separación al borde, medidos en el navegador— y 18 de holgura.
       Los 96px que esta reserva tuvo hasta hoy siempre fueron 26 cortos, y por
       eso la cápsula mordía el cierre. No se baja a ojo.
       Solo se paga cuando la cápsula se ve; ver .nvl-site-footer. */
    --nvl-footer-cart-reserve: 8.75rem;  /* 140px */
    /* RECORTE ÓPTICO DEL LOGO DEL PIE (owner, 2026-10-15). No es un ajuste de
       diseño: compensa el margen que trae DENTRO el archivo de la marca
       vigente. Medido en el navegador sobre el render real: 72 px de
       transparencia en la cabecera de un WebP de 240 (el 30 %), que a los
       56 px de alto del pie son 16.8 px de aire vacío encima del arte. Las dos
       cajas CSS —logo y títulos de columna— ya arrancaban en la misma `y`; lo
       que bajaba la firma era el archivo.
       CON UN LOGO RECORTADO, ESTE VALOR ES 0. Es lo que hay que poner el día
       que el adjunto se reexporte sin ese margen. */
    --nvl-footer-logo-trim: -1.05rem;  /* -16.8px */
    --nvl-aside-width:    360px;   /* CAMBIA desde 320px (auditoría ui-ux §8.4): a 320px las dos barras de progreso no caben en una sola línea */
    --nvl-header-h:       3.5rem;  /* alto de .nvl-site-header, verificado contra el header renderizado */
    /* Asomo de la parrilla de categorías sobre el pliegue. VALÍA 6rem = 96px y
       no asomaba nada: .nvl-section--spacious aplica --nvl-space-5xl (96px) de
       aire superior desde 1280, así que el asomo se lo comía entero el padding
       de la sección. El valor tiene que ser ese aire MÁS lo que se quiera ver;
       con --nvl-space-3xl de más asoman el título de la sección y unos 56px de
       la primera fotografía. Coste aceptado por el owner el 2026-10-21: el
       héroe se acorta 56px en pantallas de 1280px o más.
       SI CAMBIA EL PADDING DE .nvl-section--spacious, ESTE VALOR SE REHACE. */
    --nvl-peek:           calc(var(--nvl-space-5xl) + var(--nvl-space-3xl));  /* 152px */

    /* Tope de ancho de la TARJETA DE CATEGORÍA. Es el escenario que el
       `auto-fit` de nvl-home.css dejaba sin acotar: con una sola categoría el
       grid computaba una pista de 1256px y la fotografía se pintaba a 1256×942
       desde un archivo de 768 (ampliación de 1.64×). Con el tope, la banda de
       tamaño de la tarjeta pasa de 290–1256px a 397–440px.
       Va de la mano del `sizes` de components/category-card.php, que declara
       27.5rem y NO admite var(): si este valor cambia, aquel se rehace a mano. */
    --nvl-category-card-max: 27.5rem;  /* 440px */
    /* Alto de la barra promocional superior (components/top-bar.php). 0px sin
       barra o cerrada (con 0 sin unidad el calc() del asomo del hero sería
       inválido); con barra, 47px constantes (regla body.nvl-has-promo-bar). */
    --nvl-promo-bar-h:    0px;
    /* === INTERRUPTOR: ¿LA BARRA SE QUEDA FIJA? (owner, 2026-11-02) ==========
       Por defecto NO. Se va con el scroll y devuelve sus 47px a la ventana, que
       es lo que permite que la galería de la ficha se quede fija en portátiles
       (nvl-product.css: los umbrales bajaron de 820/850 a 770/800).

       Para volver a fijarla se cambian LAS TRES a la vez, y el resto del tema
       se entera solo —el tope de la galería y scroll-padding salen de aquí—:

           --nvl-promo-bar-position: sticky;
           --nvl-promo-bar-inset: var(--wp-admin--admin-bar--height, 0px);
           --nvl-promo-bar-stuck-h: calc(var(--nvl-tap-min) + var(--nvl-border-promo));

       `relative` y NO `static`: la costura de 1px (.nvl-promo-bar::before) es
       absoluta sobre la barra y necesita que esté posicionada; con `static` se
       iría a buscar otro ancestro.

       El desplazamiento también sale del interruptor porque solo tiene sentido
       pegada: en `relative`, ese mismo valor EMPUJARÍA la barra 32px hacia
       abajo con sesión iniciada y taparía el contenido.

       Y el alto va con su calc() literal, no con var(--nvl-promo-bar-h): esa
       vale 0px aquí y se sobrescribe en <body>, así que anidarla en :root la
       congelaría en 0 —el defecto de los tokens con var() anidado—. Si la barra
       estuviera apagada en Configuración y aun así se fijara aquí, sobraría ese
       hueco: las tres líneas van juntas por algo. */
    --nvl-promo-bar-position: relative;
    --nvl-promo-bar-inset:    auto;
    --nvl-promo-bar-stuck-h:  0px;
    /* Alto reservado para el formulario de Stripe mientras carga: sin esta
       reserva el diálogo daba un salto al montarse el marco. Si el formulario
       real mide otra cosa, se ajusta AQUÍ, no en la regla. */
    --nvl-payment-min-h:  13.5rem;
    --nvl-gallery-max:    40rem;   /* 640px — tope de ancho de la media de la ficha SOLO en la banda de una columna (<1024px); en escritorio lo topa la rejilla, y aplicarlo allí dejaba la foto 77px más estrecha de lo que la columna permite */

    /* ÚNICO ratio de media de PRODUCTO: tarjeta, galería de ficha y
       miniaturas. Es el mismo que fija el campo ACF 'galeria', y ahí está
       todo el valor de la decisión: el editor recorta una vez a 16:9 y ese
       encuadre es exactamente lo que se publica en las tres superficies. Si
       este valor y el del campo dejan de coincidir, vuelve el recorte
       automático que este sistema elimina. */
    --nvl-ratio-product: 16 / 9;

    /* Proporción del escenario de la FICHA, y solo de ella (owner, 2026-09-23).
       Es la de la referencia. Token aparte y no un cambio de --nvl-ratio-product
       porque ese pertenece al sistema de TARJETAS —catálogo, relacionados,
       redes— y ahí el 16:9 se queda: son dos fuentes de imagen independientes,
       con su propio campo ACF y su propio tamaño registrado.

       CUADRADA (owner, 2026-09-23). La referencia usa 1/1.08 porque sus fotos de
       producto son cuadradas y le sobra un pelo de alto; nosotros lo dejamos en
       1:1 exacto para que el relleno quede IDÉNTICO por los cuatro lados y la
       foto se lea centrada de verdad.

       Esta proporción y la del campo ACF de la galería (recorte 1:1) tienen que
       coincidir SIEMPRE: si dejan de hacerlo vuelve el hueco en blanco que
       motivó este cambio —una foto apaisada en una caja cuadrada dejaba 140px
       muertos arriba y abajo, y ningún relleno arregla eso—.

       La foto va con `contain`, así que la proporción define la CAJA y no
       recorta: lo que encuadró el editor es lo que se ve. */
    --nvl-ratio-gallery: 1 / 1;

    /* EL ESCENARIO DE LA FICHA ES MÁS ALTO QUE ANCHO, y la foto sigue cuadrada
       dentro (owner, 2026-10-25). Son DOS tokens porque la referencia usa dos
       mecanismos que compiten y gana el mayor: una proporción que da la forma y
       un suelo que la rescata cuando se queda corta.

       La proporción es la suya, 1/1.08. El suelo es el suyo de móvil,
       `min(112vw, 540px)`: por debajo de 768 el escenario baja hasta 320px de
       ancho y un 8% de eso no alcanza para la cápsula del contador, que mide
       33.6 de alto se mire donde se mire. Su suelo de ESCRITORIO no se copió:
       está calibrado a un escenario más ancho que el nuestro y a 1280px nos
       dejaba 41.9px de blanco muerto bajo la foto (medido).

       El alto sobrante es lo que le da sitio al contador debajo de la
       fotografía. Si alguno de los dos cambia, hay que volver a medir ese
       hueco en las ocho anchuras: está anotado en .nvl-gallery__main. */
    --nvl-ratio-gallery-stage: 1 / 1.08;
    --nvl-gallery-stage-floor: min(112vw, 34rem);

    /* Atenuación de lo no disponible. Único valor de opacidad con nombre del
       sistema: no hay escala de opacidad de la que derivarlo. */
    --nvl-opacity-muted: 0.7;

    --nvl-ratio-card:   4 / 3;   /* media de tarjeta de categoría (otro campo, otro componente) */
    --nvl-ratio-hero:   4 / 3;   /* media del hero */

    /* Encuadre de respaldo de la media de producto. En la ruta normal es
       inerte: si el archivo servido ya viene recortado a 4:3, la caja y el
       archivo coinciden y object-position no hace nada. Gobierna solo los
       adjuntos sin regenerar, los originales de proporción libre y las
       miniaturas, que piden un tamaño cuadrado del core.
       45% —un 5% por encima del centro— porque en la fotografía de acuario el
       sustrato ocupa el tercio inferior y el pez nada por encima de la mitad:
       recortar simétricamente se come aleta dorsal y no se come grava. */
    --nvl-media-focus:  50% 45%;

    /* Tipografía — Poppins (auto-alojada, @font-face arriba) + pila del
       sistema como respaldo mientras carga o si el archivo no está
       disponible. El respaldo NO intenta imitar la geométrica: ningún sistema
       trae una, y las candidatas (Futura, Century Gothic) solo existen en
       macOS y son mucho más anchas, así que el salto al cargar sería peor. Se
       elige por métrica vertical: SF Pro y Roboto quedan a un 4% de la altura
       de x de Poppins, medida en 0.548 em. */
    --nvl-font-sans:    "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                        "Helvetica Neue", Arial, sans-serif;
    --nvl-font-display: var(--nvl-font-sans);  /* punto de anclaje: cambiar a serif es decisión de marca vía orquestador */

    --nvl-text-2xs:  0.75rem;    /* 12px — suelo absoluto: divisa, badges, notas legales */
    --nvl-text-xs:   0.8125rem;  /* 13px */
    --nvl-text-sm:   0.875rem;   /* 14px */
    --nvl-text-base: 1rem;       /* 16px */
    --nvl-text-md:   1.125rem;   /* 18px */
    --nvl-text-lg:   1.25rem;    /* 20px */
    --nvl-text-xl:   1.5rem;     /* 24px */
    --nvl-text-2xl:  1.875rem;   /* 30px */
    --nvl-text-3xl:  2.25rem;    /* 36px */

    /* FUERA DE LA RAMPA, A PROPÓSITO (owner, 2026-10-22). Es el titular del
       héroe en el tramo pequeño y nada más. La escala no tiene escalón entre
       2xl (30px) y 3xl (36px), y los 32 no admiten un nombre natural ahí, así
       que el token se nombra por su TRABAJO y no por su posición en la rampa:
       es el mismo criterio que --nvl-category-card-max o --nvl-aside-width.
       NO se tocó el valor de --nvl-text-2xl para que diera 32: ese escalón lo
       usan otras superficies y se habrían movido en silencio.

       El owner eligió los 32 exactos sabiendo lo que cuestan, porque se midió
       antes: con el titular real, por debajo de 390px de ancho el titular cae
       en TRES líneas con una palabra suelta en las dos últimas. A 30px entraba
       en dos en todos los anchos. Es su decisión, tomada con el número
       delante, y no se revierte sin él. */
    --nvl-text-hero-mobile: 2rem;  /* 32px */
    --nvl-text-4xl:  3rem;       /* 48px */
    --nvl-text-5xl:  3.5rem;     /* 56px */

    --nvl-weight-regular:   400;
    --nvl-weight-bold:      700;
    /* 800 y no 900: el corte que hay detrás es Poppins ExtraBold. El nombre
       del token va con el archivo — un token que promete un peso del que no
       hay archivo hace que el navegador sustituya sin avisar, y el diseño deja
       de ser determinista. Por eso tampoco se conserva "black" como alias. */
    --nvl-weight-extrabold: 800;

    --nvl-leading-tight:   1.1;
    --nvl-leading-snug:    1.2;
    --nvl-leading-heading: 1.3;
    --nvl-leading-normal:  1.5;
    --nvl-leading-relaxed: 1.6;
}

/* Opción del owner: texto BLANCO en las etiquetas verdes (ver el token). */
body.nvl-success-text-light {
    --nvl-color-on-success: var(--nvl-color-surface);
}

/* El contenedor sube su padding lateral a partir de md (§2.8) */
@media (min-width: 768px) {
    :root {
        --nvl-container-pad: var(--nvl-space-xl);
    }
}

/* Reset mínimo */
*,
*::before,
*::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    /* Elimina el cuadro azul de selección (tap highlight) en móviles */
    -webkit-tap-highlight-color: transparent;
}

html {
    -webkit-text-size-adjust: 100%;
}

/* Scroll suave para anclas internas (CTA secundario del hero → #categorias),
   solo cuando el usuario no pidió movimiento reducido. */
@media (prefers-reduced-motion: no-preference) {
    html {
        scroll-behavior: smooth;
    }
}

body {
    background-color: var(--nvl-color-background);
    color: var(--nvl-color-text-primary);
    font-family: var(--nvl-font-sans);
    line-height: var(--nvl-leading-relaxed);
}

img,
svg,
video {
    display: block;
    max-width: 100%;
    height: auto;
}

a {
    color: var(--nvl-color-primary);
}

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
}

/* Foco visible consistente en todo el tema. --nvl-focus-color se redefine
   por contexto (nunca se sustituye por box-shadow, nunca se anima): ver
   nvl-components.css (CTA amarillo) y las bandas oscuras más abajo. Única
   excepción: los campos del checkout (owner, 2026-10-07; ver
   nvl-order.css), un solo trazo de borde + sombra con el outline transparente
   que se conserva para forced-colors. */
:focus-visible {
    outline: var(--nvl-focus-width) solid var(--nvl-focus-color);
    outline-offset: var(--nvl-focus-offset);
}

/* Bandas oscuras del tema. El anillo primario SÍ cumple sobre
   --nvl-color-secondary (4.36:1, por encima del 3:1 que pide un indicador no
   textual), pero el acento rinde bastante más (8.33:1): por eso estos
   contenedores redefinen el foco a --nvl-color-accent para todo interactivo
   descendiente. Es una mejora de legibilidad, no una corrección de contraste. */
.nvl-home-hero,
.nvl-cta-band,
.nvl-site-footer {
    --nvl-focus-color: var(--nvl-color-accent);
}

/* =========================================================================
   Cáscara global — contenedor, skip link, header y footer mínimos
   ========================================================================= */

.nvl-container {
    max-width: var(--nvl-container-max);
    margin: 0 auto;
    padding: 0 var(--nvl-container-pad);
}

/* Enlace "saltar al contenido": invisible hasta recibir foco de teclado */
.nvl-skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: var(--nvl-z-skip);
    background: var(--nvl-color-surface);
    color: var(--nvl-color-primary-dark);
    font-weight: var(--nvl-weight-bold);
    padding: var(--nvl-space-xs) var(--nvl-space-md);
    border-radius: 0 0 var(--nvl-radius-md) 0;
    box-shadow: var(--nvl-shadow-md);
}

.nvl-skip-link:focus {
    left: 0;
}

/* --- Barra promocional superior (components/top-bar.php) -----------------
   Componente promocional de UNA línea que se queda arriba durante toda la
   navegación: mensaje con el beneficio · contador en celdas. Rediseño de
   ui-ux (owner, 2026-09-19) con la jerarquía que pidió marketing-cro:
   PRIMARIO el beneficio (ámbar 900), SECUNDARIO el contador (celdas propias),
   TERCIARIO el nombre de la promoción y el alcance (gris claro 500). Un papel
   por color: ámbar = atención/oferta, verde = beneficio de la barra de envío
   gratis, acento = cifras, gris claro = secundario.

   SIN BOTÓN DE CERRAR y SIN ICONO EN CAMPAÑA (owner, 2026-09-19): la barra es
   permanente —se apaga desde el escritorio— y el owner escribe emojis dentro
   del texto, así que el disco solo queda en la barra de envío gratis. Los
   ~76px liberados en móvil son el aire del contador (§ contador compacto).

   YA NO SE QUEDA FIJA (owner, 2026-11-02): se va con el scroll y devuelve sus
   47px de ventana. Sigue en el flujo exactamente igual —ocupa su sitio arriba
   del documento, el hero de la Home la sigue restando—, lo único que cambia es
   que no se queda. El interruptor de :root la vuelve a pegar; cuando lo hace,
   sigue siendo STICKY y no fixed, que es lo que evita compensar el contenido y
   el salto al cargar.

   ALTO CONSTANTE, 47px en todos los anchos y estados (44px + 3px de filo),
   decidido por CSS y nunca medido con JS: lo restan el hero de la Home, la
   columna sticky de la ficha y scroll-padding. */
body.nvl-has-promo-bar {
    --nvl-promo-bar-h: calc(var(--nvl-tap-min) + var(--nvl-border-promo));
}

/* Anclas y foco nunca quedan tapados por la barra (WCAG 2.4.11). Incluye la
   barra de administración de WordPress, cuyo propio scroll-padding esto
   sustituye.

   DESDE EL INTERRUPTOR (owner, 2026-11-02): lo que tapa un ancla es la barra
   PEGADA, así que la suma usa --nvl-promo-bar-stuck-h, que vale 0 mientras la
   barra se vaya con el scroll. Antes reservaba 47px en cada salto aunque allí
   no hubiera nada que esquivar. */
html:has(body.nvl-has-promo-bar) {
    scroll-padding-top: calc(var(--wp-admin--admin-bar--height, 0px) + var(--nvl-promo-bar-stuck-h) + var(--nvl-space-sm));
}

.nvl-promo-bar {
    /* Las dos salen del interruptor de :root. Pegada, el desplazamiento la
       coloca bajo la barra de administración de WordPress. */
    position: var(--nvl-promo-bar-position);
    inset-block-start: var(--nvl-promo-bar-inset);
    /* Sobre el contenido y BAJO el panel del carrito (150). */
    z-index: var(--nvl-z-sticky);
    block-size: var(--nvl-promo-bar-h);
    border-block-end: var(--nvl-border-promo) solid var(--nvl-color-accent);
    /* Tinta con un realce central apenas perceptible hacia el azul de la
       paleta (color-mix de dos oficiales, #0d233c en el centro). */
    background-color: var(--nvl-color-secondary);
    background-image: linear-gradient(90deg, var(--nvl-color-secondary), color-mix(in srgb, var(--nvl-color-secondary) 85%, var(--nvl-color-primary-dark)), var(--nvl-color-secondary));
    color: var(--nvl-color-surface);
    /* Sobre tinta el anillo de foco es el acento (8.33:1). */
    --nvl-focus-color: var(--nvl-color-accent);
    /* Mezclas de la paleta para el disco y las celdas del estado final:
       ámbar o verde al 16% sobre la tinta. No son colores nuevos. */
    --nvl-promo-amber-soft: color-mix(in srgb, var(--nvl-color-promo-highlight) 16%, var(--nvl-color-secondary));
    --nvl-promo-green-soft: color-mix(in srgb, var(--nvl-color-success) 16%, var(--nvl-color-secondary));
}

/* Costura de 1px sobre la barra (owner, 2026-09-19): en el límite con la barra
   de administración de WordPress, a densidad de pantalla fraccionaria, asoma
   una línea del lienzo entre las dos. Esta franja de 1px en la tinta de la
   barra la tapa; queda BAJO la barra de administración (z-index 99999) y, sin
   sesión iniciada, se pinta fuera de la pantalla. No toca el flujo ni el alto
   constante: es un pseudoelemento absoluto sobre un elemento ya posicionado. */
.nvl-promo-bar::before {
    content: "";
    position: absolute;
    inset-block-end: 100%;
    inset-inline: 0;
    block-size: 1px;
    background-color: var(--nvl-color-secondary);
}

/* Por debajo de 600px la barra de administración de WordPress deja de ser
   fija y se va con el scroll: la promocional se pega al borde. */
@media (max-width: 600px) {
    .nvl-promo-bar {
        --nvl-promo-bar-inset: 0px;
    }
}

/* AUTOSUFICIENTE: la barra se imprime en TODAS las vistas, pero nvl-icons.css
   y nvl-elements.css solo cargan en las comerciales (inc/assets.php). Estas
   dos reglas repiten, acotadas a la barra, las de esas hojas. */
.nvl-promo-bar .nvl-icon {
    width: var(--nvl-icon-inline);
    height: var(--nvl-icon-inline);
    flex-shrink: 0;
    fill: currentColor;
}

.nvl-promo-bar .nvl-screen-reader-text {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

/* Rejilla de UNA columna: sin botón de cerrar no hay segunda. minmax(0, 1fr)
   se declara a propósito —la columna implícita `auto` no baja del tamaño de su
   contenido y se llevaría por delante la elipsis del alcance—. El contenido se
   centra desde 768px con justify-content (ver ese bloque): antes lo centraban
   dos columnas de 44px que existían por el botón. */
.nvl-promo-bar__inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    align-items: center;
    block-size: calc(var(--nvl-promo-bar-h) - var(--nvl-border-promo));
    max-inline-size: var(--nvl-container-max);
    margin-inline: auto;
    padding-inline: var(--nvl-container-pad);
}

.nvl-promo-bar__content {
    display: flex;
    align-items: center;
    /* 12px en móvil (era 8): parte del espacio que dejaron el disco y el
       botón de cerrar va a separar el mensaje del contador. */
    gap: var(--nvl-space-xs);
    min-inline-size: 0;
    block-size: 100%;
}

/* El mensaje (enlazado o no) ocupa todo el alto: 44px de área táctil. */
.nvl-promo-bar__message {
    display: flex;
    align-self: stretch;
    align-items: center;
    gap: var(--nvl-space-2xs);
    min-inline-size: 0;
    margin: 0;
    color: var(--nvl-color-text-inverse-muted);
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-regular);
    line-height: var(--nvl-leading-snug);
    text-decoration: none;
    white-space: nowrap;
}

/* --- Icono en disco ---------------------------------------------------------
   SOLO la barra de envío gratis lo lleva (la de campaña ya no tiene icono):
   ámbar al 16% sobre tinta con anillo ámbar, glifo a 7.67:1. Volvió al ámbar
   por decisión del owner (2026-09-19); el resaltado **…** de esa barra sigue
   en verde y no compiten, porque no son adyacentes. */
.nvl-promo-bar__badge {
    display: grid;
    flex: none;
    place-content: center;
    inline-size: var(--nvl-icon-lead);
    block-size: var(--nvl-icon-lead);
    border: var(--nvl-border-hairline) solid transparent; /* forma en forced-colors */
    border-radius: var(--nvl-radius-circle);
    background: var(--nvl-promo-amber-soft);
    box-shadow: inset 0 0 0 var(--nvl-border-hairline) color-mix(in srgb, var(--nvl-color-promo-highlight) 32%, transparent);
    color: var(--nvl-color-promo-highlight);
    font-size: var(--nvl-text-sm);
}


/* --- Mensaje ----------------------------------------------------------------
   Un mensaje por ancho (los dos vienen del servidor): el corto por debajo de
   1280px, el largo desde 1280px. display: none saca el oculto del árbol de
   accesibilidad. El beneficio no encoge; si falta sitio encoge el alcance. */
.nvl-promo-bar__text {
    flex: none;
    min-inline-size: 0;
    max-inline-size: 100%;
    overflow: hidden;
    text-overflow: ellipsis; /* solo red de seguridad */
}

.nvl-promo-bar__text--long {
    display: none;
}

/* Beneficio (**resaltado**): el primer nivel. Ámbar 900 en mayúsculas, sin
   subrayado (versión moderna aprobada por el owner). 10.69:1 sobre tinta. */
.nvl-promo-bar__highlight {
    color: var(--nvl-color-promo-highlight);
    font-weight: var(--nvl-weight-extrabold);
    text-transform: uppercase;
}

/* Barra de envío gratis: el beneficio en verde (7.04:1). */
.nvl-promo-bar--base .nvl-promo-bar__highlight {
    color: var(--nvl-color-success);
}

/* {monto} y {umbral_regalo}: la cifra, en acento; nunca se parte. */
.nvl-promo-bar__amount {
    color: var(--nvl-color-accent);
    font-weight: var(--nvl-weight-bold);
    white-space: nowrap;
}

/* Alcance que añade la barra (campaña restringida): tercer nivel, en línea
   tras « · ». Es lo que encoge con «…» si no cabe, nunca el beneficio. */
.nvl-promo-bar__scope {
    display: none;
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nvl-promo-bar__scope--short {
    display: block;
}

/* Con enlace (vista de la promoción o política de envío): subrayado solo con
   el puntero, sobre el texto. */
@media (hover: hover) and (pointer: fine) {
    a.nvl-promo-bar__message:hover .nvl-promo-bar__text,
    a.nvl-promo-bar__message:hover .nvl-promo-bar__scope {
        text-decoration: underline;
        text-decoration-thickness: var(--nvl-border-hairline);
        text-underline-offset: var(--nvl-space-3xs);
    }
}

/* Anillo por DENTRO: por fuera caería sobre la cabecera blanca. */
a.nvl-promo-bar__message:focus-visible {
    outline-offset: calc(var(--nvl-focus-width) * -1);
}

/* --- Contador ---------------------------------------------------------------
   Real y sincronizado con la fecha de fin (el script solo reescribe el número
   que cambia). Por debajo de 768px, compacto en una celda; desde 768px, en
   celdas «02 d · 14 h · 05 m · 32 s»; la etiqueta desde 1280px. */
.nvl-promo-bar__timer {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--nvl-space-3xs);
    margin: 0 0 0 auto;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.nvl-promo-bar__timer-full {
    display: none;
    align-items: center;
    gap: var(--nvl-space-3xs);
}

/* Sin caja alta (owner, 2026-10-17): 12px, tinta apagada sobre fondo oscuro y
   al lado de un número que se mueve. Sin letter-spacing, las mayúsculas aquí
   se agolpaban más que en ningún otro sitio del tema. */
.nvl-promo-bar__timer-label {
    display: none;
    margin-inline-end: var(--nvl-space-2xs);
    color: var(--nvl-color-text-inverse-muted);
    font-size: var(--nvl-text-2xs);
    font-weight: var(--nvl-weight-bold);
}

/* Celda: número en acento (5.91:1 sobre la celda) + unidad en gris claro. */
.nvl-promo-bar__timer-cell,
.nvl-promo-bar__timer-compact {
    display: inline-flex;
    align-items: baseline;
    gap: calc(var(--nvl-space-3xs) / 2);
    block-size: calc(var(--nvl-tap-min) - var(--nvl-space-xs));
    padding-inline: var(--nvl-space-2xs);
    border: var(--nvl-border-hairline) solid transparent; /* forma en forced-colors */
    border-radius: var(--nvl-radius-xs);
    background: var(--nvl-color-overlay-light);
    line-height: calc(var(--nvl-tap-min) - var(--nvl-space-xs) - 2 * var(--nvl-border-hairline));
}

.nvl-promo-bar__timer-num {
    display: inline-block;
    min-inline-size: 2ch;
    color: var(--nvl-color-accent);
    font-size: var(--nvl-text-base);
    font-weight: var(--nvl-weight-bold);
    text-align: end;
}

.nvl-promo-bar__timer-unit {
    color: var(--nvl-color-text-inverse-muted);
    font-size: var(--nvl-text-2xs);
    font-weight: var(--nvl-weight-regular);
}

/* Segundos: presentes pero sin competir. Peso 500 en gris claro en vez de
   opacidad (el acento al 70% no llega a AA). */
.nvl-promo-bar__timer-cell--s .nvl-promo-bar__timer-num {
    color: var(--nvl-color-text-inverse-muted);
    font-weight: var(--nvl-weight-regular);
}

/* Los dos puntos solo existen en las últimas 24 h. */
.nvl-promo-bar__timer-sep {
    display: none;
    color: var(--nvl-color-text-inverse-muted);
    font-weight: var(--nvl-weight-regular);
}

/* Compacto (móvil): una celda. Con más de un día «2d 14h 05m» (sin segundos,
   decisión del owner); con menos, «05:42:10». Sin ancho mínimo: horas,
   minutos y segundos van siempre con dos dígitos tabulares, así que el ancho
   solo cambia cuando cambia el número de días (una vez al día) o al pasar a
   las últimas 24 h; cualquier reserva extra se la quitaría al alcance.

   EL AIRE VA EN LA SEPARACIÓN, NO EN EL TAMAÑO (ui-ux, 2026-09-19): tres
   celdas sueltas medirían ~131px y a 320px cortarían el beneficio, y subir el
   número a 16px lo pondría por encima del resaltado. Con word-spacing de 4px
   el hueco entre «2d», «20h» y «04m» llega a ~7.6px —el ritmo de 8px— sin
   tocar el marcado ni escribir un valor suelto; el gap interno entre número y
   unidad se queda en 2px, así que los grupos por fin se leen separados. */
.nvl-promo-bar__timer-compact {
    padding-inline: var(--nvl-space-xs);
    color: var(--nvl-color-accent);
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-bold);
    justify-content: center;
    word-spacing: var(--nvl-space-3xs);
}

.nvl-promo-bar__timer-compact .nvl-promo-bar__timer-unit {
    margin-inline-start: 0.05em;
}

.nvl-promo-bar__compact-final {
    display: none;
}

/* Últimas 24 h: la etiqueta dice «ÚLTIMAS HORAS» en ámbar, la celda de días
   desaparece, aparecen los dos puntos y las celdas toman el fondo ámbar
   suave con horas y minutos en ámbar (7.69:1). Cambio instantáneo: sin rojo,
   sin parpadeo, sin animación. */
.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-label {
    color: var(--nvl-color-promo-highlight);
}

.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-cell--d,
.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-unit,
.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__compact-days {
    display: none;
}

.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-sep {
    display: inline;
}

.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__compact-final {
    display: inline;
}

.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-cell,
.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-compact {
    background: var(--nvl-promo-amber-soft);
    box-shadow: inset 0 0 0 var(--nvl-border-hairline) color-mix(in srgb, var(--nvl-color-promo-highlight) 40%, transparent);
}

.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-cell--h .nvl-promo-bar__timer-num,
.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-cell--m .nvl-promo-bar__timer-num,
.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__timer-compact {
    color: var(--nvl-color-promo-highlight);
}

.nvl-promo-bar__timer.nvl-is-final .nvl-promo-bar__compact-sec {
    color: var(--nvl-color-text-inverse-muted);
    font-weight: var(--nvl-weight-regular);
}

/* Teléfonos muy estrechos: el compacto se queda en «2d 14h». */
@media (max-width: 359px) {
    .nvl-promo-bar__compact-min {
        display: none;
    }
}

/* Movimiento reducido: sin segundero. El estado final se lee «05:42». */
@media (prefers-reduced-motion: reduce) {
    .nvl-promo-bar__timer-cell--s,
    .nvl-promo-bar__timer-sep--s,
    .nvl-promo-bar__compact-sec {
        display: none !important;
    }
}

/* --- Desde 768px: contenido centrado, disco de 32px, mensaje a 16px, divisor
   y cuatro celdas. El centrado sube de 1024px a 768px: lo hacían dos columnas
   de 44px que existían por el botón de cerrar, y sin ellas quedaban ~177px de
   hueco muerto a la derecha. `safe center` es el que evita que un mensaje más
   largo que el hueco desborde por el lado de inicio y se corte sin elipsis;
   se declara después del `center` llano, que es el respaldo. */
@media (min-width: 768px) {
    .nvl-promo-bar__content {
        gap: var(--nvl-space-sm);
        justify-content: center;
        justify-content: safe center;
    }

    .nvl-promo-bar__badge {
        inline-size: var(--nvl-space-xl);
        block-size: var(--nvl-space-xl);
        font-size: var(--nvl-text-base);
    }

    .nvl-promo-bar__highlight {
        font-size: var(--nvl-text-base);
    }

    .nvl-promo-bar__timer {
        margin-inline-start: 0;
    }

    /* Divisor entre mensaje y contador: hairline en blanco al 22%. */
    .nvl-promo-bar__timer::before {
        content: "";
        inline-size: var(--nvl-border-hairline);
        block-size: var(--nvl-space-lg);
        margin-inline-end: var(--nvl-space-sm);
        background: var(--nvl-color-overlay-strong);
    }

    .nvl-promo-bar__timer-full {
        display: inline-flex;
    }

    .nvl-promo-bar__timer-compact {
        display: none;
    }
}

/* --- Desde 1280px: mensaje largo y etiqueta del contador. ----------------- */
@media (min-width: 1280px) {
    .nvl-promo-bar__text--long {
        display: block;
    }

    .nvl-promo-bar__text--short,
    .nvl-promo-bar__scope--short {
        display: none;
    }

    .nvl-promo-bar__scope--long {
        display: block;
    }

    .nvl-promo-bar__timer-label {
        display: inline;
    }
}

@media (forced-colors: active) {
    .nvl-promo-bar {
        border-block-end-color: CanvasText;
        background-image: none;
    }

    .nvl-promo-bar__badge,
    .nvl-promo-bar__timer-cell,
    .nvl-promo-bar__timer-compact {
        border-color: CanvasText;
    }

    .nvl-promo-bar__timer::before {
        background: CanvasText;
    }

    .nvl-promo-bar__badge,
    .nvl-promo-bar__timer-num,
    .nvl-promo-bar__timer-compact {
        color: CanvasText;
    }

    /* Sin degradado no hay costura que tapar y el lienzo lo decide el sistema. */
    .nvl-promo-bar::before {
        background: none;
    }
}

.nvl-site-header {
    background: var(--nvl-color-surface);
    border-bottom: var(--nvl-border-hairline) solid var(--nvl-color-border);
}

/* --- Botón del carrito en la cabecera ------------------------------------
   Dispara components/cart-drawer.php. Va en la cáscara y no en la cadena de
   componentes porque vive en el header, que es global.

   Objetivo táctil completo (--nvl-tap-min) aunque el icono sea pequeño: es un
   control que se pulsa en móvil con el pulgar.

   El contador va EN LÍNEA junto al icono, separado por --nvl-space-2xs, no
   superpuesto sobre él. Superpuesto se leía amontonado —y más aún porque la
   cifra cambia sola al agregar o quitar—, y al crecer a tres dígitos se
   recortaba contra el borde de la cabecera. En línea, la píldora crece hacia
   la izquierda dentro del propio botón: como este es el último elemento del
   header, 1, 10 o 100 no desplazan nada más y la alineación vertical la
   sostiene align-items: center. */

/* PÍLDORA, NO ICONO SUELTO (owner, 2026-10-22, con referencia): «dejar de
   mostrar este elemento de forma aislada y convertirlo en un componente con
   apariencia de botón». Cesta, rótulo y contador dentro de una misma caja.

   EL RELLENO ES --nvl-color-section-alt Y NO el blanco de la referencia, que
   tiene la cabecera oscura: la nuestra es --nvl-color-surface, así que una
   píldora blanca sobre ella solo existiría por su filo. El tinte la dibuja sin
   levantar la voz, que es justo lo que pidió —«que destaque lo suficiente sin
   competir visualmente con los demás elementos del menú»—.

   SIN EL PUNTO SEPARADOR de la referencia, y lo pregunta él mismo. Allí separa
   dos textos planos; aquí el contador ya es un objeto con forma y color
   propios, así que un punto entre el rótulo y una píldora azul es puntuación
   que no desambigua nada. */
.nvl-cart-launcher {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--nvl-space-2xs);
    min-height: var(--nvl-tap-min);
    padding: 0 var(--nvl-space-sm);
    border: var(--nvl-border-hairline) solid var(--nvl-color-border);
    border-radius: var(--nvl-radius-pill);
    background: var(--nvl-color-section-alt);
    color: var(--nvl-color-text-primary);
    cursor: pointer;
    flex-shrink: 0;
    transition: background-color var(--nvl-duration-base) var(--nvl-ease-out),
                border-color var(--nvl-duration-base) var(--nvl-ease-out);
}

/* El min-width de --nvl-tap-min se retiró: con rótulo dentro, el ancho lo da
   el contenido y ese suelo ya no describe nada. El alto sigue garantizando el
   objetivo táctil. */

@media (hover: hover) and (pointer: fine) {
    .nvl-cart-launcher:hover {
        background: var(--nvl-color-border);
        border-color: var(--nvl-color-border-hover);
    }
}

/* El acuse del pulsado NO puede ser solo el scale: nvl-components.css lo anula
   bajo prefers-reduced-motion y el botón se quedaría sin ninguna señal. Mismo
   defecto que ya costó el arreglo de .nvl-button--secondary. */
.nvl-cart-launcher:active {
    background: var(--nvl-color-border-hover);
    transform: scale(0.98);
}

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

/* EL RÓTULO SE OCULTA POR DEBAJO DE 640, y no es una preferencia: medido con
   el CSS real a 375px, la marca y la píldora dejan SEIS píxeles entre ambas.
   En un teléfono un poco más estrecho, o con un nombre de tienda más largo,
   la cabecera se rompe. En el teléfono el icono y la cifra ya identifican el
   carrito sin ayuda; el rótulo es para donde hay sitio.

   SE RECORTA, NO SE QUITA: con `display: none` el nombre accesible del botón
   pasaría a ser «2 productos», sin decir de qué. Con esta técnica —la misma de
   .nvl-screen-reader-text— el rótulo deja de ocupar sitio pero sigue en el
   árbol de accesibilidad, así que el botón se sigue anunciando «Carrito 2
   productos» en cualquier ancho. */
@media (max-width: 639px) {
    .nvl-cart-launcher__label {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
        border: 0;
    }
}

/* Mismo escalón que el icono de encabezado de bloque: el botón es el ancla
   visual del carrito en la cabecera y con el tamaño en línea (1em) quedaba
   por debajo del peso del contador que lleva al lado. */
.nvl-cart-launcher .nvl-icon {
    width: var(--nvl-icon-lead);
    height: var(--nvl-icon-lead);
}

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

/* El contador: píldora al lado del icono. Círculo con una cifra y cápsula con
   dos o tres, porque min-width fija el suelo y el padding hace el resto.
   Numerales tabulares para que no baile al cambiar de cifra. */
.nvl-cart-launcher__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--nvl-space-lg);
    height: var(--nvl-space-lg);
    padding: 0 var(--nvl-space-2xs);
    border-radius: var(--nvl-radius-pill);
    background: var(--nvl-color-primary-dark); /* mismo tono que ya usaba el badge del resumen: contraste AA con texto blanco */
    color: var(--nvl-color-surface);
    font-size: var(--nvl-text-2xs);
    font-weight: var(--nvl-weight-bold);
    font-variant-numeric: tabular-nums;
}

/* Carrito vacío: el botón sigue ahí y sigue siendo pulsable —el usuario puede
   querer comprobar que está vacío— pero la burbuja no grita un cero. */
/* Acuse del agregado. Sustituye a la conmutación del botón, que se leía como
   un parpadeo: aquí la señal aparece donde el usuario ya sabe que vive el
   carrito y donde el número acaba de cambiar, no debajo del cursor.
   SIN `transform` a propósito: una confirmación no debe poder desbordar nada
   ni mover una sola caja. Solo color. nvl-cart.js retira la clase en
   `animationend`; la rama de movimiento reducido vive en nvl-components.css. */
@keyframes nvl-bumped {
    0%   { background: var(--nvl-color-primary-dark); color: var(--nvl-color-surface); }
    20%  { background: var(--nvl-color-success);      color: var(--nvl-color-secondary); }
    70%  { background: var(--nvl-color-success);      color: var(--nvl-color-secondary); }
    100% { background: var(--nvl-color-primary-dark); color: var(--nvl-color-surface); }
}

.nvl-cart-launcher__count.nvl-is-bumped {
    animation: nvl-bumped var(--nvl-duration-slower) var(--nvl-ease-out);
}

/* --- Aviso temporal de agregado (components/cart-toast.php) ---------------
   Confirma el acto de agregar y dice cuántas unidades y de qué producto.

   ANCLADO ABAJO Y FIJO: crece hacia arriba, así que un nombre largo que ocupe
   tres líneas no desplaza ni una caja de la página. Y por estar `fixed`, sus
   `opacity`/`transform` no pueden desbordar ningún contenedor con scroll —
   la cautela que costó el defecto de las barras de scroll del 21-ago.

   NO BLOQUEA: pointer-events: none en el bloque, `auto` solo en la acción.
   Las dos declaraciones son UN PAQUETE — quitar una rompe la otra. */
.nvl-cart-toast {
    position: fixed;
    z-index: var(--nvl-z-sticky); /* sobre el contenido y BAJO el panel del carrito (150): con el carrito abierto el aviso debe quedar bajo el velo, porque el panel ya dice lo que el aviso decía */
    /* MISMA ZONA que la cápsula flotante en todos los anchos —márgenes, ancho
       máximo y separación inferior—: el aviso la sustituye mientras está
       visible, así que ocupa su sitio. El centrado lo hacen las dos insets, el
       tope y margin-inline: auto. En escritorio solo cambia la separación
       inferior (bloque ≥768).
       env() es geometría que aporta el navegador, no un valor de diseño: no hay
       token que pueda expresar el área segura del dispositivo. */
    inset-inline: var(--nvl-space-xs);
    inset-block-end: calc(env(safe-area-inset-bottom, 0px) + var(--nvl-space-xs));
    max-inline-size: var(--nvl-cart-float-width);
    margin-inline: auto;
    display: flex;
    align-items: center;
    gap: var(--nvl-space-2xs);
    min-block-size: var(--nvl-tap-min);
    padding: var(--nvl-space-2xs) var(--nvl-space-2xs) var(--nvl-space-2xs) var(--nvl-space-sm);
    border: var(--nvl-border-hairline) solid transparent; /* sin él, en forced-colors la banda se aplana contra el fondo del sistema */
    border-radius: var(--nvl-radius-xl); /* la silueta de la cápsula, a la que sustituye */
    background: var(--nvl-color-success); /* 7.04:1 con la tinta oscura. Con blanco daba 2.54:1: el verde oficial solo aguanta texto oscuro encima */
    color: var(--nvl-color-secondary);
    box-shadow: var(--nvl-shadow-xl);
    /* El anillo de foco por defecto no rinde sobre este verde; el blanco tampoco
       (2.54:1). Dentro del aviso, el foco es la tinta oscura: 7.04:1. */
    --nvl-focus-color: var(--nvl-color-secondary);
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
}

/* VARIANTE NEUTRA (C1): el aviso que explica por qué el cliente volvió a la
   tienda («Tu carrito está vacío…»). No es un logro ni una confirmación, así que
   no va en verde: tinta oscura con texto en superficie (17.9:1). El acento
   rinde como anillo de foco sobre ese fondo (8.33:1), al contrario que la tinta
   oscura que usa la variante verde. */
.nvl-cart-toast--notice {
    background: var(--nvl-color-secondary);
    color: var(--nvl-color-surface);
    --nvl-focus-color: var(--nvl-color-accent);
}

/* Sin acción: la frase ya dice qué hacer y no hay carrito que ver. */
.nvl-cart-toast__action[hidden] {
    display: none;
}

.nvl-cart-toast__text {
    margin: 0;
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-regular);
    line-height: var(--nvl-leading-normal);
    /* Un nombre largo no puede estirar el aviso sin fin: tres líneas y elipsis. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
}

/* pointer-events se reactiva aquí a propósito: el bloque es inerte para no
   interceptar el gesto de seguir comprando, pero la acción SÍ debe recibir el
   clic. Las dos líneas van juntas o ninguna funciona. */
.nvl-cart-toast__action {
    pointer-events: auto;
    flex-shrink: 0;
    min-inline-size: var(--nvl-tap-min);
    min-block-size: var(--nvl-tap-min);
    padding: 0 var(--nvl-space-xs);
    border: 0;
    border-radius: var(--nvl-radius-sm);
    background: transparent;
    /* Tinta oscura, no blanca: el blanco sobre el verde oficial daba 2.54:1 e
       incumplía 1.4.3. Con --nvl-color-secondary, 7.04:1 (7.77:1 sobre el
       hover de blanco al 12%). */
    color: var(--nvl-color-secondary);
    /* Subrayado además del color: sobre este verde la acción no puede
       distinguirse solo por tinta (WCAG 1.4.1). */
    text-decoration: underline;
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-bold);
    line-height: var(--nvl-leading-snug);
    cursor: pointer;
}

@media (hover: hover) and (pointer: fine) {
    .nvl-cart-toast__action:hover {
        background: var(--nvl-color-overlay-light);
    }
}

/* Entrada, permanencia y salida en UNA sola animación: dos encadenadas
   emitirían dos `animationend` y obligarían al JS a filtrar por animationName.
   El movimiento acaba al 7% y vuelve a empezar al 95%; el 88% intermedio es el
   tiempo legible. Solo se animan opacity y transform. */
@keyframes nvl-toast-life {
    0%   { opacity: 0; transform: translateY(var(--nvl-space-sm)); }
    7%   { opacity: 1; transform: none; }
    95%  { opacity: 1; transform: none; }
    100% { opacity: 0; transform: translateY(var(--nvl-space-sm)); }
}

.nvl-cart-toast.nvl-is-visible {
    visibility: visible;
    animation: nvl-toast-life
        calc(var(--nvl-duration-slow) + var(--nvl-duration-notice) + var(--nvl-duration-base))
        var(--nvl-ease-out) both;
    /* Relevo limpio. Comparte zona con la cápsula, que se imprime después, se
       pinta ENCIMA y tarda --nvl-duration-base en irse: sin retardo se veía
       tinta sobre el verde a mitad de la salida. El aviso espera esa salida.
       DEBE ir detrás de la `animation` abreviada de arriba, que lo pondría a 0.
       Bajo movimiento reducido nvl-components.css vuelve a declarar la
       abreviada, el retardo cae a 0 y la cápsula desaparece de golpe. */
    animation-delay: var(--nvl-duration-base);
}

/* "No desaparece si el usuario está interactuando", sin un solo temporizador en
   JS: el puntero encima o el foco dentro pausan la propia animación, que ES el
   temporizador. Nada que cancelar, nada que se quede colgado. */
.nvl-cart-toast.nvl-is-visible:hover,
.nvl-cart-toast.nvl-is-visible:focus-within {
    animation-play-state: paused;
}

@media (min-width: 768px) {
    /* Escritorio: la misma separación inferior que la cápsula. */
    .nvl-cart-toast {
        inset-block-end: calc(env(safe-area-inset-bottom, 0px) + var(--nvl-space-lg));
    }
}

/* Sobre --nvl-color-border-hover, la tinta secundaria daría 3.21:1 con el valor
   oficial: el cero del carrito vacío pasa a la tinta primaria (9.85:1). */
.nvl-cart-launcher.nvl-is-empty .nvl-cart-launcher__count {
    background: var(--nvl-color-border-hover);
    color: var(--nvl-color-text-primary);
}

/* --- Cápsula flotante del carrito (components/cart-bar.php) ---------------
   No es una barra, ni un pie fijo, ni una barra de herramientas: es un OBJETO
   que flota sobre la página. Y lo que lo consigue no es el radio ni la sombra,
   es el MATERIAL. Una superficie clara sobre una página clara solo puede
   separarse con un filo de 1px y una sombra al 10% —y las páginas de esta
   tienda son #ffffff, #f5f5f5 y #eef4f8—: ese camino se agotó, y de ahí venía
   la sensación de barra recortada. Una superficie de TINTA se separa por
   materia. Con ella el importe deja de necesitar peso 900 para encontrarse: le
   basta ser el único blanco puro de la pieza.

   LA RAÍZ ES EL <button> (ver cart-bar.php): se pulsa la cápsula entera, no un
   trozo. Aquí no hay lógica — todo lo que muestra lo rellenan renderSummaries()
   y renderLaunchers() por contrato de data-*. */
.nvl-cart-bar {
    position: fixed;
    inset-inline: var(--nvl-space-xs);
    /* Apoyada abajo: en móvil, el mismo aire que a los lados (--nvl-space-xs),
       que es lo que la lee como un objeto y no como una barra; en escritorio,
       --nvl-space-lg (bloque ≥768). La cápsula NO se coloca en función de ningún
       elemento ajeno: si un botón flotante de terceros coincide con ella, se
       ajusta desde su propio plugin.

       env() es geometría que aporta el navegador, no un valor de diseño: no hay
       token que pueda expresar el área segura del dispositivo. */
    inset-block-end: calc(env(safe-area-inset-bottom, 0px) + var(--nvl-space-xs));
    /* Sobre el contenido y BAJO el panel del carrito (150). */
    z-index: var(--nvl-z-sticky);

    /* El titular ocupa la ÚLTIMA FILA ENTERA, de filo a filo y por debajo del
       disco (owner, 2026-10-12). El copy de los logros no cabía en las dos
       columnas de la derecha y se salía de su píldora; con el ancho completo
       pasa de 334 a 386px y entra en un renglón, y cuando no entra se parte
       dentro de la reserva de dos líneas que el hueco ya tenía.

       El disco deja de bajar hasta esa fila —lo hace con `grid-row: 1 / -2`,
       que vale para las dos plantillas— pero conserva su sitio, su tamaño y su
       visibilidad: la esquina inferior izquierda que antes rellenaba la ocupa
       ahora el texto, que es lo mismo que se buscaba. */
    display: grid;
    grid-template-areas:
        "chip head total"
        "msg  msg  msg";
    /* 1fr y no minmax(0, 1fr) en la columna del centro: con el suelo en 0 la
       rejilla encogía esa columna por debajo de su contenido y "16 artículos" se
       partía en dos líneas, que subía la cápsula de 69 a 88px. Con el suelo en
       min-content la pareja cifra + sustantivo no se separa nunca. */
    grid-template-columns: auto 1fr auto;
    /* La cifra de 14px y el importe de 16px comparten LÍNEA DE BASE. Es el
       detalle que separa "maquetado" de "compuesto". */
    align-items: baseline;
    column-gap: var(--nvl-space-xs);
    row-gap: var(--nvl-space-3xs);
    padding: var(--nvl-space-xs) var(--nvl-space-sm);
    /* Un ancho que cambia cuando cambia el importe es la definición de
       improvisado: fuera el fit-content anterior. Pero con el ancho libre, a
       767px la cápsula medía 743px y volvía a ser exactamente la barra de borde
       a borde que había que quitar: el tope entra ya aquí, no a partir de 768.
       Por debajo de ~445px el tope no llega a aplicar y la cápsula ocupa el
       hueco entre los márgenes, que es lo que necesita para caber.
       margin-inline: auto la CENTRA en cualquier ancho con un solo mecanismo:
       las dos insets más el tope reparten el sobrante a partes iguales. */
    inline-size: auto;
    max-inline-size: var(--nvl-cart-float-width);
    margin-inline: auto;

    /* background-COLOR y background-IMAGE por separado, nunca la forma
       abreviada, aquí ni en :hover ni en :active: la abreviada se llevaría por
       delante el lavado del canto y, con él, la transición de 120ms del relleno. */
    background-color: var(--nvl-color-secondary);
    /* El lavado que describe el canto iluminado. Muere a 20px, muy por encima
       del primer píxel de tinta del contenido: ningún contraste se mueve. */
    background-image: var(--nvl-gradient-relief);
    /* EL BORDE ES TRANSPARENTE desde el 2026-10-18 (owner: «el borde tan delgado
       en color blanco o gris no me termina de convencer»). Tenía razón, y la
       causa no era el color: con blanco al 22% recortado al padding-box, el
       borde se componía contra lo que hay DEBAJO —sobre #f5f5f5 daba #f7f7f7,
       1.02:1 contra la página—, así que no se veía como borde sino como un HILO
       CLARO de 1px metido entre el cuerpo oscuro y su propia sombra de contacto.
       Eso es lo que aplanaba el objeto.
       Ese mismo 22% pasa a ser la segunda capa, INTERIOR, de --nvl-shadow-relief:
       cierra la silueta igual sobre el pie oscuro y deja que la sombra de
       contacto toque la tinta. El borde se queda en transparente —no se retira—
       porque forced-colors necesita una caja a la que dar color. */
    border: var(--nvl-border-hairline) solid transparent;
    /* 24px y NO píldora. Sobre 66px de alto, la píldora (33px) volvería a crear
       las dos lentes muertas que dejaban el aire sobrante de la versión
       anterior: una píldora solo es honesta cuando el contenido es UNA línea.
       16px leería como tarjeta. */
    border-radius: var(--nvl-radius-xl);
    box-shadow: var(--nvl-shadow-relief);
    color: var(--nvl-color-surface);
    text-align: start;
    cursor: pointer;

    /* Un solo desplazamiento para tres cosas —entrar, salir y levantarse en
       hover— a través de una variable. Con dos reglas de transform compitiendo,
       la de salida y la de hover se pisaban. */
    --nvl-bar-shift: 0px;
    transform: translateY(var(--nvl-bar-shift));

    /* Entrada: 300ms con --nvl-ease-out, que frena largo y sin rebote. El
       objeto LLEGA, no aterriza de golpe. La visibility entra sin retardo para
       que el foco pueda volver a la cápsula en cuanto se cierra el panel. */
    transition:
        opacity          var(--nvl-duration-slow) var(--nvl-ease-out),
        transform        var(--nvl-duration-slow) var(--nvl-ease-out),
        visibility       0s,
        background-color var(--nvl-duration-fast) var(--nvl-ease-standard),
        box-shadow       var(--nvl-duration-base) var(--nvl-ease-standard);
}

/* El disco abarca TODAS LAS FILAS MENOS LA ÚLTIMA, que es la del titular, y se
   centra sobre ellas. `1 / -2` lo dice una sola vez para las dos plantillas: con
   dos filas ocupa la primera, con tres las dos primeras. Ancla la composición
   por la izquierda sin necesidad de un filete separador, que sobre tinta o
   desaparece o parece un arañazo. */
.nvl-cart-bar__chip {
    grid-area: chip;
    grid-row: 1 / -2;
    align-self: center;
    display: grid;
    place-content: center;
    inline-size: var(--nvl-space-2xl);
    block-size: var(--nvl-space-2xl);
    border-radius: var(--nvl-radius-circle);
    background: var(--nvl-color-overlay-light);
    /* Hueco, no botón: profundidad DENTRO del objeto. Ver el token. */
    box-shadow: var(--nvl-shadow-relief-chip);
    transition: background-color var(--nvl-duration-fast) var(--nvl-ease-standard);
}

/* `align-self: center` aquí y en el total porque, sin el disco abarcando la
   última fila, es el disco quien mide la primera (40px contra 17 de texto) y con
   la alineación por línea de base los dos se irían al borde superior dejando una
   esquina muerta a la derecha.

   La LÍNEA DE BASE COMPARTIDA que esta cápsula declara —cifra de 14px e importe
   de 16px— se conserva en la práctica: centrados en 40px sus bases caen a 23.5 y
   24.0px del borde. Medio píxel, medido, no estimado. */
.nvl-cart-bar__head {
    grid-area: head;
    align-self: center;
    line-height: var(--nvl-leading-snug);
    /* La cifra y su sustantivo son UNA unidad de lectura: partirlos en dos
       líneas cambia el alto de la cápsula y rompe la línea de base compartida
       con el importe. */
    white-space: nowrap;
}

/* Numeral que ENCABEZA la línea, no un badge sobre el icono. Un badge diría el
   número dos veces (la burbuja y la palabra), metería un tercer acento
   cromático en una pieza de 66px y contradiría la decisión ya documentada de la
   cabecera, donde el contador superpuesto se descartó por amontonado. */
.nvl-cart-bar__count {
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-bold);
    font-variant-numeric: tabular-nums;
}

/* El sustantivo NO se oculta en móvil. La regla que lo escondía bajo 640px
   existía porque su nowrap empujaba el ancho mínimo por encima de lo
   disponible; con el titular en su propia fila y el ancho ya no siendo
   fit-content, la causa desapareció. Una cifra suelta no dice de qué es. */
.nvl-cart-bar__noun {
    margin-inline-start: var(--nvl-space-3xs);
    color: var(--nvl-color-text-inverse-muted);
    font-size: var(--nvl-text-sm);
    font-weight: var(--nvl-weight-regular);
    white-space: nowrap;
}

/* MISMO tamaño y MISMO peso en los cinco anchos. Antes crecía a 640px y llevaba
   peso 900 en azul saturado: la configuración más ruidosa de todo el sistema de
   tokens aplicada al elemento más pequeño de la página. No competía por tamaño,
   competía por decibelios. Sobre tinta le basta ser el único blanco puro. */
.nvl-cart-bar__total {
    grid-area: total;
    justify-self: end;
    align-self: center;
    font-size: var(--nvl-text-base);
    font-weight: var(--nvl-weight-bold);
    line-height: var(--nvl-leading-tight);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* Fila de AHORRO del descuento progresivo (components/cart-bar.php): total sin
   descuento tachado + "Ahorras $X", bajo el total y alineados con él a la
   derecha —pagas, antes, ahorras: un solo bloque de precio—. No va en la línea
   del total porque a 320px no cabe, ni junto a la frase porque la partiría en
   tres líneas y rompería el alto estable.

   Tercera fila SOLO con el modificador, que el JS pone mientras haya una campaña
   escalonada con escalones (criterio de configuración, no de carrito). Con él la
   fila se RESERVA aunque el ahorro sea 0 —mismo patrón que el titular—; sin él
   no existe: una fila vacía sumaría el row-gap y la cápsula de siempre crecería
   4px sin motivo. */
.nvl-cart-bar--savings {
    grid-template-areas:
        "chip head total"
        "chip save save"
        "msg  msg  msg";
}

.nvl-cart-bar__savings {
    display: none;
}

.nvl-cart-bar--savings .nvl-cart-bar__savings {
    grid-area: save;
    justify-self: end;
    display: flex;
    align-items: baseline;
    column-gap: var(--nvl-space-2xs);
    font-size: var(--nvl-text-2xs);
    line-height: var(--nvl-leading-snug);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.nvl-cart-bar--savings .nvl-cart-bar__savings[hidden] {
    visibility: hidden;
}

/* El "antes": la tinta atenuada de la pieza (12.0:1 sobre tinta; 9.67:1 en
   hover, 8.50:1 en active) y peso regular, un escalón por debajo de todo lo
   demás. <s> conserva el tachado aunque el CSS falle y en forced-colors. */
.nvl-cart-bar__original {
    color: var(--nvl-color-text-inverse-muted);
    font-weight: var(--nvl-weight-regular);
    text-decoration-line: line-through;
    text-decoration-thickness: var(--nvl-border-hairline);
    text-decoration-color: currentColor;
}

/* El ahorro en VERDE, el lenguaje de la fila "Descuento" de la referencia: el
   amarillo de la frase dice lo que falta, el verde lo que ya se ganó. Como
   tinta sobre TINTA sí cumple (7.03:1; 5.65:1 en hover, 4.97:1 en active): la
   prohibición del token es sobre fondo claro. La palabra "Ahorras" sostiene el
   significado sin color (WCAG 1.4.1). */
.nvl-cart-bar__saving {
    color: var(--nvl-color-success);
    font-weight: var(--nvl-weight-regular);
}

.nvl-cart-bar__saving-figure {
    font-weight: var(--nvl-weight-bold);
}

/* Titular: UNA FRASE en fila propia —"Te faltan $39 para el pedido mínimo"—,
   que abarca las columnas del contador y del importe hasta el borde derecho.
   Tres piezas en línea (prefijo, cifra, etiqueta) que heredan todo de este
   bloque: un solo tamaño, una sola línea de base, y la frase se parte por
   palabras donde haga falta.

   ALTO ESTABLE SIN ABREVIAR. El caso más largo, "Te faltan 6 productos para
   25% de descuento", no cabe en una línea por debajo de ~380px de pantalla.
   Por eso ahí la ranura reserva SIEMPRE dos líneas, con la frase centrada en
   vertical; desde 390px basta una. El corte de 390 sale de medir ESTE
   componente, no de la escala del sistema: no normalizarlo a 360 ni a 480.
   Las reservas incluyen los 2px del filo del chip de logro (border-box), así
   que pasar de progreso a logro tampoco mueve el alto.

   La ranura existe aunque el titular esté vacío —anulando el display:none de
   [hidden]— para que la cápsula no dé un salto al cruzar una meta. visibility
   la saca del árbol de accesibilidad y no pinta tinta. */
.nvl-cart-bar__message {
    grid-area: msg;
    display: block;
    align-content: center;
    min-block-size: calc(2 * var(--nvl-text-2xs) * var(--nvl-leading-normal) + 2 * var(--nvl-border-hairline));
    color: var(--nvl-color-text-inverse-muted);
    font-size: var(--nvl-text-2xs);
    font-weight: var(--nvl-weight-regular);
    line-height: var(--nvl-leading-normal);
    text-wrap: pretty;
}

@media (min-width: 390px) {
    .nvl-cart-bar__message {
        min-block-size: calc(var(--nvl-text-2xs) * var(--nvl-leading-normal) + 2 * var(--nvl-border-hairline));
    }
}

.nvl-cart-bar__message[hidden] {
    display: block;
    visibility: hidden;
}

/* La cifra se distingue por COLOR y PESO, no por tamaño: dentro de una frase,
   dos tamaños romperían la línea de base y costarían ancho justo donde falta.
   El amarillo no es decoración: --nvl-color-cta-yellow es en este sistema el
   color de "agregar al carrito", y esta cifra dice cuánto más hay que agregar.
   12.57:1 sobre tinta. Nunca se parte: "6 productos" es una unidad. */
.nvl-cart-bar__figure {
    color: var(--nvl-color-cta-yellow);
    font-weight: var(--nvl-weight-bold);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

/* EL DISTINTIVO VA CENTRADO en el renglón (owner, 2026-10-12). Con el titular
   ocupando la última fila entera, un chip que encoge hasta el ancho de su texto
   se quedaba apoyado en el filo izquierdo y dejaba todo el sobrante a la
   derecha: en la cápsula a tope eso son decenas de píxeles de aire en un solo
   lado, que es exactamente lo que se lee como descuadrado.

   UNA SOLA DECLARACIÓN, y aquí y no en el chip, porque `text-align` hace las dos
   cosas a la vez: centra la CAJA del chip —que es inline-block— cuando el copy
   cabe en un renglón, y, por herencia, centra sus LÍNEAS cuando no cabe y el
   chip pasa a ocupar el ancho completo. Ponerla en el chip solo resolvería la
   segunda mitad.

   Solo en logro. El mensaje de progreso del mismo hueco sigue alineado al
   inicio, a plomo con el disco de la cesta: una insignia se centra, una frase se
   lee desde la izquierda. No es un olvido. */
.nvl-cart-bar__message.nvl-is-win {
    text-align: center;
}

/* Único tratamiento de LOGRO: píldora verde con tinta oscura, 7.04:1. Como
   tinta el verde oficial daría 2.54:1 sobre claro y menos todavía sobre esta
   superficie; de relleno, además, el chip se distingue por FORMA y no solo por
   color, que es lo que mantiene distinguibles carencia y logro sin ver color
   (WCAG 1.4.1). El borde transparente del reposo existe para forced-colors.

   "Listo para comprar" NO entra aquí y el JS no le pone .nvl-is-win: no
   desbloquea nada, solo constata que no falta nada. Pintarlo de verde igualaría
   "no hay nada más que ganar" con "has ganado algo" y devaluaría los tres
   logros reales, que son la palanca comercial. */
.nvl-cart-bar__message.nvl-is-win .nvl-cart-bar__label {
    display: inline-block;
    /* Filo transparente: sostiene la forma en forced-colors. Sus 2px ya están
       contados en la reserva de alto de la ranura. */
    border: var(--nvl-border-hairline) solid transparent;
    padding: 0 var(--nvl-space-2xs);
    /* NO es píldora, y por la misma razón que la cápsula no lo es: un radio de
       999px sobre una caja de dos renglones no da una píldora, da dos lentes.
       Una píldora solo es honesta cuando el contenido es UNA línea, y el copy
       de los logros no siempre cabe en una.

       Y SIN `white-space: nowrap`, que es lo que hacía que el texto creciera en
       horizontal hasta salirse del distintivo en vez de repartirse (owner,
       2026-10-12). El contenedor ya trae `text-wrap: pretty`: el reparto en dos
       líneas sale equilibrado solo. */
    border-radius: var(--nvl-radius-sm);
    background: var(--nvl-color-success);
    color: var(--nvl-color-on-success);
    font-weight: var(--nvl-weight-bold);
}

@media (hover: hover) and (pointer: fine) {
    /* La paleta tiene UN SOLO tono oscuro. Sin mezcla, el hover de una
       superficie de tinta solo podría expresarse moviendo la pieza, y eso no
       lee como "esto responde al cursor". color-mix() con dos colores OFICIALES
       entrega el segundo escalón de la misma tinta sin inventar un hex.
       Resultado #222a3b: 14.36:1 con el blanco, 9.67:1 con la tinta atenuada,
       10.11:1 con el amarillo. Si el navegador no lo entiende, la declaración se
       descarta y quedan el levantamiento y la sombra, que ya señalan el estado. */
    /* EL HOVER NO CAMBIA DE COLOR (owner, 2026-10-19): «cuando el puntero toca la
       barra, adquiere un tono grisáceo/blanquecino en el fondo y ese efecto no
       me gusta». Era el color-mix de arriba, que resolvía a #222a3b. Fuera, y
       fuera también el aclarado del disco, porque pidió eliminar el cambio de
       color «por completo».

       Queda lo que sí quiso conservar: el brinco de 2px. Y con él la sombra, que
       NO es un cambio de color de la pieza sino lo que hace legible el brinco —
       un objeto que se levanta sin que su sombra se abra no se lee como que se
       levanta, se lee como que se ha movido—. */
    .nvl-cart-bar:hover {
        --nvl-bar-shift: -2px;
        box-shadow: var(--nvl-shadow-relief-h);
    }
}

/* En táctil no hay hover: este es el único acuse de que el objeto recibió el
   gesto. #2c3344 — 12.62:1 con el blanco, 8.50:1 con la tinta atenuada.
   EL OBJETO SE HUNDE (2026-10-18): 1px hacia abajo, contacto cerrado, elevación
   colapsada y el filo apagado de 22% a 12%. Antes se quedaba quieto y solo se
   aclaraba, que es físicamente al revés —un objeto pulsado se mete en sombra—.
   El acuse lo lleva el FILO, no el relleno: #444a59 → #2c3344 es 1.42:1 en una
   línea de 1px, mientras que oscurecer la tinta daría 1.09:1. */
.nvl-cart-bar:active {
    --nvl-bar-shift: 1px;
    background-color: color-mix(in srgb, var(--nvl-color-secondary) 88%, var(--nvl-color-surface));
    box-shadow: var(--nvl-shadow-relief-a);
}

.nvl-cart-bar:active .nvl-cart-bar__chip {
    background: var(--nvl-color-overlay-strong);
}

/* El foco NO redefine --nvl-focus-color, a diferencia del hero, la banda CTA y
   el pie. Aquellos lo suben a --nvl-color-accent porque sus anillos caen SOBRE
   la tinta; aquí, con --nvl-focus-offset, el anillo cae sobre la PÁGINA CLARA,
   donde ese acento daría 2.14:1 y fallaría. El valor global (--nvl-color-primary)
   es el único que cumple contra los tres fondos de página y también contra la
   propia cápsula por si el anillo la roza. Regla escrita para que nadie copie
   aquí el patrón de las bandas oscuras. */

/* Carrito vacío, aviso de agregado en pantalla, o panel lateral abierto: la
   cápsula no existe. Las CUATRO declaraciones son UN PAQUETE — opacity:0 no
   saca del árbol de accesibilidad ni desactiva el clic, y sin visibility un
   lector de pantalla encontraba aquí un botón "Abrir el carrito" invisible.
   Eso es además lo que hace seguro haber retirado el tabindex="-1".

   La visibility se retrasa hasta DESPUÉS del desplazamiento; al volver es
   inmediata (transición de 0s sin retardo, arriba).

   Sale más rápido de lo que entra y acelerando: lo que se va no merece la misma
   ceremonia que lo que llega. */
.nvl-cart-bar.nvl-is-empty,
.nvl-cart-toast.nvl-is-visible ~ .nvl-cart-bar,
body.nvl-is-drawer-open .nvl-cart-bar {
    --nvl-bar-shift: calc(100% + var(--nvl-space-lg));
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
        opacity    var(--nvl-duration-base) var(--nvl-ease-in),
        transform  var(--nvl-duration-base) var(--nvl-ease-in),
        visibility 0s linear var(--nvl-duration-base);
}

/* Con el panel abierto, más rápido todavía. El panel entra por la derecha y
   cubre la parte inferior de la pantalla donde vive la cápsula: a 200ms el
   cliente vería el panel pasando POR ENCIMA de una cápsula todavía visible,
   que es justo lo que había que evitar. Al cerrarse vuelve con la entrada
   normal de 300ms — la reaparición sí se celebra. */
body.nvl-is-drawer-open .nvl-cart-bar {
    transition:
        opacity    var(--nvl-duration-fast) var(--nvl-ease-in),
        transform  var(--nvl-duration-fast) var(--nvl-ease-in),
        visibility 0s linear var(--nvl-duration-fast);
}

/* Botón flotante de WhatsApp (plugin JoinChat, raíz .joinchat, z-index 9000):
   se retira mientras el panel del carrito está abierto, porque queda por encima
   de sus acciones («Limpiar todo», «Finalizar compra»). Al cerrarlo vuelve tal
   como estaba —con su ventana de chat
   abierta, si lo estaba—. Su POSICIÓN no se toca: se ajusta desde el plugin.

   opacity + visibility y NO display: display lo gobierna el plugin
   (.joinchat--show) y conmutarlo repetiría su animación de entrada con rebote
   cada vez que se cierra el carrito. visibility además lo saca del tabulador.
   Sin !important: este selector pesa más que los del plugin sobre su raíz, y
   el plugin no declara ni opacity ni visibility.
   Aquí y no en nvl-cart-summary.css ni en nvl-order.css: esta es la única hoja
   que carga siempre. La clase del body solo existe donde hay carrito. */
body.nvl-is-drawer-open .joinchat {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* Mismo ritmo que la cápsula: se va en 120ms, vuelve en 300ms. Bajo movimiento
   reducido no hay transición y aparece y desaparece de golpe. */
@media (prefers-reduced-motion: no-preference) {
    body .joinchat {
        transition: opacity var(--nvl-duration-slow) var(--nvl-ease-out), visibility 0s;
    }

    body.nvl-is-drawer-open .joinchat {
        transition:
            opacity    var(--nvl-duration-fast) var(--nvl-ease-in),
            visibility 0s linear var(--nvl-duration-fast);
    }
}

/* bumpLaunchers() pone .nvl-is-bumped y la retira escuchando animationend. La
   cifra de la cápsula ya no lleva la clase del contador de la cabecera, así que
   se quedaría sin animación, el evento no llegaría nunca y la clase se quedaría
   pegada. Animación de cambio cero que SÍ termina — mismo patrón que ya resolvió
   esto bajo movimiento reducido en nvl-components.css.

   Y un pulso de verdad aquí sería una animación que nadie vería jamás: toda ruta
   que llama a bumpLaunchers() llama también a showToast(), y el aviso oculta la
   cápsula. El acuse del agregado es la REENTRADA, que llega con la cifra, el
   titular y el importe ya nuevos, cuando el cliente ha vuelto a mirar. */
@keyframes nvl-bar-bump-hold {
    from { opacity: 1; }
    to   { opacity: 1; }
}

.nvl-cart-bar__count.nvl-is-bumped {
    animation: nvl-bar-bump-hold var(--nvl-duration-fast) steps(1) both;
}

@media (min-width: 768px) {
    /* Escritorio: centrada abajo —el centrado lo hace margin-inline: auto en la
       regla base— y a --nvl-space-lg del borde inferior. Mismo ancho de 420px.
       No hay un tercer escalón: estirarla más abriría un vacío entre el contador
       y el importe —el mismo aire muerto, en horizontal—. En pantalla grande lo
       que crece es el aire ALREDEDOR del objeto. */
    .nvl-cart-bar {
        inset-block-end: calc(env(safe-area-inset-bottom, 0px) + var(--nvl-space-lg));
        padding: var(--nvl-space-sm) var(--nvl-space-md);
    }
}

@media (forced-colors: active) {
    .nvl-cart-bar {
        border-color: CanvasText;
    }

    /* El relleno de blanco al 12% se descarta en modo forzado y el disco
       desaparecería sin un filo propio. */
    .nvl-cart-bar__chip {
        border: var(--nvl-border-hairline) solid CanvasText;
    }

    .nvl-cart-bar__message.nvl-is-win .nvl-cart-bar__label {
        border-color: CanvasText;
    }
}


.nvl-site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--nvl-space-2xs) var(--nvl-space-lg);
    padding-top: var(--nvl-space-sm);
    padding-bottom: var(--nvl-space-sm);
    /* El body hereda line-height: var(--nvl-leading-relaxed) (1.6); sin
       corregirlo aquí, la marca (20px) infla su caja de línea a 32px y el
       header renderizado queda en ~65px, muy por encima de --nvl-header-h
       (3.5rem = 56px) que consume el cálculo del asomo del hero de la Home
       (§4.2). Verificado contra header.php: con este ajuste, padding
       (16+16) + línea (20px × 1.2 ≈ 24px) + borde (1px) ≈ 57px — coincide
       con el token dentro de 1px. */
    line-height: var(--nvl-leading-snug);
}

.nvl-site-header__brand {
    font-size: var(--nvl-text-lg);
    font-weight: var(--nvl-weight-extrabold);
    color: var(--nvl-color-secondary);
    text-decoration: none;
}

.nvl-site-nav__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nvl-space-3xs) var(--nvl-space-md);
    list-style: none;
}

.nvl-site-nav__list a {
    color: var(--nvl-color-text-primary);
    /* En REGULAR (tres pesos, 2026-10-17; venía de 500, y antes de 600). Una
       navegación al peso del botón de compra competiría con él: el bold queda
       para la acción, y aquí el estado lo lleva el color del hover. */
    font-weight: var(--nvl-weight-regular);
    text-decoration: none;
}

.nvl-site-nav__list a:hover {
    color: var(--nvl-color-primary);
}

.nvl-main--fallback {
    /* index.php es la ÚNICA vista sin ritmo vertical propio. Desde que el pie
       se separa por dentro y no con un margen, su contenido quedaría pegado al
       filo de la tinta. */
    padding-block: var(--nvl-space-2xl);
}

/* --- Pie de página global (components/site-footer.php) --------------------
   «El mostrador cerrado de la tienda»: ordena e informa, no llama. El
   protagonista es la retícula de información agrupada —no el logo, que sería
   una portada, ni el WhatsApp, que sería un segundo CTA a 56px del de la banda
   que tiene justo encima—. Especificación: docs/pie-global-especificacion.md.

   SIN MARGEN SUPERIOR, Y UNA COSTURA EN SU LUGAR (owner, 2026-10-13). El margen
   dejaba ver el --nvl-color-background del body, y entre dos bandas del mismo
   material una franja gris no lee como separación, lee como corte. El filete
   de 1px al 12% hace de pliegue sobre tinta y es imperceptible bajo una
   sección clara, donde el salto blanco→tinta (17.85:1) ya separa. Una regla,
   dos comportamientos correctos, cero ramas.

   ACTUALIZADO EL 2026-10-22: el caso que originó la costura ya no se da en la
   Home ni en categoría. La sección de encima era .nvl-cta-band, tinta de borde
   a borde, y el owner reportó que se fundía con el pie —«ambas secciones se
   perciben como una sola»—. Esa tinta se mudó DENTRO de una caja flotante
   sobre sección blanca, así que ahora el pie recibe por arriba una superficie
   clara y quien separa es el propio salto de material. La costura se queda:
   sigue siendo correcta ahí y en cualquier vista que vuelva a dejar tinta
   encima, y no cuesta nada donde no hace falta.

   DOS COLUMNAS DESDE EL 2026-10-15 (owner): marca, Catálogo y Políticas. El
   tercer bloque —«Envíos y entrega», con el recuento de destinos, el umbral y
   la nota— se retiró porque esa información la explica la ficha de producto, y
   la Política de Envíos se mudó a la columna legal, con las otras dos.
   LA RETÍCULA NO NECESITÓ NI UNA REGLA: el `repeat(auto-fit, minmax(...))` de
   más abajo resuelve dos pistas solo, y deja marca, Catálogo y Políticas a
   intervalos casi idénticos —32, 447 y 876 px medidos a 1320—. Con él se
   fueron .nvl-site-footer__facts, __fact y la repetición de .nvl-icon--fixed,
   que no tenían ya ningún marcado detrás.

   DOS TINTAS DE TEXTO, una por nivel: --nvl-color-surface (dato o título,
   17.85:1) y --nvl-color-text-inverse-muted (cuerpo y enlaces, 12.02:1).
   --nvl-color-text-secondary da 3.75:1 sobre esta tinta y no aparece ni una
   vez. --nvl-color-accent ERA LA TERCERA mientras existieron los tres iconos
   de entrega; hoy el cuerpo del pie no lo usa y le quedan sus dos trabajos de
   siempre: el anillo de foco y los estados del enlace de WhatsApp.

   TODA LA CSS DEL PIE VIVE AQUÍ, en la única hoja incondicional del tema
   (inc/assets.php:68-74), y por eso el bloque REPITE ACOTADA la regla de
   .nvl-icon, que vive en una hoja condicional, igual que ya hace la barra
   promocional más arriba. Sin esa repetición el icono de WhatsApp se pintaría
   negro sobre la tinta en cualquier vista sin nvl-icons.css. */
.nvl-site-footer {
    margin-block-start: 0;
    border-block-start: var(--nvl-border-hairline) solid var(--nvl-color-overlay-light);
    /* El pie CIERRA JUSTO DESPUÉS DE LAS PLACAS (owner, 2026-10-16): 24px, y ni
       uno más mientras no haga falta. */
    padding-block: var(--nvl-space-2xl) var(--nvl-space-lg);
    background: var(--nvl-color-secondary);
    color: var(--nvl-color-text-inverse-muted);
}

/* RESERVA DINÁMICA, Y SOLO CUANDO LA CÁPSULA SE VE (owner, 2026-10-16). Antes
   era fija: 96px en todas las vistas, también con el carrito vacío, que es el
   estado de casi toda visita. El owner pidió un pie compacto que habilite el
   hueco únicamente cuando la cápsula aparece, y que lo devuelva al vaciarse.

   LO HACE LA CASCADA, SIN JS NUEVO. La cápsula se oculta con .nvl-is-empty
   —más abajo en este mismo archivo—, que el JS conmuta según el recuento, y
   :has() lo lee en vivo: el cambio va en los dos sentidos solo.

   Validado en el navegador, con la página confirmada al final del documento:
   vacío 24px y nada tapado; con producto 140px y la cápsula por debajo del
   cierre, con 18px de holgura sobre las placas en escritorio y 22 sobre el
   copyright a 390. */
body:has(.nvl-cart-bar:not(.nvl-is-empty)) .nvl-site-footer {
    padding-block-end: var(--nvl-footer-cart-reserve);
}

/* Repetición acotada de nvl-icons.css: el pie se imprime en vistas que no la
   cargan. Mismo patrón y misma razón que la barra promocional. */
.nvl-site-footer .nvl-icon {
    width: var(--nvl-icon-inline);
    height: var(--nvl-icon-inline);
    flex-shrink: 0;
    fill: currentColor;
}


/* --- Zona 1 + 2 ---------------------------------------------------------- */
.nvl-site-footer__top {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--nvl-space-2xl);
}

/* LOS CUATRO BLOQUES EN UNA FILA, PEGADOS, Y EL GRUPO CENTRADO (owner,
   2026-10-16). Antes el carril se repartía en pistas de 1fr y entre el final de
   «Venta de peces» y el inicio de «POLÍTICAS» quedaban 319px de hueco muerto,
   medidos. `max-content` es la pieza: cada pista mide lo que su texto, no lo
   que el carril le reparta, y el hueco baja a 56.

   DESDE 1120 Y NO 1024: medido, con tres columnas y el eslogan la fila se
   desborda 87px a 1024. Por debajo de ese ancho la marca se pone encima de las
   columnas, que es lo que el `__top` de una sola pista ya hacía.

   :has() y no una regla fija: un pie SIN retícula —marca nueva, nada
   configurado todavía— dejaría la marca encogida a la izquierda con pistas
   vacías a la derecha. Con esto, sin retícula el bloque de marca se queda solo
   y centrado. */
@media (min-width: 1120px) {
    .nvl-site-footer__top:has(.nvl-site-footer__cols) {
        grid-template-columns: max-content max-content;
        justify-content: center;
        align-items: start;
        column-gap: var(--nvl-space-3xl);
    }
}

.nvl-site-footer__brand-block {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--nvl-space-sm);
}

/* El logo es arte propio sobre su propia banda de tinta: SIN placa blanca. Una
   placa de 180×56 sería el rectángulo más brillante del pie y convertiría la
   firma en un recorte pegado. (Los logos de pago sí la llevan, y la asimetría
   es la decisión: ese arte es de terceros y de polaridad desconocida.) */
.nvl-site-footer__brand {
    display: inline-flex;
    align-items: center;
    min-height: var(--nvl-tap-min);
    color: var(--nvl-color-surface);
    font-size: var(--nvl-text-lg);
    font-weight: var(--nvl-weight-extrabold);
    line-height: var(--nvl-leading-snug);
    text-decoration: none;
}

/* El tamaño VISUAL del logo lo fija el CSS, nunca el HTML. */
.nvl-site-footer__logo {
    display: block;
    block-size: var(--nvl-space-2xl);
    inline-size: auto;
    max-inline-size: 100%;
}

@media (min-width: 768px) {
    .nvl-site-footer__logo {
        block-size: var(--nvl-space-3xl);
    }
}

/* A PARTIR DE 1120 LA MARCA SUBE SU RECORTE, y solo a partir de ahí: es el
   único ancho en el que la retícula pone el logo al lado de los títulos de
   columna. Por debajo la marca va apilada ENCIMA y no hay nada con lo que
   alinearla, así que el recorte ahí solo acercaría el logo a la costura.
   EL UMBRAL VA ATADO al de la fila, más arriba: si uno se mueve, el otro
   también, o el recorte se aplica donde no hay títulos (owner, 2026-10-16).
   Va en el BLOQUE, no en el enlace ni en la imagen: el enlace lleva
   `min-height: var(--nvl-tap-min)` y `align-items: center`, y un margen
   negativo ahí dentro se pelearía con los dos. Al bloque entero lo mueve
   limpio dentro de su pista. */
@media (min-width: 1120px) {
    .nvl-site-footer__brand-block {
        margin-block-start: var(--nvl-footer-logo-trim);
    }
}

.nvl-site-footer__about {
    margin: 0;
    /* Sin tope, la descripción se estiraría a 96 caracteres en el bloque de
       marca a ancho completo de tablet. Token propio y no --nvl-measure-narrow
       desde que hay tres columnas (owner, 2026-10-16): con el tope viejo la
       marca medía 404px y la fila no cabía ni a 1320. */
    max-width: var(--nvl-measure-footer-brand);
    font-size: var(--nvl-text-sm);
    line-height: var(--nvl-leading-relaxed);
}

.nvl-site-footer__contact {
    margin: 0;
    font-size: var(--nvl-text-sm);
    line-height: var(--nvl-leading-normal);
}

/* Subrayado YA EN REPOSO, al contrario que los enlaces de lista: marca el dato
   que se quiere copiar o marcar y lo distingue de la navegación sin gastar un
   cuarto color. No es un botón: ni relleno, ni radio, ni sombra. */
.nvl-site-footer__wa {
    display: inline-flex;
    align-items: center;
    gap: var(--nvl-space-2xs);
    min-height: var(--nvl-tap-min);
    color: var(--nvl-color-surface);
    font-weight: var(--nvl-weight-bold);
    text-decoration: underline;
    text-underline-offset: var(--nvl-space-3xs);
    transition: color var(--nvl-duration-fast) var(--nvl-ease-standard);
}

.nvl-site-footer__cols {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--nvl-space-xl);
}

@media (min-width: 768px) {
    /* `repeat(3, max-content)` centrado, no `auto-fit … 1fr`: las pistas miden
       su texto y las tres columnas se leen como un grupo, no como tres bloques
       repartidos por el ancho. Se escribe 3 porque son tres y el pie no admite
       una cuarta; si alguna falta, `justify-content` recentra las que queden. */
    .nvl-site-footer__cols {
        grid-template-columns: repeat(3, max-content);
        justify-content: center;
        column-gap: var(--nvl-space-3xl);
    }
}

/* Reproduce .nvl-eyebrow (nvl-elements.css), que vive en una hoja condicional,
   con el único cambio del color: sobre tinta, blanco puro. Sin caja alta, como
   su original desde el 2026-10-17: si uno cambia, cambia el otro. */
.nvl-site-footer__coltitle {
    margin: 0 0 var(--nvl-space-xs);
    color: var(--nvl-color-surface);
    font-size: var(--nvl-text-xs);
    font-weight: var(--nvl-weight-bold);
    line-height: var(--nvl-leading-heading);
}

.nvl-site-footer__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Los enlaces son controles: 44px de objetivo táctil, sin excepciones. El
   ritmo más suelto que el de un pie corriente es la señal de que este bloque
   se NAVEGA; los hechos, que se LEEN, van a 12px de separación. */
.nvl-site-footer__link {
    display: flex;
    align-items: center;
    min-height: var(--nvl-tap-min);
    padding-block: var(--nvl-space-2xs);
    color: var(--nvl-color-text-inverse-muted);
    font-size: var(--nvl-text-sm);
    line-height: var(--nvl-leading-snug);
    text-decoration: none;
    transition: color var(--nvl-duration-fast) var(--nvl-ease-standard);
}


/* --- Zona 3: cierre ------------------------------------------------------
   VA A SANGRE, FUERA DEL CONTENEDOR (owner, 2026-10-16). El owner pidió la
   línea divisoria «de lado a lado de la pantalla», y .nvl-container está
   acotado a --nvl-container-max y centrado: medido a 1440, el filete se
   quedaba a 92px de cada borde. Por eso este bloque es hijo directo del
   <footer> y lleva su propio contenedor dentro: el borde mide la ventana y el
   texto sigue parando donde para el resto del sitio.

   `padding-block: md 0`: nada por debajo. Lo que separa el pie del filo de la
   página es el `padding-block-end` del <footer>, que es quien reserva —o no—
   el sitio de la cápsula. */
.nvl-site-footer__bottom {
    display: block;
    margin-block-start: var(--nvl-space-lg);
    padding-block: var(--nvl-space-md) 0;
    border-block-start: var(--nvl-border-hairline) solid var(--nvl-color-overlay-light);
}

/* A LOS EXTREMOS, NO CENTRADO (owner, 2026-10-16): copyright a un lado, placas
   al otro, aprovechando el ancho.

   row-reverse y no `order`: es una zona SIN ningún elemento enfocable, así que
   el orden visual y el de tabulación no pueden divergir. En el DOM los logos
   van antes para que en móvil la línea legal sea la última del documento; aquí
   el copyright vuelve a la izquierda, que es la convención.

   `flex-wrap` para que a 390 se apilen en vez de apretarse. */
.nvl-site-footer__bottom-inner {
    display: flex;
    flex-direction: row-reverse;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--nvl-space-sm);
}

.nvl-site-footer__pay {
    display: flex;
    flex-wrap: wrap;
    gap: var(--nvl-space-2xs);
    margin: 0;
    padding: 0;
    list-style: none;
}

/* LA PLACA NORMALIZA EL ALTO, NO LA IMAGEN: es lo que ya hace la tarjeta de
   transferencia, y es lo que mantiene legible un logo de terceros de polaridad
   desconocida sobre la tinta. El filo transparente queda reservado para
   forced-colors. */
.nvl-site-footer__pay-item {
    display: inline-flex;
    align-items: center;
    block-size: var(--nvl-space-xl);
    padding-inline: var(--nvl-space-2xs);
    border: var(--nvl-border-hairline) solid transparent;
    border-radius: var(--nvl-radius-xs);
    background: var(--nvl-color-surface);
}

.nvl-site-footer__pay-logo {
    display: block;
    block-size: var(--nvl-space-md);
    inline-size: auto;
    /* Un logo muy apaisado se encaja en la caja en vez de reventar la fila. */
    max-inline-size: var(--nvl-space-5xl);
    object-fit: contain;
}

.nvl-site-footer__copy {
    margin: 0;
    font-size: var(--nvl-text-2xs);
    line-height: var(--nvl-leading-normal);
}

/* --- Estados ------------------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
    /* El subrayado es la señal SIN COLOR del estado, y por eso aparece de
       golpe: text-decoration no se transiciona. */
    .nvl-site-footer__link:hover {
        color: var(--nvl-color-surface);
        text-decoration: underline;
        text-underline-offset: var(--nvl-space-3xs);
    }

    .nvl-site-footer__wa:hover {
        color: var(--nvl-color-accent);
    }
}

.nvl-site-footer__link:active,
.nvl-site-footer__wa:active {
    color: var(--nvl-color-accent);
}

/* El logo NO se atenúa al pasar el cursor: degradar la firma de la marca no
   comunica nada que el cursor no diga ya. La afordancia la dan el cursor y el
   anillo de foco. */

@media (prefers-reduced-motion: reduce) {
    /* El bloque de movimiento reducido del tema vive en una hoja condicional:
       el pie declara el suyo. Como no hay animaciones ni contrato con JS,
       `none` es la variante completa y no deja ningún estado pegado. */
    .nvl-site-footer__link,
    .nvl-site-footer__wa {
        transition: none;
    }
}

@media (forced-colors: active) {
    /* Los dos únicos recintos cuya legibilidad depende del fondo que tienen
       detrás: el logo de la tienda es arte pálido pensado para tinta y sobre un
       Canvas blanco desaparecería; los logos de pago son arte de terceros. Se
       conserva su fondo y SOLO el suyo. Nada se pierde: las dos imágenes llevan
       alt. La costura y el filete son `border`, así que el modo los sustituye y
       la separación de las tres zonas sobrevive. */
    .nvl-site-footer__brand {
        forced-color-adjust: none;
        background-color: var(--nvl-color-secondary);
    }

    /* CADA UNO CONSERVA EL SUYO, y no el mismo: la placa de pago es blanca en
       reposo, y pintarla de tinta en modo forzado dejaría un logo oscuro de
       terceros sobre fondo oscuro, que es justo lo que la placa existe para
       evitar. */
    .nvl-site-footer__pay-item {
        forced-color-adjust: none;
        background-color: var(--nvl-color-surface);
    }
}
