/* ============================================================
   RPH OS — Design Tokens
   Única fuente de verdad. Nada de valores crudos en componentes.
   ============================================================ */

:root {
  /* ---------- Primitivas: neutros ---------- */
  --n-0:   #ffffff;
  --n-25:  #fafafa;
  --n-50:  #f4f4f3;
  --n-100: #ededec;
  --n-150: #e4e4e2;
  --n-200: #d6d6d3;
  --n-300: #b9b9b5;
  --n-400: #9a9a96;
  --n-500: #787874;
  --n-600: #575754;
  --n-700: #3a3a38;
  --n-800: #212122;
  --n-850: #17171a;
  --n-900: #0f0f11;
  --n-950: #08080a;

  /* ---------- Primitivas: acento ácido (la firma) ---------- */
  --acid-100: #f4ffc9;
  --acid-200: #ecff97;
  --acid-300: #e3ff4f;  /* ← el acento */
  --acid-400: #cdf01f;
  --acid-500: #a8c800;

  /* ---------- Primitivas: señales ---------- */
  --blue-300:  #6d86ff;
  --blue-500:  #2e4bff;
  --blue-700:  #1a2fbd;
  --coral-300: #ff8098;
  --coral-500: #ff3d63;
  --coral-700: #c31e40;
  --amber-500: #ff8a3d;
  --green-500: #1fc47a;
  /* Interact Gateway voice accent — official ui-ref lock (hi-fi / mock).
     Not --ok: that semantic is signal; this is the Orb's one accent. */
  --voice:     #5EFF74;
  --violet-500:#8b5cf6;
  /* Los tres siguientes ya existían dentro del degradado --aurora, pero
     sin nombre. Se sacan como primitivas porque el wallpaper necesita
     nombrarlas una por una; no entra ningún color nuevo al sistema. */
  --teal-300:  #5fc6bb;
  --teal-500:  #18a39b;
  --rose-300:  #e6b8b0;   /* el "rosa polvo" */

  /* ---------- Semánticas: superficie ---------- */
  --canvas:        var(--n-100);
  --canvas-deep:   var(--n-150);
  --surface:       var(--n-0);
  --surface-2:     var(--n-50);
  --surface-sunk:  var(--n-100);
  --surface-invert:var(--n-900);

  /* Papel translúcido: el fondo POR OMISIÓN de .r-card. Recoge el color de
     atrás en vez de taparlo, y a diferencia de --frost-dark (oscuro fijo,
     para vidrio sobre color saturado) este SIGUE AL TEMA: el wallpaper es
     claro en claro y oscuro en cockpit, así que la tinta no se remapea y
     .r-card no entra en la cascada de § 23.

     El LÍMITE es de contraste, no de gusto: --ink-2 —el texto que se lee de
     corrido— no baja de 5.5:1 en el peor punto del tapiz. Con ese piso se
     volvió a barrer la opacidad componiendo la pila real (lienzo → cuatro
     manchas → velo → papel → tinta), y esta vez contra los OCHO climas y no
     solo el de omisión. El peor de todos es `slate`, que es el único con la
     geometría densa y tonos neutros oscuros:

                   --ink-2 en claro, peor punto de los 8 climas
       1.00 → 6.41   (opaco: lo de antes)
       0.74 → 5.90   (lo que estaba)
       0.66 → 5.76
       0.62 → 5.68
       0.58 → 5.61   ←  aquí
       0.54 → 5.54   (5.43 con los corazones: rompe el piso)
       0.50 → 5.46   rompe el piso

     0.58 es el suelo real: es el último escalón que aguanta 5.5 incluso en la
     cuenta pesimista que incluye los corazones de las manchas —que viven
     fuera del viewport por diseño (ver --wall-blooms), o sea que ni se ven—.
     Deja pasar 42% del color de atrás en vez de 26%: 1.6× más tapiz.

     Y el número viejo estaba medido contra un fondo que este wallpaper no
     produce. La nota anterior daba 5.74 en "el corazón de la mancha", lo que
     supone un gris de rgb(207,206,206) detrás de la tarjeta; componiendo el
     CSS de verdad, el corazón de la mancha ámbar da rgb(239,224,213) y lo que
     se ve —la falda— rgb(231,227,229). O sea que el 0.74 no estaba pegado al
     límite: le sobraban cuatro escalones. Los anclajes opacos de la tabla
     vieja (6.41 en claro, 7.32 en cockpit) los reproduce este modelo al
     centésimo, así que lo que cambió es el fondo supuesto, no la aritmética.

     En cockpit la curva va al REVÉS —el wallpaper velado (#08080a) es más
     oscuro que --surface (#17171a)—, así que ahí el vidrio GANA contraste y
     el contraste no es el límite: con 0.62 --ink-2 queda en 6.93 (7.09 con
     0.78, 7.32 opaco). Lo que manda en cockpit es lo mismo de siempre: la
     mancha lleva más tinte (--wall-bloom 48% contra 38%) y se cuela encima
     del texto. Por eso se conserva el desnivel de +0.04 con el que se eligió
     el par original, en vez de igualar los dos temas en un número.
     Ver README § "Deudas del sistema" por lo que esto le cuesta a --ink-3. */
  --surface-frost: rgba(255, 255, 255, 0.58);

  --hairline:       rgba(10, 10, 12, 0.09);
  --hairline-strong:rgba(10, 10, 12, 0.18);

  /* ---------- El tema, a prueba de contexto ----------
     Alias de los semánticos de arriba para lo que flota FUERA de su caja: el
     desplegable de .r-pick vive en la capa superior (`popover`), así que
     visualmente ya no está sobre la superficie que lo contiene, pero en el
     DOM sigue adentro y hereda lo que § 23 haya remapeado — tinta clara y
     pozos de vidrio si el disparador estaba en un .r-grad. Con eso salía
     tinta clara sobre papel del tema.

     Funcionan por una razón del lenguaje, no por truco: el var() de un
     custom property se sustituye en el elemento que lo DECLARA. Declarados
     aquí, en :root, se resuelven contra el --surface / --ink de :root —los
     del TEMA— y ningún descendiente los vuelve a resolver. Por eso basta una
     línea cada uno y no hay que repetirlos en los tres bloques de tema. */
  --theme-surface:      var(--surface);
  --theme-surface-2:    var(--surface-2);
  --theme-surface-sunk: var(--surface-sunk);
  --theme-ink:          var(--ink);
  --theme-ink-2:        var(--ink-2);
  --theme-ink-3:        var(--ink-3);
  --theme-hairline:     var(--hairline);

  /* Glass: paneles sobre foto / mapa / mesh.
     OJO: --glass-sunk / --glass-raise (abajo, en v0.2) NO viven aquí a
     propósito: estos dos sí cambian con el tema, y los pozos de dentro del
     vidrio no pueden cambiar, porque el vidrio siempre lleva tinta clara. */
  --glass-bg:      rgba(255, 255, 255, 0.62);
  --glass-border:  rgba(255, 255, 255, 0.55);
  --glass-blur:    24px;
  --glass-sat:     140%;

  /* ---------- Semánticas: tinta ---------- */
  --ink:      #0b0b0d;   /* titulares, métricas */
  --ink-2:    #5f5f5c;   /* cuerpo secundario */
  /* Labels, ejes, hints. Oscurecido de #97978f a #7d7d76: el anterior
     reprobaba 3:1 en TODOS sus contextos —2.94 sobre papel blanco opaco, o sea
     que la deuda no la trajo el vidrio, ya venía— y el argumento con el que se
     decidió es que un rótulo que hay que forzar la vista para leer no es
     discreto, es inservible: el punto de que sea chico y gris es que no
     compita, no que no se lea.

     El valor se eligió midiendo los CUATRO contextos alcanzables en claro, no
     solo la tarjeta (el primer candidato, #85857e, pasaba sobre tarjeta y
     seguía reprobando con 2.84 en el texto que va directo sobre el wallpaper):

                          #97978f   #85857e   #7d7d76
       papel opaco          2.94      3.71      4.14
       tarjeta de vidrio    2.75      3.47      3.87
       pozo / lienzo        2.51      3.17      3.54
       directo en la falda  2.25      2.84      3.17   ← el que manda

     El corazón de la mancha da 2.37 y se queda fuera de la cuenta a propósito:
     los cuatro corazones viven fuera del viewport por diseño (ver --wall-blooms),
     así que lo peor que se ve es la falda.

     El techo lo pone --ink-2: con #7a7a74 el contraste ENTRE los dos escalones
     baja a 1.48 y dejan de leerse como dos niveles. Con #7d7d76 queda en 1.55.
     Cockpit NO se toca: ahí #75756f ya mide 4.01. */
  --ink-3:    #7d7d76;
  /* TRAMA, no tinta. El gris de las graduaciones, las pistas, las barras en
     reposo y los puntos apagados: algo que se MIRA, nunca texto que se lee. No
     lleva "ink" en el nombre a propósito — la tinta es para leer, y este gris
     no puede leerse: mide 1.79:1 sobre papel blanco y 1.37 sobre el wallpaper.
     Ese es su trabajo, no su defecto.

     Salió de partir --ink-4, que hacía las dos cosas y por eso tenía cinco usos
     de TEXTO reprobando: el placeholder de un campo, los centavos de una
     métrica, la cola de un numeral de puntos, el denominador de una fracción y
     el color de reposo de .r-navlist__i, que es una etiqueta de navegación.
     Esos cinco se fueron a --ink-3. Ver README § "Trama y tinta". */
  --trace:    #c2c2bc;
  /* PUENTE. --ink-4 era el nombre de las dos cosas; hoy `app/` lo usa en 26
     lugares que no son de este paquete. Se queda apuntando a la trama —que es
     lo que casi todos esos usos quieren— para no romperlos. Cuando migren, se
     borra este renglón. En el design system ya no se usa. */
  --ink-4:    var(--trace);
  --ink-invert: #f6f6f4;

  /* ---------- Semánticas: acento y estado ---------- */
  --accent:        var(--acid-300);
  --accent-soft:   var(--acid-100);
  --accent-press:  var(--acid-400);
  --on-accent:     #101208;   /* siempre tinta oscura sobre ácido */

  /* ---------- El ácido como TINTA: la regla, dicha en un token ----------
     El ácido es tinta solo sobre oscuro. Sobre claro es relleno o marca:
     medido, --accent-press sobre papel blanco da 1.31:1 —el número grande
     se lee más débil que su propia etiqueta en gris—, y no hay escalón más
     oscuro que lo salve sin dejar de ser el color de la marca.

     De ahí este par, que SIEMPRE viaja junto:
     · --accent-ink   qué tinta usa el dato acentuado (.r-t-accent,
                      .r-lcd--accent). En cockpit, ácido. En claro, la
                      tinta normal.
     · --accent-mark  si se enciende la marca ácida chica que carga el
                      acento cuando el número ya no puede ser ácido
                      (.r-metric--mark). Donde el número ES ácido va en
                      `none`: dos acentos en la misma vista es justo lo
                      que el sistema prohíbe.

     Los `-invert` son para la única superficie que se voltea con el tema
     (.r-card--invert: oscura en claro, clara en cockpit), donde el switch
     va exactamente al revés.

     OJO: aquí se resuelven contra el tema GLOBAL, porque el var() de un
     custom property se sustituye en el elemento que lo declara. Las
     superficies que son oscuras o claras por su cuenta —gradiente, vidrio,
     panel, papel sobre color— vuelven a declarar el par en ron.css. */
  --accent-ink:         var(--ink);
  --accent-mark:        block;
  --accent-ink-invert:  var(--accent-press);
  --accent-mark-invert: none;

  /* Geometría de la marca cuando va junto a un numeral dot-matrix, que es
     un SVG y no da em del que escalar (junto a un número de tipo sí
     escala, en em).

     Y por eso hay CUATRO tamaños y no uno: en tema claro el numeral va en
     tinta y la marca es lo ÚNICO que carga el acento de la vista, así que
     su presencia tiene que crecer con la cifra. Con un solo valor fijo la
     métrica principal de un `.r-lcdrow--xl` —90px de alto— quedaba
     acentuada por una barra de 5×22 (110px²) en claro, contra el numeral
     entero en ácido en cockpit: los dos temas dejaban de tener la misma
     presencia. El alto se declara POR MODIFICADOR DE RENGLÓN, el mismo
     camino de doble puerta que --lcd-gap-*, y por la misma razón: el
     renglón no puede leer el --lcd-dot de su hijo.

     La proporción es la del base y se conserva: alto = 0.55 del numeral
     (22/40), ancho = 0.125 (5/40). Alturas del numeral por tamaño:
     30 / 40 / 60 / 90px (7 renglones de punto, ver drawLcd). */
  --amark-w: 5px;
  --amark-h: 22px;
  --amark-w-sm: 4px;     --amark-h-sm: 16.5px;
  --amark-w-lg: 7.5px;   --amark-h-lg: 33px;
  --amark-w-xl: 11px;    --amark-h-xl: 49.5px;

  --info:     var(--blue-500);
  --info-bg:  rgba(46, 75, 255, 0.10);
  --ok:       var(--green-500);
  --ok-bg:    rgba(31, 196, 122, 0.12);
  --warn:     var(--amber-500);
  --warn-bg:  rgba(255, 138, 61, 0.13);
  --crit:     var(--coral-500);
  --crit-bg:  rgba(255, 61, 99, 0.11);
  /* Tinta sobre un relleno crit, exactamente igual que --on-accent sobre el
     ácido: SIEMPRE oscura, y no cambia con el tema porque el relleno tampoco.
     Medido sobre --crit (#ff3d63): blanco da 3.57:1 —por debajo del 4.5:1 que
     pide un texto de 12px— y esta tinta da 5.4:1. */
  --on-crit:  #16070a;
  --idle:     var(--ink-4);
  --idle-bg:  rgba(10, 10, 12, 0.05);

  /* ---------- Alto de los controles ----------
     Los dos escalones que el sistema de verdad usa, y existen porque el número
     estaba a mano en tres lugares (`.r-icon-btn--sm`, `.r-confirm--sm` y
     `.r-capture`) sin ningún token que lo dijera. Eso ya costó: para alinear las
     cinco cabeceras del Kanban con el bote de «Listo» hubo que reservar con
     `--s-8` (32px) y pagar 4px de holgura inventada, porque `--cf-h` es una
     variable PRIVADA de `.r-confirm` y un padre no la puede leer.

     Y la trampa que esto cierra: el token más cercano a 28px es `--r-xl`, que
     es un RADIO. Quien buscara «el 28 del sistema» lo iba a encontrar ahí, y
     habría atado el alto de un control a la curvatura de una esquina — dos
     cosas que no tienen ninguna razón para cambiar juntas. */
  --ctl-sm: 28px;   /* icon-btn--sm, confirm--sm, capture */
  --ctl-md: 36px;   /* el escalón por omisión de .r-confirm */

  /* ---------- Radios ---------- */
  --r-xs:   6px;
  --r-sm:   10px;
  --r-md:   14px;
  --r-lg:   20px;   /* default de tarjeta */
  --r-xl:   28px;
  --r-2xl:  36px;
  --r-pill: 999px;

  /* ---------- Espacio (base 4) ---------- */
  --s-1:  4px;
  --s-2:  8px;
  --s-3:  12px;
  --s-4:  16px;
  --s-5:  20px;
  --s-6:  24px;
  --s-8:  32px;
  --s-10: 40px;
  --s-12: 48px;
  --s-16: 64px;
  --s-20: 80px;
  --s-24: 96px;

  /* ---------- Tipografía ----------
     Tres roles. El orden va de la cara del referente (comercial),
     a los sustitutos libres, al fallback del sistema.
     Ver FONTS.md: hoy en esta máquina resuelve a Helvetica Neue. */

  /* Display: titulares y métricas. Se usa SIEMPRE en Light (300). */
  --font-display: "PP Neue Montreal", "Neue Montreal", "Aeonik",
                  "TWK Everett", "Satoshi", "Switzer", "Geist",
                  "Helvetica Neue", -apple-system, BlinkMacSystemFont, sans-serif;

  /* Cuerpo e interfaz */
  --font-sans: "PP Neue Montreal", "Neue Montreal", "Aeonik", "TWK Everett",
               "Söhne", "Suisse Int'l", "Satoshi", "Switzer", "General Sans",
               "Instrument Sans", "Geist", "Helvetica Neue",
               -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* Datos, IDs, código */
  --font-mono: "PP Supply Mono", "Berkeley Mono", "SF Mono", ui-monospace,
               "JetBrains Mono", "Geist Mono", "Roboto Mono", monospace;

  /* Compensación de fallback: Helvetica Neue necesita un poco más de
     tracking negativo en display para acercarse al referente. */
  --tr-display-fallback: -0.045em;

  --fw-light:  300;
  --fw-book:   400;
  --fw-medium: 500;
  --fw-bold:   600;

  /* Escala. Display siempre light + tracking negativo. */
  --t-display:   clamp(2.75rem, 7.5vw, 6.5rem);
  --t-h1:        clamp(1.9rem, 3.4vw, 3rem);
  --t-h2:        clamp(1.375rem, 2vw, 1.75rem);
  --t-h3:        1.125rem;
  --t-metric:    clamp(2.25rem, 4.5vw, 3.5rem);
  --t-metric-sm: 1.75rem;
  /* Tamaño de la unidad diminuta CUANDO no hay número del que escalar.
     Dentro de .r-metric la unidad sigue midiendo 0.3em del número (es la
     firma del sistema); fuera de ahí —una unidad en un .r-lcdrow, donde el
     numeral es un SVG y el contenedor es texto de cuerpo— 0.3em la dejaba en
     4.5px, o sea invisible. Es el mismo 0.875rem que showcase.html ya venía
     poniendo a mano en ese caso. */
  --t-unit:      0.875rem;
  --t-body:      0.9375rem;
  --t-body-sm:   0.875rem;
  --t-label:     0.75rem;
  --t-micro:     0.6875rem;

  --lh-tight: 0.92;
  --lh-snug:  1.15;
  --lh-body:  1.5;

  --tr-display: -0.04em;
  --tr-tight:   -0.02em;
  --tr-normal:  0;
  --tr-micro:   0.07em;

  /* ---------- Elevación (mínima, casi todo es hairline) ---------- */
  --e-0: none;
  --e-1: 0 1px 2px rgba(10, 10, 12, 0.04);
  --e-2: 0 4px 16px -4px rgba(10, 10, 12, 0.10);
  --e-3: 0 18px 48px -12px rgba(10, 10, 12, 0.22);

  /* ---------- Movimiento ---------- */
  --ease:      cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-in:   cubic-bezier(0.4, 0, 1, 1);
  /* Salida larga: entra rápido y se acomoda. Para lo que aparece. */
  --ease-out:  cubic-bezier(0.16, 1, 0.3, 1);
  /* Rebote corto: solo para lo que se desplaza y "aterriza"
     (la pastilla del rail, un panel que entra). Nunca en opacidad. */
  --ease-spring: cubic-bezier(0.22, 1.35, 0.36, 1);
  --d-fast: 120ms;
  --d-base: 220ms;
  --d-slow: 380ms;
  --d-slower: 560ms;
  /* Retardo entre hermanos de una lista que se revela en cascada. */
  --stagger: 26ms;
  /* PERIODO, no duración de gesto: --d-* miden lo que tarda una cosa en
     llegar; esto mide cada cuánto vuelve. Sus consumidores son el punto
     vivo de .r-status--live, el cursor de .r-caret y el resplandor de
     .r-beam. */
  --d-pulse:      2s;
  --d-pulse-fast: 1.1s;
  /* El resplandor respira más lento que un punto que parpadea: un latido
     de 2s en una caja grande se lee como parpadeo nervioso, no como
     "está trabajando". Y la vuelta completa del punto caliente es todavía
     más lenta, porque recorre el perímetro entero.

     La vuelta tiene DOS periodos, y no son dos gustos: son las dos cosas
     que el mismo dibujo tiene que decir. En reposo el aura es AMBIENTE —la
     deriva de un faro, algo que está encendido— y mientras el modelo
     escribe es URGENCIA. Sigue siendo periodo y no duración de gesto:
     mide cada cuánto la luz vuelve al mismo canto.

     Los números salen de los dos límites, que están en lados opuestos:

       · 8.4s = 43°/s. Es lo más lento que todavía se lee como movimiento:
         en un vistazo de un segundo la luz recorre casi medio canto. Por
         debajo de ~30°/s el punto caliente parece clavado y el aura deja
         de ser luz para ser una mancha teñida.
       · 3.1s = 116°/s. Es rápido de leer sin comparar contra nada —2.7×
         el reposo, que es la diferencia que se nota de reojo— y todavía
         no es spinner: por debajo de 2s la vuelta se vuelve un giro
         nervioso y compite con el contenido que el modelo está
         escribiendo, que es justo lo que hay que mirar.

     Ninguno de los dos es múltiplo del respiro (3.4s) a propósito: si lo
     fueran, vuelta y latido se sincronizarían cada pocos ciclos y el
     conjunto se leería mecánico. 8.4/3.4 = 2.47 y 3.1/3.4 = 0.91. */
  --d-breathe: 3.4s;
  --d-orbit:      8.4s;
  --d-orbit-fast: 3.1s;

  /* ---------- El aura de trabajo (.r-beam) ----------
     La intensidad vive aquí para que un botón de 38px y una caja de 720
     se vean con los MISMOS números: si cada consumidor calibrara la
     fuerza a ojo, cada superficie tendría su propio "está pensando".

     Son las dos paradas de CADA fuente de luz — el corazón y la falda—,
     y se leen como las del wallpaper porque son la misma receta: un
     radial cuyo centro queda FUERA de la caja, del que solo se ve la
     falda entrando por el canto. Por eso el aura muere antes del centro
     sin que nadie la recorte.

     El color NO está aquí: sale de las primitivas de la aurora
     (--teal-500, --rose-300, --violet-500) en ron.css. */
  --beam-core:  76%;
  --beam-skirt: 24%;

  /* La ganancia es la idea de --accent-ink dicha para la LUZ: un aura
     desaturada se lee sola sobre oscuro y se apaga sobre papel. En claro
     sube; en cockpit vale 1. */
  --beam-gain: 1.3;

  /* ---------- Escala de capas ----------
     Los estratos se nombran por su PAPEL, no por su número: el número es un
     detalle de implementación y es justo lo que no hay que andar comparando a
     mano en cuatro archivos. Los estratos salen de lo que el OS de verdad
     apila hoy (os.css, relations.css, workspace.css, invoke.css), con hueco de
     10 entre uno y otro para lo que venga.

     Cuál usar es una pregunta de "¿encima de qué tengo que estar?":

       --z-below    la capa ambiental, debajo de todo (.r-wall)
       --z-sticky   una barra que se pega al hacer scroll
       --z-flyout   el panel flotante que CONVIVE con el contenido (.r-flyout)
       --z-chrome   el chasis del OS: el rail, los orbes, el dock
       --z-scrim    el velo que atenúa lo de atrás
       --z-panel    lo que el velo va a dejar ver: panel lateral, hoja
       --z-float    un recuadro flotante que sale de un panel
       --z-menu     un desplegable o popup anclado a su disparador
       --z-toast    un aviso, que le gana a todo lo anterior
       --z-top      el último recurso, cuando de verdad hay que ganarle a todo

     ── Por qué --z-flyout va DEBAJO del chasis, y no encima ────────────────
     Esta escala suponía algo que dejó de ser cierto: que los paneles entran
     por un canto y el chasis vive del otro, así que nunca se cruzan. Con el
     chasis a la DERECHA y el panel saliendo de atrás de él, se cruzan — y el
     síntoma es concreto: el nombre que se revela en un orbe se veía FANTASMA
     a través del papel translúcido del panel, porque el chasis (40) quedaba
     debajo del panel (60).

     La salida obvia —subir el chasis por encima de --z-panel— rompe algo
     peor: el velo (--z-scrim, 50) dejaría de cubrirlo, y un modal con el
     chasis flotando encima no es un modal.

     El arreglo es del otro lado, y dice algo verdadero: EL PANEL FLOTANTE NO
     ES --z-panel. Ese estrato es "lo que el velo va a dejar ver": una hoja
     modal, algo que toma la pantalla. El panel de módulo no toma nada —el
     centro sigue vivo, nada se vela, y se puede seguir escribiendo en el
     prompt mientras está abierto—, así que pertenece al mismo mundo que el
     contenido, por encima de una barra pegajosa y por debajo del chasis.

     Con eso la regla vuelve a ser una sola frase: EL DE MÁS AFUERA MANDA.
     conversación → panel flotante → chasis → velo → hoja modal.

     UN ESTRATO ES PARA APILARSE CONTRA OTROS COMPONENTES. El z-index interno
     de una pieza —la pastilla de .r-seg en 0 y sus ítems en 1, el panel de
     .r-confirm en 3— NO sale de aquí: vive dentro de su propio contexto de
     apilamiento y no compite con nadie.

     ── Y la capa superior, que no está en esta escala a propósito ──────────
     Un elemento con `popover` (o un <dialog> modal) se promueve a la CAPA
     SUPERIOR del navegador, que está por encima de todo el documento y donde
     el z-index simplemente no aplica. Entre dos elementos de la capa superior
     manda el orden de apertura, no un número.

     Eso no es un hueco de la escala, es la salida de emergencia: si algo
     tiene que estar encima de un velo, o escapar de un ancestro que recorta,
     `popover` lo resuelve de raíz y ya no hay estrato que mantener. Es lo que
     usa .r-pick, y es también por qué su lista aparece encima del velo de su
     propio panel sin pedir permiso — el bug clásico de "el panel salió debajo
     de su propio velo" no puede ocurrir ahí.
     Regla práctica: si se puede abrir y cerrar, `popover`. Si vive puesto,
     estrato. */
  --z-below:  -1;
  --z-sticky:  30;
  --z-flyout:  35;
  --z-chrome:  40;
  --z-scrim:   50;
  --z-panel:   60;
  --z-float:   70;
  --z-menu:    80;
  --z-toast:   90;
  --z-top:    100;

  /* ---------- El velo ----------
     Un solo grado de atenuación para todo el OS. Estaba escrito crudo en dos
     archivos y el segundo copió el valor del primero a mano para que el OS no
     tuviera dos velos distintos: copiar un valor para mantener consistencia es
     la señal de que faltaba el token.

     Va sobre la primitiva --n-950, que NO cambia con el tema: el velo es negro
     en los dos, porque en claro atenúa y en cockpit hunde, y las dos cosas se
     hacen con negro.

     --scrim-blur es un TERCIO del desenfoque de vidrio, y el tercio está
     medido, no elegido: con el desenfoque completo el OS de atrás desaparece
     y se pierde el sentido de que la caja llegó ENCIMA DE DONDE ESTABAS —el
     velo deja de ser velo y se vuelve fondo—. Y sin desenfoque, en cockpit
     un negro al 42% sobre un lienzo casi negro no se lee como modal: no hay
     suficiente diferencia entre atenuado y normal. El tercio es el punto donde
     el fondo se sigue reconociendo y la caja se siente encima. */
  --scrim-o:    42%;
  --scrim:      color-mix(in srgb, var(--n-950) var(--scrim-o), transparent);
  --scrim-blur: calc(var(--glass-blur) / 3);

  /* ---------- Grid / medidas ---------- */
  --measure:   64ch;
  --page-max:  1240px;
  --stroke:    1px;
  --stroke-2:  1.5px;

  /* Alto del divisor vertical (.r-divider--v). Es el alto de una BARRA de
     controles, no el del contenido que separa: un divisor que creciera con la
     fila competiría con el hairline de la tarjeta. Los 18px son los que los
     tres consumidores del OS ya escribían a mano; --tall es para una barra con
     controles de 38px. */
  --div-v-h:       18px;
  --div-v-h-tall:  24px;
}

/* ============================================================
   v0.2 — tokens de las piezas nuevas.
   Casi todos derivan de los semánticos de arriba, así que
   responden solos al cambio de tema.
   ============================================================ */

:root {
  /* ---------- Dot-matrix (numerales LED) ----------
     OJO: estos van a `currentColor`, no a var(--ink). La sustitución
     de var() ocurre en el elemento que DECLARA, así que un
     `--lcd-on: var(--ink)` aquí congelaría el color del tema global y
     no seguiría la tinta de la tarjeta donde se usa. */
  --lcd-dot:       4px;    /* diámetro del punto */
  --lcd-gap:       2px;    /* separación entre puntos */
  --lcd-on:        currentColor;
  --lcd-off:       transparent;
  --lcd-ghost:     rgba(128, 128, 128, 0.22);  /* puntos apagados visibles */

  /* Separación interna de cada tamaño, y el aire que le toca a la unidad
     al lado del numeral (.r-lcdrow). Son tokens y no valores dentro de
     .r-lcd--lg / --xl porque los necesitan DOS reglas: el numeral, que
     los usa como su propio --lcd-gap, y el renglón, que no puede leer el
     --lcd-gap de su hijo y tiene que volver a nombrarlo. --lcd-dot no
     entra: nadie más que el numeral lo necesita. */
  --lcd-gap-sm:    1.5px;
  --lcd-gap-lg:    3px;
  --lcd-gap-xl:    4.5px;
  --lcd-air:       3;      /* la unidad se separa 3× la separación interna */

  /* ---------- Gráfica ---------- */
  /* Misma razón: currentColor + opacidad en vez de var(--ink). */
  --chart-h:       200px;
  --chart-line:    currentColor;
  --chart-cone:    var(--acid-300);   /* la cuña de proyección */
  --chart-dot:     var(--acid-400);
  --chart-grid-o:  0.16;   /* opacidad de la retícula punteada */
  --chart-band-o:  0.42;   /* opacidad de las etiquetas de banda */

  /* ---------- Regla graduada ---------- */
  --scale-minor:      4px;   /* paso del tick menor */
  --scale-minor-h:    6px;
  --scale-major-every: 5;    /* un tick mayor cada N menores */
  --scale-major-h:    13px;
  --scale-ink:        color-mix(in srgb, currentColor 34%, transparent);

  /* ---------- Superficies de gradiente saturado ---------- */
  /* Se usan como fondo de una tarjeta con glass encima, nunca
     detrás de texto de lectura. */
  --grad-magenta: linear-gradient(168deg, #3d1040 0%, #a3106b 46%, #d81e7a 78%, #ff5fa2 100%);
  --grad-violet:  linear-gradient(168deg, #14113d 0%, #3b2bb0 52%, #6d5bff 100%);
  --grad-ocean:   linear-gradient(168deg, #06171c 0%, #0b4a5c 55%, #1c8fa8 100%);
  --grad-ember:   linear-gradient(168deg, #2b0f06 0%, #a13c0c 55%, #ff8a3d 100%);
  --grad-slate:   linear-gradient(155deg, #101214 0%, #191c1f 48%, #0c0e10 100%);

  /* ---------- Gradiente difuso vertical ----------
     Los cinco de arriba son lineales: en una tarjeta alta y angosta la
     línea del degradado se vuelve casi vertical y el primer tramo —el
     oscuro— se come la mitad de la tarjeta, así que el módulo se ve negro
     y el color solo llega al pie. Este es radial y de baja saturación:
     manchas difusas sobre base neutra, sin ramp de arriba a abajo, así que
     lee igual con cualquier proporción.

     Dos decisiones que no se ven:
     · Los tonos son del repertorio del wallpaper (teal, azul acero, rosa
       polvo, violeta), no colores nuevos, y van al 26–44% para que sea
       niebla y no un segundo acento.
     · La base va en primitivas (--n-900/--n-950), que NO cambian con el
       tema: es justo lo que le falta a --grad-slate, que en cockpit queda
       a dos puntos de --surface (#191c1f vs #17171a) y la banda se vuelve
       invisible sobre una tarjeta.
     El paso intermedio de cada mancha es el que evita que se vea el canto,
     igual que en el wallpaper. */
  --grad-mist:
    radial-gradient(80% 48% at 14% 4%,
      color-mix(in srgb, var(--teal-500) 44%, transparent) 0%,
      color-mix(in srgb, var(--teal-500) 16%, transparent) 44%,
      transparent 76%),
    radial-gradient(72% 44% at 106% 28%,
      color-mix(in srgb, var(--blue-300) 34%, transparent) 0%,
      color-mix(in srgb, var(--blue-300) 12%, transparent) 46%,
      transparent 78%),
    radial-gradient(86% 46% at 80% 98%,
      color-mix(in srgb, var(--rose-300) 34%, transparent) 0%,
      color-mix(in srgb, var(--rose-300) 12%, transparent) 46%,
      transparent 78%),
    radial-gradient(64% 40% at -10% 72%,
      color-mix(in srgb, var(--violet-500) 26%, transparent) 0%,
      color-mix(in srgb, var(--violet-500) 10%, transparent) 46%,
      transparent 74%),
    linear-gradient(180deg, var(--n-900) 0%, var(--n-950) 100%);

  /* Tinta sobre gradiente: siempre clara, el gradiente siempre es oscuro */
  --on-grad:      #ffffff;
  --on-grad-2:    rgba(255, 255, 255, 0.66);
  --on-grad-3:    rgba(255, 255, 255, 0.40);
  --on-grad-line: rgba(255, 255, 255, 0.16);
  /* El hermano fuerte de --on-grad-line. Existe porque --hairline-strong
     (el borde de .r-btn, .r-icon-btn, .r-chip--outline) también se remapea
     sobre color: sin él, en tema claro salía una línea OSCURA encima de un
     gradiente oscuro. Misma proporción que --hairline → --hairline-strong. */
  --on-grad-line-strong: rgba(255, 255, 255, 0.32);

  /* ---------- Papel sobre color ----------
     Una tarjeta que flota sobre aurora, gradiente o mapa es SIEMPRE
     blanca, en los dos temas: el fondo ya aporta el color, y una
     tarjeta oscura sobre color se lee como un agujero. Estos valores
     no cambian con el tema a propósito. */
  --fixed-surface: #ffffff;
  --fixed-sunk:    #f1f1ef;
  --fixed-ink:     #0b0b0d;
  --fixed-ink-2:   #5f5f5c;
  --fixed-ink-3:   #8d8d86;
  --fixed-ink-4:   #c2c2bc;
  --fixed-hairline: rgba(10, 10, 12, 0.10);
  /* El borde de los controles que caen en ese papel (.r-btn, .r-icon-btn,
     .r-chip--outline, .r-count). Sin este token, en cockpit heredaban
     --hairline-strong del tema —blanco al 22%— sobre papel blanco fijo, o
     sea un botón sin borde. Es el mismo valor que --hairline-strong del
     tema claro, porque el papel es el del tema claro. */
  --fixed-hairline-strong: rgba(10, 10, 12, 0.18);

  /* ---------- Aurora (fondo ambiental) ---------- */
  /* Vertical y de tono largo: teal profundo → cian → rosa polvo →
     gris pálido. Las manchas difusas van en ron.css para que el
     degradado no muestre bandas. */
  --aurora: linear-gradient(
    177deg,
    #0b4f57 0%, #10767a 14%, #18a39b 30%,
    #5fc6bb 45%, #a9c3bc 58%,
    #dcb4ac 72%, #e6b8b0 82%, #cdd5d1 100%
  );
  --aurora-warm: linear-gradient(
    177deg,
    #3a1030 0%, #7c1f4e 22%, #c34a6a 46%,
    #e88f7e 68%, #f0c9a8 86%, #e4d9cc 100%
  );
  --aurora-cool: linear-gradient(
    177deg,
    #08102e 0%, #16295f 24%, #2f5aa8 48%,
    #6d9ad6 70%, #b6c9e6 88%, #dfe6ee 100%
  );

  /* ---------- Wallpaper ambiental (.r-wall) ----------
     Una geometría de manchas para las ocho variantes (la cerrada de `slate`
     es la excepción, abajo): lo que cambia por variante son los cuatro tonos
     (--wall-a…--wall-d, se asignan en ron.css) y lo que cambia por tema son
     la fuerza del tinte y el velo.

     Los --grad-* y --aurora* NO se reúsan aquí a propósito: son gradientes
     saturados para tarjetas con glass encima. A pantalla completa y detrás
     de texto de lectura harían justo lo que el sistema prohíbe. Lo que sí
     se hereda de ellos es el repertorio de tonos.

     El velo lleva el color del lienzo del tema, así que un mismo tono se
     aclara en claro y se apaga en cockpit: es imposible que salga un fondo
     claro en cockpit o al revés. Es también la palanca de legibilidad. */
  --wall-bloom:   38%;    /* tinte en el corazón de la mancha */
  --wall-bloom-2: 15%;    /* … a media caída: sin este paso se ve el canto */
  --wall-veil:    0.66;   /* 0–1, cuánto lienzo se vuelve a poner encima */

  /* Par denso: para la variante que tiene que leerse como niebla y no como
     insinuación (hoy solo `slate`, que si no se confunde con no tener nada).
     Se activa remapeando --wall-bloom en la paleta, no con otra regla. */
  --wall-bloom-dense:   54%;
  --wall-bloom-2-dense: 26%;

  /* Velo de miniatura. Una muestra de 40px no lleva texto encima, así que no
     tiene nada que proteger: con el velo del wallpaper las ocho variantes se
     ven iguales a ese tamaño (probado) y el selector deja de servir. El que
     pinte una miniatura remapea --wall-veil a este valor. Mismo número en
     los dos temas: el velo ya lleva el color del lienzo. */
  --wall-veil-preview: 0.18;

  /* ---------- LA RAMPA DEL CIELO ----------
     Seis paradas en anillo, una vuelta de día. No son seis climas entre los
     que se elige: son los VÉRTICES de una curva por la que el fondo pasa solo
     —el consumidor dice en qué parada va y cuánto lleva recorrido hacia la
     siguiente (`data-sky` + `--sky-t`, ron.css § 27b) y la mezcla la hace el
     CSS—. Por eso viven aquí y no en el JS: la paleta se lee y se afina en el
     mismo lugar que las demás, y ron.js no escribe un solo color.

     Existe porque el fondo dejó de ser una opción. Robbie: «quiero que el
     sistema no te dé opciones de apariencia de fondo. Siempre es un fondo
     vivo. Claro en las mañanas y oscuro por las noches». Un selector de ocho
     climas es exactamente lo contrario, y dos estados tampoco alcanzan: lo
     que se pidió es una curva.

     Cero tonos nuevos: las veinticuatro casillas salen de las primitivas que
     el wallpaper ya usaba. Lo que cambia por hora es CUÁLES, no cuántas.

     El anillo, y qué dice cada parada:
       noche     frío y profundo — azul y violeta, sin nada cálido
       alba      lo más cálido de la subida: rosa polvo y ámbar
       mañana    la paleta de la casa (ámbar, azul acero, rosa, verde)
       mediodía  lo más frío: azul acero y teal, la luz alta
       tarde     vuelve el ámbar, todavía con azul
       ocaso     lo más cálido del día: ámbar, coral y violeta

     EL PISO DE CONTRASTE VALE EN TODA LA CURVA, no en dos puntos. Medido
     componiendo la pila real —lienzo → mancha → velo → papel → tinta— en las
     seis paradas × seis puntos de mezcla × dos temas, o sea 72 muestras por
     tema. `--ink-2` sobre PAPEL, que es el piso de 5.5 que declara
     `--surface-frost` más abajo:

                              peor de las paradas   peor de las SEIS
                              que ese tema pinta    (tema forzado a mano)
       claro                        5.64                  5.57
       cockpit                      6.74                  6.74

     La segunda columna existe porque el tema se puede forzar —una prueba, una
     comprobación— y ahí el anillo entero es alcanzable en los dos temas. Las
     dos aguantan. Si una parada nueva no aguanta, se dobla la curva ahí —se
     elige otra primitiva—, no se baja el piso.

     Y LO QUE ESTA CUENTA NO CUBRE, dicho con número para que nadie lo suponga
     cubierto: el texto que va DIRECTO sobre el tapiz, sin papel encima. Esa
     superficie la estrenó el rediseño —la conversación del centro— y no es la
     que este piso mide. En claro, `--ink-2` ahí:

       lienzo pelón, sin mancha            5.47
       falda de la mancha (lo que se ve)   5.09 … 5.22
       corazón (fuera del viewport)        4.54 … 4.86

     No lo trae la rampa: la paleta de la casa —que es exactamente la parada
     `mañana`— da 5.22 en la falda, o sea lo mismo. Y el lienzo pelón ya mide
     5.47 sin tapiz de por medio, así que el que no llega es el par
     `--ink-2` / `--canvas`, no el fondo. Subirlo cambia el color de todo el
     texto secundario del OS, y eso es decisión de Robbie, no de este pase. */
  --sky-noche-a:    var(--blue-500);
  --sky-noche-b:    var(--violet-500);
  --sky-noche-c:    var(--blue-500);
  --sky-noche-d:    var(--teal-500);

  --sky-alba-a:     var(--rose-300);
  --sky-alba-b:     var(--amber-500);
  --sky-alba-c:     var(--violet-500);
  --sky-alba-d:     var(--rose-300);

  --sky-manana-a:   var(--amber-500);
  --sky-manana-b:   var(--blue-300);
  --sky-manana-c:   var(--rose-300);
  --sky-manana-d:   var(--green-500);

  --sky-mediodia-a: var(--blue-300);
  --sky-mediodia-b: var(--teal-300);
  --sky-mediodia-c: var(--blue-300);
  --sky-mediodia-d: var(--teal-500);

  --sky-tarde-a:    var(--amber-500);
  --sky-tarde-b:    var(--blue-300);
  --sky-tarde-c:    var(--amber-500);
  --sky-tarde-d:    var(--teal-300);

  --sky-ocaso-a:    var(--amber-500);
  --sky-ocaso-b:    var(--coral-500);
  --sky-ocaso-c:    var(--violet-500);
  --sky-ocaso-d:    var(--rose-300);

  /* Cuánto se lleva recorrido hacia la parada siguiente. Lo escribe el reloj
     (`OS.sky`, app/app.js) y es lo ÚNICO que se mueve entre parada y parada.
     El valor por omisión es 0, o sea la parada tal cual: una página sin reloj
     —el showcase, un artifact— se ve como el vértice y nunca a medio mezclar. */
  --sky-t: 0;

  /* ---------- Punteados ----------
     El largo del guion se elige por el largo del TRAZO, no por el
     componente. --map-dash (6px 5px, abajo) es el de una ruta de mapa:
     en un trazo de 24–58px apenas alcanza para dos guiones y deja de
     leerse como punteado. De ahí las dos escalas cortas, que son la misma
     familia con otro tamaño.

     Son tokens, y no valores sueltos en la regla, porque a cada uno lo
     piden DOS componentes distintos. El `1 7` de .r-wave__base se queda
     local a propósito: tiene un solo usuario. */
  --dash-fine:     2 4;    /* textura: retícula de gráfica, anillos de telaraña */
  --dash-sm:       3 3;    /* trazo corto: cable de grafo, contorno de referencia */

  /* ---------- Mapa ---------- */
  --map-path:      #ffffff;
  --map-path-w:    2px;
  --map-dash:      6px 5px;   /* el miembro largo de la familia de punteados */
  --map-fence:     rgba(255, 255, 255, 0.55);

  /* ---------- Vidrio mate ----------
     El translúcido del sistema, y desde hoy el único: sin brillo
     especular ni canto refractado. La familia brillante (.r-liquid) se
     borró por no tener un solo consumidor en la app. */
  --frost-blur:  26px;
  --frost-dark:  rgba(14, 14, 16, 0.62);
  --frost-light: rgba(255, 255, 255, 0.30);
  --frost-solid: rgba(14, 14, 16, 0.86);
  --frost-line:  rgba(255, 255, 255, 0.13);
  --bubble-bg:   rgba(255, 255, 255, 0.86);

  /* Pozo y relieve DENTRO del vidrio, y también sobre color (ver § 23).
     Un .r-input dentro de vidrio pedía --surface-sunk y le llegaba el del
     tema: pastilla gris opaca en claro (con tinta blanca encima, ilegible)
     y pastilla negra en cockpit. Los dos valores son blanco translúcido y
     NO cambian con el tema, igual que --on-grad: el vidrio siempre lleva
     tinta clara, así que su pozo siempre es luz. --raise es el estado
     levantado (hover, foco, control activo). */
  --glass-sunk:  rgba(255, 255, 255, 0.12);
  --glass-raise: rgba(255, 255, 255, 0.24);

  /* ---------- Scatter ---------- */
  --scatter-h:   84px;

  /* ---------- Telaraña (radar) ----------
     Geometría y texturas. Los COLORES del contorno no viven aquí a
     propósito: el nivel se dibuja con --accent-ink, que se vuelve a
     declarar por superficie (tema, gradiente, vidrio, papel sobre
     color), y el var() de un custom property se sustituye en el
     elemento que lo DECLARA — puesto aquí congelaría el valor del tema
     global. Misma razón que --lcd-on. El relleno sí es token: el ácido
     como RELLENO funciona igual en los dos temas, es como TINTA donde
     se degrada. */
  --radar-ar:          1;      /* alto = ancho: una telaraña es cuadrada */
  --radar-gutter:      0.19;   /* fracción del lado reservada a etiquetas */
  --radar-pad:         8px;    /* aire entre el anillo exterior y la etiqueta */
  --radar-fill:        var(--acid-300);
  --radar-fill-o:      0.24;
  --radar-grid-o:      0.16;   /* anillos punteados; igual que --chart-grid-o */
  --radar-edge-o:      0.22;   /* el anillo exterior, un paso más firme */
  --radar-spoke-o:     0.12;   /* radios */
  --radar-target-o:    0.46;   /* contorno del objetivo: referencia, no dato */
  --radar-node:        4px;    /* lado del cuadrito de vértice */
  --radar-label-o:     0.62;   /* etiquetas de eje */
  /* Los punteados salen de la familia --dash-*, no de un valor propio:
     los anillos son la misma textura que la retícula de .r-chart. */

  /* ---------- Grafo ego (red de conocidos) ----------
     Geometría y texturas. Los COLORES del nodo que manda no viven aquí,
     por la misma razón que en la telaraña: se dibuja con --accent-ink,
     que se vuelve a declarar por superficie, y el var() de un custom
     property se sustituye en el elemento que lo DECLARA.

     El alto NO es cuadrado como la telaraña: el aire de sobra a los
     lados es donde caben las etiquetas, que son horizontales. El radio
     se calcula del lado corto, así que ese aire no deforma nada — una
     elipse sí lo haría, y ahí la distancia al centro dejaría de ser un
     dato: el mismo peso mediría distinto según el rumbo. */
  --graph-ar:          1.3;
  --graph-pad:         8px;    /* aire entre el nodo y su etiqueta */
  --graph-inner:       0.34;   /* fracción del radio donde cae la cercanía 1 */
  --graph-node:        5px;    /* radio del nodo de trama */
  --graph-node-foco:   8px;    /* radio del peso alto y del centro */
  --graph-gap:         5px;    /* aire mínimo entre dos nodos */
  --graph-ego-fill:    var(--acid-300);  /* el relleno del nodo que manda;
                                  el ácido como RELLENO funciona igual en los
                                  dos temas, es como TINTA donde se degrada */
  --graph-fill-o:      0.20;   /* relleno del nodo de trama */
  --graph-ring-o:      0.42;   /* aro del nodo de trama */
  --graph-foco-o:      0.34;   /* relleno del peso alto */
  --graph-ido-o:       0.52;   /* aro punteado del nodo que ya no existe */
  --graph-edge-o:      0.22;   /* cable entre pares: la información nueva */
  --graph-spoke-o:     0.10;   /* cable al centro: redundante con el radio */
  --graph-label-o:     0.62;   /* etiquetas; el mismo valor que --radar-label-o,
                                  porque es la misma etiqueta de dato */

  /* ============================================================
     EL CHASIS AI-FIRST — orbes, panel flotante y campo de color.
     Los tres son una sola geometría, así que sus números viven juntos:
     la reserva del chasis sale del orbe, el panel se detiene en la
     reserva y el campo de color se dibuja contra la caja del panel. Si
     estuvieran repartidos, mover el orbe dejaría al panel encimado.
     ============================================================ */

  /* ---------- Orbes (.r-orbs / .r-orb) ----------
     Aquí vivía --orb-size: 64px, de la familia "orbes glossy": una bola con
     brillo especular y sombra interior que se usaba para una estadística. Se
     borró junto con su componente (ron.css § 20), por el mismo argumento y
     el mismo precedente que .r-liquid: CERO consumidores en `app/` —tres
     muestras, todas en el propio showcase— y un reflejo especular que
     contradice el no-negociable de "vidrio: una sola familia, y es mate".
     El nombre se reusa para lo que el OS sí tiene: los botones del chasis.

     44 y no 38 (la caja de .r-rail__i): el orbe vive SUELTO sobre el tapiz,
     sin cápsula que le preste presencia, y es el objetivo táctil de más
     afuera de la pantalla. */
  --orb-size:   44px;
  --orb-gap:    var(--s-3);   /* aire ENTRE orbes */
  --orb-inset:  var(--s-5);   /* del canto derecho de la ventana */
  --orb-air:    var(--s-4);   /* del orbe al canto del panel */
  /* Ajustes no es un módulo, y eso se dice con AIRE y no con un hairline: una
     línea entre dos elementos sueltos no separa nada —no hay cápsula que
     dividir— y se lee como una raya suelta en el fondo. El separado queda a
     --orb-gap + esto = 32px del anterior. */
  --orb-apart:  var(--s-5);
  /* Techo del nombre revelado, no medida: cada orbe mide el suyo. 17ch es
     "Intelligence Hub" completo, el más largo del OS; lo que no quepa se
     recorta con elipsis y sigue entero en `title` y en la etiqueta
     accesible — la misma salida de .r-rail__name y de las etiquetas de
     .r-radar. */
  --orb-name:   17ch;

  /* Lo que el chasis le quita a la ventana. UN número derivado, no tres
     copiados: si el orbe crece, el panel se corre solo. */
  --orb-reserve: calc(var(--orb-size) + var(--orb-inset) + var(--orb-air));

  /* ---------- Panel flotante (.r-flyout) ----------
     Flota: sus cuatro esquinas se despegan del canto y su lado derecho se
     detiene ANTES de los orbes. */
  --flyout-gap:  var(--s-5);            /* del canto superior, inferior y … */
  --flyout-rail: var(--orb-reserve);    /* … del canto derecho de la ventana */
  --flyout-air:  var(--s-4);            /* del panel a la columna del centro */
  --flyout-pad:  var(--s-6);            /* relleno izquierdo del centro */
  /* Ancho de hoy. Lo escribe ron.js en el HOST; este es el respaldo para
     que el panel exista aunque el script no haya corrido. */
  --flyout-w:    480px;

  /* Lo que el shell gasta en algo que no es ni conversación ni panel. Es lo
     que ron.js resta del ancho de la ventana para saber cuánto le queda al
     panel una vez servido el piso del centro, y por eso se REGISTRA como
     <length> en ron.css: un custom property sin registrar devuelve el texto
     `calc(…)` a getComputedStyle, no un número. */
  --flyout-chrome: calc(var(--flyout-rail) + var(--flyout-air) + var(--flyout-pad));

  /* ---------- El alta del panel (.r-fab, ron.css § 33d) ----------
     El botón redondo que da de alta la entidad del módulo, en la esquina
     inferior derecha del panel.

     LA TALLA ES LA --lg DEL BOTÓN REDONDO (46), no la --xl del invocador (56),
     y se eligió midiendo en el panel más angosto del OS: a 360 de panel, 56 se
     come el 16% del renglón; 46 sigue arriba de los 44 que pide un objetivo de
     toque y deja el renglón legible. Tampoco es la --lg por casualidad: es la
     única talla del set que cabe entre "esto no es una acción de fila" (36) y
     "esto es el invocador" (56), o sea que la escala ya existía.

     --fab-gap se paga DOS veces: separa el botón del canto del panel y, sumado
     a la talla, es la reserva que el cuerpo se pone abajo para que el último
     renglón de una lista scrolleada hasta el fondo no quede debajo del botón. */
  --fab-size: 46px;
  --fab-gap:  var(--s-4);
  /* Lo que la vista tiene que dejar libre abajo para que el último renglón de
     una lista scrolleada hasta el fondo no quede debajo del botón. Es un token
     y no una suma repetida porque lo consumen CINCO módulos, cada uno en su
     propio archivo — y el porqué de que no lo pueda poner el cuerpo del panel
     está escrito en ron.css § 33d.

     Es --fab-gap DOS veces y no una: el mismo aire que el botón le pide al
     canto del panel se lo pide al contenido. Con un --s-2 de propina en vez del
     segundo hueco, medido en Intelligence con nueve notas, el último renglón
     terminaba EXACTAMENTE en el canto de arriba del botón —sin encimarse, pero
     pegado— y una fila que toca un botón se lee como una fila tapada. */
  --fab-reserve: calc(var(--fab-gap) * 2 + var(--fab-size));

  /* ============================================================
     COMPANION — la figura viva con la que se le habla al agente
     (.r-companion, ron.css § 34)

     ── LÁPIDA: LOS TOKENS DEL ORBE DE VOZ ──────────────────────────────
     Aquí vivían --voice-size, --voice-ring, --voice-strength y
     --voice-follow: la talla del orbe, el alcance del aro reactivo al
     volumen, la fuerza de su aura y la constante de tiempo con la que el
     aro seguía tu voz. Se fueron el 2026-08-26 con `.r-voice`, cuando el
     orbe se volvió una FIGURA que morfea entre estados en vez de un
     círculo con un aro.

     De los cuatro sobrevive UNO, y hasta ése cambió de valor:
       --voice-size     → --comp-size, y después subió de 44 a 88 cuando la
                          criatura salió de la caja de prompt (ver LA TALLA)
       --voice-strength → --comp-strength un rato, con el mismo .62 y la misma
                          medición, y se fue con el aura el mismo día. Ver
                          «Y NO HAY NINGÚN COLOR MÁS», abajo.
       --voice-follow   → se fue entero. El aro era lo que seguía la voz;
                          hoy lo que sigue la voz es el CUERPO, y el
                          suavizado lo hace `app/voz.js` sobre el nivel
                          antes de entregarlo. No quedaba remate visual
                          que temporizar.
       --voice-ring     → se fue entero. No hay aro.

     ── POR QUÉ SIGUE VIVIENDO JUNTO AL CHASIS ──────────────────────────
     Por la razón de geometría de siempre: la figura ocupa la caja de un
     HERMANO del orbe de navegación —la misma talla, el mismo aire— y lo
     que la distingue es que en vez de invertirse cuando está elegida,
     está VIVA. Si su talla saliera de otro lado, el día que --orb-size se
     mueva el prompt quedaría con dos círculos de distinto tamaño.

     ── EL COLOR ES TINTA, Y EL ÁCIDO ES INFORMACIÓN ────────────────────
     El companion es TINTA. No hereda nada de la paleta de doce colores
     del proyecto del que sale su geometría (ver la atribución en
     `ron.js § 34`): un personaje de color propio en una pantalla
     monocroma se vuelve el objeto más brillante del OS, y ese lugar es
     del dato que manda.

     De ahí que el ácido aparezca en UN estado y sólo en uno —`needs`, la
     figura requiere tu atención—. Ésa es toda la razón de la decisión: con
     el ácido reservado a un estado, **el color ES información**. Si el
     companion brillara en verde al pensar y en violeta al hablar, el
     no-negociable de «un solo acento por vista» estaría roto todo el día,
     porque esta pieza está en pantalla siempre.

     El cuerpo va en --ink-2 en reposo y sube a --ink cuando está
     ocupado, que es exactamente la transición que el orbe de voz ya hacía
     con su icono: «engaged» se lee más presente, no de otro color.

     LOS OJOS son la superficie de atrás, no un blanco fijo: --surface
     sigue al tema en la dirección correcta sin una regla por tema (claro
     → cuerpo gris con ojos claros; cockpit → cuerpo claro con ojos
     oscuros), y dentro de .r-frost la cascada de § 23 lo remapea sola.
     Con el cuerpo en ácido los ojos van en --fixed-ink, por la regla de
     CLAUDE.md: el ácido es claro en los dos temas, así que --ink daría
     blanco sobre casi-blanco.

     ── Y NO HAY NINGÚN COLOR MÁS, PORQUE NO HAY AURA ───────────────────
     Robbie, 2026-08-26, viendo el showcase: «no pongas auras alrededor del
     compañón. Que el compañón no tenga ninguna aura alrededor.»

     LÁPIDA: aquí se declaraban los CINCO colores del aura de esta pieza —
     --teal-500 escuchando, --ok pensando (heredado de data-beam-busy),
     --violet-500 hablando, --rose-300 ejecutando— y --comp-strength: .62,
     que era la fuerza de ese resplandor y estaba medida contra la de la caja
     de prompt (.26/.5 en os.css). Todo eso se fue.

     El argumento es el de la pieza llevado hasta el final, y lo escribió esta
     misma sección: el orbe de voz murió porque «el aro era lo único que decía
     con el cuerpo y todo lo demás lo decía con color», y la primera versión
     del companion dejó CINCO colores de aura. Con la forma diciendo los siete
     estados, el aura es redundancia de color en la pieza que está en pantalla
     más tiempo que ninguna. `needs` ya iba sin ella con el criterio correcto
     —«el cuerpo entero ya es la señal»—; extenderlo a los otros seis es la
     misma frase. Detalle completo en ron.css § 34d.

     Lo que NO se fue: el aura de la CAJA DE PROMPT. `.os-composer` lleva
     .r-beam con data-beam-on puesto en el marcado y ahí se queda —CLAUDE.md:
     «en la caja de prompt vive en reposo, porque es su identidad»—. Son dos
     elementos hermanos a 16px de distancia, así que es fácil confundirlos:
     éste es el bloque de la criatura y aquél vive en app/os.css.

     Los cuatro colores siguen existiendo como primitivas (--teal-500,
     --rose-300, --violet-500) porque son de .r-beam, que sigue entero con sus
     dos consumidores: la caja de prompt y .r-run. Lo que se fue es un
     consumidor, no la terna. */
  /* ── LA TALLA, Y ES LA EXCEPCIÓN A «EL DEFAULT ES CHICO» ─────────────
     Robbie, 2026-08-26, viendo la primera versión en pantalla: «el companion
     no tiene que estar dentro de la cajita. Tiene que estar afuera y tiene
     que ser más grande.»

     Y tiene razón por una causa estructural, no de gusto: mientras la pieza
     era un botón DENTRO del renglón del prompt, su talla la dictaba el vecino
     —44px, lo mismo que `.os-send`— y eso era correcto, porque en una fila de
     controles el que se sale de la fila está mal alineado. Fuera de la caja
     ya no hay fila que respetar, así que la pregunta cambia: cuánto mide la
     CARA del OS. La regla de `CLAUDE.md` es «el tamaño se gana», y ésta se lo
     gana: es lo único en pantalla que dice permanentemente en qué estado está
     el sistema, y a 44px sus dos ojos son cuatro píxeles cada uno.

     88 es la talla de MUELLE: criatura atracada abajo-izquierda mientras hay
     sesión de voz. Cabe con aire en el canal libre de la ventana mínima del
     shell (960px, `shell/src/main.rs`) y no tapa la conversación.

     `--comp-size-casa` (176, el doble) es la talla de REPOSO en Home, CoO
     2026-09-06: companion grande y centrado, voz primero. El tamaño se lo
     gana — es la cara del OS y el gesto que abre la conversación.

     `--comp-size-min` sigue siendo el piso de ojos-como-cara (`--orb-size`).
     Ya no elige el layout: Casa no mete la criatura en el renglón del
     prompt. Queda por si otra superficie la necesita en una fila. */
  --comp-size:      88px;
  --comp-size-casa: 176px;
  --comp-size-min:  var(--orb-size);
  --comp-body:      var(--ink-2);
  --comp-body-on:   var(--ink);
  --comp-eye:       var(--surface);
  --comp-alert:     var(--accent);
  --comp-alert-eye: var(--fixed-ink);
  /* EL RESPIRO DE REPOSO, y es un PERIODO —cada cuánto vuelve— no una
     duración de gesto. Va con la familia de --d-orbit / --d-pulse por eso,
     aunque viva aquí junto a la pieza que lo consume.

     4.6s y NO --d-breathe (3.4s), a propósito y por el mismo argumento que
     el README pone para --d-orbit: si el respiro de la figura fuera igual
     —o múltiplo— del respiro del aura que la rodea, los dos se
     sincronizarían y el conjunto se leería mecánico. Un ser vivo respira a
     su ritmo, no al de su sombra. Y más lento que el aura porque el cuerpo
     mueve GEOMETRÍA: a 3.4s la figura se lee inquieta al lado de un
     textarea donde alguien está escribiendo. */
  --comp-breath:    4.6s;

  /* ---------- Campo de color (.r-iris) ----------
     El vidrio necesita color detrás; sin nada que recoger no es vidrio, es
     un rectángulo gris. `CLAUDE.md` llama --iris a ese color y el token ya
     no existía: se borró con la familia brillante .r-liquid, donde era un
     aro cromático. Vuelve como lo que de verdad hace falta —un CAMPO, no un
     aro— y con la receta que el sistema ya declara: las manchas del tapiz
     (--wall-blooms) más el velo del lienzo, ancladas a una caja en vez de al
     viewport.

     El velo es más bajo que el del tapiz (0.66) porque aquí no protege
     texto: lo que va encima es el vidrio del panel, que ya aporta su
     opacidad. El número está medido a través de ese vidrio, con la misma
     cuenta pesimista con la que se eligió --surface-frost (pila completa:
     lienzo → manchas → velo → panel → tinta), y contra el mismo piso que
     fija tokens.css — --ink-2 no baja de 5.5:1:

       velo    claro (ink-2 · ink-3)     cockpit (ink-2 · ink-3)
       0.34        5.47  ·  3.54            6.04  ·  3.19    ← rompe el piso
       0.38        5.50  ·  3.56            6.15  ·  3.24
       0.42        5.53  ·  3.58            6.26  ·  3.30    ← aquí
       0.50        5.60  ·  3.62            6.48  ·  3.42

     La curva es casi plana: de 0.34 a 0.50 se ganan 0.13 puntos y se pierde
     color visible. Se toma el velo MÁS BAJO que aguanta el piso con margen —
     más velo no compra legibilidad, apaga el campo. */
  --iris-veil: 0.42;

  /* ---------- EL GRADO DELGADO del mismo vidrio mate ----------
     Robbie, sobre Relaciones: «probemos con este módulo ponerle liquid glass.
     Que sea como transparente el lienzo, con un toque mate, no totalmente
     glass». Lo que eso NO es: la familia brillante. El reflejo especular se
     borró del sistema (ron.css § 22) y no vuelve. Lo que sí es: la MISMA
     familia mate, un grado más delgada, sobre un campo con más color.

     Son cuatro tokens y no uno porque el vidrio no es solo el papel: si el
     panel adelgaza y sus pozos siguen opacos, los campos se leen como parches
     pegados encima. Los cuatro se encienden juntos (ron.css § 33a).

     ── Por qué el velo del campo baja MÁS que el papel del panel ──────────
     El color que llega al ojo es el producto de los dos: (1 − velo) × (1 − papel).
     Hoy son 0.58 × 0.42 = 0.244 en claro. Medido, bajar el VELO cuesta menos
     contraste por unidad de color ganado que adelgazar el PAPEL —el velo lleva
     el color del lienzo, así que quitarlo no oscurece el punto limpio, solo
     destapa la mancha— así que el reparto no es simétrico a propósito.
     Con estos valores el fondo vivo presente sube a 0.72 × 0.54 = 0.389 en
     claro (1.6×) y 0.80 × 0.56 = 0.448 en cockpit (2.0×).

     ── EL PISO, y por qué NO son los 5.5 de --surface-frost ───────────────
     Medido componiendo la pila real (lienzo → manchas → velo → papel → capa
     interior → tinta) en las seis paradas de la rampa × cuatro puntos de mezcla
     × dos temas, peor punto de la caja del panel. Tres superficies, porque el
     texto de este panel cae en tres sitios distintos:

                        el vidrio        el pozo         el relieve
                     ink-2   ink-3    ink-2  ink-3     ink-2  ink-3
       claro, hoy     5.58    3.61     5.47   3.54      5.82   3.77
       claro, delg.   5.24    3.39     4.69   3.04      5.02   3.25
       cockpit, hoy   6.85    3.61     7.83   4.13      6.58   3.47
       cockpit, delg. 6.06    3.20     6.83   3.60      5.73   3.02

     (--ink no es el límite en ninguna: 16.1 lo peor en claro, 13.7 en cockpit.
     Y el piso del campo con FOCO no se mueve, porque no adelgaza: 6.41 en claro
     y 7.32 en cockpit, los mismos de hoy.)

     Los 5.5 de --surface-frost NO se sostienen aquí, y conviene decirlo con el
     número en la mano en vez de con un adjetivo: el único ajuste que los aguanta
     deja el cambio INVISIBLE. Con el papel intacto y el velo en 0.42 → 0.34, el
     recorrido de color del panel pasa de 12.7 a 14.5 de 255. Nadie distingue eso.
     Con este grado pasa a 20.3 en claro y 36.1 en cockpit (2.0×).

     Así que el piso de este grado es el de la norma y no el margen de la casa:
     4.5:1 para --ink-2 —que es lo que WCAG AA pide para el texto de cuerpo y de
     etiqueta, y todos los tamaños de este panel son de ésos— y 3:1 para --ink-3,
     que es el mismo umbral que el sistema le exige a lo secundario en la sección
     «Trama y tinta». Las doce casillas de arriba lo cumplen; la más apretada es
     --ink-2 dentro de un pozo en claro, 4.69, y ahí es donde hay que mirar si un
     día algo se siente pálido.

     LO QUE ESO CUESTA, dicho sin adorno: 4.69 queda por debajo del 5.09 que el
     OS ya sirve en su peor superficie de lectura —el texto directo sobre el
     tapiz, en la rampa del cielo más arriba—. Es la única cifra de este grado
     que empeora el peor caso del OS, y por eso el grado es un PILOTO de un
     módulo y no el default del panel.

     Las dos perillas para recuperarla, medidas, por si Robbie la quiere de
     vuelta: --iris-veil-thin a 0.42 sube el pozo a 4.80 (y el recorrido baja a
     16.4), y --surface-frost-thin a 0.54 con el velo en 0.42 lo pone en 5.06
     (recorrido 14.0, o sea otra vez casi el de hoy). No se baja el piso: se
     sube el velo o se engrosa el papel. */
  --surface-frost-thin: rgba(255, 255, 255, 0.46);
  --iris-veil-thin:     0.28;

  /* ---- El pozo y el relieve DE DENTRO del vidrio delgado ----
     Son dos cosas distintas y por eso NO se hacen con el mismo tinte. Se
     intentó primero con uno solo —«el pozo es tinta a baja fuerza», que en
     cockpit lo dejaba más claro que el panel, como --glass-sunk dentro de
     .r-frost— y la medición lo tumbó: un pozo de tinta al 6% deja el
     placeholder de un campo (--ink-3) en 2.68:1 en cockpit, contra 4.13 hoy.
     Aclarar el fondo debajo de tinta clara es empujar el texto al piso.

     · EL POZO ES SOMBRA. Va sobre --n-950, que no cambia con el tema, y es
       exactamente el argumento con el que --scrim se decidió: «en claro atenúa
       y en cockpit hunde, y las dos cosas se hacen con negro». Un hueco es un
       hueco en los dos temas —--surface-sunk también es más oscuro que
       --surface en los dos— y de paso, en cockpit, oscurecer el fondo SUBE el
       contraste de la tinta clara en vez de bajarlo.
     · EL RELIEVE ES TINTA a baja fuerza, y ahí sí una sola declaración sirve
       para los dos temas, porque --surface-2 se mueve en las dos direcciones:
       en claro es #fafafa (más oscuro que el papel) y en cockpit #212122 (más
       claro que el papel). Eso es justo «un paso hacia la tinta».

     El relieve pesa mucho menos que el pozo, al revés que --glass-raise: ahí
     los dos son tinte blanco sobre vidrio oscuro fijo; aquí el relieve es el
     hover de una fila (un 2% en claro) y el pozo el fondo de un campo (un 7%).

     El relieve va en :root y no por tema porque el var() de un custom property
     se sustituye en el elemento que lo DECLARA: aquí se resuelve contra el
     --ink del tema y no hay que repetirlo en los tres bloques. El límite es el
     de siempre —dentro de una superficie que remapee --ink no seguiría al
     remapeo—, y el panel flotante no es una de ésas.

     El 2% del relieve lo fijó la medición, no el parecido con --surface-2: con
     3% —el paso que el tema usa en claro— el hover de una fila deja los rótulos
     de --ink-3 en 2.96:1 en cockpit. A cambio el escalón en cockpit es más corto
     que el del tema (+4 de 255 contra +10), y se acepta: un hover es de lo que
     se revela, no de lo que grita. */
  --surface-sunk-frost:  color-mix(in srgb, var(--n-950) 5%, transparent);
  --surface-raise-frost: color-mix(in srgb, var(--ink) 2%, transparent);

  /* SIN AUREOLA: el campo termina exactamente donde termina el panel.
     La primera versión lo sacaba 32px por arriba, abajo y a la derecha —para
     que el color se metiera bajo los orbes y el conjunto se leyera como una
     sola pieza— y 8 a la izquierda. Se veía bien en la maqueta y mal en el OS:
     lo que en una captura era "una pieza" en pantalla era una mancha de color
     asomándose por detrás de las esquinas del panel, y Robbie la señaló ahí,
     en las esquinas, en cuanto lo abrió.
     El campo se queda —es lo que vuelve vidrio al vidrio, sin él el panel es
     un rectángulo gris— pero confinado a su caja. Cero, no borrado: la
     aureola sigue siendo una perilla, y volver a abrirla es cambiar un token. */
  --iris-halo:      0px;
  --iris-halo-near: 0px;
}

/* ============================================================
   Geometría del wallpaper.

   Va en su propia regla, y con `[data-wall]` y `[data-sky]` además de
   `:root`, por una razón que no se ve: los var() de un custom property se
   sustituyen en el elemento que lo DECLARA, no donde se usa. Si estas dos
   solo se declararan en :root, un elemento con su propio data-wall o
   data-sky (una miniatura de 40px en el catálogo, por ejemplo) heredaría el
   gradiente ya resuelto con los tonos de :root y saldrían todas las
   miniaturas iguales. Declarándolas también aquí, cada portador del atributo
   vuelve a resolver la receta con SUS tonos. Medido en carne propia con las
   seis paradas de la rampa: sin `[data-sky]` en este selector las seis
   muestras salían del mismo ámbar.

   Nada de filter: blur() —esto es a pantalla completa—: la difuminada la da
   la caída del propio radial-gradient, ayudada por el paso intermedio.
   ============================================================ */

:root,
[data-wall],
[data-sky] {
  /* UNA SOLA LUZ, y desde abajo. Antes eran cuatro manchas, una por esquina
     (-12% -16%, 114% 14%, 88% 118%, -16% 92%), y el efecto que producían no era
     atmósfera: eran cuatro focos identificables, que es justo lo que se ve
     cuando el ojo puede contarlos. Decisión de Robbie, 2026-08-22: «que sea
     mucho más uniforme y que solo salga de abajo o arriba o en medio. Una sola
     luz.»

     ABAJO Y AL CENTRO, y esa parte sí es una decisión de diseño: una luz que
     sube del canto inferior se lee como horizonte, y el horizonte es lo que
     hace legible que el color cambie con la hora — al amanecer y al atardecer
     el sol está justo ahí. Desde arriba se habría leído como plafón, que no
     tiene hora.

     LO QUE SE CONSERVA, porque era lo importante de la receta vieja: el corazón
     queda FUERA del viewport (at 50% 118%). Lo que se ve es la falda, no el
     centro, así que el tinte más fuerte nunca cae donde va el texto. Subir el
     velo casi no cambia el contraste (probado: 0.68 → 0.80 mueve --ink-3 de
     2.13 a 2.27); la geometría sí.

     Y LOS CUATRO TONOS SIGUEN VIVOS, que es lo que evita que las ocho paletas
     se aplanen en una: en vez de cuatro manchas de un tono cada una, son
     cuatro paradas del MISMO resplandor —c en el corazón, luego b, luego a, y
     d desvaneciéndose—. Sigue siendo una luz, con una posición y una caída; la
     deriva de matiz del centro al borde es lo que tiene un cielo de verdad
     cerca del horizonte, y a estas opacidades es lo bastante suave para que no
     se banden. Alternativa descartada: quedarse con un solo tono. Era más
     simple y dejaba dos tokens muertos en cada uno de los ocho climas, y las
     paletas se parecían demasiado entre sí. */
  --wall-blooms:
    radial-gradient(150% 84% at 50% 118%,
      color-mix(in srgb, var(--wall-c) var(--wall-bloom), transparent) 0%,
      color-mix(in srgb, var(--wall-b) var(--wall-bloom-2), transparent) 34%,
      color-mix(in srgb, var(--wall-a) var(--wall-bloom-2), transparent) 56%,
      color-mix(in srgb, var(--wall-d) calc(var(--wall-bloom-2) / 2), transparent) 72%,
      transparent 92%);

  /* Geometría cerrada: la MISMA luz de abajo, más apretada y más pegada al
     canto. Da un banco de niebla definido en vez de un lavado general y deja el
     centro del lienzo aún más limpio. Una paleta la pide remapeando
     --wall-blooms (lo hace `slate`); no hay regla de pintado aparte.

     Se convirtió a una sola luz junto con la geometría ancha, y a propósito:
     dejarla en cuatro esquinas habría hecho que `slate` fuera el único clima
     con cuatro focos, o sea el bug de antes sobreviviendo en una paleta. */
  --wall-blooms-dense:
    radial-gradient(96% 56% at 50% 110%,
      color-mix(in srgb, var(--wall-c) var(--wall-bloom), transparent) 0%,
      color-mix(in srgb, var(--wall-b) var(--wall-bloom-2), transparent) 36%,
      color-mix(in srgb, var(--wall-a) var(--wall-bloom-2), transparent) 58%,
      color-mix(in srgb, var(--wall-d) calc(var(--wall-bloom-2) / 2), transparent) 74%,
      transparent 94%);
}

/* ============================================================
   DARK — el modo "cockpit". Se activa por sistema o por
   [data-theme="dark"] (el toggle siempre gana).
   ============================================================ */

@media (prefers-color-scheme: dark) {
  :root {
    --canvas:        var(--n-950);
    --canvas-deep:   #030304;
    --surface:       var(--n-850);
    --surface-2:     var(--n-800);
    --surface-sunk:  var(--n-900);
    --surface-invert:var(--n-0);
    /* Cockpit: el papel translúcido es oscuro, y un poco MÁS opaco que en
       claro. Medido: el wallpaper en cockpit lleva más tinte (--wall-bloom
       48% contra 38%) y la mancha se cuela encima del texto antes que en
       claro. El desnivel de +0.04 se conserva al bajar los dos: aquí el
       contraste no es el límite —el tapiz velado es más oscuro que
       --surface, así que el vidrio GANA contraste: --ink-2 queda en 6.93 con
       0.62, contra 7.32 opaco— y el límite sigue siendo el tinte. */
    --surface-frost: rgba(23, 23, 26, 0.62);
    /* El grado delgado adelgaza MÁS en cockpit (0.44 contra 0.46 en claro) y su
       campo lleva MENOS velo (0.20 contra 0.28), y las dos cosas salen del
       mismo hecho: aquí el tapiz velado (#08080a) es más oscuro que el papel
       (#17171a), así que el vidrio GANA contraste al adelgazar en vez de
       perderlo. Con 6.10 en --ink-2 sobra un punto entero sobre el piso de este
       grado, y lo que estorba en cockpit no es el contraste: es que un panel
       casi negro sobre un lienzo casi negro no se lee como vidrio. */
    --surface-frost-thin: rgba(23, 23, 26, 0.44);
    --iris-veil-thin:     0.20;
    /* El pozo pide MUCHO más sombra aquí, y no es un gusto: el paso que hay que
       reproducir es el de --surface (#17171a) a --surface-sunk (#0f0f11), o sea
       8 de 255 sobre un panel que ya está en ~20. El 5% de claro movería un punto
       y el campo dejaría de tener fondo. Con 30% el hueco se ve y el contraste de
       la tinta clara SUBE en vez de bajar: --ink-2 pasa de 6.06 sobre el vidrio a
       6.83 sobre el pozo, y --ink-3 de 3.20 a 3.60. */
    --surface-sunk-frost: color-mix(in srgb, var(--n-950) 30%, transparent);

    --hairline:       rgba(255, 255, 255, 0.10);
    --hairline-strong:rgba(255, 255, 255, 0.22);

    --glass-bg:     rgba(22, 22, 25, 0.58);
    --glass-border: rgba(255, 255, 255, 0.12);

    --ink:      #f6f6f4;
    --ink-2:    #a6a6a1;
    --ink-3:    #75756f;
    --trace:    #4a4a46;
    --ink-4:    var(--trace);
    --ink-invert: #0b0b0d;

    /* Cockpit: la superficie es oscura, así que el ácido SÍ es tinta y el
       número se queda con él; la marca se apaga para no ser un segundo
       acento. En la tarjeta invertida (que aquí es clara) va al revés. */
    --accent-ink:         var(--accent-press);
    --accent-mark:        none;
    --accent-ink-invert:  var(--ink-invert);
    --accent-mark-invert: block;

    /* Sobre cockpit el aura se lee sola: sin ganancia. */
    --beam-gain: 1;

    --info-bg:  rgba(109, 134, 255, 0.14);
    --ok-bg:    rgba(31, 196, 122, 0.14);
    --warn-bg:  rgba(255, 138, 61, 0.15);
    --crit-bg:  rgba(255, 61, 99, 0.15);
    --idle-bg:  rgba(255, 255, 255, 0.06);

    /* Cockpit: la mancha aguanta más tinte porque el negro se la come, y el
       velo baja un poco para que la brasa se note. Aquí hay margen de sobra:
       --ink-3 sale de 4.32:1 en plano y no baja de 3.2:1 ni en la esquina. */
    --wall-bloom:   48%;
    --wall-bloom-2: 19%;
    --wall-veil:    0.60;
    --wall-bloom-dense:   66%;
    --wall-bloom-2-dense: 30%;

    --e-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --e-2: 0 4px 18px -4px rgba(0, 0, 0, 0.55);
    --e-3: 0 20px 56px -14px rgba(0, 0, 0, 0.7);
  }
}

:root[data-theme="dark"] {
  --canvas:        var(--n-950);
  --canvas-deep:   #030304;
  --surface:       var(--n-850);
  --surface-2:     var(--n-800);
  --surface-sunk:  var(--n-900);
  --surface-invert:var(--n-0);
  --surface-frost: rgba(23, 23, 26, 0.62);
  /* Ver la nota del bloque @media de arriba. */
  --surface-frost-thin: rgba(23, 23, 26, 0.44);
  --iris-veil-thin:     0.20;
  --surface-sunk-frost: color-mix(in srgb, var(--n-950) 30%, transparent);

  --hairline:       rgba(255, 255, 255, 0.10);
  --hairline-strong:rgba(255, 255, 255, 0.22);

  --glass-bg:     rgba(22, 22, 25, 0.58);
  --glass-border: rgba(255, 255, 255, 0.12);

  --ink:      #f6f6f4;
  --ink-2:    #a6a6a1;
  --ink-3:    #75756f;
  --trace:    #4a4a46;
  --ink-4:    var(--trace);
  --ink-invert: #0b0b0d;

  --accent-ink:         var(--accent-press);
  --accent-mark:        none;
  --accent-ink-invert:  var(--ink-invert);
  --accent-mark-invert: block;

  /* Sobre cockpit el aura se lee sola: sin ganancia. */
  --beam-gain: 1;

  --info-bg:  rgba(109, 134, 255, 0.14);
  --ok-bg:    rgba(31, 196, 122, 0.14);
  --warn-bg:  rgba(255, 138, 61, 0.15);
  --crit-bg:  rgba(255, 61, 99, 0.15);
  --idle-bg:  rgba(255, 255, 255, 0.06);

  --wall-bloom:   48%;
  --wall-bloom-2: 19%;
  --wall-veil:    0.60;
  --wall-bloom-dense:   66%;
  --wall-bloom-2-dense: 30%;

  --e-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --e-2: 0 4px 18px -4px rgba(0, 0, 0, 0.55);
  --e-3: 0 20px 56px -14px rgba(0, 0, 0, 0.7);
}

:root[data-theme="light"] {
  --canvas:        var(--n-100);
  --canvas-deep:   var(--n-150);
  --surface:       var(--n-0);
  --surface-2:     var(--n-50);
  --surface-sunk:  var(--n-100);
  --surface-invert:var(--n-900);
  --surface-frost: rgba(255, 255, 255, 0.58);
  --surface-frost-thin: rgba(255, 255, 255, 0.46);
  --iris-veil-thin:     0.28;
  --surface-sunk-frost: color-mix(in srgb, var(--n-950) 5%, transparent);

  --hairline:       rgba(10, 10, 12, 0.09);
  --hairline-strong:rgba(10, 10, 12, 0.18);

  --glass-bg:     rgba(255, 255, 255, 0.62);
  --glass-border: rgba(255, 255, 255, 0.55);

  --ink:      #0b0b0d;
  --ink-2:    #5f5f5c;
  --ink-3:    #7d7d76;   /* ver la nota del bloque :root de arriba */
  --trace:    #c2c2bc;
  --ink-4:    var(--trace);
  --ink-invert: #f6f6f4;

  --accent-ink:         var(--ink);
  --accent-mark:        block;
  --accent-ink-invert:  var(--accent-press);
  --accent-mark-invert: none;

  /* Sobre papel el aura se apaga: sube la opacidad. Ver :root. */
  --beam-gain: 1.3;

  --info-bg:  rgba(46, 75, 255, 0.10);
  --ok-bg:    rgba(31, 196, 122, 0.12);
  --warn-bg:  rgba(255, 138, 61, 0.13);
  --crit-bg:  rgba(255, 61, 99, 0.11);
  --idle-bg:  rgba(10, 10, 12, 0.05);

  /* Claro: manchas pálidas sobre casi-blanco. Medido con --ink-3, que es el
     peor caso del sistema, DESPUÉS de oscurecerlo a #7d7d76:
       lienzo plano                3.54:1
       falda de la mancha          3.17:1   ← lo peor que se ve
       corazón de la mancha        2.37:1   (fuera del viewport por diseño)
     Con el #97978f anterior esos tres eran 2.51, 2.25 y 1.88.
     --ink-2, el que se lee de corrido, no baja de 4.09:1 ni en el corazón. */
  --wall-bloom:   38%;
  --wall-bloom-2: 15%;
  --wall-veil:    0.66;
  --wall-bloom-dense:   54%;
  --wall-bloom-2-dense: 26%;

  --e-1: 0 1px 2px rgba(10, 10, 12, 0.04);
  --e-2: 0 4px 16px -4px rgba(10, 10, 12, 0.10);
  --e-3: 0 18px 48px -12px rgba(10, 10, 12, 0.22);
}
