/* ==========================================================================
   Base
   ========================================================================== */

body {
  font-family: var(--font-body);
  color: var(--color-text);
  background: var(--color-white);
}

h1,
h2,
h3 {
  font-family: var(--font-heading);
  font-weight: 700;
  line-height: 1.1;
}

.container {
  width: 100%;
  max-width: var(--container-max);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* ==========================================================================
   Botones
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 1rem;
  padding: 0.9rem 1.75rem;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: transform 250ms ease, box-shadow 250ms ease, background-color 250ms ease, border-color 250ms ease, color 250ms ease;
  /* Sin white-space:nowrap: un texto largo como "Ver catálogo de
     Mayorista & Comercios" en un botón inline-flex se dimensiona por
     contenido (shrink-to-fit) — con nowrap no tenía forma de achicarse
     en pantallas angostas y se salía del contenedor (y del viewport en
     iPhones reales, 375-430px) en vez de partir en 2 líneas. max-width
     de respaldo por si algún ancestro alguna vez deja de contener el
     ancho como corresponde. Ningún otro texto de botón del sitio es tan
     largo como para necesitar wrap hoy (se revisaron los 6 usos de .btn
     con texto propio) — este cambio no les afecta visualmente. */
  white-space: normal;
  max-width: 100%;
  text-align: center;
}

.btn-primary {
  background: var(--color-accent);
  color: var(--color-white);
  box-shadow: 0 10px 24px -8px rgba(var(--color-accent-rgb), 0.45);
}

.btn-primary:hover {
  background: var(--color-accent-dark);
  transform: translateY(-2px);
  box-shadow: 0 16px 32px -8px rgba(var(--color-accent-rgb), 0.55);
}

.btn-secondary {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-text);
}

.btn-secondary:hover {
  background: var(--color-text);
  color: var(--color-white);
  transform: translateY(-2px);
}

/* El look (fondo, blur, borde con highlight, sombra, hover) ahora lo dan
   .btn-glass + .btn-glass--accent (ver más abajo) — acá solo queda el
   ancho (ocupa todo el pie del panel del carrito) y el color de texto
   propio del estado deshabilitado. */
.btn-whatsapp {
  width: 100%;
}

.btn-whatsapp:disabled {
  color: var(--color-text-soft);
  text-shadow: none;
}

/* ==========================================================================
   Botón de vidrio (.btn-glass) — componente reusable

   Extiende el lenguaje "liquid glass" de la navbar del Home (ver "Header —
   vidrio esmerilado" en css/home.css) a un botón: blur real sobre lo que
   pasa detrás, fondo semitransparente y borde con reflejo de luz. Pensado
   para vivir sobre fondos de color (no solo blanco/gris) — por eso es MÁS
   transparente que la navbar (esa vive sobre fotos/hero, esta necesita
   dejarse atravesar por un verde de marca sin taparlo) y el highlight del
   borde superior es más marcado: es el detalle que más diferencia un
   vidrio "vivo" de un simple fondo blanco translúcido.

   Solo se usa en un botón de prueba por ahora (ver comentario en
   index.html, sección "aviso compra por volumen") — todavía NO se aplicó
   al resto de los CTAs del sitio a propósito, queda para un paso aparte
   una vez confirmado que este primer caso se ve bien.

   TÉCNICA DE 3 CAPAS (evita los artefactos de blur en las esquinas
   redondeadas que salen de mezclar blur + sombra + fondo en un solo
   elemento — mismo problema ya documentado con el halo del isotipo):
   1) .btn-glass (el <a>/<button> real): sin fondo propio, solo tamaño,
      tipografía y el texto/ícono real — siempre 100% nítido, nunca pasa
      por backdrop-filter.
   2) ::before → la SOMBRA, un pseudo-elemento aparte, boxshadow puro (sin
      fondo) en z-index:-2. Al no tener relleno, sus píxeles de sombra
      caen casi enteramente FUERA del box (el blur de la sombra) y no
      "se filtran" a través del backdrop-filter de la capa de vidrio de
      encima — evita el bug de la sombra tiñendo todo el vidrio de gris.
   3) ::after → la SUPERFICIE DE VIDRIO en sí, z-index:-1: backdrop-filter
      (blur) + fondo semitransparente + el highlight del borde, todo en
      un pseudo-elemento propio para que el corte (border-radius) quede
      limpio sin que el texto real pase por el blur.
   `isolation: isolate` en el elemento real evita que estos z-index
   negativos se escapen por debajo de otro elemento hermano fuera del
   botón (importante porque este componente se va a insertar en muchos
   lugares distintos del sitio).

   Contraste de texto: sobre un fondo saturado (ej. el verde de marca)
   ningún nivel de blanco translúcido realista llega al 4.5:1 de AA para
   texto normal sin dejar de ser "vidrio" — se compensa con un
   text-shadow oscuro y sutil (igual que ya se hace en los labels de las
   hojas del isotipo), que sube la legibilidad real sin oscurecer el
   fondo. Verificado con Playwright sobre el caso de prueba (ver memoria
   del proyecto): contraste numérico moderado pero texto claramente
   legible a simple vista gracias al text-shadow — igual o mejor que el
   CTA sólido que reemplaza.
   ========================================================================== */

.btn-glass {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4em;
  padding: 0.75rem 1.5rem;
  border-radius: 999px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  color: var(--color-white);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 200ms ease;
}

.btn-glass:hover {
  transform: translateY(-1px);
}

/* Deshabilitado: se apaga el vidrio (vuelve a un gris plano, sin blur ni
   brillo — mismo lenguaje que el resto del sitio para estados inactivos)
   en vez de mostrar un CTA "de vidrio" que en realidad no se puede tocar. */
.btn-glass:disabled {
  cursor: not-allowed;
}

.btn-glass:disabled::before {
  box-shadow: none;
}

.btn-glass:disabled::after {
  background: var(--color-surface);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* Sombra — ver punto 2) del comentario de arriba: separada a propósito,
   nunca box-shadow directo sobre el elemento con backdrop-filter. */
.btn-glass::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  border-radius: inherit;
  box-shadow: 0 10px 22px -8px rgba(15, 15, 15, 0.4);
  pointer-events: none;
}

/* Vidrio — ver punto 3). Tres fondos apilados:
   1. radial-gradient arriba de todo: el "brillo" concentrado cerca del
      borde superior (mismo recurso que el halo del isotipo), recortado a
      padding-box para no pisar el borde con gradiente de abajo.
   2. linear-gradient parejo, MUY translúcido (0.22→0.10 — bastante menos
      que el 0.6 fijo de la navbar): el relleno real del vidrio, también a
      padding-box.
   3. linear-gradient de arriba hacia abajo, más brillante arriba, pintado
      a BORDER-BOX con un border:1px transparent: el truco estándar de
      "borde con degradé" en CSS puro — como las dos capas de encima solo
      pintan el padding-box, este degradé solo queda visible en el anillo
      de 1px del borde, más claro arriba que abajo → el reflejo de luz en
      el canto pedido, siguiendo la curva de la píldora sin necesitar
      mask ni SVG. */
.btn-glass::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 1px solid transparent;
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0.1) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.08) 45%, rgba(255, 255, 255, 0.32) 100%) border-box;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transition: background-color 200ms ease;
  pointer-events: none;
}

.btn-glass:hover::after,
.btn-glass:focus-visible::after {
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.68) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 0.32) 0%, rgba(255, 255, 255, 0.16) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.14) 45%, rgba(255, 255, 255, 0.42) 100%) border-box;
}

.btn-glass:focus-visible {
  outline: 2px solid var(--color-white);
  outline-offset: 2px;
}

/* ==========================================================================
   .btn-glass--accent — variante con tinte de marca

   Para cuando el botón de vidrio necesita seguir leyéndose como CTA de
   acción principal (no un chip neutro) sin importar qué haya detrás: en
   vez del relleno blanco translúcido de la variante neutra, el relleno
   (capa 2 del ::after, ver comentario de arriba) usa el verde de marca a
   bastante más alpha. El highlight del borde y el brillo radial de arriba
   se dejan en blanco puro a propósito — la "luz" que se refleja en un
   vidrio no tiñe de color, tiñe el CUERPO del vidrio; así se lee como
   vidrio verde con brillo blanco, no como un verde liso con un borde
   blanco pegado encima.

   Por qué el alpha es tan alto acá (0.78-0.9) comparado con la variante
   neutra (0.10-0.22): estos 3 botones (reseñas, "Ver mi pedido",
   WhatsApp) viven casi siempre sobre fondos CLAROS (blanco/gris muy
   claro), no sobre el verde oscuro del banner de volumen donde se probó
   la variante neutra por primera vez. Medido con Playwright: blanco
   translúcido sobre un fondo claro pierde contraste cuanto MÁS
   transparente es (se acerca a blanco puro), al revés de lo que pasa
   sobre un fondo oscuro — así que acá "más vidrio" (menos alpha) sería
   peor para la legibilidad, no mejor. Con este alpha el
   contraste del texto blanco queda parejo con el que ya tenía el botón
   sólido que reemplaza (`.btn-primary`, ~3.5:1) sin perder del todo la
   transparencia ni el blur. */
.btn-glass--accent::before {
  box-shadow: 0 10px 24px -8px rgba(var(--color-accent-rgb), 0.5);
}

.btn-glass--accent::after {
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.55) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(var(--color-accent-rgb), 0.9) 0%, rgba(var(--color-accent-rgb), 0.78) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.08) 45%, rgba(255, 255, 255, 0.32) 100%) border-box;
}

.btn-glass--accent:hover::after,
.btn-glass--accent:focus-visible::after {
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.68) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(var(--color-accent-rgb), 0.96) 0%, rgba(var(--color-accent-rgb), 0.86) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.14) 45%, rgba(255, 255, 255, 0.42) 100%) border-box;
}

/* #cart-whatsapp-btn — mismo .btn-glass--accent, pero acá el fondo real
   detrás no es un color de marca: .cart-panel es blanco sólido
   (--color-white) en las 6 páginas. Contra blanco, el tinte estándar
   (--color-accent, un verde medio-claro) NUNCA llega a 4.5:1 con texto
   blanco encima, ni siquiera al 100% opaco (techo teórico ≈3.5:1,
   medido: el mismo ~3.49:1 que ya tenía el .btn-primary sólido que este
   componente reemplaza) — el problema no es la opacidad, es que el verde
   de marca es demasiado claro para ese contraste sobre blanco. Se usa acá
   --color-accent-dark (más oscuro, ya existente como color de :hover en
   otros botones del sitio) a alpha alta en vez de --color-accent: mide
   ≈5:1 contra blanco, dentro del mismo sistema visual, y sigue siendo
   "vidrio" (blur, brillo del borde y highlight radial sin cambios) solo
   que con menos transparencia en el relleno. `:not(:disabled)` evita que
   esta regla (más específica por el id) le gane a `.btn-glass:disabled`. */
#cart-whatsapp-btn:not(:disabled)::after {
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.45) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(var(--color-accent-dark-rgb), 1) 0%, rgba(var(--color-accent-dark-rgb), 0.98) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.08) 45%, rgba(255, 255, 255, 0.32) 100%) border-box;
}

#cart-whatsapp-btn:not(:disabled):hover::after,
#cart-whatsapp-btn:not(:disabled):focus-visible::after {
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.58) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(var(--color-accent-dark-rgb), 1) 0%, rgba(var(--color-accent-dark-rgb), 0.99) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0.14) 45%, rgba(255, 255, 255, 0.42) 100%) border-box;
}

/* ==========================================================================
   .glass-surface — superficie de vidrio sin comportamiento de botón

   Misma técnica de 3 capas que .btn-glass (sombra separada en ::before,
   blur + relleno + highlight de borde en ::after) para elementos que
   necesitan el mismo lenguaje visual pero NO son interactivos — hoy los 3
   chips "5L/20L/200L" de Mayorista & Comercios (.nivel-step). A
   diferencia de .btn-glass, esta clase no define tipografía, cursor,
   padding ni estado hover: el elemento que la use ya trae su propio
   layout (el propio .nivel-step define su padding/gap/border-radius, ver
   css compartido más abajo) y esta clase solo le suma el fondo.

   Relleno oscuro (no blanco) a propósito: estos chips viven sobre una
   tarjeta blanca (.nivel-card, fondo 100% blanco) — blanco translúcido
   sobre blanco no se distingue de nada (mismo problema que un vidrio
   transparente contra una pared blanca: no hay con qué contrastar). Un
   tinte oscuro muy sutil (alpha 0.035-0.09) sí se despega del blanco de
   la tarjeta, y de paso hace que el highlight de arriba (que sigue siendo
   blanco puro) se note contra ESE relleno, no contra la página. */
.glass-surface {
  position: relative;
  isolation: isolate;
}

.glass-surface::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  border-radius: inherit;
  box-shadow: 0 6px 14px -6px rgba(20, 20, 20, 0.16);
  pointer-events: none;
}

.glass-surface::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  border: 1px solid transparent;
  background:
    radial-gradient(120% 140% at 50% -30%, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0) 55%) padding-box,
    linear-gradient(180deg, rgba(15, 20, 18, 0.035) 0%, rgba(15, 20, 18, 0.09) 100%) padding-box,
    linear-gradient(180deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.15) 45%, rgba(255, 255, 255, 0.4) 100%) border-box;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  pointer-events: none;
}

/* ==========================================================================
   Header
   ========================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: var(--header-height);
  display: flex;
  align-items: center;
  background: var(--color-white);
  border-bottom: 1px solid var(--color-surface);
  transition: background-color 300ms ease, border-color 300ms ease;
}

.logo-img-white {
  display: none;
}

/* En Home el header flota sobre el hero (necesita overlap real, no solo
   fondo transparente): position:sticky reserva su propio alto en el flujo
   normal, así que el hero arrancaba DEBAJO del header en vez de detrás —
   la foto nunca llegaba a estar detrás del header "transparente". */
body.home .site-header {
  position: fixed;
  left: 0;
  right: 0;
}

body.home {
  /* Verde oscuro derivado de --color-accent (#3f9b51), no el mismo tono
     — sensiblemente más oscuro para dar contraste de texto sobre el
     fondo claro del hero (#e2e2e2). Contraste calculado ~6.7:1, por
     encima del mínimo de accesibilidad para texto normal (4.5:1).
     Se usa para el H1 (css/home.css) y para el anillo de foco del
     isotipo — YA NO para el header (ver nota abajo). */
  --hero-dark-green: #1e5631;
}

/* Hasta acá llegaba el viejo header de dos estados (transparente sobre
   el hero + textos en --hero-dark-green mientras no había scroll, blanco
   opaco + textos en --color-text al superar el umbral de scroll, vía la
   clase .is-scrolled que sigue togglando js/main.js). Se retiró: el
   header del Home ahora es SIEMPRE la barra flotante de vidrio esmerilado
   (mismo aspecto en cualquier scroll, sin depender de .is-scrolled), así
   que los links/íconos vuelven a heredar el color estándar del sitio
   (--color-text / --color-accent, ya definidos más abajo en este archivo)
   en vez de necesitar un color propio para Home. Ver "Header — vidrio
   esmerilado" en css/home.css para el reemplazo completo. */

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

.logo-link {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

.logo-img {
  height: 28.8px;
  width: auto;
}

.main-nav {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.main-nav ul {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.nav-link {
  position: relative;
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--color-text);
  padding-bottom: 4px;
  transition: color 250ms ease;
}

.nav-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  width: 0;
  height: 2px;
  background: var(--color-accent);
  transition: width 250ms ease, background-color 250ms ease;
}

.nav-link:hover::after,
.nav-link[aria-current='page']::after {
  width: 100%;
}

.nav-link[aria-current='page'] {
  color: var(--color-accent);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.cart-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  transition: background-color 200ms ease;
}

.cart-toggle:hover {
  background: var(--color-surface-soft);
}

.cart-toggle svg {
  width: 22px;
  height: 22px;
  stroke: var(--color-text);
  transition: stroke 250ms ease;
}

.cart-count {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 18px;
  height: 18px;
  padding-inline: 4px;
  border-radius: 999px;
  background: var(--color-accent);
  color: var(--color-white);
  font-size: 0.65rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
}

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

.nav-toggle {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
}

/* Solo en punteros que realmente soportan hover (mouse/trackpad) — en
   touch, :hover no tiene "salida" (no hay mouseleave tras un tap), así
   que el navegador lo deja pegado activo hasta el próximo tap en
   cualquier otro lado. Resultado real, confirmado con Playwright en
   modo touch: el ícono se quedaba con un chip circular de fondo
   encendido apenas se abría el menú (el propio tap que lo abre dispara
   el :hover), leyéndose como un elemento de más, no como el mismo botón
   en dos estados. Sin este scope, el ícono queda IDÉNTICO abierto o
   cerrado en cualquier dispositivo táctil — que es lo pedido. */
@media (hover: hover) and (pointer: fine) {
  .nav-toggle:hover {
    background: var(--color-surface-soft);
  }
}

.nav-toggle svg {
  width: 24px;
  height: 24px;
  stroke: var(--color-text);
  transition: stroke 250ms ease;
}

/* ==========================================================================
   Panel de pedido (cart)
   ========================================================================== */

.cart-overlay {
  position: fixed;
  inset: 0;
  background: rgba(33, 33, 33, 0.4);
  z-index: 200;
  opacity: 0;
  pointer-events: none;
  transition: opacity 250ms ease;
}

.cart-overlay.is-open {
  opacity: 1;
  pointer-events: auto;
}

.cart-panel {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 201;
  width: min(420px, 100%);
  background: var(--color-white);
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  transform: translateX(100%);
  transition: transform 300ms cubic-bezier(0.25, 1, 0.5, 1);
}

.cart-panel.is-open {
  transform: translateX(0);
}

.cart-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-md);
  border-bottom: 1px solid var(--color-surface);
}

.cart-panel-header h2 {
  font-size: 1.1rem;
}

.cart-close {
  /* 44px: mínimo de área táctil (WCAG 2.5.5/2.5.8) — antes 36px. El ícono
     adentro (18px, ver .cart-close svg) no cambia de tamaño, solo crece el
     botón real que lo centra; hay sobra de espacio en .cart-panel-header
     (flex space-between contra el título), no genera overflow. */
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
}

.cart-close:hover {
  background: var(--color-surface-soft);
}

.cart-close svg {
  width: 18px;
  height: 18px;
  stroke: var(--color-text);
}

.cart-items {
  flex: 1;
  overflow-y: auto;
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.cart-empty {
  margin: auto;
  text-align: center;
  color: var(--color-text-soft);
  font-size: 0.95rem;
  padding: var(--space-lg);
}

.cart-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: var(--space-sm);
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-sm);
}

.cart-item-info {
  min-width: 0;
}

.cart-item-info p:first-child {
  font-weight: 600;
  font-size: 0.9rem;
}

.cart-item-info p:not(:first-child) {
  font-size: 0.8rem;
  color: var(--color-text-soft);
}

.cart-item-breakdown {
  margin-top: 2px;
}

.cart-item-qty {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex-shrink: 0;
}

.cart-item-qty span {
  white-space: nowrap;
}

.cart-item-qty button {
  /* 44px: mínimo de área táctil (WCAG 2.5.5/2.5.8) — antes 26px. Ver
     verificación de overflow en .cart-item (fila del carrito, el contexto
     más angosto de los 3 donde aparece este tipo de control). */
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-surface);
  border-radius: 999px;
  font-size: 0.9rem;
}

.cart-item-qty button:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.cart-item-remove {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  color: var(--color-text-soft);
  transition: background-color 200ms ease, color 200ms ease;
}

.cart-item-remove svg {
  width: 19px;
  height: 19px;
  stroke: currentColor;
}

.cart-item-remove:hover,
.cart-item-remove:focus-visible {
  background: var(--color-surface-soft);
  color: var(--color-text);
}

.cart-item-remove:active {
  background: var(--color-surface);
  color: var(--color-text);
}

.cart-panel-footer {
  padding: var(--space-md);
  border-top: 1px solid var(--color-surface);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.cart-liquid-notice {
  font-size: 0.78rem;
  color: var(--color-text-soft);
  background: var(--color-surface-soft);
  border-left: 3px solid var(--color-accent-cyan);
  border-radius: var(--radius-sm);
  padding: 0.6rem 0.75rem;
  line-height: 1.4;
}

/* ==========================================================================
   Feedback al agregar — toast + barra flotante "Ver mi pedido"
   ========================================================================== */

@keyframes qa-badge-bump {
  0% { transform: scale(1); }
  40% { transform: scale(1.35); }
  100% { transform: scale(1); }
}

.cart-count.is-bumping {
  animation: qa-badge-bump 420ms ease;
}

/* Centrado en pantalla, semi-transparente (no tapa contenido) y con un
   leve efecto de vidrio (blur) — desaparece solo, nunca bloquea clicks
   (pointer-events:none) mientras está visible. Entrada más lenta y con un
   ligerísimo "overshoot" (cubic-bezier con rebote) para que se sienta
   satisfactoria; salida más rápida y sin rebote (menos protagonismo al
   irse que al aparecer). */
.qa-toast {
  position: fixed;
  top: 50%;
  left: 50%;
  z-index: 250;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  max-width: min(360px, calc(100% - 2 * var(--space-md)));
  padding: 0.9rem 1.4rem;
  border-radius: var(--radius-md);
  background: rgba(33, 33, 33, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--color-white);
  font-size: 0.95rem;
  font-weight: 500;
  box-shadow: var(--shadow-lg);
  opacity: 0;
  pointer-events: none;
  transform: translate(-50%, calc(-50% + 10px)) scale(0.92);
  transition: opacity 200ms ease-in, transform 200ms ease-in;
}

.qa-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
  transition: opacity 380ms cubic-bezier(0.34, 1.56, 0.64, 1), transform 380ms cubic-bezier(0.34, 1.56, 0.64, 1);
}

.qa-toast svg {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  stroke: var(--color-accent-lime);
}

/* ==========================================================================
   Botón "Ver mi pedido"

   Dos variantes, pensadas para nunca superponerse al contenido que se
   scrollea (esa era la queja: tapaba cards y el bloque de niveles en
   Mayorista):
   - Home (sin .filtro-sidebar en la página): fijo, esquina inferior
     derecha — no hay sidebar de referencia en esa página.
   - Páginas de catálogo (.qa-order-bar--sidebar, insertado por
     js/main.js como último hijo de .filtro-sidebar): en desktop NO es
     position:fixed — es un elemento más del flujo normal de la sidebar,
     que ya es sticky y ya tiene su propia columna de 220px reservada en
     .catalogo-layout (grid-template-columns: 220px 1fr). Al vivir ahí
     adentro, es físicamente imposible que se superponga al grid de
     productos: ocupa espacio real en una columna que el grid ni toca.
   En mobile/tablet (≤1024px, la sidebar deja de ser sticky y pasa a ser
   una fila de pills) ambas variantes convergen a un FAB circular fijo en la
   esquina inferior derecha — position:fixed no depende de dónde vive el
   elemento en el DOM, así que esto funciona sin importar que el botón
   esté insertado dentro de .filtro-sidebar.
   ========================================================================== */

/* El look (fondo, blur, borde con highlight, sombra) lo dan .btn-glass +
   .btn-glass--accent (ver css más arriba) — acá queda solo lo que es
   layout/posición/animación de entrada propio de este botón flotante. */
.qa-order-bar {
  position: fixed;
  right: var(--space-md);
  bottom: var(--space-md);
  z-index: 150;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  padding: 0.85rem 1.5rem;
  border-radius: 999px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(16px) scale(0.96);
  transition: opacity 250ms ease, transform 250ms ease;
}

.qa-order-bar.is-visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}

.qa-order-bar svg {
  width: 20px;
  height: 20px;
  stroke: currentColor;
  flex-shrink: 0;
}

.qa-order-bar-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding-inline: 6px;
  border-radius: 999px;
  background: var(--color-white);
  color: var(--color-accent-dark);
  font-size: 0.8rem;
  font-weight: 700;
  /* .btn-glass le pone text-shadow blanco-sobre-oscuro a todo lo que esté
     adentro (pensado para el texto del botón, no para este badge, que ya
     tiene su propio fondo blanco sólido y no lo necesita — se ve manchado
     si se hereda). */
  text-shadow: none;
}

/* Variante de sidebar (desktop): sale del position:fixed y pasa a ser un
   elemento normal dentro de .filtro-sidebar, con el mismo ancho de esa
   columna. Sin transform de entrada — al ser parte del flujo no hace
   falta simular que "flota" hacia arriba.

   position:relative (no static) a propósito, aunque acá no se use ningún
   offset: .btn-glass necesita que EL BOTÓN sea el contenedor de
   posicionamiento de su propio ::before/::after (position:absolute,
   inset:0). Si esto quedara en static, esa regla (más tardía en el
   archivo, misma especificidad) le gana a la de .btn-glass y el botón
   deja de ser positioned — sus pseudo-elementos "vidrio" pasan a buscar
   el próximo ancestro posicionado real, que es .filtro-sidebar (sticky),
   y terminan estirados al tamaño de TODA la sidebar en vez del botón.
   position:relative sin offsets se comporta igual que static en el
   layout (sigue en flujo normal) pero sí resuelve esto. */
.qa-order-bar--sidebar {
  position: relative;
  width: 100%;
  margin-top: var(--space-lg);
  opacity: 1;
  transform: none;
}

/* Reserva espacio para que el botón fijo no tape el final del contenido
   (última fila de cards o footer) mientras está visible. Solo aplica a
   las variantes ancladas abajo (Home y el FAB mobile) — la variante de
   sidebar en desktop no es fixed, no le hace falta. */
body.has-order-bar {
  padding-bottom: 88px;
}

/* En mobile, el FAB inevitablemente pasa por encima de algún "Agregar +"
   en algún punto del scroll (layout de una columna, sin gutter libre) —
   se atenúa mientras se scrollea activamente para no interrumpir la
   lectura, y vuelve a su opacidad normal en cuanto el scroll se
   detiene (ver el listener de scroll en js/main.js). No aplica a la
   variante de sidebar en desktop (no es fixed, no tapa nada). */
.qa-order-bar:not(.qa-order-bar--sidebar).is-visible.qa-order-bar--scrolling {
  opacity: 0.35;
}

/* Mismo corte de 1024px que `.filtro-sidebar`/`.catalogo-layout` más abajo
   (ver "Sistema de breakpoints" en tokens.css) — a propósito el mismo
   valor: esta regla existe PORQUE la sidebar deja de ser sticky en ese
   punto, así que ambas tienen que cambiar de estado juntas o el botón
   quedaría fijo-flotante sobre una sidebar que todavía se comporta como
   sticky (o viceversa). */
@media (max-width: 1024px) {
  /* La sidebar deja de ser sticky acá (ver .filtro-sidebar), así que la
     variante "de sidebar" vuelve a comportarse como FAB fijo — igual
     que en Home, pero circular y sin texto para ocupar el mínimo
     posible de pantalla mientras se scrollea. */
  .qa-order-bar,
  .qa-order-bar--sidebar {
    position: fixed;
    left: auto;
    right: var(--space-md);
    top: auto;
    bottom: var(--space-md);
    width: 52px;
    height: 52px;
    padding: 0;
    margin-top: 0;
    opacity: 0;
    transform: translateY(16px) scale(0.9);
  }

  .qa-order-bar.is-visible,
  .qa-order-bar--sidebar.is-visible {
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  /* Acá ambas variantes son fixed (ver arriba), así que el atenuado por
     scroll aplica a las dos por igual. */
  .qa-order-bar.is-visible.qa-order-bar--scrolling,
  .qa-order-bar--sidebar.is-visible.qa-order-bar--scrolling {
    opacity: 0.35;
  }

  .qa-order-bar-label {
    display: none;
  }

  .qa-order-bar-count {
    position: absolute;
    top: -2px;
    right: -2px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .qa-toast,
  .qa-order-bar,
  .qa-order-bar--sidebar,
  .cart-count.is-bumping {
    transition: none;
    animation: none;
  }
}

/* ==========================================================================
   Footer
   ========================================================================== */

.site-footer {
  background: var(--color-text);
  color: var(--color-surface-soft);
  padding-block: var(--space-2xl) var(--space-lg);
}

.footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: var(--space-lg);
  padding-bottom: var(--space-lg);
  border-bottom: 1px solid rgba(226, 226, 226, 0.15);
}

.footer-logo-img {
  height: 30px;
  width: auto;
  margin-bottom: var(--space-sm);
}

.footer-tagline {
  font-size: 0.9rem;
  color: rgba(226, 226, 226, 0.7);
  max-width: 32ch;
}

.footer-col h3 {
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-white);
  margin-bottom: var(--space-sm);
}

.footer-col ul {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.footer-col a {
  font-size: 0.9rem;
  color: rgba(226, 226, 226, 0.75);
  transition: color 200ms ease;
}

.footer-col a:hover {
  color: var(--color-accent-lime);
}

.footer-col p {
  font-size: 0.9rem;
  color: rgba(226, 226, 226, 0.75);
}

.footer-bottom {
  padding-top: var(--space-md);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-sm);
  font-size: 0.8rem;
  color: rgba(226, 226, 226, 0.5);
}

/* ==========================================================================
   Responsive — nav / footer
   ========================================================================== */

@media (max-width: 1024px) {
  .main-nav {
    position: fixed;
    inset: var(--header-height) 0 0 0;
    background: var(--color-white);
    /* Esquinas superiores en 0 (se apoyan justo contra el borde inferior
       del header, sin radio ahí) y un radio grande solo abajo — el panel
       termina en una "hoja" redondeada en vez de un rectángulo cuadrado,
       para que se lea como la MISMA pieza que el header extendiéndose
       hacia abajo, no un cuadro aparte pegado debajo. */
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    /* Sombra propia del panel: la elevación de todo el conjunto (header +
       panel) queda a cargo de este único borde inferior mientras el menú
       está abierto — el header pierde su línea divisoria de abajo (ver
       :has() más abajo), así que no hay dos sombras/bordes compitiendo
       en la unión. */
    box-shadow: var(--shadow-lg);
    flex-direction: column;
    align-items: stretch;
    padding: var(--space-lg) var(--container-pad);
    transform: translateY(-8px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 200ms ease, transform 200ms ease;
    z-index: 99;
  }

  .main-nav.is-open {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }

  /* Sin esto, el header (fondo blanco + border-bottom de 1px) y el panel
     debajo se leen como dos formas apiladas con una costura entre ellas
     — aunque no haya hueco real, la línea divisoria rompe la continuidad.
     Con el menú abierto, esa línea desaparece y el panel (mismo fondo
     blanco, mismo ancho, sin radio arriba) queda como la extensión
     directa y sin corte del propio header. :has() evita tener que tocar
     JS solo para esto — ya soportado en todos los navegadores actuales. */
  .site-header:has(#main-nav.is-open) {
    border-bottom-color: transparent;
  }

  .main-nav ul {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-md);
  }

  .nav-link {
    font-size: 1.1rem;
  }

  .nav-toggle {
    display: inline-flex;
  }

  .footer-grid {
    grid-template-columns: 1fr;
  }

  .footer-bottom {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ==========================================================================
   Categoría — mini-hero
   ========================================================================== */

.category-hero {
  padding-block: var(--space-2xl) var(--space-xl);
  background: var(--color-surface-soft);
}

.category-hero h1 {
  font-size: clamp(1.9rem, 4vw, 2.8rem);
  letter-spacing: -0.01em;
  max-width: 20ch;
  margin-bottom: var(--space-md);
}

.category-hero-subhead {
  font-size: clamp(1rem, 1.6vw, 1.1rem);
  color: var(--color-text-soft);
  max-width: 62ch;
  margin-bottom: var(--space-lg);
}

.policy-callout {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  max-width: 62ch;
  padding: var(--space-sm) var(--space-md);
  background: var(--color-white);
  border-left: 4px solid var(--color-accent);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
}

.policy-callout svg {
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  stroke: var(--color-accent);
  margin-top: 2px;
}

.policy-callout p {
  font-size: 0.98rem;
  color: var(--color-text);
  line-height: 1.5;
}

.policy-callout strong {
  color: var(--color-accent-dark);
}

/* ==========================================================================
   Categoría — mini-hero: degradado verde (Hogar, Químicos, Mayorista &
   Comercios, Nosotros, Contacto — todas menos el Home, que tiene su propio
   sistema)

   Modificador opuesto a tocar `.category-hero` directamente: si algún día se
   revierte, alcanza con sacar la clase del <section>, sin tocar CSS.

   El degradado NO es lineal de punta a punta: es un linear-gradient base
   (lima -> medio) con dos radial-gradient "mancha de luz" superpuestos. El
   texto SIEMPRE queda sobre la zona donde domina --color-accent-dark
   (#347f43) o más oscuro, nunca sobre el verde lima puro ni sobre
   --color-accent (#3f9b51, techo de contraste ≈3.49:1 con blanco encima
   INCLUSO al 100% puro — mismo techo ya documentado con el botón de
   WhatsApp del carrito y el CTA del banner de Home, no hay degradado que lo
   arregle solo reposicionando). Confirmado con el usuario en el piloto de
   Hogar, ver [[project-hero-gradient-pilot-2026-09-17]].

   Posición/ángulo de la mancha por página vía custom properties (con los
   valores de Hogar, el piloto original, como default — así Hogar queda
   bit-a-bit igual sin necesitar su propia clase modificadora). Las otras 4
   páginas suman un modificador `--gradient-quimicos/-mayorista/-nosotros/
   -contacto` que solo pisa estas 3 variables, sin duplicar el resto de la
   regla. Todas mantienen el MISMO mecanismo de seguridad de contraste: radio
   vertical de la mancha en PX (no %), anclado en y:0px — el fade siempre
   termina dentro del padding-top fijo de la sección
   (calc(var(--header-height) + var(--space-2xl)), no cambia entre
   375/768/1280px) sin importar en qué % horizontal esté la mancha, así que
   variar el `x` entre páginas no puede reintroducir el bug de mobile donde
   el texto quedaba sobre el lima (medido: 2.4-3:1 antes de fijar el radio en
   px). Verificado con Playwright en las 5 páginas, no solo a ojo. */

.category-hero--gradient {
  --hero-blob1-x: 82%;
  --hero-blob2-x: 100%;
  --hero-linear-angle: 165deg;
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(42% 110px at var(--hero-blob1-x) 0px, rgba(141, 198, 65, 0.92) 0%, rgba(141, 198, 65, 0) 58%),
    radial-gradient(60% 150px at var(--hero-blob2-x) 0px, rgba(141, 198, 65, 0.3) 0%, rgba(141, 198, 65, 0) 55%),
    linear-gradient(var(--hero-linear-angle), #347f43 0%, #2c6c39 100%);
}

.category-hero--gradient-quimicos {
  --hero-blob1-x: 58%;
  --hero-blob2-x: 72%;
  --hero-linear-angle: 140deg;
}

.category-hero--gradient-mayorista {
  --hero-blob1-x: 20%;
  --hero-blob2-x: 4%;
  --hero-linear-angle: 205deg;
}

.category-hero--gradient-nosotros {
  --hero-blob1-x: 48%;
  --hero-blob2-x: 64%;
  --hero-linear-angle: 150deg;
}

.category-hero--gradient-contacto {
  --hero-blob1-x: 70%;
  --hero-blob2-x: 90%;
  --hero-linear-angle: 175deg;
}

.category-hero--gradient .container {
  position: relative;
  z-index: 1;
}

.category-hero--gradient h1,
.category-hero--gradient .category-hero-subhead {
  color: var(--color-white);
}

.category-hero--gradient h1 {
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.22);
}

.category-hero--gradient .category-hero-subhead {
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}

/* Grano/ruido reusable — pensado para vivir sobre CUALQUIER fondo de color
   sólido/degradado del sitio (no solo este hero), por eso es una clase
   aparte de `.category-hero--gradient` en vez de ir soldada a ella. Textura
   vía SVG feTurbulence (desaturada a blanco/negro con feColorMatrix), tileada
   a 120px, pintada en un ::before propio con mix-blend-mode:overlay a alpha
   bajo — así oscurece/aclara el fondo real de abajo en vez de taparlo con un
   gris plano. El ::before es position:absolute (queda por encima del resto
   en el orden de pintado por default), por eso el contenido real de estos
   heros levanta su propio z-index (ver `.category-hero--gradient .container`
   arriba) para no terminar con grano encima del texto. */
.hero-grain::before {
  content: '';
  position: absolute;
  inset: 0;
  /* feComponentTransfer (slope/intercept) empuja el ruido gris parejo de
     feTurbulence hacia los extremos (blanco/negro) — sin esto, fractalNoise
     desaturado se ve como una nube suave, no como grano; medido con
     Playwright (variance de luminancia en un recorte 300x150 sin texto ni
     mancha de lima): ~1.8 sin el boost de contraste, ~6-7 con él — recién
     ahí se lee como grano a simple vista y no solo "un poco de ruido". */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='2.4' intercept='-0.7'/%3E%3CfeFuncG type='linear' slope='2.4' intercept='-0.7'/%3E%3CfeFuncB type='linear' slope='2.4' intercept='-0.7'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 120px 120px;
  opacity: 0.14;
  mix-blend-mode: overlay;
  pointer-events: none;
}

/* ==========================================================================
   Header — transparente sobre mini-hero degradado (Hogar, Químicos,
   Mayorista & Comercios, Nosotros, Contacto)

   `body.has-hero-gradient` es el marcador de página — condiciona TODO este
   bloque, así que Home (su propio sistema, ver css/home.css) y cualquier
   página sin degradado quedan sin cambios. El
   toggle de scroll ya existía: `js/main.js` agrega `.is-scrolled` a
   `.site-header` pasados 50px de scroll en TODAS las páginas (antes solo lo
   consumía el Home viejo, ver comentario largo más arriba en este archivo,
   "Hasta acá llegaba el viejo header de dos estados") — acá se vuelve a
   consumir esa misma clase, sin tocar JS.

   `.site-header` base ya trae `transition: background-color 300ms ease,
   border-color 300ms ease` — alcanza con sumarle transición de color/opacity
   a nav-link, logo e íconos (hecho arriba, en sus reglas base, no acá) para
   que el fundido sea parejo en todo el header a la vez. */

/* `.site-header` es position:sticky (reserva su propio alto en el flujo
   normal) — un header sticky "transparente" en la parte de arriba de la
   página no revela el degradado del hero, revela el blanco liso de <body>
   que queda DETRÁS de él en el flujo, porque el hero arranca recién después
   de esos 84px reservados. Mismo problema y misma solución que ya existe
   para el Home (ver "Header — vidrio esmerilado" en css/home.css, comentario
   "el hero arrancaba DEBAJO del header"): position:fixed saca al header del
   flujo, el hero pasa a ocupar el y:0 real de la página y el header queda
   flotando encima, transparente, viendo el degradado de verdad detrás. */
body.has-hero-gradient .site-header {
  position: fixed;
  left: 0;
  right: 0;
}

/* Compensa el alto del header (ya no reservado en el flujo) para que el H1
   no quede pegado/tapado contra la barra flotante — mismo criterio que
   [[project-home-hero-header-gap]]: sumar el alto real del header al
   padding en vez de un margin-top fijo a mano. Solo pisa `padding-top`
   (no todo `padding-block`), así el padding-bottom de `.category-hero`
   base no se toca. */
.category-hero--gradient {
  padding-top: calc(var(--header-height) + var(--space-2xl));
}

/* No queda 100% transparente: la "mancha" de lima del hero (ver
   .category-hero--gradient) puede caer justo debajo de la esquina
   superior derecha del header —donde vive el ícono del carrito y el
   último link del nav— y el lima es demasiado claro para sostener blanco
   encima (mismo techo de contraste ya documentado). Un scrim oscuro sutil
   despareja esa variación: el header sigue leyéndose "transparente" (se ve
   el degradado/grano de fondo a través), pero el contraste queda
   garantizado sin importar qué parte del degradado caiga detrás — medido
   con Playwright en la zona real del ícono del carrito (la más expuesta a
   la mancha de lima) en los 3 anchos verificados. */
body.has-hero-gradient .site-header:not(.is-scrolled) {
  background: linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.22));
  border-bottom-color: transparent;
}

/* El logo cruza de la versión color a la mono-blanca (mismo archivo que ya
   usa el footer, isologo-mono-claro.svg) con un crossfade real, no un salto:
   se apilan los dos <img> en el mismo lugar (position:absolute sobre un
   .logo-link relativo) SOLO en este contexto — en el resto del sitio
   `.logo-img-white` sigue con `display:none` (regla base, sin tocar) y el
   logo funciona como siempre. */
body.has-hero-gradient .logo-link {
  position: relative;
}

body.has-hero-gradient .logo-img-white {
  display: block;
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 250ms ease;
}

body.has-hero-gradient .logo-img-color {
  transition: opacity 250ms ease;
}

body.has-hero-gradient .site-header:not(.is-scrolled) .logo-img-color {
  opacity: 0;
}

body.has-hero-gradient .site-header:not(.is-scrolled) .logo-img-white {
  opacity: 1;
}

/* `.main-nav:not(.is-open)` — a 375/768px el <ul> de links no vive en la
   barra del header sino en el panel desplegable (`#main-nav.is-open`,
   fondo blanco sólido siempre, ver "Responsive — nav / footer" más abajo);
   ese panel nunca debe heredar blanco-sobre-blanco. La exclusión no afecta
   1280px (ahí `.main-nav` nunca gana `.is-open`, el toggle que la dispara
   está oculto por CSS a ese ancho). */
body.has-hero-gradient .site-header:not(.is-scrolled) .main-nav:not(.is-open) .nav-link {
  color: var(--color-white);
}

body.has-hero-gradient .site-header:not(.is-scrolled) .main-nav:not(.is-open) .nav-link::after {
  background: var(--color-white);
}

body.has-hero-gradient .site-header:not(.is-scrolled) .cart-toggle svg,
body.has-hero-gradient .site-header:not(.is-scrolled) .nav-toggle svg {
  stroke: var(--color-white);
}

/* El :hover gris claro de .cart-toggle/.nav-toggle (pensado para el header
   blanco de siempre) dejaría un ícono blanco casi invisible sobre un chip
   casi blanco acá — se cambia a un chip translúcido blanco, consistente con
   el resto del header transparente. */
body.has-hero-gradient .site-header:not(.is-scrolled) .cart-toggle:hover,
body.has-hero-gradient .site-header:not(.is-scrolled) .nav-toggle:hover {
  background: rgba(255, 255, 255, 0.18);
}

/* ==========================================================================
   Categoría — layout de catálogo (sidebar + grid)
   ========================================================================== */

.catalogo-section {
  padding-block: var(--space-2xl);
}

.catalogo-layout {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--space-xl);
  align-items: start;
}

.filtro-sidebar {
  position: sticky;
  top: calc(var(--header-height) + var(--space-md));
}

.filtro-sidebar-title {
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-soft);
  margin-bottom: var(--space-sm);
}

.filtro-list {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.filtro-btn {
  width: 100%;
  text-align: left;
  padding: 0.6rem 0.85rem;
  border-radius: var(--radius-sm);
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--color-text);
  transition: background-color 200ms ease, color 200ms ease;
}

.filtro-btn:hover {
  background: var(--color-surface-soft);
}

.filtro-btn.is-active {
  background: var(--color-accent);
  color: var(--color-white);
  font-weight: 600;
}

/* ==========================================================================
   Buscador de catálogo — texto simple, sin vidrio esmerilado ni opciones
   avanzadas a propósito (ver comentario largo en js/catalogo-buscador.js):
   mismo lenguaje visual que el resto de la sidebar de filtros (borde fino,
   radius-sm, foco en --color-accent, igual que .freetext-input/.bidon-
   manual-input en Mayorista).
   ========================================================================== */

.catalogo-buscador {
  position: relative;
  margin-bottom: var(--space-md);
}

.catalogo-buscador-icon {
  position: absolute;
  left: 0.7rem;
  top: 50%;
  transform: translateY(-50%);
  width: 17px;
  height: 17px;
  stroke: var(--color-text-soft);
  pointer-events: none;
}

.catalogo-buscador-input {
  width: 100%;
  padding: 0.6rem 2.1rem 0.6rem 2.15rem;
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--color-text);
  background: var(--color-white);
  transition: border-color 200ms ease;
}

.catalogo-buscador-input::placeholder {
  color: var(--color-text-soft);
}

.catalogo-buscador-input:focus {
  outline: none;
  border-color: var(--color-accent);
}

.catalogo-buscador-clear {
  position: absolute;
  right: 0.35rem;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 999px;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--color-text-soft);
  transition: background-color 200ms ease, color 200ms ease;
}

.catalogo-buscador-clear:hover {
  background: var(--color-surface-soft);
  color: var(--color-text);
}

/* El botón de limpiar se togglea con el atributo hidden nativo (JS) —
   como acá SÍ redeclaramos display (inline-flex), hace falta este
   refuerzo explícito o la regla de arriba gana por especificidad de
   origen y el botón queda visible aunque esté "hidden". Mismo patrón que
   .cart-count[hidden] / .price-mayorista-badge[hidden] ya usan. */
.catalogo-buscador-clear[hidden] {
  display: none;
}

.catalogo-resultados-count {
  font-size: 0.9rem;
  color: var(--color-text-soft);
  margin-bottom: var(--space-md);
}

.catalogo-empty-state {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-lg);
  background: var(--color-surface-soft);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-md);
}

.catalogo-empty-state[hidden] {
  display: none;
}

.catalogo-empty-state p {
  color: var(--color-text-soft);
  max-width: 50ch;
}

.catalogo-grid-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xl);
}

.producto-grupo-title {
  font-size: clamp(1.4rem, 2.4vw, 1.8rem);
  margin-bottom: 0.5rem;
}

.producto-grupo-blurb {
  font-size: 0.95rem;
  color: var(--color-text-soft);
  max-width: 60ch;
  margin-bottom: var(--space-md);
}

.producto-grupo-notice {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  max-width: 640px;
  margin-top: var(--space-sm);
  margin-bottom: var(--space-md);
  padding: 0.75rem 1rem;
  background: var(--color-surface-soft);
  border-left: 3px solid var(--color-accent-cyan);
  border-radius: var(--radius-sm);
  font-size: 0.85rem;
  color: var(--color-text-soft);
  line-height: 1.45;
}

.producto-grupo-notice svg {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 0.1rem;
  stroke: var(--color-accent-cyan);
}

.producto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--space-md);
  /* Todas las cards de una misma fila toman la altura de la más alta;
     .product-card-actions (margin-top: auto) es lo que garantiza que
     ese espacio extra quede debajo del selector+botón, no arriba. */
  align-items: stretch;
}

.producto-subgrupo-title {
  grid-column: 1 / -1;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-soft);
  padding-top: var(--space-md);
  padding-bottom: 0.4rem;
  border-bottom: 1px solid var(--color-surface);
}

.producto-subgrupo-title:first-child {
  padding-top: 0;
}

/* ==========================================================================
   Categoría — card de producto
   ========================================================================== */

.product-card {
  display: flex;
  flex-direction: column;
  height: auto;
  gap: var(--space-md);
  padding-top: var(--space-md);
  padding-right: var(--space-md);
  padding-bottom: var(--space-md);
  padding-left: var(--space-md);
  background: var(--color-white);
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-md);
  transition: box-shadow 250ms ease, transform 250ms ease;
}

.product-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.product-card[hidden] {
  display: none;
}

.product-card-body {
  flex-shrink: 0;
}

.product-card-title {
  font-family: var(--font-heading);
  font-size: 1.15rem;
  margin-bottom: 0.4rem;
}

.product-card-desc {
  font-size: 0.9rem;
  color: var(--color-text-soft);
  line-height: 1.5;
  /* 2 líneas a este tamaño/interlineado (0.9rem * 1.5 * 2), reservadas
     siempre — el <p> se renderiza vacío en las cards sin blurb para que
     el bloque título+desc arranque en la misma altura en toda la fila. */
  min-height: 2.7rem;
}

/* Precio + umbral mayorista (inyectado por hogar.js — no aparece en
   Mayorista & Comercios ni en Químicos, esas páginas no crean este nodo). */
.product-card-price {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 0.25rem;
}

.price-value {
  font-family: var(--font-heading);
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--color-text);
}

.price-mayorista-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-accent-dark);
  background: rgba(var(--color-accent-rgb), 0.12);
  padding: 0.25rem 0.6rem;
  border-radius: 999px;
}

.price-mayorista-badge[hidden] {
  display: none;
}

.product-card-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  gap: var(--space-sm);
  /* Fija las acciones contra el padding-bottom real de .product-card,
     sin importar cuánto (o poco) contenido haya en .product-card-body.
     El gap de .product-card sigue dando el mínimo entre body y actions;
     este margen se come cualquier espacio sobrante para que el botón
     nunca quede a menos de var(--space-md) del borde inferior. */
  margin-top: auto;
}

.product-card-actions-freetext {
  justify-content: flex-end;
}

.qty-selector {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.qty-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.qty-unit {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-text-soft);
}

.qty-btn {
  /* 44px: mínimo de área táctil (WCAG 2.5.5/2.5.8) — antes 28px. Usado en
     la card de producto (.qty-stepper) Y, vía mayorista.js, en el
     selector de bidones (.bidon-count-row) — mismo control, mismo fix en
     los dos lugares con una sola regla. */
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--color-surface);
  border-radius: 999px;
  font-size: 1rem;
  color: var(--color-text);
  transition: border-color 200ms ease, color 200ms ease;
}

.qty-btn:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
}

.qty-value {
  min-width: 1.5ch;
  text-align: center;
  font-weight: 600;
  font-size: 0.95rem;
}

.btn-add,
.btn-add-freetext {
  padding: 0.7rem 1.25rem;
  font-size: 0.9rem;
  white-space: nowrap;
}

/* ==========================================================================
   Selector de bidón (Mayorista & Comercios — solo productos por litro)

   Reemplaza .qty-selector vía mayorista.js cuando el producto tiene
   `bidones` definidos. flex-basis:100% lo empuja a su propia fila dentro
   de .product-card-actions (que ya es flex-wrap) para que "Agregar +"
   quede debajo, no apretado al costado.
   ========================================================================== */

.bidon-selector {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  flex-basis: 100%;
}

.bidon-sizes {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.bidon-size-btn {
  /* min-height 44px: mínimo de área táctil (WCAG 2.5.5/2.5.8) — antes
     rendía ~37px de alto solo por padding+línea de texto. Con
     display:inline-flex centrado, el ancho sigue dándolo el padding
     horizontal + el texto (sin cambios) — solo crece el alto, y
     .bidon-sizes ya es flex-wrap, así que no genera overflow. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--color-surface);
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-text);
  transition: border-color 200ms ease, background-color 200ms ease, color 200ms ease;
}

.bidon-size-btn:hover {
  border-color: var(--color-accent);
}

.bidon-size-btn.is-active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: var(--color-white);
}

.bidon-count-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}

.bidon-count-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.bidon-count-value {
  min-width: 1.5ch;
  text-align: center;
  font-weight: 600;
  font-size: 0.95rem;
}

.bidon-total-label {
  font-size: 0.85rem;
  color: var(--color-text-soft);
}

.bidon-total-value {
  color: var(--color-text);
  font-weight: 700;
}

.bidon-manual-label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.78rem;
  color: var(--color-text-soft);
  font-weight: 600;
}

.bidon-manual-input {
  width: 100%;
  max-width: 140px;
  padding: 0.5rem 0.65rem;
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
}

.bidon-manual-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.product-card-actions--bidones .btn-add {
  width: 100%;
  justify-content: center;
}

.freetext-label {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-soft);
  margin-top: var(--space-sm);
  margin-bottom: 0.35rem;
}

.freetext-input {
  width: 100%;
  resize: vertical;
  min-height: 4.5rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-sm);
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-text);
  transition: border-color 200ms ease;
}

.freetext-input:focus {
  outline: none;
  border-color: var(--color-accent);
}

.freetext-error {
  font-size: 0.8rem;
  color: var(--color-danger);
  margin-top: 0.4rem;
}

/* ==========================================================================
   Mayorista — niveles de compra por volumen
   ========================================================================== */

.niveles-section {
  padding-block: var(--space-xl) var(--space-2xl);
  background: var(--color-surface-soft);
}

.niveles-title {
  font-size: clamp(1.4rem, 2.4vw, 1.8rem);
  max-width: 32ch;
  margin-bottom: var(--space-lg);
}

.niveles-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-md);
}

.nivel-card {
  padding: var(--space-lg);
  background: var(--color-white);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.nivel-card-eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.6rem;
}

.nivel-card-text {
  font-size: 0.98rem;
  color: var(--color-text-soft);
  line-height: 1.5;
  max-width: 60ch;
}

.nivel-card-quimica {
  grid-column: 1 / -1;
  border-top: 4px solid var(--color-accent);
}

.nivel-card-quimica .nivel-card-eyebrow {
  color: var(--color-accent-dark);
}

.nivel-steps {
  display: flex;
  align-items: stretch;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

/* El fondo plano de antes ahora lo da .glass-surface (ver css más arriba,
   sección ".glass-surface — superficie de vidrio sin comportamiento de
   botón") — acá queda solo el layout propio del chip. */
.nivel-step {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-sm);
  position: relative;
}

/* La flechita separadora entre pasos era un ::before generado por CSS,
   pero .glass-surface ya usa ::before para su propia sombra separada (ver
   comentario de esa clase) — un solo elemento no puede tener dos
   ::before distintos, así que la flecha pasó a ser un <span> real
   (.nivel-step-arrow, agregado solo en el 2do y 3er paso en el HTML). */
.nivel-step-arrow {
  position: absolute;
  left: calc(var(--space-md) * -1 - 4px);
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-accent);
  font-weight: 600;
}

.nivel-step-value {
  font-family: var(--font-heading);
  font-size: 1.3rem;
  color: var(--color-text);
}

.nivel-step-label {
  font-size: 0.82rem;
  color: var(--color-text-soft);
}

.nivel-card-envios {
  border-left: 4px solid var(--color-accent);
}

.nivel-card-envios .nivel-card-eyebrow {
  color: var(--color-text);
}

/* ==========================================================================
   Reseñas — carrusel (compartido entre index.html y contacto.html)
   ========================================================================== */

.resenas {
  padding-block: var(--space-2xl);
  background: var(--color-surface-soft);
}

.resenas-heading {
  text-align: center;
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  margin-inline: auto;
}

.reviews-carousel {
  position: relative;
  width: 100%;
  margin-top: var(--space-lg);
}

.reviews-viewport {
  overflow: hidden;
  padding-block: var(--space-xs) var(--space-md);
  -webkit-mask-image: linear-gradient(to right, transparent, black 100px, black calc(100% - 100px), transparent);
  mask-image: linear-gradient(to right, transparent, black 100px, black calc(100% - 100px), transparent);
}

.reviews-track {
  display: flex;
  gap: var(--space-md);
  width: max-content;
  animation: reviews-scroll 140s linear infinite;
}

.reviews-track.is-paused {
  animation-play-state: paused;
}

.review-card {
  flex: 0 0 clamp(260px, 32vw, 360px);
  background: var(--color-white);
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-md);
  padding: var(--space-md);
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  box-shadow: var(--shadow-sm);
  user-select: none;
}

@keyframes reviews-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-50%);
  }
}

.review-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xs);
}

.review-stars {
  font-size: 0.9rem;
  letter-spacing: 0.1em;
}

.stars-filled {
  color: var(--color-accent);
}

.stars-empty {
  color: var(--color-surface);
}

.review-source {
  color: var(--color-text-soft);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}

.review-text {
  font-size: 0.92rem;
  color: var(--color-text);
  line-height: 1.55;
}

.review-author {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-text-soft);
  margin-top: auto;
}

.reviews-nav {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
}

.reviews-cta {
  margin-top: var(--space-lg);
  text-align: center;
}

.review-arrow {
  width: 44px;
  height: 44px;
  border-radius: 999px;
  border: 1px solid var(--color-surface);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--color-white);
  transition: border-color 200ms ease, transform 200ms ease;
}

.review-arrow:hover {
  border-color: var(--color-accent);
  color: var(--color-accent);
  transform: translateY(-2px);
}

.review-arrow svg {
  width: 18px;
  height: 18px;
  stroke: currentColor;
}

/* ==========================================================================
   Contacto — tarjetas de contacto directo
   ========================================================================== */

.contacto-section {
  padding-block: var(--space-2xl);
}

.contacto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-md);
}

.contacto-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  padding: var(--space-lg);
  background: var(--color-white);
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}

.contacto-card-label {
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-soft);
}

.contacto-card-value {
  font-family: var(--font-heading);
  font-size: 1.15rem;
  line-height: 1.35;
}

.contacto-card .btn {
  width: 100%;
  margin-top: auto;
}

/* ==========================================================================
   Contacto — horario + mapa
   ========================================================================== */

.ubicacion-section {
  padding-block: 0 var(--space-2xl);
}

.ubicacion-layout {
  display: grid;
  grid-template-columns: minmax(240px, 320px) 1fr;
  gap: var(--space-xl);
  align-items: start;
}

.horario-title {
  font-size: clamp(1.2rem, 2vw, 1.4rem);
  margin-bottom: var(--space-sm);
}

.horario-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.horario-row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--color-surface);
}

.horario-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.horario-row dt {
  font-weight: 600;
  color: var(--color-text);
}

.horario-row dd {
  color: var(--color-text-soft);
  text-align: right;
}

.mapa-embed {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
}

.mapa-embed iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ==========================================================================
   Contacto — zona de envíos
   ========================================================================== */

.envios-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
}

/* ==========================================================================
   Categoría — responsive
   ========================================================================== */

@media (max-width: 1024px) {
  .catalogo-layout {
    grid-template-columns: 1fr;
  }

  .filtro-sidebar {
    position: static;
  }

  /* Lista vertical apilada en vez de fila con wrap: con etiquetas largas
     ("Limpieza de ropa y hogar", "Almacén y accesorios") un wrap de chips
     de ancho variable queda desprolijo y esconde opciones al final de la
     última fila. Apilada, cada categoría se lee completa y el touch target
     es la fila entera (ancho completo), no un link angosto de texto. */
  .filtro-list {
    flex-direction: column;
    gap: 0.5rem;
  }

  /* Todos los botones (incluido "Todos") comparten el mismo look de chip
     acá: fondo gris claro + borde sutil por default, para que se lean como
     opciones de filtro del mismo grupo y no como texto suelto de la
     página — antes solo ".is-active" (ver más abajo, fuera de este media
     query) tenía tratamiento visual, y el resto dependía de :hover, que en
     touch nunca se dispara. Padding subido para un target cómodo (~44px). */
  .filtro-btn {
    width: 100%;
    background: var(--color-surface-soft);
    border: 1px solid var(--color-surface);
    padding: 0.85rem 1rem;
  }

  .niveles-grid {
    grid-template-columns: 1fr;
  }

  .nivel-card-quimica {
    grid-column: auto;
  }

  .nivel-steps {
    flex-direction: column;
  }

  /* Los pasos se apilan verticalmente acá — la flecha rota 90° en vez de
     cambiar de carácter (es el mismo <span>, no un ::before nuevo por
     breakpoint): "→" girado en el propio eje ya se lee como "↓". */
  .nivel-step-arrow {
    left: 50%;
    top: calc(var(--space-md) * -1 - 2px);
    transform: translateX(-50%) rotate(90deg);
  }

  .ubicacion-layout {
    grid-template-columns: 1fr;
  }

  .envios-grid {
    grid-template-columns: 1fr;
  }
}

/* Niveles de compra por volumen en phones reales (320-480px): a esos anchos
   el padding de escritorio (var(--space-lg) en la card + var(--space-md) en
   cada step) dejaba el contenido de cada step en ~150px, apretado aunque no
   llegara a cortarse. Se achica el padding para que el texto respire más,
   sin tocar tipografía ni contenido.

   480px queda a propósito FUERA del sistema unificado de 640/1024 (ver
   "Sistema de breakpoints" en tokens.css): no es un corte de "modo"
   (columnas/nav — eso ya lo resuelve el @media 1024px de acá arriba, que
   ya deja `.niveles-grid`/`.nivel-steps` en una sola columna desde
   641px), es solo un ajuste fino de padding anidado DENTRO del rango
   mobile. Subirlo a 640px cambiaría el padding de esta card en 481-640px,
   que el pedido de unificación pidió explícitamente no tocar. */
@media (max-width: 480px) {
  .nivel-card {
    padding: var(--space-md);
  }

  .nivel-steps {
    margin-top: var(--space-md);
  }

  .nivel-step {
    padding: var(--space-sm);
  }
}

/* ==========================================================================
   Nosotros — página institucional

   Página sin producto/carrito propio (hereda header/footer/carrito
   compartidos, pero no agrega nada al pedido). Sistema visual estándar del
   resto del sitio fuera del Home — sin vidrio esmerilado ni fondo de
   moléculas: reusa .category-hero para el mini-hero y bandas alternadas
   surface-soft/blanco para el resto, mismo criterio de ritmo visual que ya
   separa .catalogo-section (blanco) de .category-hero (surface-soft).
   ========================================================================== */

.nosotros-porque {
  padding-block: var(--space-2xl);
}

.nosotros-porque-text {
  max-width: 68ch;
  margin-inline: auto;
  text-align: center;
  font-size: clamp(1.1rem, 2vw, 1.3rem);
  line-height: 1.65;
  color: var(--color-text);
}

.nosotros-como {
  padding-block: var(--space-2xl);
  background: var(--color-surface-soft);
}

.nosotros-como-heading {
  text-align: center;
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  margin-bottom: var(--space-xl);
}

.nosotros-como-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-md);
}

.nosotros-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg);
  background: var(--color-white);
  border: 1px solid var(--color-surface);
  border-radius: var(--radius-md);
  transition: box-shadow 250ms ease, transform 250ms ease;
}

.nosotros-card:hover {
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.nosotros-card h3 {
  font-size: 1.15rem;
}

.nosotros-card p {
  font-size: 0.95rem;
  color: var(--color-text-soft);
  line-height: 1.55;
}

.nosotros-stats {
  padding-block: var(--space-2xl);
}

.nosotros-stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-lg);
  text-align: center;
}

.nosotros-stat-number {
  display: block;
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: clamp(2rem, 4.5vw, 2.75rem);
  color: var(--color-accent-dark);
  line-height: 1.1;
  margin-bottom: 0.35rem;
}

.nosotros-stat-label {
  font-size: 0.95rem;
  color: var(--color-text-soft);
  max-width: 20ch;
  margin-inline: auto;
}

.nosotros-cta {
  padding-block: var(--space-2xl);
  background: var(--color-surface-soft);
  text-align: center;
}

.nosotros-cta h2 {
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  margin-bottom: var(--space-sm);
}

.nosotros-cta p {
  color: var(--color-text-soft);
  max-width: 52ch;
  margin-inline: auto;
  margin-bottom: var(--space-lg);
}

.nosotros-cta-actions {
  display: flex;
  justify-content: center;
  gap: var(--space-md);
  flex-wrap: wrap;
}

@media (max-width: 640px) {
  .nosotros-stats-grid {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }
}
