/* =============================================================
   LAYOUT — header, rail de índice, hero, secciones, footer
   Mobile-first: base = smartphone (320px+), luego se escala.
============================================================= */

/* ---- Header ---- */
.site-header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  /* z-index elevado por encima de #mobile-menu (60): el boton hamburguesa
     debe seguir visible/clicable ENCIMA del panel a pantalla completa
     para poder cerrarlo tocandolo de nuevo. */
  z-index: 70;
  padding-block: var(--space-4);
  background: linear-gradient(to bottom, rgba(6, 7, 12, 0.92), rgba(6, 7, 12, 0));
  backdrop-filter: blur(6px);
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.brand-mark {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: var(--tracking-wider);
  color: var(--color-ink);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 6px 10px;
}

.header-nav {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  font-size: var(--fs-small);
}

.header-nav a:not(.nav-cta) {
  display: none;
}

.header-nav .nav-cta {
  font-family: var(--font-mono);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-bg);
  background: var(--color-ink);
  padding: 9px 16px;
  border-radius: var(--radius-sm);
  transition: background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

@media (hover: hover) {
  .header-nav .nav-cta:hover {
    background: var(--color-cyan);
    color: #041018;
  }
}

@media (min-width: 640px) {
  .header-nav a:not(.nav-cta) {
    display: inline;
    color: var(--color-ink-muted);
    transition: color var(--dur-fast) var(--ease-out);
  }

  @media (hover: hover) {
    .header-nav a:not(.nav-cta):hover { color: var(--color-cyan); }
  }
}

/* ---- Menú móvil (hamburguesa) — reemplaza al rail de índice bajo 1100px ----
   El rail lateral (`.index-rail`) solo cabe desde 1100px; por debajo de
   eso el header solo mostraba el CTA "Contacto", sin forma de saltar a
   Servicios/Proyectos/Clientes salvo scrollear manualmente. */
.menu-toggle {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 44px;  /* touch target minimo (Apple HIG / Material) */
  height: 44px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--color-border-strong);
  background: transparent;
  flex-shrink: 0;
}

.menu-toggle span {
  display: block;
  width: 18px;
  height: 2px;
  background: var(--color-ink);
  transition: transform var(--dur-fast) var(--ease-out), opacity var(--dur-fast) var(--ease-out);
}

/* X al abrir — misma metafora universal de "cerrar" */
.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (max-width: 1099px) {
  .menu-toggle { display: flex; }
  /* En mobile se ocultan los links de texto del header para no
     duplicar la navegación del panel completo, PERO se mantiene
     visible "Servicios" junto al CTA "Contacto" — el usuario reportó
     que Servicios quedaba "detrás"/oculto por Contacto. */
  .header-nav a:not(.nav-cta) { display: none !important; }
  .header-nav a[href="#servicios"]:not(.nav-cta) {
    display: inline !important;
  }
}

.mobile-menu {
  position: fixed;
  inset: 0;
  z-index: 60; /* debajo del header (70) para que el boton siga clicable encima */
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--color-bg-elevated);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out);
}

.mobile-menu[data-state="open"] {
  opacity: 1;
  visibility: visible;
}

.mobile-menu ol {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  padding-inline: var(--container-pad);
}

.mobile-menu a {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-4);
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 7vw, 2.75rem);
  font-weight: 700; /* ronda 3: 500→700 — Montserrat solo tiene 400/700 cargados localmente */
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
  transition: color var(--dur-fast) var(--ease-out);
}

.mobile-menu .index-num {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  letter-spacing: var(--tracking-wide);
  color: var(--color-signal);
}

@media (hover: hover) {
  .mobile-menu a:hover { color: var(--color-cyan); }
}

@media (min-width: 1100px) {
  /* El rail lateral toma el relevo — el panel movil nunca debe mostrarse
     en desktop, incluso si quedo abierto por accidente al redimensionar. */
  .menu-toggle,
  .mobile-menu { display: none !important; }
}

/* ---- Rail de índice — navegación editorial fija (signature element) ---- */
.index-rail {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  z-index: 30;
  display: none;
}

@media (min-width: 1100px) {
  .index-rail {
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: var(--rail-width);
    padding-left: var(--space-6);
  }

  .index-rail ol {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    position: relative;
    padding-left: var(--space-5);
  }

  .index-rail__track {
    position: absolute;
    left: 0;
    top: 2px;
    bottom: 2px;
    width: 1px;
    background: var(--color-border);
  }

  .index-rail__fill {
    position: absolute;
    left: 0;
    top: 0;
    width: 1px;
    height: 0%;
    background: var(--color-signal);
    transition: height var(--dur-fast) linear;
  }

  .index-rail a {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
  }

  .index-num {
    font-family: var(--font-mono);
    font-size: var(--fs-mono);
    color: var(--color-ink-faint);
    transition: color var(--dur-base) var(--ease-out);
  }

  .index-label {
    font-family: var(--font-mono);
    font-size: 0.7rem;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--color-ink-faint);
    transition: color var(--dur-base) var(--ease-out), letter-spacing var(--dur-base) var(--ease-out);
  }

  .index-rail li.is-active .index-num,
  .index-rail li.is-active .index-label {
    color: var(--color-ink);
  }

  .index-rail li.is-active .index-num {
    color: var(--color-signal);
  }

  @media (hover: hover) {
    .index-rail a:hover .index-label {
      /* Indirección (--color-focus), no --color-cyan directo: el cian puro
         pierde visibilidad sobre fondo claro (~1.8:1, ver tokens.css). Antes
         estaba hardcodeado y quedaba casi invisible en hover sobre
         #servicios/#clientes — mismo bug de raíz que .is-light abajo. */
      color: var(--color-focus);
      letter-spacing: 0.22em;
    }
  }
}

/* ---- Rail sobre secciones de fondo claro (fix de contraste, 2026-07-29) ----
   Causa raíz: .index-rail es position:fixed y vive en el DOM como hermano
   de <main> (ver index.html), FUERA del subárbol de #servicios/#clientes.
   Los overrides "on-light" de arriba (--color-ink-faint, --color-signal,
   etc) son custom properties que heredan por el árbol DOM — el rail nunca
   los recibe aunque flote visualmente sobre esas secciones al hacer scroll.
   Resultado sin este fix: --color-ink-faint (oscuro, pensado para fondo
   oscuro) casi desaparece sobre el crema #f0ece3.
   Fix: reveal.js (indexRailAndProgress) ya sabe qué sección está activa
   vía IntersectionObserver — togglea esta clase y aquí se sobreescriben
   LOCALMENTE los mismos tokens que ya usa el bloque `#servicios, #clientes`
   (mismos valores verificados AA arriba, no colores nuevos). */
.index-rail.is-light {
  --color-ink:       var(--color-ink-on-light);
  --color-ink-faint: var(--color-ink-on-light-muted);
  --color-signal:    var(--color-accent-orange-deep);
  --color-focus:     var(--color-accent-orange-deep);
  --color-border:    rgba(28, 26, 21, 0.14);
}

/* ---- Barra de progreso — equivalente móvil del rail ----
   Revisado el mismo bug de raíz (2026-07-29): #scroll-progress TAMBIÉN es
   position:fixed y hermano de <main>, fuera del subárbol de las secciones
   claras — en teoría el mismo problema de herencia de custom properties.
   En la práctica NO le afecta: a diferencia del rail (texto sobre fondo
   variable durante todo el alto de la página), esta barra es un color
   sólido de 2px de alto que vive siempre en y=0, bajo el mismo z-index
   que .site-header (70 > 45 de esta barra) — su gradiente casi opaco
   (rgba(6,7,12,0.92) en el tramo superior) la cubre en todo momento,
   independiente de qué sección esté detrás. Y por breakpoint nunca
   coexiste con el bug del rail: esta barra solo es visible bajo 1100px,
   el rail solo desde 1100px (ver media queries abajo/arriba). Se deja
   sin cambios — no es un parche omitido, es una verificación registrada. */
#scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  z-index: 45;
  background: rgba(148, 163, 184, 0.08);
}

#scroll-progress-bar {
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--color-cyan), var(--color-signal));
  transition: width var(--dur-fast) linear;
}

@media (min-width: 1100px) {
  #scroll-progress { display: none; }
}

/* ---- Secciones genéricas ---- */
main {
  position: relative;
  z-index: 1;
}

section {
  position: relative;
  padding-block: var(--space-9);
}

@media (min-width: 1100px) {
  section { padding-left: var(--rail-width); }
}

/* ---- Ritmo de fondo intercalado por sección (ronda 3, feedback explícito) ----
   Referencias verificadas por el coordinador navegando los sitios reales
   (no descripción genérica): aiagency-599.pages.dev intercala tramos
   negros con una franja clara tipo hueso/crema para su bloque de
   metodología; evasanchez.info es un portfolio ENTERO de fondo claro con
   guías de grilla sutiles de fondo. Se aplica aquí la misma TÉCNICA de
   contraste de fondo (nunca su contenido/copy), como un RITMO alternado
   a lo largo de toda la página, no un único parche aislado:

     Inicio (oscuro, canvas) → Servicios (CLARO) → Proyectos (oscuro)
       → Clientes (CLARO) → Contacto (oscuro)

   "Servicios" y "Clientes" comparten el mismo patrón visual de "sección
   clara" (mismos tokens, mismas guías de grilla) — es un componente de
   sistema reutilizado a propósito, no dos tratamientos distintos.
   "Proyectos" y "Contacto" mantienen el degradado radial naranja sutil
   sobre fondo oscuro ya validado en la ronda 2.

   Estrategia de implementación: en vez de reescribir cada componente
   hijo (service-card, client-badge, section-tag, case-note, h2), se
   sobreescriben las custom properties de color A NIVEL DE SECCIÓN. Como
   las custom properties heredan por el árbol DOM, todo lo que vive
   dentro de #servicios/#clientes hereda automáticamente los tonos "on
   light" sin tocar components.css. Contraste verificado manualmente
   (fórmula de luminancia relativa WCAG):
     ink-on-light (#1c1a15) sobre #f0ece3          ≈ 15.8:1
     ink-on-light-muted (#6b6255) sobre #f0ece3    ≈ 5.08:1 (AA)
     ink-on-light-muted sobre card/badge bg (#eae4d6) ≈ 4.73:1 (AA)
     accent-orange-deep (#a34600) sobre #f0ece3    ≈ 5.18:1 (AA) — el
       naranja corporativo puro (#ff6a03) da solo ~2.4:1 aquí, por eso
       la variante oscurecida (ver tokens.css). */
#servicios,
#clientes {
  background-color: var(--color-surface-light);
  /* Guías de grilla verticales sutiles (5% opacidad) — referencia
     evasanchez.info: grilla de fondo tipo "estudio de diseño mostrando
     su retícula", puramente decorativa. Se combina con las etiquetas
     monoespaciadas ya existentes (section-tag, ghost-num de proyectos,
     numeración 01–04 del rail) sin clonar el reloj en vivo ni ningún
     otro elemento literal de esa referencia. No afecta el contraste
     del texto — es una capa de fondo detrás del contenido. */
  background-image: repeating-linear-gradient(
    to right,
    rgba(28, 26, 21, 0.05) 0,
    rgba(28, 26, 21, 0.05) 1px,
    transparent 1px,
    transparent 120px
  );

  --color-ink:           var(--color-ink-on-light);
  --color-ink-muted:     var(--color-ink-on-light);       /* hover "enciende" a tinta casi negra */
  --color-ink-faint:     var(--color-ink-on-light-muted);  /* estado base, AA verificado arriba */
  --color-bg-elevated:   #eae4d6;  /* superficie de service-card/client-badge — AA verificado */
  --color-surface-2:     #e6e0d1;  /* hover de service-card/client-badge — AA verificado */
  --color-border:        rgba(28, 26, 21, 0.14);
  --color-border-strong: rgba(28, 26, 21, 0.28);
  --color-signal:        var(--color-accent-orange-deep); /* naranja legible sobre claro */
  --color-focus:         var(--color-accent-orange-deep); /* anillo de foco visible sobre crema */
  color: var(--color-ink);
}

/* Transición de borde suave hacia/desde las secciones oscuras vecinas —
   evita que el salto de color se sienta como un tema roto y refuerza que
   es un respiro editorial deliberado. */
#servicios::before,
#servicios::after,
#clientes::before,
#clientes::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  height: 140px;
  z-index: -1;
  pointer-events: none;
}
#servicios::before,
#clientes::before { top: 0;    background: linear-gradient(to bottom, var(--color-bg) 0%, transparent 100%); }
#servicios::after,
#clientes::after  { bottom: 0; background: linear-gradient(to top,    var(--color-bg) 0%, transparent 100%); }

/* Variantes oscurecidas de cian/azul SOLO para uso como texto/borde de
   acento dentro de #clientes (logo wall, ronda 8). Mismo problema y
   misma solución que --color-accent-orange-deep en el bloque de arriba:
   --color-cyan (#38bdf8) y --color-blue (#60a5fa) dan ~1.8:1 y ~2.2:1
   sobre --color-surface-light (#f0ece3) — muy por debajo de AA. Se
   oscurecen hasta cruzar ~5:1 (verificado con la fórmula de luminancia
   relativa WCAG), igual que ya se hizo con el naranja en ronda 3.
   --color-blue-deep (#3957a2, ya existente, usado en Proyectos para
   Comuniads) no necesitó variante propia: ya da ~5.4:1 aquí tal cual. */
#clientes {
  --accent-cyan-onlight: #1e6788; /* ~5.3:1 sobre surface-light, ~4.9:1 sobre bg-elevated */
  --accent-blue-onlight: #396396; /* ~5.2:1 sobre surface-light, ~4.9:1 sobre bg-elevated */
}

#proyectos {
  background-image:
    radial-gradient(ellipse 720px 480px at 90% 0%, rgba(255, 106, 3, 0.05), transparent 62%),
    linear-gradient(180deg, rgba(6, 7, 12, 0) 0%, rgba(6, 7, 12, 0.55) 100%);
}

#contacto {
  background-image: radial-gradient(ellipse 780px 520px at 50% 110%, rgba(255, 106, 3, 0.065), transparent 60%);
}

.section-head {
  margin-bottom: var(--space-7);
  max-width: 46rem;
}

.section-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--color-signal);
  margin-bottom: var(--space-4);
}

.section-head h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 700; /* ronda 3: 500→700 — Montserrat solo tiene 400/700 cargados localmente */
  letter-spacing: var(--tracking-tight);
  color: var(--color-ink);
}

/* ---- Proyectos — cabecera editorial tipo "section-intro" ----
   Ronda 6 (2026-07-29): adaptación 1:1 del bloque "Evidence, not
   theatre." de aiagency-599.pages.dev (`.section-intro`, grid de 12
   columnas: kicker en 1/4, h2 en 4/11, lede en 8/12 alineada al final).
   Solo #proyectos: el resto de secciones sigue usando `.section-head`
   apilado tal cual (no se toca su comportamiento por defecto). El H2
   sube de escala considerablemente respecto al resto del sitio (más
   cerca del `heading-xl` clamp(2.8rem,6vw,6.4rem) de la referencia) y
   `.case-note` pasa de "nota al pie" (uso en #servicios/#clientes para
   el disclaimer de contenido placeholder) a "lede" real, porque aquí
   el texto ya no es un disclaimer sino la bajada editorial de la
   sección (ver copy en index.html, ronda 5). */
@media (min-width: 900px) {
  #proyectos .section-head {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: var(--space-5);
    align-items: start;
    max-width: none;
  }

  #proyectos .section-head .section-tag {
    grid-column: 1 / 4;
    margin-bottom: 0;
    align-self: start;
    margin-top: 0.6em;
  }

  #proyectos .section-head h2 {
    grid-column: 4 / 11;
    font-size: clamp(2.4rem, 1.2vw + 3.6rem, 5.25rem);
    line-height: 0.96;
  }

  #proyectos .section-head .case-note {
    grid-column: 8 / 13;
    align-self: end;
  }
}

#proyectos .case-note {
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.3vw + 0.9rem, 1.2rem);
  line-height: var(--lh-normal);
  color: var(--color-ink-muted);
  margin-top: var(--space-4);
}

/* ---- Hero ---- */
.hero {
  position: relative; /* contenedor de posicionamiento explícito para #signal-canvas —
                          antes dependía por coincidencia de que .site-header fuera
                          position:fixed y .hero fuera la primera sección del documento */
  min-height: 100dvh;
  display: flex;
  align-items: center;
  padding-top: calc(var(--space-9) + 3rem);
  padding-bottom: var(--space-8);
  overflow: hidden;
}

#signal-canvas {
  position: absolute;
  inset: 0;
  z-index: 0;
  opacity: 0.85;
  transition: transform 60ms linear; /* suaviza el parallax cinético (hero.js) */
}

.hero__inner {
  position: relative;
  z-index: 1;
  /* Deja pasar el mouse al canvas de fondo en TODA el area de texto —
     detectado en verificacion de ronda 3: los parrafos (h1, hero__value,
     hero__role, etc.) son cajas de bloque que cubren su ancho completo
     aunque el texto visible sea mas corto, y sin esto interceptaban el
     mousemove antes de que llegara a #signal-canvas, dejando el efecto
     de color/fuerza del canvas "muerto" en buena parte del hero. */
  pointer-events: none;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--color-ink-muted);
  border: 1px solid var(--color-border-strong);
  background: rgba(56, 189, 248, 0.05);
  padding: 7px 14px;
  border-radius: 999px;
  margin-bottom: var(--space-6);
}

.eyebrow-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--color-signal);
  box-shadow: 0 0 10px 2px rgba(255, 106, 3, 0.55);
  flex-shrink: 0;
  animation: pulse-dot 2.2s ease-in-out infinite;
}

.hero__name {
  /* Ronda 3 (feedback explícito del cliente): el titular pasa de Fraunces
     (serif editorial, rondas 1-2) a Montserrat 700 (sans bold grotesca)
     — es el rol que el propio cliente pidió para Montserrat ("para
     titulares de gran escala", ref. richardekwonye.com). Tras feedback
     adicional ("usa Montserrat en todas las secciones"), Fraunces se
     retiró del sitio por completo — H2/H3 también usan Montserrat
     (ver --font-display en tokens.css, ahora alias de --font-body). */
  font-family: var(--font-body);
  font-size: var(--fs-hero);
  font-weight: 700; /* única cara bold cargada localmente — sin sintetizado */
  line-height: 0.95; /* Montserrat Bold tiene ascendentes/descendentes más
                         altos que Fraunces; 0.9 colisionaba entre líneas */
  letter-spacing: -0.02em; /* grotesca bold necesita menos tracking negativo
                               que la serif — a -0.05em las letras se tocaban */
  color: var(--color-ink);
  max-width: 22ch;
  margin-bottom: var(--space-6);
  will-change: transform;
}

.hero__name .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  /* Margen de seguridad horizontal: -webkit-text-stroke (usado en
     .word--outline) pinta el contorno del glifo un poco MAS ALLA de
     su caja metrica normal (el navegador no lo cuenta en el ancho de
     layout), y ese sliver quedaba recortado por este overflow:hidden
     — reportado como "la 's' final de Morales se ve cortada". El
     padding empuja el borde de recorte hacia afuera; el margin
     negativo compensa para no desplazar el bloque de texto. */
  padding-inline: 0.06em;
  margin-inline: -0.06em;
}

.hero__name .word > span {
  display: inline-block;
  transform: translateY(112%) scale(1.04);
  transition: transform var(--dur-slow) var(--ease-out);
}

.hero__name.is-revealed .word > span {
  transform: translateY(0) scale(1);
}

/* ---- Segunda palabra en contorno — contraste tipográfico editorial
     (relleno sólido + contorno) tomado del lenguaje de las referencias
     Awwwards indicadas (tipografía cinética de gran escala) ---- */
.hero__name .word--outline > span {
  color: transparent;
  -webkit-text-stroke: 1.8px var(--color-cyan);
  text-shadow: 0 0 56px rgba(56, 189, 248, 0.24);
}

@supports not (-webkit-text-stroke: 1px black) {
  .hero__name .word--outline > span {
    color: var(--color-cyan);
    -webkit-text-stroke: 0;
  }
}

.hero__role {
  font-family: var(--font-mono);
  font-size: clamp(0.85rem, 0.5vw + 0.7rem, 1.05rem);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-cyan);
  margin-bottom: var(--space-5);
}

.hero__value {
  font-size: var(--fs-lead);
  line-height: var(--lh-normal);
  color: var(--color-ink-muted);
  max-width: 42ch;
  margin-bottom: var(--space-6);
}

.hero__ticker {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  color: var(--color-ink-faint);
  min-height: 1.4em;
  margin-bottom: var(--space-7);
}

.ticker-index {
  color: var(--color-signal);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  pointer-events: auto; /* excepcion al pointer-events:none de .hero__inner — los CTAs deben seguir siendo clicables */
}

.scroll-cue {
  position: absolute;
  bottom: var(--space-5);
  left: var(--container-pad);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-faint);
}

@media (min-width: 1100px) {
  .scroll-cue { left: calc(var(--rail-width) + var(--container-pad)); }
}

.scroll-cue__line {
  width: 32px;
  height: 1px;
  background: var(--color-ink-faint);
  position: relative;
  overflow: hidden;
}

.scroll-cue__line::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--color-cyan);
  animation: cue-sweep 2.4s ease-in-out infinite;
}

/* ---- Etiqueta vertical editorial — detalle estructural, solo desktop amplio ---- */
.hero-marquee {
  display: none;
  pointer-events: none; /* decorativa (aria-hidden) — no debe bloquear el mousemove del canvas de fondo */
}

@media (min-width: 1280px) {
  .hero-marquee {
    display: flex;
    align-items: center;
    position: absolute;
    right: var(--container-pad);
    top: 50%;
    transform: translateY(-50%);
    z-index: 1;
    height: 60%;
    writing-mode: vertical-rl;
    text-orientation: mixed;
    font-family: var(--font-mono);
    font-size: 0.72rem;
    letter-spacing: var(--tracking-wider);
    text-transform: uppercase;
    color: var(--color-ink-faint);
    opacity: 0.55;
    white-space: nowrap;
  }
}

/* ---- Diagrama de servicios (4 pilares conectados, refactor 2026-07-29) ----
   Reemplaza el grid de hairlines anterior (4 tarjetas planas a un grid de
   1px de gap, sin jerarquía ni metáfora de flujo — ver git history) por
   un diagrama real: columna vertical en mobile/tablet (cada nodo empuja
   al siguiente hacia abajo) que pasa a fila horizontal desde 1100px
   (mismo breakpoint que el rail de índice — "escritorio"). Se dejó fuera
   a propósito el intermedio de 2 columnas del grid anterior (700px):
   con nodos+flechas conectados, un 2x2 obliga a que la flecha 2→3 "salte"
   de fila, algo que no se resuelve con CSS simple sin medir en JS. Dos
   estados (vertical/horizontal) son más robustos y legibles que tres.
   Estilos de cada nodo (.service-card) y su flecha conectora
   (.services-diagram__arrow) viven en components.css — aquí solo la
   estructura del contenedor y el arco de ciclo. */
.services-diagram {
  position: relative;
  /* z-index:0 (no solo position:relative) — establece un stacking
     context PROPIO. Fix (2026-07-29 ronda 4, encontrado al verificar la
     espina vertical mobile en navegador headless: no se veía en
     absoluto pese a que sus estilos computados eran correctos): sin
     z-index explícito, position:relative NO crea stacking context por
     sí solo — el ::before con z-index:-1 de más abajo "escapaba" hacia
     el ancestro más cercano que sí lo crea, terminando pintado DETRÁS
     del propio background-color de #servicios (que es contenido normal
     de un ancestro más arriba en el árbol de stacking), invisible por
     completo. Con z-index:0 aquí, el z-index:-1 queda contenido dentro
     de ESTE stacking context — detrás de las tarjetas (contenido normal
     de .services-diagram), pero delante del fondo de #servicios. */
  z-index: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

@media (min-width: 1100px) {
  .services-diagram {
    flex-direction: row;
    align-items: stretch;
    gap: var(--space-5);
    padding-top: var(--space-8); /* aire para el arco de ciclo flotando arriba */
  }
}

/* ---- Arco de ciclo — el pilar 04 vuelve a alimentar al 01 ----
   Decorativo (aria-hidden) y visible SOLO en el layout horizontal — en
   columna vertical la idea de "vuelve a empezar" ya la comunica el texto
   de .service-card__detail del pilar 04, accesible sin depender de ver
   el arco. Coordenadas del path en unidades 0–100 sobre un
   viewBox="0 0 100 24" con preserveAspectRatio="none" (ver index.html):
   al ser proporcionales al ancho del contenedor, el arco se redibuja
   solo con el layout fluido, sin JS de medición ni listener de resize.
   Es deliberadamente aproximado (apunta a los bordes exteriores de la
   fila, no al centro exacto de cada tarjeta) — suficiente para una capa
   puramente decorativa, evita el costo/fragilidad de recalcular
   coordenadas exactas por breakpoint. */
.services-diagram__cycle {
  display: none;
}

@media (min-width: 1100px) {
  .services-diagram__cycle {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: var(--space-8);
    overflow: visible;
    pointer-events: none;
  }
}

/* ---- Punta de flecha del arco — ícono independiente, NO <marker> ----
   Fix (2026-07-29 ronda 4, feedback del cliente viendo el render real:
   "se descuadra y se ve deforme la flecha"): antes era un SVG <marker>
   referenciado por el <path> de arriba con marker-end. El problema:
   vivía DENTRO del mismo <svg viewBox="0 0 100 24" preserveAspectRatio=
   "none">, así que heredaba la MISMA deformación anisotrópica que
   estira el path (mucho más en X que en Y para que el arco se ajuste al
   ancho del contenedor) — el resultado era una cuña ancha y aplanada en
   vez de una punta de flecha limpia. Fix: ícono SVG independiente
   (viewBox propio "0 0 24 24", SIN preserveAspectRatio="none"), mismo
   trazo que las flechas conectoras entre nodos (misma "familia visual"
   de flecha en todo el diagrama) — posicionado con CSS (left en %,
   nunca coordenadas del viewBox distorsionado), así que nunca hereda la
   distorsión del padre sin importar cuánto se estire el contenedor.
   left:8% / rotate(90deg) replican dónde y hacia dónde apuntaba el
   marker original: el path termina en x=8 (de 100), con tangente recta
   hacia abajo en ese punto (ver el bezier de abajo), así que una flecha
   apuntando hacia abajo en esa posición horizontal es fiel al original,
   sin la distorsión. */
.services-diagram__cycle-arrowhead-icon {
  display: none;
}

@media (min-width: 1100px) {
  .services-diagram__cycle-arrowhead-icon {
    display: block;
    position: absolute;
    top: calc(var(--space-8) - 15px);
    left: 8%;
    width: 15px;
    height: 15px;
    transform: translateX(-50%) rotate(90deg);
    color: var(--color-accent-teal-deep);
    opacity: 0.55;
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-out), color var(--dur-base) var(--ease-out);
  }
}

.services-diagram__cycle-path {
  fill: none;
  stroke: var(--color-accent-teal-deep);
  stroke-width: 1.5;
  stroke-linecap: round;
  opacity: 0.55;
  transition: opacity var(--dur-base) var(--ease-out), stroke-width var(--dur-base) var(--ease-out);
  /* Animación ambiental de "flujo" (stroke-dashoffset) — @keyframes en
     animations.css. Costo mínimo (un solo path delgado, no transform en
     elementos grandes) y neutralizada automáticamente por la regla global
     de prefers-reduced-motion en base.css (fuerza animation-duration a
     0.01ms en *todo* el documento), sin condición extra aquí. */
  stroke-dasharray: 3 5;
  animation: cycle-flow 2.6s linear infinite;
}

.services-diagram__cycle-label {
  display: none;
}

@media (min-width: 1100px) {
  .services-diagram__cycle-label {
    display: block;
    position: absolute;
    top: 2px;
    left: 50%;
    transform: translateX(-50%);
    font-family: var(--font-mono);
    font-size: 0.65rem;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--color-accent-teal-deep);
    opacity: 0.75;
    background: var(--color-surface-light);
    padding-inline: var(--space-2);
    transition: opacity var(--dur-base) var(--ease-out);
  }
}

/* ---- Espina vertical decorativa (mobile/tablet, <1100px) ----
   Variante del arco de ciclo para el layout apilado (ronda 4, 2026-07-29:
   feedback explícito — el arco simplemente desaparecía bajo 1100px,
   dejando el diagrama "plano" comparado con el escritorio). Una línea
   punteada corre detrás de TODA la columna (cards + flechas + badge de
   ciclo); como cada .service-card es opaco y ocupa el 100% del ancho, la
   línea solo asoma en los espacios entre tarjetas — donde ya viven las
   flechas — así se lee como "las flechas están ensartadas en un mismo
   recorrido" en vez de flotar sueltas en un gran espacio en blanco.
   z-index:-1 dentro del stacking context que ya establece
   .services-diagram (position:relative, regla base arriba) — nunca
   compite visualmente con el contenido de las tarjetas. */
@media (max-width: 1099px) {
  .services-diagram::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 1px;
    z-index: -1;
    opacity: 0.5;
    background-image: repeating-linear-gradient(
      to bottom,
      var(--color-accent-teal-deep) 0,
      var(--color-accent-teal-deep) 3px,
      transparent 3px,
      transparent 9px
    );
    animation: services-spine-flow 2.6s linear infinite;
  }
}

/* ---- Badge "vuelve a 01" — variante mobile/tablet del arco de ciclo ----
   Hermano INMEDIATO del último .service-card en el DOM (ver index.html)
   a propósito: permite reforzarlo con el pilar 04 en hover/foco vía
   selector adyacente `+` en components.css, sin depender de :has(). Mismo
   icono de flujo animado (cycle-flow, ver animations.css) que el arco de
   escritorio, reescalado a un ícono pequeño en vez de un arco que cruza
   toda la fila. */
.services-diagram__cycle-mobile {
  display: none;
}

@media (max-width: 1099px) {
  .services-diagram__cycle-mobile {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    position: relative;
  }

  .services-diagram__cycle-mobile svg {
    width: 20px;
    height: 20px;
    color: var(--color-accent-teal-deep);
    flex-shrink: 0;
    opacity: 0.7;
    transition: opacity var(--dur-base) var(--ease-out);
  }

  .services-diagram__cycle-mobile-path {
    animation: cycle-flow 2.6s linear infinite;
  }

  .services-diagram__cycle-mobile span {
    font-family: var(--font-mono);
    font-size: 0.68rem;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--color-accent-teal-deep);
    opacity: 0.7;
    transition: opacity var(--dur-base) var(--ease-out);
  }
}

.services-cta {
  margin-top: var(--space-7);
}

/* ---- Nota de transparencia sobre contenido placeholder ---- */
.case-note {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--color-ink-faint);
  margin-top: var(--space-2);
}

/* ---- Proyectos — lista editorial de casos (rule-based) ----
   Ronda 6 (2026-07-29): reemplaza el bento grid 2x2 de la ronda 5 por
   una lista vertical de filas a todo el ancho, separadas por una regla
   (`border-top`), adaptando 1:1 el patrón `.case-grid`/`.case-card` de
   aiagency-599.pages.dev — feedback explícito de Frank: "debe ser igual
   como el ejemplo". Cada fila (`.project-card`, ver components.css)
   pasa a grid 7fr/5fr con imagen y cuerpo alternados par/impar en vez
   de un grid 2 columnas con 1 card "flagship" spaneada — ya no existe
   distinción especial de flagship a nivel de layout (se retiró la
   clase `.project-card--flagship` del HTML), el primer caso (Grupo
   Crear) simplemente ocupa la fila impar #1 como cualquier otro caso. */
.projects-grid {
  display: flex;
  flex-direction: column;
}

/* ---- Clientes — carrusel de logos / marquee (ronda 9, 2026-07-29) ----
   Reemplaza el grid estático de ronda 8 (ver historial en memoria del
   agente). Estructura: .client-marquee (vive DENTRO de .container,
   igual que el resto del sitio — se optó por respetar el max-width
   general en vez de un "full bleed" a sangre completa, para no
   introducir un ritmo visual distinto al de las filas de #proyectos,
   que también usan una regla de 1px acotada al container) >
   .client-marquee__viewport (overflow-x, aquí vive el recorte/máscara)
   > .client-marquee__track (fila flex que contiene el set real +
   los clones que agrega marquee.js) > .client-marquee__set (un set
   de 6 logos).

   ESTADO POR DEFECTO (sin JS, o con JS mientras no decide animar):
   scroll horizontal MANUAL, sin animación — fallback para
   prefers-reduced-motion:reduce o JS bloqueado/con error. marquee.js
   agrega la clase .client-marquee--live (y los clones) siempre que no
   haya preferencia de movimiento reducido, incluyendo dispositivos
   táctiles — ver comentario largo en marquee.js. */
.client-marquee {
  position: relative;
  z-index: 1;
  margin-top: var(--space-7);
  border-top: 1px solid var(--color-border);
  border-bottom: 1px solid var(--color-border);
}

.client-marquee__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scroll-snap-type: x proximity;
  touch-action: pan-x;
  overscroll-behavior-x: contain;
  padding-block: var(--space-3);
}

@media (hover: none) {
  .client-marquee__viewport {
    scroll-snap-type: none;
  }
}

.client-marquee__track {
  display: flex;
  width: max-content;
}

.client-marquee__set {
  display: flex;
}

/* ---- Estado animado — solo con .client-marquee--live (agregada por
   marquee.js tras clonar el set y confirmar que no hay preferencia de
   movimiento reducido). --marquee-shift (ancho en px de un set) y
   --marquee-duration se calculan en JS según el contenido real, no
   son un número fijo a ciegas — así el carrusel se sigue viendo bien
   si algún día se agrega o quita un logo. */
.client-marquee--live .client-marquee__viewport {
  overflow-x: hidden;
  scroll-snap-type: none;
  -webkit-mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
  mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
}

.client-marquee--live .client-marquee__track {
  animation: client-marquee-scroll var(--marquee-duration, 34s) linear infinite;
}

@keyframes client-marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(var(--marquee-shift, -50%)); }
}

/* Pausa al hover/foco sobre cualquier logo — comportamiento estándar
   de marquees de clientes (deja leer/hacer click con calma). Se
   engancha en el contenedor .client-marquee (no en cada .client-badge
   individual) a propósito: como los logos se tocan borde con borde
   sin huecos entre sí, "hover sobre el contenedor" y "hover sobre un
   logo individual" son, en la práctica, el mismo gesto — y evita
   depender de :has(), sin soporte universal todavía. */
.client-marquee--live:hover .client-marquee__track,
.client-marquee--live:focus-within .client-marquee__track {
  animation-play-state: paused;
}

/* ---- Contacto ---- */
.contact__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: start;
}

@media (min-width: 900px) {
  .contact__grid { grid-template-columns: 1fr 1fr; }
}

.contact__intro h2 {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  font-weight: 700; /* ronda 3: 500→700 — Montserrat solo tiene 400/700 cargados localmente */
  letter-spacing: var(--tracking-tight);
  margin-bottom: var(--space-4);
}

.contact__intro > p {
  color: var(--color-ink-muted);
  max-width: 40ch;
  margin-bottom: var(--space-6);
  font-size: var(--fs-lead);
}

.contact-channels {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.contact-channel {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-top: 1px solid var(--color-border);
  transition: gap var(--dur-fast) var(--ease-out);
}

.contact-channel:last-child {
  border-bottom: 1px solid var(--color-border);
}

@media (hover: hover) {
  .contact-channel:hover { gap: var(--space-5); }
}

.contact-channel__icon {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  color: var(--color-cyan);
}

.contact-channel__body span {
  display: block;
}

.contact-channel__label {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-ink-faint);
  margin-bottom: 2px;
}

.contact-channel__value {
  font-size: 0.95rem;
  color: var(--color-ink);
}

/* ---- Footer ---- */
.site-footer {
  position: relative;
  z-index: 1;
  padding-block: var(--space-7) var(--space-5);
  background: #030409;
  border-top: 1px solid var(--color-border);
}

@media (min-width: 1100px) {
  .site-footer { padding-left: var(--rail-width); }
}

.site-footer__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

@media (min-width: 700px) {
  .site-footer__inner {
    flex-direction: row;
    justify-content: space-between;
    align-items: flex-end;
  }
}

.footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  font-size: var(--fs-small);
}

.footer-meta {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  color: var(--color-ink-faint);
  letter-spacing: var(--tracking-wide);
}

/* ---- WhatsApp flotante ---- */
.whatsapp-fab {
  position: fixed;
  bottom: var(--space-5);
  right: var(--space-5);
  z-index: 50;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--color-whatsapp);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 20px rgba(37, 211, 102, 0.4);
  opacity: 0;
  transition: opacity var(--dur-slow) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}

.whatsapp-fab.is-visible {
  opacity: 1;
}

@media (hover: hover) {
  .whatsapp-fab:hover { transform: scale(1.08); }
}

.whatsapp-fab svg {
  width: 28px;
  height: 28px;
  fill: #fff;
}

/* ---- Responsive: reduce paddings verticales en mobile pequeño ---- */
@media (max-width: 480px) {
  section { padding-block: var(--space-8); }
  .hero { padding-top: calc(var(--space-8) + 2.5rem); }
}
