/* vida.css · TokiVideo · versión final de «vida» (con la ronda de arreglos). Va con assets/js/vida.js.
   Dos piezas, una por pantalla, con la casilla azul del logo como unidad:
   1. ENTREGA (de A, «El ID se vuelve imagen»): en la pantalla del reproductor, el ID sale de una casilla azul y el fotograma real
      se abre casilla a casilla en un lienzo; luego el lienzo se funde sobre el <img> del póster y queda quieto.
   2. CINTA (de B, «El scroll como película»): la tira de fotogramas avanza con el scroll y un cabezal por fila lee el fotograma
      y su tiempo real en el clip.
   Todo vive bajo ganchos data-tv-* y clases .tv-*; no toca nada más de la página. Usa sus tokens, cada uno con su valor de respaldo
   (si faltan, se ve igual que en el modo claro de la página): --accent, --mono, --in-ease, --in-o, --ease-out, --ease-io.
   Estados:
     <html data-tv-vida="espera">   lo pone el aviso del <head> si se permite movimiento: la pantalla espera en negro a vida.js.
     <html data-tv-vida="tarde">    el plazo del negro (1,2 s después de pintarse, en el compositor) terminó antes de que arrancara
                                    vida.js: el negro ya se fundió sobre el póster y no hay llegada. Nunca aparece nada de golpe.
     <html data-tv-vida="vivo">     vida.js arrancó.
     <html data-tv-vida="quieto">   se pidió movimiento reducido o colores forzados con la página abierta: todo quieto.
     sin el atributo                sin JS, con movimiento reducido o colores forzados, o si vida.js falló antes del primer pintado:
                                    la página como era.
     [data-tv-pantalla][data-tv-estado]   la entrega: espera · entrega · relevo · vivo (reposo) · quieto (respaldo). */

/* señal para vida.js: este archivo cargó (sin él, vida.js no hace nada y la página queda como era) */
:root { --tv-css: 1; }

/* ---------- 1 · ENTREGA: la pantalla del reproductor ---------- */
[data-tv-lienzo],
[data-tv-id] { display: none; }

/* el lienzo cubre la pantalla, entre el póster y el botón grande; nunca recibe el puntero (los clics llegan al póster y al video) */
[data-tv-vida] [data-tv-pantalla]:not([data-tv-estado="quieto"]) [data-tv-lienzo] {
  display: block; position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%; max-width: none;
  pointer-events: none;
}
/* mientras espera o llega una entrega el lienzo es opaco: el negro de la pantalla, como un reproductor que espera el video */
[data-tv-vida] [data-tv-pantalla]:not([data-tv-estado]) [data-tv-lienzo],
[data-tv-vida] [data-tv-pantalla][data-tv-estado="espera"] [data-tv-lienzo],
[data-tv-vida] [data-tv-pantalla][data-tv-estado="entrega"] [data-tv-lienzo],
[data-tv-vida] [data-tv-pantalla][data-tv-estado="relevo"] [data-tv-lienzo] { background: #000; }
/* el plazo del negro, antes de que arranque vida.js: 1,2 s después de pintarse, el negro se funde sobre el póster (la misma curva y
   duración que el relevo). Va en su propia capa y lo lleva el compositor, así que se cumple aunque el hilo principal esté ocupado
   cargando la página (un equipo lento). Si vida.js arranca antes, pone el estado de la pantalla, esta regla deja de valer y no pasa
   nada; si llega después, ve el plazo vencido y no hace la llegada (ese fundido sigue hasta el final). Al terminar el fundido, el
   aviso del <head> pasa a «tarde» y vuelve el botón grande */
@keyframes tv-rinde { from { opacity: 1; } to { opacity: 0; } }   /* solo opacidad: así la puede llevar el compositor */
[data-tv-vida="espera"] [data-tv-pantalla]:not([data-tv-estado]) [data-tv-lienzo] {
  will-change: opacity; animation: tv-rinde 250ms var(--ease-io, cubic-bezier(.4, 0, .6, 1)) 1.2s both;
}
/* tarde (el plazo del negro terminó y vida.js no había arrancado): el negro ya se fundió y no se pinta. Si vida.js llega después,
   sigue desde ahí, sin llegada. (La transición solo cuenta en un navegador sin animaciones CSS, donde el aviso se rinde a los 6 s) */
[data-tv-vida="tarde"] [data-tv-pantalla]:not([data-tv-estado]) [data-tv-lienzo] {
  will-change: opacity; opacity: 0; visibility: hidden;
  transition: opacity 250ms var(--ease-io, cubic-bezier(.4, 0, .6, 1)), visibility 0s linear 250ms;
}
/* en reposo el lienzo queda vacío y no se pinta: se ve el <img> del póster (o el video), como en la página aprobada */
[data-tv-vida] [data-tv-pantalla][data-tv-estado="vivo"] [data-tv-lienzo] { visibility: hidden; }
/* el relevo: el lienzo se funde sobre el <img> del póster, que es la misma imagen (0,25 s, la curva de la interfaz). En su propia capa,
   para que el fundido lo lleve el compositor aunque el hilo principal esté ocupado (el lienzo sin contexto 2d no la tendría) */
[data-tv-pantalla] .tv-lienzo--relevo { will-change: opacity; opacity: 0; transition: opacity 250ms var(--ease-io, cubic-bezier(.4, 0, .6, 1)); }
/* vida.js llegó con el plazo del negro ya corriendo: la misma animación sigue (no vuelve a empezar) hasta fundirse del todo */
[data-tv-pantalla] .tv-lienzo--rinde { will-change: opacity; animation: tv-rinde 250ms var(--ease-io, cubic-bezier(.4, 0, .6, 1)) 1.2s both; }
/* el botón grande se aparta mientras llega el video y vuelve con él (también si es tarde); con el foco del teclado, siempre a la vista */
[data-tv-vida="espera"] [data-tv-pantalla]:not([data-tv-estado]) [data-tv-boton]:not(:focus-visible),
[data-tv-vida] [data-tv-pantalla][data-tv-estado="espera"] [data-tv-boton]:not(:focus-visible),
[data-tv-vida] [data-tv-pantalla][data-tv-estado="entrega"] [data-tv-boton]:not(:focus-visible) { opacity: 0; }

/* la casilla azul: el ID en camino. El mismo azul y el mismo radio (7,5 %) que el cuadrado del logo; vida.js fija su sitio (bajo el
   ID de la petición) y su lado. Solo existe mientras viaja el ID: aparece con el campo, late al salir y se disuelve en la imagen. */
[data-tv-vida] [data-tv-pantalla]:not([data-tv-estado="quieto"]) [data-tv-id] {
  display: block; position: absolute; z-index: 3; left: 0; top: 0; width: 12px; height: 12px;
  border-radius: 7.5%; background: #3346f0;
  opacity: 0; pointer-events: none;
  transition: opacity 250ms var(--ease-io, cubic-bezier(.4, 0, .6, 1));
}
[data-tv-vida] [data-tv-pantalla]:not([data-tv-estado="quieto"]) [data-tv-id].tv-id--on { opacity: 1; transition: opacity 350ms var(--ease-out, cubic-bezier(.22, 1, .36, 1)); }

/* con la entrega a la vista, el rótulo dice qué es ilustrativo: el texto largo que la página ya usa en el teléfono y sin JS.
   El fotograma es real: lo dice el pie del reproductor («Clip real.») */
[data-tv-vida] [data-tv-rotulo-corto] { display: none; }
[data-tv-vida] [data-tv-rotulo-largo] { display: inline; }

/* ---------- 2 · CINTA: la tira de fotogramas ---------- */
[data-tv-pista] { position: relative; }
/* capa propia: al moverse con transform no obliga a repintar los fotogramas */
[data-tv-vida] [data-tv-fila] { will-change: transform; }
/* con un ratón, el cursor dice que la fila responde de lado a lado: el cabezal sigue al puntero (la línea vertical del cursor es
   la del cabezal). Con el dedo no hace falta: un toque lleva el cabezal al fotograma tocado */
@media (hover: hover) and (pointer: fine) {
  [data-tv-vida="vivo"] [data-tv-fila] { cursor: col-resize; }
}
/* sitio para el tiempo que lee el cabezal debajo de cada fila (la sección recorta lo que sobresale) */
[data-tv-vida] [data-tv-cinta] { padding-bottom: 30px; }
[data-tv-vida] [data-tv-pista] + [data-tv-pista] { margin-top: 38px; }
/* con la cinta en movimiento, la fila entra entera con su opacidad (la de las entradas de la página: 0,9 s, la misma curva):
   el barrido de cada fotograma dejaba franjas blancas entre cuadros que además se desplazaban. La fila ya tiene capa propia,
   así que su fundido no repinta los fotogramas. Las reglas de la página (saltos, foco, red de seguridad) siguen mandando. */
@media (prefers-reduced-motion: no-preference) {
  [data-tv-vida] .in-wait [data-tv-fila] [data-in="f"] { opacity: 1; clip-path: inset(0 0 0 0 round var(--fr, 3px)); }
  [data-tv-vida] .in-wait [data-tv-fila] { opacity: 0; visibility: hidden; }   /* escondida no se pinta: nada que rasterizar antes de entrar */
  [data-tv-vida] .in-go [data-tv-fila] { transition: opacity var(--in-o, .9s) var(--in-ease, ease); }
}

/* el cabezal: una línea fina en el azul de la página con la casilla del logo abajo; aparece 0,9 s después de que entra su fila
   (cuando sus fotogramas ya se ven) y hasta entonces no se pinta (visibility) */
.tv-cabezal {
  --tv-g: 2px; --tv-c: 8px;
  position: absolute; left: 0; top: 0; z-index: 1; width: var(--tv-g); height: 20px; margin-left: calc(var(--tv-g) / -2);
  background: var(--accent, #3346f0); pointer-events: none; will-change: transform;
  opacity: 0; visibility: hidden;
  transition: opacity .3s var(--in-ease, ease) .9s, visibility 0s linear 1.2s;
}
.tv-cabezal::after {
  content: ""; position: absolute; left: 50%; top: 100%;
  width: var(--tv-c); height: var(--tv-c); margin-left: calc(var(--tv-c) / -2); background: var(--accent, #3346f0);
}
.tv-cabezal.is-on { opacity: 1; visibility: visible; transition: opacity .3s var(--in-ease, ease) .9s, visibility 0s linear .9s; }
.tv-cabezal.is-ya { transition-delay: 0s; }
/* el tiempo del fotograma que lee (real: el instante del clip del que salió); los 8 van superpuestos y solo cambia cuál se ve,
   así nada se vuelve a maquetar mientras el cabezal se mueve */
.tv-cabezal__t {
  position: absolute; left: calc(var(--tv-c) / 2 + 6px); top: 100%; margin-top: calc(var(--tv-c) / 2 - 7px);
  display: grid; font: 400 13px/14px var(--mono, ui-monospace, "Cascadia Mono", Consolas, monospace); letter-spacing: 0; color: var(--accent, #3346f0);
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
.tv-cabezal__t > span { grid-area: 1 / 1; visibility: hidden; }
.tv-cabezal__t > span.is-on { visibility: visible; }

/* ---------- respaldos: con movimiento reducido, colores forzados o al imprimir, todo quieto y visible ---------- */
/* colores forzados: el negro del escenario pasa a ser el fondo del sistema y la casilla azul, el campo y la línea del cabezal
   pierden su color; se trata como el movimiento reducido (el aviso del <head> y vida.js tampoco arrancan) */
@media (prefers-reduced-motion: reduce) {
  [data-tv-lienzo], [data-tv-id], .tv-cabezal { display: none !important; }
  [data-tv-fila] { transform: none !important; }
}
@media (forced-colors: active) {
  [data-tv-lienzo], [data-tv-id], .tv-cabezal { display: none !important; }
  [data-tv-fila] { transform: none !important; }
}
@media print {
  [data-tv-lienzo], [data-tv-id], .tv-cabezal { display: none !important; }
  [data-tv-fila] { transform: none !important; opacity: 1 !important; visibility: visible !important; }
}
