/* =========================================================================
   Feedo — application chrome (login, dashboard, admin), in CUBE order:
   global, then Composition, Utility, Block, Exception.

   Almost all of the styling happens above the blocks. By the time a block is
   written, the global layer and the composition primitives have already
   decided its type, colour and rhythm — which is why the blocks below are
   mostly a handful of contextual lines each.

   Shares the warm design language of the injected feedback layer so the whole
   product reads as one identity. The tool stays quiet and precise; the
   client's prototype previews are where the boldness lives.
   ========================================================================= */

/* =========================================================================
   FUENTES
   Sólo las carga el panel. La capa que se inyecta en el prototipo del cliente
   usa el stack del sistema a propósito: no le sumamos descargas a una página
   que no es nuestra, y ahí Feedo tiene que pasar desapercibido.
   ========================================================================= */

@font-face {
  font-family: 'Gabarito';
  font-style: normal;
  font-weight: 400 900; /* variable: un archivo cubre todos los pesos */
  font-display: swap;
  src: url('/assets/fonts/gabarito-var.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/plex-mono-400.woff2') format('woff2');
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/plex-mono-600.woff2') format('woff2');
}

/* =========================================================================
   GLOBAL
   ========================================================================= */

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  /* El lienzo, no el papel. Feedo tiene que desaparecer detrás del diseño que se
     está juzgando: un fondo con tono le tiñe la percepción de los colores del
     cliente, y en una herramienta de revisión eso es un sesgo y no un gusto.
     El papel sigue existiendo como SUPERFICIE —panel, card, diálogo—, apoyado
     sobre el lienzo. */
  background: var(--feedo-canvas);
  color: var(--feedo-ink);
  font-family: var(--feedo-brand);
  font-size: var(--feedo-step-0);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--feedo-accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}
/* Un enlace rodeado de texto no puede distinguirse sólo por el color: quien no
   separa este azul del gris del párrafo no lo ve. Dentro de prosa va
   subrayado siempre; los enlaces sueltos de navegación no lo necesitan porque
   su lugar ya dice qué son. */
p a,
li a,
dd a {
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

/* El radio del aro está para lo que no declara una forma propia —un enlace, un
   renglón de lista—, porque el navegador sigue la esquina del elemento y sobre
   una caja recta el aro sale en punta. Era un `6px` suelto: el mismo número que
   la capa de revisión ya había cambiado por el radio chico del sistema, que es
   el que llevan todas las piezas anidadas. Lo que declara su forma —los botones
   cápsula, los campos— gana por orden de cascada y conserva la suya. */
:focus-visible {
  outline: 2px solid var(--feedo-accent);
  outline-offset: 2px;
  border-radius: var(--feedo-radius-s);
}

/* =========================================================================
   LA ESCALERA DE PESOS

   Tres, y cada uno tiene un trabajo:
     700  TITULA O SENTENCIA: los encabezados, los rótulos en caja alta, el
          nombre de una card, el veredicto de una vista.
     600  SE TOCA O SE MIDE: todos los botones y enlaces de acción, y los
          números que cuentan algo.
     400  SE LEE: la prosa, las pistas, los datos al pie.

   La declaró la capa de revisión y vale para todo el producto — la barra de
   arriba es la misma clase en las dos, así que no puede haber dos escaleras.
   Acá había cinco escalones: 800 para los titulares, 700, 600, 500 para las
   pistas y 400. Cinco escalones no son una escalera; son cinco números sueltos,
   y cada pieza nueva volvía a elegir el suyo.

   El 800 no se pierde por gusto: en Gabarito, entre 700 y 800 hay medio punto de
   diferencia visible a cuerpo de interfaz, así que estaba pagando un escalón sin
   comprarlo. El 500 se va por lo contrario: era el único peso que no significaba
   nada —ni titula, ni se toca, ni es cuerpo—, y lo que llevaba puesto es texto
   que se lee.
   ========================================================================= */

h1,
h2,
h3 {
  margin: 0;
  font-weight: 700;
  line-height: 1.15;
  text-wrap: balance;
}
h1 {
  font-size: var(--feedo-step-3);
  letter-spacing: -0.01em;
}
h2 {
  font-size: var(--feedo-step-2);
  letter-spacing: -0.015em;
}
h3 {
  font-size: var(--feedo-step-0);
}

/* La bajada que sigue a un título: una frase, más grande que el cuerpo y en
   tinta plena. Es lo que se lee cuando alguien barre la página sin detenerse;
   si hace falta más detalle, va en el párrafo siguiente y en gris. */
.lede {
  font-size: var(--feedo-step-1);
  line-height: 1.35;
  color: var(--feedo-ink);
  text-wrap: balance;
}

p {
  margin: 0;
}

code {
  font-family: var(--feedo-mono);
  /* Relative, so it tracks whatever size it sits in. Held close to 1 because
     it compounds: at 0.86em inside a 0.8rem cell the repo slug landed at
     10.7px, below anything readable. */
  font-size: 0.94em;
  font-variant-numeric: tabular-nums;
}

/* =========================================================================
   COMPOSITION — how things sit next to each other. No colour, no type.
   Each primitive takes a custom property so a block can retune its spacing
   without redefining the layout.
   ========================================================================= */

/* Vertical rhythm between siblings, without margins on the children. */
.flow > * + * {
  margin-block-start: var(--flow-space, var(--feedo-space-m));
}

/* Evenly spaced vertical list. */
.stack {
  display: grid;
  gap: var(--stack-space, var(--feedo-space-s));
}

/* Things in a row that wrap when they must. */
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: var(--cluster-align, center);
  gap: var(--cluster-space, var(--feedo-space-2xs));
}
.cluster[data-align="end"] {
  justify-content: flex-end;
}

/* Two groups pushed apart, stacking when there is no room. */
.repel {
  display: flex;
  flex-wrap: wrap;
  align-items: var(--repel-align, center);
  justify-content: space-between;
  gap: var(--repel-space, var(--feedo-space-s));
}

/* The page's horizontal frame. */
.wrapper {
  max-inline-size: var(--wrapper-max, 64rem);
  margin-inline: auto;
  padding-inline: var(--feedo-space-s-m);
}

/* Prose that should not run wider than it can be read. */
.measure {
  max-inline-size: var(--measure-max, var(--feedo-measure));
}

/* =========================================================================
   UTILITY — one job each, always through a token.
   ========================================================================= */

.visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.text-muted {
  color: var(--feedo-muted);
}

.text-meta {
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}

.text-label {
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--feedo-muted);
}

.nowrap {
  white-space: nowrap;
}

/* La familia de glifos: grilla 24, trazo 2, `currentColor` (ver
   `src/views/layout.js`). El TAMAÑO lo decide cada bloque —el que informa que
   algo se abre va un escalón por debajo del que nombra una cosa— porque depende
   del cuerpo con el que está compuesto. Lo único que vale para todos es que
   dentro de una fila flex ningún glifo se estira ni se encoge: el trazo es de 2
   y deformarlo lo delata antes que el tamaño. */
.glifo {
  flex: none;
}

/* =========================================================================
   BLOCKS
   ========================================================================= */

/* ---- the page itself ----
   .wrapper only frames horizontally; the vertical breathing room is this
   page's decision, not the primitive's. */
.page {
  padding-block: var(--feedo-space-l) var(--feedo-space-2xl);
}
/* ---- EL ANCHO SE DECLARA EN EL BODY, Y LA BARRA VIENE INCLUIDA ----
   `--wrapper-max` se declara en el `<body>`, así que TODOS los `.wrapper` de la
   página —el `main` y el de la barra, que son hermanos— leen el mismo número.
   No es comodidad: es lo que vuelve IMPOSIBLE que la barra componga distinto que
   su contenido, que es justo el defecto que esto viene a arreglar.

   El vocabulario tiene tres escalones y los tres viven acá, en la hoja. Una
   página elige un NOMBRE —`layout({ width })`—, nunca una medida, y un nombre que
   no existe cae en la medida de lectura. Por eso ninguna página puede romper la
   barra sin querer: no hay número que pueda pasarle.

   Sin atributo — 64rem, medida de LECTURA. Es para lo que se lee de corrido:
   «Cómo funciona», el login, las confirmaciones. Una línea de texto a 1680px
   obliga a barrer la cabeza de lado a lado y a perder el renglón al volver; hay
   una razón por la que los libros tienen columnas angostas.

   `wide` — 76rem. Documentos con ejemplos: la guía de estilos. La rejilla de
   muestras respira y los especímenes de tipografía, que son prosa, siguen
   entrando en una línea que se lee.

   `full` — sin tope. Lo que muestra una GRILLA DE COSAS: vistas, proyectos,
   miniaturas, tablas anchas. Feedo es una herramienta de trabajo, no un blog:
   quien revisa 22 vistas quiere ver más miniaturas por pantalla, no más margen —
   en un monitor de 1680 el tope de 76rem dejaba 232px de aire muerto a cada
   lado. La prosa que viva adentro se acota sola con `.measure`.

   Antes la barra tenía UN ancho fijo, declarado acá, para que la marca no saltara
   al navegar entre pantallas que componen distinto. La regla de verdad resultó
   ser la que ya había encontrado el marco de revisión: la barra se alinea con el
   contenido de SU página. Ver `test/topbar-consistency.test.js`. */
[data-width="wide"] {
  --wrapper-max: 76rem;
}
[data-width="full"] {
  --wrapper-max: none;
}

/* ---- skip link ---- */
.skip-link {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  z-index: 10;
  transform: translateY(-120%);
  padding: var(--feedo-space-2xs) var(--feedo-space-s);
  background: var(--feedo-accent);
  color: var(--feedo-accent-ink);
  /* Un control, como cualquier otro. Lo que lo hace imposible de ignorar cuando
     aparece es el relleno de acento y su lugar, no medio escalón de peso. */
  font-weight: 600;
  border-end-end-radius: var(--feedo-radius-s);
}
.skip-link:focus-visible {
  transform: translateY(0);
  text-decoration: none;
}

/* ---- top bar ---- */
/* ---- la marca ----
   El calderón (¶) con un ojo. Se dimensiona en em para que acompañe al texto
   con el que está compuesto, sin números sueltos por cada tamaño. */
.mark {
  inline-size: 1.45em;
  block-size: 1.45em;
  flex: none;
  display: block;
}
/* Dos capas, porque las dos animaciones usan transform y sobre un mismo
   elemento la segunda pisaría a la primera: el grupo mira, los ojos parpadean.
   Como corren sueltas, a veces coinciden — y eso es justo lo que las hace
   parecer vivas en vez de programadas. */
.mark__eyes {
  transform-box: fill-box;
  transform-origin: center;
  animation: feedo-look 9s ease-in-out infinite;
}
.mark__eye {
  /* Negro porque es contenido de una máscara: ahí el negro es el agujero. El ojo
     no tiene color propio — muestra el fondo, y así contrasta con el cuerpo de la
     marca sea el fondo claro u oscuro. */
  fill: #000;
  /* fill-box, o el origen del transform sería el del viewBox y el ojo
     se cerraría contra el borde del SVG en vez de sobre sí mismo. */
  transform-box: fill-box;
  transform-origin: center;
  animation: feedo-blink 4.5s ease-in-out infinite;
}
/* Lo único que convierte el símbolo en alguien. Breve: tiene que notarse de
   reojo, no pedir atención mientras se trabaja. */
@keyframes feedo-blink {
  0%, 90%, 100% { transform: scaleY(1); }
  94%           { transform: scaleY(0.08); }
}
/* Mira a un lado, se queda un momento, vuelve. Las pausas largas en el centro
   son lo que da la sensación de atención: un ojo que se mueve sin parar es un
   tic, no una mirada. El desplazamiento va en unidades del viewBox, así que
   escala solo con el símbolo. */
@keyframes feedo-look {
  0%, 20%   { transform: translateX(0); }
  27%, 40%  { transform: translateX(3.5px); }
  47%, 63%  { transform: translateX(0); }
  70%, 83%  { transform: translateX(-3.5px); }
  90%, 100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .mark__eyes,
  .mark__eye { animation: none; }
}

/* El borde y el fondo cruzan la ventana entera; el contenido lo encuadra el
   .wrapper de adentro, el mismo que usa el main. */
/* La barra es AZUL DE MARCA en todo el producto.
   Pasó por dos intentos y los dos fallaban por la misma razón. En papel, el cromo
   era casi el mismo crema con el que trabaja media clientela: no se sabía dónde
   termina la herramienta y dónde empieza lo que se está juzgando. En tinta se
   distinguía, pero un gris oscuro no es de nadie — podía ser de Feedo o del
   prototipo.
   El azul de la marca sí es de alguien. Es inconfundiblemente Feedo, así que no
   se puede confundir con el diseño de ningún cliente, y el blanco encima da
   7.2:1. El acento a pleno acá no compite con nada: en el lienzo blanco de abajo
   sigue significando identidad y acción, y en la barra significa "esto es la
   herramienta". */
/* ---- EL CROMO SE DECLARA UNA VEZ, EN DOS VARIABLES ----
   Toda la barra estaba escrita contra dos valores literales: el acento como
   superficie y `--feedo-accent-ink` como todo lo que va encima, incluidos ocho
   `color-mix` de blanco a distinto porcentaje. Mientras la barra tuvo un solo
   color eso alcanzaba; en la pantalla del marco no, porque ahí la barra tiene
   que responder a la sala (ver «EL CROMO DE UNA VISTA SIGUE A SU SALA»).

   Así que el par se nombra: `--feedo-cromo` es la superficie y
   `--feedo-cromo-tinta` es lo único que va encima. Las mezclas pasan a ser
   porcentajes de la tinta sobre su propia superficie, que es lo que siempre
   quisieron decir, y valen igual con el par dado vuelta. `--feedo-cromo-foco` va
   aparte: el aro no puede ser del color de lo que rodea. */
.topbar {
  --feedo-cromo: var(--feedo-accent);
  --feedo-cromo-tinta: var(--feedo-accent-ink);
  --feedo-cromo-foco: var(--feedo-accent-ink);
  padding-block: var(--feedo-space-xs);
  background: var(--feedo-cromo);
  color: var(--feedo-cromo-tinta);
  border-block-end: 1px solid transparent;
}

/* Todo lo que va sobre la superficie del cromo va en su tinta: es el único color
   que la paleta admite encima suyo. */
.topbar a,
.marco__vista {
  color: var(--feedo-cromo-tinta);
}

/* La marca en blanco, con los ojos calados: al ser agujeros muestran el azul del
   fondo, así que dentro del blanco siguen leyéndose como ojos. Es la misma razón
   por la que el dock ya usaba máscara — su encabezado cambia de color según el
   veredicto. */
.topbar__brand .mark,
.marco__marca .mark {
  color: var(--feedo-cromo-tinta);
}

.topbar__tagline {
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 72%, transparent);
  border-inline-start-color: color-mix(in srgb, var(--feedo-cromo-tinta) 32%, transparent);
}

.topbar__session {
  border-inline-start-color: color-mix(in srgb, var(--feedo-cromo-tinta) 32%, transparent);
}

/* El «Salir» y el «Cómo funciona» sobre tinta: contorno claro en vez de la
   pastilla de papel, que sobre oscuro grita más que el contenido. */
.topbar .btn[data-variant="quiet"],
.topbar__help {
  color: var(--feedo-cromo-tinta);
  border-color: color-mix(in srgb, var(--feedo-cromo-tinta) 44%, transparent);
  background: transparent;
}
/* El enlace nunca tuvo caja propia, y no se notaba mientras su fondo fue
   transparente: al pintarlo —al pasar el mouse, o estando parado en esa
   página— el color quedaba pegado a las letras, un rectángulo al ras del texto
   contra el «Salir» de al lado, que es una pastilla. La caja se dibuja siempre
   aunque casi nunca se vea. */
.topbar__help {
  padding: var(--feedo-space-3xs) var(--feedo-space-2xs);
  border-radius: var(--feedo-radius-s);
}

.topbar .btn[data-variant="quiet"]:hover,
.topbar__help:hover {
  color: var(--feedo-cromo);
  background: var(--feedo-cromo-tinta);
  border-color: var(--feedo-cromo-tinta);
}

/* El aro del foco no puede ser del color de lo que rodea, así que va aparte del
   par y no lo deduce: sobre el acento es blanco, y en el marco lo declara cada
   sala. Es la única razón por la que hay tres variables y no dos. */
/* ---- LA BARRA Y SU VERSIÓN FLOTANTE ----
   Todo lo que viste un CONTROL de la barra se declara para `.topbar` y para
   `.marco__flotante` con `:is()`. En el modo ampliado esa pastilla es lo único
   que queda de la barra, así que sus controles son los mismos controles y tienen
   que verse igual — el conmutador de medidas, el cierre, el aro de foco.
   Colgados sólo de `.topbar` pasaba lo previsible y lo invisible a la vez: el
   conmutador se dibujaba sin su caja y «Terminé de revisar» salía como un link
   azul sin fondo, sobre el diseño de un cliente. El aro de foco directamente no
   existía, que es lo peor de los tres porque no se nota mirando.
   `:is()` toma la especificidad más alta de su lista, y las dos son una clase:
   nada cambia para la barra normal.
   Lo que NO se comparte es `.topbar` en sí ni `.topbar form`: el layout y el
   fondo de la barra son suyos, y la pastilla tiene los propios. */
:is(.topbar, .marco__flotante) :focus-visible {
  outline-color: var(--feedo-cromo-foco);
}
/* Caja mixta y no versalitas espaciadas: "Feedo" tiene que leerse como alguien
   que te acompaña, y las versalitas con tracking leen a placa corporativa. */
.topbar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
  font-size: var(--feedo-step-1);
  font-weight: 700;
  letter-spacing: -0.03em;
  /* Sobre el azul de la barra, el logotipo escrito va en blanco: es texto y
     tiene que leerse antes que pertenecer. El calderón mantiene su forma y los
     ojos calados muestran el azul del fondo. */
  color: var(--feedo-cromo-tinta);
}
/* La bajada acompaña al wordmark, separada por una regla: dice qué es esto sin
   competir con la marca. Desaparece cuando no hay lugar — es contexto, no
   navegación, y nadie la necesita para operar. */
.topbar__tagline {
  padding-inline-start: var(--feedo-space-2xs);
  border-inline-start: 1px solid color-mix(in srgb, var(--feedo-cromo-tinta) 32%, transparent);
  font-size: var(--feedo-step--1);
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 76%, transparent);
}
@media (max-width: 40rem) {
  .topbar__tagline {
    display: none;
  }
}

/* Tres cosas de distinta naturaleza, agrupadas como tales: Ayuda es
   navegación; el nombre y Salir son la sesión. La separación entre los dos
   grupos es lo que evita leerlos como una fila de tres botones iguales. */
.topbar__nav {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-s);
}
.topbar__session {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  padding-inline-start: var(--feedo-space-s);
  border-inline-start: 1px solid color-mix(in srgb, var(--feedo-cromo-tinta) 32%, transparent);
}
.topbar__user {
  font-size: var(--feedo-step--1);
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 88%, transparent);
}
.topbar__nav a {
  display: inline-flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
  font-size: var(--feedo-step--1);
  /* 600, como «Salir», que es su vecino y es un `.btn`: los dos son controles
     de la barra, y pesarlos distinto los separaba en dos familias. */
  font-weight: 600;
  /* Blanco, no el acento: sobre la barra azul el acento es el fondo, y este
     enlace quedaba en 1:1 — invisible. Va acá, en el bloque del componente, y no
     en un override más arriba: con la misma especificidad gana el que va después,
     así que un override arriba no override nada. */
  color: var(--feedo-cromo-tinta);
}
.topbar__nav a svg {
  inline-size: 1.05em;
  block-size: 1.05em;
  flex: none;
}
/* El color ya distingue al enlace del texto gris de al lado, así que el hover
   suma el subrayado en vez de cambiar de tono otra vez. */
.topbar__nav a:hover {
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
/* La página en la que estás. Se marcaba con `--feedo-ink` —la tinta del tema
   claro— sobre el azul de la barra: medido con axe en /ayuda, 2.49:1 donde AA
   pide 4.5. Es la tercera vez que en esta barra un color se elige sin mirar
   contra qué fondo cae, y sobrevivió porque sólo se veía estando parado en esa
   pantalla.
   Lo único que va sobre la superficie de la barra es su par, así que la marca
   lo da vuelta: fondo tinta, texto superficie. El mismo gesto que el hover, con
   un contraste que ya estaba probado. */
.topbar__nav a[aria-current="page"] {
  color: var(--feedo-cromo);
  background: var(--feedo-cromo-tinta);
  border-color: var(--feedo-cromo-tinta);
}
.topbar form {
  margin: 0;
}

/* ---- page heading ---- */
.page-head {
  --flow-space: var(--feedo-space-2xs);
  margin-block-end: var(--feedo-space-m);
}
.page-head .meter {
  --flow-space: var(--feedo-space-xs);
}
/* The bar belongs to the sentence under it — running it the full container
   width would read as a page rule, not as this project's progress. */
.page-head .meter__bar {
  max-inline-size: 34rem;
}

/* ---- el camino de vuelta ----
   Mismo armado que `.marco__volver`, el de la barra de la vista: glifo de la
   familia, nombre escrito al lado, los dos en una fila que no se parte. Acá el
   signo era un `&larr;` —una entidad tipográfica, sin grilla ni trazo—, así que
   la misma señal tenía dos dibujos según en qué pantalla del producto estuvieras.

   El glifo va con `flex: none` y en `em`: acompaña al cuerpo del enlace en vez
   de traer un número propio. */
.backlink {
  margin-block-end: var(--feedo-space-xs);
  font-size: var(--feedo-step--1);
  font-weight: 600;
}
.backlink a {
  display: inline-flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
}
.backlink .glifo {
  inline-size: 1.05em;
  block-size: 1.05em;
}

.empty-state {
  max-inline-size: 46ch;
  margin-block-start: var(--feedo-space-m);
  padding-block: var(--feedo-space-m);
  color: var(--feedo-muted);
}

/* ---- buttons ----
   The neutral outline is the default on purpose: routine controls (log out,
   re-sync) must not read as the loudest thing on the page. Emphasis is opt-in
   through data-variant, which only the form that creates something wears. */
button,
.btn {
  font: inherit;
  font-size: var(--feedo-step--1);
  /* Un botón es un control, no una palabra en medio de una oración: el
     subrayado de la prosa no le corresponde. Se coló cuando uno quedó dentro de
     un <p> y `p a` se lo aplicó — la regla de la prosa está bien, lo que
     faltaba era que el control se defendiera. */
  text-decoration: none;
  /* 600: el peso de lo que SE TOCA. Era el último control del producto en 700 —
     el dock, los seis botones del panel de feedback y el cierre de la barra de
     una vista ya habían bajado— y es, de lejos, el más repetido: cada fila de la
     tabla lleva tres. Con 700 competía con el nombre del proyecto que está a su
     izquierda, que es lo que la fila viene a decir. Lo que hace fuerte a un
     botón es su caja, no su peso. */
  font-weight: 600;
  /* Its own leading. Inheriting a container's line-height makes a button as
     tall as whatever text happens to surround it — inside a table row that is
     1.6 lines of leading, and the control grows by a third for no reason. */
  line-height: 1.25;
  cursor: pointer;
  /* Un glifo adentro de un botón se apoyaba en la línea base del texto, así que
     el «+» flotaba por encima de las letras. En flex los dos se centran contra
     el mismo eje y el dibujo queda a la altura de lo que nombra. Va en el botón
     y no en una clase aparte: cualquier control con ícono tenía el problema, y
     los que vengan también lo tendrían. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--feedo-space-3xs);
  border: 1px solid var(--feedo-line);
  background: var(--feedo-paper);
  color: var(--feedo-ink);
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  border-radius: var(--feedo-radius-pill);
  transition: border-color 0.15s, background 0.15s, color 0.15s;
}
button:hover,
.btn:hover {
  border-color: var(--feedo-accent);
  color: var(--feedo-accent);
}

.btn[data-variant="primary"] {
  background: var(--feedo-accent);
  color: var(--feedo-accent-ink);
  border-color: transparent;
}
.btn[data-variant="primary"]:hover {
  background: color-mix(in srgb, var(--feedo-accent) 85%, black);
  border-color: transparent;
  color: var(--feedo-accent-ink);
}
/* Inactivo tiene que VERSE inactivo. El botón de publicar llevaba el atributo
   —el navegador ignoraba el clic— pero se veía igual que uno activo: fondo del
   acento, texto blanco, cursor de mano. Un botón que invita a apretarlo y no
   hace nada se lee como una función rota, no como una que todavía no
   corresponde. Va después de las variantes para ganarles a todas. */
.btn:disabled,
.btn[data-variant="primary"]:disabled {
  background: var(--feedo-paper-2);
  color: var(--feedo-muted);
  border-color: var(--feedo-line);
  cursor: not-allowed;
}

/* Destructive, in two states. At rest it is the quietest control in the row —
   a delete that competes for attention gets clicked by accident. Armed, it is
   the loudest thing on the page, because by then that is exactly what the next
   click does. */
.btn[data-variant="danger"] {
  padding: var(--feedo-space-3xs) var(--feedo-space-2xs);
  background: transparent;
  border-color: transparent;
  color: var(--feedo-muted);
}
.btn[data-variant="danger"]:hover {
  background: color-mix(in srgb, var(--feedo-danger) 8%, var(--feedo-paper));
  border-color: color-mix(in srgb, var(--feedo-danger) 35%, transparent);
  color: var(--feedo-danger);
}
.btn[data-variant="danger-armed"] {
  display: inline-flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
  background: var(--feedo-danger);
  border-color: var(--feedo-danger);
  color: #fff;
}
.btn[data-variant="danger-armed"]:hover {
  background: color-mix(in srgb, var(--feedo-danger) 85%, black);
  border-color: transparent;
  color: #fff;
}
.btn svg {
  inline-size: 1em;
  block-size: 1em;
  flex: none;
}
/* While a destructive action is armed, the row shows only that decision. */
.actions__row[data-confirming] > *:not(form[data-confirm]) {
  display: none;
}
/* A form holding an armed control also holds its Cancelar. */
.actions__row form {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
}
/* Trabajando. Sin opacidad, por lo mismo que el dock se la sacó a sus botones
   apagados: bajaba la legibilidad justo en el único momento en que el rótulo es
   lo único que está pasando en pantalla. Medido — «Guardando…» sobre el relleno
   de apagado da 6.02:1, y con el 0.75 encima cae a 3.40:1, por debajo de AA.

   Y no hace falta: un tick después del envío el botón queda `disabled` de
   verdad (ver `assets/app.js`), así que la regla de arriba ya lo pinta con el
   relleno del sistema. La opacidad no agregaba información, sólo la restaba.
   Queda el cursor, que es lo único que este estado tenía de propio. */
.btn[aria-busy="true"] {
  cursor: progress;
}

.btn[data-variant="quiet"] {
  padding: var(--feedo-space-3xs) var(--feedo-space-2xs);
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
  background: transparent;
}
.btn[data-variant="quiet"]:hover {
  background: var(--feedo-paper);
}

/* ---- auth (the front door) ---- */
.auth {
  max-inline-size: 24rem;
  margin-block: clamp(2rem, 9vh, 6rem);
  margin-inline: auto;
  padding: var(--feedo-space-m-l);
  background: var(--feedo-paper);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius);
  box-shadow: var(--feedo-shadow-md);
}
/* El descriptor es parte de la marca, no una línea suelta debajo: cuelga del
   wordmark, no del isotipo, y se mueve con el conjunto. El tamaño del símbolo
   va en em para que acompañe al bloque entero. */
.auth__brand {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  margin: 0 0 var(--feedo-space-m);
  font-size: var(--feedo-step-3);
  color: var(--feedo-accent);
}
.auth__brand .mark {
  inline-size: 1.65em;
  block-size: 1.65em;
}
.auth__brand-text {
  display: grid;
  gap: 0.04em;
}
.auth__brand-name {
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1;
}
/* Subordinado sin discusión: la mitad del cuerpo, sin peso y en gris. Si
   compitiera, el conjunto se leería como dos títulos en vez de una marca. */
.auth__brand-sub {
  font-size: 0.5em;
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.25;
  color: var(--feedo-muted);
}
.auth__error {
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  border-radius: var(--feedo-radius-s);
  border: 1px solid color-mix(in srgb, var(--feedo-danger) 30%, transparent);
  background: color-mix(in srgb, var(--feedo-danger) 10%, var(--feedo-paper));
  color: var(--feedo-danger);
  font-size: var(--feedo-step--1);
  font-weight: 600;
}

/* La salida de al lado: «no me acuerdo la contraseña» bajo el botón de
   ingresar, «volver a ingresar» bajo el de pedir el link. Es la puerta de
   servicio de la pantalla — tiene que estar a la vista de quien la busca sin
   competir con la acción principal, que es la que usa todo el mundo. */
.auth__aparte {
  margin-block-start: var(--feedo-space-2xs);
  font-size: var(--feedo-step--1);
  text-align: center;
}
.auth__aparte a {
  color: var(--feedo-muted);
}
.auth__aparte a:hover {
  color: var(--feedo-ink);
}

/* Local-only shortcuts under the login form. Deliberately drab: this is
   scaffolding, and it must not read as part of the product. */
.dev-shortcuts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--feedo-space-2xs) var(--feedo-space-s);
  margin-block-start: var(--feedo-space-m);
  padding-block-start: var(--feedo-space-s);
  border-block-start: 1px dashed var(--feedo-line);
  font-size: var(--feedo-step--1);
}
.dev-shortcuts__label {
  inline-size: 100%;
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--feedo-muted);
}

/* ---- fields ---- */
.field {
  display: grid;
  gap: var(--feedo-space-3xs);
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--feedo-muted);
}
.field input,
.field select {
  font: inherit;
  font-size: var(--feedo-step-0);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--feedo-ink);
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-s);
  background: var(--feedo-paper-2);
}
/* Un <select> mide lo que su opción más larga, y con `min-width: auto` no se
   deja encoger. Medido a 320px: la lista de repos —«tbagencia/plataforma-sol-
   alvarez (privado)»— lo estiraba a 342px y empujaba la página entera 72px
   hacia la derecha. Un input no tiene el problema porque toma el ancho de su
   contenedor; el select hay que obligarlo. */
.field select {
  max-inline-size: 100%;
  min-inline-size: 0;
}
/* Un enlace metido en un párrafo no puede distinguirse sólo por el color: quien
   no separa ese azul del texto no tiene forma de saber que hay algo para
   apretar. Los botones y los títulos de fila se leen por su forma y su lugar;
   esto es una palabra en medio de una oración. */
.field-hint a {
  text-decoration: underline;
}
.field input:focus-visible,
.field select:focus-visible {
  outline: 2px solid var(--feedo-accent);
  outline-offset: 1px;
  border-color: var(--feedo-accent);
}
/* El campo de contraseña con su botón de ver. Lo arma el script; sin JS el
   input queda tal cual, sin un botón muerto al costado. */
.password-field {
  position: relative;
  display: grid;
}
.password-field input {
  /* Lugar para el botón, para que el texto nunca quede debajo. */
  padding-inline-end: 2.75rem;
}
.password-field__toggle {
  position: absolute;
  inset-block: 1px;
  inset-inline-end: 1px;
  display: grid;
  place-items: center;
  inline-size: 2.5rem;
  padding: 0;
  border: 0;
  border-radius: var(--feedo-radius-s);
  background: none;
  color: var(--feedo-muted);
  cursor: pointer;
}
.password-field__toggle:hover {
  color: var(--feedo-ink);
}
.password-field__toggle svg {
  inline-size: 1.25rem;
  block-size: 1.25rem;
}

.field-hint {
  font-size: var(--feedo-step--1);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--feedo-muted);
}
.field-hint code {
  padding: 0.05rem 0.3rem;
  border-radius: 5px;
  background: var(--feedo-paper-2);
}

.form {
  --stack-space: var(--feedo-space-s);
  max-inline-size: 34rem;
}
.form button {
  justify-self: start;
}

/* ---- review meter ----
   The bar is a summary of the legend beside it, never the only carrier of the
   value; screen readers get the same sentence through aria-label. */
.meter {
  --stack-space: var(--feedo-space-3xs);
}
.meter__bar {
  display: flex;
  block-size: 6px;
  border-radius: var(--feedo-radius-pill);
  overflow: hidden;
  background: color-mix(in srgb, var(--feedo-ink) 8%, transparent);
}
.meter__seg {
  display: block;
  block-size: 100%;
}
.meter__seg[data-tone="approved"] {
  background: var(--feedo-approve);
}
.meter__seg[data-tone="commented"] {
  background: var(--feedo-comment);
}
.meter__seg[data-tone="pending"] {
  background: color-mix(in srgb, var(--feedo-ink) 14%, transparent);
}
.meter__legend {
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}
/* 600, el peso de lo que SE MIDE — el mismo que el contador del grabador en el
   dock. Estos números no titulan nada: cuentan cuántas vistas van. Lo que los
   separa de la frase que los rodea es la tinta plena contra el gris, y ese
   contraste ya alcanza; sumarle el peso de un titular los hacía competir con el
   nombre del proyecto que están calificando. */
.meter__legend strong,
.meter__lead {
  color: var(--feedo-ink);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
/* Nada pendiente y nada comentado: el número dejó de contar lo que falta y pasó
   a contar lo que está hecho, así que se viste del color que en este sistema ya
   significa aprobado. */
.meter__lead[data-tone="done"] {
  color: var(--feedo-approve);
}
/* In a table row the legend leads and the bar drops to the second line, so it
   should read as a quantity rather than as a rule across the column. */
/* La barra mide lo mismo que la leyenda que la explica.
   Tenía un max-inline-size de 9rem, un ancho fijo sin relación con el texto de
   al lado: en una celda angosta no se notaba, pero en la tarjeta del cliente
   —que es ancha— la leyenda ocupaba 224px y la barra se cortaba en 144. Se leía
   como una barra incompleta, que es justo lo que una barra de progreso no puede
   permitirse decir por accidente.
   El bloque toma el ancho de su contenido y la barra se estira a ese ancho, así
   coinciden siempre, diga lo que diga el texto. */
.meter[data-density="compact"] {
  min-inline-size: 10rem;
  inline-size: fit-content;
}
.meter[data-density="compact"] .meter__legend {
  white-space: nowrap;
}
.meter[data-density="compact"] .meter__bar {
  block-size: 4px;
}
.meter[data-empty] {
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}

/* ---- dashboard: project list ----
   LA TARJETA CONTESTA UNA PREGUNTA Y MUESTRA UN PROTOTIPO.

   Era una tira con dos islas: el nombre a la izquierda, el estado a la derecha
   y un océano en el medio. `.repel` reparte contra los BORDES, así que cada
   píxel de ventana alejaba el nombre de su propio estado — y con la página a
   ancho completo eso empeoró, que es lo que la volvió la pieza que peor tolera
   el ancho nuevo.

   Ahora reparte contra un EJE: una columna acotada con todo lo que hay que
   saber, y el resto del ancho para las caras del prototipo. El ancho de más ya
   no separa dos datos que se leen juntos: alimenta la única parte que crece
   bien, que son las miniaturas. Con seis proyectos, además, las seis respuestas
   quedan alineadas en la misma x y se barren de un vistazo. */
.project-list {
  --stack-space: var(--feedo-space-s-m);
  list-style: none;
  padding: 0;
  margin: 0;
}
.project-card {
  display: grid;
  /* La columna de la izquierda no pasa de una medida de lectura: es prosa, y la
     prosa no se ensancha porque la pantalla lo permita. */
  grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  gap: var(--feedo-space-s) var(--feedo-space-m);
  align-items: stretch;
  padding: var(--feedo-space-s-m);
  background: var(--feedo-paper);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius);
  box-shadow: var(--feedo-shadow-sm);
}
.project-card:focus-within {
  border-color: var(--feedo-accent);
}
/* Un prototipo que todavía no llegó no tiene caras que mostrar, así que tampoco
   tiene segunda columna: la respuesta ocupa la tarjeta entera. */
.project-card[data-sin-caras] {
  grid-template-columns: minmax(0, 1fr);
}
/* El halo del acento se mudó del aviso de arriba a la tarjeta que dice el
   hecho. Antes eran dos bloques con el mismo borde diciendo lo mismo en la
   misma pantalla, y un acento que aparece dos veces por un solo hecho deja de
   señalar algo. */
.project-card[data-turn="respondido"] {
  border-color: var(--feedo-accent);
  box-shadow: 0 0 0 3px var(--feedo-accent-soft);
}
/* La tarjeta dejó de ser UN enlace. Tenía el nombre estirado sobre toda la caja
   —una promesa que el hover de levante confirmaba— y eso era correcto mientras
   la tarjeta tuviera un solo destino. Ahora tiene siete: el prototipo y las
   caras. Un área de clic estirada por encima de ellas las taparía, y el levante
   prometería un destino único que ya no existe.
   Sin subrayado por defecto: `li a` subraya todo enlace dentro de una lista
   —regla pensada para un enlace dentro de prosa— y acá adentro no hay prosa
   sino un título y una grilla de fichas. */
.project-card a {
  text-decoration: none;
}
/* Dos bloques y el aire entre ellos, no cinco renglones seguidos: arriba QUIÉN
   es y QUÉ hay que hacer, abajo cuánto va y desde cuándo. Cuando la columna de
   caras es la más alta, ese aire se estira y la medición baja hasta apoyarse en
   el mismo piso que la última miniatura, en vez de dejar un hueco al pie de la
   tarjeta. */
.project-card__head {
  --flow-space: var(--feedo-space-2xs);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--feedo-space-m);
  min-inline-size: 0;
}
.project-card__titulo {
  font-size: var(--feedo-step-1);
}
.project-card__titulo a {
  color: var(--feedo-ink);
}
.project-card__titulo a:hover {
  color: var(--feedo-accent);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
/* LA RESPUESTA. No crece de cuerpo —dos renglones grandes apilados se pelean—:
   lo que la separa del nombre es el color del turno, y el color nunca va solo,
   porque cada estado tiene una frase distinta. El tono refuerza; la frase dice. */
.project-card__turno {
  font-weight: 700;
  color: var(--feedo-ink);
  text-wrap: balance;
}
.project-card[data-turn="respondido"] .project-card__turno {
  color: var(--feedo-accent);
}
.project-card[data-turn="cerrar"] .project-card__turno {
  color: var(--feedo-comment);
}
.project-card[data-turn="listo"] .project-card__turno {
  color: var(--feedo-approve);
}
/* No es el turno de quien mira: gris, que es lo que en esta hoja significa
   «esto está dicho, no te pide nada». */
.project-card[data-turn="trabajando"] .project-card__turno,
.project-card[data-turn="vacio"] .project-card__turno {
  color: var(--feedo-muted);
}
/* Lo que hay que hacer, en el cuerpo del texto y en la tinta de leer.
   Es la única línea de la tarjeta que explica el gesto —«podés revisarlas,
   agregar tus comentarios o aprobarlas»— y estaba compartiendo tamaño con la
   fecha, así que se leía como una nota al pie de algo. La jerarquía contra el
   turno que tiene encima la sostiene el PESO, no el achique: apagar lo único
   accionable para que el titular gane es ganar la pelea equivocada. */
.project-card__detalle {
  font-size: var(--feedo-step-0);
  color: var(--feedo-ink);
}
/* La fecha baja un escalón respecto del desglose que tiene encima, y lleva su
   glifo. Es el dato menos accionable de la tarjeta —no dice qué hacer, dice
   cuándo pasó algo— así que se lee último. El glifo no está para decorar: le
   deja al ojo una forma reconocible para SALTEAR la fecha cuando busca otra
   cosa, que es lo que hace casi siempre.

   El glifo se achica junto con el texto (`1em`) en vez de llevar medida propia:
   una fecha más chica con el mismo calendario se ve como un error de armado. */
.project-card__pie {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
  font-size: var(--feedo-step--2);
  color: var(--feedo-muted);
}
.project-card__pie .glifo {
  flex: none;
  inline-size: 1em;
  block-size: 1em;
}
.project-card__avance {
  --flow-space: var(--feedo-space-3xs);
}
.project-card__avance .meter {
  --stack-space: var(--feedo-space-3xs);
}

/* ---- las caras del prototipo ----
   La misma gramática que la tira del pie de una vista (ver `.feedo-tira__vista`
   en feedo/feedback.css): miniatura con el tono del veredicto en el filo, UNA
   marca por ficha —número mientras nadie decidió, sello cuando hay veredicto— y
   el nombre debajo a dos líneas. Son dos hojas porque son dos cromos, pero es
   un solo idioma: quien reconoce el tilde verde acá lo reconoce allá.

   UNA fila, y las fichas se reparten el ancho que haya: es lo único de esta
   pantalla que crece bien, así que se queda con todo el sobrante y el océano de
   la tarjeta vieja no reaparece del otro lado. Cuando ni al mínimo entran, la
   fila se corre — igual que la tira del prototipo, que scrollea sola para no
   desbordar la ventana con veintidós vistas. */
.project-card__tira {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(6rem, 1fr);
  gap: var(--feedo-space-2xs);
  margin: 0;
  padding: 0;
  list-style: none;
  min-inline-size: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.project-card__vista {
  --vista-tono: var(--feedo-line);
  display: grid;
  gap: var(--feedo-space-3xs);
  align-content: start;
  color: var(--feedo-muted);
}
.project-card__vista[data-state="approved"] {
  --vista-tono: var(--feedo-approve);
  --vista-tono-ink: var(--feedo-approve-ink);
}
.project-card__vista[data-state="commented"] {
  --vista-tono: var(--feedo-comment);
  --vista-tono-ink: var(--feedo-comment-ink);
}
.project-card__vista[data-state="recheck"] {
  --vista-tono: var(--feedo-accent);
  --vista-tono-ink: var(--feedo-accent-ink);
}
.project-card__miniatura {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 10;
  border-radius: var(--feedo-radius-s);
  overflow: hidden;
  background: var(--feedo-paper-2);
  /* El estado, en el borde: se lee de reojo sin tapar la imagen. Por dentro y
     no por fuera, para que no le coma el hueco a la ficha de al lado. */
  outline: 2px solid var(--vista-tono);
  outline-offset: -2px;
  font-size: var(--feedo-step-1);
  font-weight: 600;
  transition: box-shadow 0.15s;
}
.project-card__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  display: block;
}
.project-card__vacia {
  font-size: var(--feedo-step--2);
  font-weight: 400;
  color: var(--feedo-muted);
}
.project-card__vista:hover .project-card__miniatura {
  box-shadow: 0 0 0 3px var(--feedo-accent-soft);
}
.project-card__vista:hover .project-card__nombre {
  color: var(--feedo-ink);
}
/* UNA marca por miniatura. Sobre una captura cualquiera un trazo suelto
   desaparece, así que va calado sobre una pastilla opaca — el mismo recurso que
   usa la tira. Sin veredicto es el número del recorrido, en la mono que ya
   numera todo lo demás del producto. */
.project-card__marca {
  position: absolute;
  inset-block-start: var(--feedo-space-3xs);
  inset-inline-start: var(--feedo-space-3xs);
  display: grid;
  place-items: center;
  min-inline-size: 1.25rem;
  block-size: 1.25rem;
  padding-inline: var(--feedo-space-4xs);
  border-radius: var(--feedo-radius-pill);
  background: var(--feedo-ink);
  color: var(--feedo-paper);
  font-family: var(--feedo-mono);
  font-size: var(--feedo-step--2);
  font-weight: 400;
  line-height: 1;
}
.project-card__vista[data-state="approved"] .project-card__marca,
.project-card__vista[data-state="commented"] .project-card__marca,
.project-card__vista[data-state="recheck"] .project-card__marca {
  background: var(--vista-tono);
  color: var(--vista-tono-ink);
  box-shadow: var(--feedo-shadow-sm);
}
.project-card__glifo {
  inline-size: 0.75rem;
  block-size: 0.75rem;
}
.project-card__nombre {
  font-size: var(--feedo-step--2);
  line-height: 1.3;
  font-weight: 400;
  /* Dos líneas y corta: un título largo no puede desalinear la fila entera. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* Las que no entraron. Ocupa el lugar de una ficha porque ES una ficha: dice
   cuántas quedan afuera y lleva al listado donde están todas. */
.project-card__mas .project-card__miniatura {
  color: var(--feedo-muted);
}
.project-card__mas:hover .project-card__miniatura {
  color: var(--feedo-accent);
}

/* ---- notices ---- */
.notice {
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  margin-block-end: var(--feedo-space-m);
  border-radius: var(--feedo-radius-s);
  font-size: var(--feedo-step--1);
  font-weight: 600;
}
.notice[data-tone="ok"] {
  color: var(--feedo-approve);
  background: color-mix(in srgb, var(--feedo-approve) 12%, var(--feedo-paper));
  border: 1px solid color-mix(in srgb, var(--feedo-approve) 30%, transparent);
}
.notice[data-tone="err"] {
  color: var(--feedo-danger);
  background: color-mix(in srgb, var(--feedo-danger) 10%, var(--feedo-paper));
  border: 1px solid color-mix(in srgb, var(--feedo-danger) 30%, transparent);
}
/* Algo quedó empezado y sin terminar: ni falló —no es `err`— ni salió bien
   —no es `ok`—. Lo pidió la puesta en marcha de GitHub a medias, que promete
   una lista de repos que no aparece hasta instalar la App.
   El ámbar es el color con el que este producto ya dice «esto sigue abierto». */
.notice[data-tone="pendiente"] {
  color: var(--feedo-comment);
  background: color-mix(in srgb, var(--feedo-comment) 12%, var(--feedo-paper));
  border: 1px solid color-mix(in srgb, var(--feedo-comment) 30%, transparent);
}

/* ---- panels ---- */
.panel {
  margin-block-end: var(--feedo-space-m);
  background: var(--feedo-paper);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius);
  box-shadow: var(--feedo-shadow-sm);
  padding: var(--feedo-space-m);
}
/* La persiana ocupa el alto de la sala. Es una pantalla y no un aviso al paso:
   arrinconada arriba a la izquierda se lee como un error, y lo que dice no es
   que algo salió mal sino que hay trabajo en curso. */
.persiana {
  min-height: 60vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: var(--feedo-space-s);
  max-width: var(--feedo-measure);
  margin-inline: auto;
}
.persiana h1 {
  margin: 0;
}

/* Las pantallas que entran (o no) en una publicación. Nombres de archivo, así
   que van en mono y no se parten: cortar `checkout.html` a la mitad obliga a
   reconstruirlo con la vista. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--feedo-space-3xs);
}
.chip {
  font-family: var(--feedo-mono);
  font-size: var(--feedo-step--1);
  padding: var(--feedo-space-4xs) var(--feedo-space-2xs);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-pill);
  background: var(--feedo-canvas);
  color: var(--feedo-muted);
  white-space: nowrap;
}
/* Las que entran: en el azul de la marca, que es el mismo con el que el cliente
   las va a ver marcadas del otro lado. */
.chip[data-in] {
  border-color: var(--feedo-accent);
  color: var(--feedo-accent);
  background: var(--feedo-canvas);
}

/* ---- elegir desde dónde se sirve el prototipo ----
   Cada opción es una tarjeta entera y no un radio con texto al lado: es la
   decisión más importante de esa pantalla y merece un blanco del tamaño de la
   decisión, no de 13 píxeles. El radio sigue ahí —es lo que hace que funcione
   con teclado y con lector de pantalla— y la tarjeta es su etiqueta. */
.root-options {
  border: 0;
  padding: 0;
  margin: 0;
  min-inline-size: 0; /* un fieldset trae min-content de fábrica y desborda en mobile */
}
.root-options__titulo {
  padding: 0 0 var(--feedo-space-xs);
  font-size: var(--feedo-step-0);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.root-options__lista {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  /* minmax(0,1fr) y no 1fr: un grid item mide `min-width: auto` de fábrica, así
     que se niega a bajar del ancho de su contenido. Con chips que no se parten,
     eso empujaba la fila a 370px dentro de un teléfono de 320. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--feedo-space-2xs);
}
.root-option {
  display: flex;
  gap: var(--feedo-space-2xs);
  align-items: start;
  min-inline-size: 0;
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-s);
  background: var(--feedo-canvas);
  cursor: pointer;
}
/* La elegida se marca en el azul de la marca, el mismo de los chips que entran. */
.root-option:has(.root-option__radio:checked) {
  border-color: var(--feedo-accent);
  box-shadow: inset 0 0 0 1px var(--feedo-accent);
}
/* El foco se dibuja sobre la TARJETA y no sobre el radio: quien navega con
   teclado tiene que ver qué opción está mirando, no dónde está el puntito. */
.root-option:focus-within {
  outline: 2px solid var(--feedo-accent);
  outline-offset: 2px;
}
.root-option__radio {
  margin-block-start: 0.25em;
  accent-color: var(--feedo-accent);
  flex: none;
}
.root-option__cuerpo {
  display: grid;
  gap: var(--feedo-space-3xs);
  min-inline-size: 0;
}
.root-option__head {
  display: flex;
  gap: var(--feedo-space-2xs);
  align-items: baseline;
  flex-wrap: wrap;
}
.root-option__dir {
  font-family: var(--feedo-mono);
  font-size: var(--feedo-step--1);
  overflow-wrap: anywhere;
}
/* Estos chips llevan TÍTULOS de pantalla, no nombres de archivo. El `nowrap` de
   `.chip` existe para que `checkout.html` no se parta al medio —partirlo obliga
   a reconstruirlo— pero «Certificado · Sol Álvarez Roldán» es una frase, se lee
   igual en dos renglones, y sin partir desborda un teléfono de 320. */
.chip--titulo {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Flush: the table draws its own edges, so the panel only pads the heading
   and lets rows run the full width. It scrolls rather than clips — `hidden`
   here silently cut the last column off, which is how a Sincronizar button
   ends up unreachable instead of merely inconvenient. */
.panel[data-flush] {
  padding: 0;
  overflow-x: auto;
}
.panel__title {
  padding: var(--feedo-space-s) var(--feedo-space-s-m) var(--feedo-space-xs);
  font-size: var(--feedo-step-0);
  letter-spacing: 0.02em;
}

/* ---- tables ---- */
.table {
  inline-size: 100%;
  border-collapse: collapse;
}
/* Top, not middle: once any cell wraps to two lines, centring every cell
   independently floats each column to its own vertical position and the row
   loses its baseline. Aligned to the top they all start on the same line. */
.table th,
.table td {
  text-align: start;
  padding: var(--feedo-space-xs) var(--feedo-space-2xs);
  border-block-end: 1px solid var(--feedo-line);
  vertical-align: top;
}
.table thead th {
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--feedo-muted);
}
.table tbody th {
  font-weight: 700;
}
/* ---- A ANCHO COMPLETO, EL SOBRANTE SE LO QUEDA LA COLUMNA QUE NOMBRA LA FILA ----
   Sin esto, una tabla suelta a lo ancho de la ventana reparte el sobrante entre
   TODAS sus columnas en proporción, y la fila se desarma: el nombre del proyecto
   a la izquierda y sus acciones a 1400px, con un hueco entre cada dato. Dándole
   `100%` a la celda que nombra la fila, esa columna absorbe el sobrante entero y
   las demás se quedan en su ancho natural. La fila vuelve a leerse como se lee
   una card en el resto del producto —el sujeto a un lado, su estado y sus
   controles juntos al otro— y el ancho nuevo va a lo único que crece de verdad:
   nombres largos y rutas de repo, que hasta ahora se recortaban con puntos
   suspensivos.

   Los dos límites de la regla están medidos, no supuestos.

   Sólo a ancho completo. A 64rem y a 76rem el reparto proporcional del navegador
   ya estaba bien, y forzar esto ahí rompía la tabla de «Cómo funciona»: la
   columna de etiquetas se llevaba 651px y las dos de prosa que explican cada
   etiqueta quedaban en 165 y 152, partidas en renglones de dos palabras.

   Sólo en tablas sin columna de PROSA. Cuando hay una —el comentario del cliente
   en la ficha de feedback— el sobrante es suyo, no del sujeto: con la regla
   puesta a ciegas esa columna se aplastaba a 104px mientras al lado sobraban
   1265px en blanco. `:not(:has())` hace que la regla se corra sola.

   Y `<th scope="row">` y no `:first-child` a secas: lo que absorbe es la columna
   SUJETO, no la primera que toque. */
[data-width="full"] .table:not(:has(.comment-cell)) tbody th[scope="row"] {
  inline-size: 100%;
}
.table tr:last-child td,
.table tr:last-child th {
  border-block-end: 0;
}
.panel[data-flush] .table th:first-child,
.panel[data-flush] .table td:first-child {
  padding-inline-start: var(--feedo-space-s-m);
}
.panel[data-flush] .table th:last-child,
.panel[data-flush] .table td:last-child {
  padding-inline-end: var(--feedo-space-s-m);
}
/* El estado vacío ocupa el lugar de la tabla, así que hereda su problema: el
   panel se queda sin padding para que la tabla llegue a los bordes, y cada
   celda del borde lo recupera por su cuenta arriba. Un párrafo no es una celda
   y nadie se lo devolvía — quedaba apoyado contra el borde izquierdo mientras
   el título, que trae el suyo en `.panel__title`, se leía tres espacios más
   adentro. Dos textos del mismo panel arrancando en ejes distintos.

   El margen de arriba se anula por lo mismo: acá el aire ya lo puso el padding
   inferior del título, y el `margin-block-start` que el estado vacío trae para
   cuando vive suelto —el listado del cliente— se apilaba encima. */
.panel[data-flush] .empty-state {
  padding-inline: var(--feedo-space-s-m);
  margin-block-start: 0;
}
.table tbody tr:hover {
  background: color-mix(in srgb, var(--feedo-paper-2) 45%, transparent);
}

/* ---- the row's type scale ----
   A row gets two sizes and no more. Line one carries each column's primary
   value at --row-lead; line two carries supporting detail at --row-sub. Seven
   elements at five different sizes is what made this table unreadable. */
.table tbody tr {
  --row-lead: var(--feedo-step-0);
  --row-sub: var(--feedo-step--1);
  --row-line: calc(var(--feedo-step-0) * 1.6);
}
.table tbody th,
.table tbody td {
  font-size: var(--row-lead);
  line-height: var(--row-line);
}
/* Scoped to .table so it out-specifies the cell rule above, which would
   otherwise hold every cell at --row-lead. */
.table .cell-meta {
  font-size: var(--row-sub);
  color: var(--feedo-muted);
  white-space: nowrap;
}
.table .actions a,
.table .meter[data-density="compact"] .meter__legend {
  font-size: var(--row-sub);
}
/* Elements taller than a line of text sit centred on line one rather than
   hanging below it — the cells are top-aligned, so they need the offset. */
.table .avatar,
.table .identity,
.table .actions__row {
  margin-block-start: calc((var(--row-line) - 2rem) / 2);
}
/* The flex layout goes on an inner wrapper, never on the cell itself:
   display:flex takes a <td> out of the table model, so it stops stretching to
   the row height and its bottom border lands above everyone else's. And no
   vertical-align override here — a stray `middle` used to out-specify the
   shared `top` and drop the actions below the row's first line. */
.actions__row {
  --cluster-space: var(--feedo-space-xs);
  /* The cluster primitive wraps by default, which is right almost everywhere
     and wrong here: a row of actions that breaks onto two lines steals width
     from every other column and makes the row taller than its content. */
  flex-wrap: nowrap;
}
.actions__row .btn {
  white-space: nowrap;
}
.actions__row form {
  margin: 0;
}

/* Two-line identity cell: the human name leads, the machine key follows. */
.view-cell {
  min-inline-size: 15rem;
}
/* A column of twenty accent-coloured links is a wall. The name reads as text;
   the link colour is earned on hover, where it also confirms the target. */
.view-cell__title {
  display: block;
  font-weight: 700;
  line-height: var(--row-line, 1.6);
  color: var(--feedo-ink);
}
a.view-cell__title:hover {
  color: var(--feedo-accent);
}

/* ---- rows that are links ----
   Same trick as the dashboard card: the name is the link and a stretched
   pseudo-element extends its hit area over the whole row, so there is still
   exactly one link in the accessibility tree rather than a row of duplicates.
   Opt-in per table, because a row full of selectable text — the feedback
   read-out, where the comments live — should not be one big click target. */
.table[data-rows="linked"] tbody tr {
  position: relative;
}
.table[data-rows="linked"] a.view-cell__title::after {
  content: "";
  position: absolute;
  inset: 0;
}
/* Anything you can actually operate has to sit above that area, or the row
   swallows it — including the disc, whose tooltip names the client, and the
   client's mailto: escribirle es la otra cosa que se hace desde esa tabla, y
   sin esto la fila se la come. */
.table[data-rows="linked"] .actions__row,
.table[data-rows="linked"] .avatar,
.table[data-rows="linked"] td a[href^="mailto:"] {
  position: relative;
  z-index: 1;
}
/* One line, never split. "owner/repo · branch" is a single coordinate; letting
   the branch fall to its own line orphans it and makes the row taller than the
   information in it. If it truly does not fit it truncates, and the full
   string is on the title attribute. */
.view-cell__meta {
  display: block;
  margin-block-start: var(--feedo-space-4xs);
  line-height: 1.35;
  font-size: var(--row-sub, var(--feedo-step--1));
  font-weight: 400;
  color: var(--feedo-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.view-cell__stale {
  margin-inline-start: var(--feedo-space-3xs);
  padding: 1px 7px;
  border-radius: var(--feedo-radius-pill);
  background: color-mix(in srgb, var(--feedo-danger) 12%, var(--feedo-paper));
  color: var(--feedo-danger);
  font-size: var(--feedo-step--2);
  font-weight: 700;
}
/* Esta vista tiene explicación hablada. Misma píldora que «fuera del índice»,
   en acento y no en rojo: no es un problema que haya que arreglar, es un dato
   sobre la vista. */
.view-cell__narrada {
  margin-inline-start: var(--feedo-space-3xs);
  padding: 1px 7px;
  border-radius: var(--feedo-radius-pill);
  background: color-mix(in srgb, var(--feedo-accent) 12%, var(--feedo-paper));
  /* El acento a cuerpo entero, no --feedo-accent-ink: ese es blanco, y va
     SOBRE el acento sólido. Acá el fondo es el acento lavado al 12%, así que
     el texto tiene que ser el color pleno o no se lee. */
  color: var(--feedo-accent);
  font-size: var(--feedo-step--2);
  font-weight: 700;
}
.comment-cell {
  max-inline-size: 34rem;
}

/* repo · branch is a coordinate, not a status. No fill and no colour — two
   chips of different hues made the least important thing in the row the
   loudest, and pushed the branch onto a line of its own. One quiet line. */
.view-cell__meta code {
  background: none;
  padding: 0;
  color: inherit;
}
.ref-branch::before {
  content: "·";
  margin-inline: var(--feedo-space-4xs);
  font-weight: 700;
}

/* ---- identity ----
   Initials disc. The violet is free to mean identity again now that
   "commented" has its own hue, so the person marker wears the brand colour. */
.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2rem;
  block-size: 2rem;
  flex: none;
  border-radius: 50%;
  background: color-mix(in srgb, var(--feedo-accent) 12%, var(--feedo-paper));
  color: var(--feedo-accent);
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1;
  vertical-align: top;
  cursor: default;
}
.identity {
  --cluster-space: var(--feedo-space-2xs);
  display: inline-flex;
  flex-wrap: nowrap;
}

/* ---- disclosures ---- */
.disclosure {
  background: var(--feedo-paper);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius);
  box-shadow: var(--feedo-shadow-sm);
}
.disclosure__summary {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  padding: var(--feedo-space-s) var(--feedo-space-s-m);
  font-size: var(--feedo-step-0);
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}
.disclosure__summary::-webkit-details-marker {
  display: none;
}
/* ---- LA SEÑAL DE QUE ESTO SE ABRE, UNA SOLA PARA TODO EL PRODUCTO ----
   Era un `+` que pasaba a `−`, dentro de una pastilla de papel y en acento. Se
   entendía, y aun así había que cambiarlo por tres razones que van en la misma
   dirección.

   La primera: es la misma señal que la barra de la vista, el asa de la tira y el
   encabezado del dock dicen con el chevron de la familia. Dos dibujos para una
   señal es lo que hace que aprender a usar un cajón no enseñe a usar el otro.
   La segunda: un carácter tipográfico no puede pertenecer a esta familia —no
   tiene grilla de 24 ni trazo de 2— así que al lado de cualquier glifo del
   producto se lee con otro grosor.
   La tercera: la pastilla rellena en acento era lo más contrastado del renglón,
   puesta sobre la parte que menos información lleva. El título dice qué hay
   adentro; la señal sólo tiene que decir que hay un adentro.

   Apunta abajo, que es donde aparece el contenido, y se da vuelta al abrirse:
   la misma regla que los cinco desplegables de la pantalla del prototipo. */
.disclosure__chevron {
  inline-size: 1rem;
  block-size: 1rem;
  color: var(--feedo-muted);
  transition: transform 0.15s;
}
.disclosure[open] .disclosure__chevron {
  transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
  .disclosure__chevron {
    transition: none;
  }
}
.disclosure[open] .disclosure__summary {
  border-block-end: 1px solid var(--feedo-line);
}
.disclosure .form {
  padding: var(--feedo-space-m) var(--feedo-space-s-m);
}
/* El nombre se lleva el ancho que sobra para que el contador quede pegado al
   borde derecho: en el medio del renglón se lee como parte del título. */
.disclosure__nombre {
  flex: 1;
  min-inline-size: 0;
}
/* Lo único que se sabe del bloque sin abrirlo. Va en el acento y no en rojo:
   no hay nada roto —el proyecto anda igual—, hay algo sin enchufar. Se apaga
   solo: cuando no falta nada, este span no se imprime. */
.disclosure__falta {
  font-size: var(--feedo-step--1);
  /* Cuenta cuántos ajustes faltan: se mide, no titula. */
  font-weight: 600;
  color: var(--feedo-accent);
  background: color-mix(in srgb, var(--feedo-accent) 12%, var(--feedo-canvas));
  border-radius: var(--feedo-radius-pill);
  padding: var(--feedo-space-4xs) var(--feedo-space-2xs);
  white-space: nowrap;
}

/* ---- ajustes que se tocan una vez ----
   Dos gestos de alta —la credencial del agente y el aviso del repo— que antes
   eran dos paneles del mismo peso que el historial y la tabla, arriba de todo.
   Adentro del mismo desplegable dejan de competir por la primera pantalla, y la
   regla entre ellos alcanza para separarlos: son dos, no una lista. */
.ajustes {
  padding: var(--feedo-space-m) var(--feedo-space-s-m);
  display: grid;
  gap: var(--feedo-space-m);
}
/* `min-inline-size: 0` y no por prolijidad: adentro va el instructivo del agente,
   un <pre> de 90 columnas. Una celda de grilla mide `auto` por defecto, o sea que
   se estira hasta que su contenido entra — y en 320px eso empujaba la página
   entera 500px hacia la derecha. Con 0, el <pre> vuelve a scrollear solo. */
.ajuste {
  --flow-space: var(--feedo-space-s);
  min-inline-size: 0;
}
.ajuste + .ajuste {
  padding-block-start: var(--feedo-space-m);
  border-block-start: 1px solid var(--feedo-line);
}
/* Siguen siendo <h2> —el orden del documento no cambió— pero ya no titulan una
   sección de la página: titulan un ajuste adentro de un cajón cerrado, y a
   cuerpo de h2 gritaban desde adentro. */
.ajuste__titulo {
  font-size: var(--feedo-step-1);
  letter-spacing: -0.01em;
}

/* ---- status badges (shared review vocabulary) ---- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: var(--feedo-radius-pill);
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.badge[data-state="approved"] {
  background: var(--feedo-approve);
  color: var(--feedo-approve-ink);
}
.badge[data-state="commented"] {
  background: var(--feedo-comment);
  color: var(--feedo-comment-ink);
}
.badge[data-state="pending"] {
  background: var(--feedo-paper-2);
  color: var(--feedo-muted);
  border: 1px solid var(--feedo-line);
}
/* Vistas con respuesta publicada que el cliente todavía no volvió a mirar. El
   acento de la marca y no el de comentario: no es algo que él dijo, es algo que
   le contestamos. Mismo color que el aviso de arriba, que anuncia lo mismo. */
.badge[data-state="recheck"] {
  background: var(--feedo-accent-soft);
  color: var(--feedo-accent);
}

/* =========================================================================
   EXCEPTIONS — structural only. There are no type or spacing breakpoints
   anywhere in this project; the token scale is fluid and handles that.
   ========================================================================= */

/* A five-column admin table cannot shrink below its longest repo slug, so it
   stops being a table: each row becomes a labelled block. The header row is
   kept for assistive tech via the per-cell data-label.
   The switch happens at 68rem, not at a phone width, because that is where the
   five columns stop fitting — and below it the alternative is scrolling
   sideways to reach Sincronizar, which is a worse trade than a taller row. */
@media (max-width: 68rem) {
  .table[data-stacked] thead {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .table[data-stacked] tr {
    display: grid;
    gap: var(--feedo-space-3xs);
    padding: var(--feedo-space-s) var(--feedo-space-s-m);
    border-block-end: 1px solid var(--feedo-line);
  }
  .table[data-stacked] tr:last-child {
    border-block-end: 0;
  }
  .table[data-stacked] th,
  .table[data-stacked] td {
    display: block;
    padding: 0;
    border: 0;
  }
  /* The edge padding above is set with :first-child / :last-child, which
     out-specifies a bare element override — match the shape to win it back. */
  .panel[data-flush] .table[data-stacked] th:first-child,
  .panel[data-flush] .table[data-stacked] td:first-child,
  .panel[data-flush] .table[data-stacked] th:last-child,
  .panel[data-flush] .table[data-stacked] td:last-child {
    padding-inline: 0;
  }
  /* A pending row has nothing to say in most columns. Stacked, those cells
     become two labelled lines of em-dash each; drop them and the row reads as
     what it is. */
  .table[data-stacked] td[data-empty] {
    display: none;
  }
  /* Unstacked the meter is a column; stacked it would run the whole row and
     read as a divider rather than as a quantity. */
  .table[data-stacked] .meter {
    max-inline-size: 18rem;
  }
  .table[data-stacked] td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-block-end: var(--feedo-space-4xs);
    font-size: var(--feedo-step--2);
    font-weight: 700;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--feedo-muted);
  }
  .table[data-stacked] .actions__row {
    justify-content: flex-start;
    margin-block-start: var(--feedo-space-3xs);
  }
  .comment-cell {
    max-inline-size: none;
  }
  .view-cell {
    min-inline-size: 0;
  }

}

/* Las dos columnas de la tarjeta dejan de caber cuando la de la izquierda llega
   a su mínimo de lectura y a la derecha ya no entran dos caras. Debajo de eso la
   respuesta va arriba y las caras abajo, que es el mismo orden en que se leen. */
@media (max-width: 52rem) {
  .project-card {
    grid-template-columns: minmax(0, 1fr);
  }
  /* Apilada, la tarjeta tiene el ancho entero para las caras y ninguna razón
     para esconderlas detrás de un gesto lateral: la fila pasa a rejilla y
     envuelve. Un carrusel dentro de una página que ya scrollea es el peor lugar
     donde poner algo que se quiere ver. */
  .project-card__tira {
    grid-auto-flow: row;
    grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
    overflow-x: visible;
  }
}

@media (prefers-reduced-motion: reduce) {
  button,
  .btn,
  .project-card__miniatura {
    transition: none;
  }
}

/* ---- agent access ----
   A credential and a command to paste. Monospace because both are literal:
   a stray character in either and the agent gets a 401 with no clue why.
   El contenedor lo pone ahora `.ajuste`: el acceso del agente dejó de ser un
   desplegable propio y comparte cajón con la sincronización. */
.snippet {
  margin: 0;
  padding: var(--feedo-space-xs) var(--feedo-space-s);
  border-radius: var(--feedo-radius-s);
  background: var(--feedo-ink);
  color: var(--feedo-paper);
  font-size: var(--feedo-step--1);
  line-height: 1.6;
  overflow-x: auto;
}
.snippet code {
  background: none;
  padding: 0;
  color: inherit;
  white-space: pre;
}

/* El briefing del agente entra entero acá — token, endpoint, contrato y cómo
   recorrerlo — para que se copie de una sola vez. Es largo, así que se acota y
   hace scroll; por eso lleva tabindex: un contenedor que scrollea tiene que
   poder alcanzarse y recorrerse con el teclado, no sólo con la rueda. */
.snippet[data-long] {
  max-block-size: 24rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* El ciclo del producto en tres pasos. Numerados porque el orden es real: sin
   vistas publicadas no hay nada que revisar, y sin revisión no hay devolución.
   El número se dibuja con un contador en vez de dejar la viñeta del <ol>, para
   que tenga el peso de un dato y no el de una lista de compras. */
/* Las dos mitades del problema, lado a lado. En paralelo y no en lista, porque
   ninguna viene antes que la otra: son el mismo hueco visto desde cada punta. */
.duo {
  display: grid;
  gap: var(--feedo-space-s);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  margin-block-start: var(--feedo-space-s);
}
.duo__lado {
  display: grid;
  gap: var(--feedo-space-3xs);
  align-content: start;
  padding: var(--feedo-space-s);
  border-radius: var(--feedo-radius);
  background: var(--feedo-paper-2);
}
.duo__lado h3 {
  margin: 0;
  font-size: var(--feedo-step-0);
  font-weight: 700;
}
.duo__lado p {
  margin: 0;
  font-size: var(--feedo-step--1);
  line-height: 1.5;
  color: var(--feedo-muted);
}

.ciclo {
  list-style: none;
  counter-reset: paso;
  /* Aire de más contra el encabezado: la regla superior de la primera columna
     queda si no tan cerca del h2 que parece subrayarlo. */
  margin: var(--feedo-space-s) 0 0;
  padding: 0;
  display: grid;
  gap: var(--feedo-space-m) var(--feedo-space-l);
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
}
/* Cada paso es una pieza cerrada. Van sobre el papel más oscuro para separarse
   del panel que las contiene, que ya es claro: si compartieran fondo, las tres
   se leerían como un solo bloque de texto en columnas. */
.ciclo__paso {
  counter-increment: paso;
  display: grid;
  gap: var(--feedo-space-2xs);
  align-content: start;
  padding: var(--feedo-space-s);
  border-radius: var(--feedo-radius);
  background: var(--feedo-paper-2);
}
/* Relleno y no sólo color: el número es el ancla visual de cada paso y a este
   tamaño un dígito suelto se pierde entre el título y el texto. */
.ciclo__paso::before {
  content: counter(paso);
  inline-size: 2.1rem;
  block-size: 2.1rem;
  display: grid;
  place-items: center;
  border-radius: var(--feedo-radius-pill);
  background: var(--feedo-accent);
  color: var(--feedo-accent-ink);
  font-size: var(--feedo-step--1);
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.ciclo__paso h3 {
  margin: 0;
  font-size: var(--feedo-step-1);
  font-weight: 700;
  line-height: 1.2;
  text-wrap: balance;
}
.ciclo__paso p {
  margin: 0;
  font-size: var(--feedo-step--1);
  line-height: 1.5;
  color: var(--feedo-muted);
}

/* El curl y su botón de copiar: el bloque manda, el botón no compite. */
.snippet-row {
  --cluster-space: var(--feedo-space-2xs);
  flex-wrap: nowrap;
  align-items: stretch;
}
.snippet-row .snippet {
  flex: 1;
  min-inline-size: 0;
}
.snippet-row .btn {
  flex: none;
  align-self: center;
}

/* =========================================================================
   EL MARCO — una vista del prototipo.
   Ver openspec/changes/viewport-switcher/.

   Hasta acá una vista ERA el prototipo, así que sólo se podía ver al ancho de
   la ventana. Ahora el prototipo vive adentro de un documento de Feedo y esto
   es la sala donde se lo mira.
   ========================================================================= */

/* El fondo de la sala, elegido por proyecto.
   No es gusto: es contraste. Un prototipo claro sobre lienzo blanco se funde con
   la sala y quien revisa deja de ver dónde termina el diseño que vino a juzgar. */
.feedo-marco.fondo-oscuro {
  background: var(--feedo-ink);
}

/* ---- EL CROMO DE UNA VISTA SIGUE A SU SALA ----
   Acá la barra deja de ser azul de marca y pasa a ser lo contrario de la sala:
   tinta si la sala es blanca, lienzo si la sala es tinta.

   La regla vieja era «la barra no cambia nunca»: el azul se lee sobre los dos
   fondos y es inconfundiblemente Feedo, así que no se puede confundir con el
   diseño de un cliente. Eso sigue siendo cierto en el resto del producto, donde
   no hay sala ni prototipo con qué compararse, y la barra sigue azul ahí.

   En ESTA pantalla el argumento no alcanzaba, por dos motivos que se suman.
   El de sistema: la capa de revisión tiene tres piezas —la barra arriba, el dock
   y el asa de la tira abajo— que son lo mismo, cromo de la sesión y no de la
   pantalla (ver `feedback.js`, «la barra, que es cromo de la sesión y no de la
   pantalla»). Las dos de abajo son superficie opaca con filo y sombra; la de
   arriba era un plano de color saturado. Tres piezas de una misma capa vestidas
   con dos materiales distintos se leen como dos productos.
   El de contraste: la sala ya existe justamente para separar por VALOR lo que es
   de Feedo de lo que es del cliente, y el cromo se colgaba de otra cosa —el
   matiz— en vez de sumarse a esa separación. Atado a la sala, el eje es uno
   solo: el prototipo en su lienzo, la sala como foso, y el cromo del otro lado
   del foso.

   El acento no se pierde: vuelve a ser lo que `tokens.js` dice que es
   —«identidad, acción primaria y foco de teclado. Nada más»— y aparece como
   FIGURA, en el puesto marcado dentro de un panel, no como cuatro mil píxeles
   cuadrados de fondo.

   La otra mitad ya está: con la sala BLANCA el dock y el asa se dan vuelta
   también. Faltaba porque sus colores de veredicto estaban calculados sobre
   papel y sobre tinta caían a 3:1 —por debajo de AA—, así que darlos vuelta
   rompía la legibilidad de lo único que el dock tiene para decir. Cada veredicto
   tiene ahora su cara de tinta en `src/design/tokens.js`, con su guía y su test,
   y las dos caras pesan lo mismo contra su propia superficie.

   Los tres renglones de acá abajo se repiten, palabra por palabra, en
   `feedback.css` («EL CROMO DEL PIE»). No es copia: es la prueba de que las tres
   piezas son el mismo cromo. Están en dos archivos porque son dos hojas —ésta la
   sirve el marco, aquélla la inyecta el JS sobre el documento del cliente— y si
   alguna vez divergen, divergieron por error. */
/* `.marco__flotante` entra en el mismo scope y no en uno propio: en el modo
   ampliado esa pastilla ES la barra —lo único que queda de ella— así que tiene
   que heredar el mismo cromo por construcción y no por copia. Definirle sus
   propios valores era el camino a la cuarta divergencia de esta barra, que ya
   falló tres veces midiendo contraste contra el fondo equivocado.
   Y es lo que la vuelve legible sobre el diseño de un cliente cualquiera: el par
   cromo/tinta es opaco y se mide contra su propia superficie. Sin estos tokens
   la pastilla resolvía `background: var(--feedo-cromo)` a transparente y el
   único control para salir del modo quedaba casi invisible sobre un prototipo
   claro. */
.feedo-marco.fondo-claro .topbar,
.feedo-marco.fondo-claro .marco__flotante {
  --feedo-cromo: var(--feedo-ink);
  --feedo-cromo-tinta: var(--feedo-paper);
  /* El aro va en el acento, que es su trabajo en todo el producto, y sobre tinta
     se lee. Sobre el propio acento no podía, y por eso allá es blanco. */
  --feedo-cromo-foco: var(--feedo-accent-soft);
}
.feedo-marco.fondo-oscuro .topbar,
.feedo-marco.fondo-oscuro .marco__flotante {
  --feedo-cromo: var(--feedo-canvas);
  --feedo-cromo-tinta: var(--feedo-ink);
  --feedo-cromo-foco: var(--feedo-accent);
}

/* La marca conserva su azul cuando la barra es clara: es el único lugar de la
   pantalla donde el acento puede ser identidad sin pelearle a nada. Sobre la
   barra de tinta va en papel, porque el azul contra tinta da 2.3:1 y el calderón
   quedaría hecho una mancha. */
.feedo-marco.fondo-oscuro .marco__marca .mark {
  color: var(--feedo-accent);
}

/* ---- EL SITIO QUE LA REVISIÓN OCUPA ABAJO ----
   En el pie de esta pantalla viven, siempre, dos piezas de la capa de revisión:
   el asa de la tira de vistas a la izquierda y el dock de feedback plegado a la
   derecha. Las dos son `position: fixed`, así que se dibujan ENCIMA de lo que
   haya debajo — hoy, más lienzo.

   Antes le reservaban al lienzo un hueco propio, calculado en `vh`: un % de
   LA VENTANA ENTERA, menos una banda fija para el dock. Esa cuenta no sabe
   cuánto mide la barra de arriba —61px fijos, no un % de nada— así que en
   cualquier ventana más alta que aquella con la que se calibró, el % de sobra
   crecía y ni la barra ni el dock lo usaban: quedaba una franja negra entre el
   lienzo y el dock que no sostenía nada, y crecía con el monitor. Medido en
   963px de alto: 72px de esa franja no eran de nadie.

   Ahora no hace falta reservar nada: `.page` ES el resto de la ventana —`body`
   reparte alto entre la barra y ella con flex, ver `body.feedo-marco`, abajo—
   así que el lienzo pide «lo que sobra» y las dos piezas flotan encima, igual
   que antes, sólo que sobre más lienzo. En reposo caen sobre el fondo y el
   prototipo se ve entero; cuando la tira se abre sí lo tapa, y está bien: eso
   es un cajón que alguien abrió y que se cierra solo cuando deja de hacer
   falta.

   `--feedo-banda` sigue viva: es el alto del encabezado del dock y del asa de
   la tira (ver EL PIE DEL DOCK en feedback.css), y se define ACÁ y no ahí
   porque esa hoja la inyecta el JS y llega después del primer dibujo. El
   respaldo de feedback.css es la misma cuenta, para cuando la capa aterriza en
   el documento de un cliente, que no carga esta hoja. */
.feedo-marco {
  --feedo-banda: calc(2rem + var(--feedo-space-2xs) * 2);
}

/* La página se estira, no se mide: ver EL SITIO QUE LA REVISIÓN OCUPA ABAJO,
   arriba. `body` reparte el alto de la ventana entre la barra —que mide lo que
   su contenido pide— y `.page` —que se queda con el resto—, y de ahí en más
   `.marco`/`.marco__lienzo` heredan ese resto en vez de adivinarlo en `vh`. */
body.feedo-marco {
  display: flex;
  flex-direction: column;
  min-block-size: 100dvh;
}

/* Sobre tinta, el borde claro es el que separa el lienzo del fondo. */
.marco[data-fondo="oscuro"] .marco__lienzo {
  border-color: color-mix(in srgb, var(--feedo-paper) 26%, transparent);
}

/* El ancho de esta pantalla ya no se declara acá: es `width: 'full'`, el mismo
   escalón del vocabulario que usan el listado de vistas y el panel. Ver
   `[data-width]` arriba. Lo que empezó como excepción del marco —soltar el tope
   y que la barra siguiera al contenido— resultó ser la regla del producto.

   El aire de arriba y el de abajo son ahora EL MISMO token: antes abajo
   llevaba `--feedo-space-m` porque ahí vivía además la reserva del dock. Esa
   reserva la absorbió el lienzo (ver EL LIENZO, abajo), así que lo único que
   queda abajo es respiro, igual que arriba. En cero el lienzo quedaba pegado
   a la barra o al borde de la ventana, y se leía como una sola pieza con lo
   que lo toca; el prototipo necesita que se vea dónde empieza y dónde
   termina. */
.feedo-marco .page {
  /* `.page` también es `.wrapper`, que centra con `margin-inline: auto` — la
     misma cuenta que ya está resuelta unas líneas más abajo para
     `.marco > .wrapper`: como ítem de flex, un margen automático lo encoge a
     `fit-content` en vez de estirarlo. Acá `.page` es flex ITEM de `body`, así
     que le toca la misma línea. */
  inline-size: 100%;
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  padding-block: var(--feedo-space-s);
  /* Y el aire lo pone SÓLO ese padding.
     `renderFrame` emite un `<h1 class="visually-hidden">` con el nombre de la
     vista y después el marco. El h1 no ocupa nada a la vista, pero para
     `.flow > * + *` es un hermano igual que cualquier otro, así que el marco
     entraba como segundo hijo y se llevaba otros 27px arriba. El comentario de
     acá dice que el aire buscado son 18; medidos en Chrome eran 45.
     Un ritmo vertical entre hermanos no tiene sentido en una pantalla cuyo
     único hermano visible es el prototipo. */
  --flow-space: 0px;
}

/* La reserva que la capa se pone sola, acá no hace falta.
   `feedback.css` le da al body un `padding-block-end` genérico (18px) para que
   el dock no tape la última porción de contenido en CUALQUIER documento donde
   aterrice —pensado para cuando cae directo sobre el sitio de un cliente, sin
   este marco—. Acá el aire de abajo ya lo puso `.page`, arriba, con el mismo
   token que el de arriba: sumar el genérico duplicaba el respiro (36px contra
   los 18 de arriba) y en ampliado además hacía aparecer una barra de scroll
   donde el punto es que el lienzo sea EXACTAMENTE la ventana.
   Va con el atributo en el selector, y no sólo con la clase, porque
   `feedback.css` la inyecta el JS y llega después: hace falta ganar por
   especificidad y no por orden. */
body.feedo-marco[data-feedo-float] {
  padding-block-end: 0;
}

/* El cromo oscuro ya no es de esta pantalla: es de todo el producto. Ver
   `.topbar` arriba. Lo que empezó como una excepción del marco resultó ser la
   regla, porque el argumento —el cromo tiene que ser inconfundiblemente el
   cromo— no era de una vista sino de la herramienta. */

/* ---- LA BARRA DE ESTA PANTALLA SE ALINEA CON EL LIENZO ----
   Acá se descubrió la regla, y por eso queda escrita la historia aunque la línea
   que la implementaba ya no viva en esta sección.

   Esta página suelta su `main` a ancho completo —el lienzo es lo único que hay
   para mirar— y la barra, que entonces tenía un ancho fijo de 76rem, quedaba
   flotando en el medio: en una ventana de 1680 el prototipo empezaba a 27px del
   borde y la marca de Feedo a 232px, y el desfasaje crecía con la ventana. La
   misma pantalla con dos márgenes distintos.

   La regla de verdad no era «la barra mide siempre 76rem»: era «la barra se
   alinea con el contenido de SU página». Hoy eso lo garantiza el `<body>`, que
   declara `--wrapper-max` una sola vez para los dos `.wrapper` — ver
   `[data-width]` arriba. */

/* La barra de la vista ES `.topbar`, la misma clase que usa el resto del
   producto — no una barra propia que copie sus medidas.
   Copiarlas fue el primer intento y es justamente lo que divergía: un padding
   distinto acá y la vista dejaba de tener el alto del listado. Reusar la clase
   hace que el ancho y el alto coincidan por construcción, y que un cambio futuro
   en el cromo llegue a las dos pantallas al mismo tiempo. */

/* La marca queda como ancla a casa, sin el logotipo escrito: en una barra densa
   el calderón solo ya se reconoce, y las palabras que sobran son alto que le
   falta al prototipo. */
/* Sin divisor propio, y por dos motivos.
   El técnico: la línea era `--feedo-line`, que es tinta al 14% — un token de
   superficie CLARA. Sobre el azul de la barra no separaba nada, ensuciaba: se
   veía como una mancha más oscura, no como una regla.
   El de diseño: partía la marca de la ayuda, que son la misma familia —«dónde
   estoy» y «cómo funciona esto»—. La única separación que la barra necesita es
   la que aísla al SUJETO (el nombre de la vista) de todo lo que lo rodea, y esa
   vive ahora en `.marco__vistas`. */
.marco__marca {
  display: inline-flex;
  align-items: center;
}

/* El calderón, al tamaño con el que se compone en toda la app: la marca no
   cambia de cuerpo según la pantalla. */
.marco__marca .mark {
  inline-size: 1.45em;
  block-size: 1.45em;
}

/* La barra se alinea con el contenido del menú: venir del menú y entrar a una
   vista no debería mover de lugar el título ni la salida.

   Lo hace envolviéndose en `.wrapper`, la primitiva que ya usa la barra de
   arriba, y no con un max-width propio. Rederivar la caja a mano la dejaba 27px
   corrida —justo un gutter— porque el padding de la primitiva no entraba en la
   cuenta. Cuando ya existe la pieza que centra, usarla es también la única forma
   de que las dos coincidan siempre. */



.marco {
  display: grid;
  /* Una sola fila que ocupa toda la caja, y no `auto` —que se mide por el
     contenido—: es lo que deja que el lienzo pida `100%` y reciba el alto que
     `.page` le da de verdad, en vez de heredar el tamaño natural de un
     `<iframe>` suelto (150px). */
  grid-template-rows: minmax(0, 1fr);
  flex: 1 1 auto;
  min-block-size: 0;
  gap: var(--feedo-space-xs);
}

/* `.wrapper` centra con `margin-inline: auto` y un max-width, y eso funciona en
   flujo normal —un bloque con `width: auto` llena y después topa—. Como ítem de
   grid no: con márgenes automáticos el ítem deja de estirarse y se resuelve a
   `fit-content`, así que la barra se encogía a su contenido y quedaba centrada
   en vez de repartida a lo ancho de la caja. Devolverle el estirón es una línea. */
/* `.wrapper` centra con márgenes automáticos, que en flujo normal llenan y
   después topan; como ítem de grid, en cambio, resuelven a `fit-content` y la
   barra se encogía a su contenido. Dentro de `.topbar` esto ya no hace falta
   —el nav es el ítem y su wrapper vuelve al flujo normal—, pero la regla queda
   para cualquier `.wrapper` que se cuelgue directo del marco. */
.marco > .wrapper {
  inline-size: 100%;
}

/* La salida de la vista. Vive en la barra y no en el dock: el dock se puede
   ocultar, y quien lo ocultaba se quedaba encerrado en la vista. Además navegar
   no es tema del panel de feedback. */
/* El subrayado se atenúa en vez de irse.
   Estaba a pleno y era lo ÚNICO subrayado de la barra, así que se llevaba una
   mirada que le corresponde al nombre de la vista, que está pegado y es lo
   importante. Pero borrarlo dejaba el enlace distinguido sólo por su lugar:
   acá el subrayado tenue conserva la señal de «esto es un enlace» y la sube a
   pleno al pasar o al enfocar.

   El `color: var(--feedo-accent)` que estaba acá era código muerto —`.topbar a`
   le gana por especificidad y lo pinta blanco— y encima azul sobre azul sería
   invisible. El comentario viejo decía «azul y subrayado al pasar», o sea que
   la regla ya no hacía lo que su propio comentario contaba. */
.marco__volver {
  display: inline-flex;
  align-items: center;
  gap: 0.25em;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--feedo-cromo-tinta) 45%, transparent);
  text-underline-offset: 0.18em;
  font-size: var(--feedo-step--1);
  font-weight: 600;
  white-space: nowrap;
}

.marco__volver:hover,
.marco__volver:focus-visible {
  text-decoration-color: var(--feedo-cromo-tinta);
}

.marco__volver .marco__icono {
  inline-size: 1.05rem;
  block-size: 1.05rem;
  flex: none;
}

/* Un hueco chico, porque acá adentro va UN bloque y no cuatro cosas sueltas.
   Estaba en `--feedo-space-s` para todo, así que marca, ayuda, vuelta y nombre
   quedaban a la misma distancia entre sí y se leían como una fila de cuatro
   pares. Son dos cosas de distinta jerarquía: la orientación —dónde estoy, cómo
   funciona esto, por dónde salgo— y el SUJETO, que es la vista que se está
   mirando. La orientación se junta con este hueco; el sujeto se separa con la
   regla que lleva `.marco__vistas`. */
.marco__identidad {
  min-inline-size: 0;
  flex-wrap: nowrap;
  gap: var(--feedo-space-2xs);
}

/* El nombre de la vista es lo más importante de la barra, así que es lo más
   claro: blanco pleno. Estaba en `--feedo-paper`, el crema del producto, que
   sobre el azul da un blanco sucio — el dato principal terminaba siendo el de
   menos contraste de la barra, por debajo de la marca y del botón de cierre.
   El tracking apretado es el mismo gesto que ya usa `.topbar__brand`: a este
   cuerpo, un pelo de negativo lee firme en vez de suelto. */
.marco__vista {
  margin: 0;
  color: var(--feedo-cromo-tinta);
  font-weight: 700;
  font-size: var(--feedo-step-1);
  letter-spacing: -0.01em;
  /* El título viene del <title> del cliente y puede ser de cualquier largo.
     Envolvía —`overflow-wrap: anywhere`— y entonces un título largo partía la
     barra en dos renglones y le comía alto al prototipo. Ahora se queda en uno
     y recorta: la barra no cambia de alto por lo que escribió el cliente. */
  min-inline-size: 0;
  white-space: nowrap;
}

/* El caso sin variantes: el <p> recorta él mismo, porque no es una fila flex. */
.marco__identidad > .marco__vista {
  overflow: hidden;
  text-overflow: ellipsis;
}

/* El caso con variantes: recorta el nombre y no la fila, para que la píldora y
   la cuenta —que dicen en qué estado estás— nunca sean lo que se pierde. */
.marco__vista-nombre {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- los desplegables de la barra ----

   UNA pieza, dos usos: las medidas y el fondo. Los dos eligen entre opciones que
   se excluyen, y hasta acá se resolvían de dos maneras distintas pegadas una a
   la otra —un `<details>` plegado y un rótulo con dos botones sueltos—, así que
   aprender a usar uno no enseñaba a usar el otro. La clase se llama por lo que
   la pieza ES y no por el primero que la usó; cuál de los dos es cada uno lo
   dice `data-menu`, que es lo que necesita el recorrido guiado para señalarlo.

   Vive en la barra, que es oscura, así que su tratamiento es de fondo oscuro: la
   pastilla es un claro translúcido y los iconos van en papel. */
.marco__menu {
  position: relative;
}
/* Icono MÁS chevron. Sólo con la silueta no se leía como un control que se abre:
   parecía el estado actual y nada más, así que había que descubrirlo tocándolo.
   El chevron es el mismo glifo en los tres desplegables de la barra y en las dos
   asas del pie — la misma señal no puede tener dos dibujos. */
.marco__menu-boton {
  display: inline-flex;
  align-items: center;
  gap: var(--feedo-space-4xs);
  padding-inline: var(--feedo-space-2xs);
  /* El alto real es de 44px para el dedo, y los márgenes negativos devuelven al
     layout los 32px de siempre para no empujar el alto de la barra. Mismo
     truco que en `.marco__ancho`. */
  block-size: 2.75rem;
  margin-block: -0.375rem;
  border-radius: var(--feedo-radius-pill);
  background: color-mix(in srgb, var(--feedo-cromo-tinta) 16%, transparent);
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 82%, transparent);
  cursor: pointer;
  list-style: none;
}
.marco__menu-boton::-webkit-details-marker {
  display: none;
}
.marco__menu-boton:hover {
  background: color-mix(in srgb, var(--feedo-cromo-tinta) 26%, transparent);
  color: var(--feedo-cromo-tinta);
}
.marco__menu[open] .marco__menu-boton {
  background: var(--feedo-cromo-tinta);
  color: var(--feedo-cromo);
}
/* Comparte panel con las variantes: en la barra hay tres desplegables y tienen
   que abrirse igual, o cada uno enseña su propia mecánica. */
.marco__menu-lista {
  position: absolute;
  inset-block-start: calc(100% + var(--feedo-space-3xs));
  inset-inline-end: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-inline-size: 11rem;
  padding: var(--feedo-space-3xs);
  border-radius: var(--feedo-radius);
  background: var(--feedo-canvas);
  box-shadow: var(--feedo-shadow-lg);
}
/* El peso de un CONTROL, que es lo que es cada fila. La capa de revisión declaró
   su escalera de pesos hace poco —700 titula o sentencia, 600 se toca, 400 se
   lee— y la barra corría con una propia de cinco escalones: 800 para los
   titulares, 500 acá y 800 otra vez para el activo. Dos escaleras para el mismo
   producto es lo que hace que cada pieza nueva vuelva a decidir su peso. */
:is(.topbar, .marco__flotante) .marco__menu-opcion {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  padding: var(--feedo-space-3xs) var(--feedo-space-2xs);
  border-radius: var(--feedo-radius-s);
  color: var(--feedo-ink);
  font-size: var(--feedo-step--1);
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  text-decoration: none;
}
:is(.topbar, .marco__flotante) .marco__menu-opcion:hover {
  background: color-mix(in srgb, var(--feedo-accent) 10%, transparent);
}
/* Acá el nombre SÍ se imprime: plegado alcanzaba la silueta porque el activo se
   veía de un vistazo, pero en una lista tres siluetas sin nombre obligan a
   adivinar cuál es cuál. El puesto lo marca el acento, que en esta capa es
   siempre FIGURA y nunca fondo; el peso no lo distingue solo, porque el peso ya
   está gastado en decir que es un control. */
:is(.topbar, .marco__flotante) .marco__menu-opcion[aria-current="true"] {
  font-weight: 700;
  color: var(--feedo-accent);
}
.marco__menu-opcion .marco__icono {
  flex: none;
}


/* Dos cosas que se peleaban, y las dos importan.
   El área de toque tiene que ser de 44px: un control de 24px es un control para
   mouse nada más. Y esta barra tiene que medir exactamente lo mismo que la del
   listado, o entrar a una vista mueve el contenido para abajo.
   Se resuelven con márgenes negativos: el botón mide 44px de alto real y aporta
   32px al layout, así que el área de toque desborda hacia el padding de la barra
   —que está vacío— sin empujar su alto. */
.marco__ancho {
  display: inline-grid;
  place-items: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  margin-block: -0.375rem;
  border-radius: var(--feedo-radius-pill);
  text-decoration: none;
}

/* Gana a `.topbar a`, que pinta los enlaces de la barra en azul no-foto: acá el
   icono no es un enlace de texto, es un control de un grupo, y el azul lo haría
   parecer el activo. */
:is(.topbar, .marco__flotante) .marco__ancho {
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 82%, transparent);
}

/* ---- las variantes de una vista ----
   El NOMBRE de la vista es el desplegable, no un control aparte del otro lado
   de la barra. Las medidas cambian CÓMO se ve la pantalla; esto cambia QUÉ se
   ve, que es justamente lo que el nombre dice — tenerlos separados partía en
   dos puntas los dos datos que van juntos. */
/* La ÚNICA regla vertical de la barra, y separa lo que de verdad hay que
   separar: a la izquierda la orientación —marca, ayuda, camino de vuelta—, a la
   derecha el sujeto, que es la vista que se está mirando.
   Antes colgaba de la marca, donde partía en dos cosas que van juntas, y estaba
   dibujada con `--feedo-line`, un token de superficie clara que sobre el azul no
   separa: ensucia. Va en blanco translúcido, como el resto de las divisiones de
   `.topbar`.

   La llevan los dos casos, con variantes (el <details>) y sin (el <p>). Que la
   vista sin variantes también la tenga es lo que evita que su nombre quede
   flotando sin bloque cuando pierde la píldora y el caret: el sujeto sigue
   anunciándose como sujeto aunque tenga un solo estado. */
.marco__vistas,
.marco__identidad > .marco__vista {
  margin-inline-start: var(--feedo-space-2xs);
  border-inline-start: 1px solid color-mix(in srgb, var(--feedo-cromo-tinta) 28%, transparent);
}
.marco__identidad > .marco__vista {
  padding-inline-start: var(--feedo-space-xs);
}

.marco__vistas {
  position: relative;
  min-inline-size: 0;
}
/* El relleno vive en el disparador y no en el <details> para que el respaldo que
   aparece al pasar, el panel que se abre y el borde del bloque compartan la
   misma arista: tres bordes izquierdos distintos para una misma cosa se leen
   como tres cosas.
   Los márgenes negativos son el mismo truco que en `.marco__ancho`: el
   disparador crece para tener cuerpo tocable y respaldo visible, pero le
   devuelve al layout el alto de siempre para no empujar los 61px de la barra. */
.marco__vista--desplegable {
  display: inline-flex;
  align-items: center;
  min-inline-size: 0;
  gap: var(--feedo-space-2xs);
  padding: var(--feedo-space-3xs) var(--feedo-space-xs);
  margin-block: calc(var(--feedo-space-3xs) * -1);
  border-radius: var(--feedo-radius-s);
  cursor: pointer;
  transition: background-color 0.15s, color 0.15s;
  /* El triángulo de serie apunta para donde quiere y cambia por navegador. */
  list-style: none;
}
.marco__vista--desplegable::-webkit-details-marker {
  display: none;
}
/* ---- EL CHEVRON, UNO SOLO PARA TODA LA PANTALLA ----
   Los tres desplegables de la barra dibujaban su señal con un `::after`: una
   caja de 6px con dos bordes, rotada 45 grados. Servía, pero era la única figura
   del cromo que no salía de la familia de iconos —grilla 24, trazo 2,
   currentColor— y a ese tamaño, al lado de siluetas dibujadas a trazo 2, se leía
   como una tilde suelta y no como una flecha. El pie ya había hecho este mismo
   cambio en el asa de la tira y en el encabezado del dock; la barra se había
   quedado con el truco viejo, así que la misma señal tenía dos dibujos en la
   misma pantalla.

   Un tamaño para todos, en `rem` y no en `em`: heredando el cuerpo de cada
   disparador salía a 7.5px en variantes y a 6.3px en medidas. Y va un escalón
   por debajo de los iconos de contenido (1.15rem): informa que esto se abre, no
   dice qué es.

   Apunta a donde va a aparecer el panel —abajo, en la barra— y se da vuelta al
   abrirse. Es la misma regla que el pie: allá los paneles crecen hacia arriba y
   el chevron nace apuntando arriba.

   Selector compuesto y no `.marco__chevron` a secas: `.marco__icono` fija
   1.15rem y se declara más abajo en esta misma hoja, así que con la misma
   especificidad ganaría por orden. Es la lección que ya costó dos veces en este
   archivo —un bloque de override ARRIBA no override nada—, y acá se evita
   subiendo la especificidad en vez de mover la regla. */
.marco__icono.marco__chevron {
  inline-size: 1rem;
  block-size: 1rem;
  flex: none;
  opacity: 0.85;
  transition: transform 0.15s;
}
.marco__vistas[open] .marco__chevron,
.marco__menu[open] .marco__chevron {
  transform: rotate(180deg);
}
@media (prefers-reduced-motion: reduce) {
  .marco__chevron {
    transition: none;
  }
}
/* Los tres desplegables responden igual: el respaldo aparece al pasar y al abrir
   se vuelve la misma pieza blanca que el panel. Antes el de medidas invertía a
   blanco y el del nombre no hacía nada más que girar la flecha, así que cada uno
   enseñaba su propia mecánica en la misma barra. */
.marco__vista--desplegable:hover {
  background: color-mix(in srgb, var(--feedo-cromo-tinta) 16%, transparent);
}
.marco__vistas[open] .marco__vista--desplegable {
  background: var(--feedo-cromo-tinta);
  color: var(--feedo-cromo);
}
/* Cuántas hay, sin tener que abrirlo: el que abre por curiosidad y encuentra
   una sola no vuelve a abrir. */
/* Sin pastilla propia, y por dos razones que van en la misma dirección.
   La de contraste: sobre el azul de la barra, un fondo de blanco al 22% deja el
   texto blanco casi ilegible —axe lo marcó— y subirlo hasta que se leyera lo
   convertía en un segundo chip peleando con el de la variante, que es el dato
   importante. La de diseño: al lado del caret, «+4» ya se lee como «hay cuatro
   más» sin necesidad de una cápsula que lo encierre. */
/* Atenuado con `color-mix` sobre `currentColor` y no con `opacity`: al abrirse,
   el disparador se vuelve blanco y su tinta pasa a azul, y una opacidad heredada
   habría dejado el «+4» lavado justo cuando el fondo cambia. Siguiendo a
   currentColor se atenúa correctamente contra los dos fondos. */
.marco__variantes-cuenta {
  flex: none;
  color: color-mix(in srgb, currentColor 82%, transparent);
  font-size: var(--feedo-step--2);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: normal;
}
/* Se despliega desde el nombre, o sea hacia la izquierda de la barra. */
.marco__variantes-lista {
  position: absolute;
  inset-block-start: calc(100% + var(--feedo-space-3xs));
  inset-inline-start: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-inline-size: 14rem;
  max-inline-size: min(20rem, calc(100vw - var(--feedo-space-m) * 2));
  padding: var(--feedo-space-3xs);
  border-radius: var(--feedo-radius);
  background: var(--feedo-canvas);
  box-shadow: var(--feedo-shadow-lg);
}
/* El encabezado de cualquiera de los dos paneles de la barra. Nombra qué son
   las opciones: en variantes, porque con el nombre de la vista como disparador
   la palabra no aparecía en ninguna otra parte; en medidas, porque el rótulo
   dejó de estar a la vista al plegarse. */
/* 700, el peso de TITULAR de esta capa: el mismo del encabezado del dock, el del
   bloque de narración y el del asa de la tira. Iba en 800, que no era un escalón
   sino otro número. */
.marco__panel-titulo {
  margin: 0;
  padding: var(--feedo-space-3xs) var(--feedo-space-2xs) var(--feedo-space-4xs);
  color: var(--feedo-muted);
  font-size: var(--feedo-step--2);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* Mismo peso que las opciones del otro panel: son la misma clase de cosa, una
   fila que se toca. Iban en 500, el único peso intermedio que quedaba en toda la
   capa —el dock ya se lo había sacado a su globo por el mismo motivo. */
:is(.topbar, .marco__flotante) .marco__variantes-opcion {
  padding: var(--feedo-space-3xs) var(--feedo-space-2xs);
  border-radius: var(--feedo-radius-s);
  color: var(--feedo-ink);
  font-size: var(--feedo-step--1);
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  text-decoration: none;
}
:is(.topbar, .marco__flotante) .marco__variantes-opcion:hover {
  background: color-mix(in srgb, var(--feedo-accent) 10%, transparent);
}
/* La que se está mirando. El tilde carga el dato para quien no distingue el
   peso, y aria-current lo dice en el canal de los lectores. */
:is(.topbar, .marco__flotante) .marco__variantes-opcion[aria-current] {
  font-weight: 700;
  color: var(--feedo-accent);
}
:is(.topbar, .marco__flotante) .marco__variantes-opcion[aria-current]::before {
  content: '✓ ';
}


/* ---- cerrar la revisión, desde la barra ----
   Apagado casi todo el tiempo: está disponible desde el minuto uno, pero con el
   recorrido a medias no es lo que sigue y no tiene por qué llamar la atención. */
/* Tiene cuerpo desde el minuto uno: es la salida de todo el trabajo, y un
   contorno tenue lo dejaba pareciendo un control secundario al lado de las
   medidas. Lo que cambia al completar el recorrido es el PESO, no la
   existencia: pasa de sólido tenue a blanco pleno. */
/* Es la salida de TODO el trabajo, así que es lo único grande de la barra.
   Estaba en `--feedo-step--2`, el escalón más chico de la escala —el de las
   etiquetas al pie— y con el relleno justo: leído al lado del nombre de la
   vista parecía un control más del cromo y no el final del recorrido.
   Sube un escalón de cuerpo y gana relleno horizontal; el tracking baja, porque
   a más cuerpo la misma separación entre letras se vuelve dispersión.
   El alto real sube a 2.75rem —los mismos 44px que la ayuda y las medidas, así
   que los tres controles de la derecha tienen el mismo cuerpo tocable— y los
   márgenes negativos le devuelven al layout los 32px de siempre. Sin eso el
   botón empujaba la barra de 61px a 62px, que es exactamente lo que el marco no
   puede hacer: cada píxel de acá se lo saca al prototipo. */
:is(.topbar, .marco__flotante) .marco__terminar {
  display: inline-flex;
  align-items: center;
  block-size: 2.75rem;
  margin-block: -0.375rem;
  padding-inline: var(--feedo-space-m);
  border: 1px solid color-mix(in srgb, var(--feedo-cromo-tinta) 45%, transparent);
  border-radius: var(--feedo-radius-pill);
  background: color-mix(in srgb, var(--feedo-cromo-tinta) 14%, transparent);
  color: var(--feedo-cromo-tinta);
  font-size: var(--feedo-step--1);
  /* 600, el peso de los CONTROLES. Lo que lo hace la pieza principal de la barra
     es el cuerpo, la caja rellena y la caja alta — no un peso que ningún otro
     control del producto usa. */
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.15s, color 0.15s, border-color 0.15s;
}
:is(.topbar, .marco__flotante) .marco__terminar:hover {
  background: color-mix(in srgb, var(--feedo-cromo-tinta) 26%, transparent);
  border-color: var(--feedo-cromo-tinta);
}
/* Cuando ya no queda ninguna vista sin veredicto. Recién ahí es el próximo
   paso, y recién ahí se enciende: antes sería empujar a cerrar a medias. */
:is(.topbar, .marco__flotante) .marco__terminar[data-listo] {
  background: var(--feedo-cromo-tinta);
  border-color: var(--feedo-cromo-tinta);
  color: var(--feedo-cromo);
}
/* Ya avisó. Deja de ofrecerse sin desaparecer: que se vaya obligaría a
   preguntarse si pasó algo. */
:is(.topbar, .marco__flotante) .marco__terminar[data-hecho] {
  background: transparent;
  border-color: color-mix(in srgb, var(--feedo-cromo-tinta) 25%, transparent);
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 60%, transparent);
  cursor: default;
}
:is(.topbar, .marco__flotante) .marco__terminar[data-hecho]::before {
  content: '✓ ';
}

/* La variante que se está mirando, al lado del nombre: sin esto, cinco
   pantallas distintas se anunciaban todas como «Examen final».

   Cambia de material, y ése es el punto. Estaba en blanco al 16%, exactamente el
   mismo relleno que el botón de ayuda y que el disparador de medidas, así que un
   DATO se vestía igual que los CONTROLES: parecía que se podía tocar, y lo único
   tocable ahí es el disparador entero. En el celeste de marca ya no se parece a
   ningún botón de la barra, y además sigue siendo de Feedo — un color prestado
   habría metido una pieza ajena justo al lado del nombre.
   De paso deja de ser el escalón más chico de la escala: es el segundo dato en
   importancia de la barra y estaba tipografiado como una etiqueta al pie. */
.marco__variante {
  min-inline-size: 0;
  padding: var(--feedo-space-4xs) var(--feedo-space-2xs);
  border-radius: var(--feedo-radius-pill);
  background: var(--feedo-accent-soft);
  color: var(--feedo-accent);
  font-size: var(--feedo-step--1);
  font-weight: 600;
  letter-spacing: normal;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

:is(.topbar, .marco__flotante) .marco__ancho:hover {
  color: var(--feedo-cromo-tinta);
  text-decoration: none;
}

/* Los tres controles de la derecha, con el mismo hueco: ayuda, medidas y fondo.
   El fondo era un rótulo en caja alta más dos botones sueltos, y ahora es el
   mismo desplegable que las medidas — así que lo que había que separar con aire
   ya no existe, y quedan tres pastillas iguales en fila.

   Se fue con ello la única pastilla BLANCA PLENA de la barra, que era la que
   marcaba el fondo activo: el mayor contraste de toda la pantalla estaba puesto
   sobre la decisión menos importante que hay en ella, encima de la que sólo ve
   el estudio. */
.marco__controles {
  gap: var(--feedo-space-2xs);
  flex-wrap: nowrap;
}

/* Volver a ver el recorrido guiado.
   Es un <button> entre <a>: el botón nativo trae fondo, borde y tipografía
   propios que sus hermanos no tienen, y sin resetearlos quedaba una pastilla
   clara con el glifo casi blanco encima — es decir, invisible. */
.marco__ayuda {
  padding: 0;
  border: 0;
  font: inherit;
  cursor: pointer;
}
.topbar .marco__ayuda {
  background: color-mix(in srgb, var(--feedo-cromo-tinta) 16%, transparent);
  color: var(--feedo-cromo-tinta);
}
.topbar .marco__ayuda:hover {
  background: var(--feedo-cromo-tinta);
  color: var(--feedo-cromo);
}

/* El mismo control en el panel, sobre fondo claro. Comparte el glifo con la
   barra de la vista a propósito: configurar el fondo y cambiarlo tienen que
   verse como la misma decisión, no como dos cosas distintas. */
.fondo-switch {
  gap: 2px;
  padding: 2px;
  background: var(--feedo-paper-2);
  border-radius: var(--feedo-radius-pill);
  flex-wrap: nowrap;
}

.fondo-switch__op {
  display: inline-grid;
  place-items: center;
  inline-size: 2rem;
  block-size: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--feedo-radius-pill);
  background: transparent;
  color: var(--feedo-muted);
  cursor: pointer;
}

.fondo-switch__op:hover {
  color: var(--feedo-ink);
}

.fondo-switch__op[aria-current="true"] {
  background: var(--feedo-paper);
  color: var(--feedo-accent);
  box-shadow: var(--feedo-shadow-sm);
}

.fondo-switch__op .marco__icono {
  inline-size: 1rem;
  block-size: 1rem;
}

.marco__icono {
  inline-size: 1.15rem;
  block-size: 1.15rem;
}


/* Acá vivía el estado activo de `.marco__ancho`, que marcaba el fondo puesto con
   una pastilla de papel. No queda nadie que lo use: los tres controles de la
   derecha son ahora un botón y dos desplegables, y ninguno de los tres está
   «activo» — el puesto se dice adentro del panel, en palabras. La clase queda
   sólo como la caja cuadrada de un control de icono. */


/* ---- el lienzo ---- */

.marco__lienzo {
  /* El borde marca dónde termina la ventana simulada y empieza la sala. Sobre
     blanco tiene que ser más firme que una línea de sistema: si el prototipo
     también es claro, un borde tenue desaparece y el layout parece roto en vez
     de acotado. */
  display: block;
  inline-size: var(--marco-ancho, 100%);
  max-inline-size: 100%;
  margin-inline: auto;
  /* Alto: lo que `.marco` le da —que es lo que sobra de la ventana, ver
     body.feedo-marco arriba— con un techo para que un monitor enorme no
     estire el prototipo a una altura que ningún dispositivo real tiene. El
     dock y la tira flotan encima (son `position: fixed`), así que no hace
     falta descontarles un hueco: el mismo respiro que separa el lienzo de la
     barra, arriba, es el que lo separa del borde de la ventana, abajo. */
  block-size: min(100%, 900px);
  border: 1px solid color-mix(in srgb, var(--feedo-ink) 20%, transparent);
  border-radius: var(--feedo-radius);
  background: #fff;
  box-shadow: var(--feedo-shadow-lg);
}

/* A ancho completo la sombra sobra: no hay ventana que insinuar, es la sala
   entera. Y con más ancho conviene más alto, porque la página se lee como la
   página y no como una tira. */
/* A ancho completo el borde alcanza y la sombra sobra: no hay ventana que
   insinuar, es la sala entera. */
.marco[data-fluido] .marco__lienzo {
  box-shadow: var(--feedo-shadow-sm);
  border-radius: var(--feedo-radius-s);
  block-size: min(100%, 1200px);
}

@media (max-width: 45rem) {
  /* En un teléfono de verdad no hay lugar para simular otro teléfono más
     angosto: el lienzo toma lo que hay. */
  .marco__lienzo {
    inline-size: 100%;
  }
}

/* =========================================================================
   EL MODO AMPLIADO

   En una notebook el problema de esta pantalla es de ALTURA. Medido en un
   viewport de 700px: 61 la barra, 18 el padding de arriba, 27 el de abajo y 51
   la banda que el pie reserva — 157px de herramienta sobre 700 de ventana, para
   la única pantalla del producto cuyo trabajo es NO ser cromo.

   Ampliado el lienzo va a sangre y esos 157px vuelven al prototipo: un 30% más
   de alto. Lo que se pierde es la barra, y lo que se pierde de la barra vuelve
   por otro lado — el itinerario lo tiene la tira, que ya flota; el nombre de la
   vista está en el <title> de la pestaña; las medidas son ortogonales al modo
   (una decide ancho, éste alto) y «se tocan de a ratos». Queda flotando lo único
   que no puede faltar: la salida del modo y la salida del trabajo.

   Sólo de 45rem para arriba, y no es un detalle de responsive: abajo de eso la
   barra es el layout de dos renglones que esta hoja eligió a mano, y ahí viven
   el único camino de vuelta al menú y el cierre. Esconder eso en el aparato con
   menos lugar para mostrar una salida es donde el modo se vuelve una trampa —
   sin Escape en un teclado de teléfono. Como el modo vive en la URL, el servidor
   no lo puede filtrar por ancho: una URL ampliada abierta en un teléfono
   degrada al modo normal, que es lo correcto.
   ========================================================================= */

@media (min-width: 45rem) {
  .feedo-amplio {
    /* Sin barra, el dock y el asa caen sobre el prototipo con su alto más chico
       posible en vez del que comparten con la barra en modo normal. Es el
       trueque del modo, y está medido: las tres pastillas tapan un 4,8% del
       lienzo, todo en bordes y esquinas. En `w=390` y `w=834` tapan cero,
       porque caen sobre la sala. */
    --feedo-banda: 0px;
  }

  /* Sin barra, el aire de la página tampoco: el lienzo ES la página. */
  .feedo-amplio .page {
    padding-block: 0;
    max-inline-size: none;
  }

  /* Los dos selectores porque el de abajo tiene que GANARLE a
     `.marco[data-fluido] .marco__lienzo`, que son tres clases contra las dos de
     `.feedo-amplio .marco__lienzo`. Sin el segundo, el modo se dibujaba a
     sangre pero el lienzo se quedaba en el `86vh` de la regla del fluido: 778px
     de 905, y la ganancia era de 86px en lugar de 157. Medido, no deducido —
     desde afuera el modo parecía andar. */
  /* Con una medida elegida, el lienzo se dibuja 1:1 y el alto se acota a lo que
     hay, con aire arriba y abajo.

     Estuvo escalado al 80% para que se pareciera al porte de un teléfono en la
     mano, y con contenido de verdad el costo se vio: a esa escala no se puede
     juzgar un tamaño de texto, que es una de las cosas que un cliente mira en un
     prototipo. Un simulador que achica la tipografía miente sobre lo que se va a
     leer.

     El alto acotado no cuesta veracidad, y ahí está la asimetría que importa:
     si el lienzo mide MENOS que el teléfono, el cliente ve menos de lo que
     vería y el prototipo se corta abajo — igual que en un teléfono real. Si
     midiera MÁS, creería que ve algo que en su pantalla está abajo del pliegue,
     y eso sí es peligroso. Por eso el tope es el alto del dispositivo, y por
     debajo se puede recortar tranquilo.

     `84dvh` deja el mismo respiro que la sala tiene en modo normal, sin la
     reserva del pie: el modo sigue ganando alto. */
  .feedo-amplio .marco:not([data-fluido]) .marco__lienzo {
    block-size: min(var(--marco-alto, 100dvh), 84dvh);
  }
  /* Centrado en la sala: un aparato colgado del techo se lee como un error de
     layout y no como una ventana simulada. */
  .feedo-amplio .page:has(.marco:not([data-fluido])) {
    display: grid;
    place-content: center;
    block-size: 100dvh;
  }

  .feedo-amplio .marco__lienzo,
  .feedo-amplio .marco[data-fluido] .marco__lienzo {
    /* `dvh` y no `vh`: en un navegador móvil la barra de direcciones se
       esconde al scrollear y `vh` se queda con el alto viejo. Acá el lienzo ES
       la ventana, así que la diferencia se ve entera. */
    block-size: 100dvh;
  }

  /* A sangre y a ancho fluido no hay sala, así que el radio recortaría las
     esquinas del diseño del cliente y la sombra caería contra el borde de la
     ventana. Con `w=390` o `w=834` la sala sigue existiendo y los tres se
     conservan: ahí siguen diciendo dónde termina la ventana simulada. */
  .feedo-amplio .marco[data-fluido] .marco__lienzo {
    border-radius: 0;
    border: 0;
    box-shadow: none;
  }

  /* ---- la pastilla flotante ----
     La primera superficie de Feedo que se apoya directo sobre el diseño de un
     cliente. Por eso NO copia a `.marco__terminar`, cuyo fondo es tinta al 14%
     calculada para caer sobre el cromo opaco de la barra: sobre un prototipo
     desconocido un 14% no garantiza nada. Toma el material del dock, que ya
     resolvió exactamente esto — par cromo/tinta, opaco, con borde y sombra— y
     así el contraste se mide contra su propia superficie y nunca contra el
     prototipo. Esta barra ya falló tres veces por medir contra el fondo
     equivocado. */
  /* Plegada es sólo la marca: un disco del tamaño del asa de la tira, con el
     mismo material del dock. Desplegada crece hacia la izquierda —`inset-inline-end`
     fijo— para que el control de abrir no se mueva de lugar al abrirse: si se
     corriera, el segundo clic caería en otra parte. */
  .marco__flotante > summary {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--feedo-space-3xs);
    padding-inline: var(--feedo-space-2xs);
    min-inline-size: 44px;
    block-size: 44px;
    border-radius: 999px;
    cursor: pointer;
    list-style: none;
    color: var(--feedo-cromo-tinta);
  }
  .marco__flotante > summary::-webkit-details-marker {
    display: none;
  }
  .marco__flotante > summary svg {
    inline-size: 22px;
    block-size: 22px;
  }
  /* El chevron de la familia, girado hacia donde aparece el panel.
     La regla del producto es que la señal apunte a DONDE va a aparecer y se dé
     vuelta al abrirse — la misma que siguen los cajones del panel y el pie.
     Esta barra crece hacia la izquierda desde un borde fijo, así que plegada
     apunta a la izquierda y abierta a la derecha, que es por donde se va a
     cerrar. Y es la única razón por la que la marca se lee como un control:
     sola, un logo es identidad. */
  .marco__chevron {
    inline-size: 16px;
    block-size: 16px;
    rotate: 90deg;
    transition: rotate 160ms ease;
  }
  .marco__flotante[open] > summary .marco__chevron {
    rotate: -90deg;
  }
  @media (prefers-reduced-motion: reduce) {
    .marco__chevron {
      transition: none;
    }
  }
  /* Abierta, el resumen sigue ahí y es lo que la cierra. Se atenúa para que la
     jerarquía la marquen los controles y no él. */
  .marco__flotante[open] > summary {
    opacity: 0.62;
  }
  .marco__flotante[open] > summary:hover {
    opacity: 1;
  }
  .marco__flotante-controles {
    display: flex;
    align-items: center;
    gap: var(--feedo-space-2xs);
  }
  .marco__flotante[open] {
    display: flex;
    align-items: center;
    gap: var(--feedo-space-2xs);
  }
  /* Plegada no lleva el padding de la barra: es un disco, no una barra con un
     solo elemento adentro. El filo no se apaga: es el mismo doble anillo de
     abajo, y plegada lo necesita tanto como abierta — las dos caen sobre el
     prototipo de un cliente, nunca sobre una sala pareja. */
  .marco__flotante:not([open]) {
    padding: 0;
  }

  .marco__flotante {
    position: fixed;
    inset-block-start: var(--feedo-space-s);
    inset-inline-end: var(--feedo-space-s);
    z-index: 30;
    display: flex;
    align-items: center;
    gap: var(--feedo-space-2xs);
    padding: var(--feedo-space-2xs);
    border-radius: var(--feedo-radius-pill);
    background: var(--feedo-cromo);
    /* El filo de un solo color contra el prototipo no se despega de los dos
       lados posibles de la escala a la vez —medido en `feedback.css`, ver EL
       FILO CONTRA EL PROTOTIPO—, así que acá es el mismo doble anillo: uno de
       papel pegado al borde, uno de tinta un pixel más afuera. Contra
       cualquier fondo, al menos uno de los dos contrasta. */
    box-shadow:
      0 0 0 1px color-mix(in srgb, var(--feedo-paper) 90%, transparent),
      0 0 0 2px color-mix(in srgb, var(--feedo-ink) 35%, transparent),
      var(--feedo-shadow-md);
  }

  /* Arriba a la derecha porque es donde vive `.marco__controles` hoy: la salida
     del trabajo sigue estando donde estaba, y la memoria muscular no se rompe.
     Es además la esquina más lejana al dock, así que abrir el panel de
     comentarios no la tapa nunca. */
  .marco__flotante .marco__terminar {
    margin: 0;
  }
}

/* El control que entra y sale del modo. Va en los dos, así que vive afuera del
   media query: el que se esconde abajo de 45rem es el disparador, no la clase.
   Toma `marco__ancho` —los controles tenues de la barra— porque el acento de
   esta pantalla ya está tomado por la marca y el aro de foco, y ampliar no
   compite con «Terminé de revisar». */
.marco__ampliar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* El color se declara acá y no se hereda.
     `.marco__ancho` lo define dentro del scope de `.topbar`, y en el modo
     ampliado no hay `.topbar`: el control quedaba con el azul de link heredado,
     que sobre tinta da 2.50:1 — abajo del 3:1 que pide WCAG 1.4.11 para un
     gráfico. Medido, no supuesto. Y está advertido tres párrafos más arriba en
     esta misma hoja: «el azul contra tinta da 2.3:1».
     El 82% es el mismo tratamiento tenue que llevan los controles de la barra:
     ampliar no compite con «Terminé de revisar», que es la única pieza grande. */
  color: color-mix(in srgb, var(--feedo-cromo-tinta) 82%, transparent);
}
.marco__ampliar:hover {
  color: var(--feedo-cromo-tinta);
}

@media (max-width: 45rem) {
  /* En un teléfono el modo no entra (ver arriba), así que tampoco se ofrece. */
  .topbar .marco__ampliar {
    display: none;
  }
}

/* =========================================================================
   LA BARRA DE LA VISTA EN VENTANAS ANGOSTAS

   Una sola fila deja de entrar cerca de los 870px —medido, no estimado: entre
   880 y 860 el `.repel` parte— y hasta acá el `flex-wrap`
   se las arreglaba solo: la barra pasaba de 61px a 108px y después a 143px,
   repartidos en renglones accidentales que ponían el nombre de la vista abajo
   de todo y la marca sola arriba. En un teléfono eso es un sexto de la pantalla
   de cromo, en la única pantalla del producto cuyo trabajo es NO ser cromo.

   Acá el reparto se decide en vez de sufrirse: dos renglones, uno para las
   herramientas y otro para el sujeto. El nombre baja a un renglón propio y de
   ancho completo, que es la posición más fuerte que tiene — así el que menos
   lugar tiene es el que más lugar recibe.

   `display: contents` disuelve el grupo de identidad para que sus hijos suban a
   la grilla del wrapper: sin eso el nombre y las herramientas viven en cajas
   distintas y no hay forma de mandarlo a su propio renglón. Es un <div> sin
   semántica, así que disolverlo no se lleva nada puesto.
   ========================================================================= */
@media (max-width: 55rem) {
  .topbar .wrapper:has(.marco__identidad) {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      'marca volver controles'
      'vista vista  vista';
    align-items: center;
    gap: var(--feedo-space-2xs);
  }
  .marco__identidad {
    display: contents;
  }
  .marco__marca {
    grid-area: marca;
  }
  .marco__volver {
    grid-area: volver;
    justify-self: start;
  }
  .marco__controles {
    grid-area: controles;
    justify-self: end;
  }
  /* En su propio renglón el nombre no tiene vecino a la izquierda, así que la
     regla que lo separaba de la orientación ya no separa nada: sería una raya
     colgando del margen. El margen negativo compensa el relleno del disparador
     para que el nombre arranque a plomo con la marca de arriba. */
  .marco__vistas,
  .marco__identidad > .marco__vista {
    grid-area: vista;
    border-inline-start: 0;
    padding-inline-start: 0;
  }
  .marco__vistas {
    margin-inline-start: calc(var(--feedo-space-xs) * -1);
  }
  .marco__identidad > .marco__vista {
    margin-inline-start: 0;
  }
}

/* Más angosto todavía: el rótulo del camino de vuelta se pliega a la flecha
   sola. Se esconde de la vista y no del documento, así que el enlace conserva
   su nombre y quien no lo ve lo sigue escuchando. Y el botón de cierre suelta
   el tracking y el relleno, que a este ancho son puro impuesto de ancho: el
   nombre del control no cambia, cambia cuánto aire se lleva. */
@media (max-width: 34rem) {
  .marco__volver-texto {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .topbar .marco__terminar {
    padding-inline: var(--feedo-space-2xs);
    letter-spacing: 0.02em;
  }

  /* El nombre de la vista cede antes que nadie.
     A este ancho, «Examen final» con la píldora «Calificado y desaprobado»
     empujaba la barra más allá de la ventana y aparecía scroll horizontal en
     toda la página del marco. Ceder es recortarse, no desaparecer: el nombre
     completo sigue en el documento, y la variante se lee entera abriendo el
     desplegable, que es de donde salió. */
  .marco__vistas {
    min-inline-size: 0;
  }
  .marco__vista {
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .marco__variante {
    max-inline-size: 8ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Y el panel se ancla a la izquierda: colgado del borde derecho de un
     disparador que ya está contra el margen, se salía de la ventana. */
  .marco__menu-lista {
    inset-inline: auto 0;
    max-inline-size: calc(100vw - var(--feedo-space-s) * 2);
  }
}

/* En un teléfono, el conmutador de medidas miente.
   Sirve para ver el prototipo MÁS ANGOSTO que la ventana: desde un escritorio,
   apretarlo a 390 muestra algo que no se veía. Pero en una ventana de 390 el
   ancho fluido YA es un teléfono, «Teléfono» da exactamente lo mismo y
   «Tablet» —834px— no entra de ninguna manera. Quedaban tres opciones para un
   solo resultado, ocupando lugar y prometiendo algo que no puede cumplir.

   Se va del documento y no sólo de la vista: un control que no hace nada no es
   más útil por estar en el árbol de accesibilidad, es ruido para quien navega
   escuchando. El parámetro `?w=` del servidor sigue funcionando, así que un
   enlace compartido desde el escritorio no se rompe.

   Se apunta al de MEDIDAS por su `data-menu` y no a la clase del bloque: desde
   que el fondo usa la misma pieza, esconder `.marco__menu` escondería también el
   conmutador de fondo, que en un teléfono sigue significando lo mismo que en un
   escritorio. */
@media (max-width: 28rem) {
  .marco__menu[data-menu="medidas"] {
    display: none;
  }
}

/* Un sync que falló, dicho en la tabla. No es un adorno: es la única señal de
   que el prototipo que ve el cliente quedó viejo, y de por qué. */
.sync-error {
  display: block;
  margin-block-start: var(--feedo-space-3xs);
  color: var(--feedo-danger);
  font-size: var(--feedo-step--2);
  line-height: 1.35;
}

/* Su hermana en tono neutro: desde dónde se sirve el prototipo, o que todavía
   falta decirlo. Mismo renglón propio y mismo tamaño que el error —es el mismo
   tipo de apostilla— pero en gris: lo que falta hacer no es lo mismo que lo que
   se rompió, y pintarlo de rojo hace leer un paso pendiente como una falla. */
.sync-root {
  display: block;
  margin-block-start: var(--feedo-space-3xs);
  color: var(--feedo-muted);
  font-size: var(--feedo-step--2);
  line-height: 1.35;
}
.sync-root code {
  font-size: inherit;
}

/* El «cambiar» va adentro de una frase gris, y ahí el color solo no alcanza:
   quien no distingue el acento del gris no ve que hay algo para tocar. Se
   subraya, como cualquier enlace en prosa (WCAG 1.4.1). */
.sync-root a {
  text-decoration: underline;
}

/* La ayuda del campo del token: son varias frases y una lista, no la línea
   suelta que era antes. */
.field-hint p {
  margin-block: 0 var(--feedo-space-3xs);
}
.field-hint ul {
  margin-block: 0 var(--feedo-space-3xs);
  padding-inline-start: var(--feedo-space-s);
}
.field-hint li {
  margin-block-end: var(--feedo-space-4xs);
}
.token-link {
  font-weight: 600;
}

/* Quién terminó de revisar y está esperando. Es lo único del panel que le pide
   algo al estudio, así que va arriba y con el acento de la marca — no en rojo:
   no es un error, es un turno. */
.esperando-trabajo {
  border-color: var(--feedo-accent);
  box-shadow: 0 0 0 3px var(--feedo-accent-soft);
}
/* La conexión con GitHub sin terminar, contada en el panel.
   Mismo realce que su vecino de arriba —borde y halo sobre un panel normal—,
   que es el patrón que esta pantalla ya usa para «acá hay algo que hacer». En
   ámbar y no en acento porque dicen cosas distintas y pueden aparecer juntos:
   aquél es «un cliente te espera», éste es «esto quedó a medio configurar».

   Primero se probó con `.notice` y quedó mal: un párrafo entero con el peso de
   un aviso de una línea grita, y deja la acción colgada al final del texto.

   Va acá abajo y no junto a los notices por una razón que costó una vuelta:
   `.panel` también fija `border` y `box-shadow`, con la misma especificidad,
   así que arriba de su definición esto no pintaba nada. */
.puesta-en-marcha {
  /* El ritmo de `.flow` es el de una pantalla, y acá son tres líneas: con
     `space-m` entre cada una el aviso terminaba tan alto como la tabla que
     tiene debajo, y lo que reclama atención pasaba a ser el tamaño. */
  --flow-space: var(--feedo-space-xs);
  /* El borde solo no alcanzaba: con el fondo de papel de todos los paneles,
     esto se leía como una sección más de la pantalla. La superficie teñida es
     lo que lo saca de la fila. */
  background: color-mix(in srgb, var(--feedo-comment) 8%, var(--feedo-paper));
  border-color: var(--feedo-comment);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--feedo-comment) 18%, transparent);
}
/* A la altura de «Proyectos» y «Clientes», no por encima. Un `h2` suelto sale
   en `step-2` y los títulos de panel viven en `step-0`: el aviso quedaba con más
   peso tipográfico que las secciones de las que avisa. Lo que lo distingue es el
   color y el glifo, no el tamaño.
   `inline-flex` para el glifo: apoyado en la línea base flota sobre las letras. */
.puesta-en-marcha h2 {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  color: var(--feedo-comment);
  font-size: var(--feedo-step-0);
  letter-spacing: 0.02em;
}
/* `.glifo` sólo trae `flex: none`: la medida la pone cada contexto, como en
   `.backlink`. Sin esto el SVG no tiene de dónde sacar su tamaño y se estira a
   todo el ancho del panel. */
.puesta-en-marcha h2 .glifo {
  inline-size: 1.15em;
  block-size: 1.15em;
}
.esperando-trabajo__lista {
  margin: 0;
  padding-inline-start: var(--feedo-space-s);
}
.esperando-trabajo__lista li + li {
  margin-block-start: var(--feedo-space-3xs);
}
.badge[data-state="returned"] {
  background: var(--feedo-accent-soft);
  color: var(--feedo-accent);
}
/* Listo para publicar: es una acción disponible, no una espera. Va con el verde
   de aprobado, que en este sistema ya significa "esto está resuelto". */
.badge[data-state="publish"] {
  background: var(--feedo-approve);
  color: var(--feedo-approve-ink);
}

/* Acá vivía `.novedades`: un aviso de «Respondimos tus comentarios» apoyado
   arriba de las tarjetas, con borde de acento y su propia lista de proyectos.
   Se va porque la tarjeta pasó a decirlo mejor —nombra las vistas, las muestra,
   las fecha y lo pone al lado del avance de ESE proyecto— y el aviso quedaba
   contando lo mismo con menos, primero y más fuerte.
   El aviso equivalente del panel se queda: allá resume una TABLA de cinco
   columnas donde la marca de una fila se pasa por alto. Acá no hay tabla que
   resumir: hay una tarjeta por proyecto, y es la que se toca. */

/* El nombre del proyecto y de quién es el turno, en una línea. La marca estaba
   en la columna de revisión, apilada bajo la barra: ahí se leía como parte de
   la medición, cuando dice algo distinto —la barra mide cuánto se revisó, la
   marca dice quién tiene la pelota. */
.view-cell__head {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  flex-wrap: wrap;
}
/* Acá la marca acompaña al nombre, no encabeza una celda: con el alto de
   siempre pesaba más que el proyecto que califica. El padding era lo de menos
   —a 11px de tipografía, el interlineado heredado le daba 33px de alto contra
   los 29px del nombre— así que la caja se ajusta a su propio texto. */
.view-cell__head .badge {
  padding: 2px 8px;
  line-height: 1.4;
}

/* El historial del proyecto, en dos niveles.
   Un carril vertical atraviesa la lista entera y cada hito es un nodo posado
   encima: eso es lo que hace que se lea como una línea de tiempo y no como una
   tabla de log. Los renglones ya no llevan filete —el carril es la estructura, y
   dos estructuras superpuestas es una de más—, así que lo que separa un hito del
   siguiente es el aire, y el aire se reparte según cuánto pesa cada uno.

   El detalle vive adentro del hito, plegado, y se abre sólo lo que hace falta.
   Sin JavaScript: son <details> nativos.

   Tres pesos, y el nodo es el que los canta antes que el texto:
     · fuerte  (publicado, terminó) → disco lleno y de color: mueven el turno
     · medio   (pidió, contestó, alta) → disco con aro
     · de fondo (sync) → disco chico y apagado, ruido que uno saltea */
.timeline {
  /* Ancho de la columna del carril. Es constante para TODOS los hitos aunque el
     disco cambie de tamaño: si variara, el carril zigzaguearía. */
  --nodo: 2rem;
  --disco: 2rem;
  --icono: 1.1rem;
  --fila-pad: var(--feedo-space-2xs-xs);
  /* Alto de la primera línea del hecho. Con esto se centra el disco contra esa
     línea y no contra el bloque entero, que es lo que descuadra el nodo cuando
     el hito tiene dos renglones. */
  --linea-1: 1.5em;

  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--feedo-step-0);
}
.timeline__item {
  position: relative;
}
/* El carril. Va sobre el <li> y no sobre la fila, así que también corre a lo
   largo del detalle abierto: esa continuidad es lo que dice que lo de adentro
   pertenece al hito y no es un hito más. */
.timeline__item::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(var(--nodo) / 2 - 1px);
  inline-size: 2px;
  background: var(--feedo-line);
}
/* El carril empieza y termina en el centro de su nodo: arriba no hay nada más
   nuevo, abajo no hay nada más viejo, y un tramo suelto prometería lo contrario. */
.timeline__item:first-child::before {
  inset-block-start: calc(var(--fila-pad) + var(--linea-1) / 2);
}
.timeline__item:last-child::before {
  inset-block-end: calc(100% - var(--fila-pad) - var(--linea-1) / 2);
}
.timeline__fila {
  display: grid;
  /* La cuarta columna se reserva aunque el hito no se abra: si sólo existiera
     donde hay flecha, las fechas de los hitos sin detalle quedarían corridas
     respecto de las demás y la columna de la derecha dejaría de ser una columna. */
  grid-template-columns: var(--nodo) minmax(0, 1fr) auto 1rem;
  gap: var(--feedo-space-3xs) var(--feedo-space-xs);
  align-items: start;
  padding-block: var(--fila-pad);
  /* El realce del hover se pasa un poco del texto para que el blanco de la fila
     se sienta clickeable entero, sin correr el carril de lugar. */
  padding-inline: var(--feedo-space-2xs);
  margin-inline: calc(-1 * var(--feedo-space-2xs));
  border-radius: var(--feedo-radius-s);
}
/* El hito con detalle es un resumen desplegable: se comporta como algo que se
   puede abrir, y el que no tiene nada adentro no finge que sí. */
summary.timeline__fila {
  cursor: pointer;
  list-style: none;
}
summary.timeline__fila::-webkit-details-marker {
  display: none;
}
summary.timeline__fila:hover {
  background: color-mix(in srgb, var(--feedo-accent) 7%, transparent);
}
summary.timeline__fila:focus-visible {
  outline: 2px solid var(--feedo-accent);
  outline-offset: 2px;
}

/* ---- el nodo sobre el carril ---- */
.timeline__nodo {
  position: relative;
  z-index: 1;
  justify-self: center;
  display: grid;
  place-items: center;
  inline-size: var(--disco);
  block-size: var(--disco);
  /* Centrado contra la primera línea del hecho, no contra el tope de la fila. */
  margin-block-start: calc((var(--linea-1) - var(--disco)) / 2);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-pill);
  /* Opaco a propósito: el carril le pasa por detrás y tiene que cortarse ahí. */
  background: var(--feedo-canvas);
  color: var(--feedo-ink);
}
.timeline__icono {
  inline-size: var(--icono);
  block-size: var(--icono);
}
/* Los dos hitos que mueven el turno: disco lleno, y el texto en negrita. Son los
   que uno busca cuando reconstruye una vuelta, y tienen que encontrarse sin leer. */
.timeline__item[data-tipo="publicacion"],
.timeline__item[data-tipo="devuelto"] {
  --disco: 2.2rem;
  --icono: 1.2rem;
}
.timeline__item[data-tipo="publicacion"] .timeline__nodo {
  background: var(--feedo-approve);
  border-color: var(--feedo-approve);
  color: var(--feedo-approve-ink);
}
.timeline__item[data-tipo="devuelto"] .timeline__nodo {
  background: var(--feedo-accent);
  border-color: var(--feedo-accent);
  color: var(--feedo-accent-ink);
}
.timeline__item[data-tipo="publicacion"] .timeline__texto,
.timeline__item[data-tipo="devuelto"] .timeline__texto {
  font-weight: 700;
}
/* Lo que dijo el cliente lleva su color de siempre, el mismo con el que aparece
   marcado en la tabla de vistas y del otro lado del prototipo. */
.timeline__item[data-tipo="comentarios"] .timeline__nodo {
  background: color-mix(in srgb, var(--feedo-comment) 12%, var(--feedo-canvas));
  border-color: color-mix(in srgb, var(--feedo-comment) 40%, transparent);
  color: var(--feedo-comment);
}
.timeline__item[data-tipo="comentarios"] .timeline__texto,
.timeline__item[data-tipo="respuestas"] .timeline__texto {
  font-weight: 600;
}
/* El aro punteado dice lo que dice el texto: está escrito y todavía no salió.
   Es el único hito que puede quedar a medio camino, y se ve desde lejos. */
.timeline__item[data-tipo="respuestas"] .timeline__nodo {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--feedo-accent) 50%, transparent);
  color: var(--feedo-accent);
}
.timeline__item[data-tipo="alta"] .timeline__nodo {
  background: var(--feedo-paper-2);
  border-color: transparent;
  color: var(--feedo-muted);
}
/* ---- el hito que todavía no pasó ----
   Publicar era un panel suelto arriba del historial. Es, en realidad, el próximo
   renglón de esta lista: apretar el botón escribe el hito «Cambios publicados»
   justo acá. Por eso comparte nodo, carril, icono y tamaño con el hito en que se
   va a convertir — cuando pase, nada salta de lugar.

   Lo que lo distingue son dos cosas y ninguna es decorativa:
     · el aro punteado, que en esta línea de tiempo ya significa «escrito y
       todavía sin salir» (lo usa «el agente respondió»);
     · el papel blanco, que es la superficie de lo vivo. Todo lo demás en esta
       columna es papel crema, o sea: pasado. */
.timeline__item[data-tipo="pendiente"] {
  --disco: 2.2rem;
  --icono: 1.2rem;
  /* El alto del renglón del título, con el que se centra el nodo. Es el mismo
     1.5em que usa el resto de la lista, pero acá el nodo además tiene que bajar
     lo que la tarjeta padea por arriba. */
  --tarjeta-pad: var(--feedo-space-s);
}
/* Dos columnas y no cuatro: la tarjeta no tiene fecha —no pasó— ni flecha —no se
   abre—, y reservar esas columnas le comía el ancho a los chips. */
.timeline__item[data-tipo="pendiente"] .timeline__fila {
  grid-template-columns: var(--nodo) minmax(0, 1fr);
}
.timeline__item[data-tipo="pendiente"] .timeline__nodo {
  border-style: dashed;
  border-color: var(--feedo-line);
  color: var(--feedo-muted);
  margin-block-start: calc(var(--tarjeta-pad) + (var(--linea-1) - var(--disco)) / 2);
}
/* Con algo para publicar el nodo toma el verde con el que ya se dibuja
   «Cambios publicados»: es el color del hito que está a un clic de existir. */
.timeline__item[data-tipo="pendiente"][data-listo] .timeline__nodo {
  border-color: var(--feedo-approve);
  color: var(--feedo-approve);
}
/* El carril arranca en el centro de ESTE nodo, que quedó más abajo que el de una
   fila normal por el padding de la tarjeta. Sin esto sobra un tramo de carril
   arriba, que prometería un hito más nuevo que no existe. */
.timeline__item[data-tipo="pendiente"]:first-child::before {
  inset-block-start: calc(var(--fila-pad) + var(--tarjeta-pad) + var(--linea-1) / 2);
}
/* Corta donde cortan sus párrafos. Los otros hitos llegan hasta el borde porque
   apoyan la fecha ahí; éste no tiene fecha, y estirado hasta el final dejaba
   media tarjeta vacía a la derecha del botón. */
.proximo {
  --flow-space: var(--feedo-space-2xs);
  max-inline-size: calc(var(--feedo-measure) + 2 * var(--tarjeta-pad));
  padding: var(--tarjeta-pad);
  background: var(--feedo-canvas);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-s);
  min-inline-size: 0;
}
.timeline__item[data-tipo="pendiente"][data-listo] .proximo {
  border-color: color-mix(in srgb, var(--feedo-approve) 35%, transparent);
}
/* Mismo alto de renglón que el resto de la lista, para que el nodo se centre
   contra el título con la misma cuenta que usa cualquier otro hito. */
.proximo__titulo {
  line-height: 1.5;
}
/* El aire antes del botón es mayor que el que separa los párrafos: es lo que
   marca dónde termina la previa y empieza la decisión. */
.proximo form {
  margin-block-start: var(--feedo-space-s);
}

/* El sync es el ruido de fondo: pasa muchas veces y casi nunca es lo que uno vino
   a buscar. Disco chico y todo apagado; conserva el cuerpo de letra para no
   romper el ritmo de la columna, pero deja de competir. */
.timeline__item[data-tipo="sync"] {
  --disco: 1.5rem;
  --icono: 0.95rem;
}
.timeline__item[data-tipo="sync"] .timeline__nodo {
  background: var(--feedo-paper-2);
  border-color: transparent;
  color: var(--feedo-muted);
}
.timeline__item[data-tipo="sync"] .timeline__texto {
  color: var(--feedo-muted);
}
/* El detalle del sync es un commit: se lee carácter por carácter, así que va en
   mono como cualquier otro identificador del proyecto. */
.timeline__item[data-tipo="sync"] .timeline__detalle {
  font-family: var(--feedo-mono);
}

/* ---- el hecho ---- */
.timeline__cuerpo {
  display: flex;
  flex-direction: column;
  gap: var(--feedo-space-4xs);
  min-inline-size: 0;
}
/* `break-word` y no `anywhere`: `anywhere` parte palabras aunque quepan, y en una
   columna angosta dejaba «encabezado» cortado en «encabe / zado». Así sólo se
   parte lo que de verdad no entra, como el link larguísimo de un comentario. */
.timeline__texto {
  line-height: 1.5;
  overflow-wrap: break-word;
}
.timeline__detalle {
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}
.timeline__cuando {
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.timeline__fila > .timeline__cuando {
  grid-column: 3;
  /* La fecha se apoya en la misma línea que el hecho aunque sea más chica. */
  margin-block-start: calc((var(--linea-1) - 1.5em) / 2);
}
.timeline__flecha {
  grid-column: 4;
  inline-size: 1rem;
  block-size: 1rem;
  color: var(--feedo-muted);
  margin-block-start: calc((var(--linea-1) - 1rem) / 2);
  transition: transform 0.15s ease;
}
/* 180 y no 90: el chevron nace apuntando abajo, que es donde aparece el detalle,
   y se da vuelta al abrirse. Apuntaba a la derecha y giraba un cuarto, o sea que
   cerrado señalaba un lugar donde no hay nada. Misma regla que los cajones del
   panel y que los desplegables de la barra de la vista. */
details[open] > .timeline__fila .timeline__flecha {
  transform: rotate(180deg);
}

/* ---- el detalle, adentro del hito ---- */
/* Sangrado hasta donde arranca el hecho de arriba y sobre su propio papel: la
   sangría dice de quién es y el cambio de superficie dice que se está mirando
   adentro de algo, no un renglón más de la lista. */
.timeline__hijos {
  list-style: none;
  margin: var(--feedo-space-4xs) 0 var(--feedo-space-xs);
  margin-inline-start: calc(var(--nodo) + var(--feedo-space-xs));
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  background: var(--feedo-paper-2);
  border-radius: var(--feedo-radius-s);
  display: grid;
  gap: var(--feedo-space-2xs);
}
.timeline__hijo {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0 var(--feedo-space-xs);
}
.timeline__hijo + .timeline__hijo {
  padding-block-start: var(--feedo-space-2xs);
  border-block-start: 1px solid var(--feedo-line);
}

/* Corregir y eliminar una respuesta que todavía no salió.
   Fila propia y no una tercera columna: el renglón ya reparte nombre de vista y
   fecha contra los dos bordes, y meter dos controles en el medio le saca el
   ancho justo a lo que hay que leer, que es lo que se dijo. */
/* Los dos controles arrancan en el mismo renglón y ahí se quedan. Con el editor
   abierto el bloque de «Corregir» crece hacia abajo —textarea y botón— y
   «Eliminar» tiene que seguir donde estaba: un control que se va al medio de un
   campo cuando abrís otro se lee como si perteneciera a ese campo. De ahí
   `align-items: start` y las dos celdas fijas, en vez de un cluster que centra.

   Abierto, la primera columna toma el ancho libre para que el textarea sea un
   textarea y no una ranura; cerrado, las dos miden lo que dicen y quedan juntas
   a la izquierda. */
.nota__acciones {
  grid-column: 1 / -1;
  margin-block-start: var(--feedo-space-2xs);
  display: grid;
  grid-template-columns: max-content max-content;
  align-items: start;
  gap: var(--feedo-space-xs);
}
.nota__acciones:has(.nota__corregir[open]) {
  grid-template-columns: minmax(0, 1fr) max-content;
}
/* Se lee como enlace porque es una acción secundaria —el renglón está para
   leerlo, no para editarlo— pero es un `<summary>`: abre y cierra sin script.
   El padding vertical no es estético: sin él el área de click medía 20px y la
   regla del producto es 24 (WCAG 2.5.8). */
.nota__corregir > summary {
  display: inline-block;
  padding-block: var(--feedo-space-3xs);
  color: var(--feedo-muted);
  font-size: var(--feedo-step--1);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
  list-style: none;
}
.nota__corregir > summary::-webkit-details-marker {
  display: none;
}
.nota__corregir > summary:hover {
  color: var(--feedo-ink);
}
.nota__corregir > form {
  margin-block-start: var(--feedo-space-2xs);
}
/* El único textarea del panel. Toma la caja de los demás campos: si escribiera
   sus propios valores, el día que cambie el radio de un input este quedaría
   solo con el viejo. */
.campo {
  inline-size: 100%;
  max-inline-size: 100%;
  min-inline-size: 0;
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-s);
  background: var(--feedo-paper);
  color: var(--feedo-ink);
  font: inherit;
  font-size: var(--feedo-step--1);
  resize: vertical;
}
/* La vista es un nombre de archivo y no se parte: cortar `mis-cursos.html` a la
   mitad obliga a reconstruirlo. Mismo criterio que los chips de publicación. */
.timeline__vista {
  grid-column: 1;
  font-family: var(--feedo-mono);
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Lo dicho es lo único que se lee entero acá adentro, así que conserva el cuerpo
   del hito; lo que baja de jerarquía es todo lo que lo rodea. Se lleva el ancho
   completo —la vista y la fecha se reparten el renglón de arriba— porque
   compartir columna con la fecha lo dejaba en una palabra por línea. */
.timeline__dicho {
  grid-column: 1 / -1;
  line-height: 1.5;
  overflow-wrap: break-word;
}
/* «Además vino una imagen». Va debajo de lo dicho y con el ancho entero, en el
   cuerpo chico y gris con el que este panel escribe todo lo que acompaña a un
   dato sin ser el dato. No es lo que el cliente escribió, así que no puede
   leerse como parte de su frase — pero tampoco es un adorno: es la única señal
   de que ese comentario tiene algo más para mirar.
   El conteo va escrito y no sólo en el glifo: un clip solo dice que hay algo
   prendido, no cuántas cosas, y eso deja al lector abriendo la vista para
   averiguar si era una o tres. */
.timeline__capturas {
  grid-column: 1 / -1;
  display: inline-flex;
  align-items: center;
  gap: var(--feedo-space-3xs);
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}
/* Al tamaño del texto que acompaña, y alineado por su caja: apoyado en la línea
   base flotaría sobre las letras. Misma regla que el resto de los glifos. */
.timeline__clip {
  inline-size: 1em;
  block-size: 1em;
  flex: none;
}

.timeline__hijo .timeline__cuando {
  grid-row: 1;
  grid-column: 2;
}

@media (prefers-reduced-motion: reduce) {
  .timeline__flecha {
    transition: none;
  }
}

@media (max-width: 40rem) {
  /* En pantalla angosta la fecha baja debajo del hecho: cuatro columnas parten el
     texto en una palabra por línea. El carril y la sangría no se tocan — son lo
     que sostiene la lectura, y achicarlos acá la rompe justo donde más cuesta. */
  .timeline__fila {
    grid-template-columns: var(--nodo) minmax(0, 1fr) auto;
  }
  .timeline__fila > .timeline__cuando {
    grid-row: 2;
    grid-column: 2;
    margin-block-start: 0;
  }
  .timeline__flecha {
    grid-row: 1;
    grid-column: 3;
  }
  /* La tarjeta del próximo hito respira menos: en 320px lo que le sobra al aire
     le falta a los chips, que son nombres de archivo y no se parten. El nodo y
     el arranque del carril se acomodan solos — los dos se calculan desde acá. */
  .timeline__item[data-tipo="pendiente"] {
    --tarjeta-pad: var(--feedo-space-xs);
  }
  /* Adentro del hito pasa lo mismo: el nombre de la vista y la fecha no entran
     juntos en un renglón de 320px sin comerse uno al otro, así que se apilan. */
  .timeline__hijo {
    grid-template-columns: minmax(0, 1fr);
  }
  .timeline__hijo .timeline__cuando {
    grid-row: auto;
    grid-column: 1;
  }
  .timeline__vista {
    white-space: normal;
    overflow-wrap: break-word;
  }
}

/* ---- LOS PASOS DE UNA PUESTA EN MARCHA ----
   Dos cosas que hay que hacer en orden, dichas como lo que son. Antes cada
   pantalla mostraba sólo la suya y quien llegaba no sabía cuántas faltaban ni
   cuál venía después: parecían dos pedidos sueltos.

   El número va en un disco y NO es la única señal de en cuál estás: el que
   falta se lee en gris, el actual en tinta, y el hecho tacha su texto. Un
   lector de pantalla recibe «— es lo que sigue» o «— listo» en palabras. */
.pasos {
  list-style: none;
  padding: 0;
  margin-block-end: var(--feedo-space-m);
  display: grid;
  gap: var(--feedo-space-2xs);
}
.pasos__paso {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}
.pasos__n {
  flex: none;
  inline-size: 1.5rem;
  block-size: 1.5rem;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--feedo-line);
  font-weight: 700;
  font-size: var(--feedo-step--2);
}
.pasos__paso[data-estado="ahora"] {
  color: var(--feedo-ink);
  font-weight: 600;
}
.pasos__paso[data-estado="ahora"] .pasos__n {
  background: var(--feedo-accent);
  border-color: var(--feedo-accent);
  color: var(--feedo-paper);
}
.pasos__paso[data-estado="hecho"] .pasos__texto {
  text-decoration: line-through;
}

/* ---- UN BOTÓN QUE SE VE COMO ENLACE ----
   Para la salida de al lado: «omitir por ahora», junto a la acción principal.

   Tiene que ser un <button> dentro de un form y no un <a>, porque deja una
   marca en el servidor y una dirección que se puede visitar no debería cambiar
   nada. Pero pintarlo como botón lo pondría a la par de lo que sí hay que
   hacer, y son dos cosas de peso distinto: una es la acción, la otra es no
   hacerla ahora.

   Sigue siendo un control para el teclado y para un lector de pantalla; lo
   único que cambia es que se lee como lo que hace. */
.enlace {
  border: 0;
  background: none;
  /* Vertical, para que el área de click llegue a los 24px que pide WCAG 2.5.8.
     Sin esto medía 23: un pixel, pero medido, y del lado equivocado. Horizontal
     no lleva, así que el subrayado sigue siendo del ancho de las palabras. */
  padding: var(--feedo-space-3xs) 0;
  color: var(--feedo-muted);
  font: inherit;
  font-size: var(--feedo-step--1);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  cursor: pointer;
}
.enlace:hover {
  color: var(--feedo-ink);
  border-color: transparent;
}

/* «Esto ya está hecho», dicho por el panel que lo hizo.
   El estado se leía del verbo del control: «Generar credencial» cuando no
   había una, «Generar una credencial nueva» cuando sí. Dos etiquetas que se
   parecen demasiado como para deducir de ellas si el agente puede entrar o no,
   y deducir un estado de un botón es trabajo que la pantalla le pasa a quien
   la mira. Ahora el estado se afirma y el control baja a enlace: generar otra
   revoca la que anda, así que no es la acción que uno viene a hacer acá.
   El verde nunca va solo —es la regla del token—: quien no lo distingue lee
   «Credencial generada» igual, y el tilde es el mismo que el producto ya usa
   para decir que algo quedó listo. */
/* La casilla de «avisarle por mail», dentro del hito de publicar.
   El cuadro nativo mide unos 13px y el piso de área táctil es 24 (WCAG 2.5.8):
   el padding vertical se lo lleva el <label>, que es lo que de verdad se toca
   —clickear la palabra marca la casilla sin apuntarle al cuadradito—. */
.avisar {
  display: flex;
  align-items: center;
  gap: var(--feedo-space-2xs);
  margin: 0;
  font-size: var(--feedo-step--1);
}
.avisar__caja {
  inline-size: 1.05rem;
  block-size: 1.05rem;
  flex: none;
  accent-color: var(--feedo-accent);
  margin: 0;
}
.avisar label {
  padding-block: var(--feedo-space-3xs);
  cursor: pointer;
}

.hecho {
  color: var(--feedo-approve);
  font-size: var(--feedo-step--1);
  font-weight: 700;
}
.hecho__tilde {
  inline-size: 1em;
  block-size: 1em;
  flex: none;
}

/* La cabecera de un panel con tabla: el título a la izquierda, la acción que
   agrega una fila a la derecha. Estaban al pie de la página, después de las dos
   tablas, así que dar de alta pedía pasar de largo todo lo demás.

   El padding del título ya venía puesto por `.panel__title`, así que acá sólo
   se alinea y se le da aire al control del otro lado. */
/* El padding pasa del título a la cabecera: mientras lo traía el <h2>, el botón
   quedaba fuera de ese bloque y se apoyaba contra el borde de arriba del panel.
   Ahora los dos comparten el mismo aire y se centran contra el mismo eje. */
.panel__head {
  padding: var(--feedo-space-s) var(--feedo-space-s-m) var(--feedo-space-xs);
  --repel-align: center;
}
.panel__head .panel__title {
  padding: 0;
}
/* El tamaño es el de cualquier botón. Agrandarlo lo sacaba de la escala del
   producto para decir algo que su color ya dice: es la acción de esta tabla, y
   es la única llena de acento en toda la pantalla. */
.panel__head .panel__title {
  padding-inline-end: 0;
}

/* ---- BUSCAR EN UNA LISTA LARGA ----
   Un <select> de cincuenta repos se recorre a ciegas y su desplegable nativo
   no se puede filtrar. Esto lo convierte en un campo con lista propia debajo.

   El alto de la lista está acotado a propósito: cuando la última fila queda
   cortada por el borde, se ve que hay más abajo. Con la lista entera a la
   vista, o crece hasta tapar el formulario, o parece que eso es todo lo que
   hay. Seis filas y media es lo que entra sin empujar el campo siguiente fuera
   de la pantalla en un teléfono. */
.combo {
  position: relative;
  --combo-alto: 15rem;
}
.combo__campo {
  inline-size: 100%;
}
.combo__lista {
  position: absolute;
  inset-inline: 0;
  z-index: 2;
  margin: var(--feedo-space-3xs) 0 0;
  padding: var(--feedo-space-3xs);
  list-style: none;
  max-block-size: var(--combo-alto);
  overflow-y: auto;
  background: var(--feedo-paper);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-s);
  box-shadow: var(--feedo-shadow-md);
}
/* La opción es un blanco de click, así que declara su caja aunque en reposo no
   se pinte: sin padding el resaltado quedaría pegado a las letras, y sin alto
   mínimo caería debajo de los 24px que pide WCAG 2.5.8. */
.combo__opcion {
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  min-block-size: 1.5rem;
  border-radius: var(--feedo-radius-s);
  cursor: pointer;
  font-size: var(--feedo-step--1);
  /* La lista vive adentro del <label>, que es versalita en mayúsculas y 700
     para nombrar el campo. Heredarlo dejaba los repos como
     «TBAGENCIA/LIDHERMA»: ilegible, y encima falso — un nombre de repo es
     minúscula y así hay que poder copiarlo con los ojos. Es el mismo reset que
     `.field input` ya hace con lo que se escribe. */
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  color: var(--feedo-ink);
}
/* Una sola señal para «esta es la que se elige con Enter», venga del mouse o
   del teclado: dos dibujos distintos para el mismo estado obligan a aprender
   dos veces lo mismo. */
.combo__opcion:hover,
.combo__opcion[data-marcada] {
  background: var(--feedo-paper-2);
}
.combo__opcion[aria-selected="true"] {
  font-weight: 600;
}
/* Buscar y no encontrar tiene que decirlo. Una lista que se vacía en silencio
   se lee como que la búsqueda se rompió. */
.combo__vacio {
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  position: absolute;
  inset-inline: 0;
  z-index: 2;
  margin: var(--feedo-space-3xs) 0 0;
  padding: var(--feedo-space-2xs) var(--feedo-space-xs);
  background: var(--feedo-paper);
  border: 1px solid var(--feedo-line);
  border-radius: var(--feedo-radius-s);
  box-shadow: var(--feedo-shadow-md);
  font-size: var(--feedo-step--1);
  color: var(--feedo-muted);
}

/* La flecha que el <select> traía puesta y que se perdió al reemplazarlo por un
   campo de búsqueda: sin ella el control se lee igual que «Nombre», y nadie
   abre lo que no sabe que se abre. No se come el click —el campo de abajo tiene
   que recibirlo— y no la ve un lector de pantalla, que ya escucha «combobox». */
.combo__flecha {
  position: absolute;
  inset-inline-end: var(--feedo-space-xs);
  inset-block-start: 50%;
  transform: translateY(-50%);
  display: flex;
  pointer-events: none;
  color: var(--feedo-muted);
}
.combo__flecha svg {
  inline-size: 1rem;
  block-size: 1rem;
}
/* El texto no puede pasar por debajo de la flecha. */
.combo__campo {
  padding-inline-end: var(--feedo-space-m);
}
