/*
 * Componentes — construidos contra el Figma.
 * Cada uno se apoya solo en tokens.
 */

/* Botones -------------------------------------------------------------- */

/*
 * UN botón. Antes eran cuatro definiciones del mismo objeto con tres alturas
 * (44 / 48 / 52) y dos rellenos horizontales: el del hero, el de enviar del
 * buscador, el del formulario y este. Las cuatro salen ahora de acá y de los
 * dos escalones de `--control-h` (ver tokens.css).
 */
/*
 * `[hidden]` tiene que ganarle a la clase.
 *
 * La hoja del navegador declara `[hidden] { display: none }` con especificidad
 * 0-0-1, y `.btn { display: inline-flex }` es 0-1-0: la clase gana y el elemento
 * se ve igual con el atributo puesto. Medido: `el.hidden === true` y
 * `getComputedStyle(el).display === "flex"`.
 *
 * Importa porque el botón de pausa del pie sale con `hidden` a propósito —sin
 * JavaScript no funciona, y un botón que no hace nada es peor que no tenerlo— y
 * `movimiento.js` vuelve a ponérselo cuando el visitante ya tiene activada la
 * preferencia de movimiento reducido. En los dos casos el botón seguía en
 * pantalla ofreciendo algo que no iba a pasar.
 */
.btn[hidden] {
  display: none;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2xs);
  min-height: var(--control-h);
  padding: var(--sp-2xs) var(--control-pad-x);
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
  text-decoration: none;
  white-space: nowrap;   /* si no, dentro de un flex el rótulo se parte en varias líneas */
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

.btn svg { width: 16px; height: 16px; flex-shrink: 0; }

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

.btn--primary { background: var(--c-primary); color: var(--c-on-primary); }
/* El filo se aclara y el relleno casi no: el porqué está en `--c-primary-hover`
   (tokens.css). `.btn` ya trae `border: 1px solid transparent` y ya transiciona
   `border-color`, así que esto no mueve un píxel de la maqueta. */
.btn--primary:hover {
  background: var(--c-primary-hover);
  border-color: var(--c-primary-filo-hover);
  color: var(--c-on-primary);
}

/* Píldora con borde: "Ver más casos" */
.btn--pill {
  border-radius: var(--radius-pill);
  border-color: var(--c-border);
  background: transparent;
  color: var(--c-text);
  padding-inline: var(--sp-m);
}

.btn--pill:hover {
  border-color: var(--c-text-faint);
  color: var(--c-text);
}

/* Botón principal del hero. Se monta SIEMPRE sobre `.btn` (`class="btn
   btn--cta"`, ver parts/hero.php): de ahí salen la caja flex y la transición.
   Acá va lo que lo diferencia, que en el Figma es bastante: no es una píldora
   horizontal sino un bloque alto con la flecha diagonal arriba y el rótulo
   debajo, en dos líneas de mayúsculas pequeñas. Es el único botón del pliegue
   y el único con esta forma en todo el sitio. */
/*
 * DESVIACIÓN DELIBERADA DEL FIGMA (decisión de Alex, 19/08).
 *
 * El diseño dibuja este botón en vertical: flecha arriba y rótulo debajo en dos
 * líneas. Se implementó así y no convence: la caja queda alta y con aire muerto,
 * y el rótulo pierde peso frente a la flecha. Va en horizontal —icono y texto en
 * la misma línea—, que es como se comportan los demás botones del sitio y como
 * el visitante espera leer una llamada a la acción.
 *
 * Se propone al cliente con el antes y el después, como marca el acuerdo de
 * criterio de diseño; si prefiere el original, se revierte cambiando estas
 * pocas reglas.
 */
.btn--cta {
  flex-direction: row;
  align-items: center;
  justify-content: flex-start;
  gap: var(--sp-2xs);

  /* Sin ancho ni alto fijos: la caja la define el rótulo más su aire, así entra
     cualquier texto que escriba el cliente sin recortarse ni dejar hueco. El
     alto sale del escalón grande de la escala de controles: es la llamada a la
     acción principal de la pantalla, y no hay dos por página. */
  min-height: var(--control-h-lg);
  padding: var(--sp-2xs) var(--control-pad-x);
  border-radius: var(--radius-s);
  background: var(--c-primary);
  color: var(--c-on-primary);
  text-align: left;
}

.btn--cta span {
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  line-height: 1.2;

  /* En una línea mientras quepa; si el rótulo es largo, parte por palabras en
     vez de desbordar la caja. */
  white-space: normal;
  overflow-wrap: anywhere;
}

/* La flecha va detrás del rótulo: primero se lee qué hace el botón y después
   la señal de que lleva a otro sitio. */
.btn--cta svg { order: 1; }

.btn--cta:hover {
  background: var(--c-primary-hover);
  border-color: var(--c-primary-filo-hover);
  color: var(--c-on-primary);
}

.btn--cta svg {
  width: 18px;
  height: 18px;
  stroke-width: 2;
  transition: transform var(--dur-base) var(--ease-out);
}

.btn--cta:hover svg { transform: translate3d(3px, -3px, 0); }

/* Antetítulo y dato ----------------------------------------------------- *
 * Los dos rótulos chicos del sistema. Estaban escritos TRECE veces entre las
 * dos hojas, con tres colores y dos pesos: el mismo rótulo cambiaba de peso
 * entre la tarjeta de artículo y la de caso, que van una al lado de la otra.
 * Eso es, literalmente, lo que el cliente lee como "no se sigue un estilo".
 *
 *   .eyebrow   ROTULA algo: la sección, la vertical de una tarjeta, el tipo
 *              de un resultado. Versalitas monoespaciadas espaciadas.
 *              Dos colores y ninguno más:
 *                gris                → rotula, y nada más.
 *                .eyebrow--vertical  → además adscribe: dice a qué línea de
 *                                      negocio pertenece, y toma su color.
 *
 *   .datum     es el DATO al pie: la fuente citada de una cifra, la fecha de
 *              un artículo, la letra chica de condiciones. Monoespaciada
 *              chica, sin versalitas —un dato se lee, no se anuncia—.
 *
 * La diferencia no es de tamaño: es que uno nombra y el otro informa. Si algo
 * nuevo no cae claramente en uno de los dos, no es ninguno de los dos.
 */

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-eyebrow);
  font-weight: var(--fw-regular);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--c-text-faint);

  /* Los rótulos los escribe el cliente y no tienen tope de largo. Dentro de
     una celda de rejilla, `anywhere` + `min-width: 0` es lo único que impide
     que una palabra larga ensanche la columna y desbalancee la fila. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.eyebrow--vertical { color: var(--c-vertical-soft); }

.datum {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--c-text-mono);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Enlace con flecha ------------------------------------------------------ *
 * El "Ver soluciones para…" de las tarjetas de vertical, el del caso
 * destacado, el de cada solución y el pie de una tarjeta de artículo. Son 20 y
 * pico en todo el sitio y hasta el 21/08 el mismo objeto estaba escrito TRES
 * veces (`.link-arrow`, `.post-card__more` y el enlace de la tarjeta de
 * vertical), dos de ellas con valores idénticos.
 */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2xs);

  /* Medido: el texto solo daba 23 px de alto clicable, un píxel por debajo del
     mínimo de la WCAG 2.5.8. */
  min-height: var(--tap-target-min);
  color: var(--c-vertical-soft);
  font-weight: var(--fw-medium);
  font-size: var(--fs-small);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out);
}

.link-arrow svg {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  transition: transform var(--dur-base) var(--ease-out);
}

/*
 * El estado de hover es de COLOR y de SUBRAYADO, no de movimiento.
 *
 * Antes `:hover` declaraba el MISMO token que el reposo —`--c-vertical-soft`
 * en los dos—, así que el único indicio real era el desplazamiento de 4 px de
 * la flecha… que se anula con "movimiento reducido". Medido con la preferencia
 * activa: color, subrayado, fondo, opacidad y transform idénticos antes y
 * después de pasar el ratón. Veinte enlaces sin ninguna respuesta.
 *
 * El aclarado se hace mezclando el acento de la vertical con el blanco y no
 * saltando a blanco: el enlace tiene que seguir diciendo de qué vertical es.
 * Y el subrayado va ADEMÁS del color, no en su lugar: quien no distingue el
 * cambio de tono igual ve que el enlace responde.
 *
 * Los selectores de tarjeta están porque en las tarjetas-enlace el rótulo es
 * un <span>, no un <a>: quien recibe el hover es la tarjeta entera.
 */
.link-arrow:hover,
.link-arrow:focus-visible,
.vertical-card:hover .link-arrow,
.vertical-card:focus-within .link-arrow,
.post-card:hover .link-arrow,
.post-card:focus-within .link-arrow,
.case-card:hover .link-arrow,
.case-card:focus-within .link-arrow {
  color: color-mix(in srgb, var(--c-vertical-soft) 40%, var(--c-text));
  text-decoration: underline;
  text-underline-offset: 0.25em;
}

.link-arrow:hover svg,
.vertical-card:hover .link-arrow svg,
.post-card:hover .link-arrow svg,
.case-card:hover .link-arrow svg { transform: translate3d(4px, 0, 0); }

/*
 * Las flechas que se corren al pasar el ratón son adorno: el color y el
 * subrayado de arriba son los que avisan que el elemento responde, y esos NO
 * se anulan nunca. El giro del chevron del menú tampoco se toca: ese informa
 * (abierto/cerrado), y quitarlo sería quitar un dato, no una animación.
 */
@media (prefers-reduced-motion: reduce) {
  .btn--cta:hover svg,
  .link-arrow:hover svg,
  .vertical-card:hover .link-arrow svg,
  .post-card:hover .link-arrow svg,
  .case-card:hover .link-arrow svg { transform: none; }
}

/* Reveal al scroll ----------------------------------------------------- */

/* `will-change` solo mientras falta aparecer: mantenerlo después deja una capa
   de composición por elemento viva para siempre, sin ningún beneficio. */
html.js [data-reveal]:not(.is-visible) { will-change: opacity, transform; }

html.js [data-reveal]:not(.is-visible) {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
}

html.js [data-reveal] {
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
}

html.js [data-reveal].is-visible { opacity: 1; transform: none; }

/*
 * El bloque al que te MANDARON no se desliza.
 *
 * `getBoundingClientRect()` —y con él el salto al ancla— mide la caja YA
 * transformada. Un elemento que es a la vez destino de un `#hash` y
 * `[data-reveal]` está desplazado 18 px hacia abajo en el momento en que el
 * navegador calcula dónde parar, así que para 18 px de más; después la
 * animación termina, el `transform` se va, y el bloque sube esos 18 px por
 * debajo de donde tenía que quedar.
 *
 * Medido en /casos/biogen/ → /casos-de-exito/#biogen a 390 px, antes de esta
 * regla: `scrollY` 2046 en vez de 2028, y el borde superior del bloque a 71 px
 * de la ventana contra los 89 que pide `scroll-margin-top` (73 de cabecera fija
 * + 16). O sea, 2 px del bloque tapados por la cabecera y un destino que se
 * mueve solo después de aterrizar. Con la regla: 89 px, Δ 0.
 *
 * Sólo se anula el DESPLAZAMIENTO, no la aparición: el fundido de opacidad
 * sigue corriendo. Y sólo afecta al elemento del `#hash`, uno por carga.
 *
 * Va DESPUÉS de `[data-reveal]:not(.is-visible)` a propósito: las dos reglas
 * tienen la misma especificidad (0,3,1) y con especificidad igual gana la
 * última. Verificado con `getComputedStyle`, no supuesto.
 */
html.js [data-reveal]:target {
  transform: none;

  /* Sin esta línea la regla de arriba NO alcanza, y se comprobó midiendo: el
     `transform` del reveal está en `transition`, así que pasar de 18 px a 0 se
     ANIMA durante `--dur-slow` — y el navegador calcula dónde parar mientras esa
     animación corre, con la caja todavía desplazada. Medido con la regla puesta
     y sin esta línea: `scrollY` seguía dando 2046 y el bloque seguía a 71 px.
     Se deja transicionando sólo la opacidad, que es la aparición de verdad. */
  transition-property: opacity;
}

@media (prefers-reduced-motion: reduce) {
  html.js [data-reveal]:not(.is-visible) { opacity: 1; transform: none; }
}

/* Red de seguridad: si el módulo de animaciones no arrancó, el contenido se
   muestra igual. Una animación nunca puede dejar texto invisible. */
html.reveal-fallback [data-reveal] {
  opacity: 1 !important;
  transform: none !important;
}

/* Hero ----------------------------------------------------------------- */

.hero {
  position: relative;

  /* `vw` DELIBERADO: el hero es una banda a sangre, su caja ES la ventana y no
     hay ningún contenedor intermedio del que sacar la medida. Es el mismo
     criterio con el que está escrito `--section-y`. */
  padding-block: clamp(3rem, 2rem + 5vw, 6rem) clamp(3rem, 2rem + 4vw, 5rem);
  overflow: hidden;
  isolation: isolate;
}

/*
 * Fondo de luces del hero.
 *
 * En el diseño hay filamentos de luz cruzando en diagonal detrás del contenido.
 * Se resuelven con elipses muy achatadas que se desplazan y laten de opacidad.
 *
 * Por qué así y no con un video o un lienzo: la animación toca solo `translate`,
 * `scale` y `opacity`, corre fuera del hilo principal y no cuesta ni una
 * petición de red. Un fondo animado que compita con la carga sería exactamente
 * lo que arruina el LCP que hay que sostener.
 */
.hero__bg {
  position: absolute;
  inset: -15% -25%;
  z-index: -2;
  pointer-events: none;
  overflow: hidden;
}

/*
 * Cada estela es una elipse muy achatada: alta de pocos píxeles y ancha de
 * media pantalla. Con `border-radius: 50%` los extremos se afinan y el
 * degradado los desvanece, así que se lee como un filamento de luz y no como
 * una barra. Un desenfoque corto le da el halo sin convertirla en mancha.
 */
.hero__bg span {
  position: absolute;
  display: block;
  height: var(--h, 2px);
  width: var(--w, 70%);
  border-radius: 50%;
  opacity: var(--o, 0.85);
  mix-blend-mode: screen;

  /*
   * Efecto neón: el desenfoque se mantiene mínimo para que el filamento siga
   * siendo una línea nítida, y el halo lo dan dos sombras difusas del mismo
   * color, una corta y otra amplia. Es la misma lógica de un tubo de neón real:
   * un núcleo casi blanco y el color irradiando alrededor.
   */
  filter:
    blur(var(--b, 1.5px))
    drop-shadow(0 0 5px var(--c))
    drop-shadow(0 0 16px var(--c));

  background: linear-gradient(
    90deg,
    transparent,
    var(--c) 18%,
    color-mix(in srgb, var(--c) 25%, var(--c-text)) 50%,
    var(--c) 82%,
    transparent
  );

  /* La inclinación va en la propiedad `rotate` y el desplazamiento en
     `translate`. Son propiedades independientes que el navegador compone solo,
     así que la animación no tiene que repetir la rotación en cada fotograma —
     que es lo que antes dejaba el movimiento en nada. */
  rotate: var(--r, -18deg);
  animation: streak var(--dur, 18s) ease-in-out infinite alternate;
}

.hero__bg span:nth-child(1) { --c: var(--c-primary);       --r: -22deg; --w: 78%; --h: 2px;  --b: 1.5px;  --o: 1; top: 16%; left: -6%;  --dur: 14s; }
.hero__bg span:nth-child(2) { --c: var(--c-capacitacion);  --r: -14deg; --w: 66%; --h: 2px;  --b: 1px;  --o: .8; top: 30%; left: 8%;   --dur: 19s; animation-delay: -7s; }
.hero__bg span:nth-child(3) { --c: var(--c-primary);       --r: -27deg; --w: 90%; --h: 3px;  --b: 3px; --o: .7; top: 46%; left: -12%; --dur: 24s; animation-delay: -12s; }
.hero__bg span:nth-child(4) { --c: var(--c-marketing);     --r: -9deg;  --w: 46%; --h: 2px;  --b: 1.5px;  --o: .75;  top: 62%; left: -4%;  --dur: 17s; animation-delay: -4s; }
.hero__bg span:nth-child(5) { --c: var(--c-capacitacion);  --r: -30deg; --w: 72%; --h: 2px;  --b: 1px;  --o: .75;  top: 74%; left: 14%;  --dur: 21s; animation-delay: -16s; }
.hero__bg span:nth-child(6) { --c: var(--c-ecommerce);     --r: -19deg; --w: 54%; --h: 2px;  --b: 2px;  --o: .55; top: 8%;  left: 30%;  --dur: 26s; animation-delay: -20s; }
.hero__bg span:nth-child(7) { --c: var(--c-primary);       --r: -12deg; --w: 60%; --h: 2px;  --b: 1px;  --o: .85;  top: 88%; left: 22%;  --dur: 16s; animation-delay: -9s; }

/*
 * Se anima solo `transform` y `opacity`: las dos propiedades que el navegador
 * resuelve sin recalcular el diseño ni volver a pintar. Un fondo que animara
 * posición o tamaño reales costaría cuadro a cuadro justo mientras carga la
 * página, que es donde se pierde el LCP.
 */
@keyframes streak {
  from {
    translate: -16% 0;
    scale: 0.92 1;
    opacity: calc(var(--o, .55) * 0.45);
  }
  50% {
    opacity: var(--o, .55);
  }
  to {
    translate: 18% 0;
    scale: 1.14 1;
    opacity: calc(var(--o, .55) * 0.5);
  }
}

/* Retícula vertical tenue, como en el diseño. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to right,
    color-mix(in srgb, var(--c-text) 4%, transparent) 0 1px,
    transparent 1px 18%
  );
}

@media (prefers-reduced-motion: reduce) {
  .hero__bg span { animation: none; }
}

/*
 * Entrada escalonada del hero.
 *
 * Se resuelve con animaciones CSS y NO con el observador de aparición, por dos
 * motivos: el hero está siempre sobre el pliegue (no hay nada que observar) y,
 * sobre todo, dentro del hero vive el elemento que mide el LCP. Un elemento en
 * `opacity: 0` NO es candidato a LCP ("Elements with an opacity of 0, that are
 * invisible to the user", web.dev/articles/lcp), así que lo que compita por el
 * LCP anima SOLO `transform`: aparece con el primer fotograma, sube unos
 * píxeles y listo.
 *
 * CUÁL compite se midió, no se supuso (PLAN §5.5). La primera versión de este
 * bloque daba por hecho que era el titular y dejaba fundir la media; medido con
 * `PerformanceObserver` en las 9 rutas, el titular es solo el PRIMER candidato y
 * la imagen del hero lo supera después. Con la media fundiendo, el LCP de `/`
 * era 1096 ms (mediana de 5) con la imagen ya descargada a los 89 ms: 992 ms de
 * retardo de render puros, exactamente `--stagger`×3 + 720 ms. Con la media
 * animando solo `transform`, 100 ms. Por eso la media usa `hero-lift`.
 *
 * El fundido queda para bajada y botón, que sí son demasiado chicos para ganar
 * el LCP. Todo el ritmo sale de un único token, `--stagger`.
 *
 * Envuelto en `no-preference`: quien pidió menos movimiento no recibe ninguna
 * animación, y el estado por defecto del marcado ya es el visible.
 *
 * Va `backwards` y no `both`. `both` deja aplicado el último fotograma DESPUÉS
 * de terminar, y ese fotograma incluye `transform: none` — que no es lo mismo
 * que no tener transform: el valor calculado queda en `matrix(1,0,0,1,0,0)`
 * (medido acá), y un transform establecido, aunque sea la identidad, crea un
 * bloque contenedor y una capa de composición que ya no se retira nunca. Hoy no
 * rompe nada porque dentro del hero no hay ningún `position: sticky`, pero
 * mantiene vivas cuatro capas por página a cambio de nada. `backwards` aplica el
 * primer fotograma antes de arrancar —que es lo único que hace falta para que no
 * haya destello, incluso con el retardo— y al terminar no deja rastro.
 */
@media (prefers-reduced-motion: no-preference) {
  .hero__title {
    animation: hero-lift 620ms var(--ease-out) backwards;
  }

  .hero__lead {
    animation: hero-enter 620ms var(--ease-out) backwards;
    animation-delay: var(--stagger);
  }

  .hero__actions {
    animation: hero-enter 620ms var(--ease-out) backwards;
    animation-delay: calc(var(--stagger) * 2);
  }

  /* Es el elemento LCP en 7 de las 9 rutas: transform, nunca opacity. */
  .hero__media {
    animation: hero-lift 720ms var(--ease-out) backwards;
    animation-delay: calc(var(--stagger) * 3);
  }
}

/* Solo transform: el titular nunca deja de estar pintado. */
@keyframes hero-lift {
  from { transform: translate3d(0, 14px, 0); }
  to   { transform: none; }
}

@keyframes hero-enter {
  from { opacity: 0; transform: translate3d(0, 16px, 0); }
  to   { opacity: 1; transform: none; }
}

/* El título ocupa el ancho completo; debajo, dos columnas: texto y media. */
.hero__title {
  font-size: var(--fs-display);
  line-height: 1.02;
  letter-spacing: var(--tracking-tighter);
  font-weight: var(--fw-bold);
  max-width: 18ch;
}

/* La parte acentuada del titular toma el color de la vertical de la página.
   En el inicio `--c-vertical` vale `--c-primary`, así que el azul de marca se
   mantiene sin excepciones en el CSS. */
.hero__title em {
  font-style: normal;
  color: var(--c-vertical);
}

/* La coda del titular: su propia línea y cuerpo menor, como en el diseño.
   Es lo que le da tres escalones al titular en vez de un bloque plano. */
.hero__title small {
  display: block;
  margin-top: 0.08em;
  font-size: 0.56em;
  font-weight: var(--fw-bold);
  letter-spacing: -0.02em;
}

.hero__inner {
  display: grid;
  gap: var(--sp-xl);
  margin-top: var(--sp-l);
}

@media (min-width: 960px) {
  .hero__title { max-width: none; }

  .hero__inner {
    grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
    align-items: start;
    gap: var(--sp-2xl);
  }
}

.hero__lead {
  max-width: 26ch;
  font-size: var(--fs-lead);
  line-height: 1.35;
  color: var(--c-text);
}

/* `vw` DELIBERADO, mismo motivo que el relleno del hero: es el ritmo vertical
   de una banda a sangre. */
.hero__actions { margin-top: clamp(var(--sp-xl), 6vw, var(--sp-3xl)); }

.hero__media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-l);
  overflow: hidden;
  background: var(--c-surface);
}

.hero__media > img,
.hero__media > video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Cinta de clientes ---------------------------------------------------- *
 * En escala de grises, como pidió el cliente. Al pasar el ratón la cinta se
 * detiene y sube el brillo: deja leer el nombre sin sacarlo del tono sobrio.
 *
 * Tratamiento del Figma: los nombres van en cuerpo chico y gris, al pie del
 * hero. Antes eran de 36 px y una sección aparte, y competían de igual a igual
 * con el titular; la cinta acompaña, no anuncia.
 */

.client-strip {
  --marquee-duration: 42s;

  /*
   * Aire entre logos. UN solo valor porque el `padding-right` de la pista tiene
   * que ser idéntico al hueco: es lo que hace que la costura del bucle caiga
   * justo donde cae cualquier otro hueco. Estaban escritos dos veces y podían
   * separarse sin que nada avisara.
   *
   * `vw` DELIBERADO: la cinta va a sangre, de borde a borde de la ventana; no
   * hay contenedor del que sacar la medida. El techo es `--sp-2xl` y no el
   * 4,5rem suelto que había, que era el único escalón de espaciado del sitio
   * que no existía en la escala.
   */
  --strip-gap: clamp(var(--sp-l), 5vw, var(--sp-2xl));

  position: relative;
  display: flex;
  overflow: hidden;
  padding-block: var(--sp-s);
  mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}

/* Al pie del hero: pegada al bloque de arriba, sin líneas que la conviertan en
   una franja propia. */
/* `vw` DELIBERADO en las dos: es el aire vertical de una banda a sangre, el
   mismo criterio que `--section-y`. El techo pasa de un 3,5rem suelto —el único
   escalón de espaciado que no existía en la escala— a `--sp-2xl`; a 1440 px la
   diferencia es de 1,6 px. */
.client-strip--hero {
  margin-top: clamp(var(--sp-m), 4vw, var(--sp-2xl));
}

/* Suelta, bajo su propia cabecera (Sobre Nosotros): ahí sí lleva las líneas
   que la separan de lo que viene antes y después. */
.client-strip--framed {
  margin-top: clamp(var(--sp-s), 2vw, var(--sp-m));
  padding-block: var(--sp-m);
  border-block: 1px solid var(--c-border-soft);
}

.client-strip__track {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: var(--strip-gap);
  padding-right: var(--strip-gap);
  animation: marquee-scroll var(--marquee-duration) linear infinite;
}

/*
 * Fuera de pantalla se pausa de verdad: no tiene sentido animar algo que nadie
 * ve, y es lo que evita gastar batería de fondo.
 *
 * La desaceleración al pasar el ratón NO está acá: la hace `marquee.js` con
 * `updatePlaybackRate()`, porque `animation-duration` no es animable y desde
 * CSS el único gesto posible es `paused`, que corta de golpe. Ver el comentario
 * del módulo.
 */
.client-strip.is-offscreen .client-strip__track {
  animation-play-state: paused;
}

/*
 * Movimiento pausado a pedido del visitante (WCAG 2.2.2).
 *
 * PARECE el mismo caso que `prefers-reduced-motion` y no lo es. La diferencia
 * no está en el gesto: está en el DOM que se encuentra cada uno.
 *
 *   - Con la preferencia del sistema, `marquee.js` sale ANTES de duplicar
 *     (`if (reduced) return`), así que la pista tiene sólo sus elementos
 *     reales. Envolverla ahí es lo correcto: se ven todos y no falta ninguno.
 *   - Con este botón la cinta ya está montada: `fill()` copió el contenido
 *     hasta cubrir el doble de la ventana y encima `marquee.js` añadió un clon
 *     entero de la pista. Envolver ESO apila las copias.
 *
 * Medido pulsando el botón en el inicio a 390 px: `.client-strip` pasaba de 72
 * a 1224 px y `.marquee` de 79,45 a 239,81, y el documento crecía 1313 px de
 * golpe debajo de quien estaba leyendo. Y al RECARGAR con la preferencia ya
 * guardada era peor: envuelta, la pista mide 720 px, nunca llega al doble de la
 * ventana que `fill()` exige y el bucle duplica hasta el tope de 200 copias —
 * medido a 1440: cinta de clientes de 1944 px y cinta de palabras de 6070,5.
 *
 * Así que acá se PAUSA y nada más, que es literalmente lo que pide la 2.2.2.
 * `animation-play-state` y no `animation: none`: `none` devuelve la pista a su
 * posición inicial de un salto, y lo que el visitante pidió fue detenerla, no
 * rebobinarla.
 *
 * El contenido no queda escondido: la caja pasa a desplazarse a mano. Es lo que
 * el comentario de `marquee.js` ya daba por hecho —«el CSS le devuelve el
 * scroll manual»— y que no estaba implementado en ninguna parte.
 *
 * Todo el bloque va bajo `no-preference` a propósito. El atributo se escribe
 * igual con la preferencia del sistema activa (movimiento.js lo pone siempre,
 * para que diga la verdad aunque el botón se retire), y sin esta guarda estas
 * reglas —más específicas— le ganarían al `@media (prefers-reduced-motion)` de
 * más abajo y le pondrían `overflow-y: hidden` a una pista ya envuelta en
 * varias filas, recortándola.
 */
@media (prefers-reduced-motion: no-preference) {
  [data-movimiento="pausado"] .client-strip__track,
  [data-movimiento="pausado"] .marquee__track {
    animation-play-state: paused;
  }

  [data-movimiento="pausado"] .client-strip,
  [data-movimiento="pausado"] .marquee {
    overflow-x: auto;

    /* Explícito: con sólo `overflow-x`, el eje Y computa `auto` y aparece una
       barra vertical en una caja donde no hay nada que desplazar de alto. */
    overflow-y: hidden;
  }

  /* El degradado de los bordes existía para que la cinta "entrara y saliera" de
     pantalla. Quieta sólo apaga los logos de las puntas, que son justamente los
     que hay que poder alcanzar desplazando. */
  [data-movimiento="pausado"] .client-strip {
    mask-image: none;
  }
}

/*
 * El botón del pie es discreto en reposo y se afirma al enfocarlo: no compite
 * con nada, pero el teclado lo encuentra. `aria-pressed` lo convierte en un
 * interruptor, así el lector de pantalla dice si está activado.
 */
.btn--quieto {
  align-self: center;
  padding: var(--sp-3xs) var(--sp-xs);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--c-text-muted);
  font-size: var(--fs-small);
  cursor: pointer;
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}

.btn--quieto:hover,
.btn--quieto:focus-visible {
  color: var(--c-text);
  border-color: var(--c-text-faint);
}

.btn--quieto[aria-pressed="true"] {
  color: var(--c-text);
  border-color: var(--c-vertical-soft);
}

/*
 * La marca en grande al pie (parts/footer-marca.php).
 *
 * El ancho lo manda el CONTENEDOR, no la ventana: `width: 100%` dentro de
 * `.container`. Por eso no hay ningún `vw` ni ningún factor tipográfico
 * escrito a ojo —que es lo que corta la última letra de un wordmark gigante—:
 * un SVG con `viewBox` se escala solo al ancho que le den y la tinta llega
 * exactamente a los dos cantos, valga lo que valga ese ancho.
 */
.marca-pie {
  /* Hijo de grid/flex algún día: que pueda encoger por debajo de su
     contenido en vez de empujar la caja del pie. */
  min-width: 0;

  margin-top: var(--sp-2xl);
  color: var(--c-marca-pie);
}

.marca-pie__svg {
  display: block;
  width: 100%;

  /*
   * Y no se hereda solo: el valor inicial de `fill` en SVG es NEGRO, no
   * `currentColor`. Sin esta línea los ocho trazos salen en #000 —sobre este
   * fondo se ve como un agujero— y ningún token pinta nada. El isotipo se
   * pisa después con su clase, que es más específica.
   */
  fill: currentColor;

  /*
   * El alto sale de la proporción declarada, no de lo que mida el contenido
   * al pintarse: la caja ya ocupa su sitio definitivo en el primer reflow y
   * no hay nada que empujar hacia abajo (CLS 0).
   *
   * `height: auto` es obligatorio: el reset del tema trae
   * `img, svg, video… { max-width: 100%; height: auto }`, pero acá se declara
   * al lado del `aspect-ratio` para que se lea junto lo que gobierna la caja.
   */
  height: auto;
  aspect-ratio: var(--ratio-marca-wordmark);
}

/*
 * Los dos overrides van DESPUÉS de la regla base a propósito: tienen más
 * especificidad, pero aun así el orden importa entre ellos y el día que
 * alguien iguale selectores gana el último. Escritos arriba no aplicarían.
 */
.marca-pie[data-variante="lockup"] .marca-pie__svg {
  aspect-ratio: var(--ratio-marca-lockup);
}

.marca-pie[data-variante="completo"] .marca-pie__svg {
  aspect-ratio: var(--ratio-marca-completo);
}

/* El isotipo conserva los colores del archivo de marca; el wordmark hereda
   `currentColor` del bloque, que es lo que lo hace tematizable con un token. */
.marca-pie__iso-disco { fill: var(--c-marca-iso-disco); }
.marca-pie__iso-lente { fill: var(--c-marca-iso-lente); }
.marca-pie__iso-pupila { fill: var(--c-marca-iso-pupila); }

/*
 * En `lockup` el isotipo va en la MISMA tinta que las letras, y no en el azul
 * y el rojo del archivo. No es una preferencia: medido en navegador, el disco
 * a color es lo más saturado de toda la página —más que el botón principal—
 * mientras el wordmark de al lado está a 30 % de tinte. Esa diferencia de
 * registro hace que el ojo lea "hay un logo pegado encima del pie" en vez de
 * "el pie termina en la marca".
 *
 * La lente toma el color del FONDO del pie para que se lea como un hueco: es
 * lo que conserva la forma del isotipo al pasarlo a un solo tono.
 *
 * `completo` no lleva este trato a propósito: esa variante existe justamente
 * para ver el logotipo tal cual viene.
 */
.marca-pie[data-variante="lockup"] {
  --c-marca-iso-disco: var(--c-marca-pie);
  --c-marca-iso-lente: var(--c-bg-elev);
  --c-marca-iso-pupila: var(--c-marca-pie);
}

/*
 * La línea de cierre del pie se mudó a layout.css (`.pie-cierre`): las dos
 * variantes la comparten y su sitio es la hoja donde vive la maqueta del pie,
 * no la de componentes. El razonamiento del borde en el contenedor viajó con
 * ella.
 */

.client-strip__item {
  display: flex;
  align-items: center;
  white-space: nowrap;

  /* Son nombres de marca en movimiento, no texto para seleccionar: sobre ellos
     va la flecha normal, no el cursor de escritura. */
  cursor: default;
  user-select: none;

  /* Cuerpo chico del Figma. Al ser texto pequeño el umbral de contraste es
     4,5:1 en todos los anchos, así que el color es `--c-text-faint` (5,59:1
     sobre el fondo) y la opacidad no puede bajar de 0,9: medido, 0,8 lo deja
     en 4,00:1 y 0,65 en 3,04:1. */
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
  color: var(--c-text-faint);
  opacity: 0.9;

  /*
   * Más lento que el resto del sistema, y a propósito.
   *
   * `--dur-base` (260 ms) está pensada para un botón: un gesto corto sobre algo
   * que el usuario acaba de decidir tocar. Acá el elemento se está MOVIENDO
   * mientras cambia de color, y a esa velocidad el encendido se lee como un
   * parpadeo. `--dur-slow` (520 ms) acompaña la desaceleración de la cinta —que
   * hace `marquee.js`— en vez de pelearse con ella.
   */
  transition: opacity var(--dur-slow) var(--ease-out),
              color var(--dur-slow) var(--ease-out),
              filter var(--dur-slow) var(--ease-out);
}

/*
 * El gris va en el <img>, no en el contenedor.
 *
 * Estaba en `.client-strip__item`, y `filter` en el padre se aplica al subárbol
 * ya compuesto: desde ahí no se puede dar un trato distinto a cada logo, ni
 * cancelarlo desde el hijo. Sobre los nombres escritos, además, no hacía nada:
 * `--c-text-faint` (#8b8c91) y `--c-text` (#ffffff) son neutros y `grayscale()`
 * sobre ellos no tiene efecto.
 */
/* Misma cadencia lenta que el resto de la cinta: el logo se destiñe y se
   enciende al ritmo del movimiento, no al de un botón. */
.client-strip__item[data-logo="color"] img {
  /*
   * La igualación de la tinta no es decorativa: sin ella este grupo se ve al
   * doble de apagado que el que va pintado de blanco al lado, porque la
   * mediana de su gris tras `grayscale(1)` es 0,39 sobre 1 contra el 1,00 de
   * aquéllos.
   *
   * Antes se hacía con `brightness(1.6)`, que multiplica y por tanto QUEMA: es
   * lo que convertía en un rectángulo blanco a los logotipos cuya marca es un
   * bloque de color sólido. La curva de `--logo-tint` sube lo mismo sin
   * recortar nada — la derivación y los números medidos están en tokens.css.
   */
  filter: grayscale(1) var(--logo-tint);
  transition: filter var(--dur-base) var(--ease-out);
}

/*
 * Tercer trato: los archivos que YA se leen sobre oscuro.
 *
 * Son los que traen su propia placa oscura con la marca en claro encima. Sobre
 * el fondo del sitio esa placa es invisible y el logotipo se lee solo: lo único
 * que les falta es desaturarse para entrar en la tira como el resto.
 *
 * Y NO llevan `--logo-tint`. Ese realce está calibrado para subir tinta
 * subexpuesta, y sobre una placa casi negra hace justo lo contrario: la levanta
 * hasta gris visible y dibuja una caja que en el archivo no existe. Medido
 * sobre `logo-koh-i-noor.png` (placa RGB 17,18,17 en el 22 % del lienzo, marca
 * en blanco): con el realce la placa queda en 2,57:1 contra el fondo #0f1217
 * —una caja gris— y sin él en 1,00:1, o sea que desaparece. De paso la marca
 * gana legibilidad, de 5,78:1 a 13,47:1 contra su propia placa.
 *
 * Pintarlo de blanco no es la salida: `brightness(0) invert(1)` lleva la placa
 * ENTERA a blanco (18,72:1) y el resultado es un rectángulo blanco sólido.
 */
.client-strip__item[data-logo="gris"] img {
  filter: grayscale(1);
  transition: filter var(--dur-base) var(--ease-out);
}

/*
 * Los oscuros van pintados de blanco. `brightness(0)` lleva toda la tinta a
 * negro conservando el alfa e `invert(1)` la sube a blanco: queda la silueta,
 * que es la única forma de que un logotipo negro se lea sobre #0f1217.
 *
 * Estos NO vuelven a su color bajo el cursor: volver a su color es justamente
 * desaparecer. Se aclara la opacidad y nada más.
 */
.client-strip__item[data-logo="blanco"] img {
  filter: brightness(0) invert(1);
}

/*
 * Caja fija para todos los logos, no altura libre.
 *
 * Los 48 archivos vienen normalizados dentro de un lienzo de 240×120 con alfa,
 * así que la altura que se ve NO es la del logotipo: es la del lienzo. Con
 * `max-height: 28px` un logotipo apaisado —Johnson & Johnson dibuja 43 px de
 * los 120 del lienzo— quedaba en 10 px de tinta y era ilegible, mientras que
 * uno cuadrado ocupaba los 28 enteros: la misma regla daba dos pesos distintos.
 *
 * Con una caja igual para todos y `object-fit: contain`, cada logotipo entra
 * completo, ninguno se deforma —incluido el que suba mañana el cliente, tenga
 * la proporción que tenga— y los dos tratamientos pesan lo mismo en la tira.
 */
/* El segundo selector no es un descuido: `img[width][height]` de base.css
   pesa más que una clase más un elemento, y sin igualarlo su `height: auto`
   gana y la caja de acá no se aplica. */
.client-strip__item img,
.client-strip__item img[width][height] {
  width: var(--logo-w);
  height: var(--logo-h);
  object-fit: contain;

  /* Las imágenes de WordPress llevan `width`/`height` como atributos y el
     reset del tema les pone `height: auto`: sin esto, ese `auto` gana sobre la
     caja de arriba y volvemos al alto del lienzo. */
  max-width: none;
}

/* Solo se aclara el que está bajo el ratón. La cinta entera se detiene, pero
   el resto de los nombres se mantiene apagado: así la atención queda en uno.

   Acá había además un `:focus-visible` en cada una de las dos reglas, y no
   podía aplicarse nunca: los ítems de la cinta son `<span>`, o sea 0
   enfocables entre los 96 del DOM. Prometía en la hoja una pausa «al enfocar»
   que no existía. Quien necesita detener la cinta sin ratón tiene el botón de
   pausa del pie, que la para entera (WCAG 2.2.2); enfocar 48 logos uno por uno
   sería 48 paradas de tabulación que no llevan a ninguna parte. */
.client-strip__item:hover {
  opacity: 1;
  color: var(--c-text);
}

/* Figma #37: a color al pasar el mouse. Antes quedaba en `grayscale(0.2)` y a
   ese valor un logotipo se destiñe igual; va a 0, color de marca completo.

   Vale para los dos tratos que se destiñen. En un archivo con placa oscura el
   cambio puede ser chico —si la marca ya es acromática, volver al color no
   mueve nada— pero la regla es la misma y el día que entre un logotipo dorado
   sobre negro se enciende como cualquier otro. */
.client-strip__item[data-logo="color"]:hover img,
.client-strip__item[data-logo="gris"]:hover img {
  filter: grayscale(0) brightness(1);
}

/*
 * Sin movimiento, la cinta deja de ser una cinta.
 *
 * Detener la animación y ofrecer scroll horizontal parecía suficiente y no lo
 * era: medido a 360 px, 11 de los 15 nombres quedaban FUERA de la caja, y como
 * no son enlaces no hay un solo elemento enfocable adentro — con el teclado no
 * se llega nunca. Quien pide menos movimiento terminaba viendo cuatro clientes
 * de quince, sin manera de ver el resto. La medición de desborde de la página
 * no lo detecta: el recorte ocurre dentro de un contenedor con `overflow`.
 *
 * Con la pista envuelta se ven los quince a la vez, en varias líneas. Nada se
 * mueve y nada falta, que es exactamente lo que la preferencia pide.
 * `flex-shrink` vuelve a 1 y `min-width: 0` porque, si no, la pista conserva su
 * ancho intrínseco y sigue desbordando aunque pueda envolver.
 */
@media (prefers-reduced-motion: reduce) {
  .client-strip {
    overflow: visible;

    /* El degradado de los bordes existía para que la cinta "entrara y saliera"
       de pantalla; sin movimiento solo apaga los nombres de los extremos. */
    mask-image: none;
  }

  .client-strip__track {
    animation: none;
    flex-wrap: wrap;
    flex-shrink: 1;
    min-width: 0;
    justify-content: center;
    row-gap: var(--sp-2xs);
    padding-right: 0;
  }
}

/* Controles nativos de WordPress ---------------------------------------- *
 * El buscador de la 404 y de la página de resultados sale de
 * get_search_form(): sin estas reglas hereda los colores claros del
 * navegador y el texto queda blanco sobre blanco en el tema oscuro.       */

/*
 * Un solo ancho para el buscador, no tres. Estaba en 28rem acá, sin tope en el
 * estado vacío y en 32rem en la página de resultados: el mismo control con
 * tres medidas según dónde cayera.
 */
.search-form {
  display: flex;
  gap: var(--sp-2xs);
  max-width: var(--measure-form);
}

.search-form label {
  flex: 1;
  min-width: 0;
}

/* El botón de enviar NO tiene reglas propias: la plantilla `searchform.php`
   le pone `.btn .btn--primary`, que es el botón del sistema. Antes era una
   cuarta definición de botón, con la misma forma pero sin transición y sin
   estado `:active` — el único del sitio que no se hundía al pulsar. */

.search-field {
  width: 100%;
  min-height: var(--control-h);
  padding: var(--sp-2xs) var(--sp-xs);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-s);
  background: var(--c-surface);
  color: var(--c-text);
  font: inherit;
  font-size: var(--fs-small);
  transition: border-color var(--dur-fast) var(--ease-out);
}

.search-field::placeholder {
  color: var(--c-text-faint);
}

/* El campo del buscador era el único control del sitio sin respuesta al ratón:
   el del formulario de contacto sí la tenía. */
.search-field:hover {
  border-color: var(--c-text-faint);
}

/* Tarjeta de resultados ------------------------------------------------ *
 * En el diseño los cuatro números no son bloques sueltos: viven dentro de una
 * sola tarjeta, separados por líneas verticales.
 */

/* Monta sobre `.linegrid` (layout.css): de ahí salen la rejilla, el fondo y
   las líneas divisorias. Acá solo va el canto de la tarjeta. */
.stats-card {
  border: 1px solid var(--c-border);
  border-radius: var(--radius-l);
}

/*
 * Cuántas columnas, según cuántos números cargó el cliente.
 *
 * El reparto se elige para que NO quede una fila a medias: con cinco números
 * —el caso que llega en cuanto se cree la quinta vertical— van cinco columnas,
 * no cuatro más una huérfana; con seis van tres y tres. De siete en adelante
 * se queda en cuatro y la última fila va incompleta, pero bien dibujada.
 *
 * Las cuatro reglas de `:has()` son mutuamente excluyentes (cada una mira una
 * cantidad exacta), así que el orden entre ellas no decide nada: `--linegrid-cols`
 * lo fija la que coincide.
 */
@media (min-width: 640px) {
  .stats-card { --linegrid-cols: 2; }

  /* Impar y último = quedó solo en su fila: ocupa el ancho entero en vez de
     dejar media tarjeta en blanco (medido antes: con dos números a 1280
     quedaban 567 px de tarjeta vacía sobre 1133). */
  .stat:nth-child(odd):last-child { grid-column: 1 / -1; }
}

@media (min-width: 1000px) {
  .stats-card { --linegrid-cols: 4; }

  /* Acá el reparto de columnas ya evita la fila huérfana: el ensanche sobra. */
  .stat:nth-child(odd):last-child { grid-column: auto; }
  .stats-card:has(.stat:nth-child(6):last-child) { --linegrid-cols: 3; }
  .stats-card:has(.stat:nth-child(5):last-child) { --linegrid-cols: 5; }
  .stats-card:has(.stat:nth-child(3):last-child) { --linegrid-cols: 3; }
  .stats-card:has(.stat:nth-child(2):last-child) { --linegrid-cols: 2; }
  .stats-card:has(.stat:only-child) { --linegrid-cols: 1; }
}

.stat {
  display: flex;
  flex-direction: column;
  gap: var(--sp-xs);
  padding: var(--sp-l) var(--sp-m);
}

.stat__icon {
  width: 26px;
  height: 26px;
  color: var(--c-vertical-soft);
}

.stat__value {
  font-size: var(--fs-metric);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: var(--tracking-tighter);
  font-variant-numeric: tabular-nums;
  min-height: 1em;
}

.stat__text {
  font-size: var(--fs-small);
  color: var(--c-text-muted);
  line-height: 1.5;
}

/* La fuente del dato se ancla al fondo de la celda: corte donde corte el
   texto de arriba, las cifras de la fila terminan alineadas. El tipo de letra
   y el color salen de `.datum`, que es el mismo pie que usa la rejilla de
   resultados de vertical —antes eran dos pies de tamaños distintos—. */
.stat__source {
  margin-top: auto;
  padding-top: var(--sp-s);
}

.stats-note {
  margin-top: var(--sp-m);
  color: var(--c-text-muted);
  font-size: var(--fs-small);
}

/* Tarjetas de vertical -------------------------------------------------- *
 * Línea de acento ARRIBA (no a la izquierda), icono en caja teñida, etiqueta
 * grande en el color de la vertical, y la media a sangre en la mitad derecha.
 */

.vertical-card {
  /*
   * Vertical sin color asignado (una quinta línea que cree el cliente): acento
   * neutro, NO el azul de marca. Con el azul, su tarjeta salía en #1179c3 y la
   * de Farma en #147bc2: dos verticales distintas pintadas del mismo color.
   * Las cuatro conocidas sobrescriben estas dos variables más abajo.
   */
  --c-vertical: var(--c-vertical-neutral);
  --c-vertical-soft: var(--c-vertical-neutral-soft);

  /*
   * La tarjeta es la unidad de medida de su propio interior: el relleno del
   * cuerpo se calcula en `cqw` contra ELLA y no contra la ventana. Ver el
   * bloque "Contenedores" al final del archivo.
   *
   * `inline-size` y no `size`: la tarjeta tiene que seguir creciendo de alto
   * con su contenido. Con `size` el alto también quedaría contenido y las
   * cuatro colapsarían.
   */
  container-type: inline-size;
  container-name: vcard;

  position: relative;
  display: grid;
  border: 1px solid var(--c-border-soft);
  border-top: 3px solid var(--c-vertical);
  border-radius: var(--radius-m);
  background: var(--c-surface);
  overflow: hidden;
  transition: translate var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}

/* Tarjeta-enlace que se eleva ------------------------------------------- *
 * Las TRES tarjetas que son un enlace entero —vertical, caso y artículo—
 * responden igual: se elevan, encienden su borde en el color de su vertical y
 * proyectan sombra más un halo del mismo color. La señal viene del color de la
 * propia vertical, no de un gris genérico.
 *
 * Estaban escritas tres veces, con dos alturas de elevación (3 y 2 px), dos
 * intensidades de halo (28 % y 24 %) y la de artículo SIN sombra y SIN
 * `:focus-within` — es decir, esa tarjeta no respondía al teclado y era la
 * única de las tres. Una definición, un comportamiento.
 */
.vertical-card:hover,
.vertical-card:focus-within,
.case-card:hover,
.case-card:focus-within,
.post-card:hover,
.post-card:focus-within {
  /*
   * `translate` y NO `transform`, y esto es lo que hace que la elevación
   * exista: las tres tarjetas son objetivos de la aparición al scroll, y esa
   * aparición reclama `transform` para sí —termina en
   * `html.js [data-reveal].is-visible { transform: none }` (especificidad
   * 0-3-1) y, en modo de respaldo, en un `transform: none !important`—. Una
   * elevación escrita en `transform` la pierde SIEMPRE, gane quien gane el
   * orden de la cascada. Medido: las tarjetas no se movían un píxel.
   *
   * `translate` es una propiedad independiente: se compone con el `transform`
   * de la aparición en vez de pelearse con él.
   */
  translate: 0 -3px;
  border-color: color-mix(in srgb, var(--c-vertical) 45%, var(--c-border));
  box-shadow: var(--shadow-card),
              0 0 22px color-mix(in srgb, var(--c-vertical) 28%, transparent);
}

/* Sin movimiento la tarjeta igual responde: quedan el borde encendido y el
   halo, que no son movimiento. */
@media (prefers-reduced-motion: reduce) {
  .vertical-card:hover,
  .vertical-card:focus-within,
  .case-card:hover,
  .case-card:focus-within,
  .post-card:hover,
  .post-card:focus-within { translate: none; }
}

.vertical-card[data-vertical="farma"] {
  --c-vertical: var(--c-farma);
  --c-vertical-soft: var(--c-farma-soft);
}

.vertical-card[data-vertical="capacitacion"] {
  --c-vertical: var(--c-capacitacion);
  --c-vertical-soft: var(--c-capacitacion-soft);
}

.vertical-card[data-vertical="ecommerce"] {
  --c-vertical: var(--c-ecommerce);
  --c-vertical-soft: var(--c-ecommerce-soft);
}

.vertical-card[data-vertical="marketing"] {
  --c-vertical: var(--c-marketing);
  --c-vertical-soft: var(--c-marketing-soft);
}

/*
 * El corte a dos columnas lo decide EL HUECO DONDE VIVE LA TARJETA, no la
 * ventana.
 *
 * El contenedor es la rejilla que la contiene y no la tarjeta misma: un
 * elemento NO puede consultar su propio tamaño —`@container` mira siempre hacia
 * arriba—, así que una regla sobre `.vertical-card` dentro de un `@container`
 * declarado en `.vertical-card` no se aplica nunca. (Medido al equivocarme: las
 * tarjetas se quedaron en una sola columna a todos los anchos.) La rejilla de
 * verticales es de una sola columna, así que su ancho ES el de la tarjeta.
 *
 * `:has()` y no una clase nueva para no tocar el marcado que ya aprobó QA. Si
 * algún día esta rejilla pasa a varias columnas, el contenedor tiene que bajar
 * a un envoltorio por tarjeta: con varias columnas la rejilla mide el total y
 * la tarjeta ya no.
 *
 * El umbral no es un número elegido: 848 px es lo que mide la tarjeta a ancho
 * completo cuando la ventana está en 900 px, que es donde el diseño aprobado
 * hace el corte. Medido. Así la maqueta de hoy no se mueve un píxel, y lo que
 * cambia es que si mañana esta misma tarjeta cae en una columna estrecha de una
 * ventana ancha —una barra lateral, una plantilla nueva— se queda en una sola
 * columna en vez de partirse en dos columnas de 200 px.
 *
 * minmax(0, …): sin el piso en cero, el min-content del texto del media-hint
 * empuja la columna y cada tarjeta termina con columnas DISTINTAS según lo
 * largo de su marcador. Medido: 526/604 vs 611/519.
 */
.grid:has(> .vertical-card) {
  container-type: inline-size;
  container-name: vcards;
}

@container vcards (min-width: 848px) {
  .vertical-card {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr);
    min-height: 420px;
  }
}

.vertical-card__body {
  /*
   * Relleno proporcional a la TARJETA (`cqw`), no a la ventana.
   *
   * Con `3vw` el relleno seguía creciendo después de que la tarjeta dejara de
   * crecer —la tarjeta topa en `--container` (1200 px) y la ventana no topa
   * nunca—. Medido antes del cambio: a 2560 px de ventana el cuerpo medía
   * 604 px y se comía 64 px de relleno por lado, un 21 % de su propio ancho a
   * cada lado; y a 900 px, donde la tarjeta se parte en dos, el relleno SUBÍA
   * de 24 a 27 px justo cuando el cuerpo se estrechaba de 719 a 457 px.
   *
   * 3,8 % es el mismo relleno que daba el `3vw` a 1440 px —43,2 px sobre una
   * tarjeta de 1126—, que es el ancho al que se aprobó el diseño: a 1440 no
   * cambia nada, y a partir de ahí deja de despegarse de la caja que rellena.
   *
   * El techo de `--sp-xl` hoy no llega a activarse (la tarjeta topa en 1200 px
   * y 3,8 % de eso son 45,6): está para el día en que esta tarjeta se use a
   * sangre, no como valor de trabajo.
   */
  padding: clamp(var(--sp-m), 3.8cqw, var(--sp-xl));
  display: flex;
  flex-direction: column;
  gap: var(--sp-s);
}

.vertical-card__head {
  display: flex;
  align-items: center;
  gap: var(--sp-s);
}

.vertical-card__icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: var(--radius-m);
  background: color-mix(in srgb, var(--c-vertical) 16%, transparent);
  color: var(--c-vertical-soft);
}

.vertical-card__icon svg { width: 24px; height: 24px; }

/*
 * Sin icono no hay recuadro. Si una vertical nueva no tiene dibujo asignado, el
 * cuadrado teñido de 52 px quedaba vacío al lado del nombre y se leía como un
 * error de carga; así, la cabecera de la tarjeta arranca directamente en la
 * etiqueta. `:not(:has(*))` y no `:empty` para que el salto de línea y la
 * sangría del PHP no cuenten como contenido.
 *
 * Nota para el backend: si el respaldo del icono es texto suelto (una inicial),
 * envolverlo en un elemento —un <span> alcanza— para que el recuadro se vea.
 */
.vertical-card__icon:not(:has(*)) { display: none; }

/*
 * El nombre de la vertical dentro de su tarjeta. Se llamaba `__eyebrow` y no
 * lo es: los antetítulos del sitio son 12 px monoespaciados de peso normal y
 * esto son 20 px de Montserrat en negrita. Es un título de categoría, y ahora
 * el nombre lo dice — si se llama igual que otro objeto, alguien termina
 * copiándole los valores al que no debe.
 */
.vertical-card__label {
  /* Tope en `--fs-h4`: con más, "CAPACITACIÓN INDUSTRIAL" partía en dos líneas
     y desalineaba la cabecera respecto de las otras tarjetas. */
  font-size: var(--fs-h4);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--c-vertical-soft);
}

.vertical-card__title {
  font-size: var(--fs-h3);
  color: var(--c-text);
}

.vertical-card__title a {
  color: var(--c-text);
  text-decoration: none;
}

/* Toda la tarjeta es zona de click, pero el enlace real sigue siendo el
   título: el lector de pantalla anuncia el destino, no "enlace, tarjeta". */
.vertical-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.vertical-card__text {
  color: var(--c-text-muted);
  max-width: 52ch;

  /* La tarjeta es un adelanto: el texto completo vive en la página de la
     vertical. Sin el tope de líneas, cada tarjeta medía distinto según su
     copy y el conjunto se veía desparejo. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}

/* Anclado al fondo del cuerpo: todas las tarjetas muestran su enlace a la
   misma altura, corte el texto donde corte. */
.vertical-card__link { margin-top: auto; padding-top: var(--sp-2xs); }

/* Zona de media: fondo con un halo del color de la vertical y el marcador
   monoespaciado del diseño. */
/*
 * Zona de media de la tarjeta.
 *
 * El espacio está reservado por CSS ANTES de saber qué va adentro: el mismo
 * hueco sirve para una imagen o para un video, así que cambiar de uno a otro
 * —que es la decisión que el cliente todavía no tomó— no mueve ni un píxel de
 * la maqueta ni aporta un solo punto de CLS.
 */
.vertical-card__media {
  position: relative;

  width: 100%;
  aspect-ratio: 3 / 2;

  /* Sin min-height mientras la proporción manda: `min-height` + `aspect-ratio`
     dan un mínimo intrínseco de 330px (220 × 1,5) que dimensiona la COLUMNA
     del grid padre, y a 360px el bloque se salía 4px del área útil. La altura
     ya la sostiene la proporción; el mínimo solo hace falta en escritorio,
     donde la proporción pasa a `auto` (ver el @media de abajo). */
  min-height: 0;
  background:
    radial-gradient(60% 60% at 60% 45%, color-mix(in srgb, var(--c-vertical) 22%, transparent), transparent 70%),
    var(--c-surface-2);
  display: grid;
  place-items: center;
}

/* En dos columnas la media ya no manda el alto: acompaña al texto de al lado.
   Este bloque vive DESPUÉS de la regla base a propósito — antes del arreglo
   la base (aspect-ratio: 3/2) lo pisaba por orden de cascada, la media fijaba
   la altura de la tarjeta y el enlace inferior quedaba recortado por el
   overflow:hidden (medido: 81-99px fuera del borde).

   La condición es la MISMA que la del reparto en dos columnas de la tarjeta
   (`@container vcards (min-width: 848px)`), y tiene que seguir siéndolo: son
   una sola decisión escrita en dos sitios. Si una se dispara por ventana y la
   otra por contenedor, hay una franja de anchos en la que la tarjeta está en
   dos columnas y su media sigue creyendo que manda el alto — que es exactamente
   el recorte del enlace que este bloque arregló. */
@container vcards (min-width: 848px) {
  .vertical-card__media {
    aspect-ratio: auto;
    /* Acá sí: sin proporción que sostenga el alto, el bloque colapsaría a la
       altura del texto de al lado si ese texto fuera muy corto. */
    min-height: 220px;
  }
}

.vertical-card__media > img,
.vertical-card__media > video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/*
 * Marcador de media pendiente.
 *
 * Va en `--c-text-muted` y no en `--c-text-mono` porque es el ÚNICO texto del
 * sitio que cae sobre `--c-surface-2`, la superficie más clara. Ahí el
 * atenuado da 4,28:1 —por debajo de AA— y el cuerpo, 5,47:1. La regla del
 * sistema es que sobre `--c-surface-2` no va texto atenuado (ver tokens.css).
 */
.media-hint {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--c-text-muted);
  padding: var(--sp-s);
  text-align: center;
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* ══ INICIO BLOQUE REMOVIBLE: cinta de palabras ═════════════════════════ *
 *
 * El cliente duda de su utilidad (acta 18/08): si en la revisión no convence,
 * se saca. Para que sacarla sea una operación de un minuto y no una cacería,
 * todo lo suyo vive entre estas dos marcas y se retira junto con
 * `parts/marquee.php` y su línea en `front-page.php`. Nada fuera de acá
 * depende de la cinta.
 *
 * Alterna palabras sólidas y palabras de contorno, como en el diseño, pero
 * cada palabra lleva el color de SU vertical —en reposo y al interactuar—,
 * que es la corrección pedida: la cinta deja de ser un adorno azul y pasa a
 * ser la leyenda de color del sitio.
 */

.marquee {
  --marquee-duration: 34s;

  /* Mismo criterio que `--strip-gap` en la cinta de logos: un solo valor para
     el hueco y para el `padding-right` de la pista, y `vw` deliberado porque la
     cinta va a sangre. */
  --marquee-gap: clamp(var(--sp-m), 3vw, var(--sp-xl));

  position: relative;
  display: flex;
  overflow: hidden;
  padding-block: clamp(var(--sp-s), 2vw, var(--sp-m));
  border-block: 1px solid var(--c-border-soft);
}

.marquee__track {
  display: flex;
  flex-shrink: 0;
  align-items: baseline;
  gap: var(--marquee-gap);
  padding-right: var(--marquee-gap);
  animation: marquee-scroll var(--marquee-duration) linear infinite;
}

/* La cinta de palabras NO se detiene al pasar el ratón: es un elemento
   decorativo de fondo, no algo con lo que el visitante interactúe. Solo se
   pausa cuando queda fuera de pantalla, para no gastar batería de fondo. */
.marquee.is-offscreen .marquee__track {
  animation-play-state: paused;
}

.marquee__item {
  /* Cada palabra fija su propio par de variables desde `data-vertical`; el
     resto de las reglas no sabe de qué vertical se trata. */
  --c-vertical: var(--c-primary);
  --c-vertical-soft: var(--c-accent);
  font-size: var(--fs-display-l);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  line-height: 1.05;
  color: var(--c-vertical-soft);
  white-space: nowrap;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out),
              -webkit-text-stroke-color var(--dur-fast) var(--ease-out);
}

.marquee__item[data-vertical="farma"] {
  --c-vertical: var(--c-farma);
  --c-vertical-soft: var(--c-farma-soft);
}

.marquee__item[data-vertical="capacitacion"] {
  --c-vertical: var(--c-capacitacion);
  --c-vertical-soft: var(--c-capacitacion-soft);
}

.marquee__item[data-vertical="ecommerce"] {
  --c-vertical: var(--c-ecommerce);
  --c-vertical-soft: var(--c-ecommerce-soft);
}

.marquee__item[data-vertical="marketing"] {
  --c-vertical: var(--c-marketing);
  --c-vertical-soft: var(--c-marketing-soft);
}

/* Las pares van solo con contorno, en el color de su vertical. Se usa la
   variante `-soft` también para el trazo: la sólida sobre el fondo nuevo no
   llega al 3:1 que pide un elemento gráfico. */
.marquee__item:nth-child(even) {
  color: transparent;
  -webkit-text-stroke: 2px var(--c-vertical-soft);
}

/*
 * Al ser enlaces, tienen que responder. La respuesta se queda dentro de la
 * misma familia de color —la palabra de contorno se rellena, la sólida se
 * aclara— para que el color siga informando a qué vertical lleva.
 */
a.marquee__item:hover,
a.marquee__item:focus-visible {
  color: color-mix(in srgb, var(--c-vertical-soft) 60%, var(--c-text));
}

a.marquee__item:nth-child(even):hover,
a.marquee__item:nth-child(even):focus-visible {
  color: var(--c-vertical-soft);
  -webkit-text-stroke-color: transparent;
}

/*
 * Alto contraste del sistema.
 *
 * En `forced-colors: active` el navegador reemplaza colores, fondos y bordes
 * por la paleta que eligió el visitante, pero NO toca `-webkit-text-stroke-
 * color`: medido con el modo activo, las palabras pares quedaban con el
 * relleno forzado del sistema y el contorno todavía en el rojo o el cian de la
 * vertical. Media palabra respetaba la preferencia y media no.
 *
 * `currentColor` y no un color del sistema escrito a mano. Medido con el modo
 * activo, el resultado es que el trazo se APAGA: `currentColor` resuelve
 * contra el `color` declarado —que en estas palabras es `transparent`— y no
 * contra el que el navegador forzó encima. La palabra queda sólida, con el
 * relleno del sistema (medido: `rgb(0, 0, 159)`, el azul de enlace de la
 * paleta forzada) y sin contorno. Es exactamente lo que se busca: sin el color
 * de marca, el contorno solo resta legibilidad.
 *
 * Y si algún día el navegador resolviera `currentColor` contra el color ya
 * forzado, el trazo saldría del mismo color que el relleno y la palabra
 * seguiría leyéndose. Las dos salidas posibles son buenas; una constante
 * escrita a mano solo acierta en una.
 *
 * Va DESPUÉS de la regla base a propósito: misma especificidad, gana la
 * última. Escrito antes, no haría nada.
 */
@media (forced-colors: active) {
  .marquee__item:nth-child(even) {
    -webkit-text-stroke-color: currentColor;
  }
}

@keyframes marquee-scroll {
  to { transform: translate3d(-100%, 0, 0); }
}

/*
 * Mismo criterio que la cinta de clientes (ver el bloque de `.client-strip`):
 * quieta y recortada, esta cinta mostraba 1 o 2 palabras de las 4 —medido: 3 de
 * 4 fuera de la caja a 360 y 768 px—. Y acá pesa más, porque las palabras no
 * son decoración: cada una lleva el color de SU vertical y funciona como la
 * leyenda de color del sitio. Envuelta, se ven las cuatro.
 */
@media (prefers-reduced-motion: reduce) {
  .marquee { overflow: visible; }

  .marquee__track {
    animation: none;
    flex-wrap: wrap;
    flex-shrink: 1;
    min-width: 0;
    justify-content: center;
    row-gap: var(--sp-2xs);
    padding-right: 0;
  }
}

/* ══ FIN BLOQUE REMOVIBLE: cinta de palabras ════════════════════════════ */

/* Lista de diferenciales ---------------------------------------------- */

.feature-list {
  display: grid;

  /*
   * Dos columnas en cuanto entren, y NO por una media query: el reparto lo
   * decide el ancho disponible con `auto-fit`, así que la lista se comporta
   * igual si mañana vive dentro de una columna más estrecha.
   *
   * Por qué existe este reparto: acotar el texto a la medida de lectura (ver
   * `.feature > span`) es obligatorio —a 1440 px los renglones medían 113
   * caracteres—, pero acotarlo en UNA columna dejaba media tarjeta vacía a la
   * derecha en las seis tarjetas. La medida de lectura y una caja llena no son
   * compatibles a 1128 px de ancho: hacen falta dos columnas.
   *
   * El mínimo de columna se despeja, no se elige: una línea no baja de ~40
   * caracteres, que a este cuerpo son 350 px (40 × 8,74), más el icono (22), su
   * hueco (16) y el relleno de la tarjeta (48) = 436 px → 28rem. Medido con ese
   * mínimo: dos columnas desde 1024 px de ventana, con 50 caracteres por línea
   * ahí y 56 a 1440. `min(100%, …)` para que en un móvil la columna no pida más
   * ancho del que hay y saque la lista de la pantalla.
   */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 28rem), 1fr));

  gap: var(--sp-s);
  margin: 0;
  padding: 0;
  list-style: none;
}

.feature {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-s);
  align-items: start;
  padding: var(--sp-s) var(--sp-m);
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  color: var(--c-text-muted);
}

/*
 * El texto del diferencial, acotado a la medida de lectura.
 *
 * La tarjeta sigue ocupando la fila entera —es lo que la hace legible como
 * caja—; lo que se acota es la línea de dentro. Medido antes: 113 caracteres
 * por línea a 1440 px (y 104 en Sobre Nosotros, donde el diferencial entraba en
 * UNA sola línea de 1038 px de largo). Después: 72.
 *
 * `min-width: 0` porque es el hijo de un grid `auto 1fr` y el texto lo escribe
 * el cliente: sin el piso en cero, una palabra sin cortes ensancha la columna
 * y saca la tarjeta de su fila.
 */
.feature > span {
  min-width: 0;
  max-width: var(--measure);
  overflow-wrap: anywhere;
}

.feature__icon {
  width: 22px;
  height: 22px;
  color: var(--c-accent);
  flex-shrink: 0;
}

/* Testimonio ---------------------------------------------------------- */

.testimonial {
  padding: var(--sp-l);
  border-left: 2px solid var(--c-vertical);
  background: var(--c-surface);
  border-radius: 0 var(--radius-m) var(--radius-m) 0;
}

.testimonial__quote {
  font-size: var(--fs-h3);
  font-weight: var(--fw-medium);
  line-height: var(--lh-heading);
}

/*
 * Firma del testimonio: foto, nombre y —debajo— rol y empresa.
 *
 * Se arma con lo que haya. Sin foto, la fila colapsa a texto y no queda un
 * círculo vacío; sin rol ni empresa, no queda una línea suelta. Es la misma
 * regla del proyecto de siempre: campo vacío, elemento que no existe.
 */
.testimonial__author {
  display: flex;
  align-items: center;
  gap: var(--sp-s);
  margin-top: var(--sp-m);
  font-size: var(--fs-small);
  color: var(--c-text-muted);
  font-style: normal;
}

.testimonial__photo {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  object-fit: cover;
  /* El anillo repite el color de la vertical: liga la cara con la sección. */
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--c-vertical) 55%, transparent);
}

.testimonial__name {
  display: block;
  color: var(--c-text);
  font-weight: var(--fw-semibold);
}

.testimonial__role {
  display: block;
  color: var(--c-text-faint);
}

.testimonial[data-placeholder="true"] .testimonial__quote::after {
  content: " · texto pendiente de confirmación";
  color: var(--c-text-mono);
  font-size: var(--fs-small);
  font-weight: var(--fw-regular);
}

/* Preguntas frecuentes ------------------------------------------------- */

.faq { display: grid; gap: var(--sp-2xs); }

.faq__item {
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  background: var(--c-surface);
}

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-s);
  padding: var(--sp-s) var(--sp-m);
  font-weight: var(--fw-medium);
  cursor: pointer;
  list-style: none;
  transition: background var(--dur-fast) var(--ease-out);
}

.faq__item summary::-webkit-details-marker { display: none; }

.faq__item:hover { border-color: color-mix(in srgb, var(--c-vertical) 45%, var(--c-border)); }
.faq__item summary:hover { background: var(--c-surface-2); }

.faq__item summary::after {
  content: "";
  width: 10px;
  height: 10px;
  border-right: 2px solid var(--c-vertical-soft);
  border-bottom: 2px solid var(--c-vertical-soft);
  transform: rotate(45deg);
  transition: transform var(--dur-base) var(--ease-out);
  flex-shrink: 0;
}

.faq__item[open] summary::after { transform: rotate(-135deg); }

.faq__answer {
  padding: 0 var(--sp-m) var(--sp-m);
  color: var(--c-text-muted);
}

/* Medida de lectura. La respuesta la escribe el cliente en el editor y puede
   ser larga: medido antes, 84 caracteres por línea a 768 px y 79 a 1440.
   Se acota el párrafo y no `.faq__answer`, para que el relleno de la caja siga
   siendo el del acordeón. */
.faq__answer p,
.faq__answer li { max-width: var(--measure); }

/* Bloque de contacto --------------------------------------------------- *
 * Cabecera a ancho completo y, debajo, dos tarjetas hermanas: el calendario y
 * el formulario. En el Figma las dos pesan lo mismo —misma caja, mismo alto,
 * mismo tratamiento de título—, porque son dos caminos hacia lo mismo y no una
 * opción principal con su nota al pie.
 */

.cta-block {
  display: grid;
  gap: var(--sp-xl);
}

.cta-block__head {
  display: grid;
  gap: var(--sp-s);
  justify-items: center;
  text-align: center;

  /* Piso en cero: sin él un título largo sin espacios estira la columna y saca
     la página del ancho. */
  min-width: 0;
}

.cta-block__head .section__lead { max-width: 60ch; }

/* La línea de condiciones del bloque de contacto no tiene regla propia: es
   `.datum`, la letra chica del sistema. El `letter-spacing: 0.02em` que
   llevaba era el único de ese valor en toda la hoja y a 13 px monoespaciados
   no se distinguía del normal. */

.cta-block__grid {
  display: grid;
  gap: var(--sp-m);
}

@media (min-width: 900px) {
  .cta-block__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.cta-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-s);
  min-width: 0;
  padding: var(--sp-l);
  background: var(--c-surface);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-l);
}

.cta-card__title {
  display: flex;
  align-items: center;
  gap: var(--sp-2xs);
  font-size: var(--fs-h4);
  min-width: 0;
  overflow-wrap: anywhere;
}

.cta-card__icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--c-accent);
}

.cta-card__sub {
  font-size: var(--fs-small);
  color: var(--c-text-muted);
}

/* Espacio reservado para el calendario: el embebido llega tarde y sin alto
   propio empujaría media página al aparecer. `margin-top: auto` reparte el
   sobrante de la tarjeta arriba de la caja, igual que el panel del formulario
   lo reparte a los dos lados: las dos columnas terminan a la misma altura
   aunque sus subtítulos midan distinto. */
.cta-card__embed {
  display: grid;
  place-items: center;

  /*
   * 760 y no 420. Medido con la cuenta real del cliente, el iframe del
   * calendario de HubSpot mide 756 px de alto en TODOS los anchos (de 320 a
   * 1440: siempre 756). Con 420 reservados, al montarse crecía 336 px y
   * empujaba hacia abajo todo lo que viniera después.
   */
  min-height: 760px;
  margin-top: auto;
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  background: var(--c-bg-elev);
  overflow: hidden;
}

/* Panel claro para los embebidos de HubSpot --------------------------- *
 * El calendario y el formulario se montan en un <iframe> de otro origen. Su
 * interior NO se puede estilar desde acá: no hay truco, y cualquier propuesta
 * que empiece con "inyectamos CSS en el iframe" es falsa. Pintan blanco, y
 * hasta el 21/08 ese blanco caía crudo sobre la página oscura — que es la
 * causa literal del "los formularios se ven horribles" del cliente.
 *
 * La respuesta no es esconderlo: es enmarcarlo. El blanco del tercero pasa a
 * vivir dentro de un panel de luz que SÍ es del sistema —el radio del tema, la
 * sombra de tarjeta del tema y la línea de acento de la vertical arriba, la
 * misma que llevan las tarjetas de solución—. Deja de leerse como un accidente
 * y pasa a leerse como una decisión: "acá se agenda", "acá se escribe".
 *
 * El selector es `:has(iframe)` y no la clase pelada a propósito: mientras el
 * embebido no llegó, la caja sigue siendo oscura con su respaldo dentro, y el
 * día que el formulario deje de ser iframe (bloqueado hoy por el reCAPTCHA de
 * la cuenta del cliente) esta regla deja de aplicar sola y vuelve el
 * formulario oscuro del tema que ya está escrito más abajo.
 */
.cta-card__embed:has(iframe),
.cta-form__embed:has(iframe) {
  display: block;
  padding: var(--panel-inset);
  border: var(--panel-linea) solid var(--c-panel-line);

  /* La línea de acento arriba es lo que ata el panel a su vertical: sin ella
     el panel es una caja blanca correcta pero ajena. */
  border-top: var(--panel-linea-acento) solid var(--c-vertical);
  border-radius: var(--radius-m);
  background: var(--c-panel);
  box-shadow: var(--shadow-card);
}

/* El iframe hereda el redondeo del panel: sin esto sus esquinas cuadradas
   asoman por encima de las del panel y delatan el recuadro del tercero. */
.cta-card__embed iframe,
.cta-form__embed iframe {
  border-radius: calc(var(--radius-m) - 1px);
}

.cta-card__embed:has(iframe) {
  /*
   * El calendario de HubSpot tiene un ancho mínimo propio de 312 px y no baja
   * de ahí. Medido: a 320 px de ventana la caja mide 222 y se comían 45 px del
   * widget por la derecha; a 360, 25 px. Con `overflow: hidden` eso no se veía
   * —se veía un calendario cortado, sin ninguna señal de que faltaba algo—.
   *
   * `auto` en vez de `hidden`: lo que no entra se puede arrastrar. Esconder un
   * corte no lo arregla. `contain` para que arrastrar el calendario no arrastre
   * también la página por debajo.
   */
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/*
 * Cuando desplaza de verdad, `deferred-embeds.js` le pone `tabindex="0"` para
 * que se pueda arrastrar con el teclado (WCAG 2.1.1). El anillo de foco del
 * sistema sirve tal cual, pero trae `border-radius: 3px` —pensado para
 * botones y enlaces—, y sobre un panel de 760 px de alto ese radio le come las
 * esquinas al panel claro justo cuando lo estás usando. Se le devuelve el
 * radio del panel; el anillo y el halo quedan como en todo el sitio.
 */
.cta-card__embed[tabindex]:focus-visible {
  border-radius: var(--radius-m);
}

/*
 * Y el relleno de la tarjeta se achica en pantallas angostas, para que haya que
 * arrastrar lo menos posible: a 320 px devuelve 32 de los 45 que faltaban.
 */
@media (max-width: 420px) {
  .cta-card { padding: var(--sp-m); }
}

.cta-card__embed iframe { width: 100%; border: 0; }

.cta-card__fallback {
  display: grid;
  gap: var(--sp-s);
  justify-items: center;
  padding: var(--sp-m);
  color: var(--c-text-muted);
  font-size: var(--fs-small);
  text-align: center;

  /* La caja de arriba centra sin estirar, así que este bloque se dimensiona
     por su contenido y puede salirse por los dos lados. */
  max-width: 100%;
  min-width: 0;
}

/*
 * El botón del respaldo tiene que caber, también con el texto al 200 %.
 *
 * Medido a 1280 px con `html { font-size: 32px }`: «Abrir el calendario» se
 * salía 7 px de `.cta-card__embed`, que recorta. Y el respaldo se retira solo
 * cuando el embebido monta: si HubSpot no responde, el corte no es un instante
 * de carga, es el estado final de la tarjeta.
 *
 * `.btn` va con `white-space: nowrap` porque dentro de un flex el rótulo se
 * partiría solo; acá se le devuelve el permiso de envolver, que es lo que
 * arregla el corte. Esconderlo con `overflow` sería dejarlo cortado.
 */
.cta-card__fallback .btn {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* El panel del formulario agrupa el embebido y el aviso de estado. Van juntos
   porque el aviso tiene que vivir FUERA del nodo que HubSpot reemplaza.
   `margin-top: auto` lo ancla al fondo de su tarjeta, igual que la caja del
   calendario en la de al lado: así las dos columnas terminan parejas. */
.cta-form__panel {
  display: flex;
  flex-direction: column;
  gap: var(--sp-s);
  min-width: 0;
}

/*
 * El panel se centra en el alto que le impone la tarjeta hermana, tenga
 * formulario o no.
 *
 * Las dos tarjetas miden lo mismo pero sus embebidos no: el calendario reserva
 * 760 px y el formulario, 600. Ese sobrante tiene que caer en algún lado, y
 * repartido arriba y abajo se lee como aire; empujado a un solo extremo —que es
 * lo que hacía `margin-top: auto`— se lee como un hueco donde falta algo. Se
 * nota desde que el formulario es un panel claro y no una caja del color del
 * fondo.
 */
.cta-form__panel {
  flex: 1;
  justify-content: center;
}

/*
 * Espacio reservado para el formulario de HubSpot: sin esto, el embed empuja la
 * página al aparecer y arruina el CLS.
 *
 * TEMPORAL — vive lo que viva el iframe. El formulario propio es DOM real, está
 * en el primer pintado y no puede provocar salto: cuando los cinco formularios
 * pasen al marcado propio, estas dos reglas se borran enteras.
 *
 * El alto lo emite PHP por formulario (`--cta-form-alto`, ver
 * `parts/cta-form.php`) y NO es el mismo para todos. Antes se reservaba el
 * máximo de los cinco, y las cuatro páginas de vertical pagaban 112 px de panel
 * claro vacío debajo del botón: sobre una página oscura eso no se lee como
 * aire, se lee como que algo no cargó.
 *
 * Lo que emite PHP es el alto del IFRAME; el marco del panel se suma acá porque
 * es del sistema y cambia con el ancho (`--panel-inset` vale 0 por debajo de
 * 480 px). Con `box-sizing: border-box`, sumarlo es lo que evita que el relleno
 * y el borde se coman parte del alto reservado y el iframe empuje igual.
 *
 * Los respaldos del `var()` son los altos del formulario más alto de los cinco:
 * un formulario que nadie midió reserva de más —sobra aire— antes que de menos
 * —la página salta—.
 */
.cta-form__embed[data-hubspot-form] {
  min-height: calc(
    var(--cta-form-alto, 794px) + 2 * var(--panel-inset) +
    var(--panel-linea) + var(--panel-linea-acento)
  );
}

@media (min-width: 1024px) {
  .cta-form__embed[data-hubspot-form] {
    min-height: calc(
      var(--cta-form-alto-lg, 590px) + 2 * var(--panel-inset) +
      var(--panel-linea) + var(--panel-linea-acento)
    );
  }
}

/* Respaldo mientras el embed no está: tiene que parecer una alternativa
   decidida, no un error. Se reemplaza cuando HubSpot responde. */
.cta-form__fallback {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-s);
  padding: var(--sp-m);
  border: 1px dashed var(--c-border);
  border-radius: var(--radius-m);

  /* `--c-surface` y no `--c-surface-2`: el rótulo de arriba va en el gris
     atenuado, y sobre la superficie de hover ese gris no llega a AA. El
     borde punteado es el que dice "esto es un respaldo", no el tono. */
  background: var(--c-surface);
  color: var(--c-text-muted);
  font-size: var(--fs-small);
}

/* Facade de video ----------------------------------------------------- */

.video-facade {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-m);
  overflow: hidden;
  background: var(--c-surface);
  cursor: pointer;
  border: 0;
  padding: 0;
  width: 100%;
}

.video-facade img { width: 100%; height: 100%; object-fit: cover; }

.video-facade__play { position: absolute; inset: 0; display: grid; place-items: center; }

.video-facade__play span {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--c-primary) 90%, transparent);
  color: var(--c-on-primary);
  transition: transform var(--dur-base) var(--ease-out);
}

.video-facade:hover .video-facade__play span { transform: scale(1.08); }

/* El botón de play crece un 8 % al pasar por encima: decorativo, se anula. */
@media (prefers-reduced-motion: reduce) {
  .video-facade:hover .video-facade__play span { transform: none; }
}

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

/* Tarjetas de caso y de artículo --------------------------------------- *
 * Media arriba, y debajo la etiqueta de vertical, el título y el resumen.
 */

.post-card {
  --c-vertical: var(--c-primary);
  --c-vertical-soft: var(--c-accent);
  position: relative;
  display: flex;
  flex-direction: column;
  color: var(--c-text);
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  overflow: hidden;
  text-decoration: none;
  transition: translate var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/* La tarjeta de artículo es un <a>: sin esto, al pasar el ratón el navegador
   le aplicaría el color de enlace visitado a todo su contenido. La elevación
   y el borde salen de la regla compartida de más arriba. */
.post-card:hover,
.post-card:focus-within { color: var(--c-text); }

.post-card__media {
  aspect-ratio: 4 / 3;
  position: relative;
  display: grid;
  place-items: center;
  background:
    radial-gradient(60% 60% at 50% 45%, color-mix(in srgb, var(--c-vertical) 16%, transparent), transparent 70%),
    var(--c-surface-2);
}

.post-card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.post-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xs);
  padding: var(--sp-m);
}

.post-card__title { font-size: var(--fs-h4); }

.post-card__text {
  font-size: var(--fs-small);
  color: var(--c-text-muted);
}

/* Filtros del blog ---------------------------------------------------- */

.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2xs);
  margin-bottom: var(--sp-xl);
}

.chip {
  --c-vertical: var(--c-primary);
  --c-vertical-soft: var(--c-accent);
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: var(--sp-3xs) var(--sp-s);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-pill);
  font-size: var(--fs-small);
  color: var(--c-text-muted);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

/* En pantalla táctil el chip crece al objetivo cómodo de --tap-target sin engordar la
   fila en escritorio, donde el puntero es fino. */
@media (pointer: coarse) {
  .chip { min-height: var(--control-h); }
}

.chip:hover {
  color: var(--c-vertical-soft);
  border-color: color-mix(in srgb, var(--c-vertical) 55%, var(--c-border));
}

/*
 * Filtro activo: fondo teñido con SU acento y texto en la variante clara.
 *
 * No lleva el acento sólido de fondo con texto blanco encima, que era lo obvio:
 * medido, el blanco sobre los cuatro sólidos da 4,15 / 7,57 / 3,69 / 4,55 y el
 * chip es texto chico (14 px), donde el mínimo es 4,5. Dos de los cuatro no
 * pasaban. Con el fondo teñido al 18% el texto sigue siendo la variante clara,
 * que ya está medida por encima de 6:1 en las cuatro verticales.
 */
.chip[aria-current="page"] {
  background: color-mix(in srgb, var(--c-vertical) 18%, var(--c-bg));
  border-color: var(--c-vertical-soft);
  color: var(--c-vertical-soft);
  font-weight: var(--fw-semibold);
}

/* Cuerpo de artículo -------------------------------------------------- */

.prose > * + * { margin-top: var(--sp-s); }
.prose h2 { margin-top: var(--sp-xl); }
.prose h3 { margin-top: var(--sp-l); }
.prose p, .prose li { color: var(--c-text-muted); }
.prose img { border-radius: var(--radius-m); margin-block: var(--sp-l); }

.prose blockquote {
  margin: var(--sp-l) 0;
  padding-left: var(--sp-m);
  border-left: 2px solid var(--c-vertical);
  font-size: var(--fs-h4);
  color: var(--c-text);
}

/* ═══════════════════════════════════════════════════════════════════════ *
 * HITO 2 — Sobre Nosotros, Casos, Blog, Artículo, 404 y búsqueda
 * ═══════════════════════════════════════════════════════════════════════ */

/* Ajustes de sección compartidos ---------------------------------------- */

.hero__eyebrow { margin-bottom: var(--sp-s); }


/* Números en tratamiento plano ----------------------------------------- *
 * Sin icono ni nombre de vertical: el número es de la compañía. Del color de
 * vertical queda solo una barra corta de acento, que mantiene la paleta.
 */

.stats-card--plain .stat { gap: var(--sp-2xs); }

.stat__rule {
  width: 32px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--c-vertical-soft);
  margin-bottom: var(--sp-2xs);
}

.stats-card--plain .stat__text {
  font-size: var(--fs-body);
  color: var(--c-text-muted);
}

/* Dos columnas: relato + imagen ---------------------------------------- */

.split {
  display: grid;
  gap: var(--sp-xl);
}

.split__copy,
.split__media { min-width: 0; }

.split__title {
  font-size: var(--fs-h1);
  letter-spacing: var(--tracking-tight);
  margin-bottom: var(--sp-m);
}

.split__text { color: var(--c-text-muted); }
.split__text p + p { margin-top: var(--sp-s); }

.split__media img {
  width: 100%;

  /* 3/2, el mismo formato que la media de las tarjetas de vertical: recorta
     menos que 4/3 una foto apaisada —que es lo que suele mandar el cliente— y
     sigue fijando el alto, así una imagen de otra proporción no mueve el
     layout (criterio de aceptación de HU2.5). */
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--radius-l);
  border: 1px solid var(--c-border-soft);
}

@media (min-width: 900px) {
  .split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
    gap: var(--sp-2xl);
    align-items: center;
  }
}

/* Declaración de la banda editable ------------------------------------- */

.philosophy { max-width: min(100%, 46rem); }

.philosophy__title {
  font-size: var(--fs-h1);
  letter-spacing: var(--tracking-tight);
  margin-bottom: var(--sp-m);
}

.philosophy__text {
  /* Era este mismo clamp escrito letra por letra: es `--fs-h4`. */
  font-size: var(--fs-h4);
  color: var(--c-text-muted);
}

/* Cómo trabajamos: cuatro pasos ---------------------------------------- *
 * El marcador es un punto, no un número: el Figma (#3, #4, #6) pide sacar toda
 * la numeración visible. El orden lo sigue llevando el <ol> y la línea superior
 * teñida; el punto solo ancla el arranque del paso sin competir con el título.
 */

.process {
  display: grid;
  gap: var(--sp-m);
  margin: 0;
  padding: 0;
  list-style: none;
}

.process__step {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xs);
  padding-top: var(--sp-m);
  border-top: 2px solid var(--c-vertical);
}

/*
 * La caja del marcador conserva la altura que ocupaba el número (2–3 rem) para
 * que el ritmo vertical de la sección no se mueva al cambiar de uno a otro. El
 * punto va centrado dentro de esa caja, con un halo del color de la vertical
 * que repite el gesto del recuadro del icono en las tarjetas.
 */
.process__marker {
  display: grid;
  place-items: center;

  /*
   * `vw` DELIBERADO, y acá es la decisión menos evidente del archivo: este
   * marcador ocupa el sitio que ocupaba un NÚMERO, así que su tamaño pertenece
   * a la escala tipográfica —que en este sistema es fluida contra la ventana—
   * y no al ancho del paso que lo contiene.
   *
   * Medido con la alternativa: atado al paso (`cqw`) el marcador pasaba de
   * 32 a 48 px a 320 px de ventana, porque ahí el paso ocupa la fila entera
   * (288 px) mientras que en escritorio mide 258. Un punto decorativo del
   * tamaño de un titular en el móvil: el contenedor NO es quien manda acá.
   */
  width: clamp(2rem, 1.4rem + 2vw, 3rem);
  height: clamp(2rem, 1.4rem + 2vw, 3rem);
  border-radius: 50%;
  background: color-mix(in srgb, var(--c-vertical) 16%, transparent);
}

.process__marker::before {
  content: "";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--c-vertical-soft);
}

.process__title {
  font-size: var(--fs-h4);
  color: var(--c-text);
}

.process__text {
  color: var(--c-text-muted);
  font-size: var(--fs-small);
}

/* Mismo criterio que la tarjeta de resultados: la grilla sigue a la cantidad
   de pasos que escribió el cliente, no al revés. */
@media (min-width: 640px) {
  .process { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-l); }

  .process__step:nth-child(odd):last-child { grid-column: 1 / -1; }

  /* Dos líneas reservadas para el título del paso: en fila, un paso de título
     corto y otro de título largo empezaban su descripción a alturas distintas
     y los cuatro se leían desalineados. Es alto mínimo, no recorte: un título
     de tres líneas crece igual, sin perder texto. */
  .process__title { min-height: calc(2em * var(--lh-heading)); }
}

@media (min-width: 900px) {
  .process { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .process:has(.process__step:nth-child(3):last-child) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .process:has(.process__step:nth-child(2):last-child) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .process:has(.process__step:only-child) { grid-template-columns: minmax(0, 1fr); }

  .process__step:nth-child(odd):last-child { grid-column: auto; }
}

/* Tarjeta de caso de éxito --------------------------------------------- *
 * Estructura del Figma: foto arriba a ancho completo y, debajo, la vertical en
 * su color, el nombre del caso y una línea de descripción. Sin métrica: los
 * números viven en la sección "Resultados" de cada vertical.
 *
 * La tarjeta sigue SIN overflow:hidden —ese `overflow` es el que en el hito 1
 * recortó el enlace de las tarjetas de vertical cuando la rejilla las estiró—.
 * El recorte de la foto lo hace su propia caja, que sí puede permitírselo
 * porque no contiene texto ni enlaces.
 */

.case-card {
  --c-vertical: var(--c-primary);
  --c-vertical-soft: var(--c-accent);
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  transition: translate var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/*
 * Foto del caso. Fija el alto de la cabecera de TODAS las tarjetas: la
 * proporción es la misma con foto y con marcador, así que las hermanas de una
 * hilera empiezan su texto a la misma altura aunque el cliente todavía no haya
 * subido las seis imágenes.
 *
 * `aspect-ratio` sin `min-height`: con los dos juntos el mínimo intrínseco pasa
 * a ser alto × ratio y es la MEDIA la que dicta el ancho de la columna del
 * grid. Acá manda el contenedor y la proporción solo sostiene el alto.
 */
.case-card__media {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: calc(var(--radius-m) - 1px) calc(var(--radius-m) - 1px) 0 0;
  background:
    radial-gradient(70% 70% at 50% 40%, color-mix(in srgb, var(--c-vertical) 14%, transparent), transparent 72%),
    var(--c-surface-2);
}

.case-card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-out);
}

.case-card:hover .case-card__media img,
.case-card:focus-within .case-card__media img { transform: scale(1.03); }

.case-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xs);
  min-width: 0;
  padding: var(--sp-m);
}

/* La etiqueta más larga del sistema es "CAPACITACIÓN INDUSTRIAL": en una
   columna estrecha envuelve, y sin alto reservado esa tarjeta —solo esa—
   bajaría su título una línea respecto de sus hermanas. */
.case-card__tag {
  min-height: calc(2 * var(--fs-eyebrow) * 1.3);
}

.case-card__title {
  font-size: var(--fs-h4);

  /* Dos líneas exactas, ocupadas o no: el nombre del caso lo escribe el
     cliente y no tiene tope de caracteres. */
  min-height: calc(2em * var(--lh-heading));
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

.case-card__title a {
  color: var(--c-text);
  text-decoration: none;
}

/* Toda la tarjeta es zona de click, pero el enlace real es el título: el
   lector de pantalla anuncia el destino, no "enlace, tarjeta". */
.case-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.case-card__title a:hover { color: var(--c-vertical-soft); }

.case-card__text {
  color: var(--c-text-muted);
  font-size: var(--fs-small);

  /* Tope de líneas: sin él cada tarjeta medía distinto según su resumen y la
     fila entera se veía despareja. En el diseño es una línea; se admiten dos
     para que un resumen algo más largo no quede cortado a mitad de idea. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;

  /* `anywhere` y no `break-word`: en un hijo de grid una palabra kilométrica
     —una URL pegada en el resumen— sigue empujando la caja con `break-word`,
     porque no reduce el tamaño mínimo intrínseco. */
  overflow-wrap: anywhere;
}

@media (prefers-reduced-motion: reduce) {
  .case-card:hover,
  .case-card:focus-within { transform: none; }
  .case-card:hover .case-card__media img,
  .case-card:focus-within .case-card__media img { transform: none; }
}

/* Píldora de vertical -------------------------------------------------- *
 * La etiqueta con borde que encabeza un artículo (single.php). Nació de
 * unificar la misma píldora escrita dos veces —`case-head__tag` y
 * `article-head__tag`, idénticas salvo un `margin-bottom` y con el `:hover`
 * calificado en una y sin calificar en la otra, o sea con especificidades
 * distintas para el mismo estado—. Hoy queda un solo consumidor porque la
 * ficha de caso se retiró (6650556), pero sigue siendo una clase propia y no
 * un `.article-head__tag`: el margen lo pone quien la usa, que es lo que
 * permitía compartirla.
 *
 * La tipografía sale de `.eyebrow`: es un rótulo, y encima adscribe a una
 * vertical, así que lleva también `.eyebrow--vertical`.
 */
.tag-pill {
  display: inline-flex;
  align-items: center;

  /* Es un enlace: la WCAG 2.5.8 pide 24 px de zona clicable y el sistema
     apunta a `--tap-target`. Con `--control-h` la píldora queda demasiado
     alta para una etiqueta, así que sube a 44 solo en puntero grueso, que es
     donde el mínimo importa de verdad. */
  min-height: 32px;
  padding: var(--sp-3xs) var(--sp-s);
  border: 1px solid color-mix(in srgb, var(--c-vertical) 55%, var(--c-border));
  border-radius: var(--radius-pill);
  text-decoration: none;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}

@media (pointer: coarse) {
  .tag-pill { min-height: var(--control-h); }
}

a.tag-pill:hover {
  background: color-mix(in srgb, var(--c-vertical) 18%, var(--c-bg));
  border-color: var(--c-vertical-soft);
  color: var(--c-vertical-soft);
}

/* Figura del artículo --------------------------------------------------- */

.article-figure {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-l);
  overflow: hidden;
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
}

.article-figure img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Nota de alcance de una vertical --------------------------------------- *
 * Microbloque que cierra "Soluciones" (parts/solutions.php). Es una acotación
 * al alcance de lo de arriba —"las mismas soluciones se aplican a…"—, no una
 * sección: por eso no lleva título y se apoya en la barra de acento en vez de
 * en un encabezado.
 *
 * Misma barra que `.case-detail__metric`, un escalón más fina: aquélla marca un
 * dato y ésta una acotación, y con los 3 px de la métrica competían.
 */
.scope-note {
  margin: var(--sp-l) 0 0;
  padding-left: var(--sp-m);
  border-left: 2px solid var(--c-vertical);
  color: var(--c-text-muted);
  font-size: var(--fs-lead);

  /* El tope de lectura del sistema, no un `ch` elegido a ojo: `--measure` está
     despejado para dar 72 caracteres por línea (tokens.css), dentro de la franja
     de 60-75 que pide la auditoría. Se midieron los dos: 62ch suelto daba 76
     caracteres reales a 1440 —uno por encima del techo—, y `--measure` da 70.
     Los textos reales van de 70 a 105 caracteres y el campo tapa en 130, así
     que el más largo cae en dos líneas en vez de una muy ancha. */
  max-width: var(--measure);

  /* `anywhere` y no `break-word`: el texto lo escribe el cliente y en un hijo
     de flex/grid `break-word` no reduce el mínimo intrínseco, así que una URL
     pegada seguiría empujando la caja. */
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Índice del blog ------------------------------------------------------- */

.blog-head { padding-bottom: 0; }

.blog-head__inner {
  max-width: min(100%, 46rem);
  margin-bottom: var(--sp-l);
}

.blog-head__title {
  font-size: var(--fs-display);
  line-height: 1.05;
  letter-spacing: var(--tracking-tighter);
  font-weight: var(--fw-bold);
}

.blog-head__lead {
  margin-top: var(--sp-m);
  font-size: var(--fs-lead);
  color: var(--c-text-muted);
  max-width: 52ch;
}

/* La rejilla arranca donde termina la lista de chips. */
.blog-grid { margin-top: var(--sp-l); }

/* Última fila sin huérfana --------------------------------------------- *
 *
 * Sirve a la rejilla del blog (`.blog-grid`), que es una tarjeta repetida en
 * 3 columnas y terminaba con una fila corta pegada a la izquierda.
 *
 * Hasta el 24/08 servía también a `.grid--cases`, la rejilla de casos de
 * éxito. Ese selector se retiró: lo emitían la ficha individual —borrada en
 * 6650556— y la grilla de `/casos-de-exito/`, que en el mismo commit pasó a
 * ser una lista de bloques anclados (`.case-list`) sin rejilla ni fila corta.
 * Ningún marcado del tema lo escribe hoy.
 *
 * `posts_per_page` son 10 y la rejilla son 3 columnas desde 900 px: las
 * páginas 2, 3 y 4 del blog terminaban en una fila de UNA tarjeta pegada a la
 * izquierda con dos columnas vacías a la derecha (medido: filas 3-3-3-1 a 1024
 * y a 1440), y la página 5 en 3-3-1. A 768 pasa lo mismo con 2 columnas: la
 * página 1 daba 2-2-2-2-1. El número de entradas por página es del backend y
 * no se toca desde acá; esto se resuelve en la maqueta.
 *
 * El truco: la rejilla se declara con el DOBLE de pistas y cada tarjeta ocupa
 * dos. La aritmética da exactamente el mismo ancho de tarjeta que antes
 * —6 pistas + 5 huecos, tarjeta = 2 pistas + 1 hueco = (W − 2g) / 3, que es la
 * pista de una rejilla de 3—, así que las tarjetas siguen midiendo igual entre
 * hermanas. Lo único que cambia es que ahora hay medias pistas donde apoyar la
 * última fila corta y centrarla.
 *
 * Los selectores dicen «la última fila está incompleta» sin saber cuántas
 * entradas hay: si el último hijo cae en posición 3n+1, abre fila y está solo;
 * si el anteúltimo cae en 3n+1, quedan dos. No hay ninguna cuenta escrita a
 * mano que se rompa cuando Juan cambie el número de entradas por página.
 */
@media (min-width: 640px) and (max-width: 899.98px) {
  .grid.blog-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

  .grid.blog-grid > * { grid-column: span 2; }

  /* Sola en la última fila de dos columnas → al medio. */
  .grid.blog-grid > :last-child:nth-child(2n + 1) { grid-column: 2 / span 2; }
}

/*
 * El tramo de arriba se cierra en 899,98 px a propósito: si fuera sólo
 * `min-width: 640px`, la regla del huérfano de DOS columnas seguiría activa a
 * 1440 y una página de 9 tarjetas —última fila completa de 3— mandaría la
 * novena al centro rompiendo la fila. Verificado con getComputedStyle.
 */
@media (min-width: 900px) {
  .grid.blog-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }

  .grid.blog-grid > * { grid-column: span 2; }

  /* Sola en la última fila de tres columnas → al medio. */
  .grid.blog-grid > :last-child:nth-child(3n + 1) { grid-column: 3 / span 2; }

  /* Dos en la última fila → el par centrado; la segunda cae sola detrás. */
  .grid.blog-grid > :nth-last-child(2):nth-child(3n + 1) { grid-column: 2 / span 2; }
}

/* Artículo destacado: la misma tarjeta, en horizontal ------------------- */

.post-card--feature { margin-bottom: var(--sp-l); }

.post-card__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-s);
  flex-wrap: wrap;
}

/*
 * El antetítulo de vertical falta en 13 de los 47 artículos, y falta a
 * propósito. Sin reservar su caja, esas tarjetas arrancaban el título 19,8 px
 * más arriba que sus hermanas de la misma fila (medido en 320, 768, 1024 y
 * 1440: 247,5 contra 267,3 · 293,0 contra 312,8 · 261,3 contra 281,1 · 301,1
 * contra 320,9). El desfase es exactamente la caja de línea del antetítulo,
 * que colapsaba a 0.
 *
 * `1lh` = la altura de línea de ESTE elemento, resuelta por el navegador con
 * su propia tipografía. No es una constante medida a ojo: si mañana cambia
 * `--fs-eyebrow` o la interlínea, la reserva se mueve con ella. Va sobre el
 * antetítulo y no sobre `.post-card__meta` porque el contenedor hereda el
 * cuerpo del texto (17 px → 28,05 px de línea) y sobre-reservaría 8,25 px.
 *
 * El respaldo en `calc()` es el mismo valor escrito desde los tokens, para
 * navegadores sin la unidad `lh`.
 */
.post-card__meta .eyebrow {
  min-height: calc(var(--fs-eyebrow) * var(--lh-body));
  min-height: 1lh;
}

/* El pie de la tarjeta de artículo es el `.link-arrow` del sistema: acá solo
   va el aire que lo separa del extracto. Antes era una copia literal de sus
   diez declaraciones. */
.post-card__more { margin-top: var(--sp-2xs); }

/*
 * Este bloque va DESPUÉS de la regla base a propósito (lección del hito 1: un
 * override dentro de @media escrito antes de su base lo pierde por cascada).
 */
@media (min-width: 900px) {
  .post-card--feature {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
    align-items: stretch;
  }

  .post-card--feature .post-card__media { aspect-ratio: auto; min-height: 320px; }

  .post-card--feature .post-card__body {
    justify-content: center;
    gap: var(--sp-s);
    padding: var(--sp-2xl);
  }

  .post-card--feature .post-card__title { font-size: var(--fs-h2); }

  .post-card--feature .post-card__text {
    font-size: var(--fs-body);
    max-width: 46ch;
  }
}

/* Cabecera del artículo ------------------------------------------------- */

/* En la cabecera del artículo la píldora es lo primero de la columna y necesita
   aire antes del título. Es lo ÚNICO que la diferencia de la de una ficha de
   caso, y por eso es lo único que se escribe. */
.article-head .tag-pill { margin-bottom: var(--sp-m); }

.article-head__title { letter-spacing: var(--tracking-tight); }

.article-head__lead {
  margin-top: var(--sp-m);
  font-size: var(--fs-lead);
  color: var(--c-text-muted);
}

/* Firma: iniciales en vez de foto de un tercero. */
.byline {
  display: flex;
  align-items: center;
  gap: var(--sp-s);
  margin-top: var(--sp-l);
  padding-top: var(--sp-m);
  border-top: 1px solid var(--c-border-soft);
  font-size: var(--fs-small);
}

.byline__avatar {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--c-vertical) 22%, var(--c-surface));
  color: var(--c-vertical-soft);
  font-weight: var(--fw-bold);
  font-size: 1.0625rem;
  line-height: 1;
}

.byline__name {
  display: block;
  color: var(--c-text);
  font-weight: var(--fw-semibold);
}

.byline__meta {
  display: block;
  color: var(--c-text-faint);
}

.byline__dot { margin-inline: var(--sp-3xs); }

/* Cuerpo de artículo: bloques permitidos -------------------------------- *
 * Cita, lista, imagen, separador, HTML y video. El HTML es contenido que
 * escribe el editor y puede traer cualquier cosa (una tabla, un iframe de
 * calendario, una galería vieja de la migración): las reglas de acá abajo son
 * defensivas para que nada de eso saque la página de su ancho.
 */

.prose > *:first-child { margin-top: 0; }

/*
 * MEDIDA DE LECTURA del cuerpo editable.
 *
 * Va en los bloques de texto y NO en `.prose` entera a propósito: una imagen,
 * una tabla, un vídeo o un separador del editor tienen que poder ocupar la
 * columna completa. Lo que se acota es el párrafo, que es lo único que se lee
 * de corrido.
 *
 * Por qué hacía falta, medido con el copy real:
 *
 *                                        antes            después
 *   `.prose p` de una vertical @1440     133 car./línea    72
 *   `.prose p` de una vertical @768       91               72
 *   `.prose p` de un artículo @1440       87 (760 px)      72
 *
 * El caso del artículo es el que no estaba en la auditoría: se daba por bueno
 * porque la plantilla ya acotaba a `--container-narrow`, pero 760 px con este
 * cuerpo son 87 caracteres por línea — 12 por encima del techo de 75. La
 * columna sigue midiendo 760; lo que se acota ahora es el párrafo dentro.
 */
.prose > p,
.prose > ul,
.prose > ol,
.prose > blockquote { max-width: var(--measure); }

/*
 * Embebidos sociales migrados: el `<blockquote>` que pega Instagram trae
 * `min-width: 326px` en el atributo `style`, y 326 no entran en los 288 px de
 * contenedor que quedan a 320 px de ventana. Medido en
 * `/3destiny-en-el-foro-tecnologico-mendoza-tec-2021/`: la página desbordaba
 * 23 px a 320 (el `overflow-x: clip` de `html` lo escondía, por eso no saltó
 * antes). Son 2 de los 47 artículos, y el que suba Juan mañana traerá lo
 * mismo pegado del portapapeles.
 *
 * El `!important` es la única forma de ganarle a un `style` en línea de un
 * tercero, y es exactamente para lo que sirve. A 390 px y de ahí para arriba
 * el embebido ya cabía: esta regla no cambia nada de lo aprobado, sólo evita
 * que la caja exija más ancho del que hay.
 */
.prose blockquote[data-instgrm-permalink],
.prose blockquote.twitter-tweet,
.prose blockquote.tiktok-embed { min-width: 0 !important; }

/* Columna de lectura alineada a la izquierda del contenedor ancho, para las
   fichas cuya cabecera e imagen van a ancho completo. */
.prose--flush { max-width: var(--container-narrow); }

.prose a {
  color: var(--c-vertical-soft);
  text-decoration-color: color-mix(in srgb, var(--c-vertical-soft) 45%, transparent);
}

.prose a:hover {
  color: var(--c-vertical-soft);
  text-decoration-color: currentColor;
}

.prose li::marker { color: var(--c-vertical-soft); }
.prose li + li { margin-top: var(--sp-2xs); }

.prose h2 { letter-spacing: -0.02em; }

/* Imagen (core/image), con su pie */
.prose figure { margin-block: var(--sp-l); }

.prose figure img {
  border-radius: var(--radius-m);
  margin-block: 0;
}

.prose figcaption {
  margin-top: var(--sp-2xs);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--c-text-mono);
}

/* Cita (core/quote) */
.prose blockquote p:last-child { margin-bottom: 0; }

.prose blockquote cite {
  display: block;
  margin-top: var(--sp-s);
  font-size: var(--fs-small);
  font-style: normal;
  color: var(--c-text-faint);
}

/* Separador (core/separator): no una línea a todo el ancho, sino una marca
   corta en el acento de la vertical. */
.prose hr,
.prose .wp-block-separator {
  width: 96px;
  height: 3px;
  margin: var(--sp-2xl) auto;
  border: 0;
  border-radius: var(--radius-pill);
  background: linear-gradient(90deg, var(--c-vertical), var(--c-vertical-soft));
}

/* Video (core/video) */
.prose .wp-block-video video,
.prose video {
  width: 100%;
  height: auto;
  border-radius: var(--radius-m);
  background: var(--c-surface);
}

/* HTML libre (core/html) y contenido migrado: nada puede desbordar. */
.prose iframe,
.prose embed,
.prose object {
  max-width: 100%;
  width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  border: 0;
  border-radius: var(--radius-m);
  background: var(--c-surface);
}

.prose pre {
  max-width: 100%;
  overflow-x: auto;
  padding: var(--sp-m);
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  font-family: var(--font-mono);
  font-size: var(--fs-small);
}

.prose code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--c-vertical-soft);
}

.prose pre code { color: inherit; }

/* La tabla pasa a bloque con scroll propio: una tabla ancha dentro de una
   columna de 760 px es la forma más rápida de romper el ancho de la página. */
.prose table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: var(--fs-small);
}

.prose th,
.prose td {
  padding: var(--sp-2xs) var(--sp-s);
  border: 1px solid var(--c-border-soft);
  text-align: left;
}

.prose th {
  background: var(--c-surface);
  color: var(--c-text);
  font-weight: var(--fw-semibold);
}

/* Cuerpo de artículo: contenido migrado del editor clásico -------------- *
 *
 * Los 45 artículos que llegan del sitio viejo NO traen bloques de Gutenberg:
 * traen el marcado que escribía el editor clásico —imágenes flotadas con
 * `alignleft`/`alignright`, pies de foto en `.wp-caption` y galerías de
 * `[gallery]`—. Ese marcado lo vestía `wp-block-library`, que este tema quita
 * del frontend a propósito por peso (inc/assets.php) y NO se debe reponer.
 *
 * Sin estas reglas, los artículos migrados se ven así: toda imagen a ancho
 * completo apilada una debajo de otra (la flotación no existe), cada pie de
 * foto como un párrafo suelto indistinguible del texto, y las galerías como una
 * columna de fotos sueltas. No es un fallo visible en consola: es contenido que
 * simplemente se lee mal, en 45 páginas a la vez.
 *
 * Los anchos salen del sistema; nada de los píxeles del sitio viejo.
 */

/* -- Alineación de imágenes -- */

.prose .aligncenter {
  display: block;
  margin-inline: auto;
}

.prose .alignnone,
.prose .aligncenter {
  margin-block: var(--sp-l);
}

/*
 * Mobile-first: en la columna angosta del móvil NADA flota. Una foto de 300 px
 * flotada dentro de una columna de 320 px deja el texto en un canal de 20 px
 * —una palabra por renglón—, que es peor que no alinear nada. La flotación
 * entra a partir de 640 px, el mismo escalón que usa el resto del tema.
 */
.prose .alignleft,
.prose .alignright {
  display: block;
  margin-inline: auto;
  margin-block: var(--sp-l);
}

@media (min-width: 640px) {
  .prose .alignleft {
    float: left;
    max-width: 50%;
    margin: var(--sp-2xs) var(--sp-m) var(--sp-s) 0;
  }

  .prose .alignright {
    float: right;
    max-width: 50%;
    margin: var(--sp-2xs) 0 var(--sp-s) var(--sp-m);
  }
}

/* Un titular nunca arranca al costado de una foto flotada: baja hasta que el
   ancho vuelve a ser el de la columna. Sin esto, un H2 que cae junto a una
   imagen a la derecha se lee como un pie de foto largo. */
.prose h2,
.prose h3,
.prose hr,
.prose .wp-block-separator {
  clear: both;
}

/* Y el final del artículo tampoco puede quedar por encima de una foto que
   todavía flota: eso hace que la firma o el bloque de contacto se metan dentro
   del texto. */
.prose::after {
  content: "";
  display: block;
  clear: both;
}

/* -- Pie de foto clásico (`[caption]`) -- */

/*
 * El editor clásico escribe el ancho en el atributo `style` del contenedor
 * (`style="width: 610px"`), tomado del tamaño que tenía la imagen en el sitio
 * viejo. `max-width: 100%` es lo que impide que ese número —que puede ser
 * mayor que la columna de lectura— saque la página de su ancho en el móvil.
 */
.prose .wp-caption {
  max-width: 100%;
  margin-block: var(--sp-l);
}

.prose .wp-caption img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-m);
}

/* Mismo tratamiento que el `figcaption` de un bloque moderno: los artículos
   migrados y los nuevos tienen que leerse como el mismo sitio, no como dos. */
.prose .wp-caption-text {
  margin: var(--sp-2xs) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  color: var(--c-text-mono);
  text-align: left;
}

/* -- Galería clásica (`[gallery]`) -- */

/*
 * Rejilla propia. WordPress no imprime su hoja de galería porque el tema
 * declara `add_theme_support('html5', ['gallery'])` (inc/setup.php), así que
 * acá no hay nada contra lo que pelear: el ancho de columna lo decide el CSS y
 * no un `width: 33%` en línea.
 *
 * Por defecto dos columnas y no las que diga `gallery-columns-N`: en el móvil
 * una galería de seis columnas da miniaturas de 45 px. El número del shortcode
 * se respeta a partir de 640 px, que es donde tiene sentido.
 */
.prose .gallery {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-s);
  margin-block: var(--sp-l);
  padding: 0;
  list-style: none;
}

.prose .gallery-columns-1 { grid-template-columns: minmax(0, 1fr); }

@media (min-width: 640px) {
  .prose .gallery-columns-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .prose .gallery-columns-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .prose .gallery-columns-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .prose .gallery-columns-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  .prose .gallery-columns-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .prose .gallery-columns-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  .prose .gallery-columns-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); }
}

/* `min-width: 0` para que una miniatura ancha no estire su columna y descuadre
   la rejilla entera; `margin: 0` porque el hueco lo pone el `gap`. */
.prose .gallery-item {
  min-width: 0;
  margin: 0;
  text-align: left;
}

.prose .gallery-icon { margin: 0; }

.prose .gallery-icon img {
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-s);
}

.prose .gallery-caption {
  display: block;
  margin: var(--sp-3xs) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: 1.4;
  color: var(--c-text-mono);
}

/* Ajustes que ofrece el editor ------------------------------------------ *
 *
 * Cuando quien escribe elige un color o un tamaño de la paleta, el editor
 * escribe una clase en el marcado (`has-accent-color`, `has-h3-font-size`) y
 * espera que ALGUIEN la pinte. Normalmente lo hace la hoja `global-styles` que
 * WordPress genera desde theme.json — pero este tema la quita del frontend a
 * propósito (inc/assets.php), porque trae ~70 KB de reglas que no usa.
 *
 * Resultado antes de este bloque: el editor ofrecía cinco tamaños y seis
 * colores, y al publicar no pasaba nada. Se veía bien en la pantalla de edición
 * y plano en el sitio, que es la peor versión del problema: nadie lo reporta
 * como error, simplemente el ajuste "no anda".
 *
 * Van SIN `.prose` delante a propósito: el contenido del editor se dibuja en
 * dos sitios distintos —la columna de lectura (`.prose`) y la banda editable de
 * la portada (`parts/band.php`, que va suelto dentro de `.container`)— y una
 * clase de la paleta tiene que valer en los dos.
 *
 * Los valores son los MISMOS tokens que declara theme.json, así que la paleta
 * sigue teniendo una sola fuente de verdad: tokens.css.
 */

.has-small-font-size { font-size: var(--fs-small); }
.has-body-font-size  { font-size: var(--fs-body); }
.has-h4-font-size    { font-size: var(--fs-h4); }
.has-h3-font-size    { font-size: var(--fs-h3); }
.has-h2-font-size    { font-size: var(--fs-h2); }

/*
 * OJO CON EL ORDEN. La paleta de theme.json tiene un color con el slug `text`,
 * así que su clase es `has-text-color`… que es EXACTAMENTE la clase marcadora
 * que WordPress agrega a cualquier bloque con color propio. Un párrafo en
 * acento sale del editor como `class="has-text-color has-accent-color"`: las
 * dos reglas tienen la misma especificidad y gana la última que se declare.
 *
 * Por eso `has-text-color` va PRIMERA: cualquier color de verdad la pisa. Es un
 * parche; el arreglo de fondo es renombrar ese slug en theme.json (`ink`,
 * `titulo`, lo que sea menos `text`), y eso es archivo del backend.
 */
.has-text-color    { color: var(--c-text); }

.has-bg-color      { color: var(--c-bg); }
.has-surface-color { color: var(--c-surface); }
.has-muted-color   { color: var(--c-text-muted); }
.has-primary-color { color: var(--c-primary); }
.has-accent-color  { color: var(--c-accent); }

.has-bg-background-color      { background-color: var(--c-bg); }
.has-surface-background-color { background-color: var(--c-surface); }
.has-text-background-color    { background-color: var(--c-text); }
.has-muted-background-color   { background-color: var(--c-text-muted); }
.has-primary-background-color { background-color: var(--c-primary); }
.has-accent-background-color  { background-color: var(--c-accent); }

/*
 * Un bloque con fondo necesita aire, o el texto queda pegado al borde del
 * color. WordPress lo resuelve con los controles de espaciado, que este tema
 * tiene apagados (theme.json: `spacing.padding: false`), así que el aire lo
 * pone el tema.
 */
:is(p, h1, h2, h3, h4, h5, h6)[class*="-background-color"] {
  padding: var(--sp-s) var(--sp-m);
  border-radius: var(--radius-m);
}

/* Paginación ------------------------------------------------------------ */

.pagination {
  margin-top: var(--sp-2xl);
}

.pagination .page-numbers {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--sp-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

.pagination li { display: block; }

.pagination a.page-numbers,
.pagination span.page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--control-h);
  padding-inline: var(--sp-xs);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-s);
  font-size: var(--fs-small);
  color: var(--c-text-muted);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

.pagination a.page-numbers:hover {
  border-color: var(--c-accent);
  color: var(--c-accent);
}

.pagination .page-numbers.current {
  background: var(--c-surface-2);
  border-color: var(--c-accent);
  color: var(--c-text);
  font-weight: var(--fw-semibold);
}

.pagination .page-numbers.dots {
  border-color: transparent;
  min-width: 0;
  color: var(--c-text-faint);
}

/* Estado vacío ---------------------------------------------------------- */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-s);
  padding: var(--sp-2xl) var(--sp-m);
  text-align: center;
  border: 1px dashed var(--c-border);
  border-radius: var(--radius-l);
  background: var(--c-bg-elev);
}

.empty-state__mark {
  display: block;
  width: 48px;
  height: 48px;
  color: var(--c-accent);
  opacity: 0.8;
}

.empty-state__mark svg { width: 100%; height: 100%; }

.empty-state__title {
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: var(--fw-semibold);
  color: var(--c-text);
}

.empty-state__text {
  margin: 0;
  max-width: 46ch;
  color: var(--c-text-muted);
}

.empty-state__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2xs);
  margin-top: var(--sp-2xs);
}

.empty-state__search {
  width: 100%;
  max-width: 28rem;
  margin-top: var(--sp-s);
}

/* Página no encontrada -------------------------------------------------- */

.error-page { text-align: center; }

.error-page__code {
  margin: 0 0 var(--sp-s);
  font-size: var(--fs-display-xl);
  font-weight: var(--fw-bold);
  line-height: 0.9;
  letter-spacing: var(--tracking-display);

  /* Color plano primero: si el recorte de fondo no se soporta, el número se
     ve igual en el azul de marca en vez de quedar invisible. */
  color: var(--c-primary);
  background-image: linear-gradient(
    100deg,
    var(--c-farma-soft),
    var(--c-capacitacion-soft) 35%,
    var(--c-ecommerce-soft) 65%,
    var(--c-marketing-soft)
  );
  -webkit-background-clip: text;
  background-clip: text;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .error-page__code { color: transparent; }
}

.error-page__title { letter-spacing: var(--tracking-tight); }

.error-page__lead {
  margin-top: var(--sp-m);
  color: var(--c-text-muted);
}

.error-page__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2xs);
  margin-top: var(--sp-l);
}

.error-page__verticals {
  display: grid;
  gap: var(--sp-2xs);
  margin-top: var(--sp-2xl);
}

.error-page__vertical {
  display: flex;
  align-items: center;
  gap: var(--sp-s);
  min-width: 0;
  padding: var(--sp-s) var(--sp-m);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
  background: var(--c-surface);
  color: var(--c-text);
  text-align: left;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

.error-page__vertical:hover {
  border-color: color-mix(in srgb, var(--c-vertical) 55%, var(--c-border));
  background: var(--c-surface-2);
  color: var(--c-text);
}

.error-page__icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--c-vertical-soft);
}

.error-page__search { margin-top: var(--sp-2xl); }

.error-page__search-label {
  margin-bottom: var(--sp-s);
  font-size: var(--fs-small);
  color: var(--c-text-faint);
}

/* El buscador del 404 va centrado; el de la página de resultados, no. */
.error-page__search .search-form { margin-inline: auto; }

@media (min-width: 640px) {
  .error-page__verticals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Resultados de búsqueda ------------------------------------------------ *
 * Lista, no rejilla: la búsqueda mezcla artículos, casos y páginas, y una
 * tarjeta con imagen destacada deja a las páginas fijas con un hueco gris.
 *
 * El prefijo es `search-result` y no `result` porque `.result` YA existía como
 * la celda de dato de una vertical (más abajo, `.metric` desde el 21/08). Las
 * dos definiciones convivían en la misma hoja y ganaba la última: la búsqueda
 * salía con línea doble entre resultados, borde izquierdo en los impares y el
 * extracto en 17 px blanco en vez de 14 atenuado. Dos componentes distintos,
 * dos nombres distintos.
 */

.search-panel { margin-bottom: var(--sp-2xl); }

.search-results {
  margin: 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--c-border-soft);
}

.search-result {
  min-width: 0;
  padding-block: var(--sp-m);
  border-bottom: 1px solid var(--c-border-soft);
}

.search-result__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2xs) var(--sp-s);
  margin-bottom: var(--sp-2xs);
}

.search-result__vertical { color: var(--c-vertical-soft); }

.search-result__title {
  font-size: var(--fs-h4);
  margin-bottom: var(--sp-2xs);
}

.search-result__title a {
  color: var(--c-text);
  text-decoration: none;

  /*
   * El objetivo pulsable no puede depender del tamaño de fuente fluido.
   * `--fs-h4` baja con el ancho, y medido en `/?s=realidad` el enlace del
   * título daba 24,0 px a 1440 y a 1024 pero **23,0 a 768** (fuente 18,75 px):
   * por debajo de los 24 de la WCAG 2.5.8 AA. A 320 y 390 el título ocupa dos
   * renglones y ya sobra.
   *
   * Aquí NO sirve `min-height` como en `.link-arrow`: eso exige sacar el
   * enlace del flujo en línea (`inline-block`), y entonces el título deja de
   * partirse como texto y la lista crece — medido, `.search-results` pasaba de
   * 2773,6 a 2779,6 px a 768. El relleno vertical de un elemento EN LÍNEA
   * agranda la zona que recibe el puntero sin separar los renglones: la lista
   * mide exactamente lo mismo antes y después, a los cinco anchos.
   *
   * El número sale de `1lh` —la altura de línea real, sea cual sea la fuente
   * de este ancho—, no de una constante medida a ojo. El `max(0px, …)` hace
   * que la regla se anule sola donde el renglón ya llega a 24 (1024 y 1440):
   * ahí suma 0 y no toca nada.
   */
  padding-block: max(0px, calc((var(--tap-target-min) - 1lh) / 2));
}

.search-result__title a:hover {
  color: var(--c-vertical-soft);
  text-decoration: underline;
}

.search-result__text {
  margin: 0;
  color: var(--c-text-muted);
  font-size: var(--fs-small);
}

/* Bloque de contacto: respaldo y estados -------------------------------- */

/*
 * Aviso de estado del envío.
 *
 * Vacío no ocupa lugar (no hay caja fantasma bajo el formulario); con texto
 * aparece como una franja teñida. Los dos estados se distinguen por color Y
 * por el texto, nunca solo por color.
 */
.cta-form__status:empty { display: none; }

.cta-form__status {
  display: flex;
  align-items: center;
  gap: var(--sp-2xs);
  margin: 0;
  padding: var(--sp-xs) var(--sp-s);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-m);
  background: var(--c-surface-2);
  color: var(--c-text-muted);
  font-size: var(--fs-small);
}

.cta-form__status[data-state="sending"] {
  border-color: color-mix(in srgb, var(--c-accent) 45%, var(--c-border));
  color: var(--c-accent);
}

.cta-form__status[data-state="sent"] {
  border-color: color-mix(in srgb, var(--c-capacitacion-soft) 55%, var(--c-border));
  color: var(--c-capacitacion-soft);
}

.cta-form__status[data-state="error"] {
  border-color: color-mix(in srgb, var(--c-marketing-soft) 55%, var(--c-border));
  color: var(--c-marketing-soft);
}

/* Punto que late mientras se envía. Es un adorno: el texto ya lo dice todo. */
.cta-form__status[data-state="sending"]::before {
  content: "";
  width: 8px;
  height: 8px;
  flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: currentColor;
  animation: status-pulse 1.2s ease-in-out infinite;
}

@keyframes status-pulse {
  0%, 100% { opacity: 0.35; }
  50%      { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .cta-form__status[data-state="sending"]::before { animation: none; opacity: 1; }
}

/* Formulario propio del bloque de contacto ------------------------------- *
 * El formulario que pinta el tema cuando HubSpot acepta envíos por API. Usa los
 * mismos controles que el resto del sitio: fondo elevado, canto del sistema,
 * anillo de foco del sistema. Es la diferencia entre un bloque de contacto que
 * es del sitio y uno que es de otra marca.
 *
 * (La "piel del formulario de HubSpot" de más abajo es la equivalente para la
 * otra rama, la del embebido; ninguna de las dos se aplica nunca a la vez.)
 */

.contact-form {
  display: grid;
  gap: var(--sp-s);
  min-width: 0;

  /* Ancla del honeypot, que va fuera de pantalla en posición absoluta. */
  position: relative;
}

/*
 * El emparejado de campos sale del ancho del PANEL, no del de la ventana.
 *
 * La tarjeta deja ~420 px de contenido a 1440 y ~326 a 390, pero entre medio hay
 * un tramo (900-1024 px) donde la ventana ya es ancha y la columna todavía es
 * angosta: con una media query, ahí los campos caerían en dos columnas de 160 px.
 * Una consulta de contenedor pregunta lo único que importa, que es cuánto ancho
 * tiene el formulario.
 */
.cta-form__panel {
  container-type: inline-size;
  container-name: cta-form;
}

.contact-form__fields {
  display: grid;
  gap: var(--sp-s);
}

/*
 * 440 px, y el número no es a ojo: la etiqueta más larga que hoy manda HubSpot
 * ("Número de teléfono opcional") mide 208 px a 14 px, así que dos columnas más
 * el hueco de 16 px piden 432 para que ninguna etiqueta envuelva. Por debajo de
 * eso el formulario se lee mejor en una sola columna que en dos apretadas.
 *
 * Las etiquetas las escribe el cliente y no tienen tope: el día que escriba una
 * más larga, la etiqueta envuelve y su fila crece. Eso es la degradación
 * correcta —`overflow-wrap: anywhere` y `align-content: start` ya la sostienen—,
 * no un motivo para calcular el ancho sobre la etiqueta más larga de hoy.
 */
@container cta-form (min-width: 440px) {
  .contact-form__fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.field {
  display: grid;
  gap: var(--sp-3xs);

  /* Sin esto, una etiqueta larga del CMS estira su columna y descuadra la
     pareja de al lado. */
  min-width: 0;

  /* Los campos de una fila arrancan arriba: si uno tiene texto de ayuda y el
     otro no, sin esto el más corto se estira y sus controles no se alinean. */
  align-content: start;
}

/* El mensaje y los desplegables ocupan la fila entera: una caja de texto alta y
   angosta no la quiere usar nadie, y una lista de 36 opciones en media columna
   se recorta al abrirla. */
.field--full { grid-column: 1 / -1; }

.field__label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-3xs);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  color: var(--c-text);

  /* Las etiquetas las escribe el cliente en HubSpot y no tienen tope de
     caracteres: la defensa contra una palabra kilométrica va en el CSS, no en
     el campo. `anywhere` y no `break-word` porque dentro de una grilla
     `break-word` no reduce el ancho mínimo y la palabra sigue empujando. */
  overflow-wrap: anywhere;
}

/*
 * Se marcan los OPCIONALES, no los obligatorios.
 *
 * Quien está llenando un formulario no necesita saber qué es obligatorio —lo
 * son casi todos—, necesita saber qué se puede saltear. Y así la marca no
 * depende del color: es una palabra.
 */
.field__optional {
  font-size: var(--fs-micro);
  font-weight: var(--fw-regular);
  color: var(--c-text-muted);
}

.field__control {
  width: 100%;
  max-width: 100%;
  min-height: var(--control-h);
  padding: var(--sp-2xs) var(--sp-xs);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-s);
  background: var(--c-bg-elev);
  color: var(--c-text);
  font: inherit;
  font-size: var(--fs-small);
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

.field__control::placeholder { color: var(--c-text-faint); }

.field__control:hover { border-color: var(--c-text-faint); }

.field__control:focus-visible {
  outline: 2px solid var(--c-text);
  outline-offset: 2px;
  border-color: var(--c-accent);
  background: var(--c-surface);
}

/* El desplegable nativo se encarga solo en móvil; acá solo hay que sacarle la
   apariencia del sistema para que no sea la única caja gris del formulario. */
.field--select .field__control {
  appearance: none;
  padding-right: var(--sp-l);

  /* La flecha va como fondo y no como pseudo-elemento: un `::before` sobre un
     <select> no se pinta, y uno sobre el contenedor pisa el texto de la opción
     cuando es larga. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) center, calc(100% - 13px) center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.field--textarea .field__control {
  min-height: 7rem;
  resize: vertical;
}

.field__help {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--c-text-muted);
  overflow-wrap: anywhere;
}

/*
 * Error del campo. Vacío no ocupa lugar: así el formulario no reserva una
 * franja por campo que nadie va a leer, y al aparecer el error empuja lo justo.
 * No se usa el atributo `hidden` a propósito — una regla de componente con la
 * misma especificidad lo pisa sin que se note.
 */
.field__error:empty { display: none; }

.field__error {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--c-marketing-soft);
  overflow-wrap: anywhere;
}

/* El error se distingue por el texto Y por el canto, nunca solo por color. */
.field__control[aria-invalid="true"] {
  border-color: var(--c-marketing-soft);
  background: color-mix(in srgb, var(--c-marketing) 10%, var(--c-bg-elev));
}

/* Resumen de errores, arriba del formulario. Lleva `role="alert"`: quien no ve
   la pantalla se entera de que algo falló sin tener que recorrer siete campos. */
.contact-form__summary:empty { display: none; }

.contact-form__summary {
  display: grid;
  gap: var(--sp-2xs);
  padding: var(--sp-xs) var(--sp-s);
  border: 1px solid color-mix(in srgb, var(--c-marketing-soft) 55%, var(--c-border));
  border-radius: var(--radius-m);
  background: color-mix(in srgb, var(--c-marketing) 12%, var(--c-surface));
  color: var(--c-text);
  font-size: var(--fs-small);
}

.contact-form__summary p { margin: 0; font-weight: var(--fw-medium); }

.contact-form__summary ul {
  margin: 0;
  padding-left: var(--sp-s);
  display: grid;
  gap: var(--sp-3xs);
  font-size: var(--fs-micro);
}

.contact-form__summary a {
  color: var(--c-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  overflow-wrap: anywhere;
}

/*
 * Honeypot: fuera de pantalla, pero NO con `display: none`.
 *
 * Un robot que lee el marcado salta lo que está en `display: none` — que es
 * justamente lo que no queremos: queremos que lo llene. Se saca de la vista con
 * la técnica de 1×1 recortado, y del recorrido del teclado con `tabindex="-1"`
 * en el propio campo.
 */
.contact-form__hp {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.contact-form__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-s);
}

/*
 * El rótulo del botón lo escribe el cliente en HubSpot (`submitText`) y no
 * tiene tope de caracteres. `.btn` lleva `white-space: nowrap` porque dentro de
 * un flex un rótulo de dos palabras se parte feo; acá manda lo contrario: un
 * botón de dos líneas es más feo que uno de dos palabras, pero un botón que no
 * puede achicarse empuja la columna y se lleva puesta la maqueta.
 */
.contact-form__submit {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
}

.contact-form__submit:disabled {
  opacity: 0.6;
  cursor: progress;
}

/* Mientras viaja el envío, el formulario se apaga y el aviso de al lado habla. */
.cta-form__panel.is-sending .contact-form {
  opacity: 0.6;
  pointer-events: none;
}

/*
 * Contacto directo, debajo del formulario y SIEMPRE visible.
 *
 * Es el último escalón: el que sigue estando si falla el formulario, si falla
 * HubSpot y si falla el servidor. Un escalón que se esconde cuando todo va bien
 * no está cuando hace falta — por eso ya no es el respaldo que desaparecía al
 * montar el embebido, sino una línea secundaria permanente.
 */
.cta-form__direct {
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--c-text-muted);
  overflow-wrap: anywhere;
}

.cta-form__direct a { color: var(--c-accent); }

/* Piel del formulario de HubSpot ---------------------------------------- *
 * El embed llega con su propio marcado y sin estilos: sin esto se ve como un
 * formulario de terceros pegado encima del sitio —tipografía del sistema,
 * campos blancos sobre fondo oscuro, botón naranja—. Se le aplican los mismos
 * tokens que a los controles nativos de WordPress.
 */

.cta-form__embed .hs-form,
.cta-form__embed form {
  display: grid;
  gap: var(--sp-s);
  font-family: var(--font-sans);
}

.cta-form__embed .hs-form-field { min-width: 0; }

.cta-form__embed .hs-form-field > label,
.cta-form__embed .hs-form-field > label span {
  display: inline-block;
  margin-bottom: var(--sp-3xs);
  font-size: var(--fs-small);
  font-weight: var(--fw-medium);
  color: var(--c-text);
}

/* El asterisco de campo obligatorio, en el acento: se ve sin gritar. */
.cta-form__embed .hs-form-required { color: var(--c-accent); }

.cta-form__embed .hs-field-desc {
  margin-top: var(--sp-3xs);
  font-size: var(--fs-micro);
  color: var(--c-text-faint);
}

.cta-form__embed .hs-input,
.cta-form__embed input[type="text"],
.cta-form__embed input[type="email"],
.cta-form__embed input[type="tel"],
.cta-form__embed input[type="number"],
.cta-form__embed input[type="url"],
.cta-form__embed select,
.cta-form__embed textarea {
  width: 100%;
  max-width: 100%;
  min-height: var(--control-h);
  padding: var(--sp-2xs) var(--sp-xs);
  border: 1px solid var(--c-border);
  border-radius: var(--radius-s);
  background: var(--c-bg-elev);
  color: var(--c-text);
  font: inherit;
  font-size: var(--fs-small);
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

.cta-form__embed textarea { min-height: 120px; resize: vertical; }

.cta-form__embed .hs-input::placeholder,
.cta-form__embed textarea::placeholder { color: var(--c-text-faint); }

.cta-form__embed .hs-input:hover { border-color: var(--c-text-faint); }

.cta-form__embed .hs-input:focus,
.cta-form__embed .hs-input:focus-visible {
  outline: 2px solid var(--c-text);
  outline-offset: 2px;
  border-color: var(--c-accent);
  background: var(--c-surface);
}

/* Casillas y radios: el rectángulo de 100% de ancho que HubSpot les pone por
   defecto los deja gigantes. */
.cta-form__embed input[type="checkbox"],
.cta-form__embed input[type="radio"] {
  width: 18px;
  height: 18px;
  min-height: 0;
  margin-right: var(--sp-2xs);
  accent-color: var(--c-primary);
}

.cta-form__embed .hs-form-booleancheckbox-display,
.cta-form__embed .hs-form-checkbox-display,
.cta-form__embed .hs-form-radio-display {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3xs);
  font-size: var(--fs-small);
  color: var(--c-text-muted);
}

.cta-form__embed ul.inputs-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Error de validación: borde rojo Y texto, nunca solo color. */
.cta-form__embed .hs-input.error,
.cta-form__embed .hs-input.invalid {
  border-color: var(--c-marketing-soft);
  background: color-mix(in srgb, var(--c-marketing) 10%, var(--c-bg-elev));
}

.cta-form__embed .hs-error-msgs,
.cta-form__embed .hs-error-msg {
  margin: var(--sp-3xs) 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-micro);
  color: var(--c-marketing-soft);
}

.cta-form__embed .hs-richtext,
.cta-form__embed .legal-consent-container {
  font-size: var(--fs-micro);
  color: var(--c-text-muted);
}

.cta-form__embed .hs-richtext a { color: var(--c-accent); }

.cta-form__embed .hs-button,
.cta-form__embed input[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  min-height: var(--control-h);
  padding: var(--sp-2xs) var(--control-pad-x);
  border: 1px solid transparent;
  border-radius: var(--radius-s);
  background: var(--c-primary);
  color: var(--c-on-primary);
  font: inherit;
  font-size: var(--fs-small);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  cursor: pointer;

  /* La transición suma `border-color` porque este botón declara la suya y no
     hereda la de `.btn`: sin eso el filo del hover aparecería de golpe. */
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}

/* Mismo trato que el resto de los botones llenos del sistema: el filo se aclara
   y el relleno casi no. Ver `--c-primary-hover` en tokens.css. */
.cta-form__embed .hs-button:hover,
.cta-form__embed input[type="submit"]:hover {
  background: var(--c-primary-hover);
  border-color: var(--c-primary-filo-hover);
}

.cta-form__embed .hs-button:active,
.cta-form__embed input[type="submit"]:active { transform: translateY(1px); }

/* Mientras se envía, el formulario se apaga y deja de aceptar clics: el aviso
   de al lado es el que habla. */
.cta-form__panel.is-sending .cta-form__embed {
  opacity: 0.6;
  pointer-events: none;
}

/* Mensaje de agradecimiento que devuelve HubSpot tras enviar. */
.cta-form__embed .submitted-message {
  padding: var(--sp-m);
  border: 1px solid color-mix(in srgb, var(--c-capacitacion) 45%, var(--c-border));
  border-radius: var(--radius-m);
  background: color-mix(in srgb, var(--c-capacitacion) 10%, var(--c-surface));
  color: var(--c-text);
}

/* ═══════════════════════════════════════════════════════════════════════ *
 * Plantilla de vertical — soluciones, resultados y caso destacado
 *
 * Las tres secciones que el Figma coloca entre los desafíos y el testimonio.
 * Todas heredan `--c-vertical` del <body> de la página, así que ninguna
 * escribe un color: cambiar de vertical sigue siendo cambiar dos variables.
 * ═══════════════════════════════════════════════════════════════════════ */

/* Soluciones ----------------------------------------------------------- *
 * Cuatro bloques apilados a ancho completo, con la línea de color arriba y su
 * propio enlace de acción. Apilados y no en rejilla: cada solución tiene un
 * párrafo entero y, en dos columnas, el texto queda demasiado angosto para
 * leerlo de un tirón.
 */

.solution-list {
  display: grid;
  gap: var(--sp-m);
}

.solution {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2xs);
  min-width: 0;
  padding: var(--sp-m);
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-top: 2px solid var(--c-vertical);
  border-radius: var(--radius-m);
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

@media (min-width: 900px) {
  .solution { padding: var(--sp-l); }
}

.solution:hover,
.solution:focus-within {
  border-color: color-mix(in srgb, var(--c-vertical) 45%, var(--c-border));
  background: var(--c-bg-elev);
}

.solution__title {
  font-size: var(--fs-h4);
  overflow-wrap: anywhere;
}

.solution__text {
  color: var(--c-text-muted);
  font-size: var(--fs-body);
  max-width: 78ch;
  overflow-wrap: anywhere;
}

/* Aire propio antes del enlace: pegado al párrafo se lee como una línea más
   del texto y deja de parecer una acción. */
.solution__link { margin-top: var(--sp-2xs); }

/* Resultados de una vertical ------------------------------------------- *
 * Rejilla dentro de una sola caja, separada por líneas —el mismo recurso que
 * la tarjeta de números del inicio, y desde el 21/08 literalmente la misma
 * regla: las dos montan sobre `.linegrid`—. Cada celda cita su fuente: es lo
 * que separa un dato medido de una promesa de agencia.
 *
 * Se llama `metric` y no `result` porque `result` ya era el resultado de
 * BÚSQUEDA: mismo nombre, dos componentes, y ganaba el que estuviera más
 * abajo en la hoja.
 */

.metric-grid {
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-m);
}

.metric {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2xs);
  padding: var(--sp-m);
}

@media (min-width: 640px) {
  .metric-grid { --linegrid-cols: 2; }

  /* Con tres celdas, la impar que queda sola ocupa la fila entera en vez de
     dejar media caja vacía. */
  .metric:nth-child(odd):last-child { grid-column: 1 / -1; }
}

.metric__text {
  color: var(--c-text);
  font-size: var(--fs-body);
  overflow-wrap: anywhere;
}

/* La fuente, en monoespaciada y al pie: presente pero sin competir con el
   dato. `margin-top: auto` la ancla al fondo para que las celdas terminen
   alineadas aunque sus datos midan distinto. */
.metric__source {
  margin-top: auto;
  padding-top: var(--sp-2xs);
}

/* Caso destacado -------------------------------------------------------- *
 * Bloque horizontal: foto a la izquierda, marca y descripción a la derecha.
 * En móvil se apila, con la foto siempre primero.
 */

.featured-case {
  display: grid;
  gap: var(--sp-m);
  padding: var(--sp-m);
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-l);
}

@media (min-width: 900px) {
  .featured-case {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--sp-xl);
    padding: var(--sp-l);
  }
}

/* `aspect-ratio` sin `min-height`: con los dos juntos el mínimo intrínseco
   pasa a ser alto × ratio y sería la foto la que dictase el ancho de su
   columna. Acá manda la rejilla. */
.featured-case__media {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--radius-m);
  background:
    radial-gradient(70% 70% at 50% 40%, color-mix(in srgb, var(--c-vertical) 14%, transparent), transparent 72%),
    var(--c-surface-2);
}

.featured-case__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.featured-case__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-s);
  min-width: 0;
}

.featured-case__title {
  font-size: var(--fs-h3);
  letter-spacing: var(--tracking-tight);
  overflow-wrap: anywhere;
}

.featured-case__text {
  color: var(--c-text-muted);
  overflow-wrap: anywhere;
}


/* Bloque anclado de un caso -------------------------------------------- *
 *
 * Desde el 24/08 `/casos-de-exito/` dejó de ser una grilla de tarjetas: es la
 * lista de los seis casos COMPLETOS, cada uno con su `id`, y es el destino de
 * las tarjetas del inicio y de las direcciones viejas `/casos/{slug}/`
 * (arquitectura-casos.md §2.1). La ficha individual ya no existe.
 *
 * Mismo objeto visual que `.featured-case` —foto a un lado, marca y texto al
 * otro— y a propósito: es el mismo contenido en dos pantallas y tiene que
 * leerse como la misma pieza. Lo que suma este bloque es lo que la ficha
 * aportaba y la tarjeta no: el cuerpo entero (380-560 caracteres), la métrica
 * y un CTA.
 *
 * Lo que se alterna es la POSICIÓN, no el marcado: en el DOM la media va
 * siempre primero, y en los pares se la manda a la segunda columna con
 * `grid-column`. Así el orden de lectura y el de tabulación no dependen de la
 * ventana, y en móvil —donde no hay dos columnas— los seis bloques empiezan
 * igual.
 */

.case-list {
  display: grid;
  gap: var(--sp-xl);
  margin-top: var(--sp-l);
}

.case-detail {
  /* Respaldo del acento: un caso sin vertical no hereda `--c-vertical` de
     ningún lado y su borde de métrica quedaría sin color. */
  --c-vertical: var(--c-primary);
  --c-vertical-soft: var(--c-accent);
  display: grid;
  gap: var(--sp-m);
  padding: var(--sp-m);
  background: var(--c-surface);
  border: 1px solid var(--c-border-soft);
  border-radius: var(--radius-l);

  /* Sin `overflow` acá: el recorte vive en la media y sólo en la media. Un
     `overflow:hidden` en la caja que contiene el texto y el CTA es lo que en
     el hito 1 se comió el enlace de las tarjetas de vertical. */
}

@media (min-width: 900px) {
  .case-detail {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: center;
    gap: var(--sp-xl);
    padding: var(--sp-l);
  }

  /* Alternancia. `nth-child` y no una clase desde PHP: el orden lo decide la
     posición en la lista, y si el cliente reordena los casos desde el panel el
     zigzag se recompone solo. */
  .case-detail:nth-child(even) .case-detail__media {
    grid-column: 2;
    grid-row: 1;
  }

  .case-detail:nth-child(even) .case-detail__body {
    grid-column: 1;
    grid-row: 1;
  }
}

/*
 * `aspect-ratio` sin `min-height`: con los dos juntos el mínimo intrínseco
 * pasa a ser alto × ratio y sería la foto la que dictase el ancho de su
 * columna. Acá manda la rejilla y la proporción sólo sostiene el alto.
 *
 * La proporción es la misma con foto y con marcador, así que el bloque de
 * Merck —hoy el único sin imagen destacada— mide igual que sus cinco hermanos.
 */
.case-detail__media {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--radius-m);
  background:
    radial-gradient(70% 70% at 50% 40%, color-mix(in srgb, var(--c-vertical) 14%, transparent), transparent 72%),
    var(--c-surface-2);
}

/*
 * ENCUADRE — dónde se recorta la foto, para cuando lleguen las reales.
 *
 * Aquí NO se recorta nada, y está medido: la caja da exactamente 1,500 de
 * proporción a los 7 anchos probados (238×159 a 320, 308×205 a 390, 671×448 a
 * 768, 506×337 a 1440, 503×335 a 2560) y el archivo que sirve `destiny-card`
 * es 720×480, o sea 1,5 también. `object-fit: cover` con `object-position`
 * centrado no llega a cortar un píxel.
 *
 * El recorte ocurre ANTES, en WordPress: `destiny-card` está declarado con
 * recorte duro (`add_image_size('destiny-card', 720, 480, true)`) y su corte es
 * al CENTRO. Una foto vertical o cuadrada pierde arriba y abajo, y con ella
 * puede irse el sujeto. Eso no se arregla desde el CSS —bajar el `cover` a
 * `contain` dejaría bandas y rompería la uniformidad de los seis hermanos—:
 * se arregla pidiendo las fotos apaisadas y con aire, que es la pregunta 9 a
 * Juan en arquitectura-casos.md §7.
 *
 * Si algún día hace falta salvar una foto concreta, el punto de entrada es
 * `object-position` sobre este mismo selector, alimentado por un campo del
 * caso. Hoy no existe ese campo y no se inventa uno por si acaso.
 */
.case-detail__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.case-detail__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-s);

  /* Hijo de grid: sin esto, una palabra kilométrica del título o del cuerpo
     empuja la columna y desarma el reparto 1fr/1fr. */
  min-width: 0;
}

.case-detail__title {
  font-size: var(--fs-h3);
  letter-spacing: var(--tracking-tight);

  /* `anywhere` y no `break-word`: en un hijo de grid `break-word` no reduce el
     tamaño mínimo intrínseco, así que la palabra larga sigue empujando la
     caja. El título lo escribe el cliente y no tiene tope de caracteres. */
  overflow-wrap: anywhere;
}

.case-detail__text { color: var(--c-text-muted); }

/*
 * La métrica, con la misma barra de acento que llevaba la ficha que este
 * bloque reemplaza. Es el dato del caso, no el titular: por eso va DESPUÉS del
 * texto y en `--fs-h2`, no en la escala `--fs-metric-*` de la franja de
 * números, donde una cifra es lo único que hay en su celda.
 */
.case-detail__metric {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3xs);
  margin: 0;
  padding-left: var(--sp-m);
  border-left: 3px solid var(--c-vertical);
  min-width: 0;
}

.case-detail__metric-value {
  font-size: var(--fs-h2);
  font-weight: var(--fw-bold);
  line-height: 1;
  letter-spacing: var(--tracking-tighter);
  color: var(--c-vertical-soft);
}

.case-detail__metric-label {
  font-size: var(--fs-small);
  color: var(--c-text-muted);

  /* Los rótulos reales llegan a 57 caracteres («más tiempo de interacción con
     RA vs. medios convencionales»): se los deja envolver, acotados a la medida
     de lectura corta para que no se estiren a lo ancho de la columna. */
  max-width: 34ch;
  overflow-wrap: anywhere;
}

/* El CTA respira un escalón más que el resto de la columna: es el final del
   bloque, no una línea más del cuerpo. */
.case-detail__cta { margin-top: var(--sp-2xs); }

/* ═══════════════════════════════════════════════════════════════════════ *
 * CONTENEDORES — cuándo un tamaño sale de la VENTANA y cuándo de la CAJA
 * ═══════════════════════════════════════════════════════════════════════ *
 *
 * Regla del proyecto: ningún tamaño sale de una constante medida a ojo ni de
 * `vw` cuando lo que manda es el ancho del contenedor. Este bloque documenta la
 * decisión que se tomó para CADA `vw` del tema, para que el próximo que agregue
 * un componente no tenga que adivinarla.
 *
 * El criterio, en una línea: si la caja que se está midiendo crece con la
 * ventana hasta el infinito, `vw` está bien; si topa en algún ancho (porque
 * vive dentro de `--container`, de una columna o de una tarjeta), `vw` miente a
 * partir de ese tope y va `cqw`.
 *
 * ── La manda la VENTANA (se queda en `vw`) ─────────────────────────────
 *
 *   escala tipográfica (tokens.css)    el cuerpo de un texto se elige contra la
 *                                      pantalla que lo lee, no contra la caja
 *                                      donde cae — si no, el mismo párrafo
 *                                      tendría dos tamaños según el sitio
 *   --section-y, --gutter              ritmo y márgenes de página
 *   .hero (relleno) y .hero__actions   banda a sangre: su caja ES la ventana
 *   .client-strip / .marquee           ídem, van de borde a borde
 *   .process__marker                   ocupa el sitio de un número: pertenece a
 *                                      la escala tipográfica, no al paso
 *
 * ── La manda el CONTENEDOR (`container-type` + `cqw` / `@container`) ────
 *
 *   .cta-block__panel  (cta-form)      el emparejado de campos del formulario
 *                                      propio: a 900 px de ventana la columna
 *                                      mide 346, y una media query habría
 *                                      puesto dos columnas de 165
 *   .grid:has(>.vertical-card)         el reparto en dos columnas de la tarjeta
 *                      (vcards)        de vertical — el contenedor es la rejilla
 *                                      porque un elemento no puede consultarse
 *                                      a sí mismo
 *   .vertical-card     (vcard)         el relleno
 *                                      de su cuerpo. La tarjeta topa en 1200 px
 *                                      y la ventana no topa nunca: medido, a
 *                                      2560 px el cuerpo seguía en 604 px y el
 *                                      relleno ya iba por 64 px por lado
 *
 * ── Cómo se elige el número al convertir ───────────────────────────────
 *
 * El factor en `cqw` NO se elige: se despeja del valor que el `vw` daba a
 * 1440 px, que es el ancho al que el cliente aprobó el diseño.
 *
 *     factor_cqw = factor_vw × 1440 / (ancho del contenedor a 1440)
 *
 * Así la maqueta aprobada no se mueve, y lo único que cambia es el
 * comportamiento fuera de ese ancho — que es justo lo que se venía arreglando.
 * Lo mismo con los umbrales de `@container`: 848 px es lo que mide la tarjeta
 * cuando la ventana está en los 900 px donde el diseño corta.
 *
 * ── La trampa que hay que verificar cada vez ───────────────────────────
 *
 * `container-type: inline-size` aplica `contain: layout style inline-size`. Eso
 * crea un bloque contenedor nuevo para los descendientes absolutos y ROMPERÍA
 * un `position: sticky` de dentro. Ninguno de los dos contenedores del tema
 * tiene sticky adentro, y los dos ya eran `position: relative`, así que no
 * cambia nada — pero es lo primero que hay que mirar antes de declarar el
 * tercero. */
