/* ==========================================================================
   Coffee tiger Slider

   Todo está namespaceado bajo .cts-slider: el plugin no toca html, body ni
   nada del tema. El fondo tampoco: eso lo maneja la sección de Elementor.
   Por eso el panel de texto no lleva background propio — el negro del hero
   entra por atrás y la imagen tapa su mitad.

   Los colores y la altura entran como custom properties inline desde PHP
   (--cts-text, --cts-accent, --cts-grid-line, --cts-height).
   ========================================================================== */

/* ==========================================================================
   La cuadrícula es un pseudo-elemento, así que no existe en el DOM y GSAP no
   puede animarla: lo que se anima son estas dos variables, que el pseudo
   hereda del bloque de su slide.

   Van declaradas con @property y no sueltas porque sin registrar el tipo el
   navegador las trata como texto: cada frame invalida el estilo de todo el
   subárbol y no puede interpolarlas él mismo. Con el tipo declarado son un
   <length> y un <number> de verdad.
   ========================================================================== */
@property --cts-grid-x {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

@property --cts-grid-op {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}

.cts-slider {
  /* ==== MÓDULO BASE ====
     --cts-title-size es la única perilla: la celda de la cuadrícula es
     media línea de título, así que la altura de línea (2 celdas) siempre
     cae exacta sobre las líneas horizontales. Cambiás el tamaño del título
     en un breakpoint y la cuadrícula lo sigue sola.
     Para que las líneas de 1px no queden en píxeles fraccionarios, elegí
     tamaños que en px sean múltiplos de 4. */
  --cts-title-size: 2.75rem;
  --cts-cell: calc(var(--cts-title-size) / 2);

  z-index: 100;

  /* Padding lateral de la columna de texto. También define de dónde arranca
     la cuadrícula, para que su borde izquierdo caiga sobre la primera letra
     del título. */
  --cts-copy-inline: 20px;

  /* ==== CUADRÍCULA ====
     Ya no es un fondo a sangre: es un bloque decorativo arriba del título,
     medido en celdas (8x5 en desktop, que es lo que da el mockup). Al estar
     en celdas, crece con el título en cada breakpoint sin tocar nada. */
  --cts-grid-cols: 8;
  /* Menos filas en mobile: ahí el panel comparte el alto con la foto y un
     bloque de 5 se comería el aire del título. Sube a 5 en desktop. */
  --cts-grid-rows: 3;
  /* Las líneas se apagan hacia abajo a la derecha, como en el original de
     Photoshop. Es una máscara, así que sirve para cualquier color. */
  --cts-grid-fade: linear-gradient(145deg, #000 30%, rgba(0, 0, 0, 0.45) 100%);
  /* Las anima el JS en cada cambio de slide, por bloque y no acá: cada slide
     tiene su propia cuadrícula y las dos se cruzan. Estos son los valores de
     reposo, que además son los que quedan si el JS no corre. */
  --cts-grid-op: 1;
  --cts-grid-x: 0px;

  /* ==== HOJAS ====
     Decoración fija, la misma para todos los slides. Cuelgan de la ÚLTIMA
     palabra del título (el span .cts-accent) y no del panel: así siguen al
     texto sin importar dónde corte cada título, que es justo lo que un offset
     contra el panel no podía resolver.
     Sólo se ven en desktop; en pantallas chicas el título ya llena el ancho y
     no queda aire a la derecha donde apoyarlas.
     --cts-hojas-x se mide desde la DERECHA de la palabra, y -y es el
     corrimiento vertical respecto de su centro.
     Son las perillas para reubicarlas desde el CSS personalizado de Elementor
     sin tocar el plugin. */
  --cts-hojas-w: 150px;
  --cts-hojas-x: -30%;
  --cts-hojas-y: -39%;
  /* La sombra de las hojas es SUYA y no la del texto: van en nodos distintos
     justamente para poder graduarlas por separado. */
  --cts-hojas-shadow: drop-shadow(0 10px 18px rgb(0 0 0 / 0.45));

  /* ==== SOMBRA DEL TÍTULO ====
     text-shadow y no filter:drop-shadow(): el filtro se aplica al elemento
     entero —hijos y pseudo-elementos incluidos—, así que teñía también las
     hojas. text-shadow pinta sólo los glifos y deja la imagen intacta. */
  --cts-title-shadow: 0 2px 10px black;
  --cts-accent-shadow: 3px 4px 4px black;

  /* Cuánto del ancho se lleva el texto en desktop. El resto es la foto. */
  --cts-split: 57%;

  /* Piso del alto de la foto en mobile, donde va debajo del texto. El resto
     del hero es para el texto, que se sirve primero. */
  --cts-foto-min-h: 30vh;

  /* Dots: el punto dibujado, el anillo del activo, y el área de toque del
     botón (44px es el mínimo cómodo en mobile). */
  --cts-dot: 7px;
  --cts-dot-ring: 22px;
  --cts-dot-hit: 44px;

  /* Fallbacks por si el shortcode se renderiza sin variables inline. */
  --cts-text: #f2f2f2;
  --cts-accent: #f5c056;
  --cts-grid-line: rgba(242, 242, 242, 0.2);
  --cts-height: calc(100vh - 122px) !important;
  /* Si desactivás "cargar fuentes" en los ajustes, sobrescribí estas dos
     desde el Customizer o Elementor para que herede la del tema. */
  --cts-font-title: "Work Sans", sans-serif;
  --cts-font-body: "Inter", sans-serif;

  box-sizing: border-box;
  width: 100%;
  height: calc(100vh - 122px) !important;
  position: relative;
  overflow: hidden;
  z-index: 10;
  /* Mobile: texto arriba, foto abajo. El texto toma lo que necesita y la
     foto se queda con el resto; minmax(0,...) es lo que la deja encogerse
     en vez de desbordar el hero cuando el título ocupa tres líneas. */
  display: flex;
  flex-direction: column-reverse;
  /* Sin gap: la foto va a sangre contra el texto y contra los bordes. */
  gap: 0;
}

.cts-slider *,
.cts-slider *::before,
.cts-slider *::after {
  box-sizing: border-box;

}

/* ==========================================================================
   COLUMNA DE TEXTO

   El panel es sólo el marco: no reparte nada por flujo. Los N bloques de copy
   se apilan en absoluto adentro y cada uno se centra por su cuenta.
   ========================================================================== */
.cts-slider .cts-copy {
  position: relative;
  z-index: 10;
  flex: 1;
  min-width: 0;
  min-height: 0;
}

.cts-slider--sin-grid .cts-copy .cts-title::before {
  display: none;
}

/* ==========================================================================
   COPY — un bloque por slide, apilados con position:absolute.

   Antes cada pieza (título, texto, cta) tenía su propio contenedor con los N
   slides apilados en una celda de grid, y el espaciado entre piezas salía del
   flujo: dependía de cuántas líneas ocupara el título de cada slide, así que
   un título de dos palabras y uno de seis no quedaban con el mismo aire.

   Ahora la unidad es el slide, no la pieza. Cada .cts-copy__item ocupa el
   panel entero y centra su propio contenido, de modo que el bloque siempre
   queda en el mismo eje sin importar el largo del título. Y al ser un solo
   nodo por slide, la transición es una sola: fade-up del bloque completo.
   ========================================================================== */
.cts-slider .cts-copy__item {
  position: absolute;
  /* El rectángulo de contención de un absoluto es el PADDING box del
     ancestro, así que el padding del panel no lo respetaría: por eso el
     padding vive acá y .cts-copy no lleva ninguno. */
  inset: 0;
  /* Por encima del scrim, que es hermano y va en z-index 10. */
  z-index: 11;

  display: flex;
  flex-direction: column;
  align-items: flex-start; /* alineado a la izquierda */
  justify-content: flex-start; /* en desktop pasa a center */

  padding: 2rem 1em;

  /* El JS enciende el activo. El centrado NO usa transform a propósito: la
     `y` del fade-up es de GSAP y se pisarían. */
  opacity: 0;

  /* Un bloque invisible sigue siendo clickeable: sin esto el cta del slide
     que no se ve se comería el click del que sí. */
  pointer-events: none;
}

.cts-slider .cts-copy__item.is-active {
  pointer-events: auto;
}

/* flex:none para que ninguna pieza se encoja: si pudieran, el navegador les
   sacaría alto y el título terminaría pisando al subtítulo. */
.cts-slider .cts-copy__item > * {
  flex: none;
}

.cts-slider .cts-title {
  position: relative;
  z-index: 1;
  /* El ancho completo del panel es lo que mide el wrap y el text-wrap:balance;
     en una columna flex con align-items:flex-start, sin esto el título se
     encogería a su contenido. */
  width: 100%;
  margin: 0;
  padding: 0;
  text-align: left;
  font-size: var(--cts-title-size);
  line-height: calc(var(--cts-cell) * 2); /* 1 línea = 2 celdas exactas */
  text-wrap: balance;
  font-family: var(--cts-font-title);
  font-weight: 800;
  font-style: normal;
  color: var(--cts-text);
  letter-spacing: -0.05em;
  /* Sombra por glifo. Ver --cts-title-shadow: con filter se pintaba también
     sobre las hojas, que son un hijo del título. */
  text-shadow: var(--cts-title-shadow);
}

/* La última palabra del título. El span lo pone PHP: antes este color lo
   llevaba el wrapper que SplitText dejaba alrededor de cada palabra, y ese
   wrapper ya no existe.

   isolation:isolate lo convierte en contexto de apilamiento propio, que es lo
   que necesita el z-index negativo de las hojas para quedar detrás de ESTAS
   letras y no colarse detrás de medio panel. Antes ese contexto lo creaba el
   filter de rebote; ahora que no hay filter hay que pedirlo explícito. */
.cts-slider .cts-title .cts-accent {
  position: relative;
  isolation: isolate;
  color: var(--cts-accent);
  text-shadow: var(--cts-accent-shadow);
}

/* Las hojas se apoyan sobre el final de la última palabra, por DETRÁS de las
   letras. Son decoración pura: el alt vacío y el aria-hidden ya las sacan del
   árbol de accesibilidad.
   Escondidas por defecto — sólo aparecen en desktop, ver el breakpoint. */
.cts-slider .cts-title .cts-hojas {
  display: none;
  position: absolute;
  /* Detrás del contenido inline del span, o sea de las letras. */
  z-index: -1;
  top: 50%;
  right: var(--cts-hojas-x);
  width: var(--cts-hojas-w);
  /* Casi todos los temas traen `img { max-width: 100% }`, y acá el 100% es el
     ancho de UNA palabra: sin esto las hojas se achicarían con los títulos
     que terminan corto. */
  max-width: none;
  /* Cuadrada y con object-fit: el PNG no es cuadrado, así que sin esto se
     deformaría. Es el equivalente del background-size:contain que usaba el
     pseudo-elemento. */
  height: var(--cts-hojas-w);
  object-fit: contain;
  /* El centrado va en `translate` y no en `transform` a propósito: son
     propiedades independientes, así GSAP puede animar el scale (que sí vive
     en transform) sin borrar este corrimiento. Si estuvieran juntos, el
     primer tween de escala mandaría las hojas fuera de lugar. */
  translate: 0 var(--cts-hojas-y);
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  filter: var(--cts-hojas-shadow);
}

.cts-slider .cts-text {
  position: relative;
  z-index: 10;
  color: var(--cts-text);
  font-size: 1rem;
  text-align: left;
  max-width: 46ch;
  /* a propósito NO está atado a --cts-cell: con celdas de 32-44px el
     interlineado del cuerpo quedaría absurdo. El que se lee contra la
     cuadrícula es el título. */
  line-height: 1.5;
  font-weight: 400;
  font-family: var(--cts-font-body);
  padding: 0;
  /* En celdas y no en px: el interlineado del título es apretado y los
     descendentes de la última línea llegan bajo, así que el aire tiene que
     crecer junto con el cuerpo del título. */
  margin: calc(var(--cts-cell) * 0.75) 0 0;
}

/* El cta no lleva width: en la columna flex se ajusta a su texto, que es
   justo lo que antes conseguía el justify-items:start del grid. */
.cts-slider .cts-cta {
  position: relative;
  z-index: 100;
  padding: 10px 30px;
  margin: 24px 0 0;
  font-size: 0.7rem;
  text-decoration: none;
  border: 1px solid var(--cts-accent);
  border-radius: 0;
  color: var(--cts-text);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  font-family: var(--cts-font-body);
  white-space: nowrap;
  transition: background-color 0.3s ease;
}

.cts-slider .cts-cta:hover,
.cts-slider .cts-cta:focus-visible {
  background-color: color-mix(in srgb, var(--cts-accent) 18%, transparent);
  color: var(--cts-text);
}

/* ==========================================================================
   SCRIM — la costura entre el panel de texto y la foto.

   Dos reglas que hacen toda la diferencia:

   1. El stop final es el MISMO color con alfa 0, nunca blanco transparente.
      El navegador interpola el alfa sin premultiplicar, así que un
      `rgba(255,255,255,0)` al final hace que la mitad del recorrido pase por
      un gris lavado: ese es el halo sucio, no la falta de difuminado.

   2. Los stops siguen una curva de seno, no una rampa lineal. Un fade lineal
      de alfa se lee como una banda con principio y fin visibles; la curva
      arranca y termina plana, así que los dos extremos desaparecen.

   --cts-scrim va en canales sueltos para poder escribir rgb(... / alfa) sin
   repetir el color dieciséis veces. Si cambiás el fondo del hero en
   Elementor, cambiá esto y listo. */
.cts-slider {
  --cts-scrim: 0 0 0;
  overflow-x: hidden !important;
}

.cts-copy div.overlay {
  position: absolute;
  top: -30px;
  left: 0;
  width: 100%;
  /* el doble que antes: cuanto más largo el recorrido, menos banding */
  height: 60px;
  z-index: 10;
  /* está encima de la foto: no debe comerse clicks ni hovers */
  pointer-events: none;

  background: linear-gradient(0deg, #000 50%, transparent 100%);
}

/* ==========================================================================
   DOTS — el punto y el anillo son dos pseudo-elementos apilados, así el
   activo no cambia de tamaño ni empuja a los vecinos: sólo se enciende.
   ========================================================================== */
.cts-slider .cts-dots {
  position: absolute;
  z-index: 10;
  opacity: 0;
  display: flex;
  align-items: center;
  /* Al piso del panel, como en el mockup. El padding es el colchón mínimo
     para cuando no sobra espacio (mobile), donde el auto no estira nada. */

  bottom: 1em;
  right: 1em;

  /* el botón es más grande que el dibujo para tener área de toque: este
     margen negativo vuelve a alinear el anillo del primer dot con el borde
     izquierdo del texto */
  margin-inline-start: calc((var(--cts-dot-hit) - var(--cts-dot-ring)) / -2);
}

.cts-slider .cts-dots button {
  width: var(--cts-dot-hit);
  height: var(--cts-dot-hit);
  display: grid;
  place-items: center;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* el punto */
.cts-slider .cts-dots button::before {
  content: "";
  grid-area: 1 / 1;
  width: var(--cts-dot);
  height: var(--cts-dot);
  border-radius: 50%;
  background: var(--cts-text);
  opacity: 0.4;
  transition:
    opacity 0.3s ease,
    background-color 0.3s ease;
}

/* el anillo: en el mockup lo tienen los tres, no sólo el activo */
.cts-slider .cts-dots button::after {
  content: "";
  grid-area: 1 / 1;
  width: var(--cts-dot-ring);
  height: var(--cts-dot-ring);
  border-radius: 50%;
  border: 1px solid var(--cts-text);
  opacity: 0.45;
  transition:
    opacity 0.3s ease,
    border-color 0.3s ease;
}

.cts-slider .cts-dots button:hover::before,
.cts-slider .cts-dots button:focus-visible::before {
  opacity: 0.75;
}

.cts-slider .cts-dots button[aria-current="true"]::before {
  background: var(--cts-accent);
  opacity: 1;
}

.cts-slider .cts-dots button[aria-current="true"]::after {
  border-color: var(--cts-accent);
  opacity: 1;
}

/* ==========================================================================
   FOTO — una imagen CON fondo por slide (ya no un PNG recortado), a sangre
   en su mitad del hero. Las N se apilan en una celda de grid y entran con un
   wipe: clip-path de un borde al otro, más scale y blur.

   El panel SÍ recorta (overflow:hidden): la que sale se corre hacia afuera
   mientras se cierra, y no tiene que asomar por los bordes del hero.
   ========================================================================== */
.cts-slider .cts-products {
  position: relative;
  z-index: 5;
  display: grid;
  width: 100%;
  flex: 2;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

/* El selector es "> *" y no "img" para que los placeholders de los slides
   sin imagen también cuenten: así los índices nunca se desalinean. */
.cts-slider .cts-products > * {
  grid-area: 1 / 1;
  display: block;
  width: 100%;
  height: 100%;
  /* En grid, min-width/min-height valen `auto`: el item se niega a bajar de
     su tamaño intrínseco, así que una imagen grande desbordaría igual aunque
     el contenedor esté acotado. Ponerlos en 0 es lo que la deja encogerse. */
  min-width: 0;
  min-height: 0;
  opacity: 0;
  /* Estado de reposo de las que no se ven: cerradas contra el borde
     izquierdo. Es el mismo polígono del que parte el wipe en el JS. */
  clip-path: polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%);
  transform: scale(1.12);
}

.cts-slider .cts-products img {
  /* cover, no contain: la foto ya trae su propio fondo, así que tiene que
     llenar el panel aunque le recorte los costados. */
  object-fit: cover;
  object-position: center;
}

img.tiger-img {
  position: absolute;
  bottom: -10%;
  left: 12%;
  width: 30%;
  object-fit: contain;
  height: auto;
  z-index: 100;
}

/* ==========================================================================
   Breakpoints — cambian --cts-title-size, --cts-copy-inline y el reparto de
   columnas; el resto (celda, cuadrícula, hojas) se recalcula solo.
   ========================================================================== */

@media (height <=800px ) and (width <=1024px) {
  .cts-slider .cts-products {
    flex: 1;
  }
}

@media (width >=1024px){
  .cts-slider .cts-products {
    flex: 1;
  }

  /* Recién acá hay aire a la derecha del título donde apoyarlas. */
  .cts-slider .cts-title .cts-hojas {
    display: block;
  }
}

/* Tablet */
@media (min-width: 768px) and (max-width: 1023px) {
  .cts-slider {
    --cts-title-size: 4rem;
    --cts-copy-inline: 40px;
    --cts-foto-min-h: 34vh;
    --cts-grid-rows: 4;

    height: calc(100vh - 138px) !important;
  }

  .cts-slider .cts-cta {
    font-size: 1rem;
  }
}

/* Desktop: el texto a la izquierda y la foto a la derecha, partido al medio
   sin gap ni margen. */
@media (min-width: 1024px) {
  /* ==========================================================================
   CUADRÍCULA — dos gradientes repetidos, no un PNG: escala a cualquier
   pantalla, las líneas quedan siempre de 1px reales y toma el color de los
   ajustes. Es el ::before del panel de texto, o sea que es un item más del
   flex y se ubica solo arriba del título.
   El +1px de tamaño es para que se dibujen también la línea de la derecha y
   la de abajo: cada gradiente pinta la línea al COMIENZO de cada celda, así
   que un bloque de N celdas justas cerraría abierto.
   ========================================================================== */

 
  /* En desktop el scrim se pone vertical y monta 40px sobre el propio panel:
     así el tramo 100% opaco cae sobre el negro del hero y no queda ningún
     canto donde empieza el degradado. Los otros 220px se comen el borde
     izquierdo de la foto. */
  .cts-copy div.overlay {
    right: -220px;
    left: unset;
    top: unset;
    bottom: 0;
    width: 260px;
    height: 100%;
    z-index: 10;

    background: linear-gradient(
      90deg,
      rgb(var(--cts-scrim) / 1) 0%,
      rgb(var(--cts-scrim) / 0.987) 8.1%,
      rgb(var(--cts-scrim) / 0.951) 15.5%,
      rgb(var(--cts-scrim) / 0.896) 22.5%,
      rgb(var(--cts-scrim) / 0.825) 29%,
      rgb(var(--cts-scrim) / 0.741) 35.3%,
      rgb(var(--cts-scrim) / 0.648) 41.2%,
      rgb(var(--cts-scrim) / 0.55) 47.1%,
      rgb(var(--cts-scrim) / 0.45) 52.9%,
      rgb(var(--cts-scrim) / 0.352) 58.8%,
      rgb(var(--cts-scrim) / 0.259) 64.7%,
      rgb(var(--cts-scrim) / 0.175) 71%,
      rgb(var(--cts-scrim) / 0.104) 77.5%,
      rgb(var(--cts-scrim) / 0.049) 84.5%,
      rgb(var(--cts-scrim) / 0.013) 91.9%,
      rgb(var(--cts-scrim) / 0) 100%
    );
  }

  /* La línea recta que queda es el canto de la foto. El degradado la
     OSCURECE pero no la desdibuja: un borde nítido sobrevive a cualquier
     cantidad de scrim porque sigue habiendo un salto de luminancia en un
     solo píxel. Esto sí lo borra — desenfoca el backdrop (los píxeles
     reales de la foto) y después desvanece ese desenfoque con una máscara.

     Va en un pseudo-elemento y no en el overlay mismo porque `mask` recorta
     TODO el elemento: puesto arriba se comería también el degradado de
     color. Separados, cada capa lleva su propia curva.

     El pseudo pinta después del fondo del overlay, así que lo que desenfoca
     es «foto + scrim» ya compuesto. No importa: el degradado es suave, y
     desenfocar algo suave lo deja igual. */
  .cts-copy div.overlay::before {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;

    -webkit-backdrop-filter: blur(18px);
    backdrop-filter: blur(18px);

    /* La máscara cierra en 88% y no en 100% a propósito: al desenfocar,
       backdrop-filter clona los píxeles del borde del elemento, así que si
       llegara viva hasta el canto derecho dibujaría justo la línea vertical
       que vinimos a borrar. Ese 12% muerto es el colchón.
       Misma curva de seno que el degradado, reescalada a 0-88%. */
    -webkit-mask-image: linear-gradient(
      90deg,
      rgb(0 0 0 / 1) 0%,
      rgb(0 0 0 / 0.987) 7.1%,
      rgb(0 0 0 / 0.951) 13.6%,
      rgb(0 0 0 / 0.896) 19.8%,
      rgb(0 0 0 / 0.825) 25.5%,
      rgb(0 0 0 / 0.741) 31.1%,
      rgb(0 0 0 / 0.648) 36.3%,
      rgb(0 0 0 / 0.55) 41.4%,
      rgb(0 0 0 / 0.45) 46.6%,
      rgb(0 0 0 / 0.352) 51.7%,
      rgb(0 0 0 / 0.259) 56.9%,
      rgb(0 0 0 / 0.175) 62.5%,
      rgb(0 0 0 / 0.104) 68.2%,
      rgb(0 0 0 / 0.049) 74.4%,
      rgb(0 0 0 / 0.013) 80.9%,
      rgb(0 0 0 / 0) 88%
    );
    mask-image: linear-gradient(
      90deg,
      rgb(0 0 0 / 1) 0%,
      rgb(0 0 0 / 0.987) 7.1%,
      rgb(0 0 0 / 0.951) 13.6%,
      rgb(0 0 0 / 0.896) 19.8%,
      rgb(0 0 0 / 0.825) 25.5%,
      rgb(0 0 0 / 0.741) 31.1%,
      rgb(0 0 0 / 0.648) 36.3%,
      rgb(0 0 0 / 0.55) 41.4%,
      rgb(0 0 0 / 0.45) 46.6%,
      rgb(0 0 0 / 0.352) 51.7%,
      rgb(0 0 0 / 0.259) 56.9%,
      rgb(0 0 0 / 0.175) 62.5%,
      rgb(0 0 0 / 0.104) 68.2%,
      rgb(0 0 0 / 0.049) 74.4%,
      rgb(0 0 0 / 0.013) 80.9%,
      rgb(0 0 0 / 0) 88%
    );
  }

  .cts-slider .cts-copy .cts-title::before {
    content: "";
    position: absolute;
    z-index: -1;
    top: -50%;
    left: -10%;
    width: calc(var(--cts-cell) * var(--cts-grid-cols) + 1px);
    height: calc(var(--cts-cell) * var(--cts-grid-rows) + 1px);
    background-image:
      linear-gradient(to right, var(--cts-grid-line) 1px, transparent 1px),
      linear-gradient(to bottom, var(--cts-grid-line) 1px, transparent 1px);
    background-size: var(--cts-cell) var(--cts-cell);
    -webkit-mask-image: var(--cts-grid-fade);
    mask-image: var(--cts-grid-fade);
    /* La cuadrícula vive dentro del bloque, así que ya sube con él. Encima de
       eso el JS le da su propio gesto: se va apagando hacia la izquierda
       mientras el texto sube, para que el fondo no se lea como una calcomanía
       pegada al título. Ver RATIO.gridX en slider.js. */
    opacity: var(--cts-grid-op);
    translate: var(--cts-grid-x) 0;
    pointer-events: none;
  }

  .cts-slider {
    --cts-title-size: 3.5rem;
    --cts-copy-inline: 20px;
    /* Al costado de la foto el panel se queda con todo el alto: acá sí entra
       el bloque completo del mockup. */
    --cts-grid-rows: 5;

    height: calc(100vh - 176px) !important;

    flex-direction: row;
  }

  .cts-slider .cts-copy__item {
    justify-content: center;
    padding: 0 2rem 0 4rem;
  }
}

/* Desktop XL */
@media (min-width: 1280px) {
  .cts-slider {
    /* 4.75rem y no 4.8rem: 76px da una celda de 38px justos */
    --cts-title-size: 4.2rem;
    --cts-copy-inline: 30px;

    /* Título más grande, hojas más grandes y corridas hacia afuera. */
    --cts-hojas-w: 200px;
    --cts-hojas-x: -60px;
  }

  .cts-slider .cts-copy .cts-title::before {
    top: -50%;
  }
}

@media (width >= 1280px) and (height <= 800px) {
  .cts-slider .cts-copy .cts-title::before {
    top: -50%;
  }
}

/* Desktop 2XL */
@media (min-width: 1440px) {
  .cts-slider {
    --cts-title-size: 5.5rem;
    --cts-copy-inline: 200px;
    --cts-hojas-y: -35%;

     height: calc(100vh - 220px) !important;
  }
  .cts-slider .cts-copy .cts-title::before {
    top: -50%;
  }

  .cts-slider .cts-copy__item {
    justify-content: center;
    padding: 0 0 0 8rem;
  }
}

/* ==========================================================================
   Sin JS y accesibilidad
   ========================================================================== */

/* Si el JS no corre, el bloque de copy y los dots quedarían en opacity:0 para
   siempre. La clase .cts-ready la pone el propio JS ni bien arranca; mientras
   no esté, el primer slide se ve estático.

   El enganche es .is-active y no :first-of-type porque el scrim es un div más
   dentro del panel: sería él el primero de su tipo, no el bloque de copy.
   PHP ya marca el slide 0 con esa clase, así que no hace falta el JS. */
.cts-slider:not(.cts-ready) .cts-copy__item.is-active,
.cts-slider:not(.cts-ready) .cts-dots {
  opacity: 1;
}

/* Los demás bloques se apilan encima del activo: sin esto taparían el clic
   del cta visible aunque no se vean. */
.cts-slider:not(.cts-ready) .cts-copy__item:not(.is-active),
.cts-slider:not(.cts-ready) .cts-products > *:not(:first-child) {
  visibility: hidden;
}

.cts-slider:not(.cts-ready) .cts-products > *:first-child {
  opacity: 1;
  clip-path: none;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .cts-slider .cts-dots button::before,
  .cts-slider .cts-dots button::after,
  .cts-slider .cts-cta {
    transition: none;
  }
}

/* ==========================================================================
   MOTOR DE LA ANIMACIÓN — versión sin GSAP

   Todo lo que hay de acá para abajo reemplaza a los timelines de slider.js.
   Lo que antes era un tween es un estado; lo que era una posición en el
   timeline es un transition-delay.

   Los tres estados, para la foto y para el bloque de texto:

     (base)        reposo de lo que no se ve. SIN transition a propósito: es
                   lo que hace que volver acá sea instantáneo. Equivale al
                   gsap.set() que había en el onComplete.
     .is-entering  sube de capa y TRAE la transition.
     .is-active    el estado visible.
     .is-leaving   a donde se va lo que sale, con su ease y su delay propios.

   Las duraciones y los delays los calcula PHP desde el mismo campo
   "transicion" del admin y llegan como custom properties. Los fallbacks de
   los var() son los valores con transicion = 1.1s.
   ========================================================================== */
.cts-slider {
  /* ---- Las perillas ----
     Son las que antes vivían en el objeto RATIO de slider.js. Cambiarlas acá
     tiene el mismo efecto que tenía cambiarlas allá. */
  --cts-img-scale-in: 1.12; /* escala desde la que entra la foto */
  --cts-img-scale-out: 0.95; /* escala a la que sale */
  --cts-img-blur-in: 10px;
  --cts-img-blur-out: 8px;
  /* Cuánto se corre hacia afuera la que sale. En % del propio panel y no en
     px: es el mismo gesto en un panel de 300px que en uno de 900. En la
     versión con GSAP esto era salidaX(), que medía offsetWidth en cada
     transición; el % lo resuelve solo y sigue al resize gratis. */
  --cts-img-out-x: 30%;

  --cts-copy-in-y: 28px; /* desde dónde sube el bloque que entra */
  --cts-copy-out-y: -20px; /* hasta dónde sigue subiendo el que sale */
  --cts-grid-shift: 40px; /* lo que recorre la cuadrícula, al costado */
  --cts-hojas-in: 0.92;
  --cts-hojas-out: 1.08;

  /* ---- Las curvas ----
     "cts-hop" era un CustomEase:
     M0,0 C0.071,0.505 0.192,0.726 0.318,0.852 0.45,0.984 0.504,1 1,1
     Son DOS segmentos de bézier, así que no se puede escribir como un solo
     cubic-bezier(). linear() con suficientes puntos sí lo reproduce: fijate
     que en 31.8% vale 0.852, que es exactamente donde se unen los dos
     segmentos del original.

     La primera declaración es el fallback: un navegador sin linear() se queda
     con ella y descarta la segunda, que no sabe parsear. */
  --cts-hop: cubic-bezier(0.16, 0.84, 0.28, 1);
  --cts-hop: linear(
    0 0%,
    0.12 1.9%,
    0.23 3.9%,
    0.328 6.2%,
    0.417 8.6%,
    0.497 11.2%,
    0.568 13.8%,
    0.631 16.6%,
    0.687 19.5%,
    0.737 22.5%,
    0.78 25.6%,
    0.818 28.7%,
    0.852 31.8%,
    0.883 35%,
    0.909 38%,
    0.931 41%,
    0.949 44.3%,
    0.964 48%,
    0.976 52.2%,
    0.984 57.3%,
    0.991 63.2%,
    0.995 70.3%,
    0.998 78.6%,
    1 88.5%,
    1 100%
  );

  /* power2.in de GSAP. Es una ease que ACELERA: lo que sale tiene que irse,
     no frenar. Con una que desacelere parece que se arrepiente. */
  --cts-out: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}

/* ==========================================================================
   FOTO
   ========================================================================== */
/* Base. Pisa el `opacity: 0` de la regla original: la que esconde tiene que
   ser la máscara y no la opacidad. Si además hubiera un fade, el borde del
   wipe se vería difuso y el gesto se leería como una disolvencia. Antes esto
   lo ponía fotoOculta() desde el JS; ahora vive acá.

   El translateX(0) está para que la lista de funciones de transform sea la
   MISMA que la del estado de salida: es lo que permite interpolar sin salto. */
.cts-slider .cts-products > * {
  opacity: 1;
  transform: translateX(0) scale(var(--cts-img-scale-in));
  filter: blur(var(--cts-img-blur-in));
  z-index: 0;
}

.cts-slider .cts-products > *.is-active {
  z-index: 1;
  clip-path: polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%);
  transform: translateX(0) scale(1);
  filter: blur(0px);
}

/* La que entra barre por encima de la que sale: capa 2, y cuando termina de
   abrirse ya tapa el panel entero. */
.cts-slider .cts-products > *.is-entering {
  z-index: 2;
  /* Sólo mientras animan: dejarlo puesto en las N mantendría una capa de
     composición por imagen aunque estén quietas. */
  will-change: clip-path, transform, filter;
  transition:
    clip-path var(--cts-t-image, 1.54s) var(--cts-hop),
    transform var(--cts-t-image, 1.54s) var(--cts-hop),
    filter var(--cts-t-image, 1.54s) var(--cts-hop);
}

/* La que sale se cierra contra su borde izquierdo mientras se corre hacia
   afuera: es lo que se ve en la franja que el wipe todavía no cubrió, y da la
   sensación de que una empuja a la otra. Arranca apenas después que la que
   entra — ese retardo era RATIO.imageOutDelay. */
.cts-slider .cts-products > *.is-leaving {
  z-index: 1;
  clip-path: polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%);
  transform: translateX(var(--cts-img-out-x)) scale(var(--cts-img-scale-out));
  filter: blur(var(--cts-img-blur-out));
  will-change: clip-path, transform, filter;
  transition:
    clip-path var(--cts-t-image, 1.54s) var(--cts-out),
    transform var(--cts-t-image, 1.54s) var(--cts-out),
    filter var(--cts-t-image, 1.54s) var(--cts-out);
  transition-delay: var(--cts-t-image-out, 0.1s);
}

/* ==========================================================================
   BLOQUE DE TEXTO (+ su deco)

   El bloque que sale sigue subiendo y el que entra llega desde abajo: los dos
   se mueven en la MISMA dirección. Si el que sale bajara, se cruzarían en el
   aire y el ojo no sabría cuál seguir.

   La cuadrícula va por variable CSS y no por transform porque es un
   pseudo-elemento y no existe en el DOM: lo que recibe la transición es el
   bloque, y el pseudo hereda. Las dos variables están declaradas con
   @property al principio de este archivo, que es justamente lo que las vuelve
   interpolables: sin el tipo registrado el navegador las trata como texto y
   no las puede animar. Esa declaración ya estaba — es la que hace posible
   sacar GSAP sin perder este gesto.

   Se usa `translate` y no `transform` por lo mismo que el resto del archivo:
   así no pisa ningún transform que traiga el layout. */
.cts-slider .cts-copy__item {
  translate: 0 var(--cts-copy-in-y);
  --cts-grid-x: var(--cts-grid-shift);
  --cts-grid-op: 0;
}

.cts-slider .cts-copy__item.is-active {
  opacity: 1;
  translate: 0 0;
  --cts-grid-x: 0px;
  --cts-grid-op: 1;
}

.cts-slider .cts-copy__item.is-entering {
  will-change: opacity, translate;
  transition:
    opacity var(--cts-t-text, 1.1s) var(--cts-hop),
    translate var(--cts-t-text, 1.1s) var(--cts-hop),
    --cts-grid-x var(--cts-t-text, 1.1s) var(--cts-hop),
    --cts-grid-op var(--cts-t-text, 1.1s) var(--cts-hop);
  /* El texto entra DESPUÉS que la foto: este delay era RATIO.textOverlap, o
     sea la posición del tween dentro del timeline. */
  transition-delay: var(--cts-t-overlap, 0.5s);
}

.cts-slider .cts-copy__item.is-leaving {
  opacity: 0;
  translate: 0 var(--cts-copy-out-y);
  --cts-grid-x: calc(var(--cts-grid-shift) * -1);
  --cts-grid-op: 0;
  will-change: opacity, translate;
  transition:
    opacity var(--cts-t-text, 1.1s) var(--cts-out),
    translate var(--cts-t-text, 1.1s) var(--cts-out),
    --cts-grid-x var(--cts-t-text, 1.1s) var(--cts-out),
    --cts-grid-op var(--cts-t-text, 1.1s) var(--cts-out);
}

/* Las hojas hacen un scale corto, y las dos —la que sale y la que entra—
   crecen: el gesto no se invierte a mitad de camino. Comparten reloj con su
   bloque, así que llevan los mismos delays.

   `scale` como propiedad independiente y no dentro de transform: el CSS de
   arriba ya usa `translate` para posicionarlas y se pisarían. */
.cts-slider .cts-copy__item .cts-hojas {
  scale: var(--cts-hojas-in);
}

.cts-slider .cts-copy__item.is-active .cts-hojas {
  scale: 1;
}

.cts-slider .cts-copy__item.is-entering .cts-hojas {
  transition: scale var(--cts-t-text, 1.1s) var(--cts-hop);
  transition-delay: var(--cts-t-overlap, 0.5s);
}

.cts-slider .cts-copy__item.is-leaving .cts-hojas {
  scale: var(--cts-hojas-out);
  transition: scale var(--cts-t-text, 1.1s) var(--cts-out);
}

/* ==========================================================================
   INTRO

   En la carga el texto entra antes que en un cambio normal: el timeline lo
   anclaba a 0.28 de su propia duración desde el arranque de la foto, no al
   solapamiento. Es el único momento en que el delay es distinto, así que
   alcanza con pisarlo mientras la raíz tenga .is-intro.
   ========================================================================== */
.cts-slider.is-intro .cts-copy__item.is-entering,
.cts-slider.is-intro .cts-copy__item.is-entering .cts-hojas {
  transition-delay: var(--cts-t-reveal, 0.31s);
}

/* Los dots aparecen con el copy y no antes. El CSS de arriba los deja en
   opacity 0 y los encendía el JS con un tween; ahora los enciende la clase
   que el JS pone cuando termina de inicializar. */
.cts-slider.cts-ready .cts-dots {
  opacity: 1;
  transition: opacity 0.6s ease;
  transition-delay: var(--cts-t-dots, 0.01s);
}

/* ==========================================================================
   REDUCIR MOVIMIENTO

   Queda el cambio de slide —es la información— y se va el movimiento. Las
   duraciones bajan a 0.01s y no a 0 para que el JS igual tenga un tick donde
   limpiar las clases. El blur se apaga entero: es justo el tipo de efecto que
   molesta a quien pide menos movimiento. El autoplay lo desactiva el JS.

   Antes esto estaba repartido en el JS, en las funciones blur(), offset() y
   escala(); acá es una sola media query que apaga las perillas.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .cts-slider {
    --cts-t-text: 0.01s;
    --cts-t-image: 0.01s;
    --cts-t-overlap: 0s;
    --cts-t-image-out: 0s;
    --cts-t-reveal: 0s;
    --cts-t-dots: 0s;

    --cts-img-scale-in: 1;
    --cts-img-scale-out: 1;
    --cts-img-blur-in: 0px;
    --cts-img-blur-out: 0px;
    --cts-img-out-x: 0%;
    --cts-copy-in-y: 0px;
    --cts-copy-out-y: 0px;
    --cts-grid-shift: 0px;
    --cts-hojas-in: 1;
    --cts-hojas-out: 1;
  }

  .cts-slider .cts-products > * {
    filter: none;
  }
}
