/* =============================================================================
   MasterBroker.ai — Landing "Vende con sistema"
   Hoja de estilos de producción. CSS moderno, sin framework, sin build.

   Índice
   01. Tokens
   02. Reset y base
   03. Accesibilidad
   04. Tipografía y utilidades
   05. Botones y etiquetas
   06. Layout: wrap / block / scene / split
   07. Navegación
   08. 01 Hero
   09. 02 Problema
   10. 03 Ecosistema
   11. 04 CRM
   12. 05 MAIA
   13. 06 Meta CAPI
   14. 07 Propiedades + placeholders
   15. 08 Portal
   16. 09 Estimador
   17. 10 MasterAcademy
   18. 11 Comunidad
   19. 12 Integración
   20. 13 Oferta
   21. 14 Footer
   22. Motion / reveal
   23. prefers-reduced-motion
   ========================================================================== */

/* ── 01. Tokens ────────────────────────────────────────────────────────── */
:root {
  /* Paleta oficial */
  --midnight: #01082D;
  --navy:     #041D56;
  --royal:    #0F2573;
  --broker:   #266CA9;
  --broker-h: #2F7DBF;
  --sky:      #ADE1FB;

  /* Texto — todos los niveles cumplen contraste AA sobre midnight/navy */
  --text:   #F2F5FA;
  --text-2: rgba(242, 245, 250, .78);
  --text-3: rgba(242, 245, 250, .62);
  --text-4: rgba(242, 245, 250, .58);

  /* Líneas y superficies */
  --line:      rgba(173, 225, 251, .16);
  --line-soft: rgba(173, 225, 251, .10);
  --line-live: rgba(173, 225, 251, .42);
  --surface:   rgba(1, 8, 45, .60);
  --surface-2: rgba(4, 29, 86, .55);

  /* Tipografía */
  --font-display: 'Sora', system-ui, -apple-system, sans-serif;
  --font-ui:      'Inter', system-ui, -apple-system, sans-serif;
  --font-mono:    'IBM Plex Mono', ui-monospace, SFMono-Regular, monospace;

  /* Los mínimos NO cambian: móvil y tablet conservan su escala.
     Sólo suben los topes, que únicamente se alcanzan en escritorio amplio. */
  --h1: clamp(2.5rem, min(7.6vw, 11.5vh), 6.2rem);
  --h2: clamp(1.6rem, min(4.3vw, 6vh), 3.7rem);
  --h3: clamp(1.15rem, 2.3vw, 1.75rem);
  --lead: clamp(0.98rem, min(1.4vw, 2.2vh), 1.16rem);

  /* Espacio
     --wrap es el ancho TOTAL del contenedor (incluye los gutters, porque todo
     el documento es border-box). Ancho útil = --wrap − 2 × --gutter = 1528px.
     Ese valor es la retícula común: la usan .wrap, .scene__pin, .hero__content,
     .hero__hint, .nav y el footer, de modo que todas las secciones comparten
     exactamente el mismo borde izquierdo y derecho.
     El tope de --gutter (3.5rem) sólo actúa por encima de ~1120px: móvil y
     tablet conservan los márgenes actuales (5vw). */
  --gutter: clamp(1.25rem, 5vw, 3.5rem);
  --wrap: 1640px;
  --wrap-narrow: 1200px;
  --block-y: clamp(4rem, 8.5vh, 7rem);
  --radius: 18px;
  --radius-sm: 12px;

  /* Motion */
  --ease: cubic-bezier(.2, .7, .2, 1);
  --nav-h: 64px;
}

/* ── 02. Reset y base ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 12px);
  -webkit-text-size-adjust: 100%;
  /* El recorte horizontal va en la raíz: en <body> obligaría a overflow-y:auto
     (body pasaría a ser contenedor de scroll) y en móvil el viewport de
     maquetación crecería con cualquier desbordamiento, arrastrando al header
     fijo. En la raíz no rompe position: sticky. */
  overflow-x: hidden;
}

body {
  margin: 0;
  background: var(--midnight);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
}

img, video, svg { display: block; max-width: 100%; }
img, video { height: auto; }

h1, h2, h3, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: var(--sky); text-decoration: none; transition: color .25s var(--ease); }
a:hover { color: #fff; }

button { font: inherit; color: inherit; }

::selection { background: var(--broker); color: #fff; }

/* ── 03. Accesibilidad ─────────────────────────────────────────────────── */
:where(a, button, input, [tabindex]):focus-visible {
  outline: 2px solid var(--sky);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: fixed;
  top: 8px; left: 8px;
  z-index: 300;
  padding: 12px 20px;
  border-radius: 999px;
  background: var(--sky);
  color: var(--midnight);
  font-weight: 600;
  transform: translateY(-160%);
  transition: transform .22s var(--ease);
}
.skip-link:focus-visible { transform: none; color: var(--midnight); }

.u-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip-path: inset(50%);
}

/* Elementos que sólo existen en modo desarrollo (data-env="dev" en <html>).
   !important: gana a cualquier display propio del componente (p. ej. .shot__tag) */
[data-dev-only] { display: none !important; }
[data-env="dev"] [data-dev-only] { display: block !important; }

/* ── 04. Tipografía y utilidades ───────────────────────────────────────── */
.h2 {
  font-family: var(--font-display);
  font-size: var(--h2);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -.03em;
  color: #fff;
  text-wrap: balance;
}
.h2--center { text-align: center; }

.lead {
  max-width: 56ch;
  font-size: var(--lead);
  line-height: 1.65;
  color: var(--text-2);
}

.disclaimer {
  max-width: 72ch;
  font-size: .8125rem;
  line-height: 1.6;
  color: var(--text-4);
}

.eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--sky);
}
.eyebrow__dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--sky);
  flex: none;
}

.mono-label {
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.accent { color: var(--sky); }
.accent-soft { color: rgba(173, 225, 251, .92); }

/* ── 05. Botones y etiquetas ───────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 52px;
  padding: 0 28px;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: .9375rem;
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  cursor: pointer;
  transition: background .28s var(--ease), border-color .28s var(--ease), color .28s var(--ease);
}
.btn--primary {
  background: var(--broker);
  color: #fff;
  box-shadow: 0 18px 44px -22px rgba(38, 108, 169, 1);
}
.btn--primary:hover { background: var(--broker-h); color: #fff; }
.btn--ghost {
  border-color: rgba(173, 225, 251, .32);
  background: rgba(173, 225, 251, .05);
  color: var(--text);
}
.btn--ghost:hover {
  border-color: rgba(173, 225, 251, .7);
  background: rgba(173, 225, 251, .1);
  color: #fff;
}
.btn--sm { min-height: 44px; padding: 0 20px; font-size: .875rem; }
.btn--block { width: 100%; }

/* CTA cuya URL todavía no está confirmada (main.js lo marca) */
.btn[data-pending] { opacity: .62; cursor: not-allowed; }
.btn[data-pending]::after {
  content: 'URL pendiente';
  margin-left: 10px;
  font-family: var(--font-mono);
  font-size: .625rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  opacity: .8;
}
:root:not([data-env="dev"]) .btn[data-pending]::after { content: none; }

.tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 9px;
  border-radius: 6px;
  font-family: var(--font-mono);
  font-size: .625rem;
  letter-spacing: .06em;
  white-space: nowrap;
}
.tag--sky { background: rgba(173, 225, 251, .18); color: var(--sky); }

/* ── 06. Layout ────────────────────────────────────────────────────────── */
/* Retícula común. Todo contenedor de primer nivel usa exactamente esta caja:
   ancho total --wrap, gutters --gutter. Ninguna sección se sale de ella. */
.wrap,
.scene__pin,
.hero__content,
.hero__hint,
.nav {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: var(--wrap-narrow); }

.block {
  position: relative;
  padding-block: var(--block-y);
}

.scene__copy { display: grid; gap: clamp(.6rem, 1.4vh, 1rem); max-width: min(100%, 800px); }
.scene__copy--center { justify-items: center; text-align: center; margin-inline: auto; }

/* Composición partida. Reparto de la retícula de 12 columnas:
   texto ≈ 44%  ·  visualización ≈ 56%. Se aplica desde 1024px; entre 768 y
   1024 sigue siendo auto-fit para no comprimir la columna de texto. */
.split { display: grid; gap: clamp(1.75rem, 3.4vw, 3.25rem); align-items: center; }

/* Escenas con narrativa de scroll.
   Móvil: flujo normal (sin sticky). ≥768px: pin sticky. */
.scene { position: relative; }
/* position: relative aquí y NO en `.scene--problema .scene__pin`: con mayor
   especificidad ganaba a `position: sticky` del bloque ≥768px, de modo que la
   escena Problema nunca llegaba a fijarse y se iba del viewport mientras su
   animación todavía corría. Sticky también sirve de bloque contenedor para
   `.chaos`, que es lo único que `relative` aportaba. */
.scene__pin {
  position: relative;
  display: grid;
  gap: clamp(1rem, 2.4vh, 1.6rem);
  padding-block: clamp(3rem, 7vh, 5rem);
}

/* ── 07. Navegación ────────────────────────────────────────────────────── */
.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 120;
  border-bottom: 1px solid transparent;
  transition: background .4s var(--ease), border-color .4s var(--ease);
}
.site-header[data-stuck] {
  background: rgba(1, 8, 45, .86);
  border-bottom-color: var(--line-soft);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
}

/* La navegación comparte la retícula común (ver §06): el isotipo del header
   arranca en el mismo borde izquierdo que el contenido de todas las secciones. */
.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: var(--nav-h);
  padding-block: 10px;
}

.nav__brand { display: flex; align-items: center; gap: 11px; flex: none; color: #fff; }
.nav__brand img { width: 30px; height: auto; }
.nav__brand-text {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: -.01em;
  white-space: nowrap;
}

.nav__toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(1, 8, 45, .6);
  font-size: .8125rem;
  cursor: pointer;
}
.nav__toggle-box { position: relative; display: block; width: 16px; height: 10px; }
.nav__toggle-bar,
.nav__toggle-box::before,
.nav__toggle-box::after {
  position: absolute;
  left: 0;
  width: 100%; height: 1.5px;
  border-radius: 2px;
  background: var(--sky);
  transition: transform .3s var(--ease), opacity .2s linear;
}
.nav__toggle-box::before { content: ''; top: 0; }
.nav__toggle-bar { top: 4.25px; }
.nav__toggle-box::after { content: ''; bottom: 0; }
.nav__toggle[aria-expanded="true"] .nav__toggle-box::before { transform: translateY(4.25px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] .nav__toggle-bar { opacity: 0; }
.nav__toggle[aria-expanded="true"] .nav__toggle-box::after { transform: translateY(-4.25px) rotate(-45deg); }

/* Menú — panel desplegable en móvil */
.nav__menu {
  position: fixed;
  inset: var(--nav-h) 0 auto;
  display: grid;
  gap: 6px;
  padding: 18px var(--gutter) 26px;
  background: rgba(1, 8, 45, .97);
  border-bottom: 1px solid var(--line);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  transform: translateY(-8px);
  opacity: 0;
  visibility: hidden;
  transition: opacity .28s var(--ease), transform .28s var(--ease), visibility .28s;
}
.nav__menu[data-open] { opacity: 1; transform: none; visibility: visible; }

.nav__list { display: grid; gap: 2px; }
.nav__list a {
  display: block;
  padding: 12px 4px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--text-2);
  font-size: 1rem;
}
.nav__list a:hover,
.nav__list a[aria-current="true"] { color: #fff; }
.nav__list a[aria-current="true"] { border-bottom-color: var(--line-live); }
.nav__cta { margin-top: 12px; }

@media (min-width: 900px) {
  .nav__toggle { display: none; }
  .nav__menu {
    position: static;
    display: flex;
    align-items: center;
    gap: clamp(14px, 2vw, 28px);
    padding: 0;
    background: none;
    border: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    transform: none;
    opacity: 1;
    visibility: visible;
  }
  .nav__list { display: flex; align-items: center; gap: clamp(14px, 2vw, 26px); }
  .nav__list a {
    padding: 4px 0;
    border-bottom: 1px solid transparent;
    font-size: .875rem;
  }
  .nav__cta { margin-top: 0; }
}

/* ── 08. 01 Hero ───────────────────────────────────────────────────────── */
.hero {
  /* El reparto vertical vive en variables porque la capa visual (portal +
     recorrido, en absoluto sobre .hero) tiene que poder centrarse en la MISMA
     banda útil que el copy: alto del hero − cabecera − pista "Desliza". */
  --hero-pad-top: calc(var(--nav-h) + 12px);
  --hero-pad-bottom: clamp(1.5rem, 4vh, 3rem);
  --hero-hint-rail: 32px;
  --hero-hint-pad: clamp(.85rem, 2vh, 1.4rem);
  --hero-hint-h: calc(var(--hero-hint-rail) + var(--hero-hint-pad));

  position: relative;
  display: grid;
  grid-template-rows: 1fr auto;
  min-height: 100svh;
  padding: var(--hero-pad-top) 0 var(--hero-pad-bottom);
  overflow: hidden;
  isolation: isolate;
  background: radial-gradient(120% 90% at 50% 38%, #0A2A63 0%, var(--navy) 42%, var(--midnight) 82%);
}

.hero__stage { position: absolute; inset: 0; z-index: -1; will-change: transform, opacity; }

.hero__glow {
  position: absolute; inset: 0;
  background: radial-gradient(58% 44% at 50% 34%, rgba(38, 108, 169, .42), transparent 72%);
  animation: mbBreath 10s ease-in-out infinite;
}
.hero__grid {
  position: absolute; inset: 0;
  opacity: .12;
  background-image:
    linear-gradient(rgba(173, 225, 251, .5) 1px, transparent 1px),
    linear-gradient(90deg, rgba(173, 225, 251, .5) 1px, transparent 1px);
  background-size: 92px 92px;
  mask-image: radial-gradient(72% 62% at 50% 40%, #000, transparent);
  -webkit-mask-image: radial-gradient(72% 62% at 50% 40%, #000, transparent);
}

/* Iluminación selectiva de la cuadrícula ─────────────────────────────────
   Lo que se ilumina son LAS LÍNEAS, no el fondo. Esta capa es la MISMA
   cuadrícula (idéntico background-image, idéntico background-size: 92px 92px,
   idéntico origen: misma caja `inset: 0` dentro de .hero__stage) pintada más
   luminosa justo ENCIMA de la base. Al coincidir al píxel, se suma sobre las
   líneas existentes: no hay doble línea, ghosting ni moiré posibles.

   Sólo es visible dentro de una elipse difusa que recorre la escena. Y la
   elipse viaja moviendo la MÁSCARA (mask-position), nunca el elemento ni su
   background: si se animara un transform, el patrón viajaría con él y la
   cuadrícula se vería desplazarse. Aquí la cuadrícula está clavada y lo único
   que se desplaza es la zona de revelado.

   La máscara tiene dos capas compuestas con `intersect`:
     1 · la elipse móvil (mask-size 160%: el degradado se define en % de esa
         caja, así que sus radios son 0.48 × ancho y 0.416 × alto del hero);
     2 · exactamente el mismo desvanecido de bordes que usa .hero__grid, para
         que el resplandor nunca encienda líneas donde la base ya no existe.

   Por defecto está PAUSADA: sólo corre mientras motion.js marca el hero con
   [data-grid-highlight-active], es decir mientras #hero o #problema
   intersecan. Capa aditiva: si se elimina, el hero vuelve a verse igual. */
.hero__grid-highlight {
  /* Opacidad de crucero, elegida MIRANDO el hero, no midiendo píxeles: .15 era
     invisible en uso normal y .42 empezaba a competir con el isotipo. */
  --grid-highlight-max: .34;
  position: absolute; inset: 0;
  opacity: 0;
  /* Sólo LÍNEAS: ningún fondo plano. Las dos primeras capas son la cuadrícula
     —geometría idéntica a .hero__grid, en Sky y más luminosa— y las dos de
     debajo son su halo Broker: una banda de 3px desplazada 1px para quedar
     centrada bajo la misma línea. Es la única participación de #266CA9 y va
     atada a la geometría, no al fondo: hace que la línea parezca encendida en
     lugar de simplemente más blanca, sin coste de filtros. */
  background-image:
    linear-gradient(rgba(173, 225, 251, .95) 1px, transparent 1px),
    linear-gradient(90deg, rgba(173, 225, 251, .95) 1px, transparent 1px),
    linear-gradient(rgba(38, 108, 169, .45) 3px, transparent 3px),
    linear-gradient(90deg, rgba(38, 108, 169, .45) 3px, transparent 3px);
  background-size: 92px 92px;
  background-position: 0 0, 0 0, 0 -1px, -1px 0;
  /* UNA sola máscara. La versión anterior multiplicaba esta elipse por el
     desvanecido de bordes de .hero__grid con mask-composite: intersect, y el
     producto de dos degradados suaves se comía casi toda la señal —medido: a
     igualdad de opacidad, la zona iluminada era una fracción de ésta—. El
     recorte de bordes no hace falta: la propia caída de la elipse ya apaga la
     luz mucho antes del borde de la escena. */
  mask-image: radial-gradient(30% 26% at 50% 50%, #000 0%, rgba(0, 0, 0, .58) 45%, transparent 80%);
  -webkit-mask-image: radial-gradient(30% 26% at 50% 50%, #000 0%, rgba(0, 0, 0, .58) 45%, transparent 80%);
  mask-size: 160% 160%;
  -webkit-mask-size: 160% 160%;
  mask-repeat: no-repeat;
  -webkit-mask-repeat: no-repeat;
  /* 25s = bajada 7.5s + descanso 5s + subida 7.5s + descanso 5s. */
  animation: mbGridSweep 25s linear infinite;
  animation-play-state: paused;
  will-change: mask-position, opacity;
}
.hero[data-grid-highlight-active] .hero__grid-highlight { animation-play-state: running; }
/* Ahorro de datos: la capa se queda inerte (nunca llega a verse). */
.hero[data-grid-highlight-off] .hero__grid-highlight { animation: none; opacity: 0; }
.hero__vignette {
  position: absolute; inset: auto 0 0;
  height: 55%;
  background: linear-gradient(180deg, transparent, rgba(1, 8, 45, .86) 58%, var(--midnight));
}

/* Portal luminoso: el isotipo 3D vive dentro de un disco enmascarado */
.hero__portal {
  position: absolute;
  left: 50%;
  top: clamp(3.25rem, 7.5vh, 5.5rem);
  transform: translateX(-50%);
  /* Aumento moderado (520 → 560). No se lleva al tamaño de Claude Design:
     debe conservar aire entre navegación, isotipo y bloque de texto. */
  width: min(62vh, min(74vw, 560px));
  aspect-ratio: 1;
  /* Radio de revelado (antes 54% / 86%). El tamaño de la caja NO cambia: lo
     único que se amplía es la zona en la que el isotipo permanece visible
     dentro de ella. Los stops salen de medir el video, no de estimarlos: a lo
     largo de una vuelta completa la masa del isotipo llega al ~95% del radio
     del portal en su momento de mayor amplitud horizontal (~t=0 del loop) y al
     ~84% en vertical. Con el corte anterior en 86% ese extremo quedaba
     amputado de golpe; llevando el final del fade al 94/98% la masa entra
     entera en el degradado y desaparece por transparencia progresiva, nunca
     por recorte.

     Éste es el valor de los layouts APILADOS (< 1200px), donde el copy se
     superpone al portal: ahí el techo no lo marca el isotipo sino la
     legibilidad del texto que lleva encima. El escritorio de dos columnas sube
     a 78/100 en su propio bloque, porque allí el copy vive fuera del disco. */
  mask-image: radial-gradient(closest-side, #000 70%, transparent 97%);
  -webkit-mask-image: radial-gradient(closest-side, #000 70%, transparent 97%);
}
.hero__video,
.hero__iso {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
}
.hero__video {
  opacity: 0;
  mix-blend-mode: screen;
  filter: grayscale(1) contrast(1.18) brightness(.84) sepia(1) hue-rotate(176deg) saturate(2.1);
  transition: opacity .8s var(--ease);
}
.hero__video[data-ready] { opacity: .82; }

/* Fallback: isotipo plano. Visible hasta que el video esté listo, y de forma
   permanente si el video falla, si hay ahorro de datos o reduced-motion. */
.hero__iso {
  opacity: .92;
  animation: mbFloat 12s ease-in-out infinite;
  filter: drop-shadow(0 24px 60px rgba(1, 8, 45, .85));
  transition: opacity .8s var(--ease);
}
.hero__portal[data-video-ready] .hero__iso { opacity: 0; }

.hero__content {
  display: grid;
  align-content: end;
  gap: clamp(1.1rem, 2.6vh, 1.9rem);
}

/* Las dos zonas del hero sólo existen como cajas en escritorio ancho. Hasta
   1200px son `display: contents`: .hero__lead y .hero__orbit vuelven a ser
   hijos directos de la retícula de .hero__content, así que móvil y tablet
   conservan exactamente la disposición anterior (copy + recorrido en columna,
   separados por el gap de .hero__content). */
.hero__layout,
.hero__left,
.hero__right { display: contents; }

.hero__lead { display: grid; gap: clamp(.8rem, 1.6vh, 1.05rem); max-width: 780px; }

.hero__title {
  font-family: var(--font-display);
  font-size: var(--h1);
  font-weight: 600;
  line-height: .95;
  letter-spacing: -.035em;
  color: #fff;
}
.hero__sub { max-width: 52ch; font-size: var(--lead); line-height: 1.65; color: var(--text-2); }
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; padding-top: 4px; }
.hero__actions .btn { flex: 1 1 auto; min-width: min(100%, 240px); }

/* ── Recorrido alrededor del isotipo ──────────────────────────────────────
   Las cinco piezas llegan de fuera y se conectan al isotipo. Toda la
   coreografía cuelga de un único [data-on] en el contenedor: los retardos por
   paso salen de --i, así que rearmar la secuencia es quitar y volver a poner
   el atributo, sin tocar cinco elementos. Sin [data-on] no hay retardo, de
   modo que el reset (fuera del viewport) es inmediato y no cae en cascada
   inversa. */
.hero__orbit {
  --orbit-step: .7s;   /* separación entre piezas: lento y legible */
  --orbit-size: min(62vh, min(74vw, 560px));  /* = tamaño del portal */
  position: relative;
}
/* Fundido de los trazos DENTRO del portal.
   Una sola máscara radial sobre el propio <svg> resuelve los cinco trazos a la
   vez, sea cual sea su ángulo, sin tocar ni un punto de su geometría: los
   x1/y1/x2/y2 del HTML siguen intactos.

   El radio NO es `closest-side`: es el radio REAL del portal, escrito como
   `calc(50% / 1.26)` sobre la caja del recorrido. El recorrido mide
   exactamente 1.26× el portal y es cuadrado en las dos bandas, así que la
   mitad de su lado dividida por 1.26 ES el radio del disco. Con eso los stops
   se leen en la MISMA escala que la máscara del portal —100% = borde del aro—
   y no en la del recorrido, que es 1.26 veces mayor.

   Va en porcentaje y no en `calc(var(--orbit-size) / 2)` por dos razones, las
   dos comprobadas en el navegador: en escritorio --orbit-size hereda de
   --hero-iso, que lleva dentro un `min(100%, …)`, y un porcentaje dentro del
   radio de un `circle` invalida la declaración entera (la máscara se caía a
   `none`); y un radio en % se resuelve contra la caja, de modo que NO se
   deforma al descentrar el gradiente, que es justo lo que hace falta abajo.

   El centro tampoco puede ser `50% 50%`: portal y recorrido comparten centro
   en escritorio, pero por debajo de 1200px el portal se posiciona contra
   .hero__stage (caja de .hero) y el recorrido contra .hero__content, que
   arranca --hero-pad-top más abajo. Medido: 76px constantes en 900, 1024, 1100
   y 1199px. De ahí --link-fade-cy, que corrige el desfase con el mismo token
   que lo produce y vuelve a 50% en >=1200px.

   Los stops salen de medir la luminancia media del isotipo por anillos, sobre
   ocho fotogramas repartidos en una vuelta completa. En proporción del radio
   del portal:
     0.70 → el isotipo aún marca ~111 de luma: el trazo pasa al 16%
     0.83 → ~82:  el trazo va al 52%
     0.92 → ~52:  el trazo va al 84%
     1.00 → ~28 = fondo, ahí acaba el disco: el trazo va al 100%
   Es decir, la opacidad del trazo es la inversa del brillo del isotipo: donde
   el 3D manda la línea ya no está, y donde el 3D se ha apagado la línea está
   entera. Por eso se funde en lugar de cortarse.

   Reparto que produce: Publicidad, Nuevo prospecto y MAIA acaban a 0.60 del
   radio del portal, justo donde la máscara aún es casi transparente, así que
   conservan TODA su longitud convertida en degradado. CRM y Cita apuntan más
   adentro (0.35 y 0.30) y es sólo su tramo sobrante —el que se dibujaba sobre
   la cara del isotipo— el que desaparece. Ninguno se recorta en seco.

   No hace falta tocar el z-index: .hero__stage sigue en -1 y el recorrido
   encima. Subir el portal por delante del recorrido arrastraría también glow,
   cuadrícula y viñeta por encima del copy. */
.hero__orbit-links {
  display: none;
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  color: var(--line-live);
  overflow: visible;
  --link-fade-cy: calc(50% - var(--hero-pad-top));
  --link-fade-r: calc(50% / 1.26);   /* = radio del portal */
  mask-image: radial-gradient(ellipse var(--link-fade-r) var(--link-fade-r) at 50% var(--link-fade-cy),
    transparent 58%,
    rgba(0, 0, 0, .16) 70%,
    rgba(0, 0, 0, .52) 83%,
    rgba(0, 0, 0, .84) 92%,
    #000 100%);
  -webkit-mask-image: radial-gradient(ellipse var(--link-fade-r) var(--link-fade-r) at 50% var(--link-fade-cy),
    transparent 58%,
    rgba(0, 0, 0, .16) 70%,
    rgba(0, 0, 0, .52) 83%,
    rgba(0, 0, 0, .84) 92%,
    #000 100%);
}
.hero__orbit-link {
  stroke-width: 1px;
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset .8s var(--ease);
}
.hero__orbit[data-on] .hero__orbit-link {
  stroke-dashoffset: 0;
  transition-delay: calc(var(--i) * var(--orbit-step));
}

.hero__orbit-step {
  padding: clamp(.5rem, 1.2vh, .72rem) 1rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  /* Opaco frente al isotipo: las piezas pasan por delante del glow y deben
     leerse como fichas, no como texto flotando sobre el metal. */
  background: rgba(1, 8, 45, .88);
  font-family: var(--font-mono);
  font-size: clamp(.66rem, .78vw, .8rem);
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: center;
  color: #fff;
  opacity: 0;
  transition: opacity .58s var(--ease), transform .58s var(--ease);
  will-change: transform, opacity;
}
.hero__orbit[data-on] .hero__orbit-step {
  opacity: 1;
  /* La ficha entra un poco después de que su línea empiece a trazarse. */
  transition-delay: calc(var(--i) * var(--orbit-step) + .3s);
}
/* MAIA se ilumina: misma señal sky que .flow__step--ai en Integración. */
.hero__orbit-step--maia {
  border-color: var(--line-live);
  background: rgba(38, 108, 169, .3);
  color: var(--sky);
}

/* Móvil y tablet: no hay aire alrededor del isotipo, así que la secuencia se
   resuelve en columna bajo el copy. Misma coreografía, conectores verticales
   en lugar de líneas al centro. */
.hero__orbit-steps {
  --flow-gap: clamp(1.3rem, 2.8vh, 1.9rem);
  display: grid;
  justify-items: center;
  row-gap: var(--flow-gap);
}
/* El desplazamiento de entrada viaja en --orbit-off, no en el transform: así
   una sola regla lo apaga en las dos disposiciones (columna y órbita) y
   reduced-motion no tiene que sobrescribir dos transforms distintos. */
.hero__orbit-step {
  position: relative;
  width: min(100%, 320px);
  --orbit-off: 10px;
  transform: translateY(var(--orbit-off));
}
.hero__orbit[data-on] .hero__orbit-step { --orbit-off: 0px; }
.hero__orbit-step + .hero__orbit-step::before,
.hero__orbit-step + .hero__orbit-step::after {
  content: "";
  position: absolute;
  left: 50%;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s var(--ease);
}
.hero__orbit[data-on] .hero__orbit-step + .hero__orbit-step::before,
.hero__orbit[data-on] .hero__orbit-step + .hero__orbit-step::after {
  opacity: 1;
  transition-delay: calc(var(--i) * var(--orbit-step));
}
.hero__orbit-step + .hero__orbit-step::before {
  bottom: 100%;
  width: 1px;
  height: var(--flow-gap);
  transform: translateX(-50%);
  background: linear-gradient(to bottom, rgba(173, 225, 251, .1), var(--line-live));
}
.hero__orbit-step + .hero__orbit-step::after {
  bottom: calc(100% + 3px);
  width: 6px; height: 6px;
  border-right: 1px solid var(--line-live);
  border-bottom: 1px solid var(--line-live);
  transform: translate(-50%, 0) rotate(45deg);
}

.hero__hint {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-block: var(--hero-hint-pad) 0;
  font-family: var(--font-mono);
  font-size: .6875rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-4);
}
.hero__hint-rail { position: relative; display: block; width: 1px; height: var(--hero-hint-rail); background: rgba(173, 225, 251, .22); overflow: hidden; }
.hero__hint-dot { position: absolute; inset: 0; background: var(--sky); animation: mbHint 2.6s ease-in-out infinite; }

@media (min-width: 900px) {
  /* A partir de aquí hay aire alrededor del isotipo: la secuencia deja la
     columna y se despliega sobre él. La caja repite la geometría del portal
     ampliada un 26%, de modo que las piezas caen fuera de la masa visible del
     isotipo (la máscara empieza a apagarlo en el 68% del radio y lo deja en
     nada en el 98%) y las líneas entran hasta su borde. Al ampliar el revelado
     las tarjetas quedan más cerca del degradado de lo que estaban: medida la
     esquina más próxima de cada una, "Publicidad" —la más justa— cae entre
     0.94× y 0.98× el radio del portal según el viewport, es decir dentro del
     tramo final del fade, donde la máscara ya sólo deja pasar ~10% de alfa.
     No estorba porque cada paso lleva su propio fondo opaco y se pinta encima;
     pero es el margen real, así que el 1.26 no debe reducirse. */
  .hero__orbit {
    position: absolute;
    left: 50%;
    top: calc(clamp(3.25rem, 7.5vh, 5.5rem) - var(--orbit-size) * .13);
    width: calc(var(--orbit-size) * 1.26);
    aspect-ratio: 1;
    transform: translateX(-50%);
    pointer-events: none;   /* la caja cubre parte del copy: no debe capturarlo */
  }
  /* El copy se queda en la mitad izquierda. Sin esto ocuparía los 780px del
     lead —la columna derecha ya no existe— y el brazo inferior del recorrido
     le pasaría por encima en viewports cortos. */
  .hero__lead { max-width: min(46vw, 780px); }

  .hero__orbit-links { display: block; }
  .hero__orbit-steps { position: absolute; inset: 0; display: block; }
  .hero__orbit-step {
    position: absolute;
    left: var(--nx); top: var(--ny);
    width: max-content;
    max-width: 11rem;
    transform: translate(-50%, calc(-50% + var(--orbit-off)));
  }
  /* Aquí conecta el SVG, no los conectores verticales. */
  .hero__orbit-step + .hero__orbit-step::before,
  .hero__orbit-step + .hero__orbit-step::after { content: none; }

  .hero__actions .btn { flex: 0 0 auto; min-width: 0; }
}

/* ── Escritorio ancho: el hero se lee como dos columnas ───────────────────
   A partir de 1200px hay ancho suficiente para que el isotipo y su recorrido
   vivan cómodos dentro de media pantalla, así que la escena se reparte en dos
   mitades de la MISMA retícula del sitio (--wrap / --gutter): copy a la
   izquierda, composición visual a la derecha. Sin línea, borde ni fondo que
   las separe: las columnas existen por composición.

   Isotipo y recorrido tienen que compartir centro, pero NO comparten bloque
   contenedor: .hero__portal se posiciona contra .hero__stage (= caja de .hero)
   y .hero__orbit contra .hero__content, porque motion.js le aplica un
   translateY y eso ya lo convierte en bloque contenedor. En lugar de pelear
   con ello se aprovecha: .hero__content es exactamente la banda útil (fila 1
   de la retícula del hero, entre cabecera y pista "Desliza"), así que
     · el recorrido se centra en ella con un simple 50%;
     · el isotipo llega al mismo punto con --hero-focus-y, que reconstruye esa
       misma banda en coordenadas de .hero.
   Ambos ejes se han verificado idénticos al píxel en los diez viewports de
   prueba. En horizontal no hay ambigüedad: las dos cajas están centradas y
   acotadas por --wrap, de modo que --hero-focus-x vale igual en las dos. */
@media (min-width: 1200px) {
  .hero {
    /* Mitades de la retícula real, no del viewport: min(100%, --wrap) es la
       misma caja que usan .hero__content, .nav y el footer, de modo que en
       1920px o 2560px las columnas siguen alineadas con el resto del sitio. */
    --hero-gap: clamp(2.5rem, 4.5vw, 4.5rem);
    --hero-col: calc((min(100%, var(--wrap)) - 2 * var(--gutter) - var(--hero-gap)) / 2);
    /* Misma medida sin porcentajes. Hace falta porque dentro de font-size un %
       se resolvería contra el tamaño de fuente del padre, no contra el ancho. */
    --hero-col-abs: calc((min(100vw, var(--wrap)) - 2 * var(--gutter) - var(--hero-gap)) / 2);

    /* Centro de la columna derecha, en coordenadas de .hero */
    --hero-focus-x: calc(50% + (var(--hero-gap) + var(--hero-col)) / 2);
    /* Centro de la banda útil: el mismo tramo que ocupa .hero__content, ya
       descontados cabecera, pista "Desliza" y el respiro inferior. */
    --hero-focus-y: calc(
      var(--hero-pad-top) +
      (100% - var(--hero-pad-top) - var(--hero-pad-bottom) - var(--hero-hint-h)) / 2
    );

    /* Mismo conjunto, sólo que su tope ahora lo marca media retícula. La
       relación portal : recorrido (1 : 1.26) no se toca: el recorrido sigue
       derivándose de este valor, así que todo escala en bloque. */
    --hero-iso: min(560px, calc(var(--hero-col) / 1.26), calc(72vh / 1.26));
  }

  /* position: relative no cambia nada de lo que se ve: fija por CSS el bloque
     contenedor del recorrido, que si no dependería de que motion.js hubiera
     escrito ya su transform (y saltaría unos píxeles entre el primer pintado y
     el primer scroll, o con reduced-motion). */
  .hero__content { position: relative; align-content: center; }
  /* Misma razón: la banda útil se calcula descontando esta pista, así que su
     alto deja de ser una suposición y pasa a estar declarado. */
  .hero__hint { block-size: var(--hero-hint-h); }

  /* Columna izquierda: el bloque se centra dentro de su mitad, pero el texto
     conserva su alineación a la izquierda (nada de text-align: center). */
  .hero__layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: var(--hero-gap);
    align-items: center;
  }
  .hero__left { display: grid; }
  .hero__right { display: block; }   /* zona reservada: el recorrido va absoluto */
  /* width: 100% es lo que mantiene el borde izquierdo del copy exactamente
     sobre la retícula (mismo borde que .nav y que las secciones siguientes).
     Sin él, las márgenes auto convierten el bloque en shrink-to-fit y el texto
     se despega del gutter. El centrado por margen sólo entra en juego si la
     columna llegara a superar los 780px del lead. */
  .hero__lead {
    width: 100%;
    max-width: min(100%, 780px);
    margin-inline: auto;
  }
  /* Única concesión tipográfica, y sólo porque es imprescindible: entre 1200 y
     ~1330px media retícula no da para "con sistema." en una línea y el <br> del
     H1 se partiría en tres. 6.04 es el ancho real de esa línea medido en
     múltiplos del tamaño de fuente (Sora 600, letter-spacing −.035em); 6.25 deja
     holgura suficiente para absorber también la diferencia entre 100vw y el
     ancho real cuando el sistema pinta barra de scroll. A partir de ~1340px el
     mínimo lo vuelve a marcar --h1, así que ningún escritorio ancho cambia. */
  .hero__title { font-size: min(var(--h1), calc(var(--hero-col-abs) / 6.25)); }

  /* La cámara de la transición hero → problema (motion.js escribe scale() sobre
     .hero__stage) tiene que seguir "entrando al portal". Con el isotipo fuera
     del centro de la capa, un escalado de origen centrado lo empujaría hacia
     fuera en lugar de acercarse a él; moviendo el origen al mismo punto focal
     el centro del isotipo se queda quieto mientras crece y el recorrido sigue
     exactamente encima durante toda la transición. motion.js no se toca. */
  .hero__stage { transform-origin: var(--hero-focus-x) var(--hero-focus-y); }

  /* Columna derecha: isotipo y recorrido sobre el mismo centro. */
  .hero__portal,
  .hero__orbit {
    left: var(--hero-focus-x);
    transform: translate(-50%, -50%);
  }
  .hero__portal {
    top: var(--hero-focus-y);   /* banda útil, en coordenadas de .hero */
    width: var(--hero-iso);
    /* Radio de revelado máximo. Aquí —y sólo aquí— el isotipo está solo en su
       mitad: medido en los seis viewports de prueba, la caja de copy más
       cercana se queda a ≥152% del radio del portal, así que ampliar el disco
       no puede restarle contraste a ningún texto. El fade termina en 100%: por
       encima del ~95% que alcanza la masa del isotipo en su fotograma más
       ancho, de modo que ni ese extremo llega recortado y el isotipo deja de
       leerse "al ras" del aro. `width` no cambia: el isotipo mantiene
       exactamente su tamaño; lo único que crece es la zona revelada. */
    mask-image: radial-gradient(closest-side, #000 78%, transparent 100%);
    -webkit-mask-image: radial-gradient(closest-side, #000 78%, transparent 100%);
  }
  .hero__orbit {
    top: 50%;                   /* .hero__content YA es la banda útil */
    --orbit-size: var(--hero-iso);
  }
  /* Aquí portal y recorrido SÍ comparten centro (--hero-focus-y reconstruye la
     banda útil en coordenadas de .hero), así que el fundido de los trazos deja
     de necesitar la corrección de --hero-pad-top. */
  .hero__orbit-links { --link-fade-cy: 50%; }
}

/* ── 09. 02 Problema ───────────────────────────────────────────────────── */
/* Un rescoldo de navy bajo el campo del caos: el núcleo no nace sobre un negro
   plano y la absorción final tiene contra qué leerse. */
.scene--problema {
  background:
    radial-gradient(76% 46% at 50% 72%, rgba(15, 37, 115, .42), transparent 70%),
    var(--midnight);
}
.scene--problema .scene__pin { place-items: center; }
.scene--problema .scene__copy { position: relative; z-index: 2; max-width: min(100%, 1020px); }

/* Línea de apoyo: nombra lo que son los fragmentos, sin convertir la escena en
   un bloque de texto.
   Sigue siendo el TERCER nivel de jerarquía —por debajo del h2 y del lead—,
   pero a 14px y .58 de alfa se leía como pie de página. El clamp deja móvil y
   tablet como estaban (14px) y sube ~10% sólo desde ~1200px, donde el bloque
   de texto tiene sitio de sobra. */
.scene__support {
  max-width: 62ch;
  margin-top: clamp(.1rem, .55vh, .4rem);
  font-size: clamp(.875rem, .244vw + .74rem, .96rem);
  line-height: 1.62;
  color: rgba(242, 245, 250, .70);
}

/* Línea de cierre: la conclusión de la escena, no su encabezado.
   El estado oculto se aplica SÓLO cuando motion.js marca la sección con
   [data-resolve] —es decir, cuando hay pin, hay JS y no hay reduced-motion—,
   de modo que en móvil, sin JS o con movimiento reducido la frase se lee desde
   el principio en lugar de quedarse invisible. Sólo opacidad y transform: el
   elemento conserva su caja, así que el bloque de copy no cambia de altura y el
   campo del caos no se recoloca al revelarse. */
.scene--problema[data-resolve] .scene__resolve {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .75s var(--ease), transform .75s var(--ease);
}
.scene--problema[data-resolve] .scene__resolve.is-resolved {
  opacity: 1;
  transform: none;
}

/* El campo de caos se recorta contra la retícula común, no contra el viewport:
   ningún chip llega a pegarse al borde de la pantalla. */
.chaos { position: relative; min-height: 0; overflow: hidden; pointer-events: none; }
.chaos__field { position: absolute; inset: 0; }

/* ── Núcleo: el punto donde el caos se convierte en sistema ─────────────
   Mismo tratamiento de "portal" que el hero (disco enmascarado + screen), a
   otra escala. motion.js controla transform, opacity y --core-glow. */
.chaos__core {
  position: absolute;
  left: 50%; top: 50%;
  /* +11% sobre el 46vh/460px anterior: el isotipo gana presencia sin invadir
     nada. El límite real no es el viewport sino la fila del caos, que en el
     caso más apretado (1024×768, donde el copy come más proporción) mide
     ~56vh: a 51vh el núcleo final ocupa el 91% de esa fila y conserva margen
     por arriba y por abajo. El halo (inset −22%) se desvanece en el 68% de su
     radio, así que su parte visible sigue cabiendo dentro de la caja. */
  width: min(51vh, 51vw, 515px);
  aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(.58);
  opacity: .3;
  will-change: transform, opacity;
}
/* `--halo-k` recoge la expansión: motion.js la baja a .93 al entrar en el hold
   (−7%) para que el estado final sea un isotipo luminoso y no un isotipo dentro
   de un foco. Es un transform, no `inset`: no provoca layout por fotograma. */
.chaos__halo {
  position: absolute;
  inset: -22%;
  border-radius: 50%;
  background:
    radial-gradient(circle, rgba(38, 108, 169, .38) 0%, rgba(38, 108, 169, .15) 42%, transparent 68%),
    radial-gradient(circle, rgba(173, 225, 251, .10) 0%, transparent 44%);
  opacity: var(--core-glow, .3);
  transform: scale(var(--halo-k, 1));
  will-change: transform, opacity;
}
/* Aro de contención: da jerarquía y estructura al centro sin convertirlo en
   un "sol". Su nitidez sube con el protagonismo del logo. */
.chaos__ring {
  position: absolute;
  inset: 4%;
  border: 1px solid rgba(173, 225, 251, .22);
  border-radius: 50%;
  opacity: calc(var(--core-glow, .3) * .8);
}
.chaos__portal {
  position: absolute;
  inset: 0;
  mask-image: radial-gradient(closest-side, #000 56%, transparent 88%);
  -webkit-mask-image: radial-gradient(closest-side, #000 56%, transparent 88%);
}
.chaos__video,
.chaos__iso {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
}
.chaos__video {
  opacity: 0;
  mix-blend-mode: screen;
  filter: grayscale(1) contrast(1.18) brightness(.84) sepia(1) hue-rotate(176deg) saturate(2.1);
  transition: opacity .8s var(--ease);
}
/* Fallback: isotipo plano hasta que el video reproduce (o de forma permanente
   si falla, con save-data o con reduced-motion). */
.chaos__iso {
  opacity: .9;
  filter: drop-shadow(0 20px 52px rgba(1, 8, 45, .85));
  transition: opacity .8s var(--ease);
}
/* `data-video` lo pone motion.js en el primer `playing`: hasta entonces —y de
   forma permanente si el video falla, con save-data o con reduced-motion—
   manda el isotipo plano. Una vez visible, el video gira en loop durante toda
   la escena; el progreso de scroll no lo detiene ni lo sustituye por un
   fotograma fijo. */
.chaos__core[data-video] .chaos__video { opacity: .88; }
.chaos__core[data-video] .chaos__iso { opacity: 0; }

/* ── Fragmentos ────────────────────────────────────────────────────────
   Deben leerse como tarjetas reales de la operación diaria, no como ruido de
   fondo: fondo propio, borde legible, sombra de contacto y un glow contenido.
   `filter` está en will-change porque el tramo de absorción lo anima. */
.chaos__chip {
  position: absolute;
  left: 50%; top: 50%;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 12px 17px;
  border: 1px solid rgba(173, 225, 251, .30);
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, rgba(17, 42, 108, .94), rgba(4, 27, 80, .96));
  box-shadow:
    0 1px 0 rgba(173, 225, 251, .10) inset,
    0 20px 44px -26px rgba(1, 8, 45, 1),
    0 0 30px -10px rgba(38, 108, 169, .55);
  font-size: .84rem;
  font-weight: 500;
  line-height: 1.35;
  color: rgba(242, 245, 250, .94);
  white-space: nowrap;
  will-change: transform, opacity, filter;
}
/* Punto de estado: presencia visual sin subir el peso del texto */
.chaos__chip::before {
  content: '';
  flex: none;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: rgba(173, 225, 251, .82);
  box-shadow: 0 0 10px rgba(173, 225, 251, .6);
}
.chaos__chip--warn {
  border-color: rgba(255, 186, 130, .46);
  color: rgba(255, 228, 203, .96);
  box-shadow:
    0 1px 0 rgba(255, 214, 178, .12) inset,
    0 20px 44px -26px rgba(1, 8, 45, 1),
    0 0 30px -10px rgba(255, 168, 102, .42);
}
.chaos__chip--warn::before { background: rgba(255, 195, 145, .92); box-shadow: 0 0 10px rgba(255, 168, 102, .65); }
/* "Dim" ya no significa ilegible: sólo un escalón por debajo del resto */
.chaos__chip--dim { color: rgba(242, 245, 250, .78); }
.chaos__chip--dim::before { background: rgba(173, 225, 251, .5); box-shadow: none; }

/* Desde 1200px el campo del caos tiene mucho más sitio del que los fragmentos
   estaban usando: en 1440/1728/1920 se leían pequeños frente al núcleo. Subida
   contenida —siguen siendo fragmentos, no tarjetas— y sólo aquí: móvil y tablet
   se quedan exactamente como estaban.
   font +11.3% · padding 12/17 → 13/19px (+8.3% / +11.8%) · borde .30 → .32
   (+6.7%). La sombra no se toca: ampliarla los convertiría en tarjetas. */
@media (min-width: 1200px) {
  .chaos__chip {
    gap: 10px;
    padding: 13px 19px;
    border-color: rgba(173, 225, 251, .32);
    font-size: .935rem;
  }
  .chaos__chip--warn { border-color: rgba(255, 186, 130, .49); }
}

/* ── 10. 03 Ecosistema ─────────────────────────────────────────────────── */
.scene--eco { background: linear-gradient(180deg, var(--midnight), var(--navy) 58%, var(--midnight)); }

/* El h2 trae su propio salto de línea (dos frases, dos líneas). Con el ancho
   por defecto de .scene__copy (800px) cada frase se partía otra vez y el
   encabezado ocupaba cuatro líneas: 265px del pin en 1440×900, que son los que
   necesitan el anillo y el cierre. Ensanchar la caja —mismo recurso que ya usa
   #problema— respeta el h2 tal cual está escrito y le devuelve al diagrama la
   altura que el nuevo cierre le quita. 1120px = ancho del anillo. */
.scene--eco .scene__copy { max-width: min(100%, 1120px); }

/* overflow:hidden: las líneas de conexión miden cientos de px y no deben
   ampliar el ancho del documento */
.eco { position: relative; display: grid; place-items: center; min-height: 52vh; overflow: hidden; }
.eco__core {
  position: absolute;
  z-index: 2;   /* por encima de las líneas (ver .eco__field) */
  /* +27% sobre el 30vh/230px anterior. El tope real no es el viewport sino el
     anillo: los módulos superior e inferior centrales se apoyan en el eje
     vertical, a 0.43·(altura del campo − alto del módulo) del centro. De ahí el
     tercer término, que traduce esa geometría a CSS —86% de la fila menos el
     alto del módulo y un margen de respeto— para que el núcleo crezca hasta
     donde la composición lo admite y se contenga solo en pantallas bajas en
     lugar de invadir a nadie. Va en `height` (no en `width`) porque el límite
     es vertical; aspect-ratio devuelve el cuadrado. */
  height: min(38vh, 292px, calc(86% - 180px));
  width: auto;
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  opacity: 0;
  will-change: transform, opacity;
}
.eco__core::before {
  content: '';
  position: absolute; inset: -34%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(38, 108, 169, .48), transparent 66%);
}
.eco__core img { position: relative; width: 100%; height: 100%; object-fit: contain; }

/* Caja de referencia del anillo. motion.js posiciona cada módulo en px sobre
   ESTA caja (no sobre el viewport), de modo que el anillo crece con la
   retícula y nunca queda ni diminuto ni recortado. */
/* z-index: la geometría ya deja cada línea fuera del isotipo, esto es sólo la
   red de seguridad que pide el orden fondo → líneas → núcleo. Las líneas viven
   dentro de su tarjeta, así que la única forma de dejarlas por debajo del logo
   sin sacarlas de su módulo es bajar el campo entero; como las tarjetas nunca
   llegan a solaparse con el núcleo (holgura mínima medida: 18px), el reparto no
   cambia un solo píxel de la composición. */
.eco__field { position: relative; z-index: 1; width: 100%; height: 100%; min-height: 52vh; max-width: 1120px; }
.eco__mod {
  position: absolute;
  left: 50%; top: 50%;
  padding: 13px 17px;
  /* El borde es lo único que dibuja la pieza sobre el navy: a .22 se perdía
     contra el fondo en pantallas grandes. .38 lo hace legible sin convertirlo
     en un botón —sigue siendo 1px de la propia paleta Sky, sin glow— y deja
     sitio por encima para la jerarquía de CRM (.52), MAIA (.58) y el estado
     [data-next] (.74), que es el que enlaza con la sección siguiente. */
  border: 1px solid rgba(173, 225, 251, .38);
  border-radius: 14px;
  background: rgba(1, 8, 45, .88);
  transition: border-color .5s var(--ease), box-shadow .5s var(--ease);
  will-change: transform, opacity;
}
/* Línea de conexión módulo → núcleo. Vive dentro del propio módulo, así que
   siempre apunta al centro aunque el módulo se mueva.
   La longitud geométrica (--len0, distancia centro-módulo → centro-núcleo) es
   fija y sólo se recalcula al medir; lo que cambia por fotograma es --link-k,
   el factor de scaleX que recorta la línea justo en el perímetro del isotipo.
   Así el recorrido sigue resolviéndose con transform y opacity, sin escribir
   `width` ocho veces por frame.
   --link-rot compensa la pequeña rotación de entrada de la tarjeta: la línea
   vive dentro del módulo y heredaría ese giro, quedando desviada del núcleo.
   Por defecto scaleX(0): sin JS no aparece una línea a longitud completa. */
.eco__mod::before {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: var(--len0, 0px);
  height: 1px;
  background: linear-gradient(90deg, rgba(173, 225, 251, .05), rgba(173, 225, 251, .5));
  transform-origin: 0 0;
  transform: rotate(calc(var(--ang, 0deg) + var(--link-rot, 0deg))) scaleX(var(--link-k, 0));
  opacity: var(--link-o, 0);
  transition: opacity .4s var(--ease);
  pointer-events: none;
}
.eco__mod b { display: block; position: relative; font-family: var(--font-display); font-size: 1rem; font-weight: 500; color: #fff; }
.eco__mod i { display: block; position: relative; margin-top: 3px; font-size: .79rem; font-style: normal; color: var(--text-3); }
/* Los dos módulos con jerarquía propia suben en la misma proporción que la
   base, de modo que la escalera .38 → .52 → .58 → .74 se conserva. Valor
   literal y no var(--line-live): el token es global (nav, MAIA, CAPI) y no
   debe moverse por un ajuste de esta sección. */
.eco__mod--ai { border-color: rgba(173, 225, 251, .58); }
.eco__mod--ai b { color: var(--sky); }
.eco__mod--crm { border-color: rgba(173, 225, 251, .52); }
/* Continuidad ecosistema → CRM: en el tramo final de la escena el módulo CRM
   se adelanta visualmente al panel de la sección siguiente. */
.eco__mod[data-next] { border-color: rgba(173, 225, 251, .74); box-shadow: 0 0 0 1px rgba(173, 225, 251, .18), 0 20px 46px -24px rgba(38, 108, 169, .9); }

/* ── Cierre de la escena ────────────────────────────────────────────────
   Misma familia visual que los h2 (la clase .h2 aporta display, peso,
   line-height y tracking), pero a una escala menor: a tamaño completo de h2
   esta frase ocuparía cuatro líneas y le comería al diagrama la fila que
   necesita para desplegar el anillo dentro del pin. El clamp comparte la forma
   del token --h2 (vw acotado por vh) para que en pantallas bajas encoja antes
   que el diagrama.
   El estado inicial oculto sólo existe bajo [data-statement], que motion.js
   pone únicamente cuando hay pin: sin JS, en móvil o con reduced-motion la
   frase se lee siempre. */
.eco__statement {
  /* 1000px: en pantallas de 768px de alto la frase entra en una sola línea y le
     devuelve ~25px de fila al anillo, que es justo lo que allí escasea. */
  max-width: min(100%, 1000px);
  margin-inline: auto;
  font-size: clamp(1.05rem, min(1.95vw, 2.7vh), 1.7rem);
  line-height: 1.24;
  color: rgba(242, 245, 250, .92);
}
.scene--eco[data-statement] .eco__statement {
  opacity: 0;
  transform: translateY(18px);
}

/* ── 11. 04 CRM ────────────────────────────────────────────────────────── */
.scene--crm { background: var(--midnight); }

/* El copy del CRM no se queda en los 800px de .scene__copy: usa la misma caja
   que el panel para que título y descripción quepan en una sola franja. Sólo
   aquí — .scene__copy sigue intacta para las otras diez escenas. */
.scene--crm .scene__copy { max-width: 100%; }

/* Título + descripción. Apilados por defecto (móvil y tablet); en dos
   columnas desde 1200px, ver el bloque de ese breakpoint. */
.crm__head { display: grid; gap: clamp(.6rem, 1.4vh, 1rem); }

.panel {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(180deg, rgba(4, 29, 86, .6), rgba(1, 8, 45, .92));
  overflow: hidden;
  will-change: transform, opacity;
}
/* El panel no se estira hasta el borde inferior de la escena: se centra en el
   espacio disponible con una altura acotada, de modo que título y panel se
   leen como una sola composición y no queda una franja muerta debajo. */
@media (min-width: 768px) {
  /* height (no max-height): el panel necesita altura definida para que la
     fila 1fr de su propia rejilla —las seis columnas— tenga contra qué
     resolverse.
     align-self: start (antes center): al quitar el takeaway y pasar la
     descripción junto al título sobra alto, y centrado abría una franja
     muerta entre el mensaje y la tabla —215px en 2560×1440—. Pegado al
     arranque de su fila, el pipeline queda a la distancia del gap del pin.
     El techo sube de 620 a 780px para que ese alto recuperado lo absorba la
     tabla y no un hueco. */
  .panel { align-self: start; height: min(100%, 780px); }
}
.panel__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 14px clamp(16px, 1.4vw, 22px);
  border-bottom: 1px solid var(--line-soft);
}
.panel__status { font-family: var(--font-mono); font-size: .75rem; letter-spacing: .08em; color: var(--sky); }
.panel__note {
  padding: 0 clamp(16px, 1.4vw, 22px) 14px;
  font-family: var(--font-mono);
  font-size: .6875rem;
  color: var(--text-4);
}

/* Pipeline — móvil: stepper vertical */
.pipe {
  --pipe-axis: y;
  position: relative;
  display: grid;
  gap: 8px;
  padding: 16px;
}
.pipe__stage {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 10px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  background: rgba(1, 8, 45, .5);
  transition: border-color .5s var(--ease), background .5s var(--ease);
}
.pipe__stage[data-active] { border-color: var(--line-live); background: rgba(38, 108, 169, .18); }
.pipe__num { font-family: var(--font-mono); font-size: .6875rem; color: var(--text-4); }
.pipe__name { font-size: .8125rem; font-weight: 500; color: var(--text-2); line-height: 1.3; }
.pipe__stage[data-active] .pipe__name { color: #fff; }

/* Capturas reales de las columnas "Nuevos Leads" y "En contacto". En escritorio
   NO son un adorno dentro de las etapas 01 y 02: SON la etapa. Se posicionan en
   absoluto, así que no aportan un solo píxel de alto ni de ancho al pipeline
   —la tabla, las columnas y las otras cuatro etapas conservan su geometría
   exacta— y se quedan quietas mientras [data-pipe-card] recorre las seis
   columnas.
   Móvil: la etapa es una fila de 52px con el rótulo en horizontal y la tarjeta
   ocupando el 62% derecho. No hay hueco donde meter una columna de ~725x2170 sin
   crecer o tapar el rótulo, así que ahí no se pinta. */
.pipe__stage-media { display: none; }

/* La tarjeta móvil es el marco que recibe el transform de motion.js: sólo
   posición y tamaño. El fondo, el radio y el borde ya vienen dentro de la
   captura, así que aquí no se duplica ninguno de los tres. */
.pipe__card {
  position: absolute;
  left: 16px;
  top: 16px;
  width: calc(100% - 32px);
  display: block;
  padding: 0;
  will-change: transform;
}
.pipe__card-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* drop-shadow sigue el alfa del PNG: la sombra respeta sus esquinas
     redondeadas en lugar de dibujar un rectángulo por detrás. */
  filter: drop-shadow(0 14px 26px rgba(0, 0, 0, .55));
}

/* Móvil: la captura conserva su proporción y se alinea a la derecha del
   stepper, con el alto que cabe entre la etapa y el pie del panel durante
   todo el recorrido. */
@media (max-width: 767.98px) {
  .pipe__card {
    left: auto;
    right: 16px;
    width: 62%;
    height: clamp(68px, 25vw, 100px);
  }
  .pipe__card-image { width: auto; margin-left: auto; }
}

@media (min-width: 768px) {
  /* Las seis etapas son columnas reales que llenan el alto del panel: antes
     quedaban como una fila de 60px sobre un vacío del 70% del panel. */
  .pipe {
    --pipe-axis: x;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: clamp(6px, .7vw, 12px);
    /* Sólo el padding vertical crece con el panel; el horizontal se queda
       igual porque de él dependen el ancho de columna y el paso del recorrido. */
    padding: clamp(14px, 2.4vh, 24px) clamp(12px, 1.2vw, 18px);
    align-content: stretch;
  }
  .pipe__stage {
    display: grid;
    gap: 6px;
    align-content: start;
    min-height: 0;
    padding: 13px 12px;
  }
  .pipe__name { font-size: .78rem; }
  .pipe__num { font-size: .72rem; }

  /* ── Etapas 01 y 02: la captura ES la etapa ───────────────────────────
     La ficha deja de dibujarse y en su hueco se pinta la captura entera. Sólo
     las dos primeras: las otras cuatro siguen sin `position`, sin capa y sin un
     solo cambio.

     La caja NO se mueve. Con `box-sizing: border-box` (styles.css:87) anular el
     padding y el color del borde no altera ni un píxel del rect —el borde sigue
     ocupando su 1px, sólo que transparente—. Importa porque motion.js deriva el
     paso del recorrido de `stages[0]` y `stages[1]` (motion.js:619-620): si el
     rect de cualquiera de las dos cambiara, la tarjeta recorrería el pipeline
     descuadrada. El alto de la fila lo siguen fijando las etapas 03-06, que
     conservan sus rótulos en flujo. */
  .pipe__stage[data-stage="0"],
  .pipe__stage[data-stage="1"] {
    position: relative;
    padding: 0;
    border-color: transparent;
    background: none;
  }
  /* El estado activo tampoco se pinta aquí: un borde encendido alrededor de un
     screenshot se lee como un fallo, no como un realce. El latido del recorrido
     lo siguen dando la tarjeta que pasa por encima y el [data-pipe-status]. */
  .pipe__stage[data-stage="0"][data-active],
  .pipe__stage[data-stage="1"][data-active] {
    border-color: transparent;
    background: none;
  }
  /* "01"/"Lead" y "02"/"Contacto efectivo" salen de la vista pero NO del árbol
     de accesibilidad: el <ol> conserva sus seis etapas con su rótulo. Son las
     declaraciones exactas de .u-visually-hidden (:145-151). Al quedar en
     absoluto no ocupan fila de la rejilla, así que no empujan la captura ni un
     píxel hacia abajo. */
  .pipe__stage[data-stage="0"] > .pipe__num,
  .pipe__stage[data-stage="0"] > .pipe__name,
  .pipe__stage[data-stage="1"] > .pipe__num,
  .pipe__stage[data-stage="1"] > .pipe__name {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; white-space: nowrap;
    clip-path: inset(50%);
  }
  .pipe__stage-media {
    display: block;
    position: absolute;
    inset: 0;   /* la capa ya no es una fila de la rejilla: es la etapa entera */
  }
  .pipe__stage-image {
    display: block;
    width: 100%;
    height: 100%;
    /* `contain` y no `cover`: la captura tiene que verse COMPLETA. Los dos PNG
       miden prácticamente lo mismo —725x2170 (AR 0,334) y 724x2172 (AR 0,333)—
       y la etapa es más ancha en proporción (206,8x490,6 = AR 0,422 en 1440px),
       así que la imagen se ajusta por ALTO —ocupa los 490,6px enteros— y quedan
       ~21px de aire a cada lado. Ese aire es geometría pura: con 0,333 contra
       0,422 la única forma de eliminarlo sería recortar a lo ancho o deformar, y
       las dos están descartadas.
       Ojo: `contain` ajusta el LIENZO. Si el PNG trae padding transparente hay
       que compensarlo aparte —ver el bloque siguiente—, porque si no la columna
       se pinta más chica de lo que le toca. */
    object-fit: contain;
    object-position: center;
  }

  /* ── Padding de las capturas ──────────────────────────────────────────
     Las dos capturas en uso son RGB y full-bleed: la columna ocupa el lienzo
     entero —725x2170 en la etapa 01 y 865x1818 en la 02, medido: 96,2% del
     ancho y 99,5% del alto—, así que `object-fit: contain` ajusta directamente
     lo que se ve y NO hace falta compensar nada.

     Ojo si se retoman los otros cuatro `crm-*.png`: ésos son RGBA y traen
     padding transparente alrededor de la columna (en el antiguo asset de la
     etapa 02 la columna era 592x2147 dentro de 724x2172, el 81,8% del ancho).
     Con padding, `contain` ajusta el LIENZO y la columna se pinta más chica de
     lo que le toca; hay que escalar por (alto del lienzo / alto de la columna),
     que es un escalado uniforme —ni recorta ni deforma—. El factor se mide por
     asset y se le añade su propia línea. */
  .pipe__card {
    left: clamp(12px, 1.2vw, 18px);
    /* Por debajo del rótulo de etapa incluso cuando éste ocupa dos líneas
       (columnas estrechas de tablet) — ese es el suelo del max().
       El 38% es lo que reparte el aire: al crecer la tabla, un top fijo dejaba
       la tarjeta pegada al rótulo con 300px muertos debajo. En porcentaje del
       alto del pipe la tarjeta baja con la columna y conserva aire arriba y
       abajo. Es sólo CSS: motion.js escribe transform, nunca top. */
    top: max(clamp(78px, 8.5vh, 94px), 38%);
    width: calc((100% - 2 * clamp(12px, 1.2vw, 18px) - 5 * clamp(6px, .7vw, 12px)) / 6);
    /* El ancho lo sigue fijando la columna; el alto lo deriva la proporción
       3:2 de la captura, así ocupa la columna entera sin recorte ni
       deformación. Cabe de sobra: la etapa mide 300-500 px de alto. */
    aspect-ratio: 3 / 2;
  }
}

/* ── 12. 05 MAIA ───────────────────────────────────────────────────────── */
.scene--maia { background: linear-gradient(180deg, var(--midnight), var(--navy) 52%, var(--midnight)); }

.maia { display: grid; gap: clamp(.9rem, 2.2vh, 1.4rem); justify-items: center; }
.maia__core {
  position: relative;
  width: min(25vh, 195px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid rgba(173, 225, 251, .28);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(38, 108, 169, .5), rgba(1, 8, 45, .9) 72%);
  will-change: transform;
}
.maia__ring {
  position: absolute; inset: 0;
  border: 1px solid rgba(173, 225, 251, .38);
  border-radius: 50%;
  animation: mbRing 3.6s ease-out infinite;
}
.maia__ring--b { border-color: rgba(173, 225, 251, .22); animation-delay: 1.8s; }
.maia__wave { display: flex; align-items: center; gap: 6px; height: 46px; }
.maia__wave i { width: 3px; height: 40px; border-radius: 2px; background: var(--sky); animation: mbWave 1.5s ease-in-out infinite; }
.maia__wave i:nth-child(2) { animation-delay: .18s; }
.maia__wave i:nth-child(3) { animation-delay: .36s; }
.maia__wave i:nth-child(4) { animation-delay: .54s; }
.maia__wave i:nth-child(5) { animation-delay: .72s; }

.timeline { display: grid; gap: clamp(8px, 1.2vh, 11px); width: min(100%, 640px); }
.timeline__row {
  display: grid;
  grid-template-columns: 68px 1fr;
  align-items: center;
  gap: 16px;
  padding: clamp(12px, 1.7vh, 15px) 18px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  background: rgba(1, 8, 45, .6);
  opacity: .48;
  transition: opacity .45s var(--ease), border-color .45s var(--ease), background .45s var(--ease),
              box-shadow .45s var(--ease);
}
.timeline__row[data-on] { opacity: 1; border-color: rgba(173, 225, 251, .36); }
.timeline__row[data-current] { background: rgba(38, 108, 169, .22); }
/* Continuidad MAIA → CAPI: la última fila (cita sincronizada al CRM) queda
   marcada como señal, que es justo lo que la siguiente sección envía a Meta. */
.timeline__row[data-signal] {
  border-color: var(--line-live);
  background: rgba(38, 108, 169, .26);
  box-shadow: 0 0 0 1px rgba(173, 225, 251, .16);
}
.timeline__t { font-family: var(--font-mono); font-size: .78rem; color: var(--sky); }
.timeline__t--none { color: var(--text-4); }
.timeline__x { font-size: .92rem; color: var(--text-2); }

/* ── 13. 06 Meta CAPI ──────────────────────────────────────────────────── */
.scene--capi { background: var(--midnight); }

/* Columna de señales: recorrido · etiqueta técnica · letra pequeña. Van juntos
   en un solo hijo del .split para que ese orden de lectura se conserve tanto en
   dos columnas como apilado, y para que el disclaimer quede debajo de lo que
   matiza y no antes de la lista. */
.capi__signals { display: grid; align-content: center; gap: clamp(.8rem, 1.9vh, 1.25rem); }

.events {
  /* El hueco entre filas es también el alto del conector: una sola variable
     mantiene sincronizadas rejilla y línea. Con cuatro señales —antes seis— la
     lista pide ese aire de más, que es justo lo que hace legible el recorrido.
     --ev-rail alinea el conector con el inicio del nombre del evento. */
  --ev-gap: clamp(15px, 2.2vh, 24px);
  --ev-rail: calc(clamp(17px, 1.5vw, 22px) + 7px);
  display: grid;
  gap: var(--ev-gap);
}
.events__row {
  position: relative;   /* ancla del conector */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: clamp(14px, 2vh, 19px) clamp(17px, 1.5vw, 22px);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  background: rgba(4, 29, 86, .45);
  opacity: .5;
  transform: translateX(-10px);
  transition: opacity .42s var(--ease), transform .42s var(--ease),
              border-color .42s var(--ease), background .42s var(--ease);
  will-change: transform, opacity;
}
.events__row[data-on] { opacity: 1; transform: none; border-color: rgba(173, 225, 251, .3); }

/* Conector Lead → Contact → Schedule → Purchase.
   Misma gramática que el recorrido del hero (.hero__orbit-step): línea de 1px
   que ocupa el hueco y punta de flecha girada, ambas dibujadas con bordes —sin
   iconos, assets ni caracteres. Cada conector pertenece a la fila a la que
   apunta, así que enciende y se apaga con ella y la secuencia se lee en los dos
   sentidos del scroll. */
.events__row + .events__row::before,
.events__row + .events__row::after {
  content: "";
  position: absolute;
  left: var(--ev-rail);
  pointer-events: none;
  opacity: 0;
  transition: opacity .42s var(--ease), background .42s var(--ease),
              border-color .42s var(--ease);
}
.events__row + .events__row::before {
  bottom: 100%;
  width: 1px;
  height: var(--ev-gap);
  transform: translateX(-50%);
  background: linear-gradient(to bottom, rgba(173, 225, 251, .08), var(--line-live));
}
.events__row + .events__row::after {
  bottom: calc(100% + 2px);
  width: 6px; height: 6px;
  border-right: 1px solid var(--line-live);
  border-bottom: 1px solid var(--line-live);
  transform: translate(-50%, 0) rotate(45deg);
}
.events__row + .events__row[data-on]::before,
.events__row + .events__row[data-on]::after { opacity: 1; }

/* Señal profunda (Schedule y Purchase, marcadas por motion.js). El anillo es el
   mismo recurso con el que MAIA marca su fila-señal: "esto vale más" se dice
   igual en toda la landing. El tramo de conector que entra en ellas sube a sky,
   de modo que la progresión también se ve ganar profundidad.
   Van después del bloque anterior: misma especificidad, gana el orden. */
.events__row[data-deep] {
  border-color: var(--line-live);
  background: rgba(38, 108, 169, .28);
  box-shadow: 0 0 0 1px rgba(173, 225, 251, .16);
}
.events__row[data-deep] .events__name { color: var(--sky); }
.events__row + .events__row[data-deep]::before {
  background: linear-gradient(to bottom, rgba(173, 225, 251, .12), var(--sky));
}
.events__row + .events__row[data-deep]::after { border-color: var(--sky); }

.events__name { font-family: var(--font-mono); font-size: .9rem; color: #fff; }
.events__stage { font-size: .82rem; color: var(--text-3); text-align: right; }

/* Etiqueta técnica de cierre: mismo registro que .panel__status (mono, sky,
   tracking corto). Nombra el mecanismo una vez explicado el beneficio; no es
   heading ni botón, y su cuerpo queda por debajo del .lead. El guion de entrada
   la presenta como rótulo del bloque, igual que el punto presenta al eyebrow. */
.capi__tech {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: .75rem;
  letter-spacing: .08em;
  color: var(--sky);
}
.capi__tech::before {
  content: "";
  flex: none;
  width: clamp(14px, 2vw, 26px);
  height: 1px;
  background: var(--line-live);
}

/* ── 14. 07 Propiedades + placeholders ─────────────────────────────────── */
.block--propiedades { background: linear-gradient(180deg, var(--midnight), rgba(4, 29, 86, .55)); }
.block--propiedades .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.6rem); }

.prop { display: grid; gap: clamp(1.5rem, 3.2vw, 2.75rem); align-items: center; }
/* 56 / 44 — misma proporción que el resto de composiciones partidas.
   align-items: start desde aquí porque la columna visual pasó a ser bastante
   más alta que la lista al entrar las capturas reales: centrada, la lista
   flotaba suelta en mitad del hueco; arriba comparte línea de salida con la
   galería y las dos columnas arrancan juntas. */
@media (min-width: 900px) {
  .prop { grid-template-columns: minmax(0, 56fr) minmax(0, 44fr); align-items: start; }
}

.feature-list { display: grid; gap: clamp(15px, 2vh, 20px); }
.feature-list li {
  display: grid;
  gap: 5px;
  padding-left: 20px;
  border-left: 1px solid var(--line);
  font-size: .92rem;
  line-height: 1.6;
  color: var(--text-3);
}
.feature-list b { font-family: var(--font-display); font-size: 1.05rem; font-weight: 500; color: #fff; }

/* ── Prueba de producto real (Propiedades) ────────────────────────────────
   Capturas reales de la plataforma. Es lo contrario de .shot: allí la caja es
   el dibujo, aquí la caja sólo enmarca y la protagonista es la imagen. De ahí
   que no comparta ni una clase con .shot —que ya sólo queda en uso en
   Comunidad— y que no lleve ningún filtro de color: un screenshot
   recoloreado deja de leerse como screenshot.

   Hasta 1200px las dos capturas van apiladas a ancho completo: es la única
   forma de que el detalle —1320px de interfaz densa— siga siendo legible
   cuando la columna visual es estrecha. La composición superpuesta se reserva
   para escritorio ancho, donde sí hay píxeles que gastar. */
.proof { display: grid; gap: 12px; }
.proof__stage { display: grid; gap: clamp(14px, 2.4vw, 20px); }
.proof__main,
.proof__aside {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;   /* el recorte es visual: los PNG no se editan */
  box-shadow: 0 14px 34px rgba(1, 8, 45, .5);
}
/* Sin object-fit ni alto forzado: la imagen manda su propia proporción. */
.proof__main img,
.proof__aside img { width: 100%; height: auto; }
.proof__caption { font-size: .78rem; line-height: 1.55; color: var(--text-4); }

/* Desde 900px la sección ya es de dos columnas y la visual se estrecha: el
   detalle se recorta a la altura del arranque de Amenidades (encabezado y
   pestañas incluidos) para que descripción y características se vean enteras
   sin que la pieza secundaria acabe pesando más que la galería. */
@media (min-width: 900px) {
  .proof__aside img {
    aspect-ratio: var(--proof-aside-ar, 1.32);
    object-fit: cover;
    object-position: 50% 0;   /* ancla arriba: Descripción → Características */
  }
}

@media (min-width: 1200px) {
  .proof {
    --proof-aside-ar: 1.32;
    /* El ancho del detalle se fija en píxeles y sólo se acota en porcentaje.
       Así su ESCALA de reproducción (≈35% del PNG) se mantiene constante de
       1200 a 2560px: es lo que decide si la interfaz se lee o no. Los topes en
       % son los que conservan la jerarquía: nunca menos del 58% ni más del
       78% del ancho de la galería. */
    --proof-aside-w: clamp(58%, 470px, 78%);
    /* La galería termina en 32px de blanco bajo las miniaturas (3,04% de su
       alto = 1,67% del ancho de la composición). Con 1,4% el detalle se monta
       sobre ese margen muerto y sobre el borde del marco, pero no tapa ni una
       miniatura. */
    --proof-overlap: 1.4%;
  }
  .proof__stage {
    position: relative;
    display: block;
    /* Reserva exactamente lo que el detalle sobresale por debajo de la
       galería, de modo que nunca pisa el pie de foto ni la sección siguiente. */
    padding-bottom: calc(var(--proof-aside-w) / var(--proof-aside-ar) - var(--proof-overlap));
  }
  .proof__aside {
    position: absolute;
    right: 0;
    bottom: 0;
    width: var(--proof-aside-w);
    /* Segunda capa: se despega de la galería con una línea algo más viva y una
       sombra un punto más profunda. Nada de 3D, marcos de dispositivo ni
       perspectiva: sólo profundidad. */
    border-color: var(--line-live);
    box-shadow: 0 22px 50px rgba(1, 8, 45, .62);
    /* Micro-motion: entra 12px por detrás de la galería con la curva y la
       duración del reveal del sitio. Cuelga del .is-in que ya pone motion.js
       en la figura, así que no añade observadores ni JS. */
    transform: translateY(12px);
    transition: transform .62s var(--ease);
  }
  .proof.is-in .proof__aside { transform: none; }
}

/* Composición conceptual que sustituye a una captura real todavía no entregada.
   No imita una captura del producto: es deliberadamente abstracta. */
.shot { display: grid; gap: 12px; }
.shot__frame {
  aspect-ratio: 16 / 10;
  padding: clamp(14px, 2vw, 22px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(165deg, rgba(4, 29, 86, .6), rgba(1, 8, 45, .9));
  overflow: hidden;
}
.shot--wide .shot__frame { aspect-ratio: 16 / 8; }
/* Columna flexible: el bloque principal (rejilla o tarjetas) se queda con el
   espacio sobrante y las barras conservan su altura. Con el reparto uniforme
   anterior quedaban franjas vacías dentro del propio marco. */
.shot__ui { display: flex; flex-direction: column; gap: 14px; height: 100%; }
.shot__ui > * { flex: 0 0 auto; }
.shot__ui > .shot__ui-grid,
.shot__ui > .shot__ui-cards { flex: 1 1 auto; min-height: 0; }
.shot__ui-bar { height: 10px; border-radius: 4px; background: rgba(173, 225, 251, .18); }
.shot__ui-grid { display: grid; grid-template-columns: 1.1fr 1fr; gap: 12px; min-height: 0; }
.shot__ui-block {
  border-radius: 10px;
  border: 1px solid var(--line-soft);
  background:
    repeating-linear-gradient(135deg, rgba(173, 225, 251, .08) 0 9px, transparent 9px 18px),
    rgba(1, 8, 45, .5);
}
.shot__ui-block--map { background: radial-gradient(circle at 50% 50%, rgba(38, 108, 169, .3), transparent 62%), rgba(1, 8, 45, .5); }
.shot__ui-lines { display: grid; gap: 11px; align-content: start; min-height: 0; }
.shot__ui-lines i { height: 9px; border-radius: 4px; background: rgba(173, 225, 251, .14); }
.shot__ui-lines i.is-short { width: 55%; }
/* min-height explícito: como filas de una columna flex ya no reciben reparto
   del espacio libre, sin altura propia colapsarían a 0 y desaparecerían. */
.shot__ui-row { display: grid; grid-auto-flow: column; gap: 10px; min-height: 30px; max-height: 34px; }
.shot__ui-row span { border-radius: 8px; background: rgba(173, 225, 251, .1); }
.shot__ui-row--filters { min-height: 22px; max-height: 22px; }
.shot__ui-row--filters span { border-radius: 999px; }
.shot__ui-cards { display: grid; grid-auto-flow: column; gap: 12px; min-height: 84px; }
/* Al ampliarse los marcos, las tarjetas conceptuales necesitan estructura
   interna propia: sin ella quedaban como rectángulos vacíos grandes. */
.shot__ui-cards span {
  position: relative;
  border-radius: 10px;
  border: 1px solid var(--line-soft);
  background: rgba(173, 225, 251, .07);
  overflow: hidden;
}
.shot__ui-cards span::before {
  content: '';
  position: absolute;
  inset: 9px 9px auto;
  height: min(54%, 190px);
  border-radius: 7px;
  background: rgba(173, 225, 251, .09);
}
.shot__ui-cards span::after {
  content: '';
  position: absolute;
  left: 9px; right: 42%; bottom: 12px;
  height: 6px;
  border-radius: 3px;
  background: rgba(173, 225, 251, .16);
}

.shot__caption { font-size: .78rem; line-height: 1.55; color: var(--text-4); }
.shot__tag {
  display: block;
  margin-bottom: 4px;
  font-family: var(--font-mono);
  font-size: .625rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--sky);
}

/* ── 15. 08 Portal ─────────────────────────────────────────────────────── */
.block--portal { background: rgba(4, 29, 86, .55); }
.block--portal .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.6rem); }

/* La sección ya no se parte en dos columnas: la composición de capturas ocupa
   el ancho completo y el recorrido la cierra por debajo. */
.portal { display: grid; gap: clamp(1.5rem, 3.2vw, 2.75rem); }

/* ── Prueba de producto real (Portal) ─────────────────────────────────────
   Mismo criterio que .proof en Propiedades —el marco sólo enmarca, la imagen
   manda, sin filtros de color ni marcos de dispositivo—, pero con su propia
   composición: aquí las dos capturas son casi cuadradas (1908×1846 y
   1898×1828), así que la superposición absoluta de .proof no sirve y la pareja
   se resuelve en dos columnas.

   Hasta 1024px van apiladas a ancho completo: por debajo de eso una columna no
   da para que una interfaz de 1900px de ancho conserve ninguna estructura. */
.portal-proof { display: grid; gap: 12px; }
.portal-proof__stage { display: grid; gap: clamp(14px, 2.4vw, 22px); }
.portal-proof__main,
.portal-proof__aside {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;   /* el recorte es visual: los PNG no se editan */
  box-shadow: 0 14px 34px rgba(1, 8, 45, .5);
}
.portal-proof__main img,
.portal-proof__aside img { display: block; width: 100%; height: auto; }
.portal-proof__caption { font-size: .78rem; line-height: 1.55; color: var(--text-4); }

@media (min-width: 1024px) {
  /* 58 / 42: el catálogo manda. No es sólo ancho —también se ve más alto—,
     así que la jerarquía se lee sin necesidad de ningún adorno. */
  .portal-proof__stage {
    grid-template-columns: minmax(0, 58fr) minmax(0, 42fr);
    align-items: start;
  }
  /* Recortes calculados sobre el PNG, no a ojo:
     · catálogo 1.48 ⇒ se ve el 69,8% superior = barra de filtros, contador y
       las dos primeras filas completas de fichas;
     · detalle 1.22 ⇒ se ve el 85,1% superior = título, precio, galería,
       miniaturas, descripción y el bloque de contacto con "Solicitar
       información". El corte cae en una banda limpia: ni parte la fila de
       iconos de características ni deja campos del formulario a medias. */
  .portal-proof__main img {
    aspect-ratio: 1.48;
    object-fit: cover;
    object-position: 50% 0;
  }
  .portal-proof__aside img {
    aspect-ratio: 1.22;
    object-fit: cover;
    object-position: 50% 0;
  }
  /* Pieza de apoyo: descolgada, con la línea algo más viva y algo más de
     sombra. Nada de 3D ni perspectiva, sólo profundidad y segundo plano. */
  .portal-proof__aside {
    margin-top: clamp(18px, 3vw, 44px);
    border-color: var(--line-live);
    box-shadow: 0 22px 50px rgba(1, 8, 45, .62);
    /* Micro-motion: entra 12px por detrás del catálogo, con la curva y la
       duración del reveal del sitio. Cuelga del .is-in que ya pone motion.js
       en la figura, así que no añade observadores ni JS. */
    transform: translateY(12px);
    transition: transform .62s var(--ease);
  }
  .portal-proof.is-in .portal-proof__aside { transform: none; }
}

/* ── Recorrido: publicación → interesado → CRM → seguimiento ──────────────
   Misma gramática que el recorrido de Integración (.flow__step): ficha de
   1px, mono en versalitas y la flecha como conector del CSS. Aquí es un <ol>
   porque el orden es el contenido, no decoración. */
.portal-flow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 0;
  list-style: none;
}
.portal-flow__item {
  padding: 11px 17px;
  border: 1px solid rgba(173, 225, 251, .2);
  border-radius: 8px;
  background: rgba(4, 29, 86, .6);
  font-family: var(--font-mono);
  font-size: clamp(.6875rem, 1.1vw, .8125rem);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-2);
}
/* El último paso es el que cierra la promesa de la sección. */
.portal-flow__item:last-child { border-color: var(--line-live); background: rgba(38, 108, 169, .3); color: var(--sky); }
.portal-flow__item + .portal-flow__item::before {
  content: '→';
  margin-right: 12px;
  margin-left: -12px;
  color: rgba(173, 225, 251, .55);
}

/* ── 16. 09 Estimador ──────────────────────────────────────────────────── */
.block--estimador { background: linear-gradient(180deg, rgba(4, 29, 86, .55), var(--midnight)); }
.block--estimador .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.6rem); }
.split--wide { align-items: center; }
@media (min-width: 900px) { .split--wide { grid-template-columns: minmax(0, 44fr) minmax(0, 56fr); } }

.est {
  display: grid;
  gap: clamp(1.25rem, 2.6vw, 1.9rem);
  padding: clamp(1.25rem, 2.2vw, 1.9rem);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.est__map {
  position: relative;
  aspect-ratio: 3 / 2;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-soft);
  background:
    linear-gradient(rgba(173, 225, 251, .07) 1px, transparent 1px) 0 0 / 34px 34px,
    linear-gradient(90deg, rgba(173, 225, 251, .07) 1px, transparent 1px) 0 0 / 34px 34px,
    rgba(1, 8, 45, .5);
  overflow: hidden;
}
.est__radius {
  position: absolute;
  left: 50%; top: 50%;
  width: var(--r, 46%);
  aspect-ratio: 1;
  border: 1px solid var(--line-live);
  border-radius: 50%;
  background: radial-gradient(circle, rgba(38, 108, 169, .22), transparent 70%);
  transform: translate(-50%, -50%);
  transition: width .45s var(--ease);
}
.est__pin {
  position: absolute;
  left: 50%; top: 50%;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--sky);
  box-shadow: 0 0 0 4px rgba(173, 225, 251, .2);
  transform: translate(-50%, -50%);
}
.est__dots { position: absolute; inset: 0; }
.est__dots b {
  position: absolute;
  width: 7px; height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: rgba(173, 225, 251, .85);
  transition: opacity .35s var(--ease), transform .35s var(--ease);
}
.est__dots b[data-out] { opacity: .18; transform: scale(.7); }

.est__controls { display: grid; gap: 18px; }
.est__field { display: grid; grid-template-columns: 1fr auto; gap: 6px 12px; align-items: center; }
.est__field label { font-size: .875rem; color: var(--text-2); }
.est__field output {
  font-family: var(--font-mono);
  font-size: .875rem;
  color: var(--sky);
  text-align: right;
  min-width: 4ch;
}
.est__field input[type="range"] {
  grid-column: 1 / -1;
  width: 100%;
  height: 28px;
  margin: 0;
  background: transparent;
  accent-color: var(--broker);
  cursor: pointer;
}
.est__readout {
  display: grid;
  gap: 5px;
  padding: 15px 17px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: rgba(1, 8, 45, .55);
}
.est__readout-state { font-family: var(--font-display); font-size: 1.05rem; font-weight: 500; color: var(--sky); }
.est__readout[data-insufficient] { border-color: rgba(255, 186, 130, .38); }
.est__readout[data-insufficient] .est__readout-state { color: rgba(255, 214, 178, .98); }
.est__readout-detail { font-size: .875rem; color: var(--text-3); }
.est__note { font-size: .78rem; line-height: 1.55; color: var(--text-4); }

/* ── Prueba de producto real (Estimador) ──────────────────────────────────
   Mismo criterio que .proof y .portal-proof: el marco sólo enmarca, la captura
   manda y no lleva ni un filtro de color —un screenshot recoloreado deja de
   leerse como screenshot—. No comparte ni una clase con .shot, que sigue en
   uso en Comunidad.

   Lo que cambia aquí es la jerarquía, porque las tres páginas NO valen lo
   mismo. El orden de lectura es el argumento comercial:
   · valoración    → hero. El resultado del análisis.
   · portada       → contexto. Esto sale de un reporte real.
   · recomendación → cierre. El reporte además interpreta.
   De ahí que la valoración vaya sola y a ancho completo, y las otras dos
   debajo, más pequeñas. */
.est-report { display: grid; gap: clamp(1.1rem, 2.2vw, 1.75rem); }

/* Transición entre el estimador interactivo y la prueba real. Va en <h3> y con
   --h3: por debajo del H2 de la sección, que sigue mandando. */
.est-report__intro { display: grid; gap: .5rem; max-width: 60ch; }
.est-report__title {
  font-family: var(--font-display);
  font-size: var(--h3);
  font-weight: 500;
  line-height: 1.22;
  letter-spacing: -.015em;
  color: #fff;
  text-wrap: balance;
}

.est-report__showcase,
.est-report__secondary { display: grid; gap: clamp(14px, 2.4vw, 22px); }

.est-report__primary,
.est-report__cover,
.est-report__recommendation {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;   /* el recorte es visual: los PNG no se editan */
  box-shadow: 0 14px 34px rgba(1, 8, 45, .5);
}
/* Base = móvil: sin recorte ni composición. Por debajo de 768px la columna se
   queda en ~350-400px y cualquier crop convierte el reporte en una mancha, así
   que las tres se apilan a ancho completo y cada una manda su proporción. */
.est-report__primary img,
.est-report__cover img,
.est-report__recommendation img { display: block; width: 100%; height: auto; }

.est-report__caption { font-size: .78rem; line-height: 1.55; color: var(--text-4); }

/* Desde 768px ya hay píxeles para recortar sin perder legibilidad. Los tres
   recortes están calculados sobre el PNG, no a ojo:
   · valoración 1.80 ⇒ ventana y 490→1088 de 1142: 49px de aire sobre "Tabla de
     Homologación", las ocho filas completas y 16px bajo el recuadro rojo. La
     cifra "Valor Comercial Estimado: $15,703,527.53 MXN" no se corta nunca.
     Se sacrifican los párrafos superiores a propósito: caben, pero a ese
     tamaño no se leen y se comen media sección de alto.
   · portada 1.00 ⇒ el 97,5% superior de 1058: título, rúbrica, "Preparado
     por: masterbroker.ai", la foto entera y la tabla de métricas con 28px de
     aire por debajo. Lo único que se pierde es margen en blanco.
   · recomendación 1.414 ⇒ el 92,9% superior de 816: la frase de recomendación,
     "Proyección de Valorización" y la gráfica con sus dos ejes y el rótulo
     "Años". Sólo se recorta el blanco del pie.

   Entre 768 y 1200 las tres siguen apiladas: a esa anchura dos columnas dejan
   la portada por debajo del umbral en que se reconoce lo que es. La jerarquía
   la marcan el ancho y la alternancia, no el recorte. Los topes están atados a
   la altura del hero (ancho/1.8): con más ancho, una pieza secundaria acabaría
   siendo MÁS alta que la valoración —que es justo lo que pasa si se dejan las
   tres a ancho completo, y por eso el escalón entra ya en 768 y no en 900. */
@media (min-width: 768px) {
  .est-report__primary img {
    aspect-ratio: 1.8;
    object-fit: cover;
    object-position: 50% 90%;   /* ancla abajo: homologación + valor estimado */
  }
  .est-report__cover img {
    aspect-ratio: 1;
    object-fit: cover;
    object-position: 50% 0;     /* ancla arriba: encabezado → foto → tabla */
  }
  .est-report__recommendation img {
    aspect-ratio: 1.414;
    object-fit: cover;
    object-position: 50% 0;     /* ancla arriba: recomendación → gráfica */
  }
  .est-report__cover { width: 56%; }
  .est-report__recommendation { width: 78%; margin-left: auto; }
  /* Segundo plano: la línea algo más viva y algo más de sombra, igual que las
     piezas de apoyo de Propiedades y Portal. Sólo profundidad: ni 3D, ni
     perspectiva, ni marcos de dispositivo. */
  .est-report__cover,
  .est-report__recommendation {
    border-color: var(--line-live);
    box-shadow: 0 22px 50px rgba(1, 8, 45, .62);
  }
}

/* Escritorio: la composición editorial completa. La valoración se queda con la
   fila entera y portada + recomendación se reparten la segunda. El 42/58 no es
   un número redondo elegido a ojo: es la proporción que iguala sus alturas
   dadas sus relaciones de aspecto reales (1.00 y 1.414), de modo que la fila
   cierra en línea sin tener que recortar de más. La recomendación se lleva el
   lado ancho porque su contenido —gráfica horizontal y párrafo— lo pide. */
@media (min-width: 1200px) {
  .est-report__secondary {
    grid-template-columns: minmax(0, 42fr) minmax(0, 58fr);
    align-items: start;
  }
  .est-report__cover { width: auto; }
  .est-report__recommendation { width: auto; margin-left: 0; }
  .est-report__recommendation {
    /* Descolgada: cierra la lectura por debajo de la portada sin taparla. */
    margin-top: clamp(14px, 2vw, 30px);
    /* Micro-motion: entra 12px por detrás, con la curva y la duración del
       reveal del sitio. Cuelga del .is-in que ya pone motion.js en la figura,
       así que no añade observadores ni JS. */
    transform: translateY(12px);
    transition: transform .62s var(--ease);
  }
  .est-report.is-in .est-report__recommendation { transform: none; }
}

/* ── 17. 10 MasterAcademy ──────────────────────────────────────────────── */
.block--academy { background: var(--midnight); }
.block--academy .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.6rem); }

/* Composición editorial: una retícula continua de 4×2, no ocho tarjetas
   sueltas. El separador de 1px es el único borde interior. */
.modules {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* Fondo oscuro y separadores por box-shadow del propio módulo: con el fondo
     claro del contenedor, los módulos que aún no habían entrado dejaban ver
     un rectángulo luminoso durante la cascada. */
  background: var(--midnight);
  overflow: hidden;
}
.modules__item {
  display: grid;
  gap: 10px;
  align-content: start;
  padding: clamp(20px, 3vh, 30px) clamp(18px, 1.6vw, 26px);
  background: rgba(1, 8, 45, .72);
  box-shadow: 0 0 0 1px var(--line);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .45s var(--ease), transform .45s var(--ease), background .4s var(--ease);
  will-change: transform, opacity;
}
.modules__item[data-on] { opacity: 1; transform: none; }
.modules__num { font-family: var(--font-mono); font-size: .72rem; color: var(--sky); }
.modules__name { font-family: var(--font-display); font-size: clamp(.95rem, 1.15vw, 1.12rem); font-weight: 500; color: #fff; line-height: 1.25; }
.modules__item--ai .modules__name { color: var(--sky); }
@media (min-width: 600px) { .modules { grid-template-columns: repeat(4, 1fr); } }

/* ── 18. 11 Comunidad ──────────────────────────────────────────────────── */
.block--comunidad { background: linear-gradient(180deg, var(--midnight), rgba(4, 29, 86, .5)); }
.block--comunidad .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.6rem); }

.net {
  position: relative;
  aspect-ratio: 5 / 4;
  max-height: 68vh;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: radial-gradient(circle at 52% 46%, rgba(38, 108, 169, .2), transparent 62%), rgba(1, 8, 45, .5);
  overflow: hidden;
}
.net__links { position: absolute; inset: 0; width: 100%; height: 100%; color: rgba(173, 225, 251, .38); opacity: 0; }
.net__node {
  position: absolute;
  left: var(--nx); top: var(--ny);
  width: 15px; height: 15px;
  margin: -7.5px 0 0 -7.5px;
  border-radius: 50%;
  background: rgba(173, 225, 251, .75);
  opacity: 0;
  transform: scale(.4);
  transition: opacity .42s var(--ease), transform .42s var(--ease);
  will-change: transform, opacity;
}
.net__node[data-on] { opacity: 1; transform: none; }
.net__node--hub {
  width: 28px; height: 28px;
  margin: -14px 0 0 -14px;
  background: var(--sky);
  box-shadow: 0 0 0 8px rgba(173, 225, 251, .14);
}

/* ── 19. 12 Integración ────────────────────────────────────────────────── */
.block--integracion { background: var(--midnight); text-align: center; }
.block--integracion .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.4rem); justify-items: center; }

.flow {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px;
}
.flow__step {
  padding: 11px 17px;
  border: 1px solid rgba(173, 225, 251, .2);
  border-radius: 8px;
  background: rgba(4, 29, 86, .6);
  font-family: var(--font-mono);
  font-size: clamp(.6875rem, 1.1vw, .875rem);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-2);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .5s var(--ease), transform .5s var(--ease);
  will-change: transform, opacity;
}
.flow__step[data-on] { opacity: 1; transform: none; }
.flow__step--ai { border-color: var(--line-live); background: rgba(38, 108, 169, .3); color: var(--sky); }
.flow__step + .flow__step::before {
  content: '→';
  margin-right: 12px;
  margin-left: -12px;
  color: rgba(173, 225, 251, .55);
}

.flow__support { max-width: 56ch; font-size: .92rem; color: var(--text-3); }

.int__iso {
  width: min(30vw, 200px);
  opacity: 0;
  transform: scale(.86);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
  will-change: transform, opacity;
}
.int__iso[data-on] { opacity: .62; transform: none; }

/* ── 20. 13 Oferta ─────────────────────────────────────────────────────── */
.block--precios { background: linear-gradient(180deg, var(--midnight), var(--navy)); }
/* Precios vuelve a la retícula común: su título arranca en el mismo borde
   izquierdo que el resto de la página. Lo que se acota es el bloque de
   tarjetas, no el contenedor. */
.block--precios .wrap { display: grid; gap: clamp(1.5rem, 3.4vh, 2.4rem); max-width: var(--wrap); }

.plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
  gap: clamp(1rem, 1.8vw, 1.6rem);
  /* min-width: 0 — sin él, el tamaño mínimo automático del ítem de rejilla
     (min-content = 300px de columna + relleno) empujaba el documento a 453px
     en un viewport de 390. */
  min-width: 0;
  max-width: 1240px;
}
.plan {
  display: grid;
  gap: 18px;
  align-content: start;
  padding: clamp(1.6rem, 2.6vw, 2.5rem);
  border: 1px solid var(--line);
  border-radius: 20px;
  background: var(--surface);
}
.plan--featured {
  border-color: var(--line-live);
  background: linear-gradient(165deg, rgba(38, 108, 169, .3), rgba(1, 8, 45, .78));
}
.plan__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.plan__label {
  font-family: var(--font-mono);
  font-size: .75rem;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-3);
}
.plan__price { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.plan__amount {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 5vw, 3.9rem);
  font-weight: 600;
  letter-spacing: -.03em;
  color: #fff;
}
.plan__unit { font-size: .92rem; color: var(--text-3); }
.plan__desc { font-size: .875rem; line-height: 1.6; color: var(--text-3); }

/* ── 21. 14 Footer ─────────────────────────────────────────────────────── */
.footer {
  padding: clamp(3rem, 6.5vh, 4.75rem) 0 2.25rem;
  background: var(--midnight);
  border-top: 1px solid var(--line);
}
.footer .wrap { display: grid; gap: clamp(1.75rem, 4vh, 2.75rem); }
.footer__top { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr)); gap: clamp(1.5rem, 3.2vw, 3rem); }
.footer__brand { display: grid; gap: 14px; align-content: start; }
.footer__logo { display: flex; align-items: center; gap: 11px; }
.footer__logo img { width: 30px; height: auto; }
.footer__tagline { max-width: 34ch; font-size: .8125rem; line-height: 1.65; color: var(--text-3); }

.footer__col { display: grid; gap: 10px; align-content: start; }
.footer__title {
  font-family: var(--font-mono);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-4);
}
.footer__col ul { display: grid; gap: 8px; font-size: .84rem; }
.footer__col a { color: var(--text-2); }
.footer__col a:hover { color: #fff; }

/* Elementos sin URL confirmada: se muestran como texto, nunca como enlace falso */
.footer__pending li { color: var(--text-4); }
[data-env="dev"] .footer__pending li::after {
  content: ' · pendiente';
  font-family: var(--font-mono);
  font-size: .625rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(255, 214, 178, .85);
}
/* flex-wrap y no una fila de rejilla: con cuatro redes en una columna de ~205px
   la fila desbordaba el documento 1px a 1024px de ancho.
   El selector lleva `ul` para ganar a `.footer__col ul { display: grid }`. */
ul.footer__social { display: flex; flex-wrap: wrap; justify-content: start; gap: 8px 14px; margin-top: 6px; }
[data-env="dev"] ul.footer__social { flex-direction: column; }

.footer__bottom {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 1.4rem;
  border-top: 1px solid var(--line-soft);
  font-size: .75rem;
  color: var(--text-4);
}
.footer__note { font-family: var(--font-mono); letter-spacing: .05em; }

/* ── 22. Motion / reveal ───────────────────────────────────────────────── */
[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity .62s var(--ease), transform .62s var(--ease);
}
[data-reveal].is-in { opacity: 1; transform: none; }

@keyframes mbBreath { 0%, 100% { opacity: .32 } 50% { opacity: .6 } }
@keyframes mbFloat  { 0%, 100% { transform: translateY(0) } 50% { transform: translateY(-9px) } }
@keyframes mbHint   { 0% { transform: translateY(0); opacity: 0 } 35% { opacity: 1 } 100% { transform: translateY(15px); opacity: 0 } }
@keyframes mbRing   { 0% { transform: scale(.62); opacity: .75 } 100% { transform: scale(1.85); opacity: 0 } }
@keyframes mbWave   { 0%, 100% { transform: scaleY(.35) } 50% { transform: scaleY(1) } }

/* Barrido de la iluminación de la cuadrícula (.hero__grid-highlight).
   Se anima la MÁSCARA, no el elemento: la cuadrícula no se mueve.

   La máscara mide 160% del hero, de modo que un porcentaje de mask-position
   vale (100% − 160%) = −0.6 de esa caja. Con eso el centro de la elipse cae en
   0.8 − 0.006·p del ancho (e igual del alto), así que
     p = −75% → +1.25 del ancho (fuera por la derecha)
     p = 175% → −0.25 del ancho (fuera por la izquierda)
   y el eje vertical recorre lo mismo en sentido inverso, de modo que la
   diagonal va de arriba-derecha a abajo-izquierda… y luego de vuelta.

   El ciclo son 25s de ida y vuelta, en cuatro tramos:
      0% –  30%  BAJADA.  arriba-derecha → abajo-izquierda. 30% de 25s = 7.5s
                 exactos, la velocidad ya aprobada, sobre las mismas 250
                 unidades de mask-position.
     30% –  50%  descanso. 20% de 25s = 5s con la capa a 0 y la elipse quieta
                 fuera de la escena: ni brillo residual ni movimiento.
     50% –  80%  SUBIDA.   abajo-izquierda → arriba-derecha, mismos 7.5s y
                 misma recta recorrida al revés.
     80% – 100%  descanso. Otros 5s igual de limpios.

   Los fotogramas intermedios de cada pasada caen sobre su propia recta
   (±250·t/0.3), así que con `linear` la velocidad es constante en las dos
   direcciones. Los fundidos ocupan 4.2% = 1.05s, el mismo valor aprobado.

   El bucle cierra solo: la subida termina exactamente donde arranca la bajada
   (−75% 175%), de modo que el 100% y el 0% son el MISMO estado y el reinicio
   no puede verse ni siquiera en teoría. */
@keyframes mbGridSweep {
  /* ── bajada: arriba-derecha → abajo-izquierda ── */
  0% {
    opacity: 0;
    mask-position: -75% 175%;
    -webkit-mask-position: -75% 175%;
  }
  4.2% {
    opacity: var(--grid-highlight-max);
    mask-position: -40% 140%;
    -webkit-mask-position: -40% 140%;
  }
  25.8% {
    opacity: var(--grid-highlight-max);
    mask-position: 140% -40%;
    -webkit-mask-position: 140% -40%;
  }
  30% {
    opacity: 0;
    mask-position: 175% -75%;
    -webkit-mask-position: 175% -75%;
  }
  /* ── descanso abajo: 5s. Mismos valores que el 30%: nada se mueve. ── */
  50% {
    opacity: 0;
    mask-position: 175% -75%;
    -webkit-mask-position: 175% -75%;
  }
  /* ── subida: abajo-izquierda → arriba-derecha ── */
  54.2% {
    opacity: var(--grid-highlight-max);
    mask-position: 140% -40%;
    -webkit-mask-position: 140% -40%;
  }
  75.8% {
    opacity: var(--grid-highlight-max);
    mask-position: -40% 140%;
    -webkit-mask-position: -40% 140%;
  }
  80% {
    opacity: 0;
    mask-position: -75% 175%;
    -webkit-mask-position: -75% 175%;
  }
  /* ── descanso arriba: 5s, ya en el punto de partida de la bajada. ── */
  100% {
    opacity: 0;
    mask-position: -75% 175%;
    -webkit-mask-position: -75% 175%;
  }
}

/* ── Escenas con pin: sólo a partir de tablet ──────────────────────────── */
@media (min-width: 768px) {
  .scene { height: var(--scene-len, 140vh); }
  .scene__pin {
    position: sticky;
    top: 0;
    height: 100svh;
    align-content: center;
    overflow: hidden;
    padding-block: calc(var(--nav-h) + clamp(.75rem, 2.2vh, 1.6rem)) clamp(1rem, 3vh, 2rem);
  }

  /* Longitud de cada escena = recorrido de scroll que consume.
     Se ajusta a la complejidad real de la escena, no a un valor uniforme.
     Cada una reserva además su último 15–20% como hold (ver motion.js). */
  /* Problema sube de 138 a 165vh: la escena pasó de dos fases (dispersión +
     convergencia) a cuatro (dispersión · convergencia · absorción · hold), y
     con 38vh de recorrido la absorción y el hold se comían entre sí. */
  .scene--problema { --scene-len: 165vh; }
  .scene--eco      { --scene-len: 148vh; }  /* antes 200vh */
  .scene--crm      { --scene-len: 158vh; }  /* antes 210vh · la más compleja */
  .scene--maia     { --scene-len: 146vh; }  /* antes 190vh */
  .scene--capi     { --scene-len: 134vh; }  /* antes 170vh */

  /* Dos filas: el copy arriba (siempre legible) y el campo del caos ocupando
     todo el resto. El núcleo crece dentro de esa fila, nunca sobre el título. */
  .scene--problema .scene__pin {
    align-content: stretch;
    grid-template-rows: auto minmax(0, 1fr);
    gap: clamp(.9rem, 2.4vh, 1.7rem);
  }
  .scene--problema .chaos { width: 100%; align-self: stretch; }

  /* El gap de este pin ES la distancia mensaje → pipeline: 24px en pantallas
     bajas y 36px como máximo. Suficiente para que no se toquen, corto para
     que header y tabla se lean como un solo bloque. */
  .scene--crm .scene__pin {
    align-content: start;
    grid-template-rows: auto minmax(0, 1fr);
    gap: clamp(1.5rem, 2.8vh, 2.25rem);
  }
  /* Tres zonas: copy · diagrama · cierre. El diagrama es el único que cede
     espacio (minmax(0,1fr)), así que la frase nunca empuja nada fuera del pin
     ni obliga a alargar la escena. */
  .scene--eco .scene__pin {
    align-content: stretch;
    grid-template-rows: auto minmax(0, 1fr) auto;
    gap: clamp(.85rem, 2vh, 1.5rem);
  }
  /* Con el pin repartido en tres filas el diagrama ya tiene una altura
     definida: los min-height de 52vh sólo servían para reservarla cuando la
     fila era la última y ahora provocarían desbordamiento en pantallas bajas
     (1366×768). El anillo se mide contra la fila real. */
  .scene--eco .eco,
  .scene--eco .eco__field { min-height: 0; }
  .scene--eco .eco { height: 100%; }
  .scene--maia .scene__pin,
  .scene--capi .scene__pin { align-content: center; }

  .split { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
  .events__stage { text-align: right; }
}

/* Reparto 44 / 56 de la retícula: texto a la izquierda, visualización a la
   derecha. Por debajo de 1024px sigue mandando el auto-fit. */
@media (min-width: 1024px) {
  .split { grid-template-columns: minmax(0, 44fr) minmax(0, 56fr); }
}

/* CRM: título y descripción en la misma franja horizontal, aprovechando el
   hueco que quedaba a la derecha del h2.
   Por qué 1200 y no 1024: medido, el h2 necesita ~575px para seguir cortando
   en dos líneas. A 1024 la columna del título daría ~515px y tanto el título
   como la descripción se irían a tres líneas apretadas, así que hasta 1200
   se apilan (que es como se ven el resto de escenas).
   Reparto 53 / 39 del ancho útil, el resto al gap: a 1200 deja 606px de
   título y a 1440, 740px. Medido: el h2 corta en DOS líneas y la descripción
   también en dos, de 1200 a 2560.
   align-items: start alinea el arranque de ambos bloques, no su centro: es
   lo que hace que la primera línea del párrafo entre a la altura del título. */
@media (min-width: 1200px) {
  .crm__head {
    grid-template-columns: minmax(0, 53fr) minmax(0, 39fr);
    gap: clamp(1.5rem, 3vw, 4rem);
    align-items: start;
  }
}

/* Móvil: escenas en flujo normal, sin pin.
   Las escenas posicionadas en absoluto (caos y ecosistema) se convierten en
   composiciones 2D legibles, porque motion.js no las anima sin pin. */
@media (max-width: 767.98px) {
  .scene__pin { padding-block: clamp(3rem, 9vh, 4.5rem); }

  /* Problema: los fragmentos pasan a ser una lista envolvente y el núcleo se
     coloca DEBAJO de ellos (order), que es la misma lectura de la escena
     animada: primero el caos, después el sistema que lo absorbe. */
  .chaos {
    position: relative;
    inset: auto;
    display: grid;
    gap: 1.4rem;
    height: auto;
    margin-top: .5rem;
    overflow: visible;
  }
  .chaos__field {
    position: static;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 7px;
    order: 1;
  }
  .chaos__core {
    position: static;
    order: 2;
    width: min(62vw, 255px);   /* mismo +11% que el núcleo animado */
    margin-inline: auto;
    opacity: 1 !important;
    transform: none !important;
    --core-glow: .85;
  }
  .chaos__chip {
    position: static;
    max-width: 100%;
    gap: 7px;
    padding: 9px 12px;
    font-size: .72rem;
    white-space: normal;
    /* Sin el glow exterior: ocho tarjetas juntas y quietas no necesitan brillo,
       y el borde ya las separa del fondo. */
    box-shadow: 0 1px 0 rgba(173, 225, 251, .08) inset, 0 12px 26px -20px rgba(1, 8, 45, 1);
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
  .chaos__chip--warn { box-shadow: 0 1px 0 rgba(255, 214, 178, .1) inset, 0 12px 26px -20px rgba(1, 8, 45, 1); }
  .chaos__chip::before { width: 5px; height: 5px; }

  /* Ecosistema: rejilla de módulos con el núcleo arriba */
  .eco { min-height: 0; grid-template-rows: auto auto; gap: 1.5rem; justify-items: stretch; }
  .eco__core {
    position: static;
    /* En flujo la fila no tiene altura definida: el tamaño lo fija el ancho. */
    height: auto;
    width: 142px;   /* proporcional al núcleo de escritorio */
    margin-inline: auto;
    opacity: 1 !important;
    transform: none !important;
  }
  /* La frase cierra la sección en flujo normal, después de la rejilla. */
  .eco__statement { margin-top: .4rem; }
  .eco__field {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
    min-height: 0;
    height: auto;
  }
  .eco__mod {
    position: static;
    padding: 11px 12px;
    opacity: 1 !important;
    transform: none !important;
  }
  .eco__mod::before { display: none; }
  .eco__mod b { font-size: .82rem; }
  .eco__mod i { font-size: .68rem; }

  .maia__core { width: 128px; }

  .net { aspect-ratio: 1 / 1; }

  .flow { flex-direction: column; align-items: stretch; }
  .flow__step { text-align: center; }
  .flow__step + .flow__step::before { display: block; margin: 0 0 6px; }

  .hero__hint { display: none; }
  /* Mismo sistema y misma trayectoria; sólo baja la presencia, porque en una
     pantalla pequeña el copy ocupa casi todo el fondo. */
  .hero__grid-highlight { --grid-highlight-max: .25; }   /* ~75% de la intensidad de escritorio */
  .footer__bottom { flex-direction: column; align-items: flex-start; }
}

/* ── 23. prefers-reduced-motion ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }

  /* Todo el contenido se muestra en su estado final y legible */
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  .chaos__chip,
  .eco__mod,
  .modules__item,
  .net__node,
  .flow__step,
  .events__row { opacity: 1 !important; transform: none !important; }
  /* El recorrido entre señales también se muestra ya trazado: sin [data-on] los
     conectores quedarían invisibles y la lista perdería su lectura de secuencia. */
  .events__row + .events__row::before,
  .events__row + .events__row::after { opacity: 1 !important; }
  .eco__core, .net__links { opacity: 1 !important; }
  /* El recorrido del hero se muestra ya conectado: motion.js no llega a poner
     [data-on] con reduced-motion, así que el estado final lo fija el CSS. */
  .hero__orbit-step { opacity: 1 !important; --orbit-off: 0px; }
  .hero__orbit-step + .hero__orbit-step::before,
  .hero__orbit-step + .hero__orbit-step::after { opacity: 1 !important; }
  .hero__orbit-link { stroke-dashoffset: 0 !important; }
  .eco__mod { --link-o: .55; --link-k: 1; }
  .int__iso { opacity: .62 !important; transform: none !important; }
  .timeline__row { opacity: 1 !important; }
  .hero__iso { opacity: .92 !important; }
  /* La máscara no recorre nada: se queda quieta sobre el centro de la escena,
     como un realce fijo de la cuadrícula. Misma profundidad, sin un solo
     fotograma de movimiento. */
  .hero__grid-highlight {
    animation: none !important;
    opacity: .22;
    mask-position: 50% 50%;
    -webkit-mask-position: 50% 50%;
  }
  .hero__video,
  .chaos__video { display: none; }
  .chaos__chip { filter: none !important; }

  /* Sin pin: las escenas se recorren como secciones normales.
     Las composiciones posicionadas en absoluto se resuelven en flujo para
     que nada quede apilado en el centro cuando no hay animación. */
  .scene { height: auto !important; }
  .scene__pin { position: static; height: auto; overflow: visible; }
  .pipe__card { position: static; width: 100%; margin-top: 8px; }

  .chaos {
    position: relative;
    inset: auto;
    display: grid;
    gap: 1.4rem;
    height: auto;
    margin-top: .5rem;
  }
  .chaos__field {
    position: static;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    order: 1;
  }
  .chaos__chip { position: static; white-space: normal; }
  .chaos__core {
    position: static;
    order: 2;
    width: min(58vw, 244px);   /* mismo +11% que el núcleo animado */
    margin-inline: auto;
    opacity: 1 !important;
    transform: none !important;
    --core-glow: .85;
  }

  .eco { min-height: 0; grid-template-rows: auto auto; gap: 1.5rem; justify-items: stretch; }
  .eco__core { position: static; height: auto; width: 178px; margin-inline: auto; }
  /* El cierre se muestra ya resuelto: motion.js no llega a marcarlo. */
  .eco__statement { opacity: 1 !important; transform: none !important; }
  .eco__field {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 8px;
    min-height: 0;
    height: auto;
  }
  .eco__mod { position: static; }
  .eco__mod::before { display: none; }
}
