/* ⚠ CORREGIDO AL INTEGRAR (17-ago) · Los 18 guardianes de este archivo
   estaban escritos `:not(:where(#ids))`, que evalúa el PROPIO elemento —
   y como el elemento nunca tiene el id de la sección, el guardián no
   guardaba nada: las reglas entraban en t03, t04b, f01 y f02, que son las
   cuatro que no se tocan. Ahora la lista incluye también `#id *`, o sea
   los descendientes. Lo detectó el agente revisor de colisiones. */

/* ╔═══════════════════════════════════════════════════════════════════════════╗
   ║  RE-ANIMACIÓN · vocabulario de movimiento y puntos de detención           ║
   ║  Tu Plantilla v2 · 17-ago-2026                                            ║
   ║                                                                           ║
   ║  CARGA: ÚLTIMO de todos los CSS (después de escala.css y re-telefono.css,  ║
   ║  que index.html ya enlaza en las líneas 40 y 41). Varias reglas de aquí    ║
   ║  ganan por orden, no por especificidad inflada: si se carga antes, pierde. ║
   ║                                                                           ║
   ║  ── EL PROBLEMA MEDIDO ──────────────────────────────────────────────────  ║
   ║  1) En index.html hay 44 `data-reveal`: 43 dicen "up" y 1 dice "scale".    ║
   ║     Es literalmente UNA sola entrada para toda la página. base.css:350     ║
   ║     la define: translateY(56px) scale(.975) → none, .85s, misma curva.     ║
   ║  2) Las 13 cabeceras `.sec-head` traen ADEMÁS el corte palabra a palabra   ║
   ║     que core.js:45-63 inyecta solo (parte el texto en <span class="aw">).  ║
   ║     Resultado: cada sección arranca con DOS movimientos que dicen lo       ║
   ║     mismo — el bloque sube 56px y encima las palabras suben .55em.         ║
   ║  3) En 21 pantallas de scroll lo único que se queda quieto es              ║
   ║     `.z01-tabs .tab-rail` (sections.css:1122). Nada más se detiene, y por  ║
   ║     eso la página se lee como un rollo continuo: no hay respiraciones.     ║
   ║                                                                           ║
   ║  ── EL CRITERIO ────────────────────────────────────────────────────────── ║
   ║  · Cada COSA se mueve como lo que es, no como "un bloque más":             ║
   ║    un titular se revela tras una máscara, una imagen se abre, un trazo se  ║
   ║    dibuja, una nota al pie solo aparece, un precio sube.                   ║
   ║  · El ritmo cambia por ACTO, usando lo que el HTML ya declara              ║
   ║    (data-theme="light|dark|blue"): los actos claros entran cortos y        ║
   ║    firmes; los actos póster (negro/azul) entran largos y cinematográficos. ║
   ║  · Dos puntos de detención nuevos (position:sticky) + el que ya existía.   ║
   ║  · Todo dispara por SCROLL (clase .is-shown/.is-in de core.js o timeline   ║
   ║    de scroll). Ni un efecto vive en :hover — en teléfono no existiría.     ║
   ║                                                                           ║
   ║  ── LO QUE NO SE TOCA (y por qué) ────────────────────────────────────────  ║
   ║  · #t03-sietedias · #t04b-chat · #f01-como · #f02-calc: calibrados a mano  ║
   ║    con el cliente. Ni ellos ni sus hijos. Van excluidos con el "guardián"  ║
   ║    :not(:where(#…)) que aparece en cada regla global de este archivo.      ║
   ║  · #c01-hero y #z02-cierre: su coreografía vive en sections.js             ║
   ║    (clase .c01-in y los .z02-w palabra a palabra). Además ninguno de los   ║
   ║    dos usa `.sec-head` (el hero no tiene header; el cierre usa .z02-head), ║
   ║    así que los selectores de aquí ya no los alcanzan solos.                ║
   ║  · Todo lo que entrances.js "se lleva" con take(): .t01-card, .t01-chips,  ║
   ║    .t02-sello-wrap, .t02-pasos, .t02-paso-num, .t02-compromisos,           ║
   ║    .t05-grid/.t05-card, .f03-ancla/.f03-grid/.f03-card/.f03-bonos/         ║
   ║    .f03-condiciones, .f04-cards/.f04-wrap, .f05-banda, .z01-tabs.          ║
   ║    GSAP escribe transform EN LÍNEA sobre ellos: un transform mío ahí no    ║
   ║    "compite", simplemente se pierde. Cuando necesito animar algo que vive  ║
   ║    dentro de un envoltorio tomado por GSAP, uso el truco del hermano:      ║
   ║    `.sec-head.is-shown ~ .lo-que-sea` — la cabecera SÍ conserva su         ║
   ║    data-reveal (take() nunca la toca) y sirve de gatillo CSS gratis.       ║
   ╚═══════════════════════════════════════════════════════════════════════════╝ */


/* ═══════════════════════════════════════════════════════════════════════════
   0 · AUDITORÍA DE ANCESTROS PARA position:sticky Y PARA view()
   ───────────────────────────────────────────────────────────────────────────
   Antes de proponer un solo sticky verifiqué la cadena completa, porque en
   este proyecto ya nos costó caro: un overflow mal puesto mata el sticky sin
   dar ningún error visible.

   · body{overflow-x:hidden} (base.css:101) NO lo mata. Parece que sí, pero
     <html> tiene overflow:visible, y en un documento HTML el overflow del
     BODY se propaga al viewport mientras la raíz sea visible: el body queda
     tratado como visible y NO es contenedor de scroll. El scrollport sigue
     siendo el viewport → sticky vive.
   · main{overflow-x:clip} (sections.css:1189) tampoco lo mata, y esto sí es
     suerte medida: `clip` es el único valor que recorta sin crear contenedor
     de scroll (y no obliga al otro eje a volverse auto). Si alguien lo cambia
     a `hidden` para "arreglar" un desborde, mueren de golpe los tres stickies
     de la página, incluido el rail del FAQ que ya funcionaba.
   · SÍ lo matan, y por eso NO hay ningún sticky dentro de ellos:
       #c02-marquee, #t03-sietedias, #z02-cierre, #t02-garantia
       (sections.css:1190) y #t06-portafolio (sections.css:1465) → overflow:hidden.
       .window-card (base.css:221) y .t01-poster (sections.css:146) → idem.
       .table-wrap (base.css:342) → overflow-x:auto, y con un eje en auto el
       otro deja de ser visible: es contenedor de scroll en vertical también.
     Quería colgar el sello de t02 y el portafolio de un timeline de scroll y
     NO SE PUEDE: `view()` se engancha al scrollport más cercano, que ahí es
     una sección que nunca scrollea → el progreso queda congelado y el efecto
     se quedaría a medio camino PARA SIEMPRE (con el riesgo de dejar contenido
     invisible). Por eso en esas zonas el gatillo es de clase, no de scroll.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ═══════════════════════════════════════════════════════════════════════════
   1 · CURVAS Y TIEMPOS — el ritmo por acto
   Sin colores nuevos: la paleta ya está cerrada (crema #f4efe6, azul #2440ff,
   noche #060608). Lo único que se agrega aquí es tiempo.
   Las distancias salen de la escala --s1..--s8 de escala.css: si un movimiento
   recorre 23px y el espaciado de al lado mide 20, se nota como descuido.
   ═══════════════════════════════════════════════════════════════════════════ */
:root{
  /* Salida fuerte con frenada larga: es la que hace que una máscara se sienta
     "revelada" y no "movida". */
  --tpra-mask:cubic-bezier(.22,.68,0,1);
  /* Acto claro: corto y firme. Acto póster: largo, con más aire. */
  --tpra-t-claro:.52s;
  --tpra-t-poster:.82s;
  --tpra-firme:cubic-bezier(.2,.9,.3,1.06);
  /* Escalón entre hermanos. 55ms es el que ya usa core.js:55 para las palabras;
     lo reuso para que la cascada de la página sea UNA sola. */
  --tpra-paso:.055s;
}


/* ═══════════════════════════════════════════════════════════════════════════
   2 · TITULARES · dos maneras de revelarse, una por temperatura de acto
   ───────────────────────────────────────────────────────────────────────────
   core.js:45 parte el titular en <span class="aw"> palabra por palabra, salvo
   dos casos que hay que respetar: si el h2 contiene .scribble (t06 y cierre)
   o si tiene menos de 2 / más de 14 palabras (core.js:49). Por eso el h2 de
   t01 —"Hola, somos Tu Plantilla…", 16 palabras— NO se parte: para esos va la
   variante :not(.anim-words) más abajo.

   POR QUÉ ASÍ Y NO CON UN <span> ENVOLVENTE POR LÍNEA: la máscara "de verdad"
   (caja quieta + texto que sube por dentro) necesita un envoltorio por línea, y
   aquí no puedo tocar el HTML. La alternativa honesta es recortar cada palabra
   con clip-path SIN moverla: como el elemento no se desplaza, el borde del
   recorte queda fijo en pantalla y se lee exactamente como una máscara — y al
   ser una caja por palabra, el corte respeta los saltos de línea solo, que es
   justo lo que falla cuando se recorta el bloque entero.
   ⚠ Es incompatible mezclarlo con translate en el MISMO elemento: el recorte
   viaja con el transform y la máscara deja de estar quieta. Se elige uno.
   ═══════════════════════════════════════════════════════════════════════════ */

/* 2.1 · ACTOS CLAROS (data-theme="light") — la palabra emerge desde su base */
html.js
  :is(.section,.subsec)[data-theme="light"]:not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .anim-words .aw{
  opacity:0;
  transform:none;                      /* anula el translateY(.55em) rotate(2deg) de base.css:128 */
  clip-path:inset(100% 0 0 0);         /* alto cero, borde apoyado en la línea base */
}
html.js
  :is(.section,.subsec)[data-theme="light"]:not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .anim-words.is-in .aw{
  opacity:1;
  /* Holgura negativa: clip-path recorta la caja del inline-block y las
     ascendentes/descendentes (la "g" de página) sobresalen de ella. Con 0 se
     les come la cola. Valor explícito, no variable: el estado final de una
     animación no puede depender de que el navegador reevalúe una var. */
  clip-path:inset(-28% -6% -28% -6%);
  transition:
    clip-path var(--tpra-t-claro) var(--tpra-mask),
    opacity calc(var(--tpra-t-claro) * .7) linear;
  /* el escalonado por palabra ya viene en línea desde core.js:55 */
}

/* 2.2 · ACTOS PÓSTER (dark y blue) — la palabra se endereza desde el suelo.
   Aquí sí conviene transform: sobre negro un recorte se ve como un parpadeo,
   mientras que el giro sobre la línea base da la sensación de letra de cartel
   levantándose. La perspectiva va DENTRO del transform de cada palabra (y no
   como `perspective` en el h2) para que cada una tenga su propio punto de
   fuga: con uno compartido, las palabras del borde salen torcidas. */
html.js
  :is(.section,.subsec):is([data-theme="dark"],[data-theme="blue"]):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .anim-words .aw{
  opacity:0;
  transform-origin:50% 100%;
  transform:perspective(620px) rotateX(-82deg) translateY(var(--s1));
}
html.js
  :is(.section,.subsec):is([data-theme="dark"],[data-theme="blue"]):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .anim-words.is-in .aw{
  opacity:1;
  transform:perspective(620px) rotateX(0deg) translateY(0);
  transition:
    transform var(--tpra-t-poster) var(--tpra-firme),
    opacity calc(var(--tpra-t-poster) * .5) linear;
}

/* 2.3 · Titulares que core.js NO parte (t01 por largo, t06 por el .scribble).
   Un solo bloque de texto: recorte de abajo hacia arriba, sin mover nada. */
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head .h-display:not(.anim-words){
  clip-path:inset(100% 0 0 0);
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head.is-shown .h-display:not(.anim-words){
  clip-path:inset(-14% -4% -14% -4%);
  transition:clip-path .78s var(--tpra-mask) .06s;
}
/* ⚠ CHOQUE CONOCIDO: en #t06-portafolio el titular lleva <span class="scribble">
   y su SVG se sale de la caja (base.css:171, inset:-22% -10%). Con -14%/-4% de
   holgura el garabato entra justo; si alguien agranda ese scribble, lo primero
   que se ve mal es que el trazo aparece cortado por arriba. No es el scribble:
   es esta holgura. */


/* ═══════════════════════════════════════════════════════════════════════════
   3 · LA CABECERA DEJA DE SUBIR EN BLOQUE
   El movimiento se reparte entre sus piezas (barra del kicker, palabras,
   bajada) en vez de aplicarse al contenedor. Se va el movimiento doble y, de
   paso, se va la sensación de que las 9 secciones arrancan igual.
   Solo opacidad en el contenedor → nada de reflow, nada de capa nueva.
   ═══════════════════════════════════════════════════════════════════════════ */
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head[data-reveal="up"]{
  transform:none;
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head[data-reveal="up"].is-shown{
  transform:none;
  transition:opacity .5s linear;
}

/* 3.1 · La barra azul del kicker se DIBUJA (base.css:160 ya la define:
   18px × 2px, background var(--azul), border-radius 2px). Es el trazo más
   barato de la página y aparece 9 veces: anuncia la sección antes del texto. */
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head .kicker::before{
  transform:scaleX(0);
  transform-origin:0 50%;
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head.is-shown .kicker::before{
  transform:scaleX(1);
  transition:transform .42s var(--tpra-firme);
}

/* 3.2 · La bajada llega DESPUÉS del titular, no junto con él.
   Distancia = --s2 (8px): es un texto de apoyo, no un bloque que aterriza. */
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head .sub{
  opacity:0;
  transform:translateY(var(--s2));
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .sec-head.is-shown .sub{
  opacity:1;
  transform:none;
  transition:opacity .5s linear .26s,transform .5s var(--tpra-firme) .26s;
}


/* ═══════════════════════════════════════════════════════════════════════════
   4 · CADA PIEZA SEGÚN SU PESO (el resto de los 44 data-reveal)
   No hace falta inventar efectos: basta con que una nota al pie no entre como
   entra una tarjeta. La distancia la fija la escala.
   ═══════════════════════════════════════════════════════════════════════════ */

/* 4.1 · Notas al pie: solo aparecen. Un texto de .8rem que recorre 56px se ve
   como un error de maquetación, no como una entrada. */
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .note[data-reveal="up"]{
  transform:none;
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .note[data-reveal="up"].is-shown{
  transform:none;
  transition:opacity .6s linear;
}

/* 4.2 · Bandas de CTA: el bloque no se mueve, se mueven los botones, uno tras
   otro. Es el único sitio donde el escalonado se nota como intención.
   OJO: .btn ya trae transition de transform .25s para su :hover (base.css:179).
   Al declarar aquí la transición de entrada, el hover sigue funcionando porque
   ambas terminan en transform:none / translateY(-2px) sin pisarse. */
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .cta-band[data-reveal="up"]{
  transform:none;
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .cta-band[data-reveal="up"] > *{
  opacity:0;
  transform:translateY(var(--s4));
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .cta-band[data-reveal="up"].is-shown{
  transform:none;
  transition:opacity .4s linear;
}
html.js
  :is(.section,.subsec):not(:where(#t03-sietedias,#t04b-chat,#f01-como,#f02-calc,#t03-sietedias *,#t04b-chat *,#f01-como *,#f02-calc *))
  .cta-band[data-reveal="up"].is-shown > *{
  opacity:1;
  transform:translateY(0);
  transition:opacity .45s linear,transform .55s var(--tpra-firme);
}
html.js .cta-band[data-reveal="up"].is-shown > *:nth-child(2){transition-delay:calc(var(--tpra-paso) * 2)}
html.js .cta-band[data-reveal="up"].is-shown > *:nth-child(3){transition-delay:calc(var(--tpra-paso) * 4)}


/* ═══════════════════════════════════════════════════════════════════════════
   5 · UN TRAZO QUE SE DIBUJA DE VERDAD · el sello 50/50 de t02
   ───────────────────────────────────────────────────────────────────────────
   index.html:535-536 tiene dos <circle> reales dentro de .t02-sello:
       r="96" stroke="#09090e"   → perímetro 2π·96 = 603,19
       r="58" stroke="#2440ff"   → perímetro 2π·58 = 364,42
   Redondeo a 604 y 365 (un pelo de más, para que el trazo quede oculto del
   todo antes de dibujarse). El anillo exterior está re-entintado a crema en
   sections.css:203 porque t02 vive en acto-dark: no lo toco, solo el guion.
   GATILLO: hermano de la cabecera. #t02-garantia tiene overflow:hidden
   (sections.css:1190) → un timeline de scroll aquí quedaría congelado, y
   .t02-sello-wrap se la llevó entrances.js con take(): la cabecera es el
   único gancho limpio que queda.
   El texto que gira (.t02-sello-giro, animación t02spin 24s) no se toca.
   ═══════════════════════════════════════════════════════════════════════════ */
html.js #t02-garantia .t02-sello circle{
  stroke-dasharray:604;
  stroke-dashoffset:604;
}
html.js #t02-garantia .t02-sello circle:nth-of-type(2){
  stroke-dasharray:365;
  stroke-dashoffset:365;
}
html.js #t02-garantia .sec-head.is-shown ~ .t02-layout .t02-sello circle{
  stroke-dashoffset:0;
  transition:stroke-dashoffset 1.15s cubic-bezier(.6,0,.2,1) .2s;
}
html.js #t02-garantia .sec-head.is-shown ~ .t02-layout .t02-sello circle:nth-of-type(2){
  transition-delay:.42s;   /* el anillo azul entra detrás del negro: se lee como un compás */
}


/* ═══════════════════════════════════════════════════════════════════════════
   6 · IMÁGENES QUE SE ABREN CON CLIP-PATH
   ═══════════════════════════════════════════════════════════════════════════ */

/* 6.1 · Las tres páginas vivas del portafolio se despliegan dentro del
   teléfono. .t06-screen mide 100%×100% con border-radius:23px
   (sections.css:1495): el `round 23px` va en LOS DOS estados, porque dos
   inset() solo interpolan si tienen la misma forma de redondeo.
   NO cierro el recorte al 100%: dejo el 68% inferior tapado y el 32% de arriba
   visible. Si algún día el gatillo .is-shown no llegara (JS caído, IO que no
   dispara), un teléfono con la cabecera de la página adentro se ve bien; un
   teléfono negro se ve roto. La falla tiene que ser fea, no muda. */
html.js #t06-portafolio .t06-screen{
  clip-path:inset(0 0 68% 0 round 23px);
}
html.js #t06-portafolio .t06-grid.is-shown .t06-screen{
  clip-path:inset(0 0 0 0 round 23px);
  transition:clip-path .95s var(--tpra-mask);
}
html.js #t06-portafolio .t06-grid.is-shown .t06-item:nth-child(2) .t06-screen{transition-delay:.13s}
html.js #t06-portafolio .t06-grid.is-shown .t06-item:nth-child(3) .t06-screen{transition-delay:.26s}
/* El latido de carga (.t06-screen::after, keyframes t06Carga) queda debajo del
   recorte y no molesta: se apaga solo cuando el iframe se pone encima. */

/* 6.2 · El póster de marca de t01 se abre desde el centro.
   .t01-poster es un bloque 4/5, negro, con radio 16px (sections.css:145) —
   funciona como imagen aunque sea CSS. Aquí SÍ uso timeline de scroll: la
   cadena #t01-equipo → .container → .t01-card → .t01-poster no tiene ningún
   overflow ni transform permanente, así que el scrollport es el viewport.
   Va con @supports: donde no exista, el póster simplemente está abierto.
   Se raspa contra la entrada de entrances.js (la .t01-card gira al entrar):
   por eso el rango termina temprano (entry 62%), para que el póster ya esté
   abierto cuando la tarjeta termina de asentarse. */
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    #t01-equipo .t01-poster{
      animation-name:tpraAbrePoster;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
      animation-range:entry 12% entry 62%;
      /* Sin animation-duration a propósito: con timeline de progreso la
         duración es `auto` (= el rango completo). Si se usa el atajo
         `animation:` la duración se resetea a 0s y el efecto queda congelado
         en el primer fotograma. Longhands o nada. */
    }
  }
}
@keyframes tpraAbrePoster{
  from{clip-path:inset(42% 0 42% 0 round 16px)}
  to{clip-path:inset(0 0 0 0 round 16px)}
}
/* ⚠ NO animo .t01-poster-logo: ya tiene animation:t01flota infinita
   (sections.css:167) y cualquier transform mío la reemplazaría. Y .t01-firma-img
   (la firma chica de la cita) la dibuja entrances.js con clip-path: dos wipes
   sobre la misma firma se ven como un tartamudeo. */


/* ═══════════════════════════════════════════════════════════════════════════
   7 · NÚMEROS QUE SUBEN · los tres precios de f03
   ───────────────────────────────────────────────────────────────────────────
   .f03-precio son $180.000 / $290.000 / $420.000 (index.html:1107,1129,1148),
   font-size clamp(2rem,5vw,2.5rem) con tabular-nums heredado de .h-marca.
   Suben desde detrás de su propia línea base: recorte estático, cero
   transform, así el borde no viaja con las cifras.
   HONESTIDAD: esto NO es un contador. CSS no puede contar sin reemplazar el
   texto del elemento (counter + @property borra el contenido real y con él la
   accesibilidad del precio). Si el cliente quiere que los precios cuenten de verdad,
   el motor YA existe y no cuesta nada: core.js:79 lee [data-count] y bastaría
   escribir en el HTML  data-count="180000" data-prefix="$"  en cada precio.
   Aquí no se toca el HTML, así que se entrega el gesto, no el conteo.
   GATILLO: hermano de la cabecera — .f03-grid se la llevó entrances.js, y
   .window-card tiene overflow:hidden (base.css:221), así que dentro de la
   tarjeta un view() quedaría enganchado a un scrollport que nunca scrollea.
   ═══════════════════════════════════════════════════════════════════════════ */
html.js #f03-planes .f03-precio{
  clip-path:inset(105% 0 -12% 0);
}
html.js #f03-planes .f03-cabeza:has(.sec-head.is-shown) ~ .f03-grid .f03-precio{
  clip-path:inset(-12% 0 -12% 0);
  transition:clip-path .72s var(--tpra-mask) .34s;
}
/* Escalonado por tarjeta. OJO al orden real: en móvil la destacada salta al
   primer lugar con order:-1 (sections.css:961), así que el retardo por
   nth-child NO coincide con el orden visual bajo 900px. Es un desfase de
   130ms entre dos precios que ni se cruzan en pantalla; corregirlo pediría
   duplicar la regla por breakpoint y no lo vale. */
html.js #f03-planes .f03-cabeza:has(.sec-head.is-shown) ~ .f03-grid .f03-card:nth-child(2) .f03-precio{transition-delay:.47s}
html.js #f03-planes .f03-cabeza:has(.sec-head.is-shown) ~ .f03-grid .f03-card:nth-child(3) .f03-precio{transition-delay:.60s}

/* La mensualidad llega detrás del precio: primero el número grande, después la
   letra chica. Es el orden en que se lee, no el orden del DOM. */
html.js #f03-planes .f03-mensual{opacity:0}
html.js #f03-planes .f03-cabeza:has(.sec-head.is-shown) ~ .f03-grid .f03-mensual{
  opacity:1;
  transition:opacity .5s linear .72s;
}


/* ═══════════════════════════════════════════════════════════════════════════
   8 · PUNTOS DE DETENCIÓN · lo que se queda quieto mientras pasa el contenido
   ───────────────────────────────────────────────────────────────────────────
   Antes había uno solo en 21 pantallas. Ahora tres, y los tres tienen razón
   de ser comercial, no decorativa:
     D1 · #f03-planes .f03-ancla  — el ancla de precio ("una agencia: desde
          $500.000") se queda pegada mientras pasan los tres planes baratos.
          Es el argumento entero de la sección: si el número caro desaparece,
          el barato deja de ser barato.
     D2 · #f04-comparacion .sec-head — la pregunta se queda arriba mientras
          pasan las tres columnas de la comparación.
     D3 · #z01-faq .tab-rail — YA EXISTÍA (sections.css:1122). No se toca; solo
          se respeta su lenguaje: mismo tipo de offset y de z-index.
   ═══════════════════════════════════════════════════════════════════════════ */

/* D1 · El ancla de f03.
   Por qué aquí se puede: #f03-planes NO está en la lista de secciones con
   overflow:hidden de sections.css:1190, y .container solo trae
   position:relative;z-index:2 (chrome.css:10) — relative no rompe sticky.
   Por qué NO en móvil: bajo 900px las tres tarjetas se apilan
   (sections.css:956) y el recorrido pasa de ~500px a ~2.400px; el ancla
   quedaría clavada un cuarto de pantalla durante media sección. Se activa en
   el mismo breakpoint en que la grilla se pone en tres columnas: 900px.
   El sticky NO necesita fondo propio — .f03-ancla ya es una tarjeta blanca
   opaca con borde negro y sombra dura (sections.css:940-949).
   z-index:6 es obligatorio: .f03-card es position:relative y sus adornos
   (.f03-tag-flotante z-index:3, stickers z-index:2) comparten el contexto de
   apilamiento de .container; sin z-index el ancla pasaría POR DEBAJO. */
@media (min-width:900px){
  #f03-planes .f03-ancla{
    position:sticky;
    top:calc(var(--nav-h) + var(--s2));
    z-index:6;
  }
}
/* ⚠ LÍMITE CONOCIDO: el rango del sticky lo marca el contenedor, así que el
   ancla se queda pegada también sobre los bonos y las condiciones, hasta el
   final de la sección. Si el cliente prefiere que se suelte al terminar las
   tarjetas, la única forma es envolver .f03-grid en un div (una línea de HTML)
   y mover el sticky ahí dentro. Se deja dicho, no resuelto a medias. */

/* D2 · La cabecera de la comparación, en TODOS los tamaños.
   Es una .sec-head--mini (kicker + un titular de una línea, sin bajada):
   ~80px incluso en 375px, así que también funciona en teléfono — que es donde
   la comparación se lee deslizando tarjetas y donde más se agradece que el
   criterio no se vaya.
   El fondo: se copia el patrón que ya usa el rail del FAQ (sections.css:1122
   fija a mano #fafaf8 "para que calce con el data-bg"). Aquí el data-bg de la
   sección es #fbfaf6 (index.html:1196). Es un color a mano, y eso es una
   deuda: si alguien cambia el data-bg de f04, esta franja se ve como un
   parche. Va anotado aquí y no escondido.
   La franja se sangra con un ::before hasta el borde del viewport porque
   .sec-head mide máximo 760px (base.css:162) y la tabla ocupa los 1140 del
   contenedor: sin sangrado, las filas se verían pasar por el costado del
   titular. El desborde lateral no genera scroll horizontal porque
   main{overflow-x:clip} lo recorta (sections.css:1189). */
#f04-comparacion .sec-head{
  position:sticky;
  top:calc(var(--nav-h) + var(--s1));
  z-index:5;
  padding-block:var(--s2);
}
#f04-comparacion .sec-head::before{
  content:"";
  position:absolute;
  inset:0 -50vw;
  background:#fbfaf6;
  z-index:-1;
  pointer-events:none;
}

/* D2b · Y como algo se quedó quieto, el contenido que pasa por su lado tiene
   que notarse: las filas de la tabla van llegando una a una.
   SOLO opacidad, a propósito: transform sobre <tr>/<td> (display:table-row /
   table-cell) no es fiable entre navegadores — es de los pocos sitios donde
   un translate se comporta distinto en cada motor. */
html.js #f04-comparacion .f04-table tbody tr{opacity:0}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap .f04-table tbody tr{
  opacity:1;
  transition:opacity .5s linear;
}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap tbody tr:nth-child(2){transition-delay:calc(var(--tpra-paso) * 1.5)}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap tbody tr:nth-child(3){transition-delay:calc(var(--tpra-paso) * 3)}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap tbody tr:nth-child(4){transition-delay:calc(var(--tpra-paso) * 4.5)}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap tbody tr:nth-child(5){transition-delay:calc(var(--tpra-paso) * 6)}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap tbody tr:nth-child(6){transition-delay:calc(var(--tpra-paso) * 7.5)}
html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap tbody tr:nth-child(7){transition-delay:calc(var(--tpra-paso) * 9)}

/* D2c · Los ✓ de Tu Plantilla hacen pop con rebote cuando la zona entra en
   pantalla — por scroll (is-shown), jamás hover. En PC los dispara la
   cabecera; en teléfono cada bloque criterio trae su propio is-shown. */
@media (prefers-reduced-motion:no-preference){
  html.js #f04-comparacion .f04-veri--si{opacity:0;transform:scale(.3)}
  html.js #f04-comparacion .sec-head.is-shown ~ .f04-wrap .f04-veri--si,
  html.js #f04-comparacion .f04-crit.is-shown .f04-veri--si{
    opacity:1;transform:none;
    transition:transform .5s cubic-bezier(.34,1.56,.64,1) .35s,opacity .25s ease .35s;
  }
}


/* ═══════════════════════════════════════════════════════════════════════════
   9 · PROFUNDIDAD · capas que no van a la misma velocidad
   ───────────────────────────────────────────────────────────────────────────
   El DESIGN-DNA lo pide con nombre y apellido ("2.5D en todo: capas, sombras
   duras, parallax, tilt"). Todo con la propiedad `translate` en vez de
   `transform`: así compone con los transform que ya existen en esos elementos
   en vez de reemplazarlos, y no toca `will-change` en ningún caso — una capa
   permanente en cada adorno es exactamente lo que dispara el peor fotograma.
   ═══════════════════════════════════════════════════════════════════════════ */

@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference) and (min-width:768px){
    /* 9.1 · t01: el póster y el texto no suben iguales. A 768px la grilla se
       parte en dos columnas .85fr/1.15fr (sections.css:142); ahí el desfase se
       lee como profundidad. Abajo de 768px están apilados y un desfase
       vertical solo estorbaría la lectura, así que no se aplica. */
    #t01-equipo .t01-texto{
      animation-name:tpraCapaLenta;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
      animation-range:cover 0% cover 100%;
    }
    /* 9.2 · El cierre respira: las cuatro columnas del footer llegan a
       distinta altura. Es lo último que se ve; que no caiga todo de golpe. */
    .footer .footer-grid > *{
      animation-name:tpraCapaLenta;
      animation-timing-function:linear;
      animation-fill-mode:both;
      animation-timeline:view();
      animation-range:entry 0% cover 40%;
    }
    .footer .footer-grid > *:nth-child(2){animation-range:entry 6% cover 46%}
    .footer .footer-grid > *:nth-child(3){animation-range:entry 12% cover 52%}
    .footer .footer-grid > *:nth-child(4){animation-range:entry 18% cover 58%}
  }
}
@keyframes tpraCapaLenta{
  from{translate:0 var(--s4)}
  to{translate:0 calc(var(--s4) * -1)}
}

/* 9.3 · Los adornos de t06 y del póster de t01 SÍ necesitan profundidad, pero
   NO pueden usar timeline de scroll: #t06-portafolio (sections.css:1465) y
   .t01-poster (sections.css:146) son overflow:hidden, o sea contenedores de
   scroll que nunca scrollean → view() se congelaría ahí dentro.
   Solución: deriva lenta y continua, con periodos primos entre sí (9s/13s/17s
   y 11s/19s) para que nunca coincidan y no se lea como un latido. Son cinco
   elementos chicos, transform-only, dentro de cajas que ya recortan. */
@media (prefers-reduced-motion:no-preference){
  .t01-poster-arc--a{animation:tpraDeriva 17s ease-in-out infinite}
  .t01-poster-arc--b{animation:tpraDeriva 13s ease-in-out infinite -4s}
  .t01-poster-arc--c{animation:tpraDerivaX 9s ease-in-out infinite -2s}
  #t06-portafolio .t06-arc--a{animation:tpraDeriva 19s ease-in-out infinite}
  #t06-portafolio .t06-arc--b{animation:tpraDerivaX 11s ease-in-out infinite -5s}
}
@keyframes tpraDeriva{
  0%,100%{translate:0 calc(var(--s2) * -1)}
  50%{translate:0 var(--s2)}
}
@keyframes tpraDerivaX{
  0%,100%{translate:calc(var(--s1) * -1) var(--s2)}
  50%{translate:var(--s2) calc(var(--s1) * -1)}
}


/* ═══════════════════════════════════════════════════════════════════════════
   10 · SALIDAS DE EMERGENCIA
   Regla de la casa: si algo falla, tiene que quedar TODO visible. Un elemento
   en opacity:0 sin error de consola es el bug más caro que hemos tenido.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Sin GSAP el motor CSS de core.js sigue a cargo y los data-reveal siguen
   vivos, así que casi todo funciona igual. Lo que NO puede quedar a medias es
   lo que depende de un gatillo: se deja en su estado final. */
html.no-gsap .aw{clip-path:none!important;transform:none!important;opacity:1!important}
html.no-gsap .sec-head .h-display{clip-path:none!important}
html.no-gsap .sec-head .kicker::before{transform:scaleX(1)!important}
html.no-gsap .sec-head .sub,
html.no-gsap .cta-band > *,
html.no-gsap .f03-mensual,
html.no-gsap #f04-comparacion .f04-table tbody tr{opacity:1!important;transform:none!important}
html.no-gsap #f04-comparacion .f04-veri--si{opacity:1!important;transform:none!important}
html.no-gsap #t06-portafolio .t06-screen{clip-path:inset(0 0 0 0 round 23px)!important}
html.no-gsap #f03-planes .f03-precio{clip-path:none!important}
html.no-gsap #t02-garantia .t02-sello circle{stroke-dasharray:none!important;stroke-dashoffset:0!important}

/* Motion reducido: se apaga TODO lo de este archivo y se fija el estado final.
   base.css:363 ya recorta duraciones a .01ms de forma global; esto se ocupa de
   lo que esa regla no puede saber: los recortes, el guion del trazo y las
   derivas infinitas.
   Los tres puntos de detención SE MANTIENEN: un sticky no es movimiento —
   es justamente lo contrario, y ayuda a quien necesita menos animación. */
@media (prefers-reduced-motion:reduce){
  .aw{clip-path:none!important;transform:none!important;opacity:1!important}
  .sec-head .h-display{clip-path:none!important}
  .sec-head .kicker::before{transform:scaleX(1)!important}
  .sec-head .sub,
  .cta-band[data-reveal="up"] > *,
  #f03-planes .f03-mensual,
  #f04-comparacion .f04-table tbody tr{opacity:1!important;transform:none!important}
  #f04-comparacion .f04-veri--si{opacity:1!important;transform:none!important}
  #t06-portafolio .t06-screen{clip-path:inset(0 0 0 0 round 23px)!important}
  #t01-equipo .t01-poster{clip-path:none!important}
  #f03-planes .f03-precio{clip-path:none!important}
  #t02-garantia .t02-sello circle{stroke-dasharray:none!important;stroke-dashoffset:0!important}
  .t01-poster-arc--a,.t01-poster-arc--b,.t01-poster-arc--c,
  #t06-portafolio .t06-arc--a,#t06-portafolio .t06-arc--b,
  #t01-equipo .t01-texto,.footer .footer-grid > *{
    animation:none!important;
    translate:none!important;
  }
}
