/* ==========================================================================
   Cuentela · hoja de la web pública (`web/publico/`)
   --------------------------------------------------------------------------
   Esta hoja es un ESPEJO AUTÓNOMO de `plataforma/src/styles.scss` §1 (que a su
   vez viene de `docs/diseno-clientes.md` §1). Se copian los valores, no se
   importa nada: esta carpeta se sirve tal cual con nginx, sin build y sin una
   sola dependencia, así que no puede compartir archivo con la SPA de Angular.
   Cada token lleva anotado el contraste medido en su origen; si la paleta
   cambia allá, hay que traer el cambio aquí a mano — es el precio de que la
   página puente abra en un teléfono de gama baja sin cargar la plataforma.

   Diferencia deliberada con el origen: aquí las tipografías son las DEL
   SISTEMA. La plataforma autoaloja Quicksand y Figtree; esta página es el
   enlace que circula por WhatsApp y se abre con la señal que haya, así que por
   omisión no pide ni un byte de fuente por red.

   La ÚNICA excepción es Quicksand 700, declarada en el §1b y gastada por dos
   cosas contadas: los titulares de la portada (§10) y los títulos de los
   bloques de la vitrina (§6b). En los bloques es un requisito del diseño y no
   un adorno —la app empaqueta esa misma fuente, y la vitrina tiene que verse
   igual en las dos—; el corte pesa 14.8 KB, va con `font-display: swap` y sólo
   se pide cuando la página de verdad lo usa: un negocio sin bloques sigue sin
   descargar una sola fuente.
   ========================================================================== */

/* DE QUÉ LOCAL SE SURTE (0572).

   Va debajo de las condiciones de venta y con su mismo peso visual: es un dato
   del negocio, no una decisión que haya que tomar antes de mirar el catálogo.
   Envuelve porque en un teléfono angosto —el escenario real— dos nombres de
   local y su rótulo no caben en una línea.

   `[hidden]` se repite por lo mismo que en `.condiciones`: el `display: flex`
   de autor tapa el del user-agent, y esta caja nace oculta para el negocio de
   un solo local. */
.locales {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-1) var(--esp-2);
  margin: var(--esp-2) 0 0;
}

.locales[hidden] {
  display: none;
}

.locales__rotulo {
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--texto-tenue);
}

.locales__opciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-1);
}

/* QA-FLUJOS claude-5#94 · el local del pedido, en el paso de cómo se recibe. */
.carrito__local {
  display: grid;
  gap: var(--esp-1);
  margin-top: var(--esp-2);
}

.carrito__local-texto {
  margin: 0;
}

/* «Revisamos tu bolsa para este local» NO es un error y no se pinta como uno
   (decisión 3): mismo tenue que el resto de la tarjeta, en su propia línea para
   que se lea entero en un ancho de teléfono. */
.locales__aviso {
  flex-basis: 100%;
  margin: 0;
  font-size: 0.8125rem;
  line-height: 1.4;
  color: var(--texto-tenue);
}

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Paleta de marca — §1.1. Los cinco hexadecimales no cambian nunca y van
     completos aunque esta hoja no gaste dos de ellos: `--cuentela-rosa` porque
     sobre papel una etiqueta menuda en ese tono se queda en 4.0:1 y manda el
     derivado —es el rosa del logotipo, que va incrustado como SVG en el HTML y
     no puede leer variables de CSS—, y `--cuentela-nopal` porque aquí el verde
     sólo aparece aclarado, en `--exito-suave`. La paleta se copia entera para
     que traer un cambio de la plataforma sea comparar listas iguales. */
  --cuentela-papel: #fbf6f3;
  --cuentela-cacao: #2b2226;
  --cuentela-rosa: #e4287c;
  --cuentela-nopal: #206b4e;
  --cuentela-maiz: #f5c84c;

  /* Derivados documentados — §1.2 */
  --cuentela-rosa-oscuro: #b81e63; /* 5.75:1 sobre papel: texto pequeño y botones */
  --cuentela-rosa-claro: #f78cba; /* 6.93:1 sobre cacao: acciones en modo oscuro */

  /* Papeles semánticos — modo claro */
  --fondo: var(--cuentela-papel);
  --fondo-elevado: #ffffff;
  --fondo-hundido: #f2e9e4;
  --texto: var(--cuentela-cacao);
  --texto-tenue: #6b5b62; /* 5.94:1 sobre papel */
  /* La flecha del `<select>`, que aquí tampoco la dibuja ya el navegador. Es un
     trazo, como los demás iconos, pero va de fondo y no con `currentColor`: un
     SVG de fondo es otro documento y no hereda el color de quien lo pinta, así
     que se escribe una vez por papel con el tono de `--texto-tenue`. */
  --flecha-select: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236b5b62' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3.5 6.25 4.5 4.5 4.5-4.5'/%3E%3C/svg%3E");
  --borde: #e6d9d3;
  --borde-fuerte: #d3c1ba;
  --borde-control: #8d7a72; /* 3.79:1 sobre papel · 4.07:1 sobre blanco */

  --accion: var(--cuentela-rosa-oscuro);
  --accion-hover: #9a1953; /* papel encima 7.42:1 */
  --accion-texto: var(--cuentela-papel);
  --accion-suave: #fbe4ee; /* relleno tenue; `--texto` encima 12.82:1 */

  /* Fondos de portada sin foto: claves cerradas en el API, tokens medidos con
     `--texto` encima. No llegan hex/CSS desde una tienda. */
  --portada-nopal: #d5e4dd;
  --portada-maiz: #f8e1a8;

  --exito-suave: #e2f0ea; /* nopal aclarado; encima va `--texto` */
  --realce: var(--cuentela-maiz);
  --realce-texto: var(--cuentela-cacao); /* 9.75:1 — el maíz JAMÁS como texto */
  --peligro: #a3202c; /* 6.99:1 sobre papel */
  --peligro-suave: #f7e2e3;

  /* Apagado: es un COLOR propio, no una opacidad (§1.2). */
  --apagado-fondo: #c8b7b0;
  --apagado-texto: #514348; /* 4.83:1 sobre el relleno apagado */

  --foco: var(--cuentela-rosa-oscuro);

  /* Forma — §1.4 */
  --radio-tesela: 18px;
  --radio-chico: 10px;
  --radio-pildora: 999px;
  --sombra-tesela: 0 1px 2px rgb(43 34 38 / 6%), 0 6px 18px rgb(43 34 38 / 6%);
  --sombra-flotante: 0 8px 28px rgb(43 34 38 / 14%);

  /* Ritmo */
  --esp-1: 4px;
  --esp-2: 8px;
  --esp-3: 12px;
  --esp-4: 16px;
  --esp-5: 24px;
  --esp-6: 32px;
  --esp-7: 48px;

  /* Tipografía del sistema: cero peticiones de red (ver encabezado). */
  --fuente-titulo:
    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    sans-serif;
  --fuente-cuerpo:
    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial,
    sans-serif;

  /* La única fuente autoalojada de esta carpeta (§1b). NOMBRARLA no descarga
     nada —el navegador pide el archivo cuando una regla la usa de verdad—, así
     que declararla aquí no le cuesta un byte a quien no la gasta. */
  --fuente-quicksand: "Quicksand", var(--fuente-titulo);

  /* Alto de la barra de descarga fija, que el cuerpo reserva abajo. */
  --alto-cta: 92px;

  color-scheme: light;
}

/* En pantalla angosta la barra se acomoda en dos o tres renglones —el texto
   envuelve y los botones se van abajo—, así que hay que reservarle más hueco o
   el último producto de la rejilla queda debajo de ella. */
@media (max-width: 560px) {
  :root {
    --alto-cta: 152px;
  }
}

/* Modo oscuro por preferencia del sistema. Aquí NO hay interruptor manual para
   el visitante: la página se abre una vez desde un enlace y no tiene dónde
   guardar la preferencia de nadie. Lo que sí manda es el NEGOCIO: cuando su
   escaparate declara `tema: 'claro'`, `tienda.js` (y desde 0019 `cotizacion.js`,
   con el mismo `tema` que trae `api.cotizacion_publica`) le ponen `.tema-claro`
   al `<html>` en cuanto llega el arranque y esta redefinición se hace a un
   lado —la tienda pública y la cotización se ven claras aunque el teléfono del
   visitante venga en oscuro—. Es el mismo patrón de `plataforma/src/styles.scss`
   §1. Inocuo para `index.html`: es la landing, no la página de un negocio, y
   nunca lleva esa clase. Solo se redefinen papeles semánticos. */
@media (prefers-color-scheme: dark) {
  :root:not(.tema-claro) {
    --fondo: var(--cuentela-cacao);
    --fondo-elevado: #362c31;
    --fondo-hundido: #211a1d;
    --texto: var(--cuentela-papel);
    --texto-tenue: #c5b3ba; /* 7.75:1 sobre cacao */
    /* La misma flecha del `<select>`, con el tenue de este papel. */
    --flecha-select: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23c5b3ba' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3.5 6.25 4.5 4.5 4.5-4.5'/%3E%3C/svg%3E");
    --borde: #473a3f;
    --borde-fuerte: #5d4c53;
    --borde-control: #8a757d; /* 3.15:1 sobre la tesela · 3.62:1 sobre cacao */

    /* Sobre cacao el rosa de marca da 3.6:1: no alcanza, manda rosa-claro. */
    --accion: var(--cuentela-rosa-claro);
    --accion-hover: #fba5c9; /* cacao encima 8.33:1 */
    --accion-texto: var(--cuentela-cacao);
    --accion-suave: #4a2b3a; /* `--texto` (papel) encima 11.53:1 */
    --portada-nopal: #314b42;
    --portada-maiz: #57472e;

    --exito-suave: #23402f;
    --realce: var(--cuentela-maiz); /* el maíz no se recolorea (§1.4) */
    --realce-texto: var(--cuentela-cacao);
    --peligro: #f4a0a7; /* 7.67:1 sobre cacao · 6.68:1 sobre la tesela */
    --peligro-suave: #4a2a2d;

    --apagado-fondo: #5c4d52;
    --apagado-texto: #d6c8cc; /* 4.92:1 sobre el relleno apagado */

    --foco: var(--cuentela-maiz);

    --sombra-tesela: 0 1px 2px rgb(0 0 0 / 30%), 0 6px 18px rgb(0 0 0 / 28%);
    --sombra-flotante: 0 8px 28px rgb(0 0 0 / 45%);

    color-scheme: dark;
  }
}

/* --------------------------------------------------------------------------
   1b. Quicksand autoalojada
   --------------------------------------------------------------------------
   Sólo el corte 700, y en una sección propia porque lo comparten DOS páginas:
   los titulares de la portada (§10) y los títulos de los bloques de la vitrina
   en `/t` (§6b). Vivió dentro del §10 mientras la portada era la única que la
   gastaba; con la vitrina componible (0018) la fuente dejó de ser de la
   portada, y una `@font-face` duplicada sería el mismo archivo declarado dos
   veces esperando a que alguien corrija una sola.

   `latin-ext` va declarado con su rango para que quede documentado, pero con
   texto en español el navegador no llega a pedirlo.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Quicksand";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fuentes/quicksand-latin-700-normal.woff2") format("woff2");
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Quicksand";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fuentes/quicksand-latin-ext-700-normal.woff2") format("woff2");
  unicode-range:
    U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --------------------------------------------------------------------------
   2. Base
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Un ancla dentro de la página no debe quedar debajo de la cabecera. */
  scroll-padding-top: var(--esp-6);
}

body {
  margin: 0;
  background: var(--fondo);
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 16px; /* ≥16px en pantalla chica (§1.4) */
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  overflow-wrap: break-word;
}

h1,
h2,
h3 {
  margin: 0 0 var(--esp-3);
  font-family: var(--fuente-titulo);
  font-weight: 700;
  line-height: 1.2;
  color: var(--texto);
}

h1 {
  font-size: 1.75rem;
}
h2 {
  font-size: 1.375rem;
}
h3 {
  font-size: 1.125rem;
}

p {
  margin: 0 0 var(--esp-3);
}

img,
video {
  max-width: 100%;
}

a {
  color: var(--accion);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--accion-hover);
  text-decoration-thickness: 2px;
}

/* Foco visible SIEMPRE: se navega con teclado y con lector de pantalla. */
:where(a, button, input, [tabindex]):focus-visible {
  outline: 3px solid var(--foco);
  outline-offset: 2px;
  border-radius: var(--radio-chico);
}

.salto-contenido {
  position: absolute;
  left: -9999px;
  top: 0;
  padding: var(--esp-3) var(--esp-4);
  background: var(--fondo-elevado);
  color: var(--texto);
  border-radius: var(--radio-chico);
  z-index: 100;
}

.salto-contenido:focus {
  left: var(--esp-3);
  top: var(--esp-3);
}

.texto-tenue {
  color: var(--texto-tenue);
}

.pista {
  font-size: 0.875rem;
}

/* La barra de descarga es fija y sólo la lleva la página de tienda: el cuerpo
   le reserva su alto para que el último producto de la rejilla no quede
   tapado. La landing no la usa y no debe pagar el hueco. */
.con-barra-cta {
  padding-bottom: calc(var(--alto-cta) + var(--esp-4));
}

/* El hueco de la barra fija también hay que reservarlo PARA EL DESPLAZAMIENTO,
   no solo para el final del documento. Al tabular, el navegador arrastra el
   elemento enfocado hasta el borde de la ventana; sin esto lo dejaba justo
   DEBAJO de la barra —«Ver cómo funciona» del hero desaparecía entero al
   recibir el foco— y quien navega con teclado se quedaba sin saber dónde
   estaba. `scroll-padding-bottom` es la línea que Chromium respeta tanto al
   saltar a un ancla como al mover el foco. */
html:has(body.con-barra-cta) {
  scroll-padding-bottom: calc(var(--alto-cta) + var(--esp-3));
}

.envoltura {
  width: 100%;
  max-width: 1040px;
  margin: 0 auto;
  padding: 0 var(--esp-4);
}

/* --------------------------------------------------------------------------
   3. Cabecera
   -------------------------------------------------------------------------- */

.cabecera {
  border-bottom: 1px solid var(--borde);
  background: var(--fondo-elevado);
}

.cabecera__contenido {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--esp-3) var(--esp-4);
  padding-top: var(--esp-3);
  padding-bottom: var(--esp-3);
}

.marca {
  display: inline-flex;
  align-items: center;
  gap: var(--esp-2);
  min-height: 44px;
  font-family: var(--fuente-titulo);
  font-weight: 700;
  font-size: 1.0625rem;
  color: var(--texto);
  text-decoration: none;
}

.marca__logo {
  width: 28px;
  height: 28px;
  flex: none;
}

.negocio {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  min-width: 0;
}

/* Misma historia que en `.boton[hidden]`: el `display: flex` de autor tapa el
   `[hidden]` del user-agent. La tarjeta del negocio nace oculta en `t.html` y
   solo aparece cuando llega el escaparate. */
.negocio[hidden] {
  display: none;
}

.negocio__logo {
  width: 56px;
  height: 56px;
  flex: none;
  object-fit: cover;
  border-radius: var(--radio-chico);
  border: 1px solid var(--borde);
  background: var(--fondo-hundido);
}

.negocio__nombre {
  margin: 0;
  font-size: 1.375rem;
}

.negocio__dato {
  margin: 0;
  font-size: 0.875rem;
}

/* Condiciones de venta de la tienda en línea (0165).
   Píldoras y no una frase corrida porque son datos que el comprador COMPARA
   —cuánto cobra de envío, desde cuánto es gratis, cuál es el mínimo— y en un
   teléfono angosto una frase con tres cifras se lee dos veces. Envuelven solas:
   un negocio puede tener una sola condición o las cinco. */
.condiciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-1) var(--esp-2);
  margin: var(--esp-2) 0 0;
  padding: 0;
  list-style: none;
}

/* La misma historia que en `.negocio[hidden]`: el `display: flex` de autor tapa
   el `[hidden]` del user-agent, y esta lista nace oculta para el negocio que no
   vende por internet. */
.condiciones[hidden] {
  display: none;
}

.condiciones__dato {
  font-size: 0.8125rem;
  line-height: 1.4;
  padding: 2px var(--esp-2);
  border-radius: var(--radio-pildora);
  background: var(--fondo-hundido);
  color: var(--texto-tenue);
}

/* La condición que es una BUENA noticia —envío gratis, envío incluido— se pinta
   con el verde aclarado y texto normal: es lo único de esta lista que el
   comprador quiere que le salte a la vista. Nunca es sólo color: el texto ya
   dice «gratis», así que quien no distingue el verde no se pierde nada. */
.condiciones__dato--bien {
  background: var(--exito-suave);
  color: var(--texto);
}

/* --------------------------------------------------------------------------
   4. Teselas, avisos y estados
   -------------------------------------------------------------------------- */

/* Especificidad de id a propósito: `.envoltura` (0-1-0) le gana a un simple
   `main` (0-0-1) y deja el padding-top en 0 sin este empate hacia arriba. */
#contenido {
  padding-top: var(--esp-5);
}

.seccion {
  margin-bottom: var(--esp-6);
}

.tesela {
  background: var(--fondo-elevado);
  border: 1px solid var(--borde);
  border-radius: var(--radio-tesela);
  box-shadow: var(--sombra-tesela);
  padding: var(--esp-5);
}

.aviso {
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-4);
  border-radius: var(--radio-chico);
  border: 1px solid transparent;
  font-size: 0.9375rem;
}

/* El tono no se dice solo con el color: cada aviso abre con su signo. El
   segundo valor de `content` es el texto alternativo, vacío a propósito, para
   que el lector de pantalla no anteponga «signo de admiración» al mensaje. */
.aviso::before {
  flex: none;
  font-family: var(--fuente-titulo);
  font-weight: 700;
  line-height: inherit;
}

.aviso--error {
  background: var(--peligro-suave);
  border-color: var(--peligro);
  color: var(--texto);
}

.aviso--error::before {
  content: "!" / "";
}

.aviso--realce {
  background: var(--realce);
  border-color: var(--realce-texto);
  color: var(--realce-texto);
}

.aviso--realce::before {
  content: "i" / "";
}

.aviso__cuerpo {
  min-width: 0;
}

.aviso__cuerpo p:last-child {
  margin-bottom: 0;
}

/* Aviso que vive suelto en la página y no dentro del hueco de estado. */
.aviso--suelto {
  margin-bottom: var(--esp-5);
}

/* Estado de carga: una tesela hueca que ocupa el sitio del contenido para que
   la página no salte cuando llegan los datos. */
.estado {
  margin-bottom: var(--esp-5);
}

.estado:empty {
  display: none;
}

.cargando {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  color: var(--texto-tenue);
  font-size: 0.9375rem;
}

.cargando::before {
  content: "";
  flex: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 3px solid var(--borde-fuerte);
  border-top-color: var(--accion);
  animation: giro 900ms linear infinite;
}

@keyframes giro {
  to {
    transform: rotate(360deg);
  }
}

/* --------------------------------------------------------------------------
   5. Botones y chips
   -------------------------------------------------------------------------- */

.boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--esp-2);
  min-height: 44px; /* blanco táctil cómodo */
  padding: 0 var(--esp-5);
  border: 1px solid transparent;
  border-radius: var(--radio-pildora);
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  background: transparent;
  color: var(--texto);
  transition:
    background-color 120ms ease,
    color 120ms ease,
    border-color 120ms ease;
}

/* `[hidden]` del navegador es `display: none` en la hoja del USER-AGENT, y en
   la cascada cualquier declaración de autor le gana: el `display: inline-flex`
   de arriba deja el botón a la vista aunque el guion lo marque como oculto —así
   se quedaba «Ver más productos» al final del catálogo—. El contrapeso hay que
   escribirlo aquí, elemento por elemento, como ya se hace con `.modal[hidden]`. */
.boton[hidden] {
  display: none;
}

.boton:disabled {
  background: var(--apagado-fondo);
  border-color: var(--apagado-fondo);
  color: var(--apagado-texto);
  cursor: not-allowed;
}

.boton--primario {
  background: var(--accion);
  color: var(--accion-texto);
}

.boton--primario:hover:not(:disabled) {
  background: var(--accion-hover);
  color: var(--accion-texto);
}

/* Hueco: su relleno es el mismo de la tesela, así que la frontera de lo que se
   toca la pone el borde y WCAG la mide a 3:1 → `--borde-control`. */
.boton--secundario {
  background: var(--fondo-elevado);
  border-color: var(--borde-control);
  color: var(--texto);
}

.boton--secundario:hover:not(:disabled) {
  background: var(--fondo-hundido);
  color: var(--texto);
}

.boton--menudo {
  min-height: 44px;
  padding: 0 var(--esp-4);
  font-size: 0.9375rem;
}

/* --------------------------------------------------------------------------
   El carrito del visitante (0166)
   -------------------------------------------------------------------------- */

/* El botón de la cabecera. Secundario y no primario: la acción principal de
   esta página sigue siendo mirar el catálogo, y un botón rosa fijo arriba
   competiría con cada «Agregar» de abajo. */
.boton--carrito {
  min-height: 44px;
  padding: 0 var(--esp-4);
  font-size: 0.9375rem;
  background: var(--fondo-elevado);
  border-color: var(--borde-control);
  color: var(--texto);
  flex: none;
  margin-left: auto;
}

.boton--carrito:hover:not(:disabled) {
  background: var(--fondo-hundido);
}

/* El contador. Rosa y con su número: es lo único de la cabecera que cambia
   solo, y tiene que verse desde lejos. */
.boton__contador {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: var(--radio-pildora);
  background: var(--accion);
  color: var(--accion-texto);
  font-size: 0.8125rem;
  font-weight: 700;
}

.boton__contador[hidden] {
  display: none;
}

/* El «Agregar» de cada presentación. Primario porque ES la acción de esta
   página desde que el negocio abrió su tienda. */
.boton--agregar {
  min-height: 44px;
  padding: 0 var(--esp-4);
  font-size: 0.9375rem;
  background: var(--accion);
  color: var(--accion-texto);
  flex: none;
}

.boton--agregar:hover:not(:disabled) {
  background: var(--accion-hover);
}

/* El «Vaciar»: destructivo y por eso discreto, sin relleno. Nunca al lado de
   nada que se toque sin querer. */
.boton--fantasma {
  min-height: 44px;
  padding: 0 var(--esp-4);
  font-size: 0.9375rem;
  border-color: var(--borde);
  color: var(--texto-tenue);
}

.boton--fantasma:hover:not(:disabled) {
  border-color: var(--borde-fuerte);
  color: var(--texto);
}

.variantes__agregar {
  margin-top: var(--esp-3);
}

.variantes__agregar[hidden] {
  display: none;
}

.renglones {
  list-style: none;
  margin: 0 0 var(--esp-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
}

.renglon {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--esp-3);
  padding-bottom: var(--esp-3);
  border-bottom: 1px solid var(--borde);
  flex-wrap: wrap;
}

/* Lo que ya no se puede pedir se queda a la vista, apagado. Esconderlo dejaría
   a la persona sin saber por qué el total bajó solo. El color NO es la única
   señal: cada renglón apagado lleva su píldora con la causa escrita. */
.renglon--apagado .renglon__nombre,
.renglon--apagado .renglon__presentacion {
  color: var(--apagado-texto);
  text-decoration: line-through;
}

.renglon__cuerpo {
  min-width: 0;
  flex: 1 1 55%;
}

.renglon__nombre {
  margin: 0;
  font-weight: 600;
}

.renglon__presentacion,
.renglon__precio {
  margin: 2px 0 0;
  font-size: 0.875rem;
}

/* La promoción del renglón (0186): la píldora con la etiqueta corta y, al lado,
   el nombre completo de la promoción. En una pantalla angosta el nombre envuelve
   debajo de la píldora en vez de empujar el mando de la cantidad. */
.renglon__promocion {
  margin: var(--esp-2) 0 0;
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  flex-wrap: wrap;
}

.renglon__promocion-nombre {
  font-size: 0.875rem;
}

.renglon__mando {
  display: flex;
  align-items: center;
  gap: var(--esp-2);
  flex: none;
}

.renglon__cantidad {
  width: 72px;
  min-height: 44px;
  padding: 0 var(--esp-2);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-chico);
  background: var(--fondo-elevado);
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
  text-align: center;
}

.renglon__importe {
  margin: 0;
  font-weight: 600;
  min-width: 72px;
  text-align: right;
}

.renglon__quitar {
  min-height: 44px;
  padding: 0 var(--esp-2);
  border: 0;
  background: transparent;
  color: var(--texto-tenue);
  font-family: var(--fuente-cuerpo);
  font-size: 0.875rem;
  text-decoration: underline;
  cursor: pointer;
}

.renglon__quitar:hover {
  color: var(--peligro);
}

.totales {
  margin: var(--esp-4) 0;
  padding-top: var(--esp-3);
  border-top: 1px solid var(--borde-fuerte);
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
}

.totales__fila {
  display: flex;
  justify-content: space-between;
  gap: var(--esp-3);
}

.totales__fila--fuerte {
  font-size: 1.125rem;
  font-weight: 700;
  padding-top: var(--esp-2);
  border-top: 1px solid var(--borde);
}

.totales__nota {
  margin: 0;
  font-size: 0.875rem;
}

.carrito__acciones {
  display: flex;
  justify-content: flex-end;
  gap: var(--esp-3);
  flex-wrap: wrap;
  margin-bottom: var(--esp-4);
}

/* --------------------------------------------------------------------------
   El código de promoción en el carrito y en el checkout (0191 §6)
   --------------------------------------------------------------------------

   Piezas nuevas: sólo la fila del campo y el chip. Todo lo demás reusa lo que
   el panel ya tiene —`.campo`, `.campo__control`, `.boton--fantasma`— porque
   quien acaba de teclear su teléfono en este mismo panel tiene que reconocer
   dónde está.

   El chip NO se distingue sólo por color: el aplicado lleva su borde verde Y
   dice «Aplicado» con todas sus letras, y el que no aplicó dice por qué en la
   línea de abajo. Un color por sí solo no le dice nada a quien no lo ve.
   -------------------------------------------------------------------------- */

.cupon {
  margin-bottom: var(--esp-4);
}

/* El campo y su botón, en una sola línea. El campo se estira y el botón se
   queda con lo suyo: en un teléfono de 320 px «Aplicar» tiene que caber sin
   partirse, y el código rara vez pasa de diez caracteres. */
.cupon__fila {
  display: flex;
  gap: var(--esp-2);
  align-items: stretch;
}

.cupon__campo {
  margin-bottom: 0;
}

.cupon__control {
  flex: 1 1 auto;
  min-width: 0;
  /* El código se guarda y se compara en mayúsculas: verlo así mientras se
     teclea evita la duda de si «verano10» es el mismo cupón del cartel. */
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.cupon__aplicar {
  flex: 0 0 auto;
  min-height: 48px;
}

.cupon__chip {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  flex-wrap: wrap;
  padding: var(--esp-2) var(--esp-3);
  border: 1px solid var(--borde-fuerte);
  border-radius: var(--radio-pildora);
  background: var(--fondo-elevado);
}

/* Aplicado: el verde del acierto, el mismo relleno de `.pildora--disponible`. */
.cupon__chip--aplicado {
  border: 2px solid var(--cuentela-nopal);
  background: var(--exito-suave);
}

/* Puesto pero sin efecto: ni verde ni rojo. Nadie hizo nada mal y el pedido
   sigue en pie; lo que falta es la explicación, que va debajo. */
.cupon__chip--pendiente {
  border: 1px dashed var(--borde-control);
  background: var(--fondo-hundido);
}

.cupon__codigo {
  font-weight: 700;
  letter-spacing: 0.06em;
}

.cupon__estado {
  flex: 1 1 auto;
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

.cupon__quitar {
  border: 0;
  background: none;
  padding: var(--esp-1) 0;
  font-family: inherit;
  font-size: 0.875rem;
  color: var(--texto-tenue);
  text-decoration: underline;
  cursor: pointer;
}

.cupon__quitar:hover {
  color: var(--peligro);
}

.cupon__razon {
  margin: var(--esp-2) 0 0;
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

/* ==========================================================================
   Checkout de la tienda en línea (0171)
   ========================================================================== */

/* Ancho completo para la acción principal de cada paso: en un teléfono, el
   pulgar tiene que dar con ella sin apuntar. */
.boton--ancho {
  width: 100%;
  margin-top: var(--esp-3);
}

.carrito__titulo {
  margin: var(--esp-4) 0 var(--esp-2);
  font-family: var(--fuente-titulo, inherit);
  font-size: 1.0625rem;
  font-weight: 700;
  color: var(--texto);
}

/* Lo que las promociones le ahorraron, en la pantalla del desenlace (0186). Va
   sobre el relleno del realce y no en maíz pleno: es una frase entera, y el maíz
   nunca se usa como color de texto. */
.carrito__ahorro {
  margin: 0 0 var(--esp-3);
  padding: var(--esp-2) var(--esp-3);
  border-radius: var(--radio-chico);
  background: var(--portada-maiz);
  color: var(--texto);
  font-weight: 600;
}

/* El folio, que es lo que la persona va a leerle al negocio por teléfono. */
.carrito__folio {
  margin: 0 0 var(--esp-3);
  font-size: 1.375rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--accion);
}

.campo {
  display: block;
  margin-bottom: var(--esp-4);
}

.campo__etiqueta {
  display: block;
  margin-bottom: var(--esp-2);
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--texto);
}

/* 16px como mínimo en el control: por debajo de eso, Safari en iOS hace zoom
   al enfocar y descoloca la pantalla entera a media compra. */
.campo__control {
  width: 100%;
  min-height: 48px;
  padding: 0 var(--esp-3);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-control, 8px);
  background: var(--fondo-elevado);
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
}

.campo__control:focus-visible {
  outline: 2px solid var(--accion);
  outline-offset: 1px;
}

.campo__ayuda {
  display: block;
  margin-top: var(--esp-2);
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

.opciones {
  display: flex;
  gap: var(--esp-3);
  flex-wrap: wrap;
  margin-bottom: var(--esp-4);
}

.opcion {
  flex: 1 1 auto;
  min-height: 48px;
  padding: 0 var(--esp-4);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-pildora);
  background: var(--fondo-elevado);
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
}

/* La opción elegida se distingue por COLOR Y BORDE, no sólo por color: quien
   no distingue el rosa del papel tiene que poder ver cuál escogió. */
.opcion--activa {
  border: 2px solid var(--accion);
  background: var(--accion-suave);
  color: var(--texto);
}

.opcion:focus-visible {
  outline: 2px solid var(--accion);
  outline-offset: 2px;
}

/* «Lo antes posible» mide y acaba como el campo de la hora, no como
   etiqueta + campo (UIUX-WEB-01). */
.opciones--cuando {
  align-items: flex-end;
}

.opciones--cuando > .opcion,
.opciones--cuando > .campo {
  flex: 1 1 200px;
}

.opciones--cuando > .campo {
  margin-bottom: 0;
}

/* REP-08-01 · la dirección del domicilio en el checkout de /t. Una dirección
   es un bloque de dos o tres renglones —título, colonia y referencias—, así
   que no cabe en la píldora de una opción de una línea: van apiladas, a todo
   lo ancho, y se leen de izquierda a derecha. */
.direccion-envio {
  display: grid;
  gap: var(--esp-3);
  margin-bottom: var(--esp-4);
}

.opciones--direcciones {
  flex-direction: column;
  margin-bottom: 0;
}

.opcion--direccion {
  display: grid;
  gap: 2px;
  padding: var(--esp-3) var(--esp-4);
  border-radius: var(--radio-chico);
  text-align: left;
}

.opcion__titulo {
  font-weight: 700;
}

.opcion__detalle {
  font-weight: 400;
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

.direccion-envio__alta {
  display: grid;
  gap: var(--esp-3);
  padding: var(--esp-4);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-chico);
  background: var(--fondo-elevado);
}

/* La rejilla ya separa. */
.direccion-envio__alta .campo {
  margin-bottom: 0;
}

.direccion-envio__acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-3);
}

/* El control con un problema, como en la factura pública: dos píxeles en
   `--peligro` para ver de lejos cuál es, además del texto que lo dice. */
.direccion-envio .campo__control[aria-invalid='true'] {
  border: 2px solid var(--peligro);
}

/* ==========================================================================
   Factura de esta compra en el checkout (0226)
   ==========================================================================

   Casi nada propio, y a propósito: el formulario fiscal se arma con las mismas
   piezas del checkout de 0171 —`.campo`, `.campo__control`, `.carrito__titulo`,
   `.boton--ancho`, `.aviso`— porque quien acaba de teclear su teléfono en este
   mismo panel tiene que reconocer dónde está. Lo único que esta fase inventa es
   la CAJA que separa el paso opcional del camino principal, y el acuse de que
   quedó guardado.

   Nada de esto se pinta sin la herramienta: el JavaScript no crea el `.factura`
   cuando el escaparate no trae `factura_disponible`, así que estas reglas no
   alcanzan a un negocio que no factura.
   ========================================================================== */

/* La caja del paso opcional. Un filete arriba y aire, para que se lea como algo
   AL LADO del camino y no como un peldaño más: debajo sigue el botón de hacer
   el pedido, y esa jerarquía tiene que verse antes de leerse. */
.factura {
  margin-top: var(--esp-5);
  padding-top: var(--esp-4);
  border-top: 1px solid var(--borde);
}

/* El primer título dentro de la caja no necesita el respiro de arriba: el
   filete y el `padding-top` ya lo dieron. */
.factura .carrito__titulo:first-child,
.factura .carrito__titulo:first-of-type {
  margin-top: 0;
}

/* El acuse de guardado. Verde aclarado con el texto normal encima (12.6:1),
   nunca el nopal como letra: el color no es la única señal —lleva su palabra— y
   el nodo va con `role="status"` para que un lector de pantalla lo cante. */
.factura__acuse {
  margin: 0 0 var(--esp-3);
  padding: var(--esp-3);
  border-radius: var(--radio-chico);
  background: var(--exito-suave);
  color: var(--texto);
  font-size: 0.9375rem;
  font-weight: 600;
}

/* Los dos selectores de catálogo del SAT comparten el control del checkout,
   con arreglos que un `<input>` no necesita. Fijar el alto —porque Safari
   ignora `min-height` mientras el control se pinta como `menulist`— resolvía la
   mitad: MEDIDO en WebKit el 2026-09-20, con la apariencia nativa puesta Safari
   ignora TAMBIÉN el `padding`, así que el régimen fiscal se leía pegado al
   borde izquierdo mientras el campo de al lado tenía su sangría. La única
   manera de que la hoja mande sobre la caja es renunciar a esa apariencia, y
   entonces la flecha la ponemos nosotros: un trazo de fondo, sin archivo ni
   petición de más. El ancho se sigue acotando para que una descripción larga
   del SAT no desborde el panel a 360 px. */
select.campo__control {
  appearance: none;
  height: 48px;
  max-width: 100%;
  padding-right: calc(var(--esp-3) + 14px + var(--esp-2));
  line-height: 1.2;
  cursor: pointer;
  background-image: var(--flecha-select);
  background-repeat: no-repeat;
  background-position: right var(--esp-3) center;
  background-size: 14px 14px;
}

/* ==========================================================================
   Pagar con tarjeta desde la tienda (0172)
   ==========================================================================

   Casi nada nuevo, y es a propósito: la pantalla del pago se arma con las
   mismas piezas del checkout —`.carrito__titulo`, `.carrito__folio`,
   `.boton--ancho`, `.cargando`, `.opcion`— porque quien viene de teclear su
   teléfono en este mismo panel tiene que reconocer dónde está. Lo único que
   esta fase inventa es la banda, y sólo porque no había nada que hiciera lo
   que hace: sobrevivir a que la persona se vaya de la página.
   ========================================================================== */

/* La banda del pago sin desenlace. Es el asidero de quien volvió del navegador
   de WhatsApp y perdió el contexto: va arriba del contenido, antes del hueco
   de estado, y NO fija —abajo ya vive la barra de la app, y dos bandas
   peleándose el pulgar no ayudan a nadie—.

   Es un `button` entero y no un enlace con un botón dentro: apuntarle a dos
   palabras con el pulgar, en la calle, es puntería que nadie tiene. Mismo
   criterio que la tarjeta de producto, que también se toca completa. */
.banda-pago {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--esp-1);
  width: 100%;
  min-height: 56px;
  margin-bottom: var(--esp-5);
  padding: var(--esp-3) var(--esp-4);
  border: 2px solid var(--accion);
  border-radius: var(--radio-chico);
  background: var(--accion-suave);
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  /* 16px de cuerpo y 56px de alto mínimo: esto se lee de pie, con el teléfono
     en una mano y el sol encima, y es lo único que separa a alguien de creer
     que su dinero se perdió. No es un renglón de ayuda: es una acción. */
  font-size: 1rem;
  text-align: left;
  cursor: pointer;
}

.banda-pago:hover {
  border-color: var(--accion-hover);
}

.banda-pago__titulo {
  display: block;
  font-weight: 700;
}

/* La pista dice QUÉ PASA AL TOCAR. Sin ella, la banda es un aviso que parece
   no hacer nada, y nadie la toca. */
.banda-pago__pista {
  display: block;
  font-size: 0.9375rem;
  /* No `--texto-tenue`: sobre el relleno de acción —cacao aclarado en oscuro,
     rosa lavado en claro— ese token no está medido. El texto normal sí. */
  color: var(--texto);
  opacity: 0.85;
}

.chip {
  min-height: 44px;
  padding: 0 var(--esp-4);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-pildora);
  background: var(--fondo-elevado);
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
}

.chip:hover:not(.chip--activo) {
  background: var(--fondo-hundido);
}

/* Activo: relleno rosa-oscuro con texto papel — la etiqueta es menuda y el
   rosa de marca (4.0:1) no bastaría (§1.2). */
.chip--activo {
  background: var(--accion);
  border-color: var(--accion);
  color: var(--accion-texto);
}

/* Envoltura de bloque de una etiqueta de existencias. Es un `span` porque la
   ficha del catálogo es un `button` y ahí no cabe un `p`. */
.pildoras {
  display: block;
  margin: 0 0 var(--esp-2);
}

.pildora {
  display: inline-block;
  padding: 2px var(--esp-3);
  border-radius: var(--radio-pildora);
  font-size: 0.8125rem;
  font-weight: 600;
}

.pildora--disponible {
  background: var(--exito-suave);
  color: var(--texto); /* cacao 13.16:1 sobre el verde claro; papel en oscuro */
}

.pildora--agotado {
  background: var(--apagado-fondo);
  color: var(--apagado-texto);
}

/* La promoción vigente (0186). El maíz es el REALCE de la paleta (§1.4) y es el
   único de los cinco que no se recolorea en modo oscuro, así que la insignia se
   ve igual con el teléfono en claro y en oscuro —que es lo que se espera de una
   oferta: que salte—. Encima va `--realce-texto`, el cacao, a 9.75:1; el maíz
   JAMÁS como texto. Y no es sólo el color: la píldora dice «−20 %» o «2×1» con
   todas sus letras, que es lo que anuncia el lector de pantalla. */
.pildora--promocion {
  background: var(--realce);
  color: var(--realce-texto);
}

/* La envoltura `.pildoras` nació para la ficha de la rejilla, que es un `button`
   y necesita un bloque con aire debajo. En la lista de presentaciones, en el
   renglón del carrito y bajo los chips de variante la insignia va EN LÍNEA, al
   lado del precio, así que ahí la envoltura se deja de comportar como bloque en
   vez de escribir un segundo generador de píldoras. */
.presentacion .pildoras,
.renglon__promocion .pildoras,
.variantes__promocion .pildoras {
  display: inline-block;
  margin: 0;
}

/* --------------------------------------------------------------------------
   6. Escaparate
   -------------------------------------------------------------------------- */

.escaparate__mensaje {
  font-size: 1.0625rem;
  max-width: 60ch;
}

/* Carrusel horizontal: en una pantalla de 320 px cabe un medio y se arrastra
   con el pulgar; en pantalla ancha caben tres. Nada de autoplay ni de saltos
   automáticos: quien mira decide. */
.galeria {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(240px, 1fr);
  gap: var(--esp-3);
  overflow-x: auto;
  padding-bottom: var(--esp-2);
  scroll-snap-type: x mandatory;
  list-style: none;
  margin: 0 0 var(--esp-4);
}

/* El `display: grid` de autor le gana al `[hidden]` del user-agent: sin este
   contrapeso, un escaparate sin medios dejaría la rejilla vacía ocupando su
   hueco y su `padding-bottom`. */
.galeria[hidden] {
  display: none;
}

.galeria__pieza {
  position: relative;
  scroll-snap-align: start;
}

/* APP-VISOR-DE-FOTOS-01 · el recuadro recorta; «Ver en grande» abre el medio
   entero. La foto también se abre al tocarla: el cursor lo dice. */
.medio--ampliable {
  cursor: zoom-in;
}

.galeria__ampliar {
  position: absolute;
  top: var(--esp-2);
  right: var(--esp-2);
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--borde);
  border-radius: var(--radio-pildora);
  background: var(--fondo-elevado);
  color: var(--texto);
  opacity: 0.92;
  cursor: pointer;
}

.galeria__ampliar:hover {
  border-color: currentcolor;
  opacity: 1;
}

.medio {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--fondo-hundido);
  border: 1px solid var(--borde);
  border-radius: var(--radio-tesela);
}

/* --------------------------------------------------------------------------
   6b. Bloques de la vitrina (0018)
   --------------------------------------------------------------------------
   La vitrina componible: una lista ordenada de bloques que el dueño arma en su
   panel y que esta página pinta EXACTAMENTE igual que la app. Esa palabra
   —exactamente— es la que manda en toda la sección: cada medida de aquí es un
   número de la especificación y su gemelo en Flutter es el MISMO número en dp.
   Por eso conviven tokens y píxeles crudos: donde el token ya vale lo que pide
   la espec (`--esp-5` = 24 de separación, `--radio-tesela` = 18 de esquina) se
   usa el token; donde la espec fija un cuerpo de letra —24, 18, 15, 13— va el
   píxel, porque un `rem` se movería con el tamaño de letra del navegador y la
   app no tiene forma de seguirlo.

   El color, en cambio, es SIEMPRE token: el modo oscuro sale gratis y ninguna
   regla de aquí conoce un hexadecimal que no esté en el §1. Las dos únicas
   excepciones son los `rgb()` con alfa del hero, que son Cacao y Papel con
   transparencia —lo mismo que ya hace `.modal` en el §8— porque una variable
   de color no se puede usar dentro de `rgb()` sin `color-mix()`, y esta hoja
   se abre en teléfonos donde `color-mix()` no está.

   Sin bloques configurados nada de esto se pinta: la vitrina se ve como
   siempre (§6) y esta sección no cuesta ni un byte de fuente.
   -------------------------------------------------------------------------- */

/* Los bloques son la columna de contenido de la sección del escaparate, uno
   debajo de otro y separados por los 24 de la espec. */
.bloques {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--esp-4);
  row-gap: 0;
  margin: 0 0 var(--esp-5);
}

/* `figure` —el bloque de imagen y el de video— trae margen del user-agent. */
.bloque {
  margin: 0;
  min-width: 0;
}

.bloque--ancho-completo { grid-column: span 12; }
.bloque--ancho-mitad { grid-column: span 6; }
.bloque--ancho-tercio { grid-column: span 4; }

.bloque--centrado {
  justify-self: center;
  width: 100%;
}

.bloque--max-estrecho { max-width: 32rem; }
.bloque--max-medio { max-width: 48rem; }
.bloque--max-ancho { max-width: 64rem; }

/* El espacio vertical dejó de venir del `gap` de la retícula —que era igual
   para todos— y ahora lo pone el margen de cada bloque, que es lo que permite
   que cada uno lleve su propia preferencia. Van hijos de `.bloques` y no la
   clase suelta porque dos reglas de esta misma hoja escriben `margin: 0` sobre
   bloques concretos —`.bloque--separador` (§6b) y `.rejilla` (§7, la de los
   destacados)—: con la clase suelta empatan en especificidad, vienen después y
   ganan, y esos dos bloques quedaban pegados al siguiente. Con el hijo directo
   la preferencia pesa más que el reseteo, sin depender del orden del archivo. */
.bloques > .bloque--gap-ninguno { margin-bottom: 0; }
.bloques > .bloque--gap-compacto { margin-bottom: var(--esp-2); }
.bloques > .bloque--gap-normal { margin-bottom: var(--esp-5); }
.bloques > .bloque--gap-amplio { margin-bottom: var(--esp-7); }

@media (max-width: 719px) {
  .bloque--ancho-mitad,
  .bloque--ancho-tercio {
    grid-column: span 12;
  }
}

/* ----- hero ----- */

/* El 16:9 lo lleva el CONTENEDOR y no la imagen: así el hero sin media —fondo
   rosa tenue— mide exactamente lo mismo que el hero con foto, y la capa del
   texto se cuelga de un alto que existe antes de que la imagen cargue. */
.bloque--hero {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  border-radius: var(--radio-tesela);
  background: var(--fondo-hundido);
}

.bloque--hero-suave {
  background: var(--accion-suave);
}

.bloque--hero-fondo-rosa {
  background: var(--accion-suave);
}

.bloque--hero-fondo-nopal {
  background: var(--portada-nopal);
}

.bloque--hero-fondo-maiz {
  background: var(--portada-maiz);
}

.bloque__hero-medio {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Texto abajo a la izquierda, con el degradado que lo hace legible encima de
   cualquier foto: transparente arriba y Cacao al 55 % en el borde inferior. */
.bloque__hero-capa {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--esp-4);
  background: linear-gradient(
    to bottom,
    rgb(43 34 38 / 0) 45%,
    rgb(43 34 38 / 55%) 100%
  );
}

/* Sin foto no hay nada que apagar: el fondo ya es plano y el texto va en el
   color normal, que en oscuro es papel sobre el contenedor rosa (11.53:1). */
.bloque--hero-suave .bloque__hero-capa {
  background: none;
}

.bloque__hero-titulo,
.bloque__hero-sub {
  /* Dos líneas y corta: el hero es una portada, no un párrafo. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.bloque__hero-titulo {
  margin: 0;
  font-family: var(--fuente-quicksand);
  font-weight: 700;
  font-size: 24px;
  line-height: 1.2;
  color: var(--cuentela-papel);
}

.bloque__hero-sub {
  margin: var(--esp-1) 0 0;
  font-size: 15px;
  line-height: 1.35;
  color: rgb(251 246 243 / 92%); /* Papel al 92 % */
}

.bloque--hero-suave .bloque__hero-titulo,
.bloque--hero-suave .bloque__hero-sub {
  color: var(--texto);
}

/* ----- texto ----- */

/* 60ch es la misma medida de lectura que `.escaparate__mensaje` del §6. En la
   app no hay equivalente porque el ancho de un teléfono ya está por debajo. */
.bloque--texto {
  max-width: 60ch;
}

.bloque__titulo {
  margin: 0 0 var(--esp-2);
  font-family: var(--fuente-quicksand);
  font-weight: 700;
  font-size: 18px;
  line-height: 1.25;
  color: var(--texto);
}

.bloque__cuerpo {
  margin: 0;
  font-family: var(--fuente-cuerpo);
  font-size: 15px;
  line-height: 1.5;
  color: var(--texto);
}

/* El respaldo plano conserva TAL CUAL los renglones y espacios interiores que
   recibió. */
.bloque__cuerpo:not(.bloque__cuerpo--rico) {
  white-space: pre-wrap;
}

/* El HTML rico nunca entra directo: `tienda.js` lo reconstruye con doce
   etiquetas cerradas y cero atributos. Esta clase sólo presenta ese árbol. */
.bloque__cuerpo--rico {
  /* El renderer genera HTML compacto: no hay sangría entre etiquetas que
     pudiera aparecer. Pre-wrap conserva espacios, tabs y saltos dentro de un
     nodo de texto igual que Flutter y el editor. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.bloque__cuerpo--rico p,
.bloque__cuerpo--rico h2,
.bloque__cuerpo--rico h3,
.bloque__cuerpo--rico ul,
.bloque__cuerpo--rico ol,
.bloque__cuerpo--rico blockquote {
  margin: 0 0 var(--esp-2);
}

.bloque__cuerpo--rico > :last-child,
.bloque__cuerpo--rico blockquote > :last-child,
.bloque__cuerpo--rico li > :last-child {
  margin-bottom: 0;
}

.bloque__cuerpo--rico h2,
.bloque__cuerpo--rico h3 {
  font-family: var(--fuente-quicksand);
  font-weight: 700;
  line-height: 1.3;
  color: var(--texto);
}

.bloque__cuerpo--rico h2 {
  font-size: 18px;
}

.bloque__cuerpo--rico h3 {
  font-size: 16px;
}

.bloque__cuerpo--rico ul,
.bloque__cuerpo--rico ol {
  padding-left: 1.4rem;
}

.bloque__cuerpo--rico li + li {
  margin-top: var(--esp-1);
}

.bloque__cuerpo--rico li > ul,
.bloque__cuerpo--rico li > ol {
  margin-top: var(--esp-1);
}

.bloque__cuerpo--rico blockquote {
  padding-left: var(--esp-3);
  border-left: 3px solid var(--cuentela-rosa);
  color: var(--texto-tenue);
}

.bloque__cuerpo--rico strong {
  font-weight: 700;
}

.bloque__cuerpo--rico em {
  font-style: italic;
}

.bloque__cuerpo--rico u {
  text-decoration: underline;
}

.bloque__cuerpo--rico s {
  text-decoration: line-through;
}

/* ----- imagen y video ----- */

/* Proporción NATURAL, a diferencia del 4:3 recortado de `.medio` (§6): un
   bloque de imagen es la foto que el dueño eligió, entera y sin tijeras. */
.bloque__imagen,
.bloque__video {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radio-tesela);
  background: var(--fondo-hundido);
}

.bloque__pie {
  margin: var(--esp-2) 0 0;
  font-size: 13px;
  line-height: 1.4;
  color: var(--texto-tenue);
}

/* ----- separador ----- */

/* Un `hr` pelado y nada dentro: alto TOTAL 32 y la línea de 1px dibujada como
   fondo, 40 % de ancho y centrada. Se pinta con `background` y no con un borde
   para que el alto del bloque sea el respiro completo y no dependa de márgenes
   que se colapsan con los del vecino. */
.bloque--separador {
  height: 32px;
  margin: 0;
  border: 0;
  background: linear-gradient(var(--borde), var(--borde)) center / 40% 1px
    no-repeat;
}

/* --------------------------------------------------------------------------
   7. Catálogo
   -------------------------------------------------------------------------- */

.barra {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-3);
  align-items: flex-end;
  margin-bottom: var(--esp-4);
}

/* La base de 280px NO es decorativa: es la promesa del campo.
   `.barra` envuelve, así que el buscador comparte renglón con «Buscar»
   mientras quepa su base y se lleva el renglón entero en cuanto no. Con 240
   cabía a la fuerza en un teléfono de 390 px —el más común que hay— y el
   campo quedaba en 209 px útiles para un texto de ejemplo de 225: se cortaba
   en «Arroz, jabón, corte de cabe» y se perdía justo el ejemplo que enseña
   que aquí también se buscan SERVICIOS. Con 280 el campo nunca comparte
   renglón con menos de 246 px útiles, y por debajo de eso baja el botón.
   La medida se comprueba en `pruebas_web/guiones/catalogo.mjs`. */
.buscador {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
  flex: 1 1 280px;
  min-width: 0;
  margin: 0;
}

.buscador__etiqueta {
  font-size: 0.9375rem;
  font-weight: 600;
}

/* 0533 · orden y rango de precio.
   Comparte `.barra`, así que envuelve con la misma regla que el buscador. El
   desplegable pide más base que los dos importes porque su texto más largo
   —«Precio: de mayor a menor»— no se puede recortar sin dejar de decir qué
   hace; los importes viven con cuatro cifras. Con eso, a 390 px el orden se
   lleva un renglón y los dos importes comparten el siguiente, que es
   exactamente como se lee. */
.barra--orden {
  align-items: flex-end;
}

/* APP-FILTROS-01 · el botón que abre los filtros, con la cuenta de lo puesto.
   Centrado y no alineado abajo como los campos: aquí no hay rótulos encima que
   alinear, y con `flex-end` el número quedaba pegado a la base del botón. */
.barra--filtros {
  align-items: center;
  margin-bottom: var(--esp-3);
}

.filtros-puestos {
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

/* Sólo en la barra: escrito como `.campo` a secas les quitaba el margen a
   los campos de todas las formas (UIUX-WEB-01). `:where` no le sube la
   especificidad: `.campo--oferta` la sigue ganando. */
:where(.barra--orden) .campo {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
  min-width: 0;
  margin: 0;
}

.campo--orden {
  flex: 1 1 232px;
}

.campo--precio {
  flex: 1 1 108px;
}

/* QN-TIENDA-FILTRO-PRECIO-TOPE-SILENCIOSO-01 · el importe que no se aplicó: el campo con borde
   de peligro y el motivo en su propio renglón, debajo de la barra. */
.barra--orden .campo__control[aria-invalid='true'] {
  border: 2px solid var(--peligro);
}

.barra--orden .campo__error {
  flex: 1 1 100%;
  margin-top: 0;
}

/* La casilla de «en oferta» va en renglón, no en columna como los campos: la
   etiqueta pertenece a la casilla y separarlas rompe el gesto de tocar el
   texto para marcarla, que es como se usa en un teléfono. */
/* 0535 · «hasta las 8:00 pm» junto a la insignia. Tenue y pequeño: acompaña al
   descuento, no compite con él. */
.pildora__hasta {
  margin-inline-start: var(--esp-1);
  font-size: 0.75rem;
  color: var(--texto-tenue);
  white-space: nowrap;
}

/* 0536 · una tira de chips por eje, con su rótulo delante. El rótulo va DENTRO
   del grupo y no como encabezado aparte para que en un teléfono «Talla» y sus
   tallas se lean como una sola cosa y no como dos renglones sueltos. */
.filtros--faceta {
  align-items: center;
  margin-bottom: var(--esp-2);
}

.filtros__titulo {
  font-size: 0.875rem;
  font-weight: 600;
  margin-inline-end: var(--esp-1);
}

.campo--oferta {
  flex: 0 0 auto;
  flex-direction: row;
  align-items: center;
  gap: var(--esp-2);
  padding: var(--esp-2) 0;
  cursor: pointer;
}

/* La salida de los filtros: es una acción de deshacer, no una llamada a la
   acción, y compite con «Aplicar» por la mirada. Va sin caja. */
.boton--texto {
  background: none;
  border: none;
  padding: var(--esp-2) 0;
  color: var(--texto-tenue);
  text-decoration: underline;
}

/* Caja del campo: la «✕» de borrar va encima, contra la orilla derecha. */
.buscador__campo {
  display: block;
  position: relative;
}

/* Sólo cuando hay texto que borrar. Se pone desde `tienda.js` junto con el
   `hidden` del botón, y no con `:has()`, que en un Android viejo no existe:
   sin este hueco el texto tecleado pasaría por debajo de la «✕». */
.buscador__campo--con-texto .campo__control {
  padding-right: 44px;
}

.buscador__limpiar {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translateY(-50%);
  /* 44px es el mínimo táctil, igual que los chips: el campo mide 48 de alto y
     el botón cabe entero dentro. */
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: var(--radio-pildora);
  background: transparent;
  color: var(--texto-tenue);
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

.buscador__limpiar:hover {
  color: var(--texto);
}

/* Contrapeso al `[hidden]` del user-agent: `position: absolute` es
   declaración de autor y le gana, igual que el `inline-flex` de `.boton`. */
.buscador__limpiar[hidden] {
  display: none;
}

.campo__control {
  min-height: 48px;
  width: 100%;
  padding: 0 var(--esp-4);
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
  color: var(--texto);
  background: var(--fondo-elevado);
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-chico);
}

.campo__control::placeholder {
  color: var(--texto-tenue);
}

/* La «✕» que WebKit y Blink pintan solos dentro de un `type="search"`. Se
   quita porque el buscador ya trae la suya: dejar las dos pone dos aspas
   pegadas en el mismo campo. Y la nuestra es la que se puede tocar —44 px, el
   mínimo táctil, contra los 12 de la nativa— y la única que existe en Chrome de
   Android, donde esta pseudo-clase no pinta nada. */
.campo__control::-webkit-search-cancel-button {
  display: none;
}

.filtros {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
  margin-bottom: var(--esp-4);
}

/* Contrapeso al `[hidden]` del user-agent, igual que en `.boton[hidden]`: un
   negocio con una sola categoría no pinta chips, y sin esto la caja seguiría
   reservando su margen bajo el buscador. */
.filtros[hidden] {
  display: none;
}

.rejilla {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--esp-4);
  list-style: none;
  margin: 0;
  padding: 0;
}

.rejilla--menuda {
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
}

/* La ficha entera es el botón que abre el detalle: en un teléfono, apuntar a
   un enlace de dos palabras dentro de la tarjeta es pedir puntería que no se
   tiene con una mano ocupada. */
.ficha {
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
  width: 100%;
  height: 100%;
  padding: 0 0 var(--esp-4);
  text-align: left;
  background: var(--fondo-elevado);
  border: 1px solid var(--borde);
  border-radius: var(--radio-tesela);
  box-shadow: var(--sombra-tesela);
  overflow: hidden;
  cursor: pointer;
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
  color: var(--texto);
}

.ficha:hover {
  border-color: var(--borde-control);
}

.ficha__medio {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--fondo-hundido);
  border-bottom: 1px solid var(--borde);
}

/* Marca de agua cuando el producto no tiene ni una foto: las teselas del
   logotipo, en tenue, para que la rejilla no quede con huecos blancos. */
.ficha__medio--vacio {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--texto-tenue);
  font-size: 0.875rem;
  text-align: center;
  padding: var(--esp-4);
}

/* En pantalla angosta la rejilla es de UNA columna y el hueco «Sin foto» se
   paga entero en vertical: medido a 390 px son 267 px de los 335 que mide la
   tarjeta —cuatro productos sin foto, 1958 px, dos pantallas y media de
   arrastre para leer cuatro renglones—. Y no es un estado raro: un negocio
   recién dado de alta no tiene fotos, y la página puente es lo primero
   que ve su clientela.

   La marca de agua se queda —la rejilla sigue sin huecos blancos— pero deja de
   reservarle el sitio a una foto que no existe: sin `aspect-ratio` la banda
   mide lo que su texto, con un mínimo para que se lea como una banda y no como
   un renglón suelto. Arriba de este ancho la rejilla ya trae dos o más
   columnas, el sitio de la foto sostiene la retícula y ahí NO se toca; por eso
   la regla cuelga del mismo corte de 560 px que ya usa la barra de la app.
   Sólo cambia el estado VACÍO: un producto con foto conserva su 4/3 en todas
   las pantallas. */
@media (max-width: 560px) {
  .ficha__medio--vacio {
    aspect-ratio: auto;
    min-height: 56px;
    padding: var(--esp-3) var(--esp-4);
  }
}

.ficha__cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--esp-1);
  padding: 0 var(--esp-4);
}

.ficha__titulo {
  margin: 0;
  font-family: var(--fuente-titulo);
  font-weight: 700;
  font-size: 1.0625rem;
}

.ficha__precio {
  margin: 0;
  font-weight: 600;
}

.ficha__meta {
  color: var(--texto-tenue);
  font-size: 0.875rem;
}

/* Las salidas de una búsqueda sin resultados. Van pegadas al mensaje que las
   provoca —ahí es donde alguien se atora— y desaparecen en cuanto hay algo
   que enseñar. El `[hidden]` necesita su contrapeso por el `display: flex`,
   igual que `.filtros` y `.boton`. */
.salidas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
  margin: 0 0 var(--esp-4);
}

.salidas[hidden] {
  display: none;
}

.mas {
  display: flex;
  justify-content: center;
  margin-top: var(--esp-5);
}

.vacio {
  padding: var(--esp-5);
  text-align: center;
  color: var(--texto-tenue);
}

/* --------------------------------------------------------------------------
   7b. Visor de fotos y videos (APP-VISOR-DE-FOTOS-01)
   --------------------------------------------------------------------------
   A pantalla completa y sobre negro, en cualquier tema: el medio se ve entero
   (`contain`) y nada compite con él. Va encima del detalle (60) y debajo del
   salto al contenido (100). Las cuatro esquinas, en su forma larga, por lo
   mismo que el modal. */
.visor {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 80;
  display: flex;
  flex-direction: column;
  background: #000;
  color: #fff;
}

.visor[hidden] {
  display: none;
}

.visor__barra {
  display: flex;
  align-items: center;
  gap: var(--esp-3);
  padding: var(--esp-3) var(--esp-3) var(--esp-2) var(--esp-4);
}

.visor__titulo {
  flex: 1;
  min-width: 0;
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.visor__contador {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.visor__escenario {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--esp-2) var(--esp-4);
}

/* Ocupa todo el escenario y la foto cabe entera adentro: una chica también se
   ve en grande, igual que en la app. */
.visor__medio {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.visor__boton {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid rgb(255 255 255 / 30%);
  border-radius: var(--radio-pildora);
  background: rgb(255 255 255 / 12%);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.visor__boton:hover,
.visor__boton:focus-visible {
  border-color: #fff;
  background: rgb(255 255 255 / 22%);
}

.visor__boton[hidden] {
  display: none;
}

.visor__anterior,
.visor__siguiente {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}

.visor__anterior {
  left: var(--esp-2);
}

.visor__siguiente {
  right: var(--esp-2);
}

/* --------------------------------------------------------------------------
   8. Detalle del producto (modal)
   -------------------------------------------------------------------------- */

/* Las cuatro esquinas van en su forma larga a propósito: `inset` es de 2021 y
   el navegador de un teléfono viejo que no lo entienda dejaría el modal suelto
   a media página en vez de cubrirla. */
.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 60;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: var(--esp-3);
  background: rgb(43 34 38 / 55%);
}

.modal[hidden] {
  display: none;
}

.modal__panel {
  position: relative;
  width: 100%;
  max-width: 640px;
  max-height: 88vh;
  overflow-y: auto;
  padding: var(--esp-5);
  background: var(--fondo-elevado);
  border: 1px solid var(--borde);
  border-radius: var(--radio-tesela);
  box-shadow: var(--sombra-flotante);
}

@media (min-width: 640px) {
  .modal {
    align-items: center;
  }
}

.modal__cerrar {
  position: absolute;
  top: var(--esp-3);
  right: var(--esp-3);
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radio-pildora);
  background: var(--fondo-hundido);
  color: var(--texto);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.modal__cerrar:hover {
  border-color: currentcolor;
}

.modal__titulo {
  margin: 0 var(--esp-7) var(--esp-2) 0;
}

.presentaciones {
  list-style: none;
  margin: 0 0 var(--esp-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
}

.presentacion {
  display: flex;
  /* QN-TIENDA-PRESENTACION-NOMBRE-SIN-ANCHO-MOVIL-01 · a 360 y 390 px, «− 1 + Agregar» se
     quedaba con todo el renglón y el nombre se pintaba una letra por línea. Ahora lo que no
     cabe baja al renglón de abajo y el nombre conserva un ancho para leerse. */
  flex-wrap: wrap;
  gap: var(--esp-3);
  align-items: baseline;
  padding-bottom: var(--esp-2);
  border-bottom: 1px solid var(--borde);
}

.presentacion__nombre {
  flex: 1 1 8rem;
  min-width: min(8rem, 100%);
  overflow-wrap: break-word;
}

/* La presentación agotada se queda en la lista, en su sitio y con su precio:
   lo que cambia es que lo dice. La tinta baja acompaña a la etiqueta, nunca la
   sustituye (§1.2): el color solo no puede ser la única señal. */
.presentacion--agotada .presentacion__nombre,
.presentacion--agotada .presentacion__precio {
  color: var(--texto-tenue);
}

.presentacion__precio {
  font-weight: 600;
  white-space: nowrap;
}

.presentacion--servicio {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--esp-1) var(--esp-3);
}

.presentacion--servicio .presentacion__nombre {
  grid-column: 1;
}

.presentacion--servicio .presentacion__precio {
  grid-column: 2;
  grid-row: 1;
  text-align: right;
}

.presentacion__duracion {
  grid-column: 1;
  color: var(--texto-tenue);
  font-size: 0.875rem;
}

.presentacion__accion {
  grid-column: 2;
  min-height: 44px;
}

/* ---------------------------------------------------------------------------
   8b. Variantes: chips por eje
   ---------------------------------------------------------------------------
   Un producto con matriz —tallas por colores— se elige por chips y no por una
   lista: en treinta combinaciones, treinta renglones obligan a leerlos todos
   para encontrar la propia. El chip apagado es información y no un estorbo:
   dice que ESA combinación no la tiene este negocio, en vez de dejar elegirla
   y contestar después que no.

   Desde la 0037 hay un tercer estado, y no se puede confundir con el apagado:
   el chip AGOTADO. Esa talla existe y este negocio la vende, sólo que ahora no
   tiene; se puede tocar, se ve su precio y se dice que hoy no hay. Apagarlo
   sería decir que no la tiene, que es mentira.
   -------------------------------------------------------------------------- */

.variantes {
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
  margin-bottom: var(--esp-4);
}

.variantes__eje {
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
}

.variantes__rotulo {
  margin: 0;
  font-weight: 600;
  font-size: 0.9375rem;
}

/* La fila de chips de un eje no lleva el margen inferior de `.filtros`, que
   está pensado para la barra de categorías del catálogo. */
.variantes__eje .filtros {
  margin-bottom: 0;
}

.variantes__precio {
  margin: 0;
  font-family: var(--fuente-titulo);
  font-weight: 700;
  font-size: 1.125rem;
}

.variantes__aviso {
  margin: 0;
  padding: var(--esp-2) var(--esp-3);
  border-radius: var(--radio-chico);
  background: var(--fondo-hundido);
  font-size: 0.9375rem;
}

/* La etiqueta de la combinación elegida cuando está agotada. Va debajo del
   precio: el precio se queda a la vista —es al que se vende cuando hay— y esto
   dice que hoy no. */
.variantes__marca {
  margin: 0;
}

/* La insignia de la promoción de la combinación elegida (0186), debajo del
   precio y de la marca de agotado. */
.variantes__promocion {
  margin: 0;
}

.variantes__precio[hidden],
.variantes__marca[hidden],
.variantes__promocion[hidden],
.variantes__aviso[hidden] {
  display: none;
}

/* Mismo relleno apagado que el botón deshabilitado: no se puede elegir, y se
   ve que no se puede antes de intentarlo. */
.chip:disabled {
  background: var(--apagado-fondo);
  border-color: var(--apagado-fondo);
  color: var(--apagado-texto);
  cursor: not-allowed;
}

/* Agotado NO es lo mismo que apagado y no se puede ver igual: esa talla existe,
   se puede tocar y se puede mirar su precio; lo que no hay es existencias. Por
   eso conserva el relleno del chip vivo y lo que cambia es el borde —punteado,
   «esto está de paso»— y la tinta. La marca de verdad no es el color: el chip
   se anuncia como «talla, agotado» (`aria-label`) y la frase de abajo lo dice
   con todas sus letras (§1.2). */
.chip--agotado:not(:disabled) {
  border-style: dashed;
  border-color: var(--apagado-fondo);
  color: var(--texto-tenue);
}

/* El activo manda sobre la marca: si es el que está elegido, se pinta elegido y
   quien explica que está agotado es la etiqueta de la combinación. */
.chip--agotado.chip--activo:not(:disabled) {
  border-color: var(--accion);
  color: var(--accion-texto);
}

/* ---------------------------------------------------------------------------
   8c. Datos de la ficha
   ---------------------------------------------------------------------------
   Pares nombre/valor tal como los escribió el negocio. Es exhibición y nada
   más: no filtra, no busca y no cambia el precio. Un `dl` de verdad, para que
   el lector de pantalla anuncie cada dato con su nombre.
   -------------------------------------------------------------------------- */

.atributos {
  margin: 0 0 var(--esp-4);
  display: flex;
  flex-direction: column;
  gap: var(--esp-2);
}

.atributos__par {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2) var(--esp-3);
  padding-bottom: var(--esp-2);
  border-bottom: 1px solid var(--borde);
}

.atributos__nombre {
  min-width: 9rem;
  color: var(--texto-tenue);
}

.atributos__valor {
  flex: 1;
  min-width: 0;
  margin: 0;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   9. Barra de descarga: la razón de ser de esta página
   --------------------------------------------------------------------------
   Va FIJA abajo y no se puede cerrar. La web pública es de solo lectura: todo
   lo que se puede hacer de verdad —pedir, apartar, ver el fiado— pasa en la
   app, así que la invitación a bajarla acompaña siempre, sin taparlo todo.
   -------------------------------------------------------------------------- */

.barra-cta {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 50;
  border-top: 1px solid var(--borde-fuerte);
  background: var(--fondo-elevado);
  box-shadow: var(--sombra-flotante);
}

.barra-cta__contenido {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--esp-2) var(--esp-4);
  padding-top: var(--esp-3);
  padding-bottom: var(--esp-3);
}

.barra-cta__texto {
  margin: 0;
  min-width: 0;
  flex: 1 1 220px;
  font-size: 0.9375rem;
  font-weight: 600;
}

/* Renglón propio: pegada al texto principal se leía como una sola frase
   corrida, y esta línea es justo la que explica qué hacer si no tienes la app. */
.barra-cta__pista {
  display: block;
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--texto-tenue);
}

.barra-cta__enlaces {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
}

/* --------------------------------------------------------------------------
   10. Páginas de marketing: la portada (`/`) y las landings (`/para/{slug}`)
   --------------------------------------------------------------------------
   Se mudaron a `portada.css` (web 2.0, 2026-10-03), que cargan SÓLO
   `index.html` y las trece landings: esta hoja la pagan todas las páginas de
   la carpeta, y `/f/` y `/p/` van al filo de su presupuesto. Aquí queda sólo
   el pie, que también usan las páginas legales y «Cuentela aprende».
   -------------------------------------------------------------------------- */

.pie {
  border-top: 1px solid var(--borde);
  padding: var(--esp-6) 0;
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

/* --------------------------------------------------------------------------
   10b. Cotización pública (`/c/{token}`)
   --------------------------------------------------------------------------
   La página de un DOCUMENTO, no de un catálogo: una sola tesela, sin rejilla,
   sin buscador y sin nada que abrir. Todo lo de aquí son componentes propios de
   esa página; los tokens siguen siendo los del §1, sin excepción.

   Las dos decisiones visuales que sostienen el resto: el TOTAL tiene que
   leerse de un vistazo y sin acercar el teléfono —es la única cifra por la que
   se abre este enlace— y la VIGENCIA tiene que verse junto a él, porque un
   precio sin fecha es media promesa.
   -------------------------------------------------------------------------- */

.cotizacion__cabeza {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--esp-2) var(--esp-4);
  margin-bottom: var(--esp-4);
}

.cotizacion__folio {
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--texto-tenue);
}

/* Los renglones del documento. Tres columnas en pantalla ancha; en el teléfono
   el importe se queda a la derecha y el detalle baja, porque lo que se compara
   con el ojo es la columna de importes. */
.renglones {
  list-style: none;
  margin: 0 0 var(--esp-4);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--esp-3);
}

.renglon {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-1) var(--esp-3);
  align-items: baseline;
  padding-bottom: var(--esp-3);
  border-bottom: 1px solid var(--borde);
}

.renglon__que {
  flex: 1 1 60%;
  min-width: 0;
  font-weight: 600;
}

.renglon__importe {
  margin-left: auto;
  font-weight: 700;
  white-space: nowrap;
}

.renglon__detalle {
  flex-basis: 100%;
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

/* Lo agotado HOY. El renglón no se tacha ni se apaga: el documento sigue
   valiendo entero y su precio también; lo único que se agrega es la noticia de
   que ahora mismo no hay. La etiqueta va junto al nombre, no al margen. */
.renglon__marca {
  margin-left: var(--esp-2);
  vertical-align: middle;
}

/* El aviso de la barra de acciones: una línea, antes del botón de WhatsApp y en
   su propio renglón, para que se lea antes de escribir «quiero pedirla». */
.cotizacion__agotados {
  flex-basis: 100%;
  margin: 0;
  max-width: 60ch;
  color: var(--texto-tenue);
  font-size: 0.9375rem;
}

.cotizacion__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--esp-4);
  padding-top: var(--esp-3);
  border-top: 2px solid var(--borde-fuerte);
  margin-bottom: var(--esp-4);
}

.cotizacion__total-etiqueta {
  font-size: 1.0625rem;
  font-weight: 600;
}

.cotizacion__total-cifra {
  font-family: var(--fuente-titulo);
  font-size: 1.75rem;
  font-weight: 700;
  white-space: nowrap;
}

.cotizacion__nota {
  background: var(--fondo-hundido);
  border-radius: var(--radio-chico);
  padding: var(--esp-3) var(--esp-4);
  margin-bottom: var(--esp-4);
  max-width: 60ch;
}

.cotizacion__acciones {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-3);
}

/* LAS ACCIONES DEL DOCUMENTO MIDEN LO MISMO (QA-COTIZACIONES-01, PIXEL-02).
   El primario era `--menudo` (15 px, 16 de sangría) y el de WhatsApp no (16 px,
   24): la acción secundaria pesaba más que la principal. Ahora las tres son
   `.boton` a secas y la jerarquía la pone sólo el relleno. El interlineado se
   fija porque un `<button>` no lo hereda y un `<a>` sí: sin esto «Aceptar» e
   «Imprimir» (botones) y WhatsApp (enlace) tendrían líneas distintas. */
.cotizacion__acciones .boton {
  line-height: 1.5;
}

/* En el teléfono las tres acciones de la cotización bajan una por renglón:
   a lo ancho miden lo mismo y el pulgar da con cualquiera sin apuntar. */
@media (max-width: 479px) {
  #cotizacion .cotizacion__acciones .boton {
    flex: 1 1 100%;
  }
}

/* La descripción de un concepto escrito a mano (0666): va en su renglón, con
   los saltos que escribió el negocio —fechas, lo que incluye—, y en el tono del
   texto y no en el tenue: es parte de lo que se cotiza, no una nota al margen. */
.renglon__descripcion {
  flex-basis: 100%;
  font-size: 0.9375rem;
  white-space: pre-line;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   Aceptarla sin la app (QA-COTIZACIONES-01, UX-04; 0668)
   --------------------------------------------------------------------------
   Las piezas son las del checkout y la autofactura —`.campo`, `.campo__control`,
   `.campo__ayuda`, `.campo__error`, `.boton--ancho`—: quien acepta aquí y quien
   compra en `/t` tienen que reconocer la misma casa. Lo propio es la caja: un
   filete arriba la separa del documento, y el acuse va en verde aclarado con el
   texto normal encima (12.6:1), igual que `.factura-publica__acuse`. */
.aceptar {
  margin-top: var(--esp-5);
  padding-top: var(--esp-4);
  border-top: 1px solid var(--borde);
  max-width: 34rem;
}

.aceptar__titulo {
  margin: 0 0 var(--esp-2);
  font-size: 1.25rem;
}

.aceptar__titulo:focus {
  outline: none;
}

.aceptar__titulo:focus-visible {
  outline: 2px solid var(--accion);
  outline-offset: 2px;
}

.aceptar__pista {
  margin: 0 0 var(--esp-4);
  max-width: 60ch;
}

/* `.campo` llega aquí sin margen (la regla del buscador de `/t`, §7, lo deja
   en 0), y sin aire entre un campo y la etiqueta del siguiente esa etiqueta se
   leía como del campo de arriba. El hueco ENTRE campos es mayor que el de la
   etiqueta a su control: así se agrupa cada par. */
.aceptar__paso .campo {
  margin-bottom: var(--esp-4);
}

.aceptar__otras {
  display: flex;
  flex-wrap: wrap;
  gap: var(--esp-2);
  margin-top: var(--esp-3);
}

.aceptar__otras .boton {
  flex: 1 1 auto;
}

.aceptar__listo {
  padding: var(--esp-3) var(--esp-4);
  border-radius: var(--radio-chico);
  background: var(--exito-suave);
  color: var(--texto);
}

.aceptar__listo:focus {
  outline: none;
}

.aceptar__listo p {
  margin: 0;
}

.aceptar__listo p + p {
  margin-top: var(--esp-2);
}

.aceptar__listo-titulo {
  font-weight: 700;
}

/* LA COTIZACIÓN EN PAPEL O EN PDF (UX-04). «Imprimir o guardar en PDF» abre el
   diálogo del navegador —que ya trae «Guardar como PDF»—, y lo que sale es el
   DOCUMENTO: quién lo manda, para quién, los renglones, el total, la vigencia y
   la nota. Fuera los botones, la barra de la app y el paso de aceptar, que en
   un papel no se pueden pulsar; en negro sobre blanco, que es lo que imprime
   cualquier impresora de oficina. */
@media print {
  .salto-contenido,
  .barra-cta,
  .estado,
  .cotizacion__acciones,
  .aceptar {
    display: none !important;
  }

  .con-barra-cta {
    padding-bottom: 0;
  }

  html,
  body,
  .cabecera,
  .tesela {
    background: #fff !important;
    color: #000 !important;
  }

  .tesela {
    border: none;
    box-shadow: none;
    padding: 0;
  }

  .texto-tenue,
  .renglon__detalle,
  .cotizacion__folio {
    color: #333 !important;
  }

  .renglon {
    break-inside: avoid;
  }

  .pildora {
    border: 1px solid #000;
    background: none !important;
    color: #000 !important;
  }
}

/* --------------------------------------------------------------------------
   10e. Autofactura pública (`/f/{token}`) — plan 0231 §C
   --------------------------------------------------------------------------
   Casi nada propio, y a propósito. El formulario de esta página se arma con
   las MISMAS piezas que el paso de factura del checkout de `/t` (0226) —
   `.campo`, `.campo__etiqueta`, `.campo__control`, `.campo__ayuda`,
   `select.campo__control`, `.boton--ancho`, `.aviso`— y el pedido con las de
   la cotización (§10b): `.tesela`, `.cotizacion__cabeza`, `.cotizacion__folio`
   y `.cotizacion__total`. Quien llega aquí desde un ticket no ha visto ninguna
   de las dos, pero quien la abre después de haber comprado en `/t` sí, y tiene
   que reconocer dónde está.

   Lo único que esta fase inventa son tres cosas que no existían: el hueco de
   error PEGADO a su campo —aquí un rechazo del SAT tiene que decirse junto al
   control que hay que corregir, no en una banda arriba de todo—, la lista de
   los datos ya guardados y el pliegue de la dirección opcional.
   -------------------------------------------------------------------------- */

/* El error de un campo. Vive SIEMPRE en el árbol —`f.html` lo escribe vacío—
   para que el `aria-describedby` del control lo nombre desde el principio;
   `:empty` es lo que lo mantiene invisible mientras no hay nada que decir.
   Ocultarlo con `hidden` habría roto esa referencia a ratos.

   El color no es la única señal: el texto dice qué corregir, el control se
   marca con `aria-invalid` y el cursor se va a él. */
.campo__error {
  display: block;
  margin-top: var(--esp-2);
  padding: var(--esp-2) var(--esp-3);
  border-radius: var(--radio-chico);
  background: var(--peligro-suave);
  color: var(--texto);
  font-size: 0.875rem;
  font-weight: 600;
}

.campo__error:empty {
  display: none;
}

/* El control marcado por el servidor. Dos píxeles de borde en `--peligro` para
   que se vea de lejos cuál de los cinco campos es, sin recolorear el texto. */
.factura-publica .campo__control[aria-invalid='true'] {
  border: 2px solid var(--peligro);
}

/* Mientras bajan las listas del SAT los dos selectores están deshabilitados:
   se apagan sin desaparecer, para que no se pierda el sitio que ocupan. */
.factura-publica .campo__control:disabled {
  background: var(--fondo-hundido);
  color: var(--apagado-texto);
  cursor: progress;
}

/* El acuse de que la factura quedó pedida. Verde aclarado con el texto normal
   encima (12.6:1), nunca el nopal como letra — mismo trato que el acuse del
   checkout (`.factura__acuse`), y con `role="status"` en el HTML para que un
   lector de pantalla lo cante. */
.factura-publica__acuse {
  margin: 0 0 var(--esp-4);
  padding: var(--esp-3);
  border-radius: var(--radio-chico);
  background: var(--exito-suave);
  color: var(--texto);
  font-size: 0.9375rem;
  font-weight: 600;
}

/* Los datos ya guardados, en dos columnas de etiqueta y valor. En el teléfono
   la etiqueta se queda angosta y el valor manda: lo que se lee es el RFC. La
   misma clase pinta el folio fiscal y la fecha de timbrado en `#timbre-datos`
   (plan 0232 §C): dos `<dl>` en la página, la misma forma para los dos. */
.factura-publica__datos {
  display: grid;
  grid-template-columns: minmax(7rem, auto) 1fr;
  gap: var(--esp-2) var(--esp-4);
  margin: 0 0 var(--esp-4);
}

.factura-publica__clave {
  margin: 0;
  color: var(--texto-tenue);
  font-size: 0.875rem;
}

.factura-publica__valor {
  margin: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* La dirección opcional, plegada. El pliegue no es adorno: la pantalla de
   quien no la necesita —que son casi todos— se queda en cinco campos, y en un
   teléfono eso es la diferencia entre un formulario y una cuesta. */
.factura-publica__direccion {
  margin: 0 0 var(--esp-4);
  padding: var(--esp-3) var(--esp-4);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--fondo-hundido);
}

/* 44 px de blanco alrededor del disparador: se toca con el pulgar, no con un
   ratón. El marcador nativo se conserva —dibujar uno propio costaría una
   imagen, y ninguna cabe en esta hoja. */
.factura-publica__direccion > summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  font-weight: 600;
  cursor: pointer;
}

.factura-publica__direccion > summary:focus-visible {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
}

/* Recordar los datos en el teléfono de quien compra. La casilla se toca con el
   pulgar, así que el renglón entero es el blanco: 44 px de alto mínimo y el
   texto de ayuda debajo, dentro de la misma etiqueta. */
.factura-publica__recordar {
  display: flex;
  gap: var(--esp-3);
  align-items: flex-start;
  min-height: 44px;
  margin: 0 0 var(--esp-3);
  padding: var(--esp-3);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  cursor: pointer;
}


.factura-publica__recordar .campo__ayuda {
  display: block;
  margin-top: 2px;
}

.factura-publica__recordar:focus-within {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
}

/* Un botón que se lee como enlace: «olvidar mis datos» no es una acción
   principal y no debe competir con «Pedir mi factura». */
.boton-enlace {
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--texto-tenue);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}

.boton-enlace:focus-visible {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
}

/* Los títulos de las tres caras reciben el foco por guion (`tabindex="-1"`)
   cuando la página cambia de estado, para que el lector de pantalla lea el
   encabezado nuevo. El anillo tiene que verse: un foco invisible en un destino
   que se movió solo es peor que no moverlo. */
.tesela h2[tabindex]:focus-visible {
  outline: 2px solid var(--foco);
  outline-offset: 4px;
}

/* --------------------------------------------------------------------------
   10f. Enlace del proveedor (`/p/{token}`) — plan 0237 §L2
   --------------------------------------------------------------------------
   Casi todo se reusa: la lista de lo que piden se pinta con los `.renglon` de
   la cotización (§10b) y los dos formularios con las piezas de la autofactura
   (§10e) — `.campo`, `.campo__error`, `.factura-publica__datos`,
   `.boton--ancho`. Esta página sólo inventa el INTERRUPTOR y la LÍNEA, que es
   el bloque de campos de un renglón.

   La línea decide si esto se puede usar en un teléfono. Un proveedor cotiza de
   pie, en su bodega, con el pulgar: los campos van uno debajo del otro dentro
   de una tarjeta hundida que se lee como una unidad, y la nota —lo que casi
   nadie escribe— va plegada para que cada renglón mida lo que mide.
   -------------------------------------------------------------------------- */

/* El área de texto comparte `.campo__control`, que nace para un `<input>` de
   una línea: sin esto el texto se pegaría al borde de arriba. */
textarea.campo__control {
  min-height: 92px;
  padding-top: var(--esp-3);
  padding-bottom: var(--esp-3);
  line-height: 1.5;
  resize: vertical;
}

/* 44 px de blanco, y la etiqueta ENVOLVIENDO al control: el toque vale en toda
   la línea de texto y no sólo en el cuadrito de 13 px del navegador. */
.interruptor {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--esp-3);
  min-height: 44px;
  margin-bottom: var(--esp-4);
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
}

/* CAJ-R42 · esto se llamaba interruptor y dibujaba el cuadrito del navegador.
   Ahora es el mismo control del panel (`compartido/interruptor`): riel y
   perilla, con la casilla real encima —invisible pero enfocable, anunciable y
   clicable— para no perder ni el teclado ni el lector de pantalla. El
   `role="switch"` lo pone el HTML. */
.interruptor__control {
  position: absolute;
  z-index: 1;
  width: 48px;
  height: 28px;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  cursor: inherit;
}

.interruptor__riel {
  flex: none;
  position: relative;
  width: 48px;
  height: 28px;
  border-radius: 999px;
  border: 1px solid var(--borde-control, currentColor);
  background: var(--fondo-hundido, rgb(0 0 0 / 6%));
  transition: background-color 120ms ease;
}

.interruptor__perilla {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--fondo-elevado, #fff);
  border: 1px solid var(--borde-control, currentColor);
  transition: transform 120ms ease;
}

.interruptor__control:checked + .interruptor__riel {
  background: var(--accion);
  border-color: var(--accion);
}

.interruptor__control:checked + .interruptor__riel .interruptor__perilla {
  transform: translateX(20px);
}

.interruptor__control:focus-visible + .interruptor__riel {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
}

.interruptor__control:disabled + .interruptor__riel {
  background: var(--apagado-fondo, rgb(0 0 0 / 10%));
}

.interruptor__texto {
  min-width: 0;
}

/* Los renglones que hay que cotizar. Sin viñetas ni números: el número de
   renglón no le dice nada a quien surte; el nombre del producto sí. */
.lineas {
  list-style: none;
  margin: 0 0 var(--esp-5);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--esp-4);
}

.linea {
  padding: var(--esp-4);
  border: 1px solid var(--borde);
  border-radius: var(--radio-chico);
  background: var(--fondo-hundido);
}

/* La cabeza de la línea: qué es y cuánto piden. La raya la separa del primer
   campo para que se lea como el enunciado del bloque, no como otra etiqueta. */
.linea__que {
  margin-bottom: var(--esp-4);
  padding-bottom: var(--esp-3);
  border-bottom: 1px solid var(--borde);
}

.linea__nombre {
  margin: 0;
  font-weight: 700;
}

.linea__detalle {
  margin: var(--esp-1) 0 0;
  font-size: 0.875rem;
  color: var(--texto-tenue);
}

/* El recado del negocio: la misma tesela hundida de `.cotizacion__nota`, pero
   con dos párrafos dentro —la etiqueta y el texto— en vez de texto suelto,
   así que hay que quitarles el margen que traen de serie. En `/c/` esa clase
   envuelve texto sin hijos, de modo que estas reglas no la tocan. */
.cotizacion__nota > p {
  margin: 0;
}

.cotizacion__nota > p + p {
  margin-top: var(--esp-1);
}

/* La nota de un renglón, plegada; es lo último de la línea. */
.linea__nota > summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  font-size: 0.9375rem;
  font-weight: 600;
  cursor: pointer;
}

.linea__nota > summary:focus-visible {
  outline: 2px solid var(--foco);
  outline-offset: 2px;
}

.linea__nota .campo {
  margin: var(--esp-2) 0 0;
}

/* --------------------------------------------------------------------------
   11. Respeto por quien pide menos movimiento
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   Cantidad del carrito: menos, número, más
   ========================================================================== */

.cantidad {
  display: flex;
  align-items: stretch;
  gap: 0;
  border: 1px solid var(--borde-control);
  border-radius: var(--radio-pildora);
  overflow: hidden;
  background: var(--fondo-elevado);
}

/* 44px de lado: el mínimo con el que un pulgar acierta. Las flechas nativas de
   `input[type=number]` miden tres píxeles y en móvil ni siquiera se pintan, que
   es la razón por la que estos botones existen. */
.cantidad__paso {
  min-width: 44px;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.cantidad__paso:hover:not(:disabled) {
  background: var(--fondo-hundido);
}

.cantidad__paso:disabled {
  color: var(--apagado-fondo);
  cursor: default;
}

.cantidad__paso:focus-visible {
  outline: 2px solid var(--accion);
  outline-offset: -2px;
}

/* El número entre los dos botones. 16px como mínimo: por debajo, Safari en iOS
   hace zoom al enfocar y descoloca la pantalla a media compra. */
.cantidad .renglon__cantidad {
  width: 3.5rem;
  min-height: 44px;
  border: 0;
  border-left: 1px solid var(--borde);
  border-right: 1px solid var(--borde);
  border-radius: 0;
  background: transparent;
  color: var(--texto);
  font-family: var(--fuente-cuerpo);
  font-size: 1rem;
  font-weight: 600;
  text-align: center;
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Las flechitas nativas sobran: para eso están los dos botones. */
.cantidad .renglon__cantidad::-webkit-outer-spin-button,
.cantidad .renglon__cantidad::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.cantidad .renglon__cantidad:focus-visible {
  outline: 2px solid var(--accion);
  outline-offset: -2px;
}

/* --------------------------------------------------------------------------
   30. Páginas legales — /privacidad y /terminos
   --------------------------------------------------------------------------
   Documentos largos que se leen de corrido y se citan por sección. Nada de
   estilo en línea: la CSP de producción es `style-src 'self'` y un `<style>`
   incrustado quedaría bloqueado en el navegador de quien nos visita.
   -------------------------------------------------------------------------- */

.legal__hero {
  background: var(--fondo-hundido);
  border-bottom: 1px solid var(--borde);
  padding: var(--esp-7) 0 var(--esp-6);
  text-align: center;
}

.legal__titulo {
  margin: 0 0 var(--esp-2);
  font-size: clamp(1.75rem, 5vw, 2.5rem);
  line-height: 1.15;
}

.legal__pista {
  margin: 0;
  color: var(--texto-tenue);
  font-size: 1.0625rem;
}

.legal__fecha {
  margin: var(--esp-3) 0 0;
  color: var(--texto-tenue);
  font-size: 0.875rem;
}

/* El índice: en un documento de veinte secciones, poder saltar a la que
   importa es la diferencia entre que se lea y que no. */
.legal__indice {
  padding: var(--esp-5) 0;
  border-bottom: 1px solid var(--borde);
}

.legal__indice-titulo {
  margin: 0 0 var(--esp-3);
  font-size: 1rem;
}

.legal__indice-lista {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--esp-2) var(--esp-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.legal__indice-lista a {
  color: var(--texto-tenue);
  font-size: 0.9375rem;
  text-decoration: none;
}

.legal__indice-lista a:hover,
.legal__indice-lista a:focus-visible {
  color: var(--accion);
  text-decoration: underline;
}

.legal__cuerpo {
  max-width: 74ch;
  margin: 0 auto;
  padding-top: var(--esp-6);
  padding-bottom: var(--esp-7);
}

.legal__entrada {
  padding-bottom: var(--esp-5);
  margin-bottom: var(--esp-6);
  border-bottom: 1px solid var(--borde);
  color: var(--texto-tenue);
}

.legal__seccion {
  margin-bottom: var(--esp-6);
  /* Que el ancla no quede debajo de nada al saltar desde el índice. */
  scroll-margin-top: var(--esp-4);
}

.legal__seccion h2 {
  margin: 0 0 var(--esp-3);
  font-size: 1.25rem;
}

.legal__seccion h3 {
  margin: var(--esp-4) 0 var(--esp-2);
  font-size: 1rem;
}

.legal__seccion p,
.legal__seccion li {
  color: var(--texto-tenue);
  line-height: 1.65;
}

.legal__seccion p {
  margin: 0 0 var(--esp-3);
}

.legal__seccion ul {
  margin: 0 0 var(--esp-3);
  padding-left: 1.25rem;
}

.legal__seccion li {
  margin-bottom: var(--esp-1);
}

.legal__seccion strong {
  color: var(--texto);
}

/* Lo que el lector NO debe pasar por alto: quién responde por los datos de
   los clientes de una tienda, y que el fiado no lo otorga Cuentela. */
.legal__nota {
  background: var(--accion-suave);
  border: 1px solid var(--borde-fuerte);
  border-left: 4px solid var(--accion);
  border-radius: var(--radio-chico);
  padding: var(--esp-4);
  margin: 0 0 var(--esp-4);
}

.legal__nota > :last-child {
  margin-bottom: 0;
}

.legal__nota-titulo {
  margin: 0 0 var(--esp-2);
  font-size: 0.9375rem;
  color: var(--texto);
}

.legal__cierre {
  margin-top: var(--esp-6);
  padding-top: var(--esp-5);
  border-top: 1px solid var(--borde);
  color: var(--texto-tenue);
  font-size: 0.875rem;
  text-align: center;
}

/* La marca acompaña discretamente al negocio en el pie. */
.cabecera .negocio { flex: 1; width: 100%; flex-wrap: wrap; }
.cabecera .negocio > div { flex: 1; min-width: 120px; }
.cabecera #abrir-carrito { margin-inline-start: auto; }
.icono-plano { width: 20px; height: 20px; flex: none; }
.tienda__pie { padding-block: 32px; display: flex; justify-content: center; }
.marca--pie { font-size: 0.8125rem; color: var(--texto-tenue); }
.marca--pie .marca__logo { width: 20px; height: 20px; }
.producto-cantidad { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.producto-cantidad__valor { min-width: 2ch; text-align: center; font-variant-numeric: tabular-nums; }
.producto-cantidad > .boton { min-height: 44px; min-width: 44px; }

.acceso-selectores__fila { display: flex; gap: 4px; padding: 4px; margin-bottom: 16px; background: var(--fondo-hundido); border-radius: 999px; }
.acceso-selectores__fila > button { flex: 1; min-height: 44px; padding: 0 12px; border: 0; border-radius: 999px; background: transparent; font: 600 0.9375rem var(--fuente-cuerpo); color: var(--texto-tenue); cursor: pointer; }
.acceso-selectores__fila > button[aria-pressed="true"] { background: var(--fondo-elevado); color: var(--accion); box-shadow: var(--sombra-tesela); }

.fortaleza-password { display: grid; gap: .5rem; font-size: .875rem; }
.fortaleza-password meter { width: 100%; height: .75rem; }
