/* =========================================================================
   INGELOGSS — components.css
   Estilos de componentes reutilizables, construidos sobre las variables
   y el reset definidos en main.css.

   PASO 5A: sistema de botones (.btn, .btn-primary, .btn-secondary).
   PASO 5B: Header (desktop) y Hero.
   Tarjetas, formularios y el resto de secciones se desarrollan en pasos
   posteriores. Los ajustes específicos de móvil/tablet se hacen en
   responsive.css, no aquí.
   ========================================================================= */

/* -------------------------------------------------------------------------
   UTILIDADES DE ACCESIBILIDAD
   Necesarias para que el Header (botón de menú móvil, skip-link) sea
   accesible, aunque su comportamiento interactivo se implemente después.
   ------------------------------------------------------------------------- */

/* Oculta visualmente un texto mientras lo mantiene disponible para lectores
   de pantalla (usado en el botón de menú móvil del Header). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Enlace "Saltar al contenido principal": oculto hasta recibir foco por
   teclado, para no interferir visualmente con el Header. */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-4);
  z-index: 100;
  background: var(--color-amber);
  color: var(--color-black);
  font-weight: var(--font-weight-bold);
  font-size: var(--text-sm);
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  transition: top var(--transition-fast);
}

/* El color se repite aquí a propósito: la regla global `a:hover, a:focus-visible`
   (main.css, especificidad 0,1,1) es más específica que `.skip-link` (0,1,0)
   y le cambiaba el texto a ámbar oscuro sobre su fondo ámbar (2.64:1). Con
   negro sobre ámbar son 8.31:1. Solo afecta a este enlace. */
.skip-link:hover,
.skip-link:focus-visible {
  color: var(--color-black);
}

.skip-link:focus-visible {
  top: var(--space-4);
}

/* -------------------------------------------------------------------------
   TEXTO EDITORIAL COMPARTIDO
   "Eyebrow": etiqueta corta en versalitas/mono que antecede a los títulos
   de sección en toda la V1. Aquí solo se define la base tipográfica; cada
   sección fija su propio color según si vive sobre fondo claro u oscuro.
   ------------------------------------------------------------------------- */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--letter-spacing-eyebrow);
  text-transform: uppercase;
  margin-bottom: var(--space-4);
}

/* -------------------------------------------------------------------------
   BOTONES
   ------------------------------------------------------------------------- */

/* Base compartida por todos los botones (elementos <a> o <button>). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.875rem 1.75rem; /* ~14px 28px: asegura un área táctil cómoda (~44px+) en móvil */
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-bold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition:
    background-color var(--transition-fast),
    border-color var(--transition-fast),
    color var(--transition-fast),
    transform var(--transition-fast);
}

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

.btn:active {
  transform: translateY(1px);
}

/* --- Primario: acento ámbar de marca, para la acción principal (cotizar). --- */
.btn-primary {
  background-color: var(--color-amber);
  border-color: var(--color-amber);
  color: var(--color-black);
}

.btn-primary:hover {
  background-color: var(--color-amber-dark);
  border-color: var(--color-amber-dark);
  color: var(--color-black);
}

.btn-primary:active {
  background-color: var(--color-amber-dark);
  border-color: var(--color-amber-dark);
}

/* --- Secundario: contorno con el color de texto heredado del contexto.
   Al usar "currentColor" se adapta automáticamente tanto a secciones
   claras (texto/borde negro) como oscuras (texto/borde blanco cálido),
   sin necesidad de variantes adicionales. El relleno en hover/active usa
   una mezcla translúcida del propio currentColor, por lo que mantiene el
   contraste de texto en cualquiera de los dos contextos. --- */
.btn-secondary {
  background-color: transparent;
  border-color: currentColor;
  color: inherit;
}

.btn-secondary:hover {
  background-color: color-mix(in srgb, currentColor 12%, transparent);
  border-color: currentColor;
}

.btn-secondary:active {
  background-color: color-mix(in srgb, currentColor 20%, transparent);
}

/* --- Modificador de tamaño: para el CTA protagonista del Hero. --- */
.btn-large {
  padding: 1rem 2rem;
  font-size: var(--text-md);
}

/* -------------------------------------------------------------------------
   HEADER (desktop)
   Barra fija superior sobre fondo oscuro, coherente con la V1 aprobada.
   El botón de menú móvil se oculta aquí por defecto; responsive.css lo
   mostrará y ocultará la navegación principal en los breakpoints pequeños.
   ------------------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg-dark);
  border-bottom: 1px solid var(--border-on-dark);
  transition:
    background-color var(--transition-base),
    border-color var(--transition-base),
    transform var(--transition-base);
}

/* Transparente mientras el Hero está detrás (clase controlada por
   js/header-scroll.js vía IntersectionObserver). Sin fondo ni borde: se ve
   la foto detrás del logo y la navegación. */
.site-header--transparent {
  background: transparent;
  border-bottom-color: transparent;
}

/* Oculto al bajar / visible al subir, una vez pasado el Hero. */
.site-header--hidden {
  transform: translateY(-100%);
}

/* Red de seguridad: el header nunca debe quedar oculto mientras todavía
   está en su estado transparente (dentro del Hero), aunque por algún
   timing de scroll llegara a tener ambas clases a la vez. */
.site-header--transparent.site-header--hidden {
  transform: none;
}

/* El header sticky reserva su propio alto en el flujo del documento, así
   que sin este ajuste el Hero empezaría justo DEBAJO de esa franja (fondo
   de página, no foto) en vez de detrás de ella. Se sube #main-content ese
   mismo alto (--site-header-height, medido en vivo por
   js/header-scroll.js, con un valor de respaldo por si el JS no llegó a
   correr) para que la foto del Hero quede realmente detrás del header
   transparente. El resto de la página no se ve afectado: el header sigue
   reservando su espacio como antes para todo lo que va después del Hero. */
/* container-type:inline-size: convierte a #main-content en contenedor de
   consulta, así las unidades cqw (hoy, las de .allies-marquee; el fondo
   de borde a borde de las secciones ya no las necesita, ver el mecanismo
   único más abajo) pueden medir SU ancho real ya renderizado en vez del
   ancho crudo del viewport.
   CORRECCIÓN: 100vw (y cualquier cálculo derivado de vw, como
   margin:calc(50% - 50vw)) incluye por especificación el grosor de la
   barra de scroll vertical en navegadores con scrollbars clásicas
   (Windows Chrome/Brave, ~15-17px) — un elemento con width:100vw es
   entonces más ancho que document.documentElement.clientWidth, y al ser
   una caja real (no box-shadow, que es "ink overflow" y no cuenta), sí
   genera scroll horizontal real. No se detectó antes porque Chromium
   headless no reserva espacio para su scrollbar, así que ahí vw==clientWidth
   por coincidencia. #main-content (bloque normal, sin position ni
   max-width propios) ya mide exactamente el ancho visible correcto sin
   necesitar ningún cálculo — es el candidato natural para medir contra él.
   overflow-x no sirve como solución (probado: no cambia scrollWidth, solo
   oculta la barra) — cqw sí resuelve la causa real, no solo el síntoma.
   position:sticky del header no se ve afectado: container-type no crea un
   nuevo contenedor de scroll, solo un contenedor de layout/tamaño. */
#main-content {
  container-type: inline-size;
  margin-top: calc(-1 * var(--site-header-height, 73px));
}

/* -------------------------------------------------------------------------
   FONDO DE BORDE A BORDE — MECANISMO ÚNICO (estándar para toda sección
   nueva con fondo propio: Cotización, Footer, etc.)

   Cómo usarlo: la sección es hijo directo de <main id="main-content"> (o
   de <body>, para el footer) y lleva data-theme="dark|light|yellow" (ver
   main.css, "TEMAS DE SECCIÓN"). Nada más: no declares background,
   max-width, margin-inline ni padding-inline horizontal propios en la
   regla de la sección — los pone esta regla, y cambiar el data-theme
   recolora la sección entera (fondo de borde a borde incluido).

   Qué hace: la propia sección ocupa el 100% del ancho de su contenedor
   (que ya excluye la scrollbar) y pinta su fondo var(--bg) con su propia
   caja. El contenido sigue centrado a max-width (--max-width-content)
   porque el padding lateral suma el margen normal de sección
   (--space-section-x) más la mitad de lo que sobra del ancho:
   max(0, (100% − max-width) / 2). Con box-sizing:border-box el ancho útil
   resulta idéntico al de antes (min(100%, max-width) − 2 × margen), así
   que el contenido no se mueve. El % del padding se mide contra el ancho
   del contenedor de la sección (main o body), no contra vw: excluye la
   scrollbar sin necesitar cqw aquí (cqw sigue en .allies-marquee, que sí
   se sale de su contenedor).

   Por qué no un pseudo-elemento: la versión anterior era un ::before con
   left:50% + transform:translateX(-50%) + width:100cqw. Con scrollbar
   clásica el ancho del contenedor es fraccionario (ej. 1424.8px) y ese
   translateX fraccional se dibuja con antialiasing: la columna de píxeles
   de cada extremo (y las filas de costura entre secciones) quedaba
   mezclada con lo que hay detrás — una línea fina clara, muy visible
   sobre Obras negro, sobre todo a escala de pantalla 125% (Windows). Sin
   transform ni pseudo-elemento el navegador ajusta el fondo a píxeles
   enteros. Medido en Chrome real (anchos 1920/1440/1024/768 × escalas 1,
   1.25 y 1.5): a escala 1.25 (Windows 125%) 0 diferencias en ambos
   extremos; el extremo izquierdo queda limpio en todas las escalas.
   Límite del navegador, no de este CSS: cuando el ancho visible no cae en
   un píxel físico entero (escalas 1 y 1.5 con la scrollbar de 15.2px CSS
   de este equipo), Chrome recorta el viewport con antialiasing y la
   última columna junto a la scrollbar se mezcla ~20% con el fondo del
   canvas — se reproduce igual en una página sin nada de este CSS.

   position:relative + z-index:0: aísla el z-index interno de cada sección
   (flechas, aros, etc.) para que no compitan entre secciones. */
:is(#main-content, body) > [data-theme] {
  position: relative;
  z-index: 0;
  background: var(--bg);
  max-width: none;
  margin-inline: 0;
  padding-inline: calc(
    var(--space-section-x) + max(0px, (100% - var(--max-width-content)) / 2)
  );
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-5);
}

/* Sin caja/respaldo: ícono y wordmark flotan directamente sobre lo que
   haya detrás (foto del Hero o header sólido). El contraste ya no lo da
   un fondo, lo da cada elemento por su cuenta — drop-shadow claro en el
   ícono (ver .logo-img) y text-shadow oscuro en el wordmark (ver
   .logo-wordmark). */
.logo-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

.logo-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 5rem;
  height: 5rem;
}

/* Contorno claro simulado con varias drop-shadow delgadas superpuestas
   (offset de 1px en las 4 direcciones, casi sin blur): sin esto, la
   silueta negra de la máquina se pierde casi por completo contra el
   header en su estado sólido oscuro (un drop-shadow oscuro, como el que
   ya había, no ayuda ahí — solo servía contra fondos claros). El
   drop-shadow oscuro final se conserva como respaldo adicional de
   profundidad, ya no es el único recurso de contraste. */
.logo-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter:
    drop-shadow(1px 0 0.5px var(--color-white-warm))
    drop-shadow(-1px 0 0.5px var(--color-white-warm))
    drop-shadow(0 1px 0.5px var(--color-white-warm))
    drop-shadow(0 -1px 0.5px var(--color-white-warm))
    drop-shadow(0 1px 4px rgba(0, 0, 0, 0.55));
}

/* Restaurado junto al ícono (se había quitado por error de interpretación
   en un paso anterior). Tamaño subido de 1.625rem a 1.875rem para
   mantener el equilibrio con el ícono, que ahora es más grande (5rem).
   text-shadow oscuro (no caja de fondo) para el mismo problema de
   contraste que resuelve el drop-shadow del ícono: el texto (claro) ya se
   lee solo sobre el header sólido oscuro; el shadow es lo que lo sostiene
   sobre las zonas claras de la foto del Hero (cielo, nubes). Mismo
   patrón que el contorno del ícono (offsets de 1px en las 4 direcciones)
   más una sombra difusa para profundidad, en tono oscuro en vez de claro
   porque aquí el texto de base ya es claro. */
.logo-wordmark {
  font-family: var(--font-logo);
  color: var(--color-text-inverse);
  font-size: 1.875rem;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  white-space: nowrap;
  text-shadow:
    1px 0 1px rgba(21, 24, 28, 0.9),
    -1px 0 1px rgba(21, 24, 28, 0.9),
    0 1px 1px rgba(21, 24, 28, 0.9),
    0 -1px 1px rgba(21, 24, 28, 0.9),
    0 2px 6px rgba(21, 24, 28, 0.6);
}

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

.primary-nav a {
  position: relative;
  display: inline-block;
  color: var(--color-text-inverse-secondary);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
}

.primary-nav a:hover {
  color: var(--color-text-inverse);
}

.primary-nav a:focus-visible {
  color: var(--color-text-inverse);
  outline: 2px solid var(--color-amber);
  outline-offset: 4px;
}

/* Línea de acento bajo el enlace activo/enfocado: elemento absoluto, no
   ocupa espacio en el flujo, así que no desplaza a los enlaces vecinos. */
.primary-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 0;
  height: 2px;
  background: var(--color-amber);
  transition: width var(--transition-fast);
}

.primary-nav a:hover::after,
.primary-nav a:focus-visible::after {
  width: 100%;
}

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

/* CTA principal del header: Nivel 1 del sistema de botones por niveles
   (.btn-tier-primary, ver "SISTEMA DE BOTONES POR NIVELES" al final). Aquí
   solo queda el tamaño de texto propio del header. */
.header-actions .btn-primary {
  font-size: var(--text-base);
}

/* Enlace "Correo" (webmail) del header: Nivel 2 implícito (.btn-secondary,
   contorno con currentColor, sin relleno ni esquina cortada) para que el CTA
   siga siendo el único botón relleno. Color explícito porque el header no
   fija un color de texto propio (lo pone cada hijo, igual que .logo-wordmark
   y .menu-toggle) — sin esto heredaría el negro del body, invisible sobre
   fondo oscuro. Oculto por defecto; responsive.css lo muestra desde 1025px. */
.icon-envelope {
  flex-shrink: 0;
}

.header-webmail-link {
  color: var(--color-text-inverse);
  display: none;
}

/* Sin esto, la regla global a:hover/a:focus-visible (main.css, color:
   var(--link)) le gana en especificidad y el texto/ícono (currentColor)
   se vuelve ámbar al interactuar — mismo fix que .primary-nav a:hover /
   :focus-visible ya aplica, aquí para mantener blanco cálido en los tres
   estados interactivos. */
.header-webmail-link:hover,
.header-webmail-link:focus-visible,
.header-webmail-link:active {
  color: var(--color-text-inverse);
}

/* Botón hamburguesa: oculto en el Header de escritorio de este paso. */
.menu-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid var(--border-on-dark-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text-inverse);
}

.menu-toggle:focus-visible {
  outline: 2px solid var(--color-amber);
  outline-offset: 2px;
}

.menu-icon {
  display: block;
  width: 1rem;
  height: 2px;
  background: currentColor;
  position: relative;
  transition: background-color var(--transition-fast);
}

.menu-icon::before,
.menu-icon::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
  transition: top var(--transition-fast), transform var(--transition-fast);
}

.menu-icon::before {
  top: -5px;
}

.menu-icon::after {
  top: 5px;
}

/* Estado abierto (aria-expanded en el botón, ya actualizado por
   navigation.js): las tres líneas se transforman en una "X" — la del
   medio se oculta, las de arriba/abajo se centran y rotan. */
.menu-toggle[aria-expanded="true"] .menu-icon {
  background: transparent;
}

.menu-toggle[aria-expanded="true"] .menu-icon::before {
  top: 0;
  transform: rotate(45deg);
}

.menu-toggle[aria-expanded="true"] .menu-icon::after {
  top: 0;
  transform: rotate(-45deg);
}

/* -------------------------------------------------------------------------
   MENÚ MÓVIL (mobile-nav)
   Panel desplegado bajo el header, controlado por js/navigation.js
   (hidden/aria-expanded). Mismo tratamiento de fondo oscuro que el header.
   ------------------------------------------------------------------------- */
.mobile-nav {
  background: var(--bg-dark);
  border-top: 1px solid var(--border-on-dark);
}

.mobile-nav ul {
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--space-5) var(--space-5);
}

.mobile-nav li:not(:last-child) a {
  border-bottom: 1px solid var(--border-on-dark);
}

.mobile-nav a {
  display: block;
  padding: var(--space-3) 0;
  color: var(--color-text-inverse);
  font-size: var(--text-md);
  font-weight: var(--font-weight-medium);
}

/* Los enlaces ya están en el color pleno (--color-text-inverse), así que el
   estado hover/foco ya no puede ser "gris → blanco": subrayado para el
   hover y anillo ámbar para el foco de teclado (antes el foco solo cambiaba
   el color del texto). */
.mobile-nav a:hover {
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.mobile-nav a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.25em;
  outline: 2px solid var(--color-amber);
  outline-offset: 2px;
}

/* Ítem "Correo corporativo" del menú móvil: mismo trato de color/foco que
   .mobile-nav a (hereda por el selector de tipo), con ícono + fondo sutil
   para separarlo de los 4 enlaces de navegación. El borde inferior ya lo
   pone .mobile-nav li:not(:last-child) a. */
.mobile-nav .mobile-nav-webmail-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;
  background: color-mix(in srgb, var(--color-white-warm) 8%, transparent);
}

/* Menú abierto: header sólido en cualquier posición de scroll. Con la
   página en lo alto el header es transparente (el Hero va detrás), pero
   abrir el panel lo hace más alto (sticky, en flujo) y empuja el contenido:
   lo que quedaba detrás del header era el fondo blanco del <body> — el logo
   claro y el botón de cerrar (×, currentColor = blanco cálido) casi no se
   veían. El estado lo marca aria-expanded en el botón (js/navigation.js),
   así que no hace falta JS. Al cerrar, la regla deja de aplicar y el header
   vuelve solo a su estado normal (transparente sobre el Hero / sólido).
   transform:none: con la página ya pasada del Hero, abrir el panel hace
   crecer el header (sticky, en flujo, ~200px), Chrome compensa ese cambio
   moviendo scrollY hacia abajo esos mismos px, y js/header-scroll.js lo lee
   como "el usuario bajó" y añade .site-header--hidden (translateY(-100%)):
   el menú se abría pero quedaba fuera de pantalla al instante (fallo
   anterior a esta regla, medido igual en la versión previa). Mientras el
   menú está abierto el header no se oculta; al cerrarlo, el propio ajuste
   de scroll en sentido contrario hace que el JS quite la clase. */
.site-header:has(.menu-toggle[aria-expanded="true"]) {
  background: var(--bg-dark);
  border-bottom-color: var(--border-on-dark);
  transform: none;
}

.site-header:has(.menu-toggle[aria-expanded="true"]) .menu-toggle {
  border-color: var(--color-text-inverse);
}

/* -------------------------------------------------------------------------
   HERO
   Composición cinematográfica: imágenes a sangre + degradado + contenido
   textual anclado en la parte inferior, según la V1 aprobada.
   ------------------------------------------------------------------------- */
.hero {
  position: relative;
  overflow: hidden;
  min-height: clamp(35rem, 92vh, 55rem);
  background: var(--bg-dark);
}

.hero-media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.hero-media-main {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.hero-img-main {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 40%;
}

/* Marca de agua decorativa: mismo tratamiento que .machinery-index (tono
   plano, opacidad baja) pero para el ícono del logo. brightness(0) aplana
   el PNG (que trae negro + ámbar) a un solo tono sólido, preservando su
   canal alfa/transparencia — mismo resultado visual que enmascarar un
   bloque de color con la silueta del ícono, pero cargado como <img>
   normal: mask-image con url() no carga bajo file:// en Chrome (bloquea
   el recurso con ERR_FAILED aunque el mismo archivo sí carga como <img>),
   así que esta ruta es la que funciona de forma confiable en cualquier
   contexto, no solo servido por HTTP. Va sobre la foto pero debajo del
   tinte de .hero::after (z-index:1), así el degradado la integra en vez de
   quedar "pegada" encima de todo. Posición: cuadrante superior derecho, la
   zona de cielo más despejada de la foto actual, lejos del bloque de texto
   (que ancla abajo a la izquierda). */
.hero-watermark {
  position: absolute;
  z-index: 0;
  top: 6%;
  right: -6%;
  width: clamp(20rem, 36vw, 34rem);
  height: auto;
  opacity: 0.1;
  filter: brightness(0);
  pointer-events: none;
}

/* Legibilidad sobre la foto única: tres capas en el mismo pseudo-elemento.
   1) degradado corto arriba (oscuro → transparente, ~112px) — base
      consistente para el header transparente, sin depender de si hay cielo
      claro justo detrás del logo en esa foto en particular.
   2) degradado inferior ya existente (transparente → oscuro) — refuerzo
      extra donde va el botón "Solicitar cotización".
   3) tinte parejo de fondo (38%, dentro del rango 30–45% pedido) sobre el
      100% de la foto — así el texto se lee igual de bien esté sobre cielo
      o sobre la máquina, sin depender de la composición de cada foto. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bg-dark) 70%, transparent) 0, transparent 112px),
    linear-gradient(
      180deg,
      rgba(21, 24, 28, 0) 0%,
      rgba(21, 24, 28, 0.5) 45%,
      rgba(21, 24, 28, 1) 100%
    ),
    color-mix(in srgb, var(--bg-dark) 38%, transparent);
}

.hero-content {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: clamp(1.75rem, 5vw, 4rem);
  color: var(--color-text-inverse);
}

.hero .eyebrow {
  color: var(--color-amber);
}

.hero-content h1 {
  max-width: 51.25rem;
  font-size: var(--text-3xl);
  margin-bottom: var(--space-5);
  text-wrap: pretty;
}

.hero-subtitle {
  max-width: 37.5rem;
  color: var(--color-text-inverse-secondary);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
  margin-bottom: var(--space-6);
}

/* Enlace liviano, no botón: el CTA sólido ya vive en el header ("Solicitar
   cotización") y es la acción principal de toda la página — duplicarlo
   aquí competía con él, además de ser pesado en mobile. Solo texto con
   peso + flecha, sin caja de fondo. Ámbar sobre el tinte del Hero: mismo
   color y el mismo criterio de contraste que ya usa .hero .eyebrow contra
   el mismo fondo, sin necesidad de una sombra aparte. */
.hero-content .link-cta {
  display: inline-flex;
  align-items: center;
  color: var(--color-amber);
  font-weight: var(--font-weight-bold);
  font-size: var(--text-md);
}

.hero-content .link-cta:hover {
  color: var(--color-white-warm);
}

/* -------------------------------------------------------------------------
   INGELOGSS / PRESENTACIÓN (versión recortada)
   Ya no es un bloque de lectura de dos columnas: es solo una pausa
   editorial breve (eyebrow + una línea de titular) entre el Hero y
   Maquinaria, centrada, con --space-6 de aire vertical en vez de
   --space-section-y. El HTML ya no tiene .ingelogss-content ni la
   media query de 768px que armaba la grilla de dos columnas — no aplican
   porque no hay una segunda columna de texto que posicionar.
   ------------------------------------------------------------------------- */
/* Fondo de borde a borde: lo pone el mecanismo único de
   `:is(#main-content, body) > [data-theme]` (ver su comentario junto a
   #main-content) — fondo var(--bg) del tema, ancho completo y contenido
   centrado a max-width. Esta regla solo aporta color de texto, aire
   vertical y alineación. */
#ingelogss {
  color: var(--fg);
  padding-block: var(--space-6);
  text-align: center;
}

/* Logo completo (ícono + "INGELOGSS") como un solo elemento gráfico —
   reemplaza el eyebrow de texto + ícono pequeño de un paso anterior. Es un
   momento de marca dentro del contenido, no un elemento de navegación
   utilitario: considerablemente más grande que el ícono del header
   (5rem/80px) y centrado arriba del título. width fijo + height:auto
   preserva la proporción real del archivo (540×462); object-fit:contain
   es respaldo por si el contenedor llega a forzar una altura.
   filter: lo define el tema de la sección (--logo-filter, ver main.css).
   El logo es negro sobre transparente: sobre fondo oscuro necesita el
   contorno claro de drop-shadow (tema "dark"); sobre fondo claro se
   muestra tal cual, sin contorno (temas "light" y "yellow"). */
.ingelogss-logo {
  width: clamp(7rem, 20vw, 10rem);
  height: auto;
  object-fit: contain;
  margin-inline: auto;
  margin-bottom: var(--space-5);
  filter: var(--logo-filter);
}

#ingelogss > h2 {
  max-width: 42.5rem;
  margin-inline: auto;
  font-size: var(--text-xl);
}

/* -------------------------------------------------------------------------
   MAQUINARIA (unifica Servicios + Catálogo + Transporte + Vehículos)
   Galería en grid: dos bloques destacados iguales (Excavadora y Cargador
   frontal, 2×2) arriba, y una fila pareja de 4 cuadrados (Volqueta,
   Camionetas, Carro tanque, Compresores, 1×1) debajo — ver
   grid-template-areas. Cada tile muestra solo la foto por defecto. Al
   hover/focus, un overlay con degradado revela
   número + categoría, título, descripción y CTA juntos. Reemplaza por
   completo la versión anterior en zig-zag (filas con bloques de color
   alternados claro/oscuro) — no queda ninguna regla de esa versión
   (.machinery-list, .machinery-item, .machinery-media, .machinery-tag,
   .machinery-body, .machinery-index se eliminaron todas).
   Bajo 640px (breakpoint mobile ya usado en el proyecto) no hay hover
   posible: el grid se simplifica a una columna y el overlay queda
   permanentemente visible — ver responsive.css.
   Colores de la sección (fondo, títulos, eyebrow, línea, nota, botón y
   foco): salen del tema (data-theme en el HTML, variables definidas en
   main.css) — hoy "light". Las tarjetas (.machinery-tile*) NO usan el
   tema: sus overlays oscuros y sus textos viven sobre fotos y conservan
   sus colores fijos sea cual sea el fondo de la sección.
   ------------------------------------------------------------------------- */
/* Fondo de borde a borde: mecanismo único de
   `:is(#main-content, body) > [data-theme]` (ver su comentario junto a
   #main-content). */
#maquinaria {
  color: var(--fg);
  padding-block: var(--space-section-y);
}

#maquinaria > .eyebrow {
  font-size: var(--text-lg);
  color: var(--eyebrow);
}

#maquinaria > h2 {
  max-width: 42.5rem;
  font-size: var(--text-2xl);
  margin-bottom: var(--space-7);
  color: var(--fg);
}

/* Franja amarilla UNIFICADA (Presentación + Maquinaria): un solo cuadrilátero
   de un trazo (4 vértices, sin segmentos intermedios), del lado derecho en
   toda su extensión, cuyo borde izquierdo es UNA línea recta entre el borde
   superior de Presentación y el borde inferior de Maquinaria — no dos formas
   independientes por sección. Cada sección dibuja su propio tramo (el trozo
   de ese cuadrilátero que le toca verticalmente) con un pseudo-elemento con
   z-index:-1 dentro de su contexto de apilamiento propio (position:relative
   + z-index:0, ver `:is(#main-content, body) > [data-theme]`): queda encima
   del fondo pero detrás de todo el contenido y no ocupa espacio en el flujo.
   Los vértices (en píxeles) los calcula js/franja-industrial.js a partir de
   la posición y el alto reales de las dos secciones, con UNA sola fórmula
   (mismas proporciones en cualquier ancho, sin variantes) y los deja en
   --franja-clip; sin JS no hay franja (es decorativa, clip-path vacío por
   defecto). El color es --color-brand-yellow y no cambia con el tema. */
#ingelogss::before,
#maquinaria::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--color-brand-yellow);
  clip-path: var(--franja-clip, polygon(0 0, 0 0, 0 0));
}

/* 1px por encima de la sección: el alto de Presentación es fraccionario y, al
   ras, el límite entre las dos secciones dejaba una línea clara de
   suavizado sobre la franja. Se dibuja sobre esa última fila de Presentación
   con el mismo color y la misma forma (ver js/franja-industrial.js). */
#maquinaria::before {
  top: -1px;
}

/* Grilla de 3 columnas, 9 celdas: las 6 fotos reales comparten una
   proporción cercana a cuadrada (~1.24–1.28:1 en origen) — pedirles
   formas muy distintas a esa (franjas 20:9, cuadros 16:9) forzaba
   recorte fuerte sin importar cómo se organizaran. Excavadora/
   Retrocargador (la mejor compuesta, con margen de sobra) se queda como
   bloque grande 2×2; las otras cinco pasan a cuadrados 1×1 uniformes.
   Matemática de celdas: Excavadora 2×2 (4) + 5 cuadrados 1×1 (5) = 9 =
   exactas 3 filas × 3 columnas, sin huecos ni superposiciones.
   Alturas en "auto": aspect-ratio:1/1 en los 5 cuadrados define la altura
   real de cada fila a partir del ancho de columna (fluido). Excavadora
   comparte filas 1-2 con dos de esos cuadrados (columna 3), así que se
   estira a esa misma altura combinada — como ambas filas miden lo mismo
   que su propio ancho (2 columnas), termina siendo un cuadrado también,
   sin necesidad de fijarle aspect-ratio aparte. */
.machinery-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: auto auto auto;
  grid-template-areas:
    "excavadora excavadora cargador"
    "excavadora excavadora volqueta"
    "camionetas  tanque     compresores";
  gap: var(--space-2);
}

.machinery-tile {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.machinery-tile:nth-child(1) {
  grid-area: excavadora;
}

.machinery-tile:nth-child(2) {
  grid-area: cargador;
}

.machinery-tile:nth-child(3) {
  grid-area: volqueta;
}

.machinery-tile:nth-child(4) {
  grid-area: camionetas;
}

.machinery-tile:nth-child(5) {
  grid-area: tanque;
}

.machinery-tile:nth-child(6) {
  grid-area: compresores;
}

/* Los 5 cuadrados (todo menos Excavadora): tamaño y proporción uniformes. */
.machinery-tile:nth-child(2),
.machinery-tile:nth-child(3),
.machinery-tile:nth-child(4),
.machinery-tile:nth-child(5),
.machinery-tile:nth-child(6) {
  aspect-ratio: 1 / 1;
}

.machinery-tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transition: transform var(--transition-slow);
}

/* Excavadora/Retrocargador: con object-position:center en la caja
   cuadrada, el brazo queda cortado antes del balde. Desplazado hacia la
   derecha para incluirlo. */
.machinery-tile:nth-child(1) .machinery-tile-img {
  object-position: 82% center;
}

/* Carro tanque (1600×720, 2.22:1 real) sigue sin ser casi-cuadrada como
   se esperaba de la foto original — con object-position:center en la
   caja cuadrada el recorte perdía el tanque por completo, dejando solo
   cabina/rueda. Desplazado a la izquierda para que se vea la estructura
   del tanque/tráiler. */
.machinery-tile:nth-child(5) .machinery-tile-img {
  object-position: 10% center;
}

.machinery-tile:hover .machinery-tile-img,
.machinery-tile:focus-visible .machinery-tile-img,
.machinery-tile:focus-within .machinery-tile-img {
  transform: scale(1.04);
}

.machinery-tile:focus-visible {
  outline: 2px solid var(--color-amber);
  outline-offset: -2px;
}

/* Overlay oculto por defecto (solo la foto es visible): opacity, no
   display/visibility, para que el contenido siga en el árbol de
   accesibilidad y en el orden de lectura sin depender del hover. */
.machinery-tile-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  gap: var(--space-1);
  padding: var(--space-4);
  background: linear-gradient(
    180deg,
    rgba(21, 24, 28, 0) 40%,
    rgba(21, 24, 28, 0.92) 100%
  );
  color: var(--color-text-inverse);
  opacity: 0;
  transform: translateY(0.5rem);
  transition: opacity var(--transition-base), transform var(--transition-base);
  pointer-events: none;
}

.machinery-tile:hover .machinery-tile-overlay,
.machinery-tile:focus-visible .machinery-tile-overlay,
.machinery-tile:focus-within .machinery-tile-overlay {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

/* Prefijo .machinery-tile-overlay: .machinery-tile-meta es un <p>, así que
   sin esto la regla genérica .machinery-tile-overlay p (más específica por
   el selector de tipo) le ganaría el color/tamaño a esta. */
.machinery-tile-overlay .machinery-tile-meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: var(--letter-spacing-eyebrow);
  color: var(--color-amber);
}

.machinery-tile-index {
  font-weight: var(--font-weight-bold);
}

.machinery-tile-overlay h3 {
  font-size: var(--text-lg);
  color: var(--color-text-inverse);
}

.machinery-tile-overlay p {
  color: var(--color-text-inverse-secondary);
  font-size: var(--text-sm);
}

.machinery-tile-overlay .link-cta {
  font-weight: var(--font-weight-bold);
  font-size: var(--text-sm);
  color: var(--color-amber);
}

.machinery-tile-overlay .link-cta:hover {
  color: var(--color-white-warm);
}

.machinery-footer {
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}

/* "Ver todo el catálogo": botón de Nivel 2 (.btn.btn-tier-secondary en el
   HTML); su forma y colores salen del sistema de botones por niveles. Aquí
   solo se conservan las medidas que tenía como enlace (padding menos el 1px
   de borde de .btn, mismo tamaño de texto e interlineado): así el bloque no
   cambia de alto y nada de lo de abajo se mueve. */
.machinery-footer .btn {
  padding: calc(0.875rem - 1px) calc(1.75rem - 1px);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

/* -------------------------------------------------------------------------
   OBRAS (fusión de Obras civiles + Obras mecánicas)
   Antes eran dos <section> separadas con su propio eyebrow duplicado
   ("Obras civiles y mecánicas" repetido dos veces) y un CTA solo en la
   segunda. Fusionadas en una sola #obras-civiles con dos paneles internos
   (.obra-panel) y un CTA compartido al final. El id se conserva porque
   nav y footer ya apuntan a #obras-civiles como ancla de "Obras".
   aria-labelledby apunta al eyebrow (no hay un h2 único que titule toda la
   sección — cada panel tiene el suyo, "Obras civiles" / "Obras mecánicas"),
   así la sección sigue teniendo nombre accesible sin inventar un título
   nuevo que no pidió la instrucción.
   Fondo: mecanismo único de `:is(#main-content, body) > [data-theme]`
   (ver su comentario junto a #main-content) — fondo var(--bg) del tema,
   de borde a borde, con corte recto contra la sección de arriba. */
#obras-civiles {
  color: var(--fg);
  padding-block: var(--space-section-y);
}

/* Colores de la sección (fondo, eyebrow y botón): salen del tema
   (data-theme en el HTML, variables definidas en main.css) — hoy "dark".
   Los paneles de fotos (.obra-panel*: degradado, títulos, subtítulos,
   flechas y puntos) NO usan el tema: viven sobre fotos y conservan sus
   colores fijos sea cual sea el fondo de la sección. */
#obras-civiles > .eyebrow {
  color: var(--eyebrow);
}

/* .btn-primary (global) fija ámbar/ámbar-oscuro; aquí el botón toma el
   tema. El foco lleva --focus (el .btn:focus-visible global es ámbar y
   se perdería sobre un tema amarillo). */
.obras-cta .btn-primary {
  background-color: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-fg);
}

.obras-cta .btn-primary:hover,
.obras-cta .btn-primary:active {
  background-color: var(--btn-bg-hover);
  border-color: var(--btn-bg-hover);
  color: var(--btn-fg-hover);
}

.obras-cta .btn-primary:focus-visible {
  outline-color: var(--focus);
}

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

/* Cada panel: ahora un carrusel (ver js/obras-carousel.js) en vez de una
   sola foto fija. aspect-ratio 4/3 (antes 3/4): las fotos reales miden
   entre 1.25:1 y 1.30:1, así que un recuadro apaisado calza con muchísimo
   menos recorte que el vertical original. */
.obra-panel {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-sm);
}

.obra-panel-slides {
  position: absolute;
  inset: 0;
}

/* Crossfade (no desplazamiento horizontal): más sobrio, coherente con el
   resto del sitio, y evita cualquier riesgo de overflow horizontal que sí
   podría introducir un carrusel de scroll/translateX. Las diapositivas
   inactivas quedan con opacity:0 y pointer-events:none (no interactivas,
   fuera del orden de tabulación no hace falta — no contienen nada
   enfocable, son solo <img>). */
.obra-panel-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition-slow);
}

.obra-panel-slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.obra-panel-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Degradado inferior: se queda igual que antes, ahora por encima de
   .obra-panel-slides (que comparte la misma capa z-index:auto, pero pinta
   después en el orden del documento) en vez de por encima de una sola
   .obra-panel-img suelta — el criterio de legibilidad no cambia con el
   carrusel, se aplica igual sobre la foto activa en cada momento. */
.obra-panel::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(21, 24, 28, 0) 40%,
    rgba(21, 24, 28, 0.85) 100%
  );
}

.obra-panel-content {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  padding: var(--space-5);
  color: var(--color-text-inverse);
}

.obra-panel-content h2 {
  font-size: var(--text-2xl);
  margin-bottom: var(--space-3);
}

.obra-panel-content .tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.obra-panel-content .tag-list li {
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  color: var(--color-text-inverse-secondary);
}

.obra-panel-content .tag-list li:not(:last-child)::after {
  content: "·";
  margin-left: var(--space-2);
  color: var(--color-text-inverse-muted);
}

/* Flechas y puntos: inyectados por js/obras-carousel.js (no tienen
   sentido/función sin JS, así que no viven en el HTML estático — mismo
   criterio que ya usa el resto del sitio para lo que depende de
   comportamiento, p. ej. .mobile-nav). z-index:2, por encima del
   degradado (::after, capa implícita) y de .obra-panel-content (1). */
.obra-panel-arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  transform: translateY(-50%);
  border-radius: var(--radius-full);
  background-color: color-mix(in srgb, var(--bg-dark) 55%, transparent);
  color: var(--color-text-inverse);
  font-size: var(--text-xl);
  line-height: 1;
  transition: background-color var(--transition-fast), color var(--transition-fast);
}

.obra-panel-arrow:hover,
.obra-panel-arrow:focus-visible {
  background-color: var(--color-amber);
  color: var(--color-black);
}

.obra-panel-arrow--prev {
  left: var(--space-3);
}

.obra-panel-arrow--next {
  right: var(--space-3);
}

.obra-panel-dots {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  display: flex;
  gap: var(--space-1);
}

.obra-panel-dot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  background: none;
  border: none;
  padding: 0;
}

.obra-panel-dot::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-full);
  background-color: color-mix(in srgb, var(--color-text-inverse) 45%, transparent);
  transition: background-color var(--transition-fast), transform var(--transition-fast);
}

.obra-panel-dot:hover::before,
.obra-panel-dot:focus-visible::before {
  background-color: var(--color-text-inverse);
}

.obra-panel-dot.is-active::before {
  background-color: var(--color-amber);
  transform: scale(1.2);
}

.obras-cta {
  margin-top: var(--space-6);
}

/* -------------------------------------------------------------------------
   CÓMO TRABAJAMOS
   Construcción completa (antes: texto plano sin ningún estilo). 4 pasos
   conectados por una línea — ondulada en desktop (≥900px), vertical recta
   en mobile.

   COLORES: salen del tema (data-theme en el HTML, variables en main.css)
   — hoy "yellow". Fondo, eyebrow, título y descripciones usan
   --bg / --eyebrow / --fg / --fg-muted; círculo, ícono, aro, números y
   curva usan --dot-bg / --dot-ink / --ring / --num-stroke / --num-fill /
   --wave. Cambiar el tema recolorea todo, incluida la curva.
   --steps-bg guarda el fondo de la sección (= var(--bg)) en una variable
   local, heredada por toda la sección — la usa el primer aro de
   .step-circle (ver su regla), que necesita coincidir exactamente con el
   fondo real para "cortar" la línea alrededor de cada círculo sin dejar
   un cerco de otro color: al seguir siempre a --bg, cambiar el tema no
   deja anillos de otro color.
   Fondo de borde a borde: mecanismo único de
   `:is(#main-content, body) > [data-theme]` (ver su comentario junto a
   #main-content). */
#como-trabajamos {
  --steps-bg: var(--bg);
  color: var(--fg);
  padding-block: var(--space-section-y);
}

#como-trabajamos > .eyebrow {
  color: var(--eyebrow);
}

#como-trabajamos > h2 {
  max-width: 42.5rem;
  font-size: var(--text-2xl);
  margin-bottom: var(--space-7);
}

/* --steps-amp: única variable que controla la diferencia de altura entre
   pasos bajos (1, 3) y altos (2, 4) — cambiarla no requiere tocar nada
   más (círculos, números y textos se reposicionan solos, ver más abajo).
   --steps-node-h: alto de la "zona de onda" que reserva espacio para
   ambas variantes sin recortarlas — círculo (3.5rem) + el rango completo
   de la ondulación.
   --steps-ring-outer: hasta dónde llega el aro del círculo desde su
   propio centro — radio (1.75rem) + la separación del fondo (0.4rem) +
   el aro (1.5px), los mismos tres valores de .step-circle más abajo.
   --steps-number-gap / --steps-number-h: separación pedida entre el
   número y el aro (0.6rem), y el alto aproximado del número (font-size
   con line-height:1, así el alto real ronda el font-size). Ambos
   alimentan tanto la posición del número (ver .step-number) como el
   margin-top de aquí abajo, que reserva el espacio para que el número
   del paso "alto" no choque con el h2 — ver ese cálculo para el detalle. */
.steps-track {
  --steps-amp: 4rem;
  --steps-node-h: calc(3.5rem + var(--steps-amp));
  --steps-ring-outer: calc(1.75rem + 0.4rem + 1.5px);
  --steps-number-gap: 0.6rem;
  --steps-number-h: 3.25rem;
  position: relative;
  /* Separación mínima de 1.5rem entre el h2 y el número más alto (el del
     paso "alto": centro a steps-node-h/2 − steps-amp/2 desde el propio
     borde superior de .steps-track).
     CORRECCIÓN: la primera versión de este cálculo asumía que este
     margin-top se SUMA al --space-7 (3rem) que ya trae el margin-bottom
     del h2 — no es así: son márgenes de dos hermanos adyacentes (el h2 y
     este mismo .steps-track), y en flujo normal CSS los colapsa al
     MAYOR de los dos, no a la suma (verificado: con margin-top:45.5px
     aquí, el hueco real seguía siendo de 48px, el del h2, no 93.5px) —
     por eso el número terminaba encima del h2 en vez de 1.5rem por
     debajo. Con el h2 fuera de alcance de esta instrucción, la solución
     es que este margin-top por sí solo ya sea mayor que --space-7 y
     cubra TODO el hueco necesario (así gana el colapso, no hace falta
     sumarle nada del h2): cuánto sobresale el número por encima de
     .steps-track (su propio alto + el hueco al aro + el aro, menos cuán
     abajo está el centro del círculo "alto") más el margen pedido — uso
     1.75rem en vez de 1.5rem exactos como colchón de seguridad ante
     redondeos de subpíxel (medido hasta 1px de variación entre anchos).
     Fórmula completa (no un número fijo): si --steps-amp cambia, este
     cálculo se reacomoda solo. */
  margin-top: calc(
    var(--steps-number-h) + var(--steps-number-gap) + var(--steps-ring-outer)
    - (var(--steps-node-h) / 2 - var(--steps-amp) / 2)
    + 1.75rem
  );
}

/* La curva ya NO es un SVG estirado (preserveAspectRatio="none") — la
   calcula js/como-trabajamos.js en píxeles reales, a partir de la
   posición ya renderizada de los 4 círculos (getBoundingClientRect), con
   tangente horizontal en cada uno para que la onda se vea suave. El SVG
   mide 100%×--steps-node-h (mismo alto que .step-node) y JS le pone su
   propio viewBox 1:1 con su tamaño real en cada recálculo (resize,
   fuentes cargadas) — por eso ya no hace falta vector-effect ni ningún
   otro truco de escala: 1 unidad del path = 1px real, siempre.
   mask-image difumina cada extremo (~9% del ancho) para que la onda
   "aparezca" y "desaparezca" en los bordes en vez de cortarse de golpe. */
.steps-wave {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: var(--steps-node-h);
  pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, black 9%, black 91%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, black 9%, black 91%, transparent 100%);
}

/* Pista punteada tenue, debajo del trazo sólido — mismo "d" que
   .steps-wave-line (JS le asigna el mismo path a ambos). Sin pathLength
   aquí: a diferencia de .steps-wave-line (que sí lo necesita para animar
   el dibujado 0→1 sin calcular la longitud real), el patrón punteado
   necesita unidades de píxel reales (1 de trazo, 9 de espacio) — con
   pathLength="1" esos números se leerían como fracciones de la longitud
   total del path, no como píxeles, y el punteado saldría gigante. */
.steps-wave-track {
  fill: none;
  stroke: var(--wave);
  stroke-width: 1;
  stroke-linecap: round;
  stroke-dasharray: 1 9;
  opacity: 0.5;
}

.steps-wave-line {
  fill: none;
  stroke: var(--wave);
  stroke-width: 2.5;
  stroke-linecap: round;
}

.steps-list {
  position: relative;
  display: flex;
}

.step-item {
  display: flex;
  flex: 1;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding-inline: var(--space-2);
}

.step-node {
  position: relative;
  z-index: 1;
  width: 100%;
  height: var(--steps-node-h);
}

/* Anillo nítido (no un halo borroso): dos box-shadow sin blur —
   primero una separación del color de fondo de la sección (0.4rem,
   var(--steps-bg), definida en #como-trabajamos — sigue al fondo real
   de la sección automáticamente si --steps-bg cambia, sin dejar un
   cerco de otro color) y luego un aro fino (1.5px) en --ring justo
   afuera de esa separación. La línea va por detrás de .step-node
   (z-index) — esa primera sombra, sólida y del color del fondo, es lo
   que la "corta" visualmente alrededor del círculo en vez de dejarla
   pasar por encima. */
.step-circle {
  position: absolute;
  left: 50%;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-full);
  background: var(--dot-bg);
  box-shadow:
    0 0 0 0.4rem var(--steps-bg),
    0 0 0 calc(0.4rem + 1.5px) var(--ring);
  color: var(--dot-ink);
}

.step-icon {
  width: 1.5rem;
  height: 1.5rem;
}

/* Ruedas del camión (paso 3): rellenas con el color del propio círculo
   en vez de quedar huecas — con fill:none (heredado del <svg>) su
   contorno se cruzaba visualmente con el borde inferior de la caja de
   carga. */
.step-icon-wheel {
  fill: var(--dot-bg);
}

/* CORRECCIÓN: "encima" pedía arriba en la página, sin tocar el círculo —
   no apilado en el mismo punto (así se había entendido antes, y por eso
   tapaba el ícono). Ahora el número ancla su propio borde INFERIOR
   (transform: translate(-50%,-100%) en vez de -50%,-100% — el -100% en Y
   sube el elemento su alto completo, dejando el punto "top" justo en su
   base) a --steps-number-gap (0.6rem) por encima de --steps-ring-outer,
   medido desde el mismo centro que usa .step-circle para esa variante —
   así el número queda centrado, arriba del aro, sin solaparlo, y sigue
   seguiendo la ondulación (más arriba en los pasos altos) sin necesitar
   una regla aparte. El círculo NO se mueve — sigue siendo el que mide
   js/como-trabajamos.js para la curva, ver su propia regla top más
   abajo, ahora separada de esta. Oswald 700 (font-display, no
   font-mono: ya no es "numeración de catálogo" como
   .machinery-tile-index, es un elemento gráfico grande).
   -webkit-text-stroke es la única forma con soporte amplio de dibujar un
   contorno de texto en CSS — no existe un "text-stroke" sin prefijo con
   soporte equivalente todavía. color (relleno) y el contorno salen del
   tema (--num-fill / --num-stroke): el relleno es el mismo color del
   contorno vía color-mix a ~8–12% según el tema, el contorno es sólido
   de 1.5px. */
.step-number {
  position: absolute;
  left: 50%;
  transform: translate(-50%, -100%);
  font-family: var(--font-display);
  font-weight: var(--font-weight-bold);
  font-size: var(--steps-number-h);
  line-height: 1;
  color: var(--num-fill);
  -webkit-text-stroke: 1.5px var(--num-stroke);
  pointer-events: none;
  user-select: none;
}

/* Etiqueta "Paso 01" de mobile (ver responsive.css) — aquí solo se
   oculta en desktop; el contenido/color/tipografía se definen en el
   breakpoint donde sí se usa, para no declarar reglas que no aplican en
   este ancho. */
.step-label {
  display: none;
}

/* 1 bajo, 3 bajo / 2 alto, 4 alto. Los cuatro valores salen de
   --steps-amp (ver .steps-track): "bajo" = mitad de --steps-node-h más
   medio --steps-amp; "alto" = mitad de --steps-node-h menos medio
   --steps-amp — cambiar solo --steps-amp reacomoda las cuatro posiciones
   sin tocar esta regla.
   .step-circle es el único que define el centro real de cada variante —
   js/como-trabajamos.js mide getBoundingClientRect() de .step-circle
   (no de .step-node ni de ningún contenedor), así que la curva sigue el
   círculo exactamente. .step-number ya NO comparte este selector: resta
   --steps-ring-outer + --steps-number-gap al mismo centro para anclar su
   propio borde inferior ahí arriba (ver su regla), sin mover el círculo. */
.step-item:nth-child(odd) .step-circle {
  top: calc(var(--steps-node-h) / 2 + var(--steps-amp) / 2);
}

.step-item:nth-child(even) .step-circle {
  top: calc(var(--steps-node-h) / 2 - var(--steps-amp) / 2);
}

.step-item:nth-child(odd) .step-number {
  top: calc(
    var(--steps-node-h) / 2 + var(--steps-amp) / 2
    - var(--steps-ring-outer) - var(--steps-number-gap)
  );
}

.step-item:nth-child(even) .step-number {
  top: calc(
    var(--steps-node-h) / 2 - var(--steps-amp) / 2
    - var(--steps-ring-outer) - var(--steps-number-gap)
  );
}

/* El texto acompaña a su propio círculo, no a una fila fija: para los
   pasos "bajos" (impares) el margen normal de 1.75rem ya cae justo
   debajo de su círculo (el borde inferior del círculo bajo coincide con
   el borde inferior de .step-node). Los pasos "altos" (pares) necesitan
   restar --steps-amp a ese margen — su círculo termina --steps-amp más
   arriba que el borde de .step-node, así que el texto debe subir esa
   misma distancia para mantener los mismos 1.75rem reales respecto a SU
   círculo. Fórmula general, no un número fijo: sigue funcionando si
   --steps-amp cambia. */
.step-content {
  margin-top: 1.75rem;
  max-width: 15rem;
}

.step-item:nth-child(even) .step-content {
  margin-top: calc(1.75rem - var(--steps-amp));
}

.step-content h3 {
  font-size: var(--text-lg);
}

.step-content p {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--fg-muted);
}

/* Animación de entrada — ver js/como-trabajamos.js. El estado POR
   DEFECTO (sin .is-armed) ya es el estado final/completo: círculos,
   números y textos se ven bien aunque el script falle del todo. La
   curva es la única excepción real (lo pide la instrucción): sin script
   no hay "d" en los <path>, así que no se dibuja nada — no hay un
   respaldo en CSS puro para una ruta calculada en JS.
   .is-armed lo agrega el script siempre que corre (con o sin
   prefers-reduced-motion): con la preferencia activa, el script calcula
   la curva igual (para que se vea completa) pero nunca agrega
   .is-visible con transición — ver el script para el detalle. */
.steps-track.is-armed .steps-wave-line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.steps-track.is-armed.is-visible .steps-wave-line {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 1.1s ease;
}

.steps-track.is-armed .step-node,
.steps-track.is-armed .step-content {
  opacity: 0;
  transform: translateY(0.5rem);
}

.steps-track.is-armed.is-visible .step-node,
.steps-track.is-armed.is-visible .step-content {
  opacity: 1;
  transform: none;
  transition: opacity 0.4s ease, transform 0.4s ease;
}

.steps-track.is-armed.is-visible .step-item:nth-child(1) .step-node,
.steps-track.is-armed.is-visible .step-item:nth-child(1) .step-content {
  transition-delay: 0s;
}

.steps-track.is-armed.is-visible .step-item:nth-child(2) .step-node,
.steps-track.is-armed.is-visible .step-item:nth-child(2) .step-content {
  transition-delay: 0.25s;
}

.steps-track.is-armed.is-visible .step-item:nth-child(3) .step-node,
.steps-track.is-armed.is-visible .step-item:nth-child(3) .step-content {
  transition-delay: 0.5s;
}

.steps-track.is-armed.is-visible .step-item:nth-child(4) .step-node,
.steps-track.is-armed.is-visible .step-item:nth-child(4) .step-content {
  transition-delay: 0.75s;
}

/* -------------------------------------------------------------------------
   ALIADOS
   Reconstrucción completa (no tenía ningún estilo — los 6 recuadros
   punteados "LOGO ALIADO" eran solo placeholder de estructura). Cinta con
   desplazamiento horizontal continuo (no por pasos, sin flechas/puntos —
   a diferencia del carrusel de Obras): .allies-track contiene dos
   <ul class="allies-set"> idénticos (el segundo, aria-hidden, es el
   duplicado necesario para el loop) uno junto a otro; animar
   translateX(0) → translateX(-50%) sobre .allies-track desplaza
   exactamente el ancho de un set completo (mismo ancho que el otro, por
   ser el mismo contenido), así el reinicio del loop cae exacto sobre el
   punto de partida, sin salto visible. width:max-content en .allies-track
   es necesario para que su ancho real sea la suma de ambos sets (no el
   100% del contenedor) — si no, el -50% sería relativo al ancho del
   contenedor visible, no al ancho de un set, y el salto sí se notaría.
   Fondo: mecanismo único de `:is(#main-content, body) > [data-theme]`
   (ver su comentario junto a #main-content). */
#aliados {
  color: var(--fg);
  padding-block: var(--space-section-y);
}

/* Colores de la sección (fondo, eyebrow, título, foco de la cinta): salen
   del tema (data-theme en el HTML, variables en main.css) — hoy
   "light". Las fichas de los logos NO usan el tema (ver .allies-logo). */
#aliados > .eyebrow {
  color: var(--eyebrow);
}

#aliados > h2 {
  max-width: 42.5rem;
  font-size: var(--text-2xl);
  margin-bottom: var(--space-7);
}

/* Ancho completo (real, sin scrollbar — ver cqw abajo), aunque #aliados
   (su contenedor) se queda centrado y con max-width — solo la cinta se
   sale de ese ancho, el eyebrow y el h2 (hermanos de .allies-marquee, no
   tocados aquí) siguen dentro del contenedor normal. .allies-marquee
   sigue en flujo normal (no es un pseudo-elemento posicionado), así que
   left:50%+translateX(-50%) no sirve igual — para un elemento en flujo,
   "left" en porcentaje se resuelve contra el ancho de su contenedor
   (angosto), no contra el viewport. margin-left/right:calc(50% - 50cqw)
   es el mecanismo equivalente: el 50% (relativo al contenedor angosto de
   #aliados) y el 50cqw (mitad del ancho real de #main-content, ver
   container-type en su regla) se cancelan de forma que el borde de la
   cinta cae siempre en el borde real de la página.
   CORRECCIÓN: esto usaba 50vw, no 50cqw — 100vw/50vw incluyen por
   especificación el grosor de la barra de scroll vertical en navegadores
   con scrollbars clásicas (Windows Chrome/Brave), así que la cinta
   quedaba ~8px más ancha que el viewport realmente visible y generaba
   scroll horizontal real en esos navegadores (invisible en Chromium
   headless, que no reserva espacio para su propia scrollbar). cqw mide
   el ancho ya renderizado de #main-content — que sí excluye la
   scrollbar, igual que clientWidth — así que corrige la causa, no solo
   la oculta. padding-inline propio (no --space-section-x, que es el
   margen grande de sección): un respiro chico y fijo para que los logos
   no queden pegados al borde real del navegador. */
.allies-marquee {
  width: 100cqw;
  margin-left: calc(50% - 50cqw);
  margin-right: calc(50% - 50cqw);
  padding-inline: var(--space-5);
  padding-block: var(--space-2);
  overflow: hidden;
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent 0,
    black 8%,
    black 92%,
    transparent 100%
  );
  mask-image: linear-gradient(
    90deg,
    transparent 0,
    black 8%,
    black 92%,
    transparent 100%
  );
}

/* El foco va DENTRO del borde de la cinta, en el colchón vertical de
   --space-2 (padding-block) que hay entre el borde y las fichas: un
   outline hacia afuera (offset positivo) queda fuera de la caja y el
   mask-image de .allies-marquee lo recorta por completo (medido: el
   anillo antiguo no se pintaba). Adentro sí se ve, y cae sobre el fondo
   de la sección (no sobre una ficha blanca), así --focus conserva el
   contraste previsto del tema. Se desvanece hacia los extremos junto
   con la cinta. */
.allies-marquee:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: -4px;
}

.allies-track {
  display: flex;
  width: max-content;
  animation: allies-scroll 40s linear infinite;
}

.allies-marquee:hover .allies-track,
.allies-marquee:focus .allies-track {
  animation-play-state: paused;
}

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

.allies-set {
  display: flex;
  flex-shrink: 0;
}

/* Ficha blanca (parte del logo, no del tema): los 14 PNG de
   assets/aliados/ no tienen canal alfa — traen el fondo blanco pegado —,
   así que sobre cualquier fondo de sección se ven como un rectángulo
   blanco. En vez de disimularlo, cada logo va en una ficha blanca de
   tamaño idéntico (fondo y borde con tokens fijos, NO variables de tema:
   debe verse igual sobre dark, light o yellow). Borde de 1px negro al 14%
   (color-mix, sobre el blanco de la propia ficha), sin radio.
   Tamaño: 10.5rem × 6rem con 0.75rem de padding y 1px de borde deja un
   área de logo de 142×70px, mayor en ambas dimensiones que la anterior
   de 7.5rem × 3.5rem (120×56px) — ningún logo se ve más chico que antes —
   y cada ficha ocupa 10.5rem + 1rem de separación: caben ~2 a 390px de
   ancho y ~7–8 en escritorio a la vez. Las fichas se desvanecen con el
   mask-image de .allies-marquee, que deja ver el fondo real de la
   sección (sin borde blanco/gris al fundirse).
   VERIFICADO (tema "light", ficha blanca sobre fondo también blanco): el
   borde al 14% da 1.34:1 medido contra el blanco — prácticamente el mismo
   1.31:1 que ya tenía contra el amarillo del tema "yellow" anterior (no
   busca 3:1 de WCAG; la separación real entre fichas la da el margen, no
   el borde). Confirmado a ojo, con fichas contiguas: se distingue con
   claridad dónde termina una y empieza la siguiente — se deja el 14% sin
   cambios. */
.allies-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 10.5rem;
  height: 6rem;
  padding: var(--space-3);
  margin-inline: var(--space-2);
  background: var(--bg-light);
  border: 1px solid color-mix(in srgb, var(--color-black) 14%, transparent);
}

.allies-logo img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* -------------------------------------------------------------------------
   COTIZACIÓN
   Dos columnas en escritorio (≥900px, ver responsive.css para móvil):
   .quote-intro (eyebrow, título, introducción, WhatsApp, horario) a la
   izquierda y .quote-form — la tarjeta — a la derecha.
   Colores de la columna izquierda: del tema (data-theme, ver main.css).
   La tarjeta es siempre oscura (--card-bg) y su texto siempre
   --color-white-warm, sea cual sea el tema; campos, acento y borde de la
   tarjeta también salen del tema (--input-bg, --card-accent, --card-line).
   Fondo de borde a borde: mecanismo único de
   `:is(#main-content, body) > [data-theme]` (ver #main-content).

   GANCHOS DE ESTADO (los maneja js/quote-form.js): este CSS no cambia
   ningún hook existente (ids, clases, name, data-step, data-step-panel,
   data-action) y define cómo se ve cada estado; el JS los expresa así:
     - Sin data-current-step en <form> (sin JS): los 7
       pasos se muestran apilados, sin barra de progreso ni contador ni
       Atrás/Siguiente (no harían nada); solo "Enviar solicitud".
     - data-current-step="1".."7" en <form>: modo por pasos. Barra de
       progreso (segmentos ≤ paso actual en --card-accent), contador
       "PASO N DE 7" y botones (Atrás oculto en el 1; Siguiente hasta el
       6; Enviar solo en el 7) salen de ese único atributo. Qué paso se
       ve lo decide el atributo `hidden` de cada <fieldset data-step-panel>.
     - Error de validación: quitar `hidden` a <p class="form-error">
       (role="alert") y ponerle el texto.
     - Archivos elegidos: <li> dentro de <ul class="file-list"> con
       <span class="file-name"> y <button type="button" class="file-remove"
       aria-label="Quitar …">.
     - data-state="success" en <form>: muestra .quote-success y oculta el
       resto de la tarjeta; quitarlo reinicia (data-action="restart").
   ------------------------------------------------------------------------- */
/* scroll-margin-top: el header es sticky y el eyebrow queda a
   --space-section-y del borde de la sección. Al llegar por el ancla
   (#cotizacion: botón del header, "Contacto" del menú, botón de Obras,
   "Cotizar" del footer) el borde de la sección queda a (altura del header
   + 1rem − padding de la sección) del borde de la ventana, es decir, el
   eyebrow cae 1rem por debajo del header. Puede ser negativo (válido en
   scroll-margin). Solo actúa en el eje superior: convive con el
   scroll-padding-bottom de <html> (botón flotante de WhatsApp). */
#cotizacion {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  column-gap: var(--space-8);
  align-items: start;
  padding-block: var(--space-section-y);
  color: var(--fg);
  scroll-margin-top: calc(
    var(--site-header-height, 73px) + 1rem - var(--space-section-y)
  );
}

.quote-intro .eyebrow {
  color: var(--eyebrow);
}

.quote-intro h2 {
  font-size: var(--text-2xl);
  margin-bottom: var(--space-5);
  color: var(--fg);
}

.quote-lead {
  max-width: 32rem;
  margin-bottom: var(--space-5);
  font-size: var(--text-md);
  color: var(--fg-muted);
}

/* Vía rápida: botón de Nivel 3 (.btn-tier-whatsapp en el HTML). Forma, verde
   de marca, hover y foco salen del sistema de botones por niveles; aquí
   solo queda su tamaño. */
.quote-whatsapp {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  padding: calc(0.875rem - 1px) calc(1.5rem - 1px); /* menos el 1px de borde de .btn: misma caja que antes */
  font-weight: var(--font-weight-bold);
  font-size: var(--text-base);
  line-height: 1.2;
  white-space: normal;
}

.quote-hours {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--fg-muted);
}

/* Enlace secundario a Catálogo: mismo lenguaje que .btn-secondary (borde en
   vez de relleno, currentColor) que ya usa "Atrás" en el formulario — no
   compite con "¿Prefieres WhatsApp? Escríbenos" (la acción destacada de esta
   columna, con relleno sólido). .btn ya da ≥44px de alto (padding 0.875rem
   1.75rem); solo se agrega el espacio respecto al horario de arriba. */
.quote-catalog-link {
  display: inline-flex;
  margin-top: var(--space-4);
}

/* --- Tarjeta --- */
.quote-form {
  --card-text: var(--color-white-warm);
  --card-muted: color-mix(in srgb, var(--color-white-warm) 80%, transparent);
  /* Borde de componentes (campos, opciones, zona de carga, segmentos
     inactivos, filas de archivo): 45% de blanco cálido. WCAG 1.4.11 pide
     ≥3:1 contra el fondo. Mínimo medido por tema (blanco cálido sobre
     --card-bg / --input-bg, ambos lados del borde): dark 38.7%, light y
     yellow 35.8% — con 45% el peor de los 15 casos da 3.66:1. */
  --card-border: color-mix(in srgb, var(--color-white-warm) 45%, transparent);
  min-width: 0;
  scroll-margin-top: calc(var(--site-header-height, 73px) + var(--space-4));
  padding: clamp(1.25rem, 3vw, 2rem);
  background: var(--card-bg);
  border: 1px solid var(--card-line);
  border-radius: var(--radius-md);
  color: var(--card-text);
}

/* Progreso: la <ol> existente pasa a barra de 7 segmentos; los rótulos
   (Servicio, Detalle, …) siguen en el árbol de accesibilidad pero fuera de
   la vista (text-indent + overflow, no display:none). Solo en modo por
   pasos (data-current-step). */
.form-steps-list {
  display: none;
}

.quote-form[data-current-step] .form-steps-list {
  display: flex;
  gap: 0.375rem;
  margin-bottom: var(--space-3);
}

.form-steps-list li {
  flex: 1;
  height: 4px;
  overflow: hidden;
  border-radius: 2px;
  background: var(--card-border);
  text-indent: -9999px;
  white-space: nowrap;
  transition: background-color var(--transition-base);
}

.quote-form[data-current-step="1"] .form-steps-list li:nth-child(-n + 1),
.quote-form[data-current-step="2"] .form-steps-list li:nth-child(-n + 2),
.quote-form[data-current-step="3"] .form-steps-list li:nth-child(-n + 3),
.quote-form[data-current-step="4"] .form-steps-list li:nth-child(-n + 4),
.quote-form[data-current-step="5"] .form-steps-list li:nth-child(-n + 5),
.quote-form[data-current-step="6"] .form-steps-list li:nth-child(-n + 6),
.quote-form[data-current-step="7"] .form-steps-list li:nth-child(-n + 7) {
  background: var(--card-accent);
}

.form-step-counter {
  display: none;
  margin-bottom: var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--letter-spacing-eyebrow);
  text-transform: uppercase;
  color: var(--card-muted);
}

.quote-form[data-current-step] .form-step-counter {
  display: block;
}

.form-step-counter::before {
  content: var(--quote-step-label);
}

.quote-form[data-current-step="1"] { --quote-step-label: "Paso 1 de 7"; }
.quote-form[data-current-step="2"] { --quote-step-label: "Paso 2 de 7"; }
.quote-form[data-current-step="3"] { --quote-step-label: "Paso 3 de 7"; }
.quote-form[data-current-step="4"] { --quote-step-label: "Paso 4 de 7"; }
.quote-form[data-current-step="5"] { --quote-step-label: "Paso 5 de 7"; }
.quote-form[data-current-step="6"] { --quote-step-label: "Paso 6 de 7"; }
.quote-form[data-current-step="7"] { --quote-step-label: "Paso 7 de 7"; }

/* --- Resumen de lo precargado desde el catálogo ---
   Tarjeta con acento: mismo lenguaje que .form-error (borde izquierdo de 3px
   en --card-accent sobre un velo de blanco cálido al 7%). Visible en todos los
   pasos; js/quote-form.js lo muestra solo si la URL trajo servicio o equipo, y
   lo mantiene al día si el usuario cambia esos campos. Queda oculto por atributo
   `hidden` (y con data-state="success" lo oculta la regla de la pantalla de
   éxito, que esconde todo menos .quote-success). El enlace toma el acento de la
   tarjeta (siempre oscura), no el --link de la sección, y va subrayado. */
.quote-summary {
  margin-bottom: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--card-accent);
  background: color-mix(in srgb, var(--color-white-warm) 7%, transparent);
  color: var(--card-text);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  overflow-wrap: anywhere;
}

.quote-summary[hidden],
.quote-summary [hidden] {
  display: none;
}

.quote-summary-label {
  margin-bottom: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--letter-spacing-eyebrow);
  text-transform: uppercase;
  color: var(--card-muted);
}

.quote-summary-main {
  font-size: var(--text-base);
  font-weight: var(--font-weight-bold);
}

.quote-summary-dates {
  color: var(--card-muted);
}

.quote-summary-link {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  color: var(--card-accent);
  font-weight: var(--font-weight-bold);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

.quote-summary-link:hover {
  color: var(--card-accent);
  text-decoration-thickness: 2px;
}

.quote-summary-link:focus-visible {
  color: var(--card-accent);
  outline: 2px solid var(--card-accent);
  outline-offset: 2px;
}

/* --- Pasos --- */
/* min-width:0 — un <fieldset> tiene min-width:min-content por defecto y el
   <input type="file"> nativo (paso 7) trae un ancho intrínseco mayor que
   una pantalla de 320px: sin esto el fieldset se ensanchaba con él y la
   página desbordaba en horizontal (el bug conocido de 53px a 320px). */
.form-step {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.form-step:not([hidden]) ~ .form-step:not([hidden]) {
  margin-top: var(--space-6);
}

.form-step > legend {
  padding: 0;
  margin-bottom: var(--space-4);
  font-family: var(--font-display);
  font-size: 1.4rem;
  font-weight: var(--font-weight-bold);
  line-height: var(--leading-tight);
  color: var(--card-text);
}

/* En los pasos de un solo campo la <label> repite el texto de la <legend>
   (el título visible del paso): se oculta a la vista, no del lector. */
.form-step[data-step-panel="2"] > label,
.form-step[data-step-panel="3"] > label,
.form-step[data-step-panel="5"] > label,
.form-step[data-step-panel="7"] > label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.quote-form label {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  color: var(--card-text);
}

.quote-form input + label {
  margin-top: var(--space-4);
}

/* --- Campos de texto, textarea, teléfono, correo y fechas --- */
.quote-form input[type="text"],
.quote-form input[type="tel"],
.quote-form input[type="email"],
.quote-form input[type="date"],
.quote-form textarea {
  display: block;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  padding: 0.75rem 0.875rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-sm);
  background: var(--input-bg);
  color: var(--card-text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: var(--leading-snug);
  transition: border-color var(--transition-fast);
}

.quote-form textarea {
  min-height: 7.5rem;
  resize: vertical;
}

.quote-form input[type="date"] {
  color-scheme: dark;
}

/* Cuarto Tab de cada fecha = el botón del calendario (un control interno
   del propio input): el input ya no cumple :focus-visible ahí, así que el
   anillo del acento se dibuja sobre el botón. */
.quote-form input[type="date"]::-webkit-calendar-picker-indicator:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 2px;
}

.quote-form ::placeholder {
  color: var(--color-text-inverse-secondary);
  opacity: 1;
}

.quote-form input[type="text"]:focus-visible,
.quote-form input[type="tel"]:focus-visible,
.quote-form input[type="email"]:focus-visible,
.quote-form input[type="date"]:focus-visible,
.quote-form textarea:focus-visible {
  border-color: var(--card-accent);
  outline: 2px solid var(--card-accent);
  outline-offset: 1px;
}

.form-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}

.form-field label {
  margin-top: 0;
}

.form-note {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--card-muted);
}

/* Aviso de adjuntos (paso 7): mismo tratamiento pequeño que .form-note
   (13px, blanco cálido al 80%, 9.4:1 sobre la tarjeta), pegado a su título
   y con aire antes de la zona de carga. */
.form-note.file-note {
  margin-top: 0;
  margin-bottom: var(--space-4);
}

/* Texto sobre el botón del último paso: solo visible en el paso 7. */
.form-send-note {
  display: none;
  margin-top: var(--space-5);
  font-size: var(--text-sm);
  color: var(--card-muted);
}

.quote-form[data-current-step="7"] .form-send-note {
  display: block;
}

.form-send-note + .form-error {
  margin-top: var(--space-3);
}

/* Destino del foco al cambiar de paso (el JS le pone tabindex="-1"). */
.form-step > legend:focus {
  outline: none;
}

.form-step > legend:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 4px;
}

/* --- Paso 1: opciones de servicio como tarjetas ---
   El <input type="radio"> se queda en el DOM, enfocable y operable con
   teclado (flechas dentro del grupo), pero transparente y cubriendo toda
   la tarjeta: la tarjeta ES el control. La selección no depende solo del
   color del borde: suma fondo más claro y una barra interna de 3px a la
   izquierda (box-shadow inset). */
.service-options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
}

.service-options label {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 3.5rem;
  margin: 0;
  padding: 0.875rem 1rem 0.875rem 1.25rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-sm);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  line-height: var(--leading-snug);
  color: var(--card-text);
  cursor: pointer;
  transition:
    border-color var(--transition-fast),
    background-color var(--transition-fast),
    box-shadow var(--transition-fast);
}

.service-options input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.service-options label:hover {
  border-color: var(--card-accent);
}

.service-options label:has(input:checked) {
  border-color: var(--card-accent);
  background: color-mix(in srgb, var(--color-white-warm) 7%, transparent);
  box-shadow: inset 3px 0 0 var(--card-accent);
}

.service-options label:has(input:focus-visible) {
  outline: 2px solid var(--card-accent);
  outline-offset: 2px;
}

/* --- Paso 7: adjuntos ---
   El propio <input type="file"> es la zona de carga (borde punteado).
   width/max-width/min-width:0 lo mantienen dentro de su caja a cualquier
   ancho (junto con min-width:0 de .form-step, ver arriba). */
.quote-form input[type="file"] {
  display: block;
  width: 100%;
  min-width: 0;
  max-width: 100%;
  padding: var(--space-4);
  border: 1px dashed var(--card-border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--card-text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  cursor: pointer;
}

.quote-form input[type="file"]::file-selector-button {
  margin-right: var(--space-3);
  padding: 0.5rem 1rem;
  border: 1px solid var(--card-border);
  border-radius: var(--radius-sm);
  background: var(--input-bg);
  color: var(--card-text);
  font: inherit;
  cursor: pointer;
}

.quote-form input[type="file"]:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 2px;
}

.file-list {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-4);
}

.file-list:empty {
  display: none;
}

.file-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-width: 0;
  padding: var(--space-2) var(--space-2) var(--space-2) var(--space-4);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-sm);
  background: var(--input-bg);
  color: var(--card-text);
  font-size: var(--text-sm);
}

.file-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-size {
  flex: none;
  margin-left: auto;
  color: var(--card-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}

.file-remove {
  flex: none;
  min-width: 2.75rem;
  min-height: 2.75rem;
  padding: 0 var(--space-3);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--card-text);
  font: inherit;
  font-size: var(--text-sm);
  transition: border-color var(--transition-fast);
}

.file-remove:hover {
  border-color: var(--card-accent);
}

.file-remove:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 2px;
}

/* --- Error de validación: borde izquierdo de 3px + texto (no depende solo
   del color), pegado a los botones. El estado lo controla el atributo
   `hidden` (el JS lo quita y pone el mensaje). --- */
.form-error {
  margin-top: var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--card-accent);
  background: color-mix(in srgb, var(--color-white-warm) 7%, transparent);
  color: var(--card-text);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.form-error[hidden] {
  display: none;
}

/* --- Botones ---
   Atrás: contorno en blanco cálido (.btn-secondary, currentColor).
   Siguiente / Enviar: fondo --card-accent y texto negro. ≥44px de alto.
   Visibilidad por paso: todo sale de data-current-step (ver arriba). */
.form-navigation {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.form-navigation [data-action="prev"] {
  margin-right: auto;
}

.quote-form .btn {
  min-height: 2.75rem;
  font-size: var(--text-base);
}

.quote-form .btn-secondary {
  color: var(--card-text);
}

.quote-form .btn-primary {
  background-color: var(--card-accent);
  border-color: var(--card-accent);
  color: var(--color-black);
}

.quote-form .btn-primary:hover,
.quote-form .btn-primary:active {
  background-color: color-mix(in srgb, var(--card-accent) 82%, var(--color-white-warm));
  border-color: color-mix(in srgb, var(--card-accent) 82%, var(--color-white-warm));
  color: var(--color-black);
}

.quote-form .btn:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 3px;
}

.quote-form .btn:disabled,
.quote-form .btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
}

.quote-form:not([data-current-step]) .form-navigation [data-action="prev"],
.quote-form:not([data-current-step]) .form-navigation [data-action="next"],
.quote-form[data-current-step="1"] .form-navigation [data-action="prev"],
.quote-form[data-current-step="7"] .form-navigation [data-action="next"],
.quote-form[data-current-step]:not([data-current-step="7"]) .form-navigation [data-action="submit"] {
  display: none;
}

/* --- Pantalla de éxito --- */
.quote-success {
  display: none;
}

.quote-form[data-state="success"] > :not(.quote-success) {
  display: none;
}

.quote-form[data-state="success"] .quote-success {
  display: block;
}

/* Destino del foco al mostrar la pantalla de éxito (el JS le pone
   tabindex="-1"): anillo del tema en vez del del navegador. */
.quote-success h3:focus {
  outline: none;
}

.quote-success h3:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 4px;
}

.quote-success-label {
  margin-bottom: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--letter-spacing-eyebrow);
  text-transform: uppercase;
  color: var(--card-accent);
}

.quote-success h3 {
  margin-bottom: var(--space-3);
  font-size: 1.4rem;
  color: var(--card-text);
}

.quote-success p:not(.quote-success-label) {
  margin-bottom: var(--space-5);
  color: var(--card-text);
}

.quote-restart {
  display: block;
  min-height: 2.75rem;
  margin-top: var(--space-3);
  padding: 0;
  border: 0;
  background: none;
  color: var(--card-muted);
  font-family: var(--font-sans);
  font-size: var(--text-sm);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
}

.quote-restart:hover {
  color: var(--card-text);
}

.quote-restart:focus-visible {
  outline: 2px solid var(--card-accent);
  outline-offset: 2px;
}

/* -------------------------------------------------------------------------
   FOOTER (.site-footer, data-theme="light")
   Hijo directo de <body>: su fondo de borde a borde lo pinta la MISMA regla
   compartida (parte `body > [data-theme]`), sin mecanismo propio. Todo el
   color sale del tema (--fg, --fg-muted, --line, --link, --focus): cambiar
   el data-theme del HTML lo recolorea entero.
   Bloques: marca (wordmark + dirección), navegación (Maquinaria, Obras,
   Cotizar), contacto (#contacto-datos: rótulo "Contacto" y tres filas
   Correo / WhatsApp / Teléfono, cada una con su rótulo y su enlace) y línea
   legal. Escritorio: 3 columnas con la marca más ancha; 700–899px: 2
   columnas (la marca ocupa las dos); <700px: 1 columna (cortes en
   responsive.css). La columna de contacto tiene un mínimo de 16rem (256px):
   el correo, en 1.05rem/600, mide 246px y no se debe partir a media palabra.
   Los enlaces son de texto sin clase: toman el color --link y el subrayado
   de la regla global de main.css (que cubre <main> y este footer).
   El "logo" del footer es texto (<p class="footer-logo">), no una imagen:
   no lleva contorno claro ni --logo-filter, y su color es --fg.
   ------------------------------------------------------------------------- */
.site-footer {
  padding-block: var(--space-7) var(--space-5);
  color: var(--fg);
}

.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) minmax(16rem, 1fr);
  gap: var(--space-6) var(--space-5);
}

.footer-logo {
  margin-bottom: var(--space-3);
  font-family: var(--font-logo);
  font-size: 1.875rem;
  font-weight: var(--font-weight-bold);
  letter-spacing: 0.04em;
  line-height: var(--leading-tight);
  color: var(--fg);
}

.site-footer address {
  font-style: normal;
  color: var(--fg-muted);
}

/* Rótulo pequeño (el "Contacto" del bloque y el de cada fila): Space Mono,
   mayúsculas, en --fg-muted. Mismo criterio tipográfico que .eyebrow. */
.footer-label,
.footer-contact-list dt {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-regular);
  letter-spacing: var(--letter-spacing-eyebrow);
  line-height: var(--leading-snug);
  text-transform: uppercase;
  color: var(--fg-muted);
}

.footer-label {
  margin-bottom: var(--space-4);
}

.footer-contact-list,
.footer-contact-list dd {
  margin: 0;
}

.footer-contact-list > div + div {
  margin-top: var(--space-2);
}

/* Enlaces: caja de 2rem en escritorio y de 44px hasta 899px (responsive.css).
   inline-flex para que la caja crezca sin perder el subrayado del texto.
   overflow-wrap:anywhere es solo red de seguridad en pantallas muy angostas. */
.footer-nav a,
.footer-contact a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* Valores de contacto: un punto más grandes que el resto del texto del footer. */
.footer-contact a {
  font-size: 1.05rem;
  line-height: var(--leading-snug);
}

.footer-nav a:focus-visible,
.footer-contact a:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.footer-legal {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
  font-size: var(--text-sm);
  color: var(--fg-muted);
}

/* -------------------------------------------------------------------------
   BOTÓN FLOTANTE DE WHATSAPP (a.whatsapp-float)
   Círculo verde fijo abajo a la derecha, visible SIEMPRE: sin animación de
   entrada, sin ocultarse al hacer scroll ni sobre ninguna sección. Sigue en
   su lugar del DOM (entre <main> y <footer>); position:fixed lo saca del
   flujo. Su color de marca (--color-whatsapp, main.css) no depende de los
   temas de sección, y como vive fuera de todo [data-theme] tampoco hereda
   ninguna variable de tema.

   Medidas en variables para que el botón y el espacio que se le reserva
   nunca se desincronicen: --wa-float-edge es la distancia al borde derecho
   (16px en móvil, 24px desde 900px, ver responsive.css) y --wa-float-bottom
   la distancia al borde inferior (en móvil, al menos 16px y nunca por
   debajo del área segura de un iPhone; 24px desde 900px). Las usan también
   el padding-bottom del Footer (abajo) y, con su valor redondeado (5.5rem),
   el scroll-padding-bottom de <html> (main.css).

   Capa: z-index 40 = por encima de todas las secciones (cada una es un
   contexto de apilamiento z-index:0) y POR DEBAJO del header (50), que
   contiene también el panel del menú móvil: con el menú abierto el panel
   tapa al botón, no al revés.

   Ícono: SVG inline de 28px, en blanco (fill fijo en el propio svg: el
   `a:hover` global cambia `color`, así que no se usa currentColor). Aro de
   2px en blanco cálido + sombra suave para que se lea sobre blanco,
   amarillo y negro. Contraste del ícono (#FFF) sobre #128C7E: 4.13:1.
   Foco (dos tonos): contorno blanco de 3px separado 2px del aro, con un
   halo oscuro (--color-black) de 8px por debajo: el blanco resalta sobre el
   halo oscuro y el halo sobre blanco y amarillo; sobre negro el contorno
   blanco resalta contra el fondo.
   ------------------------------------------------------------------------- */
:root {
  --wa-float-size: 56px;
  --wa-float-edge: 16px;
  --wa-float-bottom: max(var(--wa-float-edge), env(safe-area-inset-bottom, 0px));
}

.whatsapp-float {
  position: fixed;
  right: var(--wa-float-edge);
  bottom: var(--wa-float-bottom);
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--wa-float-size);
  height: var(--wa-float-size);
  border: 2px solid var(--color-white-warm);
  border-radius: var(--radius-full);
  background-color: var(--color-whatsapp);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.4);
  -webkit-tap-highlight-color: transparent;
  transition: filter var(--transition-fast);
}

.whatsapp-float svg {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  fill: #FFFFFF;
}

.whatsapp-float:hover {
  filter: brightness(1.08);
}

.whatsapp-float:focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 2px;
  box-shadow:
    0 0 0 8px var(--color-black),
    0 8px 22px rgba(0, 0, 0, 0.4);
}

/* Reserva al pie del Footer: la línea legal y los enlaces nunca quedan
   detrás del botón al llegar al final de la página. Alto del botón + su
   distancia al borde + 1rem (88px en móvil, 96px desde 900px). Va aparte
   del padding del resto del Footer, solo para el eje inferior. */
.site-footer {
  padding-bottom: calc(var(--wa-float-size) + var(--wa-float-bottom) + var(--space-4));
}

@media (prefers-reduced-motion: reduce) {
  .whatsapp-float {
    transition: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  #cotizacion,
  #cotizacion * {
    transition: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .machinery-tile-img,
  .machinery-tile-overlay {
    transition: none;
  }

  /* El crossfade en sí (no el avance automático, que js/obras-carousel.js
     ya desactiva por completo bajo esta preferencia) es la parte de
     movimiento — se anula igual, así el cambio manual (flechas/puntos)
     salta directo sin transición. */
  .obra-panel-slide {
    transition: none;
  }

  /* Red de seguridad, no la ruta principal: js/como-trabajamos.js ya
     comprueba esta preferencia y nunca agrega .is-armed si está activa
     (la curva se calcula igual — su "d" no depende de .is-armed — pero
     círculos/números/textos quedan directamente en su estado final).
     Esto cubre el caso borde de que el usuario cambie la preferencia del
     SO a mitad de la animación (.is-armed ya agregado, .is-visible aún
     no) — fuerza el estado final sin transición en vez de dejarla a
     medio dibujar. */
  .steps-wave-line {
    stroke-dashoffset: 0;
    transition: none;
  }

  .step-node,
  .step-content {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .steps-list::before {
    transform: translateX(-50%) scaleY(1);
    transition: none;
  }

  /* Sin movimiento: fila estática de los 14 logos reales (el segundo set,
     duplicado para el loop, se oculta por completo — mostrarlo aquí
     duplicaría visualmente cada logo, y ninguno de los 14 debe faltar ni
     repetirse). flex-wrap para que quepan en varias líneas en anchos
     angostos sin recortarse ni encimarse. */
  .allies-marquee {
    overflow: visible;
    -webkit-mask-image: none;
    mask-image: none;
  }

  .allies-track {
    animation: none;
    transform: none;
    width: 100%;
  }

  .allies-set:first-of-type {
    flex-wrap: wrap;
    justify-content: center;
    row-gap: var(--space-3);
    width: 100%;
  }

  .allies-set:last-of-type {
    display: none;
  }

  .primary-nav a::after {
    transition: none;
  }

  .menu-icon,
  .menu-icon::before,
  .menu-icon::after {
    transition: none;
  }

  /* El cambio de fondo (transparente↔sólido) es un color, no un movimiento:
     se queda instantáneo. El ocultado al hacer scroll sí es movimiento —
     js/header-scroll.js ya no lo activa bajo esta preferencia, y esta regla
     es la red de seguridad: el header nunca se transforma/oculta. */
  .site-header {
    transition: background-color 0s, border-color 0s;
  }

  .site-header--hidden {
    transform: none;
  }
}

/* =========================================================================
   CATÁLOGO COMPLETO (catalogocompleto.html, <html class="page-catalogo">)
   Banda de título (dark) + barra de búsqueda y filtros (light, sticky) +
   cuadrícula de 33 tarjetas (light) + cuadro de fechas (<dialog>). Reusa el
   header, el footer, el botón flotante, los temas (data-theme) y el fondo de
   borde a borde compartido: aquí no hay mecanismos nuevos, solo los estilos
   propios de la página. Los cortes de columnas y de la barra en móvil están
   en responsive.css.
   ========================================================================= */

/* Sin Hero no hay nada que subir bajo el header: #main-content empieza donde
   termina el header, que aquí es siempre sólido. */
.page-catalogo #main-content {
  margin-top: 0;
}

/* Header y barra son sticky: el desplazamiento por teclado o por anclas deja
   libre lo que tapan (alto del header + alto de la barra, medido por
   js/catalogo.js en --catalog-toolbar-height). */
.page-catalogo {
  scroll-padding-top: calc(
    var(--site-header-height, 73px) + var(--catalog-toolbar-height, 0px) + var(--space-4)
  );
}

/* Cuadro de fechas abierto: el fondo no se desplaza. Al quitar la barra de scroll
   la página ganaría su ancho (15px con barras clásicas) y todo se correría:
   js/catalogo.js mide ese ancho en --scrollbar-gap y aquí se reserva como padding.
   (scrollbar-gutter:stable no conserva el hueco en el elemento raíz.) */
.page-catalogo.has-dialog {
  overflow: hidden;
  padding-right: var(--scrollbar-gap, 0px);
}

/* Página actual en el header: enlace claro con la línea ámbar ya visible (la
   misma que el resto de enlaces muestran en hover/foco). */
.primary-nav a[aria-current="page"] {
  color: var(--color-text-inverse);
}

.primary-nav a[aria-current="page"]::after {
  width: 100%;
}

.mobile-nav a[aria-current="page"] {
  color: var(--color-amber);
}

/* --- Banda de título ---------------------------------------------------- */
.catalog-hero {
  color: var(--fg);
  padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(2rem, 5vw, 3rem);
}

.catalog-hero .eyebrow {
  color: var(--eyebrow);
}

.catalog-hero h1 {
  max-width: 51.25rem;
  margin-bottom: var(--space-4);
  font-size: var(--text-3xl);
  text-wrap: pretty;
}

.catalog-hero-lead {
  max-width: 40rem;
  color: var(--fg-muted);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
}

/* --- Barra de búsqueda y filtros ----------------------------------------
   Sticky bajo el header. Con el header visible, la barra queda justo debajo
   (top = alto medido del header, --site-header-height); cuando el header se
   oculta al bajar (.site-header--hidden), la barra sube a top:0 con la misma
   duración que el header, así nunca queda un hueco ni la tapa el header.
   La regla compartida de fondo de borde a borde pone position:relative y
   z-index:0 a todo [data-theme] hijo de <main>: aquí se sustituyen (misma
   especificidad, esta va después) por sticky y z-index:2 — sin z-index las
   secciones que vienen después, también z-index:0, pintarían encima. */
#main-content > .catalog-toolbar {
  /* Borde de controles (campo, filtros, recuadro de ayuda): 55% del color del
     texto sobre el fondo blanco = 3.95:1 (WCAG 1.4.11 pide 3:1). */
  --catalog-control-border: color-mix(in srgb, var(--fg) 55%, transparent);
  position: sticky;
  top: var(--site-header-height, 73px);
  z-index: 2;
  display: grid;
  grid-template-columns: minmax(0, 40rem) auto;
  grid-template-areas:
    "search count"
    "filters filters";
  align-items: center;
  gap: var(--space-2) var(--space-5);
  padding-block: var(--space-3);
  color: var(--fg);
  box-shadow: 0 1px 0 var(--border-on-light);
  transition: top var(--transition-base);
}

body:has(.site-header--hidden) #main-content > .catalog-toolbar {
  top: 0;
}

.catalog-search {
  grid-area: search;
  position: relative;
}

.catalog-search-icon {
  position: absolute;
  top: 50%;
  left: var(--space-4);
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  color: var(--fg-muted);
  pointer-events: none;
}

.catalog-search input {
  width: 100%;
  height: 3rem;
  padding: 0 3.25rem;
  border: 1.5px solid var(--catalog-control-border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--fg);
  font-size: var(--text-base);
  text-overflow: ellipsis;
  -webkit-appearance: none;
  appearance: none;
}

.catalog-search input::placeholder {
  color: var(--fg-muted);
  opacity: 1;
}

/* La × propia del campo reemplaza a la del navegador (evita dos a la vez). */
.catalog-search input::-webkit-search-cancel-button {
  display: none;
}

.catalog-search input:focus-visible {
  border-color: var(--fg);
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.catalog-search-clear {
  position: absolute;
  top: 50%;
  right: 0.25rem;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;
  color: var(--fg-muted);
}

.catalog-search-clear[hidden] {
  display: none;
}

.catalog-search-clear:hover {
  color: var(--fg);
}

.catalog-search-clear:focus-visible {
  color: var(--fg);
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.catalog-filters {
  grid-area: filters;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.catalog-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 2.75rem;
  padding: 0.4rem 1rem;
  border: 1.5px solid var(--catalog-control-border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--fg);
  font-size: 0.9375rem;
  font-weight: var(--font-weight-medium);
  line-height: 1.2;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}

.catalog-chip:hover {
  border-color: var(--fg);
}

.catalog-chip[aria-pressed="true"] {
  border-color: var(--fg);
  background: var(--fg);
  color: var(--color-text-inverse);
}

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

.catalog-chip-n {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.catalog-count {
  grid-area: count;
  min-height: 1.5em;
  color: var(--fg-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}

/* --- Cuadrícula -------------------------------------------------------- */
.catalog-list {
  --catalog-control-border: color-mix(in srgb, var(--fg) 55%, transparent);
  color: var(--fg);
  padding-block: var(--space-6) var(--space-section-y);
}

.catalog-note {
  margin-bottom: var(--space-5);
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.catalog-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-4);
}

.catalog-grid > li {
  min-width: 0;
}

.catalog-grid > li[hidden] {
  display: none;
}

/* Tarjeta = enlace. Todas del mismo alto en su fila (la <li> se estira y la
   tarjeta llena la <li>). Reposo: borde de 1.5px claro; hover y foco: borde
   negro + barra amarilla de 3px que se despliega bajo el nombre. */
.catalog-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1.5px solid color-mix(in srgb, var(--fg) 16%, transparent);
  background: var(--bg);
  color: var(--fg);
  transition: border-color 0.15s ease;
}

.catalog-card:hover,
.catalog-card:focus-visible {
  border-color: var(--fg);
  color: var(--fg);
}

.catalog-card:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 2px;
}

/* Marco 4:3 de la foto: object-fit cover (las fotos de 800px miden ~1.27:1). */
.catalog-card-media {
  display: block;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: color-mix(in srgb, var(--fg) 6%, var(--bg));
}

.catalog-card-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Sin foto: marco neutro con el ícono del tipo y "Imagen por confirmar". */
.catalog-card-media--empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  color: var(--fg-muted);
  text-align: center;
}

.catalog-card-icon {
  width: 2.75rem;
  height: 2.75rem;
}

.catalog-card-media-text {
  font-size: var(--text-sm);
}

.catalog-card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3) var(--space-4) var(--space-4);
  border-top: 1px solid var(--border-on-light);
}

.catalog-card-type {
  color: var(--eyebrow);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--letter-spacing-eyebrow);
  line-height: var(--leading-snug);
  text-transform: uppercase;
  overflow-wrap: anywhere;
}

.catalog-card-name {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: var(--font-weight-black);
  line-height: 1.15;
  overflow-wrap: anywhere;
  hyphens: auto;
}

.catalog-card-bar {
  display: block;
  height: 3px;
  margin-top: var(--space-2);
  background: var(--color-brand-yellow);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s ease;
}

.catalog-card:hover .catalog-card-bar,
.catalog-card:focus-visible .catalog-card-bar {
  transform: scaleX(1);
}

.catalog-card-cta {
  margin-top: auto;
  padding-top: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-bold);
}

/* --- Sin resultados y ayuda ---------------------------------------------- */
.catalog-empty {
  max-width: 30rem;
  margin: var(--space-7) auto 0;
  text-align: center;
}

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

.catalog-empty h2 {
  margin-bottom: var(--space-2);
  font-size: var(--text-xl);
}

.catalog-empty p {
  margin-bottom: var(--space-5);
  color: var(--fg-muted);
}

/* "Escribir por WhatsApp" (sin resultados): botón de Nivel 3 (.btn-tier-whatsapp
   en el HTML); su verde, forma, hover y foco salen del sistema de botones. */

.catalog-help {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-7);
  padding: var(--space-5);
  border: 1.5px dashed var(--catalog-control-border);
}

.catalog-help p {
  max-width: 34rem;
}

/* .btn:focus-visible usa el ámbar, que no se lee sobre blanco: aquí el foco es el del tema. */
.catalog-list .btn:focus-visible,
.date-dialog .btn:focus-visible {
  outline-color: var(--focus);
}

/* --- Cuadro de fechas (<dialog> modal) -------------------------------------
   El contenedor interno (.date-dialog-inner) lleva el tema light, llena todo el
   cuadro y es quien se desplaza si no cabe: así un clic en el fondo (::backdrop)
   es el único que tiene al <dialog> como destino, y la barra de scroll no cuenta
   como "fondo". */
.date-dialog {
  width: min(46rem, calc(100vw - 2rem));
  max-width: none;
  max-height: none;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  overflow: visible;
}

.date-dialog::backdrop {
  background: rgba(21, 24, 28, 0.7);
  background: color-mix(in srgb, var(--color-black) 70%, transparent);
}

.date-dialog-inner {
  max-height: calc(100vh - 2rem);
  max-height: calc(100dvh - 2rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg);
  color: var(--fg);
}

.date-dialog-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5) 0;
}

.date-dialog-top .eyebrow {
  margin-bottom: 0;
  color: var(--eyebrow);
}

.date-dialog-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 2.75rem;
  height: 2.75rem;
  border: 1.5px solid color-mix(in srgb, var(--fg) 55%, transparent);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--fg);
}

.date-dialog-close:hover {
  border-color: var(--fg);
}

.date-dialog-close:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.date-dialog-body {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5) var(--space-5);
}

.date-dialog-media .catalog-card-media {
  border: 1.5px solid color-mix(in srgb, var(--fg) 16%, transparent);
}

.date-dialog-ref {
  margin-top: var(--space-2);
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.date-dialog-form h2 {
  margin-bottom: var(--space-2);
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  overflow-wrap: anywhere;
}

.date-dialog-lead {
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.date-dialog-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.date-dialog-field label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-bold);
}

.date-dialog-field input {
  width: 100%;
  height: 3rem;
  padding: 0 var(--space-3);
  border: 1.5px solid color-mix(in srgb, var(--fg) 55%, transparent);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--fg);
  font-size: var(--text-base);
}

.date-dialog-field input:focus-visible {
  border-color: var(--fg);
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.date-dialog-field input[aria-invalid="true"] {
  border-color: #B3261E;
}

/* Mensaje de error: región role="alert" siempre presente (vacía y sin caja); al
   llenarla se anuncia. Texto negro sobre fondo rojizo muy claro y borde rojo de
   3px: el aviso no depende solo del color. */
.date-dialog-error {
  margin-top: var(--space-3);
  font-size: var(--text-sm);
}

.date-dialog-error:not(:empty) {
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid #B3261E;
  background: #FDECEA;
  color: var(--color-black);
}

.date-dialog-note {
  margin-top: var(--space-3);
  color: var(--fg-muted);
  font-size: var(--text-sm);
}

.date-dialog-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.date-dialog-actions .btn {
  white-space: normal;
}

@media (prefers-reduced-motion: reduce) {
  #main-content > .catalog-toolbar,
  .catalog-card,
  .catalog-card-bar {
    transition: none;
  }
}

/* =========================================================================
   SISTEMA DE BOTONES POR NIVELES
   Un solo mecanismo compartido: cualquier botón nuevo entra a la familia con
   UNA clase de nivel junto a .btn (y, si aplica, .btn-large). No copies el
   corte de esquina en cada botón: cambia --btn-notch (main.css) o esta regla.

     .btn-tier-primary    Nivel 1 · Principal. Esquina cortada + relleno de
                          reposo del contexto (--btn-bg / --card-accent) +
                          barrido de color al hover.
     .btn-tier-secondary  Nivel 2 · Secundario. Esquina cortada + borde de
                          1.5px, sin relleno; borde y texto en --link (el
                          acento de cada tema); al hover, tinte leve.
     .btn-tier-whatsapp   Nivel 3 · WhatsApp. Esquina cortada + verde de marca
                          (--color-whatsapp-btn); al hover, el mismo verde más
                          oscuro (--color-whatsapp-btn-hover).
     (Nivel 4 · Utilitario: sin clase de nivel; "Atrás", "Enviar otra
     solicitud" y las píldoras de filtro no llevan esquina ni animación.)

   Barrido de Nivel 1: el color sale de --btn-sweep-bg / --btn-sweep-fg (tema
   de la sección, ver main.css): claro sobre fondos oscuros, negro sobre
   fondos claros. Un barrido fijo a negro se fundiría con Header, Obras y la
   tarjeta del formulario (todos oscuros). Fuera de un tema (Header) cae al
   claro. Con la tarjeta del formulario, que es oscura en cualquier tema, se
   fija más abajo.

   Foco de teclado: clip-path recorta todo lo que sale de la caja, incluido
   el outline, así que el anillo de foco es un pseudo-elemento (::after) con
   la MISMA silueta que el botón, dibujado dentro del borde (3px). Va en
   --btn-sweep-bg: contrasta con el fondo de la sección, que es donde termina
   la silueta. El borde de Nivel 2 (::before) se dibuja igual (1.5px), para
   que la esquina cortada también lleve línea; un border normal la dejaría
   abierta.

   Va al FINAL de components.css a propósito: empata en especificidad con
   reglas de contexto anteriores (.quote-form .btn:focus-visible,
   .btn-primary:hover, etc.) y gana por orden.
   ========================================================================= */
.btn-tier-primary,
.btn-tier-secondary,
.btn-tier-whatsapp {
  --notch: var(--btn-notch, 12px);
  position: relative;
  isolation: isolate;
  border-color: transparent;
  border-radius: 0;
  clip-path: polygon(
    0 0,
    calc(100% - var(--notch)) 0,
    100% var(--notch),
    100% 100%,
    var(--notch) 100%,
    0 calc(100% - var(--notch))
  );
}

/* Anillo con la silueta del botón: contorno exterior (con las dos esquinas
   cortadas) y contorno interior a --t de distancia, unidos por un tramo de
   ancho cero que recorre A6→I6 de ida y vuelta. 0.414 = √2 − 1: el corte a
   45° del contorno interior se acorta esa fracción de --t para que la banda
   del corte mida lo mismo que la de los lados. */
.btn-tier-primary::after,
.btn-tier-secondary::after,
.btn-tier-whatsapp::after,
.btn-tier-secondary::before {
  content: "";
  position: absolute;
  inset: -1px; /* cubre también el borde transparente de 1px de .btn */
  z-index: 1;
  pointer-events: none;
  background: currentColor;
  clip-path: polygon(
    0 0,
    calc(100% - var(--notch)) 0,
    100% var(--notch),
    100% 100%,
    var(--notch) 100%,
    0 calc(100% - var(--notch)),
    var(--t) calc(100% - var(--notch) - var(--t) * 0.414),
    calc(var(--notch) + var(--t) * 0.414) calc(100% - var(--t)),
    calc(100% - var(--t)) calc(100% - var(--t)),
    calc(100% - var(--t)) calc(var(--notch) + var(--t) * 0.414),
    calc(100% - var(--notch) - var(--t) * 0.414) var(--t),
    var(--t) var(--t),
    var(--t) calc(100% - var(--notch) - var(--t) * 0.414),
    0 calc(100% - var(--notch))
  );
}

.btn-tier-primary::after,
.btn-tier-secondary::after,
.btn-tier-whatsapp::after {
  --t: 3px;
  display: none;
  background: var(--btn-sweep-bg, var(--color-white-warm));
}

.btn-tier-secondary::before {
  --t: 1.5px;
}

.btn.btn-tier-primary:focus-visible,
.btn.btn-tier-secondary:focus-visible,
.btn.btn-tier-whatsapp:focus-visible {
  outline: none;
}

.btn-tier-primary:focus-visible::after,
.btn-tier-secondary:focus-visible::after,
.btn-tier-whatsapp:focus-visible::after {
  display: block;
}

/* --- Nivel 1 · Principal --- */
.btn.btn-tier-primary {
  --tier-bg: var(--btn-bg, var(--color-amber));
  --tier-fg: var(--btn-fg, var(--color-black));
  background-color: var(--tier-bg);
  color: var(--tier-fg);
}

.btn-tier-primary::before {
  content: "";
  position: absolute;
  inset: -2px; /* borde de 1px de .btn + 1px de margen: el barrido usa transform y se dibuja en su propia capa (posición fraccionaria); si quedara justo al ras, dejaría una línea de amarillo en el borde. El clip-path del botón lo recorta a la silueta */
  z-index: -1;
  pointer-events: none;
  background: var(--btn-sweep-bg, var(--color-white-warm));
  transform: translateY(101%);
  transition: transform 0.22s ease;
}

.btn.btn-tier-primary:hover,
.btn.btn-tier-primary:active {
  background-color: var(--tier-bg);
  color: var(--btn-sweep-fg, var(--color-black));
}

.btn-tier-primary:hover::before,
.btn-tier-primary:active::before {
  transform: translateY(0);
}

/* La tarjeta del formulario es oscura en cualquier tema (--card-bg): el
   reposo sale de --card-accent y el barrido siempre es claro. */
.quote-form .btn.btn-tier-primary {
  --tier-bg: var(--card-accent);
  --tier-fg: var(--color-black);
  --btn-sweep-bg: var(--color-white-warm);
  --btn-sweep-fg: var(--color-black);
}

/* --- Nivel 2 · Secundario --- */
.btn.btn-tier-secondary {
  background-color: transparent;
  color: var(--link, var(--color-amber-darker));
}

.btn.btn-tier-secondary:hover,
.btn.btn-tier-secondary:active {
  background-color: color-mix(in srgb, currentColor var(--btn-tint, 5%), transparent);
  color: var(--link, var(--color-amber-darker));
}

/* --- Nivel 3 · WhatsApp --- */
.btn.btn-tier-whatsapp {
  background-color: var(--color-whatsapp-btn);
  color: #FFFFFF;
}

.btn.btn-tier-whatsapp:hover,
.btn.btn-tier-whatsapp:active {
  background-color: var(--color-whatsapp-btn-hover);
  color: #FFFFFF;
}

@media (prefers-reduced-motion: reduce) {
  .btn-tier-primary,
  .btn-tier-secondary,
  .btn-tier-whatsapp,
  .btn-tier-primary::before {
    transition: none;
  }
}

/* Modo de alto contraste de Windows: los fondos se anulan y el anillo (un
   fondo) desaparecería; se vuelve a un botón rectangular con borde y outline
   del sistema. */
@media (forced-colors: active) {
  .btn-tier-primary,
  .btn-tier-secondary,
  .btn-tier-whatsapp {
    clip-path: none;
    border-color: ButtonText;
  }

  .btn-tier-primary::before,
  .btn-tier-primary::after,
  .btn-tier-secondary::before,
  .btn-tier-secondary::after,
  .btn-tier-whatsapp::after {
    display: none;
  }

  .btn.btn-tier-primary:focus-visible,
  .btn.btn-tier-secondary:focus-visible,
  .btn.btn-tier-whatsapp:focus-visible {
    outline: 2px solid Highlight;
    outline-offset: 2px;
  }
}
