/* Estilo mínimo compartido por las páginas de tools/eval-ui. */
:root { --fg:#1a1a1a; --mut:#666; --ok:#0a7d32; --err:#c0272d; --bd:#d8d8d8; --bg:#fafafa; }
* { box-sizing:border-box }
/* `hidden` tiene que ganar SIEMPRE. La regla `label { display:flex }` de más
   abajo es de autor y pisa el `[hidden]{display:none}` de la hoja del
   navegador, así que sin esto un <label hidden> se seguiría viendo — que es
   exactamente lo que le pasa a la casilla de la interpretación de
   Konstellation, oculta salvo con ?debug=1. */
[hidden] { display:none !important }
body {
  margin:0; padding:24px; font:15px/1.5 -apple-system,Segoe UI,system-ui,sans-serif;
  color:var(--fg); background:var(--bg);
}

body.centrado {
  min-height:100vh; display:flex; align-items:center; justify-content:center;
}

.tarjeta {
  width:100%; max-width:320px; background:#fff; border:1px solid var(--bd);
  border-radius:8px; padding:24px; display:flex; flex-direction:column; gap:14px;
}

h1 { font-size:20px; margin:0; }

label { display:flex; flex-direction:column; gap:4px; font-size:14px; color:var(--mut); }

input {
  font:inherit; padding:8px 10px; border:1px solid var(--bd); border-radius:6px; color:var(--fg);
}

button {
  font:inherit; padding:9px 18px; border:1px solid var(--bd); border-radius:6px;
  background:#fff; cursor:pointer;
}
button:disabled { opacity:.45; cursor:default }
button.primario { background:var(--fg); color:#fff; border-color:var(--fg) }

/* QA §9: no había NINGUNA regla `:focus`/`:focus-visible` en la hoja, así que
   el navegador ponía su anillo por defecto — negro sobre negro en
   el botón de capturar (`button.primario`, fondo `--fg` #1a1a1a), invisible con
   teclado. `outline-offset` separa el anillo del propio botón: el hueco deja
   ver el fondo de la página (`--bg` #fafafa) o de la tarjeta (blanco) a los
   dos lados del trazo, nunca el fondo oscuro del botón, así que el azul
   elegido (el mismo `#1a5fb4` de la estela y el cursor) mide 6,29:1 contra
   blanco y 6,02:1 contra `--bg` — pasa de sobra el 3:1 de componentes no
   textuales (WCAG 1.4.11) en los dos sitios donde puede aparecer. */
:focus-visible {
  outline:3px solid #1a5fb4; outline-offset:2px; border-radius:2px;
}

.err { color:var(--err); margin:0; font-size:14px; }

/* ── página de captura (index.html) ───────────────────────────────────────── */
/* La página va a sangre. El tope de 1240 px que había aquí protegía a la
   escena, pero cobraba el peaje a TODO lo demás: en una pantalla de 1920 dejaba
   409 px sin usar y, dentro, la caja de los mandos se quedaba con 617 px vacíos
   a la derecha de un aviso de 575. Lo que de verdad necesitaba tope ya lo tiene
   por su cuenta (`70ch`, `82ch`, `640px`, `360px`) y lo que faltaba se añade
   abajo: `#vista`, `.mandos`, `#plegHistorial` y `.inst-interpretacion`. */
body.captura { max-width:none; margin-inline:0 }

/* ── el riel técnico de la derecha ────────────────────────────────────────── */
/* TODO el CSS del riel vive dentro de esta media query, y eso es deliberado:
   por debajo del umbral, `body.riel-abierto` queda inerte y el <aside> vuelve a
   ser un bloque en flujo con los cuatro plegables donde estaban siempre. Sin
   JS de reordenación y sin regresión. Importa porque el zoom del navegador
   cruza el umbral: a 150 % sobre 1920 son 1280 px CSS.

   1380 px es el umbral exacto por el que ABRIR el riel nunca apila la escena:
   48 (padding) + 930 (lo que la escena necesita para ir lado a lado) + 28
   (hueco) + 360 (riel mínimo) = 1366. */
@media (min-width: 1380px) {
  body.captura {
    --riel-cerrado:40px;
    /* Ni un tercio literal ni un gusto: el suelo de 360 lo fija la tabla de
       calidad (etiquetas de hasta 43 caracteres) y por encima de 520 el riel
       deja de mejorar y empieza a quitarle eje a la línea de tiempo, que es
       quien de verdad rentabiliza el ancho. A 1920 son 422 px (22 %). */
    --riel-abierto:clamp(360px, 22vw, 520px);
    --riel:var(--riel-cerrado);
    --riel-gap:16px;
    display:grid;
    grid-template-columns:minmax(0, 1fr) var(--riel);
    /* El riel abarca las filas del flujo Y la del historial: si solo abarcara
       la primera, al llegar al historial —que es cuando la página es más
       larga— el sticky se despegaría y la pestaña desaparecería justo donde
       más falta hace. */
    grid-template-areas:"cabecera cabecera" "flujo riel" "historial riel";
    column-gap:var(--riel-gap);
    align-items:start;
  }
  /* 28px abierto es el MISMO hueco que `.escena` usa entre vídeo y plano: hace
     que el riel se lea como un hermano del plano y no como cromo. */
  body.captura.riel-abierto { --riel:var(--riel-abierto); --riel-gap:28px }

  .cabecera { grid-area:cabecera }
  /* `container-type` convierte a `.flujo` en la unidad que miden las container
     queries de abajo. Es lo que arregla el breakpoint que mentía: con el riel
     abierto el viewport puede decir 1440 mientras la escena solo tiene 990. */
  .flujo { grid-area:flujo }
  /* NUNCA `overflow` aquí ni en `.flujo`: un ancestro con overflow mata el
     sticky de dentro sin avisar. El scroll va en `.riel-caja`. */
  #riel { grid-area:riel; align-self:stretch; min-width:0 }
  #plegHistorial { grid-area:historial }

  .riel-caja {
    position:sticky; top:24px; max-height:calc(100vh - 48px); overflow:auto;
    background:#fff; border:1px solid var(--bd); border-radius:10px;
  }
  #bRiel {
    display:flex; align-items:center; justify-content:center; gap:8px;
    width:100%; background:none; border:0; cursor:pointer;
    font:inherit; font-size:13px; color:var(--mut); padding:14px 0;
    /* Cerrado es una pestaña vertical de 40 px. Siendo columna del grid está
       visible a cualquier profundidad de scroll sin `position:fixed` y sin
       tapar nada — que es justo lo que descartaba superponer: el riel se
       consulta MIRANDO los carriles y el plano al mismo tiempo. */
    writing-mode:vertical-rl; height:100%;
  }
  #bRiel:hover { color:var(--fg) }
  #bRiel::after { content:'◂'; font-size:11px }
  body.riel-abierto #bRiel {
    writing-mode:horizontal-tb; height:auto; padding:11px 14px;
    justify-content:space-between; border-bottom:1px solid var(--bd); color:var(--fg);
  }
  body.riel-abierto #bRiel::after { content:'✕' }

  .riel-cuerpo { display:none }
  body.riel-abierto .riel-cuerpo { display:block; padding:0 12px 12px }
  /* Dentro del riel los plegables ya están enmarcados por la caja: quitarles el
     borde evita la caja-dentro-de-caja. */
  .riel-cuerpo .plegable { border:0; padding:0; margin-top:4px }
  .riel-cuerpo .plegable + .plegable { border-top:1px solid var(--bd) }

  /* El contador de avisos de la pestaña. Símbolo Y texto Y color, nunca el
     color solo: la misma disciplina que `.carril-cobertura.err`. */
  .riel-aviso { color:var(--err); font-weight:600; font-size:12px }
  /* La pestaña cerrada escribe en vertical; el contador y la flecha, NO: un
     «⚠ 1» girado 90° no se lee, y es la única señal de que hay algo dentro que
     merece abrirse. */
  .riel-aviso, #bRiel::after { writing-mode:horizontal-tb; text-orientation:upright }
  /* Abierta, la barra tiene tres hijos: sin esto el «⚠ 1» quedaba flotando en
     mitad del espacio en vez de junto al rótulo. */
  body.riel-abierto #bRiel { justify-content:flex-start }
  body.riel-abierto .riel-rotulo { margin-right:auto }

  /* En un riel de ~420 px, la tabla de calidad («Frames descartados (sin
     valence o arousal)» y una celda de prosa de ~400 caracteres) se convierte
     en una escalera de una palabra por línea. Apilar etiqueta sobre valor. */
  .riel-cuerpo :is(table, tbody, tr) { display:block; border:0 }
  .riel-cuerpo :is(th, td) { display:block; border:0; padding:2px 0 }
  .riel-cuerpo th { background:none; color:var(--mut); font-size:12.5px; padding-top:10px }
  /* `<th>Qué</th><th>Valor</th>` es cabecera de COLUMNAS; con el `display:block`
     de arriba se convierte en dos etiquetas apiladas sin nada debajo. */
  .riel-cuerpo tr:first-child:not(:has(td)) { display:none }
  .riel-cuerpo tr + tr { border-top:1px solid var(--bd) }
  /* La sangría del JSON ES la estructura: con `pre-wrap` en 420 px se
     convierte en sopa. Mejor un scroll horizontal que perderla. */
  .riel-cuerpo .volcado { white-space:pre; overflow-x:auto; font-size:11.5px; max-height:280px }
  /* Dos scrolls anidados (la caja del riel y el log) con el log a 260 px fijos
     hacen que la rueda del ratón sobre el log arrastre la página. */
  .riel-cuerpo #log { height:clamp(160px, 28vh, 320px) }
  /* El <summary> ya dice de qué es la sección; el <h2> de dentro repetía el
     mismo título dos líneas más abajo. En la página ancha costaba poco, en un
     riel de 360 px es media pantalla de títulos duplicados. Solo el primero:
     los <h2> de subsecciones que pinta `pedirCircumplejo` sí dicen algo nuevo. */
  .riel-cuerpo .plegable section > h2:first-child { display:none }
}

/* `container-type` va FUERA de la media query del riel, en la regla base, para
   que la container query de `.escena` mida bien a cualquier ancho: si solo
   existiera en el régimen ancho, por debajo de 1380 px no habría contenedor y
   la escena no se apilaría nunca. */
.flujo { container-type:inline-size; min-width:0 }

.cabecera { display:flex; justify-content:space-between; align-items:flex-start; gap:16px; margin-bottom:18px }
.cabecera h1 { margin:0 0 4px }
/* Sin tope: es UNA frase y con 70ch ocupaba tres líneas y media pantalla.
   A lo ancho cabe en una o dos y el hueco de la derecha deja de sobrar. */
p.sub { color:var(--mut); margin:0 }

.fila { display:flex; gap:12px; align-items:center; margin-bottom:16px; flex-wrap:wrap }
.opcion { flex-direction:row; align-items:center; gap:6px; cursor:pointer; color:var(--fg) }
/* `padding-right` generoso: la flecha nativa del <select> se dibuja dentro de
   la caja y con 10 px quedaba pegada al borde. */
select { font:inherit; padding:8px 12px; padding-right:8px; border:1px solid var(--bd); border-radius:6px; color:var(--fg); background:#fff }

/* Los mandos: una sola franja, siempre a la vista, visiblemente separada de lo
   que se mira. Sus avisos van debajo y en pequeño — son condiciones de la
   captura, no resultados. */
.mandos {
  background:#fff; border:1px solid var(--bd); border-radius:10px;
  padding:14px 16px 4px; margin-bottom:20px;
  /* Sin tope. Lo tuvo (1180 px) y era uno de los CUATRO bordes derechos
     distintos que tenía la página —mandos 1180, escena 1362, línea de tiempo
     1527, historial 1080—, que es lo que la hacía parecer descuadrada. Ahora
     todo lo del flujo termina en el mismo sitio y lo que se ajusta por dentro
     es el contenido, no la caja. */
}
/* Los avisos, en dos columnas cuando hay sitio.
   Cada uno sigue leyéndose a 82ch —eso no se toca, es el ancho de lectura— pero
   apilados dejaban medio contenedor vacío a su derecha y empujaban el vídeo
   medio pliegue hacia abajo. En dos columnas el bloque mide la mitad de alto y
   el espacio deja de sobrar. Container query: con el riel abierto la columna es
   más estrecha que la ventana. */
/* Los avisos van en dos columnas DENTRO de su plegable: cada uno se sigue
   leyendo a 82ch, pero el bloque mide la mitad de alto. El grid vive aquí y no
   en `.mandos` porque envolverlos en un <details> los sacó de ser hijos
   directos, y con el grid arriba la fila de mandos y la casilla de Gemma
   habrían quedado en columnas. */
.mandos .fila:last-of-type { margin-bottom:12px }
.mandos > p { margin:0 0 12px }
.aviso-fino { font-size:13px; max-width:82ch }

video { width:100%; border-radius:8px; background:#000; display:block }

.ok { color:var(--ok) } .mut { color:var(--mut) }

section { margin-top:22px }
section h2 { font-size:17px; margin:0 0 8px }

/* ── la escena: el vídeo y el plano, lado a lado (spec §3) ────────────────── */
/* Los dos juntos son la herramienta: la cara es el patrón contra el que se
   juzga y el plano es la lectura del modelo sobre esa misma cara. Separarlos
   verticalmente —como hacía la v1, con el plano debajo del log— obliga a
   recordar de un sitio a otro, que es justo lo que no se puede pedir cuando lo
   que se compara cambia 30 veces por segundo. */
/* Dos columnas y dos filas: el plano ocupa la columna derecha ENTERA (es más
   alto que el vídeo, porque debajo del cuadrado van la emoción y las
   coordenadas) y el panel del instante rellena la fila de abajo a la
   izquierda. Sin esto quedaba un hueco muerto de ~290 px bajo el vídeo. */
/* La columna del vídeo se topa por su propio contenido, no por la página: como
   `#vista` no puede pasar de `58vh * 16/9`, dejar que la columna creciera más
   solo añadía bandas negras. Y de ahí sale la propiedad que hace barato el
   riel: vive en la holgura que la escena NO puede usar, así que abrirlo cuesta
   un 3 % del vídeo en vez de un cuarto de la pantalla. */
.escena {
  display:grid; gap:20px 28px; align-items:start;
  /* La segunda pista se lleva TODO lo que sobra (`1fr`) en vez de pararse en
     470: el vídeo no puede crecer más que su tope de alto, así que sin esto la
     escena terminaba 165 px antes que la línea de tiempo. El cuadrado no crece
     con ella —lo topa `.cx-plano`, porque crecer un cuadrado cuesta ALTO y el
     plano ya decide dónde cae el pliegue—; el ancho de más se lo queda la
     lectura, que es texto y lo agradece. */
  grid-template-columns:minmax(520px, calc(58vh * 16 / 9)) minmax(340px, 1fr);
  grid-template-areas:"video plano" "instante plano";
}
.escena-video { grid-area:video }
.escena > #plano { grid-area:plano }
.escena > #instante { grid-area:instante; margin-top:0 }
.escena-video { position:relative; min-width:0 }
/* Tope de altura: una webcam vertical, o un fichero 9:16, llenaría la pantalla
   entera y empujaría el plano —y todo lo demás— bajo el pliegue. Con
   `object-fit:contain` se ve entero, con bandas, en vez de recortado. */
/* El tope de ANCHO está acoplado al de alto: un 16:9 limitado a 58vh no puede
   pasar de `58vh * 16/9` de ancho, y sin decirlo la caja crecía con la columna
   dejando bandas negras más anchas que la propia imagen (el <video> lleva
   `background:#000`). Si se cambia uno hay que cambiar el otro. */
#vista { max-height:58vh; max-width:calc(58vh * 16 / 9); object-fit:contain }
/* El plano ya trae su propio <h2>; aquí solo se le quita el margen superior de
   `section` para que su título quede a la altura del borde del vídeo. */
.escena > #plano { margin-top:0 }

/* El mando de velocidad, justo debajo del vídeo y alineado a su derecha: es
   una propiedad de ESE vídeo, no de la página, así que vive pegado a él y no
   arriba con los mandos de captura. Discreto a propósito —se usa de vez en
   cuando, al mirar un tramo de cerca— pero no escondido. */
.velocidad {
  /* `flex-direction:row` NO es decorativo: la regla base `label` de arriba pone
     `column`, y sin resetearla `justify-content:flex-end` alinea hacia ABAJO en
     vez de hacia la derecha. Es lo que hacía que este mando saliera apilado y
     centrado bajo el vídeo (MEDIDO: caja de 710x53, dos líneas). `.opcion`
     lleva el mismo reset por exactamente la misma razón. */
  display:flex; flex-direction:row; align-items:center; justify-content:flex-end; gap:6px;
  margin-top:6px; font-size:.85rem; color:var(--mut); cursor:pointer;
}
.velocidad select { padding:3px 6px; font-size:.85rem }

/* Antes de la primera captura no hay nada que comparar: una columna, la caja
   del vídeo pequeña y encima qué hacer. El <video> NO se oculta (el SDK lo
   dibuja en un canvas cada rAF y un elemento sin caja deja de entregar
   fotogramas): se queda negro debajo del mensaje. */
.escena-vacia { grid-template-columns:minmax(0, 520px); grid-template-areas:"video" }
.escena-vacia #vista { aspect-ratio:16/9 }
.escena-mensaje {
  position:absolute; inset:0; margin:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:4px; text-align:center;
  color:#e8e8e8; font-size:15px; pointer-events:none;
}
.escena-mensaje-fino { color:#b0b0b0; font-size:13.5px }
.escena:not(.escena-vacia) .escena-mensaje { display:none }

/* La banda de validez (QA §4/§12, `app.mjs:marcarValidezDeLaEscena`): va ARRIBA
   de la escena, antes de cualquier cosa interpretativa, cuando la captura es
   inválida o todavía no tiene resultados. `.escena-invalida` es el hook que
   pone la misma función sobre `#escena`, por si algún día se quiere atenuar el
   plano o la emoción bajo una captura que no vale (no se hace todavía). */
/* Sin tope de ancho: esto es una ALERTA de tres frases, no prosa que se lee
   seguida, y el evaluador tiene que absorberla de un vistazo. Con el tope de
   82ch en la caja ocupaba media columna; con el tope en el texto quedaban 7
   líneas y el título en una línea aparte. Ocupando el ancho de la columna son
   dos o tres líneas y se lee de golpe, que es lo que hace falta cuando lo que
   dice es «no te creas nada de esta pantalla». */
#avisoValidez {
  margin:0 0 16px; padding:12px 14px; border:1px solid var(--err);
  border-left-width:6px; border-radius:8px; background:#fff3f3; color:var(--fg);
  line-height:1.5;
}
/* El título y la explicación, en la MISMA línea: son una frase, no dos bloques. */
#avisoValidez .aviso-texto { margin-left:.35em }
#avisoValidez strong { color:var(--err) }

/* CONTAINER query, no media query, y esta es la diferencia que importa: mide la
   columna donde vive la escena, no la ventana. Con el riel abierto el viewport
   puede decir 1440 mientras la escena solo tiene 990 — una `@media` no se
   enteraría y el vídeo se quedaría aplastado al lado del plano.
   930 px de columna ≡ los 980 px de viewport de antes menos el padding: mismo
   comportamiento, medido sobre la caja correcta. */
@container (max-width: 930px) {
  /* En estrecho se apila, pero el ORDEN de la página no cambia con el ancho:
     vídeo, plano, «en este instante» — y todo lo demás sigue debajo. */
  .escena {
    grid-template-columns:minmax(0, 1fr);
    grid-template-areas:"video" "plano" "instante";
  }
}
@media (max-width: 560px) {
  body { padding:14px }
}

/* ── los plegables de lo técnico (spec §3: «el log arranca plegado») ──────── */
.plegable {
  margin-top:12px; background:#fff; border:1px solid var(--bd); border-radius:10px;
  padding:0 16px;
}
/* El triángulo va FUERA del flujo del texto (`outside`), así que sin sangría
   queda pegado al borde de la caja. `padding-left` le da su sitio. */
.plegable > summary {
  cursor:pointer; padding:11px 0 11px 18px; font-size:14px; color:var(--mut);
  list-style-position:outside;
}
.plegable > summary:hover { color:var(--fg) }
.plegable[open] > summary { color:var(--fg); border-bottom:1px solid var(--bd); margin-bottom:12px }
.plegable > *:last-child { margin-bottom:16px }
/* Un plegable cuyo contenido sigue oculto no es «una sección vacía»: es una
   sección que todavía no existe (no ha habido captura, o Konstellation no ha
   dicho nada). Enseñar su título prometería algo que no hay dentro. */
.plegable:has(> section[hidden]) { display:none }
/* Lo que hay dentro de un plegable es material de diagnóstico: su título ya lo
   da el <summary>, así que el <h2> de cada módulo se rebaja en vez de competir
   con los títulos de las secciones interpretativas de arriba. */
.plegable section { margin-top:0 }
.plegable section h2 { font-size:14px; font-weight:600; color:var(--mut) }
/* El aviso del <summary>: se enciende desde app.mjs cuando dentro hay algo que
   de verdad merece abrirse (un análisis que falló, una evaluación inválida,
   errores en el log). */
.pleg-aviso { color:var(--err); font-weight:600 }

#log {
  font:12px/1.5 ui-monospace,Consolas,monospace; background:var(--bg);
  border:1px solid var(--bd); border-radius:8px; padding:12px; height:260px; overflow:auto;
  white-space:pre-wrap;
}
/* .err viene de la tarjeta de login (14px): dentro del log manda el monoespaciado */
#log .err { font:inherit; margin:0 }

/* ── historial (historial.mjs) ────────────────────────────────────────────── */
/* Se queda al fondo de la página, no en el riel, y termina donde termina todo
   lo demás: su tope de 1080 px era el cuarto borde derecho distinto. Lo que se
   ajusta por dentro es la tabla, no la caja. */
#historial table { max-width:1080px }
#historialDetalle { margin-top:14px }
#historialDetalle h3 { font-size:15px; margin:0 0 6px }
#historialDetalle h4 { font-size:14px; margin:14px 0 6px }
#vistaHistorial { width:320px; border-radius:8px; background:#000; display:block; margin:8px 0 }

table { border-collapse:collapse; width:100%; background:#fff; border:1px solid var(--bd);
        border-radius:8px; overflow:hidden; font-size:14px }
th,td { text-align:left; padding:9px 12px; border-bottom:1px solid var(--bd) }
th { background:#f2f2f2; font-weight:600 }
tr:last-child td { border-bottom:none }

.volcado {
  background:#fff; border:1px solid var(--bd); border-radius:8px; padding:12px;
  font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre-wrap; max-height:340px; overflow:auto;
}

/* ── línea de tiempo (timeline.mjs) ───────────────────────────────────────── */
/* El eje X de TODO lo de aquí abajo es el mismo: 0 → último segundo medido del
   vídeo. Por eso el cursor, los carriles y la regla se pueden superponer sin
   más cuentas, y por eso los rótulos del eje Y van encima del lienzo (flotando)
   en vez de ocupar una columna que desalinearía los tres. */
.tl-resumen { margin:0 0 10px }
.tl-mandos { display:flex; gap:12px; align-items:center; flex-wrap:wrap; margin-bottom:10px }
.tl-mandos select { max-width:260px }
.tl-reloj { font:13px/1 ui-monospace,Consolas,monospace }

.tl-carriles {
  position:relative; background:#fff; border:1px solid var(--bd);
  border-radius:8px; overflow:hidden; cursor:crosshair;
}
.carril { border-bottom:1px solid var(--bd) }
.carril:last-of-type { border-bottom:none }
.carril-cab {
  display:flex; align-items:center; gap:10px; padding:5px 10px; font-size:13px; background:#f7f7f7;
  cursor:default;   /* la cabecera no es lienzo: pinchar ahí no mueve el vídeo */
}
.carril-nombre { font:600 13px/1.4 ui-monospace,Consolas,monospace }
/* El `margin-left:auto` va en el PRIMER elemento del grupo de la derecha, que
   desde el 26-09-2026 es la escala: a partir de ahí, metadatos y mandos. Antes
   estaba en la cobertura, que iba después del valor, y por eso el valor viajaba
   al borde con ella. Y los metadatos bajan de 13 px: el eje del carril es un
   dato fijo y pesaba igual que el nombre del HCI y que el valor que cambia. */
.carril-escala { margin-left:auto; font-size:11.5px }
/* El conmutador de eje (Tarea 17, A6): un botón de texto, no un `<select>` —
   solo hay dos estados y alternar es más rápido que abrir un desplegable.
   Solo aparece cuando el HCI declara catálogo (`ESCALAS`, escalas.mjs): sin
   eso el eje sale siempre del observado y no habría nada que conmutar. */
.carril-eje {
  font-size:11.5px; padding:1px 6px; border-radius:10px; border:1px solid var(--bd);
  background:#fff; cursor:pointer;
}
.carril-eje:hover { background:#f0f0f0 }
.carril-eje[aria-pressed="true"] { border-color:#1a5fb4; color:#1a5fb4 }
.carril-cobertura { font-size:11.5px }
/* QA §9: el rojo (`.err`) era la ÚNICA señal — el texto seguía teniendo la
   misma forma que al 100%. La clase ya existe; esto solo le añade un símbolo
   delante para que quien no distingue el color siga viendo que algo cambió.
   No toca `timeline.mjs`: se engancha a la clase que ese módulo ya pone.
   Tarea 21: `.err` ya no lo dispara la cobertura baja, lo dispara la PÉRDIDA
   (`patronDePresencia`, timeline.mjs) — una cadencia propia (Engine
   muestreando más lento a propósito) sale en gris (`.mut`), sin el `⚠`. */
.carril-cobertura.err::before { content:'⚠ '; }
/* Sin `min-width:80px` ni `text-align:right`: pegado al nombre, esa caja de 80
   px con el número a la derecha dejaba ~50 px de hueco entre los dos, que es
   justo lo que este cambio viene a cerrar. El ancho fijo estaba para que el
   número no bailase al cambiar de dígitos, y eso lo resuelve ahora el
   tabular-nums sin reservar hueco de más. */
.carril-valor { font:13px/1.4 ui-monospace,Consolas,monospace; font-variant-numeric:tabular-nums }
/* Único control destructivo de la línea de tiempo, y MEDIDO en 25x22 px: por
   debajo del mínimo de 24x24, y rodeado de un área que sí reacciona al clic
   llevando el vídeo a otro instante. El `font-size:15px` se conserva: sin él
   la × hereda los 13 px de la cabecera y el arreglo se come a sí mismo. */
.carril-quitar {
  min-width:26px; min-height:26px; padding:0; font-size:15px;
  display:grid; place-items:center; border-radius:6px;
}
.carril-quitar:hover { background:#f0f0f0 }

/* La tira de estadísticos (Tarea 17, A6): mín/máx/mediana/σ/p5-p95, en las
   unidades PROPIAS del HCI —no las del eje elegido, que solo decide cuánto
   espacio ocupa la serie en pantalla—. Va debajo de la cabecera y encima del
   lienzo: es el dato que revela una variación que un eje de catálogo grande
   (`valence` −100..100, `arousal` 0..100) puede esconder por completo. */
.carril-stats {
  padding:2px 10px; font:12px/1.4 ui-monospace,Consolas,monospace;
  font-variant-numeric:tabular-nums; background:#fbfbfb; border-bottom:1px solid var(--bd);
}

/* El alto lo fija `aplicarAlto` (timeline.mjs) con `--alto-carril`. 56 px es el
   modo compacto: bueno para ver diez carriles a la vez y localizar dónde pasa
   algo, pero aplasta la curva —en un eje 0..100, diez puntos de HCI son cinco
   píxeles y medio—. El mando «Alto» sube a 130 o 260 px sin repintar nada:
   el viewBox del SVG sigue midiendo 56 y `preserveAspectRatio="none"` lo
   estira. */
.tl-carriles { --alto-carril:56px }
.carril-cuerpo { position:relative; height:var(--alto-carril, 56px) }
.carril-cuerpo svg { display:block; width:100%; height:var(--alto-carril, 56px) }

/* La rejilla de cuartos y sus valores se dibujan siempre y se enseñan solo con
   sitio: a 56 px serían un rayado que tapa la serie. */
.tl-rejilla { stroke:#ececec; stroke-width:1; vector-effect:non-scaling-stroke }
.carril-marca {
  position:absolute; right:6px; transform:translateY(-50%);
  font:11px/1 ui-monospace,Consolas,monospace; color:var(--mut);
  background:rgba(255,255,255,.75); padding:0 3px; border-radius:2px;
  pointer-events:none;
}
.tl-carriles[data-alto="compacto"] .tl-rejilla,
.tl-carriles[data-alto="compacto"] .carril-marca { display:none }
/* Sin non-scaling-stroke el estirado horizontal del viewBox adelgazaría el
   trazo de forma distinta según el ancho de la ventana. */
.tl-serie { fill:none; stroke:#1a5fb4; stroke-width:1.5; vector-effect:non-scaling-stroke }
.tl-punto { fill:#1a5fb4 }
.tl-cero { stroke:#bbb; stroke-width:1; stroke-dasharray:3 3; vector-effect:non-scaling-stroke }
.carril-tope {
  position:absolute; left:4px; font-size:10px; color:var(--mut);
  background:rgba(255,255,255,.78); padding:0 2px; pointer-events:none;
}
.carril-tope-alto { top:1px }
.carril-tope-bajo { bottom:1px }

.tl-cursor { position:absolute; top:0; bottom:0; width:0; border-left:2px solid var(--err); pointer-events:none }

/* Las bandas de los momentos dominantes (Tarea 14, momentos.mjs): van en el
   marcado DESPUÉS de los carriles y ANTES del cursor, así que por orden de
   pintado quedan por encima de la serie (semitransparentes, no la tapan) y
   por debajo del cursor (que sigue leyéndose encima). Ámbar, no rojo (el
   cursor) ni azul (la serie): es una tercera cosa, un RANGO, no un instante
   ni un valor.
   `overflow:hidden` es lo que deja que `.tl-banda-etiqueta` se recorte con
   elegancia en una banda estrecha, en vez de desbordar sobre los carriles
   vecinos (revisión 26-09-2026: el rótulo pasó de un `title` inalcanzable —
   `pointer-events:none` le impedía aparecer NUNCA— a texto visible).
   Revisión-e2e (punto 2): el usuario pidió un tooltip al pasar el ratón por
   la banda («faltaría... para que al poner el ratón encima se identifique
   fácilmente»), y con `pointer-events:none` el `title` de `bandaHTML`
   (timeline.mjs) seguía siendo tan inalcanzable como el de antes — por eso
   se quita aquí. Comprobado que esto NO le roba el clic al carril: el
   `onclick` que mueve el vídeo está delegado en `.tl-carriles`, el
   contenedor (`timeline.mjs:refs.carriles.onclick`), y calcula la posición
   con `e.clientX` contra el rectángulo del contenedor — no con `e.target` —
   así que da igual qué elemento reciba el evento; solo excluye botones y
   `.carril-cab`, ninguno de los cuales es esta banda. El cursor
   `crosshair` de `.tl-carriles` se hereda igual (no se fija aquí ninguno
   propio), así que la banda sigue pareciendo tan pinchable como el resto
   del carril. */
.tl-banda {
  position:absolute; top:0; bottom:0; background:rgba(184,134,11,.14);
  border-left:1px solid rgba(184,134,11,.55); border-right:1px solid rgba(184,134,11,.55);
  overflow:hidden;
}
/* El mismo ocre que el borde de la banda (`rgb(184,134,11)`), a tamaño
   pequeño y pegado al borde izquierdo. `white-space:nowrap` +
   `text-overflow:ellipsis` es lo que recorta el texto sin desbordar cuando
   la banda es más estrecha que la etiqueta: el `overflow:hidden` del padre
   ya corta el elemento, esto corta el TEXTO con un "…" en vez de a lo bruto.
   `pointer-events:none`: el hover (y por tanto el `title`) tiene que
   resolverse SIEMPRE contra `.tl-banda`, tanto si el ratón pasa por encima
   del rótulo como si pasa por el resto de la banda — sin esto, sobre el
   texto el tooltip sería el de este `<span>` (que no lleva `title` propio,
   así que no mostraría nada) en vez de heredar el de su padre en todos los
   navegadores por igual. */
.tl-banda-etiqueta {
  display:block; padding:1px 3px 0; font-size:10px; line-height:1.3;
  color:rgb(184,134,11); font-weight:600;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  pointer-events:none;
}

/* Los huecos de medición (C3 de revisión-e2e, `huecos.mjs:bandasDeHuecos`):
   la MISMA banda que un momento dominante (mismo `.tl-banda`, mismo
   mecanismo de `pintarBandas`), pero con su propio color — un gris rayado,
   ni el ámbar de un momento (que SÍ tiene medición, solo que destacada) ni
   el rojo del cursor (que no es un rango) — para que el evaluador distinga
   de un vistazo "aquí hubo algo notable" de "aquí no hubo NINGUNA medición".
   El patrón rayado (`repeating-linear-gradient`) refuerza la idea de "hueco"
   frente al relleno sólido de `.tl-banda`, para quien no distinga colores. */
.tl-banda-hueco {
  background:repeating-linear-gradient(
    45deg, rgba(90,90,90,.22), rgba(90,90,90,.22) 4px,
    rgba(90,90,90,.08) 4px, rgba(90,90,90,.08) 8px
  );
  border-left:1px solid rgba(90,90,90,.65); border-right:1px solid rgba(90,90,90,.65);
}
.tl-banda-hueco .tl-banda-etiqueta { color:rgb(90,90,90); }

/* Las MARCAS de instante (`timeline.mjs:marcaHTML`, hoy los eventos de
   contraste de `eventos.mjs`): una LÍNEA, no una superficie. Es la tercera
   cosa que se dibuja sobre los carriles y tiene que distinguirse de las otras
   dos de un vistazo: el ámbar relleno de `.tl-banda` es un RANGO con medición
   destacada, el gris rayado de `.tl-banda-hueco` es un RANGO sin ninguna
   medición, y esto es un INSTANTE — un cruce de cuadrante, que no dura.
   Violeta y a trazos: el color la separa del ámbar, del gris y del azul de la
   serie (`.tl-serie`, #1a5fb4), y el trazo discontinuo la separa del cursor
   (`.tl-cursor`, rojo y CONTINUO), que es la otra línea vertical de la
   sección — para quien no distinga colores, la forma ya basta.
   Va DESPUÉS de las bandas en el marcado, así que por orden de pintado se lee
   sobre ellas y bajo el cursor.

   La caja de ratón mide 3 px y la línea 2, así que lo que se queda el `title`
   de la marca es la línea que se ve, ±0,5 px. La primera versión le daba 9 px
   —para que el `title` no viviera en un objetivo de 2 px— y eso le ROBABA a la
   banda ámbar su propio tooltip en los 7 px invisibles de alrededor: el que el
   usuario pidió expresamente («faltaría un tooltip sobre las bandas amarillas
   … para que al poner el ratón encima se identifique fácilmente»). Medido con
   `elementFromPoint` sobre una marca que cruza una banda: a 3 px del centro
   responde ya la banda, no la marca. Perder ese tooltip para ganar puntería es
   un mal cambio; apuntar a la línea es lo que el evaluador hace de todos modos,
   porque es lo único que ve.
   `translateX(-1.5px)` centra la caja sobre el instante y `left:0.5px` deja la
   línea de 2 px justo en su medio, así que lo que se ve sigue estando EN el
   segundo que marca. Las tres cifras van atadas entre sí por el test «la clase
   de marca … es una LÍNEA» (`test/estructura-pagina.test.mjs`), que comprueba
   que la caja no sobresale de la línea más de 1 px por lado y que la línea
   queda centrada en ella.
   Sin `pointer-events:none` (a propósito: sin ratón no hay `title`) y sin
   robarle el clic al carril, por lo mismo que `.tl-banda`: el `onclick` está
   delegado en `.tl-carriles` y se calcula con `e.clientX`, no con `e.target`. */
.tl-marca {
  position:absolute; top:0; bottom:0; width:3px; transform:translateX(-1.5px);
}
.tl-marca::before {
  content:''; position:absolute; top:0; bottom:0; left:0.5px;
  border-left:2px dashed #7a1fa2;
}

.tl-regla { position:relative; height:16px; margin-top:3px; font-size:11px; color:var(--mut) }
/* Las cifras de la regla (`reglaHTML`): texto bajo los carriles. Se llamaban
   `.tl-marca`, que no estaba acotada a `.tl-regla` y hoy es la línea de
   arriba — dos cosas sin relación compartiendo nombre y estilo. */
.tl-regla-marca { position:absolute; transform:translateX(-50%); white-space:nowrap }
.tl-regla-marca-ini { transform:none }

/* ── el plano del circumplejo (circumplejo.mjs) ───────────────────────────── */
/* El plano es CUADRADO y no puede dejar de serlo: si se estirara, la distancia
   que recorre el punto en valencia y la que recorre en activación dejarían de
   medir lo mismo sobre la pantalla, y la forma de la trayectoria —que es justo
   lo que se viene a leer— saldría deformada. De ahí el aspect-ratio. */
.cx-resumen { margin:0 0 12px }
.cx-caja { display:flex; gap:28px; align-items:center; flex-wrap:wrap }

/* Los rótulos de los ejes rodean el cuadrado: arriba/abajo la activación,
   a los lados la valencia. Así se entiende el plano sin que nadie lo explique. */
/* `min-width:0` + `minmax(0, …)`: sin los dos, este grid es un elemento flex
   cuyo tamaño mínimo es su contenido (360 px de plano + los dos rótulos
   verticales = 408 px), y eso fijaba un ancho MÍNIMO de documento de 486 px —
   la página se desplazaba en horizontal entre ~420 y ~500 px de ventana. El
   tope de 360 px se mantiene: por encima no crece. */
.cx-marco {
  /* `flex:1 1 100%` + `1fr`: el tope de 360 dejaba 110 px muertos a la derecha
     del cuadrado, de toda la altura de la escena, en el elemento que la
     herramienta existe para mirar. MEDIDO: 360 -> 422 en una columna de 470.
     El `flex-basis:100%` es lo que hace que la pista tenga de qué repartir. Sin
     él, `1fr` se dimensiona por lo que aportan los ítems del grid, y `.cx-plano`
     no aporta nada (`width:100%` contra un contenedor indefinido, con el lienzo
     y los rótulos en `position:absolute`): el único que cruza la pista es el
     rótulo «activación alta» y el cuadrado cae a 78 px. MEDIDO también.
     Y es lo que garantiza que la lectura y el marco sigan envolviendo a
     cualquier ancho, que es la maqueta que busca el orden del ARMAZÓN.
     NO se sube el mínimo de la pista de `.escena` (340 px): los umbrales de
     1380 px del riel y 930 px de la container query están calculados con él. */
  /* El tope va AQUÍ y no en `.cx-plano`: con la pista del plano llevándose
     todo lo que sobra, topar solo el cuadrado lo centraba en una columna ancha
     y mandaba los rótulos de los ejes a los bordes, despegados de él. Topando
     el marco entero, cuadrado y rótulos viajan juntos y el ancho de más se lo
     queda la lectura de arriba, que es texto.
     520 = 470 del cuadrado + los dos rótulos verticales + sus huecos. */
  display:grid; gap:6px; min-width:0; flex:1 1 100%; max-width:520px;
  grid-template-columns:auto minmax(0, 1fr) auto;
  grid-template-rows:auto auto auto;
  grid-template-areas:". arriba ." "izq plano der" ". abajo .";
  align-items:center; justify-items:center;
}
.cx-eje { font-size:12px; color:var(--mut); text-transform:lowercase; white-space:nowrap }
.cx-eje-arriba { grid-area:arriba } .cx-eje-abajo { grid-area:abajo }
.cx-eje-izq { grid-area:izq; writing-mode:vertical-rl; transform:rotate(180deg) }
.cx-eje-der { grid-area:der; writing-mode:vertical-rl }

.cx-plano {
  grid-area:plano; position:relative; width:100%; aspect-ratio:1;
  background:#fff; border:1px solid var(--bd); border-radius:8px;
}
.cx-lienzo { position:absolute; inset:0; display:block }
/* Los cuadrantes van nombrados por lo que SIGNIFICAN, no solo por su número:
   «2» no le dice nada a quien evalúa; «desagradable y activado», sí. */
/* Medido (ver el informe de esta tarea): #9a9a9a a 10.5px daba 2,81:1 contra
   blanco — la única leyenda que explica el plano, y la menos legible de toda
   la página. `var(--mut)` (#666) da 5,74:1 contra blanco y 5,18:1 contra el
   tinte del cuadrante activo (rgba(26,95,180,.07) sobre blanco): pasa AA con
   margen en los dos fondos posibles. El tamaño sube a 12px porque el fallo
   medido citaba los dos a la vez (contraste Y tamaño), y el contraste solo no
   arregla lo pequeñas que son. */
.cx-cuad {
  position:absolute; font-size:12px; line-height:1.3; color:var(--mut);
  max-width:44%; pointer-events:none;
}
.cx-cuad1 { top:8px; right:8px; text-align:right }
.cx-cuad2 { top:8px; left:8px }
.cx-cuad3 { bottom:8px; left:8px }
.cx-cuad4 { bottom:8px; right:8px; text-align:right }

/* ── el bloque «Tramo dominante» (tramoDominante.mjs) ─────────────────────── */
/* Vive DENTRO de `#plano`, como hermano de `.cx-caja` (lo cuelga `app.mjs`,
   NO el `ARMAZON` de `pintarCircumplejo`: los momentos dominantes salen de
   `enrichment_summary`, un dato que `circumplejo.mjs` no conoce). Ocupa el
   hueco que deja el plano al no estirarse hasta la altura de la columna del
   vídeo — `.escena` usa `align-items:start`, así que `#plano` no se estira a
   la pista entera. MEDIDO en la página real (26-09-2026, ventana 1646×838,
   riel abierto): la columna de `#plano` mide 340 px de ancho, el cuadrado
   `.cx-plano` (dentro de `.cx-marco`, que incluye además el rótulo «activación
   baja») termina en y=788 y la pista de `.escena` llega hasta y=982 — unos
   194 px de blanco muerto bajo el plano, sin alargar la página. */
.tramo-dominante {
  margin-top:8px; padding-top:6px; border-top:1px solid var(--bd);
  font-size:11px; line-height:1.4; color:var(--fg);
}
.tramo-dominante-cabecera, .tramo-dominante-titulo { margin:0 0 3px; font-weight:600; color:var(--mut) }
.tramo-dominante-rango { font-weight:400; color:var(--fg) }
.tramo-dominante-dato { margin:0 0 2px; display:flex; gap:6px }
.tramo-dominante-etq { color:var(--mut); min-width:72px; flex:none }
.tramo-dominante-fila { margin:0 0 3px }
.tramo-dominante-fila:last-child { margin-bottom:0 }

.cx-lectura { flex:1; min-width:200px }
/* El aviso de «esto no es el fotograma que se está viendo» (`.cx-cuando`) no
   tenía regla en ninguna de las dos hojas: heredaba el `margin:0` de `.err` y
   quedaba pegado a la emoción de 34 px, como si fuera parte de ella. */
.cx-cuando { margin:0 0 6px; font-size:13px }
/* La emoción del instante, en grande: es lo que el evaluador compara con la
   cara del vídeo. */
.cx-emocion { font-size:34px; line-height:1.1; font-weight:600 }
.cx-emocion.cx-sin { color:var(--mut); font-weight:400 }
.cx-zona { font-size:13px; margin-top:4px }
/* Solo sale cuando la zona de la emoción y la del punto dibujado NO coinciden.
   No es un error (el motor tiene razón al elegir así), así que no va en rojo:
   va en el gris de dato, con un filete que lo ata a la línea de la zona. */
/* Hermano de `.cx-marco`, no hijo de `.cx-lectura`: aparece y desaparece en
   cada fotograma, y encima del cuadrado lo movía 79 px arriba y abajo durante
   toda la reproducción. `flex-basis:100%` para que caiga en su propia fila. */
.cx-desajuste {
  flex:1 1 100%;
  margin:6px 0 0; padding-left:9px; border-left:2px solid var(--bd);
  font-size:12.5px; line-height:1.45; color:var(--mut);
}
/* En UNA línea: eran dos pares de etiqueta y valor ocupando dos renglones
   enteros para cuatro palabras y dos números. En fila se leen igual de bien y
   el bloque de la lectura baja 20 px. */
.cx-coords {
  display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 6px;
  margin:12px 0 0; font-size:13px;
}
.cx-coords dt:not(:first-of-type) { margin-left:14px }
.cx-coords dt { color:var(--mut) }
.cx-coords dd { margin:0; font:13px/1.5 ui-monospace,Consolas,monospace }
.cx-reloj { margin:10px 0 0; font:12px/1.4 ui-monospace,Consolas,monospace }

/* === instante === */
/* El panel «en este instante» (instante.mjs). No repite emoción, cuadrante ni
   coordenadas: eso ya lo pinta el plano de arriba (circumplejo.mjs). Aquí solo
   va lo que le falta al evaluador para juzgar si la lectura se mueve con
   sentido: hacia dónde va, a qué velocidad, cuánto lleva así, si cruzó un eje,
   y la frase de `narration.py` para ESE instante. */
.inst-reloj { font-weight:400; font-size:13px }
.inst-aviso { margin:0 0 12px }
.inst-datos {
  display:grid; grid-template-columns:auto 1fr; gap:4px 14px;
  justify-content:start; margin:10px 0; font-size:13px;
}
.inst-datos dt { color:var(--mut) }
.inst-datos dd { margin:0 }
/* La frase interpretativa es lo más valioso del panel: tipografía de lectura,
   no de tabla, y con sitio propio para que se vea de un vistazo junto a la
   cara del vídeo. */
.inst-interpretacion {
  margin:14px 0 0; padding:0 0 0 14px; border-left:3px solid var(--bd);
  font-size:15px; line-height:1.5; font-style:italic;
  /* Ancho de lectura: es prosa, y bajo un vídeo ancho se iba a ~110 caracteres
     por línea. 80ch en vez de 62: con 62 quedaba encajonada en la mitad de su
     columna. Los datos de arriba (`.inst-datos`) no lo necesitan, son un `dl`
     de dos columnas alineado a la izquierda. */
  max-width:80ch;
}
.inst-interpretacion.inst-sin { color:var(--mut); font-style:normal }
/* === fin instante === */
