/**
 * base.css — reset ligero, retícula, tipografía y componentes transversales
 * (botones, enlaces con flecha, marquesina). Solo consume tokens.
 */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
	margin: 0;
	font-family: var(--cdx-fuente);
	font-size: var(--cdx-fs-body);
	line-height: var(--cdx-lh-body);
	font-weight: var(--cdx-peso-normal);
	color: var(--cdx-color-texto);
	background: var(--cdx-color-fondo);
	-webkit-font-smoothing: antialiased;
	overflow-x: hidden;
}

/* Selección de texto en el NEGRO de la marca, no en el azul del navegador.
   Va en TODA la web, no solo en el blog.
   ⚠️ Las dos reglas van SEPARADAS y no en una lista: `::selection` y
   `::-moz-selection` son pseudoelementos distintos y un navegador que no
   entienda uno **descarta el selector entero**, así que agruparlos dejaría a
   Firefox sin regla. */
::selection { background: var(--cdx-negro); color: var(--cdx-blanco); }
::-moz-selection { background: var(--cdx-negro); color: var(--cdx-blanco); }

/* Recorte horizontal del contenido.
   Las animaciones de entrada desplazan las piezas 50 px a un lado
   (`data-cdx-dir="left|right"`), así que mientras no entran en pantalla se
   salen del contenedor y abren barra horizontal: se medían 38 px de arrastre
   en la ficha de servicio, y los bloques alternos lo tenían desde siempre.
   El `overflow-x: hidden` del `body` no bastaba.

   ⚠️ Va `clip` y NO `hidden`: `hidden` crea un contenedor de desplazamiento y
   eso DESACTIVA el `position: sticky` de la fila anclada de «qué más podemos
   hacer». `clip` recorta sin crear contenedor, así que el anclaje sigue vivo. */
main { overflow-x: clip; }

img, svg, video { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

/* ---------- Retícula ---------- */
.cdx-contenedor {
	width: 100%;
	max-width: var(--cdx-ancho-max);
	margin-inline: auto;
	padding-inline: var(--cdx-margen-lateral);
}

/* Ritmo del diseño: **130px de hueco BLANCO entre bloques**, no 260. Por eso el
   espacio lo pone cada bloque SOLO por arriba; si además lo pusiera por abajo,
   cada salto sumaría los dos rellenos.

   La regla exacta de la referencia (verificada bloque a bloque el 2026-08-24)
   tiene TRES excepciones que sí llevan relleno inferior:
     1. el bloque con FONDO propio        → ahí no es hueco entre secciones,
                                            es aire DENTRO de la banda de color;
     2. el bloque ANTERIOR a uno con fondo → hace falta blanco real antes de que
                                            arranque la banda. Este faltaba, y
                                            es lo que dejaba los contadores
                                            pegados al portfolio oscuro;
     3. el ÚLTIMO de la página            → contra el pie.

   Los casos 2 y 3 van por SELECTOR y no bloque a bloque, a propósito: así una
   página nueva hereda el ritmo sin que nadie tenga que acordarse. */
.cdx-bloque {
	padding-top: var(--cdx-sec-top);
	padding-bottom: 0;
}
/* ⚠️ **`.cdx-portfolio--claro` queda FUERA de las dos listas, y es el arreglo
   de un hueco que se veía en móvil.** Esta regla existe para separar del pie de
   un bloque que lleva **banda de color** (§34.1), y la variante clara de la
   retícula no tiene banda: va sobre blanco. Con ella dentro, el hero de
   `/casos-de-exito/` recibía `padding-bottom` (65 en móvil) por el
   `:has(+ .cdx-portfolio)`, se sumaba al `padding-top` de la retícula y quedaban
   **130px** entre el h1 y el primer caso, el doble que en `/mejores/`, cuyo
   listado no es `.cdx-portfolio` y por eso nunca lo tuvo. */
.cdx-bloque.tiene-fondo,
.cdx-portfolio:not(.cdx-portfolio--claro),
.cdx-servicios-home,
.cdx-bloque:has(+ .tiene-fondo),
.cdx-bloque:has(+ .cdx-portfolio:not(.cdx-portfolio--claro)),
.cdx-bloque:has(+ .cdx-servicios-home) {
	padding-bottom: var(--cdx-sec-bottom);
}

/* ⚠️ CASO 3, EL AIRE CONTRA EL PIE — REGLA ÚNICA, NO LA REPITAS POR PÁGINA.
   Esto ha reaparecido varias veces (la última, en la página de contacto) y
   siempre por lo mismo: no había regla general, así que cada plantilla nueva
   nacía pegada al pie hasta que alguien se daba cuenta y le ponía SU propio
   `padding-bottom`. Llegó a haber NUEVE reglas sueltas haciendo lo mismo.

   Desde aquí, **el último bloque de cualquier página lleva siempre el mismo
   aire**, sin tocar nada al crear una plantilla.

   Dos selectores porque hay dos formas de colgar los bloques:
     · sueltos bajo `<main>` — el caso normal;
     · dentro de UN envoltorio — así es la portada, cuyo único hijo de `<main>`
       es `.cdx-home`. Sin el segundo selector la portada se quedaría fuera.

   💡 Si un bloque necesita OTRO aire, se sobreescribe en su propia regla; el
   sitio donde se decide sigue siendo uno solo. */
main > .cdx-bloque:last-child,
main > :last-child > .cdx-bloque:last-child {
	padding-bottom: var(--cdx-sec-bottom);
}

/* ---------- Titulares ---------- */
h1, h2, h3, h4 {
	margin: 0;
	font-weight: var(--cdx-peso-fuerte);
	color: var(--cdx-negro-puro);
	text-wrap: balance;
	/* ⚠⚠ **Una palabra sola más ancha que su caja NO da barra horizontal en
	   este tema: se RECORTA EN SILENCIO**, porque `main` va con
	   `overflow-x: clip` y `body` con `overflow: hidden`. Se cazó con el h1 de
	   `/caso-exito/operarme-es/`, que es literalmente «Operarme.es»: a 375 se
	   pasaba 10px de su caja y a 320 treinta, y el final del título se comía sin
	   que ningún desbordamiento lo delatara (§53.1).
	   `break-word` solo actúa cuando la palabra NO cabe por sí sola en la línea,
	   así que no toca el reparto de ningún titular normal. Va en los cuatro
	   niveles: cualquiera puede recibir un dominio, un slug o un nombre largo. */
	overflow-wrap: break-word;
}
h1 { font-size: var(--cdx-fs-h1); line-height: var(--cdx-lh-titulo); letter-spacing: var(--cdx-ls-h1); }
h2 { font-size: var(--cdx-fs-h2); line-height: var(--cdx-lh-titulo-2); letter-spacing: var(--cdx-ls-h2); }
h3 { font-size: var(--cdx-fs-h3); line-height: var(--cdx-lh-tarjeta); letter-spacing: var(--cdx-ls-tarjeta); }
h4 { font-size: var(--cdx-fs-h4); line-height: 1.3; letter-spacing: -.01em; }

p { margin: 0 0 1.6rem; }
p:last-child { margin-bottom: 0; }

/* Texto largo (wysiwyg migrado) */
.cdx-prosa { max-width: 75ch; }
/* ⚠️ AL BAJAR EL CUERPO HAY QUE BAJAR TAMBIÉN EL `letter-spacing`. La regla
   global de `h2` lleva `--cdx-ls-h2`, que son **−2px FIJOS** calibrados para los
   80px del titular grande (−0,025em). Aquí el cuerpo baja a tamaño de h3 pero el
   tracking se quedaba en −2px, o sea **−0,071em**; y con las clases de tamaño de
   Gutenberg que traía el contenido migrado (`has-small-font-size`, 13px) llegaba
   a **−0,154em**: las letras se montan y el texto NO SE LEE.
   Se le pone el mismo token que ya usa `h3`, que está medido justo para
   titulares de este cuerpo (§36.6: rótulo de 28 con −0,28). No se toca el
   `h3` de prosa: ese ya lo heredaba bien. */
.cdx-prosa h2 { font-size: var(--cdx-fs-h3); margin: 2.5rem 0 1rem; letter-spacing: var(--cdx-ls-tarjeta); }
.cdx-prosa h3 { font-size: var(--cdx-fs-h4); margin: 2rem 0 .75rem; }
/* Letra legal dentro de la prosa: descargos y avisos de letra pequeña. La usa
   el descargo de afiliación que venía al final del texto SEO de la portada
   marcado como `<h6>` — un descargo no es un encabezado, y además saltaba de
   h2 a h6 y rompía la jerarquía que dejó limpia §65.3. */
.cdx-prosa .cdx-letra-legal {
	font-size: var(--cdx-fs-legal);
	line-height: 1.5;
	color: var(--cdx-gris-texto);
}
.cdx-prosa ul, .cdx-prosa ol { margin: 0 0 1.6rem; padding-left: 1.25rem; }
.cdx-prosa li { margin-bottom: .5rem; }
.cdx-prosa a { text-decoration: underline; text-underline-offset: .2em; }

/* ⚠️ El contenido migrado trae `<mark>` de Gutenberg dentro de algún enlace, y
   el AMARILLO FOSFORITO que se veía no lo pone el contenido: es el estilo por
   defecto del navegador para `mark`. Aquí `mark` no resalta con fondo —el
   diseño no tiene ese gesto—, así que se devuelve al color del texto y el
   enlace se queda con lo suyo: negro y subrayado.
   Se cubre también cualquier fondo que venga en un enlace del wysiwyg migrado,
   por la misma razón. */
.cdx-prosa mark {
	background: none;
	/* ⚠️ `!important` a propósito, y es la única vez en todo el tema. La clase
	   `has-vivid-cyan-blue-color` que trae el contenido migrado viene de la hoja
	   de Gutenberg y declara el color CON `!important`, así que por
	   especificidad no hay forma de ganarle: el enlace se quedaba en cian
	   chillón sobre blanco. Aquí un enlace es negro y subrayado, sin más. */
	color: inherit !important;
}
.cdx-prosa a,
.cdx-prosa a * { background-color: transparent; }

.cdx-texto-apagado { color: var(--cdx-gris-texto); }

/* ---------- Botón píldora ---------- */
.cdx-boton {
	display: inline-flex;
	align-items: center;
	gap: .625rem;
	padding: var(--cdx-btn-padding);
	background: var(--cdx-negro);
	color: var(--cdx-blanco);
	border: 0;
	border-radius: var(--cdx-btn-radio);
	font-size: var(--cdx-btn-fs);
	font-weight: var(--cdx-peso-fuerte);
	line-height: 1.55;
	cursor: pointer;
	transition: background var(--cdx-transicion), color var(--cdx-transicion), transform var(--cdx-transicion);
}
.cdx-boton:hover, .cdx-boton:focus-visible { background: var(--cdx-negro); color: var(--cdx-blanco); }
/* El diseño encoge la píldora ya en 991, no en 575. */
@media (max-width: 991px) { .cdx-boton { padding: 10px 20px; } }
/* En móvil el CTA de la cabecera va otro punto más pequeño: comparte la barra
   con el logo y el botón de menú, y a 18px se comía el sitio. Solo ESE botón
   (`.cdx-cabecera__cta`), no los del cuerpo de la página. */
@media (max-width: 767px) {
	.cdx-cabecera__cta { padding: 8px 16px; font-size: 15px; gap: .5rem; }
}

/* Variante clara (sobre fondo oscuro) */
.cdx-boton--claro { background: var(--cdx-blanco); color: var(--cdx-negro); }
.cdx-boton--claro:hover, .cdx-boton--claro:focus-visible { background: var(--cdx-gris-fondo); color: var(--cdx-negro); }

/* Variante de CONTORNO: transparente con borde. Medida en la tarjeta de precio
   del diseño de referencia, que es donde la usa: mismo relleno y mismo radio
   que el botón normal, pero borde de 1 y sin fondo macizo.
   ⚠️ El borde SÍ ocupa caja aquí, así que se le resta al relleno vertical para
   que los dos botones midan lo mismo: 14−1 = 13. */
.cdx-boton--contorno {
	padding: 13px 19px 13px 23px;
	background: transparent;
	color: var(--cdx-negro);
	border: 1px solid var(--cdx-negro);
}
.cdx-boton--contorno:hover,
.cdx-boton--contorno:focus-visible { background: var(--cdx-negro); color: var(--cdx-blanco); }

/* ---------- Enlace con flecha (estilo "View details") ----------
   Medidas del diseño: 18px/600, ls -.01em, interlínea 1.44, hueco 8px,
   4px hasta el subrayado de 1px. */
.cdx-enlace {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	font-size: var(--cdx-fs-small);
	font-weight: var(--cdx-peso-fuerte);
	line-height: 1.44;
	letter-spacing: -.01em;
	padding-bottom: 4px;
	border-bottom: 1px solid currentColor;
	width: max-content;
	transition: opacity var(--cdx-transicion);
}
/* Dentro de un texto (`.cdx-prosa`), los enlaces llevan subrayado; el
   `.cdx-enlace` ya tiene su filete inferior, y con los dos salía doble. Es el
   «Ir a la web» de los proyectos (§109). */
.cdx-prosa a.cdx-enlace { text-decoration: none; }
/* En pantallas muy pequeñas el diseño pega el subrayado al texto. */
@media (max-width: 575px) { .cdx-enlace { padding-bottom: 0; } }
/* ---------- Flecha de relevo (mecanismo del diseño) ----------
   Caja enmascarada de 16×14 con DOS flechas: al hacer hover la primera sale
   hacia arriba-derecha y la segunda entra desde abajo-izquierda. */
/* Todas las medidas salen de variables para poder reutilizar EL MISMO mecanismo
   a otro tamaño (la flecha ornamental del hero es este componente a escala 7). */
.cdx-flecha {
	--f-caja-an: 16px;      /* caja enmascarada */
	--f-caja-al: 14px;
	--f-glifo: 11px;        /* la flecha es CUADRADA y ya apunta ↗: no se gira */
	--f-reposo-t: 1px;      /* A en reposo */
	--f-fuera-t: -15px;     /* A al salir, arriba-derecha */
	--f-fuera-i: 15px;
	--f-dentro-b: -15px;    /* B esperando, abajo-izquierda */
	--f-dentro-i: -10px;
	--f-fin-b: 2px;         /* B al ocupar el sitio de A */
	--f-dur: .3s;
	/* ⚠️ INVARIANTE del relevo: la B al llegar tiene que caer EXACTAMENTE donde
	   reposa la A, o al reiniciar sin transición se ve un salto. Es decir:
	       --f-reposo-t = --f-caja-al − --f-glifo − --f-fin-b
	   Aquí: 1 = 14 − 11 − 2. Al reescalar el componente hay que mantenerlo. */

	position: relative;
	overflow: hidden;
	display: inline-block;
	width: var(--f-caja-an);
	height: var(--f-caja-al);
	flex: none;
	color: inherit;
}
.cdx-flecha__a, .cdx-flecha__b {
	position: absolute;
	display: block;
	width: var(--f-glifo);
	height: var(--f-glifo);
	transition: top var(--f-dur) cubic-bezier(.37, .08, .02, .93),
		left var(--f-dur) cubic-bezier(.37, .08, .02, .93),
		bottom var(--f-dur) cubic-bezier(.37, .08, .02, .93);
}
.cdx-flecha__a svg, .cdx-flecha__b svg { width: 100%; height: 100%; display: block; }
.cdx-flecha__a { top: var(--f-reposo-t); left: 0; }
.cdx-flecha__b { bottom: var(--f-dentro-b); left: var(--f-dentro-i); }

/* Relevo: lo disparan el hover de enlaces/botones y, en la flecha ornamental,
   la clase `en-vuelo` que pone el JS cada 8 s.

   Los dos últimos selectores de cada lista son el enganche del TÍTULO de la
   tarjeta de servicio: al ser también enlace, pasar por él dispara la flecha del
   «Saber más» de su misma tarjeta. Va con `:has()` porque título y enlace son
   hermanos dentro de `__datos`, no uno dentro del otro, así que un descendiente
   normal no llega. Y viven AQUÍ, en la misma lista, para que los valores del
   relevo sigan definidos en un solo sitio. */
.cdx-boton:hover .cdx-flecha__a,
.cdx-boton:focus-visible .cdx-flecha__a,
.cdx-enlace:hover .cdx-flecha__a,
.cdx-enlace:focus-visible .cdx-flecha__a,
.cdx-marquesina__enlace:hover .cdx-flecha__a,
.cdx-tarjeta-servicio__datos:has(.cdx-enlace-titulo:hover) .cdx-flecha__a,
.cdx-tarjeta-servicio__datos:has(.cdx-enlace-titulo:focus-visible) .cdx-flecha__a,
.cdx-tarjeta-servicio:has(.cdx-tarjeta-servicio__icono-enlace:hover) .cdx-flecha__a,
a.cdx-proceso__caja:hover .cdx-flecha__a,
a.cdx-proceso__caja:focus-visible .cdx-flecha__a,
.cdx-flecha.en-vuelo .cdx-flecha__a { top: var(--f-fuera-t); left: var(--f-fuera-i); }

.cdx-boton:hover .cdx-flecha__b,
.cdx-boton:focus-visible .cdx-flecha__b,
.cdx-enlace:hover .cdx-flecha__b,
.cdx-enlace:focus-visible .cdx-flecha__b,
.cdx-marquesina__enlace:hover .cdx-flecha__b,
.cdx-tarjeta-servicio__datos:has(.cdx-enlace-titulo:hover) .cdx-flecha__b,
.cdx-tarjeta-servicio__datos:has(.cdx-enlace-titulo:focus-visible) .cdx-flecha__b,
.cdx-tarjeta-servicio:has(.cdx-tarjeta-servicio__icono-enlace:hover) .cdx-flecha__b,
a.cdx-proceso__caja:hover .cdx-flecha__b,
a.cdx-proceso__caja:focus-visible .cdx-flecha__b,
.cdx-flecha.en-vuelo .cdx-flecha__b { bottom: var(--f-fin-b); left: 0; }

/* ---------- Título que es enlace ---------- */
/* ⚠️ **SIN SUBRAYADO, y es a propósito.** Hubo uno —un degradado de
   `currentColor` que crecía de izquierda a derecha— y se retiró el 2026-09-03 a
   petición del usuario, por el mismo criterio que en el listado de proyectos
   (§51.3): **el hover de la tarjeta ya lo señala el relevo de la flecha del
   «Saber más»**, y dos gestos a la vez para el mismo destino sobran.
   La clase SE QUEDA y no está vacía: es la que apaga el subrayado del navegador,
   hereda el color, y sobre todo es **el enganche del relevo de la flecha** —los
   selectores `:has(.cdx-enlace-titulo:hover)` de la lista de arriba— y el de la
   diana táctil de 44px (bloques.css, ≤1024). Quitarla dejaría el título sin
   ninguna de las tres cosas. */
.cdx-enlace-titulo {
	color: inherit;
	text-decoration: none;
	display: inline;
}

/* Al cerrar el ciclo se vuelve al estado inicial SIN transición: la flecha B en
   su sitio y la A en reposo son idénticas, así que el salto no se ve. */
.cdx-flecha.sin-transicion .cdx-flecha__a,
.cdx-flecha.sin-transicion .cdx-flecha__b { transition: none; }

@media (prefers-reduced-motion: reduce) {
	.cdx-flecha__a, .cdx-flecha__b { transition: none; }
}

/* ---------- Cabecera de sección (título + enlace a la derecha) ---------- */
.cdx-seccion-cabecera {
	display: flex;
	align-items: flex-end;
	justify-content: space-between;
	gap: var(--cdx-gap);
	margin-bottom: var(--cdx-espacio-bloque);
	flex-wrap: wrap;
}

/* ---------- Foco visible ---------- */
:focus-visible {
	outline: 2px solid var(--cdx-negro);
	outline-offset: 3px;
	border-radius: 2px;
}

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

/* ---------- Animación de entrada (fade-up al hacer scroll) ---------- */
.cdx-anim {
	opacity: 0;
	transform: translateY(40px);
	transition: opacity var(--cdx-transicion-lenta), transform var(--cdx-transicion-lenta);
	/* Sin `will-change` permanente (§93): ver `.cdx-fade`. */
}
.cdx-anim.es-visible { opacity: 1; transform: none; }

/* ============================ Animaciones del diseño ======================= *
 * Réplica de las tres del diseño de referencia, con sus valores literales (ver
 * el bloque «Animación de entrada» de tokens.css). Allí las mueve GSAP; aquí
 * son transiciones CSS puras y el JS solo añade `es-visible` y reparte los
 * retardos, así que seguimos con CERO dependencias.
 *
 * El retardo de cada pieza entra por `--cdx-d` (lo pone el JS desde
 * `data-cdx-delay`), no por CSS: así una fila de tarjetas se escalona sin
 * necesidad de una regla por posición.
 * -------------------------------------------------------------------------- */

/* ---------- 1) `fade-anim`: opacidad + 50px desde una dirección ---------- */
.cdx-fade {
	opacity: 0;
	transition:
		opacity var(--cdx-anim-dur) var(--cdx-anim-ease) var(--cdx-d, var(--cdx-anim-delay)),
		transform var(--cdx-anim-dur) var(--cdx-anim-ease) var(--cdx-d, var(--cdx-anim-delay));
	/* ⚠️ Sin `will-change` (§93). Lo llevaba SIEMPRE, también ya visible, y con
	   eso Chrome deja la pieza (y las fotos de dentro) como capa aparte de la
	   tarjeta gráfica, que reescala con un filtro bilineal simple: las fotos se
	   veían más blandas que abiertas sueltas. Mientras dura la transición Chrome
	   ya la sube a capa por su cuenta, y la baja al acabar. */
}
/* `bottom` es el valor por defecto del diseño, así que también el nuestro. */
.cdx-fade,
.cdx-fade[data-cdx-dir="bottom"] { transform: translateY(var(--cdx-anim-offset)); }
.cdx-fade[data-cdx-dir="top"]    { transform: translateY(calc(-1 * var(--cdx-anim-offset))); }
.cdx-fade[data-cdx-dir="left"]   { transform: translateX(calc(-1 * var(--cdx-anim-offset))); }
.cdx-fade[data-cdx-dir="right"]  { transform: translateX(var(--cdx-anim-offset)); }
.cdx-fade.es-visible { opacity: 1; transform: none; }

/* ---------- 2) Rodillo del contador (su `odometer`) ----------
   El diseño usa la librería Odometer con su tema por defecto. Mecanismo leído
   de su `odometer.css` e instanciando la librería para ver cómo construye el
   rodillo: cada DÍGITO es una ventana recortada del alto de una línea con una
   cinta vertical dentro; el ÚLTIMO valor de la cinta va en `position:absolute`
   (así no suma alto) y la cinta se sube `translateY(-100%)`, que resulta ser
   justo el alto de los valores que quedan. Duración 2s.

   La cinta de cada posición lleva los valores por los que pasa ese dígito al
   contar de 0 a N: para 0→20 son `0,1,2` en las decenas y `0,1..9,0,1..9,0` en
   las unidades (verificado contra la librería, 3 y 21 valores). Por eso las
   unidades giran deprisa y las decenas despacio, que es el efecto de odómetro.
   El ancho lo reserva un dígito oculto, como su `odometer-digit-spacer`. */
.cdx-odo {
	display: inline-block;
	position: relative;
	/* Suyas (`.odometer-auto-theme`): la caja del rodillo va a interlineado 1,
	   aunque la cifra que lo contiene esté a 1.1. */
	line-height: 1;
	vertical-align: unset;
	/* NUESTRO, y es lo que arregla el recorte: en Instrument Sans los dígitos
	   NO son de ancho fijo — medidos a 100px, el «1» ocupa 38.7 y el «0» 67.7,
	   29px de diferencia. El ancho de cada ventana lo reserva un «8» (59.81),
	   así que cualquier «0» se salía casi 8px y `overflow:hidden` lo cortaba
	   por la derecha; y al rodar TODAS las posiciones pasan por el 0. Con
	   cifras tabulares los diez dígitos miden exactamente 60, así que la
	   reserva es exacta, no se corta nada y la cifra tampoco baila al girar.
	   (El diseño no lo necesita porque sus números —20, 220, 46, 98— solo
	   llevan ceros en la última posición, que es la única con holgura.) */
	font-variant-numeric: tabular-nums;
	font-feature-settings: "tnum";
}
/* Su `.odometer-digit`: 1px a cada lado. */
.cdx-odo__digito { display: inline-block; position: relative; padding: 0 1px; }
.cdx-odo__hueco { visibility: hidden; }
.cdx-odo__caja {
	position: absolute;
	inset: 0;
	overflow: hidden;
	text-align: left;
}
.cdx-odo__cinta {
	display: block;
	transition: transform var(--cdx-odo-dur) var(--cdx-odo-ease);
	will-change: transform;
}
.cdx-odo__valor { display: block; }
/* `absolute` SIN `top`/`left`: así conserva su posición estática (la última de
   la cinta) pero deja de sumar alto. Es el truco de su `odometer-last-value`:
   la cinta mide (n−1) líneas, y por eso `translateY(-100%)` cae exactamente
   sobre este valor. Poniéndole `top:0` se superpondría al primero. */
.cdx-odo__valor--ultimo { position: absolute; }
.cdx-odo.es-rodando .cdx-odo__cinta { transform: translateY(-100%); }

/* El signo («+») va FUERA del rodillo, como su `<em>`, que es hermano del
   odómetro dentro del flex de la cifra. Su regla deja 6px entre el número y el
   signo (`padding-right:6px` en el último dígito); como el nuestro va DELANTE,
   los 6px van aquí, al otro lado. */
.cdx-odo-signo { font-style: normal; }
.cdx-odo-signo--prefijo { margin-right: 6px; }
.cdx-odo-signo--sufijo { margin-left: 6px; }

/* ---------- 3) `img_reveal`: barrido lateral ----------
 *
 * ⚠⚠ **UN SOLO MOVIMIENTO, NO DOS EN SENTIDO CONTRARIO.** Hasta el 2026-09-03
 * esto se interpretó como un barrido de máscara: el MARCO entrando desde la
 * izquierda y la IMAGEN desde la derecha para cancelarse. **No es lo que hace la
 * referencia, y además estaba mal**: las dos piezas iban a duraciones distintas
 * (1s el marco, 1,5s la foto), así que no se cancelaban y la foto patinaba de
 * lado mientras se desescalaba desde 1,5. El usuario lo describió como «un
 * efecto muy raro, como circular». Y tenía razón.
 *
 * Esto es lo que hace Quanto, **medido ejecutando su timeline** (§56.1). Su
 * código es este:
 *
 *     tl.from(image, 1,   { xPercent: -100, ease: Power2.out });
 *     tl.from(image, 1.5, { xPercent: 100, scale: 1.5, delay: -1.5, ease: Power2.out });
 *
 * ⚠⚠ **DEDUCIRLO DE AHÍ DA LA DIRECCIÓN AL REVÉS. Hay que ejecutarlo.**
 * Reconstruida su timeline y muestreada con `tl.time()`, los hijos quedan así:
 *
 *     tween B  empieza en 0     dura 1,5   xPercent 100 → -100 · scale 1,5 → 1
 *     tween A  empieza en 0,5   dura 1     xPercent -100 → 0
 *
 * El `delay: -1.5` **no** mete el segundo al principio: como la línea ya medía
 * 1, lo coloca en -0,5 y GSAP **desplaza toda la línea +0,5** para que nada sea
 * negativo. Así que A no es letra muerta: es **el último tramo**, el que dura más
 * y el que fija el estado final. Y B acaba en -100 (no en 0) porque el
 * `immediateRender` de A ya había puesto el elemento en -100 antes de crearse B,
 * así que B capturó ESE valor como destino.
 *
 * Lo que manda, por tanto, es **la foto entrando DESDE LA IZQUIERDA**
 * (-100% → 0) en 1 s. Lo confirma su propia página: las fotos se quedan
 * clavadas en `transform: translate(-100%, 0%)`, que es justo el arranque de una
 * entrada de izquierda a derecha.
 *
 * ⚠ **Lo que NO se replica, a propósito**: el primer medio segundo, o sea el
 * tramo de x del tween B (+100 → -100). Cruza la foto de derecha a izquierda y
 * luego A la devuelve de golpe a -100: un **salto** visible. Es un defecto de su
 * timeline —nadie lo ha visto, porque en su demo el ScrollTrigger no llega a
 * dispararse— y replicarlo sería replicar el fallo.
 *
 * ⚠⚠ **PERO ESO OBLIGA A CORREGIR LA ESCALA, y saltárselo fue el error de la
 * vuelta anterior.** La escala de B empieza en 1,5 **medio segundo antes** de que
 * arranque el barrido; cuando el barrido empieza, ya ha bajado a **1,148148**
 * (= 1 + 4/27). Si se replica el barrido con la escala a 1,5 se estira el 50 %
 * de zoom entero sobre el segundo visible, y se ve fatal. Lo correcto es
 * **1,148148 → 1**, o sea el 14,8 % que le queda.
 * 💡 Y ese tramo de escala resulta ser **el mismo Power2.out y la misma duración**
 * que el desplazamiento: normalizando, `(1,148148 − escala) / 0,148148` da
 * exactamente `1 − (1 − u)³`. Por eso las dos transiciones comparten token.
 *
 * El recorte lo pone el contenedor, que va con `overflow: hidden`.
 *
 * Aquí el marco **no se mueve**: solo existe para que la foto tenga una caja al
 * 100 %, igual que su `.quanto-project-thumb`.
 *
 * El barrido usa `translate` y `scale` SUELTAS, no `transform`: así el zoom del
 * hover de la pieza (que sí va en `transform`) compone con ellas en vez de
 * pisarlas. ⚠ Pero ojo con la lección 71: eso vale para los VALORES, no para la
 * declaración `transition`, que hay que repetir entera donde se redeclare. */
.cdx-revelado { overflow: hidden; }
/* El marco NO se anima: es solo la caja al 100 % dentro del recorte. */
.cdx-revelado__marco {
	display: block;
	width: 100%; height: 100%;
}
/* ⚠ El barrido consume `--cdx-d`, el retardo que el JS reparte desde
   `data-cdx-delay`, para que una parrilla pueda escalonarlo por columna
   (`page-mejores`). Va con respaldo `var(--cdx-d, 0s)`: sin declarar, el
   `var()` tumbaría la declaración de `transition` entera y el barrido se
   quedaría muerto sin dar un solo error (lección 57). */
.cdx-revelado__marco > img {
	/* IZQUIERDA → derecha: su tween A. */
	translate: -100% 0;
	scale: var(--cdx-rev-escala);
	transition:
		translate var(--cdx-rev-dur) var(--cdx-rev-ease) var(--cdx-d, 0s),
		scale var(--cdx-rev-dur) var(--cdx-rev-ease) var(--cdx-d, 0s);
	/* Sin `will-change` (§93): ver `.cdx-fade`. */
}
.cdx-revelado.es-visible .cdx-revelado__marco > img { translate: none; scale: 1; }

/* ---------- Punto que sigue al ratón ----------
   Truco del diseño: el punto es BLANCO con `mix-blend-mode: exclusion`, así se
   ve negro sobre fondo claro y blanco sobre las secciones oscuras, sin cambiar
   de color por JS. El retardo elástico lo da la transición del `transform`,
   que el JS actualiza en cada movimiento. Solo escritorio y solo con ratón. */
.cdx-cursor {
	position: fixed;
	top: 0; left: 0;
	width: 10px; height: 10px;
	border-radius: 50%;
	background: var(--cdx-blanco);
	mix-blend-mode: exclusion;
	pointer-events: none;
	z-index: 99999;
	opacity: 0;
	transform: translate(-50%, -50%);
	transition: transform .3s ease-out, opacity .4s, width .3s, height .3s;
}
.cdx-cursor.es-visible { opacity: 1; }
/* Sobre algo pulsable crece y se difumina. */
.cdx-cursor.es-activo { width: 40px; height: 40px; opacity: .35; }

@media (max-width: 991px), (pointer: coarse), (prefers-reduced-motion: reduce) {
	.cdx-cursor { display: none; }
}

/* ---------- Marquesina ---------- */
/* BUCLE CONTINUO. La plantilla imprime el bloque de copias DOS veces y aquí se
   desplaza exactamente la MITAD de la pista: al cerrar el ciclo, la segunda
   mitad ha ocupado el sitio de la primera, así que se repite sin salto ni hueco.
   (Antes se desplazaba el 100 %: la pista entera se iba de la pantalla y dejaba
   un vacío hasta que la animación reiniciaba.)
   El recorte lo hace la propia caja, así que la altura de línea tiene que dar
   de sí: medido a 140px, la tinta baja 29px de la línea base y con `1.1` la
   caja solo daba 27 → la «¿» se cortaba 2px. Con `1.3` sobran 12.
   (Se deja `overflow: hidden` en los dos ejes: poner uno en `visible` junto a
   otro en `hidden` lo convierte en `auto` y puede sacar barra de scroll.) */
.cdx-marquesina { overflow: hidden; display: flex; user-select: none; }
.cdx-marquesina__pista {
	display: flex;
	flex: none;
	width: max-content;
	animation: cdx-desplazar 40s linear infinite;
}
.cdx-marquesina__item {
	display: inline-flex;
	align-items: center;
	/* En `em` para que la separación acompañe al tamaño del texto: hueco hasta
	   la flecha y, sobre todo, aire hasta la copia siguiente. */
	gap: .22em;
	padding-right: .55em;
	font-size: var(--cdx-fs-display);
	font-weight: var(--cdx-peso-fuerte);
	/* 1.1 dejaba la caja tan justa que la «¿» se salía por abajo. */
	line-height: 1.3;
	letter-spacing: var(--cdx-ls-h1);
	white-space: nowrap;
}
/* La flecha acompaña a un texto enorme, así que sus medidas pasan de px fijos a
   `em`: se mantiene la MISMA proporción que en un enlace normal del diseño
   (caja 16×14 y punta 11 sobre 18px de texto), pero escalada al tamaño real. */
/* La última palabra es un enlace a contacto: hereda el tamaño enorme del texto
   y dispara el mismo relevo de flechas que cualquier otro enlace del sitio. */
.cdx-marquesina__enlace {
	display: inline-flex;
	align-items: center;
	gap: inherit;
	color: inherit;
	transition: opacity var(--cdx-transicion);
}
.cdx-marquesina__enlace:hover { opacity: .6; }

.cdx-marquesina__item .cdx-flecha {
	--f-caja-an: .89em;     /* 16/18 */
	--f-caja-al: .78em;     /* 14/18 */
	--f-glifo: .61em;       /* 11/18 */
	--f-reposo-t: .06em;    /* invariante: .06 = .78 − .61 − .11 */
	--f-fuera-t: -.83em;    /* -15/18 */
	--f-fuera-i: .83em;
	--f-dentro-b: -.83em;
	--f-dentro-i: -.56em;   /* -10/18 */
	--f-fin-b: .11em;       /* 2/18 */
}
/* -50 %, no -100 %: la pista contiene el bloque de copias dos veces. */
@keyframes cdx-desplazar { to { transform: translateX(-50%); } }

/* ---------- Respeto por "reducir movimiento" ---------- */
@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
	.cdx-anim { opacity: 1; transform: none; }
	.cdx-fade { opacity: 1; transform: none; }
	/* El rodillo del contador no se llega a construir con «reducir
	   movimiento» (lo salta el JS), pero por si acaso queda quieto. */
	.cdx-odo__cinta { transform: none; }
	.cdx-revelado__marco > img { translate: none; scale: 1; }
	.cdx-marquesina__pista { animation: none; }
}
