/* ============================================================================
   ROTERMELLER · efectos.css
   Ocho efectos vanilla para la landing. Sin build, sin CDN, sin librerías.

   IDENTIDAD (sacada de Compartido/Tema.swift, no inventada):
     marfil #F7F6F2 · coral #FF594D · coral oscuro #E94B41 · tinta #161A24
     gris #77777F · verde #2FA36B · naranja #E2872B · lila #8D5FE0
     radios 26 / 20 / 14 y cápsula · sombras al 6 % · tipografía redonda

   REGLA DE ORO DE TODO ESTE FICHERO: **nada se ve por defecto gracias a un
   efecto**. Todo el contenido es visible con el CSS base; los efectos sólo
   añaden movimiento encima. Si falla el JS, si el navegador no soporta
   `animation-timeline` o si el usuario pide movimiento reducido, la página
   se lee entera igual. Es lo contrario de lo habitual (opacity:0 + JS que
   revela) y es lo correcto: un `@supports` mal cerrado no puede dejar la
   web en blanco.
   ========================================================================= */

:root{
  /* ------------------------------------------------------------------------
     efectos.css NO es dueño de la paleta: la lee de identidad.css, que es
     donde vive la marca. Aquí sólo se hace un alias --fx-* de cada token que
     los efectos necesitan, CON VALOR DE RESERVA — así este fichero también
     funciona suelto (una demo, una prueba aislada) sin arrastrar el otro.

     Se usa un espacio de nombres propio en vez de redeclarar --coral, --linea
     o --r-grande: si los dos ficheros declararan los mismos nombres, quien
     gana depende del orden de los <link> y eso es un fallo que aparece meses
     después, al reordenar el <head>.
     --------------------------------------------------------------------- */
  --fx-marfil:     var(--marfil,      #F7F6F2);
  --fx-blanco:     var(--superficie,  #FFFFFF);
  --fx-linea:      var(--divisor,     #ECEBE8);
  --fx-tinta:      var(--tinta,       #161A24);
  --fx-gris:       var(--secundario,  #77777F);

  --fx-coral:      var(--coral,        #FF594D);
  --fx-coral-osc:  var(--coral-oscuro, #E94B41);
  --fx-verde:      var(--verde,        #2FA36B);
  --fx-naranja:    var(--naranja,      #E2872B);
  --fx-lila:       var(--lila,         #8D5FE0);

  --fx-r-xl:   var(--r-grande,  26px);
  --fx-r-l:    var(--r-medio,   20px);
  --fx-r-m:    var(--r-pequeno, 14px);
  --fx-r-cap:  var(--r-capsula, 999px);

  --fx-sombra-s: 0 2px 8px rgba(22,26,36,.05);
  --fx-sombra-m: var(--s-tarjeta,  0 6px 18px rgba(22,26,36,.06));
  --fx-sombra-l: var(--s-flotante, 0 12px 32px rgba(22,26,36,.12));

  /* La curva de la casa: sale rápido y aterriza despacio. */
  --fx-curva: cubic-bezier(.16,1,.3,1);
}

/* ⚠ El coral sobre marfil da ~3:1. Vale para titulares grandes, cifras y
   botones (con texto blanco encima del coral), NO para texto corrido. El
   texto pequeño va en --tinta o --gris. */


/* ============================================================================
   1 · FONDO-AULA — degradado animado de ambiente. Cero JS.
   ----------------------------------------------------------------------------
   Tres manchas de color (coral, lila, verde) que van y vienen muy despacio
   detrás del hero. Sobre fondo claro la clave es la contención: por encima
   de un 12 % de tinta el marfil se ensucia y parece un mockup de IA.

   Detalle que evita el fallo nº1 de este efecto: se anima `translate` y
   `rotate` (propiedades independientes), NO `transform`. Con `transform`, un
   keyframe compartido pisa la posición base de cada mancha y las tres acaban
   apuntando al mismo sitio. Además, si el navegador no soporta las
   propiedades sueltas, las manchas simplemente se quedan quietas: se ve bien.
   ========================================================================= */
[data-fx~="aula"]{
  position:relative;
  isolation:isolate;
  overflow:hidden;               /* obligatorio: las manchas se salen de la caja */
  background:var(--fx-marfil);
}

[data-fx~="aula"] > .fx-mancha{
  position:absolute;
  z-index:-1;
  pointer-events:none;
  border-radius:50%;
  filter:blur(60px);
  opacity:.55;
}

/* Fallback en rgba primero, color-mix después: donde se soporte, gana el
   segundo y el color sale siempre del token de marca. */
[data-fx~="aula"] .fx-mancha--coral{
  inset:-12% auto auto -8%;
  width:min(52vw,620px); aspect-ratio:1;
  background:radial-gradient(circle at 50% 50%, rgba(255,89,77,.30), rgba(255,89,77,0) 70%);
  background:radial-gradient(circle at 50% 50%,
             color-mix(in oklab, var(--fx-coral) 30%, transparent),
             color-mix(in oklab, var(--fx-coral) 0%, transparent) 70%);
  animation:fx-deriva-a 17s ease-in-out infinite alternate;
}
[data-fx~="aula"] .fx-mancha--lila{
  inset:auto -10% -18% auto;
  width:min(46vw,540px); aspect-ratio:1;
  background:radial-gradient(circle at 50% 50%, rgba(141,95,224,.22), rgba(141,95,224,0) 70%);
  background:radial-gradient(circle at 50% 50%,
             color-mix(in oklab, var(--fx-lila) 22%, transparent),
             color-mix(in oklab, var(--fx-lila) 0%, transparent) 70%);
  animation:fx-deriva-b 23s ease-in-out infinite alternate;
}
[data-fx~="aula"] .fx-mancha--verde{
  inset:30% 22% auto auto;
  width:min(34vw,400px); aspect-ratio:1;
  background:radial-gradient(circle at 50% 50%, rgba(47,163,107,.16), rgba(47,163,107,0) 70%);
  background:radial-gradient(circle at 50% 50%,
             color-mix(in oklab, var(--fx-verde) 16%, transparent),
             color-mix(in oklab, var(--fx-verde) 0%, transparent) 70%);
  animation:fx-deriva-c 29s ease-in-out infinite alternate;
}

/* Duraciones primas entre sí (17/23/29 s) a propósito: así el conjunto no
   vuelve nunca a la misma postura y no se percibe el bucle. */
@keyframes fx-deriva-a{ from{translate:0 0}      to{translate:9vw 5vh} }
@keyframes fx-deriva-b{ from{translate:0 0}      to{translate:-7vw -6vh} }
@keyframes fx-deriva-c{ from{translate:0 0; scale:1} to{translate:4vw -8vh; scale:1.15} }

/* Quietas en táctil (batería) y con movimiento reducido. Y paradas del todo
   cuando el hero sale de pantalla: lo hace efectos.js con `fx-fuera`. */
@media (prefers-reduced-motion:reduce),(hover:none){
  [data-fx~="aula"] > .fx-mancha{animation:none}
}
[data-fx~="aula"].fx-fuera > .fx-mancha{animation-play-state:paused}


/* ============================================================================
   2 · FOCO-RATON — un foco cálido que persigue al cursor por la sección.
   ----------------------------------------------------------------------------
   El truco fino está en `@property`: al registrar --fx-x/--fx-y como
   <length-percentage>, el navegador SABE interpolarlas, así que una simple
   `transition` hace que la luz llegue con retraso al cursor en vez de
   pegarse a él. Ese retraso de 120 ms es todo el efecto: sin él parece un
   puntero, con él parece luz.

   Degradado limpio: sin @property las variables no interpolan y la luz sigue
   al cursor al instante — se ve algo peor, no se rompe nada. Sin JS, la luz
   se queda centrada arriba, que es una posición decente.
   ========================================================================= */
@property --fx-x{ syntax:"<length-percentage>"; inherits:true; initial-value:50%; }
@property --fx-y{ syntax:"<length-percentage>"; inherits:true; initial-value:0%; }

[data-fx~="foco"]{
  position:relative;
  isolation:isolate;
  transition:--fx-x .12s linear, --fx-y .12s linear;
}

[data-fx~="foco"]::before{
  content:"";
  position:absolute; inset:0; z-index:-1;
  pointer-events:none;
  border-radius:inherit;
  opacity:0;
  transition:opacity .4s ease;
  background:radial-gradient(420px circle at var(--fx-x) var(--fx-y),
             rgba(255,89,77,.16), transparent 65%);
  background:radial-gradient(420px circle at var(--fx-x) var(--fx-y),
             color-mix(in oklab, var(--fx-coral) 16%, transparent), transparent 65%);
}

/* Sólo se enciende cuando hay un ratón de verdad dentro. La clase la pone el
   JS al entrar; así en táctil no aparece nunca un foco que nadie mueve. */
[data-fx~="foco"].fx-foco-on::before{opacity:1}

@media (hover:none){ [data-fx~="foco"]::before{display:none} }
@media (prefers-reduced-motion:reduce){
  [data-fx~="foco"]{transition:none}
}


/* ============================================================================
   3 · TARJETA-HALO — tarjeta con halo y canto de luz que siguen al cursor.
   ----------------------------------------------------------------------------
   Es el efecto de rejilla más rentable: sirve para las 10 funciones de la app
   sin coste de atención. Sobre fondo claro el lavado va MUCHO más bajo que
   sobre oscuro (9 %) o el marfil se ensucia; el canto, en cambio, sube al
   65 %, porque sobre blanco un canto flojo no se ve. Medido en pantalla, no
   copiado del componente oscuro de la librería.

   Con `--fx-tono` por tarjeta el color del halo pasa a SIGNIFICAR algo: aquí
   se usa el mismo código de color de la app (verde = notas, lila = campus,
   naranja = estudio…), así la web y la app hablan el mismo idioma.
   ========================================================================= */
[data-fx~="halo"]{
  position:relative;
  --fx-tono:var(--fx-coral);
  --fx-radio:260px;
  border-radius:var(--fx-r-l);
  background:var(--fx-blanco);
  border:1px solid var(--fx-linea);
  box-shadow:var(--fx-sombra-m);
  transition:transform .35s var(--fx-curva), box-shadow .35s var(--fx-curva);
}
[data-fx~="halo"]:hover{ transform:translateY(-3px); box-shadow:var(--fx-sombra-l); }

/* Lavado interior. */
[data-fx~="halo"]::before{
  content:""; position:absolute; inset:0; z-index:0;
  border-radius:inherit; pointer-events:none;
  opacity:0; transition:opacity .28s ease;
  background:radial-gradient(var(--fx-radio) circle at var(--fx-mx,50%) var(--fx-my,0%),
             rgba(255,89,77,.09), transparent 62%);
  background:radial-gradient(var(--fx-radio) circle at var(--fx-mx,50%) var(--fx-my,0%),
             color-mix(in oklab, var(--fx-tono) 9%, transparent), transparent 62%);
}

/* Canto de luz: el mismo gradiente recortado a 1,5 px restando dos máscaras.
   El bloque -webkit-* va ANTES y con `xor` para los Safari viejos; el
   estándar `exclude` va después y gana donde se soporta. Sin máscara, el
   navegador simplemente pinta el gradiente completo… por eso lleva también
   `opacity:0` de partida y sólo se enciende en hover: si algún día la
   máscara fallara, se vería un halo, no un rectángulo naranja. */
[data-fx~="halo"]::after{
  content:""; position:absolute; inset:0; z-index:1;
  border-radius:inherit; pointer-events:none; padding:1.5px;
  opacity:0; transition:opacity .28s ease;
  background:radial-gradient(220px circle at var(--fx-mx,50%) var(--fx-my,0%),
             rgba(255,89,77,.65), transparent 58%);
  background:radial-gradient(220px circle at var(--fx-mx,50%) var(--fx-my,0%),
             color-mix(in oklab, var(--fx-tono) 65%, transparent), transparent 58%);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
          mask:linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}

[data-fx~="halo"]:hover::before, [data-fx~="halo"]:focus-within::before,
[data-fx~="halo"]:hover::after,  [data-fx~="halo"]:focus-within::after{opacity:1}

/* Todo hijo directo por encima de los dos pseudoelementos. */
[data-fx~="halo"] > *{position:relative; z-index:2}

/* Tonos listos, uno por mundo de la app. */
[data-fx~="halo"][data-fx-tono="verde"]  {--fx-tono:var(--fx-verde)}
[data-fx~="halo"][data-fx-tono="lila"]   {--fx-tono:var(--fx-lila)}
[data-fx~="halo"][data-fx-tono="naranja"]{--fx-tono:var(--fx-naranja)}

@media (prefers-reduced-motion:reduce){
  [data-fx~="halo"],[data-fx~="halo"]::before,[data-fx~="halo"]::after{transition:none}
  [data-fx~="halo"]:hover{transform:none}
}


/* ============================================================================
   4 · MOVIL-3D — el iPhone se inclina hacia el cursor, con brillo y capas.
   ----------------------------------------------------------------------------
   El efecto NO es la inclinación: es que el marco, la pantalla y lo que
   flota alrededor se muevan cantidades distintas (`translateZ`). Sin eso se
   mueve un cartón plano.

   ⚠ Las capturas YA traen su barra de estado y su isla dinámica pintadas
   (mira cualquier /capturas/*.jpg): no hay que dibujar una encima. El marco
   es sólo el bisel.
   ========================================================================= */
[data-fx~="movil3d"]{
  --fx-giro:9deg;               /* por encima de 14 marea */
  perspective:1100px;           /* va en el CONTENEDOR; en el hijo no hace nada */
  display:inline-block;
}

[data-fx~="movil3d"] .fx-cara{
  position:relative;
  transform-style:preserve-3d;  /* va en el HIJO */
  transform:rotateX(var(--fx-rx,0deg)) rotateY(var(--fx-ry,0deg));
  transition:transform .25s var(--fx-curva);
}
/* Persigue rápido, vuelve despacio: eso es lo que lo hace parecer físico. */
[data-fx~="movil3d"]:not(:hover) .fx-cara{transition-duration:.7s}

/* El bisel. `aspect-ratio` fijo + width/height en el <img> = cero saltos de
   maqueta mientras cargan las capturas. */
[data-fx~="movil3d"] .fx-marco{
  position:relative;
  width:min(280px,72vw);
  aspect-ratio:460/956;
  padding:9px;
  border-radius:44px;
  background:linear-gradient(160deg,#2A2F3C,#161A24 40%,#0E1119);
  box-shadow:0 30px 70px rgba(22,26,36,.22), 0 2px 0 rgba(255,255,255,.18) inset;
}
/* Con <picture> el hijo directo del marco es el <picture>, no el <img>: sin
   esto el height:100% del img no tiene contra qué medirse. */
[data-fx~="movil3d"] .fx-marco picture{display:block;width:100%;height:100%}
[data-fx~="movil3d"] .fx-marco img{
  display:block; width:100%; height:100%;
  object-fit:cover;
  border-radius:36px;
  background:var(--fx-marfil);     /* color mientras carga: nada de gris feo */
}

/* Brillo especular: una banda de luz que barre la pantalla en sentido
   CONTRARIO a la inclinación. Es lo que convence de que hay cristal. */
[data-fx~="movil3d"] .fx-marco::after{
  content:""; position:absolute; inset:9px;
  border-radius:36px; pointer-events:none;
  background:linear-gradient(105deg, transparent 35%,
             rgba(255,255,255,.38) 48%, transparent 62%);
  opacity:0; transition:opacity .3s ease;
  translate:calc((var(--fx-mxn, .5) - .5) * -46%) 0;
}
[data-fx~="movil3d"]:hover .fx-marco::after{opacity:1}

/* Profundidades: escalona lo que flote alrededor (la señora, una pastilla de
   texto, un icono). translateZ necesita el preserve-3d del padre. */
[data-fx~="movil3d"] [data-fx-z="1"]{transform:translateZ(30px)}
[data-fx~="movil3d"] [data-fx-z="2"]{transform:translateZ(60px)}
[data-fx~="movil3d"] [data-fx-z="3"]{transform:translateZ(95px)}

/* En táctil el dedo tapa justo el móvil: se queda plano y quieto, que es lo
   correcto. El JS ni siquiera se engancha (ver efectos.js). */
@media (hover:none),(prefers-reduced-motion:reduce){
  [data-fx~="movil3d"] .fx-cara{transform:none}
  [data-fx~="movil3d"] [data-fx-z]{transform:none}
  [data-fx~="movil3d"] .fx-marco::after{display:none}
}


/* ============================================================================
   5 · REVELAR — el contenido entra al asomar por el borde de la pantalla.
   ----------------------------------------------------------------------------
   Dos caminos, y el orden importa:

     a) `animation-timeline: view()` — scroll-driven NATIVO. Cero JS, cero
        IntersectionObserver, el navegador lo resuelve fuera del hilo
        principal y va ligado a la posición real del scroll (si subes, se
        deshace solo).
     b) IntersectionObserver — para quien no lo tenga. Lo activa el JS
        poniendo la clase `fx-io` en <html>.

   Y el caso c), que es el que suele olvidarse: **ni lo uno ni lo otro**
   (JS caído, navegador viejo). Entonces no hay ninguna regla que ponga
   `opacity:0` y el contenido se ve tal cual. Por eso el estado de partida
   invisible vive DENTRO del @supports y DENTRO de `.fx-io`, nunca suelto.
   ========================================================================= */

/* (a) camino nativo */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    [data-fx~="revelar"]{
      animation:fx-entra linear both;
      animation-timeline:view();
      /* Empieza cuando asoma un 8 % y termina bastante antes de estar
         centrado: si se espera al centro, el usuario ve la animación
         terminar sobre algo que lleva rato mirando y parece que llega tarde. */
      animation-range:entry 8% cover 30%;
    }
  }
}

/* (b) camino IntersectionObserver: sólo si <html class="fx-io"> */

/* ⚠ TODO el estado invisible vive dentro de `no-preference`. Sin esta guarda,
   alguien con movimiento reducido y sin scroll-driven se encuentra la página
   EN BLANCO: el CSS lo esconde y el JS (que no anima nada en ese modo) no lo
   enseña nunca. Pasó al probarlo con --force-prefers-reduced-motion, y es el
   fallo que se lleva la mitad de las landings con animaciones. */
@media (prefers-reduced-motion: no-preference){
.fx-io [data-fx~="revelar"]{
  opacity:0;
  translate:0 var(--fx-desde,20px);
  transition:opacity .75s var(--fx-curva), translate .75s var(--fx-curva);
  transition-delay:var(--fx-retardo,0ms);
  will-change:opacity,translate;
}
.fx-io [data-fx~="revelar"].fx-dentro{
  opacity:1; translate:0 0;
  will-change:auto;             /* soltarlo al acabar: mantenerlo gasta memoria */
}
.fx-io [data-fx~="revelar"][data-fx-desde="izquierda"]{translate:-26px 0}
.fx-io [data-fx~="revelar"][data-fx-desde="derecha"]  {translate:26px 0}
.fx-io [data-fx~="revelar"][data-fx-desde="escala"]   {translate:0 0; scale:.96}
.fx-io [data-fx~="revelar"][data-fx-desde="escala"].fx-dentro{scale:1}
}

@keyframes fx-entra{
  from{opacity:0; translate:0 22px}
  to  {opacity:1; translate:0 0}
}


/* ============================================================================
   6 · TEXTO-LEE — el titular se enciende palabra a palabra CON el scroll.
   ----------------------------------------------------------------------------
   No es "aparece al entrar": las palabras se van encendiendo conforme
   arrastras la rueda, y si subes se apagan. Eso lo da el scroll-driven
   nativo; el escalonado sale de `--i` (el índice de palabra) metido en el
   `animation-range`, no de un `animation-delay` — con una timeline de scroll,
   el retardo se expresa en distancia recorrida, no en segundos.

   Las palabras las parte el JS. Por tanto: sin JS, el titular está en el HTML
   tal cual y se lee perfecto (y Google lo indexa igual, porque el texto
   siempre estuvo ahí: el JS lo trocea, no lo inventa).
   ========================================================================= */
.fx-pal{
  display:inline-block;
  white-space:pre;   /* imprescindible: en inline-block el espacio final se
                        colapsa y las palabras salen pegadas */
}

/* (a) nativo: encendido ligado al scroll */
@supports (animation-timeline: view()){
  @media (prefers-reduced-motion: no-preference){
    .fx-sd [data-fx~="texto"] .fx-pal{
      opacity:.18;
      animation:fx-enciende linear both;
      animation-timeline:view();
      /* Cada palabra termina un poco más tarde que la anterior. El tope de
         14 pasos evita que en un párrafo largo la última no llegue nunca. */
      animation-range:entry 20% cover calc(24% + min(var(--i,0),14) * 2.4%);
    }
  }
}

/* (b) fallback: aparece por tiempo al entrar en pantalla.
   Misma guarda que arriba y por el mismo motivo: con movimiento reducido las
   palabras deben nacer visibles, no esperar a un observador que no va a correr. */
@media (prefers-reduced-motion: no-preference){
.fx-io [data-fx~="texto"] .fx-pal{
  opacity:0;
  filter:blur(8px);
  transition:opacity .5s ease, filter .5s ease;
  transition-delay:calc(min(var(--i,0),14) * 55ms);
}
.fx-io [data-fx~="texto"].fx-dentro .fx-pal{opacity:1; filter:blur(0)}
}

@keyframes fx-enciende{
  from{opacity:.18; filter:blur(4px)}
  to  {opacity:1;   filter:blur(0)}
}

/* Una palabra en coral dentro del titular: <b class="fx-marca">Rotermeller</b> */
.fx-marca{color:var(--fx-coral); font-weight:800}


/* ============================================================================
   7 · MARQUESINA — las 14 capturas pasando en bucle, sin corte ni salto.
   ----------------------------------------------------------------------------
   El bucle es SÓLO CSS. El JS se limita a duplicar la fila una vez (y a
   marcar la copia con aria-hidden, para que un lector de pantalla no lea
   catorce capturas dos veces). Nada de requestAnimationFrame: una landing
   que calienta el teléfono es una landing mala.

   Dos filas en sentidos opuestos y a distinta velocidad es lo que hace que
   parezca una vitrina y no una cinta transportadora.
   ========================================================================= */
[data-fx~="marquesina"]{
  --fx-vel:46s;
  --fx-hueco:1.1rem;
  overflow:hidden;
  /* Sin este desvanecido lateral las capturas aparecen y desaparecen de golpe
     contra el canto y se ve el truco. */
  -webkit-mask:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
          mask:linear-gradient(90deg,transparent,#000 7%,#000 93%,transparent);
}
[data-fx~="marquesina"] .fx-pista{
  display:flex;
  gap:var(--fx-hueco);
  width:max-content;
  animation:fx-desliza var(--fx-vel) linear infinite;
}
[data-fx~="marquesina"][data-fx-sentido="derecha"] .fx-pista{animation-direction:reverse}

/* Se para al pasar el ratón o al entrar el foco con el tabulador: si hay algo
   que mirar, moverlo mientras lo miran es la forma segura de que no lo miren. */
[data-fx~="marquesina"]:hover .fx-pista,
[data-fx~="marquesina"]:focus-within .fx-pista{animation-play-state:paused}

/* Y con el botón de pausa (que es lo que pide WCAG 2.2.2: el hover no vale
   con el dedo ni con el teclado) o cuando la sección no está en pantalla. */
[data-fx~="marquesina"].fx-parada .fx-pista,
[data-fx~="marquesina"].fx-fuera  .fx-pista{animation-play-state:paused}

/* -50 % y no -100 %: el contenido está duplicado, así que media vuelta deja la
   fila exactamente donde empezó. Si cambias una cosa, cambia la otra. */
@keyframes fx-desliza{
  from{transform:translateX(0)}
  to  {transform:translateX(calc(-50% - var(--fx-hueco) / 2))}
}

[data-fx~="marquesina"] .fx-mini{
  flex:0 0 auto;
  width:clamp(190px,20vw,250px);
  aspect-ratio:460/956;
  padding:7px;
  border-radius:36px;
  background:linear-gradient(160deg,#2A2F3C,#161A24 40%,#0E1119);
  box-shadow:0 18px 40px rgba(22,26,36,.18), 0 2px 0 rgba(255,255,255,.18) inset;
}
[data-fx~="marquesina"] .fx-mini picture{display:block;width:100%;height:100%}
[data-fx~="marquesina"] .fx-mini img{display:block;width:100%;height:100%;object-fit:cover;
  border-radius:29px;background:var(--fx-marfil)}

@media (prefers-reduced-motion:reduce){
  /* Quieto y con arrastre manual, que sigue siendo usable con dedo y teclado.
     Nada de pararlo dejando media captura cortada. */
  [data-fx~="marquesina"] .fx-pista{animation:none}
  [data-fx~="marquesina"]{overflow-x:auto;-webkit-mask:none;mask:none}
}


/* ============================================================================
   8 · RELATO-FIJO — el móvil se queda quieto y cambia de pantalla al bajar.
   ----------------------------------------------------------------------------
   El mejor uso posible de 14 capturas reales: en vez de una parrilla que
   nadie mira, el teléfono se queda fijo a un lado y va enseñando la pantalla
   de la función que estás leyendo.

   El cambio de imagen usa View Transitions si existen (crossfade real del
   navegador, sin duplicar nodos); si no, un fundido con dos capas; si no hay
   JS, se queda la primera captura y la lista de funciones se lee entera. Las
   tres versiones son aceptables.
   ========================================================================= */
[data-fx~="relato"]{
  display:grid;
  grid-template-columns:minmax(0,540px) min(340px,34vw);
  justify-content:space-between;
  gap:clamp(2rem,4vw,3.5rem);
  align-items:start;
}
/* Container query: si la sección es estrecha (no la ventana — la SECCIÓN),
   el móvil se va arriba y se queda pegado bajo la cabecera. */
@container (max-width: 780px){
  [data-fx~="relato"]{grid-template-columns:1fr}
}
@media (max-width:780px){
  [data-fx~="relato"]{grid-template-columns:1fr}
  [data-fx~="relato"] .fx-relato-movil{order:-1}
}

/* Centrado por la ALTURA REAL del marco (ancho × 958/460 + 18 px de bisel),
   no por un número a ojo: con 320 px el móvil quedaba 40 px alto y se notaba
   como una descompensación difusa. */
[data-fx~="relato"] .fx-relato-movil{
  position:sticky;
  --fx-alto-movil:calc(min(340px,34vw) * 956 / 460 + 18px);
  top:max(5rem, calc(50vh - var(--fx-alto-movil) / 2));
}

[data-fx~="relato"] .fx-paso{
  min-height:min(62vh,520px);
  display:flex; flex-direction:column; justify-content:center;
  padding-block:1.5rem;
  transition:opacity .45s ease;
}
/* El atenuado SÓLO cuando el JS ha podido engancharse (pone `fx-relato-listo`
   al montar). Si no carga, ningún paso se activaría nunca y la sección entera
   se quedaría permanentemente ilegible. Y .68 en vez de .42: a .42 el cuerpo
   daba 1,78:1 sobre marfil, que no es «atenuado», es invisible. */
.fx-relato-listo [data-fx~="relato"] .fx-paso{opacity:.68}
.fx-relato-listo [data-fx~="relato"] .fx-paso.fx-activo{opacity:1}

/* Fundido de la captura cuando NO hay View Transitions. */
[data-fx~="relato"] .fx-marco img{transition:opacity .3s ease}
[data-fx~="relato"] .fx-marco img.fx-cambiando{opacity:0}

/* Crossfade nativo con View Transitions. El nombre debe ser único en la
   página: si hubiera dos elementos con el mismo `view-transition-name`
   activos a la vez, la transición se cancela entera y no se ve nada. */
/* quitado: `view-transition-name` tiene que ser único en toda la página y
   esta regla se lo ponía a TODAS las capturas de los relatos. El navegador
   encontraba varias iguales, abortaba la transición («Transition was aborted
   because of invalid state») y la página se quedaba a medio pintar: no se
   veía ninguna captura. */
::view-transition-old(fx-pantalla),
::view-transition-new(fx-pantalla){animation-duration:.32s}

@media (prefers-reduced-motion:reduce){
  .fx-relato-listo [data-fx~="relato"] .fx-paso{opacity:1}
  [data-fx~="relato"] .fx-paso{transition:none}
  [data-fx~="relato"] .fx-marco img{transition:none}
}
/* En estrecho el móvil pegajoso desaparece y cada paso enseña su propia
   captura (ver estilo.css): ahí no hay nada que la mirada active, así que
   atenuar la mitad de la pantalla sólo parece contenido deshabilitado. */
@media (max-width:900px){
  .fx-relato-listo [data-fx~="relato"] .fx-paso{opacity:1}
}


/* ============================================================================
   EXTRA A · BORDE-VIVO — un canto de luz que gira. Para UNA sola pieza.
   ----------------------------------------------------------------------------
   Aquí, la tarjeta del Tutor: es la función que diferencia la app, y este es
   el subrayado. Es un subrayado, no una decoración: si brillan tres cosas no
   destaca ninguna y pasa de caro a hortera.

   CÓMO ESTÁ HECHO, y por qué NO como se suele hacer. La receta clásica es una
   capa cuadrada del 180 % girando por debajo, tapada por otra capa del color
   del fondo. Tiene dos trampas caras: la capa gigante alarga el scroll
   horizontal de toda la página si olvidas el `overflow:hidden`, y la tapa
   interior deja un recuadro blanco en cuanto el fondo real de la sección no
   es exactamente el color que le pusiste.

   Aquí no gira nada: gira el ÁNGULO del cónico, registrado con `@property`
   para que el navegador sepa interpolarlo. Sin capa gigante, sin tapa, sin
   `overflow:hidden` y sin color de fondo que acertar — el canto se recorta
   restando dos máscaras y por el centro se ve lo que haya detrás.

   Degradado limpio: sin `@property` el ángulo no anima y queda un borde
   degradado fijo, que sigue siendo un buen borde. Nada se rompe.
   ========================================================================= */
@property --fx-ang{ syntax:"<angle>"; inherits:false; initial-value:0deg; }

[data-fx~="bordevivo"]{
  --fx-grosor:1.5px;
  --fx-vel:5s;
  position:relative;
  border-radius:var(--fx-r-xl);
  background:var(--fx-blanco);
  box-shadow:var(--fx-sombra-m);
}
[data-fx~="bordevivo"]::before{
  content:""; position:absolute; inset:0;
  border-radius:inherit; pointer-events:none;
  padding:var(--fx-grosor);
  background:conic-gradient(from var(--fx-ang),
             transparent 0deg, var(--fx-coral) 60deg, var(--fx-naranja) 105deg, transparent 190deg);
  /* El bloque -webkit-* va antes y con `xor`, para los Safari anteriores a
     `mask-composite`; el estándar `exclude` va después y gana donde existe. */
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
          mask:linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  animation:fx-gira var(--fx-vel) linear infinite;
}
@keyframes fx-gira{ to{--fx-ang:360deg} }

/* Sobre fondo de color, basta cambiar el relleno de la tarjeta. */
[data-fx~="bordevivo"][data-fx-relleno="marfil"]{background:var(--fx-marfil)}

@media (prefers-reduced-motion:reduce){
  /* Canto fijo: sigue señalando la pieza, sin girar. */
  [data-fx~="bordevivo"]::before{animation:none}
}


/* ============================================================================
   EXTRA B · BOTON-IMAN — el CTA se acerca un poco al cursor.
   ----------------------------------------------------------------------------
   Dos o tres en toda la web, y nunca en un formulario: un botón que se mueve
   justo cuando vas a pulsarlo es molesto de verdad.
   ========================================================================= */
[data-fx~="iman"]{
  display:inline-flex; align-items:center; gap:.55rem;
  padding:.95rem 1.7rem;
  border:0; cursor:pointer;
  border-radius:var(--fx-r-cap);
  background:var(--coral-boton,var(--fx-coral));
  color:#fff; font:inherit; font-weight:700; font-size:1.02rem;
  box-shadow:0 8px 22px rgba(255,89,77,.28);
  translate:var(--fx-ix,0) var(--fx-iy,0);
  transition:translate .35s var(--fx-curva), background .2s ease, box-shadow .3s ease;
}
[data-fx~="iman"]:hover{background:var(--coral-boton-osc,var(--fx-coral-osc)); box-shadow:0 12px 28px rgba(255,89,77,.34)}
/* Foco visible SIEMPRE: es el CTA principal y tiene que verse con teclado. */
[data-fx~="iman"]:focus-visible{outline:3px solid var(--fx-tinta); outline-offset:3px}
[data-fx~="iman"]:active{translate:0 1px}

@media (hover:none),(prefers-reduced-motion:reduce){
  [data-fx~="iman"]{translate:0 0 !important; transition:background .2s ease}
}
