/* ============================================================
   Growth — estilos locales del módulo. Prefijo .gw-
   Cero overrides de .r-, cero valores crudos: todo sale de tokens.

   El acento de la vista lo carga la telaraña (`--radar-fill`, ácido del
   sistema). Aquí no hay un segundo acento: la métrica de la cabeza va en
   tinta, y los semánticos —el punto ok de un tramo cerrado, el info de una
   propuesta, el crit de un error de motor— son señal, no decoración.

   ------------------------------------------------------------
   UN PANEL NO ES UNA PÁGINA, y aquí eso cuesta dos cosas
   ------------------------------------------------------------

   1 · LOS CORTES SE MIDEN CONTRA EL PANEL, NO CONTRA LA VENTANA. Este
       módulo vive en una columna de 420 a 520 dentro de una ventana de
       1280 o de 3440. Un `@media (max-width: 720px)` nunca se cumple ahí:
       la ventana es ancha y la columna no. Los cortes de este archivo son
       `@container`, y el contenedor es `#gwRoot`.

       Lo que se fue del todo: el `.gw-top` de dos columnas (mapa | lista).
       Era la respuesta correcta a una página de 1240 y en 478 de columna
       recortaba la lista en silencio —medido: la fila pedía 264 px en una
       caja de 223 y el panel entero desbordaba 21 px a lo ancho—. Un
       módulo que declara 420/520 declara una columna; dos columnas dentro
       de ella no caben nunca.

   2 · LA FIGURA SE MIDE CONTRA EL ALTO QUE LE SOBRA. La telaraña quiere
       ser cuadrada, y una columna alta y angosta es justo donde eso
       estorba: con `width: 100%` se lleva 478 de alto y empuja la lista
       fuera de la pantalla. El patrón —el mismo que usa `.rel-red` en
       Relaciones con el grafo— tiene tres piezas:

         · el módulo es una columna flex del alto del panel, así que la
           figura y su alrededor se reparten UN presupuesto;
         · la figura vive en una caja que se queda con lo que sobra
           (`flex: 1`, con piso) y esa caja es un CONTENEDOR DE TAMAÑO;
         · adentro, el ancho de la figura sale de ese alto multiplicado por
           la proporción del componente (`--radar-ar`, `--graph-ar`), que
           es la única manera de que el consumidor gobierne el alto sin
           fijarlo —`.r-radar` y `.r-graph` sacan su alto de su ancho y
           piden explícitamente que nadie les escriba `height`.

       Así la figura es tan grande como el hueco permite, sigue cuadrada, y
       nunca es ella la que estrena una barra de scroll.
   ============================================================ */

/* ---------- El módulo, dentro del panel ----------
   La cadena de flex existe para una sola cosa: que `.gw-web` sepa cuánto
   alto le sobra. `.os-view` ya es un item flex con `min-height: 0` dentro
   del cuerpo del panel, así que su alto es el del panel; de ahí para abajo
   cada eslabón lo pasa. Romper un eslabón no rompe la vista —la figura
   vuelve a su piso— pero deja de crecer.

   `container-type: inline-size` es lo que hace consultables los cortes de
   más abajo. No contiene el alto (eso lo hace `size`, y lo usa solo la caja
   de la figura), así que el módulo sigue creciendo con su contenido. */
#viewGrow { display: flex; flex-direction: column; min-height: 0; }
#gwRoot {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  container-type: inline-size;
  container-name: gw;
}
/* En una columna flex TODO cede por omisión, y lo que cede se estruja sin
   avisar: un contenedor con `overflow` tiene mínimo automático 0 y se aplasta
   hasta desaparecer cuando la lista de abajo empuja. Aquí se declara al revés:
   nada cede, y quien crece lo pide por su nombre. `:where` deja la
   especificidad en cero para que la regla de cada pieza le gane sin pelear. */
:where(#gwRoot > *, #gwRoot > [data-gw-sec] > *, .gw-sec > *) { flex: 0 0 auto; }

/* La sección al frente se queda con el alto; la escondida es `hidden`. */
#gwRoot > [data-gw-sec] {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ---------- Cabeza ---------- */

/* El aire de abajo era `--s-8` cuando la cabeza abría una página de 1240. En
   una columna de 480 ese hueco es alto que la telaraña necesita, y la
   separación se sigue leyendo con `--s-5`: la métrica no toca nada. */
.gw-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s-6);
  margin-bottom: var(--s-5);
}
/* Sin margen arriba: el `--s-2` separaba la métrica del título de la vista, y
   ese título ya no se dibuja (lo dice la cabecera del panel; aquí quedó como
   `.r-sr`, fuera de flujo). Con el margen se quedaba un hueco de nada.

   La tinta se declara aquí a propósito: `--lcd-on` del sistema es
   `currentColor` —para poder seguir la tinta de la tarjeta donde se use— así
   que el numeral hereda el color del contenedor, y en `.os-page` eso no es
   `--ink`. Sin esta línea la métrica principal salía un escalón más apagada
   que cuando era `.r-metric`, que sí fija su color. */
.gw-metric { margin: 0; color: var(--ink); }
.gw-stats { margin: 0; }

/* ---------- Rótulo con la explicación en el `title` ----------
   `width: max-content` no es cosmético: sin él el `<p>` ocupa la columna
   entera y el `title` se dispara con el ratón a 400px del texto, lo que se
   lee como un tooltip que salió de la nada. El blanco de hover es la
   palabra. */
.gw-lbl { width: max-content; max-width: 100%; }
/* La fracción de cuántas habilidades entraron al mapa. Un dato, no un
   adorno: la regla del módulo es que una gráfica que esconde datos lo dice a
   la vista. Va en la tinta más callada, igual que el denominador de una
   escala. */
.gw-lbl__n { color: var(--ink-4); }

/* ---------- Estado vacío ---------- */

/* `width: 100%` no es redundante junto a `margin-inline: auto`, y esa es la
   trampa: en una columna FLEX el eje transversal es el horizontal, así que un
   margen automático a los lados apaga el `stretch` y la caja se encoge a su
   contenido. Con el ancho declarado el centrado sigue siendo el de siempre y
   la caja vuelve a medir la columna. Aplica igual a `.gw-x`. */
.gw-blank {
  flex: 1 1 auto;
  padding: var(--s-8) var(--s-4);
  text-align: center;
  display: grid;
  gap: var(--s-3);
  justify-items: center;
  align-content: center;
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
}
.gw-blank__s {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  justify-content: center;
  margin-top: var(--s-3);
}

/* ---------- Las dos superficies del mapa ----------
   El mapa arriba y la lista abajo, en ese orden y SIEMPRE: son la misma
   lectura en dos gramáticas y la telaraña va primero porque manda. Las dos
   columnas de la época de la página se fueron (ver la cabecera del archivo).

   Sin canto inferior ni aire de cierre: separaba esto de los programas, y los
   programas ya no vienen abajo —viven en su propia sección—. Un separador que
   no separa nada cuesta 64px de hueco al final de la sección. */
.gw-top {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-6);
}
.gw-sec { min-width: 0; }
/* El mapa se queda con lo que sobra —dentro de él solo crece la telaraña— y
   la lista pide lo suyo y no cede: es contenido, no relleno.

   `min-height: min-content` y no `0`, y es la diferencia entre "se acomoda" y
   "se encima": con `0` el flex encogía el mapa por debajo de lo que sus
   propios hijos piden y el rótulo de la lista aterrizaba encima de la última
   fila de chips (medido: 289 px de caja para 380 px de contenido). Con el
   mínimo puesto, cuando ya no cabe lo que se pasa es la sección entera y
   quien scrollea es el cuerpo del panel — que es el único que debe. */
.gw-sec--map {
  flex: 1 1 auto;
  min-height: min-content;
  display: flex;
  flex-direction: column;
}
.gw-sec--list { flex: 0 0 auto; }

/* ---------- La caja de la telaraña ----------
   Es el contenedor de tamaño del patrón: se lleva el alto que sobró y de ahí
   sale el ancho de la figura. `place-items: center` porque cuando la columna
   es más ancha que el alto disponible —el caso normal en un panel— la figura
   queda cuadrada y centrada en vez de estirarse a una elipse.

   El piso no es cosmético: por debajo de 200 los cinco anillos y las
   etiquetas de eje se vuelven una mancha, y ahí es mejor pagar scroll que
   dibujar algo que no se lee. Cuando el piso gana, quien scrollea es el
   cuerpo del panel, no la figura. */
.gw-web {
  flex: 1 1 auto;
  min-height: var(--gw-web-min);
  container-type: size;
  display: grid;
  place-items: center;
  margin-block: var(--s-2);
  --gw-web-min: 200px;
  /* El techo de siempre: la lectura de una telaraña no mejora después de
     cierto tamaño, y a 360 ya es la pieza más grande de la sección. Es el
     mismo número que el sistema usa para acotar figuras dentro del panel
     (`--flyout-fig`), así que los dos lados de la caja se cierran con la
     misma cifra y la figura no letterboxea. */
  --gw-web-max: var(--flyout-fig, 360px);
}
/* El marco es lo único que se dimensiona; el componente lo llena y saca su
   alto de ahí, como pide su contrato. `100cqh` es el alto que la caja de
   arriba acabó de repartir. */
.gw-web__f {
  width: 100%;
  max-width: min(100%, var(--gw-web-max), calc(100cqh * var(--radar-ar, 1)));
}

.gw-note { margin: var(--s-3) 0 0; }

/* Botón de alta: la misma pastilla punteada que Relaciones usa para
   "+ interés". Es una invitación, no una acción principal. */
.gw-add {
  height: 26px;
  border: var(--stroke) dashed var(--hairline-strong);
  background: transparent;
  border-radius: var(--r-pill);
  color: var(--ink-3);
  font-size: var(--t-label);
  font-family: inherit;
  padding-inline: var(--s-3);
  margin-top: var(--s-4);
  transition: color var(--d-fast) var(--ease), border-color var(--d-fast) var(--ease);
}
.gw-add:hover, .gw-add:focus-visible { color: var(--ink); border-color: var(--ink-4); }

/* Alta de habilidad y de programa: el `.r-capture` del sistema dentro de un
   bloque propio. El bloque existe porque el componente es `inline-grid` —un
   margen encima arrastraría el interlineado— y porque así la vista no declara
   NADA sobre `.r-*`. La caja del flujo sigue siendo la del disparador, 28×28, y
   no cambia al expandirse: el campo se abre en la capa superior. */
.gw-cap { display: flex; margin-top: var(--s-3); }
/* En una barra ya armada el aire lo pone el `gap` del contenedor. */
.gw-inv .gw-cap, .gw-prog__ops .gw-cap { margin-top: 0; }

/* ---------- La tira de ejes ----------
   No es una leyenda: es la lectura numérica que la telaraña no imprime (ahí
   el dato vive en el title) y la puerta al editor. Por eso el SVG se queda
   decorativo, como pide el sistema, y esta tira es la tabla que lo acompaña. */

.gw-axes {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}
.gw-ax {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  max-width: 100%;
  min-width: 0;
  /* Alto de .r-chip: el eje se para junto a los chips del sistema (la escala
     propia, la etiqueta de un grupo) y tienen que medir lo mismo. */
  height: 26px;
  padding-inline: var(--s-3);
  border: var(--stroke) solid var(--hairline);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: var(--t-label);
  transition: background var(--d-fast) var(--ease), border-color var(--d-fast) var(--ease);
  animation: os-in var(--d-base) var(--ease) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
/* El eje es la entrada al editor: toda su caja debe aceptar un dedo. */
.os-shell[data-os-mobile] .gw-ax { min-height: var(--orb-size); }

.gw-ax:hover, .gw-ax:focus-visible { background: var(--surface-2); border-color: var(--hairline-strong); }
.gw-ax--on { background: var(--surface-sunk); border-color: var(--hairline-strong); }

/* Cuadrado, como el punto de estado del sistema y como el vértice que la
   telaraña dibuja en cada eje: es el mismo dato en otra escala. */
.gw-ax__d {
  flex: 0 0 auto;
  width: var(--s-2);
  height: var(--s-2);
  background: var(--ink);
}
.gw-ax__n {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  letter-spacing: var(--tr-tight);
}
.gw-ax__v { flex: 0 0 auto; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.gw-ax__t { color: var(--ink-3); }

/* El denominador de la escala ("/10", "/8"). Es la tinta más callada del
   módulo, un escalón por debajo del →objetivo: dice de qué tamaño es la
   escala y nada más. Aparece una vez por cifra y solo donde no hay gráfica
   que ya la cargue — la regla del renglón la carga, un chip no. */
.gw-of { color: var(--ink-4); }

/* ---------- La lista: idiomas y lo demás ---------- */

.gw-rows {
  border-top: var(--stroke) solid var(--hairline);
  margin-top: var(--s-3);
}
.gw-row {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-3) var(--s-2);
  border: 0;
  border-bottom: var(--stroke) solid var(--hairline);
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  text-align: left;
  transition: background var(--d-fast) var(--ease);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
.gw-row:hover, .gw-row:focus-visible { background: var(--surface-2); }
.gw-row--on { background: var(--surface-2); }
.gw-row__n {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--t-body-sm);
  letter-spacing: var(--tr-tight);
}
.gw-row__l { flex: 0 0 auto; }
/* General que no alcanzó lugar en el mapa: el punto lo dice, el title lo
   explica. Sin palabras en el renglón. */
.gw-row__off { flex: 0 0 auto; }
.gw-row__v {
  flex: 0 0 auto;
  font-size: var(--t-label);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* Regla de nivel: la gráfica del renglón, porque aquí no hay telaraña que
   dibuje el nivel. Llenos hasta el nivel; aro en el objetivo. */
.gw-rule {
  flex: 0 0 auto;
  display: flex;
  gap: var(--s-1);
  align-items: center;
  /* Local del módulo, y no es un px suelto: es el cuadrito de vértice de la
     telaraña (`--radar-node`), que significa exactamente lo mismo —un nivel
     marcado sobre un eje— escalado a lo mínimo en que un cuadro HUECO con
     borde de 1px todavía se lee. A 4px el hueco y el lleno se confunden.
     No entra a tokens.css porque tiene un solo consumidor y el sistema ya
     tiene el valor del que se deriva. */
  --gw-unit: calc(var(--radar-node) * 1.5);
}
.gw-rule__u {
  box-sizing: border-box;
  width: var(--gw-unit);
  height: var(--gw-unit);
  border: var(--stroke) solid var(--ink-4);
}
.gw-rule__u--on { background: var(--ink); border-color: var(--ink); }
.gw-rule__u--tgt { outline: var(--stroke) solid var(--ink-3); outline-offset: 2px; }
/* Corte a la mitad: con diez cuadros seguidos hay que contarlos uno por uno
   para saber si dicen 6 o 7. Partidos 5+5 se lee "cinco más dos" de un
   golpe — y son los mismos cinco tramos de dos niveles que la telaraña
   dibuja con sus cinco anillos. */
.gw-rule__u--half { margin-left: var(--s-2); }

/* ---------- Editor de una habilidad ----------
   Se revela al abrir una habilidad, una a la vez. Los campos guardan al
   salir del campo, no por tecla. */

/* Tres renglones, no seis. El nombre y "dónde vive" comparten el primero —el
   segmentado mide 26px y el campo 32, así que el renglón no crece por
   juntarlos—; los tres diales comparten el segundo. Medido a 1280: 244px → 132.
   Lo que se recortó no fue contenido: fue el renglón entero que cada campo se
   estaba llevando. */
.gw-ed {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s-3);
  align-items: center;
  margin-top: var(--s-4);
  padding: var(--s-4);
  /* `--surface` y no `--surface-sunk`, y esto es «que se entienda», no
     estética: `.r-input` del sistema se pinta con `--surface-sunk` y su borde
     es transparente en reposo, así que sobre un panel del MISMO token los
     campos no tenían caja — se leían como texto puesto, no como algo que se
     puede escribir. Medido: campo y panel daban el mismo `#0f0f11` en cockpit
     y el mismo `#ededec` en claro.
     El token es `--surface` porque es la pareja que el sistema diseñó: un
     `.r-input` es el hundido DE una superficie, y ese es el escalón completo
     (`#fff` contra `#ededec` en claro, `#17171a` contra `#0f0f11` en cockpit).
     `--surface-2` también los distinguía, pero en claro por tres unidades de
     luz, que no se leen. Se arregla del lado del panel: el componente no se
     toca. Y al enfocar, el campo sube a `--surface` —queda igual que el
     panel— con el borde de `--hairline-strong` que trae el sistema: se lee
     como levantado, que es lo que significa. */
  background: var(--surface);
  border-radius: var(--r-lg);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
}
.gw-ed__n { grid-column: 1; }
.gw-ed__k {
  grid-column: 2;
  justify-self: end;
  display: grid;
  gap: var(--s-2);
  justify-items: end;
}
/* Los diales en una sola tira. `align-items: end` para que las tres cifras se
   paren en la misma línea de base aunque "Nivel" cargue su procedencia arriba. */
.gw-ed__d {
  grid-column: 1 / -1;
  display: flex;
  align-items: end;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
}
.gw-f { display: grid; gap: var(--s-2); align-content: start; min-width: 0; }
/* Un campo de dos dígitos no necesita media retícula, pero tampoco puede
   apretarse a la medida del texto: `.r-input` trae 16px de relleno por lado y
   el navegador reserva su propia flecha de incremento. Medido con el valor
   «10» dentro: a 43px la cifra se RECORTABA (contenido 49 en una caja de 41) y
   el campo se veía vacío; 64 es el primer ancho que no recorta y 72 deja el
   aire de un dígito más. Es ancho de caja, no de contenido — de ahí que un
   `ch` no sirviera. */
.gw-f--dial { flex: 0 0 auto; }
.gw-f--dial .r-input { width: 4.5rem; }
.gw-f--grow { flex: 1 1 12ch; min-width: 0; }
/* El renglón de la etiqueta: nombre del campo a la izquierda, procedencia a
   la derecha. Existe porque el nivel es el único campo que puede no ser de
   Robbie y eso hay que poder verlo sin abrir nada. */
.gw-f__l { display: flex; align-items: baseline; gap: var(--s-2); min-width: 0; }
/* Pegada a "Nivel", no empujada al canto de la columna: contra el borde
   derecho quedaba a un pelo de la etiqueta "Objetivo" de la columna de al
   lado y parecía decir de dónde salió el objetivo. */
.gw-src { flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `align-items: center` porque aquí conviven alturas distintas: la pastilla
   de "Medir" (32px) y el bote de basura del sistema (36px). Sin esto el
   `stretch` del flex las deja desalineadas por la base. */
.gw-ed__ops {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-3);
}
/* Los dos controles del pie miden lo mismo desde que "Medir" es un icono
   (36px los dos, el del sistema), así que ya no hay alturas que negociar. */
/* La consecuencia de quitar la habilidad, en su propio renglón arriba de los
   botones. Compartiendo renglón cabía a 1280 pero a 375 el panel armado
   —opaco, 126px— aterrizaba encima de la última palabra, y con dos cifras de
   programas la habría tapado en cualquier ancho. Un renglón entero cuesta una
   línea dentro de un panel que ya mide seis y quita el problema en todos los
   anchos: la explicación arriba, las acciones abajo. */
.gw-ed__warn { flex: 1 1 100%; max-width: var(--measure); }

/* Los temas que falló el examen. Es lo que come el generador de programas,
   así que se ve: esconderlo dejaría el número solo, que es justo lo que
   medir vino a arreglar. */
.gw-ed__w { grid-column: 1 / -1; display: grid; gap: var(--s-2); }
.gw-ed__wl { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* ---------- Programas ---------- */

.gw-progs { display: grid; gap: var(--s-8); }

.gw-grp { display: grid; gap: var(--s-3); }
.gw-grp__h {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s-3);
  margin: 0;
  padding-bottom: var(--s-2);
  border-bottom: var(--stroke) solid var(--hairline);
}
.gw-grp__n {
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  letter-spacing: var(--tr-tight);
  font-size: var(--t-h3);
  color: var(--ink);
}
.gw-grp--orphan .gw-grp__n { color: var(--ink-2); }

/* ---------- Habilidades sin programa ----------
   Un renglón por habilidad en vez de un encabezado de display con su propia
   invitación. Medido a 1280: 78px por habilidad → 32, y con once eso son
   864px contra 352. Las dos acciones siguen siendo un clic cada una; lo que
   se fue es el peso tipográfico de un título para algo que todavía no
   existe. */
.gw-pends { display: grid; gap: var(--s-2); }
.gw-pends__l {
  border-top: var(--stroke) solid var(--hairline);
}
.gw-pend {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: var(--s-1) var(--s-2);
  border-bottom: var(--stroke) solid var(--hairline);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
.gw-pend:hover, .gw-pend:focus-within { background: var(--surface-2); }
.gw-pend__n {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--t-body-sm);
  letter-spacing: var(--tr-tight);
  color: var(--ink);
}
.gw-pend__v { flex: 0 0 auto; }
/* Las acciones se revelan, con el espacio ya reservado: sin `pointer-events`
   apagado un clic a ciegas dispararía una generación que nadie pidió. */
.gw-pend__a {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--s-1);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--d-fast) var(--ease);
}
.gw-pend:hover .gw-pend__a,
.gw-pend:focus-within .gw-pend__a { opacity: 1; pointer-events: auto; }
.gw-pend__a .gw-cap { margin-top: 0; }

.gw-inv { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; }
/* El botón de alta manual va a la misma línea de base que el sólido: es la
   salida de emergencia cuando no hay motor y no debe verse como un apéndice. */
.gw-inv .gw-add { margin-top: 0; }

.gw-prog {
  display: grid;
  gap: var(--s-2);
  padding: var(--s-4) 0 var(--s-3);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
.gw-prog + .gw-prog { border-top: var(--stroke) solid var(--hairline); }

.gw-prog__h { display: flex; align-items: center; gap: var(--s-3); }
.gw-prog__n {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  letter-spacing: var(--tr-tight);
  font-size: var(--t-body);
  color: var(--ink);
}
.gw-prog--done .gw-prog__n { color: var(--ink-2); }
.gw-prog__c { flex: 0 0 auto; }
/* Quitar el programa es secundario y destructivo: se revela, con el ancho ya
   reservado para que el contador no se mueva al aparecer. Ahora envuelve al
   `.r-confirm` del sistema —36px, lo mismo que medía el `.r-icon-btn` que
   estaba antes—, así que el alto del encabezado no cambió.

   El color del hover ya no se toca aquí: el componente lo trae. */
.gw-prog__x {
  flex: 0 0 auto;
  display: inline-flex;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--d-fast) var(--ease);
}
.gw-prog:hover .gw-prog__x,
.gw-prog:focus-within .gw-prog__x { opacity: 1; pointer-events: auto; }
/* Armado se queda a la vista aunque el ratón se vaya. `focus-within` ya lo
   cubre —el componente enfoca el "sí" al armar—, pero un panel destructivo
   invisible es un fallo demasiado caro para depender de una sola condición. */
.gw-prog__x:has(.r-confirm[data-armed]) { opacity: 1; pointer-events: auto; }

.gw-prog__t { max-width: 260px; }

/* ---------- Pasos ---------- */

.gw-steps { list-style: none; margin: var(--s-2) 0 0; padding: 0; display: grid; gap: var(--s-1); }
/* Alineación de un hijo dentro de mi caja, no restyling del componente —
   misma forma que `.rel-log__i .r-status` en Relaciones. */
.gw-step {
  display: flex;
  align-items: baseline;
  gap: var(--s-3);
  padding-block: var(--s-2);
  border-bottom: var(--stroke) solid var(--hairline);
  animation: os-in var(--d-base) var(--ease) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
.gw-step:last-child { border-bottom: 0; }
.gw-step .r-check { flex: 0 0 auto; align-self: center; }
.gw-step__t {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-body-sm);
  line-height: var(--lh-body);
  color: var(--ink);
  overflow-wrap: anywhere;
}
.gw-step--done .gw-step__t { color: var(--ink-4); text-decoration: line-through; }

/* Cuándo se cerró y el × de quitar: secundarios, se revelan al hover y al
   foco. Con opacidad y ancho reservado, para que el renglón no salte. */
.gw-step__w {
  flex: 0 0 auto;
  min-width: var(--s-16);
  text-align: right;
  opacity: 0;
  transition: opacity var(--d-fast) var(--ease);
}
.gw-step__x {
  flex: 0 0 auto;
  align-self: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--d-fast) var(--ease), color var(--d-fast) var(--ease);
}
.gw-step:hover .gw-step__w,
.gw-step:focus-within .gw-step__w { opacity: 1; }
.gw-step:hover .gw-step__x,
.gw-step:focus-within .gw-step__x { opacity: 1; pointer-events: auto; }
.gw-step__x:hover, .gw-step__x:focus-visible { color: var(--crit); }

/* Capturar un paso a mano vive siempre a la vista: es el camino que
   funciona con el motor apagado y esconderlo sería dejar la vista muerta. */
.gw-prog__ops {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin-top: var(--s-2);
}
.gw-prog__ops .gw-add { margin-top: 0; }
.gw-in { height: 30px; max-width: 320px; }

/* ---------- Propuesta ----------
   Vive fuera del store hasta que se acepta, y se ve distinta a propósito:
   canto punteado a la izquierda, tinta más callada. Nada aquí es todavía
   parte del programa. */

.gw-prop {
  margin-top: var(--s-3);
  padding: var(--s-3) 0 var(--s-3) var(--s-4);
  border-left: var(--stroke-2) dashed var(--hairline-strong);
  display: grid;
  gap: var(--s-2);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
}
.gw-prop__h { display: flex; align-items: center; gap: var(--s-2); margin: 0; }
.gw-prop__n { color: var(--ink); }
/* El texto crudo que va llegando: seña de vida, no lectura. Se queda en una
   línea recortada para que el bloque no crezca a saltos mientras escribe. */
.gw-prop__raw {
  margin: 0;
  min-height: var(--s-5);
  max-height: var(--s-12);
  overflow: hidden;
  font-family: var(--font-mono);
  overflow-wrap: anywhere;
}
.gw-prop__ops { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.gw-steps--prop { margin-top: 0; }
.gw-step--prop .gw-step__t { color: var(--ink-2); }
/* En la propuesta el × es la curaduría, no una acción secundaria: se ve. */
.gw-step--prop .gw-step__x { opacity: 1; pointer-events: auto; }

/* Paso que nombra a alguien que no aparece en los datos de Robbie. El aviso
   toma su propio renglón —el paso ya ocupa el suyo— y el punto de estado es
   `warn`: es una señal, no el acento de la vista. Se ve siempre, sin hover:
   un aviso que hay que descubrir no sirve de aviso. */
.gw-step--ojo { flex-wrap: wrap; }
.gw-step--ojo .gw-step__t { color: var(--ink); }
.gw-step__aviso {
  flex: 1 1 100%;
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  margin: var(--s-1) 0 0;
  color: var(--warn);
}
.gw-step__aviso .r-status { flex: 0 0 auto; }

/* ============================================================
   El examen — pantalla completa, una pregunta a la vez

   Se toma la vista entera a propósito: una prueba con la telaraña, la lista
   y los programas alrededor deja de sentirse como prueba y a 375px no cabe.
   Y como la telaraña no está en pantalla, el acento ácido queda libre: aquí
   lo carga el avance, y en el resultado el nivel. Nunca los dos, porque
   nunca coinciden.

   El ancho es `--measure`, el token de "hasta dónde se lee cómodo un
   texto": un examen es texto, no un tablero.
   ============================================================ */

.gw-x {
  width: 100%;                     /* ver el porqué en `.gw-blank` */
  max-width: var(--measure);
  margin-inline: auto;
  padding-block: var(--s-4) var(--s-12);
}
.gw-x__h {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: var(--stroke) solid var(--hairline);
  margin-bottom: var(--s-8);
}
.gw-x__ops {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  margin-top: var(--s-6);
}
.gw-x__ops .gw-add { margin-top: 0; }
/* El avance sin respuesta no se esconde, se apaga: quitarlo movería el
   footer en cada pregunta y el botón dejaría de estar donde ya estaba. */
.gw-x__ops button[disabled] { opacity: 0.4; pointer-events: none; }

.gw-x__wait, .gw-x__err { display: grid; gap: var(--s-3); }

/* ---------- Avance ----------
   La cifra da la precisión, la barra el vistazo. El ácido va como RELLENO,
   que es donde funciona igual en los dos temas (misma razón que
   --radar-fill); como tinta sobre papel blanco se degradaría. */

.gw-x__prog {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-6);
}
.gw-x__at { flex: 0 0 auto; font-size: var(--t-label); color: var(--ink-2); }
.gw-bar {
  position: relative;
  flex: 1 1 auto;
  height: var(--stroke-2);
  background: var(--hairline);
  overflow: hidden;
}
.gw-bar__f {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: var(--at, 0%);
  background: var(--accent);
  transition: width var(--d-base) var(--ease-out);
}

/* ---------- La pregunta ----------
   Es lo único que manda en esta vista, así que es lo único que crece. El
   alto mínimo son dos renglones suyos: sin él las opciones brincan hacia
   arriba y hacia abajo al cambiar de pregunta. */
.gw-q {
  margin: 0 0 var(--s-5);
  min-height: 2.4em;
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h2);
  line-height: var(--lh-tight);
  letter-spacing: var(--tr-tight);
  color: var(--ink);
}

/* ---------- Opciones ----------
   El numeral de la izquierda es la tecla que la elige: la instrucción no
   vive en pantalla porque la instrucción ES el número que se ve. Cuadrado,
   como el punto de estado del sistema. */

.gw-opts { display: grid; gap: var(--s-2); }
.gw-opt {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  width: 100%;
  padding: var(--s-3) var(--s-4);
  border: var(--stroke) solid var(--hairline);
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: var(--t-body-sm);
  line-height: var(--lh-body);
  text-align: left;
  transition: background var(--d-fast) var(--ease), border-color var(--d-fast) var(--ease);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
.gw-opt:hover, .gw-opt:focus-visible { background: var(--surface-2); border-color: var(--hairline-strong); }
.gw-opt--on { background: var(--surface-sunk); border-color: var(--ink); }
.gw-opt__k {
  box-sizing: border-box;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--s-5);
  height: var(--s-5);
  border: var(--stroke) solid var(--hairline-strong);
  font-size: var(--t-micro);
  color: var(--ink-3);
  transition: background var(--d-fast) var(--ease), color var(--d-fast) var(--ease);
}
.gw-opt--on .gw-opt__k { background: var(--ink); border-color: var(--ink); color: var(--ink-invert); }
.gw-opt__t { min-width: 0; overflow-wrap: anywhere; }

/* ---------- Resultado ----------
   El nivel es la métrica y carga el acento; el puntaje del que salió va
   debajo en micro, para que la cifra se pueda revisar. */

.gw-res { display: grid; gap: var(--s-2); justify-items: start; }
.gw-res__n { margin: 0; }
.gw-res__s, .gw-res__cap { margin: 0; }
.gw-res__cap { max-width: var(--measure); }
/* Lo que se pierde al descartar va pegado al pie, no flotando a media vista:
   se lee justo antes de decidir. */
.gw-res .gw-fine.gw-res__cap { margin-top: var(--s-4); }
.gw-res__w { margin: var(--s-6) 0 0; display: grid; gap: var(--s-2); width: 100%; }

/* Lo que falló. No se esconde detrás de un hover: es la mitad del valor de
   haber medido, y es lo que va a alimentar el programa. */
.gw-miss {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  border-top: var(--stroke) solid var(--hairline);
}
.gw-miss__i {
  display: grid;
  gap: var(--s-1);
  padding-block: var(--s-3);
  border-bottom: var(--stroke) solid var(--hairline);
  animation: os-slide-in var(--d-base) var(--ease-out) both;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
.gw-miss__t {
  margin: 0;
  font-size: var(--t-body-sm);
  letter-spacing: var(--tr-tight);
  color: var(--ink);
}
/* ---------- Letra chica que es PROSA, no etiqueta ----------
   `.r-micro` del sistema es una etiqueta MICRO: versalitas y tracking
   abierto. Sirve para "TEST · HOY 19:33" o para un contador, y ahí se queda;
   sobre una oración completa la convierte en un grito —"CON 3 HABILIDADES
   GENERALES SE DIBUJA LA TELARAÑA"—. Todo lo que se LEE lleva el tamaño a
   mano y deja la caja alta en paz.

   `.gw-fine` es para las dos oraciones que no tenían clase propia; el resto
   ya la tenía. Lo que sigue en `.r-micro` a propósito: `.gw-src`, el par
   `nivel/objetivo`, el contador de pasos, la fecha de un paso cerrado y
   `.gw-prop__raw` —esa última es seña de vida del motor, no lectura, y la
   mono en mayúsculas es justo lo que la delata como texto de máquina. */
.gw-note, .gw-fine,
.gw-miss__q, .gw-miss__a, .gw-miss__b, .gw-res__cap {
  font-size: var(--t-label);
  line-height: var(--lh-body);
}
.gw-miss__q { margin: 0; }
/* La paloma y la cruz sustituyen a "correcta:" y "elegiste:" — el set de
   iconos dice en un trazo lo que dos etiquetas dirían en once caracteres.
   Un solo semántico, y va en la correcta: es la información que sirve. La
   equivocada se queda en tinta callada, que es todo el peso que merece. */
.gw-miss__a, .gw-miss__b {
  display: flex;
  align-items: flex-start;
  gap: var(--s-2);
  margin: 0;
}
.gw-miss__a { color: var(--ink-2); }
.gw-miss__a .r-ico { flex: 0 0 auto; color: var(--ok); }
.gw-miss__b .r-ico { flex: 0 0 auto; color: var(--ink-4); }

/* ---------- Movimiento reducido ----------
   La animación se apaga completa, no se acorta: el retardo en cascada no lo
   toca la regla global de ron.css y los renglones se quedarían invisibles su
   turno. */
@media (prefers-reduced-motion: reduce) {
  .gw-ax, .gw-row, .gw-ed, .gw-prog, .gw-step, .gw-prop,
  .gw-opt, .gw-miss__i, .gw-pend { animation: none; }
  .gw-ax, .gw-row, .gw-add, .gw-step__w, .gw-step__x, .gw-prog__x,
  .gw-opt, .gw-opt__k, .gw-bar__f, .gw-pend, .gw-pend__a { transition: none; }
}

/* Sin hover no hay revelación: en táctil lo secundario se queda visible. */
@media (hover: none) {
  .gw-step__w { opacity: 1; }
  .gw-step__x, .gw-prog__x { opacity: 1; pointer-events: auto; }
  .gw-pend__a { opacity: 1; pointer-events: auto; }
}

/* ---------- Angosto ----------
   El corte mira la COLUMNA, no la ventana: dentro del panel la ventana
   siempre es ancha y la columna nunca. 420 es el mínimo declarado del
   módulo, así que el corte va justo encima —a 460— para que el mínimo caiga
   completo del lado apretado y el cómodo (520) no. */

@container gw (max-width: 460px) {
  .gw-head { flex-direction: column; align-items: flex-start; gap: var(--s-3); }
  /* El nombre y el segmentado no caben en el mismo renglón: el campo se
     quedaba en 90px. Cada uno se lleva el suyo, el segmentado al inicio. */
  .gw-ed { grid-template-columns: minmax(0, 1fr); }
  .gw-ed__k { grid-column: 1; justify-self: start; justify-items: start; }
  .gw-prog__t { max-width: none; }
  .gw-in { max-width: none; flex: 1 1 100%; }
  /* El nombre necesita el renglón completo antes que la regla y la cifra. */
  .gw-row { flex-wrap: wrap; }
  .gw-row__n { flex: 1 1 100%; }
  /* En el examen los dos botones del pie se reparten el ancho: no caben lado
     a lado con aire. */
  .gw-x__ops { gap: var(--s-2); }
  .gw-x__ops .r-btn { flex: 1 1 auto; }
  .gw-opt { padding: var(--s-3); }
}
