/* MiratechSpA — Landing page styles (Inicio.html)
   Built on design-system tokens + store/store.css chrome. */

/* ===== Scroll reveal =====
   Fade + slide-up sutil al entrar en pantalla (ver scroll-reveal.js). El estado
   OCULTO lo pone el propio script vía [data-reveal-listo] (una clase que solo
   existe si el JS corrió) — no un [data-reveal] a secas: si CSS ocultara la
   sección desde el HTML estático y el script no llegara a correr (JS
   deshabilitado, error de red), quedaría invisible para siempre. Con el gate
   en el script, sin JS la sección simplemente aparece normal, sin animación —
   degrada con seguridad en vez de romper el contenido. */
[data-reveal][data-reveal-listo] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .7s var(--ease-out), transform .7s var(--ease-out);
}
[data-reveal][data-reveal-listo].is-revealed {
  opacity: 1;
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal][data-reveal-listo] {
    opacity: 1; transform: none; transition: none;
  }
}

/* ===== Superficie de la primera pantalla =====
   UNA sola definición de degradado, compartida por el navbar de la landing
   (.site-header.hd-dark) y por el slide de hero del carrusel
   (.carrusel-slide--hero). Las dos la pintan con el mismo background-size
   (--portada-alto) y el mismo anclaje (top), así que juntas se leen como una
   superficie continua en vez de dos capas que se topan.

   VERTICAL (180deg), no diagonal: con un degradado diagonal el color de cada
   punto depende también del ancho, y el navbar —que es una franja de 74px que
   arranca 74px más arriba que el hero— no puede calzar en todo el ancho, solo
   en un punto (medido: hasta 9 puntos de diferencia hacia los bordes, la línea
   que se notaba). Vertical, el color depende solo de la altura y ambas piezas
   coinciden en toda su extensión.

   --portada-alto es la escala del degradado, no la altura de ningún elemento:
   navbar (74px) + hero (~520px) más holgura. Si cambia sustancialmente el alto
   de la primera pantalla, ajustar acá y las dos piezas siguen calzando. */
:root {
  --portada-alto: 620px;
  --portada-degradado: linear-gradient(
    180deg,
    #0A1B29 0%,
    var(--marine-800) 22%,
    var(--marine-700) 46%,
    var(--marine-600) 72%,
    var(--marine-500) 100%
  );
}

/* ===== Dark header variant =====
   Opaco SIEMPRE (nunca semitransparente): el header vive ANTES del carrusel
   dentro de .portada-primera, así que al cargar está sobre el SLIDE ACTIVO —
   que puede ser un banner de imagen clara. Con opacidad parcial se veía
   lavado/casi blanco sobre esas piezas. Opaco es consistente sin importar qué
   slide esté visible, y es lo que permite integrarlo con el hero sin riesgo.

   El fondo continúa el degradado del hero, que ahora es VERTICAL (ver
   .carrusel-slide--hero y --portada-degradado más abajo). Vertical es lo que
   hace posible la integración: con un degradado diagonal el color de cada
   punto depende del ancho, así que un navbar plano calzaba solo en el centro
   y se despegaba hasta 9 puntos hacia los bordes (medido con Playwright a lo
   ancho: el slide iba de rgb(18,44,63) a la izquierda a rgb(25,55,78) a la
   derecha) — justo la línea que se notaba. Con el degradado vertical el color
   solo depende de la altura, y ambas superficies comparten la misma escala
   (--portada-alto) y el mismo origen (top), así que calzan en TODO el ancho.

   La franja del navbar es la parte de arriba de esa superficie y el hero es
   el resto: una sola pieza continuada, no dos capas que se topan. El navbar
   sigue OPACO, que es lo que lo mantiene legible cuando el carrusel rota a un
   banner de imagen clara. */
.site-header.hd-dark {
  background: var(--portada-degradado);
  background-size: 100% var(--portada-alto);
  /* Desplazado la altura de la utility bar: el header no arranca en y=0, la
     utility bar va encima, así que le toca la franja del degradado que empieza
     en --util-bar-height. El hero sigue desde util-bar + header (ver
     .carrusel--portada .carrusel-slide--hero más abajo). */
  background-position: center calc(-1 * var(--util-bar-height));
  background-repeat: no-repeat;
  /* border-bottom:none explícito, no basta con omitirlo: store.css le da al
     header un borde base gris claro (--border-subtle, rgb(221,228,234)) para
     el header blanco, y sobre el navy quedaba MÁS marcado que la línea blanca
     al 8% que este cambio venía a quitar (confirmado con getComputedStyle). */
  border-bottom: none;
}
/* Difuminado inferior propio del header, no del contenido de abajo: 24px de
   navy a transparente que suavizan el borde contra lo que venga debajo. La
   clave es que NO depende de qué slide del carrusel esté activo — el header
   sigue siendo opaco y el fade es suyo, así que funciona igual con el hero de
   texto y con un banner de imagen clara (el caso que rompía las versiones
   translúcidas anteriores: navbar lavado casi blanco sobre pieza clara).
   pointer-events:none para no robarle clics al carrusel que queda debajo. */
.site-header.hd-dark::after {
  content: '';
  position: absolute; left: 0; right: 0; top: 100%;
  height: 24px; pointer-events: none;
  background: linear-gradient(180deg, rgba(17, 42, 61, 0.5), rgba(17, 42, 61, 0));
  opacity: 0;
  transition: opacity 240ms var(--ease-out, ease-out);
}
/* El fade aparece al scrollear y reemplaza a la box-shadow dura: el header
   "se asienta" sobre el contenido en vez de marcar una línea. Motivo concreto
   (no decoración): comunica el cambio de estado tope-de-página vs scrolleado,
   que es justo lo que antes decía la sombra. */
.site-header.hd-dark.scrolled::after { opacity: 1; }
/* store.css le pone al header scrolleado una box-shadow azul al 8% pensada
   para el header BLANCO normal; sobre el navy de la landing esa sombra clara
   no comunica nada y ensucia el borde justo donde ahora vive el fade. Acá el
   estado de scroll lo dice el ::after de arriba, así que se anula. */
.site-header.hd-dark.scrolled { box-shadow: none; }
.site-header.hd-dark .brand img { filter: brightness(0) invert(1); }
.site-header.hd-dark nav.links a:not(.nav-tienda) { color: var(--slate-300); }
.site-header.hd-dark nav.links a:hover,
.site-header.hd-dark nav.links a.active { color: #fff; }
.site-header.hd-dark nav.links a::after { background: var(--steel-300); }
.site-header.hd-dark .cart-btn { background: rgba(255, 255, 255, 0.12); border: 1px solid rgba(255, 255, 255, 0.18); }
.site-header.hd-dark .cart-btn:hover { background: rgba(255, 255, 255, 0.2); }
.site-header.hd-dark .acct-btn { border-color: rgba(255, 255, 255, 0.28); color: #fff; }
.site-header.hd-dark .acct-btn:hover { border-color: rgba(255, 255, 255, 0.6); background: rgba(255, 255, 255, 0.08); color: #fff; }
/* .nav-burger (icono hamburguesa, solo visible bajo 880px) se quedaba con su
   color fijo de store.css (--text-strong, oscuro) sin importar el header — sobre
   el navy transparente de hd-dark quedaba un ícono oscuro sobre fondo oscuro,
   casi invisible (reportado por Emmanuel). Mismo criterio que el logo/links/
   carrito/cuenta de arriba: blanco sobre hd-dark. */
.site-header.hd-dark .nav-burger { color: #fff; }
.site-header.hd-dark .nav-burger:hover { background: rgba(255, 255, 255, 0.12); }
/* Bajo 900px (ver store.css) el botón de cuenta invitado deja de ser transparente
   y pasa a un círculo con fondo blanco sólido — pensado para el header claro
   normal. Sobre el header azul (hd-dark) esa regla de arriba seguía forzando
   color:#fff, y un ícono blanco sobre un botón blanco se veía como un círculo
   vacío (el bug real detrás del "botón de cuenta roto en mobile"). Acá se
   restaura el color de marca que store.css ya le puso al fondo blanco. */
@media (max-width: 900px) {
  .site-header.hd-dark .acct-btn:not(.is-logged) { color: var(--steel-600); }
}

/* Navbar sobre la capa de fondo animada (.bg-animada, ver más abajo): mientras
   el header sigue en su estado inicial (sin scroll, html.bg-animada-on activo)
   se vuelve REALMENTE transparente (sin tinte de color propio) para dejar ver
   la misma capa de fondo que se ve en el hero, ni un azul "parecido" — dos
   intentos anteriores (tinte a .35, luego a .16 + blur) seguían sumando un
   azul propio sobre el azul de la animación, y el resultado se leía como dos
   tonos distintos entre navbar y hero (reportado por Emmanuel marcando la
   franja en dos capturas seguidas: "son azules distintos"). Sin tinte ni
   blur, el navbar dejó de pintar nada propio: lo que se ve ahí es literalmente
   .bg-animada, igual que en el hero de abajo. En cuanto aparece .scrolled
   vuelve a su navy opaco de siempre — el mismo estado que ya existía, sin
   tocarlo — porque a esa altura la capa fixed ya se ocultó (ver
   html.bg-animada-on en base.html) y quedaría tapando el navy sólido si el
   navbar siguiera transparente.

   :not(.banner-activo) — clase que pone carrusel.js en el header cuando el
   slide activo deja de ser el hero (rotó a un banner de imagen real): el
   navbar debe oscurecerse ahí también, no solo al hacer scroll (pedido de
   Emmanuel: "al cambiarse de banner, debe cambiar el estilo del navbar
   también a color oscuro"). La transición la da .site-header (background/
   backdrop-filter 320ms, ver store.css), así que el cambio de estado —por
   scroll o por banner— siempre es un fundido, nunca un corte. */
html.bg-animada-on .site-header.hd-dark:not(.scrolled):not(.banner-activo) {
  background: transparent;
  /* store.css (.site-header base) le pone backdrop-filter:blur(14px)+saturate
     al header, pensado para el header BLANCO normal (difumina lo que scrollea
     debajo). Sobre .bg-animada ese blur aplastaba la retícula de puntos y las
     auroras hasta volverlas un color casi plano — el navbar se leía sólido
     aunque su background ya fuera transparente de verdad (backdrop-filter no
     se ve ni en backgroundColor ni en elementsFromPoint, por eso costó
     encontrarlo: Emmanuel insistía en que "sigue exactamente igual" tras
     reposicionar la aurora, y tenía razón — el blur volvía irrelevante
     cualquier cambio de posición). */
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
/* Los botones (.cart-btn, .acct-btn) SÍ conservan su pastilla translúcida
   blanca de siempre (rgba(255,255,255,.12) etc., ver arriba): son controles,
   necesitan leerse como superficie interactiva encima del fondo — a
   diferencia del header en sí, que ahora no pinta nada propio. */

/* ===== Hero =====
   Solo la RAÍZ queda navy (lo que asoma en el overscroll elástico por arriba, antes
   de que el <body> empiece a pintar). El <body> mismo YA NO se tiñe global: antes
   heredaban ese navy todas las secciones sin fondo propio (destacados, necesidad,
   marcas), así que buena parte de la landing se leía oscura sin que ninguna regla lo
   pidiera a propósito — la causa real de "demasiado azul oscuro" en la 2ª iteración.
   .portada-primera ya pinta su propio fondo (var(--marine-800) más abajo), así que
   basta con que el body vuelva a su blanco de siempre. */
html:has(.site-header.hd-dark) { background: #0A1B29; }

/* ===== Primera pantalla: min-height moderno, sin reparto rígido =====
   Antes: banner y hero eran dos bloques separados que se repartían el alto de la
   ventana (--hero-alto = "lo que sobra después del banner"). Fusionados en un solo
   carrusel (slides secuenciales, uno visible a la vez — ver carrusel.html), ya no
   hay dos bloques que repartir: hay un track que mide su contenido real
   (.carrusel-medida-stack en carrusel.css, toma el más alto entre el hero de texto
   y el banner de imagen) y un mínimo garantizado para que la primera pantalla no
   sea una franja angosta.

   El techo NO persigue "casi toda la pantalla" (min(100svh-header, 900px) se probó
   y dejaba el hero de texto —480px de contenido natural— centrado en un espacio
   ~794px, con aire vacío notorio arriba/abajo: verificado con Playwright, no es
   protagonismo, es un vacío). El valor real (--hero-techo, hoy 540px) se calibró
   a la escala natural del contenido (banner real: 480px de alto; hero de texto:
   480px) más un margen de aire — es una hipótesis validada visualmente, no una
   fórmula matemática cerrada: si el copy del hero cambia sustancialmente o el
   arte de los banners cambia de proporción, revalidar con capturas reales antes
   de tocar este número a ciegas.

   Sigue siendo un PISO (min-height), no una caja fija: si el hero necesita más
   espacio que este mínimo (viewport angosto, copy largo), el track crece con su
   contenido real — nunca se corta, nunca hay overflow.

   Al sumar la trust bar del hero (ver .hero-stats), el hero real (~482px) quedó
   MÁS ALTO que el banner (~443px con la pieza 2400×640 actual) — antes del hero
   más alto que el techo era al revés. --hero-techo (540px) ya no es la escala
   natural del hero, es un margen sobre ella, y con el PISO puesto en los dos
   (.portada-primera Y .carrusel-track a la vez) el hero llegaba a 540px de
   contenedor con solo 482px de contenido: 58px de aire de más, encima del
   sobrante propio del banner (443px) — dos franjas vacías sumándose en vez de
   una. .portada-primera no necesita su propio piso: es un simple wrapper del
   carrusel, así que basta con que SIGA al alto real que el track termine
   ocupando (el fondo navy es solo un respaldo por si el stack de medida diera
   0 — caso borde que no debería ocurrir con el hero siempre presente). El único
   PISO real vive en el track, y ahí sí sigue aplicando —evita una franja
   angosta en monitores anchos y bajos—, pero ahora referenciado contra el alto
   real del hero (ver --hero-techo abajo, recalculado tras la trust bar). */
/* Comparte el degradado vertical con el navbar de la landing (ver
   --portada-degradado arriba): .portada-primera es lo que REALMENTE toca el
   navbar (el slide de hero puede estar mucho más abajo, medido en top:627 a
   1920px, con otro slide del carrusel entre ambos), así que la continuidad se
   resuelve acá, no en el hero. El navbar pinta la franja de 0 a
   util-bar+header de esta misma superficie y este bloque continúa desde ahí,
   con el mismo background-size, así que la costura es invisible en todo el
   ancho — el degradado es vertical justamente para que el color no dependa de
   la posición horizontal (con el diagonal anterior calzaba solo en un punto). */
.portada-primera {
  background: var(--portada-degradado);
  background-size: 100% var(--portada-alto);
  background-position: center calc(-1 * (var(--util-bar-height) + var(--header-height)));
  background-repeat: no-repeat;
  background-color: var(--marine-600);
}
/* Con el hero (slide 0) activo, .portada-primera también deja ver la capa de
   fondo animada (.bg-animada) detrás — mismo criterio que .carrusel-slide--hero
   más abajo. Al rotar a un banner de imagen (is-active se mueve a otro slide)
   este selector deja de matchear y .portada-primera recupera su degradado
   opaco de siempre, tapando la capa fija: la animación solo se ve con el hero
   de texto, nunca compitiendo contra el arte de un banner real. */
html.bg-animada-on .portada-primera:has(.carrusel-slide--hero.is-active) {
  background: none;
}
.carrusel--portada .carrusel-track {
  --hero-techo: 500px;
  min-height: min(calc(100svh - var(--util-bar-height) - var(--header-height)), var(--hero-techo));
}

@media (max-width: 880px) {
  /* Bajo 880px el mínimo se suelta: en pantallas angostas forzar casi toda la
     ventana para un carrusel apretaría demasiado el resto de la portada debajo.
     Ahí también la utility bar ya está oculta (ver store.css), así que el hero
     fluye a su alto natural sin ninguna resta. */
  .carrusel--portada .carrusel-track { min-height: 0; }
}

.hero-title-measure {
  font-size: clamp(34px, 4.2vw, 52px);
  font-weight: var(--fw-extra);
  letter-spacing: var(--ls-tight);
  line-height: 1.06;
  margin-top: 20px;
  color: #fff;
}

/* Todas las capas de la primera pantalla comparten --portada-degradado con el
   mismo background-size y anclaje, así que navbar + wrapper + cualquier slide
   activo se leen como UNA superficie continua.

   Hizo falta unificar las tres porque bajo el navbar no hay una sola capa:
   medido con Playwright, según ancho y slide activo puede tocarlo
   .portada-primera, el slide de banner (que traía su propio 165deg de
   carrusel.css) o el slide de hero (118deg diagonal con dos radiales). Cada
   una con su fondo propio, por eso cada arreglo parcial dejaba una costura
   distinta en otro punto.

   El selector lleva .carrusel--portada por especificidad: carrusel.css se
   carga después de este archivo y su .carrusel-slide (mismo peso) ganaba.
   La variante "tienda" del mismo componente (en /catalogo/) queda intacta. */
.carrusel--portada .carrusel-slide,
.carrusel--portada .carrusel-slide--hero {
  background: var(--portada-degradado);
  background-size: 100% var(--portada-alto);
  background-position: center calc(-1 * (var(--util-bar-height) + var(--header-height)));
  background-repeat: no-repeat;
  background-color: var(--marine-600);
}
.carrusel-slide--hero {
  position: relative;
  color: #fff;
  overflow: hidden;
  /* El fondo animado ahora lo da .bg-animada (fixed, compartida con el
     navbar — ver más abajo "Fondo animado compartido"): este slide deja de
     pintar su propio degradado/auroras para dejarla ver detrás. */
  background: none !important;
}
/* El fondo animado compartido (.bg-animada + capas .ba-*) que este slide deja
   ver detrás vive en store.css, no acá: .bg-animada es global (insertada en
   base.html, visible en cualquier página con header hd-dark), y store.css es
   la única hoja que TODAS esas páginas cargan siempre — landing.css solo lo
   carga home.html. Ver el bloque "Fondo animado compartido" en store.css. */

.hero-grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: 1fr;
  width: 100%;
  padding-top: 26px;
  /* Los dots del carrusel (.carrusel-dots, en carrusel.css) son absolute contra
     el track completo, bottom:18px + ~21px de alto — comunes a todos los slides,
     así que no se pueden mover solo para el hero sin descuadrar los banners de
     imagen. Antes de la trust bar el CTA terminaba lejos de ahí abajo y nunca
     chocaban; con .hero-stats el contenido real ya llega a ese borde inferior
     (confirmado con Playwright: se solapaban en 768px y 390px, y el bloque de
     cifras se salía 7px del propio track, recortado por overflow:hidden de
     .carrusel). Este padding es el que comparte hero_slide.html y su copia de
     medida (ambos usan .hero-grid), así que reservar aire acá sincroniza los dos
     con un solo cambio. */
  padding-bottom: 64px;
}
/* Se acota para no dejar el h1 en una línea de 1200px, pero ancho: el hero es una
   banda a lo largo, no una columna. Centrado en los dos ejes (decisión ya
   tomada): el slide es flex, así que basta con estos selectores directos, sin
   depender de .portada-primera como ancestro (el hero ya es siempre parte de la
   variante portada del carrusel, no se reusa suelto en otra pantalla). */
.hero-copy { max-width: 900px; margin-inline: auto; text-align: center; }
.carrusel-slide--hero .eyebrow,
.carrusel-medida--hero .eyebrow { color: var(--steel-200); justify-content: center; }
.carrusel-slide--hero .eyebrow::before,
.carrusel-medida--hero .eyebrow::before { background: var(--steel-300); }
/* Entre 681 y 880px las flechas del carrusel (.carrusel-arrow, absolute a 16px del
   borde) siguen visibles — se ocultan recién bajo 680px (carrusel.css) — pero el
   contenedor ya es angosto: el párrafo del hero, centrado y sin este padding,
   llegaba hasta casi tocarlas y en la práctica quedaba tapado por ellas (hallazgo
   real de Playwright a 768px, no una hipótesis). Los banners de imagen no tienen
   este problema porque no llevan texto ahí — es exclusivo del slide de hero. */
@media (min-width: 681px) and (max-width: 880px) {
  .hero-grid { padding-inline: 72px; }
}
.carrusel-slide--hero h1 {
  color: #fff;
  font-size: clamp(34px, 4.2vw, 52px);
  font-weight: var(--fw-extra);
  letter-spacing: var(--ls-tight);
  line-height: 1.06;
  margin-top: 20px;
}
/* .carrusel-medida--hero p.sub/.cta-row: mismas reglas que el slide real, aplicadas
   también a la copia de medida oculta. Sin esto, p.sub caía al font-size heredado
   genérico (16px) en vez de --fs-md (18px) real del slide — mismo texto, mismo
   ancho de caja, pero un tamaño de fuente distinto le cambiaba el wrap (2 líneas
   en la medida vs 3 en el real), y la copia de medida terminaba midiendo ~40px
   MENOS que el contenido real. Con overflow:hidden en .carrusel (más abajo), ese
   faltante se traducía en el CTA del hero real recortado silenciosamente en vez
   de aportar su altura real al track — la copia de medida solo sirve si es un
   calco fiel del slide real, no solo del texto/HTML, también de su tipografía. */
.carrusel-slide--hero p.sub,
.carrusel-medida--hero p.sub { font-size: var(--fs-md); color: var(--slate-300); max-width: 700px; margin: 18px auto 26px; line-height: var(--lh-relaxed); }
.carrusel-slide--hero .cta-row,
.carrusel-medida--hero .cta-row { display: flex; flex-wrap: wrap; gap: 14px; align-items: center; justify-content: center; }
.carrusel-slide--hero .btn-light { background: #fff; color: var(--brand-navy); box-shadow: 0 8px 28px rgba(7, 18, 28, 0.4); }
.carrusel-slide--hero .btn-light:hover { background: var(--slate-100); transform: translateY(-1px); }

/* Cifras reales bajo el CTA (ver hero_slide.html): reemplazan el segundo botón
   "Proveedores del Estado" que vivía en el hero — ese enlace ya está en la
   utility bar y en su propia sección #estado, un tercer lugar en el mismo
   viewport era redundante. n_productos es el único valor dinámico; los otros
   dos son compromisos de proceso fijos. */
.carrusel-slide--hero .hero-stats,
.carrusel-medida--hero .hero-stats {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 32px;
  margin-top: 34px; padding-top: 26px;
  border-top: 1px solid rgba(255, 255, 255, 0.16);
}
.hero-stat { display: flex; flex-direction: column; align-items: center; gap: 4px; }
/* La trust bar es información de apoyo, no un segundo titular. Tres pasos de
   atenuación acumulados a pedido de Emmanuel: fw-extra (800) -> semibold para
   que no compita con el H1, luego fs-xl (28px) -> fs-lg (22px) y el blanco
   puro -> slate-200 semitransparente, "que sean menos presentes, un poco más
   difuminado". El dato sigue legible; lo que baja es su peso en la jerarquía. */
.hero-stat-num {
  font-size: var(--fs-lg); font-weight: var(--fw-semibold);
  color: rgba(226, 232, 240, 0.82); letter-spacing: var(--ls-tight);
}
/* Antes en --font-mono + mayúsculas + tracking ancho: un tratamiento "técnico"
   de label fuerte que llamaba más la atención que el propio H1 (pedido:
   "toma mucha personalidad sobre el título"). Fuente normal (heredada, misma
   que el body), sin mayúsculas forzadas ni tracking extra — el color apagado y
   el tamaño chico ya bastan para leerse como apoyo, no como un segundo titular. */
.hero-stat-label {
  font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  color: var(--steel-200);
}



/* ===== Trust bar =====
   Responde "¿por qué comprar aquí?" en una sola franja compacta — reemplaza lo que
   antes era "Un socio para tu operación" en su propia section.land con 4 cards
   grandes: mismo mensaje (calidad/envío/precio/atención), sin otro bloque separado
   compitiendo visualmente con productos ni repitiendo el patrón título+cards. */
.trust-bar {
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--border-subtle);
}
.trust-row {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 10px 36px; padding: 16px 0;
}
.trust-item {
  display: inline-flex; align-items: center; gap: 9px;
  font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-body);
  white-space: nowrap;
}
.trust-item .lucide, .trust-item i { width: 17px; height: 17px; color: var(--steel-600); flex: none; }
@media (max-width: 720px) {
  /* justify-content seguía en flex-start (pensado para cuando esta fila no
     envolvía y necesitaba scroll horizontal) — con flex-wrap ya activo arriba,
     los 4 ítems envuelven en 2 filas de 2 sin necesitar ese scroll, y quedaban
     pegados a la izquierda en vez de centrados como el resto de la franja
     (reportado por Emmanuel, con captura). overflow-x:auto se retira: ya no
     hace nada útil con el contenido envuelto, y dejarlo no causa daño pero es
     código muerto. */
  .trust-row { gap: 10px 20px; padding: 12px 0; justify-content: center; }
  .trust-item { font-size: var(--fs-xs); }
}

/* ===== Section shells =====
   64px por defecto, pero cada sección pesa distinto (ver la jerarquía de escala:
   Hero/Productos/Mercado Público son los momentos principales, Categorías/
   Necesidad/Marcas son secundarios y llevan menos aire — .land--tight abajo). */
section.land { padding: 64px 0; }
section.land--tight { padding: 44px 0; }
/* El header es sticky: al saltar a un ancla (#faq, #contacto, #cotizacion) el
   título no debe quedar tapado por él. */
section[id] { scroll-margin-top: 88px; }
.sec-head { max-width: 640px; margin: 0 auto 54px; text-align: center; }
.sec-head.left { margin: 0 0 44px; text-align: left; max-width: none; }
.sec-head h2 { font-size: clamp(28px, 3.4vw, 40px); font-weight: var(--fw-bold); margin: 14px 0 12px; }
.sec-head p { color: var(--text-muted); font-size: var(--fs-md); }
/* Cabecera de sección en fila (título+intro a la izquierda, acción a la derecha):
   rompe el patrón "todo centrado" que se repetía sección tras sección. Usada por
   Categorías y Productos destacados, los dos "momentos" que llevan a la tienda. */
.sec-head-row {
  display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;
  margin-bottom: 32px;
}
.sec-head-row .sec-head { margin: 0; text-align: left; max-width: 560px; }
.sec-head-row .sec-head p { margin-top: 6px; }
/* Sin esto, en mobile el título+intro y el botón "Ver catálogo completo" se
   apretaban uno junto al otro en la misma fila (reportado por Emmanuel: "se ve
   mal", captura del bloque de Productos destacados) — el botón terminaba medio
   cortado contra el borde. align-items:flex-start (no center): con el botón
   angosto a lo ancho, centrarlo verticalmente contra un título de 2 líneas se
   ve descolgado; alineado arriba, junto al eyebrow, se lee mejor. */
@media (max-width: 640px) {
  .sec-head-row { flex-direction: column; align-items: flex-start; gap: 16px; }
  .sec-head-row .btn { align-self: stretch; justify-content: center; }
}

/* ===== Categorías: puerta de entrada editorial, no un grid de cards =====
   Con solo 1-2 categorías reales, un grid centrado deja un vacío enorme a los
   lados (era exactamente la queja: "demasiado espacio para el contenido
   disponible"). En vez de eso: una franja compacta, fondo claro casi blanco (no
   el degradado azul de antes — reservamos el azul para momentos importantes), con
   la categoría principal como bloque grande a la izquierda y el resto como lista
   compacta a la derecha. Escala con cualquier número de categorías: 1 sola se ve
   igual de bien que 4. */
section.cats {
  background: var(--surface-page);
  border-bottom: 1px solid var(--border-subtle);
}
.cat-door {
  display: grid; grid-template-columns: 1.3fr 1fr; gap: 28px;
}
/* Rediseño: texto sobre foto (versión anterior) quedaba con contraste variable
   según la imagen de turno — "Papelería" se leía débil sobre ciertas piezas de
   producto claras (reportado por Emmanuel: "el contenido no se ve tan bien").
   Dos zonas fijas en vez de una encima de la otra: texto en un panel sólido de
   marca (contraste garantizado, no depende de la foto) + media aparte con el
   mini-carrusel de productos reales. Sin imágenes disponibles, la media
   colapsa (grid-template-columns se ajusta) y el texto ocupa todo el ancho —
   mismo criterio de "no rellenar con nada artificial" que el resto del sitio. */
.cat-door-main {
  position: relative; overflow: hidden;
  display: grid; grid-template-columns: 1fr;
  min-height: 260px;
  border-radius: var(--radius-xl);
  color: #fff;
}
.cat-door-main.tiene-imagenes { grid-template-columns: 1fr 1.15fr; }
.cat-door-texto {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; justify-content: center;
  padding: 32px;
  background: linear-gradient(150deg, var(--marine-800) 0%, var(--marine-600) 70%, var(--steel-500) 130%);
}
/* Firma visual sutil, propia de esta pieza (no la misma retícula de puntos del
   hero/Mercado Público): líneas finas horizontales espaciadas como en un casillero
   de inventario — la idea de "organización/logística" del brief, más literal que
   decorativa. */
.cat-door-texto::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: repeating-linear-gradient(180deg, rgba(255,255,255,.06) 0 1px, transparent 1px 17px);
  mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,.5) 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0,0,0,.5) 100%);
}
.cat-door-main .c-eyebrow {
  position: relative; z-index: 1;
  font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wider); text-transform: uppercase; color: var(--steel-200);
}
.cat-door-main h3 { position: relative; z-index: 1; color: #fff; font-size: clamp(24px, 2.6vw, 32px); font-weight: var(--fw-bold); margin: 10px 0 8px; }
.cat-door-main p { position: relative; z-index: 1; color: var(--slate-300); font-size: var(--fs-sm); max-width: 380px; margin-bottom: 20px; }
.cat-door-main .c-go {
  position: relative; z-index: 1;
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: var(--fw-semibold); font-size: var(--fs-sm); color: #fff;
}
.cat-door-main .c-go .lucide, .cat-door-main .c-go i { width: 16px; height: 16px; transition: transform .25s var(--ease-out); }
.cat-door-main:hover .c-go i, .cat-door-main:hover .c-go .lucide { transform: translateX(5px); }

/* Mini-carrusel de fondo: cross-fade puro CSS, sin controles (es la pieza
   visual de la card, no un carrusel interactivo aparte). Cada <img> ocupa toda
   la celda (grid-area 1/1, mismo truco que .carrusel-medida-stack).

   Ciclo de duración FIJA (20s = 5 turnos de 4s), calibrado contra el máximo
   real de imágenes que trae la vista (_imagenes_destacadas_categoria(...,
   maximo=5) en apps/core/views.py) — con menos de 5 imágenes reales
   simplemente no existen los <img> de más, así que sus turnos del ciclo no
   los ocupa nadie. El keyframe describe un ÚNICO turno visible (2%-18% del
   ciclo de 20s), en opacity:0 el resto — todas las <img> comparten ese mismo
   keyframe, y animation-delay POSITIVO (múltiplo de 4s, escalonado por --i)
   retrasa cuándo arranca cada una: la Web Animations API define
   active_time = currentTime - delay, así que con delay positivo la imagen i
   "ve" su ventana visible del keyframe recién cuando el reloj real llega a su
   franja de 4s correspondiente (confirmado leyendo Animation.effect.getTiming()
   en un navegador real, no solo razonado a mano: con delay NEGATIVO —intento
   previo— el active_time queda adelantado y todas las imágenes salvo la
   primera terminan siempre fuera de su ventana visible, nunca llegan a
   opacity:1). Si el máximo de imágenes en views.py cambia, hay que rehacer la
   duración total (nuevo_máximo × 4s), el delay por imagen sigue en +N×4s. */
.cat-door-media { position: relative; overflow: hidden; background: #fff; }
.cat-door-media img {
  grid-area: 1 / 1; position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* contain, no cover: fotos de producto reales (sobre todo las de
     marketplace, con varias piezas distribuidas por toda la composición) no
     tienen margen que sacrificar — con cover, un marco tan apaisado
     (~1.15:1) recortaba contenido real por ambos costados de una foto casi
     cuadrada (799×691 en el caso de la resma equalit). contain siempre
     muestra la pieza completa; el fondo blanco de .cat-door-media (arriba)
     evita que el aire sobrante se note como una franja de otro color, ya
     que estas fotos de producto casi siempre traen fondo blanco propio. */
  object-fit: contain;
  opacity: 0;
  animation: catDoorFade 20s ease-in-out infinite;
  animation-delay: calc(var(--i) * 4s);
}
@keyframes catDoorFade {
  0%   { opacity: 0; }
  2%   { opacity: 1; }
  18%  { opacity: 1; }
  20%  { opacity: 0; }
  100% { opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cat-door-media img { animation: none; opacity: 0; }
  .cat-door-media img[style*="--i: 0"] { opacity: 1; }
}
@media (max-width: 880px) {
  .cat-door-main.tiene-imagenes { grid-template-columns: 1fr; }
  .cat-door-media { min-height: 200px; }
}

/* Secundarias: lista compacta, no cards — un divisor fino entre cada una en vez de
   bordes/sombras propias, así no compiten visualmente con la pieza principal. */
.cat-door-list { display: flex; flex-direction: column; }
.cat-door-item {
  display: flex; align-items: center; gap: 16px;
  padding: 20px 4px; border-bottom: 1px solid var(--border-subtle);
  transition: gap .25s var(--ease-out);
}
.cat-door-list .cat-door-item:first-child { padding-top: 0; }
.cat-door-item:hover { gap: 20px; }
.cat-door-item .c-ico {
  flex: none; width: 44px; height: 44px; border-radius: 12px;
  background: var(--surface-accent-soft); color: var(--steel-600);
  display: grid; place-items: center; transition: background .25s, color .25s;
}
.cat-door-item:hover .c-ico { background: var(--brand-navy); color: #fff; }
.cat-door-item .c-ico .lucide, .cat-door-item .c-ico i { width: 20px; height: 20px; }
.cat-door-item .c-body { flex: 1; min-width: 0; }
.cat-door-item h4 { font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--text-strong); margin-bottom: 3px; }
.cat-door-item p { font-size: var(--fs-xs); color: var(--text-muted); }
.cat-door-item .lucide.chev, .cat-door-item i.chev { flex: none; width: 16px; height: 16px; color: var(--text-subtle); }
/* Si solo hay una categoría secundaria (o ninguna), la lista queda vertical y
   centrada con la principal en vez de un hueco vacío debajo. */
.cat-door-list:empty { display: none; }
.cats-cta { display: flex; justify-content: flex-start; margin-top: 28px; }
@media (max-width: 880px) {
  .cat-door { grid-template-columns: 1fr; }
  .cats-cta { justify-content: stretch; }
  .cats-cta .btn { width: 100%; justify-content: center; }
}

/* ===== Productos destacados: el momento principal de la home ecommerce =====
   Reusa la estructura de .p-card/tienda.css (mismo botón de agregar, mismo hover,
   cero lógica duplicada) pero con .p-card--feature encima: imagen más grande, tipo
   más grande, precio con más peso. En el catálogo la tarjeta es densa (3-4 por fila
   apretadas, para comparar muchas); acá son 4 protagonistas, no una previsualización
   en miniatura — "esta es una tienda", no una vitrina decorativa. */
/* Carrusel con centro resaltado y desplazamiento real (translateX vía JS sobre
   .dc-track, mismo mecanismo que .brands-marquee — no reasignar tamaño/opacidad
   a tarjetas fijas, que se veía con saltos porque 3-4 elementos cambiaban a la
   vez en lugar de deslizarse, reportado por Emmanuel). .dc-viewport recorta lo
   que no cabe; .dc-track mide TODOS los slots (incluidos los 2 clones de borde
   para el loop infinito) en fila, sin justify-content — el JS calcula el
   translateX exacto para centrar el slot activo cada vez. */
.dc-carrusel { position: relative; display: flex; align-items: center; gap: 12px; }
/* padding-top calculado, no adivinado: con scale(1.06) y transform-origin
   bottom center, el crecimiento vertical real de la card centro (altura base
   ~581px medida con Playwright) es (1.06-1)*581 ≈ 35px hacia arriba — 28px de
   margen no alcanzaba a cubrirlo del todo (corte residual de ~9px). 40px deja
   margen de sobra. Si el ancho/alto base de .p-card--feature cambia, esta
   cuenta hay que revisarla (34.9px es específico al tamaño actual). */
.dc-viewport { flex: 1; overflow: hidden; padding: 40px 4px 32px; }
/* transition acá, no solo en .dc-slot: el elemento que el JS mueve de verdad
   es .dc-track (translateX vía style.transform, ver destacados-carrusel.js) —
   sin esta línea el desplazamiento saltaba de golpe sin animar en absoluto
   (confirmado con Playwright: transform idéntico en 6 muestras de 80ms tras
   el click, sin ningún paso intermedio). El JS anula esta transición con
   style.transition="none" para el posicionamiento inicial y el salto
   invisible al cruzar el borde del loop infinito. */
/* .7s (antes .5s) — pedido explícito: la animación de escala/desplazamiento
   se sentía poco fluida, y una transición más lenta con la misma curva
   ease-out se percibe más suave. El track y cada .dc-slot comparten la MISMA
   duración a propósito, para que el desplazamiento de posición y el cambio
   de tamaño del centro lleguen exactamente juntos. */
.dc-track { display: flex; align-items: center; gap: 24px; will-change: transform; transition: transform .7s var(--ease-out); }
.dc-track[style*="none"] { transition: none; }
.dc-slot {
  flex: 0 0 auto; width: 300px;
  /* transform-origin left/center por defecto crecía el centro hacia arriba Y
     hacia los lados desde el punto medio de la card — con poco padding arriba
     en .dc-viewport, la mitad superior del scale se salía del overflow:hidden
     y se veía cortada (reportado por Emmanuel). "bottom center" ancla el
     crecimiento hacia arriba solamente, dejando la base de la card fija (se
     alinea con las de al lado) y todo el crecimiento extra sube dentro del
     padding ya reservado. */
  transform-origin: bottom center;
  transition: transform .7s var(--ease-out), opacity .4s var(--ease-out);
}
/* El centro se resalta SOLO con tamaño (scale) + borde/sombra — sin cambio
   de color de fondo. Se probó con el body en navy de marca (varios tonos),
   pero el fundido de color a mitad de camino (blanco→gris→navy) se leía como
   un parpadeo feo durante el desplazamiento — reportado por Emmanuel: "la
   animación de cambio es muy fea... parpadea entre que el antiguo azul
   cambia de color". Revertido a blanco, con el acento en el borde/sombra
   como antes de ese experimento.

   width FIJO (300px, igual que el resto) — NO 340px: un ancho distinto para
   el centro cambia el layout real del flex (afecta cuánto miden sus vecinos
   en el track), y mezclar eso con una transición de posición fluida es
   exactamente lo que impedía que el resaltado se transfiriera EN PARALELO
   con el movimiento. Con solo scale() (que no afecta el layout, solo el
   tamaño visual), el resaltado transiciona sin desincronizarse nunca. */
.dc-slot[data-dc-pos="centro"] {
  transform: scale(1.06); z-index: 1;
  background: #fff;
  border-color: var(--steel-300); box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.dc-slot[data-dc-pos="izq"],
.dc-slot[data-dc-pos="der"] { opacity: .78; }
.dc-slot[data-dc-pos="izq"]:hover,
.dc-slot[data-dc-pos="der"]:hover { opacity: 1; }
/* Slots sin regla propia: solo se les asigna "centro"/"izq"/"der" a los 3
   vecinos inmediatos del activo (ver pintarPosiciones() en
   destacados-carrusel.js) — el resto queda en "oculto", visible SOLO como
   opacity:0 (NO width:0/flex-basis:0 como una versión anterior de esta
   regla): destacados-carrusel.js calcula cuánto mover el track a partir del
   offsetLeft real del slot activo, y con algunos slots midiendo 0px y otros
   300px ese offset quedaba comprimido — el track apenas se desplazaba unos
   px entre un paso y el siguiente aunque el CONTENIDO cambiara entero de
   golpe, así que el movimiento se sentía como un salto en vez de un
   deslizamiento (reportado dos veces por Emmanuel: "el desplazamiento sigue
   siendo muy golpeado, instantáneo, no es suave"). Con TODOS los slots
   ocupando siempre su ancho real (300px), cada paso recorre exactamente esa
   distancia (+ el gap) sin importar cuántos estén ocultos — .dc-viewport ya
   los recorta con overflow:hidden, así que no hace falta compactarlos. */
.dc-slot[data-dc-pos="oculto"] {
  opacity: 0; pointer-events: none;
}
/* .p-card (tienda.css) trae "animation: cardIn .45s both": el fade de entrada
   no aporta nada dentro de este carrusel (el JS ya controla qué se ve), y
   competía con las transiciones de opacity de arriba. */
.dc-slot { animation: none; border-color: transparent; box-shadow: none; }
.dc-slot:hover { border-color: var(--steel-200); }
.dc-slot .p-thumb { padding: 10%; }
.dc-slot .p-body { padding: 20px 20px 22px; }
.dc-slot .p-meta { font-size: var(--fs-xs); margin-bottom: 9px; }
.dc-slot h3 { font-size: var(--fs-base); min-height: 44px; margin-bottom: 10px; }
.dc-slot .p-price { font-size: var(--fs-xl); }
.dc-slot .p-price-row { margin: 14px 0 4px; }
.dc-slot .p-add { padding: 13px; font-size: var(--fs-base); }
.dc-arrow {
  flex: none; display: grid; place-items: center;
  width: 44px; height: 44px; border-radius: var(--radius-pill);
  border: 1px solid var(--border-default); background: var(--surface-card);
  color: var(--steel-600); cursor: pointer;
  transition: border-color .2s, transform .2s, box-shadow .2s;
}
.dc-arrow:hover { border-color: var(--steel-400); box-shadow: var(--shadow-md); }
.dc-arrow .lucide, .dc-arrow i { width: 20px; height: 20px; }
/* Con 1 solo producto real no hay nada que rotar — mismo criterio que el
   carrusel de banners (ver carrusel.js): sin flechas cuando no aplican. */
.dc-carrusel[data-dc-un-solo] .dc-arrow { display: none; }
@media (max-width: 1080px) {
  /* Bajo 1080px no cabe el trío completo con el centro agrandado sin apretar:
     1 sola tarjeta visible (siempre la destacada), rotando igual que desktop.
     Las flechas pasan a overlay absolute sobre .dc-carrusel (mismo patrón que
     .carrusel-arrow del hero) en vez de competir por ancho con el track —
     compitiendo dejaban muy poco espacio real a la tarjeta y esta terminaba
     desbordando su contenedor (bug ya resuelto en la iteración anterior, se
     mantiene la misma solución). */
  .dc-carrusel { display: block; position: relative; }
  .dc-viewport { width: 100%; }
  .dc-arrow {
    position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
    background: rgba(255,255,255,.92); box-shadow: var(--shadow-md);
  }
  .dc-arrow.prev { left: 4px; }
  .dc-arrow.next { right: 4px; }
  /* El diseño de mover() en destacados-carrusel.js depende de que TODOS los
     slots no-centro mantengan su ancho REAL (300px, ver la nota junto a
     [data-dc-pos="oculto"] más arriba): así cada avance recorre siempre la
     misma distancia real en el track, sin importar cuántos slots ocultos haya
     antes. display:none en izq/der (como estaba acá) los saca del flujo —
     dejan de aportar ese ancho — mientras que "oculto" seguía aportándolo:
     esa asimetría hacía que el offsetLeft del nuevo centro, en cada avance,
     cambiara casi lo mismo que el translateX que el JS aplicaba para
     compensarlo, y el resultado neto en pantalla era casi cero movimiento
     visible (medido con Playwright: la card "centro" quedaba en el mismo
     x/left/right antes y después del click — reportado por Emmanuel como "se
     ve quieto"). Fix: izq/der pasan a opacity:0 (mismo criterio que oculto),
     no display:none — todos los no-centro vuelven a aportar ancho real por
     igual, consistente con desktop. */
  .dc-slot[data-dc-pos="centro"] { width: min(88vw, 340px); transform: none; }
  .dc-slot[data-dc-pos="izq"],
  .dc-slot[data-dc-pos="der"] { opacity: 0; pointer-events: none; }
}
@media (max-width: 560px) {
  .dc-viewport { padding: 8px 0 26px; }
  .dc-arrow { width: 38px; height: 38px; }
  .dc-arrow .lucide, .dc-arrow i { width: 17px; height: 17px; }
}

/* ===== Descubrimiento por necesidad =====
   Bento de 3 piezas: la primera ocupa 2 columnas y lleva el ícono/copy más grande, no
   son tres cards idénticas — la intención "equipar una oficina" es la más genérica y
   la que más gente busca primero, así que se lleva el protagonismo visual. */
.need-bento {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;
}
.need-card {
  position: relative; display: flex; flex-direction: column;
  background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg);
  padding: 30px 28px;
  box-shadow: 0 1px 2px rgba(17,42,61,.04), 0 10px 26px rgba(17,42,61,.07);
  transition: transform .3s var(--ease-out), box-shadow .3s;
  /* Dos cards son <a>, una es <button> (abre el modal de cotización en vez de
     navegar): un <button> no hereda text-align/font/cursor de un link, y sin
     este reset se veía descuadrada frente a sus vecinas (reportado por
     Emmanuel). */
  width: 100%; text-align: left; font: inherit; color: inherit; cursor: pointer;
}
.need-card:hover { transform: translateY(-6px); box-shadow: 0 16px 38px rgba(17,42,61,.12); border-color: var(--steel-200); }
/* Acento propio por card chica (antes las dos eran gemelas: mismo blanco, mismo
   ícono en cuadrito celeste — se leían genéricas una junto a la otra, reportado
   por Emmanuel). Cada acento anticipa el color de la sección a la que lleva:
   volumen → steel (el acento general del sitio); institucional → marine, mismo
   navy que la sección Mercado Público de más abajo. El ícono "despega" un poco
   más que el resto de la card al hover (rotate + scale), la seña de vida que
   pidió Emmanuel además del acento de color. */
.need-card--volumen .need-ico { background: rgba(76,139,162,.16); color: var(--steel-600); }
.need-card--volumen:hover { border-color: var(--steel-300); }
.need-card--volumen:hover .need-ico { background: var(--steel-500); color: #fff; }
.need-card--institucional .need-ico { background: var(--marine-50); color: var(--marine-600); }
.need-card--institucional:hover { border-color: var(--marine-300); }
.need-card--institucional:hover .need-ico { background: var(--marine-700); color: #fff; }
.need-card--volumen .need-ico, .need-card--institucional .need-ico {
  transition: transform .35s var(--ease-out), background .25s, color .25s;
}
.need-card--volumen:hover .need-ico, .need-card--institucional:hover .need-ico {
  transform: rotate(-6deg) scale(1.08);
}
@media (prefers-reduced-motion: reduce) {
  .need-card--volumen .need-ico, .need-card--institucional .need-ico { transition: background .25s, color .25s; }
  .need-card--volumen:hover .need-ico, .need-card--institucional:hover .need-ico { transform: none; }
}
.need-card--lg {
  grid-row: span 2; justify-content: center;
  background: linear-gradient(160deg, var(--marine-800), var(--marine-600));
  border-color: transparent; color: #fff;
  overflow: hidden;
}
/* Textura sutil de puntos, mismo lenguaje visual que .ba-rejilla — la card era
   un bloque de color plano sin nada que la distinguiera de una card sólida
   cualquiera; reusa el patrón ya establecido en vez de inventar uno nuevo
   (pedido: "rellenar" sin competir con las fotos reales de Productos
   destacados, que es donde SÍ debe ir imagen de producto). */
.need-card--lg::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(rgba(255,255,255,0.08) 1px, transparent 1px);
  background-size: 24px 24px;
  mask-image: radial-gradient(circle at 100% 0%, rgba(0,0,0,.6), transparent 65%);
  -webkit-mask-image: radial-gradient(circle at 100% 0%, rgba(0,0,0,.6), transparent 65%);
}
.need-card--lg .need-ico { position: relative; background: rgba(255,255,255,.14); color: #fff; }
.need-card--lg .need-go { position: relative; color: var(--steel-200); }
.need-ico {
  width: 48px; height: 48px; border-radius: 12px; flex: none;
  background: rgba(76,139,162,.14); color: var(--steel-600);
  display: grid; place-items: center; margin-bottom: 18px;
}
.need-ico .lucide, .need-ico i { width: 23px; height: 23px; }
.need-card h3 { font-size: var(--fs-base); font-weight: var(--fw-bold); margin-bottom: 8px; color: var(--text-strong); }
.need-card p { color: var(--text-muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); flex: 1; }
/* Van DESPUÉS de .need-card h3/p (arriba): misma especificidad (0,1,1), gana la
   última — antes estaban antes y .need-card h3/p las pisaba silenciosamente,
   dejando el título y párrafo de la card grande en --text-strong (oscuro) sobre
   el fondo navy en vez del blanco/slate-300 declarados acá, casi ilegibles
   (reportado por Emmanuel, confirmado con Playwright). */
.need-card--lg h3 { color: #fff; font-size: var(--fs-xl); }
.need-card--lg p { color: var(--slate-300); }
.need-go { display: inline-flex; align-items: center; gap: 7px; margin-top: 18px; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--steel-600); }
.need-go .lucide, .need-go i { width: 14px; height: 14px; transition: transform .25s; }
.need-card:hover .need-go i, .need-card:hover .need-go .lucide { transform: translateX(4px); }
@media (max-width: 720px) {
  .need-bento { grid-template-columns: 1fr; }
  .need-card--lg { grid-row: auto; }
}
/* .necesidad usa un gris apenas perceptible para diferenciarse de las secciones
   blancas vecinas (destacados arriba, marcas abajo): ritmo sin recurrir a otro
   bloque de color fuerte. */
.necesidad { background: var(--surface-sunken); }

/* ===== Marcas: marquee horizontal ligero, no una sección con título+subtítulo
   propios ===== Franja navy a ancho completo, logos blancos grandes flotando
   directo sobre el fondo — sin píldora individual por logo (versión anterior,
   retirada a pedido de Emmanuel: "se ven como píldoras chicas, antes quiero una
   franja azul oscura grande con los logos deslizándose"). Los 3 logos de marca
   (static/img/marcas/*-blanco.png) ya son la versión blanca pensada exactamente
   para ir sobre navy, así que no necesitan fondo propio para leerse. .wrap es
   hermano del track, no ancestro (ver home.html): .brands-marquee es un <div>
   de nivel de sección sin contenedor limitante encima, así que el fondo a ancho
   completo no necesita el truco de margin-inline:calc(50%-50vw) que sí hace
   falta cuando una pieza full-bleed vive DENTRO de .wrap.
   CSS puro (sin JS): animation-play-state:paused en :hover/:focus-within cubre
   mouse y teclado (Tab a un logo real del primer set pausa el marquee), y
   prefers-reduced-motion detiene el movimiento del todo. */
.brands-marquee {
  padding: 48px 0;
  background: linear-gradient(160deg, var(--marine-900), var(--marine-800));
  overflow: hidden;
}
.brands-marquee-label {
  display: block; margin-bottom: 20px;
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  letter-spacing: var(--ls-wide); text-transform: uppercase; color: var(--steel-200);
}
.brands-marquee-track {
  display: flex; align-items: center; width: max-content;
  /* 32s era la duración de cuando el track medía 1 marca por set; al agregar
     repeticiones internas (ver marquee_repeticiones en views.py, necesarias para
     que un set cubra pantallas anchas) el track quedó ~5x más largo con la misma
     duración — mismo recorrido angular (33.3%) pero mucho más veloz en px/seg,
     reportado por Emmanuel como "va muy rápido". La velocidad real depende de
     cuántas marcas/repeticiones haya, así que en vez de un número fijo, la
     duración escala con --marquee-reps (ver home.html) para mantener siempre el
     mismo ritmo visual sin importar cuántas marcas se carguen a futuro. */
  animation: brandsMarquee calc(32s * var(--marquee-reps, 1)) linear infinite;
}
.brands-marquee-track:hover,
.brands-marquee-track:focus-within { animation-play-state: paused; }
/* 3 copias del set (ver home.html): -33.3333% anima exactamente 1/3 del track
   completo (una vuelta del set original), así el fotograma final es idéntico
   al de partida y el bucle no se nota — igual de imperceptible sea cual sea N. */
.brands-marquee-set { display: flex; align-items: center; gap: 72px; flex-shrink: 0; padding-right: 72px; }
@keyframes brandsMarquee {
  from { transform: translateX(0); }
  to { transform: translateX(-33.3333%); }
}
@media (prefers-reduced-motion: reduce) {
  .brands-marquee-track { animation: none; }
}
/* marquee_repeticiones (arriba) apunta a cubrir un monitor ANCHO (~2400px, ver
   _repeticiones_marquee en views.py) — el cálculo vive en el servidor, sin saber
   el viewport real del visitante. En un celular (~390-414px de ancho) esas
   mismas repeticiones son bastante más contenido del que ese ancho necesita, y
   con la duración escalando proporcional a --marquee-reps, el recorrido visible
   se ve mucho más lento que en desktop (reportado por Emmanuel: "casi
   estático"). Sin acceso al viewport real desde CSS puro, se acorta la duración
   base a la mitad bajo 720px — no elimina la desproporción, pero la compensa lo
   suficiente para que el movimiento se perciba igual de vivo que en desktop. */
@media (max-width: 720px) {
  .brands-marquee-track { animation-duration: calc(16s * var(--marquee-reps, 1)); }
}
.brand-logo {
  display: flex; align-items: center; justify-content: center;
  transition: transform .25s var(--ease-out), opacity .25s var(--ease-out);
  opacity: .82;
}
.brand-logo img { height: 34px; width: auto; max-width: none; object-fit: contain; }
a.brand-logo:hover { transform: translateY(-2px); opacity: 1; }
.brand-logo.is-static { cursor: default; }
@media (max-width: 720px) {
  .brands-marquee { padding: 36px 0; }
  .brands-marquee-set { gap: 48px; padding-right: 48px; }
  .brand-logo img { height: 26px; }
}

/* ===== Cotización: CTA que abre el form en modal (.adm-modal, ver la nota en
   home.html). ===== */
/* Dentro del modal .quote-card pierde su propio marco (border/radius/fondo):
   .adm-modal-card ya pone el suyo, y anidar los dos dejaba un doble borde/radio
   pegado sin aire entre ambos (reportado por Emmanuel, captura del modal). */
.quote-modal .quote-card { padding: 0; border: none; border-radius: 0; background: none; }
.quote-modal .adm-modal-card-head h3 { display: flex; align-items: center; gap: 12px; font-size: var(--fs-md); font-weight: var(--fw-bold); color: var(--text-strong); margin: 0; }
/* Más ancho que el modal chico (460px, campos apretados) pero acotado (no .ancho,
   920px: sobraba ~200px de aire a cada lado). 860px (subido de 760, campos más
   grandes a pedido de Emmanuel) es el techo en desktop; calc(100vw - 40px) es lo
   que manda en una ventana angosta. El @container (no @media) hace que las dos
   columnas de .f-row colapsen a una según el ancho REAL de esta card, no el del
   viewport — el corte sube de 560 a 640px porque a una ventana intermedia (ej.
   ~776px, con el modal ya pegado a calc(100vw-40px)) el card quedaba en ese rango
   y las dos columnas se veían apretadas en vez de caer a una por fila.
   .adm-modal-card.quote-modal-card (especificidad 0,2,0), no .quote-modal-card
   solo: admin-productos.css carga DESPUÉS de landing.css en home.html, así que
   con la misma especificidad (0,1,0) su .adm-modal-card{max-width:460px} pisaba
   a este en la cascada — subir la especificidad evita depender del orden de los
   <link> entre los dos archivos. */
.adm-modal-card.quote-modal-card { max-width: min(860px, calc(100vw - 40px)); }
@container (max-width: 640px) {
  .quote-card .f-row { grid-template-columns: 1fr; }
}

/* ===== Proveedores del Estado: uno de los 3 momentos principales (Hero, Productos,
   Mercado Público) — el brief lo marca como diferenciador comercial importante, así
   que se queda oscura y con más aire que las secciones funcionales. ===== */
.estado {
  position: relative; overflow: hidden;
  background:
    radial-gradient(760px 420px at 110% 10%, rgba(76,139,162,.4), transparent 62%),
    linear-gradient(150deg, var(--marine-900), var(--marine-700));
  color: #fff;
  padding: 88px 0;
}
/* Firma visual sutil, mismo lenguaje que .cat-door-main y .hero::after: retícula de
   puntos + máscara diagonal. Organización/precisión, no decoración. */
.estado::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image: radial-gradient(rgba(255,255,255,.06) 1px, transparent 1px);
  background-size: 24px 24px;
  mask-image: linear-gradient(115deg, transparent 45%, rgba(0,0,0,.5) 85%);
  -webkit-mask-image: linear-gradient(115deg, transparent 45%, rgba(0,0,0,.5) 85%);
}
.estado-grid { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 64px; align-items: center; position: relative; z-index: 1; }
.estado .eyebrow { color: var(--steel-200); }
.estado .eyebrow::before { background: var(--steel-300); }
.estado h2 { color: #fff; font-size: clamp(28px, 3.4vw, 40px); font-weight: var(--fw-bold); margin: 14px 0 14px; }
.estado p.lead { color: var(--slate-300); font-size: var(--fs-md); line-height: var(--lh-relaxed); max-width: 480px; }
.estado-points { list-style: none; margin: 28px 0 34px; display: grid; gap: 14px; }
.estado-points li { display: flex; gap: 13px; align-items: flex-start; font-size: var(--fs-base); color: var(--slate-200); }
.estado-points li .pt-ico {
  flex: none; width: 26px; height: 26px; border-radius: 8px; margin-top: 1px;
  background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.16);
  display: grid; place-items: center; color: var(--steel-300);
}
.estado-points li .pt-ico .lucide, .estado-points li .pt-ico i { width: 14px; height: 14px; }
.estado-points li strong { color: #fff; font-weight: var(--fw-semibold); }

/* Credential card — white "certificate" panel on the dark section */
.cred-card {
  background: #fff; border: 1px solid var(--border-subtle);
  border-radius: 20px; padding: 30px;
  box-shadow: 0 30px 70px rgba(7,18,28,.42);
}
.cred-brand { display: flex; align-items: center; justify-content: space-between; gap: 18px; padding-bottom: 22px; border-bottom: 1px solid var(--border-subtle); }
.cred-brand .mp-hero { height: 52px; width: auto; display: block; flex: none; }
.cred-org { display: flex; align-items: center; gap: 12px; margin-top: 20px; }
.cred-org .ch-emblem { width: 40px; height: 40px; border-radius: 10px; flex: none; background: var(--surface-accent-soft); display: grid; place-items: center; }
.cred-org .ch-emblem img { width: 26px; height: 26px; object-fit: contain; }
.cred-org .ch-t { font-size: var(--fs-base); font-weight: var(--fw-bold); color: var(--text-strong); }
.cred-org .ch-s { display: block; font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-muted); letter-spacing: var(--ls-wide); text-transform: uppercase; margin-top: 3px; }
.cred-state {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--fs-xs); font-weight: var(--fw-semibold);
  background: rgba(31,138,90,.1); border: 1px solid rgba(31,138,90,.32); color: #1F8A5B;
  padding: 6px 12px; border-radius: 999px; white-space: nowrap; flex: none;
}
.cred-state .dot { width: 7px; height: 7px; border-radius: 50%; background: #2E9E6B; box-shadow: 0 0 0 3px rgba(46,158,107,.22); }
.cred-rows { display: grid; gap: 0; margin-top: 18px; }
.cred-row { display: flex; justify-content: space-between; gap: 18px; padding: 13px 0; border-bottom: 1px solid var(--border-subtle); }
.cred-row:last-child { border-bottom: none; }
.cred-row .cr-k { font-size: var(--fs-sm); color: var(--text-muted); }
.cred-row .cr-v { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text-strong); text-align: right; }
.cred-row .cr-v.mono { font-family: var(--font-mono); font-weight: var(--fw-medium); color: var(--steel-600); }
.cred-foot { display: flex; align-items: center; gap: 9px; margin-top: 18px; padding: 13px 16px; border-radius: 12px; background: var(--surface-accent-soft); border: 1px dashed var(--border-default); font-size: var(--fs-xs); color: var(--text-muted); }
.cred-foot .lucide, .cred-foot i { width: 15px; height: 15px; color: var(--steel-600); flex: none; }

/* ===== Cotización + Contacto ===== */
.quote-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }
.quote-card {
  background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: 16px;
  padding: 30px; display: flex; flex-direction: column; gap: 4px;
}
/* Sobre la sección oscura de contacto, la card se eleva como la cred-card sobre estado. */
.contact .quote-card { border-color: transparent; box-shadow: 0 24px 60px rgba(5, 15, 24, .5); }
.quote-card h3 {
  display: flex; align-items: center; gap: 12px;
  font-size: var(--fs-md); font-weight: var(--fw-bold); color: var(--text-strong);
  margin: 0 0 22px;
}
.quote-card .qc-ico {
  width: 36px; height: 36px; border-radius: 10px; flex: none;
  background: var(--surface-accent-soft); color: var(--steel-600);
  display: grid; place-items: center;
}
.quote-card .qc-ico .lucide, .quote-card .qc-ico i { width: 18px; height: 18px; }
/* align-items:start → si un label ocupa 2 líneas y el vecino 1, cada input arranca
   a su propia altura y no se estira a la celda vecina (misma trampa que en pago.css). */
.quote-card .f-row { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; align-items: start; }
.quote-card .f-field { display: grid; gap: 6px; margin-bottom: 14px; align-content: start; }
.quote-card .f-field:last-of-type { margin-bottom: 18px; }
.quote-card .f-field label { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-body); }
.quote-card .f-field label .req { color: var(--danger); }
.quote-card .f-field input, .quote-card .f-field textarea {
  width: 100%; padding: 12px 14px; border-radius: 10px; font: inherit;
  border: 1.5px solid var(--border-default); background: var(--surface-card); color: var(--text-strong);
  transition: border-color 120ms, box-shadow 120ms;
}
.quote-card .f-field input:focus, .quote-card .f-field textarea:focus {
  outline: none; border-color: var(--focus-ring); box-shadow: 0 0 0 3px rgba(99,157,174,.18);
}
.quote-card .f-field textarea { resize: vertical; min-height: 72px; font-family: inherit; }

/* ===== Cotización (form único, centrado) ===== */
.quote {
  background: linear-gradient(180deg, var(--slate-50) 0%, var(--marine-50) 100%);
  border-bottom: 1px solid var(--border-subtle);
}
.quote-card--solo { max-width: 720px; margin: 0 auto; }

/* ===== FAQ: fondo claro, layout editorial de dos columnas =====
   El brief lo pide explícito: "no gran sección azul oscura, preferir fondo claro".
   Intro a la izquierda (título + cómo contactar si no encuentra respuesta) y el
   acordeón a la derecha, en vez de todo centrado en una columna — es la sección
   que más repetía el patrón título-centrado/subtítulo-centrado/lista, así que es
   donde más se nota el cambio de ritmo. */
.faq { background: var(--surface-page); }
.faq-grid { display: grid; grid-template-columns: 0.85fr 1.15fr; gap: 56px; align-items: start; }
.faq-intro { position: sticky; top: 104px; }
.faq-intro .eyebrow { justify-content: flex-start; }
.faq-intro h2 { margin: 14px 0 12px; }
.faq-intro p { color: var(--text-muted); font-size: var(--fs-md); line-height: var(--lh-relaxed); }
.faq-intro p a { color: var(--text-link); text-decoration: underline; }
.faq-list { display: flex; flex-direction: column; gap: 10px; }
.faq-item {
  border: 1px solid var(--border-subtle); border-radius: 14px;
  background: var(--surface-card); overflow: hidden;
  transition: border-color 160ms ease, box-shadow 160ms ease;
}
.faq-item[open] { border-color: var(--steel-300); box-shadow: 0 6px 20px rgba(17,42,61,.06); }
.faq-q {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding: 18px 22px; cursor: pointer; list-style: none;
  font-weight: var(--fw-semibold); font-size: var(--fs-base); color: var(--text-strong);
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:hover { color: var(--brand-navy); }
.faq-chevron { width: 20px; height: 20px; flex: none; color: var(--text-subtle); transition: transform 240ms cubic-bezier(0.23,1,0.32,1); }
.faq-item[open] .faq-chevron { transform: rotate(180deg); color: var(--steel-600); }
.faq-a { padding: 0 22px 20px; color: var(--text-body); font-size: var(--fs-sm); line-height: var(--lh-relaxed); }
.faq-a p { margin: 0; }
.faq-a a { color: var(--text-link); text-decoration: underline; }
/* Entrada suave del contenido al abrir (details no anima solo el height). */
.faq-item[open] .faq-a { animation: faqReveal 240ms cubic-bezier(0.23,1,0.32,1); }
@keyframes faqReveal { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 1040px) {
  .faq-intro { position: static; }
}

/* ===== Contacto (sección oscura, encadena con el footer oscuro) ===== */
.contact {
  position: relative; overflow: hidden;
  background:
    radial-gradient(720px 420px at 108% 100%, rgba(76,139,162,.32), transparent 60%),
    linear-gradient(150deg, var(--marine-900), var(--marine-800));
}
.contact-grid { display: grid; grid-template-columns: 1fr 1.1fr; gap: 48px; align-items: center; position: relative; z-index: 1; }
.contact-aside .eyebrow { color: var(--steel-200); }
.contact-aside .eyebrow::before { background: var(--steel-300); }
.contact-aside h2 { color: #fff; font-size: var(--fs-2xl); font-weight: var(--fw-extra); margin: 10px 0 14px; }
.contact-aside .lead { color: var(--slate-300); font-size: var(--fs-md); line-height: var(--lh-relaxed); margin-bottom: 26px; }
.contact-points { list-style: none; display: flex; flex-direction: column; gap: 16px; }
.contact-points li { display: flex; align-items: center; gap: 12px; color: var(--slate-200); font-size: var(--fs-sm); }
.contact-points a { color: var(--slate-200); text-decoration: none; }
.contact-points a:hover { color: #fff; text-decoration: underline; }
.cp-ico { width: 38px; height: 38px; flex: none; border-radius: 10px; background: rgba(132,183,198,.16); color: var(--steel-200); display: grid; place-items: center; }
.cp-ico .lucide, .cp-ico i { width: 18px; height: 18px; }

/* ===== Contenido legal (editor rico) ===== */
.legal-body { color: var(--text-strong); font-size: var(--fs-md); line-height: var(--lh-relaxed); }
.legal-body h2, .legal-body h3 { font-weight: var(--fw-bold); color: var(--text-strong); margin: 28px 0 10px; }
.legal-body h2 { font-size: var(--fs-xl); }
.legal-body h3 { font-size: var(--fs-lg); }
.legal-body p { margin: 0 0 14px; }
.legal-body ul, .legal-body ol { margin: 0 0 14px; padding-left: 22px; }
.legal-body li { margin-bottom: 6px; }
.legal-body a { color: var(--marine-700); text-decoration: underline; }
.legal-body img { max-width: 100%; height: auto; border-radius: 10px; margin: 8px 0; }
.legal-body table { width: 100%; border-collapse: collapse; margin: 0 0 14px; }
.legal-body th, .legal-body td { border: 1px solid var(--border-subtle); padding: 8px 12px; text-align: left; }

/* ===== Seguir mi pedido =====
   Banda tras las categorías: quien viene a mirar productos ya los vio, y quien vuelve
   a preguntar por su compra lo encuentra sin recorrer toda la página. */
/* ===== Responsive ===== */
@media (max-width: 1040px) {
  .faq-grid { grid-template-columns: 1fr; gap: 32px; }
  .estado-grid { grid-template-columns: 1fr; gap: 44px; }
  .quote-grid { grid-template-columns: 1fr; }
  .contact-grid { grid-template-columns: 1fr; gap: 36px; }
}
@media (max-width: 560px) {
  .quote-card .f-row { grid-template-columns: 1fr; }
}
@media (max-width: 880px) {
  .hero-grid { padding-top: 40px; padding-bottom: 34px; }
  section.land { padding: 48px 0; }
  /* La trust bar (.hero-stats) hacía crecer demasiado el hero en mobile: con el
     banner real bastante más bajo (260px vs 591px de hero con trust bar,
     medido con Playwright a 390px), la pieza quedaba flotando en ~330px de
     franja navy vacía. En vez de comprimir tipografía o forzar el banner a
     estirarse (ya descartado antes por dejarlo con franjas a los lados), se
     oculta la trust bar bajo este breakpoint: el hero vuelve a su altura previa
     (más cercana al banner) y las cifras se ven en tablet/desktop, donde sí hay
     espacio de sobra para mostrarlas sin desbalancear el carrusel. */
  .carrusel-slide--hero .hero-stats,
  .carrusel-medida--hero .hero-stats { display: none; }
}
/* En celular el degradado del hero se veía marcado: recorre el MISMO salto de brillo
   que en escritorio (103 puntos, de navy casi negro a steel claro) pero en un tercio
   de la distancia, así que la pendiente se duplicaba (0,16 vs 0,084 puntos por px) y
   el paso de color se leía como una banda. Al no poder darle más distancia en un
   teléfono, se le baja el recorrido: la rampa termina en marine-500 en vez de
   steel-400, corre por el lado largo (155deg) y los dos brillos radiales van en % —
   así escalan con la caja en vez de quedar sobredimensionados— y con menos opacidad.
   Queda en 0,102 por px, a la par del escritorio. Medido con Chrome headless. */
@media (max-width: 720px) {
  .hero {
    background:
      radial-gradient(120% 80% at 95% -10%, rgba(175, 210, 220, 0.20), rgba(175, 210, 220, 0.07) 45%, transparent 72%),
      radial-gradient(130% 85% at -15% 112%, rgba(79, 137, 174, 0.30), rgba(79, 137, 174, 0.10) 48%, transparent 78%),
      linear-gradient(155deg, #0A1B29 0%, var(--marine-800) 34%, var(--marine-700) 62%, var(--marine-600) 88%, var(--marine-500) 100%);
  }
}
@media (max-width: 560px) {
  /* La tarjeta de credenciales se desbordaba de la pantalla: el logo de Mercado
     Público mide 225px a 52px de alto y la píldora "Habilitado" ~103px, y ambos
     eran flex:none, así que no cedían y estiraban la tarjeta más allá del ancho
     del celular (interior disponible a 390px: 290px). Se le baja el relleno y el
     logo pasa a escalar en proporción en vez de tener el alto clavado. */
  .cred-card { padding: 24px 20px; }
  .cred-brand { gap: 12px; padding-bottom: 18px; }
  .cred-brand .mp-hero { height: auto; max-height: 46px; max-width: 55%; }
}


