/* =============================================================================
   site.css — el sitio público de describe
   -----------------------------------------------------------------------------
   Regla dura heredada de design/README.md: NI UN COLOR LITERAL. Todo sale de
   var(--token). Donde hace falta transparencia se usa color-mix() sobre un
   token, que sigue siendo el token — nunca un rgba() escrito a mano.

   Las fuentes que declaran los temas (Figtree, IBM Plex Mono, Space Grotesk,
   Roboto Mono, Source Serif 4) NO se cargan: este sitio se sirve estático desde
   S3+CloudFront y no puede depender de un CDN de fuentes. Cada declaración de
   los temas ya trae su stack de sistema detrás, así que degradan solas. Es
   deuda anotada en design/README.md ("Fuentes por CDN"), no un olvido.
   ============================================================================= */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--surface-page);
  background-image: var(--texture-page);
  background-size: var(--texture-size);
  color: var(--text-primary);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-base);
  -webkit-font-smoothing: antialiased;
}

:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-chip); }

/* 🔴 UNA CLASE CON `display` LE GANA A `hidden`. La regla del navegador es
   `[hidden]{display:none}` en la hoja del USUARIO-AGENTE, así que cualquier
   `display` declarado acá —`.verdict{display:grid}`, `.proof{...}`— la pisa y
   el bloque sigue en pantalla con el atributo puesto. En el perfil eso dejaba
   los rótulos de un bloque todavía no comprado a la vista sobre celdas vacías:
   la pantalla afirmaba tener una respuesta que no tiene.
   Se arregla SÓLO en las dos clases que lo necesitan y no con un
   `[hidden]{display:none!important}` global: `.seg[hidden]` de metrics.html
   tiene exactamente el mismo síntoma (sus seis selectores se muestran siempre,
   aunque el JS los apague), y arreglarlo desde acá cambiaría esa página en el
   mismo commit que rehace el perfil. Queda anotado, no corregido de rebote. */
.proof[hidden], .verdict[hidden] { display: none; }

a { color: var(--text-link); text-underline-offset: 2px; }
a:hover { color: var(--text-link-hover); }

.wrap { max-width: 76rem; margin: 0 auto; padding: 0 var(--space-6); }
.narrow { max-width: 46rem; }

/* Salto directo al contenido: la primera pantalla tiene un switcher de tema y
   una barra de estado antes del texto, y no se le puede pedir a un lector de
   pantalla que los recorra en cada página. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 10;
  background: var(--surface-inverse); color: var(--text-on-inverse);
  padding: var(--space-2) var(--space-4); border-radius: var(--radius-control);
}
.skip:focus { left: var(--space-4); top: var(--space-2); }

/* ---------------------------------------------------------------------------
   BARRA SUPERIOR
   ------------------------------------------------------------------------ */
.top {
  border-bottom: var(--border-width) solid var(--border-subtle);
  background: var(--surface-raised);
  position: sticky; top: 0; z-index: 5;
}
.top .wrap {
  display: flex; align-items: center; gap: var(--space-5);
  min-height: 3.5rem; flex-wrap: wrap;
}
.brand {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: var(--text-lg); letter-spacing: var(--tracking-tight);
  text-decoration: none; color: var(--text-primary); white-space: nowrap;
}
.brand span { color: var(--accent); }
.nav { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.nav a {
  font-size: var(--text-sm); text-decoration: none; color: var(--text-secondary);
  padding: var(--space-1) 0; border-bottom: var(--border-width-emphasis) solid transparent;
}
.nav a:hover { color: var(--text-primary); border-bottom-color: var(--border-hover); }
.nav a[aria-current="page"] { color: var(--text-primary); border-bottom-color: var(--accent); }
.top .switcher { margin-left: auto; }

/* Selector de tema — visible, no escondido en un menú. Son dos ejes
   ortogonales (piel × claro/oscuro) y se presentan como dos controles. */
.switcher { display: flex; gap: var(--space-2); align-items: center; flex-wrap: wrap; }
/* 🔴 EL GRUPO SE DESLIZA ADENTRO; LA PAGINA NO SE DESLIZA. `.seg` nacio para
   tres botones de tema, pero `#chainpick` (explore) y `#picker` (chain) meten
   una cadena por boton: medido, 818 px de grupo. Un `inline-flex` sin techo se
   estira hasta ese ancho, desborda la `section` —que tiene overflow visible— y
   el que termina deslizandose de lado es el DOCUMENTO: 842 px de scroll a 768
   de ventana, 834 a 375. Con `max-width` el grupo no puede pasarse de su
   contenedor, y el `overflow-x: auto` le da el deslizamiento a EL en vez de a
   la pagina. El `overflow: hidden` se queda: es lo que recorta las esquinas de
   los botones contra el `border-radius` del grupo, y en el eje Y sigue siendo
   hidden, asi que no aparece una barra vertical de rebote.
   `flex: none` va sobre el HIJO DIRECTO y no sobre `button`: `#chainpick` de
   explore.html enfila `<a class="btn ghost">`, no botones. Sin eso los hijos
   se encogen antes de deslizarse —`flex-shrink` vale 1 por defecto— y el
   resultado no es un grupo con scroll sino diez rotulos partidos en dos
   lineas: medido a 375 px, 42 px de alto pasaban a 66. */
.seg {
  display: inline-flex; border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-control); overflow: hidden;
  max-width: 100%; overflow-x: auto;
}
.seg > * { flex: none; }
.seg button {
  font: inherit; font-size: var(--text-2xs); font-family: var(--font-data);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  padding: var(--space-1) var(--space-2); border: 0; cursor: pointer;
  background: var(--surface-raised); color: var(--text-secondary);
}
.seg button + button { border-left: var(--border-width) solid var(--border-subtle); }
/* El toggle de modo lleva un SVG en vez de texto: block mata el hueco de
   baseline y el alto queda parejo con los botones de texto del grupo vecino. */
.seg button svg { display: block; }
.seg button:hover { background: var(--table-row-hover); color: var(--text-primary); }
.seg button[aria-pressed="true"] {
  background: var(--surface-inverse); color: var(--text-on-inverse);
}

/* ---------------------------------------------------------------------------
   EL CABLE — estado de la lectura en vivo.
   No es un banner de error: es un estado de primera clase del producto. O el
   índice contesta al navegador, o las cifras están congeladas con su fecha.
   Nunca se muestra un número sin decir de cuál de los dos mundos salió.
   ------------------------------------------------------------------------ */
.wire {
  display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap;
  font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  padding: var(--space-2) 0; color: var(--text-tertiary);
  border-bottom: var(--border-width) solid var(--border-subtle);
}
.wire b { font-weight: var(--weight-semibold); color: var(--text-secondary); }
.wire .dot {
  width: 0.5rem; height: 0.5rem; border-radius: var(--radius-circle);
  background: var(--accent); display: inline-block; flex: none;
}
/* CONGELADO usa la gramática de ausencia del contrato: punteado y sin relleno.
   No un color de alarma — la piel B no tiene ninguno con el que gritar. */
.wire[data-state="frozen"] .dot {
  background: transparent;
  border: var(--border-width) var(--border-absent-style) var(--nodata-border);
}
.wire[data-state="loading"] .dot { background: var(--skeleton-base); }
.wire button {
  font: inherit; background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--text-link); text-decoration: underline; text-transform: none;
  letter-spacing: var(--tracking-normal);
}
.wire-why {
  display: none; font-family: var(--font-body); font-size: var(--text-sm);
  text-transform: none; letter-spacing: var(--tracking-normal);
  line-height: var(--leading-sm); color: var(--text-secondary);
  border-left: var(--border-width-emphasis) solid var(--accent);
  padding: var(--space-3) var(--space-4); margin: var(--space-3) 0;
  background: var(--surface-accent-soft); border-radius: var(--radius-surface);
}
.wire-why[data-open="1"] { display: block; }
.wire-why code, code.k {
  font-family: var(--font-data); font-size: 0.9em;
  background: var(--surface-sunken); padding: 0 var(--space-1);
  border-radius: var(--radius-chip);
  /* Un `code` en línea suele ser una URL o una ruta completa —
     `curl https://api.describe.net/reputation/wallet/0x715d…` mide 340 px en
     mono— y para el navegador es UNA palabra. Sin esto, la frase que explica
     un fallo de lectura es justamente la que empuja la página 250 px a lo
     ancho en un teléfono. Tiene que ser `anywhere` y NO `break-word`: los dos
     parten la línea, pero sólo `anywhere` achica el ancho MÍNIMO del bloque, y
     el ancho mínimo es lo que dimensiona la columna de una grilla. Con
     `break-word` el texto se partía y la página seguía desbordada. */
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   TIPOGRAFÍA DE PÁGINA
   ------------------------------------------------------------------------ */
h1, h2, h3 {
  font-family: var(--font-display); font-weight: var(--weight-heading);
  letter-spacing: var(--tracking-tight); margin: 0;
}
h1 { font-size: var(--text-4xl); line-height: var(--leading-4xl); }
h2 { font-size: var(--text-2xl); line-height: var(--leading-2xl); }
h3 { font-size: var(--text-lg); line-height: var(--leading-lg); }

/* Sub-títulos semánticos que se dibujan como texto corriente: un <h3> que es
   la etiqueta de un bloque («Send this to your agent:») o una etiqueta en
   línea («Machine-readable:»). Estructura para lectores y crawlers, mismo
   dibujo que el <p>/<span> que reemplazan. */
h3.fine { font-weight: var(--weight-regular); font-size: var(--text-sm); line-height: var(--leading-sm); margin: var(--space-4) 0; }
h3.inline-h { display: inline; font: inherit; margin: 0; }

.eyebrow {
  font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-tertiary); margin: 0 0 var(--space-3);
  /* Un eyebrow puede ser un <h2> (los títulos de sección del landing lo son
     desde 2026-08-23: estructura real para lectores y crawlers sin cambiar el
     dibujo). Se neutralizan el peso y la altura de línea que h2 heredaría. */
  font-weight: var(--weight-regular); line-height: var(--leading-2xs);
}
p { margin: var(--space-4) 0; }
.lede { font-size: var(--text-lg); line-height: var(--leading-lg); color: var(--text-secondary); }
.fine { font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--text-secondary); }
.meta {
  font-family: var(--font-data); font-size: var(--text-2xs);
  color: var(--text-tertiary); letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
/* 32px y no 48: con 48 la página era una columna de bloques flotando. */
section { padding: var(--space-8) 0; border-top: var(--border-width) solid var(--border-subtle); }
section:first-of-type { border-top: 0; }
.stack > * + * { margin-top: var(--space-4); }

/* La cita — voz humana, no dato. Familia aparte por contrato. */
blockquote.voice {
  font-family: var(--font-voice); font-style: italic;
  font-size: var(--text-xl); line-height: var(--leading-xl);
  border-left: var(--border-width-emphasis) solid var(--accent);
  margin: var(--space-6) 0; padding: 0 0 0 var(--space-5); color: var(--text-primary);
}

/* ---------------------------------------------------------------------------
   PORTADA — LA PRIMERA PANTALLA

   🔴 EL PRESUPUESTO DE ALTURA ES LA REGLA DE DISEÑO DE ESTA SECCIÓN. La vara
   de la portada es que alguien que nunca oyó hablar del proyecto entienda en
   DIEZ SEGUNDOS qué hace describe. Diez segundos son una pantalla: la
   frase, la línea que la explica, y el primer número del ejemplo asomando.
   Por eso el hero no lleva `--space-16` arriba y abajo como antes (eso solo
   ya empujaba el ejemplo fuera de una pantalla de portátil) ni botones ni
   contadores: todo lo que se meta acá le come el lugar al ejemplo, que es lo
   único que se explica solo.
   ------------------------------------------------------------------------ */
.hero { padding: var(--space-12) 0 var(--space-4); }
.hero h1 {
  /* Techo 3rem, no 4.25: el titular cede pantalla al primer número vivo. */
  font-size: clamp(1.75rem, 4.5vw, 3rem); line-height: 1.08;
  max-width: 24ch;
}
.hero .sub { max-width: 58ch; }

/* Regla entre secciones. `section { border-top }` ya existe, pero cada sección
   de esta portada vive dentro de su PROPIO `.wrap` —para que el ancho máximo lo
   ponga el contenedor y no la sección—, así que todas son `:first-of-type` de
   su padre y ninguna dibuja la línea. Sin ella la página es una columna de
   bloques flotando en blanco, que es justamente la sensación de «disperso».
   Se resuelve con una clase explícita en vez de tocar el selector compartido:
   explore.html y agent.html sí tienen sus secciones como hermanas y ahí la
   regla actual funciona bien. */
section.ruled { border-top: var(--border-width) solid var(--border-subtle); }

/* ---------------------------------------------------------------------------
   EL EJEMPLO — cuatro números y nada más.
   Es el bloque que tiene que leerse sin prosa: 300.001 / 57 / 1 / 1,0. La
   tipografía es la del dato y el tamaño es deliberadamente el del titular,
   porque acá el dato ES el titular.
   ------------------------------------------------------------------------ */
/* 🔴 CUATRO O DOS COLUMNAS, NUNCA TRES. Es la única rejilla del sitio que NO
   usa `auto-fit`: con cuatro celdas, `auto-fit` pasa por un tramo de tres
   —medido a 768 px— y deja la cuarta sola en una segunda fila. Cuatro números
   que hay que leer como un conjunto no pueden repartirse 3+1; 2+2 sí se lee
   como un conjunto. El corte es 62rem, el MISMO que usan `.ring-wrap`,
   `.verdict` y `.lookup`: dos cortes a 2rem de distancia en un archivo
   compartido son una invitación a que el siguiente elija un tercero. Medido a
   62rem (992 px): cada celda queda en 218 px y «300,001» a 2.5rem mide ~168,
   así que las cuatro entran con aire. */
.exhibit {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-6); margin: var(--space-8) 0;
  border-top: var(--border-width-emphasis) solid var(--border-strong);
  border-bottom: var(--border-width-emphasis) solid var(--border-strong);
  padding: var(--space-6) 0;
}
@media (max-width: 62rem) { .exhibit { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.exhibit-cell { min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.exhibit-cell .n {
  font-family: var(--font-data); font-weight: var(--weight-bold);
  font-variant-numeric: var(--numeric-figures);
  /* El tope de la clamp es 2.5rem y no 3: las cuatro celdas son columnas
     iguales, pero «300,001» mide siete glifos y «1» mide uno. A 3rem el número
     más largo llegaba hasta el borde de su celda y se leía pegado al de al
     lado; a 2.5rem sobra aire y sigue siendo, con diferencia, lo más grande de
     la página después del titular. */
  font-size: clamp(1.75rem, 4vw, 2.5rem); line-height: 1;
  letter-spacing: var(--tracking-tight);
}
.exhibit-cell .k {
  font-size: var(--text-sm); line-height: var(--leading-sm); color: var(--text-secondary);
  /* Sin `text-transform: uppercase` a propósito: estas cuatro etiquetas son la
     frase que el visitante tiene que poder LEER en voz alta ("57 wallets
     escribieron todas"), no metadatos. Las versalitas de --tracking-label
     leen bien en dos palabras y mal en cinco. */
}
.exhibit-cell .note {
  font-family: var(--font-data); font-size: var(--text-2xs);
  color: var(--text-tertiary); letter-spacing: var(--tracking-label);
  text-transform: uppercase; overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
   QUÉ DEVUELVE UNA CONSULTA — dos columnas con distinto estatus:
   la de la izquierda es gratis y viva, la de la derecha se paga. La diferencia
   se dibuja con el BORDE (la paga va enmarcada), nunca con un matiz.
   ------------------------------------------------------------------------ */
.lookup {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--space-6); align-items: start; margin-top: var(--space-6);
}
.lookup-free { min-width: 0; padding-top: var(--space-4); }
.lookup-paid {
  min-width: 0; border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-surface); background: var(--surface-raised);
  padding: var(--density-card);
}
/* `minmax(0, 1fr)` y no `1fr` en las dos columnas de arriba: el mínimo
   automático de un `1fr` es el min-content de su contenido, y adentro hay una
   ruta `/reputation/wallet/{w}` sin espacios que estiraría la columna y con
   ella la página entera. */
@media (max-width: 62rem) { .lookup { grid-template-columns: minmax(0, 1fr); } }

/* El desmentido: cada afirmación se acompaña de la medición que la sostiene.
   Si la medición no llega, se dice — no se calla. */
.proof {
  margin-top: var(--space-8); border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-surface); background: var(--surface-raised);
  padding: var(--density-card);
}
.proof-row { display: flex; gap: var(--space-6); flex-wrap: wrap; align-items: flex-start; }
.proof-cell { flex: 1 1 12rem; min-width: 0; }
.proof-cell .k {
  font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-tertiary); display: block; margin-bottom: var(--space-1);
}
.proof-cell .v {
  font-family: var(--font-data); font-size: var(--text-3xl);
  font-variant-numeric: var(--numeric-figures); font-weight: var(--weight-bold);
  line-height: 1.05; display: block;
}
.proof-cell .n { font-size: var(--text-sm); color: var(--text-secondary); display: block; margin-top: var(--space-1); }

/* ---------------------------------------------------------------------------
   CIFRAS DESCOMPUESTAS — la fila de contadores, pero cada contador viene con
   el número que lo desmiente. Es la tesis del producto aplicada a nuestro
   propio material de venta.
   ------------------------------------------------------------------------ */
/* El eyebrow y su aviso comparten línea. `wrap` es obligatorio: a 375 px el
   chip baja solo en vez de estirar la página — y por eso el margen
   inferior se muda del `p` a la FILA: si se quedara en el `p`, al envolverse
   el hueco caería entre el eyebrow y el chip en vez de debajo del chip, y
   el aviso terminaría pegado a la primera tarjeta. */
.eyebrow-row { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--space-1) var(--space-3); margin-bottom: var(--space-3); }
.eyebrow-row .eyebrow { margin-bottom: 0; }

/* ---------------------------------------------------------------------------
   INDEXING — el aviso de que el índice todavía camina.
   Existe sólo mientras alguna cadena va atrás: cuando todas están
   al día el nodo no se dibuja (index.html), así que acá no hay
   variante «apagada». Lo que se mueve es EL PUNTO y nada más: la
   palabra queda fija porque texto que parpadea no se lee, y el punto usa el
   mismo reloj que el skeleton (`--skeleton-duration`) porque es el mismo
   mensaje — «esto todavía está pasando»— y el sitio
   no necesita un segundo ritmo de espera.
   ------------------------------------------------------------------------ */
.idxflag {
  display: inline-flex; align-items: baseline; gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-chip);
  background: var(--state-caution-surface);
  font-family: var(--font-data); font-size: var(--text-2xs);
  line-height: var(--leading-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-primary);
}
.idxflag b { font-weight: var(--weight-semibold); }
/* La cláusula NO va en versalitas: es prosa que acota la palabra grande,
   y en mayúsculas competiría con ella en vez de matizarla. */
.idxflag-q {
  text-transform: none; letter-spacing: var(--tracking-normal);
  color: var(--text-secondary); font-variant-numeric: var(--numeric-figures);
}
.idxflag-dot {
  flex: none; align-self: center;
  width: 0.4rem; height: 0.4rem; border-radius: var(--radius-circle);
  background: var(--state-caution);
  animation: idxpulse var(--skeleton-duration) var(--ease-standard) infinite;
}
@keyframes idxpulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.15; } }

/* Con la preferencia puesta el aviso NO desaparece: deja de latir. Y se apaga
   con `animation: none`, no con `duration: 0ms` — una animación de cero
   segundos sigue siendo una animación corriendo, y el punto se quedaría
   congelado en el fotograma que toque, que puede ser el de opacidad 0.15. */
@media (prefers-reduced-motion: reduce) {
  .idxflag-dot { animation: none; opacity: 1; }
}

.decomp { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--space-4); }
.dcard {
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-surface); background: var(--surface-raised);
  padding: var(--density-card); display: flex; flex-direction: column; gap: var(--space-2);
}
.dcard .big {
  font-family: var(--font-data); font-size: var(--text-4xl); line-height: 1;
  font-weight: var(--weight-bold); font-variant-numeric: var(--numeric-figures);
}
.dcard .lab {
  font-size: var(--text-2xs); font-family: var(--font-data);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-tertiary);
}
/* El desmentido va SEPARADO por una regla y con la tipografía de dato: no es
   una nota al pie, es la mitad del contenido de la tarjeta. */
.dcard .but {
  border-top: var(--border-width) solid var(--table-rule);
  padding-top: var(--space-3); font-size: var(--text-sm);
  color: var(--text-secondary); line-height: var(--leading-sm);
}
.dcard .but b { font-family: var(--font-data); color: var(--text-primary); font-variant-numeric: var(--numeric-figures); }

/* ---------------------------------------------------------------------------
   LA TIRA — los 451.774 ratings del índice dibujados a escala.
   Dos tercios de la tira son una sola campaña. No hace falta explicarlo.
   ------------------------------------------------------------------------ */
.strip { margin: var(--space-6) 0 var(--space-3); }
.strip-bar {
  display: flex; height: 3.5rem; width: 100%;
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-chip); overflow: hidden;
}
.strip-seg { position: relative; min-width: 2px; }
.strip-seg.camp { background: var(--surface-inverse); }
/* Los 57 calificadores, uno por tick. Repeating-gradient sobre tokens: la
   subdivisión es del dato, no decorativa. */
.strip-seg.camp .ticks {
  position: absolute; inset: 0;
  background-image: repeating-linear-gradient(
    to right,
    color-mix(in srgb, var(--text-on-inverse) 55%, transparent) 0 1px,
    transparent 1px calc(100% / 57)
  );
}
.strip-seg.rest { background: var(--surface-sunken); border-left: var(--border-width-emphasis) solid var(--border-strong); }
.strip-legend { display: flex; gap: var(--space-6); flex-wrap: wrap; margin-top: var(--space-3); }
.strip-legend div { flex: 1 1 16rem; font-size: var(--text-sm); color: var(--text-secondary); }
.strip-legend b { font-family: var(--font-data); color: var(--text-primary); }

/* ---------------------------------------------------------------------------
   EL ANILLO — 58 nodos. El dibujo se genera en JS y es determinista.
   ------------------------------------------------------------------------ */
.ring-wrap { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); gap: var(--space-8); align-items: center; }
@media (max-width: 62rem) { .ring-wrap { grid-template-columns: minmax(0, 1fr); } }
.ring-svg { width: 100%; height: auto; display: block; }
.ring-svg .spoke { stroke: var(--accent-support); stroke-width: 1; fill: none; }
.ring-svg .spoke.back { stroke: var(--accent); stroke-width: 3; opacity: 0.9; }
.ring-svg .miner { fill: var(--surface-raised); stroke: var(--border-strong); stroke-width: 1.25; }
/* Los 10 mineros que SÍ tienen reputación propia van llenos; los 47 que no,
   punteados — ausencia estructural, no un color más claro. */
.ring-svg .miner.scored { fill: var(--accent-support); stroke: var(--accent-support); }
.ring-svg .miner.unscored { stroke-dasharray: 2 2; }
.ring-svg .hub { fill: var(--surface-inverse); stroke: var(--border-strong); stroke-width: 1.5; }
.ring-svg .hub-label { fill: var(--text-on-inverse); font-family: var(--font-data); font-weight: var(--weight-bold); }
.ring-svg text { fill: var(--text-tertiary); font-family: var(--font-data); }
/* Halo, no caja. Estas tres etiquetas caen sobre los 57 radios y sin nada
   detrás se leen a medias. Un `<rect>` opaco taparía el dibujo justo donde
   está la información; un contorno del color de la superficie pintado DEBAJO
   del glifo (`paint-order: stroke`) abre el hueco exacto de cada letra. Sigue
   siendo un token: el halo es la superficie, no un color inventado. */
.ring-svg .hub-sub, .ring-svg .ring-caption {
  stroke: var(--surface-raised); stroke-width: 3; stroke-linejoin: round;
  paint-order: stroke;
}
.ring-svg g.node:hover .miner { fill: var(--accent); stroke: var(--accent); }
.ring-facts { list-style: none; margin: 0; padding: 0; }
.ring-facts li {
  border-top: var(--border-width) solid var(--table-rule);
  padding: var(--space-3) 0; font-size: var(--text-sm); color: var(--text-secondary);
}
.ring-facts li b {
  display: block; font-family: var(--font-data); font-size: var(--text-xl);
  color: var(--text-primary); font-variant-numeric: var(--numeric-figures);
  line-height: 1.2;
}

/* ---------------------------------------------------------------------------
   LA GRILLA DE EVIDENCIA — 2×2 log-log. Sección y página.
   ------------------------------------------------------------------------ */
.grid-svg { width: 100%; height: auto; display: block; background: var(--surface-raised); border-radius: var(--radius-surface); }
.grid-svg .axis { stroke: var(--border-strong); stroke-width: 1; }
.grid-svg .divider { stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 4 3; }
.grid-svg .qlabel {
  fill: var(--text-tertiary); font-family: var(--font-data);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
}
/* El cuadrante SIN EVIDENCIA se marca punteado, nunca con un color pálido. */
.grid-svg .qbox-absent { fill: none; stroke: var(--nodata-border); stroke-width: 1; stroke-dasharray: 3 3; }
.grid-svg .qbox-alert { fill: none; stroke: var(--text-primary); stroke-width: var(--border-width-emphasis); }
.grid-svg .pt { fill: var(--accent); stroke: var(--surface-raised); stroke-width: 1; cursor: pointer; }
/* Alarma = GROSOR de borde. Los cuadrantes de arriba llevan anillo grueso. */
.grid-svg .pt.flag { stroke: var(--text-primary); stroke-width: var(--border-width-emphasis); }
.grid-svg .pt:hover, .grid-svg .pt:focus { fill: var(--text-primary); }
.grid-svg .named { fill: var(--text-primary); }
.grid-svg .tick { fill: var(--text-tertiary); font-family: var(--font-data); }
.grid-svg .axis-title { fill: var(--text-secondary); font-family: var(--font-data); letter-spacing: var(--tracking-label); text-transform: uppercase; }
.grid-tip {
  min-height: 3.25rem; margin-top: var(--space-3); font-size: var(--text-sm);
  font-family: var(--font-data); color: var(--text-secondary);
  border-left: var(--border-width-emphasis) solid var(--border-subtle);
  padding-left: var(--space-3); line-height: var(--leading-sm);
}
.grid-tip b { color: var(--text-primary); }

/* Tabla de los cuatro habitantes */
.quads { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--space-4); }
.quad {
  border: var(--border-width) solid var(--border-subtle); border-radius: var(--radius-surface);
  padding: var(--density-card); background: var(--surface-raised);
}
.quad.absent { border-style: var(--border-absent-style); border-color: var(--nodata-border); }
.quad.alert { border-width: var(--border-width-emphasis); border-color: var(--text-primary); }
.quad h3 { font-family: var(--font-data); font-size: var(--text-sm); letter-spacing: var(--tracking-label); text-transform: uppercase; }
.quad p { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--text-secondary); }
.quad .who { font-family: var(--font-data); font-size: var(--text-xs); color: var(--text-primary); }

/* ---------------------------------------------------------------------------
   EL SELLO DE CUADRANTE — 4 puntos en 2×2, 20px. Se estampa al lado de cada
   score, en toda superficie. Su tamaño es deliberadamente el de un glifo.
   ------------------------------------------------------------------------ */
.seal { width: 20px; height: 20px; flex: none; display: inline-block; vertical-align: -4px; }
.seal .on { fill: var(--text-primary); }
.seal .off { fill: none; stroke: var(--nodata-border); stroke-width: 1; stroke-dasharray: 1.6 1.4; }
.seal .frame { fill: none; stroke: var(--border-subtle); stroke-width: 1; }

/* El score y su composición son UN componente. No existe marcado para
   renderizar el número solo — ver scoreMark() en app.js. */
.smark { display: inline-flex; align-items: baseline; gap: var(--space-2); }
.smark .num {
  font-family: var(--font-data); font-variant-numeric: var(--numeric-figures);
  font-weight: var(--weight-semibold);
}
.smark .comp {
  font-family: var(--font-data); font-size: var(--text-2xs);
  color: var(--text-tertiary); white-space: nowrap;
}
.smark.big .num { font-size: var(--text-5xl); line-height: 1; letter-spacing: var(--tracking-tight); }
.smark.big .comp { font-size: var(--text-sm); }

.nodata {
  color: var(--nodata-ink); background: var(--nodata-surface);
  border: var(--border-width) var(--border-absent-style) var(--nodata-border);
  border-radius: var(--radius-chip); padding: 0 var(--space-2);
  font-family: var(--font-data); font-size: var(--text-2xs);
}

/* Aviso: informativo, jamás un veredicto. La alarma la carga el grosor. */
.chip {
  display: inline-block; padding: 0 var(--space-2); border-radius: var(--radius-chip);
  font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-secondary); background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
}
.chip.warn {
  color: var(--state-caution); background: var(--state-caution-surface);
  border: var(--border-width-emphasis) solid var(--state-caution);
  letter-spacing: var(--tracking-normal); text-transform: none;
}
.chip.ok { color: var(--state-positive); background: var(--state-positive-surface); }

/* ---------------------------------------------------------------------------
   TABLAS
   ------------------------------------------------------------------------ */
.scroll { overflow-x: auto; border: var(--border-width) solid var(--border-subtle); border-radius: var(--radius-surface); }
table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); background: var(--surface-raised); }
/* `top: 0`, NO la altura de la barra superior, y esto costó un bug visible:
   `.scroll` declara `overflow-x: auto`, y por spec eso hace que `overflow-y`
   compute a `auto` también. O sea que `.scroll` ES un contenedor de scroll en
   los dos ejes, y el sticky se ancla a ÉL, no al viewport. Con `top: 3.5rem`
   el header quedaba empujado 56 px hacia abajo dentro de su propia caja y
   TAPABA la primera fila de datos — medido: el borde superior de la primera
   fila caía por encima del borde inferior del header. Con `top: 0` el header
   se apoya donde corresponde. */
thead th {
  position: sticky; top: 0; z-index: 1;
  /* Header de explorador: 12px en minúsculas — el énfasis lo pone el semibold,
     no las versalitas estiradas. */
  text-align: left; font-size: var(--text-xs); font-family: var(--font-data);
  font-weight: var(--weight-semibold); letter-spacing: var(--tracking-normal);
  color: var(--table-header-ink); background: var(--table-header-surface);
  padding: var(--space-1) var(--table-cell-x); white-space: nowrap;
  border-bottom: var(--border-width-emphasis) solid var(--table-sticky-edge);
}
thead th.num, td.num { text-align: right; font-variant-numeric: var(--numeric-figures); }
tbody td {
  padding: var(--density-cell) var(--table-cell-x);
  border-bottom: var(--border-width) solid var(--table-rule);
  vertical-align: top;
}
tbody tr:nth-child(even) { background: var(--table-zebra); }
tbody tr:hover { background: var(--table-row-hover); }
/* La fila-resumen que cierra una tabla (el «Global Reputation» del perfil):
   raya de suma arriba —el mismo borde de énfasis del encabezado— y semibold,
   no bold: la fila se distingue por la raya, el peso sólo la acompaña. Un bold
   entero competiría con el score grande que está tres bloques más arriba. */
tr.total > td {
  border-top: var(--border-width-emphasis) solid var(--border-strong);
  font-weight: var(--weight-semibold);
}
td.mono, .mono { font-family: var(--font-data); font-variant-numeric: var(--numeric-figures); }
td.addr a { font-family: var(--font-data); font-size: var(--text-xs); }
.chains { display: inline-flex; gap: 3px; align-items: center; }
.chains img { border-radius: var(--radius-circle); display: block; }
.chains .fallback {
  width: 16px; height: 16px; border-radius: var(--radius-circle);
  background: var(--series-other); display: inline-block;
}
.delta { font-family: var(--font-data); font-size: var(--text-xs); white-space: nowrap; }
.delta .g { color: var(--text-primary); font-weight: var(--weight-semibold); }

/* ---------------------------------------------------------------------------
   CARGA — skeleton con la FORMA del contenido, nunca un spinner que borra
   el layout. Bajo prefers-reduced-motion deja de brillar (tokens.css).
   ------------------------------------------------------------------------ */
.sk {
  display: inline-block; border-radius: var(--radius-chip);
  background: linear-gradient(90deg, var(--skeleton-base) 0%, var(--skeleton-sheen) 50%, var(--skeleton-base) 100%);
  background-size: 200% 100%;
  animation: sheen var(--skeleton-duration) linear infinite;
  height: 0.85rem; width: 100%;
}
.sk.tall { height: 2.25rem; }
.sk.w4 { width: 4rem; } .sk.w8 { width: 8rem; } .sk.w12 { width: 12rem; }
@keyframes sheen { from { background-position: 200% 0; } to { background-position: -200% 0; } }

/* ---------------------------------------------------------------------------
   PERFIL
   ------------------------------------------------------------------------ */
.finder { display: flex; gap: var(--space-2); flex-wrap: wrap; margin: var(--space-4) 0; }
.finder input {
  flex: 1 1 26rem; min-width: 0; font-family: var(--font-data); font-size: var(--text-sm);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised); color: var(--text-primary);
  border: var(--border-width) solid var(--border-strong); border-radius: var(--radius-control);
}
.btn {
  font: inherit; font-size: var(--text-sm); font-weight: var(--weight-medium);
  cursor: pointer; padding: var(--space-2) var(--space-4);
  background: var(--action); color: var(--text-on-action);
  border: var(--border-width) solid var(--action-edge); border-radius: var(--radius-control);
}
.btn:hover { background: var(--action-hover); }
.btn.ghost { background: var(--surface-raised); color: var(--text-primary); border-color: var(--border-strong); }
.btn.ghost:hover { background: var(--table-row-hover); border-color: var(--border-hover); }
.btn[disabled] { cursor: default; background: var(--surface-sunken); color: var(--text-tertiary); border-color: var(--border-subtle); }

.verdict {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 20rem); gap: var(--space-8);
  align-items: start;
}
/* `minmax(0, 1fr)` y no `1fr`: el mínimo automático de un `1fr` es el
   min-content de su contenido, así que un `curl https://…` sin espacios
   estira la columna y con ella la página. */
@media (max-width: 62rem) { .verdict { grid-template-columns: minmax(0, 1fr); } }

/* El orden de la pantalla ES la tesis: sello, después las facetas de mayor
   volumen CON SU N, después el número. El número nunca abre la página. */
.topfacets { display: flex; gap: var(--space-4); flex-wrap: wrap; margin: var(--space-4) 0; }
.tf {
  flex: 1 1 10rem; border-top: var(--border-width-emphasis) solid var(--border-strong);
  padding-top: var(--space-2);
}
.tf .name { font-family: var(--font-data); font-size: var(--text-xs); color: var(--text-secondary); word-break: break-word; }
.tf .val { font-family: var(--font-data); font-size: var(--text-2xl); font-variant-numeric: var(--numeric-figures); }
.tf .n { font-family: var(--font-data); font-size: var(--text-2xs); color: var(--text-tertiary); }

.kv { display: grid; grid-template-columns: auto 1fr; gap: var(--space-2) var(--space-4); font-size: var(--text-sm); }
.kv dt { font-family: var(--font-data); font-size: var(--text-2xs); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-tertiary); }
.kv dd { margin: 0; font-family: var(--font-data); font-variant-numeric: var(--numeric-figures); }

.bar {
  height: var(--score-track-height); background: var(--score-track);
  border: var(--border-width) solid var(--score-track-border);
  border-radius: var(--radius-pill); overflow: hidden; min-width: 6rem;
}
.bar > i { display: block; height: 100%; background: var(--score-fill-neutral); }
/* Barra de cobertura RAYADA: no es progreso, es "cuánto pudimos fechar". */
.bar.striped > i {
  background-image: repeating-linear-gradient(
    45deg,
    var(--score-fill-neutral) 0 4px,
    color-mix(in srgb, var(--score-fill-neutral) 45%, var(--score-track)) 4px 8px
  );
}

/* El selector de cadena de la chispa vive DENTRO del eyebrow y habla con su
   misma voz: mismo mono, mismo cuerpo, mismas mayúsculas (los <select> no
   heredan tipografía, hay que decirla). Sólo el subrayado hairline y el cursor
   dicen «tocable» — es un instrumento, no un widget. El subrayado es SÓLIDO a
   propósito: el punteado del sistema significa ausencia, no decoración. */
#spark-net {
  font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-secondary);
  background: transparent; border: none;
  border-bottom: var(--border-width) solid var(--border-subtle);
  padding: 0; cursor: pointer;
}
#spark-net:hover { color: var(--text-primary); border-bottom-color: var(--border-strong); }
/* La lista desplegada no hereda el fondo transparente: sin esto quedan
   opciones blancas sobre tema oscuro (o al revés) según el navegador. */
#spark-net option { background: var(--surface-raised); color: var(--text-primary); }

.spark { width: 100%; height: auto; display: block; }
.spark .line { fill: none; stroke: var(--accent); stroke-width: var(--series-stroke-width); }
.spark .dot { fill: var(--accent); }
.spark .base { stroke: var(--border-subtle); stroke-width: 1; }
.spark text { fill: var(--text-tertiary); font-family: var(--font-data); font-size: 10px; }

/* El icono de la cadena elegida, pegado al select del rótulo. */
#spark-icon img, #spark-icon .fallback {
  vertical-align: -3px; margin-right: var(--space-1);
  width: 16px; height: 16px; display: inline-block; border-radius: var(--radius-circle);
}

.facetlist { list-style: none; margin: 0; padding: 0; }
.facetlist li {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-2) var(--space-4); align-items: baseline;
  padding: var(--space-3) 0; border-bottom: var(--border-width) solid var(--table-rule);
}
.facetlist .fname { font-family: var(--font-data); font-size: var(--text-sm); word-break: break-word; min-width: 0; }
/* Un tag1 puede ser un párrafo de 471 caracteres. Cuando lo es, deja de ser
   una etiqueta y se renderiza como lo que es: la voz de alguien. */
.facetlist .prose { font-family: var(--font-voice); font-style: italic; font-size: var(--text-sm); color: var(--text-secondary); }
.facetlist .gloss { grid-column: 1 / -1; font-size: var(--text-xs); color: var(--text-tertiary); font-family: var(--font-data); }
.facetlist .fval { font-family: var(--font-data); font-variant-numeric: var(--numeric-figures); white-space: nowrap; }

.ratings { list-style: none; margin: 0; padding: 0; font-size: var(--text-sm); }
.ratings li {
  display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: baseline;
  padding: var(--space-2) 0; border-bottom: var(--border-width) solid var(--table-rule);
  font-family: var(--font-data);
}
.ratings .v { font-variant-numeric: var(--numeric-figures); font-weight: var(--weight-semibold); min-width: 3.5rem; }
.ratings .t { color: var(--text-secondary); word-break: break-word; flex: 1 1 10rem; }
.ratings .x { margin-left: auto; font-size: var(--text-xs); }
.ratings .rev { text-decoration: line-through; color: var(--text-tertiary); }

/* ---------------------------------------------------------------------------
   EL EMBUDO DEL PERFIL — cinco tarjetas, una por pregunta (agent.html).

   El ESTADO de una tarjeta se lee en el BORDE, no en un color: respondida =
   borde fino sólido; paga y cerrada = punteado, que es la misma gramática de
   ausencia que usan `.nodata`, `.quad.absent` y el sello sin cuadrante — «esto
   todavía no está», nunca «esto está mal»; lo próximo a comprar = borde grueso,
   el mismo peso que `.quad.alert`, porque es lo único que pide una decisión.
   Cero color nuevo: sólo tokens que ya existen.
   ------------------------------------------------------------------------ */
.subject { display: flex; align-items: baseline; gap: var(--space-3); flex-wrap: wrap; }
/* La dirección completa al lado de la corta: `anywhere` y no `break-word`
   porque 42 caracteres sin espacios son UNA palabra para el navegador y su
   ancho mínimo empujaría la página entera fuera de la pantalla. */
.subject .full { font-size: var(--text-xs); color: var(--text-tertiary); overflow-wrap: anywhere; }

.funnel { display: grid; gap: var(--space-4); margin: var(--space-6) 0; }
.step {
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-surface); background: var(--surface-raised);
  padding: var(--density-card);
}
.step[data-state="locked"] { border-style: var(--border-absent-style); border-color: var(--nodata-border); }
.step[data-state="next"] { border-width: var(--border-width-emphasis); border-color: var(--text-primary); }
.step-head { display: flex; align-items: flex-start; gap: var(--space-4); }
.step-n {
  flex: none; width: var(--space-8); height: var(--space-8);
  border-radius: var(--radius-circle); display: grid; place-items: center;
  font-family: var(--font-data); font-size: var(--text-xs);
  background: var(--surface-inverse); color: var(--text-on-inverse);
}
.step[data-state="locked"] .step-n {
  background: transparent; color: var(--nodata-ink);
  border: var(--border-width) var(--border-absent-style) var(--nodata-border);
}
.step-head h2 { font-size: var(--text-xl); line-height: var(--leading-xl); }
.step-head .eyebrow { margin-bottom: var(--space-1); }
.step-head .right {
  margin-left: auto; display: flex; gap: var(--space-2); align-items: center;
  flex-wrap: wrap; justify-content: flex-end;
}
/* El cuerpo se alinea con el título, no con el numeral. En teléfono la sangría
   desaparece: 3rem de 375 px es un décimo del ancho útil. */
.step-body { margin-top: var(--space-4); padding-left: calc(var(--space-8) + var(--space-4)); }
.step-body > p { margin: 0 0 var(--space-3); }
.step-body > p:empty { display: none; }
.step-body .answer { font-size: var(--text-lg); line-height: var(--leading-lg); }
.step-body .answer b { font-family: var(--font-data); font-variant-numeric: var(--numeric-figures); }
@media (max-width: 40rem) { .step-body { padding-left: 0; } }

/* Lo que se recibe al pagar, dicho como celdas punteadas: se ve la FORMA de la
   respuesta en vez de un párrafo que la describe. */
.gets { display: flex; gap: var(--space-3); flex-wrap: wrap; margin: 0 0 var(--space-3); padding: 0; list-style: none; }
.gets:empty { display: none; }
.gets li {
  flex: 1 1 9rem; min-width: 0;
  border: var(--border-width) var(--border-absent-style) var(--nodata-border);
  border-radius: var(--radius-chip); padding: var(--space-2) var(--space-3);
}
.gets .k {
  font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--text-tertiary); display: block;
}
.gets .v { font-family: var(--font-data); color: var(--nodata-ink); display: block; margin-top: var(--space-1); }

/* Acá vivía `.rails`, la fila de seis botones de red que tenía cada bloque pago.
   Se fue con ellos: la elección de riel es UNA y vive en la barra fija, que
   trae su propia hoja (`dn-pay.css`). Lo que la regla defendía —≥44 px de alto
   real, porque cada toque es plata— sigue en pie allá, en `.pb-pay`. */

/* El chip de wallet del header (G14): quién firma, en qué red, y cuánto lleva
   desbloqueado en esta sesión. NO se reusa `.pb-wallet` de la barra aunque se
   vea igual: esa regla se esconde en ≤30rem justamente porque su dato está
   acá arriba, y reusarla dejaría el header sin chip en un teléfono. */
.wchip {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-data); font-size: var(--text-xs); color: var(--text-secondary);
  padding: var(--space-1) var(--space-2);
  border: var(--border-width) solid var(--border-subtle); border-radius: var(--radius-chip);
}
.wchip[hidden] { display: none; }
.wchip .addr { color: var(--text-primary); }
.wchip .net, .wchip .ses { color: var(--text-tertiary); }
/* En pantalla angosta el header ya compite con la marca, el menú y dos
   selectores: queda la dirección, que es lo único que no se puede deducir. */
@media (max-width: 48rem) {
  .wchip .net, .wchip .ses { display: none; }
}

/* El aviso de serie parcial que pinta `viz.js` (`<p class="fine warn-line">`):
   una línea con menos cobertura de fechado que el umbral se dibuja igual, pero
   punteada y con esta frase al lado. Es un aviso, no un veredicto — de ahí el
   mismo token que usa `.chip.warn` y ni un fondo de alarma. */
.warn-line { color: var(--state-caution); }

/* Una fila por identidad (paso 5). */
.ids { list-style: none; margin: 0; padding: 0; }
.ids li {
  display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
  padding: var(--space-3) 0; border-top: var(--border-width) solid var(--table-rule);
}
.ids li:first-child { border-top: 0; }
.ids .id { font-family: var(--font-data); font-weight: var(--weight-semibold); }
.ids .btn { margin-left: auto; }

/* «As an agent»: el mismo paso dicho en HTTP, plegado. El marcador propio
   porque `list-style` en un <summary> no es fiable entre navegadores. */
.asagent { margin-top: var(--space-4); border-top: var(--border-width) solid var(--table-rule); padding-top: var(--space-2); }
.asagent summary {
  cursor: pointer; font-family: var(--font-data); font-size: var(--text-2xs);
  letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--text-tertiary);
  list-style: none; display: inline-flex; gap: var(--space-2); align-items: center;
}
.asagent summary::-webkit-details-marker { display: none; }
.asagent summary::before { content: "\25B8"; }
.asagent[open] summary::before { content: "\25BE"; }
.asagent pre.code { margin: var(--space-3) 0 0; }

/* ---------------------------------------------------------------------------
   PÁGINA PARA AGENTES
   ------------------------------------------------------------------------ */
pre.code {
  overflow-x: auto; background: var(--surface-sunken); color: var(--text-primary);
  border: var(--border-width) solid var(--border-subtle); border-radius: var(--radius-surface);
  padding: var(--space-4); font-family: var(--font-data); font-size: var(--text-xs);
  line-height: var(--leading-sm); margin: var(--space-4) 0;
}
.pricetable td.why { font-size: var(--text-xs); color: var(--text-secondary); font-family: var(--font-body); min-width: 18rem; }
.pricetable td.path { min-width: 16rem; }
.callout {
  border: var(--border-width-emphasis) solid var(--state-caution);
  background: var(--state-caution-surface); color: var(--text-primary);
  border-radius: var(--radius-surface); padding: var(--density-card); margin: var(--space-6) 0;
}
.callout h3 { font-size: var(--text-base); }
.callout ul { margin: var(--space-3) 0 0; padding-left: var(--space-5); font-size: var(--text-sm); color: var(--text-secondary); }
.callout li { margin: var(--space-2) 0; }
.steps { counter-reset: s; list-style: none; margin: 0; padding: 0; }
.steps > li {
  counter-increment: s; position: relative; padding: 0 0 var(--space-6) var(--space-10);
  border-left: var(--border-width) solid var(--border-subtle); margin-left: var(--space-3);
}
.steps > li::before {
  content: counter(s); position: absolute; left: calc(-1 * var(--space-4)); top: 0;
  width: var(--space-8); height: var(--space-8); border-radius: var(--radius-circle);
  background: var(--surface-inverse); color: var(--text-on-inverse);
  font-family: var(--font-data); font-size: var(--text-xs);
  display: grid; place-items: center;
}
.steps > li:last-child { border-left-color: transparent; }
.steps h3 { margin-bottom: var(--space-2); }

footer.foot {
  border-top: var(--border-width) solid var(--border-subtle);
  padding: var(--space-10) 0 var(--space-16); color: var(--text-tertiary);
  font-size: var(--text-xs); line-height: var(--leading-sm);
}
footer.foot a { color: var(--text-secondary); }
footer.foot .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: var(--space-6); }

/* =============================================================================
   RESPONSIVE — EL CONTRATO QUE ESTE ARCHIVO LE EXPONE A LAS CINCO PÁGINAS
   -----------------------------------------------------------------------------
   Verificado en un navegador real a 375 px (teléfono), 768 px (tableta) y
   1440 px (escritorio), no "a ojo": la comprobación que importa es que
   `document.documentElement.scrollWidth <= window.innerWidth` en los tres
   anchos y en las cinco páginas. Si eso falla, hay un elemento empujando el
   <body> y NO se arregla con `overflow-x: hidden` en el body — eso esconde el
   síntoma y deja el contenido cortado sin forma de llegar a él.

   ─────────────────────────────────────────────────────────────────────────
   PATRÓN 1 · TABLA → TARJETAS. Es el único patrón admitido para una tabla.
   Cómo se usa, desde el marcado de cualquier página:

       <div class="scroll cards">          ← 2-5 columnas: apila a 52rem
       <div class="scroll cards cards-wide"> ← 6+ columnas: apila a 68rem
         <table>
           <thead><tr><th data-i18n="exp.th.rank">#</th>…
           <tbody><tr>
             <td data-label="#" data-i18n-label="exp.th.rank">1</td>   ← OBLIGATORIO

   Tres reglas que no son negociables si querés que funcione:
     a. TODO `<td>` con contenido lleva `data-label`, y su texto sale de la
        MISMA clave i18n que su `<th>` (`data-i18n-label`, que app.js resuelve
        en `I18N_ATTRS`). Así el encabezado y la etiqueta de la tarjeta no se
        pueden desincronizar, ni siquiera al cambiar de idioma.
     b. Un `<td>` SIN `data-label` es la escotilla de escape prevista, no un
        olvido: botones, celdas de detalle con `colspan`. Se dibuja a ancho
        completo, sin columna de etiqueta vacía.
     c. `min-width` en una celda es exactamente lo que rompe la tarjeta. Si una
        columna necesita `min-width` en escritorio (`.pricetable td.why`), hay
        que devolverlo a 0 debajo del corte — ya está hecho para las que
        existen hoy; una columna nueva con `min-width` necesita su línea.

   POR QUÉ TARJETAS Y NO `overflow-x: auto`: una tabla de nueve columnas dentro
   de un scroll horizontal en 375 px sólo la lee quien ya sabe qué busca — el
   encabezado se va de pantalla en la segunda columna y cada número queda
   huérfano de su etiqueta. Y por qué DOS cortes: el leaderboard tiene nueve
   columnas y no entra ni en una tableta; las de dos y tres se leen bien hasta
   el teléfono. El bloque de reglas está escrito dos veces a propósito — un
   media query no puede leer una custom property, y las alternativas (style
   queries, una clase puesta por JS) cambian soporte o dejan la página rota sin
   JavaScript por un problema de tipografía.

   ─────────────────────────────────────────────────────────────────────────
   PATRÓN 2 · REJILLAS QUE SE REACOMODAN SOLAS. No llevan media query: son
   `repeat(auto-fit, minmax(X, 1fr))` y el corte lo decide el ancho disponible.
   Las que este archivo ya define, listas para reusar:
       .decomp   minmax(15rem)  tarjeta con cifra grande + su desmentido
       .quads    minmax(15rem)  tarjeta de texto corto
       .foot .cols minmax(14rem)
   Y las que SÍ llevan corte, todas en el MISMO (62rem) para que el archivo
   tenga un corte y no cuatro parecidos:
       .ring-wrap · .verdict · .lookup   2 columnas → 1 (reparto asimétrico,
                                         no se puede deducir del ancho)
       .exhibit                          4 columnas → 2 (con `auto-fit` pasaba
                                         por un tramo de 3 y dejaba huérfana
                                         la cuarta celda; ver su nota)
   En todas, las pistas van `minmax(0, 1fr)` y nunca `1fr`: el mínimo
   automático de un `1fr` es el min-content de su contenido, así que UNA sola
   URL sin espacios adentro estira la columna y con ella la página.

   ─────────────────────────────────────────────────────────────────────────
   PATRÓN 3 · PALABRAS QUE NO SON PALABRAS. Una ruta, un `curl`, un hash o un
   `tag1` de 471 caracteres son UNA palabra para el navegador y su min-content
   dimensiona la columna que los contiene. Va `overflow-wrap: anywhere`, NUNCA
   `break-word`: los dos parten la línea, pero sólo `anywhere` achica el ancho
   mínimo del bloque, que es lo que importa. Ya aplicado en `code.k`, en las
   celdas de tarjeta y en `.exhibit-cell .note`.

   ─────────────────────────────────────────────────────────────────────────
   PATRÓN 4 · EL TEXTO DE LOS SVG NO ESCALA SOLO. `viewBox` + `width: 100%`
   achica la tipografía junto con el dibujo: los 10 px de un tick, en una
   grilla de 660 unidades servida en 343, se ven a 5 px. Abajo se suben las
   unidades de usuario para compensar. Es el bug de responsive más fácil de no
   ver, porque en el escritorio del que lo escribió se ve perfecto. Un SVG
   nuevo con texto adentro necesita su línea en esos dos bloques.
   ============================================================================= */

/* --- 1a. Tablas-tarjeta: corte ancho (leaderboard, 9 columnas) ----------- */
@media (max-width: 68rem) {
  .scroll.cards-wide { overflow: visible; border: 0; border-radius: 0; }
  .scroll.cards-wide table, .scroll.cards-wide tbody { display: block; background: none; }
  /* El thead se oculta a la vista pero NO al lector de pantalla: sigue siendo
     el encabezado de la tabla y sacarlo del árbol rompería la navegación. */
  .scroll.cards-wide thead {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; overflow: hidden; clip-path: inset(50%); border: 0;
  }
  .scroll.cards-wide tbody tr {
    display: block; background: var(--surface-raised);
    border: var(--border-width) solid var(--border-subtle);
    border-radius: var(--radius-surface);
    padding: var(--density-card); margin-bottom: var(--space-3);
  }
  .scroll.cards-wide tbody tr:nth-child(even),
  .scroll.cards-wide tbody tr:hover { background: var(--surface-raised); }
  .scroll.cards-wide tbody td {
    display: grid; grid-template-columns: minmax(0, 38%) minmax(0, 1fr);
    gap: var(--space-3); align-items: baseline; text-align: left;
    padding: var(--space-2) 0; min-width: 0;
    border-bottom: var(--border-width) solid var(--table-rule);
  }
  .scroll.cards-wide tbody td::before {
    content: attr(data-label);
    font-family: var(--font-data); font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label); text-transform: uppercase;
    color: var(--text-tertiary);
  }
  /* Celdas sin encabezado (botones, filas de detalle con colspan): ocupan el
     ancho entero en vez de dejar una columna de etiqueta vacía. */
  .scroll.cards-wide tbody td:not([data-label]) { display: block; }
  /* Una ruta como `/reputation/wallet/{wallet}?snapshot=true` es UNA palabra
     para el navegador: sin esto su ancho mínimo empuja la tarjeta —y con ella
     la página entera— fuera de la pantalla. `anywhere` y no `break-word`
     porque sólo `anywhere` achica también el min-content de la columna. */
  .scroll.cards-wide tbody td { overflow-wrap: anywhere; }
  .scroll.cards-wide tbody td:first-child { padding-top: 0; }
  .scroll.cards-wide tbody td:last-child { padding-bottom: 0; border-bottom: 0; }
}

/* --- 1b. Tablas-tarjeta: corte angosto (2-5 columnas) -------------------- */
@media (max-width: 52rem) {
  .scroll.cards { overflow: visible; border: 0; border-radius: 0; }
  .scroll.cards table, .scroll.cards tbody { display: block; background: none; }
  .scroll.cards thead {
    position: absolute; width: 1px; height: 1px; margin: -1px;
    padding: 0; overflow: hidden; clip-path: inset(50%); border: 0;
  }
  .scroll.cards tbody tr {
    display: block; background: var(--surface-raised);
    border: var(--border-width) solid var(--border-subtle);
    border-radius: var(--radius-surface);
    padding: var(--density-card); margin-bottom: var(--space-3);
  }
  .scroll.cards tbody tr:nth-child(even),
  .scroll.cards tbody tr:hover { background: var(--surface-raised); }
  .scroll.cards tbody td {
    display: grid; grid-template-columns: minmax(0, 38%) minmax(0, 1fr);
    gap: var(--space-3); align-items: baseline; text-align: left;
    padding: var(--space-2) 0; min-width: 0;
    border-bottom: var(--border-width) solid var(--table-rule);
  }
  .scroll.cards tbody td::before {
    content: attr(data-label);
    font-family: var(--font-data); font-size: var(--text-2xs);
    letter-spacing: var(--tracking-label); text-transform: uppercase;
    color: var(--text-tertiary);
  }
  .scroll.cards tbody td:not([data-label]) { display: block; }
  /* Ver la nota del corte ancho: las rutas y los `tag1` son palabras larguísimas. */
  .scroll.cards tbody td { overflow-wrap: anywhere; }
  .scroll.cards tbody td:first-child { padding-top: 0; }
  .scroll.cards tbody td:last-child { padding-bottom: 0; border-bottom: 0; }
  /* Los `min-width` que sirven para que una columna no se aplaste son
     exactamente lo que empuja una tarjeta fuera de la pantalla. */
  .pricetable td.why, .pricetable td.path { min-width: 0; }
  /* El score y su composición dejan de ir en una línea: en 375 px la
     composición sola mide más que la pantalla. */
  .smark { flex-wrap: wrap; }
  .smark .comp { white-space: normal; }
}

/* --- 2. Tipografía de los SVG ------------------------------------------- */
/* Las unidades son del `viewBox` (grilla 660, anillo 620, sparkline 620), no
   píxeles de pantalla: el navegador las multiplica por el factor de escala. */
@media (max-width: 40rem) {
  .grid-svg text, .spark text { font-size: 13px; }
  .ring-svg text { font-size: 14px; }
}
@media (max-width: 27rem) {
  /* 375 px de viewport ⇒ la grilla se sirve a ~343: factor 0.52. 19 unidades
     se ven a 10 px, que es donde vuelve a ser legible. */
  .grid-svg text, .spark text { font-size: 19px; }
  .ring-svg text { font-size: 20px; }
  /* Un punto de radio 4 se ve a 2 px y no se puede tocar. Propiedad geométrica
     de CSS: donde no esté soportada, queda el atributo `r` del marcado. */
  .grid-svg .pt { r: 7px; }
  .grid-svg .pt.named { r: 9px; }
  /* Con la tipografía agrandada, el nombre de un habitante se monta encima de
     la etiqueta de su cuadrante, y el pie de la grilla encima de los dos. Se
     retiran los dos textos accesorios: los cuadrantes —que son la estructura—
     se quedan, y el habitante sigue nombrándose al tocarlo. */
  .grid-svg .name-label, .grid-svg .caption { display: none; }
  /* Lo mismo en el anillo, por el mismo motivo: a 20 unidades las dos líneas
     del pie —separadas por 26— se tocan, y el subtítulo del hub se come los
     radios. Las tres cifras siguen en texto de verdad fuera del dibujo. */
  .ring-svg .ring-caption, .ring-svg .hub-sub { display: none; }
}

/* --- 3. Cromo de página -------------------------------------------------- */
@media (max-width: 48rem) {
  /* La barra superior deja de ser pegajosa: con el nav y los TRES grupos de
     control (piel, claro/oscuro, idioma) envolviendo, ocupa más de un tercio
     de la altura de un teléfono. Pegada, se come la pantalla en cada scroll. */
  .top { position: static; }
}

@media (max-width: 40rem) {
  .wrap { padding: 0 var(--space-4); }
  /* 🔴 EL RITMO VERTICAL DEL TELÉFONO SE APRIETA A PROPÓSITO. En 812 px de alto
     útil, el aire que en escritorio ordena la página acá decide si el primer
     número del ejemplo entra en la primera pantalla o no — y ese número es la
     prueba de los diez segundos. Medido en 375×812: con los valores de antes
     (hero 40/32, section 40, h2 a 1.5rem) el bloque de cifras arrancaba fuera
     de pantalla; con éstos asoma. No es gusto, es el presupuesto de altura. */
  .hero { padding: var(--space-8) 0 var(--space-2); }
  /* --space-6 y no --space-8: el escritorio bajó a --space-8, y sin este paso
     el teléfono perdía su escalón de ritmo respecto de él. */
  section { padding: var(--space-6) 0; }
  h2 { font-size: var(--text-xl); line-height: var(--leading-xl); }
  /* En pantalla angosta el switcher deja de empujarse contra el borde derecho:
     ahí quedaba cortado el último botón del grupo de modo. Arranca a la
     izquierda, envuelve, y ningún control queda fuera de la pantalla. */
  .top .switcher { margin-left: 0; }
  .top .wrap { padding-top: var(--space-2); padding-bottom: var(--space-2); gap: var(--space-3); }
  .nav { gap: var(--space-3); }
  .seg button { padding: var(--space-1) var(--space-2); }
  /* El `dt` de la composición es una etiqueta larga («ratings por
     calificador») contra una columna `auto`: en 375 px se parte letra por
     letra. Se le da un ancho mínimo real y permiso para cortar palabras. */
  .kv { grid-template-columns: minmax(0, 9rem) minmax(0, 1fr); gap: var(--space-2) var(--space-3); }
  .kv dt { overflow-wrap: anywhere; }
  .steps > li { padding-left: var(--space-8); }
  .verdict, .ring-wrap, .lookup { gap: var(--space-6); }
  blockquote.voice { font-size: var(--text-lg); line-height: var(--leading-lg); padding-left: var(--space-4); }
  .proof-cell .v { font-size: var(--text-2xl); }
  .smark.big .num { font-size: var(--text-4xl); }
  /* Las dos columnas ya las fijó el corte de 64rem; acá sólo se aprieta el
     aire. La rejilla 2×2 se mantiene hasta 320 px: `minmax(0, 1fr)` no tiene
     ancho mínimo propio, así que nunca colapsa a una sola columna — que es
     justo lo que convertiría este bloque en una torre de scroll. */
  .exhibit { gap: var(--space-5) var(--space-4); padding: var(--space-5) 0; margin: var(--space-6) 0; }
  /* Las citas de código son lo único que puede empujar la página a lo ancho:
     scrollean adentro de su caja, nunca arrastran al <body>. */
  pre.code { font-size: var(--text-2xs); padding: var(--space-3); }
}
