/*
  site.css — cómo se usan los tokens de assets/css/tokens.css en el layout
  de ESTE sitio (header, sidebar, grid de productos, footer). Este archivo
  se toca seguido conforme se arman páginas nuevas; tokens.css casi nunca
  (solo si cambia el diseño de marca). Ver el comentario de cabecera de
  tokens.css para esa separación.

  Historial de decisiones y ajustes de diseño: ver "Bitácora de cambios"
  en README.md.
*/

/* ─── Reset mínimo ────────────────────────────────────────────────
   No se usa un reset completo tipo normalize.css a propósito: el sitio es
   chico y un reset mínimo escrito a mano es más fácil de entender para
   quien abra el proyecto por primera vez que depender de una librería
   externa cuyo contenido no está a la vista. */
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--color-body-text);
  background: var(--color-body-bg);
  /* Ancho del sidebar fijo (ver `.sitio-sidebar` más abajo), declarado
     aquí en `body` y no dentro de `.sitio-sidebar`: una variable CSS solo
     cae en cascada a los DESCENDIENTES del elemento donde se declara, y
     `.sitio-header`/`.sitio-main` también necesitan este valor para
     despejar el panel fijo aunque NO son descendientes de
     `.sitio-sidebar` — son hermanos, los tres viven directo bajo <body>
     (ver includes/header.php). `body` es el ancestro común más cercano
     de los tres.

     El valor (190px, con el padding asimétrico de `.sitio-sidebar` más
     abajo) es el mínimo necesario para que el título
     "MOBILIARIO URBANO Y EQUIPAMIENTO:" (ver `.sidebar-titulo`) se parta
     en exactamente 2 líneas, igual que en el mockup de referencia. Si
     el ancho vuelve a ajustarse, basta con cambiarlo aquí — todo lo
     demás lo referencia vía `var(--sidebar-ancho)`. */
  --sidebar-ancho: 190px;

  /* Alto de la barra fija de mobile (logo + botón hamburguesa, ver
     `.sitio-sidebar__barra` en el breakpoint de mobile más abajo) — solo
     tiene efecto bajo 700px, pero vive aquí junto a --sidebar-ancho por
     la misma razón: la necesitan varios selectores no-descendientes
     entre sí (`.sitio-sidebar__barra`, `.sitio-layout`), así que hace
     falta declararla en un ancestro común. Es un valor FIJO a propósito
     (no una medida real del contenido): el resto de la hoja compensa
     exactamente este número para que nada quede tapado detrás de la
     barra — si el contenido de la barra cambia de alto, ajustar este
     valor en conjunto. */
  --menu-movil-barra-alto: 56px;
}

a {
  color: var(--color-link);
  text-decoration: none;
}

img {
  max-width: 100%;
  display: block;
}

h1, h2, h3, h4, h5 {
  margin: 0 0 0.5em;
}

h1 {
  font-family: var(--font-h1);
  font-size: var(--fs-h1);
  font-weight: var(--fw-h1);
  letter-spacing: var(--ls-h1);
  color: var(--color-h1);
}

/* h5 se agrega aquí (Oswald + mismo verde/teal que h2/h3) porque
   Informativa (bloque-home--informativa, ver más abajo) es la primera
   sección del sitio que usa <h5> — mismo criterio que el h4 de
   Productos Destacados: hereda lo compartible de la familia de
   encabezados, cada uso puntual solo agrega tamaño/peso/alineación. */
h2, h3, h5 {
  font-family: var(--font-heading);
  letter-spacing: var(--ls-heading);
  color: var(--color-heading-accent);
}

h2 {
  font-size: var(--fs-h2);
  font-weight: var(--fw-h2);
}

h3 {
  font-size: var(--fs-h3);
  font-weight: var(--fw-h3);
}

h4 {
  font-family: var(--font-heading);
  font-size: var(--fs-h4);
  font-weight: var(--fw-h4);
  letter-spacing: var(--ls-h4);
}

/* ─── Header: barra superior estática (menú de secciones del sitio) ────
   `.top-nav-estatico` sigue siendo el <nav> contenedor que
   assets/js/sidebar.js mueve completo a mobile — se conserva ese nombre
   de clase sin cambios porque es el gancho que usa ese script
   (querySelector('.top-nav-estatico'), ver ese archivo). Adentro,
   `.top-nav` es la lista real de items (ver includes/header.php para el
   HTML completo y el porqué de cada pieza — MUPA®/PRODUCTOS/RECURSOS con
   desplegable, el resto plano). `margin-left` en `.sitio-header` desplaza
   la barra para no quedar tapada por el sidebar fijo, que está fuera del
   flujo normal del documento. `justify-content: flex-end` porque, con un
   solo hijo (el <nav>), es la forma de mantener el menú pegado al borde
   derecho (igual que en el mockup de referencia). */
.sitio-header {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 2rem;
  padding: 1rem 2rem;
  margin-left: var(--sidebar-ancho);
  border-bottom: 1px solid #eee;
}

.top-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* `position: relative` ancla el panel `.top-nav__submenu` de los items
   con desplegable (ver más abajo) contra su propio <li>, no contra el
   <body>. El resto de estas propiedades tipográficas las heredan sin
   redeclarar los hijos normales (<a>, <span>) — solo los <button>
   (`.top-nav__disparador`/`.top-nav__chevron`, más abajo) necesitan
   `font: inherit` explícito, porque los navegadores no heredan tipografía
   en botones por default. */
.top-nav__item {
  position: relative;
  font-family: var(--font-body);
  font-size: var(--fs-category-button);
  font-weight: var(--fw-category-button);
  letter-spacing: 0.5px;
  white-space: nowrap;
}

/* Reset del `margin-top` que trae `.enlace-pendiente` en su uso normal
   (texto suelto después de un párrafo, ver esa clase más abajo) — aquí es
   un hijo directo de un `.top-nav__item` en una barra horizontal, no hay
   párrafo previo del que separarse. Selector con `>` a propósito: NO debe
   afectar los `.enlace-pendiente` anidados dentro de `.top-nav__submenu`
   (esos tienen su propia regla de layout más abajo). El resto del estilo
   de `.enlace-pendiente` (subrayado punteado, opacidad, cursor de ayuda)
   se conserva tal cual en ambos casos: mismo caso de uso ("un link que en
   el mockup existe pero acá no tiene destino todavía"). */
.top-nav__item > .enlace-pendiente {
  margin-top: 0;
}

/* Items reales (<a>, hoy solo PRODUCTOS y sus 8 hijos dentro del
   submenu): color propio + estado :hover/:focus-visible para que se lean
   como clickeables — a diferencia de `.enlace-pendiente`, que
   deliberadamente NO quiere leerse como link real (ver esa clase).
   `--color-h1` (no `--color-body-text`): mismo verde que el resto de
   textos de marca. El estado :hover/:focus-visible de abajo usa
   `--color-link`, distinto del color de reposo, como señal de
   interacción. */
.top-nav__link {
  color: var(--color-h1);
  text-decoration: none;
}

.top-nav__link:hover,
.top-nav__link:focus-visible {
  color: var(--color-link);
}

/* Items con desplegable (MUPA®, PRODUCTOS, RECURSOS): en PRODUCTOS el
   <li> tiene DOS hijos visibles en flujo normal (el <a> y el
   <button class="top-nav__chevron">, hermanos a propósito — ver
   includes/header.php); `inline-flex` los alinea en fila sin que el
   <li> ocupe todo el ancho disponible. `.top-nav__submenu` no participa
   de este flex (es `position: absolute`, fuera de flujo, ver abajo), así
   que esta regla no le afecta. */
.top-nav__item--submenu {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
}

/* ── Panel desplegable ───────────────────────────────────────────────
   Mismo mecanismo que ya usa el sidebar de categorías para su panel
   flotante (ver `.sidebar-subcategorias` más abajo y su comentario
   completo) — esta es la versión "hacia abajo" del mismo truco (ahí se
   abre hacia la derecha): oculto por `opacity`/`visibility` (animables,
   a diferencia de `display`) hasta que el `.top-nav__item--submenu`
   padre cumple `:focus-within` (teclado), trae la clase `--abierto`
   (tap en touch, ver assets/js/sidebar.js) o, en dispositivos con hover
   real, `:hover` — las tres condiciones disparan la misma transición. */
.top-nav__submenu {
  position: absolute;
  left: 0;
  top: 100%;
  margin: 0.5rem 0 0;
  min-width: 220px;
  list-style: none;
  padding: 0.6rem 0;
  background: #fff;
  border-radius: 4px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
  z-index: 60;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity 0.2s ease, transform 0.2s ease, visibility 0s linear 0.2s;
}

.top-nav__item--submenu:focus-within > .top-nav__submenu,
.top-nav__item--submenu.top-nav__item--abierto > .top-nav__submenu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

@media (hover: hover) and (pointer: fine) {
  .top-nav__item--submenu:hover > .top-nav__submenu {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
    transition: opacity 0.2s ease, transform 0.2s ease;
  }
}

.top-nav__submenu li {
  padding: 0;
}

.top-nav__submenu a {
  display: block;
  padding: 0.4rem 1.1rem;
  font-size: var(--fs-category-button);
  font-weight: 400;
  letter-spacing: normal;
  white-space: nowrap;
  color: var(--color-body-text);
  text-decoration: none;
}

.top-nav__submenu a:hover,
.top-nav__submenu a:focus-visible {
  background: #f5f5f5;
  color: var(--color-link);
}

/* Layout de los `.enlace-pendiente` anidados en un desplegable (MUPA®,
   RECURSOS) — mismo padding/tamaño que los <a> reales de arriba para que
   todo el panel se vea como una sola lista consistente; color, subrayado
   punteado, opacidad y cursor de ayuda quedan intactos (heredados de la
   regla base `.enlace-pendiente` más abajo en este archivo), que es
   justamente lo que distingue a simple vista un item pendiente de uno
   real dentro del mismo panel. */
.top-nav__submenu .enlace-pendiente {
  display: block;
  padding: 0.4rem 1.1rem;
  font-size: var(--fs-category-button);
  font-weight: 400;
  letter-spacing: normal;
  white-space: nowrap;
  margin-top: 0;
}

/* `.top-nav__disparador` (MUPA®/RECURSOS: todo el item es el botón, sin
   <a> propio porque no hay destino que fingir) y `.top-nav__chevron`
   (PRODUCTOS: botón hermano del <a>, mismo truco de "elementos hermanos,
   no anidados" que ya usa el sidebar — ver comentario en
   includes/header.php) resetean el estilo nativo de <button> y heredan
   tipografía/color del <li> (los navegadores NO heredan fuente en
   botones por default, de ahí el `font: inherit` explícito). */
.top-nav__disparador {
  font: inherit;
  letter-spacing: inherit;
  color: var(--color-body-text);
  background: transparent;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  -webkit-tap-highlight-color: transparent;
}

.top-nav__chevron {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Triángulo hacia abajo con el truco clásico de bordes (0x0 + tres
   bordes, dos transparentes) — más simple de razonar acá que rotar un
   corner (`.sidebar-chevron::after`, ver ese comentario): el panel del
   sidebar se abre hacia la DERECHA y su flecha rota para apuntar en esa
   dirección; este panel se abre hacia ABAJO, así que un triángulo que ya
   nace apuntando abajo no necesita rotación en su estado cerrado — solo
   gira 180° al abrir, para apuntar hacia arriba ("ya desplegado"). */
.top-nav__caret {
  display: inline-block;
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform 0.2s ease;
}

.top-nav__chevron::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform 0.2s ease;
}

.top-nav__item--submenu:focus-within .top-nav__caret,
.top-nav__item--submenu.top-nav__item--abierto .top-nav__caret,
.top-nav__item--submenu:focus-within .top-nav__chevron::after,
.top-nav__item--submenu.top-nav__item--abierto .top-nav__chevron::after {
  transform: rotate(180deg);
}

@media (hover: hover) and (pointer: fine) {
  .top-nav__item--submenu:hover .top-nav__caret,
  .top-nav__item--submenu:hover .top-nav__chevron::after {
    transform: rotate(180deg);
  }
}

/* ─── Layout de dos columnas: sidebar + contenido ─────────────────────
   `.sitio-sidebar` es `position: fixed` (ver más abajo), así que sale del
   flujo normal del documento aunque su HTML siga anidado aquí — por eso
   este contenedor no necesita flexbox/gap para acomodarlo: el único hijo
   que le queda en flujo es `.sitio-main`, que se separa del sidebar con
   su propio `margin-left`. */
.sitio-layout {
  padding: 1.5rem 2rem;
}

/* Panel fijo a la izquierda con logo, título y categorías. Ancho angosto
   (190px) a propósito: fuerza el salto de línea de los nombres de
   categoría, igual que el mockup de referencia. `background`/`box-shadow`
   dan el efecto "tarjeta" que lo separa visualmente del resto de la
   página (ambos fondos son blancos; la diferenciación es la sombra, no un
   color distinto). `position: fixed` + `height: 100vh`: el panel se queda
   clavado en pantalla al hacer scroll — no se mueve con el resto del
   contenido. `overflow-y: visible` porque con 8 categorías + título + logo
   el contenido nunca se acerca a desbordar un alto de pantalla típico; si
   el listado de categorías crece mucho, revisar este valor. Padding
   asimétrico (más a la izquierda que a la derecha): dejar aire entre los
   botones y el borde izquierdo de la página, y los botones/título al ras
   del borde derecho del panel (sin padding-right). */
.sitio-sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: var(--sidebar-ancho);
  height: 100vh;
  overflow-y: visible;
  z-index: 100;
  background: rgba(255, 255, 255, 0.75);
  box-shadow: 0 0 7px rgba(0, 0, 0, 0.1);
  padding: 0.75rem 0 0.75rem 1rem;
}

/* Logo — primer elemento del sidebar, antes del título y las categorías.
   `margin-bottom` lo separa verticalmente de `.sidebar-titulo`, que sigue
   debajo. Envuelve una <img> real (assets/img/logo/mupa-logo.avif).
   `width: 130px` (el archivo real mide 220×57 — se escala manteniendo
   proporción, `height: auto`) es un valor medido a ojo contra el ancho
   disponible del sidebar (190px, menos su padding); si el sidebar vuelve
   a ajustar su ancho (`--sidebar-ancho` en `body`, ver arriba), revisar
   este valor en conjunto. */
.sitio-logo {
  display: block;
  margin-bottom: 1.25rem;
}

.sitio-logo img {
  display: block;
  width: 130px;
  height: auto;
}

/* Botón hamburguesa (ver includes/header.php): oculto por default
   siempre (regla base, fuera de cualquier media query) — solo
   `@media (max-width: 700px)` lo revela (ver ese bloque más abajo). Así
   queda oculto en escritorio sin depender de que la media query se
   evalúe en un orden particular. */
.menu-hamburguesa {
  display: none;
}

.sitio-main {
  min-height: 60vh;
  /* Despeja el sidebar fijo — `.sitio-main` no está en el mismo flex que
     el sidebar (que está fuera de flujo por `position: fixed`), así que
     necesita su propio espacio explícito para no quedar tapado debajo
     del panel. */
  margin-left: var(--sidebar-ancho);
}

/* Enlace "MOBILIARIO URBANO Y EQUIPAMIENTO:" que encabeza el sidebar,
   arriba de los 8 botones de categoría (ver includes/menu.php,
   imprimirSidebarCompleta()) — texto simple, sin fondo de color, sin
   chevron, sin panel flotante: no es una `.sidebar-categoria`, enlaza
   directo a la categoría raíz completa. `font-size`/`font-weight`
   referencian las mismas variables que usan los botones del menú
   (`--fs-category-button`/`--fw-category-button`) para que ambos queden
   siempre con tipografía idéntica, incluso si esas variables cambian más
   adelante. `color: var(--color-h1)` — mismo verde de marca que el logo. */
.sidebar-titulo {
  display: block;
  padding: 0.4rem 0.75rem;
  margin-bottom: 0.35rem;
  font-family: var(--font-body);
  font-size: var(--fs-category-button);
  font-weight: var(--fw-category-button);
  text-transform: uppercase;
  color: var(--color-h1);
  line-height: 1.2;
}

/* ─── Buscador (includes/menu.php, imprimirBuscador()) ────────────────
   Dos instancias en el DOM (`.buscador--sidebar` en escritorio,
   `.buscador--movil` en la barra fija de mobile — ver
   assets/js/buscador.js para el toggle abrir/cerrar). Reglas BASE aquí
   cubren lo que ambas comparten: el ícono de lupa dibujado con CSS puro
   (mismo criterio que `.sidebar-chevron` más arriba: sin imagen ni
   carácter, geometría con pseudo-elementos) y el formulario que arranca
   oculto hasta que se abre.

   Colores/tipografía calcados de getComputedStyle() en vivo contra
   mupa2.com.mx (#et_search_icon / .et-search-field): ícono y placeholder
   en `#666666` (== var(--color-body-text), reuso real del token, no
   coincidencia — es literalmente el mismo gris de cuerpo de texto del
   sitio), campo sin borde/fondo, `font-size: 15px`. */
.buscador {
  position: relative;
}

.buscador__icono {
  display: block;
  width: 14px;
  height: 14px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* Ícono real (assets/img/iconos/lupa.svg, Font Awesome) en vez del
   aro+mango dibujados con CSS puro de la primera versión — ver docblock
   de imprimirBuscador() en includes/menu.php para el porqué del cambio.
   `display:block` evita el hueco fantasma de unos px que un <img>
   inline deja debajo por el line-height del texto que lo rodea. */
.buscador__icono img {
  display: block;
  width: 100%;
  height: 100%;
}

.buscador__form {
  display: none;
  align-items: center;
}

.buscador--abierto .buscador__icono {
  display: none;
}

.buscador--abierto .buscador__form {
  display: flex;
}

.buscador__campo {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--color-body-text);
  padding: 0.25rem 0;
}

.buscador__campo:focus {
  outline: none;
}

.buscador__campo::placeholder {
  color: var(--color-body-text);
  opacity: 0.85;
}

.buscador__cerrar {
  flex-shrink: 0;
  margin: 0 0 0 0.5rem;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  font-size: 1.15rem;
  line-height: 1;
  color: var(--color-body-text);
  -webkit-tap-highlight-color: transparent;
}

/* Posición ESCRITORIO: al final del sidebar de categorías, misma columna
   de --sidebar-ancho — mismo padding horizontal que `.sidebar-titulo`
   arriba, para que el ícono quede alineado con el resto de la columna. */
.buscador--sidebar {
  margin-top: 0.5rem;
  padding: 0.4rem 0.75rem;
}

.buscador--sidebar .buscador__form {
  width: 100%;
}

/* Posición MOBILE: oculta en escritorio por default (regla base, fuera
   de cualquier media query, mismo criterio que `.menu-hamburguesa` más
   arriba) — solo `@media (max-width: 700px)` la revela, ver ese bloque
   más abajo. */
.buscador--movil {
  display: none;
}

/* ─── Sidebar de categorías (includes/menu.php) ───────────────────────
   .sidebar-categorias--nivel1 = la lista raíz que ve includes/menu.php
   desde imprimirSidebarCompleta(): las 8 categorías con color de marca.
   Niveles más profundos (--nivel2, --nivel3, ...) son las subcategorías,
   mostradas dentro de paneles flotantes anidados (ver
   .sidebar-subcategorias más abajo).

   .sidebar-categoria--nivel1 (bandera de LAYOUT, solo profundidad 1 real,
   marca las 8 raíces por si algún estilo futuro necesita distinguirlas)
   y .sidebar-categoria--con-color (bandera de COLOR, cualquier fila con
   color propio o heredado) son dos clases independientes que solo
   coinciden siempre en las 8 raíces — ver includes/menu.php.

   `gap` vive en la clase base (no solo en --nivel1) para que TODOS los
   niveles anidados tengan el mismo espaciado entre items, con esquinas
   redondeadas visibles en cada uno. */
.sidebar-categorias {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

/* position:relative en TODAS las categorías (no solo nivel 1): es el
   ancla para el panel flotante `position:absolute` de sus propios hijos
   (.sidebar-subcategorias más abajo) — sin esto, el panel se posicionaría
   relativo al primer ancestro posicionado que encuentre (probablemente
   .sitio-sidebar), no relativo a SU categoría, y aparecería en el lugar
   equivocado. */
.sidebar-categoria {
  position: relative;
}

.sidebar-categoria > a {
  display: block;
  /* padding-left/right NO se tocan con el shorthand completo en otras
     reglas: el left tiene su propio ajuste más abajo (solo nivel 1) y el
     right lo pisa `.sidebar-categoria--con-hijos > a` para dejar espacio
     al chevron — cualquier cambio de padding debe usar `padding-top`/
     `padding-bottom` sueltos si no quiere afectar esas dos reglas. */
  padding: 0.375rem 0.75rem;
  font-family: var(--font-body);
  font-size: var(--fs-category-button);
  font-weight: var(--fw-category-button);
  color: var(--color-body-text);
  /* line-height explícito (no heredado de `--lh-body`, pensado para texto
     de párrafo más grande): proporción 1.2 sobre el tamaño de fuente del
     botón, sin unidad para que escale si `--fs-category-button` cambia. */
  line-height: 1.2;
  /* La transición vive en la regla base (no dentro de :hover) para que la
     animación de opacidad se note tanto al entrar como al salir del
     hover, no solo al entrar. */
  transition: opacity 0.4s ease-in-out;
}

/* Padding-left extra SOLO para los 8 botones de nivel 1 (los que se ven
   directamente en la barra estática) — los items de los paneles
   flotantes de subcategorías (nivel 2, 3, ...) ya tienen su propio ancho
   calibrado por separado y no comparten este ajuste. */
.sidebar-categorias--nivel1 > .sidebar-categoria > a {
  padding-left: 1.75rem;
}

/* Hover no cambia el color de fondo, baja la opacidad — con fondo sólido,
   el efecto aclara el color hacia el blanco de atrás. */
.sidebar-categoria > a:hover {
  opacity: 0.7;
}

/* Cualquier fila con color (propia o heredada, ver --con-color en
   includes/menu.php) lleva fondo sólido de marca + texto blanco (--cat-*
   en tokens.css — el color en sí es un dato de BD, aplicado inline desde
   menu.php; esta regla solo pone el tratamiento visual). Radio de esquina
   completo en las 4 esquinas. */
.sidebar-categoria--con-color > a {
  color: var(--cat-button-text);
  border-radius: 5px;
}

/* Mismo color de texto que la regla de arriba, pero para el chevron: como
   es un <button> HERMANO del <a> (no un hijo), no hereda `color` por
   estructura del DOM — hay que declararlo aparte para que el triángulo se
   vea blanco sobre fondo de color igual que el texto. */
.sidebar-categoria--con-color > .sidebar-chevron {
  color: var(--cat-button-text);
}

/* Espacio reservado a la derecha del texto para el chevron — el chevron
   en sí vive en un elemento aparte (ver `.sidebar-chevron` más abajo),
   esta regla solo reserva el hueco. Selector: clase
   `.sidebar-categoria--con-hijos` en el <li> (ver includes/menu.php), no
   un atributo sobre el propio <a> — el <a> no sabe nada del chevron. */
.sidebar-categoria--con-hijos > a {
  padding-right: 2rem; /* deja espacio a la derecha para que el texto no quede debajo del chevron */
}

/* Botón del chevron (▾) en categorías con hijos. Estructuralmente es un
   elemento HERMANO del <a> (ver includes/menu.php para el porqué no va
   anidado dentro de él: un <button> dentro de un <a> es HTML inválido),
   posicionado ENCIMA de la esquina superior derecha del <a> (el hueco que
   reserva `padding-right: 2rem` de la regla anterior) — al ser hermanos
   dentro del mismo <li position:relative>, un clic en uno nunca llega al
   otro. El comportamiento de clic (agregar la clase que revela el panel
   de subcategorías) lo maneja assets/js/sidebar.js — este bloque es solo
   la parte visual: reset de estilos nativos de <button>, área de toque y
   el triángulo dibujado con CSS puro. */
.sidebar-chevron {
  /* Reset de apariencia nativa de <button> — sin esto se ve como un botón
     de sistema (bordes, fondo gris, etc.) en vez de una superficie
     invisible con solo el triángulo dibujado encima. */
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent; /* evita el flash gris/azul que Android/iOS dibujan por default al tocar un elemento clickeable */

  position: absolute;
  top: 0;
  right: 0;
  /* `top:0; right:0; bottom:0;` (sin `height` fijo) en vez de
     `height:100%`: así el área de toque siempre ocupa el alto REAL del
     <li> sin importar si el texto del botón partió en 1 o 2 líneas
     (nivel 1) o es una sola línea corta (subcategorías). */
  bottom: 0;
  /* Ancho del área de toque: más generoso que el triángulo visual de 7px
     (que se dibuja centrado dentro, ver ::after) pero acotado para no
     invadir el `padding-right: 2rem` ya reservado para el texto del
     link — el alto cubre la fila completa (bottom:0 arriba), dando un
     área de toque más alta que ancha, fácil de acertar con el dedo. */
  width: 2.25rem;
}

/* Combinador `~` (hermano siguiente): el <a> siempre se imprime ANTES que
   el <button> en includes/menu.php, así que "cuando el <a> anterior está
   en :hover, atenuar este chevron que le sigue" iguala el efecto de
   opacidad de toda la "pastilla" (texto + chevron) sin duplicar la
   transición en ambos elementos. */
.sidebar-categoria > a:hover ~ .sidebar-chevron {
  opacity: 0.7;
}

.sidebar-chevron::after {
  content: "";
  position: absolute;
  right: 0.9rem;
  /* `top` igual al `padding-top` del link hermano, para que el chevron
     arranque justo donde arranca el texto (la primera línea, en botones
     con texto envuelto a 2 líneas). Si el padding-top del link
     (`.sidebar-categoria > a`, arriba) vuelve a ajustarse, este valor debe
     actualizarse en conjunto. */
  top: 0.375rem;
  width: 7px;
  height: 7px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateY(0.15rem) rotate(45deg);
  /* Misma duración/curva que la transición del panel (ver
     .sidebar-subcategorias más abajo) para que el giro del chevron y el
     fundido del panel se perciban como una sola animación, no dos
     independientes. */
  transition: transform 0.25s ease;
}

/* Chevron apuntando hacia arriba mientras el panel está abierto — mismo
   truco de esquina (border-right + border-bottom), solo que rotado otros
   180° (45° → 225°) en vez de un carácter o ícono distinto: es la señal
   visual de que un segundo tap/clic en el mismo botón lo vuelve a
   colapsar. Mismos tres selectores que activan el panel (ver
   .sidebar-subcategorias más abajo) para que el chevron y el panel
   cambien de estado siempre juntos, sin importar cómo se abrió. */
.sidebar-categoria:focus-within > .sidebar-chevron::after,
.sidebar-categoria--abierto > .sidebar-chevron::after {
  transform: translateY(0.15rem) rotate(225deg);
}

@media (hover: hover) and (pointer: fine) {
  .sidebar-categoria:hover > .sidebar-chevron::after {
    transform: translateY(0.15rem) rotate(225deg);
  }
}

/* Panel flotante de subcategorías: oculto por default, aparece a la
   derecha de su categoría al pasar el mouse, al llegar por teclado (ver
   :focus-within abajo), o al tocar el chevron en touch (ver
   .sidebar-categoria--abierto abajo). Fondo semitransparente + sombra
   suave para que lea como un panel flotando sobre la página. `opacity` +
   `visibility` (en vez de `display:none/block`) porque `display` no es
   animable — el `transition-delay` en `visibility` hace que el panel deje
   de ser interactivo/detectable por mouse recién cuando el fundido de
   salida termina, no de golpe al inicio. El `transform: translateX(-15px)`
   en estado oculto (animando a `translateX(0)` visible) reproduce el
   desplazamiento horizontal que tiene la animación de apertura en el
   mockup de referencia, en simultáneo con el fundido de opacidad. */
.sidebar-subcategorias {
  position: absolute;
  left: 100%;
  top: 0;
  min-width: 220px;
  padding: 0.75rem 0.5rem;
  background: rgba(255, 255, 255, 0.75);
  border-radius: 4px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
  z-index: 20;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-15px);
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0s linear 0.25s;
}

/* Tres condiciones distintas activan el mismo panel, y las tres disparan
   la MISMA transición (se ve y anima igual sin importar cómo se abrió):
   `:focus-within` para navegación por teclado (Tab hasta el link de una
   categoría con hijos — mejora barata de accesibilidad; no hay forma de
   "cerrar" el panel con teclado más que tabular fuera de él) y
   `.sidebar-categoria--abierto`, que agrega/quita assets/js/sidebar.js al
   tocar el `<button class="sidebar-chevron">` — la única forma de llegar
   a este panel en una pantalla táctil, donde no existe ":hover". El
   `:hover` real (mouse) vive en un bloque aparte, ver el siguiente. Al
   ENTRAR no hay `transition-delay` en `visibility` (a diferencia de la
   regla base de arriba): se vuelve visible de inmediato mientras
   `opacity` sube, para que el fundido se vea desde el primer instante. */
.sidebar-categoria:focus-within > .sidebar-subcategorias,
.sidebar-categoria--abierto > .sidebar-subcategorias {
  opacity: 1;
  visibility: visible;
  transform: translateX(0); /* posición final del desplazamiento horizontal */
  transition: opacity 0.25s ease, transform 0.25s ease;
}

/* `@media (hover: hover) and (pointer: fine)` limita el `:hover` real a
   dispositivos con un puntero fino que sostiene un hover genuino (mouse o
   trackpad) — en touch, un tap puede simular `:hover` de forma "pegajosa"
   en el navegador (se queda activo hasta tocar otra parte de la
   pantalla), lo que pelearía con el cierre por JS de la clase
   `--abierto` de la regla anterior. En un dispositivo táctil esta regla
   completa queda fuera de la hoja de estilos aplicada, así que ahí la
   única forma de abrir/cerrar el panel es la clase `--abierto`. En
   escritorio esta condición siempre es verdadera, sin cambio de
   comportamiento. */
@media (hover: hover) and (pointer: fine) {
  .sidebar-categoria:hover > .sidebar-subcategorias {
    opacity: 1;
    visibility: visible;
    transform: translateX(0);
    transition: opacity 0.25s ease, transform 0.25s ease;
  }
}

/* Texto normal/atenuado para el caso (hoy inexistente, pero posible en el
   futuro) de una subcategoría sin ningún color heredado — así nunca queda
   un link invisible (blanco sobre blanco) si aparece una rama sin
   categoría de marca como ancestro. Incluye `.sidebar-chevron` por el
   mismo motivo defensivo, aunque `font-weight` no tenga efecto visual
   sobre un <button> sin texto. */
.sidebar-subcategorias .sidebar-categoria:not(.sidebar-categoria--con-color) > a,
.sidebar-subcategorias .sidebar-categoria:not(.sidebar-categoria--con-color) > .sidebar-chevron {
  font-weight: 400;
  color: var(--color-body-text);
}

/* ─── Home: tarjetas de categoría de nivel 1 (index.php) ──────────────
   Mismo color sólido + texto blanco que los botones del sidebar, pero como
   tarjetas grandes en vez de items de lista — es la puerta de entrada
   visual a cada categoría desde la home. Ícono + nombre + insignia "+"
   verificados contra el mosaico real de mupa2.com.mx (colores, orden e
   íconos: ver contenido.php/bloques.php; fondo de sección: ver
   .bloque-home--categorias más abajo). */
.grid-categorias-inicio {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 2rem 0 0;
  padding: 0;
}

/* NO auto-fit/auto-fill aquí (a diferencia de Productos Destacados, ver
   esa regla más abajo): ese mecanismo calcula cuántas columnas caben
   según un mínimo de ancho, y con 8 tarjetas fijas eso significa que la
   cantidad de columnas CAMBIA según qué tan ancha esté la pantalla —
   probado en vivo: 5 columnas a 1466px, 6 a 1920px, nunca las 4 fijas
   del mockup (Divi arma este mosaico como fila FIJA de 4, clase
   `et_pb_row_4col`, no como lista que fluye). `repeat(4, 1fr)` fija el
   número de columnas sin importar el ancho — las tarjetas se estiran
   parejas igual que en Productos Destacados, pero siempre son 4, nunca
   más. Se reduce a 2 en el mismo punto de quiebre que el resto del sitio
   usa para su sidebar/contenido (ver .sitio-layout), y se QUEDA en 2
   hasta el ancho mínimo — José, 2026-08-08: "no porque no se vean bien
   en una columna, sino porque la lista queda innecesariamente larga" —
   a diferencia de otras grillas del sitio (Productos Destacados,
   Catálogos, Noticias) que sí llegan a 1 columna, esta es la única
   excepción deliberada por densidad de la lista, no por legibilidad de
   la tarjeta individual. */
.bloque-home--categorias .grid-categorias-inicio {
  grid-template-columns: repeat(4, 1fr);
}

@media (max-width: 900px) {
  .bloque-home--categorias .grid-categorias-inicio {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* `position: relative`: contenedor de posicionamiento para la insignia
   `position: absolute` de abajo (ver esa regla — necesita quedar al ras
   exacto de esta esquina, verificado en vivo que en el mockup real el
   link `a.botonesquina` tiene gap 0px contra el borde de su columna). */
.categoria-inicio-card {
  position: relative;
  border-radius: 6px;
  overflow: hidden;
}

/* Contenido no-clicable de la tarjeta (ícono + nombre): 2026-08-08, ver
   docblock de renderBloqueCategorias() en bloques.php — antes esto era
   el <a> completo de la tarjeta, ahora es un <div> sin link porque el
   único link real es la insignia "+", igual que en el mockup. Mismos
   valores exactos de layout que el <a> anterior (nada de esto cambió,
   solo el elemento contenedor). */
.categoria-inicio-card__contenido {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 100%;
  min-height: 220px;
  padding: 2.5rem 1.5rem 1rem;
  color: var(--cat-button-text);
  text-align: center;
}

.categoria-inicio-card__icono {
  margin-bottom: 1.25rem;
}

.categoria-inicio-card__nombre {
  font-family: var(--font-heading);
  font-size: var(--fs-h2);
  font-weight: var(--fw-h2);
  letter-spacing: var(--ls-heading);
}

/* Insignia "+": 2026-08-08, ver docblock de renderBloqueCategorias() en
   bloques.php — ahora es el único <a> real de la tarjeta (antes era
   `aria-hidden` sobre un <span> decorativo dentro del <a> que envolvía
   toda la tarjeta). `position: absolute` contra `.categoria-inicio-card`
   con `right: 0; bottom: 0`, en vez del truco de márgenes negativos
   anterior: José reportó (con captura) que esos márgenes dejaban un
   hueco visible contra el borde derecho porque el <a> padre era flex
   con `align-items: center` (centra cada hijo por su propio ancho, un
   margen negativo ahí no garantiza tocar el borde) — confirmado además
   en vivo contra mupa2.com.mx que el link real de cada tarjeta tiene
   gap 0px exacto contra el borde de su columna, no unos px de aire.
   `right/bottom: 0` es la forma directa y robusta de lograr eso sin
   depender del layout del contenido de al lado.
   Medidas, color y sombra verificados con getComputedStyle en vivo:
   background #d4e4ba, texto verde oscuro (var(--color-accent-default)),
   radio SOLO en la esquina superior izquierda (35px, el resto en 0px)
   — con `overflow: hidden` en `.categoria-inicio-card`, el resultado
   visual es el cuarto de círculo del mockup — y
   `box-shadow: 0 2px 18px rgba(0,0,0,.1)`, antes ausente aquí.
   `opacity`: José describe que en el mockup el botón se ve "más
   transparente" en reposo y "más brillante" (menos transparente) en
   hover; el mockup real no expuso ese cambio de forma medible en vivo
   (mismo valor computado en reposo y en :hover — puede depender de un
   trigger JS que no se logró disparar por inspección remota), así que
   los valores de opacidad de abajo son una implementación directa de
   la descripción de José, no una medición 1:1 — ajustar si al verlo en
   pantalla el contraste no es el esperado. */
.categoria-inicio-card__insignia {
  display: flex;
  position: absolute;
  right: 0;
  bottom: 0;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 35px 0 0 0;
  background: #d4e4ba;
  box-shadow: 0 2px 18px rgba(0, 0, 0, 0.1);
  color: var(--color-accent-default);
  font-size: 20px;
  font-weight: 600;
  text-decoration: none;
  opacity: 0.75;
  transition: opacity 0.25s ease;
}

.categoria-inicio-card__insignia:hover,
.categoria-inicio-card__insignia:focus-visible {
  opacity: 1;
}

/* ─── Listado de categoría (categoria.php) ──────────────────────────
   Grid simple de tarjetas de producto. auto-fill + minmax: el número de
   columnas se ajusta solo al ancho de pantalla, sin media queries
   manuales por breakpoint. */
.grid-productos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

.tarjeta-producto {
  border: 1px solid #eee;
  border-radius: 6px;
  overflow: hidden;
}

/* `object-fit: contain` (no `cover`): las 14 fotos reales de producto
   son cuadradas (700×700, ver bitácora "Placeholders de producto" en
   README.md), pero el recuadro de la tarjeta es 4/3 — con `cover` se
   recortaban arriba/abajo para rellenar el rectángulo (reportado por
   José: "se ven como recortadas"). `contain` ajusta la foto completa
   dentro del recuadro (deja aire a los lados en vez de recortar
   arriba/abajo) y de paso queda a prueba de cualquier producto futuro
   que no sea cuadrado. El placeholder SVG (400×300, exactamente 4/3) no
   se ve afectado por el cambio: ya llena el recuadro completo con
   cualquiera de los dos modos. */
.tarjeta-producto img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  background: #fff; /* blanco (no #f2f2f2): las fotos reales ya vienen sobre fondo blanco, así el margen que deja `contain` en fotos cuadradas se funde con la foto en vez de notarse como un borde gris (reportado por José) */
}

.tarjeta-producto .tarjeta-producto__cuerpo {
  padding: 0.75rem 1rem 1rem;
}

.tarjeta-producto h3 {
  font-size: var(--fs-body);
  margin-bottom: 0.25rem;
}

.tarjeta-producto .tarjeta-producto__sku {
  font-size: 12px;
  color: #999;
}

.categoria-vacia {
  color: #999;
  font-style: italic;
}

/* ─── Breadcrumb (categoria.php y producto.php) ───────────────────────
   Clase compartida entre las dos páginas: ambas imprimen la RUTA COMPLETA
   (ver obtenerRutaCategoria() en includes/menu.php), con la misma
   estructura de segmentos separados por "/". */
.breadcrumb {
  font-size: 13px;
  margin-bottom: 1rem;
}

.breadcrumb__separador {
  margin: 0 0.4em;
  color: #ccc;
}

.ficha-producto {
  display: grid;
  grid-template-columns: minmax(280px, 480px) 1fr;
  gap: 2rem;
  align-items: start;
}

.ficha-producto__imagen {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain; /* mismo motivo que .tarjeta-producto img arriba: fotos cuadradas, recuadro 4/3 — contain en vez de cover para no recortar */
  background: #fff; /* blanco, no gris — mismo motivo que .tarjeta-producto img arriba (reportado por José: se notaba el borde) */
  border-radius: 6px;
}

/* h1 de la ficha usa el acento de categoría en vez del verde de marca fijo
   (--color-h1) — es la única excepción a "h1 siempre --color-h1" en todo
   el sitio, documentada aquí para que no se vea como un olvido. */
.ficha-producto__info h1 {
  color: var(--acento-producto, var(--color-h1));
}

.ficha-producto__sku {
  font-size: 13px;
  color: #999;
  margin: 0 0 1rem;
}

.ficha-producto__descripcion {
  margin-bottom: 1.5rem;
}

/* Botón outline sobre el acento de categoría en vez de sobre una imagen
   con overlay oscuro (que es el caso del mockup de referencia) — aquí el
   fondo es plano, así que se invierte a "relleno sólido con texto blanco"
   para que siga siendo legible; misma forma asimétrica de esquinas
   (--btn-outline-radius). */
.btn-outline {
  display: inline-block;
  background: var(--acento-producto, var(--color-accent-default));
  color: var(--btn-outline-text);
  border: var(--btn-outline-border);
  border-radius: var(--btn-outline-radius);
  font-size: var(--btn-outline-font-size);
  padding: var(--btn-outline-padding);
  cursor: not-allowed; /* disabled hasta que exista el flujo de cotizador — ver comentario en producto.php */
  opacity: 0.85;
}

/* Lista "Categorías:" al final de la ficha (N:N — ver consulta de
   $categoriasProducto en producto.php). Distinta del breadcrumb de arriba:
   el breadcrumb es UNA ruta (la de origen), esto es TODAS las categorías a
   las que pertenece el producto. */
.ficha-producto__categorias {
  margin-top: 1.5rem;
  font-size: 13px;
  color: #999;
}

.ficha-producto__categorias a {
  color: inherit;
  text-decoration: underline;
}

/* ─── Footer ─────────────────────────────────────────────────────── *
   Plantilla global de Divi (`et-l et-l--footer`), verificada IDÉNTICA
   con getComputedStyle en dos páginas distintas (home y /contacto/,
   mismo `outerHTML.length`) — a diferencia de la franja "Suscríbete al
   boletín" (home-only, confirmado con el mismo método), este footer
   completo se repite en todas las páginas del sitio real.

   `.sitio-sidebar` es `position: fixed` (ver esa regla arriba), así que
   queda encima de CUALQUIER contenido sin importar el scroll — el
   footer necesita el mismo `margin-left: var(--sidebar-ancho)` que ya
   usa `.sitio-main` para no quedar tapado, aunque en el HTML el
   `<footer>` es hermano de `.sitio-layout`, no su hijo (por eso no
   hereda ese margen automáticamente). Sin negative-margin de
   full-bleed (a diferencia de Hero/Informativa/Boletín): esos viven
   DENTRO del padding de `.sitio-layout` y necesitan cancelarlo; el
   footer vive FUERA de ese contenedor, así que ya es naturalmente
   ancho completo desde el borde del sidebar hasta el borde derecho de
   la ventana. */
.sitio-footer {
  margin-left: var(--sidebar-ancho);
}

/* Sección superior: fondo #607a24 — coincide exacto con
   --cat-espacios-publicos, pero es coincidencia del propio mockup (ese
   verde es el fondo de TODO el footer, no una referencia a esa
   categoría) — se deja fijo con esta nota, mismo criterio que el navy
   del bloque Boletín. */
.sitio-footer__superior {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2rem;
  padding: 3rem 2rem 2.5rem;
  background: #607a24;
  text-align: center;
}

/* Fila superior: tagline (izquierda) — íconos (centro) — logo
   (derecha), medido en vivo con `space-between` entre los 3 hijos
   directos (el mockup lo arma con columnas Divi de ancho fijo, mismo
   criterio ya usado en Boletín de preferir flex sobre replicar ese
   mecanismo). `flex-wrap: wrap` + `max-width` centrado para que no se
   estire a todo lo ancho en pantallas grandes ni se encime en angostas. */
.sitio-footer__marca {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.5rem;
  width: 100%;
  max-width: 900px;
}

.sitio-footer__tagline {
  flex: 1 1 190px;
  max-width: 220px;
  margin: 0;
  color: #fff;
  font-family: var(--font-body);
  font-size: 21px;
  font-weight: 500;
  text-align: left;
}

.sitio-footer__redes {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Círculo de 56px (32px de ícono + 12px de padding, medido en vivo
   contra mupa2.com.mx — `.et_pb_social_icon a`, padding: 12px,
   border-radius: 100px) sobre fondo `#f8faf5` (el mismo "verde claro"
   que ya usan los paneles de Hero/Informativa) con el ícono en navy
   `#23383f` — mismo navy que el título/botón de Boletín. Extiende
   `.enlace-pendiente` (href="#top" en el mockup también, ver footer.php)
   en vez de reemplazarlo del todo: se sobreescribe el tratamiento visual
   completo (era pensado para texto suelto, no para un botón circular),
   pero se conserva `cursor: help` como la señal de "no lleva a ningún
   lado todavía" — mismo criterio que el resto del sitio.

   Selector compuesto `.sitio-footer__redes .sitio-footer__red` (no solo
   `.sitio-footer__red`): `.enlace-pendiente` se define MÁS ABAJO en este
   archivo, así que por orden de cascada (misma especificidad de una sola
   clase) ganaba ella sobre este override si el selector se quedaba en una
   sola clase — verificado con getComputedStyle en `mupa.test`, el
   border-bottom punteado se colaba en el círculo. Dos clases sube la
   especificidad lo suficiente sin recurrir a `!important` (que no se usa
   en ningún otro lugar de este archivo). */
.sitio-footer__redes .sitio-footer__red {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  margin-top: 0;
  border-bottom: 0;
  border-radius: 999px;
  background: #f8faf5;
  opacity: 1;
}

.sitio-footer__red img {
  width: 18px;
  height: 18px;
}

.sitio-footer__logo {
  flex: 0 0 auto;
  width: 220px;
  height: auto;
}

/* Nav de las 8 categorías raíz (mismas del sidebar, ver
   obtenerCategoriasRaizFooter() en menu.php) — fila centrada con wrap,
   Poppins 700 13px mayúsculas blanco, mismo tratamiento medido en vivo
   que el resto de textos en negritas del sitio. El `gap` (24px columna,
   igual medido; algo más de aire en fila para que no se sientan
   amontonadas al envolver) reemplaza el padding por `<li>` que usa Divi
   para el mismo efecto visual. */
.sitio-footer__categorias {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem 1.5rem;
  width: 100%;
  max-width: 700px;
}

.sitio-footer__categorias a {
  color: #fff;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  text-decoration: none;
}

.sitio-footer__categorias a:hover {
  text-decoration: underline;
}

/* Párrafo de palabras clave (SEO, texto real y completo del mockup, ver
   footer.php) — Poppins 400 15px blanco, letter-spacing 0.7px, medido en
   vivo. `max-width` para que no se estire ilegible a todo lo ancho en
   pantallas muy grandes. */
.sitio-footer__keywords {
  max-width: 820px;
  margin: 0;
  color: #fff;
  font-family: var(--font-body);
  font-size: 15px;
  letter-spacing: 0.7px;
  line-height: 1.3;
}

/* Sección inferior: fondo #526723 — coincide exacto con --color-h1 (el
   verde de marca, no una coincidencia esta vez: es el mismo verde que
   ya usa el h1 del sitio, reutilizado aquí a propósito). */
.sitio-footer__legal {
  padding: 1.25rem 2rem;
  background: var(--color-h1);
}

.sitio-footer__legal p {
  max-width: 760px;
  margin: 0 auto;
  color: #f8faf5;
  font-family: var(--font-body);
  font-size: 13px;
  text-align: center;
}

@media (max-width: 700px) {
  /* `.sitio-sidebar` deja de ser `position: fixed` en mobile (ver esa
     regla más abajo) — el margen que lo esquiva ya no hace falta, mismo
     ajuste que `.sitio-main` hace para sí mismo en este breakpoint. */
  .sitio-footer {
    margin-left: 0;
  }

  .sitio-footer__marca {
    justify-content: center;
    text-align: center;
  }

  .sitio-footer__tagline {
    text-align: center;
  }
}

/* ─── Bloques de la página de inicio (index.php + includes/home/) ──────
   Estilo deliberadamente MÍNIMO: el objetivo de esta pasada es la
   separación datos/orquestación/render (ver includes/home/contenido.php),
   no el acabado visual pixel-a-pixel contra el mockup — eso es un pase
   aparte, una vez confirmado que la arquitectura de bloques es la
   correcta. Cada <section class="bloque-home"> es un bloque completo de
   index.php; el espaciado vertical entre bloques vive aquí, no en cada
   bloque individual, para que agregar/quitar bloques no desalinee nada. */
.bloque-home {
  padding: 2.5rem 0;
  border-top: 1px solid #f0f0f0;
}

.bloque-home:first-child {
  border-top: none;
}

/* Hero (includes/home/bloques.php, renderBloqueHero()). Fondo verdoso a
   sangre completa: `margin-left`/`margin-right` negativos cancelan el
   padding horizontal compartido de `.sitio-layout` (2rem) para llegar
   hasta el sidebar y hasta el borde de la ventana — mismo mecanismo que
   `.bloque-home--carrusel` (ver esa regla más abajo). `margin-top: 0`
   (no negativo, a diferencia del carrusel): este bloque no es el primero
   pegado a `.sitio-layout`, así que no hay padding de ese ancestro que
   cancelar aquí — el carrusel ya deja su propio borde inferior al ras.
   Sin `border-radius`: bloque estático, sin el recorte a media foto que
   sí justifica la esquina redondeada del carrusel. El aire hacia el
   bloque de abajo (Productos Destacados) lo aporta el padding-top propio
   de ESE bloque, no `margin-bottom` de este. */
.bloque-home--hero {
  background-color: #f8faf5;
  margin: 0 -2rem;
  padding: 2.5rem 2rem;
}

/* El -2rem de arriba cancela el padding horizontal de escritorio de
   `.sitio-layout` (2rem, ver esa regla). En mobile ese padding se pone
   en 0 y es `.sitio-main` quien aporta el suyo propio (1.25rem, ver el
   bloque `@media (max-width: 700px)` correspondiente), así que aquí hay
   que cancelar ESE valor en su lugar. */
@media (max-width: 700px) {
  .bloque-home--hero {
    margin-left: -1.25rem;
    margin-right: -1.25rem;
  }
}

/* Título centrado; los pilares de abajo siguen alineados a la izquierda
   dentro de cada columna. */
.bloque-home--hero h1 {
  text-align: center;
}

/* Nota visible de "falta contenido real" (ver renderAvisoPendiente() en
   includes/home/bloques.php). Empezó con el mismo azul de
   `.enlace-pendiente` (borde punteado, texto azul, sin relleno) para
   leerse como la misma señal de "falta esto" en todo el sitio — pero
   José reportó que así se confundía con el resto del diseño (mismo tono
   que un link normal) y pidió un recuadro que destaque más: relleno
   amarillo paja sólido en vez de solo un borde punteado sobre fondo
   blanco. Texto en un café oscuro (no el azul de `--color-link`, que ya
   no tiene contraste suficiente sobre amarillo) para que siga leyéndose
   como nota/advertencia y no como un link. */
.bloque-home__aviso-pendiente {
  margin-top: 1rem;
  padding: 0.5rem 0.75rem;
  border: 1px dashed #c9a227;
  border-radius: 4px;
  background: #fbeec2;
  font-size: 12px;
  font-style: italic;
  color: #6b5a1e;
  max-width: 640px;
}

/* Botón flotante "Ocultar/Mostrar avisos" (ver assets/js/avisos-
   pendientes.js e includes/footer.php) — apaga TODOS los recuadros de
   arriba de un solo golpe, sin importar en qué página/bloque estén.
   Mismo amarillo paja de los recuadros que controla, para que se lea
   como "el interruptor de esto" a simple vista. `z-index: 200` (por
   encima de `.sitio-sidebar`, que usa 100 — ver esa regla) para que
   nunca quede tapado. Esquina inferior DERECHA a propósito: el mockup
   trae su propio botón flotante de WhatsApp abajo a la izquierda (ver
   captura de referencia) — este sitio todavía no lo construye, pero se
   deja el lado libre para cuando exista, en vez de competir por el
   mismo rincón. */
.boton-flotante-avisos {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 200;
  padding: 0.75rem 1.25rem;
  border: none;
  border-radius: 999px;
  background: #fbeec2;
  color: #6b5a1e;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  cursor: pointer;
}

.boton-flotante-avisos:hover {
  background: #f5e3a0;
}

/* La clase que el botón prende/apaga en <body> — una sola regla cubre
   TODOS los lugares del sitio que usan `.bloque-home__aviso-pendiente`
   (home, footer, noticias.php, proyectos.php, formulario_lead.php,
   includes/widgets.php) porque todos comparten esa misma clase. */
body.ocultar-avisos-pendientes .bloque-home__aviso-pendiente {
  display: none;
}

/* ─── Widgets globales de sitio (includes/widgets.php) ──────────────
   Namespace propio (`widget-flotante-*`, no `bloque-home*`): a
   diferencia de los bloques de home, estos NO viven dentro del flujo
   normal del documento — son overlays `position: fixed` en TODAS las
   páginas, sin padding de bloque ni border-top de separador. */

/* Botón de WhatsApp: verde de marca de WhatsApp (#25d366, no un token
   de este sitio — es el color con el que el usuario reconoce el
   servicio, mismo criterio que usar el ícono real en vez de uno
   genérico). Esquina inferior IZQUIERDA a propósito — el botón de
   avisos (`.boton-flotante-avisos` arriba) ya reservó la derecha para
   sí mismo, y el mockup real también trae WhatsApp a la izquierda. */
.widget-flotante-whatsapp {
  /* `border: 0; padding: 0;` — reportado por José como "borde raro": es
     un `<button>` sin resetear, así que Chrome le dibuja su borde nativo
     (`2px outset`, confirmado con getComputedStyle) encima del círculo
     verde. Mismo reset que YA tienen todos los demás botones del sitio
     (`.boton-flotante-avisos`, `.buscador__icono`, `.buscador__cerrar`,
     `.widget-whatsapp__cerrar`) — este quedó como excepción por
     descuido, no por diseño. `cursor: pointer` tampoco estaba (el
     navegador ya lo pone en botones por default, pero se deja explícito
     por consistencia con los otros botones flotantes del sitio). */
  border: 0;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  position: fixed;
  left: 1.5rem;
  bottom: 1.5rem;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: 999px;
  background: #25d366;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
  /* Delay + animación de entrada — calcados del mockup real (plugin
     Joinchat/Creame WhatsApp Me: `window.joinchat_obj.settings.button_delay`
     confirmado en vivo en 3 segundos; la curva de bezier
     `cubic-bezier(.18,.89,.32,1.28)` es literal de su CSS —
     `joinchat.min.css`, animación `joinchat_show`, valores >1 son los
     que producen el rebote/overshoot final).
     `animation-delay: 3s` + `fill-mode: both` en vez de un setTimeout
     en JS (que es como lo resuelve Joinchat, con más lógica alrededor
     — mobile-only, tracking, etc. que este widget no necesita): el
     fotograma 0% (escala 0) se mantiene durante los 3s de delay en vez
     de mostrar el botón sin animar y luego "saltar" a la animación,
     así el botón simplemente no existe visualmente hasta que le toca
     aparecer. `transform-origin: bottom left` en vez de centro: el
     botón crece desde la esquina donde está anclado (mismo criterio
     que el mockup, que lo ancla —y hace crecer— desde SU esquina, la
     inferior derecha, ver `transform-origin` en `.joinchat` de
     joinchat.min.css). */
  transform-origin: bottom left;
  animation: widget-whatsapp-entrada 0.5s cubic-bezier(0.18, 0.89, 0.32, 1.28) 3s both;
}

.widget-flotante-whatsapp:hover {
  background: #20bd5a;
}

@keyframes widget-whatsapp-entrada {
  0% {
    transform: scale(0);
  }
  100% {
    transform: scale(1);
  }
}

/* Mismo criterio de accesibilidad que el propio mockup (ver
   `@media (prefers-reduced-motion)` en joinchat.min.css): sin
   animación para quien configuró su sistema para reducir movimiento.
   Quitar la animación completa (no solo el rebote) también anula el
   `animation-delay`/`fill-mode` de la regla base — el botón queda
   visible de inmediato en vez de esperar 3s invisible; es la lectura
   más simple de "reducir movimiento" (cero sorpresas de temporización,
   no solo cero rebote) y no depende de duplicar el delay por otro
   medio (`transition`, JS) solo para este caso. */
@media (prefers-reduced-motion: reduce) {
  .widget-flotante-whatsapp {
    animation-name: none;
  }
}

/* Caja de chat (includes/widgets.php, renderWidgetWhatsapp()) — se abre
   al hacer clic en `.widget-flotante-whatsapp`, reemplazándolo (mismo
   ancla `left/bottom`, uno de los dos siempre oculto vía
   `.widget-whatsapp--abierto`, ver más abajo — nunca los dos visibles a
   la vez, igual que el mockup real: `.joinchat--chatbox .joinchat__button
   {display:none}`). Tema oscuro calcado del mockup (`.joinchat--dark`):
   fondo casi negro, burbujas gris oscuro — es el único elemento oscuro
   de todo el sitio, deliberado (mismo "skin" que WhatsApp Web/App usa
   para sus propias burbujas de chat, reconocible para cualquiera que use
   la app). Sin animación de entrada propia (a diferencia del botón
   circular): abre/cierra instantáneo vía `display`, ver
   assets/js/widget-whatsapp.js — mismo criterio de simplicidad que
   assets/js/buscador.js. */
.widget-whatsapp__caja {
  display: none;
  position: fixed;
  left: 1.5rem;
  bottom: 1.5rem;
  z-index: 210; /* por encima del botón (200) y de .boton-flotante-avisos */
  flex-direction: column;
  width: min(320px, calc(100vw - 3rem));
  max-height: calc(100vh - 3rem);
  border-radius: 16px;
  overflow: hidden;
  background: #1a1a1a;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.35);
}

.widget-whatsapp--abierto .widget-whatsapp__caja {
  display: flex;
}

.widget-whatsapp--abierto .widget-flotante-whatsapp {
  display: none;
}

.widget-whatsapp__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
  padding: 0.9rem 1.1rem;
  background: #25d366;
}

.widget-whatsapp__titulo {
  font-weight: 700;
  font-size: 1.05rem;
  color: #fff;
}

.widget-whatsapp__cerrar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.25);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.widget-whatsapp__cerrar:hover {
  background: rgba(0, 0, 0, 0.4);
}

.widget-whatsapp__cuerpo {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  padding: 1.1rem;
  overflow-y: auto;
}

/* Burbujas alineadas a la izquierda (`align-self: flex-start`, no
   `width: 100%`): mismo criterio visual que cualquier chat — el mensaje
   corto se ve como mensaje, no como una barra que cruza toda la caja. */
.widget-whatsapp__burbuja {
  align-self: flex-start;
  max-width: 85%;
  margin: 0;
  padding: 0.6rem 0.9rem;
  border-radius: 16px;
  background: #505050;
  color: #d8d8d8;
  font-size: 0.9rem;
  line-height: 1.4;
}

/* Reemplaza el video/sticker del mockup (ver docblock de
   renderWidgetWhatsapp() en includes/widgets.php para el porqué) — un
   emoji simple, centrado, sin fondo de burbuja (es decorativo, no un
   mensaje de texto). */
.widget-whatsapp__sticker {
  align-self: center;
  margin: 0.25rem 0;
  font-size: 3rem;
  line-height: 1;
}

.widget-whatsapp__abrir {
  display: block;
  flex-shrink: 0;
  margin-top: 0.4rem;
  padding: 0.7rem 1rem;
  border-radius: 999px;
  background: #25d366;
  color: #fff;
  font-weight: 700;
  text-align: center;
  text-decoration: none;
}

.widget-whatsapp__abrir:hover {
  background: #20bd5a;
}

/* Aviso de "pendiente" de un widget desactivado (ver
   renderAvisoPendienteWidget() en includes/widgets.php) — se imprime
   suelto al final de <body>, fuera del flujo normal de contenido, así
   que necesita su propio margen para no quedar pegado al footer. */
.widget-flotante__aviso-pendiente {
  margin: 1rem 1.5rem;
}

/* Texto que en el mockup es un link pero todavía no tiene página destino
   real (ver docblock de contenido.php: "un link que no lleva a ningún
   lado es peor que una etiqueta que se ve pero no promete nada" — mismo
   criterio ya usado en el nav superior, includes/header.php). */
.enlace-pendiente {
  display: inline-block;
  margin-top: 0.5rem;
  color: var(--color-link);
  border-bottom: 1px dashed var(--color-link);
  cursor: help;
  opacity: 0.75;
}

/* `margin-top: 3.25rem` (antes 1.5rem): José reportó que el espacio
   entre el h1 y esta fila se ve más apretado que en el mockup real.
   Medido en vivo contra mupa2.com.mx (getBoundingClientRect del h1 vs.
   del primer h3 "HECHO EN MÉXICO"): 52px de separación = 3.25rem a
   16px base — se ajusta a ese valor exacto. */
.bloque-hero__pilares {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 3.25rem 0 0;
  padding: 0;
}

/* Cada pilar en Oswald (el `h3` ya la trae por herencia de la regla base
   `h2, h3` más arriba — `--font-heading` — esto la fuerza también en el
   párrafo de descripción, que si no heredaría Poppins del body) más una
   franja vertical izquierda del mismo verde que el `<h1>` de la sección
   (`var(--color-h1)` — no `--color-heading-accent`, que es el verde/teal
   distinto que ya trae cada `h3`). */
.bloque-hero__pilar {
  font-family: var(--font-heading);
  border-left: 3px solid var(--color-h1);
  padding-left: 1rem;
}

.bloque-hero__pilar h3 {
  margin-bottom: 0.35rem;
  font-size: var(--fs-h2);
  font-weight: var(--fw-h2);
  text-transform: uppercase;
}

.bloque-hero__pilar p {
  color: var(--color-heading-accent);
  font-size: var(--fs-h3);
  text-transform: uppercase;
}

/* ─── Carrusel de categorías (includes/home/bloques.php,
   renderBloqueCarrusel()). Es el primer bloque de la home, ANTES del
   Hero (ver obtenerBloquesHome() en contenido.php) — su selector es el
   modificador `.bloque-home--carrusel` que ya imprime index.php para
   TODOS los bloques (`bloque-home--<tipo>`), no una clase nueva en el
   <section>.
   `overflow: hidden` oculta la mitad duplicada de la pista fuera del
   viewport del bloque — el contenido en sí no se recorta nunca, solo lo
   que cae fuera de este contenedor.
   A diferencia del resto de la home, este bloque CANCELA el padding
   compartido de `.sitio-layout` (1.5rem 2rem) con márgenes negativos en
   vez de sumarle algo, para ir a sangre completa contra el sidebar y la
   barra superior y llegar hasta el borde derecho de la ventana — es el
   único bloque con fotos de borde a borde, por eso el único con esta
   excepción. El padding-bottom (heredado de `.bloque-home`) también se
   cancela, para que `border-radius` recorte justo contra la foto en vez
   de contra espacio en blanco. El aire antes del Hero lo aporta el
   padding-top de ESE bloque (cada `.bloque-home` trae el suyo por
   separado, no es el mismo espacio). */
.bloque-home--carrusel {
  overflow: hidden;
  border-radius: 8px;
  padding: 0;
  margin-top: -1.5rem;
  margin-left: -2rem;
  margin-right: -2rem;
}

/* La cancelación de arriba usa los valores de escritorio de
   `.sitio-layout` (1.5rem 2rem) — en mobile ese padding se pone en 0 y
   es `.sitio-main` quien aporta el suyo propio (ver el bloque
   `@media (max-width: 700px)` de más abajo), así que aquí hay que
   cancelar ESE padding en su lugar, con sus propios valores.
   En mobile la barra superior no es un <header> normal en el flujo — es
   `.sitio-sidebar__barra`, `position: fixed` (ver ese selector más
   abajo), así que no "empuja" contenido: el padding-top de `.sitio-main`
   es puro espacio simulado para no quedar tapado por ella. Por eso aquí
   solo se cancela el 1.5rem EXTRA que suma `.sitio-main` (el aire, no la
   barra) — `--menu-movil-barra-alto` (56px) se deja intacto, así el
   carrusel empieza justo debajo del borde de la barra fija, no detrás de
   ella. */
@media (max-width: 700px) {
  .bloque-home--carrusel {
    margin-top: -1.5rem;
    margin-left: -1.25rem;
    margin-right: -1.25rem;
  }
}

/* Pista con TODOS los items impresos dos veces seguidas (ver
   renderBloqueCarrusel() para el porqué) — `width: max-content` deja que
   la pista mida lo que su contenido real necesita, no el ancho del
   contenedor: `assets/js/carrusel.js` necesita ese ancho real completo
   para calcular dónde está "un juego completo" y reiniciar la posición
   sin salto, tanto en automático como al soltar un arrastre.
   `touch-action: pan-y` es lo que le permite a `carrusel.js` capturar el
   arrastre horizontal con Pointer Events sin pelearse con el scroll
   vertical nativo de la página en touch (el navegador sigue manejando el
   scroll vertical solo; el horizontal lo captura el script). `cursor:
   grab`/`:active { cursor: grabbing }` confirma con el propio puntero
   que la franja se puede jalar. El movimiento en sí (automático,
   arrastre e inercia) lo controla `assets/js/carrusel.js` escribiendo
   `transform` inline en este elemento en cada frame — por eso no hay
   `animation`/`@keyframes` aquí. */
.bloque-carrusel__pista {
  display: flex;
  width: max-content;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
  touch-action: pan-y;
  cursor: grab;
  will-change: transform; /* le avisa al navegador que este transform cambia cada frame — evita repintados de más mientras carrusel.js anima */
  user-select: none; /* sin esto, arrastrar con el mouse selecciona el texto de las etiquetas en vez de mover la pista */
  -webkit-user-select: none;
}

.bloque-carrusel__pista:active {
  cursor: grabbing;
}

/* `.bloque-carrusel__item` carga una <img> real (ver
   renderBloqueCarrusel()) cuyo tamaño de caja cambia en 3 cortes — 220px
   en mobile (por default, sin media query), 350px desde tablet y 500px
   desde escritorio grande. Los mismos tres cortes (700px / 1200px) los
   usa el `sizes` del `<img>` en renderBloqueCarrusel() — si estos
   valores cambian, actualizar también ahí para que la imagen que el
   navegador DESCARGA siga coincidiendo con el tamaño de caja que en
   verdad se muestra. */
.bloque-carrusel__item {
  flex: 0 0 auto;
  width: 220px;
}

@media (min-width: 700px) {
  .bloque-carrusel__item {
    width: 350px;
  }
}

@media (min-width: 1200px) {
  .bloque-carrusel__item {
    width: 500px;
  }
}

/* `position: relative` ancla la etiqueta superpuesta (ver
   `.bloque-carrusel__etiqueta` abajo) contra la propia foto — la
   etiqueta va pegada al borde inferior de cada foto. `overflow: hidden`
   recorta la foto y el degradado de la etiqueta a las esquinas
   redondeadas del recuadro. */
.bloque-carrusel__imagen {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 8px;
  overflow: hidden;
}

/* La <img> llena por completo el recuadro cuadrado — `object-fit: cover`
   recorta en vez de deformar si la proporción de alguna foto no fuera
   exactamente 1:1 (las 7 reales sí lo son, pero esto defiende contra una
   foto futura que no lo sea). `pointer-events: none` deja que los
   eventos de arrastre (mousedown/touchstart) le lleguen siempre a
   `.bloque-carrusel__pista`/`carrusel.js`, nunca a la <img> suelta — sin
   esto, el navegador podría interpretar un arrastre sobre la imagen como
   "arrastrar la imagen" (aunque ya viene con `draggable="false"` en el
   HTML, esta es la defensa a nivel CSS). */
.bloque-carrusel__imagen img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}

/* Texto blanco en mayúsculas sobre un degradado oscuro, pegado abajo de
   la foto. El degradado (en vez de un fondo sólido) dobla como
   contraste de lectura y como pista visual de que hay una foto detrás. */
.bloque-carrusel__etiqueta {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 1.5rem 0.6rem 0.6rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.65), rgba(0, 0, 0, 0));
  color: #fff;
  font-family: 'Oswald', sans-serif;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.3px;
  text-align: center;
}

/* Tamaño de fuente escalado a la misma proporción que el ancho de la foto
   (220/350/500px, ver .bloque-carrusel__item arriba): 24px en el tamaño
   grande, y 350px/220px escalan esos 24px en esa misma proporción
   (16.8px→17px, 10.5px→11px, este último ya es el valor base de arriba). */
@media (min-width: 700px) {
  .bloque-carrusel__etiqueta {
    font-size: 17px;
  }
}

@media (min-width: 1200px) {
  .bloque-carrusel__etiqueta {
    font-size: 24px;
  }
}

.grid-productos-destacados,
.grid-catalogos,
.grid-noticias {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1.25rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

/* Separación horizontal entre tarjetas propia de esta sección: en el
   mockup (mupa2.com.mx) el espacio real entre columnas es ~29px contra
   columnas de ~223px de ancho — más aire que el 1.25rem/20px genérico
   que comparten Catálogos/Noticias arriba. Solo se escala aquí (no en
   la regla compartida) para no mover esas otras dos secciones, fuera de
   lo pedido.

   `grid-template-columns` con `auto-fit` (no `auto-fill`, que es lo que
   trae la regla compartida de arriba): con exactamente 4 tarjetas y
   `auto-fill`, en pantallas anchas el grid calcula cuántas columnas de
   200px caben en el contenedor (verificado en vivo: 5 a 1466px de
   viewport) y crea esa 5ª columna VACÍA en vez de repartir su espacio —
   resultado, las 4 tarjetas reales quedan apretadas a la izquierda con un
   hueco muerto a la derecha (reportado por José con captura). `auto-fit`
   colapsa las columnas vacías a 0 y redistribuye ese espacio entre las
   columnas con contenido (mismo mecanismo 1fr), así que las 4 tarjetas se
   estiran para llenar el ancho disponible de forma simétrica — sin volver
   a fijar el número de columnas a mano, que se rompería en cuanto cambie
   el ancho de sidebar o el contenido pase a tener más/menos de 4 items. */
.bloque-home--productos-destacados .grid-productos-destacados {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.875rem;
}

/* Mismo mecanismo que la regla de arriba: `auto-fit` en vez del
   `auto-fill` compartido, para que las 3 tarjetas de Catálogos se
   estiren a todo el ancho disponible en vez de dejar una columna vacía.
   `gap: 4.625rem` (74px, medido en vivo contra mupa2.com.mx con
   getBoundingClientRect entre tarjetas — mucho más aire que el 1.25rem
   genérico, propio de esta sección). `margin-top: 4.4375rem` (71px,
   medido igual: separación real entre el h2 "Catálogos" y la primera
   fila de tarjetas) reemplaza el 1.5rem de la regla compartida — scoped
   aquí para no mover Productos Destacados/Noticias, mismo criterio que
   la regla de arriba. */
.bloque-home--catalogos .grid-catalogos {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 4.625rem;
  margin-top: 4.4375rem;
}

/* Foto real de la tarjeta (antes un <div aria-hidden> placeholder, ver
   bitácora) para el rediseño "como el mockup" pedido por José. Antes
   compartía esta regla de placeholder con `.tarjeta-noticia` (ver esa
   clase justo abajo, que hoy no se usa en ningún template PHP — se
   conserva tal cual por si Noticias reutiliza el mismo placeholder más
   adelante); se separan porque ya no es el mismo tratamiento visual.
   Mismo aspect-ratio 4/3 que el placeholder que reemplaza, pero SIN
   radio de esquina: medido con getComputedStyle contra mupa2.com.mx, la
   foto de Catálogos ahí no lleva border-radius (0px), a diferencia de
   `.tarjeta-producto-destacado__imagen` (6px) — secciones distintas,
   tratamiento distinto, confirmado con el mockup. `object-fit: cover`
   por el mismo motivo que esa regla: las fotos que llegan (800x600,
   jaladas de mupa2.com.mx) ya son 4:3 pero no conviene depender de que
   todas las futuras lo sean. `margin-bottom: 2.3125rem` (37px): mismo
   aire uniforme medido entre imagen/título/párrafo/botón en las 3
   tarjetas del mockup (ver `.tarjeta-catalogo h3`/`p` más abajo). */
.tarjeta-catalogo__imagen {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-bottom: 2.3125rem;
}

.tarjeta-noticia {
  aspect-ratio: 4 / 3;
  background: #f2f2f2; /* placeholder visual mientras no hay imagen real, ver .tarjeta-producto img en categoria.php */
  border-radius: 6px;
}

/* Flex column para que el botón "descargar" quede siempre alineado
   abajo, mismo mecanismo (y mismo motivo: título con word-wrap
   distinto entre tarjetas) que `.tarjeta-producto-destacado` arriba —
   ver el comentario de esa regla para el detalle completo. */
.tarjeta-catalogo {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* Flex column para que el botón "más" quede siempre alineado abajo:
   reportado por José con captura — con las 4 tarjetas en el mismo
   renglón de grid (que ya las estira a igual altura por el
   `align-items: stretch` por defecto de `display: grid`), el título a
   veces hace word-wrap a una línea más que en otra tarjeta y eso
   empuja el párrafo y el botón hacia abajo un poco distinto en cada
   una. `.tarjeta-producto-destacado__boton` de abajo cambia su
   `margin-top` fijo por `auto`: en un contenedor flex column con
   `height: 100%` eso hace que el botón absorba TODO el espacio
   sobrante debajo del párrafo y quede pegado al mismo renglón inferior
   en las 4 tarjetas, sin importar cuántas líneas ocupen el título o el
   párrafo arriba — mismo mecanismo que ya usa `.categoria-inicio-card`
   para empujar contenido al fondo de una tarjeta de altura variable. */
.tarjeta-producto-destacado {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* Foto real de la tarjeta (antes un <div aria-hidden> placeholder, ver
   bitácora): mismo aspect-ratio 4/3 y radio que el placeholder que
   reemplaza, más `object-fit: cover` porque la foto que llega
   (600x450, jalada de mupa2.com.mx) ya es justo 4:3 pero no conviene
   depender de que TODAS las fotos futuras lo sean. */
.tarjeta-producto-destacado__imagen {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 6px;
  object-fit: cover;
}

/* El <h3> base (regla `h2, h3, h5` más arriba, más --fs-h3/--fw-h3/
   --color-heading-accent de tokens.css) YA da exactamente el
   tratamiento del título de tarjeta del mockup — Oswald 300, 17px,
   #3f6964, letter-spacing 1px — verificado con getComputedStyle contra
   mupa2.com.mx (sección Catálogos, valores idénticos letra por letra).
   El `font-size: var(--fs-body)` que traía esta regla era un bug: dejaba
   el título al mismo tamaño que el párrafo (15px). Solo hace falta
   centrar y poner mayúsculas, que la regla base no trae. `margin`
   ajustado al aire uniforme medido en el mockup (~37px = 2.3125rem,
   igual entre imagen→título, título→párrafo y párrafo→botón). */
.tarjeta-catalogo h3 {
  margin: 0 0 2.3125rem;
  text-align: center;
  text-transform: uppercase;
}

/* Párrafo de descripción (Lorem ipsum, ver contenido.php): mismo color
   cálido #665f52 y tamaño 15px que `.bloque-informativa__tarjeta p`
   (verificado con getComputedStyle, valores idénticos), `line-height:
   1.3` medido igual (19.5px en el mockup). `margin-bottom: 2.3125rem`
   es el mínimo garantizado (mismo valor que arriba); el `margin-top:
   auto` del botón de abajo absorbe el espacio extra cuando el título
   hace word-wrap distinto entre las 3 tarjetas — mismo mecanismo que
   `.tarjeta-producto-destacado p`/`__boton`. */
.tarjeta-catalogo p {
  margin: 0 0 2.3125rem;
  color: #665f52;
  font-size: 15px;
  line-height: 1.3;
}

/* Botón "descargar": mismos valores exactos que
   `.tarjeta-producto-destacado__boton`/`.bloque-informativa__boton`
   (padding, radio 35px/7px, fondo #607a24, sombra, Poppins 600 13px con
   letter-spacing 3px) — verificado con getComputedStyle contra
   mupa2.com.mx, es el mismo botón reutilizado en las 3 secciones del
   mockup. Se duplica la regla en vez de compartir una clase, mismo
   criterio conservador que el resto de este archivo. A diferencia de
   esos otros dos botones, este SÍ es un <a> real (no
   `enlace-pendiente`): el destino (catalogos.php#formulario-catalogos)
   ya existe, ver contenido.php. `margin: auto auto 0` alinea el botón
   al fondo de la tarjeta (`.tarjeta-catalogo` es flex column arriba). */
.tarjeta-catalogo__boton {
  display: block;
  width: fit-content;
  margin: auto auto 0;
  padding: 15px 26px;
  border-bottom: 0;
  border-radius: 35px 7px;
  background: #607a24;
  box-shadow: 0 2px 18px rgba(0, 0, 0, 0.1);
  color: #fff;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  opacity: 1;
}

/* Título de tarjeta en <h4> (no <h3>): mismo tag y tratamiento tipográfico
   que usa mupa2.com.mx para este mismo texto — Oswald 24px/600, ya cubierto
   por el token --fs-h4/--fw-h4 (regla base `h4` más arriba); aquí solo se
   agrega lo que esa regla base no trae (color, mayúsculas, centrado),
   porque hoy es el único lugar del sitio que usa <h4>.
   `line-height: 1.2` (28.8px en el mockup, verificado con getComputedStyle
   contra mupa2.com.mx): SIN esto se hereda `--lh-body` (24px fijo, pensado
   para el párrafo de 15px del sitio), que con esta letra de 24px queda
   apenas 1:1 y se ve encimada en cuanto el título hace word-wrap a 2
   líneas en pantallas angostas — mismo bug reportado en el h2 de abajo.
   `margin-bottom: 1.25rem` (antes 0.25rem): José reportó que el título
   quedaba muy pegado al párrafo de abajo en las 4 tarjetas. Medido en
   vivo contra mupa2.com.mx (getBoundingClientRect del módulo de
   título vs. el del párrafo, sección "Productos Destacados"): 20px de
   separación real = 1.25rem a 16px base. */
.tarjeta-producto-destacado h4 {
  margin: 0.75rem 0 1.25rem;
  color: var(--color-heading-accent);
  text-transform: uppercase;
  text-align: center;
  line-height: 1.2;
}

/* `margin-bottom: 1.5rem` (no `margin: 0` como antes): José reportó
   que el botón "más" quedaba muy pegado al párrafo — "como si
   agregáramos un <br> al final de cada texto" — sobre todo en la
   tarjeta más alta del renglón, donde el `margin-top: auto` del botón
   (ver esa regla más abajo) casi no tiene espacio libre que absorber.
   1.5rem ≈ un renglón de `--lh-body` (24px, la altura de línea fija de
   este párrafo), o sea el mismo aire visual que dejaría esa línea en
   blanco extra que describió, como mínimo garantizado en las 4
   tarjetas sin importar cuánto espacio sobrante haya. */
.tarjeta-producto-destacado p {
  margin: 0 0 1.5rem;
}

/* Botón "más" de cada tarjeta: en el mockup es un botón real de Divi
   (fondo #607A24, radio asimétrico 35px/7px, sombra suave, Poppins
   600 13px con letter-spacing 3px) pero SIN destino (href="" también
   ahí) — por eso el marcado es un <span class="enlace-pendiente">
   (ver esa clase más abajo, mismo criterio del resto del repo: "un
   link que no lleva a ningún lado es peor que una etiqueta que se ve
   pero no promete nada") en vez de un <a>. Esta regla solo REEMPLAZA
   lo que `.enlace-pendiente` trae por defecto para texto suelto
   (subrayado punteado, display inline-block, opacidad) por el
   tratamiento de botón/píldora del mockup; el cursor "help" de
   `.enlace-pendiente` se conserva a propósito, es la señal correcta
   incluso disfrazado de botón. */
.tarjeta-producto-destacado__boton {
  display: block;
  width: fit-content;
  margin: auto auto 0;
  padding: 15px 26px;
  border-bottom: 0;
  border-radius: 35px 7px;
  background: #607a24;
  box-shadow: 0 2px 18px rgba(0, 0, 0, 0.1);
  color: #fff;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  opacity: 1;
}

/* Título de la sección (h2 "Productos Destacados"): tratamiento propio de
   mupa2.com.mx para esta sección, más grande y más ligero que el h2
   estándar del resto del sitio (18px/600, ver regla base `h2` arriba) —
   30px/300, mayúsculas, centrado. Color y tipografía (Oswald, teal) ya
   los trae la regla base, sin necesidad de repetirlos aquí.
   `line-height: 1.2` (36px en el mockup, verificado con getComputedStyle):
   José reportó el título encimado en celular al hacer word-wrap a 2
   líneas ("PRODUCTOS" / "DESTACADOS") — la causa es que sin esta regla se
   hereda `--lh-body` (24px fijo, pensado para el párrafo de 15px del
   sitio), MENOR que este tamaño de letra de 30px, así que cada línea
   queda más baja que sus propios glifos y la segunda línea se monta
   sobre la primera. */
.bloque-home--productos-destacados h2 {
  font-size: 30px;
  font-weight: 300;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.2;
}

/* ─── Informativa: "¿Qué es el mobiliario urbano?" (includes/home/
   bloques.php, renderBloqueInformativa()). En el mockup (Divi,
   et_pb_section_5) es una foto de fondo a sangre completa
   (fondo-2026.jpg) con una tarjeta clara centrada encima y una foto de
   planta decorativa que sobresale de la esquina inferior derecha de la
   tarjeta — verificado con getComputedStyle en vivo contra
   mupa2.com.mx, no a ojo.

   Fondo a sangre completa: mismo mecanismo de `margin: 0 -2rem` (y su
   contraparte en `@media (max-width: 700px)`, ver bloque de abajo) que
   ya usa `.bloque-home--hero` para cancelar el padding horizontal de
   `.sitio-layout`. `background-position: center` en vez del `50% 100%`
   del mockup: esa foto (bokeh verde difuminado) no tiene un punto focal
   marcado, así que centrarla es equivalente visualmente y más simple de
   mantener. */
.bloque-home--informativa {
  position: relative;
  margin: 0 -2rem;
  padding: 3rem 2rem 5rem;
  background-image: url(/assets/img/informativa/fondo.avif);
  background-size: cover;
  background-position: center;
}

@media (max-width: 700px) {
  .bloque-home--informativa {
    margin-left: -1.25rem;
    margin-right: -1.25rem;
  }
}

/* Tarjeta clara centrada: en el mockup ocupa ~70% del ancho de su
   contenedor, medido con getBoundingClientRect en vivo (1176px de
   1680px). `max-width: 860px` FIJO (versión anterior) no reproducía
   eso: a un viewport ancho (1920px) el contenedor real de este sitio
   mide ~1730px, así que un valor fijo de 860px cae a ~50% en vez de
   70% — la tarjeta se veía más angosta que el mockup en pantallas
   grandes, justo lo que José reportó. `width: 70%` (con `max-width`
   como tope, no como valor fijo) reproduce el mismo comportamiento
   fluido en vez de solo acertar a un viewport de referencia.
   Padding interno también aumentado (antes 2.5rem 3rem): José
   reportó "márgenes internos más amplios" — 3.5rem arriba/4.5rem a
   los lados/4rem abajo se acerca a los insets medidos en vivo contra
   mupa2.com.mx (~55px arriba, ~66px abajo del botón, proporcional a
   los lados). Mismo fondo que `.bloque-home--hero` (`#f8faf5`) —
   incluso con el nombre de bloque distinto, es el mismo "panel claro
   sobre fondo", confirmado igual en el mockup (rgb(248,250,245) en
   ambos). */
.bloque-informativa__tarjeta {
  position: relative;
  z-index: 1;
  width: 70%;
  max-width: 1176px;
  margin: 0 auto;
  padding: 3.5rem 4.5rem 4rem;
  background: #f8faf5;
  border-radius: 5px;
}

@media (max-width: 700px) {
  .bloque-informativa__tarjeta {
    /* `width: auto` deshace el `width: 70%` de la regla base: en
       mobile la tarjeta debe volver a ocupar el ancho disponible
       completo (igual que antes, cuando `max-width: 860px` nunca se
       alcanzaba a este viewport) — José pidió expresamente no tocar
       la versión móvil. */
    width: auto;
    padding: 2rem 1.5rem;
  }
}

/* 30px/300 Oswald centrado y en mayúsculas — mismo patrón que el h2 de
   Productos Destacados de arriba (con su mismo bug de interlineado si
   se omite `line-height`, ver el comentario extenso de esa regla: aquí
   se agrega `line-height: 1.2` desde el principio en vez de esperar a
   que alguien lo reporte otra vez).
   `margin-bottom: 2.5rem` (antes 1.25rem): mismo reporte de José sobre
   aire interno — medido en vivo contra mupa2.com.mx, el espacio real
   entre el título y el primer párrafo es ~40px = 2.5rem. */
.bloque-informativa__tarjeta h5 {
  margin: 0 0 2.5rem;
  font-size: 30px;
  font-weight: 300;
  text-align: center;
  text-transform: uppercase;
  line-height: 1.2;
}

/* Color de párrafo propio de este bloque (#665f52, verificado con
   getComputedStyle) — NO es `--color-body-text` (#666666): son valores
   distintos aunque parecidos, el mockup usa un tono más cálido/café
   solo aquí, no el gris neutro del resto del sitio. */
.bloque-informativa__tarjeta p {
  margin: 0 0 1rem;
  color: #665f52;
  font-size: 15px;
  line-height: 1.3;
}

.bloque-informativa__tarjeta p:last-of-type {
  margin-bottom: 0;
}

.bloque-informativa__tarjeta .bloque-home__aviso-pendiente {
  margin-top: 1.25rem;
}

/* Botón "conoce más de mupa®": mismos valores exactos que
   `.tarjeta-producto-destacado__boton` (padding, radio 35px/7px, fondo
   #607a24, sombra, Poppins 600 13px con letter-spacing 3px) —
   verificado con getComputedStyle contra mupa2.com.mx, es literalmente
   el mismo botón reutilizado en dos secciones distintas del mockup. Se
   duplica la regla en vez de compartir una clase para no tocar
   `.tarjeta-producto-destacado__boton` (ya verificado y en producción)
   — mismo criterio conservador que el resto de este archivo: no mover
   código que funciona sin que se haya pedido. También un `<span
   class="enlace-pendiente">` (no `<a>`), mismo motivo: href="" en el
   mockup también aquí.
   `margin-top: 2.75rem` (antes 1.75rem): mismo reporte de José sobre
   aire interno — medido en vivo contra mupa2.com.mx, el espacio real
   entre el último párrafo y el botón es ~45px = 2.75rem. */
.bloque-informativa__boton {
  display: block;
  width: fit-content;
  margin: 2.75rem auto 0;
  padding: 15px 26px;
  border-bottom: 0;
  border-radius: 35px 7px;
  background: #607a24;
  box-shadow: 0 2px 18px rgba(0, 0, 0, 0.1);
  color: #fff;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  opacity: 1;
}

/* Planta decorativa (`alt=""` a propósito, ver bloques.php — es
   puramente decorativa, igual que en el mockup): sobresale de la
   esquina inferior derecha de la tarjeta, mitad encima/mitad afuera del
   borde derecho, y debe quedar al ras del borde inferior de la SECCIÓN
   completa (`.bloque-home--informativa`), no de la tarjeta — corregido
   2026-08-08 (José: "no debe haber separación", ver captura). `bottom:
   -40px` dejaba un hueco de 40px porque solo cubría la mitad del
   `padding-bottom: 5rem` (80px) de la sección; `position: absolute` es
   contra `.bloque-informativa__tarjeta` (`position: relative` arriba),
   así que -80px equivale exactamente a ese padding-bottom y deja la
   planta al ras del borde inferior real de la sección. Se mantiene
   posicionada contra la tarjeta (no contra la sección completa) para
   que se mueva junto con ella sin importar el ancho real del
   contenedor en cada viewport, que es distinto al del mockup (sin
   sidebar). Oculta en mobile (ver media query de abajo): a ese ancho la
   tarjeta ocupa casi todo el viewport y ya no sobra espacio a los lados
   para que la planta sobresalga sin recortarse o encimarse con el
   texto.
   `width: 278px`: José reportó que el +10% (220px→242px) seguía
   viéndose notablemente más chica que el original. Medido con
   getComputedStyle/naturalWidth contra mupa2.com.mx: la imagen del
   mockup tiene `width="278" height="342"` como atributos HTML fijos —
   NO escala con el contenedor, es tamaño fijo en cualquier viewport
   (verificado en 1703px y 1839px, mismo resultado: 278px). El +10%
   inicial partía de una relación proporcional (ancho de la planta vs.
   ancho de la tarjeta) que resultó ser una coincidencia de un solo
   viewport, no el comportamiento real — por eso quedaba corto. 278px
   es el tamaño natural exacto del archivo `planta.avif` (278×342),
   mismo que usa el mockup, así que aquí también se usa sin escalar.
   `right: -206px` (antes -170px): misma fórmula que antes,
   recalculada para el nuevo ancho — `right' = padding-derecho -
   ancho-nuevo = 72 - 278 = -206px` — deja la planta al ras del borde
   del texto (~0px de invasión) en cualquier ancho de pantalla. */
.bloque-informativa__planta {
  position: absolute;
  right: -206px;
  bottom: -80px;
  z-index: 2;
  width: 278px;
  height: auto;
}

@media (max-width: 700px) {
  .bloque-informativa__planta {
    display: none;
  }
}

/* ─── Categorías: mosaico de 8 tarjetas (includes/home/bloques.php,
   renderBloqueCategorias()). Fondo verde claro a sangre completa, mismo
   mecanismo que `.bloque-home--hero` (margin negativo cancela el padding
   horizontal de `.sitio-layout`, ver esa regla para el detalle completo)
   — incluso el mismo color exacto (#f8faf5, verificado igual en el
   mockup para esta sección). Sin título de sección: verificado contra
   mupa2.com.mx que el mockup real no imprime ningún h1/h2/h3 fuera de
   las 8 tarjetas en este bloque (por eso renderBloqueCategorias() ya no
   imprime un <h2> — ver el docblock de esa función). El grid en sí
   (`.grid-categorias-inicio`, íconos, insignia) vive más arriba, junto a
   `.categoria-inicio-card`. */
.bloque-home--categorias {
  background-color: #f8faf5;
  margin: 0 -2rem;
  padding: 2.5rem 2rem;
}

@media (max-width: 700px) {
  .bloque-home--categorias {
    margin-left: -1.25rem;
    margin-right: -1.25rem;
  }
}

/* ─── Catálogos (bloque de home, includes/home/bloques.php,
   renderBloqueCatalogos()) — rediseño completo pedido por José para
   igualar el mockup ("Rediseño completo como el mockup", no solo las 3
   fotos). Fondo verde claro a sangre completa, mismo mecanismo y mismo
   color exacto que `.bloque-home--categorias` arriba (#f8faf5,
   verificado igual en el mockup para esta sección también).
   `padding: 3.4375rem 2rem` (55px verticales): medido en vivo contra
   mupa2.com.mx — la franja verde mide lo mismo arriba del título y
   abajo de las tarjetas (55px en ambos lados, simétrico). */
.bloque-home--catalogos {
  background-color: #f8faf5;
  margin: 0 -2rem;
  padding: 3.4375rem 2rem;
}

@media (max-width: 700px) {
  .bloque-home--catalogos {
    margin-left: -1.25rem;
    margin-right: -1.25rem;
  }
}

/* Título de la sección (h2 "Catálogos"): mismo tratamiento que el h2 de
   Productos Destacados (`.bloque-home--productos-destacados h2` arriba)
   — 30px/300, mayúsculas, centrado, `line-height: 1.2` — verificado con
   getComputedStyle contra mupa2.com.mx, valores idénticos (30px/300,
   uppercase, center, line-height 36px/1.2). Color y tipografía (Oswald,
   teal) ya los trae la regla base. */
.bloque-home--catalogos h2 {
  font-size: 30px;
  font-weight: 300;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.2;
}

/* ─── Proyectos (bloque de home, includes/home/bloques.php,
   renderBloqueProyectos() — NO confundir con proyectos.php, la página
   de destino completa del menú superior, que usa `.grid-proyectos` /
   `.tarjeta-proyecto*` más abajo). Dos columnas fiel al mockup,
   verificado en vivo con Chrome contra mupa2.com.mx (getComputedStyle
   + DOM real) el 2026-08-08 — reemplaza el placeholder anterior (lista
   de texto plano sin fotos). Columnas ~47%/47% con separación en el
   mockup (medido en vivo, fila de 1344px con dos `.et_pb_column_1_2`
   de 635px cada una) — `1fr 1fr` con gap es la forma simple de lograr
   la misma proporción sin fijar anchos en px que no sobreviven a un
   contenedor de ancho distinto (este sitio trae sidebar, el mockup
   no). */
.bloque-proyectos__columnas {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2.5rem;
  align-items: start;
  margin-top: 1.5rem;
}

@media (max-width: 700px) {
  .bloque-proyectos__columnas {
    grid-template-columns: 1fr;
  }
}

/* Título 30px/300 mayúsculas (mismo tratamiento que
   `.bloque-home--productos-destacados h2`, el mockup también lo trae
   así aquí — verificado en vivo: 30px/300, color
   `--color-heading-accent`, ya heredado de la regla base `h2`) pero
   SIN centrar: a diferencia de Productos Destacados/Informativa, en
   este bloque el título queda alineado a la izquierda de su columna
   (confirmado contra el mockup, no es un descuido). `line-height: 1.2`
   por el mismo bug de interlineado ya documentado arriba en Productos
   Destacados/Informativa (se agrega desde el principio en vez de
   esperar a que alguien lo reporte). */
.bloque-proyectos__intro h2 {
  margin: 0 0 1.25rem;
  font-size: 30px;
  font-weight: 300;
  text-transform: uppercase;
  line-height: 1.2;
}

.bloque-proyectos__intro p {
  margin: 0 0 1rem;
}

.bloque-proyectos__intro p:last-of-type {
  margin-bottom: 0;
}

/* Botón "conoce más proyectos": MISMOS valores exactos que
   `.bloque-informativa__boton`/`.tarjeta-producto-destacado__boton`
   (fondo #607A24, radio 35px/7px, sombra, Poppins 600 13px con
   letter-spacing 3px) — se duplica la regla en vez de compartir una
   clase, mismo criterio conservador ya documentado en esas dos reglas.
   Única diferencia real: acá SÍ es un `<a>` real (el mockup le da href
   real a este botón, a diferencia de los otros dos) y queda alineado a
   la izquierda (`margin` sin `auto`, no centrado) en vez de centrado
   bajo una tarjeta. */
.bloque-proyectos__boton {
  display: block;
  width: fit-content;
  margin: 1.5rem 0 0;
  padding: 15px 26px;
  border-bottom: 0;
  border-radius: 35px 7px;
  background: #607a24;
  box-shadow: 0 2px 18px rgba(0, 0, 0, 0.1);
  color: #fff;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 3px;
  text-decoration: none;
}

/* Mosaico 2×2 de fotos con leyenda: sin link (ni siquiera roto) porque
   el mockup tampoco envuelve estas 4 fotos en un `<a>` — son
   puramente decorativas, ver docblock de renderBloqueProyectos() en
   bloques.php. `gap` reutiliza el mismo valor ya establecido en
   Productos Destacados (1.875rem) en vez del ~39px medido en vivo:
   suficientemente cerca, y consistente con el resto del sitio en vez
   de un valor de un solo uso. */
.bloque-proyectos__categorias {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.875rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* José reportó que en mobile las fotos se ven de tamaños dispares —
   el grid 2×2 sigue siendo `1fr 1fr` (columnas parejas), pero cada
   celda cambia de alto según cuánto ocupe su leyenda al hacer wrap
   ("CONSTRUCTORAS Y DESARROLLADORES" ocupa 2 líneas, "GOBIERNO" una
   sola), y eso se percibe como fotos de tamaño distinto. Una sola
   columna evita el problema: las 4 fotos quedan del mismo ancho y la
   leyenda ya no compite por espacio con la celda vecina. */
@media (max-width: 700px) {
  .bloque-proyectos__categorias {
    grid-template-columns: 1fr;
  }
}

/* Sin `border-radius` ni `object-fit: cover` (a diferencia del resto
   de fotos del sitio, que sí llevan ambos): verificado con
   getComputedStyle en vivo que el mockup usa esquinas cuadradas
   (`border-radius: 0px`) para estas 4 fotos específicas — se respeta
   la medición en vez del radio 6px que comparten Productos
   Destacados/Catálogos/Noticias. `aspect-ratio: 560/450` es la
   proporción nativa real de los 4 archivos (`assets/img/proyectos/`,
   confirmada con `identify`), no un valor genérico 4/3. */
.bloque-proyectos__categoria-imagen {
  display: block;
  width: 100%;
  aspect-ratio: 560 / 450;
  object-fit: cover;
}

/* Leyenda bajo cada foto: 18px/600 mayúsculas centrado, mismo
   tratamiento que ya trae la regla base `h2` de este sitio (color
   `--color-heading-accent` incluido) — verificado en vivo que en el
   mockup esta leyenda es, de hecho, un `<h2>` con exactamente estos
   valores computados. Aquí es un `<span>` en vez de un heading real
   (mismo criterio que `.categoria-inicio-card__nombre`): 4 leyendas
   como "h2" repetidos en una sola página inflarían el esquema de
   encabezados sin necesidad, el título real de la sección
   ("Nuestros Proyectos") ya es el único <h2> que hace falta aquí. */
.bloque-proyectos__categoria-nombre {
  display: block;
  margin-top: 1rem;
  font-family: var(--font-heading);
  font-size: var(--fs-h2);
  font-weight: var(--fw-h2);
  letter-spacing: var(--ls-heading);
  color: var(--color-heading-accent);
  text-transform: uppercase;
  text-align: center;
  line-height: 1.3;
}

/* José reportó que en escritorio los 5 logos deberían escalar de
   forma proporcional para quedar siempre en una sola línea, y no lo
   hacían: el slot tenía ancho fijo (120px), así que en una ventana
   angosta pero todavía de escritorio (justo arriba del breakpoint
   mobile de 700px) el renglón de 5×120px + 4 gaps (~696px) ya no
   cabía dentro del área de contenido (que descuenta el sidebar) y se
   rompía. Grid de 5 columnas iguales reparte el ancho disponible
   entre los 5 logos sin depender de un ancho fijo — cada slot ocupa
   exactamente 1/5 del renglón sin importar cuánto mida. `aspect-ratio:
   3/2` conserva la misma proporción que tenía el slot fijo (120/80),
   así los 5 logos se ven del mismo alto entre sí a cualquier ancho.
   Mobile (`@media max-width:700px` abajo) se queda exactamente igual
   que antes (flex-wrap + slot fijo 120×80px) — José pidió
   explícitamente no tocar esa versión.
   `margin: 0` (antes 1.5rem arriba): ese margen separaba los logos del
   <h2>Certificaciones</h2> que imprimía renderBloqueCertificaciones() —
   José pidió quitar el título para dejar solo los logos. Sin título que
   separar, el espacio superior del bloque lo aporta `.bloque-home`
   (padding: 2.5rem 0, ver esa regla), mismo criterio que
   `.bloque-home--categorias`, que tampoco imprime h2. */
.bloque-certificaciones__logos {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.bloque-certificaciones__slot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 3 / 2;
}

.bloque-certificaciones__slot img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

@media (max-width: 700px) {
  .bloque-certificaciones__logos {
    display: flex;
    flex-wrap: wrap;
  }

  .bloque-certificaciones__slot {
    width: 120px;
    height: 80px;
    aspect-ratio: auto;
  }
}

/* ─── Boletín: "Suscríbete a nuestro boletín de noticias" (includes/home/
   bloques.php, renderBloqueBoletin()). En el mockup es una franja angosta
   a sangre completa con la MISMA foto de fondo que Informativa
   (fondo-2026.jpg, ver `.bloque-home--informativa` arriba) — verificado
   con getComputedStyle en vivo contra mupa2.com.mx (misma URL de imagen,
   mismo mecanismo `background-size: cover`). Se copia el archivo a
   `assets/img/boletin/fondo.avif` en vez de apuntar a la ruta de
   Informativa: son bloques independientes y no debe haber acoplamiento
   entre sus assets aunque hoy compartan la foto — si alguno cambia de
   fondo más adelante, no debe arrastrar al otro.

   Encabezado, campo y botón: mismo full-bleed que Informativa/Hero
   (margin: 0 -2rem cancela el padding de `.sitio-layout`). Layout de fila
   (título a la izquierda, formulario a la derecha) con flexbox — el
   mockup lo arma con columnas de Divi (dos `et_pb_column` de ancho fijo),
   pero flex con wrap reproduce el mismo resultado visual y además
   resuelve solo el colapso a una columna en móvil, sin breakpoint
   aparte. */
.bloque-home--boletin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.25rem;
  margin: 0 -2rem;
  padding: 2.25rem 2rem;
  background-image: url(/assets/img/boletin/fondo.avif);
  background-size: cover;
  background-position: center bottom; /* 50% 100% medido en vivo */
}

/* José reportó una franja blanca entre esta franja verde y el footer
   (también verde) — el `margin: 0 -2rem` de arriba solo cancela el
   padding LATERAL de `.sitio-layout` (1.5rem 2rem), no el vertical: al
   ser hoy el ÚLTIMO bloque de la home, el `padding-bottom: 1.5rem` de
   `.sitio-layout` (o, en móvil, el `padding-bottom: 1.5rem` propio de
   `.sitio-main`, ver el `@media` de esa regla — mismo valor en los dos
   casos) se quedaba como espacio blanco antes de que el footer
   empezara. `:last-child` en vez de aplicarlo siempre: si algún día se
   agrega un bloque después de Boletín, este margen negativo no debe
   arrastrarlo/encimarlo — solo tiene sentido cuando de verdad es lo
   último antes del footer. */
.bloque-home--boletin:last-child {
  margin-bottom: -1.5rem;
}

@media (max-width: 700px) {
  .bloque-home--boletin {
    margin-left: -1.25rem;
    margin-right: -1.25rem;
    justify-content: center;
    text-align: center;
  }
}

/* Título: Oswald 600/1px de letter-spacing ya los cubre la regla
   compartida `h2, h3, h5` — aquí solo lo que es propio de este bloque:
   21px (no el --fs-h2 de 18px de la regla base), cursiva, mayúsculas, y
   un color navy (#23383f) distinto del teal compartido
   (--color-heading-accent) — coincide en valor con --cat-movilidad-
   urbana, pero es una coincidencia del propio mockup, no una reutilización
   deliberada de esa categoría: se deja como valor fijo con esta nota en
   vez de tomar prestado un token semánticamente ajeno. */
.bloque-home--boletin h2 {
  flex: 1 1 280px;
  margin: 0;
  font-size: 21px;
  font-style: italic;
  text-transform: uppercase;
  color: #23383f;
}

.bloque-boletin__form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  flex: 0 1 auto;
}

.bloque-boletin__form input {
  flex: 1 1 200px;
  padding: 16px;
  border: none;
  border-radius: 5px;
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--color-h1); /* #526723 medido en vivo, coincide exacto con el token */
}

/* Botón "enviar": relleno navy (mismo #23383f del título), radio
   asimétrico 35px/7px — mismo valor que `--btn-outline-radius`, aquí
   fijo porque el resto del botón (relleno sólido, no outline) no
   corresponde a ese token. Deshabilitado (sin backend de suscripción,
   ver docblock de `obtenerBloqueBoletin()`): mismo criterio visual que
   `.btn-outline` para CTAs pendientes — cursor "not-allowed" + opacidad
   reducida, sin bloquear la lectura del texto. */
.bloque-boletin__boton {
  flex-shrink: 0;
  border: none;
  border-radius: 35px 7px;
  background: #23383f;
  color: #ffffff;
  font-family: var(--font-body);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 3px;
  padding: 15px 26px;
  cursor: not-allowed;
  opacity: 0.9;
}

.bloque-home--boletin .bloque-home__aviso-pendiente {
  flex-basis: 100%;
  margin-top: 0;
}

/* .bloque-redes-sociales se retiró 2026-08-09 junto con el bloque de
   home del mismo nombre — ver el comentario en bloques.php. Los estilos
   reales de los íconos ahora viven en `.sitio-footer__redes` más abajo. */

/* ─── Páginas de destino del menú superior (proyectos.php, catalogos.php,
   noticias.php, contacto.php). Reutilizan al máximo los patrones ya
   establecidos arriba (grid de tarjetas con placeholder #f2f2f2,
   .btn-outline, .bloque-home__aviso-pendiente, tokens de tipografía) en
   vez de inventar un lenguaje visual nuevo, anclándose a los tokens
   globales ya verificados contra el mockup (assets/css/tokens.css) sin
   necesidad de extraer CSS en vivo del mockup en cada página nueva. */

.pagina-intro {
  max-width: 720px;
  margin: 0.75rem 0 2rem;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  color: var(--color-body-text);
}

/* ── Proyectos ── */
.seccion-proyectos-categoria {
  margin-top: 2.5rem;
}

.seccion-proyectos-categoria:first-of-type {
  margin-top: 1.5rem;
}

.grid-proyectos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1.25rem;
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
}

.tarjeta-proyecto__imagen {
  aspect-ratio: 4 / 3;
  background: #f2f2f2; /* mismo placeholder que .tarjeta-catalogo__imagen */
  border-radius: 6px;
}

.tarjeta-proyecto h3 {
  margin: 0.75rem 0 0.15rem;
  font-size: var(--fs-body);
}

.tarjeta-proyecto__ubicacion {
  margin: 0;
  font-size: 12px;
  color: #999;
}

.cta-cotizacion {
  margin-top: 2.5rem;
  padding: 2rem;
  border-top: 1px solid #f0f0f0;
  text-align: center;
}

.cta-cotizacion h2 {
  margin-bottom: 0.75rem;
}

/* ── Noticias (grid-posts) — mismo patrón de .grid-catalogos; --compacto
   es la variante usada en el bloque de home (3 columnas más apretadas,
   sin fecha/categorías visibles porque el bloque de home ya las omite
   en su propia función de render). ── */
.grid-posts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

/* Título de sección ("Noticias MUPA®"): mismo tratamiento 30px/300/
   mayúsculas/centrado que .bloque-home--productos-destacados h2 y
   .bloque-home--catalogos h2 — verificado con getComputedStyle contra
   mupa2.com.mx (Oswald 300, 30px, uppercase, center, color heredado de
   la regla base h2). Es un <h5> en el markup real del mockup, aquí sigue
   como <h2> por el mismo criterio de esquema de encabezados que ya usa
   el resto de la home (un solo nivel de heading por bloque). */
.bloque-home--noticias h2 {
  font-size: 30px;
  font-weight: 300;
  text-transform: uppercase;
  text-align: center;
  line-height: 1.2;
}

/* Gap real medido contra el mockup (Claude en Chrome, 3 columnas de
   ~250px con ~46px entre ellas — proporción bastante más generosa que
   el resto de grids compactos del sitio, que comparten 1.25rem). Se
   sube solo para este bloque, sin tocar `.grid-posts--compacto` base
   (noticias.php sigue usando el gap compartido). */
/* `auto-fit`, no `auto-fill`: mismo bug ya corregido antes en Productos
   Destacados (ver entrada 2026-08-07 de la bitácora en README.md) —
   `auto-fill` reserva tantas columnas como caben en el ancho disponible
   aunque no tengan tarjeta que mostrar, y esas columnas vacías se comen
   el espacio que debería repartirse entre las 3 tarjetas reales: a mayor
   ancho de pantalla, más columnas fantasma, y las 3 reales se ven cada
   vez MÁS angostas en vez de más anchas. `auto-fit` colapsa las columnas
   vacías a 0 y reparte su espacio entre las que sí tienen contenido. */
.bloque-home--noticias .grid-posts--compacto {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 2.5rem;
}

/* "ver todas las noticias": José reportó, con captura, que quedaba
   pegado justo debajo de la tarjeta más corta del grid (el `margin-top:
   auto` de "Leer más", ver esa regla más abajo, deja a esta tarjeta sin
   aire propio ahí). `display: block` para que el `margin-top` aplique de
   forma consistente sobre un `<a>` (inline por default). */
.bloque-noticias__ver-todas {
  display: block;
  margin-top: 2rem;
}

/* Flex column para que "Leer más" quede siempre alineado en el mismo
   renglón inferior, sin importar que el título o el extracto ocupen más
   o menos líneas entre una tarjeta y otra — mismo mecanismo (y mismo
   motivo, reportado por José con captura) que ya usa
   `.tarjeta-producto-destacado`/`.tarjeta-catalogo`: el grid ya estira
   las 3 tarjetas a igual altura (`align-items: stretch`, default de
   `display: grid`), así que un `margin-top: auto` en `.tarjeta-post__
   leer-mas` (ver esa regla más abajo) absorbe el espacio sobrante y
   empuja el link al fondo en las 3 por igual. */
.tarjeta-post {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* 870/395: proporción nativa de los banners de blog del mockup (traen
   título superpuesto y logo horneados en la propia imagen) — un
   aspect-ratio distinto (p.ej. 4/3, el que usa .tarjeta-producto) recorta
   verticalmente y corta ese texto a la mitad. */
.tarjeta-post__imagen {
  display: block;
  width: 100%;
  aspect-ratio: 870 / 395;
  object-fit: cover;
  background: #f2f2f2; /* placeholder visual mientras carga */
  border-radius: 6px;
}

.tarjeta-post__fecha {
  margin: 0.75rem 0 0;
  font-size: 12px;
  color: #999;
}

.tarjeta-post h3 {
  margin: 0.25rem 0 0.5rem;
  font-size: var(--fs-body);
}

/* El <h3> base (h2, h3, h5 + --fs-h3/--fw-h3/--color-heading-accent) YA
   da el tratamiento correcto de título de tarjeta — Oswald 300, 17px,
   #3f6964, letter-spacing propio — mismo hallazgo que ya se documentó
   para .tarjeta-catalogo h3. El `font-size: var(--fs-body)` de la regla
   base de arriba es el mismo bug de siempre (deja el título al tamaño
   del párrafo, 15px); se sobreescribe aquí solo para este bloque. A
   diferencia de Catálogos, el mockup deja el título de post SIN centrar
   (alineado a la izquierda de su columna, verificado contra
   mupa2.com.mx) — no se agrega text-align. `line-height: 1.4` medido en
   vivo (23.8px/17px) — se agrega desde el inicio porque títulos largos
   ("CÓMO INTEGRAR JUEGOS INFANTILES...") hacen wrap a 2-3 líneas y ya
   sabemos por Productos Destacados/Informativa que sin esto se montan. */
.bloque-home--noticias .tarjeta-post h3 {
  margin: 0.75rem 0 0.5rem;
  font-size: var(--fs-h3);
  text-transform: uppercase;
  line-height: 1.4;
}

/* Extracto: mismo color/tamaño que .bloque-informativa__tarjeta p y
   .tarjeta-catalogo p (--color-body-text es #666666 contra el #665c52
   medido en el mockup — un solo tono de gris de diferencia, mismo
   criterio ya usado en el resto del sitio de no crear token nuevo solo
   por eso). `line-height: 1.3` medido en vivo (19.5px/15px). */
/* `margin-bottom: 0.75rem` (antes 0): mínimo garantizado entre el
   extracto y "Leer más" en las 3 tarjetas — el `margin-top: auto` del
   link de abajo solo absorbe el espacio EXTRA cuando lo hay, mismo
   mecanismo de dos piezas (mínimo fijo aquí + auto en el elemento
   siguiente) que ya usa `.tarjeta-producto-destacado p`/`__boton`. */
.tarjeta-post__extracto {
  margin: 0 0 0.75rem;
  color: var(--color-body-text);
  font-size: var(--fs-body);
  line-height: 1.3;
}

/* "Leer más": extiende .enlace-pendiente (href="#" en el mockup
   también — no hay ficha de post propia en este repo, mismo criterio
   que el resto del sitio) en vez de reemplazarlo, porque el subrayado
   punteado + cursor "help" siguen siendo la señal correcta de "esto no
   lleva a ningún lado todavía". Solo se ajustan los dos valores que sí
   se midieron distintos contra el mockup: negritas (500, la base no
   trae peso propio) y letter-spacing (1px, la base no lo trae). El
   color YA coincide sin tocar nada — `--color-link` (#2ea3f2) es
   exactamente el mismo azul medido aquí; con este segundo caso real ya
   no es una "muestra puntual sin confirmar" (ver comentario en
   tokens.css, pendiente de actualizar esa nota).

   `margin-top: auto` (antes fijo en 0.75rem): mismo pedido de José que
   ya resolvimos en Productos Destacados — con las 3 tarjetas estiradas
   a igual altura por el grid, el título/extracto no siempre ocupan el
   mismo número de líneas entre una tarjeta y otra, así que un margen
   fijo dejaba "Leer más" en un renglón distinto en cada una. `auto`
   dentro de `.tarjeta-post` (flex column, ver esa regla arriba) empuja
   el link al mismo renglón inferior en las 3, sin importar cuánto texto
   haya arriba — el mínimo de aire ya lo garantiza el `margin-bottom`
   del extracto, no este `auto`. */
.tarjeta-post__leer-mas {
  display: block;
  margin-top: auto;
  font-weight: 500;
  letter-spacing: 1px;
}

.tarjeta-post__categorias {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  font-size: 11px;
}

.tarjeta-post__categorias li {
  padding: 0.15rem 0.5rem;
  border: 1px solid #eee;
  border-radius: 999px;
  color: #999;
}

/* ── Contacto ── */
.tarjetas-servicios-contacto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

.tarjeta-servicio-contacto {
  padding: 1.5rem;
  border: 1px solid #f0f0f0;
  border-radius: 6px;
}

.tarjeta-servicio-contacto__icono {
  width: 48px;
  height: 48px;
  margin-bottom: 1rem;
  background: #f2f2f2; /* mismo criterio de placeholder que el resto del sitio: sin ícono real todavía */
  border-radius: 50%;
}

.tarjeta-servicio-contacto h2 {
  font-size: var(--fs-body);
  font-family: var(--font-heading);
  margin-bottom: 0.5rem;
}

.tarjeta-servicio-contacto p {
  font-size: 13px;
  color: var(--color-body-text);
  margin-bottom: 1rem;
}

.contacto-datos {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid #f0f0f0;
}

.contacto-datos dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.5rem 1.5rem;
  margin: 1rem 0 0;
}

.contacto-datos dt {
  font-family: var(--font-heading);
  color: #999;
}

.contacto-datos dd {
  margin: 0;
}

/* ── Formulario de captura (includes/formulario_lead.php) — usado por
   catalogos.php y contacto.php. Grid de 2 columnas, campos "completo"
   (Comentarios) ocupan el ancho total, mismo criterio que
   .ficha-producto__descripcion para separación de bloques. ── */
.formulario-lead {
  display: grid;
  grid-template-columns: repeat(2, minmax(220px, 1fr));
  gap: 1.25rem;
  max-width: 720px;
  margin-top: 1.5rem;
}

.formulario-lead__campo {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.formulario-lead__campo--completo {
  grid-column: 1 / -1;
}

.formulario-lead__campo label {
  font-size: 13px;
  font-family: var(--font-heading);
  color: var(--color-body-text);
}

.formulario-lead__campo input,
.formulario-lead__campo select,
.formulario-lead__campo textarea {
  padding: 0.6rem 0.85rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  background: #fff;
}

.formulario-lead__campo textarea {
  resize: vertical;
}

.formulario-lead__nota {
  margin: 0.15rem 0 0;
  font-size: 11px;
  color: #999;
}

.formulario-lead__acciones {
  grid-column: 1 / -1;
  margin-top: 0.5rem;
}

/* ─── Responsive: menú vertical con acordeón anidado en mobile ─────────
   El sidebar fijo de 190px en escritorio no tiene sentido en pantallas de
   celular: aquí se apila arriba de todo el contenido, a ancho completo
   (`position: static` en vez de `fixed`, ver más abajo — a pantalla
   completa `position: fixed` + `height: 100vh` taparía todo el
   contenido).

   El panel flotante de subcategorías (`.sidebar-subcategorias`,
   `position: absolute` + `left: 100%`, ver la regla base más arriba) SÍ
   tiene sentido en escritorio (hay espacio a la derecha), pero no en
   celular: en una pantalla angosta un panel "a la derecha" se sale del
   viewport o se encima con el resto del menú. Aquí se convierte en un
   bloque normal, EN EL MISMO FLUJO del documento, justo debajo de su
   categoría — la jerarquía se lee por INDENTACIÓN (`padding-left`, ver
   más abajo) en vez de por desplazamiento lateral, igual que el mockup de
   referencia en mobile. El mecanismo de abrir/cerrar (clase `--abierto`
   por tap en el chevron, o `:focus-within` por teclado — el `:hover` real
   ya queda descartado en touch por el propio `@media (hover: hover) and
   (pointer: fine)` de la regla base) es EXACTAMENTE el mismo que en
   escritorio. Lo único que cambia es CÓMO se anima el panel: como aquí SÍ
   está en el flujo normal (no `position: absolute`), animar solo
   `opacity`/`visibility` dejaría un hueco vacío del tamaño del panel
   mientras está "cerrado" — por eso se anima `max-height` en su lugar,
   que al colapsar a 0 también colapsa el espacio que ocupa. */
@media (max-width: 700px) {
  /* ─── Menú móvil: panel flotante tras botón hamburguesa ─────────────
     En mobile el menú de navegación completo (categorías + nav superior)
     está OCULTO por default, revelado por un botón hamburguesa, FLOTANDO
     sobre el contenido de la página (no empujándolo hacia abajo) y con
     TRANSPARENCIA — para que se alcancen a ver, detrás del panel, los
     botones blancos sin color asignado y el resto del contenido de fondo.

     Estructura (ver includes/header.php y assets/js/sidebar.js):
       <aside class="sitio-sidebar">
         <div class="sitio-sidebar__barra">  <!-- SIEMPRE visible, fija -->
           <a class="sitio-logo">...</a>
           <button id="menu-hamburguesa">...</button>
         </div>
         <div class="sitio-sidebar__contenido" id="sitio-sidebar-contenido">
           ...título + árbol de categorías...    <!-- oculto por default -->
           ...nav superior (.top-nav-estatico)...  <!-- MOVIDO aquí por JS -->
         </div>
       </aside>
       <header class="sitio-header" id="menu-nav-superior"></header>
                                    <!-- vacío en mobile, oculto por CSS -->

     UN SOLO panel flotante: assets/js/sidebar.js mueve el nav superior
     DENTRO del panel de categorías al entrar a mobile (y lo regresa a
     <header> al volver a escritorio) — así ambos comparten el mismo
     scroll y el mismo alto real, sin necesitar coordinar dos paneles
     independientes ni adivinar cuánto mide uno para anclar el otro. Una
     sola clase en <body> (`.menu-movil-abierto`) controla la visibilidad
     del panel único. */

  /* Barra fija superior: logo + botón hamburguesa, siempre visible
     (abierto o cerrado el menú) — es el único punto de entrada para
     abrir el menú, así que no puede depender de que el menú ya esté
     abierto. Alto FIJO y explícito (--menu-movil-barra-alto, declarada
     en `body` más arriba): el resto de esta hoja compensa exactamente
     ese valor, no una estimación. */
  .sitio-sidebar__barra {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 300;
    display: flex;
    align-items: center;
    justify-content: space-between;
    height: var(--menu-movil-barra-alto);
    padding: 0 1.25rem;
    background: rgba(255, 255, 255, 0.95);
    border-bottom: 1px solid #eee;
  }

  /* El margen de escritorio (separar el logo del título/categorías que
     antes venían justo debajo, en el mismo contenedor) no aplica aquí:
     en mobile el logo está solo, en una fila fija de alto fijo. */
  .sitio-sidebar__barra .sitio-logo {
    margin-bottom: 0;
  }

  /* Botón hamburguesa: mismo lenguaje visual que el resto de la
     iconografía del sitio (ver .sidebar-chevron más arriba) — reset de
     apariencia nativa de <button>, tres barras dibujadas con CSS puro
     (sin carácter ni imagen), que se transforman en una "X" cuando el
     menú está abierto (mismo tipo de transición que el giro del
     chevron). El estado abierto/cerrado lo refleja `aria-expanded`
     (assets/js/sidebar.js lo sincroniza al hacer clic) — se usa ese
     atributo como selector en vez de depender de `.menu-movil-abierto`
     en <body>, para que el ícono del botón y su propio estado semántico
     cambien siempre juntos. */
  .menu-hamburguesa {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 32px;
    height: 32px;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }

  .menu-hamburguesa__barra {
    display: block;
    width: 22px;
    height: 2px;
    background: var(--color-h1);
    transition: transform 0.25s ease, opacity 0.25s ease;
  }

  /* Barras 1 y 3 giran hasta formar una "X"; la barra 2 (del medio) se
     desvanece. */
  .menu-hamburguesa[aria-expanded="true"] .menu-hamburguesa__barra:nth-child(1) {
    transform: translateY(7px) rotate(45deg);
  }

  .menu-hamburguesa[aria-expanded="true"] .menu-hamburguesa__barra:nth-child(2) {
    opacity: 0;
  }

  .menu-hamburguesa[aria-expanded="true"] .menu-hamburguesa__barra:nth-child(3) {
    transform: translateY(-7px) rotate(-45deg);
  }

  /* Buscador en mobile: instancia `--sidebar` se apaga (ya se ve la de
     `--movil`, sin esto quedaría duplicado dentro del panel del menú
     hamburguesa) y `--movil` se revela, viviendo en la barra fija junto
     al botón hamburguesa — confirmado contra el mockup en iframe de
     390px que la lupa vive en esa barra superior, NO dentro del panel de
     categorías (ver includes/menu.php, imprimirBuscador()).

     `margin-left: auto` en el ícono cerrado es el truco clásico de
     flexbox "empujar este elemento y todos los que le siguen hacia la
     derecha": con eso, logo se queda a la izquierda y buscador+hamburguesa
     quedan juntos pegados al borde derecho de la barra, sin tocar
     `justify-content` de `.sitio-sidebar__barra` (ver esa regla arriba;
     `space-between` deja de tener espacio libre que repartir en cuanto el
     margen automático ya se lo consumió todo). */
  .buscador--sidebar {
    display: none;
  }

  .buscador--movil {
    display: flex;
    align-items: center;
    margin-left: auto;
    margin-right: 1rem;
  }

  /* Campo abierto: ocupa el espacio disponible de la barra (mismo criterio
     "inline dentro del contenedor" que escritorio, solo que aquí el
     contenedor es la barra fija en vez de la columna del sidebar) — el
     botón hamburguesa se oculta mientras tanto (ver
     `body.buscador-movil-abierto` más abajo, clase que pone
     assets/js/buscador.js), igual que en el mockup real: el campo
     reemplaza visualmente al ícono de hamburguesa, no conviven los dos a
     la vez en una barra tan angosta. */
  .buscador--movil.buscador--abierto {
    flex: 1;
    /* Fijo en vez de heredar el `auto` de la regla cerrada (arriba): con
       flex:1 de por medio, un margen automático compite con el
       crecimiento del propio elemento por el espacio libre y deja
       apenas un hueco mínimo junto al logo — un valor fijo es
       predecible sin importar cuánto texto quepa. */
    margin-left: 0.75rem;
    margin-right: 0;
  }

  .buscador--movil .buscador__form {
    width: 100%;
  }

  body.buscador-movil-abierto .menu-hamburguesa {
    display: none;
  }

  /* <aside> en sí ya no necesita ancho/alto/padding propios: sus dos
     hijos (barra y contenido) resuelven su propia posición por separado
     (fija la primera, flotante-condicional la segunda) — el <aside>
     como caja queda vacío/colapsado en el flujo normal, sin efecto
     visual. */
  .sitio-sidebar {
    position: static;
    width: 100%;
    height: auto;
    padding: 0;
  }

  /* Panel único: categorías + nav superior (movido aquí por
     assets/js/sidebar.js, ver comentario de cabecera de este bloque).
     Oculto por default (`display: none`), flotante cuando el menú está
     abierto. `max-height` usa TODO el alto disponible bajo la barra fija
     (`calc(100vh - ...)`, no un porcentaje arbitrario): con el alto real
     de la pantalla como único límite, el panel solo necesita scroll
     interno (`overflow-y: auto`) si el contenido de verdad no cabe en la
     pantalla completa, no antes. */
  .sitio-sidebar__contenido {
    display: none;
  }

  body.menu-movil-abierto .sitio-sidebar__contenido {
    display: block;
    position: fixed;
    top: calc(var(--menu-movil-barra-alto) + 0.5rem);
    left: 1rem;
    right: 1rem;
    max-height: calc(100vh - var(--menu-movil-barra-alto) - 1rem);
    overflow-y: auto;
    z-index: 250;
    padding: 0.75rem;
    /* Transparencia a propósito: el fondo semisólido deja ver el
       contenido de la página detrás del panel (imagen del hero, tarjetas,
       y los botones BLANCOS del árbol de categorías que no tienen color
       de marca asignado, que sin este fondo traslúcido quedarían
       indistinguibles del blanco del panel). Mismo tratamiento que ya usa
       el panel flotante de subcategorías en escritorio
       (`.sidebar-subcategorias`, ver la regla base más arriba), reusado
       aquí para que los paneles flotantes del sitio se vean
       consistentes entre sí. */
    background: rgba(255, 255, 255, 0.75);
    border-radius: 8px;
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.2);
  }

  /* <header> queda vacío en mobile (su contenido real, `.top-nav-estatico`,
     se mudó dentro de `.sitio-sidebar__contenido` — ver comentario de
     cabecera de este bloque): oculto siempre aquí, sin condicionarlo a
     `.menu-movil-abierto` — un <header> vacío nunca debe mostrarse. */
  .sitio-header {
    display: none;
  }

  /* Nav superior (MUPA®, PRODUCTOS, PROYECTOS...) ya reubicado dentro del
     panel único de categorías: pasa de fila horizontal alineada a la
     derecha (como en escritorio) a lista vertical, mismo trato que el
     resto del menú. El borde superior + separación lo distingue como una
     sección distinta DENTRO del mismo panel — un solo panel con un
     divisor interno en vez de dos cajas flotantes separadas, para que se
     vean como un menú único. Selector actualizado a
     `.top-nav` (la lista real de items, ver arriba en este archivo) — el
     <nav> que assets/js/sidebar.js mueve sigue siendo `.top-nav-estatico`
     sin cambios, pero el flex container de adentro se renombró junto con
     el resto del nav superior (ver includes/header.php). */
  .sitio-sidebar__contenido .top-nav {
    flex-direction: column;
    gap: 0.35rem;
    margin-top: 0.75rem;
    padding-top: 0.75rem;
    border-top: 1px solid rgba(0, 0, 0, 0.12);
  }

  /* Nav superior (MUPA®, PRODUCTOS...) y el enlace "MOBILIARIO URBANO Y
     EQUIPAMIENTO:" (`.sidebar-titulo`, ver más arriba) como botones
     blancos SÓLIDOS, mismo tratamiento que los botones de categoría con
     color de marca (`.sidebar-categoria--con-color > a`, ver la regla
     base más arriba: mismo `border-radius`, mismo padding) pero con fondo
     blanco en vez de color — estos dos elementos no tienen color de marca
     propio, así que sin un fondo sólido quedan como texto suelto sobre el
     panel semitransparente, ilegible según qué contenido de la página
     quede detrás (imagen del hero, fondos oscuros, etc.). `box-shadow`
     sutil (no presente en los botones de color) porque un botón blanco
     sólido sobre el fondo del propio panel (`rgba(255,255,255,0.75)`,
     casi blanco) se distinguiría poco sin un borde de separación visual.
     Cambio acotado a mobile: en escritorio el nav superior vive sobre
     `.sitio-header`, con fondo opaco de la página detrás — no tiene el
     problema de legibilidad que resuelve este bloque, así que no se toca.

     La "píldora" blanca va en el `.top-nav__item` (el <li> completo, ver
     includes/header.php), no en el <a>/<span> suelto — así un mismo
     tratamiento visual cubre los tres casos por igual (item plano
     pendiente, link real, item con desplegable) sin repetir la regla tres
     veces. `flex-wrap: wrap` + `.top-nav__submenu` con `flex-basis: 100%`
     (ver más abajo) hacen que el panel del desplegable caiga en su propia
     línea, DENTRO de la misma píldora, en vez de angostarla. */
  .sidebar-titulo,
  .top-nav__item {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    padding: 0.375rem 0.75rem;
    background: #fff;
    border-radius: 5px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
  }

  /* El <a> o <button> visible de cada fila reparte el ancho con el
     `.top-nav__chevron` que lo acompaña (solo PRODUCTOS) — sin esto, el
     texto quedaría del tamaño de su contenido y el space-between de la
     regla de arriba pegaría el chevron contra el borde derecho dejando
     un hueco vacío entre texto y flecha en vez de una fila cohesiva. */
  .top-nav__link,
  .top-nav__disparador {
    flex: 1 1 auto;
  }

  /* El panel de un desplegable cae en SU PROPIA línea dentro de la misma
     píldora blanca (ver `flex-wrap: wrap` arriba) — `flex-basis: 100%`
     es el truco estándar para forzar el salto de línea en un hijo flex
     sin necesitar una fila/columna anidada aparte. La mecánica de
     abrir/cerrar (max-height, ver más abajo) es la MISMA que ya usa
     `.sidebar-subcategorias` en este breakpoint — incluso el comentario
     aplica igual: "no se puede animar a auto en CSS puro, así que el
     tope es arbitrariamente grande". */
  .top-nav__submenu {
    flex-basis: 100%;
    position: static;
    left: auto;
    top: auto;
    margin: 0;
    min-width: 0;
    width: 100%;
    padding: 0.25rem 0 0 0.9rem;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    z-index: auto;
    opacity: 1;
    visibility: visible;
    transform: none;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }

  /* Mismos selectores que la regla de apertura de escritorio (ver más
     arriba en este archivo) — se repiten aquí porque en mobile la
     propiedad que hay que animar para "abrir" es otra (`max-height`, no
     `opacity`/`transform`, ya fijos en 1/visible/none arriba); al tener
     la MISMA especificidad y venir DESPUÉS en el archivo, esta versión
     gana dentro de este breakpoint sin tocar la de escritorio. */
  .top-nav__item--submenu:focus-within > .top-nav__submenu,
  .top-nav__item--submenu.top-nav__item--abierto > .top-nav__submenu {
    max-height: 3000px;
    transition: max-height 0.3s ease;
  }

  /* Blanco de toque más generoso para el chevron de PRODUCTOS en touch
     (1.25rem en escritorio es un blanco fino de mouse) — mismo criterio
     ya aplicado en `.sidebar-chevron` (2.25rem) para el mismo problema. */
  .top-nav__chevron {
    width: 2.25rem;
    height: 2.25rem;
  }

  /* .sitio-layout aporta su padding de escritorio (1.5rem 2rem) también
     en mobile si no se resetea aquí — se pone en 0 porque ahora
     `.sitio-main` (más abajo) trae su propio padding completo: dejar
     los dos activos a la vez duplicaría el espaciado (2rem+1.25rem a
     los costados, etc). `.sitio-sidebar` no necesita nada de este
     padding: sus dos hijos (barra fija, contenido oculto/flotante)
     resuelven su propia posición por separado, sin depender del padding
     del padre. */
  .sitio-layout {
    padding: 0;
  }

  .sitio-main {
    margin-left: 0;
    /* Compensa la barra fija de arriba (logo + hamburguesa), que ya no
       aporta altura al flujo normal (position: fixed) — mismo valor
       exacto que --menu-movil-barra-alto, no una estimación. */
    padding: calc(var(--menu-movil-barra-alto) + 1.5rem) 1.25rem 1.5rem;
  }

  /* Un solo botón por fila, de ancho completo — no una fila de pills que
     se envuelven (así se veía antes, de forma incidental, según cupieran
     o no dos botones lado a lado; con `column` es explícito y no depende
     del ancho exacto del viewport). */
  .sidebar-categorias--nivel1 {
    flex-direction: column;
  }

  .sidebar-subcategorias {
    position: static;
    left: auto;
    top: auto;
    min-width: 0;
    width: 100%;
    /* Como cada nivel de subcategorías está anidado DENTRO del anterior
       (ver includes/menu.php, imprimirSidebarCategorias()), este
       padding-left se ACUMULA solo por estar anidado — no hace falta una
       regla distinta por profundidad (--nivel2, --nivel3, ...): el nivel
       3 queda indentado 2x este valor de forma automática, igual que el
       mockup de referencia. */
    padding: 0.25rem 0 0 0.9rem;
    margin: 0;
    background: transparent;
    border-radius: 0;
    box-shadow: none;
    z-index: auto;
    opacity: 1;
    visibility: visible;
    transform: none;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.3s ease;
  }

  /* Mismos selectores que la regla de apertura de escritorio (ver más
     arriba) — se repiten aquí porque en mobile la propiedad que hay que
     animar para "abrir" es otra (`max-height`, no `opacity`/`transform`);
     al tener la MISMA especificidad y venir DESPUÉS en el archivo, esta
     versión gana dentro de este breakpoint sin tocar la de escritorio. */
  .sidebar-categoria:focus-within > .sidebar-subcategorias,
  .sidebar-categoria--abierto > .sidebar-subcategorias {
    /* Alto arbitrariamente grande — CSS puro no puede animar a "auto".
       Muy por encima de cualquier subárbol real (la jerarquía completa
       tiene ~123 categorías, ver includes/menu.php), así nunca corta
       contenido, aunque la duración de la transición no sea perfectamente
       proporcional al alto real de cada panel particular. */
    max-height: 3000px;
    transition: max-height 0.4s ease;
  }

  /* Formulario de captura (catalogos.php, contacto.php) y datos de
     contacto: en escritorio son grid de 2 columnas (ver reglas base más
     arriba); en el ancho de mobile 2 columnas dejan cada campo demasiado
     angosto para su label — una sola columna, igual que el resto de los
     bloques de esta página en este breakpoint. */
  .formulario-lead,
  .contacto-datos dl {
    grid-template-columns: 1fr;
  }
}

/* ── Páginas institucionales (nosotros.php, servicios.php,
   mobiliario-urbano.php, certificaciones.php) — contenido real,
   extraído en vivo de mupa.com.mx (sitio de producción real, NO el
   mockup mupa2.com.mx: estas 4 subpáginas de MUPA® tampoco existían ahí,
   ver includes/home/contenido.php y README.md). Mismo patrón visual de
   tarjeta que .tarjeta-servicio-contacto (contacto.php), con nombre
   genérico porque estas páginas no son de contacto. ── */
.grid-institucional {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

/* Modificador para servicios.php (8 tarjetas) — tope de 4 columnas por
   línea sin perder el colapso responsivo de `.grid-institucional`. Truco
   "RAM" (Repeat/Auto/Minmax) con techo: el mínimo de cada columna se
   calcula como el ancho que le tocaría a 4 columnas parejas
   (`(100% - 3 gaps) / 4`), así nunca caben más de 4 aunque la pantalla
   sea muy ancha; `max()` con 240px evita que se encimen en pantallas
   angostas — ahí gana el piso de 240px y el grid baja a menos columnas
   por su cuenta, igual que `.grid-institucional` a secas. */
.grid-institucional--4cols {
  grid-template-columns: repeat(auto-fit, minmax(max(240px, (100% - 3 * 1.5rem) / 4), 1fr));
}

.tarjeta-institucional {
  padding: 1.5rem;
  border: 1px solid #f0f0f0;
  border-radius: 6px;
}

.tarjeta-institucional h2,
.tarjeta-institucional h3 {
  font-size: var(--fs-body);
  font-family: var(--font-heading);
  margin-bottom: 0.5rem;
}

.tarjeta-institucional p {
  font-size: 13px;
  color: var(--color-body-text);
  margin: 0;
}

/* Ícono real de cada tarjeta de servicios.php (assets/img/servicios/,
   150×110 en el sitio real) — antes era un <div> vacío sin regla propia
   (no llegaba a verse, ver bitácora 2026-08-15). */
.tarjeta-institucional__icono {
  display: block;
  width: 75px;
  height: auto;
  margin-bottom: 1rem;
}

/* Modificador para Misión/Visión en nosotros.php — José pidió el mismo
   verde pálido que ya usa el bloque de Código de Ética
   (`.seccion-institucional--verde`, #fafcf2). Clase aparte en vez de
   tocar `.tarjeta-institucional` directo: esa clase también arma las 8
   tarjetas de servicios.php, que no deben cambiar de color. */
.tarjeta-institucional--verde {
  background: #fafcf2;
}

.seccion-institucional {
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid #f0f0f0;
}

.lista-ventajas {
  margin: 1rem 0 0;
  padding-left: 1.25rem;
}

.lista-ventajas li {
  margin-bottom: 0.5rem;
}

.grid-certificaciones {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1.5rem;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  text-align: center;
}

.tarjeta-certificacion img {
  max-width: 120px;
  height: auto;
}

.tarjeta-certificacion p {
  margin: 0.75rem 0 0;
  font-size: 13px;
  color: var(--color-body-text);
}

/* ── nosotros.php: 3 fotos reales que acompañan Misión/Visión, Ventajas
   y Garantía MUPA® — mismo orden y contexto que mupa.com.mx/acerca-de
   (imagen antes del texto en Misión/Visión y Garantía; después de la
   lista en Ventajas, ver el HTML de nosotros.php). Fila (imagen +
   contenido) en escritorio, apilado en mobile — mismo breakpoint 700px
   que el resto del sitio. ── */
.seccion-con-imagen {
  display: flex;
  align-items: flex-start;
  gap: 2rem;
}

.seccion-con-imagen__imagen {
  flex: 0 0 240px;
  border-radius: 6px;
  overflow: hidden;
}

.seccion-con-imagen__imagen img {
  width: 100%;
  height: auto;
  display: block;
}

.seccion-con-imagen__contenido {
  flex: 1;
  min-width: 0;
}

@media (max-width: 700px) {
  .seccion-con-imagen {
    flex-direction: column;
  }

  .seccion-con-imagen__imagen {
    flex-basis: auto;
    max-width: 240px;
  }
}

/* Bloques alternados de color — nosotros.php reproduce el patrón de la
   página real (Divi: secciones con fondo blanco/gris claro/verde muy
   claro alternadas, `getComputedStyle` en vivo contra mupa.com.mx/acerca-de
   confirmó rgb(244,244,244) en Ventajas y en la franja del video,
   rgb(250,252,242) en Código de Ética, el resto transparente/blanco).
   Reemplazan el `border-top` de `.seccion-institucional` por un panel con
   fondo y esquinas redondeadas — mismo criterio de tarjeta que el resto
   del sitio, en vez de copiar el "sangrado a todo el ancho" de Divi (nuestro
   layout tiene sidebar fijo, un fondo a todo el ancho del viewport se vería
   descuadrado con `.sitio-main`). */
.seccion-institucional--gris,
.seccion-institucional--verde {
  border-top: none;
  padding: 1.75rem 2rem;
  border-radius: 6px;
}

.seccion-institucional--gris {
  background: #f4f4f4;
}

.seccion-institucional--verde {
  background: #fafcf2;
}

/* Homologar tipografía de títulos en nosotros.php — José pidió que
   Misión, Visión, Ventajas, Garantía MUPA®, Código de Ética y Conducta y
   Conócenos usen la misma tipografía que el <h1> de la página ("Acerca
   de MUPA®") en vez del h2 genérico del sitio (Oswald + verde-teal,
   `--font-heading`/`--color-heading-accent`) — aquí Poppins + verde de
   marca, mismos tokens que ya usa el h1 (`--font-h1`/`--color-h1`).
   Selector `h2.titulo-como-h1` (tag + clase) para ganarle en
   especificidad a `.tarjeta-institucional h2` (que comparten Misión y
   Visión) sin recurrir a `!important`. */
h2.titulo-como-h1 {
  font-family: var(--font-h1);
  font-size: var(--fs-h1);
  font-weight: var(--fw-h1);
  letter-spacing: var(--ls-h1);
  color: var(--color-h1);
}

/* Video de YouTube — última sección de la página real
   (mupa.com.mx/acerca-de), mismo criterio de "al final" y mismo fondo gris
   claro que trae esa franja ahí (ver bloque de arriba). El embed real usa
   width/height fijos (100%/450px); aquí se prefiere un contenedor 16:9
   responsivo (mismo criterio mobile-first que el resto de este rebuild) en
   vez de copiar el alto fijo, que se ve mal en pantallas angostas.
   Centrado (`margin: 0 auto` + `text-align: center` en el título) para que
   no quede pegado al borde izquierdo del área visible en resoluciones
   anchas — a diferencia de Divi, `.sitio-main` no tiene un ancho máximo
   propio, así que sin este centrado el video quedaría descuadrado hacia
   la izquierda en pantallas grandes. */
.seccion-video-nosotros {
  margin-top: 2.5rem;
  padding: 2.5rem 2rem;
  background: #f4f4f4;
  border-radius: 6px;
  text-align: center;
}

.seccion-video-nosotros__embed {
  position: relative;
  width: 100%;
  max-width: 800px;
  margin: 1.5rem auto 0;
  aspect-ratio: 16 / 9;
}

.seccion-video-nosotros__embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}
