/* ============================================================
   Sinai — Catálogo · Sistema de diseño premium
   ============================================================ */
:root {
  /* Marca (paleta oficial) */
  --brand: #a61124;       /* 7426 C — vino */
  --brand-deep: #8a021a;  /* 207 C — burdeos */
  /* ⬇ CUÁNDO VA CADA UNO EN UN BORDE — la regla, escrita por fin (27 jul 2026).
     Había dos convenciones vivas y ninguna estaba dicha en ningún sitio, así que se
     elegía a ojo. Al hacer el censo de verdad resultó que **no eran arbitrarias: ya
     había un sentido debajo**, solo que nadie lo había escrito. Es este —

       · `--brand-tint` (#c4132c) = **PROVISIONAL**. Lo que está pasando ahora mismo y
         se puede deshacer soltando: el **hover de algo que se elige** (chips,
         miniaturas, filas de dirección/fecha/pedido, radios) y el **foco de un campo**.
       · `--brand` (#a61124) = **DEFINITIVO**. Lo elegido de verdad (`.chip.on`,
         `.g-thumb.active`, `.co-date.is-on`, `.co-addr:has(input:checked)`,
         `.fav-heart.is-fav`, `.addr-default`) y el **hover de un botón**, que no es una
         opción entre varias sino una acción que se dispara.

     Se corrigieron las CUATRO que iban al revés: `.refresh-btn:hover` (botón, iba en
     tint), los focos de `.cancel-input` y del textarea del checkout (campos, iban en
     brand) y `.addr-default` (es una elección HECHA —la dirección predeterminada—, iba
     en tint mientras su gemela del checkout ya iba en brand).
     **Única excepción viva, y con motivo:** `input.invalid` usa `--brand` para el borde
     de error. No es "elegido": es que la paleta no tiene un rojo de error y el vino es
     el único que hay. Si algún día se añade uno, este borde se va con él.
     ⚠️ **La diferencia entre los dos es de un pelo** (#c4132c contra #a61124) y por eso
     esto se pudre solo: nadie ve la incoherencia, se copia la regla de al lado y a los
     tres meses hay cuatro convenciones. Hay prueba que lo vigila **con las dos listas
     explícitas**: un borde nuevo no pasa hasta que alguien lo clasifique a mano. */
  --brand-tint: #c4132c;
  --brand-soft: #f7e4e7;
  /* Se llamaba `--gold` y se renombró el 27 jul 2026 (decisión suya). El nombre viejo
     contradecía a su propio comentario —que ya decía "ámbar"— y a la paleta oficial,
     que NO tiene ningún dorado. **El riesgo no era el nombre: era la familia que
     invita a inventar.** Alguien añade `--gold-dark` creyendo que existe una gama
     dorada, y los cinco ámbares oscuros sueltos que hubo que tokenizar en julio eran
     exactamente eso empezando. Se renombró la familia ENTERA de una pasada
     (`--gold-soft`, `--gold-ink`, `--gold-ink-deep`): dejar `--amber` conviviendo con
     `--gold-soft` habría sido peor que no tocar nada. Cero cambio visual — se comprobó
     que el archivo es idéntico al de antes con el token sustituido. */
  --amber: #f1a532;        /* 143 C — ámbar de marca. FONDOS Y ACENTOS, NUNCA TEXTO: sobre
                             blanco da 2.07:1 y sobre --bg 1.83:1 (AA pide 4.5). Para texto
                             ámbar está --amber-ink. */
  --amber-soft: #fbe6c4;
  --ok: #1f7a3d;          /* verde de ÉXITO (entregado, CP con cobertura, sin costo, ✓).
                             5.37:1 con blanco encima → pasa AA.
                             ⚠️ Había TRES verdes distintos y nadie los eligió: #1f9d55,
                             #1f7a3d y #1f7a45. Se unificaron aquí el 16 jul, y **uno de
                             ellos fallaba AA**: #1f9d55 da 3.49:1 y se usaba como TEXTO
                             en los avisos "ok" de login, registro, cuenta y el CP. */
  --ok-soft: #e6f4ea;     /* su fondo. También estaba duplicado (#e6f4ea vs #e4f4ea). */
  --amber-ink: #8a5a00;    /* el ámbar CUANDO HACE DE TEXTO. Pasa AA en los tres fondos del
                             sitio: 5.93:1 blanco · 5.25:1 --bg · 4.86:1 --amber-soft.
                             No es un color nuevo: ya estaba escrito a mano en 3 sitios
                             (.encargo-tag, .cart-item-ant, la nota de "sobre pedido"). */
  --amber-ink-deep: #4a3100; /* ámbar-tinta más oscuro para el ÉNFASIS sobre --amber-soft
                             (el nombre del producto en la nota de encargo). Tokeniza el
                             #4a3100 que estaba suelto; el #6b4700 del cuerpo se unificó
                             a --amber-ink (17 jul). */

  /* Neutros oficiales (grises fríos + pizarra) */
  --ink: #141c1e;         /* 433 C */
  --text: #2a3538;        /* 432 C */
  --muted: #5f676c;      /* gris secundario legible (AA sobre blanco y sobre --bg) */
  --faint: #9aa1a5;      /* gris tenue: separadores, íconos decorativos, estados :disabled
                            (WCAG exime lo inactivo) y placeholders de campos QUE YA TIENEN
                            <label> visible. 2.62:1 sobre blanco → NO llega a AA.
                            ⚠️ NO se usa para texto que haya que LEER (27 jul 2026): eran seis
                            —el placeholder del buscador (su único rótulo), la categoría de la
                            tarjeta, "(opcional)", los rótulos de Mis pedidos, el "Cerrado" de
                            los horarios y el "ya no está disponible" del checkout— y todos
                            pasaron a --muted. La regla ya estaba escrita más abajo y se
                            incumplía; ahora está aquí, junto al token. */
  --line: #dfdddb;        /* Cool Gray 3C */
  --line-soft: #eeecea;
  --bg: #f2f1f0;          /* Cool Gray 1C */
  --card: #ffffff;

  /* Tipografía del sistema (cero descargas, carga instantánea).
     Títulos en serif del sistema; cuerpo en sans del sistema. */
  /* Serif de títulos — ELEGIDA POR ÉL el 26 jul 2026 sobre un muestrario de 12 serif del
     sistema, viéndolas con nombre de producto y precio reales: **Charter, Palatino,
     "Palatino Linotype"**, en ese orden y con esas palabras.
     Qué cambia respecto a lo de antes (`ui-serif, "New York", …`): en iPhone y Mac los
     títulos dejan de ser New York y pasan a **Charter**, que es una serif dibujada para
     pantalla; si no estuviera, cae a Palatino, que sí está en los dos. En Windows no hay
     ninguna de las dos primeras y entra **Palatino Linotype** (cifras alineadas de
     fábrica), que es justo lo que ya se buscaba antes.
     ⚠️ La cola —Georgia, serif— NO la pidió él: es la red para Android y para cualquier
     sistema sin las tres, donde si no quedaría la serif por defecto (Times). Si alguna vez
     sobra, se quita sin tocar su orden.
     Sin webfonts, a propósito. Su marca es Garamond Premier Pro y no la trae ningún
     sistema. ⚠️ Esta pila tiene una SEGUNDA COPIA en `core/gate.php` (el portón del cambio
     de dominio, con su CSS propio): las dos se cambian juntas. */
  --serif: Charter, Palatino, "Palatino Linotype", Georgia, serif;
  --sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  /* Radios */
  --r-sm: 12px;
  --r: 18px;
  --r-lg: 26px;
  --r-pill: 999px;

  /* Sombras y velos (frías y suaves). Todas cuelgan de --ink-rgb: una sola base
     para sombras Y velos → cambiar la pizarra las mueve todas. Antes había cuatro
     negros sueltos (20,28,30 y 15,20,22) que ninguna era --ink; a esta opacidad
     tan baja las dos se ven igual, así que se unificaron en la de --ink (17 jul). */
  --ink-rgb: 20, 28, 30;   /* = --ink #141c1e, para rgba() de sombras y velos */
  --shadow-xs: 0 1px 2px rgba(var(--ink-rgb),.06);
  --shadow-sm: 0 4px 16px rgba(var(--ink-rgb),.08);
  --shadow: 0 14px 38px rgba(var(--ink-rgb),.11);
  --shadow-lg: 0 28px 64px rgba(var(--ink-rgb),.20);

  /* Movimiento. Las curvas de entrada/salida arrancan rápido y frenan: lo que
     el usuario mira es el principio, no el final. --ease-drawer es la curva que
     ya tenían los dos cajones; se conserva su valor exacto. */
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-drawer: cubic-bezier(.2, .8, .2, 1);

  --dur-xs: 120ms;
  --dur-sm: 150ms;
  --dur: 200ms;
  --dur-lg: 260ms;

  /* Ancho tope del sitio. Es FLUIDO hasta aquí: se encoge con la ventana y se
     detiene al llegar a este número. 1280 era el estándar de hace unos años y
     se quedó angosto; los temas modernos de Shopify arrancan en 1440 (el propio
     Tinker de shop.sinaidelibakery.com ofrece 1440/1920/2400 y usa 1920). */
  --maxw: 1320px;
  --pad: 26px;

  /* ── LOS HUECOS DE LAS REJAS DE TARJETAS ─────────────────────────────────
     TRES tokens, valores elegidos por él sobre un preview (27 jul 2026).

     ⚠️ POR QUÉ SON TOKENS Y NO NÚMEROS. Se contaron los huecos de todas las rejas
     del sitio y salieron **SIETE distintos en escritorio (10, 14, 15, 16, 18, 20 y
     22) y cuatro en móvil**, sin ningún criterio que los separase: cada reja nueva
     inventaba el suyo. Ahora son tres, y el que toque una reja no tiene que decidir
     nada. Al ancho tope, Imprescindibles e Instagram son las dos de 5 columnas con
     el mismo hueco, así que **quedan alineadas columna a columna**.

     QUIÉN BEBE DE CADA UNO — si se añade una reja, va a uno de estos tres:
       · --gap-producto  → .grid · .fav-grid            (la reja densa de producto)
       · --gap-reja      → .rail-doble · .ig-grid · .dept-mosaic · .rel-grid
                           .app-feats · .addr-grid · .pay-cards
       · --gap-contenido → .recipe-grid (+ .recipe-grid-all) · .info-cards
                           .branch-grid · .app-steps

     ⚠️ QUIÉN NO ENTRA, y es decisión, no olvido — son los tres que NO son rejas de
     tarjetas: `.co-addr-list` (direcciones del checkout) y `.gallery-thumbs`
     (miniaturas de la ficha) son listas apretadas dentro de una columna estrecha,
     donde este hueco partiría la lista en dos; y `.about-story` es el CANALÓN entre
     dos columnas (foto + texto), que necesita el triple. Meterlos para que "cuadre
     el censo" sería cambiar el sitio para que la tabla quede bonita. */
  --gap-producto:  12px;
  --gap-reja:      14px;
  --gap-contenido: 16px;

  /* Ancho del banner del inicio. El alto SALE de aquí: las 8 imágenes del panel
     son 16:9, así que alto = ancho / 1.78. Es la única palanca que no recorta el
     arte (ver el comentario de .hero-slide). Tocar aquí y nada más.
     900 → 850 → 870 el 18 jul 2026, elegido moviéndolo en vivo en un tablero con
     el sitio a escala real. Criterio suyo: los departamentos SIEMPRE se ven,
     aunque sea una parte. A 900 el mosaico asomaba 83px (las baldosas cortadas por
     la mitad); a 870 asoma 100px, la baldosa entera. Si alguien sube este número,
     mide qué pasa abajo. */
  --hero-w: 870px;
  /* Velo que APAGA las vecinas del carrusel. Va ENCIMA de cada vecina, no como
     fondo detrás: de fondo se asomaba por el hueco entre banners y se veía una
     franja (lo cazó el usuario con una captura); así el hueco enseña el fondo de
     la página y no hay banda que asome.
     ⚠️ HISTORIA, porque este valor ya fue y volvió el MISMO día (18 jul 2026):
     era GRIS, pasó a BLANCO al 58% —el gris dejaba turbios los carteles de color—
     y volvió al GRIS al 50%, esta vez acompañado de un `grayscale(20%)` en la foto.
     Y ahora sí funciona por eso: **antes el gris ensuciaba porque tapaba color con
     color; ahora la foto se desatura un poco primero, así que el gris cae sobre
     algo que ya va hacia el gris.** Los dos números los eligió él moviéndolos en
     vivo, uno al lado del otro. No cambiar uno sin mirar el otro. */
  --hero-apagado: #9aa1a5;
  /* ⚠️ Coincide con `--faint` en el valor y NO cuelga de él a propósito (27 jul 2026,
     había un P3 preguntándolo). Son dos cosas distintas: `--faint` es un gris de TEXTO
     y se mueve por CONTRASTE —de hecho el 27 jul seis usos suyos se fueron a `--muted`
     por AA—, y esto es un VELO sobre una foto, elegido a ojo por él junto al
     `grayscale(20%)` de aquí abajo. Atarlos haría que subir el contraste de un rótulo
     cambiara el banner del inicio, sin que nadie lo pidiera ni lo notara. */

  /* ===== Tipografía =====
     Escala única del sitio: de aquí se elige, no se inventa un número nuevo.
     Todo en rem sobre la base de 16px del navegador, así que quien tenga puesto
     "texto grande" en su sistema ve el sitio crecer. Los tamaños se eligieron con
     previews delante (17 jul 2026); los de la tarjeta vienen del 16 jul: no se tocan.
     Regla: si hace falta un tamaño nuevo, se decide y se añade AQUÍ, no en la clase.

     ⚠️ 26 jul 2026 — LOS TOKENS DE TÍTULO CRECIERON 1 px (0.0625rem), pedido suyo al
     estrenar la serif Charter, tras verlos uno al lado del otro en un preview.
     Se movieron los TOKENS y no las clases porque está medido: de los siete, seis **no
     los usa nadie más que los títulos**; el único invitado es `.co-date-num` (el número
     del día en el calendario del pago), que crece con `--t-block` y a 21 px sigue
     cuadrando en su casilla.
     **Las TRES excepciones, decididas por él viendo el preview:**
       · el NOMBRE DEL PRODUCTO en la ficha sube solo en escritorio (36→37): el mínimo del
         clamp —los 28 del teléfono— se queda como estaba;
       · el nombre en la TARJETA no se toca en ninguna pantalla (vive en la escala aparte
         `--tc-*`, elegida el 16 jul y ya afinada por breakpoint);
       · el nombre del departamento en el MOSAICO tampoco: va en sans y su token
         (`--t-strong`) lo comparten 25 sitios.
     Y dos que no podían subir aunque se quisiera: `--t-body` (lo usa el cuerpo de lectura
     entero y es hermano de `--t-field`, que NO puede bajar de 16 o iOS hace zoom al
     enfocar) y `--t-price`, que no es un título. Con `--t-body` se queda en 16 el único
     título chico del sitio, `.trk-title`.
     ⚠️ En los clamp solo crecieron los EXTREMOS: en la franja donde manda el `vw` el
     tamaño lo sigue poniendo el ancho de la pantalla. Meter el px dentro del `calc` habría
     roto la regla de "todo en rem" (y con ella el "texto grande" del sistema). */
  --t-hero: clamp(1.8125rem, 5vw, 2.8125rem);  /* 29→45  título de lo que estás viendo */
  /* 28→36  el NOMBRE del producto en la ficha. Es un papel aparte del --t-hero y él
     lo decidió mirándolo (18 jul 2026): el título de una PÁGINA ocupa todo el ancho,
     pero el nombre del producto vive en una columna de 608px, así que a 44 casi
     todos los nombres caían en dos líneas y 101 de los 2.043 se iban a TRES. A 36
     caben ~34 caracteres por línea. En el teléfono se queda en 28, que es donde
     estaba y le gustó — y por eso el +1 px del 26 jul NO tocó el mínimo del clamp. */
  --t-product: clamp(1.75rem, 4.5vw, 2.3125rem);  /* 28 en el teléfono → 37 en escritorio */
  --t-banner: clamp(1.5625rem, 2.8vw, 1.8125rem); /* 25→29  el banner de la app */
  --t-page: 1.8125rem;    /* 29  título de la pantalla */
  --t-panel: 1.5625rem;   /* 25  título de pantalla dentro de la cuenta */
  --t-section: 1.5625rem; /* 25  título de sección (h2): va bajo el de pantalla y sobre el bloque */
  --t-block: 1.3125rem;   /* 21  título de bloque o tarjeta (h3) */
  /* 23  EL ENCABEZADO DE SECCIÓN EN EL TELÉFONO, y es uno solo para los dos que se veían
     distintos: el título del listado (`#browse-title`, que bajaba a 21) y los de los
     carruseles del inicio (`.sec-head`, que se quedaban en 25). Él los vio descuadrados en
     el teléfono y pidió unificarlos aquí (26 jul 2026); en escritorio los dos siguen en 25.
     Es token y no un número suelto porque 23 no estaba en la escala, y es un token PROPIO
     porque subir `--t-block` habría movido los títulos de bloque de todo el sitio y el
     número del día del calendario del pago.
     ⚠️ Nació el mismo día como `--t-listado` a 22 y duró una tanda: al unificar dejó de ser
     "el del listado" y el nombre habría mentido. */
  --t-section-movil: 1.4375rem;
  --t-body: 1rem;         /* 16  cuerpo de lectura */
  --t-field: 1rem;        /* 16  campos de texto. NO BAJAR: por debajo de 16 Safari en iOS
                                 hace zoom al enfocar y descuadra la página. */
  --t-strong: .9375rem;   /* 15  dato que pesa: precio, total, nombre en una fila */
  /* 14  interfaz: botones, enlaces, migas, filtros. **Es 14 SOLO EN ESCRITORIO**: en el
     teléfono vuelve a 13, abajo, en el `@media` de 720 — pedido suyo el 26 jul 2026 (*"súbelo
     a 14 en web"*). Es el token más repartido del sitio: lo usan **86 reglas**, así que un
     punto aquí mueve carrito, checkout, filtros, migas, botones, avisos y pie a la vez. */
  --t-ui: .875rem;
  /* 15  el menú. Es OTRO papel, no interfaz: es el rótulo permanente del sitio, no pertenece
     a ninguna pantalla. A 13 se veía chico dentro de su barra — y el preview lo enseñaba suelto
     sobre papel blanco, sin el aire de al lado (17 jul). **Es 15 SOLO EN ESCRITORIO**: en el
     teléfono vuelve a 14 (26 jul 2026, suyo), donde además el menú vive dentro del cajón y no
     en la barra, que es otro contexto y otro aire. */
  --t-nav: .9375rem;
  /* 14  el SUBMENÚ de departamentos que cuelga del menú. Un punto por debajo de su padre
     (26 jul 2026, suyo: *"el submenú sí debe estar 1 px más chico"*, **en web**) — al subir
     el menú a 15 los dos quedaron iguales y el desplegable dejó de leerse como lo que es,
     una lista subordinada. Token propio y no `--t-ui` porque el papel es el menú, no la
     interfaz: si mañana se mueve la letra de los botones, el submenú no tiene por qué irse
     con ella. **En las DOS pantallas va un punto por debajo del menú**: 15/14 en escritorio
     y 14/13 en el teléfono (el override de móvil está abajo, con el resto de la escala del
     teléfono). */
  --t-nav-sub: .875rem;
  --t-small: .75rem;      /* 12  letra chica */
  --t-label: .6875rem;    /* 11  etiquetas en mayúsculas y distintivos */
  --t-tiny: .625rem;      /* 10  atributos de la tarjeta (16 jul) */
  --t-micro: .5625rem;    /* 9   la categoría de la tarjeta (16 jul) */
  /* 26→32  el precio de la ficha: DOS PUNTOS por debajo del nombre, en las dos
     pantallas (18 jul 2026, suyo). Era 34 fijo, con un salto a 28 en móvil escrito
     aparte — y en el teléfono empataba con el nombre, que también es 28: dos cosas
     del mismo tamaño peleando. Ahora es una sola declaración con clamp y la
     distancia con el nombre se mantiene arriba y abajo. */
  --t-price: clamp(1.625rem, 4vw, 2rem);
  --t-price-was: 1.125rem;/* 18  su tachado */

  /* Interlineado (leading). Regla: inverso al tamaño — apretado en títulos, suelto en
     lectura. Sustituye a los line-height sueltos que iban clase por clase (22 jul 2026).
     Elegidos con preview: cuerpo 1.65, títulos grandes 1.10 (suyo). */
  --lh-display: 1.1;   /* hero, nombre de producto, precio */
  --lh-heading: 1.2;   /* títulos de página, sección y bloque */
  --lh-snug: 1.3;      /* dato fuerte, nombre en tarjeta, atributos */
  --lh-ui: 1.45;       /* interfaz, notas, direcciones, hints */
  --lh-body: 1.65;     /* cuerpo de lectura largo */
  /* Tracking: 0 en TODO por decisión suya (22 jul 2026). La fuente de sistema ya viene
     ajustada; no se añade letter-spacing en ningún lado, ni en mayúsculas. */

  /* Glifos. Aquí font-size es el tamaño de un DIBUJO (☰ ✕ ▾ › ✓), no de una letra:
     no pertenecen a la escala de texto y no se consolidan con ella. */
  --i-xs: .625rem;    /* 10 ▾ */
  --i-sm: .875rem;    /* 14 */
  --i-md: .9375rem;   /* 15 ✕ ↻ ♥ */
  --i-base: 1rem;     /* 16 📍 */
  --i-lg: 1.0625rem;  /* 17 › */
  --i-xl: 1.125rem;   /* 18 › del menú */
  --i-2xl: 1.25rem;   /* 20 − + */
  --i-3xl: 1.375rem;  /* 22 el + de la FAQ */
  --i-4xl: 1.5625rem; /* 25 ☰ (18 jul: creció para igualar a los otros dos iconos) */
  --i-hero: 2.375rem; /* 38 ✓ de la confirmación */

  /* ESCALA PROPIA DE LA TARJETA (23 jul 2026, suya con preview). Va APARTE de --t-* a
     propósito: la tarjeta es su propio papel y no debe moverse cuando cambie el texto
     general (antes tiraba de --t-body/--t-strong/…). Se separó al UNIFICAR las dos
     tarjetas —catálogo y carrusel, que tenía 13/15/8 propios en móvil— y subir todo +1.
     Regla que se conserva: el NOMBRE manda sobre el precio (17 > 16) y el precio pesa 800
     contra 600, para que empatados siga mandando el nombre. No es estética, es negocio. */
  --tc-name: 1.0625rem;  /* 17  nombre */
  --tc-price: 1rem;      /* 16  precio (800, un punto bajo el nombre a propósito) */
  --tc-was: .8125rem;    /* 13  precio tachado */
  --tc-add: .8125rem;    /* 13  botón "Agregar" */
  --tc-attr: .6875rem;   /* 11  atributos + supervisión (.attr-kosher) */
  --tc-cat: .625rem;     /* 10  categoría (.card-eyebrow; antes 8 en móvil, ya unificada) */
  --tc-unit: .8125rem;   /* 13  la unidad /kg junto al precio */
}

/* touch-action NO se hereda: se pone en TODOS los elementos para quitar el
   zoom/salto por doble toque en móvil (el pellizco para acercar sigue funcionando). */
* { box-sizing: border-box; touch-action: manipulation; }
/* ⚠️ AQUÍ HABÍA UN `html { scroll-behavior: smooth }` GLOBAL Y SE BORRÓ (20 jul 2026).
   Hacía que se animara CUALQUIER desplazamiento de la página, y con él el salto al
   abrir un producto: estando abajo en el catálogo, la página entera se iba
   deslizando hasta arriba. Él lo reportó tres veces y yo intenté apagarlo llamada
   por llamada —primero con `behavior:'auto'` (que NO significa "sin animación":
   significa "usa el scroll-behavior del CSS", o sea este) y después apagando el
   estilo a mano—, peleándome con la regla en vez de quitarla.
   **No le hacía falta a nadie:** los cuatro movimientos que sí queremos suaves
   (el carrusel, los chips, la lista de categorías y el formulario de direcciones)
   piden `behavior: 'smooth'` ellos mismos. Lo único que dependía de esta regla era
   el enlace "Saltar al contenido", y para un salto de accesibilidad es mejor
   instantáneo.
   Si alguien la repone, vuelve el problema y no falla ninguna prueba salvo la que
   lo vigila en probar-accesibilidad.js. */
/* El header es sticky y mide ~129px: sin esto, al tabular hacia abajo el navegador
   deja el elemento pegado al borde de arriba y el header lo TAPA — el usuario de
   teclado no ve dónde está el foco. La altura no se escribe a mano: la mide site.js
   y la publica en --header-h (WCAG 2.4.11). */
html { scroll-padding-top: calc(var(--header-h, 129px) + 12px); }
html, body { margin: 0; }

/* Enlace de salto: primer elemento tabulable de la página, invisible hasta que
   recibe foco. Sin él, cada página obliga a tabular ~15 elementos repetidos
   (buscador, cuenta, carrito, nav) antes de llegar al contenido (WCAG 2.4.1). */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: #fff; padding: 12px 18px; border-radius: 0 0 var(--r-sm) 0;
  font-size: var(--t-ui); font-weight: 600; text-decoration: none;
}
.skip-link:focus { left: 0; }

/* Foco visible global (Emil, 22 jul 2026): completa los controles que NO tenían anillo
   de foco — botones de tarjeta/ficha, ±, departamentos, miniaturas, migas, "Cargar más",
   filtros, variante… Se usa `outline` (no el box-shadow de los inputs) a propósito: varios
   viven en cajas con `overflow:hidden` (.card, .g-thumb) que RECORTARÍAN una sombra.
   `:where()` deja la especificidad en 0 → los focos propios que ya existen (#search, sort,
   precio, auth) siguen ganando sin cambiar. Solo teclado (`:focus-visible`): cero cambio
   para ratón/táctil. */
:where(a[href], button, [role="button"], input, select, textarea, summary,
  [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
/* Protección básica de imágenes: no arrastrar, no seleccionar y sin menú de
   "guardar imagen" al mantener presionado (iOS). Refuerza lo de site.js. */
img { -webkit-user-drag: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
/* Controles: sin selección de texto ni destello al tocar en móvil */
button, a, .chip, .card, .dept-tile, .cat-nav-all, .cat-nav-head, .cat-nav-sub,
.qty-btn, .hero-link, .crumb, select {
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
}
body {
  font-family: var(--sans);
  background: var(--bg);
  color: var(--text);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
::selection { background: var(--brand); color: #fff; }
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }
.hidden { display: none !important; }
/* El atributo `hidden` tiene que ganar SIEMPRE. El navegador lo aplica con un
   `display:none` suyo, y CUALQUIER `display` nuestro se lo tumba: entonces el
   elemento sigue ahí, vacío, y se ve como una barra o un hueco. Ya pasó dos veces
   (`.cart-badge`, que se parchó a mano, y `#co-solo-tienda`, que salía como una
   barra gris encima de las direcciones del checkout). Esta línea lo cierra para
   todo el sitio: no hace falta acordarse en cada `display:flex`. */
[hidden] { display: none !important; }
code { background: var(--line-soft); padding: 2px 6px; border-radius: 6px; font-size: var(--t-small); }

/* ============================================================
   Header
   ============================================================ */
header {
  position: sticky; top: 0; z-index: 30;
  background: rgba(255,255,255,.85);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid var(--line);
}
.header-row {
  display: flex; align-items: center; gap: 20px;
  max-width: var(--maxw); margin: 0 auto; padding: 15px var(--pad) 12px;
}
.logo { height: 50px; width: auto; display: block; flex: 0 0 auto; }

/* El buscador vive en su PROPIA FILA, debajo del logo y del menú (18 jul 2026,
   decisión suya: "menú arriba, buscador abajo"). Antes iba en la fila del logo y
   el menú debajo. */
.header-search { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad) 13px; display: flex; justify-content: center; }
.search-wrap { flex: 1 1 auto; position: relative; display: flex; align-items: center; max-width: 540px; margin: 0 auto; }
.search-icon { position: absolute; left: 17px; width: 18px; height: 18px; color: var(--faint); pointer-events: none; }
/* ⚠️ El ALTO se fija a 40 (era 46) y por eso el relleno vertical pasa a 0: con
   padding y alto a la vez, el que manda depende del box-sizing y se acaba
   peleando. La letra NO se toca — `--t-field` son 16px y por debajo Safari en iOS
   hace zoom al enfocar el campo, y 36 es el mínimo que aguanta esa cuenta (16 de
   letra + 10 de aire). **42 en las dos pantallas** — se probó partirlo (40 arriba,
   42 abajo) y él lo dejó en uno solo: un número, un sitio. */
#search {
  width: 100%; height: 42px; box-sizing: border-box; padding: 0 18px 0 46px;
  border: 1px solid var(--line); border-radius: var(--r-pill);
  font-size: var(--t-field); color: var(--ink); background: var(--card);
  transition: border-color .18s, box-shadow .18s;
}
/* --muted y no --faint: el buscador NO tiene <label> visible, así que este
   placeholder ES su rótulo — y en --faint daba 2.62:1 (AA pide 4.5). Los
   placeholders de login/registro/cuenta sí se quedan tenues: ahí el <label>
   está encima y el placeholder solo repite. */
#search::placeholder { color: var(--muted); }
#search:focus { outline: none; border-color: var(--brand-tint); box-shadow: 0 0 0 4px var(--brand-soft); }

/* `margin-left: auto` NO sobra aunque el menú tenga `flex: 1 1 auto`: entre 720 y
   920 el menú está OCULTO (display:none) y entonces no empuja nada, así que el logo
   y los iconos se apelotonaban a la izquierda. Se veía en una captura suya. */
.header-actions { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; margin-left: auto; }
.refresh-btn {
  display: flex; align-items: center; gap: 7px;
  width: fit-content; margin: 18px auto; /* centrado bajo el pie */
  border: 1px solid var(--line); background: var(--card); color: var(--muted);
  font-size: var(--t-small); font-weight: 600; padding: 7px 13px; border-radius: var(--r-pill);
  cursor: pointer; white-space: nowrap; transition: border-color .15s, box-shadow .15s, transform .1s;
}
.refresh-btn:hover { border-color: var(--brand); box-shadow: var(--shadow-xs); }   /* es un BOTÓN: va en --brand, ver la regla junto a los tokens */
.refresh-btn:active { transform: scale(.97); }
.refresh-btn:disabled { opacity: .55; cursor: default; }
.refresh-ico { font-size: var(--i-md); line-height: 1; }

main { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--pad) 48px; }

/* ============================================================
   Hero (slider de banners)
   ============================================================ */
/* SANGRADO: el carrusel se sale del ancho del sitio hasta los bordes de la
   ventana, para que el corte de las vecinas caiga en la orilla de la pantalla
   (donde se lee natural) y no rebanadas a media página.
   ⚠️ El `- var(--sbw)` NO sobra: 100vw INCLUYE la barra de scroll, así que en
   Windows el hero saldría ~15px más ancho que la ventana y aparecería una barra
   horizontal. site.js mide esa barra (en Mac da 0). */
.hero {
  position: relative; margin: 14px 0 0;
  margin-inline: calc((100% - 100vw + var(--sbw, 0px)) / 2);
}
/* CON máscara de desvanecido desde el 18 jul 2026 — y OJO, porque esto se probó
   ANTES y se quitó: con el velo GRIS, la máscara dejaba una banda gris VACÍA en
   la orilla, un rectángulo de gris sin nada. Con el velo BLANCO de ahora eso
   desaparece, porque lo que queda al desvanecerse es el color del papel. O sea:
   la máscara no era mala, era incompatible con el gris. Elegido con previews.
   ⚠️ El ancho del fundido NO puede ser un % del track ni un número fijo: los dos
   se comen el banner del centro en cuanto la ventana se estrecha (un 13% ya lo
   mordía por debajo de 1150px). Se calcula desde el hueco REAL que dejan las
   vecinas —(ancho del track − ancho del banner) / 2— y se toma el 60% de ese
   hueco, así el fundido SIEMPRE muere antes de tocar el del centro y en el
   teléfono se apaga solo. */
/* ⚠️ DOS cosas hacen que el fundido no se sienta brusco, y la segunda es la que de
   verdad lo arregló:
   1. La curva NO es una rampa recta. Con dos paradas la opacidad sube a ritmo
      constante y se ve el codo donde llega al 100 %. Las de abajo dibujan una S
      (`3t²−2t³`), que arranca y termina plana.
   2. **El degradado TERMINA FUERA DE LA PANTALLA.** Con la S puesta él seguía
      sintiéndolo brusco, y dio con la causa: *"que termine después de la pantalla,
      no en el borde"*. Antes la transparencia total caía justo en x=0, así que el
      final del fundido se veía **dentro** de la imagen. Ahora el cero está a
      `-.7 × --fade` (fuera de la ventana) y en el borde la foto todavía va al
      **37 %**: lo que la corta es la pantalla, no el degradado, y así no queda
      ningún punto donde el ojo pueda decir "aquí se acabó". Las paradas reparten
      la S entre `-.7` y `1`; el `.7` lo eligió él moviéndolo en vivo.
   Se declara UNA vez en `--hero-mask` y se usa dos: escribirla dos veces (una por
   prefijo) es la segunda copia de siempre, y la que se queda vieja es la del
   navegador que no miras. */
.hero-track {
  display: flex; gap: 24px; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none;
  --fade: calc((100% - min(88%, var(--hero-w, 850px))) / 2 * .6);
  --hero-mask: linear-gradient(to right,
    rgba(0,0,0,0)    calc(var(--fade) * -.7),
    rgba(0,0,0,.043) calc(var(--fade) * -.4875),
    rgba(0,0,0,.156) calc(var(--fade) * -.275),
    rgba(0,0,0,.316) calc(var(--fade) * -.0625),
    rgba(0,0,0,.5)   calc(var(--fade) * .15),
    rgba(0,0,0,.684) calc(var(--fade) * .3625),
    rgba(0,0,0,.844) calc(var(--fade) * .575),
    rgba(0,0,0,.957) calc(var(--fade) * .7875),
    #000 var(--fade),
    #000 calc(100% - var(--fade)),
    rgba(0,0,0,.957) calc(100% - var(--fade) * .7875),
    rgba(0,0,0,.844) calc(100% - var(--fade) * .575),
    rgba(0,0,0,.684) calc(100% - var(--fade) * .3625),
    rgba(0,0,0,.5)   calc(100% - var(--fade) * .15),
    rgba(0,0,0,.316) calc(100% + var(--fade) * .0625),
    rgba(0,0,0,.156) calc(100% + var(--fade) * .275),
    rgba(0,0,0,.043) calc(100% + var(--fade) * .4875),
    rgba(0,0,0,0)    calc(100% + var(--fade) * .7));
  -webkit-mask-image: var(--hero-mask);
          mask-image: var(--hero-mask);
}
.hero-track::-webkit-scrollbar { display: none; }
/* ⚠️ aspect-ratio 16/9 = el de las imágenes REALES del panel (1500×844 y 1200×675,
   los 8 verificados). Antes el escritorio usaba 2/1 y RECORTABA ~12% del alto:
   en el banner de helados se comía el borde del título. No volver a "acomodar"
   este número: se manda solo si cambia el arte que suben al panel.
   El ancho manda y el alto lo sigue; el sobrante son las orillas asomando. */
/* Las vecinas van apagadas; solo manda la del centro (la clase la pone ui.js).
   El apagado es un VELO gris ENCIMA (::after), no opacidad sobre un fondo: ver
   el comentario de --hero-apagado. El velo se recorta solo con las esquinas
   redondeadas del slide (overflow: hidden) y no estorba al clic del banner. */
.hero-slide {
  position: relative;
  flex: 0 0 min(88%, var(--hero-w, 900px)); scroll-snap-align: center;
  aspect-ratio: 16/9; background: var(--line-soft);
  border-radius: var(--r); overflow: hidden; box-shadow: var(--shadow-xs);
}
/* `--foco` lo escribe ui.js en CADA cuadro del scroll: 1 = está en el centro,
   0 = está lejos. El velo se levanta siguiendo al dedo.
   ⚠️ NO poner aquí una `transition`: se probó y se sentía pegajosa — el velo
   tardaba en quitarse porque esperaba a que el scroll parara y luego aún tenía
   que fundirse. Lo que manda el ritmo es el scroll, no un temporizador. */
.hero-slide::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: var(--hero-apagado); opacity: calc(.5 * (1 - var(--foco, 0)));
}
/* La vecina se DESATURA un 20% además de apagarse (18 jul 2026, elegido moviéndolo
   en vivo). Va de la mano del velo gris: ver el comentario de --hero-apagado.
   Sigue a `--foco` con la misma cuenta que el velo, así que el color vuelve
   mientras arrastras, no de golpe al soltar.
   ⚠️ Esto SÍ puede colgar de --foco y el desenfoque NO podía: `grayscale` es una
   matriz de color de una pasada y la tarjeta gráfica la resuelve sola; un `blur`
   son varias pasadas y se re-rasteriza entero en cada cuadro. No son comparables
   aunque los dos sean `filter`.
   ⚠️ Y SIN DESENFOQUE: se probó el mismo día y él lo quitó — *"no se ve bien"*.
   Ya se miró en pantalla y se descartó; no es un olvido. */
.hero-slide img {
  width: 100%; height: 100%; object-fit: cover; object-position: center; display: block;
  filter: grayscale(calc(20% * (1 - var(--foco, 0))));
  /* Fundido de entrada al cargar (como las tarjetas). SOLO opacidad: el `filter` lo
     escribe ui.js cada cuadro y no debe llevar transición o el grayscale iría con retraso. */
  opacity: 0; transition: opacity var(--dur) var(--ease-out);
}
.hero-slide img.loaded { opacity: 1; }
.hero-link { cursor: pointer; display: block; }
.hero-link:focus-visible { outline: 3px solid var(--brand); outline-offset: 3px; }
/* ⚠️ ESTOS SE QUEDAN EN 9px Y SIN ZONA TÁCTIL. NO "arreglarlos" a 44.
   Decisión suya (16 jul), y hay dos motivos, los dos buenos:
   1. Nadie los toca: para cambiar de banner se DESLIZA. Son un indicador, no un mando.
   2. Y si tuvieran 44, sería PEOR: flotan encima del arte, así que la zona taparía una
      franja de 44px del banner — y el banner ES un enlace a su categoría. Les robaría
      los toques a lo que la gente sí toca.
   Bajados de 14 a 8: estorban menos al arte. */
.hero-dots { position: absolute; bottom: 8px; left: 0; right: 0; display: flex; justify-content: center; align-items: center; gap: 8px; pointer-events: none; }
.hero-dot { width: 9px; height: 9px; border-radius: 50%; border: none; background: rgba(255,255,255,.55); cursor: pointer; padding: 0; transition: width .2s, background .2s; }
.hero-dot.on { background: var(--card); width: 26px; border-radius: var(--r-pill); }
/* La FILA no intercepta toques, solo sus controles. Importa desde que existe el
   botón de parar: la franja pasó de 9px de alto a 24, y todo lo que ella absorba
   se lo quita al banner, que ES un enlace. Así solo roban los 9px del punto y los
   24 del botón, no la banda entera de lado a lado. */
.hero-dot, .hero-pausa { pointer-events: auto; }
/* 24×24 = el mínimo de WCAG 2.5.8 (AA). No 44: flota sobre el arte del banner y
   ahí cada píxel se lo quita al enlace (mismo motivo que los puntos, 16 jul). */
.hero-pausa {
  width: 24px; height: 24px; border-radius: 50%; border: none; padding: 0; margin-left: 4px;
  background: rgba(var(--ink-rgb),.45); color: #fff; cursor: pointer;
  /* El ❙❙/▶ es un DIBUJO, no texto: va en la escala de glifos (--i-*), no en la de
     tipografía. Lo cazó probar-tipografia.js — lo había puesto en 9px crudo. */
  font-size: var(--i-xs); line-height: 1; display: flex; align-items: center; justify-content: center;
}
.hero-pausa:hover { background: rgba(var(--ink-rgb),.7); }

/* ============================================================
   Bloques / títulos de sección
   ============================================================ */
.sec-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
.sec-head :is(h2, h3) {
  font-family: var(--serif); font-weight: 700; color: var(--ink);
  font-size: var(--t-section); margin: 0;
}
/* Tira de beneficios (inicio) */
.bene { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; margin-top: 56px; padding: 22px 10px; background: var(--card); border: 1px solid var(--line); border-radius: var(--r); box-shadow: var(--shadow-xs); }
.bene-item { display: flex; align-items: center; gap: 14px; padding: 6px 24px; }
.bene-item + .bene-item { border-left: 1px solid var(--line-soft); }
.bene-item svg { width: 28px; height: 28px; color: var(--brand); flex: none; }
.bene-item b { display: block; font-size: var(--t-ui); color: var(--ink); }
.bene-item span { font-size: var(--t-small); color: var(--muted); }
/* El corte de 4→2 columnas es 1040 y NO 760 (27 jul 2026). Estaba en 760, así que en
   cualquier portátil chico las cuatro columnas dejaban ~83px de texto y los rótulos
   caían en dos renglones. **El número está MEDIDO, no elegido**: con AppKit
   (`NSString.sizeWithAttributes`, la misma San Francisco del sistema) el par más ancho
   —"+1,500 productos" a 13 semibold sobre "Nacionales e importados" a 12— pide
   **140px**, y con el relleno del item (48), el hueco (14) y el icono (28) eso exige
   una columna de 230 → **1040px de ventana**. A 900, que es lo que proponía el P3,
   solo hay 118: seguiría partiéndose. No se pone al filo (a 1000 justo hay 143) porque
   los 140 salen de una medida del texto de HOY: si mañana un rótulo crece, el margen
   se lo come. Entre 761 y 1040 pasa a 2 columnas, donde sobra sitio. */
@media (max-width: 1040px) { .bene { grid-template-columns: 1fr 1fr; gap: 16px 0; } .bene-item { padding: 6px 16px; } .bene-item:nth-child(odd) { border-left: none; } }

/* El bloque "Descarga la App" ahora es un partial reutilizable (views/layout/
   app-hero.php) y usa los estilos .app-hero* de la sección "Página: Descarga la
   App", tanto en la home como en /pages/app. */

/* ── Instagram (inicio, debajo del banner de la app) ──────────────────────────
   Reja de fotos en RETRATO 4:5 que enlazan a la publicación. Medidas elegidas por
   él sobre el preview (27 jul 2026): **5 por fila y 15 fotos**; en el teléfono, **2 por
   fila y 6 fotos**. El **hueco y el redondeo son los MISMOS que Imprescindibles**, por
   orden suya del 27 jul: `--gap-reja` y `--r-sm`, los dos compartidos — no dos copias
   con el mismo valor. Al ancho tope las dos rejas son de 5 columnas con el mismo hueco,
   así que **las fotos quedan alineadas con las tarjetas de arriba**. El valor del hueco
   NO se mira aquí: vive en `:root`, con el censo de las 12 rejas que lo comparten.

   ⚠️ 4:5 es la proporción con la que se PUBLICA en Instagram (las fotos llegan a
   1152×1440, medido), así que aquí no se recorta nada: la jalá entera, el pastel
   entero. Con el cuadrado de antes se comía el alto de casi todas.

   ⚠️ En el TELÉFONO solo se enseñan 6, decisión suya (*"para no saturar"*): las 15
   siguen en el HTML —el mismo para todos los anchos— y las 9 de más se ocultan por
   CSS. Como van con `loading="lazy"` y `display:none`, el navegador **ni siquiera
   las descarga**, así que el móvil también ahorra datos.

   ⚠️ LOS DOS NÚMEROS DE FOTOS TIENEN QUE CUADRAR EN SU REJA o la última fila sale
   coja: **15 entre 5** (escritorio y tableta) y **6 entre 2** (teléfono). Es una regla
   suya, dicha con todas las letras: **«nunca dejar espacio vacío».** Si se cambia
   `INSTAGRAM_POSTS` en `core/config.php`, comprobar las dos — hay prueba que lo
   vigila (`pruebas/probar-instagram.py`). */
.ig-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--gap-reja); }
.ig-item {
  position: relative; display: block; aspect-ratio: 4 / 5; overflow: hidden;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-sm);
  transition: box-shadow .18s, transform var(--dur-xs) var(--ease-out);
}
.ig-item img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  opacity: 0; transition: opacity var(--dur) var(--ease-out), transform .35s var(--ease-out);
}
.ig-item img.loaded { opacity: 1; }
.ig-item:hover { box-shadow: var(--shadow-sm); }
.ig-item:hover img { transform: scale(1.05); }
/* Señal de "esto es un carrusel / un reel", como en Instagram: arriba a la
   derecha y en blanco, con sombra porque debajo puede haber foto clara. */
.ig-tipo { position: absolute; top: 7px; right: 7px; color: #fff; pointer-events: none;
           filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .5)); }
.ig-tipo svg { width: 17px; height: 17px; display: block; }
/* Un solo corte, y no dos: con 5 columnas la foto sigue midiendo ~110px a 600 de
   ancho, así que la tableta no necesita reja propia. Debajo de 560 sí: **2 columnas
   y solo 6 fotos** (3 filas), que es lo que él pidió para el teléfono — pocas y
   grandes, no muchas y diminutas. */
@media (max-width: 560px) {
  .ig-grid { grid-template-columns: repeat(2, 1fr); }
  .ig-item:nth-child(n + 7) { display: none; }
  .ig-tipo svg { width: 14px; height: 14px; }
}

section { margin-top: 46px; }
.sec-head .count { color: var(--muted); font-size: var(--t-ui); font-weight: 500; font-family: var(--sans); }
.link-btn {
  border: none; background: none; color: var(--brand); font-size: var(--t-ui); font-weight: 700;
  cursor: pointer; padding: 6px 0; transition: gap .15s, opacity .15s; display: inline-flex; align-items: center; gap: 4px;
  white-space: nowrap;
}
.link-btn:hover { opacity: .7; }

/* ============================================================
   Mosaico de departamentos (inicio)
   ============================================================ */
.dept-mosaic-section { margin-top: 40px; }
/* Fluido con FLEXBOX, no grid. Antes era repeat(8,1fr) con saltos en 900/600 (a 901px eran
   8 baldosas de ~95px y a 900px SALTABAN a 4). El grid `auto-fit` que probé el 17 jul quitó
   el salto pero dejaba la ÚLTIMA FILA a medias con hueco a la derecha; el usuario las quería
   "todas juntas". Con flex + `flex-grow` cada fila LLENA el ancho (las baldosas crecen para
   repartir el sobrante), el nº por fila cambia suave con la ventana y no hay hueco. `max-width`
   evita que una última fila con pocas baldosas las agrande de más → se centran (17 jul). */
/* ⚠️ EL ESQUELETO DE CARGA COMPARTE ESTAS TRES REGLAS, y no es cosmética (27 jul
   2026). Su único trabajo es ocupar el sitio EXACTO de lo que va a llegar, y tenía
   medidas propias: hueco 14 contra 18, baldosas de 88/104 contra 130/150 y 10
   piezas contra 16 — al cargar, todo crecía y se recolocaba. En el teléfono el
   error iba al revés (el esqueleto no tenía reglas de móvil y el mosaico sí), así
   que se separaban en las dos direcciones a la vez. Con `.sk-*` en la MISMA lista
   de selectores, divergir otra vez exige borrar un selector a mano. Es lo que ya
   hacía bien su hermano: el esqueleto de la reja de producto reusa `.grid`. */
.dept-mosaic, .sk-mosaic { display: flex; flex-wrap: wrap; justify-content: flex-start; align-items: flex-start; gap: var(--gap-reja); margin-top: 22px; }
.dept-tile, .sk-tile { flex: 1 1 130px; max-width: 150px; border: none; background: none; padding: 0; cursor: pointer; text-align: center; font-family: inherit; }
.dept-tile-img, .sk-tile-img {
  display: flex; align-items: center; justify-content: center; aspect-ratio: 1;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden;
  transition: transform .18s, box-shadow .18s;
}
.dept-tile-img img { width: 100%; height: 100%; object-fit: contain; mix-blend-mode: multiply; opacity: 0; transition: opacity var(--dur) var(--ease-out); }
.dept-tile-img img.loaded { opacity: 1; }
.dept-tile:hover .dept-tile-img { transform: translateY(-4px); box-shadow: var(--shadow-sm); }
.dept-tile-name { display: block; margin-top: 12px; font-size: var(--t-strong); font-weight: 600; color: var(--ink); line-height: var(--lh-snug); }

/* ============================================================
   Carruseles horizontales
   ============================================================ */
/* Carrusel de Imprescindibles: DOBLE fila. La rejilla fluye en COLUMNAS y llena
   2 renglones, y el scroll es horizontal. (Sustituyó a `.hscroll`, de una fila,
   que era el único carrusel del inicio y quedó sin uso.) */
/* Imprescindibles: cuadrícula FIJA, SIN scroll (17 jul, decisión del usuario). Antes era
   un carrusel de scroll horizontal (`grid-auto-flow: column; overflow-x: auto`) con 16
   tarjetas fijas de 242px → se salía y había que arrastrar. Ahora **10 tarjetas en 5×2**
   (dos filas), y las columnas mandan el ancho (`1fr`), así que llenan la fila sin scroll.
   El ancho por columna a --maxw:1320 sale ~239px, casi el 242 de la tarjeta de la tienda,
   así que el mismo producto se sigue viendo del mismo tamaño en las dos páginas.
   En móvil: 2×2 (ver el bloque de ≤720). */
.rail-doble {
  display: grid; grid-template-columns: repeat(5, 1fr);
  gap: var(--gap-reja); padding: 4px 4px 14px;   /* 22 → 18 (decisión suya, 16 jul), hoy en --gap-reja */
}

/* Tarjeta de Imprescindibles: nombre, precio y agregar SOBRE la foto.
   ⚠️ El degradado es BLANCO, no oscuro como el de las tarjetas del blog, y NO es
   un capricho: las fotos de producto son packshots CONTENIDOS sobre blanco
   (`.thumb` usa object-fit: contain). Un degradado oscuro caería sobre el blanco
   vacío de la foto —una barra pegada— o sobre la etiqueta del producto. El del
   blog funciona porque ahí la foto llena el cuadro. Si algún día las fotos
   cambian a fotografía ambientada, esto se puede reconsiderar. */
.card-over { position: relative; }
/* Alto de la tarjeta, en % del ancho (125 = el 4/5 de siempre). Alargarla NO
   encoge la foto —es cuadrada y se contiene— así que el alto de más cae todo
   abajo, detrás del texto: es la perilla para que el texto deje de tapar
   producto sin tener que apretarlo. */
.card-over .card-media { aspect-ratio: 100 / var(--card-over-alto, 135); }
/* La foto es CUADRADA y la tarjeta 4/5: al contenerse sobran dos franjas iguales
   arriba y abajo. Centrada, el producto se ve caído, porque abajo la franja se
   suma al texto. Subiéndola, el hueco se junta todo abajo — que es justo donde
   va el texto — y el producto queda centrado en lo que se ve.
   0% = arriba del todo, 50% = centrada. */
.card-over .thumb { object-position: center var(--card-over-foto, 0%); }
.card-over .card-body {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  padding: 36px 15px 14px; gap: 7px;
  background: linear-gradient(to top, #fff 57%, rgba(255,255,255,0));
}
/* Reserva 2 renglones para el nombre: así el precio de TODAS las tarjetas queda
   a la misma altura (misma receta que el <h3> de las tarjetas del blog). */
.card-over .name { min-height: 2.5em; }
.card-over .card-foot { padding-top: 0; }

/* EL HUECO ENTRE TARJETAS (decisión suya, 16 jul, elegido viendo un preview con las
   tarjetas y las fotos reales): 26 → 15, "las siento muy separadas".
   ⚠️ Cada reja tenía su hueco sin que nadie lo decidiera: tienda 26 · carrusel 22 ·
   favoritos 18. Ahora tienda y favoritos comparten el 15 —el hueco se escribe UNA vez
   y por eso no pueden volver a separarse— y el carrusel va a 18 (suyo también: ahí
   las tarjetas se deslizan y quiere algo más de aire). En móvil, 12 y 16.
   Lo ÚNICO que sigue siendo distinto entre tienda y favoritos es el `minmax`, porque
   favoritos lleva otra tarjeta (`.fav-card`, el diseño viejo) → ver abajo.
   ⚠️ Y ARRASTRA EL ANCHO DEL CARRUSEL: ver `.rail-doble .card`. Con 4 columnas fijas,
   cerrar el hueco ENSANCHA la tarjeta (234 → 242) y la del carrusel es FIJA. */
.grid, .fav-grid { display: grid; gap: var(--gap-producto); }
.grid { grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); }

/* ============================================================
   Tarjeta de producto
   ============================================================ */
.card {
  position: relative;
  background: var(--card); border-radius: var(--r-sm); overflow: hidden;
  display: flex; flex-direction: column; cursor: pointer;
  border: 1px solid var(--line); box-shadow: var(--shadow-xs);
  transition: transform .18s ease, box-shadow .18s ease;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }

/* ⚠️ ESTO ES RENDIMIENTO, NO ESTILO — y es el arreglo del "fondo sólido y luego
   carga" al volver con el swipe (21 jul 2026, cuarto intento; los tres anteriores
   eran defectos reales pero no eran ESTE).
   Qué pasaba: abrir una ficha mete el catálogo entero en `display: none`
   (`showOnly`). Al volver, esas tarjetas —hasta 240 desde que la paginación es de
   48— salen de la nada y el navegador tiene que **layoutear y pintar las 240 en un
   solo fotograma**. Con el botón atrás eso es un parpadeo que no se ve; **debajo de
   la animación del swipe, ese fotograma queda a la vista y es el fondo desnudo**.
   Lo confirmó él: no salía «Cargando catálogo…», o sea que la página nunca se fue.
   `content-visibility: auto` hace que el navegador **se salte el layout de las
   tarjetas que están fuera de la pantalla**: al reaparecer la reja solo trabaja con
   las visibles (~8), no con 240.
   `contain-intrinsic-size` es OBLIGATORIO con ella: es el alto que se le supone a
   lo que aún no se ha medido. Sin él, las tarjetas de fuera valen 0, la página
   colapsa de alto y **se pierde la posición al volver** — justo lo contrario de lo
   que se quiere. El `auto` de delante hace que recuerde el alto REAL una vez
   medida, así que **el número solo manda la primera vez y es una ESTIMACIÓN**: foto
   cuadrada del ancho de la columna + ~110 de cuerpo (~242+110 en escritorio,
   ~160+100 en el teléfono). **No está medido en pantalla**; si algún día se ve un
   salto al desplazar rápido, el sospechoso es este número.
   ⚠️ Se acota a `.grid > .card` a propósito: la tarjeta del carrusel
   (`.rail-doble .card`) tiene otro alto y va en scroll horizontal, donde esto no
   ayuda y el tamaño supuesto sí estorbaría. */
.grid > .card { content-visibility: auto; contain-intrinsic-size: auto 352px; }
@media (max-width: 720px) {
  /* La tarjeta del teléfono es más estrecha (minmax 150) y por tanto más baja: la
     foto es cuadrada, así que el alto sigue al ancho. Un número de escritorio aquí
     dejaría huecos al desplazar hasta que cada tarjeta se mide. */
  .grid > .card { contain-intrinsic-size: auto 262px; }
}

/* PATRÓN "CARD LINK" — no tocar sin leer esto.
   La tarjeta entera se puede clicar, pero lo que el TECLADO y el lector de pantalla
   alcanzan es un enlace real sobre el nombre (ui.js, renderCard). El ::after lo
   estira sobre toda la tarjeta y hace de zona de clic.
   Antes la tarjeta era un <article> sin tabindex ni role, y solo se abría por el
   click delegado de app.js: con teclado NO se podía abrir NINGUNA ficha del catálogo.
   Era el único P0 del sitio (WCAG 2.1.1 Keyboard, nivel A).
   El destino es la MISMA URL que ya escribía openProduct con pushState y que el
   servidor sí resuelve (index.php:47) → el enlace es real, no un adorno: se puede
   abrir en pestaña nueva, copiar y compartir. app.js hace preventDefault salvo con
   Cmd/Ctrl/mayús, para no romper "abrir en pestaña nueva". */
.card-link { color: inherit; text-decoration: none; }
.card-link::after { content: ""; position: absolute; inset: 0; z-index: 1; }
/* El foco se pinta sobre la tarjeta entera, no sobre el texto del nombre: el ::after
   ya la cubre, así que basta con darle el contorno a él. */
.card-link:focus-visible { outline: none; }
.card-link:focus-visible::after { outline: 3px solid var(--brand); outline-offset: -3px; border-radius: var(--r-sm); }
/* Todo lo que se pueda tocar dentro de la tarjeta va por encima del ::after.
   (.fav-heart ya está en z-index 3 por su cuenta.) */
.card-buy { position: relative; z-index: 2; }
/* En pantallas táctiles el efecto de elevación no debe "quedarse pegado" al tocar. */
@media (hover: none) {
  /* ⚠️ La lista de `transform: none` NO está aquí: es la MISMA que necesita
     `prefers-reduced-motion` y estaba escrita dos veces, a 1.700 líneas de
     distancia (una copia disfrazada de lista de selectores). Vive una sola vez, al
     final del archivo, en un @media con las dos condiciones. Lo de aquí abajo es lo
     que SÍ es solo del táctil: las sombras (que en movimiento reducido se conservan
     a propósito) y el borde del ♥. */
  .card:hover, .fav-card:hover, .recipe-card:hover { box-shadow: var(--shadow-xs); }
  .dept-tile:hover .dept-tile-img { box-shadow: none; }
  /* El borde vino del ♥ es además la señal de "guardado": si se queda pegado
     al tocar, un corazón sin guardar parece guardado. */
  .fav-heart:hover { border-color: var(--line); }
}
/* padding 10 → 6 (decisión suya, 16 jul, elegido sobre el preview). NO se puede
   bajar a 0: los packshots traen su propio fondo blanco y bordes duros, y pegados al
   borde algunos se ven cortados. Lo hereda también la tarjeta del carrusel del
   inicio (`.card-over .card-media`), que solo le pisa el aspect-ratio. */
.card-media { position: relative; background: var(--card); aspect-ratio: 1/1; padding: 6px; }
.thumb {
  width: 100%; height: 100%; object-fit: contain; display: block;
  opacity: 0; transition: opacity var(--dur) var(--ease-out);
}
.thumb.loaded { opacity: 1; }
/* Distintivo sobre la imagen: "Destacado" a la izquierda. La derecha es del ♥
   del usuario (.fav-heart), por eso ahí ya no va ningún badge de la casa. */
/* top/left = radio de la tarjeta + 1 (borde): despeja la curva de la esquina. Con
   `.card { overflow:hidden; border-radius }`, Safari recorta a esa curva más que
   Chrome y a 10px le comía un pelo a la esquina del overlay (reportado 22 jul). */
.badge-feat {
  position: absolute; top: calc(var(--r-sm) + 1px); left: calc(var(--r-sm) + 1px); z-index: 2;
  font-size: var(--t-tiny); font-weight: 700; text-transform: uppercase;
  padding: 4px 9px; border-radius: var(--r-pill); line-height: 1;
  color: #fff; background: var(--brand);
}
.card-body { padding: 0 15px 16px; display: flex; flex-direction: column; gap: 5px; flex: 1; }

/* --- Atributos de la tarjeta (catálogo y favoritos: misma jerarquía) ---
   Se distinguen por PESO y color, no por forma: la única píldora de la tarjeta es
   "Sobre pedido", que es lo único que avisa algo. Antes había tres píldoras de tres
   colores compitiendo con el nombre, y la más ruidosa ("A granel", en vino) era
   justo la que repetía lo que el "/kg" del precio ya decía. */
.card-eyebrow {   /* categoría: casi siempre ya sabes dónde estás → rótulo tenue */
  font-size: var(--tc-cat); font-weight: 600; text-transform: uppercase;   /* 10, escala de tarjeta (23 jul); peso 700 → 600 el 16 jul */
  /* --muted, no --faint (27 jul 2026): en --faint daba 2.62:1 y esto es texto que
     se lee. Sigue siendo el renglón más tenue de la tarjeta —el nombre va en --ink
     a 800— y ahora es el MISMO gris que su hermano de dos líneas más abajo,
     `.attr` ("A granel"), que ya estaba en --muted. La jerarquía por peso que él
     eligió el 14 jul no se toca: cambia el gris, no el orden. */
  color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* El `margin-top: 1px` es suyo (16 jul): despega la supervisión del nombre justo un
   pelo. Va en margen y NO tocando el `gap: 5px` de `.card-body`, que separa TODOS los
   renglones de la tarjeta — subirlo ahí habría movido también la categoría, "Sobre
   pedido" y el pie. */
.card-attrs { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: var(--tc-attr); line-height: var(--lh-snug); margin-top: 1px; }   /* 11, escala de tarjeta (23 jul) */
.attr-kosher {    /* supervisión: la razón por la que se compra aquí → en tinta */
  font-weight: 700; text-transform: uppercase; color: var(--text);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.attr { color: var(--muted); }        /* "A granel": el /kg ya lo dice → gris, sin píldora */
.attr-dot { color: var(--faint); }

/* Sufijo "/kg" junto al precio */
.price-unit { font-size: var(--tc-unit); font-weight: 600; color: var(--muted); margin-left: 3px; }
.p-price-unit { font-size: var(--t-ui); font-weight: 600; color: var(--muted); margin-left: 5px; }
/* "Sobre pedido": la ÚNICA píldora de la tarjeta (no llega hoy = hay que avisarlo).
   La clase se sigue llamando "encargo" por el campo `anticipacion` de la BD; lo que
   cambió es el rótulo. align-self porque cuelga directo del cuerpo (columna flex). */
.encargo-tag {
  align-self: flex-start;
  font-size: var(--t-tiny); font-weight: 700; text-transform: uppercase;
  color: var(--amber-ink); background: var(--amber-soft); padding: 3px 8px; border-radius: var(--r-pill); line-height: 1;
}
/* Listón de promoción (esquina inferior izquierda de la tarjeta) */
.promo-ribbon {
  position: absolute; bottom: 10px; left: 10px; z-index: 2;
  background: var(--brand); color: #fff;
  font-size: var(--t-label); font-weight: 800;
  padding: 4px 10px; border-radius: var(--r-pill); box-shadow: var(--shadow-xs);
}
.promo-ribbon-inline { position: static; box-shadow: none; }
/* Cinta de departamento distintivo ("Pesaj"), calcada de la app.
   EN LA TARJETA (catálogo, inicio y /favoritos) va abajo a la DERECHA, la esquina
   contraria al listón de promoción, para que puedan salir las dos a la vez (en la app
   también conviven, una por lado). En la FICHA va arriba a la IZQUIERDA y DENTRO de la
   foto (25 jul 2026, decisión suya) — ver más abajo.
   Anclas `position: relative`: .card-media, .fav-media y .gallery-main.

   ⚠️ COLORES: los eligió él (25 jul 2026), y **los DOS son de la paleta oficial**, así que
   los dos van por su token y ninguno en hexadecimal. El `#2a3538` que pidió es el
   **pizarra 432 C**, que ya vivía como `--text`; el `#f1a532`, el **ámbar de marca**, que
   es `--amber`. Escribir cualquiera de los dos a mano sería una segunda copia del token —el
   fallo nº1 de este proyecto—, y en el caso del ámbar además **se colaría por debajo de la
   red de `probar-accesibilidad.js`, que vigila `var(--amber)` y no el hexadecimal**.
   *(Sí: `--text` se llama así porque nació para texto. Aquí hace de FONDO y está bien —lo
   que manda es que sea el mismo color de marca—; renombrarlo tocaría medio archivo.)*
   ⚠️ El ámbar como TEXTO es la excepción, no la regla: sobre blanco da 2.07:1 y por eso
   está prohibido en general. Aquí el fondo es oscuro y el par da **6.1:1**, holgado para
   AA. La excepción está anotada en la lista blanca de esa prueba, con este motivo.
   *(Pasó antes por #3f5972/#baaf4b —los de la app— y por --ink/--amber-soft; los dos
   descartados por él. El primero además daba 3.4:1, por debajo de AA.)* */
.distinctive-ribbon {
  position: absolute; bottom: 10px; right: 10px; z-index: 2;
  background: var(--text); color: var(--amber);
  font-size: var(--t-label); font-weight: 800; letter-spacing: .02em; line-height: 1;
  padding: 5px 10px; border-radius: var(--r-pill); box-shadow: var(--shadow-xs);
}
/* En la FICHA: arriba a la izquierda y DENTRO de la foto. Va dentro de `.gallery-main`
   —que por eso es `position: relative`— y no fuera como el ♥: ese necesita salirse del
   `overflow: hidden` para no perder su zona táctil de 44px, y la cinta no se toca.
   `bottom/right: auto` son obligatorios: sin ellos gana la regla de arriba y la cinta
   se queda abajo a la derecha con un `top` que no hace nada. Y sube de tamaño porque la
   foto es mucho más grande (la app hace lo mismo: 1rem aquí contra .75rem en la tarjeta). */
.gallery-main > .distinctive-ribbon {
  top: 12px; left: 12px; bottom: auto; right: auto;
  font-size: var(--t-small); padding: 6px 13px;
}
/* Precio de tarjeta con descuento (tachado + nuevo) */
.card-price { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.price-was { font-size: var(--tc-was); color: var(--muted); text-decoration: line-through; }   /* 13 (escala de tarjeta, 23 jul; el carrito lo re-fija en --t-small) */
.price.price-now { color: var(--brand); } /* gana a .price (negro) para que el descuento sí salga en vino */
/* LA ESCALA DE LA TARJETA — vive en tokens `--tc-*` (:root). Valores (23 jul 2026,
   UNIFICADA y +1 respecto a la del 16 jul, suya con preview): nombre 17/600 · precio
   16/800 · tachado 13 · Agregar 13/600 · atributos+supervisión 11 · categoría 10/600.
   ⚠️ **El nombre SIGUE por encima del precio (17 > 16), y eso es a propósito**: es la
   jerarquía que él eligió el 14 jul —manda el producto, no el precio—. Ojo si alguien
   baja el nombre a 16 o menos: el precio pesa **800** contra 600, así que empatado en
   tamaño ya manda ÉL, y la tarjeta pasa a vender precio. No es estética, es negocio.
   ⚠️ El carrusel del inicio (`.card-over`) usa AHORA esta misma escala en las dos
   pantallas (antes tenía 13/15/8 propios en móvil); el mismo producto se lee igual en
   las dos páginas. Cambiar un tamaño = cambiar el token, no la clase. */
.name {
  font-size: var(--tc-name); font-weight: 600; color: var(--ink); margin: 0; line-height: var(--lh-heading);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* ⚠️ Aquí había `min-height: 2.6em` y SE QUITÓ (decisión suya, 16 jul, sobre un
   preview): el nombre reservaba SIEMPRE dos líneas, así que en los nombres de una
   línea quedaba un hueco de ~21px antes de la supervisión — y solo en esas tarjetas,
   que es lo que se veía raro.
   El `-webkit-line-clamp: 2` SE QUEDA: sigue cortando a dos líneas los nombres
   largos. Lo que se fue es la RESERVA, no el tope.
   Lo que se pierde a sabiendas: la supervisión ya NO cae a la misma altura en toda
   la fila; cada una se pega a su nombre. Se prefirió eso a un hueco dentro de la
   tarjeta.
   Y lo que NO cambia: el hueco sobrante no desaparece, se MUDA. `.card-foot` lleva
   `margin-top: auto` y la reja estira todas las tarjetas a la altura de la más alta,
   así que ese aire ahora se junta encima del precio. */
.card-foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 8px 10px; padding-top: 8px; flex-wrap: wrap; }
.price { font-size: var(--tc-price); font-weight: 800; color: var(--ink); font-variant-numeric: tabular-nums; }   /* 16, escala de tarjeta (23 jul). Se queda POR DEBAJO del nombre (17) a propósito */
.card-add {
  border: 1.5px solid var(--brand); color: var(--brand); background: none; font-family: inherit;
  font-weight: 600; font-size: var(--tc-add); padding: 8px 13px; border-radius: var(--r-pill); cursor: pointer;
  white-space: nowrap; transition: background .15s, color .15s, transform var(--dur-xs) var(--ease-out);
}
/* ZONA TÁCTIL SIN CAMBIAR EL ASPECTO (16 jul, decisión suya: "se ve muy alto el
   botón" cuando se probó a engordarlo de verdad). La píldora sigue midiendo 34px de
   alto; el ::after le da el área sin mover un píxel. Es el botón que convierte.

   ⬇ LA ZONA SE REPARTIÓ DE OTRA FORMA (27 jul 2026) — mismo tamaño, otro sitio.
   Antes era `top: 50%; height: 44px; translateY(-50%)`: centrada, o sea **5px por
   arriba y 5 por abajo**. Y arriba está el cuerpo de la tarjeta, que abre la ficha
   → tocar justo encima de "Agregar" agregaba al carrito en vez de abrir el producto.
   Aquello se aceptó a sabiendas en julio, pero no hacía falta pagarlo: **la tarjeta
   tiene 16px de relleno abajo y 15 a la derecha donde no hay NADA que robar**, así
   que la zona crece hacia ahí en vez de hacia el contenido.
   - `bottom: -16px` → hasta el borde inferior de la tarjeta (el relleno de
     `.card-body`). Es hueco muerto: nadie toca el filo de una tarjeta para abrirla.
   - `right: -15px` → hasta el borde derecho, por el mismo motivo. Esto ataca el
     fallo CARO, que no es agregar de más: es **apuntar a "Agregar" y abrir la ficha**
     — en el teléfono la tarjeta mide ~150px y la píldora es lo único que convierte.
   - `left: -5px` → la MITAD del hueco de 10px que la separa del precio. La otra
     mitad se queda del precio; partir el hueco no se lo quita a nadie.
   - `top: -1px` → prácticamente nada. Es el píxel que causaba el toque errado.
   ⚠️ **Los números NO son decorativos: son el relleno de `.card-body` (`0 15px 16px`)
   y el `gap` de `.card-foot` (10px).** Si alguno cambia, estos van detrás o la zona
   se sale de la tarjeta —y `.card` tiene `overflow: hidden`, así que lo que se salga
   no recibe toques: se pierde en silencio, sin que nada se vea mal.
   Alto resultante: 34 + 1 + 16 = **51px**, por encima de los 44 que pide WCAG. */
.card-add { position: relative; }
.card-add::after {
  content: ""; position: absolute;
  left: -5px; right: -15px; top: -1px; bottom: -16px;
}
/* Los TRES estados del botón fantasma (borde vino sobre nada) se comparten con
   `.fav-add`, que es el mismo botón con otra caja: eran dos copias con sus cuatro
   estados cada una. Lo propio de cada uno (tamaño, caja, zona táctil) se queda
   junto a su componente. */
.card-add:hover, .fav-add:hover { background: var(--brand); color: #fff; }
.card-add:disabled, .fav-add:disabled { border-color: var(--line); color: var(--faint); background: none; cursor: not-allowed; }
.card-add:disabled:hover, .fav-add:disabled:hover { background: none; color: var(--faint); }
/* "Agregar" es <button>, pero "Ver carrito" es un enlace: que se vean igual. */
a.card-add { display: inline-block; text-decoration: none; line-height: 1.15; }
/* Selector de cantidad dentro de la tarjeta: aparece en lugar de "Agregar"
   cuando el producto YA está en el carrito (como la app). Mismo control que el
   carrito, en compacto para que quepa en el pie de la tarjeta. */
.card-qty {
  display: inline-flex; align-items: center; overflow: hidden;
  border: 1.5px solid var(--brand); border-radius: var(--r-pill); background: var(--card);
}
/* El selector NO engorda (mismo motivo que .card-add): sigue en 30×31 a la vista y
   el ::after le da los 44. Cada toque errado aquí es una unidad de más o de menos en
   un pedido real. Los centros quedan a 46px, así que las dos zonas no se solapan. */
/* `overflow: visible` para que salga el ::after — pero el `overflow: hidden` que
   había NO era decorativo: recortaba el fondo del :hover de los botones a la curva
   de la píldora. Al quitarlo, ese fondo asomaba en esquinas CUADRADAS. Se reproduce
   a mano con el mismo resultado: redondeado por fuera, recto contra el número. */
.card-qty { overflow: visible; }
.card-qty .qty-btn:first-child { border-radius: var(--r-pill) 0 0 var(--r-pill); }
.card-qty .qty-btn:last-child  { border-radius: 0 var(--r-pill) var(--r-pill) 0; }
.card-qty .qty-btn { width: 30px; height: 31px; font-size: var(--t-strong); color: var(--brand); position: relative; }
/* Misma corrección que en `.card-add` (27 jul 2026) y por el mismo motivo: la zona
   estaba centrada, así que asomaba ~6px por ARRIBA, sobre el cuerpo de la tarjeta.
   Aquí duele más que en "Agregar": un toque errado en el ± es **una unidad de más o
   de menos en un pedido real**. Baja al relleno inferior de la tarjeta, que es hueco
   muerto: 31 + 1 + 12 = 44 clavados.
   El ancho NO se toca: los 44 horizontales y el `left: 50%` mantienen los centros de
   "−" y "+" a 46px, que es lo que impide que las dos zonas se pisen entre sí. */
.card-qty .qty-btn::after {
  content: ""; position: absolute; top: -1px; bottom: -12px; left: 50%;
  width: 44px; transform: translateX(-50%);
}
.card-qty .qty-btn:hover:not(:disabled) { background: var(--brand-soft); }
.card-qty .qty-btn:disabled { opacity: .35; cursor: not-allowed; }
.card-qty .qty-val { min-width: 46px; font-size: var(--t-small); font-weight: 700; }
/* Producto agotado */
.card.sold-out .thumb { opacity: .45; filter: grayscale(.4); }
.card.sold-out .card-price { opacity: .6; }
.sold-badge {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 3;
  background: var(--ink); color: #fff; font-size: var(--t-small); font-weight: 800; text-transform: uppercase; padding: 6px 14px; border-radius: var(--r-pill);
}

/* ============================================================
   Migas de pan
   ============================================================ */
/* El 24 de abajo NO es un capricho: los 8px de antes casi nunca se veían. El margen
   de la miga se FUNDE con el del bloque que va debajo (son hermanos) y gana el
   mayor, así que el aire real lo ponía el otro y salía distinto en cada página:
   10px en el listado (.browse-layout), 16px en la ficha (.product-layout) y 14px en
   todo lo de la cuenta (.account-layout) y en el detalle del pedido (.order-detail).
   Con 24 gana SIEMPRE la miga → el mismo aire en todas, y es el 24 que ya usan
   .section-heading y .order-detail-head para separarse de su contenido.
   ⚠️ Por eso NO se toca ninguno de esos márgenes: subirlos aquí no haría nada. */
.breadcrumb { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin: 30px 0 24px; font-size: var(--t-ui); }
/* ~17px de alto. Es el camino de vuelta de la ficha a su categoría, de lo más usado
   del catálogo. El margin negativo compensa el padding: no se mueve nada de sitio. */
.crumb {
  border: none; background: none; color: var(--brand); font-weight: 600; cursor: pointer;
  padding: 13px 2px; margin: -13px 0; font-size: var(--t-ui); transition: opacity .15s;
}
.crumb:hover { opacity: .65; }

/* (El `.chip:hover` vivía AQUÍ, con su propio encabezado "Chips", 100 líneas por
   encima de donde se define `.chip` — y por eso perdía contra `.chip.on`. Se mudó
   junto a las demás reglas del chip; ver allá el porqué. 27 jul 2026.) */

/* ============================================================
   Cargar más
   ============================================================ */
.load-more-wrap { display: flex; justify-content: center; margin-top: 42px; }
.load-more {
  /* Sólido de marca como `.btn-primary` — el porqué, allí. */
  border: 1px solid var(--brand); background: var(--brand); color: #fff; font-weight: 600; font-size: var(--t-ui);
  padding: 15px 40px; border-radius: var(--r-pill); cursor: pointer;
  transition: background .18s, border-color .18s, box-shadow .18s, transform .18s;
}
.load-more:hover { background: var(--brand-deep); border-color: var(--brand-deep); box-shadow: var(--shadow-sm); }
.load-more:active { transform: scale(.98); }

/* ============================================================
   Estado / carga
   ============================================================ */
.status {
  padding: 40px 24px; border-radius: var(--r); background: var(--card); border: 1px solid var(--line);
  color: var(--muted); margin-top: 30px; font-size: var(--t-strong); text-align: center; box-shadow: var(--shadow-xs);
}
.status.error { border-color: #eebcc2; background: #fdf3f4; color: var(--brand-deep); text-align: left; }
.status strong { color: var(--ink); }
.status-actions { margin-top: 16px; }
.spinner {
  width: 22px; height: 22px; border: 2.5px solid var(--line); border-top-color: var(--brand);
  border-radius: 50%; animation: spin .7s linear infinite; display: inline-block; vertical-align: -5px; margin-right: 10px;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Texto solo para lector de pantalla (no existía la utilidad; la usa el skeleton para
   conservar el "Cargando catálogo…" que antes era el texto visible del spinner). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ===== Skeleton de carga (Emil, 22 jul 2026) =====
   Vive DENTRO de #status. `.status-loading` le quita la caja del bar de estado para que
   el skeleton se vea como el contenido que viene (hero a todo ancho, mosaico, reja).
   La forma la pinta el servidor (home.php) según la ruta; en /departamento con bloque
   NO hay skeleton (el servidor ya pintó tarjetas reales). */
.status.status-loading { padding: 0; border: none; background: none; box-shadow: none; text-align: left; }
.sk {
  background: linear-gradient(90deg, var(--line-soft) 25%, var(--line) 37%, var(--line-soft) 63%);
  background-size: 400% 100%; animation: shimmer 1.5s ease-in-out infinite;
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
/* Forma HOME: banda de hero + fila de departamentos (más chicos, decisión suya). */
/* El banner: la MISMA caja que `.hero-slide` —`min(88%, --hero-w)` a 16:9, que es
   la proporción de las 8 imágenes del panel— y no un 16:6 a todo el ancho. Así el
   hueco que reserva es el que va a ocupar. */
.sk-hero { width: min(88%, var(--hero-w)); aspect-ratio: 16 / 9; margin: 14px auto 0; border-radius: var(--r); }
/* El título va dentro de un `.sec-head` de verdad, con un espacio duro dentro: así
   el alto que reserva es EL DEL h2 real (`--t-section` con su interlínea), no un 16
   inventado que se quedaría viejo al tocar la escala tipográfica. Lo único propio
   es cuánto mide la barra de lado a lado. */
.sk-h2 { width: 170px; border-radius: 6px; }
/* Del esqueleto del mosaico solo queda AQUÍ lo que lo distingue de una baldosa de
   verdad; la caja y el hueco los pone `.dept-mosaic` (ver allá el porqué).
   ⚠️ El renglón del nombre reserva UNA LÍNEA A LA ALTURA REAL —`--t-strong` por
   `--lh-snug`, no un 9px inventado—, así que si esos tokens cambian, el hueco
   reservado cambia con ellos.
   🚫 RESIDUO ACEPTADO, NO SE ARREGLA (27 jul 2026, palabras suyas: *"da igual eso"*,
   contestando a si el nombre del departamento pasaba a dos líneas fijas). Dos de los
   16 departamentos ("carnicería y embutidos" y "ensaladas y encurtidos") ocupan DOS
   líneas, así que esas filas crecen un renglón al cargar. **No re-proponerlo:** el
   único arreglo era fijar el alto del nombre en el mosaico REAL, y eso cambia el
   diseño de la home para tapar medio segundo. */
.sk-tile-name { display: block; width: 70%; margin: 12px auto 0; border-radius: 6px;
                height: calc(var(--t-strong) * var(--lh-snug)); }
/* Forma REJA: reusa `.grid` para las MISMAS columnas que el catálogo real. */
.sk-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--r-sm); box-shadow: var(--shadow-xs); overflow: hidden; display: flex; flex-direction: column; }
.sk-media { aspect-ratio: 1 / 1; }
.sk-body { padding: 12px 14px 14px; display: flex; flex-direction: column; gap: 9px; }
.sk-line { height: 11px; border-radius: 6px; }
.sk-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 4px; }
.sk-price { height: 14px; width: 46px; border-radius: 6px; }
.sk-btn { height: 30px; width: 74px; border-radius: var(--r-pill); }
/* 🚫 NO extender el esqueleto a las páginas de la CUENTA (/pedidos, /direcciones,
   /pedido, /confirmacion) — se probó el 25 jul 2026 y lo descartó él: "pintan raro
   y brinca mucho cuando carga". El esqueleto solo paga cuando (a) la espera es
   larga y (b) la altura del contenido es PREVISIBLE. En el catálogo se cumplen las
   dos; en la cuenta ninguna: la respuesta es rápida y el alto lo pone el dato (una
   `.order-row` mide ~185px y una caja gris genérica ~98) → el brinco al llegar era
   peor que el del renglón "Cargando…" que sustituía. No re-proponerlo. */
@media (prefers-reduced-motion: reduce) { .sk { animation: none; background: var(--line-soft); } }


/* ============================================================
   Listado: layout con sidebar
   ============================================================ */
/* 30 y no 10 desde el 21 jul: el catálogo se quedó SIN miga, y ese aire lo daba
   ella (su margen superior era justo 30). Mismo número y mismo motivo que en
   `.account-layout`. */
/* DOS filas, no una (21 jul 2026, decisión suya): arriba a la derecha la barra del
   título, y debajo las dos columnas ARRANCANDO A LA MISMA ALTURA. Con la barra
   dentro de `.browse-main` la reja empezaba ~64px por debajo de "CATEGORÍAS" y las
   dos columnas se veían desniveladas.
   ⚠️ `row-gap: 0` y no el `gap: 36px` que había: el hueco entre la barra y la reja
   ya lo pone `.toolbar { margin-bottom }` — con los dos, la reja bajaba 60px y el
   arreglo se anulaba solo. La celda vacía de arriba a la izquierda es a propósito.
   `side` sigue nombrada en el teléfono aunque la barra sea `fixed` allí: así el
   `grid-area` de `.sidebar` apunta siempre a un área que existe. */
.browse-layout {
  display: grid; grid-template-columns: 220px 1fr;
  grid-template-areas: ".    toolbar"
                       "side main";
  column-gap: 36px; row-gap: 0; align-items: start; margin-top: 18px;
}
.browse-layout > .toolbar { grid-area: toolbar; }
/* La columna de productos NO debe estirarse con su contenido (evita desbordar de lado). */
.browse-main { grid-area: main; min-width: 0; }

/* Barra de categorías rápida (solo móvil) */
.mobile-cats { display: none; min-width: 0; max-width: 100%; }
/* ⚠️ `overflow-y: hidden` NO es decorativo ni redundante (21 jul 2026, lo reportó
   él: "los departamentos y categorías en móvil los puedo mover para abajo y para
   arriba"). Con `overflow-x: auto` y el eje Y en `visible`, el CSS obliga a la Y a
   valer `auto` también —no hay forma de tener un eje recortado y el otro no—, así
   que la fila de chips quedaba arrastrable en vertical y rebotaba bajo el dedo.
   `overscroll-behavior-x: contain` es lo mismo por el otro eje: sin él, seguir
   deslizando al llegar al final de los chips dispara el "atrás" del navegador. */
.chip-row { display: flex; gap: 8px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; scrollbar-width: none; -webkit-overflow-scrolling: touch; padding: 2px 0; max-width: 100%; }
.chip-row::-webkit-scrollbar { display: none; }
.cat-chip-row { margin-top: 8px; }
.chip {
  flex: 0 0 auto; border: 1px solid var(--line); background: var(--card); color: var(--ink);
  font-family: inherit; font-size: var(--t-ui); font-weight: 600; padding: 8px 14px;
  border-radius: var(--r-pill); cursor: pointer; white-space: nowrap; text-transform: capitalize;
  transition: background .15s, color .15s, border-color .15s;
}
.chip.on { background: var(--brand); color: #fff; border-color: var(--brand); }
/* El hover va DESPUÉS de `.chip.on` y acotado con `:not(.on)`, y las dos cosas
   hacen falta (27 jul 2026):
   · **Después**, porque tenía la misma especificidad (0,1,0) y estaba 100 líneas
     más arriba, en una sección "Chips" que se quedó separada de su propio bloque
     → ganaba `.chip.on` y **pasar el ratón sobre el chip activo no hacía nada**.
   · **`:not(.on)`**, porque ahora que gana no puede aplicarse al activo: ese es
     vino sólido con texto blanco, y recolorearle el texto lo dejaría ilegible.
   Y el color pasa de `--amber` a vino: era el ÚNICO hover ámbar del sitio; los
   otros siete van a `--brand`/`--brand-tint`. Ver [[web-sinai-marca]]. */
.chip:not(.on):hover { border-color: var(--brand-tint); color: var(--brand); }
.chip-sm { font-size: var(--t-small); padding: 6px 12px; color: var(--muted); }
.chip-sm.on { background: var(--brand-soft); color: var(--brand); border-color: var(--brand-soft); }
/* Se pega justo debajo del header, con aire. El alto del header lo mide site.js
   (--header-h): escrito a mano se quedaba corto y el título quedaba metido en él. */
.sidebar { grid-area: side; position: sticky; top: calc(var(--header-h, 129px) + 16px); align-self: start; }
.sidebar-head { display: none; }
.filter-group { padding-bottom: 22px; border-bottom: 1px solid var(--line-soft); margin-bottom: 22px; }
.filter-group:last-of-type { border-bottom: none; margin-bottom: 14px; }
.filter-title { font-size: var(--t-label); font-weight: 700; text-transform: uppercase; color: var(--muted); margin: 0 0 14px; }

/* Ahora que Categorías es lo único de la barra, la lista aprovecha el alto de la
   pantalla. El descuento NO es el del header pegado (124px): manda dónde arranca
   la barra al entrar, sin haber bajado nada — banner (~36) + header (~128) +
   margen (30) + la barra del título, que desde el 21 jul 2026 va ENCIMA de las dos
   columnas (~64) ≈ 258, más el título del bloque (~31) y aire abajo. (Antes de esa
   fecha esos ~64 los ponían las migas: el número no cambió, cambió de dueño.)
   Si se pasa de ahí, la barra sale de la pantalla en una categoría de pocos
   productos, donde no hay reja que la acompañe. */
.cat-nav { display: flex; flex-direction: column; gap: 2px; max-height: calc(100vh - 320px); overflow-y: auto; }
.cat-nav-all, .cat-nav-head, .cat-nav-sub { text-align: left; border: none; background: none; cursor: pointer; border-radius: 10px; font-family: inherit; }
.cat-nav-all { padding: 9px 11px; font-size: var(--t-ui); font-weight: 600; color: var(--text); }
.cat-nav-all:hover { background: var(--line-soft); }
.cat-nav-all.active { color: var(--brand); background: var(--brand-soft); }
.cat-nav-head { display: flex; align-items: center; justify-content: space-between; width: 100%; padding: 10px 11px; font-size: var(--t-ui); font-weight: 600; color: var(--ink); }
.cat-nav-head:hover { background: var(--line-soft); }
.cat-nav-head.active { color: var(--brand); }
.cat-nav-head .chev { color: var(--faint); transition: transform .2s; font-size: var(--i-lg); }
.cat-nav-dept.open .cat-nav-head .chev { transform: rotate(90deg); }
.cat-nav-subs { display: none; flex-direction: column; gap: 1px; padding: 2px 0 8px 12px; }
.cat-nav-dept.open .cat-nav-subs { display: flex; }
.cat-nav-sub { padding: 8px 11px; font-size: var(--t-ui); color: var(--muted); }
.cat-nav-sub:hover { background: var(--line-soft); color: var(--text); }
.cat-nav-sub.active { color: var(--brand); background: var(--brand-soft); font-weight: 600; }

.price-range { display: flex; align-items: center; gap: 8px; }
.price-range input { width: 100%; min-width: 0; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; font-size: var(--t-field); font-family: inherit; }
.price-range input:focus { outline: none; border-color: var(--brand-tint); box-shadow: 0 0 0 3px var(--brand-soft); }
.price-range .dash { color: var(--faint); }
/* Hoy no se pinta en ninguna página: su único uso es el "OK" del filtro de precio, que
   está COMENTADO en `home.php` desde el 14 jul 2026. Se dejó en pie —y se pasó a vino con
   los demás sólidos— justo por eso: borrar la regla haría que el filtro volviera sin
   estilo el día que se descomente. Ver `.btn-primary`. */
.mini-btn { border: none; background: var(--brand); color: #fff; border-radius: 10px; padding: 10px 14px; font-size: var(--t-ui); font-weight: 600; cursor: pointer; transition: background .15s; }
.mini-btn:hover { background: var(--brand-deep); }

.check-list { display: flex; flex-direction: column; gap: 7px; max-height: 220px; overflow-y: auto; }
.check { display: flex; align-items: center; gap: 10px; font-size: var(--t-ui); color: var(--text); cursor: pointer; text-transform: capitalize; padding: 3px 0; }
.check input { width: 17px; height: 17px; accent-color: var(--brand); cursor: pointer; }

.clear-filters { width: 100%; border: 1px solid var(--line); background: var(--card); color: var(--text); border-radius: var(--r-pill); padding: 12px; font-size: var(--t-ui); font-weight: 600; cursor: pointer; transition: border-color var(--dur-sm), color var(--dur-sm); }
.clear-filters:hover { border-color: var(--brand); color: var(--brand); }

/* El velo va en pareja con su cajón: mismo tiempo y misma curva, para que
   entren y salgan juntos. La `visibility` espera al fundido para ocultar; si no,
   un velo a opacidad 0 se seguiría comiendo todos los clics de la página. */
/* Los DOS velos (barra de la tienda y menú móvil) comparten todo menos el z-index
   —que sí es distinto a propósito, son dos capas— y la opacidad del negro.
   ⚠️ Esa opacidad (.45 vs .25) es la que YA divergió sola y nadie decidió: se
   conservan las dos tal cual para no cambiar nada de lo que se ve; unificarlas es
   una decisión de diseño, no una limpieza. El `visibility` con retardo es lo que
   evita que el velo se quede tabulable al cerrar: no tocarlo. */
.sidebar-backdrop, .mm-backdrop {
  position: fixed; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity var(--dur-lg) var(--ease-drawer), visibility 0s linear var(--dur-lg);
}
.sidebar-backdrop.open, .mm-backdrop.open { opacity: 1; visibility: visible; transition: opacity var(--dur-lg) var(--ease-drawer), visibility 0s; }
.sidebar-backdrop { background: rgba(var(--ink-rgb),.45); z-index: 39; }

/* ============================================================
   Listado: barra de herramientas (orden)
   ============================================================ */
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
.toolbar-controls { display: flex; align-items: center; gap: 12px; }
.open-filters { display: none; align-items: center; gap: 8px; border: 1px solid var(--line); background: var(--card); border-radius: var(--r-pill); padding: 10px 16px; font-size: var(--t-ui); font-weight: 600; color: var(--ink); cursor: pointer; }
.toolbar-title { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; min-width: 0; }
/* ⚠️ `h1` entra aquí el 23 jul 2026 (auditoría SEO): en /departamento/<x> el título del
   listado pasó a ser el `h1` de la página, y en la home sigue siendo `h2`. El aspecto NO
   cambia con el nivel —eso es lo que comprueba `probar-tipografia.js` (lista ATAN)— así que
   la regla cubre los tres. Hay OTRA copia de este selector en el @media de 720: si se toca
   una, tocar la otra. */
.toolbar-title :is(h1, h2, h3) { font-family: var(--serif); font-weight: 600; font-size: var(--t-section); margin: 0; color: var(--ink); }
.toolbar-title .count { color: var(--muted); font-size: var(--t-ui); font-weight: 500; }
.toolbar-right { display: flex; align-items: center; gap: 9px; }
.sort-label { font-size: var(--t-ui); color: var(--muted); font-weight: 500; }
.sort-select { border: 1px solid var(--line); background: var(--card); border-radius: 10px; padding: 10px 14px; font-size: var(--t-ui); font-family: inherit; color: var(--ink); font-weight: 600; cursor: pointer; }
.sort-select:focus { outline: none; border-color: var(--brand-tint); box-shadow: 0 0 0 3px var(--brand-soft); }

/* ============================================================
   Página de producto
   ============================================================ */
/* Fila 1: imagen grande + info (la info se estira a la altura de la imagen).
   Fila 2: miniaturas debajo de la imagen; el lado derecho queda vacío. */
/* El reparto NO es mitad y mitad (20 jul 2026, decisión suya mirando el preview):
   la foto se lleva 530 y el texto 686. De dónde salen esos dos números: el ancho
   útil son 1268 (--maxw 1320 − 2×--pad) y 52 se los lleva el hueco → quedan 1216.
   ⚠️ Van en `fr` y NO en px: con `530px 1fr`, al estrecharse la ventana la foto se
   quedaría clavada y el texto se comería todo el recorte. En fr encogen los dos.
   Dos motivos, y ninguno es estético: (1) las fotos del panel son de 500×500, así
   que a 608 se estiraban un 22%; (2) a 608 de alto la foto se comía la primera
   pantalla y el precio y "Agregar" caían por debajo del pliegue. */
.product-layout { display: grid; grid-template-columns: 530fr 686fr;
  grid-template-areas: "gallery info" "thumbs ."; column-gap: 52px; row-gap: 14px;
  margin-top: 16px; align-items: stretch; }
/* `relative` NO es decorativo: es el ancla del ♥ que flota sobre la foto (y de su
   zona táctil de 44px). Si se quita, el ♥ se va al ancestro posicionado más
   cercano y aparece en otra parte de la página, sin error y sin verse. */
.product-gallery { grid-area: gallery; position: relative; }
.product-thumbs { grid-area: thumbs; }

/* Recomendaciones bajo la ficha: reusa .sec-head y .grid del catálogo (sin estilos
   nuevos). El separador la despega de la ficha; el margen lo pone la regla global
   de <section>. La reja ya es responsive (auto-fill minmax 210). */
/* Separador "línea + 46px de aire": lo comparten los recomendados del detalle de
   producto y el banner de la app del carrito (`.cart-app`). UNA sola declaración
   para que no se separen (el fallo nº1 del proyecto). */
.product-recs, .cart-app { border-top: 1px solid var(--line); padding-top: 46px; }
.product-recs[hidden] { display: none; }
/* En el carrito el banner ya trae su propio margin-top (para el home); aquí sobra:
   la línea + 46px son el aire, no hay que doblarlo. */
.cart-app .app-hero-compact { margin-top: 0; }
/* A diferencia del detalle de producto —donde el layout de arriba ya deja hueco antes
   de la línea—, el carrito termina a ras: sin este margen la línea se pega al resumen. */
.cart-app { margin-top: 40px; }

/* Reja de RELACIONADOS: la MISMA para las recomendaciones de la ficha de producto y
   para "Más preparaciones" del artículo del blog. 5 fichas en escritorio, 4 en móvil
   (2×2: la 5ª se oculta). NO se toca .grid (catálogo) ni .recipe-grid-all (índice del
   blog, 21 recetas): esas dos usan la misma clase en otras pantallas. */
.rel-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--gap-reja); margin-top: 32px; }
@media (max-width: 920px) { .rel-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) {
  .rel-grid { grid-template-columns: repeat(2, 1fr); }
  .rel-grid > :nth-child(5) { display: none; }   /* 4 fichas en móvil, en 2×2 */
}
/* `position: relative` desde el 25 jul 2026: es el ancla de la cinta "Pesaj", que en la
   ficha va DENTRO de la foto. Sin esto se ancla al documento y aparece en una esquina de
   la PÁGINA. No cambia nada más: la img es el único hijo en flujo. */
.gallery-main { position: relative; background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r-lg); overflow: hidden; aspect-ratio: 1/1; display: flex; align-items: center; justify-content: center; box-shadow: var(--shadow-xs); }
.gallery-main img { width: 100%; height: 100%; object-fit: contain; }
/* Fundido de la foto grande SOLO al cambiar de miniatura (la clase la pone renderGallery
   con `animar`, ui.js). En la hidratación inicial NO lleva la clase → instantáneo, sin
   parpadear la foto que el servidor ya pintó. */
.gallery-main img.ga-fade { opacity: 0; transition: opacity var(--dur-sm) var(--ease-out); }
.gallery-main img.ga-fade.loaded { opacity: 1; }
.gallery-thumbs { display: flex; gap: 10px; flex-wrap: wrap; }
.g-thumb { width: 74px; height: 74px; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--card); cursor: pointer; padding: 0; transition: border-color .15s; }
.g-thumb:hover { border-color: var(--brand-tint); }
.g-thumb img { width: 100%; height: 100%; object-fit: contain; }
.g-thumb.active { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }

.product-info { grid-area: info; padding-top: 12px; display: flex; flex-direction: column; }
/* Empuja el bloque de precio + cantidades hacia abajo para balancear con la
   imagen (cuadrada y fija). Deja al menos 36px de aire; crece si sobra espacio. */
.p-buy-spacer { flex: 1 1 36px; min-height: 36px; }
.p-cat { font-size: var(--t-small); font-weight: 700; text-transform: uppercase; color: var(--amber-ink); margin-bottom: 14px; }
.p-name { font-family: var(--serif); font-weight: 600; font-size: var(--t-product); line-height: var(--lh-display); color: var(--ink); margin: 0 0 10px; }
.p-clave { font-size: var(--t-small); color: var(--muted); margin: 0; }
.p-clave span { font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.p-price { font-size: var(--t-price); font-weight: 700; color: var(--ink); margin: 0; font-variant-numeric: tabular-nums; }
#buy-total { font-variant-numeric: tabular-nums; }
.p-attrs { border-top: 1px solid var(--line); }
.p-attr { display: flex; justify-content: space-between; gap: 16px; padding: 17px 0; border-bottom: 1px solid var(--line); font-size: var(--t-ui); }
.p-attr span { color: var(--muted); flex: 0 0 auto; white-space: nowrap; }
.p-attr strong { color: var(--ink); font-weight: 600; text-transform: capitalize; text-align: right; min-width: 0; overflow-wrap: anywhere; }
/* Promoción en la ficha (precio grande tachado + aviso) */
.p-price-was { font-size: var(--t-price-was); color: var(--muted); text-decoration: line-through; font-weight: 600; }
.p-price-now { color: var(--brand); }
.p-promo { display: inline-flex; align-items: center; gap: 8px; margin: 10px 0 0; font-size: var(--t-ui); font-weight: 700; color: var(--brand); }
/* Toggle de variante (por kilo / por pieza) */
.variant-toggle { display: inline-flex; border: 1px solid var(--line); border-radius: var(--r-pill); overflow: hidden; margin: 22px 0 0; }
.variant-btn { border: none; background: var(--card); color: var(--ink); font-family: inherit; font-weight: 600; font-size: var(--t-ui); padding: 9px 20px; cursor: pointer; transition: background .15s, color .15s, transform var(--dur-xs) var(--ease-out); }
.variant-btn:not(.on):hover { background: var(--line-soft); }
/* La mitad ELEGIDA, en vino (27 jul 2026). No es un botón, es un estado, y por eso se
   dudó: "elegido" y "tócame" pasan a ser el mismo color. Lo decidió él viéndolo — si
   fuera el único tinta que queda en toda la ficha, lo que se leería es un descuido.
   Su gemelo es `.co-seg-btn.is-on`: los dos se mueven juntos o vuelve el desajuste. */
.variant-btn.on { background: var(--brand); color: #fff; }
.p-buy { display: flex; gap: 14px; margin-top: 24px; flex-wrap: wrap; }
.qty { display: flex; align-items: center; border: 1px solid var(--line); border-radius: var(--r-pill); overflow: hidden; }
.qty-btn { border: none; background: var(--card); width: 48px; height: 50px; font-size: var(--i-2xl); cursor: pointer; color: var(--ink); transition: background .15s, transform var(--dur-xs) var(--ease-out); }
.qty-btn:hover { background: var(--line-soft); }
.qty-btn:disabled { opacity: .35; cursor: not-allowed; background: var(--card); }
/* El `padding` NO es aire decorativo: con piezas el valor es "1" y sobra sitio dentro de
   los 42, pero **a granel el texto es "300 g" o "1.25 kg"** y el span crece hasta lo que
   mida su contenido — sin relleno, las letras quedaban PEGADAS a los botones − y + (lo vio
   él en la ficha, 26 jul 2026). Va en la regla BASE y no en la de la ficha porque el mismo
   `.qty-val` lo usan la tarjeta del catálogo y el editor de un pedido, y a granel les pasa
   exactamente lo mismo. Con `box-sizing: border-box` el relleno va DENTRO del `min-width`,
   así que el control no crece cuando el valor es corto: no cambia nada donde ya estaba bien. */
.qty-val { min-width: 42px; padding: 0 10px; text-align: center; font-weight: 700; font-size: var(--t-strong); }
/* Vino con hover burdeos como el resto de los sólidos — el porqué está entero en
   `.btn-primary`, que es la regla canónica. No inventar aquí otro par de colores. */
.add-btn { flex: 1; min-width: 220px; border: none; background: var(--brand); color: #fff; border-radius: var(--r-pill); padding: 0 28px; height: 52px; font-size: var(--t-body); font-weight: 600; cursor: pointer; transition: background .15s, transform var(--dur-xs) var(--ease-out); }
.add-btn:hover { background: var(--brand-deep); }
.add-btn:disabled { cursor: not-allowed; opacity: .8; }
.p-buy-hint { font-size: var(--t-ui); color: var(--muted); margin: 12px 0 0; }
/* Cuando esta variante ya está en el carrito, el selector − n + ocupa el sitio del
   botón "Agregar" y a su lado va la salida: "Ver carrito · <subtotal>" (decisión
   suya con preview delante, 20 jul 2026). Sin ese segundo bloque la fila se queda
   con una píldora chica donde había un botón a todo el ancho, y se lee como si
   faltara algo.
   ⚠️ Va DESPUÉS de `.add-btn` a propósito: comparte su forma (alto, píldora,
   relleno) pero le cambia el relleno de color y el borde, y las dos son una sola
   clase (0,1,0) → **manda el orden en el archivo, no la especificidad**. Si esto
   sube por encima de `.add-btn`, el botón vuelve a salir negro sin que falle nada.
   Es `<a>` y no `<button>` porque de verdad navega: así se puede abrir en otra
   pestaña y el teclado lo trata como lo que es. */
/* Lo que llevas de ESTE producto, con su importe. Va pegado al selector y NO al
   botón: junto a "Ver carrito", un importe se lee como el total del carrito.
   El dinero cuelga de la cantidad ("2 piezas · $196.00") y así no hay duda. */
.p-en-carrito {
  flex: 0 1 auto; align-self: center; margin: 0; padding-right: 4px;
  font-size: var(--t-ui); color: var(--muted); white-space: nowrap;
}
.p-en-carrito strong { color: var(--ink); font-weight: 700; font-variant-numeric: tabular-nums; }
.p-ver-carrito {
  flex: 1; min-width: 160px; display: inline-flex; align-items: center; justify-content: center;
  text-decoration: none; background: var(--card); color: var(--ink); border: 1px solid var(--line);
}
.p-ver-carrito strong { font-weight: 700; font-variant-numeric: tabular-nums; }
.p-ver-carrito:hover { background: var(--bg); border-color: var(--brand); color: var(--brand); }

/* ============================================================
   Páginas de marca (Nosotros, Contacto, etc.)
   ============================================================ */
.page-hero { text-align: center; padding: clamp(24px, 4vw, 34px) 16px clamp(12px, 2vw, 18px); max-width: 720px; margin: 0 auto; }
.page-title { font-family: var(--serif); font-weight: 600; color: var(--ink); font-size: var(--t-hero); line-height: var(--lh-display); margin: 0; }
.page-sub { font-size: var(--t-body); color: var(--muted); margin: 14px 0 0; line-height: var(--lh-body); }
.page-body { max-width: 720px; margin: 28px auto 0; }
.page-body p { font-size: var(--t-body); line-height: var(--lh-body); color: var(--text); margin: 0 0 18px; }
.page-body em { color: var(--muted); }
/* Enlaces de TEXTO de las páginas informativas. El global es `a { color: inherit;
   text-decoration: none }`, así que un enlace metido en un párrafo no se distinguía
   en nada del texto de al lado — no se veía que tuviera clic. Se excluyen los que
   llevan clase (el `.btn-primary` de /facturacion vive dentro de un <p>): esos ya
   tienen su propio aspecto y no se subrayan. */
.page-body a:not([class]) {
  color: var(--brand); text-decoration: underline;
  text-underline-offset: 2px; text-decoration-thickness: 1px;
  transition: color var(--dur-xs) var(--ease-out);
}
.page-body a:not([class]):hover { color: var(--brand-deep); }
/* `.info-list` nació dentro de las tarjetas de /envios, donde 14px es lo correcto.
   En prosa quedaba más chica que los párrafos de arriba y abajo (16px/1.7) y se leía
   como "otro formato". Aquí va al tamaño de su propia página. */
.page-body .info-list li { font-size: var(--t-body); line-height: var(--lh-body); }

/* Sucursales */
.branches { margin-top: 28px; }
.branch-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--gap-contenido); }
.branch-card { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 26px 24px; box-shadow: var(--shadow-xs); display: flex; flex-direction: column; }
.branch-card :is(h2, h3, h4) { font-family: var(--serif); font-weight: 600; font-size: var(--t-block); color: var(--ink); margin: 0 0 12px; display: flex; align-items: center; gap: 8px; }
.branch-addr { font-size: var(--t-ui); color: var(--text); line-height: var(--lh-body); margin: 0 0 12px; }
/* Aire debajo del teléfono para separarlo del horario. */
.branch-line { font-size: var(--t-ui); color: var(--muted); margin: 0 0 18px; }
.branch-line a { color: var(--brand); font-weight: 600; }
.branch-hours { margin: 0 0 18px; display: flex; flex-direction: column; gap: 5px; }
.bh-row { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; font-size: var(--t-small); line-height: var(--lh-ui); padding: 3px 0; border-bottom: 1px dashed var(--line-soft); }
.bh-row:last-child { border-bottom: none; }
.bh-row dt { color: var(--muted); white-space: nowrap; }
.bh-row dd { margin: 0; color: var(--text); font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.bh-row.closed dd { color: var(--muted); font-weight: 500; }   /* --faint (2.62:1) → --muted el 27 jul 2026: "Cerrado" es el dato, no un adorno */
.branch-map { margin-top: auto; align-self: flex-start; font-size: var(--t-ui); font-weight: 700; color: var(--brand); transition: opacity .15s; }
.branch-map:hover { opacity: .7; }

/* Contacto: información principal (WhatsApp + teléfono + redes) */
.contact-primary { display: flex; flex-direction: column; align-items: center; gap: 14px; margin-top: 26px; }
.cta-wa { display: inline-flex; align-items: center; gap: 9px; height: 52px; padding: 0 26px; border-radius: var(--r-pill); font-size: var(--t-strong); font-weight: 700; text-decoration: none; background: #12833a; color: #fff; border: none; box-shadow: var(--shadow-sm); transition: transform .12s, background .15s; }
.cta-wa:hover { background: #0f6f31; transform: translateY(-1px); }
.cta-wa svg { width: 20px; height: 20px; }
.contact-or { font-size: var(--t-ui); font-weight: 600; color: var(--muted); margin-bottom: -4px; }
.contact-phones { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
.cta-phone { display: inline-flex; align-items: center; gap: 9px; height: 52px; padding: 0 24px; border-radius: var(--r-pill); font-size: var(--t-strong); font-weight: 700; text-decoration: none; background: var(--card); color: var(--ink); border: 1.5px solid var(--line); transition: border-color .15s, color .15s, transform .12s; }
.cta-phone svg { width: 18px; height: 18px; color: var(--brand); }
.cta-phone:hover { border-color: var(--brand); color: var(--brand); transform: translateY(-1px); }
.contact-follow { display: flex; flex-direction: column; align-items: center; gap: 12px; margin-top: 10px; padding-top: 22px; border-top: 1px solid var(--line-soft); width: 100%; max-width: 240px; }
.contact-follow-label { font-size: var(--t-label); font-weight: 700; text-transform: uppercase; color: var(--muted); }
.contact-follow .socials { display: flex; gap: 11px; justify-content: center; }
.contact-follow .social { width: 48px; height: 48px; }
.contact-follow .social svg { width: 23px; height: 23px; }

/* Contacto: pin en las tarjetas de sucursal (el flex del título vive arriba, en
   la definición única de .branch-card :is(h2,h3,h4) — era la segunda copia). */
.branch-pin { font-size: var(--i-base); line-height: 1; }

/* Nosotros: historia (imagen y texto lado a lado, usando todo el ancho) */
/* ⚠️ ESTE HUECO NO ES UNA REJA Y NO VA POR TOKEN, aunque lo parezca: es el CANALÓN
   entre la foto y la columna de texto. Con los 14–16 de las rejas, las dos columnas
   se tocarían y el texto se leería peor — mismo caso que `.co-addr-list` y
   `.gallery-thumbs`. Lo que sí se hizo el 27 jul 2026: **bajarlo de 52 a 38** (tope
   elegido por él: *"en 38 mejor"*). No había crecido — era el ÚNICO hueco del sitio
   que no bajó al pasar las rejas a 12/14/16, y de golpe medía casi cuatro veces el
   de una reja. Lo que se corrigió es la escala, no un fallo. El mínimo baja de 28 a
   24 con él, para que la curva no se quede plana en las anchuras medias. */
.about-story { display: grid; grid-template-columns: 0.9fr 1.1fr; gap: clamp(24px, 3vw, 38px); align-items: stretch; margin-top: 34px; }
.about-figure { margin: 0; border-radius: var(--r-lg); overflow: hidden; box-shadow: var(--shadow-sm); background: var(--line-soft); }
.about-figure img { width: 100%; height: 100%; min-height: 380px; object-fit: cover; object-position: center; display: block; }
.about-narrative { min-width: 0; }
/* ⚠️ Aquí había un comentario que describía un `clamp(21px … 32px)` que ESTA REGLA YA NO TIENE
   —hoy es un token pelado, `--t-section`— y que además daba por hecho que `.sec-head h2` y
   `.prose h2` valían 21 en móvil: `.sec-head` no tiene versión de móvil (va a 25 en todas las
   pantallas) y el mínimo de `.prose h2` es 20. Las dos cosas eran FALSAS y se dejan dichas para
   que nadie las vuelva a dar por buenas (26 jul 2026).
   Lo que SÍ sigue vivo de aquella decisión suya: en Nosotros, el h1 y el encabezado de sección
   no pueden leerse del mismo tamaño en el teléfono. Hoy no lo hacen —`.page-title` topa en 29 y
   esto va a 25—, así que no hace falta nada; si alguien acerca los dos, vuelve el problema. */
.about-h2 { font-family: var(--serif); font-weight: 600; font-size: var(--t-section); line-height: var(--lh-heading); color: var(--ink); margin: 0 0 18px; text-wrap: balance; }
.about-narrative p { font-size: var(--t-body); line-height: var(--lh-body); color: var(--text); margin: 0 0 15px; }
.about-narrative strong { color: var(--ink); font-weight: 700; }
.about-narrative em { color: var(--ink); font-style: normal; font-weight: 600; }
.about-cta { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 26px; }
@media (max-width: 820px) {
  .about-story { grid-template-columns: 1fr; gap: 20px; margin-top: 28px; }
  .about-figure img { height: auto; min-height: 0; aspect-ratio: 16 / 10; }
}

/* ============================================================
   Botón de cuenta (header)
   ============================================================ */
.account-link { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line); background: var(--card); color: var(--ink); border-radius: var(--r-pill); padding: 9px 15px; font-size: var(--t-ui); font-weight: 600; text-decoration: none; transition: border-color var(--dur-sm), color var(--dur-sm); }
.account-link:hover { border-color: var(--brand); color: var(--brand); }
/* Si el nombre es largo se enseña SOLO UNA PARTE, con "…" (decisión suya, 20 jul
   2026). Bajó de 160 a 100 px: con 160, un nombre largo ensancha tanto este lado
   que —aun con el reparto a partes iguales— el menú se empieza a correr por debajo
   de ~1080 px. Con 100 entra "Jose Sacal" entero y el menú se queda clavado en el
   centro en todo el rango.
   ⚠️ El recorte va en CSS y NO en el texto a propósito: el nombre lo escriben DOS
   sitios —`header.php` en el servidor y `paintSession()` de auth.js al editar el
   perfil— y cortarlo en el texto obligaría a repetir la misma regla en los dos, que
   es el fallo nº1 de este proyecto. Así hay una sola regla y da igual quién pinte. */
.account-txt { max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============================================================
   Páginas de cuenta / auth (esqueleto)
   ============================================================ */
.demo-note { display: inline-flex; align-items: center; gap: 8px; background: var(--line-soft); color: var(--muted); font-size: var(--t-small); font-weight: 600; padding: 7px 13px 7px 11px; border-radius: var(--r-pill); border: 1px solid var(--line); margin: 18px 0 2px; }
.demo-note::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); flex: none; }
/* ⬇ EL BOTÓN SÓLIDO DEL SITIO — vino con hover burdeos (decisión suya, 27 jul 2026).
   Hasta hoy había DOS familias de sólido conviviendo sin que nadie lo hubiera decidido:
   unos en tinta que se ponían vinos al pasar el ratón (`.btn-primary`, `.add-btn`,
   `.load-more`, `.mini-btn`) y otros ya en vino que se oscurecían a burdeos
   (`.m-btn-primary`, `.consent-si`, `.btn-danger`). Ganó el vino: *"algunos detalles
   pierden color"*. Los CUATRO de tinta pasan a `--brand` con hover `--brand-deep`, y
   con ellos los dos segmentos (`.variant-btn.on`, `.co-seg-btn.is-on`) para que
   "elegido" no se quede siendo el último tinta suelto.
   **Lo que NO se tocó y no es un olvido:** los de contorno (`.btn-ghost`, `.cta-phone`,
   `.icon-btn`…) ya apuntaban al vino y son lo que distingue la segunda acción de la
   primera; el verde de `.cta-wa` es la marca de WhatsApp; `.qty-btn` es un dial, no una
   acción; y los fondos tinta que quedan (`.cart-toast`, `.trk-step.on`, `.step-num`) no
   son botones.
   ⚠️ **Efecto conocido y aceptado por él:** `.btn-danger` ("Sí, cancelar") deja de ser el
   único vino de la pantalla del pedido y ahora pesa igual que "Volver a pedir". Se le
   enseñó en el preview con esa pantalla montada y dijo que sí. Si algún día vuelve el
   problema, la salida es dejar `.btn-danger` de CONTORNO vino, no devolver el resto a
   tinta. Contraste: blanco sobre vino 7.7:1 y sobre burdeos 10:1 — AA pide 4.5. */
.btn-primary { display: inline-block; text-align: center; text-decoration: none; border: none; background: var(--brand); color: #fff; border-radius: var(--r-pill); padding: 14px 22px; font-size: var(--t-ui); font-weight: 600; cursor: pointer; transition: background var(--dur-sm), opacity var(--dur-sm), transform var(--dur-xs) var(--ease-out); }
.btn-primary:hover:not(:disabled) { background: var(--brand-deep); }
.btn-primary:disabled { opacity: .55; cursor: not-allowed; }

/* ============================================================
   Campos de formulario — login, registro, recuperar, perfil y direcciones
   ============================================================
   UNA sola regla para los cinco. Antes había DOS copias, `.auth-form` y
   `.account-form`, y se habían separado sin que nadie lo notara: login iba en
   blanco y **perfil y direcciones salían con el fondo GRIS de la página**, o sea
   con pinta de apagados —nadie escribe en un campo gris— y **sin marca de foco**,
   así que tampoco se veía dónde estabas escribiendo. Lo cazó el usuario con una
   captura de /direcciones ("cuadros en gris donde se debe de escribir"). El bueno
   era el de login, y es el que queda para todos.
   Si aparece otro formulario, que use `.auth-form` o `.account-form`: no se copian
   estos estilos otra vez. */
.auth-form label, .account-form label {
  /* BLOQUE, no flex en columna. En flex-columna, "(opcional)" —o cualquier
     aclaración suelta dentro del <label>— se vuelve un renglón propio y EMPUJA el
     campo hacia abajo: en las filas de dos, el de la derecha quedaba más bajo que
     el de la izquierda (Núm. interior, Ciudad/Alcaldía). Como bloque, la aclaración
     se queda pegada al rótulo, en su renglón, y los campos vuelven a alinearse. */
  display: block;
  font-size: var(--t-ui); font-weight: 600; color: var(--ink);
}
.auth-form label input, .account-form label input { display: block; margin-top: 7px; }
.auth-form input, .account-form input {
  width: 100%; min-width: 0;
  border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; font-size: var(--t-field); font-family: inherit;
  color: var(--ink); background: var(--card);
  transition: border-color .15s, box-shadow .15s;
}
.auth-form input::placeholder, .account-form input::placeholder { color: var(--faint); }
/* Foco NEUTRO (pizarra), no vino: el vino se confundía con el rojo de error (23 jul). */
.auth-form input:focus, .account-form input:focus {
  outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(var(--ink-rgb), .12);
}
/* El gris se GANA: es de lo que de verdad NO se puede escribir (el correo y el
   teléfono del perfil, que se cambian en la app). Si todo es gris, el gris no
   dice nada. */
.auth-form input:disabled, .account-form input:disabled {
  background: var(--bg); color: var(--muted); cursor: not-allowed;
}
/* Ver la contraseña — el ojo que inyecta site.js (29 jul 2026).
   La envoltura existe solo para colgar el botón: se lleva el margen que traía el
   campo (`.auth-form label input { margin-top: 7px }`) para no depender de que
   ese margen se colapse hacia fuera, y así el ojo se centra sobre el campo y no
   sobre el hueco del rótulo. */
.pass-wrap { position: relative; display: block; margin-top: 7px; }
.auth-form label .pass-wrap input, .account-form label .pass-wrap input { margin-top: 0; }
/* Sitio para el ojo: sin esto, una contraseña larga pasa por debajo del icono. */
.pass-wrap input { padding-right: 46px; }
.pass-ojo {
  position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  /* La zona se AGRANDA hasta 40 sin engordar el icono (19px): dentro del campo no
     hay ningún otro control al que robarle toques. */
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 10px; background: none; padding: 0;
  color: var(--muted); cursor: pointer;
  transition: color .15s;
}
.pass-ojo:hover { color: var(--ink); }
.pass-ojo[aria-pressed="true"] { color: var(--ink); }
.pass-ojo:active { transform: translateY(-50%) scale(.92); }

/* Aclaración del rótulo: "(opcional)", "(y se llena sola)". Global a propósito
   —el checkout también la usa—; antes solo existía dentro de `.addr-form`, así que
   en el título de Comentarios se veía del tamaño del encabezado. */
.opt { font-weight: 400; color: var(--muted); font-size: var(--t-small); }   /* --faint (2.62:1) → --muted el 27 jul 2026: "(opcional)" decide si escribes o no */

/* Login */
.auth-wrap { display: flex; justify-content: center; padding: 30px 0 70px; }
/* Las TRES páginas de sesión comparten esta tarjeta. `width: 100%` + un tope: en el
   teléfono el tope ni llega a aplicarse —la tarjeta ya ocupa el ancho disponible— y
   por eso ahí siempre se vio bien. */
.auth-card { width: 100%; max-width: 420px; background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r-lg); box-shadow: var(--shadow-sm); padding: 40px 34px; }
/* SOLO «Crear cuenta» (27 jul 2026, y así lo acotó él: *"inicio de sesión y
   recuperar sí se quedan como antes, solo cambia crear"*). Empezó como *"en móvil
   está perfecto el tamaño, pero en web es muy angosto"*, y la primera versión movió
   las tres páginas: eso era pasarse del encargo.
   ⚠️ Y el ancho SOLO no arreglaba nada: lo que sobra en «Crear cuenta» es ALTO —siete
   campos en fila india—, no ancho. Estirar una columna a 720 deja los campos más
   largos que lo que se escribe en ellos; lo que aprovecha el espacio es EMPAREJARLOS,
   con la reja de abajo. Se le enseñaron las dos formas en una vista previa.
   Iniciar sesión (dos campos) y Recuperar (uno) no tienen nada que emparejar: se
   quedan en 420 a propósito. **Son dos anchos porque son dos formularios distintos,
   no por descuido: no los "unifiques" luego.** */
.auth-card-ancha { max-width: 720px; }
.auth-title { font-family: var(--serif); font-weight: 600; font-size: var(--t-page); color: var(--ink); margin: 0 0 6px; text-align: center; }
.auth-sub { color: var(--muted); font-size: var(--t-ui); text-align: center; margin: 0 0 20px; }
.auth-note { display: flex; align-items: center; gap: 10px; background: var(--brand-soft); color: var(--brand-deep); border-radius: 12px; padding: 12px 14px; font-size: var(--t-ui); line-height: var(--lh-ui); margin: 0 0 24px; }
.auth-note svg { flex: 0 0 auto; color: var(--brand); }
.auth-note strong { font-weight: 700; }
.auth-form { display: flex; flex-direction: column; gap: 16px; }
.auth-form .btn-primary { width: 100%; margin-top: 4px; }
.auth-form .btn-primary[aria-busy="true"] { opacity: .6; cursor: progress; }
/* DOS COLUMNAS en escritorio, y SOLO en «Crear cuenta» (27 jul 2026). El corte es
   721 porque a ≤720 ya existe la regla que devuelve `.form-row` a una columna: las
   dos son el mismo formulario y tienen que hablar del mismo número. En el teléfono
   esto no se enciende, así que ahí NO cambia nada — que es lo que él pidió.
   `display: contents` en `.form-row` es la pieza clave: disuelve el div y deja que
   Nombre y Apellido entren como celdas sueltas de la reja, sin tocar el HTML.
   Lo que va de lado a lado lo dice cada elemento, no su posición. */
@media (min-width: 721px) {
  .auth-card-ancha .auth-form { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 20px; align-items: start; }
  .auth-card-ancha .auth-form > .form-row { display: contents; }
  .auth-card-ancha .auth-form > .reg-cp,
  .auth-card-ancha .auth-form > .auth-feedback,
  .auth-card-ancha .auth-form > .btn-primary { grid-column: 1 / -1; }
  /* El botón deja de ocupar los 720: a esa anchura un botón de lado a lado parece
     una barra, no algo que se pulsa. Centrado y con suelo de 280 para que siga
     siendo un objetivo grande. */
  .auth-card-ancha .auth-form > .btn-primary { width: auto; justify-self: center; min-width: 280px; }
}
/* Registro: campo de CP OPCIONAL siempre visible (aclaración con `.opt`). CP y teléfono
   se topan por longitud en auth.js (5 y 10). El ERROR de un campo va en rojo (vino): ya
   NO choca con el foco, que pasó a pizarra (23 jul). Va después de `:focus`, así que en un
   campo enfocado E inválido gana el rojo (es lo correcto: sigue estando mal). */
.auth-form input.invalid, .account-form input.invalid { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
/* El aviso de los formularios (auth). Los colores `ok` eran #1f9d55 (3.49:1, fallaba
   AA) antes del token `--ok`.
   ⚠️ Estas tres reglas llevaban además `.cp-feedback`, del comprobador de código
   postal — y se retiró el 28 jul 2026 porque **ya no existe nada que ponga esa
   clase**: su único consumidor era `cpcheck.js`, que dejó de cargarse el 23 jul y
   cuyo archivo tampoco está ya; `#cp-input` no aparece en ninguna vista. Medido: 0
   apariciones de `cp-feedback` en todo `web-sinai/` fuera de este archivo. Era el
   caso de libro de "lista de selectores que es una copia disfrazada": el componente
   se comparte, pero uno de los dos dueños ya no existe. */
.auth-feedback { font-size: var(--t-ui); font-weight: 600; margin: -6px 2px 2px; min-height: 1px; }
.auth-feedback.ok { color: var(--ok); }
.auth-feedback.no { color: var(--brand); }
.auth-foot { margin-top: 22px; display: flex; flex-direction: column; gap: 10px; text-align: center; font-size: var(--t-ui); color: var(--muted); }
.auth-foot a { color: var(--brand); font-weight: 600; }

/* Cuenta (layout) */
/* 30 y no 14 desde el 21 jul 2026: al quitarles la miga a las páginas de cuenta
   (el menú lateral ya dice dónde estás) el contenido se quedaba pegado a la
   cabecera. El 30 no es a ojo — es el margen superior que tenía `.breadcrumb`,
   así que el bloque empieza justo donde empezaba la miga. */
.account-layout { display: grid; grid-template-columns: 240px 1fr; gap: 32px; margin-top: 30px; align-items: start; }
.account-nav { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 14px; display: flex; flex-direction: column; gap: 2px; box-shadow: var(--shadow-xs); }
/* Sin `gap` desde el 29 jul 2026: era la separación con la foto de perfil, y esa
   se quitó (decisión suya). Con un solo hijo, un `gap` no separa de nada. */
.account-user { display: flex; align-items: center; padding: 10px 12px 16px; border-bottom: 1px solid var(--line-soft); margin-bottom: 8px; }
.account-user strong { display: block; font-size: var(--t-ui); color: var(--ink); }
.account-user span { font-size: var(--t-small); color: var(--muted); }
.account-link-item { padding: 11px 12px; border-radius: 10px; font-size: var(--t-ui); font-weight: 600; color: var(--text); text-decoration: none; transition: background var(--dur-sm), color var(--dur-sm); }
.account-link-item:hover { background: var(--line-soft); }
.account-link-item.active { background: var(--brand-soft); color: var(--brand); }
.account-logout { color: var(--muted); margin-top: 6px; border-top: 1px solid var(--line-soft); border-radius: 0; padding-top: 14px; }
.account-panel { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 18px 30px 30px; box-shadow: var(--shadow-xs); margin-top: 0; }
.panel-title { font-family: var(--serif); font-weight: 600; font-size: var(--t-panel); color: var(--ink); margin: 0 0 22px; }
.panel-subtitle { font-family: var(--serif); font-weight: 600; font-size: var(--t-block); color: var(--ink); margin: 34px 0 16px; padding-top: 24px; border-top: 1px solid var(--line-soft); }
.account-form { display: flex; flex-direction: column; gap: 16px; max-width: 460px; }

/* Perfil en dos columnas: datos a la izquierda, contraseña en un recuadro a la
   derecha (21 jul 2026, suyo — el panel dejaba ~500px de aire muerto a la derecha).
   Las columnas NO son 1fr 1fr: la de datos lleva DOS campos en fila (Nombre y
   Apellido) y la de contraseña solo uno por renglón, así que a mitades la primera
   se estrangula. 1.15/1 le da su hueco sin que la segunda se vea residual. */
.perfil-cols { display: grid; grid-template-columns: 1.15fr 1fr; gap: 28px; align-items: start; }
/* Dentro de una columna el tope de 460 sobra: la columna ya es el tope. */
.perfil-cols .account-form { max-width: none; }
/* El recuadro va en el gris de la base, no en blanco: el panel que lo contiene YA
   es blanco y los campos también, así que un cajón blanco sobre blanco no se leería
   como cajón. Sin sombra: no flota, solo agrupa. */
.perfil-box { background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 20px 22px 24px; }
/* `.panel-subtitle` trae margen y raya de arriba porque nació para separar bloques
   apilados. Dentro del recuadro no separa nada: es su encabezado. */
.panel-subtitle.is-flush { margin-top: 0; padding-top: 0; border-top: none; }
.account-form .btn-primary[aria-busy="true"] { opacity: .6; cursor: progress; }
/* Los campos van arriba: ver "Campos de formulario". */
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
/* Los campos deben poder encogerse dentro del grid; si no, el de la derecha
   (Apellido) se sale de la tarjeta en pantallas angostas. */
.form-row > * { min-width: 0; }

/* Pedidos */
.orders-list { display: flex; flex-direction: column; gap: 12px; }
.order-row { display: flex; flex-direction: column; gap: 14px; padding: 18px 20px; border: 1px solid var(--line); border-radius: var(--r); text-decoration: none; transition: border-color var(--dur-sm), box-shadow var(--dur-sm); background: var(--card); }
.order-row:hover { border-color: var(--brand-tint); box-shadow: var(--shadow-xs); }
.order-row-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.order-row-head strong { font-size: var(--t-strong); color: var(--ink); }
.order-row-meta { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 22px; }
.meta-field { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.meta-lbl { font-size: var(--t-label); font-weight: 700; text-transform: uppercase; color: var(--muted); }   /* --faint (2.62:1) → --muted el 27 jul 2026: son los rótulos de Mis pedidos, y a 11px */
.meta-val { font-size: var(--t-ui); color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.meta-val.strong { font-weight: 800; color: var(--ink); }
.order-row-foot { display: flex; align-items: center; justify-content: space-between; border-top: 1px solid var(--line-soft); padding-top: 12px; }
.order-row-foot .order-arrow { color: var(--brand); font-weight: 700; font-size: var(--t-ui); }
@media (max-width: 560px) { .order-row-meta { grid-template-columns: 1fr; gap: 10px; } }
.order-date { font-size: var(--t-small); color: var(--muted); }
.order-status { font-size: var(--t-label); font-weight: 700; padding: 5px 11px; border-radius: var(--r-pill); white-space: nowrap; }
.status-entregado { background: var(--ok-soft); color: var(--ok); }
.status-camino { background: var(--amber-soft); color: var(--amber-ink); }   /* era #8a6512 = 4.36:1, fallaba AA por poco */
.status-cancelado { background: var(--brand-soft); color: var(--brand); }   /* eran #fbe9e9/#a3232a, indistinguibles de --brand-soft/--brand a 12px */
.order-total { font-weight: 800; color: var(--ink); font-size: var(--t-strong); }
.muted-note { margin-top: 22px; font-size: var(--t-ui); color: var(--muted); }
.orders-empty { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; }
.orders-empty .btn-primary { width: auto; padding: 11px 22px; }

/* Detalle de pedido */
.order-detail { margin-top: 14px; }
.order-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 24px; }
.order-detail-grid { display: grid; grid-template-columns: 1fr 320px; gap: 30px; align-items: start; }
.order-items { display: flex; flex-direction: column; gap: 14px; }
.order-item { display: flex; align-items: center; gap: 16px; background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 16px; }
.order-item-img { width: 60px; height: 60px; border-radius: 12px; background: var(--line-soft); flex: 0 0 auto; }
.order-item-info { flex: 1; }
.order-item-info strong { display: block; font-size: var(--t-strong); color: var(--ink); }
.order-item-info span { font-size: var(--t-ui); color: var(--muted); }
.order-item-price { font-weight: 700; color: var(--ink); }
.order-summary { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 24px; box-shadow: var(--shadow-xs); }
.order-summary :is(h2, h3, h4) { font-family: var(--serif); font-size: var(--t-block); color: var(--ink); margin: 0 0 16px; }
.sum-row { display: flex; justify-content: space-between; font-size: var(--t-ui); color: var(--text); padding: 8px 0; }
.sum-total { border-top: 1px solid var(--line); margin-top: 6px; padding-top: 14px; font-weight: 800; font-size: var(--t-body); color: var(--ink); }
.sum-addr { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 16px; }

/* ============================================================
   TRACKER DEL PEDIDO
   ============================================================
   Estructura calcada de la app: título del estado · barra con los iconos ENCIMA ·
   descripción. Los iconos van absolutos sobre la barra, repartidos con
   space-between, así que el primero y el último quedan a ras de los extremos.
   Vive DENTRO de `.order-summary` (columna de 320px → 272px de hueco): los cuatro
   iconos de 44px suman 176px y le sobran 96 para repartir. Las medidas de aquí son
   para ESE ancho — no hay una segunda copia para otro sitio, porque no se pinta en
   ningún otro. */
.trk { margin: 0 0 4px; }
.trk-title { font-family: var(--serif); font-size: var(--t-body); font-weight: 600; color: var(--ink); text-align: center; margin: 0 0 16px; }
.trk-rail { position: relative; height: 44px; display: flex; align-items: center; }
.trk-track { position: absolute; left: 0; right: 0; height: 5px; background: var(--line); border-radius: var(--r-pill); }
.trk-fill { height: 100%; background: var(--ink); border-radius: var(--r-pill); transition: width var(--dur-lg) var(--ease-out); }
.trk-icons { position: absolute; inset: 0; display: flex; justify-content: space-between; align-items: center; }
/* El borde del color de la tarjeta recorta la barra por detrás del icono: sin él,
   la línea se ve cruzando el círculo. */
.trk-step {
  width: 44px; height: 44px; border-radius: 50%; flex: none;
  background: var(--line); color: var(--ink);
  display: grid; place-items: center;
  border: 3px solid var(--card);
  transition: background var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.trk-step svg { width: 20px; height: 20px; stroke-width: 1.9; }
.trk-step.on { background: var(--ink); color: #fff; }
.trk-desc { text-align: center; color: var(--muted); font-size: var(--t-ui); line-height: var(--lh-ui); margin: 14px 0 0; }

/* ENTREGADO → verde (decisión suya, 16 jul). Es el MISMO verde de la etiqueta
   `.status-entregado` que está justo arriba, en el encabezado del pedido: si fueran
   dos verdes distintos se vería el desajuste en la misma pantalla. Blanco sobre
   --ok da 5.37:1 → pasa AA. */
.trk-ok .trk-fill { background: var(--ok); }
.trk-ok .trk-step.on { background: var(--ok); }

/* Ya NO hay un bloque @media que apriete el título y la descripción en móvil: al
   bajar el tracker a la columna del resumen, las medidas base pasaron a ser las de
   ese hueco de 272px, que es MÁS estrecho que el móvil (ahí el resumen se pone a
   una columna y el tracker gana aire). Aquel @media repetía exactamente los mismos
   valores que ahora están arriba: una segunda copia esperando a divergir. */
@media (prefers-reduced-motion: reduce) {
  /* La barra NO se anima: no explica nada que el usuario no vea ya. Los fundidos de
     color se quedan (son los que dicen qué acaba de cambiar). */
  .trk-fill { transition: none; }
}

/* Un pedido cancelado ya no cuesta nada: el total va tachado. Es lo que hace la app
   (ngStyle con line-through en status 5), y sin ello un "$0.00" a secas parece un
   error de cálculo en vez de "esto ya no se cobra". */
.sum-total-cancelado span:last-child { text-decoration: line-through; color: var(--muted); }

/* Acciones del pedido: viven DENTRO del aside (ver renderDetail). En vertical
   porque la columna mide 320px y dos botones en fila no caben con holgura. */
.order-actions { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 16px; display: flex; flex-direction: column; gap: 10px; }
.order-actions .btn-primary, .order-actions .btn-ghost { width: 100%; text-align: center; }

/* Cancelar: el botón NO es rojo hasta que se confirma. `.btn-ghost` para abrir y
   `.btn-danger` solo dentro de la caja de confirmación — un botón rojo permanente
   en la pantalla del pedido invita a tocarlo. */
.btn-danger {
  display: inline-block; text-decoration: none; border: 1px solid var(--brand);
  background: var(--brand); color: #fff; border-radius: var(--r-pill);
  padding: 14px 22px; font-size: var(--t-ui); font-weight: 600; cursor: pointer;
  font-family: inherit; transition: background var(--dur-sm), border-color var(--dur-sm);
}
.btn-danger:hover:not(:disabled) { background: var(--brand-deep); border-color: var(--brand-deep); }
.btn-danger:disabled { opacity: .5; cursor: not-allowed; }

.cancel-box { margin-top: 14px; background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.cancel-box strong { font-size: var(--t-ui); color: var(--ink); }
.cancel-sub { margin: 0; font-size: var(--t-ui); color: var(--muted); }
.cancel-input {
  width: 100%; min-width: 0; border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px; font-size: var(--t-field); font-family: inherit; color: var(--ink); background: var(--card);
  transition: border-color .15s, box-shadow .15s;
}
.cancel-input:focus { outline: none; border-color: var(--brand-tint); box-shadow: 0 0 0 3px var(--brand-soft); }   /* es un CAMPO: va en --brand-tint */
.cancel-actions { display: flex; gap: 8px; }
.cancel-actions .btn-ghost, .cancel-actions .btn-danger, .cancel-actions .btn-primary { flex: 1; padding: 12px 10px; font-size: var(--t-ui); text-align: center; }

/* Editar pedido: los renglones con su ± dentro de la caja (reusa .cancel-box).
   El selector es el MISMO `.card-qty`/`.qty-btn` del carrito y de la ficha: es el
   mismo control haciendo el mismo trabajo, así que no se inventa otro aspecto. */
/* Día de entrega: una LISTA de fechas válidas, como la rueda de la app — no un
   calendario. Reusa `.sort-select` (el mismo control de "Ordenar" en la tienda). */
.edit-fecha:not(:empty) { margin-bottom: 4px; }
.edit-fecha-lbl { display: flex; flex-direction: column; gap: 6px; font-size: var(--t-ui); font-weight: 600; color: var(--ink); }
.edit-fecha-lbl .sort-select { width: 100%; }
.edit-lines { display: flex; flex-direction: column; gap: 8px; }
.edit-line { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.edit-line-name { font-size: var(--t-ui); color: var(--text); min-width: 0; }
.edit-qty { display: flex; align-items: center; gap: 2px; background: var(--card); border: 1px solid var(--line); border-radius: var(--r-pill); flex: none; }
.edit-qty .qty-btn { border: none; background: none; cursor: pointer; width: 34px; height: 34px; font-size: var(--i-md); color: var(--ink); border-radius: 50%; }
.edit-qty .qty-btn:hover { background: var(--line-soft); }
.edit-qty .qty-val { font-size: var(--t-ui); font-weight: 600; color: var(--ink); min-width: 54px; text-align: center; font-variant-numeric: tabular-nums; }
.sum-addr strong { font-size: var(--t-ui); color: var(--ink); }
.sum-addr p { font-size: var(--t-ui); color: var(--muted); margin: 6px 0 0; }
/* Bloque de entrega arriba del resumen: sin borde superior (va primero). */
.sum-entrega { margin-top: 0; border-top: 0; border-bottom: 1px solid var(--line); padding-top: 0; padding-bottom: 18px; margin-bottom: 4px; }
.sum-fecha { color: var(--ink) !important; font-weight: 600; }

/* ============================================================
   Iconos de header (favoritos / carrito)
   ============================================================ */
.icon-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 42px; height: 42px; border: 1px solid var(--line); border-radius: 50%; color: var(--ink); background: var(--card); transition: border-color var(--dur-sm), color var(--dur-sm), transform var(--dur-xs) var(--ease-out); }
.icon-btn:hover { border-color: var(--brand); color: var(--brand); }
/* Caja y relleno en `em` (27 jul 2026). Es el que peor lo llevaba de los cuatro: con
   "Tamaño grande" el número ya se salía —11px de letra en una caja de 18—, y encima es
   el contador del carrito, o sea el dato que se mira. 1.636em = los 18px de hoy.
   `top`/`right` se quedan en px: colocan la píldora contra el ICONO, no contra su
   propia letra, y ese icono no crece con la tipografía. */
.cart-badge { position: absolute; top: -4px; right: -4px; min-width: 1.636em; height: 1.636em; padding: 0 .4545em; background: var(--brand); color: #fff; border-radius: 999px; font-size: var(--t-label); font-weight: 700; display: flex; align-items: center; justify-content: center; }

/* ============================================================
   Encabezado de sección
   ============================================================ */
.section-heading { font-family: var(--serif); font-weight: 600; font-size: var(--t-page); color: var(--ink); margin: 18px 0 24px; }

/* ============================================================
   Carrito
   ============================================================ */
.cart-layout { display: grid; grid-template-columns: 1fr 340px; gap: 30px; align-items: start; }
.cart-items { display: flex; flex-direction: column; gap: 14px; }
.cart-item { display: grid; grid-template-columns: 70px 1fr auto auto auto; align-items: center; gap: 16px; background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 16px; box-shadow: var(--shadow-xs); }
/* Salida de una fila al pulsar ✕ (25 jul 2026). `renderCartPage` rehace
   `root.innerHTML` ENTERO, así que sin esto la fila se esfuma de golpe y todo lo
   de abajo brinca. SOLO la salida: una entrada parpadearía la lista completa en
   cada `+`/`−`, que repinta igual. La `transition` vive en el estado y no en
   `.cart-item` para no cargar la fila en reposo. La clase la pone cart.js. */
.cart-item.is-out {
  opacity: 0; transform: scale(.97); pointer-events: none;
  transition: opacity var(--dur-sm) var(--ease-out), transform var(--dur-sm) var(--ease-out);
}
.cart-item-img { width: 70px; height: 70px; border-radius: 12px; background: var(--line-soft); overflow: hidden; }
.cart-item-img img { width: 100%; height: 100%; object-fit: contain; }
.cart-item-info strong { display: block; font-size: var(--t-strong); color: var(--ink); }
/* Hijo directo, NO descendencia: `.cart-item-info span` (0,1,1) le ganaba a
   `.mini-promo` (0,1,0) y pintaba la píldora de promo en gris sobre vino (1.34:1). */
.cart-item-info > span { font-size: var(--t-ui); color: var(--muted); }
/* "2 días de anticipación": el producto no llega hoy, así que va en su propio
   renglón y en el ámbar de `.encargo-tag` ("Sobre pedido" de la tienda), no en el
   gris del subtítulo — es una condición de entrega, no un dato más de la ficha. */
.cart-item-ant { margin: 3px 0 0; font-size: var(--t-small); font-weight: 600; color: var(--amber-ink); }
.cart-item .qty { transform: scale(.85); }
.cart-item-price { font-weight: 800; color: var(--ink); font-size: var(--t-strong); }
.cart-item-prices { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.cart-item-prices .price-was { font-size: var(--t-small); }
/* En pantallas angostas, el renglón del carrito se acomoda en 2 filas (sin desbordar). */
@media (max-width: 560px) {
  .cart-item {
    grid-template-columns: 56px 1fr auto;
    grid-template-areas: "img info remove" "img qty price";
    gap: 6px 12px; padding: 14px;
  }
  .cart-item-img { grid-area: img; width: 56px; height: 56px; align-self: start; }
  .cart-item-info { grid-area: info; min-width: 0; }
  .cart-item .qty { grid-area: qty; justify-self: start; transform: scale(.9); transform-origin: left center; }
  .cart-item-prices { grid-area: price; align-self: center; }
  .cart-remove { grid-area: remove; align-self: start; justify-self: end; }
}
/* Misma píldora que .promo-ribbon de las tarjetas: a 9.5px los trazos blancos se
   difuminaban contra el vino y se leían grises, aunque el contraste diera 7.7:1. */
.mini-promo {
  display: inline-block; background: var(--brand); color: #fff;
  font-size: var(--t-label); font-weight: 800; line-height: 1;
  padding: 3px 9px; border-radius: var(--r-pill); vertical-align: middle;
}
.sum-save { color: var(--brand); font-weight: 600; }
/* Acción destructiva y SIN confirmación, a 27×28 y pegada al nombre del producto.
   El margen negativo devuelve el glifo a su sitio: la fila no crece ni se mueve. */
.cart-remove {
  border: none; background: none; color: var(--faint); font-size: var(--i-md); cursor: pointer;
  width: 44px; height: 44px; padding: 0; margin: -8px -8px -8px 0;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; transition: color .15s;
}
.cart-remove:hover { color: var(--brand); }
/* Aquí vivía `.back-link`, el enlace de volver de /carrito y /checkout. Se BORRÓ el
   16 jul: era una segunda forma de vestir la misma franja. Ahora esas dos páginas
   usan el marcado de siempre —`<nav class="breadcrumb"><a class="crumb">`— con un
   solo enlace dentro, y se quedan sin migas de verdad (no hay árbol, hay un paso
   atrás). Lo que se ganó al matarla:
     · la ZONA TÁCTIL de 44px, que `.crumb` trae con su padding + margen negativo y
       que `.back-link` NO tenía — o sea que era una regresión del trabajo de hoy;
     · la letra (13.5px, no 14) y el hover por opacidad, como el resto;
     · y de paso el lío de los márgenes: `.back-link` era inline-block, así que sus
       márgenes NO se fundían con los del título y SE SUMABAN — era el único sitio
       del sitio donde el aire se calculaba distinto. */
/* Mismo criterio que .sidebar: se pega bajo el header medido, no a un número a
   ojo. Con los 90px que tenía, su borde superior quedaba METIDO en el header. */
.cart-summary { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 24px; box-shadow: var(--shadow-xs); position: sticky; top: calc(var(--header-h, 129px) + 16px); }
.cart-summary :is(h2, h3, h4) { font-family: var(--serif); font-size: var(--t-block); color: var(--ink); margin: 0 0 16px; }
/* El :hover NO se repite aquí: el elemento YA es `.btn-primary` (cart.js lo pinta
   con las dos clases) y `.btn-primary:hover:not(:disabled)` ya pone el vino. */
.cart-checkout { width: 100%; margin-top: 16px; }
.cart-note { font-size: var(--t-small); color: var(--muted); text-align: center; margin: 12px 0 0; }
.cart-empty { text-align: center; padding: 48px 20px; display: flex; flex-direction: column; align-items: center; gap: 18px; }
.cart-empty p { color: var(--muted); font-size: var(--t-strong); margin: 0; }

/* Aviso flotante "Agregado al carrito" */
.cart-toast {
  position: fixed; left: 50%; bottom: 28px; z-index: 2000;
  transform: translateX(-50%) translateY(20px);
  background: var(--ink); color: #fff; padding: 12px 20px; border-radius: var(--r-pill);
  font-size: var(--t-ui); font-weight: 600; box-shadow: var(--shadow);
  opacity: 0; pointer-events: none; transition: opacity .25s ease, transform .25s ease;
}
.cart-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
/* El "Deshacer" del aviso (27 jul 2026). Ámbar sobre el pizarra del aviso: son 6.1:1,
   la misma pareja medida de la cinta "Pesaj" — el ámbar vale sobre fondo OSCURO, que es
   la regla real ([[web-sinai-marca]]). Va SUBRAYADO además del color, para que no
   dependa solo de él.
   ⚠️ `pointer-events: auto` sube a `.cart-toast.show`, no aquí: el aviso entero está en
   `none` para no robar toques cuando es solo texto, y un hijo no puede recuperarlos si
   su padre no los tiene. Con el botón dentro, el aviso SÍ es interactivo mientras se ve. */
.cart-toast-accion {
  margin-left: 14px; border: none; background: none; padding: 6px 2px; margin-top: -6px; margin-bottom: -6px;
  font: inherit; font-weight: 800; color: var(--amber); text-decoration: underline;
  text-underline-offset: 3px; cursor: pointer;
}
.cart-toast-accion:hover { color: #fff; }

/* Aviso de cambios de disponibilidad del carrito */
/* `height: 100dvh` por el mismo motivo que los dos cajones (ver `.sidebar`): con
   solo `inset: 0`, en iOS el overlay se centra contra el viewport GRANDE y el
   diálogo queda más bajo de lo que se ve — con los botones debajo de la barra de
   Safari. El `overflow-y: auto` es para el caso de un diálogo más alto que la
   pantalla: sin él, lo que sobra no se alcanza. */
.cart-modal-overlay { position: fixed; inset: 0; height: 100vh; height: 100dvh; overflow-y: auto; z-index: 2100; background: rgba(var(--ink-rgb),.5); display: flex; align-items: center; justify-content: center; padding: 20px; }
.cart-modal { background: var(--card); border-radius: var(--r); max-width: 460px; width: 100%; padding: 36px; box-shadow: var(--shadow); }
.cart-modal :is(h2, h3, h4) { font-family: var(--serif); font-size: var(--t-section); color: var(--ink); margin: 0 0 14px; }
.cart-modal > p { font-size: var(--t-body); color: var(--muted); margin: 0 0 26px; line-height: var(--lh-body); }
.cart-modal-list { list-style: none; margin: 0 0 26px; padding: 0; max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 10px; }
.cart-modal-list li { font-size: var(--t-ui); color: var(--ink); padding: 12px 14px; background: var(--line-soft); border-radius: 10px; }
.cart-modal-list li em { font-style: normal; font-weight: 600; color: var(--brand); text-transform: none; }
/* Los botones del diálogo, en fila y a partes iguales. Con UNO solo (el aviso de
   disponibilidad) el `flex: 1` le da el mismo ancho completo que tenía antes, así
   que ese diálogo no cambia de aspecto. */
.cart-modal-acciones { display: flex; gap: 14px; }
.cart-modal-ok { flex: 1; text-align: center; border: none; font-family: inherit; cursor: pointer; }
/* `.btn-ghost` nació para enlaces (`display: inline-block`); aquí es un <button>
   de verdad y necesita su borde y su fondo, que ya trae. Solo se recupera el
   puntero y la fuente, que un <button> no hereda. */
@media (max-width: 420px) {
  /* En pantallas estrechas «Cancelar» y el botón de acción no caben lado a lado
     sin partir el texto: se apilan, con la acción arriba. */
  .cart-modal-acciones { flex-direction: column-reverse; }
}
/* Móvil: el aviso un punto más chico que en escritorio, donde se pidió grande (23 jul).
   Los botones NO cambian (petición suya: "botones no tanto más"). */
@media (max-width: 720px) {
  .cart-modal { max-width: 420px; padding: 30px; }
  .cart-modal :is(h2, h3, h4) { font-size: var(--t-block); }
  .cart-modal > p { font-size: var(--t-strong); }
}

/* ============================================================
   Checkout
   ============================================================ */
.checkout-layout { display: grid; grid-template-columns: 1fr 340px; gap: 30px; align-items: start; }
.checkout-forms { display: flex; flex-direction: column; gap: 18px; }
.checkout-block { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 24px 26px; box-shadow: var(--shadow-xs); }
/* El primer bloque no lleva el margen global de <section> (así arranca a la
   misma altura que el resumen de la derecha). Entre bloques sí queda separación. */
.checkout-forms > .checkout-block:first-child { margin-top: 0; }
.checkout-step { display: flex; align-items: center; gap: 12px; font-family: var(--serif); font-size: var(--t-block); color: var(--ink); margin: 0 0 18px; }
/* La caja va en `em`, o sea RELATIVA a su propia letra, no en px (27 jul 2026). Con
   "Tamaño de fuente: Grande" del navegador —o al ampliar al 200%, que es lo que exige
   WCAG 1.4.4— la letra crece porque la escala está en `rem` y el círculo se quedaba
   clavado en 28px: el número se salía. 2em = los mismos 28px de hoy con `--t-ui` en 14.
   Efecto de paso, y es correcto: en el teléfono `--t-ui` baja a 13, así que el círculo
   baja a 26 y sigue siendo proporcional a su número. */
.step-num { display: inline-flex; align-items: center; justify-content: center; width: 2em; height: 2em; border-radius: 50%; background: var(--ink); color: #fff; font-family: var(--sans, inherit); font-size: var(--t-ui); font-weight: 700; }
.radio-row { display: flex; align-items: center; gap: 12px; padding: 13px 14px; border: 1px solid var(--line); border-radius: 12px; font-size: var(--t-ui); font-weight: 600; color: var(--text); cursor: pointer; margin-bottom: 10px; transition: border-color .15s; }
.radio-row:hover { border-color: var(--brand-tint); }
.radio-row input { accent-color: var(--brand); }
.radio-sub { color: var(--muted); font-weight: 400; }
.field-hint { font-size: var(--t-small); color: var(--muted); margin: 12px 2px 0; line-height: var(--lh-body); }

/* Contenido enriquecido traído de la API (aviso de privacidad) */
.rich-content h1, .rich-content h2, .rich-content h3, .rich-content h4 { color: var(--ink); margin: 22px 0 8px; line-height: var(--lh-heading); }
.rich-content p { margin: 0 0 12px; line-height: var(--lh-body); }
.rich-content ul, .rich-content ol { margin: 0 0 14px; padding-left: 20px; line-height: var(--lh-body); }
.rich-content a { color: var(--brand); }

/* Preguntas frecuentes (acordeón) */
.faq-list { max-width: 720px; margin: 0 auto; }
.faq-loading, .faq-empty { color: var(--muted); font-size: var(--t-ui); }
.faq-item { border-bottom: 1px solid var(--line); }
.faq-item summary { list-style: none; cursor: pointer; padding: 18px 40px 18px 0; position: relative; font-weight: 600; font-size: var(--t-body); color: var(--ink); }
.faq-item summary::-webkit-details-marker { display: none; }
/* El +/− llevaba `transition: transform .18s` que NO se ejecutó nunca: lo que
   cambiaba era el `content`, y eso no se transiciona. Se pasa al chevron que el
   sitio YA usa dos veces —`.m-chev` gira 90° y `.fm-chev` 180°— para que la
   transición sea de verdad y el gesto sea el mismo en todo el sitio. 25 jul 2026. */
.faq-item summary::after { content: '\203A'; position: absolute; right: 6px; top: 50%; transform: translateY(-50%); font-size: var(--i-3xl); font-weight: 400; color: var(--brand); transition: transform var(--dur) var(--ease-out); }
.faq-item[open] summary::after { transform: translateY(-50%) rotate(90deg); }
.faq-a { padding: 0 0 18px; color: var(--text); font-size: var(--t-strong); line-height: var(--lh-body); white-space: pre-line; }
.co-line { display: flex; justify-content: space-between; gap: 12px; font-size: var(--t-ui); color: var(--muted); padding: 6px 0; border-bottom: 1px dashed var(--line-soft); }
.co-line em { font-style: normal; color: var(--muted); }   /* --faint (2.62:1) → --muted el 27 jul 2026: "ya no está disponible", en el pago */
.checkout-summary .sum-row:first-of-type { margin-top: 10px; }

/* ---- Checkout conectado (checkout.js) ---- */
/* Aquí NO van esqueletos: el formulario real ya está en el HTML (checkout.php) y
   `data-loading` solo lo atenúa hasta que checkout.js lo hidrata
   (checkout.js:1053 `removeAttribute`). Lo que faltaba era el puente de ese
   .55 → 1, que saltaba seco. 25 jul 2026. */
#co-root { transition: opacity var(--dur) var(--ease-out); }
#co-root[data-loading] { opacity: .55; pointer-events: none; }

/* Domicilio / tienda: mismo par de botones que el segmento de la app */
.co-segment { display: inline-flex; padding: 4px; background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-pill); margin-bottom: 18px; }
.co-seg-btn { border: 0; background: none; font: inherit; font-size: var(--t-ui); font-weight: 600; color: var(--muted); padding: 9px 20px; border-radius: var(--r-pill); cursor: pointer; transition: background .15s, color .15s; }
.co-seg-btn:hover { color: var(--ink); }
.co-seg-btn.is-on { background: var(--brand); color: #fff; } /* gemelo de `.variant-btn.on` */

/* Direcciones — cuadrícula de 2 en escritorio, 1 en el teléfono (decisión suya,
   22 jul 2026). El bloque del checkout mide ~846px de contenido: con un mínimo de
   300px caben 2 columnas y no 3 (la reja de /direcciones usa 240 y da 3 — aquí NO).
   `min(100%, 300px)` evita que se desborde en pantallas muy angostas: ahí cae a 1. */
.co-addr-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 10px; align-items: start; }
.co-addr { display: flex; align-items: flex-start; gap: 12px; padding: 14px; border: 1px solid var(--line); border-radius: 12px; cursor: pointer; transition: border-color .15s; }
.co-addr:hover { border-color: var(--brand-tint); }
.co-addr:has(input:checked) { border-color: var(--brand); background: var(--brand-soft); }
.co-addr input { accent-color: var(--brand); margin-top: 3px; }
.co-addr-body { display: flex; flex-direction: column; gap: 2px; font-size: var(--t-ui); color: var(--muted); }
.co-addr-body strong { font-size: var(--t-strong); color: var(--ink); font-weight: 700; }
.co-addr-body .addr-tag { margin: 0 0 0 6px; vertical-align: 1px; }
.co-addr-cost { color: var(--text); font-weight: 600; margin-top: 4px; }
/* Sin costo de envío = la app la rechaza al pedir. Se ve, pero se avisa. */
.co-addr-bad { opacity: .75; }
.co-addr-warn { color: var(--brand); font-weight: 600; margin-top: 4px; }
.co-addr-add { display: inline-block; margin-top: 14px; font-size: var(--t-ui); }

/* Sucursal (recoger) */
.co-store { padding: 16px 18px; border: 1px solid var(--line); border-radius: 12px; background: var(--bg); }
/* Nombre y "Sin costo" en la misma fila: el dato de que no se cobra va con el
   título, no colgando al final del bloque. */
.co-store-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.co-store strong { font-size: var(--t-strong); color: var(--ink); }
.co-store-addr { margin: 4px 0 0; font-size: var(--t-ui); color: var(--muted); line-height: var(--lh-body); }
/* Rótulo del horario: dice que es el de RECOGER, no el de la tienda (que es más
   amplio y vive en /contacto). Sin él, los números se leen como el horario de la
   sucursal y no cuadran con lo que dice la página de contacto. */
.co-store-label { margin: 12px 0 5px; font-size: var(--t-label); font-weight: 700; text-transform: uppercase; color: var(--muted); }
/* Los horarios, en dos columnas alineadas. Antes era UNA línea con puntos ("Lun–Jue
   10:30–17:45 · Vie…") y no se leía: había que buscar el día dentro del renglón. */
.co-store-hours { display: grid; grid-template-columns: max-content auto; gap: 3px 14px; margin: 0; font-size: var(--t-small); }
.co-store-hours dt { color: var(--muted); }
/* tabular-nums: alinea las horas en columna aunque cambien los dígitos. */
.co-store-hours dd { margin: 0; color: var(--text); font-variant-numeric: tabular-nums; }
.co-free { flex: none; font-size: var(--t-small); font-weight: 700; color: var(--ok); background: var(--ok-soft); padding: 3px 10px; border-radius: var(--r-pill); }   /* era #1f7a45 sobre #e4f4ea: un tercer verde y un segundo fondo, a 8 y 2 de los otros. Nadie eligió eso. */

/* Fechas: fichas de calendario, todas de la misma forma (día · número · mes).
   "Hoy" solo cambia el rótulo de arriba; si cambiara el formato, se lee raro.
   El número de fechas lo decide el PANEL (`rangoEntrega`): hoy 6, pero puede subir
   a 30. Por eso la reja se acota en alto y hace scroll dentro de sí misma en vez de
   empujar el resto de la página (con 30 serían 4 filas en escritorio y 8 en móvil).
   `auto` = si caben, no aparece scroll ni se nota. */
.co-dates { display: flex; flex-wrap: wrap; gap: 8px; max-height: 232px; overflow-y: auto; padding-right: 2px; }
/* Barra discreta, para que no compita con las fichas */
.co-dates::-webkit-scrollbar { width: 6px; }
.co-dates::-webkit-scrollbar-thumb { background: var(--line); border-radius: 999px; }
.co-dates::-webkit-scrollbar-track { background: transparent; }
.co-date { display: flex; flex-direction: column; align-items: center; gap: 0; width: 64px; padding: 9px 4px 8px; border: 1px solid var(--line); border-radius: 12px; background: var(--card); cursor: pointer; font: inherit; line-height: 1.15; transition: border-color .15s, background .15s; }
.co-date:hover { border-color: var(--brand-tint); }
.co-date.is-on { border-color: var(--brand); background: var(--brand); }
.co-date.is-on .co-date-dow, .co-date.is-on .co-date-num, .co-date.is-on .co-date-mon { color: #fff; }
.co-date-dow { font-size: var(--t-label); font-weight: 700; color: var(--muted); text-transform: uppercase; }
.co-date-num { font-size: var(--t-block); font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; margin: 1px 0; }
.co-date-mon { font-size: var(--t-label); color: var(--muted); text-transform: uppercase; }
/* "Hoy" en tinta de marca para que se distinga sin romper la forma */
.co-date-hoy .co-date-dow { color: var(--brand); }
.co-date-hoy.is-on .co-date-dow { color: #fff; }
/* Fecha que la anticipación deja fuera: se muestra, no se esconde, para que se
   entienda por qué el calendario no empieza hoy. Borde punteado + número tachado.
   `.co-date.is-off` (0,2,0) le gana a `.co-date` (0,1,0), y el :hover propio le
   gana a `.co-date:hover`; sin eso el borde se pintaría de vino al pasar encima. */
.co-date.is-off { border-style: dashed; background: var(--bg); cursor: not-allowed; }
.co-date.is-off:hover { border-color: var(--line); }
.co-date.is-off .co-date-dow, .co-date.is-off .co-date-mon { color: var(--faint); }
/* El número se queda en `--muted` (5.1:1), no en `--faint` (2.3:1): la fecha tiene
   que poder LEERSE aunque no se pueda elegir. Lo de "no disponible" lo dicen el
   tachado, el borde punteado y el cursor, no el desvanecerla hasta no verse. */
.co-date.is-off .co-date-num { color: var(--muted); text-decoration: line-through; }
/* Aviso de la sección de fecha. Cuando hay fichas en gris, explica POR QUÉ, así que
   pesa más que una nota al pie (`.field-hint`, que se sigue usando cuando hoy sí se
   alcanza y no hay nada que explicar). Sin margen de `p` del navegador: no hay reset. */
.co-date-note {
  display: flex; flex-direction: column; gap: 4px;
  margin: 14px 0 0; padding: 12px 14px;
  border-radius: 10px;
  font-size: var(--t-ui); line-height: var(--lh-body);
}
.co-date-note-title { font-size: var(--t-label); font-weight: 700; text-transform: uppercase; }
.co-date-note-prod { font-weight: 700; }
/* Por encargo → ámbar: misma señal que `.encargo-tag` en tienda y `.cart-item-ant`.
   La señal la lleva el FONDO (--amber-soft), no un borde lateral (17 jul: se quitó el
   `border-left: 3px`, que es un anti-patrón de impeccable). */
.co-date-note.is-encargo { background: var(--amber-soft); color: var(--amber-ink); }
.co-date-note.is-encargo .co-date-note-title { color: var(--amber-ink); }
.co-date-note.is-encargo strong, .co-date-note.is-encargo .co-date-note-prod { color: var(--amber-ink-deep); }
/* Cierre / hora de corte → pizarra, NO ámbar: el ámbar significa "por encargo" y
   reutilizarlo aquí mezclaría dos señales que no son lo mismo. El fondo (--line-soft)
   distingue esta nota; sin borde lateral. */
.co-date-note.is-cierre { background: var(--line-soft); color: var(--text); }
.co-date-note.is-cierre .co-date-note-title { color: var(--muted); }
.co-date-note.is-cierre strong { color: var(--ink); }

/* Comentario */
.co-comment-label textarea { width: 100%; border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; font: inherit; font-size: var(--t-field); color: var(--text); resize: vertical; }
.co-comment-label textarea:focus { outline: none; border-color: var(--brand-tint); }   /* es un CAMPO: va en --brand-tint */

/* Cupón */
.co-coupon { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line-soft); }
.co-coupon label { display: block; font-size: var(--t-small); font-weight: 700; color: var(--muted); text-transform: uppercase; margin-bottom: 7px; }
.co-coupon select { width: 100%; border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; font: inherit; font-size: var(--t-ui); color: var(--text); background: var(--card); cursor: pointer; }
.co-coupon select:disabled { color: var(--faint); cursor: default; }
.co-coupon-note { font-size: var(--t-small); color: var(--muted); line-height: var(--lh-body); margin: 8px 2px 0; }

/* Mismo aspecto que .cart-checkout, pero con su propia clase: la otra la
   intercepta cart.js (ver checkout.php). */
/* Igual que .cart-checkout: el botón ya es `.btn-primary` (checkout.php), así que
   su :hover ya está puesto. El :disabled SÍ se queda: baja a .5, no a .55. */
.co-pay { width: 100%; margin-top: 16px; }
.co-pay:disabled { opacity: .5; cursor: not-allowed; }

/* ---- Buscador de direcciones (Google Places · maps.js) ---- */
/* Se destaca del resto del formulario porque es el camino recomendado: al elegir
   de la lista llegan las COORDENADAS, y sin ellas el servidor puede no calcular
   el costo de envío (= "sin cobertura" en el checkout). */
.addr-search { position: relative; background: var(--bg); border: 1px solid var(--line); border-radius: 12px; padding: 14px 16px 12px; margin-bottom: 20px; }
.addr-search label { font-weight: 700; }
.addr-search-hint { font-size: var(--t-small); color: var(--muted); margin: 8px 2px 0; line-height: var(--lh-ui); }
.addr-search-warn { color: var(--brand); font-weight: 600; }
.addr-predictions { list-style: none; margin: 4px 0 0; padding: 6px; position: absolute; left: 16px; right: 16px; z-index: 20; background: var(--card); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow-sm, 0 10px 30px rgba(var(--ink-rgb),.12)); max-height: 260px; overflow-y: auto; }
.addr-pred { padding: 10px 12px; font-size: var(--t-ui); color: var(--text); border-radius: 8px; cursor: pointer; line-height: var(--lh-ui); }
/* La resaltada con las flechas se ve IGUAL que la que tiene el ratón encima: es el
   mismo estado ("esta es la que se elige"), contado por dos caminos distintos. El
   foco real nunca sale del campo (patrón combobox), así que sin esto quien navega
   con teclado no vería dónde está. */
.addr-pred:hover, .addr-pred.activo { background: var(--brand-soft); color: var(--ink); }

.co-err { color: var(--brand); font-weight: 600; }
/* Mientras el pedido va por la red: es un estado, no una alarma. */
.co-wait { color: var(--muted); font-weight: 600; }
/* El pedido pudo quedar registrado y no lo sabemos. NO es un error (no se pinta
   de vino): es un "ve a comprobarlo", y por eso pesa más que un aviso normal.
   Mismo tono pizarra que el aviso de cierre del calendario (.is-cierre). */
.co-duda {
  display: block; text-align: left; font-weight: 600; color: var(--text);
  background: var(--line-soft);
  border-radius: 8px; padding: 9px 11px; line-height: var(--lh-ui);
}
.co-duda a { color: var(--ink); font-weight: 700; }
.co-min-note { font-size: var(--t-small); color: var(--brand); background: var(--brand-soft); border-radius: 10px; padding: 8px 11px; margin: 12px 0 0; line-height: var(--lh-ui); }
/* Envío aún desconocido en el carrito (depende de la dirección) */
.sum-pend { color: var(--muted); font-size: var(--t-small); }

/* ============================================================
   Confirmación de pedido
   ============================================================ */
/* El envoltorio NO lleva tope de ancho ni `text-align: center`: dentro va
   `#order-detail`, que es el MISMO detalle de dos columnas (1fr 320px) de /pedido.
   Encerrado en 520px centrados, la columna del resumen se estrujaba contra los
   productos y todo salía centrado por herencia — cosas pegadas y movidas.
   Lo que se centra es el SALUDO, en su propio bloque. */
.confirm-wrap { margin: 30px auto 70px; }
.confirm-hero { max-width: 520px; margin: 0 auto 26px; text-align: center; }
/* Caja en `em` por lo mismo que los otros tres: 1.947em = los 74px de hoy con --i-hero. */
.confirm-icon { width: 1.947em; height: 1.947em; margin: 0 auto 20px; border-radius: 50%; background: var(--ok-soft); color: var(--ok); font-size: var(--i-hero); display: flex; align-items: center; justify-content: center; }
.confirm-title { font-family: var(--serif); font-weight: 600; font-size: var(--t-page); color: var(--ink); margin: 0 0 10px; }
.confirm-sub { color: var(--muted); font-size: var(--t-strong); line-height: var(--lh-body); margin: 0; }
.confirm-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
/* Entrada del "¡Gracias por tu pedido!" (25 jul 2026). Es el ÚNICO sitio del
   sitio donde el presupuesto de deleite aplica: se ve una vez por pedido. Un
   @keyframes se dispara solo al pintarse el HTML (el servidor lo manda entero)
   → cero JS, igual que el materialize del modal del carrito.
   SIN rebote a propósito: no hay ninguno en todo el sitio y uno solo se leería
   como plantilla. Los botones NO entran: son la salida, no esperan.
   `both` deja el estado inicial fijado durante el retardo; con
   `prefers-reduced-motion` la animación se apaga entera y todo sale a opacidad 1. */
@keyframes confirm-icon-in { from { opacity: 0; transform: scale(.92); } to { opacity: 1; transform: scale(1); } }
@keyframes confirm-text-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
.confirm-icon  { animation: confirm-icon-in var(--dur-lg) var(--ease-out) both; }
.confirm-title { animation: confirm-text-in var(--dur) var(--ease-out) 80ms both; }
.confirm-sub   { animation: confirm-text-in var(--dur) var(--ease-out) 140ms both; }
.btn-ghost { display: inline-block; text-decoration: none; border: 1px solid var(--line); background: var(--card); color: var(--ink); border-radius: var(--r-pill); padding: 14px 22px; font-size: var(--t-ui); font-weight: 600; transition: border-color var(--dur-sm), color var(--dur-sm); }
.btn-ghost:hover { border-color: var(--brand); color: var(--brand); }

/* ============================================================
   404 — Página no encontrada
   ============================================================ */
.notfound { max-width: 620px; margin: 0 auto; padding: 4px 20px 56px; text-align: center; display: flex; flex-direction: column; align-items: center; }
.notfound-actions { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.notfound-links { margin-top: 30px; padding-top: 22px; border-top: 1px solid var(--line); display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: center; justify-content: center; font-size: var(--t-ui); }
.notfound-links span { color: var(--faint); }
.notfound-links a { color: var(--brand); font-weight: 600; transition: opacity .15s; }
.notfound-links a:hover { opacity: .65; }

/* ============================================================
   Favoritos DEL USUARIO
   (el ♥ que marca cada quien; no confundir con el flag global de la casa,
    que solo arma el carrusel "Favoritos" del inicio)
   ============================================================ */
/* Botón ♥. Flota arriba a la derecha de la imagen: sirve igual dentro de la
   tarjeta del catálogo (.card-media) y de la tarjeta de esta página
   (.fav-media), porque las dos son position: relative. */
/* El círculo se queda en 32px EXACTOS, en el mismo sitio de siempre, y la zona la
   pone un ::after invisible — mismo patrón que .card-add y .qty-btn.
   ⚠️ NO usar `width:44 + padding + background-clip:content-box` aquí: recorta el
   FONDO a 32, pero el `border` y el `box-shadow` se siguen dibujando en el borde
   exterior → saldría un círculo de 44. Se probó y se descartó.
   Hace falta porque el ♥ vive en la esquina de otro objetivo (la tarjeta entera abre
   la ficha) → hoy se acaba abriendo el producto en vez de guardarlo. */
.fav-heart {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  width: 32px; height: 32px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 50%;
  background: rgba(255, 255, 255, .94); color: var(--brand);
  font-size: var(--i-md); line-height: 1; cursor: pointer;
  box-shadow: var(--shadow-xs);
  transition: transform .15s, background .15s, color .15s, border-color .15s;
}
.fav-heart::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
.fav-heart:hover { transform: scale(1.12); border-color: var(--brand); }
.fav-heart.is-fav { background: var(--brand); border-color: var(--brand); color: #fff; }
.fav-heart .fav-ico { pointer-events: none; }
/* En la TARJETA el ♥ se separa de la esquina lo justo para despejar el radio del
   `.card` (`--r-sm` + 1 del borde): con `overflow:hidden` + `border-radius`, Safari
   recorta a la curva más que Chrome y le comía un pelo al círculo (22 jul). La FICHA
   NO lo necesita —su ♥ cuelga de `.product-gallery`, sin overflow (ver abajo)—, por
   eso se acota a `.card-media` y queda a la misma altura que `.badge-feat`. */
.card-media > .fav-heart { top: calc(var(--r-sm) + 1px); right: calc(var(--r-sm) + 1px); }
/* En la ficha el ♥ flota sobre la FOTO, igual que en las tarjetas (20 jul 2026,
   decisión suya). Antes iba con la palabra "Guardar" en la columna de texto y se
   comía un renglón entero justo donde falta sitio: lo que sube al quitarlo es el
   precio y el botón de comprar.
   ⚠️ Se ancla a `.product-gallery` y NO a `.gallery-main`: esa tiene
   `overflow: hidden` (recorta la foto a la curva) y le cortaría la zona táctil de
   44px justo en la esquina, que es donde vive.
   33px y no 32: uno más que en la tarjeta, porque aquí la foto es el doble de
   grande y el mismo círculo se veía chico. Lo eligió él. */
.product-gallery > .fav-heart { width: 33px; height: 33px; }

/* Página /favoritos */
/* ⚠️ El HUECO no está aquí a propósito: se declara con `.grid` (busca "EL HUECO ENTRE
   TARJETAS"), en una sola regla compartida, para que no puedan separarse otra vez —
   ya lo hicieron (18 vs 26, sin que nadie lo decidiera). Aquí solo va lo que de
   verdad es distinto: el `minmax`, más chico porque esta reja lleva `.fav-card`, el
   diseño VIEJO. Y por eso en móvil favoritos se queda a UNA columna donde la tienda
   pone dos: 🚫 revisado con él el 16 jul y **está bien así**, no es un pendiente.
   (Igualar la TARJETA entera sí está pendiente, y no es CSS: los datos de favoritos
   no traen `promo` ni `precioPieza`, así que la tarjeta de la tienda enseñaría un
   precio sin descuento. Ver la nota en el CHECKLIST.) */
.fav-grid { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.fav-card { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); overflow: hidden; box-shadow: var(--shadow-xs); transition: transform .18s, box-shadow .18s; }
.fav-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-sm); }
.fav-media { position: relative; aspect-ratio: 1/1; background: var(--card); }
.fav-media-link { display: block; width: 100%; height: 100%; }
.fav-media-link img { width: 100%; height: 100%; object-fit: contain; display: block; padding: 10px; }
.fav-body { padding: 14px 15px 16px; display: flex; flex-direction: column; gap: 5px; }
.fav-name-link { text-decoration: none; color: inherit; }
.fav-add {
  display: block; width: 100%; margin-top: 8px; text-align: center; text-decoration: none;
  border: 1.5px solid var(--brand); background: none; color: var(--brand);
  border-radius: var(--r-pill); padding: 9px; font-family: inherit; font-size: var(--t-ui); font-weight: 600;
  cursor: pointer; transition: background .15s, color .15s;
}
/* Los estados viven arriba, junto a .card-add (mismo botón fantasma). */
/* Agotado: mismo trato que en la tarjeta del catálogo. */
.fav-card.sold-out .fav-media-link img { opacity: .45; filter: grayscale(.4); }
.fav-card.sold-out .price { opacity: .6; }

/* ============================================================
   Direcciones + métodos de pago
   ============================================================ */
.addr-grid, .pay-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--gap-reja); }
.addr-card { position: relative; background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: 20px; }
.addr-default { border-color: var(--brand); }   /* elección HECHA, no hover: va en --brand como .co-addr:has(input:checked) */
.addr-tag { display: inline-block; font-size: var(--t-label); font-weight: 700; color: var(--brand); background: var(--brand-soft); padding: 3px 9px; border-radius: 999px; margin-bottom: 8px; }
.addr-card strong { display: block; font-size: var(--t-strong); color: var(--ink); margin-bottom: 6px; }
.addr-card p { font-size: var(--t-ui); color: var(--muted); margin: 0 0 4px; line-height: var(--lh-ui); }
.addr-phone { font-weight: 600; }
.addr-actions { display: flex; gap: 14px; margin-top: 12px; }
.link-danger { color: var(--muted); }
.link-danger:hover { color: var(--brand); }   /* era #b3261e, el rojo de Material Design, ajeno a la paleta */
.addr-add { border: 1.5px dashed var(--line); background: var(--bg); border-radius: var(--r); padding: 20px; font-family: inherit; font-size: var(--t-ui); font-weight: 600; color: var(--muted); cursor: pointer; transition: border-color var(--dur-sm), color var(--dur-sm); }
.addr-add:hover { border-color: var(--brand); color: var(--brand); }
#addr-add-btn { display: block; width: 100%; margin-top: 18px; }

/* Formulario de alta/edición de dirección */
.addr-form-wrap { margin-top: 26px; border-top: 1px solid var(--line-soft); padding-top: 6px; }
.addr-form { max-width: 560px; }
/* Los campos van en su propia caja (se enseñan al elegir la dirección), y esa caja
   es UN hijo del formulario: el espacio entre campos lo pone ella. */
.addr-fields { display: flex; flex-direction: column; gap: 16px; }
/* Paso 1: solo el buscador. Se guardan las dos cosas que no tienen sentido todavía
   —los campos y el botón de guardar—; **Cancelar se queda**, o no habría forma de
   salir del alta. Lo lleva un atributo del formulario y no `hidden` en cada trozo:
   así el estado vive en UN sitio y no puede quedar a medias. */
.addr-form[data-paso="buscar"] .addr-fields,
.addr-form[data-paso="buscar"] .addr-form-actions .btn-primary { display: none; }
/* Y la salida de emergencia sobra en cuanto el formulario ya está abierto. */
.addr-form[data-paso="corregir"] .addr-manual { display: none; }
.addr-manual { display: inline-block; margin-top: 12px; font-size: var(--t-ui); }
/* El asterisco de obligatorio: en vino, que es el color con el que el sitio marca
   lo que reclama atención. `aria-hidden` en el HTML — a un lector de pantalla se lo
   dice el `required` del campo, y "asterisco" en voz alta no ayuda a nadie. */
.req { color: var(--brand); font-weight: 700; }
/* La casilla es la excepción: su rótulo va AL LADO, no encima. Vuelve a flex (los
   demás <label> son bloque) y el campo suelta el `display:block` y el margen que
   llevan los de escribir, o la casilla queda descolgada del texto.
   **Va con `.account-form` delante a propósito:** `.check-label` a secas PIERDE
   contra `.account-form label` —un selector con tipo pesa más que uno de clase
   suelta— y la casilla se salía a dos renglones. Lo cazó el usuario en la vista
   previa. */
.account-form .check-label { display: flex; flex-direction: row; align-items: center; gap: 10px; font-weight: 600; color: var(--ink); }
.account-form .check-label input { width: auto; margin: 0; padding: 0; border: 0; background: none; accent-color: var(--brand); }
.account-form .check-label input:focus { box-shadow: none; }
.addr-form-actions { display: flex; align-items: center; gap: 20px; margin-top: 4px; }
.addr-form-actions .btn-primary { width: auto; padding: 12px 26px; margin-top: 0; }
.pay-card { background: linear-gradient(135deg, var(--ink), var(--text)); color: #fff; border-radius: var(--r); padding: 22px; display: flex; flex-direction: column; gap: 14px; min-height: 140px; }
.pay-card-brand { font-size: var(--t-strong); font-weight: 800; }
.pay-card-num { font-size: var(--t-body); }
.pay-card-foot { display: flex; align-items: center; justify-content: space-between; margin-top: auto; font-size: var(--t-small); color: rgba(255,255,255,.75); }
.pay-card-foot .link-danger { color: rgba(255,255,255,.75); }
.pay-card-foot .link-danger:hover { color: #fff; }

@media (max-width: 920px) {
  .account-layout { grid-template-columns: 1fr; gap: 20px; }
  /* Se colapsa a 720 y no aquí: entre 720 y 920 el panel ya ocupa todo el ancho
     (la barra de cuenta se va arriba), así que las dos columnas siguen cabiendo
     holgadas. Bajarlo a una sola columna aquí solo alargaría la página. */
  .order-detail-grid { grid-template-columns: 1fr; }
  .cart-layout, .checkout-layout { grid-template-columns: 1fr; }
  .cart-summary { position: static; }
}
@media (max-width: 720px) {
  .form-row { grid-template-columns: 1fr; }
  /* Una sola columna: a esta anchura, dos columnas dejan los campos tan angostos
     que "Confirmar contraseña" ya no cabe en un renglón. Y va JUNTO a `.form-row`,
     que es la otra regla del mismo formulario: separadas, se tocaría una y no la
     otra. */
  .perfil-cols { grid-template-columns: 1fr; gap: 22px; }
}

/* ============================================================
   Barra de anuncios
   ============================================================ */
.announcement {
  background: var(--brand); color: #fff; text-align: center;
  font-size: var(--t-small); font-weight: 500; line-height: var(--lh-ui); padding: 9px 16px;
}

/* ============================================================
   Navegación (menú)
   ============================================================ */
.logo-link { display: block; flex: 0 0 auto; }
/* En escritorio `.logo-link` CRECE (`flex: 1 1 0`, @media 921 abajo) para centrar el menú
   en la PÁGINA; como es `<a href="/">`, esa zona vacía a la derecha del logo era un enlace
   GIGANTE invisible al inicio (lo vio él: "sale como que puedo hacer clic entre el logo y el
   menú"). Se restringe el clic a la imagen: el hueco de crecimiento deja de capturar el
   puntero, pero clicar la img sigue navegando (activa el <a>; `data-home` lo pilla por
   bubbling) y el teclado no se afecta (pointer-events no toca el foco). */
.logo-link { pointer-events: none; }
.logo-link .logo { pointer-events: auto; }

/* 44×44 y no 34×26: por debajo de 920px .header-nav se oculta y este botón es el
   ÚNICO acceso a Departamentos, Aplicación, Preparación, Nosotros y Contacto.
   El glifo sigue en 22px: NO cambia de aspecto, solo su área. */
.nav-toggle {
  display: none; border: none; background: none; font-size: var(--i-4xl); cursor: pointer;
  color: var(--ink); padding: 0; line-height: 1;
  width: 44px; height: 44px; align-items: center; justify-content: center;
}

/* Va DENTRO de la fila del logo desde el 18 jul 2026 (antes era una fila propia
   debajo, con su `max-width` y su `padding`; aquí ya está dentro del contenedor de
   la fila, así que no los necesita). `flex: 1 1 auto` es lo que lo deja centrado
   entre el logo y los iconos. */
.header-nav {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  flex: 1 1 auto; min-width: 0;
}

/* ⚠️⚠️ ESTE BLOQUE VA AQUÍ, AL FINAL DE LAS TRES REGLAS QUE SOBREESCRIBE, Y NO
   ARRIBA CON EL RESTO DE LA CABECERA. NO MOVERLO.
   Primero lo escribí arriba, junto a `.header-row`, y **no hacía absolutamente
   nada**: `@media` NO añade especificidad, así que con la misma especificidad gana
   el que va DESPUÉS — y `.header-actions { flex: 0 0 auto }`, `.logo-link { flex:
   0 0 auto }` y `.header-nav { flex: 1 1 auto }` van todas después. Se subió al
   servidor y el menú seguía descentrado igual; **lo cazó él mirando, no las
   pruebas**, porque las pruebas solo miraban que la regla ESTUVIERA escrita.
   Hoy `probar-accesibilidad.js` comprueba la POSICIÓN contra las tres.
   Con los @media de móvil no hay pelea: `min-width: 921` y `max-width: 920` nunca
   se aplican a la vez, así que da igual quién vaya antes.

   EL MENÚ VA CENTRADO EN LA PÁGINA, NO ENTRE SUS VECINOS (20 jul 2026, lo vio él:
   "siento que el menú no está centrado" — y lo estaba, pero del hueco, no de la
   página).
   El fallo: `.header-nav` tenía `flex: 1 1 auto` y se centraba dentro del espacio
   SOBRANTE, y los dos lados no miden lo mismo — logo 83 px contra 196 px del botón
   de cuenta + el carrito. Su centro caía **57 px a la izquierda** del centro real.
   Peor que los 57: el botón de la derecha dice "Iniciar sesión" o el NOMBRE del
   cliente, así que el menú **cambiaba de sitio al iniciar sesión** (30 px con un
   nombre corto, hasta 95 con uno largo).
   El arreglo: los dos lados con `flex: 1 1 0`, o sea reparto a partes iguales del
   espacio libre, y el menú a su tamaño. Con lados iguales, el centro del menú ES
   el centro de la página, mida lo que mida el nombre.
   ⚠️ `min-width` se queda en `auto` (el de por defecto) a propósito: con `0` los
   lados podrían encogerse por debajo de su contenido y aplastarían los iconos.
   ⚠️ Y solo de 921 para arriba: por debajo el menú se esconde tras el ☰ y el logo
   se centra en absoluto con sus propias reglas — ahí no hay que tocar nada. */
@media (min-width: 921px) {
  .logo-link, .header-actions { flex: 1 1 0; }
  /* El `margin-left: auto` de .header-actions sobra aquí (ya no hay espacio libre
     que absorber) y estorbaría al reparto; se apaga solo en este tramo, porque
     entre 720 y 920 SÍ hace falta —ahí el menú está oculto y sin él el logo y los
     iconos se apelotonan a la izquierda. */
  .header-actions { justify-content: flex-end; margin-left: 0; }
  .header-nav { flex: 0 0 auto; }
}
/* `white-space: nowrap` NO es cosmético: sin él, "Departamentos ▾" se parte en dos
   líneas en cuanto la fila se estrecha y la cabecera entera crece de alto — que es
   el "se rompe el menú" que él vio (18 jul 2026). */
.nav-link {
  border: none; background: none; cursor: pointer; font-family: inherit;
  font-size: var(--t-nav); font-weight: 600; color: var(--text); padding: 9px 17px; border-radius: var(--r-pill);
  white-space: nowrap; transition: color .15s, background .15s;
}
.nav-link:hover { color: var(--brand); background: var(--brand-soft); }
.nav-link.active { color: var(--brand); background: var(--brand-soft); font-weight: 700; }
.nav-dd { position: relative; }
.nav-dd-btn { display: inline-flex; align-items: center; gap: 6px; }
.caret { font-size: var(--i-xs); opacity: .7; }
.nav-dd-menu {
  position: absolute; top: 100%; left: 0; margin-top: 6px;
  /* Crece desde la esquina del botón, no de su propio centro. */
  transform-origin: top left; transform: translateY(-4px) scale(.97);
  background: var(--card); border: 1px solid var(--line); border-radius: var(--r); box-shadow: var(--shadow);
  padding: 8px;
  display: grid; grid-template-columns: repeat(2, minmax(190px, 1fr)); gap: 1px 16px;
  opacity: 0; visibility: hidden;
  /* SALIDA más corta que la entrada: 110ms (28 jul 2026, opción A elegida por él sobre
     un preview con cuatro tiempos — *"tarda mucho en cerrar y estorba un poco"*).
     ⚠️ Va en un token PROPIO y no en `--dur-sm`: ese lo comparten 18 componentes
     (botones, tarjetas, campos, la galería…) y tocarlo por el menú los aceleraría
     todos de rebote. Aquí la duración es del componente, no del sistema.
     La `visibility` espera al fundido para ocultar; si no, la salida no se ve — por eso
     su retraso tiene que seguir al MISMO valor, o el menú desaparece de golpe. */
  --dd-sale: 110ms;
  transition: opacity var(--dd-sale) var(--ease-out), transform var(--dd-sale) var(--ease-out), visibility 0s linear var(--dd-sale);
  z-index: 40;
}
/* Separador sutil entre las 2 columnas del menú de departamentos. */
.nav-dd-menu::after {
  content: ""; position: absolute; top: 10px; bottom: 10px; left: 50%; width: 1px;
  background: var(--line-soft); transform: translateX(-50%); pointer-events: none;
}
/* Puente invisible que cubre el hueco entre el botón y el menú (evita que se cierre al bajar el mouse). */
.nav-dd-menu::before { content: ""; position: absolute; left: 0; right: 0; top: -14px; height: 14px; }
.nav-dd.open .nav-dd-menu {
  opacity: 1; visibility: visible; transform: translateY(0) scale(1);
  transition: opacity var(--dur-sm) var(--ease-out), transform var(--dur-sm) var(--ease-out), visibility 0s;
}
.nav-dept-link {
  display: block; text-decoration: none;
  text-align: left; border: none; background: none; cursor: pointer; padding: 9px 12px; border-radius: 9px;
  font-size: var(--t-nav-sub); font-weight: 600; color: var(--text); font-family: inherit;
}
.nav-dept-link:hover { background: var(--line-soft); color: var(--brand); }

/* Menú móvil */
/* La `visibility` NO es decoración: es lo que lo saca del orden de tabulación.
   Cerrado, este cajón solo se apartaba con translateX → seguía siendo tabulable, y
   como esta regla NO está dentro de ningún @media, pasaba también en ESCRITORIO: el
   usuario de teclado pulsaba Tab tras el nav y el foco desaparecía ~10 paradas dentro
   de un menú invisible, en TODAS las páginas. El lector leía un menú duplicado.
   El `transition: visibility 0s linear var(--dur-lg)` retrasa el ocultado hasta que
   termina de deslizarse (si no, desaparecería de golpe). Mismo patrón que .mm-backdrop
   aquí abajo y que .nav-dd-menu. Si se toca el cierre, se toca la visibility. */
.mobile-menu {
  /* Mismo `100dvh` y mismo motivo que `.sidebar` (ver su comentario): con
     `bottom: 0`, en iOS el cajón se dimensiona contra el viewport grande y su
     parte de abajo se queda bajo la barra de Safari, sin scroll que la alcance.
     **Los dos cajones se tocan juntos**: comparten patrón y comparten fallo. */
  position: fixed; top: 0; left: 0; height: 100vh; height: 100dvh;
  width: 300px; max-width: 84vw; background: var(--card); z-index: 60;
  touch-action: pan-y;   /* scroll vertical nativo; el gesto horizontal lo toma el helper (site.js) */
  transform: translateX(-100%); visibility: hidden;
  transition: transform var(--dur-lg) var(--ease-drawer), visibility 0s linear var(--dur-lg);
  /* El aire de abajo respeta la franja del iPhone: la cuenta va anclada al borde
     inferior y sin esto quedaba debajo de la barra del gesto. */
  padding: 18px 18px calc(22px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto; overscroll-behavior: contain;
  /* `gap: 0` a propósito: los tres bloques (.mm-brand, .m-mid, .m-account) ponen
     su propia separación. El `gap: 1px` de antes venía de cuando esto era una
     lista plana de siete enlaces pegados — que es justo lo que se quitó. */
  display: flex; flex-direction: column; gap: 0;
}
.mobile-menu.open {
  transform: translateX(0); visibility: visible; box-shadow: var(--shadow-lg);
  transition: transform var(--dur-lg) var(--ease-drawer), visibility 0s;
}
/* Solo lo PROPIO: el resto lo pone la regla compartida con .sidebar-backdrop. */
.mm-backdrop { background: rgba(var(--ink-rgb),.25); z-index: 59; }
/* Mientras el dedo arrastra el cajón: SIN transición, sigue al dedo 1:1. Al soltar
   se quita `.dragging` y la transición ya probada remata el cierre o la vuelta
   (helper dragParaCerrar en site.js). Va fuera del @media a propósito: solo se
   añade en un arrastre activo, que solo ocurre con el cajón abierto (móvil). */
.mobile-menu.dragging, .sidebar.dragging,
.sidebar-backdrop.dragging, .mm-backdrop.dragging { transition: none; }
/* La cabecera de la barra de la tienda. Antes esta regla la compartía con
   `.mobile-menu-head` (eran dos copias y habían divergido solas, 22 vs 21px antes
   de los tokens); el 21 jul 2026 el menú móvil pasó a abrir con el LOGO en vez de
   con la palabra «Menú» —que era la más vacía en el sitio más visible— y se quedó
   sola. El ✕ sí sigue siendo el mismo en los dos cajones. */
.sidebar-head { align-items: center; justify-content: space-between; font-family: var(--serif); font-size: var(--t-block); font-weight: 600; color: var(--ink); }
#mobile-menu-close, .sidebar-close { border: none; background: var(--line-soft); width: 36px; height: 36px; border-radius: 50%; cursor: pointer; font-size: var(--i-md); }

/* ---- Las tres piezas del cajón: marca arriba, navegación al medio, cuenta abajo ----
   El reparto es de alto, y por eso es dinámico: en un teléfono alto el sobrante se
   va al medio en vez de acumularse al final, y la cuenta cae siempre donde alcanza
   el pulgar. Decisión suya (21 jul 2026), sobre preview y en tres alturas. */
.mm-brand { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; }
/* El relleno vertical es la zona táctil del logo (30 de dibujo + 7 y 7 = 44). No se
   agranda el dibujo: se agranda su zona, y aquí no le roba nada al ✕, que está al
   otro extremo de la fila. */
.mm-brand-logo { display: inline-flex; align-items: center; padding: 7px 0; }
.mm-brand-logo img { height: 30px; width: auto; display: block; }
/* ⚠️ `flex: 1 0 auto` y NO `1 1 auto`: al no encoger nunca por debajo de su
   contenido, el `center` no puede repartir hueco NEGATIVO. Con `1 1 auto`, en un
   teléfono bajo el centrado recorta el principio de la lista y —esto es lo malo—
   el scroll tampoco lo alcanza, porque lo que sobresale por arriba de un flex
   centrado es inalcanzable. Así, en pantalla alta reparte el sobrante y en
   pantalla baja simplemente crece y el cajón se desliza. */
.m-mid { flex: 1 0 auto; display: flex; flex-direction: column; justify-content: center; }
.m-group { display: flex; flex-direction: column; gap: 2px; }
.m-group + .m-group { margin-top: 22px; }
/* --muted y no --faint (27 jul 2026). Este NO estaba en la lista de la auditoría —que
   es del 14 jul y el menú móvil se rehizo el 21— y es el mismo fallo: son los rótulos
   que dicen de qué va cada grupo ("Comprar", "Ayuda"), o sea texto que se lee, a 11px
   y en mayúsculas, que es donde menos se perdona el contraste. */
.m-group-label { font-size: var(--t-tiny); font-weight: 700; text-transform: uppercase; color: var(--muted); margin: 0 0 6px; padding: 0 10px; }
.m-link { display: block; text-align: left; text-decoration: none; border: none; background: none; cursor: pointer; font-family: inherit; font-size: var(--t-strong); font-weight: 600; color: var(--ink); padding: 13px 10px; border-radius: 10px; }
.m-link:hover { background: var(--line-soft); }
.m-link.active { color: var(--brand); background: var(--brand-soft); }

/* Departamentos desplegable (nativo, sin JS) */
.m-acc { display: block; }
.m-acc-sum { display: flex; align-items: center; justify-content: space-between; list-style: none; cursor: pointer; font-size: var(--t-strong); font-weight: 600; color: var(--ink); padding: 13px 10px; border-radius: 10px; }
.m-acc-sum::-webkit-details-marker { display: none; }
.m-acc-sum:hover { background: var(--line-soft); }
.m-chev { color: var(--faint); font-size: var(--i-lg); transition: transform .2s; }
.m-acc[open] .m-chev { transform: rotate(90deg); }
.m-dept-list { display: flex; flex-direction: column; gap: 2px; padding: 2px 0 10px 8px; }

/* Sección de cuenta. `flex: 0 0 auto` para que no la estire ni la encoja el
   reparto de .m-mid: vive pegada abajo. */
.m-account { flex: 0 0 auto; margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line-soft); display: flex; flex-direction: column; gap: 10px; }
/* La fila de identidad. En el teléfono la barra esconde el nombre
   (.account-txt → display:none), así que este es el único sitio donde el cliente
   ve con qué cuenta está comprando. */
.m-ident { display: flex; align-items: center; gap: 11px; text-decoration: none; padding: 11px 10px; border-radius: 12px; }
.m-ident:hover { background: var(--line-soft); }
.m-ini { flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%; background: var(--brand); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-family: var(--serif); font-size: var(--t-strong); font-weight: 700; }
.m-ident-txt { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: var(--lh-heading); }
.m-ident-txt strong { font-size: var(--t-strong); color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.m-ident-txt small { font-size: var(--t-small); color: var(--muted); }
.m-ident .m-chev { flex: 0 0 auto; }
.m-mini { display: flex; justify-content: center; gap: 18px; }
/* ⚠️ `--t-nav` aquí es por el NÚMERO (14), no por el papel: al bajar todo el menú
   un escalón el 21 jul, 14 era el único token de 14 y ese token es el del menú de
   ESCRITORIO. Si algún día se retoca `--t-nav` para la barra de arriba, estos dos
   botones se mueven con ella — y no tienen nada que ver. Si pasa, se les da su
   propio token en la escala; no se escribe un 14 suelto aquí. */
.m-btn { display: block; text-align: center; padding: 12px; border-radius: var(--r-pill); font-weight: 600; font-size: var(--t-nav); text-decoration: none; transition: background var(--dur-sm), border-color var(--dur-sm), color var(--dur-sm), transform var(--dur-xs) var(--ease-out); }
.m-btn-primary { background: var(--brand); color: #fff; }
.m-btn-primary:hover { background: var(--brand-deep); }
.m-btn-outline { border: 1px solid var(--line); color: var(--ink); }
.m-btn-outline:hover { border-color: var(--brand); color: var(--brand); }
.m-link-sm { display: block; text-align: center; font-size: var(--t-small); font-weight: 600; color: var(--muted); text-decoration: none; padding: 6px; }
.m-link-sm:hover { color: var(--brand); }
/* El cajón del teléfono usa el MISMO token que el desplegable de escritorio: el submenú va
   un punto por debajo de su menú en las dos pantallas (26 jul 2026, suyo). Aquí solo cambia
   el relleno, que en el teléfono necesita más zona de toque. */
.m-dept-list .nav-dept-link { font-size: var(--t-nav-sub); padding: 11px 12px; }

/* ============================================================
   Footer completo
   ============================================================ */
footer { border-top: 1px solid var(--line); margin-top: 44px; background: var(--card); text-align: left; padding: 0; }
.footer-grid {
  max-width: var(--maxw); margin: 0 auto; padding: 40px var(--pad) 38px;
  display: grid; grid-template-columns: 1.7fr 1fr 1fr; gap: 40px 48px;
}
.footer-col :is(h2, h3, h4) { font-size: var(--t-small); font-weight: 700; text-transform: uppercase; color: var(--ink); margin: 0 0 18px; }
.footer-col ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
.footer-col li { font-size: var(--t-ui); color: var(--muted); line-height: var(--lh-body); }
.footer-link { display: inline-block; border: none; background: none; cursor: pointer; padding: 0; text-align: left; font-family: inherit; font-size: var(--t-ui); color: var(--muted); text-decoration: none; transition: color .15s; }
.footer-link:hover { color: var(--brand); }
.footer-brand .footer-logo { height: 46px; width: auto; margin-bottom: 18px; opacity: 1; }

/* Bloque de contacto bajo el logo (sucursal Chamizal) */
.footer-contact-info { font-style: normal; margin: 0 0 18px; }
.fci-addr { font-size: var(--t-ui); color: var(--muted); line-height: var(--lh-body); margin: 0 0 12px; max-width: 260px; }
.footer-phone { display: inline-flex; align-items: center; gap: 8px; font-size: var(--t-body); font-weight: 800; color: var(--ink); text-decoration: none; transition: color .15s; }
.footer-phone svg { color: var(--brand); flex: none; }
.footer-phone:hover { color: var(--brand); }

/* Redes: WhatsApp destacado + iconos secundarios */
.socials { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.social { width: 40px; height: 40px; border: 1px solid var(--line); border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; color: var(--text); transition: background var(--dur-sm), border-color var(--dur-sm), color var(--dur-sm); }
.social:hover { background: var(--brand); border-color: var(--brand); color: #fff; }
.social svg { width: 19px; height: 19px; }
.social-wa { width: auto; height: 40px; gap: 8px; padding: 0 16px; border: none; border-radius: var(--r-pill); background: #12833a; color: #fff; font-weight: 700; font-size: var(--t-ui); box-shadow: var(--shadow-xs); }
.social-wa svg { width: 18px; height: 18px; }
.social-wa:hover { background: #0f6f31; border: none; color: #fff; }

/* Departamentos: se muestran tantos como (enlaces de Información − 1) + "Ver más"
   que abre todos. El umbral (nth-child) lo genera el PHP del footer de forma
   dinámica, por eso aquí solo van los estilos base. */
.depts-cb { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.footer-more { display: none; align-items: center; gap: 5px; margin-top: 13px; padding: 0; background: none; border: none; cursor: pointer; font-family: inherit; font-size: var(--t-ui); font-weight: 700; color: var(--brand); transition: opacity .15s; }
.footer-more:hover { opacity: .7; }
.fm-txt::after { content: "Ver más"; }
.depts-cb:checked ~ .footer-more .fm-txt::after { content: "Ver menos"; }
.fm-chev { transition: transform .2s; }
.depts-cb:checked ~ .footer-more .fm-chev { transform: rotate(180deg); }
.depts-cb:focus-visible ~ .footer-more { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 4px; }

/* Barra inferior: copyright (año dinámico) a la izquierda, legales a la derecha */
.footer-bottom { border-top: 1px solid var(--line); }
.footer-bottom-inner { max-width: var(--maxw); margin: 0 auto; padding: 18px var(--pad); display: flex; align-items: center; justify-content: space-between; gap: 8px 18px; flex-wrap: wrap; }
.footer-legal { display: flex; align-items: center; gap: 8px 18px; flex-wrap: wrap; }
.footer-copy, .footer-legal a { font-size: var(--t-small); color: var(--muted); }
.footer-legal a { transition: color .15s; }
.footer-legal a:hover { color: var(--brand); }

/* ============================================================
   Página: Descarga la App
   ============================================================ */
.app-hero {
  display: grid; grid-template-columns: 1.05fr .95fr; align-items: center;
  gap: clamp(24px, 5vw, 64px); margin: 0 auto;
  padding: clamp(20px, 4vw, 44px) 0 clamp(8px, 2vw, 16px);
}
.app-hero-copy { max-width: 520px; }
.app-hero .page-title { text-align: left; }
.app-hero .page-sub { text-align: left; margin-bottom: 24px; }

.app-stores { display: flex; flex-wrap: wrap; gap: 12px; }
.app-store-img { display: inline-block; transition: transform .15s; }
.app-store-img:hover { transform: translateY(-2px); }
.app-store-img img { height: 52px; width: auto; display: block; border-radius: 10px; }

.app-hero-visual { position: relative; display: flex; justify-content: center; align-items: center; }
.app-hero-visual::before {
  content: ""; position: absolute; inset: -6% 0; z-index: 0;
  background: radial-gradient(58% 52% at 50% 44%, var(--brand-soft), transparent 72%),
              radial-gradient(42% 40% at 72% 78%, var(--amber-soft), transparent 72%);
}
.app-hero-visual img {
  position: relative; z-index: 1; width: 100%; max-width: 320px; height: auto; display: block;
  filter: drop-shadow(0 28px 60px rgba(var(--ink-rgb),.24));
}
/* Versión compacta (home): contenida tipo banner, sin QR y con teléfono más chico.
   La versión completa (grande) sigue viva en /pages/app. */
/* Etiqueta pequeña arriba del título del bloque de la app (p. ej. "Descarga la app"). */
.app-hero-eyebrow { display: inline-block; font-size: var(--t-small); font-weight: 700; text-transform: uppercase; color: var(--amber-ink); margin-bottom: 12px; }
/* Banner compacto (home): SEPARADO de la franja (margin-top propio porque .app-hero
   trae margin:0), copy a la izquierda y el teléfono GRANDE asomando por la derecha.
   En móvil se apila: texto arriba y la foto centrada debajo (no se oculta). */
.app-hero-compact { position: relative; overflow: hidden; display: flex; align-items: center; gap: 0; min-height: 220px; margin-top: clamp(30px, 4vw, 46px); background: var(--card); border: 1px solid var(--line); border-radius: var(--r); padding: clamp(26px, 3.4vw, 40px) clamp(26px, 4vw, 46px); }
.app-hero-compact .app-hero-copy { max-width: 520px; position: relative; z-index: 1; }
.app-hero-compact .page-title { font-size: var(--t-banner); }
.app-hero-compact .page-sub { margin-top: 10px; }
.app-hero-compact .app-qr-row { display: none; }
.app-hero-compact .app-hero-visual { position: absolute; top: clamp(20px, 3vw, 36px); right: clamp(24px, 5vw, 72px); width: clamp(160px, 18vw, 210px); margin: 0; }
.app-hero-compact .app-hero-visual::before { display: none; }
.app-hero-compact .app-hero-visual img { width: 100%; max-width: none; filter: drop-shadow(0 16px 36px rgba(var(--ink-rgb),.16)); }
/* El banner compacto se apila a 860, EL MISMO corte en que el hero grande (con el que
   comparte la clase .app-hero) activa su centrado. Antes se apilaba a 760 y entre
   761–860 quedaba a dos columnas RECIBIENDO las reglas de centrado del hero grande:
   ahí vivían los tres bugs del banner (el teléfono tapando el párrafo, el texto
   descentrado y los 18px de padding fugados). Se unifican en 860 (17 jul).
   El selector es `.app-hero.app-hero-compact` (0,2,0) para GANARLE el `padding-top:18px`
   de `.app-hero` (0,1,0) de la media query de abajo, sin depender del orden. */
@media (max-width: 860px) {
  .app-hero.app-hero-compact { display: block; min-height: 0; text-align: center; padding: 24px 22px 26px; }
  .app-hero-compact .app-hero-copy { max-width: none; }
  .app-hero-compact .app-hero-visual { position: static; margin: 22px auto 0; width: clamp(180px, 54vw, 220px); }
}

/* Secciones de la app (funciones + pasos) */
.app-section { margin-top: clamp(48px, 6vw, 72px); }
/* Las cabeceras de esta página usan --t-page (28), no --t-section (24): es una LANDING y van
   CENTRADAS en 640px dentro de 1320. Un título centrado en tanto blanco se lee más chico que
   uno alineado del mismo cuerpo — a 24 se veían por debajo de "Nuestra historia" midiendo
   exactamente lo mismo (él, 17 jul). Antes no se notaba porque eran 30 contra 32: el centrado
   ya restaba y esos 2px lo tapaban por accidente. */
.app-section-head { text-align: center; max-width: 640px; margin: 0 auto 32px; }
.app-section-head :is(h2, h3) { font-family: var(--serif); font-weight: 700; font-size: var(--t-page); color: var(--ink); margin: 0 0 10px; text-wrap: balance; }
.app-section-head p { font-size: var(--t-strong); color: var(--muted); line-height: var(--lh-body); margin: 0; }

.app-feats { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap-reja); }
.app-feat { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 24px; box-shadow: var(--shadow-xs); }
.app-feat-ico { width: 44px; height: 44px; border-radius: 12px; background: var(--brand-soft); color: var(--brand); display: flex; align-items: center; justify-content: center; margin-bottom: 14px; }
.app-feat-ico svg { width: 22px; height: 22px; }
.app-feat :is(h2, h3, h4) { font-size: var(--t-body); font-weight: 700; color: var(--ink); margin: 0 0 6px; }
.app-feat p { font-size: var(--t-ui); color: var(--muted); line-height: var(--lh-body); margin: 0; }
@media (max-width: 820px) { .app-feats { grid-template-columns: 1fr 1fr; } }
@media (max-width: 520px) { .app-feats { grid-template-columns: 1fr; } }

.app-steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap-contenido); }
.app-step { padding: 26px 22px; background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); box-shadow: var(--shadow-xs); }
/* Caja en `em` por lo mismo que `.step-num`: 2.267em = los 34px de hoy con --t-strong. */
.app-step-num { display: inline-flex; align-items: center; justify-content: center; width: 2.267em; height: 2.267em; border-radius: 50%; background: var(--brand); color: #fff; font-weight: 800; font-size: var(--t-strong); margin-bottom: 14px; }
.app-step :is(h2, h3, h4) { font-size: var(--t-body); font-weight: 700; color: var(--ink); margin: 0 0 6px; }
.app-step p { font-size: var(--t-ui); color: var(--muted); line-height: var(--lh-body); margin: 0; }
@media (max-width: 720px) { .app-steps { grid-template-columns: 1fr; } }

/* Banda de descarga (cierre) */
.app-download { margin-top: clamp(48px, 6vw, 72px); background: var(--line-soft); border: 1px solid var(--line); border-radius: var(--r-lg); padding: clamp(30px, 4vw, 48px); text-align: center; }
.app-download :is(h2, h3) { font-family: var(--serif); font-weight: 700; font-size: var(--t-page); color: var(--ink); margin: 0 0 10px; }
.app-download p { font-size: var(--t-strong); color: var(--muted); margin: 0 0 22px; }
.app-download .app-stores { justify-content: center; }
.app-qr-row { display: flex; gap: 20px; margin-top: 24px; }
.app-qr { margin: 0; text-align: center; }
.app-qr img { width: 104px; height: 104px; display: block; background: var(--card); padding: 8px; border-radius: var(--r-sm); border: 1px solid var(--line-soft); box-shadow: var(--shadow-xs); }
.app-qr figcaption { margin-top: 9px; font-size: var(--t-small); font-weight: 600; color: var(--muted); }

@media (max-width: 860px) {
  .app-hero { grid-template-columns: 1fr; text-align: center; padding-top: 18px; }
  .app-hero-copy { max-width: none; margin: 0 auto; }
  .app-hero .page-title, .app-hero .page-sub { text-align: center; }
  .app-stores { justify-content: center; }
  .app-qr-row { display: none; }
  .app-hero-visual { order: -1; }
  .app-hero-visual img { max-width: 240px; }
}
@media (max-width: 460px) {
  .app-store-img img { height: 46px; }
  .app-qr-row { gap: 16px; }
}

/* ============================================================
   Páginas de info (envíos, devoluciones, facturación) + recetas
   ============================================================ */
.info-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--gap-contenido); margin-top: 30px; }
.info-card { background: var(--card); border: 1px solid var(--line-soft); border-radius: var(--r); padding: 26px; box-shadow: var(--shadow-xs); }
.info-card :is(h2, h3, h4) { margin: 0 0 12px; font-size: var(--t-block); color: var(--ink); }
.info-card p { font-size: var(--t-ui); color: var(--muted); line-height: var(--lh-body); margin: 0 0 12px; }
.info-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.info-list li { font-size: var(--t-ui); color: var(--text); line-height: var(--lh-body); }
/* En un documento (una política, un artículo) el nivel SÍ decide el aspecto: el h2 es la
   sección y el h3 su apartado, y quien lee con los ojos necesita ver esa jerarquía igual
   que quien la oye. Aquí NO se desata la etiqueta: se desata en los componentes, donde el
   título de una tarjeta debe verse igual sea h2 o h3.
   ⚠️ La sección de un documento usa --t-block (20), NO --t-section (24): se probó a 24 y
   "creció mucho" (él, 17 jul). Una política se LEE de arriba abajo detrás de un h1 de 44;
   no está compitiendo por la atención como el paso de un checkout. El h3 baja a --t-body
   para que, si algún día hace falta un apartado, el nivel se siga viendo. */
/* La sección de una política encoge a 18 en móvil (clamp), 20 en escritorio. Mismo caso que el
   blog: el h1 (--t-hero) cae de 44 a 28 en el teléfono y la sección fija se le acercaba; a 20
   se sentía grande en la columna estrecha, y "un poco" también en escritorio (él, 17 jul).
   NO es un tamaño por página: el TOPE es --t-block (20), el token de siempre; 18 es solo cuánto
   encoge. .page-body lo usan únicamente los documentos (devoluciones, facturación, términos,
   privacidad), no toca nada del checkout ni las tarjetas. */
.page-body h2 { font-size: clamp(1.125rem, 3vw, var(--t-block)); line-height: var(--lh-heading); margin: 34px 0 10px; }
.page-body h3 { font-size: var(--t-body); color: var(--ink); margin: 28px 0 10px; }
/* La letra chica de /envios. Dos cosas que la hacen pertenecer a la página, y la primera
   es la que importa: va al ANCHO DE LA REJA de .info-cards, no dentro de .page-body (720px).
   Estaba centrada a 720 debajo de unas tarjetas de 1320 y sus cantos no coincidían con nada
   → se leía como pegada al final. La segunda es el tono: gris en vez de blanco y sin sombra,
   porque es una nota al pie y no una quinta tarjeta. El gris es el mismo --line-soft que ya
   usa .co-duda en el checkout, no un color nuevo.
   Dos columnas en escritorio (son dos avisos independientes, no una lista que se lea en
   orden) y una sola en cuanto no caben: el auto-fit lo resuelve sin media query. */
.disclaimers {
  margin: 22px 0 0; padding: 20px 26px; list-style: none;
  background: var(--line-soft); border-radius: var(--r);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px 34px;
}
.disclaimers li {
  position: relative; padding-left: 20px;
  font-size: var(--t-ui); line-height: var(--lh-body); color: var(--muted);
}
/* El asterisco de la letra chica de toda la vida. Gris, nunca vino: avisa, no alarma. */
.disclaimers li::before {
  content: "*"; position: absolute; left: 0; top: .1em;
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--faint);
}

.recipe-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--gap-contenido); margin-top: 30px; }
/* Índice del blog: 5 por fila en escritorio. */
/* ⚠️ El índice del blog NO lleva columnas fijas en escritorio, y quitarlas fue el
   arreglo (27 jul 2026). Tenía `repeat(5, 1fr)` desde 921px, o sea CINCO columnas
   metidas en un portátil: a 921 la tarjeta medía 162px y su foto 202 de alto, mientras
   el bloque del degradado mide 184 fijos (100 de relleno + el kicker + el título de
   2.44em + 15) → **el degradado tapaba el 91% de la foto y la receta no se veía**. Y
   encima daba un salto brutal en el corte: a 920px pasaba de golpe a 3 columnas de
   277px. Hereda el `repeat(auto-fill, minmax(240px, 1fr))` de `.recipe-grid`, que ya
   estaba declarado ahí arriba y que esta regla estaba anulando: 3 columnas a 921 (53%
   de degradado), 4 a 1120 y **5 desde 1320 — o sea que en escritorio ancho no cambia
   NADA**, y el salto del corte desaparece. Los dos `@media` de abajo (3 y 2 columnas)
   se quedan: en el teléfono `auto-fill` daría UNA sola. */
.recipe-card { display: block; background: var(--card); border-radius: var(--r); overflow: hidden; box-shadow: var(--shadow-xs); transition: transform .15s, box-shadow .15s; text-decoration: none; color: inherit; }
.recipe-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-sm); }
/* Foto vertical con el título encima (degradado en tono vino para legibilidad). */
.recipe-thumb { position: relative; aspect-ratio: 4/5; background: var(--line-soft); overflow: hidden; }
.recipe-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform var(--dur-xs) var(--ease-out); }
.recipe-card:hover .recipe-thumb img { transform: scale(1.05); }
.recipe-card-body {
  position: absolute; left: 0; right: 0; bottom: 0; padding: 100px 16px 15px;
  background: linear-gradient(to top,
    rgba(10,13,14,.93) 0%, rgba(10,13,14,.72) 34%, rgba(10,13,14,.4) 62%, rgba(10,13,14,.14) 84%, rgba(10,13,14,0) 100%);
}
.recipe-kick { display: block; font-size: var(--t-label); font-weight: 700; text-transform: uppercase; color: var(--amber); margin: 0 0 5px; text-shadow: 0 1px 3px rgba(0,0,0,.4); }
/* Reserva 2 líneas para que el título arranque a la misma altura en todas las tarjetas. */
.recipe-card :is(h2, h3, h4) { margin: 0; font-family: var(--serif); font-size: var(--t-block); font-weight: 600; color: #fff; line-height: var(--lh-heading); min-height: 2.44em; text-shadow: 0 1px 3px rgba(0,0,0,.4); }

/* ---------- Artículo de receta ---------- */
.receta { max-width: 960px; margin: 0 auto; padding-top: 6px; }
/* Botón "volver" al ancho de la PÁGINA (borde de main), no del artículo centrado.
   Su margen INFERIOR es el aire de ARRIBA del título: subirlo aquí y no con un
   margin-top en .receta-head, que colapsaría contra este y no haría nada. */
.receta-back { margin: 20px 0 20px; }
.receta-head { text-align: center; margin: 0 0 40px; }
.receta-title { text-align: center; font-family: var(--serif); font-weight: 600; color: var(--ink); font-size: var(--t-hero); line-height: var(--lh-display); margin: 0; }
.receta-figure { border-radius: var(--r-lg); overflow: hidden; background: var(--line-soft); margin-bottom: 34px; box-shadow: var(--shadow-sm); }
.receta-figure img { width: 100%; height: auto; display: block; aspect-ratio: 2.4 / 1; object-fit: cover; }
.prose { font-size: var(--t-body); line-height: var(--lh-body); color: var(--text); }
/* ⚠️ El artículo va a 24 y la política a 20 (.page-body h2). NO es una incoherencia y NO se
   unifica: se miró cada uno y se decidió (él, 17 jul — "el blog lo veo bien así", y en las
   políticas "creció mucho"). Un artículo de Preparación es editorial, con fotos y secciones
   que se ojean; una política es texto legal denso que se lee seguido detrás de un h1 de 44. */
/* SOLO el artículo del blog (.prose lo usa únicamente blog-receta.php): el h2/h3 del cuerpo
   encogen en móvil con clamp. NO se toca el token —--t-section (24) y --t-block (20) siguen
   igual para las otras 18 clases—; el máximo del clamp ES el token, así que en escritorio no
   cambia nada. El blog es el ÚNICO que lo necesita porque es el único cuyo h1 (44) cae a 28 en
   móvil: sin esto, los títulos fijos del cuerpo se le pegan y la jerarquía se aplasta (él, 17 jul). */
.prose h2 { font-family: var(--serif); font-size: clamp(1.25rem, 4.5vw, var(--t-section)); font-weight: 600; color: var(--ink); margin: 34px 0 14px; }
.prose h3 { font-family: var(--serif); font-size: clamp(1.0625rem, 4vw, var(--t-block)); font-weight: 600; color: var(--brand); margin: 26px 0 8px; }
.prose p { margin: 0 0 14px; }
.prose ol, .prose ul { margin: 0 0 16px; padding-left: 26px; display: flex; flex-direction: column; gap: 10px; }
.prose li { line-height: var(--lh-body); }
.prose ol li::marker { color: var(--brand); font-weight: 700; }
.prose ul li::marker { color: var(--amber); }
.btn-back { display: inline-flex; align-items: center; gap: 6px; font-size: var(--t-ui); font-weight: 700; color: var(--brand); text-decoration: none; }
.btn-back:hover { opacity: .7; }
.receta-mas { max-width: var(--maxw); margin: 78px auto 0; }
.receta-mas-title { font-family: var(--serif); font-size: var(--t-section); font-weight: 600; color: var(--ink); margin: 0; }

/* ============================================================
   Responsive
   ============================================================ */
/* LA FRANJA QUE SE ROMPÍA. Con el menú dentro de la fila del logo, la cuenta es:
   logo 83 + menú ~676 + acciones ~190 + huecos y respiro ~92 = **1041 px**. Pero el
   menú no se esconde hasta los 920, así que entre 920 y 1041 no cabía y se apretaba
   contra los iconos. Aquí se recorta el aire de los enlaces y, por debajo de 1000,
   se esconde el TEXTO de "Iniciar sesión" (el icono se queda): con eso la fila cabe
   holgada hasta el corte de los 920. */
/* ⚠️ Este corte SUBIÓ de 1080 a 1160 el 20 jul 2026, y el número está calculado,
   no elegido a ojo. Con el menú a relleno completo (669 px) y el botón de cuenta
   con su texto (196 px), para que los dos lados puedan repartirse el espacio A
   PARTES IGUALES —que es lo que deja el menú centrado en la página— hace falta
   `669 + 40 de huecos + 2 × 213 = 1135 px` de contenido, o sea **1187 px de
   ventana**. Con el corte en 1080 quedaba una franja donde el menú volvía a su
   relleno grande antes de que la fila diera para tanto, y ahí se descentraba 36 px.
   Subiéndolo a 1190 esa franja desaparece: por debajo el menú va compacto (que sí
   cabe centrado) y por encima va holgado.
   ⚠️ El 213 es el lado derecho en su caso PEOR: 61 de la píldora + 100 de nombre
   topado + 10 + 42 del carrito. Primero eché la cuenta con los 196 de "Iniciar
   sesión" y me salió 1153 — quedaba una franja de 25 px mal. **El número lo manda
   el caso peor, no el habitual.** */
@media (max-width: 1190px) {
  .header-row { gap: 12px; }
  .header-nav { gap: 2px; }
  .nav-link { padding: 9px 10px; }
}
/* El TEXTO del botón de cuenta ("Iniciar sesión" o el nombre) se esconde antes que
   antes: el corte subió de 1000 a 1080 el 20 jul 2026, por la misma cuenta que el de
   arriba. Con el texto visible ese lado mide hasta 213 px (61 de la píldora + 100 de
   nombre topado + 10 + 42 del carrito), y para que los dos lados se repartan a partes
   iguales hace falta `565 + 24 + 2 × 213 = 1015 px` de contenido → **1067 px de
   ventana**. Entre 1001 y 1067 el texto salía pero la fila no daba, y el menú se
   corría ~21 px. Con el corte en 1080 queda holgado. El icono se queda siempre. */
@media (max-width: 1080px) {
  .account-txt { display: none; }
}
@media (max-width: 920px) {
  .header-nav { display: none; }
  .nav-toggle { display: inline-flex; }
  /* LOS TRES ICONOS DE LA BARRA, IGUALES (18 jul 2026, lo vio él en una captura).
     En cuanto aparece el ☰, la cuenta y el carrito sueltan su píldora y su círculo
     y se quedan los tres en la MISMA caja de 44 — que además es la zona táctil que
     ya tenía el ☰ por ser la única entrada al menú. Antes esto solo pasaba por
     debajo de 720, así que entre 720 y 920 salían dos círculos con borde y un ☰
     pelado, de tres tamaños distintos. */
  .icon-btn, .account-link, .nav-toggle {
    width: 44px; height: 44px; padding: 0; border: none; background: none;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .account-link .account-txt { display: none; }
  /* El logo se centra en cuanto aparece el ☰ — antes esto solo pasaba por debajo de
     720 y entre 720 y 920 el logo quedaba pegado al ☰ (lo preguntó él: "¿y por qué
     el logo no se centra?").
     ⚠️ Centrado con `top: 50%` y no con un número: el `top: 10px` que había se
     quedó viejo en cuanto la fila pasó a 12 de aire, y esa clase de número escrito
     a ojo contra el layout ya ha mordido antes en este archivo. */
  .header-row { position: relative; }
  .logo-link { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }
  /* En cuanto el menú se esconde tras el ☰, el buscador deja de tener tope y llena
     el ancho del contenido — el MISMO que el mosaico de departamentos y las
     tarjetas, porque los dos cuelgan de `--maxw` + `--pad` (18 jul 2026, suyo).
     El tope de 540 solo tiene sentido con la fila del menú al lado; sin ella, una
     caja de 540 centrada en 800 se lee más angosta que todo lo de abajo. */
  .search-wrap { max-width: none; }
  .footer-grid { grid-template-columns: 1fr 1fr; gap: 32px 40px; }
  .footer-brand { grid-column: 1 / -1; }
  /* Una sola columna: la barra lateral pasa a cajón `fixed` y no ocupa flujo, pero
     su área se sigue nombrando para que `grid-area: side` apunte a algo que existe. */
  .browse-layout { grid-template-columns: 1fr; grid-template-areas: "toolbar" "side" "main"; column-gap: 0; }
  .recipe-grid-all { grid-template-columns: repeat(3, 1fr); }
  .mobile-cats { display: block; margin: 2px 0 16px; }
  /* Cerrado, este cajón lleva TODA la navegación de categorías. Sin la `visibility`
     se quedaba tabulable fuera de pantalla. Mismo patrón y mismo motivo que
     .mobile-menu — si se toca uno, se toca el otro. */
  /* ⚠️ `height: 100dvh` y NO `bottom: 0` — y es EL arreglo del scroll (21 jul 2026,
     tras dos intentos fallidos míos y dos reportes suyos).
     En iOS, un `position: fixed` con `top: 0; bottom: 0` se resuelve contra el
     viewport GRANDE (el que hay con las barras de Safari escondidas), no contra lo
     que se ve. Con 16 departamentos el contenido mide ~790px: cabe de sobra en los
     ~852 del viewport grande, **así que no hay desbordamiento y no hay scroll** —
     pero no cabe en los ~745 que quedan visibles con la barra puesta, así que la
     lista **se ve cortada y no se puede alcanzar**. Los dos síntomas a la vez, y
     por eso en la Mac funcionaba: ahí no hay barra que aparezca y desaparezca.
     `dvh` es la altura del viewport DINÁMICO: la que de verdad se ve. El `100vh`
     de arriba es el respaldo para navegadores sin `dvh`. */
  .sidebar {
    position: fixed; top: 0; left: 0; height: 100vh; height: 100dvh;
    width: 310px; max-width: 86vw; z-index: 40;
    touch-action: pan-y;   /* scroll vertical nativo; el gesto horizontal lo toma el helper (site.js) */
    background: var(--card); overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* El aire de abajo respeta la franja del iPhone, igual que `.mobile-menu`: sin
       esto el ultimo departamento queda bajo la barra del gesto y no se alcanza
       aunque el cajon SI se desplace. Los 22 de los otros tres lados no cambian. */
    padding: 22px 22px calc(22px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-100%); visibility: hidden;
    transition: transform var(--dur-lg) var(--ease-drawer), visibility 0s linear var(--dur-lg);
  }
  .sidebar.open {
    transform: translateX(0); visibility: visible; box-shadow: var(--shadow-lg);
    transition: transform var(--dur-lg) var(--ease-drawer), visibility 0s;
  }
  /* En el cajón móvil manda el scroll del cajón: la lista no lleva el suyo. */
  .cat-nav { max-height: none; overflow: visible; }
  /* Solo lo PROPIO: el aspecto lo pone `.sidebar-head` allá arriba (ya no es una
     regla compartida: el menú móvil dejó de tener cabecera de texto el 21 jul). */
  .sidebar-head { display: flex; margin-bottom: 20px; }
  .open-filters { display: inline-flex; flex: 0 0 auto; }
  /* En móvil: título a todo el ancho y, debajo, Filtros (izq) + Ordenar (der). */
  .toolbar { flex-direction: column; align-items: stretch; gap: 12px; margin-bottom: 16px; }
  .toolbar-controls { justify-content: space-between; gap: 10px; }
  .toolbar-right { min-width: 0; }
  .sort-select { min-width: 0; max-width: 100%; }
  .product-layout { grid-template-columns: 1fr; grid-template-areas: "gallery" "thumbs" "info"; column-gap: 0; row-gap: 14px; }
  /* ⚠️ Aquí había un `.product-gallery { position: static }` que no hacía nada
     —la clase ya era static— y que el 20 jul 2026 se volvió peligroso: desde que
     el ♥ flota sobre la foto, `relative` es su ancla, y devolverlo a `static` en
     el teléfono habría mandado el ♥ a otra parte de la página, sin error y sin
     verse. Se borró. No reponerlo. */
  .product-info { padding-top: 8px; }
}

@media (max-width: 720px) {
  :root { --pad: 16px; --gap-producto: 8px; --gap-reja: 10px; --gap-contenido: 12px; }
  /* MÓVIL: catálogo −1 / inicio −2 (23 jul 2026, suya, confirmado en el iPhone real —el
     Artifact no bastaba: rem vs px y Safari iOS renderiza distinto). El +1 unificado se veía
     grande en el teléfono; el carrusel es más angosto y aguanta menos, por eso baja más.
     El catálogo baja los --tc-* aquí; el carrusel redefine los MISMOS tokens en su propio
     ámbito (.card-over), NO con overrides de font-size, así el tamaño sigue viniendo de un
     token (la prueba no lo marca como suelto y sigue viendo "sin tamaños propios"). */
  :root {
    /* Los dos que subieron el 26 jul 2026 SOLO para escritorio vuelven aquí a su valor de
       siempre: la interfaz a 13 y el menú a 14. Van con los `--tc-*` y no en una regla
       aparte porque es el mismo tipo de decisión —la escala del teléfono— y así se ve de
       una vez todo lo que cambia de tamaño al encoger. */
    --t-ui: .8125rem;     /* 14 → 13 */
    --t-nav: .875rem;     /* 15 → 14 */
    --t-nav-sub: .8125rem;/* 14 → 13, siempre un punto por debajo del menú */
    --tc-name: 1rem;      /* 17 → 16 */
    --tc-price: .9375rem; /* 16 → 15 */
    --tc-was: .75rem;     /* 13 → 12 */
    --tc-add: .75rem;     /* 13 → 12 */
    --tc-attr: .625rem;   /* 11 → 10 */
    --tc-cat: .5625rem;   /* 10 → 9 */
    --tc-unit: .75rem;    /* 13 → 12 */
  }
  .card-over {
    --tc-name: .9375rem;  /* 17 → 15 */
    --tc-price: .875rem;  /* 16 → 14 */
    --tc-cat: .5rem;      /* 10 → 8 */
  }
  /* El precio ya no necesita regla de móvil: --t-price lleva su propio clamp
     y así el salto no vive en dos sitios.
     ⚠️ AQUÍ HABÍA `.p-attr { font-size: var(--t-small); }` y se BORRÓ el 26 jul 2026,
     pedido suyo al verlo en el teléfono (*"lo de supervisión tiene otro tamaño"*). Bajaba
     las CUATRO filas de la ficha —Supervisión, Departamento, Tipo de venta, Disponibilidad—
     a 12 mientras el resto de la interfaz de esa pantalla iba a 13. **Era la única regla de
     tamaño del archivo sin un comentario que la justificara**, que aquí es la señal de que
     se coló de paso. Lo que podía haberla motivado —que la fila larga *"Sobre pedido · N
     días de anticipación"* quepa en una pantalla de 360— no se sostiene: se parte en dos
     líneas igual a 12 que a 13. Ahora las cuatro siguen a `--t-ui`, como todo lo demás. */
  /* Ficha, variante YA EN EL CARRITO: en el teléfono no caben las tres cosas en una fila
     —selector `− 1.05 kg +`, el importe y "Ver carrito"—, así que el botón se bajaba… pero
     **solo cuando el valor era largo**: con "300 g" entraba y con "1.05 kg" no. Lo que
     molestaba no era la segunda fila sino que apareciera y desapareciera según lo que
     llevaras (él, 26 jul 2026: *"en móvil casi siempre está abajo, ponlo alineado"*).
     Ahora es SIEMPRE igual: arriba los dos CONTROLES —el selector y "Ver carrito", en la
     misma línea y a la misma altura— y debajo el importe, que es dato y no acción. El botón
     suelta su `min-width` de 160 y encoge el relleno para caber al lado del selector en una
     pantalla de 360 con cualquier cantidad; `flex: 1` le da todo lo que sobre, así que su
     borde derecho cae siempre en el mismo sitio. */
  /* Y lo mismo para "Agregar al carrito", que es el que él vio abajo en el iPhone: su
     `min-width: 220px` no cabe NUNCA al lado del selector en un teléfono, así que se bajaba
     siempre. **Medido con la fuente del sistema (la misma del iPhone), no a ojo:** el rótulo
     mide 131.6 px a 16 semibold; con los ± a 48 y relleno 28 el botón pedía 188 y al lado
     del selector solo quedan 161-191 según la cantidad → no cabía ni con "300 g". Bajando
     los ± a 44 —el mínimo táctil, que se respeta— y el relleno a 12, pide 156 y **entra
     desde 375 px de ancho con cualquier cantidad**, incluida "1.025 kg". Por debajo de 375
     vuelve a bajarse, que es la caída correcta: antes que apretar el rótulo, que baje. */
  .p-buy .add-btn { min-width: 0; padding: 0 12px; }
  .p-buy .qty-btn { width: 44px; }
  .p-ver-carrito { flex: 1; min-width: 0; padding: 0 12px; }
  .p-en-carrito { order: 3; flex-basis: 100%; padding-right: 0; }
  /* El catálogo en el teléfono (21 jul 2026, decisión suya sobre un preview):
     el nombre del departamento se pinta MÁS CHICO que en escritorio —hoy 25 → 23,
     `--t-section` → `--t-section-movil`— y el aire de arriba pasa de 30 a 14.
     ⚠️ Aquí decía «24 → 20, --t-section → --t-block» y quedó FALSO dos veces el
     26 jul 2026: primero por el +1 px de los títulos y luego porque él vio los 21
     resultantes y pidió 22 y luego 23, unificado con los del inicio. Los 30 son de escritorio y
     se quedan: vienen del hueco que dejó la miga al podarse, y ahí sí hacen falta.
     ⚠️ El tamaño se pisa con la MISMA especificidad que la regla de arriba
     (`.toolbar-title :is(h1, h2, h3)`, línea ~1009) y gana solo por ir después en el
     archivo — `@media` no añade especificidad. Si esta regla se mueve por encima
     de aquella, deja de aplicar sin dar ningún error. */
  .browse-layout { margin-top: 14px; }
  /* Los dos encabezados de sección del teléfono, con el MISMO tamaño y en el mismo sitio
     del archivo: si un día cambia uno, se ve el otro al lado. `.sec-head` lo usan también
     los carruseles bajo la ficha de producto, así que esto los alcanza — es lo coherente:
     en el teléfono todos los encabezados de sección se leen igual.
     ⚠️ Los dos pisan a su regla de arriba con la MISMA especificidad y ganan solo por ir
     después en el archivo: `@media` no añade especificidad. Si este bloque se mueve por
     encima, dejan de aplicar sin dar ningún error. */
  .toolbar-title :is(h1, h2, h3) { font-size: var(--t-section-movil); }
  .sec-head :is(h2, h3) { font-size: var(--t-section-movil); }
  .header-row { position: relative; flex-wrap: wrap; gap: 8px 12px; padding: 12px var(--pad); }
  .nav-toggle { order: 1; }
  .logo-link { order: 2; }
  .header-actions { order: 3; margin-left: auto; gap: 4px; }
  /* El tope de ancho ya se quitó a los 920 (ahí se esconde el menú). Aquí solo
     queda el aire de abajo. Los cuatro números de la barra en el teléfono —logo 44,
     12 de aire en la fila, buscador de 42 y 12 debajo— los eligió él moviéndolos en
     vivo (18 jul 2026). El alto del buscador es el mismo arriba y abajo. La barra
     entera mide 158 px con la franja de anuncios. */
  .header-search { padding-bottom: 12px; }
  .logo { height: 44px; }
  /* Cuenta y carrito idénticos: mismo tamaño, planos, sin borde. */
  /* 12 en móvil, decisión suya (16 jul): más apretado que los 15 del escritorio,
     porque aquí el ancho es oro. **Este número SÍ se queda**, y la distinción
     importa: el 16 que había antes se quitó porque **no se distinguía** del 15 (mismas
     columnas, 1px) — era una copia a la deriva. 12 es otra cosa: es una decisión, y
     una decisión sí merece su número.
     Comprobado de 320 a 430px: mismas columnas que con 15 (2 desde 360, 1 por debajo)
     y la tarjeta gana 2px. Solo entre 344 y 346px cambia —ahí 12 mete 2 columnas y 15
     deja 1—, y en esa franja no hay teléfonos. */
  /* El hueco, otra vez COMPARTIDO entre tienda y favoritos: si aquí se separan, se
     separan igual que antes. Lo que cambia solo en la tienda es el minmax. */
  .grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  /* Móvil: 2×2 = 4 tarjetas (decisión suya, 17 jul). Columnas capadas a 220px y
     centradas para que no se agranden en la franja 560–720px. Se esconden de la 5ª
     en adelante (se pintan 10 en el DOM; en móvil solo se ven 4). */
  .rail-doble { grid-template-columns: repeat(2, minmax(0, 220px)); justify-content: center; }   /* el hueco lo pone --gap-reja */
  .rail-doble .card:nth-child(n+5) { display: none; }
  /* El bloque de texto NO encoge solo con la tarjeta: es alto fijo. Se aprieta el
     relleno para que la proporción sobre la foto se parezca a escritorio. */
  .card-over .card-body { padding: 26px 11px 11px; gap: 4px; }
  /* UNIFICADO el 23 jul 2026 (decisión suya con preview): el carrusel dejó de tener sus
     tamaños propios de móvil (nombre 13, precio 15, categoría 8) y usa la MISMA escala de
     tarjeta que el catálogo (--tc-*). Por eso aquí ya NO hay override de nombre/precio ni
     el `.card-eyebrow{font-size:.5rem}`. Si un día el carrusel debe ser más chico, se
     decide un token propio, no se hereda a ojo. */
  .card-attrs { gap: 5px; }   /* solo el hueco es de móvil; el tamaño ya es el de la escala de tarjeta */
  /* ~3 por fila en el teléfono (con basis 130 daría 2). **max-width SÍ, no `none`:** sin
     tope, una última fila con UNA baldosa la estira `flex-grow` a todo el ancho → gigante.
     Con tope, una baldosa suelta se queda a tamaño normal y centrada (17 jul, bug del usuario). */
  /* Se quedan como estaban: 95/120. Se probó bajarlos a 84/110 y él lo devolvió
     (18 jul 2026). Lo que SÍ es cierto y conviene no re-descubrir: en un teléfono
     de **320 px** —un SE, o cualquier iPhone con la "Pantalla ampliada" puesta—
     con 95 de mínimo solo caben DOS por fila y quedan ~48px muertos a la derecha,
     porque tres pedirían 309 y solo hay 288. En 375 y 390 entran tres. No es un
     fallo escondido: está mirado y decidido así. */
  .dept-tile, .sk-tile { flex-basis: 95px; max-width: 120px; }
  /* Los campos ya no necesitan una regla aquí: llevan --t-field (16px) en todas las
     pantallas. Antes esto vivía solo bajo 720px, y el zoom de iOS no depende del ancho
     de la pantalla sino del tamaño de la letra -> en un iPad (768px) sí hacía zoom. */
  .dept-tile-name { font-size: var(--t-ui); }
  /* En móvil se recorta el aire de arriba. El de ABAJO se queda en 16: por lo mismo
     de siempre, con los 6px de antes ganaba el margen del bloque de debajo
     (10/14/16) y la miga volvía a quedar pegada al contenido. */
  .breadcrumb { margin: 16px 0; }
  /* Barra de controles más limpia: solo el selector, sin la palabra "Ordenar:". */
  .sort-label { display: none; }
  /* Chips más compactos en móvil. */
  .chip { padding: 8px 14px; font-size: var(--t-small); }
  .cat-chip-row { margin-top: 6px; }
  .footer-grid { grid-template-columns: 1fr; gap: 30px; padding: 30px var(--pad) 30px; }
  .footer-bottom-inner { flex-direction: column; align-items: flex-start; gap: 12px; }
  /* Menos "aire" vertical en móvil: separaciones más compactas. */
  main { padding-bottom: 32px; }
  section { margin-top: 32px; }
  /* Banner de portada: menos espacio arriba y abajo en móvil. */
  .hero { margin-top: 8px; }
  /* EN EL TELÉFONO LAS VECINAS SÍ ASOMAN — decisión suya, 18 jul 2026 (*"en móvil
     sí me gustaba que se asomaran"*). Con el hueco de 24 del escritorio no daban:
     el margen que deja el 88% en un teléfono (~22px) es MÁS CHICO que el propio
     hueco, así que la vecina quedaba fuera de pantalla. Aquí el hueco baja a 14 y
     vuelve a asomar una rebanada.
     ⚠️ Y la máscara se APAGA: esa rebanada mide ~8px y el fundido, que se calcula
     desde el hueco de los lados, se la comería entera — o sea, apagaría justo lo
     que él quiere ver. El fundido es de escritorio, donde hay 300px de vecina que
     desvanecer. */
  .hero-track { gap: 14px; -webkit-mask-image: none; mask-image: none; }
  .dept-mosaic-section { margin-top: 20px; }
  .bene { margin-top: 34px; padding: 22px 18px; }
  .page-hero { padding: 20px 16px 12px; }
  .info-cards { margin-top: 24px; }
  .branches { margin-top: 22px; }
  .page-body { margin-top: 24px; }
  .recipe-grid { margin-top: 24px; }
  .recipe-grid-all { grid-template-columns: repeat(2, 1fr); }
  .receta-mas { margin-top: 58px; }
  /* Botón "volver" arriba a la izquierda; título centrado debajo. El margen inferior
     es el aire de arriba del título (ver el bloque de escritorio). */
  .receta-back { margin: 12px 0 16px; }
  .receta-head { margin-bottom: 36px; }
  footer { margin-top: 32px; }
}

/* ============================================================
   Reducir movimiento
   ============================================================
   Cuando el sistema pide menos movimiento (macOS: Accesibilidad → Pantalla →
   Reducir movimiento; iOS: Accesibilidad → Movimiento) NO se apaga todo: se
   quitan los DESPLAZAMIENTOS y se conservan los fundidos, los colores y las
   sombras, que son los que explican qué acaba de pasar.
   Va al final del archivo a propósito: así gana por orden de aparición a las
   reglas de dentro de los @media de móvil (misma especificidad). */
/* Hover sin levantado ni zoom, por DOS motivos distintos que piden lo mismo: en
   táctil el hover se queda pegado tras el toque, y en movimiento reducido el
   desplazamiento sobra. Estaba escrito DOS veces (aquí y en el @media (hover:none)
   de arriba, a 1.700 líneas): misma lista, sincronizada de casualidad, y ya
   arrastraba un selector muerto (`.store-badge:hover`, 0 usos en php/js — fuera).
   El cambio de sombra y de color se queda: es lo que señala "esto responde".
   Va al final del archivo a propósito: así gana por orden de aparición a las reglas
   de dentro de los @media de móvil (misma especificidad). */
@media (hover: none), (prefers-reduced-motion: reduce) {
  .card:hover, .fav-card:hover, .recipe-card:hover,
  .dept-tile:hover .dept-tile-img, .app-store-img:hover,
  .recipe-card:hover .recipe-thumb img, .fav-heart:hover,
  .ig-item:hover img,
  .cta-wa:hover, .cta-phone:hover { transform: none; }
}

/* ============================================================
   FEEDBACK DE PRESIÓN — apple-design, 22 jul 2026
   ============================================================
   El sitio ya había inventado el idioma (.refresh-btn y .load-more con
   `:active { scale }`) y no lo propagó. Aquí se completa para el resto de
   controles que se tocan. Va agrupado y AQUÍ —después de todos los :hover,
   antes del bloque de movimiento-reducido— por cascada, no por gusto:
     · gana por ORDEN a los :hover de la tarjeta y las CTA, que usan la
       MISMA propiedad (transform): al presionar, HUNDIR sustituye al
       levantado, que es lo correcto. (En los botones de color no compite:
       el hover cambia fondo, no transform.)
     · el neutralizador de movimiento-reducido va justo debajo (dentro del
       @media siguiente), así que gana a esto por orden y apaga el hundido
       sin depender de la especificidad.
   En táctil el :hover está apagado (@media hover:none) → esto es lo ÚNICO
   que acusa el toque en el móvil, que es donde importa. Valores: los mismos
   del preview que aprobó él (.98/.96/.92/.9), curva y duración ya tokenizadas. */
.card:active                       { transform: scale(.98); }
.card-add:active, .fav-add:active  { transform: scale(.96); }
.add-btn:active                    { transform: scale(.96); }
.qty-btn:active                    { transform: scale(.92); }
.btn-primary:active                { transform: scale(.96); }
.icon-btn:active                   { transform: scale(.9); }
.cta-wa:active, .cta-phone:active  { transform: scale(.97); }
.m-btn:active                      { transform: scale(.96); }
.variant-btn:active                { transform: scale(.96); }
.dept-tile:active .dept-tile-img   { transform: scale(.96); }
.ig-item:active                    { transform: scale(.98); }

/* Modal del carrito: se monta con appendChild y aparecía DE GOLPE
   (cart.js:474, sin tocar). Un @keyframes se dispara solo al insertarse el
   nodo → materializa sin cambiar el JS. La SALIDA se queda instantánea a
   propósito: animarla obligaría a retrasar el `overlay.remove()` y su
   devolución de foco, que ya está probada. Sin rebote (no "parece IA"). */
@keyframes cart-modal-in   { from { opacity: 0; transform: scale(.96); } to { opacity: 1; transform: scale(1); } }
@keyframes cart-overlay-in { from { opacity: 0; } to { opacity: 1; } }
.cart-modal-overlay { animation: cart-overlay-in var(--dur-sm) var(--ease-out); }
.cart-modal         { animation: cart-modal-in var(--dur-sm) var(--ease-out); }

/* El header es la ÚNICA superficie translúcida del sitio (backdrop-filter,
   :221). Faltaban sus dos respaldos de accesibilidad: sin ellos, quien pide
   menos transparencia o más contraste seguía recibiendo el blur. */
@media (prefers-reduced-transparency: reduce) {
  header { background: #fff; backdrop-filter: none; -webkit-backdrop-filter: none; }
}
@media (prefers-contrast: more) {
  header { background: #fff; backdrop-filter: none; -webkit-backdrop-filter: none; border-bottom-color: var(--ink); }
}

@media (prefers-reduced-motion: reduce) {
  /* El hundido de presión y el materialize del carrito: sin recorrido. Va
     DENTRO del @media y después del grupo de arriba → gana por orden. */
  .card:active, .card-add:active, .fav-add:active, .add-btn:active, .qty-btn:active,
  .btn-primary:active, .icon-btn:active, .cta-wa:active, .cta-phone:active,
  .m-btn:active, .variant-btn:active, .dept-tile:active .dept-tile-img,
  .ig-item:active,
  .load-more:active, .refresh-btn:active { transform: none; }
  /* El ojo de la contraseña va APARTE y NO lleva `none`: su `transform` hace dos
     trabajos a la vez —centrarlo en el campo y el hundido—, así que quitarlo
     entero lo descolocaría media altura. Se le quita solo el `scale`. */
  .pass-ojo:active { transform: translateY(-50%); }
  .cart-modal-overlay, .cart-modal { animation: none; }

  /* Lo del 25 jul: la confirmación sale ya puesta (sin `animation` el `both` no
     aplica y todo queda a opacidad 1), el chevron del FAQ gira sin recorrido y la
     fila del carrito se va sin fundido — cart.js además comprueba la preferencia
     y la quita sin esperar los 150ms. */
  .confirm-icon, .confirm-title, .confirm-sub { animation: none; }
  .faq-item summary::after { transition: none; }
  .cart-item.is-out { transition: none; }

  /* La página no se desplaza sola. */
  html { scroll-behavior: auto; }

  /* Los cajones aparecen en su sitio, sin recorrido. OJO: no se les puede poner
     `transform: none` — su translateX(-100%) es lo que los mantiene fuera de la
     pantalla; sin él taparían la página entera. El velo SÍ se funde (la opacidad
     no es movimiento) y es lo que explica la apertura. */
  .mobile-menu, .sidebar { transition: none; }

  /* El aviso del carrito y el menú de departamentos: se funden sin viajar.
     El translateX(-50%) del aviso NO es movimiento, es lo que lo centra: se
     queda en los dos estados. */
  .cart-toast { transform: translateX(-50%); transition: opacity var(--dur-sm) var(--ease-out); }
  .cart-toast.show { transform: translateX(-50%); }
  .nav-dd-menu, .nav-dd.open .nav-dd-menu { transform: translateY(0); }
}

/* ============================================================
   ZONAS TÁCTILES — la segunda tanda (16 jul)
   ============================================================
   La primera tanda arregló SEIS controles (☰, ±, "Agregar", ✕, ♥, migas) y el
   documento dio el problema por cerrado. No lo estaba: al preguntar el usuario
   "¿tenemos algún error más como ese?" salieron TRECE más. Mismo trato de siempre y
   misma promesa: **no cambia ni un píxel de lo que se ve**, la zona la pone un
   ::after invisible. Dos patrones, los mismos que ya usaba el archivo:
     · cuadrados  → 44×44 centrado         (como .fav-heart)
     · anchos     → left/right 0, alto 44  (como .card-add)

   POR QUÉ ESTÁN TODOS JUNTOS Y NO CADA UNO CON SU COMPONENTE: son trece, se
   revisan de una ojeada y se ve al momento cuál falta. Los seis de la primera tanda
   siguen junto a su componente — no se movieron para no tocar el orden de la
   cascada, que es lo único que decide entre reglas de la misma especificidad.

   ⚠️ LA REGLA QUE LIMITA ESTO, y por la que faltan tres: una zona invisible puede
   pisar TEXTO sin problema, pero si pisa OTRO CONTROL le roba los toques. Por eso
   el hueco del vecino manda:
     · `.chip` (36 + hueco 8), `.cat-nav-head` (40 + 2) y `.account-link-item`
       (42 + 2) llegan a 44 y sus zonas se TOCAN sin solaparse. Justo, pero entra.
     · `.cat-nav-sub` tiene 1px de hueco: llegar a 44 se comería 7px del vecino.
       Se queda en 36 — que cumple AA (24) de sobra. NO "arreglarlo" sin separar
       antes los renglones de verdad, que es un cambio que se ve.
     · Los enlaces del footer van a 11px unos de otros: 44 se comería 13. Ver abajo.
   ============================================================ */

/* --- Cuadrados: 44×44 centrado --- */
.social { position: relative; }
.social::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}
/* .icon-btn ya era position:relative (lo necesita su globo de cantidad). El ::after
   se pinta por encima del globo, pero es transparente y ambos son del mismo botón:
   el toque acaba donde tiene que acabar. */
.icon-btn::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px; transform: translate(-50%, -50%);
}

/* --- Anchos: alto 44, todo el ancho del control --- */
.co-seg-btn, .chip, .chip-sm, .variant-btn, .mini-btn, .open-filters,
.refresh-btn, .account-link, .account-link-item, .cat-nav-head,
.branch-map, .link-btn, .nav-dept-link, .m-link-sm {
  position: relative;
}
.co-seg-btn::after, .chip::after, .chip-sm::after, .variant-btn::after,
.mini-btn::after, .open-filters::after, .refresh-btn::after, .account-link::after,
.account-link-item::after, .cat-nav-head::after, .branch-map::after,
.link-btn::after, .nav-dept-link::after, .m-link-sm::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: 44px; transform: translateY(-50%);
}

/* --- El footer: lo que CABE, que no es 44 ---
   Los enlaces miden 20px y van a 11 unos de otros: la zona de 44 se comería 13px
   del enlace de arriba y del de abajo, y ahí sí se roban los toques (son todos
   clicables). Se estira a 30, que llena el hueco y deja 1px de aire.
   No es un apaño: **20px INCUMPLE el mínimo de WCAG AA (24×24); 30 lo cumple.**
   Los 44 son AAA. Llegar a 44 aquí obliga a separar los renglones a 24px de hueco
   —el footer crece bastante y SE VE—, así que es decisión suya, no mía. */
.footer-link, .footer-phone { position: relative; }
.footer-link::after, .footer-phone::after {
  content: ""; position: absolute; left: 0; right: 0; top: 50%;
  height: 30px; transform: translateY(-50%);
}

/* ── Consentimiento de cookies (Consent Mode v2) ──────────────────────────────
   Banner inyectado por consent.js SOLO si no hay elección previa. Colores de marca,
   sin modo oscuro (el sitio no lo tiene). z-index 2000: por debajo de los modales
   (2100) para que no los tape si coincidieran. */
.consent {
  position: fixed; z-index: 2000;
  left: 50%; transform: translateX(-50%);
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  width: min(680px, calc(100vw - 32px));
  display: flex; align-items: center; gap: 16px;
  padding: 15px 18px;
  background: var(--card); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow-lg);
  animation: consent-in var(--dur-lg) var(--ease-out);
}
/* El banner es un AVISO de interfaz, así que va en el mismo papel que los avisos
   del checkout y del login (`--t-ui`, `--lh-ui`): eran los dos únicos números
   sueltos que quedaban en la hoja (`.9rem` = 14,4 px y un 1.45 a mano, que es
   exactamente `--lh-ui`). Y los BOTONES llevaban `font: inherit`, o sea 16 px —
   más grandes que el texto que contestan, y el único par de botones del sitio
   fuera de `--t-ui`. 24 jul 2026. */
.consent-txt { margin: 0; font-size: var(--t-ui); line-height: var(--lh-ui); }
.consent-txt a { color: var(--brand); text-decoration: underline; }
.consent-btns { display: flex; gap: 8px; flex-shrink: 0; }
.consent-btn {
  font: inherit; font-size: var(--t-ui); font-weight: 600; cursor: pointer; white-space: nowrap;
  /* 12 y no 10: al bajar la letra de 16 a 13 el botón habría perdido 4 px de alto
     (43 → 39). El relleno los repone, así que la CAJA mide lo mismo que antes y
     solo cambia la letra. La regla de siempre: la zona no se recorta sin querer. */
  padding: 12px 18px; border-radius: var(--r-pill);
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  transition: transform var(--dur-xs) var(--ease-out), background var(--dur-xs) var(--ease-out);
}
.consent-btn:active { transform: scale(.96); }
.consent-si { background: var(--brand); border-color: var(--brand); color: #fff; }
.consent-si:hover { background: var(--brand-deep); border-color: var(--brand-deep); }
@keyframes consent-in { from { opacity: 0; transform: translate(-50%, 14px); } to { opacity: 1; transform: translate(-50%, 0); } }
@media (max-width: 560px) {
  .consent { flex-direction: column; align-items: stretch; gap: 12px;
             bottom: calc(8px + env(safe-area-inset-bottom, 0px)); }
  .consent-btns { justify-content: flex-end; }
  .consent-btn { flex: 1; }
}
@media (prefers-reduced-motion: reduce) { .consent { animation: none; } }
