/* ==========================================================================
   Secciones
   Cada padding vertical está derivado de la posición absoluta del hito
   correspondiente en el frame de Figma (1750 x 8875). Los valores en el
   comentario son la coordenada y de destino en el diseño.
   ========================================================================== */

/* ----------------------------------------------------------------- HERO --- */
.hero {
  position: relative;
  isolation: isolate;
  padding-top: calc(200 * var(--s));      /* H1 en y≈208 */
  text-align: center;
  background:
    radial-gradient(115% 62% at 50% -8%, #FDFEFE 0%, rgba(253, 254, 254, 0) 64%),
    var(--bg);
}

.hero__art { position: absolute; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }

/* Un solo chevron del asset 3D, girado 180° para el lado izquierdo.
   Ambos apuntan hacia el centro, como en el diseño. */
.hero__glass {
  position: absolute;
  height: auto;
  filter: saturate(1.04);
}
/* Tamaños ajustados contra la envolvente del vidrio en el render del Figma:
   izquierda 356x540 px, derecha 369x721 px. */
.hero__glass--left {
  top: calc(92 * var(--s));
  left: calc(-60 * var(--s));
  width: calc(356 * var(--s));
  rotate: 168deg;
}
.hero__glass--right {
  top: calc(126 * var(--s));
  right: calc(-100 * var(--s));
  width: calc(427 * var(--s));
  rotate: -6deg;
}

.hero__title {
  font-size: var(--fs-h1);
  line-height: var(--lh-h1);
  color: var(--blue);
}
.hero__lead {
  margin-top: calc(30 * var(--s));        /* lead en y≈363 */
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--ink);
}
.hero__actions {
  margin-top: calc(20 * var(--s));        /* botones en y=438 */
  display: flex;
  justify-content: center;
  gap: calc(25 * var(--s));
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------- STATS --- */
/* Tres columnas iguales del contenedor: los centros caen en x≈344 / 875 / 1406
   del frame, igual que en el diseño. Los separadores se posicionan aparte
   porque en el Figma no están en el punto medio de las columnas. */
.stats {
  position: relative;
  margin-top: calc(206 * var(--s));       /* cifras en y=700 */
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}
.stat { text-align: center; }
.stat__num {
  font-size: var(--fs-stat);
  line-height: 1;
  color: var(--blue);
}
.stat__label {
  margin-top: calc(34 * var(--s));
  font-size: var(--fs-stat-label);
  line-height: 1;
  font-weight: 500;
  letter-spacing: var(--ls-stat-label);
  /* letter-spacing agrega hueco también después del último carácter: lo
     compensamos para que el texto quede óptimamente centrado */
  margin-right: calc(var(--ls-stat-label) * -1);
  text-transform: uppercase;
  color: var(--blue-strong);   /* 18px: necesita 4.5:1 */
}
.stat__rule {
  position: absolute;
  top: calc(28 * var(--s));
  width: 1px;
  height: calc(30 * var(--s));
  background: var(--rule);
}
.stat__rule:nth-of-type(1) { left: calc(590 * var(--s)); }
.stat__rule:nth-of-type(2) { left: calc(984 * var(--s)); }

/* --------------------------------------------------------------- BRANDS --- */
/* logos en y=950 */
.brands {
  --brand-h: calc(76 * var(--s));   /* alto de celda = alto del logo = recorrido */
  margin-top: calc(141 * var(--s));
  overflow: hidden;
}
/* La tira está QUIETA, como en el diseño. Lo que se mueve es el contenido: cada
   celda reemplaza su logo por otro del pool, en orden aleatorio; el que entra
   empuja desde abajo al que sale mientras éste se disuelve (ver js/main.js). */
.brands__track {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* Ancho fijo por celda: los logos tienen anchos muy distintos y, si la celda se
   adaptara a cada uno, cambiar de logo correría toda la fila.
   overflow oculto + alto fijo = la ventana por la que entra y sale el logo. */
.brands__track li {
  flex: 0 0 auto;
  width: calc(170 * var(--s));
  height: var(--brand-h);
  overflow: hidden;
  display: grid;
  place-items: center;
}
/* Las dos imágenes (la que sale y la que entra) comparten la misma celda de
   grilla, así se apilan centradas sin sacarlas del flujo. */
.brands__track img {
  grid-area: 1 / 1;
  max-height: var(--brand-h);
  max-width: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  opacity: .95;
  transition: transform 560ms var(--ease-out), opacity 560ms var(--ease);
}
/* El recorrido se mide contra la CELDA, no contra la imagen: los logos tienen
   alturas distintas y un 100% propio no alcanzaría a despejar la ventana. */
.brands__track img.is-enter {
  transform: translateY(var(--brand-h));
  opacity: 0;
  transition: none;
}
.brands__track img.is-leave {
  transform: translateY(calc(var(--brand-h) * -1));
  opacity: 0;
}

/* ------------------------------------------------------------ SERVICIOS --- */
.services { padding-top: calc(158 * var(--s)); }   /* chip en y=1184 */
.services__grid {
  margin-top: calc(70 * var(--s));                 /* cards en y=1423 */
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: calc(36 * var(--s)) calc(32 * var(--s));
}
.card--wide { grid-column: span 3; }
.services__grid > .card:not(.card--wide) { grid-column: span 2; }
/* Alturas de mockup medidas en el diseño: fila 1 = 205, fila 2 = 210 */
.card--wide .card__media { height: calc(205 * var(--s)); }
.services__grid > .card:not(.card--wide) .card__media { height: calc(210 * var(--s)); }

/* ------------------------------------------------------ NUESTRO ESTÁNDAR -- */
/* clip y no hidden: hidden crearia un contenedor de scroll y el sticky del
   bloque pinneado pasaria a pegarse a el en vez de a la pantalla. */
.philosophy { overflow-x: clip; }

/* ---- Bloque pinneado -------------------------------------------------------
   El track aporta la distancia de scroll: 100vh de "pin" + --hl-run de
   recorrido. El bloque queda quieto en el centro de la pantalla mientras se
   scrollea --hl-run, y en ese tramo se colorean las dos frases.
   Subir o bajar --hl-run es lo único que hay que tocar para que el scroll
   dure más o menos.                                                         */
.standard {
  --hl-run: 200vh;
  position: relative;
  isolation: isolate;
  /* Mucho menos que los 358px del Figma: como el bloque queda pinneado y
     centrado en la pantalla, el aire de arriba ya lo da el propio viewport y
     ese padding sólo agregaba un vacío largo antes de que enganche el pin. */
  padding-top: calc(110 * var(--s));
}
.standard__track {
  position: relative;
  height: calc(100vh + var(--hl-run));
}
.standard__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  display: grid;
  place-items: center;
  overflow: hidden;      /* recorta la lluvia al alto de la pantalla */
}
.standard__inner { text-align: center; display: flex; flex-direction: column; align-items: center; }

/* Caja de la lluvia centrada en el bloque de texto: 900px de alto, o sea
   ~340px de aire arriba y abajo del párrafo. */
/* El alto NO se fija: sale de la cantidad de filas, y esa cantidad la calcula
   el JS a partir del ancho para que el óvalo siempre quede más ancho que alto
   (relación ~2.35:1, la de la referencia). Si se fijara en px, la proporción
   cambiaría en cada viewport. */
.standard__rain {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: -1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: calc(8 * var(--s));            /* paso de fila 29px, como en el diseño */
  /* Monoespaciada a propósito: el óvalo lo forma el LARGO de cada fila, así que
     todos los caracteres tienen que medir lo mismo o el borde sale dentado. */
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: calc(21 * var(--s));
  font-weight: 600;                   /* si no, los trazos quedan muy finos */
  line-height: 1;
  color: var(--blue);
  white-space: nowrap;
  pointer-events: none;
  overflow: hidden;
  /* Suave: la forma ya la da el largo de las filas, la máscara sólo apaga los
     extremos para que no haya un corte duro. */
  -webkit-mask-image: radial-gradient(80% 78% at 50% 50%, #000 0%, #000 56%, rgba(0, 0, 0, .55) 82%, transparent 100%);
          mask-image: radial-gradient(80% 78% at 50% 50%, #000 0%, #000 56%, rgba(0, 0, 0, .55) 82%, transparent 100%);
}
.standard__rain span {
  display: block;
  text-align: center;
  opacity: .22;
  will-change: transform;
}

/* ---- Resaltado por scroll -------------------------------------------------
   Las dos frases se apilan en el mismo lugar. Alto fijo de 3 líneas para que
   el ritmo vertical del diseño no se mueva al cambiar de frase.
   El color de cada palabra lo interpola el JS entre --hl-dim y --hl-lit según
   el avance del scroll.                                                     */
.standard__title {
  --hl-dim: #9FB6CE;          /* palabra apagada — tiene que aguantar sobre la lluvia */
  --hl-lit: var(--blue);      /* palabra encendida */
  position: relative;
  width: 100%;
  margin-top: calc(30 * var(--s));   /* título en y≈2566 */
  font-size: var(--fs-h2-standard);
  line-height: var(--lh-h2-standard);
  color: var(--blue);
}

/* La primera frase queda en flujo: es la que define el alto del bloque, así
   que si alguna línea se parte en pantallas chicas el alto acompaña. La
   segunda va superpuesta encima, en la misma posición. */
.hl__phrase { display: block; }
.hl__phrase + .hl__phrase { position: absolute; inset: 0; }
/* Sin JS sólo se ve la primera frase. */
.hl__phrase:not(.is-split):not(.is-active) { visibility: hidden; }
/* Con JS el módulo escribe la opacidad de las DOS en cada frame (hay un cruce
   en el relevo), así que acá sólo dejamos el estado previo al primer pintado. */
.hl__phrase.is-split { opacity: 0; }
.hl__phrase.is-split:first-child { opacity: 1; }
.hl__line { display: block; }

.hl__word {
  display: inline-block;
  white-space: pre;
  /* Sin JS (o con reduced-motion) las palabras quedan en el azul de marca */
  color: inherit;
}

/* -------------------------------------------------------------- PROCESO --- */
/* El panel es de ancho completo, con esquinas superiores de ~110px, y llega
   por debajo del bloque "Filosofía de trabajo" (se continúa en .philosophy). */
/* Igual que arriba: el pin ya deja aire al soltarse, así que no hacen falta los
   415px del Figma antes del panel. */
/* ---- Cortina --------------------------------------------------------------
   El panel sube desde abajo y tapa el final de la seccion anterior, como en la
   referencia. Son tres piezas:
   - el margen negativo hace que la seccion arranque solapada;
   - z-index la pone POR ENCIMA de lo que ya estaba dibujado;
   - el reveal le da el movimiento de subida, con un recorrido en vh (no los
     38px del resto del texto) para que se lea como una cortina y no como un
     texto acomodandose.
   La sombra superior es la que despega el panel del fondo mientras sube; sin
   ella el borde redondeado se confunde con el cambio de color. */
.process {
  position: relative;
  z-index: 2;
  padding-top: calc(140 * var(--s));
  margin-top: -14vh;
}
.process__panel {
  padding-top: calc(165 * var(--s));               /* chip en y=3295 */
  border-radius: calc(110 * var(--s)) calc(110 * var(--s)) 0 0;
  background: var(--panel);
  box-shadow: 0 calc(-18 * var(--s)) calc(60 * var(--s)) rgb(0 40 90 / .07);
}
.process__panel[data-reveal] { --reveal-shift: 26vh; }
.process__panel > .container {
  width: min(100% - 130 * var(--s), calc(1550 * var(--s)));
}
.process__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(40 * var(--s));
}
.process__badges { display: flex; align-items: center; margin-top: calc(83 * var(--s)); }
.process__badge {
  display: grid;
  place-items: center;
  width: calc(76 * var(--s));
  height: calc(76 * var(--s));
  border-radius: 50%;
}
.process__badge svg { width: 44%; height: 44%; }
.process__badge--soft { background: #DCE6EF; }
.process__badge--solid {
  margin-left: calc(-18 * var(--s));
  background: var(--blue);
  box-shadow: 0 calc(10 * var(--s)) calc(26 * var(--s)) rgba(0, 111, 255, .3);
}

.process__shot {
  margin: calc(59 * var(--s)) auto 0;              /* dashboard en y=3523 */
  width: 100%;
  border-radius: calc(24 * var(--s));
  overflow: hidden;
  box-shadow: 0 calc(20 * var(--s)) calc(60 * var(--s)) rgba(0, 60, 120, .07);
  transform-style: preserve-3d;
  transition: transform 600ms var(--ease-out);
  -webkit-mask-image: linear-gradient(180deg, #000 72%, transparent 100%);
          mask-image: linear-gradient(180deg, #000 72%, transparent 100%);
}
.process__shot img { width: 100%; }

/* ------------------------------------------------------------ FILOSOFÍA --- */
/* Continúa el panel de proceso y lo desvanece hacia el fondo de página. */
.philosophy {
  padding-top: calc(62 * var(--s));                /* título en y≈4163 */
  text-align: center;
  background: linear-gradient(180deg,
    var(--panel) 0,
    var(--panel) calc(200 * var(--s)),
    var(--bg) calc(344 * var(--s)));
}
.philosophy__title {
  font-size: var(--fs-h2-center);
  line-height: 1.1;
}
.philosophy__lead {
  margin-top: calc(15 * var(--s));
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--ink);
}

/* ---- Bloque pinneado ------------------------------------------------------
   Mismo mecanismo que "Nuestro Estándar": el track aporta la distancia de
   scroll y el bloque queda quieto en el centro de la pantalla mientras se
   recorre. Sin el pin, al pasar del 01 al 03 el bloque ya se habia ido para
   arriba y el numeral grande de la izquierda salia de cuadro.
   --steps-run es la unica perilla para que el recorrido dure mas o menos. */
.steps__track {
  --steps-run: 190vh;
  position: relative;
  height: calc(100vh + var(--steps-run));
}
.steps__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  box-sizing: border-box;
  /* El nav es fijo y tapa los primeros 114px: sin este padding el bloque se
     centra en la pantalla entera y la tarjeta del 01 queda por debajo del nav,
     cortada. Asi se centra en el espacio que realmente queda libre. */
  padding-top: var(--nav-h);
  display: grid;
  align-content: center;
}
.steps {
  position: relative;
  min-height: calc(736 * var(--s));
  text-align: left;
}

/* El arte (aros, arco, numeral) va al fondo y la lista por encima. Sin esto,
   la tarjeta —que es un ::before con z-index -1— se iba por detras del arte y
   las lineas de los aros cruzaban por encima de las tarjetas. */
.steps__art { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
@keyframes stepsNumSwap {
  0%   { opacity: 0; transform: translateY(12%) scale(.94); }
  100% { opacity: 1; transform: none; }
}
.steps__numeral.is-swap { animation: stepsNumSwap 520ms var(--ease-out); }
.steps__numeral {
  position: absolute;
  left: calc(118 * var(--s) - 0.129 * var(--fs-numeral));
  top: calc(240 * var(--s));
  font-size: var(--fs-numeral);
  line-height: .78;
  color: var(--blue);
  /* Cal Sans no tiene cifras tabulares: el avance del 1 es 99.8 y el del 2 es
     187.7 sobre el mismo cuerpo, casi el doble. La caja se fija al avance mas
     ancho para que ningun digito se desborde, y se centra el texto adentro. */
  width: calc(0.551 * var(--fs-numeral));
  text-align: center;
}
.steps__rings {
  position: absolute;
  /* Los aros tienen que RODEAR al "1", no quedar a un costado: el centro del
     viewBox (330,300 de 900) se hace coincidir con el centro del numeral.
     Todo se expresa contra --fs-numeral y no en px sueltos, porque el numeral
     cambia de tamano en los breakpoints y con valores fijos el centro se
     volvia a correr. Los factores salen de sus proporciones medidas:
     ancho = .293 x fs, alto = .78 x fs, y el aro interior a .73 del alto. */
  /* --num-ink-dx lo pone el JS: es cuanto se corre el centro de TINTA del
     digito respecto del centro de su avance. Sin esta correccion los aros
     quedaban centrados en la caja y no en el dibujo, y como el 1 esta 9px mas
     a la izquierda que el 2 y el 3, se notaba al cambiar de numero. */
  left: calc(118 * var(--s) - 0.9588 * var(--fs-numeral) + var(--num-ink-dx, 0px));
  top: calc(240 * var(--s) - 0.6148 * var(--fs-numeral));
  width: calc(3.0145 * var(--fs-numeral));
}
/* Los puntos giran alrededor del centro de los anillos (330,300 en el viewBox),
   asi que recorren los anillos como una orbita. Los circulos concentricos no se
   animan: al ser concentricos con el eje de giro, rotarlos no se veria, y
   ademas mover solo los puntos es mucho mas barato de pintar.
   transform-box: view-box es lo que hace que transform-origin se lea en
   unidades del viewBox y no de la caja del elemento. */
.steps__orbit {
  transform-box: view-box;
  transform-origin: 330px 300px;
}
/* Cada punto con su propia vuelta. Si dos comparten grupo o duracion quedan
   solidarios y se lee como una pieza rigida girando, no como una orbita: por
   eso el gris tiene el suyo aunque comparta anillo con el azul grande. Las
   duraciones no son multiplos entre si, asi el conjunto tarda en repetirse. */
.steps__orbit--in  { animation: stepsOrbita 54s linear infinite; }
.steps__orbit--out { animation: stepsOrbita 96s linear infinite reverse; }
.steps__orbit--dim { animation: stepsOrbita 67s linear infinite; }
@keyframes stepsOrbita { to { transform: rotate(360deg); } }

/* El trazo ahora vive dentro del SVG de los aros, asi que ya no se posiciona
   por su cuenta: solo conserva el trazado progresivo. */
.steps__path {
  stroke-dasharray: var(--len, 1200);
  stroke-dashoffset: var(--len, 1200);
  transition: stroke-dashoffset 1.6s var(--ease-out);
}
.steps.is-drawn .steps__path { stroke-dashoffset: 0; }

.steps__list {
  position: relative;
  z-index: 1;
  margin-left: calc(459 * var(--s));
  display: flex;
  flex-direction: column;
  /* Ahora que el bloque esta pinneado, el recorrido de scroll de cada paso lo
     da --steps-run y no esta separacion. Pero el aire tiene techo: los tres
     pasos mas el desborde de la tarjeta tienen que entrar en la pantalla menos
     el nav, o el 01 se corta arriba.
     Por eso el gap sale del ALTO del viewport y no de --s (que escala con el
     ancho): en una pantalla alta hay lugar para mas aire, y en una baja el
     min() lo recorta solo antes de que algo se corte. */
  gap: min(calc(175 * var(--s)), 13vh);
}

.step {
  position: relative;
  /* Crea contexto de apilado propio: asi el ::before de la tarjeta (z-index -1)
     queda DENTRO del paso — debajo de su texto pero encima del arte— en vez de
     caer al fondo de la seccion. */
  isolation: isolate;
  display: flex;
  align-items: flex-start;
  gap: calc(48 * var(--s));
  max-width: calc(1130 * var(--s));
  padding-inline: calc(72 * var(--s));
  /* Se repiten los tiempos del reveal ademas del de hover: este shorthand tiene
     la misma especificidad que el de [data-reveal] y se declara despues, asi
     que lo pisaria entero y los pasos entrarian de golpe. */
  transition:
    opacity calc(var(--t-reveal) * 0.7) var(--ease-out),
    transform var(--t-reveal) var(--ease-out);
}
/* ---- Entrada en cortina ---------------------------------------------------
   Los pasos entran desde los costados de la pantalla, alternando lado. La
   distancia va en vw para que salgan de cuadro en cualquier ancho.
   NO usan [data-reveal]: ese sistema marca cada elemento cuando entra en
   pantalla, y estos arrancan 1000px afuera, asi que el observador nunca los
   veia y solo entraba el primero. El disparador es .is-drawn, que ya lo pone
   el observador del bloque entero. */
.step { opacity: 0; transform: translate3d(var(--cortina), 0, 0); }
.step:nth-child(odd)  { --cortina: -75vw; }
.step:nth-child(even) { --cortina:  75vw; }
.step:nth-child(2) { transition-delay: 130ms; }
.step:nth-child(3) { transition-delay: 260ms; }
.steps.is-drawn .step { opacity: 1; transform: none; }

/* El paso activo se agranda un poco respecto de los otros. Crece desde la
   izquierda y no desde el centro: a la izquierda esta el numero, que es el
   ancla visual de la fila, y asi el crecimiento se manda hacia la derecha,
   donde hay aire (64px hasta el borde de pantalla) en vez de descolocar la
   columna. Escalar no toca el layout, asi que los otros pasos no se mueven. */
.step { --step-zoom: 1.03; transform-origin: left center; }
.steps.is-drawn .step.is-active { transform: scale(var(--step-zoom)); }
/* Ya entrado, el hover suma el levante sin perder la escala. */
.steps.is-drawn .step.is-active:hover {
  transform: translateY(calc(-4 * var(--s))) scale(var(--step-zoom));
  transition: transform var(--t) var(--ease-out);
}
/* La tarjeta va en un pseudo-elemento y no en el padding del paso. El resalte
   se mueve con el scroll, y si el paso activo ganara padding, cada cambio
   correria todo lo de abajo y la pagina saltaria mientras se scrollea. Asi la
   caja se dibuja por fuera del flujo y el layout no se entera. */
.step::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: calc(-58 * var(--s)) 0 calc(-76 * var(--s));
  border-radius: var(--r-card);
  background: #fff;
  box-shadow: var(--shadow-card);
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease), box-shadow var(--t) var(--ease-out);
}
.step.is-active::before { opacity: 1; }
.step.is-active:hover::before { box-shadow: var(--shadow-lift); }
.step:nth-child(2) { margin-left: calc(112 * var(--s)); }
.step:nth-child(3) { margin-left: calc(25 * var(--s)); margin-top: calc(17 * var(--s)); }

.step__num {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: calc(100 * var(--s));
  height: calc(100 * var(--s));
  margin-top: calc(46 * var(--s));
  border-radius: 50%;
  background: #E4EBF2;
  font-family: var(--font-display);
  font-size: calc(32 * var(--s));
  color: var(--blue);
  transition: background var(--t) var(--ease), transform var(--t) var(--ease-out);
}
/* El paso activo se marca tambien en el numero: sin esto, en el momento en que
   la tarjeta esta cruzando de un paso al otro no queda claro cual manda. */
.step.is-active .step__num { background: var(--blue); color: #fff; }
.step.is-active:hover .step__num { transform: scale(1.06); }

.step__title {
  font-size: var(--fs-step-title);
  line-height: 1.2;
  color: var(--blue);
}
.step__text {
  margin-top: calc(20 * var(--s));
  max-width: calc(830 * var(--s));   /* reproduce los cortes de línea del diseño */
  font-size: var(--fs-step-text);
  line-height: var(--lh-step-text);
  color: var(--blue-ink);
}

/* --------------------------------------------------------------- TRABAJOS - */
.work { padding-top: calc(365 * var(--s)); }       /* chip en y=5532 */

.work__viewport {
  /* La tarjeta sube 8px en hover y acá se recorta en Y (overflow-y:hidden hace
     falta para que el auto-scroll no saque scrollbar vertical). Sin colchón el
     hover le come esos 8px al borde de arriba. Los márgenes lo compensan, así
     que la geometría de la sección no se mueve. */
  --work-bleed: calc(12 * var(--s));
  padding-block: var(--work-bleed);
  margin-top: calc(53 * var(--s) - var(--work-bleed));   /* tarjetas en y=5754 */
  margin-bottom: calc(-1 * var(--work-bleed));
  overflow-x: auto;
  overflow-y: hidden;
  cursor: grab;
  scrollbar-width: none;
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;
  touch-action: pan-x pan-y;
}
.work__viewport::-webkit-scrollbar { display: none; }
.work__viewport.is-dragging { cursor: grabbing; }
.work__viewport.is-dragging .work-card { pointer-events: none; }

.work__track {
  display: flex;
  width: max-content;
}
/* Margen por tarjeta, no gap: el loop del carrusel salta de copia en copia y
   con gap la copia no mide un múltiplo exacto (N ítems dejan N-1 huecos). */
.work__track > li { margin-right: calc(14 * var(--s)); }

.work__nav {
  margin-top: calc(32 * var(--s));
  display: flex;
  justify-content: flex-end;
  gap: calc(12 * var(--s));
}
.work__arrow {
  display: grid;
  place-items: center;
  width: calc(52 * var(--s));
  height: calc(52 * var(--s));
  border-radius: 50%;
  color: var(--blue);
  box-shadow: inset 0 0 0 1px var(--blue-soft);
  transition: background var(--t) var(--ease), color var(--t) var(--ease);
}
.work__arrow:hover { background: var(--blue); color: #fff; }
.work__arrow svg { width: 46%; }

/* ------------------------------------------------------------- EL MOTOR --- */
/* Pantalla completa: el bloque queda solo en cuadro, sin que asome la seccion
   de arriba ni la de abajo. El padding del Figma (246 arriba) se reduce mucho
   porque, al centrarse en el viewport, el aire ya lo da la propia pantalla.
   Es min-height y no height para que en pantallas muy bajas crezca en vez de
   recortar el orbe. */
.engine {
  min-height: 100vh;
  display: grid;
  align-content: center;
  padding-block: calc(72 * var(--s));
  text-align: center;
  overflow: hidden;
}
.engine__title {
  position: relative;
  display: inline-block;
  font-size: var(--fs-h2-engine);
  line-height: 1.1;
}
.engine__spark { position: absolute; width: calc(40 * var(--s)); }
.engine__spark--l { left: calc(-56 * var(--s)); top: calc(-14 * var(--s)); }
.engine__spark--r {
  right: calc(-64 * var(--s));
  top: calc(-30 * var(--s));
  width: calc(56 * var(--s));
}

.orbit {
  position: relative;
  /* Mas aire que los 63 del Figma: la seccion ahora esta centrada a pantalla
     completa, asi que el titulo quedaba pegado a la tira de logos. */
  margin-top: calc(140 * var(--s));
  height: calc(345 * var(--s));
  display: grid;
  place-items: center;
}
.orbit__rings {
  position: absolute;
  inset: calc(-60 * var(--s)) 0;
  display: grid;
  place-items: center;
}
.orbit__rings svg { width: calc(1100 * var(--s)); height: calc(460 * var(--s)); }

.orbit__viewport {
  width: 100%;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.orbit__track {
  display: flex;
  align-items: center;
  width: max-content;
  animation: orbitMarquee var(--orbit-vuelta) linear infinite;
}
/* Celda de ancho FIJO, no margen: los logos miden distinto y con un margen
   constante el paso de centro a centro cambia con cada uno, asi que el relevo
   del disco caia a destiempo. Con la celda fija el paso es siempre el mismo y
   la animacion es pareja. */
.orbit__track li {
  flex: 0 0 auto;
  width: var(--orbit-celda);
  display: grid;
  place-items: center;
}
.orbit__track img {
  height: var(--orbit-tira);
  width: auto;
  max-width: var(--orbit-tira-max);
  object-fit: contain;
  /* Los logos de marca vienen a color y la tira los muestra como silueta gris,
     igual que el disco los muestra como silueta blanca. Se resuelve con filtro
     y no retocando los archivos, asi el original queda intacto y sirve para
     los dos estados. El 0.47 esta elegido para que el gris compuesto de al
     mismo tono que tenian los logos anteriores (#5A646D al 80%). */
  filter: brightness(0) opacity(.47);
}
@keyframes orbitMarquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

.orbit {
  /* Una vuelta completa del marquee. Con 9 posiciones da un logo cada ~4.9s:
     ese es el tiempo que cada uno se queda grande dentro del disco. */
  --orbit-vuelta: 44s;
  /* El logo del disco es bastante mas grande que el de la tira (76). El tope
     de ancho existe porque los logotipos anchos (Spotify) llegarian al borde
     del circulo y el overflow los cortaria. */
  --orbit-logo: calc(90 * var(--s));
  --orbit-logo-max: calc(163 * var(--s));
  --orbit-salto: 980ms;
  --orbit-fundido: 560ms;
  --orbit-latido: 2.6s;
  /* Ancho de celda y tiempo por logo. La duracion de la vuelta NO se escribe a
     mano: la calcula el JS como celdas x paso, asi el ritmo no cambia al
     agregar o sacar logos. */
  --orbit-celda: calc(344 * var(--s));
  /* Alto del logo en la tira y tope de ancho. Los logotipos anchos (Claude,
     Gemini) topan por ancho y los compactos por alto, asi que hacen falta los
     dos para que ninguno domine. */
  --orbit-tira: calc(62 * var(--s));
  --orbit-tira-max: calc(196 * var(--s));
  --orbit-paso: 4.75s;
  /* Diametro del disco. La ventana de .orbit__slot sale de aca: si los dos
     valores se escriben por separado, en los breakpoints se desfasan y el
     logo asoma por fuera del azul (medido: 4.2px a 768px antes de unificarlos). */
  --orbit-d: calc(268 * var(--s));
}
.orbit__hub {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: var(--orbit-d);
  height: var(--orbit-d);
}
.orbit__disc {
  display: grid;
  place-items: center;
  width: var(--orbit-d);
  height: var(--orbit-d);
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 calc(18 * var(--s)) calc(48 * var(--s)) rgba(0, 111, 255, .34);
}
/* El logo NO cruza el disco a la vista: la tira pasa por detras (el disco es
   opaco) y adentro del circulo vive este hueco, que se renueva. El relevo es
   un fundido en el centro: el que entra aparece creciendo desde chico y el que
   sale se disuelve agrandandose. El borde redondeado con overflow queda como
   red de seguridad para que nada asome fuera del azul. */
.orbit__slot {
  position: absolute;
  z-index: 2;                       /* por encima del disco */
  width: var(--orbit-d);
  height: var(--orbit-d);
  border-radius: 50%;
  overflow: hidden;
  display: grid;
  place-items: center;
  pointer-events: none;
}
/* Apilados en la misma celda: durante el relevo conviven dos. */
.orbit__slot img {
  grid-area: 1 / 1;
  height: var(--orbit-logo);
  width: auto;
  max-width: var(--orbit-logo-max);
  object-fit: contain;
  filter: brightness(0) invert(1);
  will-change: transform, opacity;
}
@keyframes orbitIn {
  0%   { transform: scale(.58); opacity: 0; }
  46%  { opacity: 1; }
  66%  { transform: scale(1.09); }   /* el latido, ya entrado */
  100% { transform: scale(1); }
}
@keyframes orbitOut {
  0%   { transform: scale(1); opacity: 1; }
  100% { transform: scale(1.22); opacity: 0; }
}
/* Dos animaciones encadenadas: primero la entrada y despues, arrancando justo
   cuando esa termina, un latido suave que no se detiene. Van en la misma
   declaracion porque las dos mueven transform y la ultima de la lista es la
   que manda mientras corre; con el delay igual a la entrada, ninguna pisa a la
   otra. */
@keyframes orbitLatido {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.045); }
}
.orbit__slot img.is-in {
  animation: orbitIn var(--orbit-salto) var(--ease-out) both,
             orbitLatido var(--orbit-latido) var(--ease) var(--orbit-salto) infinite;
}
/* La salida va mas rapido que la entrada a proposito: los dos logos son
   blancos sobre el mismo azul, y si se cruzan a media opacidad el centro se
   ensucia. Asi el que sale ya casi no esta cuando el que entra toma cuerpo. */
.orbit__slot img.is-out { animation: orbitOut var(--orbit-fundido) var(--ease-out) both; }
.orbit__pulse {
  position: absolute;
  width: var(--orbit-d);
  height: var(--orbit-d);
  border-radius: 50%;
  /* El diseño no tiene halo: dejamos un pulso muy tenue, sólo como señal
     de que el nodo está "vivo". */
  box-shadow: 0 0 0 1px rgba(0, 111, 255, .35);
  animation: orbitPulse 3.6s var(--ease) infinite;
}
@keyframes orbitPulse {
  0%   { transform: scale(1);    opacity: .55; }
  70%  { transform: scale(1.22); opacity: 0; }
  100% { transform: scale(1.22); opacity: 0; }
}
.orbit__spin {
  position: absolute;
  width: calc(300 * var(--s));
  height: calc(300 * var(--s));
  animation: orbitSpin 24s linear infinite;
}
@keyframes orbitSpin { to { transform: rotate(360deg); } }

/* -------------------------------------------------------------- CTA ------- */
.cta {
  position: relative;
  padding-top: calc(288 * var(--s));               /* chip en y=7330 */
  background: linear-gradient(180deg,
    var(--bg) 0,
    var(--bg) calc(60 * var(--s)),
    var(--panel) calc(90 * var(--s)),
    var(--panel) 100%);
}
.cta__inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(40 * var(--s));
}
.cta__title {
  margin-top: calc(29 * var(--s));                 /* título en y≈7406 */
  font-size: var(--fs-h2-cta);
  line-height: var(--lh-h2-cta);
}
.cta__slot { margin-top: calc(17 * var(--s)); }

/* ---- El boton se transforma en formulario --------------------------------
   El panel arranca a la escala del boton y crece hasta su tamano, con el mismo
   gris y el mismo radio: asi se lee como que la pastilla se abre y no como que
   aparece otra cosa encima. El [hidden] lo saca el JS un frame antes de
   animar, porque un elemento en display:none no transiciona.

   Va FUERA DEL FLUJO, clavado en la esquina superior izquierda de la pastilla.
   Antes era una fila mas del flex y al abrirse estiraba la seccion: el panel
   nacia por debajo del robot y todo lo de abajo —footer incluido— se corria.
   Ahora el punto donde hiciste click no se mueve ni un pixel, y el panel crece
   desde ahi hacia la derecha y hacia abajo, que es donde hay aire (la pastilla
   esta al pie de la columna y quedan 141px hasta el bloque azul del footer). */
.cta__slot { position: relative; }
.cta__form {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;                      /* por delante del robot */
  /* El ancho llega hasta donde empieza el robot, no hasta el borde del
     contenedor. Como el panel ya no es una fila propia, invadir esa franja
     le taparia las patas. */
  width: calc(1158 * var(--s));
  /* Escala propia del panel. La referencia se midio sobre un panel de 1582
     unidades de ancho; este mide 1158, asi que TODAS sus medidas internas se
     multiplican por 1158/1582 = .732 y las proporciones quedan identicas
     (alto/ancho .153, la caja del mensaje al 46% del alto). Sin esto el panel
     se achica pero su contenido no, y deja de parecerse a la referencia. */
  --sf: calc(var(--s) * .732);
  display: block;
  padding: calc(39 * var(--sf)) calc(32 * var(--sf)) calc(22 * var(--sf));
  border-radius: calc(58 * var(--sf));
  background: var(--chip-bg);
  opacity: 0;
  transform: scale(.94);
  transform-origin: left top;      /* crece desde la esquina de la pastilla */
  transition: opacity var(--t) var(--ease), transform var(--t) var(--ease-out);
}
.cta__form.is-open { opacity: 1; transform: none; }
.cta__form[hidden] { display: none; }

.cta__field {
  width: 100%;
  border: 0;
  background: none;
  font-family: var(--font-text);
  /* Piso de 16px: por debajo de eso Safari en iOS hace zoom solo al enfocar
     un campo y descoloca toda la pantalla. En escritorio manda el calc (18px). */
  font-size: max(16px, calc(30 * var(--sf)));
  line-height: 1.3;
  color: var(--ink);
  resize: none;
}
/* El placeholder ES la etiqueta visible de cada campo, asi que tiene que
   leerse: con --ink-mute daba 3.96:1 sobre el gris del panel, debajo del 4.5.
   Sobre este fondo el margen para aclararlo llega hasta #656565, o sea nada,
   asi que va al mismo color que el texto escrito (4.96). */
.cta__field::placeholder { color: var(--ink); opacity: 1; }
.cta__field:focus-visible { outline: none; }
/* El foco se marca en el panel entero: los campos no tienen borde propio, asi
   que un contorno por campo quedaria flotando en el medio del gris. */
.cta__form:has(.cta__field--email:focus) { box-shadow: inset 0 0 0 2px var(--blue-strong); }
.cta__msgbox:has(.cta__field--msg:focus) { box-shadow: inset 0 0 0 2px var(--blue-strong); }
/* La linea del email queda sobre el panel, con algo mas de sangria que la caja
   de abajo: es lo que hace que se lea como titulo del bloque. */
.cta__field--email { padding-left: calc(16 * var(--sf)); }

/* Honeypot: fuera de pantalla de verdad (no display:none, que algunos bots
   ya saben ignorar) pero sin dejarle caja al layout ni orden de tabulacion —
   el tabindex="-1" y el aria-hidden del HTML rematan lo que el u-sr del label
   no cubre. */
.cta__hp {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* La caja del mensaje: mas clara que el panel, con el boton adentro a la
   derecha. Es la pieza que faltaba — en el diseño el campo esta APOYADO sobre
   el panel, no suelto sobre el mismo gris. */
.cta__msgbox {
  margin-top: calc(26 * var(--sf));
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: calc(24 * var(--sf));
  min-height: calc(106 * var(--sf));
  padding: calc(18 * var(--sf)) calc(20 * var(--sf)) calc(18 * var(--sf)) calc(32 * var(--sf));
  border-radius: calc(46 * var(--sf));
  background: rgb(255 255 255 / .55);
}
.cta__field--msg { overflow: hidden; }

.cta__send {
  display: grid;
  place-items: center;
  width: calc(72 * var(--sf));
  height: calc(72 * var(--sf));
  border: 0;
  border-radius: 50%;
  background: var(--blue);
  cursor: pointer;
  transition: background var(--t) var(--ease), transform var(--t) var(--ease-out);
}
.cta__send:hover { background: var(--blue-strong); transform: scale(1.06); }
.cta__send svg { width: 46%; }
/* is-sending deshabilita el boton (anti doble-submit): el disabled nativo ya
   frena el click, esto es solo la señal visual de que no hay nada para
   apretar mientras el fetch esta en vuelo. */
.cta__send:disabled { opacity: .55; cursor: default; }
.cta__send:disabled:hover { background: var(--blue); transform: none; }

/* Avisos de resultado del envio, sobre el gris compuesto del panel
   (--panel + --chip-bg encima ≈ #EAEBEB). --blue-strong ahí da 4.42:1 —
   se queda corto de 4.5 — así que estos dos colores son propios del aviso,
   afinados contra ese fondo puntual: #0052D6 da 5.53:1, #B23016 da 5.28:1. */
.cta__note {
  margin-top: calc(20 * var(--sf));
  font-family: var(--font-text);
  font-size: max(14px, calc(24 * var(--sf)));
  line-height: 1.4;
}
.cta__note--ok { color: #0052D6; }
.cta__note--error { color: #B23016; }
.cta__note--error a { text-decoration: underline; }

/* El retardo en visibility es lo que deja que la opacidad termine su viaje
   antes de que el elemento desaparezca de verdad. */
.pill.is-morphing {
  opacity: 0;
  visibility: hidden;
  transform: scale(.94);
  pointer-events: none;
  transition: opacity var(--t) var(--ease), transform var(--t) var(--ease-out),
              visibility 0s linear var(--t);
}
.pill[hidden] { display: none; }

.cta__robot {
  width: calc(318 * var(--s));
  height: auto;
  margin-right: calc(64 * var(--s));
  margin-top: calc(10 * var(--s));
  /* sombra de contacto, como en el diseño */
  filter: drop-shadow(0 calc(26 * var(--s)) calc(20 * var(--s)) rgba(90, 110, 130, .16));
  animation: robotFloat 6s var(--ease) infinite;
}
@keyframes robotFloat {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(calc(-18 * var(--s))) rotate(-1.5deg); }
}

/* -------------------------------------------------------------- FOOTER ---- */
/* El bloque azul arranca sobre el panel #F9F9F9 y termina sobre el fondo
   de página: de ahí el corte duro del degradé a los 364px. */
.footer {
  padding-top: calc(171 * var(--s));               /* bloque azul en y=7942 */
  padding-bottom: calc(267 * var(--s));
  background: linear-gradient(180deg,
    var(--panel) 0,
    var(--panel) calc(364 * var(--s)),
    var(--bg) calc(364 * var(--s)));
}
.footer__block {
  width: min(100% - 210 * var(--s), calc(1540 * var(--s)));
  margin-inline: auto;
  border-radius: calc(60 * var(--s));
  background: var(--blue);
  color: #fff;
  overflow: hidden;
  position: relative;
}
.footer__inner { padding: calc(110 * var(--s)) calc(107 * var(--s)) 0; }

.footer__top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(40 * var(--s));
}
.footer__brand img { width: calc(264 * var(--s)); }
.footer__tagline {
  /* Medido contra el diseño: entre la tinta del logotipo y el tagline hay ~25px.
     El 2 de antes no alcanzaba porque el lienzo del logo ya trae aire propio
     abajo, pero mucho menos del que pide el diseño, y el logo se leia pegado. */
  margin-top: calc(22 * var(--s));
  font-size: var(--fs-lead);
  line-height: 1.3;
  font-weight: 600;
  color: #fff;
}

.footer__cols {
  margin-top: calc(78 * var(--s));                 /* columnas en y=8215 */
  display: grid;
  grid-template-columns: calc(225 * var(--s)) calc(298 * var(--s)) 1fr auto;
  gap: calc(30 * var(--s));
}
.footer__col-title {
  font-size: var(--fs-footer-title);
  line-height: 1;
  color: #fff;
}
.footer__col ul { margin-top: calc(30 * var(--s)); display: grid; gap: calc(14 * var(--s)); }
.footer__col a,
.footer__contact a,
.footer__contact p {
  font-size: var(--fs-footer-link);
  line-height: var(--lh-footer-link);
  font-weight: 500;
  color: var(--on-blue-soft);
  transition: color var(--t-fast) var(--ease);
}
.footer__col a:hover,
.footer__contact a:hover { color: #fff; }

.footer__contact { display: grid; gap: calc(26 * var(--s)); text-align: right; }
.footer__mail { text-decoration: underline; text-underline-offset: 3px; }

.footer__bottom {
  position: relative;
  margin-top: calc(131 * var(--s));
  height: calc(85 * var(--s));       /* bloque azul = 666px de alto */
}
.footer__ghost {
  position: absolute;
  left: calc(-107 * var(--s));
  bottom: calc(-30 * var(--s));
  width: calc(810 * var(--s));
  opacity: .22;
  pointer-events: none;
}
.footer__copy {
  position: absolute;
  right: 0;
  top: calc(14 * var(--s));
  font-size: calc(17 * var(--s));
  line-height: 1;
  font-weight: 500;
  color: var(--on-blue-soft);
}

/* ---- 404 (404.html) -------------------------------------------------------
   Página aparte, no hay hito en el Figma del one-pager para medir: los
   valores acá repiten el ritmo del .hero (mismo gap entre título/lead/CTAs)
   en vez de inventar una escala nueva. Ocupa el viewport completo porque es
   la única sección de esa página — no hay nada más abajo para hacer scroll. */
.notfound {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background:
    radial-gradient(115% 62% at 50% -8%, #FDFEFE 0%, rgba(253, 254, 254, 0) 64%),
    var(--bg);
}
.notfound__inner { padding-block: calc(80 * var(--s)); }
.notfound__brand { display: inline-block; margin-bottom: calc(56 * var(--s)); }
.notfound__eyebrow {
  font-family: var(--font-text);
  font-size: calc(17 * var(--s));
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--blue-strong);
}
.notfound__title {
  margin-top: calc(14 * var(--s));
  font-size: var(--fs-h1);
  line-height: var(--lh-h1);
  color: var(--blue);
}
.notfound__lead {
  margin: calc(30 * var(--s)) auto 0;
  max-width: calc(560 * var(--s));
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  color: var(--ink);
}
.notfound__actions {
  margin-top: calc(30 * var(--s));
  display: flex;
  justify-content: center;
  gap: calc(25 * var(--s));
  flex-wrap: wrap;
}
